gsd-pi 2.24.0 → 2.26.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 (206) hide show
  1. package/README.md +13 -3
  2. package/dist/headless.js +24 -4
  3. package/dist/models-resolver.d.ts +0 -11
  4. package/dist/models-resolver.js +0 -15
  5. package/dist/resource-loader.d.ts +0 -1
  6. package/dist/resource-loader.js +0 -9
  7. package/dist/resources/GSD-WORKFLOW.md +12 -9
  8. package/dist/resources/extensions/async-jobs/index.ts +9 -1
  9. package/dist/resources/extensions/bg-shell/index.ts +3 -2
  10. package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
  11. package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
  12. package/dist/resources/extensions/gsd/activity-log.ts +5 -3
  13. package/dist/resources/extensions/gsd/auto-prompts.ts +14 -0
  14. package/dist/resources/extensions/gsd/auto-recovery.ts +7 -4
  15. package/dist/resources/extensions/gsd/auto-worktree.ts +132 -3
  16. package/dist/resources/extensions/gsd/auto.ts +265 -48
  17. package/dist/resources/extensions/gsd/cache.ts +3 -1
  18. package/dist/resources/extensions/gsd/doctor-proactive.ts +7 -6
  19. package/dist/resources/extensions/gsd/doctor.ts +26 -1
  20. package/dist/resources/extensions/gsd/files.ts +13 -2
  21. package/dist/resources/extensions/gsd/git-service.ts +74 -14
  22. package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
  23. package/dist/resources/extensions/gsd/guided-flow.ts +54 -22
  24. package/dist/resources/extensions/gsd/index.ts +62 -8
  25. package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
  26. package/dist/resources/extensions/gsd/memory-store.ts +441 -0
  27. package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
  28. package/dist/resources/extensions/gsd/migrate/writer.ts +39 -0
  29. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
  30. package/dist/resources/extensions/gsd/preferences.ts +2 -1
  31. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  32. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
  33. package/dist/resources/extensions/gsd/prompts/discuss.md +5 -5
  34. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  36. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  37. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/queue.md +3 -3
  39. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  40. package/dist/resources/extensions/gsd/roadmap-slices.ts +45 -1
  41. package/dist/resources/extensions/gsd/state.ts +17 -6
  42. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  43. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  44. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
  45. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
  46. package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  47. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  48. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  49. package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  50. package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  51. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
  52. package/dist/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
  53. package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  54. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
  55. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  56. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  57. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  58. package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
  59. package/dist/resources/extensions/gsd/types.ts +2 -0
  60. package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
  61. package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  62. package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
  63. package/dist/resources/extensions/gsd/worktree.ts +9 -2
  64. package/dist/resources/extensions/search-the-web/native-search.ts +19 -5
  65. package/dist/resources/extensions/shared/path-display.ts +19 -0
  66. package/package.json +1 -6
  67. package/packages/pi-agent-core/dist/agent-loop.js +2 -0
  68. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  69. package/packages/pi-agent-core/src/agent-loop.ts +2 -0
  70. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  71. package/packages/pi-ai/dist/providers/anthropic.js +64 -0
  72. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  73. package/packages/pi-ai/dist/providers/mistral.js +3 -0
  74. package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
  75. package/packages/pi-ai/dist/types.d.ts +23 -1
  76. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  77. package/packages/pi-ai/dist/types.js.map +1 -1
  78. package/packages/pi-ai/src/providers/anthropic.ts +65 -1
  79. package/packages/pi-ai/src/providers/mistral.ts +3 -0
  80. package/packages/pi-ai/src/types.ts +19 -1
  81. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +7 -0
  82. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  83. package/packages/pi-coding-agent/dist/core/agent-session.js +32 -0
  84. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  85. package/packages/pi-coding-agent/dist/core/keybindings.js +1 -1
  86. package/packages/pi-coding-agent/dist/core/keybindings.js.map +1 -1
  87. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/lsp/client.js +12 -1
  89. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  91. package/packages/pi-coding-agent/dist/core/lsp/index.js +7 -0
  92. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  93. package/packages/pi-coding-agent/dist/core/sdk.d.ts +2 -2
  94. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/sdk.js +8 -3
  96. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +3 -0
  98. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  99. package/packages/pi-coding-agent/dist/core/settings-manager.js +8 -0
  100. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
  103. package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -1
  106. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/index.d.ts +2 -1
  108. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  109. package/packages/pi-coding-agent/dist/index.js +5 -1
  110. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +41 -3
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +301 -62
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +5 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +135 -30
  121. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts +8 -0
  123. package/packages/pi-coding-agent/dist/tests/path-display.test.d.ts.map +1 -0
  124. package/packages/pi-coding-agent/dist/tests/path-display.test.js +60 -0
  125. package/packages/pi-coding-agent/dist/tests/path-display.test.js.map +1 -0
  126. package/packages/pi-coding-agent/dist/utils/clipboard-image.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/utils/clipboard-image.js +32 -6
  128. package/packages/pi-coding-agent/dist/utils/clipboard-image.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/utils/path-display.d.ts +34 -0
  130. package/packages/pi-coding-agent/dist/utils/path-display.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/utils/path-display.js +36 -0
  132. package/packages/pi-coding-agent/dist/utils/path-display.js.map +1 -0
  133. package/packages/pi-coding-agent/src/core/agent-session.ts +36 -0
  134. package/packages/pi-coding-agent/src/core/keybindings.ts +1 -1
  135. package/packages/pi-coding-agent/src/core/lsp/client.ts +11 -1
  136. package/packages/pi-coding-agent/src/core/lsp/index.ts +7 -0
  137. package/packages/pi-coding-agent/src/core/sdk.ts +17 -1
  138. package/packages/pi-coding-agent/src/core/settings-manager.ts +11 -0
  139. package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
  140. package/packages/pi-coding-agent/src/core/system-prompt.ts +2 -1
  141. package/packages/pi-coding-agent/src/index.ts +15 -0
  142. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +347 -62
  143. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
  144. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +124 -4
  145. package/packages/pi-coding-agent/src/tests/path-display.test.ts +85 -0
  146. package/packages/pi-coding-agent/src/utils/clipboard-image.ts +33 -6
  147. package/packages/pi-coding-agent/src/utils/path-display.ts +36 -0
  148. package/src/resources/GSD-WORKFLOW.md +12 -9
  149. package/src/resources/extensions/async-jobs/index.ts +9 -1
  150. package/src/resources/extensions/bg-shell/index.ts +3 -2
  151. package/src/resources/extensions/bg-shell/overlay.ts +18 -17
  152. package/src/resources/extensions/get-secrets-from-user.ts +5 -23
  153. package/src/resources/extensions/gsd/activity-log.ts +5 -3
  154. package/src/resources/extensions/gsd/auto-prompts.ts +14 -0
  155. package/src/resources/extensions/gsd/auto-recovery.ts +7 -4
  156. package/src/resources/extensions/gsd/auto-worktree.ts +132 -3
  157. package/src/resources/extensions/gsd/auto.ts +265 -48
  158. package/src/resources/extensions/gsd/cache.ts +3 -1
  159. package/src/resources/extensions/gsd/doctor-proactive.ts +7 -6
  160. package/src/resources/extensions/gsd/doctor.ts +26 -1
  161. package/src/resources/extensions/gsd/files.ts +13 -2
  162. package/src/resources/extensions/gsd/git-service.ts +74 -14
  163. package/src/resources/extensions/gsd/gsd-db.ts +78 -1
  164. package/src/resources/extensions/gsd/guided-flow.ts +54 -22
  165. package/src/resources/extensions/gsd/index.ts +62 -8
  166. package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
  167. package/src/resources/extensions/gsd/memory-store.ts +441 -0
  168. package/src/resources/extensions/gsd/migrate/command.ts +2 -2
  169. package/src/resources/extensions/gsd/migrate/writer.ts +39 -0
  170. package/src/resources/extensions/gsd/parallel-orchestrator.ts +122 -4
  171. package/src/resources/extensions/gsd/preferences.ts +2 -1
  172. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -4
  174. package/src/resources/extensions/gsd/prompts/discuss.md +5 -5
  175. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  176. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  177. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  178. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  179. package/src/resources/extensions/gsd/prompts/queue.md +3 -3
  180. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  181. package/src/resources/extensions/gsd/roadmap-slices.ts +45 -1
  182. package/src/resources/extensions/gsd/state.ts +17 -6
  183. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +54 -0
  184. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  185. package/src/resources/extensions/gsd/tests/derive-state.test.ts +70 -0
  186. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +23 -3
  187. package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  188. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  189. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  190. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  191. package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  192. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +13 -7
  193. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +171 -0
  194. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  195. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +8 -4
  196. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  197. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  198. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  199. package/src/resources/extensions/gsd/triage-ui.ts +1 -1
  200. package/src/resources/extensions/gsd/types.ts +2 -0
  201. package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
  202. package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  203. package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
  204. package/src/resources/extensions/gsd/worktree.ts +9 -2
  205. package/src/resources/extensions/search-the-web/native-search.ts +19 -5
  206. package/src/resources/extensions/shared/path-display.ts +19 -0
@@ -5,6 +5,7 @@
5
5
 
6
6
  import { promises as fs } from 'node:fs';
7
7
  import { dirname, resolve } from 'node:path';
8
+ import { randomBytes } from 'node:crypto';
8
9
  import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
9
10
  import { milestoneIdSort, findMilestoneIds } from './guided-flow.js';
10
11
 
@@ -705,9 +706,19 @@ export async function saveFile(path: string, content: string): Promise<void> {
705
706
  const dir = dirname(path);
706
707
  await fs.mkdir(dir, { recursive: true });
707
708
 
708
- const tmpPath = path + '.tmp';
709
+ // Use a unique temp path per call to avoid collisions when parallel
710
+ // tool calls target the same file (e.g. concurrent gsd_save_decision).
711
+ // rename() is atomic on POSIX, so last-writer-wins is correct for
712
+ // regenerate-from-DB writes.
713
+ const tmpPath = path + `.tmp.${randomBytes(4).toString("hex")}`;
709
714
  await fs.writeFile(tmpPath, content, 'utf-8');
710
- await fs.rename(tmpPath, path);
715
+ try {
716
+ await fs.rename(tmpPath, path);
717
+ } catch (err) {
718
+ // Clean up orphaned temp file on rename failure
719
+ await fs.unlink(tmpPath).catch(() => {});
720
+ throw err;
721
+ }
711
722
  }
712
723
 
713
724
  export function parseRequirementCounts(content: string | null): RequirementCounts {
@@ -68,6 +68,50 @@ export interface CommitOptions {
68
68
  allowEmpty?: boolean;
69
69
  }
70
70
 
71
+ // ─── Meaningful Commit Message Generation ───────────────────────────────────
72
+
73
+ /** Context for generating a meaningful commit message from task execution results. */
74
+ export interface TaskCommitContext {
75
+ taskId: string;
76
+ taskTitle: string;
77
+ /** The one-liner from the task summary (e.g. "Added retry-aware worker status logging") */
78
+ oneLiner?: string;
79
+ /** Files modified by this task (from task summary frontmatter) */
80
+ keyFiles?: string[];
81
+ }
82
+
83
+ /**
84
+ * Build a meaningful conventional commit message from task execution context.
85
+ * Format: `{type}({sliceId}/{taskId}): {description}`
86
+ *
87
+ * The description is the task summary one-liner if available (it describes
88
+ * what was actually built), falling back to the task title (what was planned).
89
+ */
90
+ export function buildTaskCommitMessage(ctx: TaskCommitContext): string {
91
+ const scope = ctx.taskId; // e.g. "S01/T02" or just "T02"
92
+ const description = ctx.oneLiner || ctx.taskTitle;
93
+ const type = inferCommitType(ctx.taskTitle, ctx.oneLiner);
94
+
95
+ // Truncate description to ~72 chars for subject line
96
+ const maxDescLen = 68 - type.length - scope.length;
97
+ const truncated = description.length > maxDescLen
98
+ ? description.slice(0, maxDescLen - 1).trimEnd() + "…"
99
+ : description;
100
+
101
+ const subject = `${type}(${scope}): ${truncated}`;
102
+
103
+ // Build body with key files if available
104
+ if (ctx.keyFiles && ctx.keyFiles.length > 0) {
105
+ const fileLines = ctx.keyFiles
106
+ .slice(0, 8) // cap at 8 files to keep commit concise
107
+ .map(f => `- ${f}`)
108
+ .join("\n");
109
+ return `${subject}\n\n${fileLines}`;
110
+ }
111
+
112
+ return subject;
113
+ }
114
+
71
115
  /**
72
116
  * Thrown when a slice merge hits code conflicts in non-.gsd files.
73
117
  * The working tree is left in a conflicted state (no reset) so the
@@ -253,18 +297,14 @@ export function runGit(basePath: string, args: string[], options: { allowFailure
253
297
  * Each entry: [keywords[], commitType]
254
298
  */
255
299
  const COMMIT_TYPE_RULES: [string[], string][] = [
256
- [["fix", "bug", "patch", "hotfix"], "fix"],
300
+ [["fix", "fixed", "fixes", "bug", "patch", "hotfix", "repair", "correct"], "fix"],
257
301
  [["refactor", "restructure", "reorganize"], "refactor"],
258
- [["doc", "docs", "documentation"], "docs"],
259
- [["test", "tests", "testing"], "test"],
260
- [["chore", "cleanup", "clean up", "archive", "remove", "delete"], "chore"],
302
+ [["doc", "docs", "documentation", "readme", "changelog"], "docs"],
303
+ [["test", "tests", "testing", "spec", "coverage"], "test"],
304
+ [["perf", "performance", "optimize", "speed", "cache"], "perf"],
305
+ [["chore", "cleanup", "clean up", "dependencies", "deps", "bump", "config", "ci", "archive", "remove", "delete"], "chore"],
261
306
  ];
262
307
 
263
- /**
264
- * Infer a conventional commit type from a slice title.
265
- * Uses case-insensitive word-boundary matching against known keywords.
266
- * Returns "feat" when no keywords match.
267
- */
268
308
  // ─── GitServiceImpl ────────────────────────────────────────────────────
269
309
 
270
310
  export class GitServiceImpl {
@@ -356,11 +396,22 @@ export class GitServiceImpl {
356
396
  }
357
397
 
358
398
  /**
359
- * Auto-commit dirty working tree with a conventional chore message.
399
+ * Auto-commit dirty working tree.
400
+ *
401
+ * When `taskContext` is provided, generates a meaningful conventional commit
402
+ * message from the task execution results (one-liner, title, inferred type).
403
+ * Falls back to a generic `chore()` message when no context is available
404
+ * (e.g. pre-switch commits, stop commits, state rebuild commits).
405
+ *
360
406
  * Returns the commit message on success, or null if nothing to commit.
361
407
  * @param extraExclusions Additional paths to exclude from staging (e.g. [".gsd/"] for pre-switch commits).
362
408
  */
363
- autoCommit(unitType: string, unitId: string, extraExclusions: readonly string[] = []): string | null {
409
+ autoCommit(
410
+ unitType: string,
411
+ unitId: string,
412
+ extraExclusions: readonly string[] = [],
413
+ taskContext?: TaskCommitContext,
414
+ ): string | null {
364
415
  // Quick check: is there anything dirty at all?
365
416
  // Native path uses libgit2 (single syscall), fallback spawns git.
366
417
  if (!nativeHasChanges(this.basePath)) return null;
@@ -371,7 +422,9 @@ export class GitServiceImpl {
371
422
  // (all changes might have been runtime files that got excluded)
372
423
  if (!nativeHasStagedChanges(this.basePath)) return null;
373
424
 
374
- const message = `chore(${unitId}): auto-commit after ${unitType}`;
425
+ const message = taskContext
426
+ ? buildTaskCommitMessage(taskContext)
427
+ : `chore(${unitId}): auto-commit after ${unitType}`;
375
428
  nativeCommit(this.basePath, message, { allowEmpty: false });
376
429
  return message;
377
430
  }
@@ -497,8 +550,15 @@ export class GitServiceImpl {
497
550
 
498
551
  // ─── Commit Type Inference ─────────────────────────────────────────────────
499
552
 
500
- export function inferCommitType(sliceTitle: string): string {
501
- const lower = sliceTitle.toLowerCase();
553
+ /**
554
+ * Infer a conventional commit type from a title (and optional one-liner).
555
+ * Uses case-insensitive word-boundary matching against known keywords.
556
+ * Returns "feat" when no keywords match.
557
+ *
558
+ * Used for both slice squash-merge titles and task commit messages.
559
+ */
560
+ export function inferCommitType(title: string, oneLiner?: string): string {
561
+ const lower = `${title} ${oneLiner || ""}`.toLowerCase();
502
562
 
503
563
  for (const [keywords, commitType] of COMMIT_TYPE_RULES) {
504
564
  for (const keyword of keywords) {
@@ -161,7 +161,7 @@ function openRawDb(path: string): unknown {
161
161
 
162
162
  // ─── Schema ────────────────────────────────────────────────────────────────
163
163
 
164
- const SCHEMA_VERSION = 2;
164
+ const SCHEMA_VERSION = 3;
165
165
 
166
166
  function initSchema(db: DbAdapter, fileBacked: boolean): void {
167
167
  // WAL mode for file-backed databases (must be outside transaction)
@@ -221,9 +221,36 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
221
221
  )
222
222
  `);
223
223
 
224
+ db.exec(`
225
+ CREATE TABLE IF NOT EXISTS memories (
226
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
227
+ id TEXT NOT NULL UNIQUE,
228
+ category TEXT NOT NULL,
229
+ content TEXT NOT NULL,
230
+ confidence REAL NOT NULL DEFAULT 0.8,
231
+ source_unit_type TEXT,
232
+ source_unit_id TEXT,
233
+ created_at TEXT NOT NULL,
234
+ updated_at TEXT NOT NULL,
235
+ superseded_by TEXT DEFAULT NULL,
236
+ hit_count INTEGER NOT NULL DEFAULT 0
237
+ )
238
+ `);
239
+
240
+ db.exec(`
241
+ CREATE TABLE IF NOT EXISTS memory_processed_units (
242
+ unit_key TEXT PRIMARY KEY,
243
+ activity_file TEXT,
244
+ processed_at TEXT NOT NULL
245
+ )
246
+ `);
247
+
248
+ db.exec('CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)');
249
+
224
250
  // Views — DROP + CREATE since CREATE VIEW IF NOT EXISTS doesn't update definitions
225
251
  db.exec(`CREATE VIEW IF NOT EXISTS active_decisions AS SELECT * FROM decisions WHERE superseded_by IS NULL`);
226
252
  db.exec(`CREATE VIEW IF NOT EXISTS active_requirements AS SELECT * FROM requirements WHERE superseded_by IS NULL`);
253
+ db.exec(`CREATE VIEW IF NOT EXISTS active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL`);
227
254
 
228
255
  // Insert schema version if not already present
229
256
  const existing = db.prepare('SELECT count(*) as cnt FROM schema_version').get();
@@ -274,6 +301,41 @@ function migrateSchema(db: DbAdapter): void {
274
301
  );
275
302
  }
276
303
 
304
+ // v2 → v3: add memories + memory_processed_units tables
305
+ if (currentVersion < 3) {
306
+ db.exec(`
307
+ CREATE TABLE IF NOT EXISTS memories (
308
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
309
+ id TEXT NOT NULL UNIQUE,
310
+ category TEXT NOT NULL,
311
+ content TEXT NOT NULL,
312
+ confidence REAL NOT NULL DEFAULT 0.8,
313
+ source_unit_type TEXT,
314
+ source_unit_id TEXT,
315
+ created_at TEXT NOT NULL,
316
+ updated_at TEXT NOT NULL,
317
+ superseded_by TEXT DEFAULT NULL,
318
+ hit_count INTEGER NOT NULL DEFAULT 0
319
+ )
320
+ `);
321
+
322
+ db.exec(`
323
+ CREATE TABLE IF NOT EXISTS memory_processed_units (
324
+ unit_key TEXT PRIMARY KEY,
325
+ activity_file TEXT,
326
+ processed_at TEXT NOT NULL
327
+ )
328
+ `);
329
+
330
+ db.exec('CREATE INDEX IF NOT EXISTS idx_memories_active ON memories(superseded_by)');
331
+ db.exec('DROP VIEW IF EXISTS active_memories');
332
+ db.exec('CREATE VIEW active_memories AS SELECT * FROM memories WHERE superseded_by IS NULL');
333
+
334
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (:version, :applied_at)').run(
335
+ { ':version': 3, ':applied_at': new Date().toISOString() },
336
+ );
337
+ }
338
+
277
339
  db.exec('COMMIT');
278
340
  } catch (err) {
279
341
  db.exec('ROLLBACK');
@@ -728,6 +790,21 @@ export function upsertRequirement(r: Requirement): void {
728
790
  /**
729
791
  * Insert or replace an artifact. Uses the `path` PK for idempotency.
730
792
  */
793
+ /**
794
+ * Delete all rows from the artifacts table.
795
+ * The artifacts table is a read cache — clearing it forces the next
796
+ * deriveState() to fall through to disk reads (native Rust batch parse).
797
+ * Safe to call when no database is open (no-op).
798
+ */
799
+ export function clearArtifacts(): void {
800
+ if (!currentDb) return;
801
+ try {
802
+ currentDb.exec('DELETE FROM artifacts');
803
+ } catch {
804
+ // Clearing a cache should never be fatal
805
+ }
806
+ }
807
+
731
808
  export function insertArtifact(a: {
732
809
  path: string;
733
810
  artifact_type: string;
@@ -29,6 +29,17 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
29
29
  import { showConfirm } from "../shared/confirm-ui.js";
30
30
  import { loadQueueOrder, sortByQueueOrder, saveQueueOrder } from "./queue-order.js";
31
31
 
32
+ // ─── Commit Instruction Helpers ──────────────────────────────────────────────
33
+
34
+ /** Build conditional commit instruction for planning prompts based on commit_docs preference. */
35
+ function buildDocsCommitInstruction(message: string): string {
36
+ const prefs = loadEffectiveGSDPreferences();
37
+ const commitDocsEnabled = prefs?.preferences?.git?.commit_docs !== false;
38
+ return commitDocsEnabled
39
+ ? `Commit: \`${message}\``
40
+ : "Do not commit — planning docs are not tracked in git for this project.";
41
+ }
42
+
32
43
  // ─── Auto-start after discuss ─────────────────────────────────────────────────
33
44
 
34
45
  /** Stashed context + flag for auto-starting after discuss phase completes */
@@ -198,6 +209,8 @@ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string)
198
209
  contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
199
210
  roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
200
211
  inlinedTemplates,
212
+ commitInstruction: buildDocsCommitInstruction(`docs(${nextId}): context, requirements, and roadmap`),
213
+ multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
201
214
  });
202
215
  }
203
216
 
@@ -220,6 +233,8 @@ function buildHeadlessDiscussPrompt(nextId: string, seedContext: string, _basePa
220
233
  contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
221
234
  roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
222
235
  inlinedTemplates,
236
+ commitInstruction: buildDocsCommitInstruction(`docs(${nextId}): context, requirements, and roadmap`),
237
+ multiMilestoneCommitInstruction: buildDocsCommitInstruction("docs: project plan — N milestones"),
223
238
  });
224
239
  }
225
240
 
@@ -621,9 +636,11 @@ async function showQueueAdd(
621
636
  const existingContext = await buildExistingMilestonesContext(basePath, milestoneIds, state);
622
637
 
623
638
  // ── Determine next milestone ID ─────────────────────────────────────
639
+ // Note: the LLM will use the gsd_generate_milestone_id tool to get IDs
640
+ // at creation time, but we still mention the next ID in the preamble
641
+ // for context about where the sequence is.
624
642
  const uniqueEnabled = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
625
643
  const nextId = nextMilestoneId(milestoneIds, uniqueEnabled);
626
- const nextIdPlus1 = nextMilestoneId([...milestoneIds, nextId], uniqueEnabled);
627
644
 
628
645
  // ── Build preamble ──────────────────────────────────────────────────
629
646
  const activePart = state.activeMilestone
@@ -644,10 +661,9 @@ async function showQueueAdd(
644
661
  const queueInlinedTemplates = inlineTemplate("context", "Context");
645
662
  const prompt = loadPrompt("queue", {
646
663
  preamble,
647
- nextId,
648
- nextIdPlus1,
649
664
  existingMilestonesContext: existingContext,
650
665
  inlinedTemplates: queueInlinedTemplates,
666
+ commitInstruction: buildDocsCommitInstruction("docs: queue <milestone list>"),
651
667
  });
652
668
 
653
669
  pi.sendMessage(
@@ -834,6 +850,7 @@ async function buildDiscussSlicePrompt(
834
850
  contextPath: sliceContextPath,
835
851
  projectRoot: base,
836
852
  inlinedTemplates,
853
+ commitInstruction: buildDocsCommitInstruction(`docs(${mid}/${sid}): slice context from discuss`),
837
854
  });
838
855
  }
839
856
 
@@ -870,7 +887,7 @@ export async function showDiscuss(
870
887
  const draftFile = resolveMilestoneFile(basePath, mid, "CONTEXT-DRAFT");
871
888
  const draftContent = draftFile ? await loadFile(draftFile) : null;
872
889
 
873
- const choice = await showNextAction(ctx as any, {
890
+ const choice = await showNextAction(ctx, {
874
891
  title: `GSD — ${mid}: ${milestoneTitle}`,
875
892
  summary: ["This milestone has a draft context from a prior discussion.", "It needs a dedicated discussion before auto-planning can begin."],
876
893
  actions: [
@@ -899,6 +916,7 @@ export async function showDiscuss(
899
916
  const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
900
917
  const basePrompt = loadPrompt("guided-discuss-milestone", {
901
918
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
919
+ commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
902
920
  });
903
921
  const seed = draftContent
904
922
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
@@ -911,6 +929,7 @@ export async function showDiscuss(
911
929
  pendingAutoStart = { ctx, pi, basePath, milestoneId: mid, step: false };
912
930
  dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
913
931
  milestoneId: mid, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
932
+ commitInstruction: buildDocsCommitInstruction(`docs(${mid}): milestone context from discuss`),
914
933
  }), "gsd-discuss");
915
934
  } else if (choice === "skip_milestone") {
916
935
  const milestoneIds = findMilestoneIds(basePath);
@@ -940,14 +959,24 @@ export async function showDiscuss(
940
959
 
941
960
  // Loop: show picker, dispatch discuss, repeat until "not_yet"
942
961
  while (true) {
943
- const actions = pendingSlices.map((s, i) => ({
944
- id: s.id,
945
- label: `${s.id}: ${s.title}`,
946
- description: state.activeSlice?.id === s.id ? "active slice" : "upcoming",
947
- recommended: i === 0,
948
- }));
949
-
950
- const choice = await showNextAction(ctx as any, {
962
+ const actions = pendingSlices.map((s, i) => {
963
+ // Check if this slice has already been discussed (CONTEXT file exists)
964
+ const contextFile = resolveSliceFile(basePath, mid, s.id, "CONTEXT");
965
+ const discussed = !!contextFile;
966
+ const statusParts: string[] = [];
967
+ if (state.activeSlice?.id === s.id) statusParts.push("active");
968
+ else statusParts.push("upcoming");
969
+ statusParts.push(discussed ? "discussed ✓" : "not discussed");
970
+
971
+ return {
972
+ id: s.id,
973
+ label: `${s.id}: ${s.title}`,
974
+ description: statusParts.join(" · "),
975
+ recommended: i === 0,
976
+ };
977
+ });
978
+
979
+ const choice = await showNextAction(ctx, {
951
980
  title: "GSD — Discuss a slice",
952
981
  summary: [
953
982
  `${mid}: ${milestoneTitle}`,
@@ -1056,7 +1085,7 @@ export async function showSmartEntry(
1056
1085
  const crashLock = readCrashLock(basePath);
1057
1086
  if (crashLock) {
1058
1087
  clearLock(basePath);
1059
- const resume = await showNextAction(ctx as any, {
1088
+ const resume = await showNextAction(ctx, {
1060
1089
  title: "GSD — Interrupted Session Detected",
1061
1090
  summary: [formatCrashInfo(crashLock)],
1062
1091
  actions: [
@@ -1116,7 +1145,7 @@ export async function showSmartEntry(
1116
1145
  basePath
1117
1146
  ));
1118
1147
  } else {
1119
- const choice = await showNextAction(ctx as any, {
1148
+ const choice = await showNextAction(ctx, {
1120
1149
  title: "GSD — Get Shit Done",
1121
1150
  summary: ["No active milestone."],
1122
1151
  actions: [
@@ -1146,7 +1175,7 @@ export async function showSmartEntry(
1146
1175
 
1147
1176
  // ── All milestones complete → New milestone ──────────────────────────
1148
1177
  if (state.phase === "complete") {
1149
- const choice = await showNextAction(ctx as any, {
1178
+ const choice = await showNextAction(ctx, {
1150
1179
  title: `GSD — ${milestoneId}: ${milestoneTitle}`,
1151
1180
  summary: ["All milestones complete."],
1152
1181
  actions: [
@@ -1187,7 +1216,7 @@ export async function showSmartEntry(
1187
1216
  const draftFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT-DRAFT");
1188
1217
  const draftContent = draftFile ? await loadFile(draftFile) : null;
1189
1218
 
1190
- const choice = await showNextAction(ctx as any, {
1219
+ const choice = await showNextAction(ctx, {
1191
1220
  title: `GSD — ${milestoneId}: ${milestoneTitle}`,
1192
1221
  summary: ["This milestone has a draft context from a prior discussion.", "It needs a dedicated discussion before auto-planning can begin."],
1193
1222
  actions: [
@@ -1216,6 +1245,7 @@ export async function showSmartEntry(
1216
1245
  const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1217
1246
  const basePrompt = loadPrompt("guided-discuss-milestone", {
1218
1247
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1248
+ commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
1219
1249
  });
1220
1250
  const seed = draftContent
1221
1251
  ? `${basePrompt}\n\n## Prior Discussion (Draft Seed)\n\n${draftContent}`
@@ -1228,6 +1258,7 @@ export async function showSmartEntry(
1228
1258
  pendingAutoStart = { ctx, pi, basePath, milestoneId, step: stepMode };
1229
1259
  dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1230
1260
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1261
+ commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
1231
1262
  }), "gsd-discuss");
1232
1263
  } else if (choice === "skip_milestone") {
1233
1264
  const milestoneIds = findMilestoneIds(basePath);
@@ -1278,7 +1309,7 @@ export async function showSmartEntry(
1278
1309
  },
1279
1310
  ];
1280
1311
 
1281
- const choice = await showNextAction(ctx as any, {
1312
+ const choice = await showNextAction(ctx, {
1282
1313
  title: `GSD — ${milestoneId}: ${milestoneTitle}`,
1283
1314
  summary: [hasContext ? "Context captured. Ready to create roadmap." : "New milestone — no roadmap yet."],
1284
1315
  actions,
@@ -1302,6 +1333,7 @@ export async function showSmartEntry(
1302
1333
  const structuredQuestionsAvailable = pi.getActiveTools().includes("ask_user_questions") ? "true" : "false";
1303
1334
  dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
1304
1335
  milestoneId, milestoneTitle, inlinedTemplates: discussMilestoneTemplates, structuredQuestionsAvailable,
1336
+ commitInstruction: buildDocsCommitInstruction(`docs(${milestoneId}): milestone context from discuss`),
1305
1337
  }));
1306
1338
  } else if (choice === "skip_milestone") {
1307
1339
  const milestoneIds = findMilestoneIds(basePath);
@@ -1315,7 +1347,7 @@ export async function showSmartEntry(
1315
1347
  } else if (choice === "discard_milestone") {
1316
1348
  const mDir = resolveMilestonePath(basePath, milestoneId);
1317
1349
  if (!mDir) return;
1318
- const confirmed = await showConfirm(ctx as any, {
1350
+ const confirmed = await showConfirm(ctx, {
1319
1351
  title: "Discard milestone?",
1320
1352
  message: `This will permanently delete ${milestoneId} and all its contents.`,
1321
1353
  confirmLabel: "Discard",
@@ -1342,7 +1374,7 @@ export async function showSmartEntry(
1342
1374
  },
1343
1375
  ];
1344
1376
 
1345
- const choice = await showNextAction(ctx as any, {
1377
+ const choice = await showNextAction(ctx, {
1346
1378
  title: `GSD — ${milestoneId}: ${milestoneTitle}`,
1347
1379
  summary: ["Roadmap exists. Ready to execute."],
1348
1380
  actions,
@@ -1400,7 +1432,7 @@ export async function showSmartEntry(
1400
1432
  ? `${sliceId}: ${sliceTitle} (${summaryParts.join(", ")})`
1401
1433
  : `${sliceId}: ${sliceTitle} — ready for planning.`;
1402
1434
 
1403
- const choice = await showNextAction(ctx as any, {
1435
+ const choice = await showNextAction(ctx, {
1404
1436
  title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
1405
1437
  summary: [summaryLine],
1406
1438
  actions,
@@ -1431,7 +1463,7 @@ export async function showSmartEntry(
1431
1463
 
1432
1464
  // ── All tasks done → Complete slice ──────────────────────────────────
1433
1465
  if (state.phase === "summarizing") {
1434
- const choice = await showNextAction(ctx as any, {
1466
+ const choice = await showNextAction(ctx, {
1435
1467
  title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
1436
1468
  summary: ["All tasks complete. Ready for slice summary."],
1437
1469
  actions: [
@@ -1475,7 +1507,7 @@ export async function showSmartEntry(
1475
1507
  const hasInterrupted = !!(continueFile && await loadFile(continueFile)) ||
1476
1508
  !!(sDir && await loadFile(join(sDir, "continue.md")));
1477
1509
 
1478
- const choice = await showNextAction(ctx as any, {
1510
+ const choice = await showNextAction(ctx, {
1479
1511
  title: `GSD — ${milestoneId} / ${sliceId}: ${sliceTitle}`,
1480
1512
  summary: [
1481
1513
  hasInterrupted
@@ -36,7 +36,7 @@ import { loadPrompt } from "./prompt-loader.js";
36
36
  import { deriveState } from "./state.js";
37
37
  import { isAutoActive, isAutoPaused, handleAgentEnd, pauseAuto, getAutoDashboardData, markToolStart, markToolEnd } from "./auto.js";
38
38
  import { saveActivityLog } from "./activity-log.js";
39
- import { checkAutoStartAfterDiscuss, getDiscussionMilestoneId } from "./guided-flow.js";
39
+ import { checkAutoStartAfterDiscuss, getDiscussionMilestoneId, findMilestoneIds, nextMilestoneId } from "./guided-flow.js";
40
40
  import { GSDDashboardOverlay } from "./dashboard-overlay.js";
41
41
  import {
42
42
  loadEffectiveGSDPreferences,
@@ -59,6 +59,7 @@ import { homedir } from "node:os";
59
59
  import { shortcutDesc } from "../shared/terminal.js";
60
60
  import { Text } from "@gsd/pi-tui";
61
61
  import { pauseAutoForProviderError } from "./provider-error-pause.js";
62
+ import { toPosixPath } from "../shared/path-display.js";
62
63
 
63
64
  // ── Agent Instructions ────────────────────────────────────────────────────
64
65
  // Lightweight "always follow" files injected into every GSD agent session.
@@ -467,6 +468,46 @@ export default function (pi: ExtensionAPI) {
467
468
  },
468
469
  });
469
470
 
471
+ // ── gsd_generate_milestone_id — canonical milestone ID generation ──────
472
+ // The LLM cannot generate random suffixes for unique_milestone_ids on its
473
+ // own. This tool calls back into the TS code that owns ID generation,
474
+ // ensuring the preference is always respected and IDs are always valid.
475
+ pi.registerTool({
476
+ name: "gsd_generate_milestone_id",
477
+ label: "Generate Milestone ID",
478
+ description:
479
+ "Generate the next milestone ID for a new GSD milestone. " +
480
+ "Scans existing milestones on disk and respects the unique_milestone_ids preference. " +
481
+ "Always use this tool when creating a new milestone — never invent milestone IDs manually.",
482
+ promptSnippet: "Generate a valid milestone ID (respects unique_milestone_ids preference)",
483
+ promptGuidelines: [
484
+ "ALWAYS call gsd_generate_milestone_id before creating a new milestone directory or writing milestone files.",
485
+ "Never invent or hardcode milestone IDs like M001, M002 — always use this tool.",
486
+ "Call it once per milestone you need to create. For multi-milestone projects, call it once for each milestone in sequence.",
487
+ "The tool returns the correct format based on project preferences (e.g. M001 or M001-r5jzab).",
488
+ ],
489
+ parameters: Type.Object({}),
490
+ async execute(_toolCallId, _params, _signal, _onUpdate, _ctx) {
491
+ try {
492
+ const basePath = process.cwd();
493
+ const existingIds = findMilestoneIds(basePath);
494
+ const uniqueEnabled = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
495
+ const newId = nextMilestoneId(existingIds, uniqueEnabled);
496
+ return {
497
+ content: [{ type: "text" as const, text: newId }],
498
+ details: { operation: "generate_milestone_id", id: newId, existingCount: existingIds.length, uniqueEnabled },
499
+ };
500
+ } catch (err) {
501
+ const msg = err instanceof Error ? err.message : String(err);
502
+ return {
503
+ content: [{ type: "text" as const, text: `Error generating milestone ID: ${msg}` }],
504
+ isError: true,
505
+ details: { operation: "generate_milestone_id", error: msg },
506
+ };
507
+ }
508
+ },
509
+ });
510
+
470
511
  // ── session_start: render branded GSD header + load tool keys + remote status ──
471
512
  pi.on("session_start", async (_event, ctx) => {
472
513
  // Theme access throws in RPC mode (no TUI) — header is decorative, skip it
@@ -566,6 +607,19 @@ export default function (pi: ExtensionAPI) {
566
607
  }
567
608
  }
568
609
 
610
+ // Inject auto-learned project memories
611
+ let memoryBlock = "";
612
+ try {
613
+ const { getActiveMemoriesRanked, formatMemoriesForPrompt } = await import("./memory-store.js");
614
+ const memories = getActiveMemoriesRanked(30);
615
+ if (memories.length > 0) {
616
+ const formatted = formatMemoriesForPrompt(memories, 2000);
617
+ if (formatted) {
618
+ memoryBlock = `\n\n${formatted}`;
619
+ }
620
+ }
621
+ } catch { /* non-fatal */ }
622
+
569
623
  // Detect skills installed during this auto-mode session
570
624
  let newSkillsBlock = "";
571
625
  if (hasSkillSnapshot()) {
@@ -595,12 +649,12 @@ export default function (pi: ExtensionAPI) {
595
649
  "",
596
650
  "[WORKTREE CONTEXT — OVERRIDES CURRENT WORKING DIRECTORY ABOVE]",
597
651
  `IMPORTANT: Ignore the "Current working directory" shown earlier in this prompt.`,
598
- `The actual current working directory is: ${process.cwd()}`,
652
+ `The actual current working directory is: ${toPosixPath(process.cwd())}`,
599
653
  "",
600
654
  `You are working inside a GSD worktree.`,
601
655
  `- Worktree name: ${worktreeName}`,
602
- `- Worktree path (this is the real cwd): ${process.cwd()}`,
603
- `- Main project: ${worktreeMainCwd}`,
656
+ `- Worktree path (this is the real cwd): ${toPosixPath(process.cwd())}`,
657
+ `- Main project: ${toPosixPath(worktreeMainCwd)}`,
604
658
  `- Branch: worktree/${worktreeName}`,
605
659
  "",
606
660
  "All file operations, bash commands, and GSD state resolve against the worktree path above.",
@@ -612,12 +666,12 @@ export default function (pi: ExtensionAPI) {
612
666
  "",
613
667
  "[WORKTREE CONTEXT — OVERRIDES CURRENT WORKING DIRECTORY ABOVE]",
614
668
  `IMPORTANT: Ignore the "Current working directory" shown earlier in this prompt.`,
615
- `The actual current working directory is: ${process.cwd()}`,
669
+ `The actual current working directory is: ${toPosixPath(process.cwd())}`,
616
670
  "",
617
671
  "You are working inside a GSD auto-worktree.",
618
672
  `- Milestone worktree: ${autoWorktree.worktreeName}`,
619
- `- Worktree path (this is the real cwd): ${process.cwd()}`,
620
- `- Main project: ${autoWorktree.originalBase}`,
673
+ `- Worktree path (this is the real cwd): ${toPosixPath(process.cwd())}`,
674
+ `- Main project: ${toPosixPath(autoWorktree.originalBase)}`,
621
675
  `- Branch: ${autoWorktree.branch}`,
622
676
  "",
623
677
  "All file operations, bash commands, and GSD state resolve against the worktree path above.",
@@ -625,7 +679,7 @@ export default function (pi: ExtensionAPI) {
625
679
  ].join("\n");
626
680
  }
627
681
 
628
- const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${agentInstructionsBlock}${knowledgeBlock}${newSkillsBlock}${worktreeBlock}`;
682
+ const fullSystem = `${event.systemPrompt}\n\n[SYSTEM CONTEXT — GSD]\n\n${systemContent}${preferenceBlock}${agentInstructionsBlock}${knowledgeBlock}${memoryBlock}${newSkillsBlock}${worktreeBlock}`;
629
683
  stopContextTimer({
630
684
  systemPromptSize: fullSystem.length,
631
685
  injectionSize: injection?.length ?? 0,