gsd-pi 2.8.0 → 2.8.2

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 (143) hide show
  1. package/dist/loader.js +5 -0
  2. package/node_modules/@gsd/pi-coding-agent/dist/config.d.ts +2 -0
  3. package/node_modules/@gsd/pi-coding-agent/dist/config.d.ts.map +1 -1
  4. package/node_modules/@gsd/pi-coding-agent/dist/config.js +4 -0
  5. package/node_modules/@gsd/pi-coding-agent/dist/config.js.map +1 -1
  6. package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.d.ts +52 -0
  7. package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.d.ts.map +1 -0
  8. package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.js +117 -0
  9. package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.js.map +1 -0
  10. package/node_modules/@gsd/pi-coding-agent/dist/core/bash-executor.js +2 -2
  11. package/node_modules/@gsd/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
  12. package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.d.ts +31 -0
  13. package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -0
  14. package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.js +97 -0
  15. package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.js.map +1 -0
  16. package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.d.ts +1 -0
  17. package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  18. package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.js +112 -3
  19. package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  20. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
  21. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  22. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js +32 -22
  23. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  24. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.d.ts +1 -1
  25. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.d.ts.map +1 -1
  26. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.js +13 -2
  27. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.js.map +1 -1
  28. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.d.ts +2 -0
  29. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.d.ts.map +1 -0
  30. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.js +57 -0
  31. package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -0
  32. package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts +3 -1
  33. package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts.map +1 -1
  34. package/node_modules/@gsd/pi-coding-agent/dist/index.js +4 -1
  35. package/node_modules/@gsd/pi-coding-agent/dist/index.js.map +1 -1
  36. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  37. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -5
  38. package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  39. package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.d.ts +7 -0
  40. package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  41. package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.js +11 -0
  42. package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.js.map +1 -1
  43. package/node_modules/@gsd/pi-coding-agent/src/config.ts +5 -0
  44. package/node_modules/@gsd/pi-coding-agent/src/core/artifact-manager.ts +125 -0
  45. package/node_modules/@gsd/pi-coding-agent/src/core/bash-executor.ts +2 -2
  46. package/node_modules/@gsd/pi-coding-agent/src/core/blob-store.ts +106 -0
  47. package/node_modules/@gsd/pi-coding-agent/src/core/session-manager.ts +119 -3
  48. package/node_modules/@gsd/pi-coding-agent/src/core/tools/bash.ts +35 -22
  49. package/node_modules/@gsd/pi-coding-agent/src/core/tools/path-utils.test.ts +66 -0
  50. package/node_modules/@gsd/pi-coding-agent/src/core/tools/path-utils.ts +14 -2
  51. package/node_modules/@gsd/pi-coding-agent/src/index.ts +4 -1
  52. package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +6 -4
  53. package/node_modules/@gsd/pi-coding-agent/src/utils/shell.ts +11 -0
  54. package/package.json +6 -1
  55. package/packages/pi-coding-agent/dist/config.d.ts +2 -0
  56. package/packages/pi-coding-agent/dist/config.d.ts.map +1 -1
  57. package/packages/pi-coding-agent/dist/config.js +4 -0
  58. package/packages/pi-coding-agent/dist/config.js.map +1 -1
  59. package/packages/pi-coding-agent/dist/core/artifact-manager.d.ts +52 -0
  60. package/packages/pi-coding-agent/dist/core/artifact-manager.d.ts.map +1 -0
  61. package/packages/pi-coding-agent/dist/core/artifact-manager.js +117 -0
  62. package/packages/pi-coding-agent/dist/core/artifact-manager.js.map +1 -0
  63. package/packages/pi-coding-agent/dist/core/bash-executor.js +2 -2
  64. package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
  65. package/packages/pi-coding-agent/dist/core/blob-store.d.ts +31 -0
  66. package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -0
  67. package/packages/pi-coding-agent/dist/core/blob-store.js +97 -0
  68. package/packages/pi-coding-agent/dist/core/blob-store.js.map +1 -0
  69. package/packages/pi-coding-agent/dist/core/session-manager.d.ts +1 -0
  70. package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
  71. package/packages/pi-coding-agent/dist/core/session-manager.js +112 -3
  72. package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
  73. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
  74. package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
  75. package/packages/pi-coding-agent/dist/core/tools/bash.js +32 -22
  76. package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
  77. package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts +1 -1
  78. package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts.map +1 -1
  79. package/packages/pi-coding-agent/dist/core/tools/path-utils.js +13 -2
  80. package/packages/pi-coding-agent/dist/core/tools/path-utils.js.map +1 -1
  81. package/packages/pi-coding-agent/dist/core/tools/path-utils.test.d.ts +2 -0
  82. package/packages/pi-coding-agent/dist/core/tools/path-utils.test.d.ts.map +1 -0
  83. package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js +57 -0
  84. package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -0
  85. package/packages/pi-coding-agent/dist/index.d.ts +3 -1
  86. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  87. package/packages/pi-coding-agent/dist/index.js +4 -1
  88. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  89. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  90. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -5
  91. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  92. package/packages/pi-coding-agent/dist/utils/shell.d.ts +7 -0
  93. package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/utils/shell.js +11 -0
  95. package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
  96. package/packages/pi-coding-agent/src/config.ts +5 -0
  97. package/packages/pi-coding-agent/src/core/artifact-manager.ts +125 -0
  98. package/packages/pi-coding-agent/src/core/bash-executor.ts +2 -2
  99. package/packages/pi-coding-agent/src/core/blob-store.ts +106 -0
  100. package/packages/pi-coding-agent/src/core/session-manager.ts +119 -3
  101. package/packages/pi-coding-agent/src/core/tools/bash.ts +35 -22
  102. package/packages/pi-coding-agent/src/core/tools/path-utils.test.ts +66 -0
  103. package/packages/pi-coding-agent/src/core/tools/path-utils.ts +14 -2
  104. package/packages/pi-coding-agent/src/index.ts +4 -1
  105. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +6 -4
  106. package/packages/pi-coding-agent/src/utils/shell.ts +11 -0
  107. package/src/resources/extensions/bg-shell/index.ts +2 -1
  108. package/src/resources/extensions/browser-tools/lifecycle.ts +6 -1
  109. package/src/resources/extensions/gsd/auto.ts +92 -49
  110. package/src/resources/extensions/gsd/dispatch-guard.ts +65 -0
  111. package/src/resources/extensions/gsd/docs/preferences-reference.md +76 -0
  112. package/src/resources/extensions/gsd/exit-command.ts +18 -0
  113. package/src/resources/extensions/gsd/files.ts +9 -40
  114. package/src/resources/extensions/gsd/git-service.ts +62 -17
  115. package/src/resources/extensions/gsd/gitignore.ts +28 -0
  116. package/src/resources/extensions/gsd/guided-flow.ts +49 -11
  117. package/src/resources/extensions/gsd/index.ts +111 -16
  118. package/src/resources/extensions/gsd/preferences.ts +8 -0
  119. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -2
  120. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
  121. package/src/resources/extensions/gsd/prompts/discuss.md +27 -2
  122. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
  123. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -3
  124. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  125. package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -2
  126. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -3
  127. package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  128. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  129. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  130. package/src/resources/extensions/gsd/prompts/run-uat.md +4 -4
  131. package/src/resources/extensions/gsd/roadmap-slices.ts +50 -0
  132. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +102 -0
  133. package/src/resources/extensions/gsd/tests/exit-command.test.ts +50 -0
  134. package/src/resources/extensions/gsd/tests/git-service.test.ts +116 -39
  135. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +5 -5
  136. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +2 -1
  137. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +59 -0
  138. package/src/resources/extensions/gsd/tests/run-uat.test.ts +2 -4
  139. package/src/resources/extensions/gsd/tests/write-gate.test.ts +122 -0
  140. package/src/resources/extensions/ttsr/index.ts +163 -0
  141. package/src/resources/extensions/ttsr/rule-loader.ts +121 -0
  142. package/src/resources/extensions/ttsr/ttsr-interrupt.md +6 -0
  143. package/src/resources/extensions/ttsr/ttsr-manager.ts +344 -0
@@ -27,6 +27,7 @@ export interface GitPreferences {
27
27
  pre_merge_check?: boolean | string;
28
28
  commit_type?: string;
29
29
  main_branch?: string;
30
+ merge_strategy?: "squash" | "merge";
30
31
  }
31
32
 
32
33
  export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-\/.]+$/;
@@ -55,7 +56,7 @@ export interface PreMergeCheckResult {
55
56
  * GSD runtime paths that should be excluded from smart staging.
56
57
  * These are transient/generated artifacts that should never be committed.
57
58
  * Matches the union of SKIP_PATHS + SKIP_EXACT in worktree-manager.ts
58
- * and the first 6 entries in gitignore.ts BASELINE_PATTERNS.
59
+ * and the first 7 entries in gitignore.ts BASELINE_PATTERNS.
59
60
  */
60
61
  export const RUNTIME_EXCLUSION_PATHS: readonly string[] = [
61
62
  ".gsd/activity/",
@@ -63,6 +64,7 @@ export const RUNTIME_EXCLUSION_PATHS: readonly string[] = [
63
64
  ".gsd/worktrees/",
64
65
  ".gsd/auto.lock",
65
66
  ".gsd/metrics.json",
67
+ ".gsd/completed-units.json",
66
68
  ".gsd/STATE.md",
67
69
  ];
68
70
 
@@ -130,16 +132,42 @@ export class GitServiceImpl {
130
132
  */
131
133
  private smartStage(extraExclusions: readonly string[] = []): void {
132
134
  const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
133
- const excludes = allExclusions.map(p => `':(exclude)${p}'`);
134
- const args = ["add", "-A", "--", ".", ...excludes];
135
- try {
136
- this.git(args);
137
- } catch {
138
- console.error("GitService: smart staging failed, falling back to git add -A");
139
- this.git(["add", "-A"]);
135
+
136
+ // One-time cleanup: if runtime files are already tracked in the index
137
+ // (from older versions where the fallback bug staged them), untrack them
138
+ // in a dedicated commit. This must happen as a separate commit because
139
+ // the git reset HEAD step below would otherwise undo the rm --cached.
140
+ if (!this._runtimeFilesCleanedUp) {
141
+ let cleaned = false;
142
+ for (const exclusion of RUNTIME_EXCLUSION_PATHS) {
143
+ const result = this.git(["rm", "--cached", "-r", "--ignore-unmatch", exclusion], { allowFailure: true });
144
+ if (result && result.includes("rm '")) cleaned = true;
145
+ }
146
+ if (cleaned) {
147
+ this.git(["commit", "-F", "-"], { input: "chore: untrack .gsd/ runtime files from git index" });
148
+ }
149
+ this._runtimeFilesCleanedUp = true;
150
+ }
151
+
152
+ // Stage everything, then unstage excluded paths.
153
+ //
154
+ // Previous approach used pathspec excludes (:(exclude)...) with git add -A,
155
+ // but that fails when .gsd/ is in .gitignore — git exits non-zero before
156
+ // evaluating the excludes. The catch fallback ran plain `git add -A`,
157
+ // staging all tracked runtime files unconditionally and defeating the
158
+ // exclusion list entirely.
159
+ //
160
+ // git reset HEAD silently succeeds when the path isn't staged, so no
161
+ // error handling is needed per-path.
162
+ this.git(["add", "-A"]);
163
+ for (const exclusion of allExclusions) {
164
+ this.git(["reset", "HEAD", "--", exclusion], { allowFailure: true });
140
165
  }
141
166
  }
142
167
 
168
+ /** Tracks whether runtime file cleanup has run this session. */
169
+ private _runtimeFilesCleanedUp = false;
170
+
143
171
  /**
144
172
  * Stage files (smart staging) and commit.
145
173
  * Returns the commit message string on success, or null if nothing to commit.
@@ -312,6 +340,12 @@ export class GitServiceImpl {
312
340
  // Exclude .gsd/ to prevent merge conflicts when both branches modify planning artifacts.
313
341
  this.autoCommit("pre-switch", current, [".gsd/"]);
314
342
 
343
+ // Discard uncommitted .gsd/ changes so checkout doesn't fail.
344
+ // These are runtime files (metrics, completed-units, STATE) that were
345
+ // intentionally excluded from the commit above. If they remain dirty,
346
+ // git checkout refuses when the target branch has different versions.
347
+ this.git(["checkout", "--", ".gsd/"], { allowFailure: true });
348
+
315
349
  this.git(["checkout", branch]);
316
350
  return created;
317
351
  }
@@ -327,6 +361,9 @@ export class GitServiceImpl {
327
361
  // Exclude .gsd/ to prevent merge conflicts when both branches modify planning artifacts.
328
362
  this.autoCommit("pre-switch", current, [".gsd/"]);
329
363
 
364
+ // Discard uncommitted .gsd/ changes so checkout doesn't fail.
365
+ this.git(["checkout", "--", ".gsd/"], { allowFailure: true });
366
+
330
367
  this.git(["checkout", mainBranch]);
331
368
  }
332
369
 
@@ -490,25 +527,33 @@ export class GitServiceImpl {
490
527
  // Pull latest main before merging to avoid conflicts from remote changes
491
528
  this.git(["pull", "--rebase", "origin", mainBranch], { allowFailure: true });
492
529
 
493
- // Squash mergeabort cleanly on conflict so the working tree is never
494
- // left in a half-merged state (see: merge-bug-fix).
530
+ // Merge slice branch strategy is configurable via git.merge_strategy
531
+ // preference. Default: "squash" (preserves existing behavior).
532
+ // "merge" uses --no-ff which is more resilient to conflicts from
533
+ // long-lived branches or frequently-changing .gsd/* artifacts.
534
+ const strategy = this.prefs.merge_strategy ?? "squash";
535
+ const mergeArgs = strategy === "merge"
536
+ ? ["merge", "--no-ff", "-m", message, branch]
537
+ : ["merge", "--squash", branch];
538
+
495
539
  try {
496
- this.git(["merge", "--squash", branch]);
540
+ this.git(mergeArgs);
497
541
  } catch (mergeError) {
498
- // git merge --squash exits non-zero on conflict. The working tree now
499
- // has conflict markers and a dirty index. Reset to restore a clean state.
542
+ // Merge exits non-zero on conflict. Reset to restore a clean state.
500
543
  this.git(["reset", "--hard", "HEAD"], { allowFailure: true });
501
544
  const msg = mergeError instanceof Error ? mergeError.message : String(mergeError);
502
545
  throw new Error(
503
- `Squash-merge of "${branch}" into "${mainBranch}" failed with conflicts. ` +
546
+ `${strategy === "merge" ? "Merge" : "Squash-merge"} of "${branch}" into "${mainBranch}" failed with conflicts. ` +
504
547
  `Working tree has been reset to a clean state. ` +
505
- `Resolve manually: git checkout ${mainBranch} && git merge --squash ${branch}\n` +
548
+ `Resolve manually: git checkout ${mainBranch} && git merge ${strategy === "merge" ? "--no-ff" : "--squash"} ${branch}\n` +
506
549
  `Original error: ${msg}`,
507
550
  );
508
551
  }
509
552
 
510
- // Commit with rich message via stdin pipe
511
- this.git(["commit", "-F", "-"], { input: message });
553
+ // Squash merge needs a separate commit; --no-ff merge already committed
554
+ if (strategy === "squash") {
555
+ this.git(["commit", "-F", "-"], { input: message });
556
+ }
512
557
 
513
558
  // Delete the merged branch
514
559
  this.git(["branch", "-D", branch]);
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { join } from "node:path";
10
10
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
11
+ import { execSync } from "node:child_process";
11
12
 
12
13
  /**
13
14
  * Patterns that are always correct regardless of project type.
@@ -20,6 +21,7 @@ const BASELINE_PATTERNS = [
20
21
  ".gsd/worktrees/",
21
22
  ".gsd/auto.lock",
22
23
  ".gsd/metrics.json",
24
+ ".gsd/completed-units.json",
23
25
  ".gsd/STATE.md",
24
26
 
25
27
  // ── OS junk ──
@@ -105,6 +107,32 @@ export function ensureGitignore(basePath: string): boolean {
105
107
  return true;
106
108
  }
107
109
 
110
+ /**
111
+ * Remove BASELINE_PATTERNS runtime paths from the git index if they are
112
+ * currently tracked. This fixes repos that started tracking these files
113
+ * before the .gitignore rule was added — git continues tracking files
114
+ * already in the index even after .gitignore is updated.
115
+ *
116
+ * Only removes from the index (`--cached`), never from disk. Idempotent.
117
+ */
118
+ export function untrackRuntimeFiles(basePath: string): void {
119
+ // The GSD runtime paths are the first 7 entries in BASELINE_PATTERNS
120
+ const runtimePaths = BASELINE_PATTERNS.slice(0, 7);
121
+
122
+ for (const pattern of runtimePaths) {
123
+ // Use -r for directory patterns (trailing slash), strip the slash for the command
124
+ const target = pattern.endsWith("/") ? pattern.slice(0, -1) : pattern;
125
+ try {
126
+ execSync(`git rm -r --cached ${target}`, {
127
+ cwd: basePath,
128
+ stdio: ["ignore", "ignore", "ignore"],
129
+ });
130
+ } catch {
131
+ // File not tracked or doesn't exist — expected, ignore
132
+ }
133
+ }
134
+ }
135
+
108
136
  /**
109
137
  * Ensure basePath/.gsd/PREFERENCES.md exists as an empty template.
110
138
  * Creates the file with frontmatter only if it doesn't exist.
@@ -14,15 +14,16 @@ import { deriveState } from "./state.js";
14
14
  import { startAuto } from "./auto.js";
15
15
  import { readCrashLock, clearLock, formatCrashInfo } from "./crash-recovery.js";
16
16
  import {
17
- gsdRoot, milestonesDir, resolveMilestoneFile,
17
+ gsdRoot, milestonesDir, resolveMilestoneFile, resolveMilestonePath,
18
18
  resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile,
19
19
  relMilestoneFile, relSliceFile, relSlicePath,
20
20
  } from "./paths.js";
21
21
  import { join } from "node:path";
22
- import { readFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
22
+ import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
23
23
  import { execSync, execFileSync } from "node:child_process";
24
- import { ensureGitignore, ensurePreferences } from "./gitignore.js";
24
+ import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
25
25
  import { loadEffectiveGSDPreferences } from "./preferences.js";
26
+ import { showConfirm } from "../shared/confirm-ui.js";
26
27
 
27
28
  // ─── Auto-start after discuss ─────────────────────────────────────────────────
28
29
 
@@ -35,6 +36,11 @@ let pendingAutoStart: {
35
36
  step?: boolean; // preserve step mode through discuss → auto transition
36
37
  } | null = null;
37
38
 
39
+ /** Returns the milestoneId being discussed, or null if no discussion is active */
40
+ export function getDiscussionMilestoneId(): string | null {
41
+ return pendingAutoStart?.milestoneId ?? null;
42
+ }
43
+
38
44
  /** Called from agent_end to check if auto-mode should start after discuss */
39
45
  export function checkAutoStartAfterDiscuss(): boolean {
40
46
  if (!pendingAutoStart) return false;
@@ -81,13 +87,13 @@ function dispatchWorkflow(pi: ExtensionAPI, note: string, customType = "gsd-run"
81
87
  * Build the discuss-and-plan prompt for a new milestone.
82
88
  * Used by all three "new milestone" paths (first ever, no active, all complete).
83
89
  */
84
- function buildDiscussPrompt(nextId: string, preamble: string, basePath: string): string {
85
- const milestoneDirAbs = join(basePath, ".gsd", "milestones", nextId);
90
+ function buildDiscussPrompt(nextId: string, preamble: string, _basePath: string): string {
91
+ const milestoneRel = `.gsd/milestones/${nextId}`;
86
92
  return loadPrompt("discuss", {
87
93
  milestoneId: nextId,
88
94
  preamble,
89
- contextAbsPath: join(milestoneDirAbs, `${nextId}-CONTEXT.md`),
90
- roadmapAbsPath: join(milestoneDirAbs, `${nextId}-ROADMAP.md`),
95
+ contextPath: `${milestoneRel}/${nextId}-CONTEXT.md`,
96
+ roadmapPath: `${milestoneRel}/${nextId}-ROADMAP.md`,
91
97
  });
92
98
  }
93
99
 
@@ -339,16 +345,16 @@ async function buildDiscussSlicePrompt(
339
345
  ? `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`
340
346
  : `## Inlined Context\n\n_(no context files found yet — go in blind and ask broad questions)_`;
341
347
 
342
- const sliceDirAbsPath = join(base, ".gsd", "milestones", mid, "slices", sid);
343
- const contextAbsPath = join(sliceDirAbsPath, `${sid}-CONTEXT.md`);
348
+ const sliceDirPath = `.gsd/milestones/${mid}/slices/${sid}`;
349
+ const sliceContextPath = `${sliceDirPath}/${sid}-CONTEXT.md`;
344
350
 
345
351
  return loadPrompt("guided-discuss-slice", {
346
352
  milestoneId: mid,
347
353
  sliceId: sid,
348
354
  sliceTitle: sTitle,
349
355
  inlinedContext,
350
- sliceDirAbsPath,
351
- contextAbsPath,
356
+ sliceDirPath,
357
+ contextPath: sliceContextPath,
352
358
  projectRoot: base,
353
359
  });
354
360
  }
@@ -451,6 +457,7 @@ export async function showSmartEntry(
451
457
 
452
458
  // ── Ensure .gitignore has baseline patterns ──────────────────────────
453
459
  ensureGitignore(basePath);
460
+ untrackRuntimeFiles(basePath);
454
461
 
455
462
  // ── No GSD project OR no milestone → Create first/next milestone ────
456
463
  if (!existsSync(join(basePath, ".gsd"))) {
@@ -601,6 +608,16 @@ export async function showSmartEntry(
601
608
  label: "Discuss first",
602
609
  description: "Capture decisions on gray areas before planning.",
603
610
  }] : []),
611
+ {
612
+ id: "skip_milestone",
613
+ label: "Skip — create new milestone",
614
+ description: "Leave this milestone on disk and start a fresh one.",
615
+ },
616
+ {
617
+ id: "discard_milestone",
618
+ label: "Discard this milestone",
619
+ description: "Delete the milestone directory and start over.",
620
+ },
604
621
  ];
605
622
 
606
623
  const choice = await showNextAction(ctx as any, {
@@ -619,6 +636,27 @@ export async function showSmartEntry(
619
636
  dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
620
637
  milestoneId, milestoneTitle,
621
638
  }));
639
+ } else if (choice === "skip_milestone") {
640
+ const milestoneIds = findMilestoneIds(basePath);
641
+ const nextId = `M${String(milestoneIds.length + 1).padStart(3, "0")}`;
642
+ pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
643
+ dispatchWorkflow(pi, buildDiscussPrompt(nextId,
644
+ `New milestone ${nextId}.`,
645
+ basePath
646
+ ));
647
+ } else if (choice === "discard_milestone") {
648
+ const mDir = resolveMilestonePath(basePath, milestoneId);
649
+ if (!mDir) return;
650
+ const confirmed = await showConfirm(ctx as any, {
651
+ title: "Discard milestone?",
652
+ message: `This will permanently delete ${milestoneId} and all its contents.`,
653
+ confirmLabel: "Discard",
654
+ declineLabel: "Cancel",
655
+ });
656
+ if (confirmed) {
657
+ rmSync(mDir, { recursive: true, force: true });
658
+ return showSmartEntry(ctx, pi, basePath, options);
659
+ }
622
660
  }
623
661
  } else {
624
662
  // Roadmap exists — either blocked or ready for auto
@@ -20,18 +20,20 @@
20
20
 
21
21
  import type {
22
22
  ExtensionAPI,
23
+ ExtensionCommandContext,
23
24
  ExtensionContext,
24
25
  } from "@gsd/pi-coding-agent";
25
- import { createBashTool, createWriteTool, createReadTool, createEditTool } from "@gsd/pi-coding-agent";
26
+ import { createBashTool, createWriteTool, createReadTool, createEditTool, isToolCallEventType } from "@gsd/pi-coding-agent";
26
27
 
27
28
  import { registerGSDCommand } from "./commands.js";
29
+ import { registerExitCommand } from "./exit-command.js";
28
30
  import { registerWorktreeCommand, getWorktreeOriginalCwd, getActiveWorktreeName } from "./worktree-command.js";
29
31
  import { saveFile, formatContinue, loadFile, parseContinue, parseSummary } from "./files.js";
30
32
  import { loadPrompt } from "./prompt-loader.js";
31
33
  import { deriveState } from "./state.js";
32
34
  import { isAutoActive, isAutoPaused, handleAgentEnd, pauseAuto, getAutoDashboardData } from "./auto.js";
33
35
  import { saveActivityLog } from "./activity-log.js";
34
- import { checkAutoStartAfterDiscuss } from "./guided-flow.js";
36
+ import { checkAutoStartAfterDiscuss, getDiscussionMilestoneId } from "./guided-flow.js";
35
37
  import { GSDDashboardOverlay } from "./dashboard-overlay.js";
36
38
  import {
37
39
  loadEffectiveGSDPreferences,
@@ -42,7 +44,7 @@ import { hasSkillSnapshot, detectNewSkills, formatSkillsXml } from "./skill-disc
42
44
  import {
43
45
  resolveSlicePath, resolveSliceFile, resolveTaskFile, resolveTaskFiles, resolveTasksDir,
44
46
  relSliceFile, relSlicePath, relTaskFile,
45
- buildSliceFileName, gsdRoot,
47
+ buildSliceFileName, buildMilestoneFileName, gsdRoot, resolveMilestonePath,
46
48
  } from "./paths.js";
47
49
  import { Key } from "@gsd/pi-tui";
48
50
  import { join } from "node:path";
@@ -50,6 +52,32 @@ import { existsSync } from "node:fs";
50
52
  import { shortcutDesc } from "../shared/terminal.js";
51
53
  import { Text } from "@gsd/pi-tui";
52
54
 
55
+ // ── Depth verification state ──────────────────────────────────────────────
56
+ let depthVerificationDone = false;
57
+
58
+ export function isDepthVerified(): boolean {
59
+ return depthVerificationDone;
60
+ }
61
+
62
+ // ── Write-gate: block CONTEXT.md writes during discussion without depth verification ──
63
+ const MILESTONE_CONTEXT_RE = /M\d+-CONTEXT\.md$/;
64
+
65
+ export function shouldBlockContextWrite(
66
+ toolName: string,
67
+ inputPath: string,
68
+ milestoneId: string | null,
69
+ depthVerified: boolean,
70
+ ): { block: boolean; reason?: string } {
71
+ if (toolName !== "write") return { block: false };
72
+ if (!milestoneId) return { block: false };
73
+ if (!MILESTONE_CONTEXT_RE.test(inputPath)) return { block: false };
74
+ if (depthVerified) return { block: false };
75
+ return {
76
+ block: true,
77
+ reason: `Blocked: Cannot write to milestone CONTEXT.md during discussion phase without depth verification. Call ask_user_questions with question id "depth_verification" first to confirm discussion depth before writing context.`,
78
+ };
79
+ }
80
+
53
81
  // ── ASCII logo ────────────────────────────────────────────────────────────
54
82
  const GSD_LOGO_LINES = [
55
83
  " ██████╗ ███████╗██████╗ ",
@@ -63,22 +91,12 @@ const GSD_LOGO_LINES = [
63
91
  export default function (pi: ExtensionAPI) {
64
92
  registerGSDCommand(pi);
65
93
  registerWorktreeCommand(pi);
66
-
67
- // ── /exit — graceful exit (cleanup auto-mode, save state) ──────────────
68
- pi.registerCommand("exit", {
69
- description: "Exit GSD gracefully (saves auto-mode state)",
70
- handler: async (_ctx) => {
71
- // Gracefully stop auto-mode if running (saves activity log, clears locks)
72
- const { stopAuto } = await import("./auto.js");
73
- await stopAuto(_ctx, pi);
74
- process.exit(0);
75
- },
76
- });
94
+ registerExitCommand(pi);
77
95
 
78
96
  // ── /kill — immediate exit (bypass cleanup) ─────────────────────────────
79
97
  pi.registerCommand("kill", {
80
98
  description: "Exit GSD immediately (no cleanup)",
81
- handler: async (_ctx) => {
99
+ handler: async (_args: string, _ctx: ExtensionCommandContext) => {
82
100
  process.exit(0);
83
101
  },
84
102
  });
@@ -299,7 +317,10 @@ export default function (pi: ExtensionAPI) {
299
317
  // ── agent_end: auto-mode advancement or auto-start after discuss ───────────
300
318
  pi.on("agent_end", async (event, ctx: ExtensionContext) => {
301
319
  // If discuss phase just finished, start auto-mode
302
- if (checkAutoStartAfterDiscuss()) return;
320
+ if (checkAutoStartAfterDiscuss()) {
321
+ depthVerificationDone = false;
322
+ return;
323
+ }
303
324
 
304
325
  // If auto-mode is already running, advance to next unit
305
326
  if (!isAutoActive()) return;
@@ -373,6 +394,80 @@ export default function (pi: ExtensionAPI) {
373
394
  saveActivityLog(ctx, dash.basePath, dash.currentUnit.type, dash.currentUnit.id);
374
395
  }
375
396
  });
397
+
398
+ // ── tool_call: block CONTEXT.md writes during discussion without depth verification ──
399
+ pi.on("tool_call", async (event) => {
400
+ if (!isToolCallEventType("write", event)) return;
401
+ const result = shouldBlockContextWrite(
402
+ event.toolName,
403
+ event.input.path,
404
+ getDiscussionMilestoneId(),
405
+ isDepthVerified(),
406
+ );
407
+ if (result.block) return result;
408
+ });
409
+
410
+ // ── tool_result: persist discussion exchanges & detect depth gate ──────
411
+ pi.on("tool_result", async (event) => {
412
+ if (event.toolName !== "ask_user_questions") return;
413
+
414
+ const milestoneId = getDiscussionMilestoneId();
415
+ if (!milestoneId) return;
416
+
417
+ const details = event.details as any;
418
+ if (details?.cancelled || !details?.response) return;
419
+
420
+ // ── Depth gate detection ──────────────────────────────────────────
421
+ const questions: any[] = (event.input as any)?.questions ?? [];
422
+ for (const q of questions) {
423
+ if (typeof q.id === "string" && q.id.includes("depth_verification")) {
424
+ depthVerificationDone = true;
425
+ break;
426
+ }
427
+ }
428
+
429
+ // ── Persist exchange to DISCUSSION.md ──────────────────────────────
430
+ const basePath = process.cwd();
431
+ const milestoneDir = resolveMilestonePath(basePath, milestoneId);
432
+ if (!milestoneDir) return;
433
+
434
+ const fileName = buildMilestoneFileName(milestoneId, "DISCUSSION");
435
+ const discussionPath = join(milestoneDir, fileName);
436
+ const timestamp = new Date().toISOString();
437
+
438
+ // Format exchange as markdown
439
+ const lines: string[] = [`## Exchange — ${timestamp}`, ""];
440
+
441
+ for (const q of questions) {
442
+ lines.push(`### ${q.header ?? "Question"}`);
443
+ lines.push("");
444
+ lines.push(q.question ?? "");
445
+ if (Array.isArray(q.options)) {
446
+ lines.push("");
447
+ for (const opt of q.options) {
448
+ lines.push(`- **${opt.label}** — ${opt.description ?? ""}`);
449
+ }
450
+ }
451
+
452
+ // Append user response for this question
453
+ const answer = details.response?.answers?.[q.id];
454
+ if (answer) {
455
+ lines.push("");
456
+ const selected = Array.isArray(answer.selected) ? answer.selected.join(", ") : answer.selected;
457
+ lines.push(`**Selected:** ${selected}`);
458
+ if (answer.notes) {
459
+ lines.push(`**Notes:** ${answer.notes}`);
460
+ }
461
+ }
462
+ lines.push("");
463
+ }
464
+
465
+ lines.push("---", "");
466
+
467
+ const newBlock = lines.join("\n");
468
+ const existing = await loadFile(discussionPath) ?? `# ${milestoneId} Discussion Log\n\n`;
469
+ await saveFile(discussionPath, existing + newBlock);
470
+ });
376
471
  }
377
472
 
378
473
  async function buildGuidedExecuteContextInjection(prompt: string, basePath: string): Promise<string | null> {
@@ -702,6 +702,14 @@ function validatePreferences(preferences: GSDPreferences): {
702
702
  errors.push(`git.commit_type must be one of: feat, fix, refactor, docs, test, chore, perf, ci, build, style`);
703
703
  }
704
704
  }
705
+ if (g.merge_strategy !== undefined) {
706
+ const validStrategies = new Set(["squash", "merge"]);
707
+ if (typeof g.merge_strategy === "string" && validStrategies.has(g.merge_strategy)) {
708
+ git.merge_strategy = g.merge_strategy as "squash" | "merge";
709
+ } else {
710
+ errors.push("git.merge_strategy must be one of: squash, merge");
711
+ }
712
+ }
705
713
  if (g.main_branch !== undefined) {
706
714
  if (typeof g.main_branch === "string" && g.main_branch.trim() !== "" && VALID_BRANCH_NAME.test(g.main_branch)) {
707
715
  git.main_branch = g.main_branch;
@@ -12,7 +12,7 @@ Then:
12
12
  3. Verify each **success criterion** from the milestone definition in `{{roadmapPath}}`. For each criterion, confirm it was met with specific evidence from slice summaries, test results, or observable behavior. List any criterion that was NOT met.
13
13
  4. Verify the milestone's **definition of done** — all slices are `[x]`, all slice summaries exist, and any cross-slice integration points work correctly.
14
14
  5. Validate **requirement status transitions**. For each requirement that changed status during this milestone, confirm the transition is supported by evidence. Requirements can move between Active, Validated, Deferred, Blocked, or Out of Scope — but only with proof.
15
- 6. Write `{{milestoneSummaryAbsPath}}` using the milestone-summary template. Fill all frontmatter fields and narrative sections. The `requirement_outcomes` field must list every requirement that changed status with `from_status`, `to_status`, and `proof`.
15
+ 6. Write `{{milestoneSummaryPath}}` using the milestone-summary template. Fill all frontmatter fields and narrative sections. The `requirement_outcomes` field must list every requirement that changed status with `from_status`, `to_status`, and `proof`.
16
16
  7. Update `.gsd/REQUIREMENTS.md` if any requirement status transitions were validated in step 5.
17
17
  8. Update `.gsd/PROJECT.md` to reflect milestone completion and current project state.
18
18
  9. Do not commit manually — the system auto-commits your changes after this unit completes.
@@ -20,6 +20,6 @@ Then:
20
20
 
21
21
  **Important:** Do NOT skip the success criteria and definition of done verification (steps 3-4). The milestone summary must reflect actual verified outcomes, not assumed success. If any criterion was not met, document it clearly in the summary and do not mark the milestone as passing verification.
22
22
 
23
- **You MUST write `{{milestoneSummaryAbsPath}}` AND update PROJECT.md before finishing.**
23
+ **You MUST write `{{milestoneSummaryPath}}` AND update PROJECT.md before finishing.**
24
24
 
25
25
  When done, say: "Milestone {{milestoneId}} complete."
@@ -16,14 +16,14 @@ Then:
16
16
  3. Run all slice-level verification checks defined in the slice plan. All must pass before marking the slice done. If any fail, fix them first.
17
17
  4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
18
18
  5. If `.gsd/REQUIREMENTS.md` exists, update it based on what this slice actually proved. Move requirements between Active, Validated, Deferred, Blocked, or Out of Scope only when the evidence from execution supports that change.
19
- 6. Write `{{sliceSummaryAbsPath}}` (compress all task summaries).
20
- 7. Write `{{sliceUatAbsPath}}`.
19
+ 6. Write `{{sliceSummaryPath}}` (compress all task summaries).
20
+ 7. Write `{{sliceUatPath}}` — a concrete UAT script with real test cases derived from the slice plan and task summaries. Include preconditions, numbered steps with expected outcomes, and edge cases. This must NOT be a placeholder or generic template — tailor every test case to what this slice actually built.
21
21
  8. Review task summaries for `key_decisions`. Append any significant decisions to `.gsd/DECISIONS.md` if missing.
22
22
  9. Mark {{sliceId}} done in `{{roadmapPath}}` (change `[ ]` to `[x]`)
23
23
  10. Do not commit or squash-merge manually — the system auto-commits your changes and handles the merge after this unit succeeds.
24
24
  11. Update `.gsd/PROJECT.md` if it exists — refresh current state if needed.
25
25
  12. Update `.gsd/STATE.md`
26
26
 
27
- **You MUST mark {{sliceId}} as `[x]` in `{{roadmapPath}}` AND write `{{sliceSummaryAbsPath}}` before finishing.**
27
+ **You MUST do ALL THREE before finishing: (1) write `{{sliceSummaryPath}}`, (2) write `{{sliceUatPath}}`, (3) mark {{sliceId}} as `[x]` in `{{roadmapPath}}`. The unit will not be marked complete if any of these files are missing.**
28
28
 
29
29
  When done, say: "Slice {{sliceId}} complete."
@@ -52,6 +52,16 @@ You are a thinking partner, not an interviewer.
52
52
 
53
53
  **Freeform rule:** When the user selects "Other" or clearly wants to explain something freely, stop using `ask_user_questions` and switch to plain text follow-ups. Let them talk. Resume structured questions when appropriate.
54
54
 
55
+ **Depth-signal awareness.** When a user writes extensively about something — long notes, detailed explanations, specific examples — that's a signal. Probe that area deeper. Don't spread attention evenly across all topics when the user is clearly investing energy in one.
56
+
57
+ **Enrichment fusion.** Weave the user's specific language, terminology, and framing into your subsequent questions. If they said "craft feel," your next question references "craft feel" — don't paraphrase it into "user experience quality." Their precision is signal, not noise.
58
+
59
+ **Position-first framing.** Have opinions. State your read of a tradeoff with rationale before asking what they think. "I'd lean toward X because Y — does that match your thinking, or am I missing context?" is better than "what do you think about X vs Y?" You're a thinking partner, not a neutral interviewer.
60
+
61
+ **Negative constraints.** Ask what would disappoint them. What they explicitly don't want. What the product should never feel like. Negative constraints are sharper than positive wishes — "never feel sluggish" defines the performance bar more precisely than "should be fast."
62
+
63
+ **Observation ≠ Conclusion.** Technical facts you discover in the codebase during investigation are context, not decisions. Present them as context and let the user decide what they mean for direction. "The current auth uses JWT with 24h expiry" is an observation. Whether to keep that pattern is the user's call.
64
+
55
65
  **Anti-patterns — never do these:**
56
66
  - **Checklist walking** — going through a predetermined list of topics regardless of what the user said
57
67
  - **Canned questions** — asking generic questions that could apply to any project
@@ -73,10 +83,22 @@ Do NOT offer to proceed until ALL of the following are satisfied. Track these in
73
83
  - [ ] **The biggest technical unknowns / risks** — what could fail, what hasn't been proven
74
84
  - [ ] **What external systems/services this touches** — APIs, databases, third-party services, hardware
75
85
 
86
+ Before offering to proceed, demonstrate absorption: reference specific things the user emphasized, specific terminology they used, specific nuance they sharpened — and show how those shaped your understanding. Synthesize, don't recite. "Your emphasis on X led me to prioritize Y over Z" is good. "You said X, you said Y, you said Z" is not. The user should feel heard in the specifics, not just acknowledged in the abstract.
87
+
76
88
  **Questioning depth should match scope.** Simple, well-defined work needs fewer rounds — maybe 1-2. Large, ambiguous visions need more — maybe 4+. Don't pad rounds to hit a number. Stop when the depth checklist is satisfied and you genuinely understand the work.
77
89
 
78
90
  Do not count the reflection step as a question round. Rounds start after reflection is confirmed.
79
91
 
92
+ ## Depth Verification
93
+
94
+ Before moving to the wrap-up gate, present a structured depth summary to the user via `ask_user_questions`. This is a checkpoint — show what you captured across the depth checklist dimensions, using the user's own terminology and framing.
95
+
96
+ The question should summarize: what you understood them to be building, what shaped your understanding most (their emphasis, constraints, concerns), and any areas where you're least confident in your understanding. Frame it as: "Before we move to planning, here's what I captured — did I get the depth right?"
97
+
98
+ **Convention:** The question ID must contain `depth_verification` (e.g., `depth_verification_summary`). This naming convention enables downstream mechanical detection of this step.
99
+
100
+ Offer two options: "Yes, you got it (Recommended)" and "Not quite — let me clarify." If they clarify, absorb the correction and re-verify.
101
+
80
102
  ## Wrap-up Gate
81
103
 
82
104
  Only after the depth checklist is fully satisfied and you genuinely understand the work, offer to proceed.
@@ -166,8 +188,11 @@ Once the user is satisfied, in a single pass:
166
188
  1. `mkdir -p .gsd/milestones/{{milestoneId}}/slices`
167
189
  2. Write or update `.gsd/PROJECT.md` — read the template at `~/.gsd/agent/extensions/gsd/templates/project.md` first. Describe what the project is, its current state, and list the milestone sequence.
168
190
  3. Write or update `.gsd/REQUIREMENTS.md` — read the template at `~/.gsd/agent/extensions/gsd/templates/requirements.md` first. Confirm requirement states, ownership, and traceability before roadmap creation.
169
- 4. Write `{{contextAbsPath}}` — read the template at `~/.gsd/agent/extensions/gsd/templates/context.md` first. Preserve key risks, unknowns, existing codebase constraints, integration points, and relevant requirements surfaced during discussion.
170
- 5. Write `{{roadmapAbsPath}}` read the template at `~/.gsd/agent/extensions/gsd/templates/roadmap.md` first. Decompose into demoable vertical slices with checkboxes, risk, depends, demo sentences, proof strategy, verification classes, milestone definition of done, requirement coverage, and a boundary map. If the milestone crosses multiple runtime boundaries, include an explicit final integration slice that proves the assembled system works end-to-end in a real environment.
191
+ **Depth-Preservation Guidance for context.md:**
192
+ When writing context.md, preserve the user's exact terminology, emphasis, and specific framing from the discussion. Do not paraphrase user nuance into generic summaries. If the user said "craft feel," write "craft feel" not "high-quality user experience." If they emphasized a specific constraint or negative requirement, carry that emphasis through verbatim. The context file is downstream agents' only window into this conversation flattening specifics into generics loses the signal that shaped every decision.
193
+
194
+ 4. Write `{{contextPath}}` — read the template at `~/.gsd/agent/extensions/gsd/templates/context.md` first. Preserve key risks, unknowns, existing codebase constraints, integration points, and relevant requirements surfaced during discussion.
195
+ 5. Write `{{roadmapPath}}` — read the template at `~/.gsd/agent/extensions/gsd/templates/roadmap.md` first. Decompose into demoable vertical slices with checkboxes, risk, depends, demo sentences, proof strategy, verification classes, milestone definition of done, requirement coverage, and a boundary map. If the milestone crosses multiple runtime boundaries, include an explicit final integration slice that proves the assembled system works end-to-end in a real environment.
171
196
  6. Seed `.gsd/DECISIONS.md` — read the template at `~/.gsd/agent/extensions/gsd/templates/decisions.md` first. Append rows for any architectural or pattern decisions made during discussion.
172
197
  7. Update `.gsd/STATE.md`
173
198
  8. Commit: `docs({{milestoneId}}): context, requirements, and roadmap`
@@ -49,13 +49,13 @@ Then:
49
49
  11. **Blocker discovery:** If execution reveals that the remaining slice plan is fundamentally invalid — not just a bug or minor deviation, but a plan-invalidating finding like a wrong API, missing capability, or architectural mismatch — set `blocker_discovered: true` in the task summary frontmatter and describe the blocker clearly in the summary narrative. Do NOT set `blocker_discovered: true` for ordinary debugging, minor deviations, or issues that can be fixed within the current task or the remaining plan. This flag triggers an automatic replan of the slice.
50
50
  12. If you made an architectural, pattern, library, or observability decision during this task that downstream work should know about, append it to `.gsd/DECISIONS.md` (read the template at `~/.gsd/agent/extensions/gsd/templates/decisions.md` if the file doesn't exist yet). Not every task produces decisions — only append when a meaningful choice was made.
51
51
  13. Read the template at `~/.gsd/agent/extensions/gsd/templates/task-summary.md`
52
- 14. Write `{{taskSummaryAbsPath}}`
52
+ 14. Write `{{taskSummaryPath}}`
53
53
  15. Mark {{taskId}} done in `{{planPath}}` (change `[ ]` to `[x]`)
54
54
  16. Do not commit manually — the system auto-commits your changes after this unit completes.
55
55
  17. Update `.gsd/STATE.md`
56
56
 
57
57
  You are on the slice branch. All work stays here.
58
58
 
59
- **You MUST mark {{taskId}} as `[x]` in `{{planPath}}` AND write `{{taskSummaryAbsPath}}` before finishing.**
59
+ **You MUST mark {{taskId}} as `[x]` in `{{planPath}}` AND write `{{taskSummaryPath}}` before finishing.**
60
60
 
61
61
  When done, say: "Task {{taskId}} complete."
@@ -46,8 +46,8 @@ If the user wants to keep going, keep asking. Stop when they say wrap up.
46
46
  Once the user is ready to wrap up:
47
47
 
48
48
  1. Read the slice context template at `~/.gsd/agent/extensions/gsd/templates/slice-context.md`
49
- 2. `mkdir -p {{sliceDirAbsPath}}`
50
- 3. Write `{{contextAbsPath}}` — use the template structure, filling in:
49
+ 2. `mkdir -p {{sliceDirPath}}`
50
+ 3. Write `{{contextPath}}` — use the template structure, filling in:
51
51
  - **Goal** — one sentence: what this slice delivers
52
52
  - **Why this Slice** — why now, what it unblocks
53
53
  - **Scope / In Scope** — what was confirmed in scope during the interview
@@ -55,5 +55,5 @@ Once the user is ready to wrap up:
55
55
  - **Constraints** — anything the user flagged as a hard constraint
56
56
  - **Integration Points** — what this slice consumes and produces
57
57
  - **Open Questions** — anything still unresolved, with current thinking
58
- 4. Commit: `git -C {{projectRoot}} add {{contextAbsPath}} && git -C {{projectRoot}} commit -m "docs({{milestoneId}}/{{sliceId}}): slice context from discuss"`
58
+ 4. Commit: `git -C {{projectRoot}} add {{contextPath}} && git -C {{projectRoot}} commit -m "docs({{milestoneId}}/{{sliceId}}): slice context from discuss"`
59
59
  5. Say exactly: `"{{sliceId}} context written."` — nothing else.