gsd-pi 2.38.0-dev.96dc7fb → 2.38.0-dev.98b44dc

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 (217) hide show
  1. package/README.md +15 -11
  2. package/dist/app-paths.js +1 -1
  3. package/dist/extension-registry.js +2 -2
  4. package/dist/remote-questions-config.js +2 -2
  5. package/dist/resource-loader.js +34 -1
  6. package/dist/resources/extensions/browser-tools/index.js +3 -1
  7. package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
  8. package/dist/resources/extensions/env-utils.js +29 -0
  9. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  10. package/dist/resources/extensions/github-sync/cli.js +284 -0
  11. package/dist/resources/extensions/github-sync/index.js +73 -0
  12. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  13. package/dist/resources/extensions/github-sync/sync.js +424 -0
  14. package/dist/resources/extensions/github-sync/templates.js +118 -0
  15. package/dist/resources/extensions/github-sync/types.js +7 -0
  16. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  18. package/dist/resources/extensions/gsd/auto-loop.js +636 -594
  19. package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
  20. package/dist/resources/extensions/gsd/auto-prompts.js +202 -48
  21. package/dist/resources/extensions/gsd/auto-start.js +7 -1
  22. package/dist/resources/extensions/gsd/auto-worktree-sync.js +2 -1
  23. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  24. package/dist/resources/extensions/gsd/auto.js +143 -96
  25. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  26. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  27. package/dist/resources/extensions/gsd/commands.js +4 -2
  28. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  29. package/dist/resources/extensions/gsd/detection.js +1 -2
  30. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  31. package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
  32. package/dist/resources/extensions/gsd/doctor.js +20 -1
  33. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  34. package/dist/resources/extensions/gsd/export.js +1 -1
  35. package/dist/resources/extensions/gsd/files.js +48 -9
  36. package/dist/resources/extensions/gsd/forensics.js +1 -1
  37. package/dist/resources/extensions/gsd/git-service.js +30 -12
  38. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  39. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  40. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  41. package/dist/resources/extensions/gsd/health-widget.js +3 -86
  42. package/dist/resources/extensions/gsd/index.js +24 -20
  43. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  44. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  45. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  46. package/dist/resources/extensions/gsd/paths.js +3 -0
  47. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  48. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  49. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  50. package/dist/resources/extensions/gsd/preferences.js +22 -11
  51. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  52. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  55. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
  56. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  57. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  58. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  59. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  60. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  61. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  62. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  63. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  64. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  65. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  66. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  67. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  68. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  69. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  70. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  71. package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
  72. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  73. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  74. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  75. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  76. package/dist/resources/extensions/gsd/state.js +42 -23
  77. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  78. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  79. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  80. package/dist/resources/extensions/mcp-client/index.js +14 -1
  81. package/dist/resources/extensions/remote-questions/status.js +4 -1
  82. package/dist/resources/extensions/remote-questions/store.js +4 -1
  83. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  84. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  85. package/dist/resources/extensions/subagent/isolation.js +2 -1
  86. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  87. package/package.json +1 -1
  88. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  89. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  90. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  91. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  93. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  95. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/skills.js +6 -1
  97. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  99. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/index.js +1 -1
  101. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  102. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  103. package/packages/pi-coding-agent/src/core/skills.ts +9 -1
  104. package/packages/pi-coding-agent/src/index.ts +1 -0
  105. package/src/resources/extensions/browser-tools/index.ts +3 -0
  106. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  107. package/src/resources/extensions/env-utils.ts +31 -0
  108. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  109. package/src/resources/extensions/github-sync/cli.ts +364 -0
  110. package/src/resources/extensions/github-sync/index.ts +93 -0
  111. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  112. package/src/resources/extensions/github-sync/sync.ts +556 -0
  113. package/src/resources/extensions/github-sync/templates.ts +183 -0
  114. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  115. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  116. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  117. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  118. package/src/resources/extensions/github-sync/types.ts +47 -0
  119. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  120. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  121. package/src/resources/extensions/gsd/auto-loop.ts +526 -545
  122. package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
  123. package/src/resources/extensions/gsd/auto-prompts.ts +247 -50
  124. package/src/resources/extensions/gsd/auto-start.ts +11 -1
  125. package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -1
  126. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  127. package/src/resources/extensions/gsd/auto.ts +139 -101
  128. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  129. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  130. package/src/resources/extensions/gsd/commands.ts +5 -3
  131. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  132. package/src/resources/extensions/gsd/detection.ts +2 -2
  133. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  134. package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
  135. package/src/resources/extensions/gsd/doctor.ts +22 -1
  136. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  137. package/src/resources/extensions/gsd/export.ts +1 -1
  138. package/src/resources/extensions/gsd/files.ts +51 -11
  139. package/src/resources/extensions/gsd/forensics.ts +1 -1
  140. package/src/resources/extensions/gsd/git-service.ts +44 -10
  141. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  142. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  143. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  144. package/src/resources/extensions/gsd/health-widget.ts +3 -89
  145. package/src/resources/extensions/gsd/index.ts +24 -17
  146. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  147. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  148. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  149. package/src/resources/extensions/gsd/paths.ts +4 -0
  150. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  151. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  152. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  153. package/src/resources/extensions/gsd/preferences.ts +25 -11
  154. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  155. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  156. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  157. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  158. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
  159. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  160. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  161. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  162. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  163. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  164. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  165. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  166. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  167. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  168. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  169. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  170. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  171. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  172. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  174. package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
  175. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  176. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  177. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  178. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  179. package/src/resources/extensions/gsd/state.ts +39 -21
  180. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  181. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  182. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  183. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  184. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  185. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  186. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  187. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  188. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  189. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  190. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  191. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  192. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  193. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  194. package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
  195. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  196. package/src/resources/extensions/gsd/types.ts +18 -1
  197. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  198. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  199. package/src/resources/extensions/mcp-client/index.ts +17 -1
  200. package/src/resources/extensions/remote-questions/status.ts +5 -1
  201. package/src/resources/extensions/remote-questions/store.ts +5 -1
  202. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  203. package/src/resources/extensions/shared/frontmatter.ts +1 -1
  204. package/src/resources/extensions/subagent/isolation.ts +3 -1
  205. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  206. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  207. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  208. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  209. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  210. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  211. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  212. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  213. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  214. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  215. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  216. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  217. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -11,12 +11,10 @@ import { runProviderChecks, summariseProviderIssues } from "./doctor-providers.j
11
11
  import { runEnvironmentChecks } from "./doctor-environment.js";
12
12
  import { loadEffectiveGSDPreferences } from "./preferences.js";
13
13
  import { loadLedgerFromDisk, getProjectTotals } from "./metrics.js";
14
- import { describeNextUnit, estimateTimeRemaining, updateSliceProgressCache } from "./auto-dashboard.js";
15
14
  import { projectRoot } from "./commands.js";
16
- import { deriveState, invalidateStateCache } from "./state.js";
17
15
  import { buildHealthLines, detectHealthWidgetProjectState, } from "./health-widget-core.js";
18
16
  // ── Data loader ────────────────────────────────────────────────────────────────
19
- function loadBaseHealthWidgetData(basePath) {
17
+ function loadHealthWidgetData(basePath) {
20
18
  let budgetCeiling;
21
19
  let budgetSpent = 0;
22
20
  let providerIssue = null;
@@ -58,86 +56,6 @@ function loadBaseHealthWidgetData(basePath) {
58
56
  lastRefreshed: Date.now(),
59
57
  };
60
58
  }
61
- function compactText(text, max = 64) {
62
- const trimmed = text.replace(/\s+/g, " ").trim();
63
- if (trimmed.length <= max)
64
- return trimmed;
65
- return `${trimmed.slice(0, max - 1).trimEnd()}…`;
66
- }
67
- function summarizeExecutionStatus(state) {
68
- switch (state.phase) {
69
- case "blocked": return "Blocked";
70
- case "paused": return "Paused";
71
- case "complete": return "Complete";
72
- case "executing": return "Executing";
73
- case "planning": return "Planning";
74
- case "pre-planning": return "Pre-planning";
75
- case "summarizing": return "Summarizing";
76
- case "validating-milestone": return "Validating";
77
- case "completing-milestone": return "Completing";
78
- case "needs-discussion": return "Needs discussion";
79
- case "replanning-slice": return "Replanning";
80
- default: return "Active";
81
- }
82
- }
83
- function summarizeExecutionTarget(state) {
84
- switch (state.phase) {
85
- case "needs-discussion":
86
- return state.activeMilestone ? `Discuss ${state.activeMilestone.id}` : "Discuss milestone draft";
87
- case "pre-planning":
88
- return state.activeMilestone ? `Plan ${state.activeMilestone.id}` : "Research & plan milestone";
89
- case "planning":
90
- return state.activeSlice ? `Plan ${state.activeSlice.id}` : "Plan next slice";
91
- case "executing":
92
- return state.activeTask ? `Execute ${state.activeTask.id}` : "Execute next task";
93
- case "summarizing":
94
- return state.activeSlice ? `Complete ${state.activeSlice.id}` : "Complete current slice";
95
- case "validating-milestone":
96
- return state.activeMilestone ? `Validate ${state.activeMilestone.id}` : "Validate milestone";
97
- case "completing-milestone":
98
- return state.activeMilestone ? `Complete ${state.activeMilestone.id}` : "Complete milestone";
99
- case "replanning-slice":
100
- return state.activeSlice ? `Replan ${state.activeSlice.id}` : "Replan current slice";
101
- case "blocked":
102
- return `waiting on ${compactText(state.blockers[0] ?? state.nextAction, 56)}`;
103
- case "paused":
104
- return compactText(state.nextAction || "waiting to resume", 56);
105
- case "complete":
106
- return "All milestones complete";
107
- default:
108
- return compactText(describeNextUnit(state).label, 56);
109
- }
110
- }
111
- async function enrichHealthWidgetData(basePath, baseData) {
112
- if (baseData.projectState !== "active")
113
- return baseData;
114
- try {
115
- invalidateStateCache();
116
- const state = await deriveState(basePath);
117
- if (state.activeMilestone) {
118
- // Warm the slice-progress cache so estimateTimeRemaining() has data
119
- updateSliceProgressCache(basePath, state.activeMilestone.id, state.activeSlice?.id);
120
- }
121
- return {
122
- ...baseData,
123
- executionPhase: state.phase,
124
- executionStatus: summarizeExecutionStatus(state),
125
- executionTarget: summarizeExecutionTarget(state),
126
- nextAction: state.nextAction,
127
- blocker: state.blockers[0] ?? null,
128
- activeMilestoneId: state.activeMilestone?.id,
129
- activeSliceId: state.activeSlice?.id,
130
- activeTaskId: state.activeTask?.id,
131
- progress: state.progress,
132
- eta: state.phase === "blocked" || state.phase === "paused" || state.phase === "complete"
133
- ? null
134
- : estimateTimeRemaining(),
135
- };
136
- }
137
- catch {
138
- return baseData;
139
- }
140
- }
141
59
  // ── Widget init ────────────────────────────────────────────────────────────────
142
60
  const REFRESH_INTERVAL_MS = 60_000;
143
61
  /**
@@ -149,7 +67,7 @@ export function initHealthWidget(ctx) {
149
67
  return;
150
68
  const basePath = projectRoot();
151
69
  // String-array fallback — used in RPC mode (factory is a no-op there)
152
- const initialData = loadBaseHealthWidgetData(basePath);
70
+ const initialData = loadHealthWidgetData(basePath);
153
71
  ctx.ui.setWidget("gsd-health", buildHealthLines(initialData), { placement: "belowEditor" });
154
72
  // Factory-based widget for TUI mode — replaces the string-array above
155
73
  ctx.ui.setWidget("gsd-health", (_tui, _theme) => {
@@ -161,8 +79,7 @@ export function initHealthWidget(ctx) {
161
79
  return;
162
80
  refreshInFlight = true;
163
81
  try {
164
- const baseData = loadBaseHealthWidgetData(basePath);
165
- data = await enrichHealthWidgetData(basePath, baseData);
82
+ data = loadHealthWidgetData(basePath);
166
83
  cachedLines = undefined;
167
84
  _tui.requestRender();
168
85
  }
@@ -41,6 +41,7 @@ import { join } from "node:path";
41
41
  import { existsSync, readFileSync } from "node:fs";
42
42
  import { homedir } from "node:os";
43
43
  import { shortcutDesc } from "../shared/mod.js";
44
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
44
45
  import { Text } from "@gsd/pi-tui";
45
46
  import { pauseAutoForProviderError, classifyProviderError } from "./provider-error-pause.js";
46
47
  import { toPosixPath } from "../shared/mod.js";
@@ -52,7 +53,7 @@ import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../cmux/index.js"
52
53
  // Pi core natively supports AGENTS.md (with CLAUDE.md fallback) per directory.
53
54
  function warnDeprecatedAgentInstructions() {
54
55
  const paths = [
55
- join(homedir(), ".gsd", "agent-instructions.md"),
56
+ join(gsdHome, "agent-instructions.md"),
56
57
  join(process.cwd(), ".gsd", "agent-instructions.md"),
57
58
  ];
58
59
  for (const p of paths) {
@@ -65,6 +66,24 @@ function warnDeprecatedAgentInstructions() {
65
66
  }
66
67
  // ── Depth verification state ──────────────────────────────────────────────
67
68
  let depthVerificationDone = false;
69
+ // ── DB lazy-open helper ───────────────────────────────────────────────────
70
+ // In manual sessions (no auto-mode), the DB is never opened by bootstrapAutoSession.
71
+ // This helper ensures the DB is lazily opened on first tool call that needs it.
72
+ async function ensureDbOpen() {
73
+ try {
74
+ const db = await import("./gsd-db.js");
75
+ if (db.isDbAvailable())
76
+ return true;
77
+ const dbPath = join(process.cwd(), ".gsd", "gsd.db");
78
+ if (existsSync(dbPath)) {
79
+ return db.openDatabase(dbPath);
80
+ }
81
+ return false;
82
+ }
83
+ catch {
84
+ return false;
85
+ }
86
+ }
68
87
  // ── Queue phase tracking ──────────────────────────────────────────────────
69
88
  // When true, the LLM is in a queue flow writing CONTEXT.md files.
70
89
  // The write-gate applies during queue flows just like discussion flows.
@@ -227,13 +246,8 @@ export default function (pi) {
227
246
  when_context: Type.Optional(Type.String({ description: "When/context for the decision (e.g. milestone ID)" })),
228
247
  }),
229
248
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
230
- // Check DB availability
231
- let dbAvailable = false;
232
- try {
233
- const db = await import("./gsd-db.js");
234
- dbAvailable = db.isDbAvailable();
235
- }
236
- catch { /* dynamic import failed */ }
249
+ // Ensure DB is open (lazy-open on first tool call in manual sessions)
250
+ const dbAvailable = await ensureDbOpen();
237
251
  if (!dbAvailable) {
238
252
  return {
239
253
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot save decision." }],
@@ -289,12 +303,7 @@ export default function (pi) {
289
303
  supporting_slices: Type.Optional(Type.String({ description: "Supporting slices" })),
290
304
  }),
291
305
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
292
- let dbAvailable = false;
293
- try {
294
- const db = await import("./gsd-db.js");
295
- dbAvailable = db.isDbAvailable();
296
- }
297
- catch { /* dynamic import failed */ }
306
+ const dbAvailable = await ensureDbOpen();
298
307
  if (!dbAvailable) {
299
308
  return {
300
309
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot update requirement." }],
@@ -364,12 +373,7 @@ export default function (pi) {
364
373
  content: Type.String({ description: "The full markdown content of the artifact" }),
365
374
  }),
366
375
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
367
- let dbAvailable = false;
368
- try {
369
- const db = await import("./gsd-db.js");
370
- dbAvailable = db.isDbAvailable();
371
- }
372
- catch { /* dynamic import failed */ }
376
+ const dbAvailable = await ensureDbOpen();
373
377
  if (!dbAvailable) {
374
378
  return {
375
379
  content: [{ type: "text", text: "Error: GSD database is not available. Cannot save artifact." }],
@@ -2,7 +2,7 @@
2
2
  // Pure functions that take file content (string) and return typed data.
3
3
  // Zero Pi dependencies — uses only exported helpers from files.ts.
4
4
  import { splitFrontmatter, parseFrontmatterMap, extractBoldField } from '../files.js';
5
- import { normalizeStringArray } from '../../shared/mod.js';
5
+ import { normalizeStringArray } from '../../shared/format-utils.js';
6
6
  // Re-export PlanningProjectMeta — not in types.ts yet, use string for project field
7
7
  // Actually PlanningProjectMeta isn't in types.ts — project is stored as string | null.
8
8
  // We'll keep parseOldProject returning a simple shape.
@@ -5,11 +5,13 @@
5
5
  * `~/.gsd/projects/<hash>/` state directory. After migration, a
6
6
  * symlink replaces the original directory so all paths remain valid.
7
7
  */
8
+ import { execFileSync } from "node:child_process";
8
9
  import { existsSync, lstatSync, mkdirSync, readdirSync, realpathSync, renameSync, cpSync, rmSync, symlinkSync } from "node:fs";
9
10
  import { join } from "node:path";
10
11
  import { externalGsdRoot } from "./repo-identity.js";
11
12
  import { getErrorMessage } from "./error-utils.js";
12
13
  import { hasGitTrackedGsdFiles } from "./gitignore.js";
14
+ import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
13
15
  /**
14
16
  * Migrate a legacy in-project `.gsd/` directory to external storage.
15
17
  *
@@ -142,7 +144,22 @@ export function migrateToExternalState(basePath) {
142
144
  catch { /* best-effort restore */ }
143
145
  return { migrated: false, error: `Migration verification failed: ${getErrorMessage(verifyErr)}` };
144
146
  }
145
- // Remove .gsd.migrating only after symlink is verified
147
+ // Clean the git index — any .gsd/* files tracked before migration now
148
+ // sit behind the symlink and git can't follow it, causing them to show
149
+ // as deleted. Remove them from the index so the working tree stays clean.
150
+ // --ignore-unmatch makes this a no-op on fresh projects with no tracked .gsd/.
151
+ try {
152
+ execFileSync("git", ["rm", "-r", "--cached", "--ignore-unmatch", ".gsd"], {
153
+ cwd: basePath,
154
+ stdio: ["ignore", "pipe", "ignore"],
155
+ env: GIT_NO_PROMPT_ENV,
156
+ timeout: 10_000,
157
+ });
158
+ }
159
+ catch {
160
+ // Non-fatal — git may be unavailable or nothing was tracked
161
+ }
162
+ // Remove .gsd.migrating only after symlink is verified and index is clean
146
163
  rmSync(migratingPath, { recursive: true, force: true });
147
164
  return { migrated: true };
148
165
  }
@@ -518,6 +518,43 @@ export function nativeAddAll(basePath) {
518
518
  }
519
519
  gitFileExec(basePath, ["add", "-A"]);
520
520
  }
521
+ /**
522
+ * Stage all files with pathspec exclusions (git add -A -- ':!pattern' ...).
523
+ * Excluded paths are never hashed by git, preventing hangs on large
524
+ * untracked artifact trees (57GB+, 11K+ files). See #1605.
525
+ *
526
+ * Falls back to plain `git add -A` when no exclusions are provided.
527
+ * Always uses the CLI path (not libgit2) because libgit2's add_all
528
+ * does not support pathspec exclusion syntax.
529
+ *
530
+ * When excluded paths are already covered by .gitignore, git may exit
531
+ * with code 1 and an "ignored by .gitignore" warning. This is harmless
532
+ * (the staging succeeds for all non-ignored files) and is suppressed.
533
+ */
534
+ export function nativeAddAllWithExclusions(basePath, exclusions) {
535
+ if (exclusions.length === 0) {
536
+ nativeAddAll(basePath);
537
+ return;
538
+ }
539
+ const pathspecs = exclusions.map(e => `:!${e}`);
540
+ try {
541
+ execFileSync("git", ["add", "-A", "--", ...pathspecs], {
542
+ cwd: basePath,
543
+ stdio: ["ignore", "pipe", "pipe"],
544
+ encoding: "utf-8",
545
+ env: GIT_NO_PROMPT_ENV,
546
+ });
547
+ }
548
+ catch (err) {
549
+ // git exits 1 when pathspec exclusions reference paths already covered
550
+ // by .gitignore. The staging itself succeeds — only suppress that case.
551
+ const stderr = err?.stderr ?? "";
552
+ if (stderr.includes("ignored by one of your .gitignore files")) {
553
+ return;
554
+ }
555
+ throw new GSDError(GSD_GIT_ERROR, `git add -A with exclusions failed in ${basePath}: ${getErrorMessage(err)}`);
556
+ }
557
+ }
521
558
  /**
522
559
  * Stage specific files.
523
560
  * Native: libgit2 index add.
@@ -343,6 +343,9 @@ function probeGsdRoot(rawBasePath) {
343
343
  export function milestonesDir(basePath) {
344
344
  return join(gsdRoot(basePath), "milestones");
345
345
  }
346
+ export function resolveRuntimeFile(basePath) {
347
+ return join(gsdRoot(basePath), "RUNTIME.md");
348
+ }
346
349
  export function resolveGsdRootFile(basePath, key) {
347
350
  const root = gsdRoot(basePath);
348
351
  const canonical = join(root, GSD_ROOT_FILES[key]);
@@ -260,18 +260,6 @@ export function resolveInlineLevel() {
260
260
  case "quality": return "full";
261
261
  }
262
262
  }
263
- /**
264
- * Resolve the compression strategy from the active token profile.
265
- * budget/balanced -> "compress", quality -> "truncate".
266
- * Explicit preference always wins.
267
- */
268
- export function resolveCompressionStrategy() {
269
- const prefs = loadEffectiveGSDPreferences();
270
- if (prefs?.preferences.compression_strategy)
271
- return prefs.preferences.compression_strategy;
272
- const profile = resolveEffectiveProfile();
273
- return profile === "quality" ? "truncate" : "compress";
274
- }
275
263
  /**
276
264
  * Resolve the context selection mode from the active token profile.
277
265
  * budget -> "smart", balanced/quality -> "full".
@@ -62,10 +62,10 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
62
62
  "verification_auto_fix",
63
63
  "verification_max_retries",
64
64
  "search_provider",
65
- "compression_strategy",
66
65
  "context_selection",
67
66
  "widget_mode",
68
67
  "reactive_execution",
68
+ "github",
69
69
  ]);
70
70
  /** Canonical list of all dispatch unit types. */
71
71
  export const KNOWN_UNIT_TYPES = [
@@ -6,7 +6,7 @@
6
6
  * together with any errors and warnings.
7
7
  */
8
8
  import { VALID_BRANCH_NAME } from "./git-service.js";
9
- import { normalizeStringArray } from "../shared/mod.js";
9
+ import { normalizeStringArray } from "../shared/format-utils.js";
10
10
  import { KNOWN_PREFERENCE_KEYS, KNOWN_UNIT_TYPES, SKILL_ACTIONS, } from "./preferences-types.js";
11
11
  const VALID_TOKEN_PROFILES = new Set(["budget", "balanced", "quality"]);
12
12
  export function validatePreferences(preferences) {
@@ -707,16 +707,6 @@ export function validatePreferences(preferences) {
707
707
  errors.push("auto_report must be a boolean");
708
708
  }
709
709
  }
710
- // ─── Compression Strategy ───────────────────────────────────────────
711
- if (preferences.compression_strategy !== undefined) {
712
- const validStrategies = new Set(["truncate", "compress"]);
713
- if (typeof preferences.compression_strategy === "string" && validStrategies.has(preferences.compression_strategy)) {
714
- validated.compression_strategy = preferences.compression_strategy;
715
- }
716
- else {
717
- errors.push(`compression_strategy must be one of: truncate, compress`);
718
- }
719
- }
720
710
  // ─── Context Selection ──────────────────────────────────────────────
721
711
  if (preferences.context_selection !== undefined) {
722
712
  const validModes = new Set(["full", "smart"]);
@@ -727,5 +717,63 @@ export function validatePreferences(preferences) {
727
717
  errors.push(`context_selection must be one of: full, smart`);
728
718
  }
729
719
  }
720
+ // ─── GitHub Sync ────────────────────────────────────────────────────────
721
+ if (preferences.github !== undefined) {
722
+ if (typeof preferences.github === "object" && preferences.github !== null) {
723
+ const gh = preferences.github;
724
+ const validGh = {};
725
+ if (gh.enabled !== undefined) {
726
+ if (typeof gh.enabled === "boolean")
727
+ validGh.enabled = gh.enabled;
728
+ else
729
+ errors.push("github.enabled must be a boolean");
730
+ }
731
+ if (gh.repo !== undefined) {
732
+ if (typeof gh.repo === "string" && gh.repo.includes("/"))
733
+ validGh.repo = gh.repo;
734
+ else
735
+ errors.push('github.repo must be a string in "owner/repo" format');
736
+ }
737
+ if (gh.project !== undefined) {
738
+ const p = typeof gh.project === "number" ? gh.project : Number(gh.project);
739
+ if (Number.isFinite(p) && p > 0)
740
+ validGh.project = Math.floor(p);
741
+ else
742
+ errors.push("github.project must be a positive number");
743
+ }
744
+ if (gh.labels !== undefined) {
745
+ if (Array.isArray(gh.labels) && gh.labels.every((l) => typeof l === "string")) {
746
+ validGh.labels = gh.labels;
747
+ }
748
+ else {
749
+ errors.push("github.labels must be an array of strings");
750
+ }
751
+ }
752
+ if (gh.auto_link_commits !== undefined) {
753
+ if (typeof gh.auto_link_commits === "boolean")
754
+ validGh.auto_link_commits = gh.auto_link_commits;
755
+ else
756
+ errors.push("github.auto_link_commits must be a boolean");
757
+ }
758
+ if (gh.slice_prs !== undefined) {
759
+ if (typeof gh.slice_prs === "boolean")
760
+ validGh.slice_prs = gh.slice_prs;
761
+ else
762
+ errors.push("github.slice_prs must be a boolean");
763
+ }
764
+ const knownGhKeys = new Set(["enabled", "repo", "project", "labels", "auto_link_commits", "slice_prs"]);
765
+ for (const key of Object.keys(gh)) {
766
+ if (!knownGhKeys.has(key)) {
767
+ warnings.push(`unknown github key "${key}" — ignored`);
768
+ }
769
+ }
770
+ if (Object.keys(validGh).length > 0) {
771
+ validated.github = validGh;
772
+ }
773
+ }
774
+ else {
775
+ errors.push("github must be an object");
776
+ }
777
+ }
730
778
  return { preferences: validated, errors, warnings };
731
779
  }
@@ -14,7 +14,7 @@ import { homedir } from "node:os";
14
14
  import { join } from "node:path";
15
15
  import { gsdRoot } from "./paths.js";
16
16
  import { parse as parseYaml } from "yaml";
17
- import { normalizeStringArray } from "../shared/mod.js";
17
+ import { normalizeStringArray } from "../shared/format-utils.js";
18
18
  import { resolveProfileDefaults as _resolveProfileDefaults } from "./preferences-models.js";
19
19
  import { MODE_DEFAULTS, } from "./preferences-types.js";
20
20
  import { validatePreferences } from "./preferences-validation.js";
@@ -24,33 +24,42 @@ export { validatePreferences } from "./preferences-validation.js";
24
24
  // ─── Re-exports: skills ─────────────────────────────────────────────────────
25
25
  export { resolveAllSkillReferences, resolveSkillDiscoveryMode, resolveSkillStalenessDays, } from "./preferences-skills.js";
26
26
  // ─── Re-exports: models ─────────────────────────────────────────────────────
27
- export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel, isTransientNetworkError, validateModelId, updatePreferencesModels, resolveDynamicRoutingConfig, resolveAutoSupervisorConfig, resolveProfileDefaults, resolveEffectiveProfile, resolveInlineLevel, resolveCompressionStrategy, resolveContextSelection, resolveSearchProviderFromPreferences, } from "./preferences-models.js";
27
+ export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel, isTransientNetworkError, validateModelId, updatePreferencesModels, resolveDynamicRoutingConfig, resolveAutoSupervisorConfig, resolveProfileDefaults, resolveEffectiveProfile, resolveInlineLevel, resolveContextSelection, resolveSearchProviderFromPreferences, } from "./preferences-models.js";
28
28
  // ─── Path Constants & Getters ───────────────────────────────────────────────
29
- const GLOBAL_PREFERENCES_PATH = join(homedir(), ".gsd", "preferences.md");
30
- const LEGACY_GLOBAL_PREFERENCES_PATH = join(homedir(), ".pi", "agent", "gsd-preferences.md");
29
+ function gsdHome() {
30
+ return process.env.GSD_HOME || join(homedir(), ".gsd");
31
+ }
32
+ function globalPreferencesPath() {
33
+ return join(gsdHome(), "preferences.md");
34
+ }
35
+ function legacyGlobalPreferencesPath() {
36
+ return join(homedir(), ".pi", "agent", "gsd-preferences.md");
37
+ }
31
38
  function projectPreferencesPath() {
32
39
  return join(gsdRoot(process.cwd()), "preferences.md");
33
40
  }
34
41
  // Bootstrap in gitignore.ts historically created PREFERENCES.md (uppercase) by mistake.
35
42
  // Check uppercase as a fallback so those files aren't silently ignored.
36
- const GLOBAL_PREFERENCES_PATH_UPPERCASE = join(homedir(), ".gsd", "PREFERENCES.md");
43
+ function globalPreferencesPathUppercase() {
44
+ return join(gsdHome(), "PREFERENCES.md");
45
+ }
37
46
  function projectPreferencesPathUppercase() {
38
47
  return join(gsdRoot(process.cwd()), "PREFERENCES.md");
39
48
  }
40
49
  export function getGlobalGSDPreferencesPath() {
41
- return GLOBAL_PREFERENCES_PATH;
50
+ return globalPreferencesPath();
42
51
  }
43
52
  export function getLegacyGlobalGSDPreferencesPath() {
44
- return LEGACY_GLOBAL_PREFERENCES_PATH;
53
+ return legacyGlobalPreferencesPath();
45
54
  }
46
55
  export function getProjectGSDPreferencesPath() {
47
56
  return projectPreferencesPath();
48
57
  }
49
58
  // ─── Loading ────────────────────────────────────────────────────────────────
50
59
  export function loadGlobalGSDPreferences() {
51
- return loadPreferencesFile(GLOBAL_PREFERENCES_PATH, "global")
52
- ?? loadPreferencesFile(GLOBAL_PREFERENCES_PATH_UPPERCASE, "global")
53
- ?? loadPreferencesFile(LEGACY_GLOBAL_PREFERENCES_PATH, "global");
60
+ return loadPreferencesFile(globalPreferencesPath(), "global")
61
+ ?? loadPreferencesFile(globalPreferencesPathUppercase(), "global")
62
+ ?? loadPreferencesFile(legacyGlobalPreferencesPath(), "global");
54
63
  }
55
64
  export function loadProjectGSDPreferences() {
56
65
  return loadPreferencesFile(projectPreferencesPath(), "project")
@@ -199,10 +208,12 @@ function mergePreferences(base, override) {
199
208
  verification_auto_fix: override.verification_auto_fix ?? base.verification_auto_fix,
200
209
  verification_max_retries: override.verification_max_retries ?? base.verification_max_retries,
201
210
  search_provider: override.search_provider ?? base.search_provider,
202
- compression_strategy: override.compression_strategy ?? base.compression_strategy,
203
211
  context_selection: override.context_selection ?? base.context_selection,
204
212
  auto_visualize: override.auto_visualize ?? base.auto_visualize,
205
213
  auto_report: override.auto_report ?? base.auto_report,
214
+ github: (base.github || override.github)
215
+ ? { ...(base.github ?? {}), ...(override.github ?? {}) }
216
+ : undefined,
206
217
  };
207
218
  }
208
219
  function mergeStringLists(base, override) {
@@ -74,6 +74,10 @@ export function loadPrompt(name, vars = {}) {
74
74
  content = readFileSync(path, "utf-8");
75
75
  templateCache.set(name, content);
76
76
  }
77
+ const effectiveVars = {
78
+ skillActivation: "If a `GSD Skill Preferences` block is present in system context, use it and the `<available_skills>` catalog in your system prompt to decide which skills to load and follow for this unit, without relaxing required verification or artifact rules.",
79
+ ...vars,
80
+ };
77
81
  // Check BEFORE substitution: find all {{varName}} placeholders the template
78
82
  // declares and verify every one has a value in vars. Checking after substitution
79
83
  // would also flag {{...}} patterns injected by inlined content (e.g. template
@@ -82,14 +86,14 @@ export function loadPrompt(name, vars = {}) {
82
86
  if (declared) {
83
87
  const missing = [...new Set(declared)]
84
88
  .map(m => m.slice(2, -2))
85
- .filter(key => !(key in vars));
89
+ .filter(key => !(key in effectiveVars));
86
90
  if (missing.length > 0) {
87
91
  throw new GSDError(GSD_PARSE_ERROR, `loadPrompt("${name}"): template declares {{${missing.join("}}, {{")}}}} but no value was provided. ` +
88
92
  `This usually means the extension code in memory is older than the template on disk. ` +
89
93
  `Restart pi to reload the extension.`);
90
94
  }
91
95
  }
92
- for (const [key, value] of Object.entries(vars)) {
96
+ for (const [key, value] of Object.entries(effectiveVars)) {
93
97
  content = content.replaceAll(`{{${key}}}`, value);
94
98
  }
95
99
  return content.trim();
@@ -16,7 +16,7 @@ All relevant context has been preloaded below — the roadmap, all slice summari
16
16
 
17
17
  Then:
18
18
  1. Use the **Milestone Summary** output template from the inlined context above
19
- 2. If a `GSD Skill Preferences` block is present in system context, use it to decide which skills to load and follow during completion, without relaxing required verification or artifact rules
19
+ 2. {{skillActivation}}
20
20
  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.
21
21
  4. Verify the milestone's **definition of done** — all slices are `[x]`, all slice summaries exist, and any cross-slice integration points work correctly.
22
22
  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.
@@ -20,7 +20,7 @@ All relevant context has been preloaded below — the slice plan, all task summa
20
20
 
21
21
  Then:
22
22
  1. Use the **Slice Summary** and **UAT** output templates from the inlined context above
23
- 2. If a `GSD Skill Preferences` block is present in system context, use it to decide which skills to load and follow during completion, without relaxing required verification or artifact rules
23
+ 2. {{skillActivation}}
24
24
  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.
25
25
  4. If the slice plan includes observability/diagnostic surfaces, confirm they work. Skip this for simple slices that don't have observability sections.
26
26
  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.
@@ -11,7 +11,7 @@ After the user describes their idea, **do not ask questions yet**. First, prove
11
11
  1. Summarize what you understood in your own words — concretely, not abstractly.
12
12
  2. Give an honest size read: roughly how many milestones, roughly how many slices in the first one. Base this on the actual work involved, not a classification label. A config change might be 1 milestone with 1 slice. A social network might be 5 milestones with 8+ slices each. Use your judgment.
13
13
  3. Include scope honesty — a bullet list of the major capabilities you're hearing: "Here's what I'm hearing: [bullet list of major capabilities]."
14
- 4. Ask: "Does that capture it? If not, tell me what I missed." — plain text, not `ask_user_questions`. Let them correct freely.
14
+ 4. Invite correction in one plain sentence: "Here's my read. Correct anything important I missed." — plain text, not `ask_user_questions`.
15
15
 
16
16
  This prevents runaway questioning by forcing comprehension proof before anything else. Do not skip this step. Do not combine it with the first question round.
17
17
 
@@ -21,7 +21,7 @@ After reflection is confirmed, decide the approach based on the actual scope —
21
21
 
22
22
  **If the work spans multiple milestones:** Before drilling into details, map the full landscape:
23
23
  1. Propose a milestone sequence — names, one-line intents, rough dependencies
24
- 2. Present this to the user for confirmation or adjustment
24
+ 2. Present this as the working milestone sequence. Adjust it if the user objects, sharpens it, or adds constraints; otherwise keep moving.
25
25
  3. Only then begin the deep Q&A — and scope the Q&A to the full vision, not just M001
26
26
 
27
27
  **If the work fits in a single milestone:** Proceed directly to questioning.
@@ -48,7 +48,7 @@ You are a thinking partner, not an interviewer.
48
48
 
49
49
  **Challenge vagueness, make abstract concrete.** When the user says something abstract ("it should be smart" / "it needs to handle edge cases" / "good UX"), push for specifics. What does "smart" mean in practice? Which edge cases? What does good UX look like for this specific interaction?
50
50
 
51
- **Questions must be about the experience, not the implementation.** Never ask "what auth provider?" ask "when someone logs in, what should that feel like?" Never ask "what database?" ask "when they come back tomorrow, what should they see?" Implementation is your job. Understanding what they want to experience is the discussion's job.
51
+ **Lead with experience, but ask implementation when it materially matters.** Default questions should target the experience and outcome. But when implementation choices materially change scope, proof, compliance, integration, deployment, or irreversible architecture, ask them directly instead of forcing a fake UX phrasing.
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
 
@@ -105,16 +105,13 @@ Example flow:
105
105
 
106
106
  If they clarify, absorb the correction and re-verify.
107
107
 
108
- ## Wrap-up Gate
109
-
110
- Only after the depth checklist is fully satisfied and you genuinely understand the work, offer to proceed.
108
+ The depth verification is the required write-gate. Do **not** add another meta "ready to proceed?" checkpoint immediately after it unless there is still material ambiguity.
111
109
 
112
- The wrap-up gate must include a scope reflection:
113
- "Here's what I'm planning to build: [list of capabilities with rough complexity]. Does this match your vision, or did I miss something?"
110
+ ## Wrap-up Gate
114
111
 
115
- Then offer options: "Ready to confirm requirements and milestone plan (Recommended)", "I have more to discuss"
112
+ Once the depth checklist is fully satisfied, move directly into requirements and roadmap preview. Do not insert a separate "are you ready to continue?" gate unless the user explicitly wants to keep brainstorming or you still see material ambiguity.
116
113
 
117
- If the user wants to keep going, keep asking. If they're ready, proceed.
114
+ If you need a final scope reflection, fold it into the depth summary or roadmap preview rather than asking for permission twice.
118
115
 
119
116
  ## Focused Research
120
117
 
@@ -165,9 +162,9 @@ Rules:
165
162
 
166
163
  For multi-milestone projects, requirements should span the full vision. Requirements owned by later milestones get provisional ownership. The full requirement set captures the user's complete vision — milestones are the sequencing strategy, not the scope boundary.
167
164
 
168
- If the project is new or has no `REQUIREMENTS.md`, confirm candidate requirements with the user before writing the roadmap.
165
+ If the project is new or has no `REQUIREMENTS.md`, surface candidate requirements in chat before writing the roadmap. Ask for correction only on material omissions, wrong ownership, or wrong scope. If the user has already been specific and raises no substantive objection, treat the requirement set as confirmed and continue.
169
166
 
170
- **Print the requirements in chat before asking for confirmation.** Do not say "here are the requirements" and then only write them to a file. The user must see them in the terminal. Print a markdown table with columns: ID, Title, Status, Owner, Source. Group by status (Active, Deferred, Out of Scope). After the table, ask: "Confirm, adjust, or add?"
167
+ **Print the requirements in chat before writing the roadmap.** Do not say "here are the requirements" and then only write them to a file. The user must see them in the terminal. Print a markdown table with columns: ID, Title, Status, Owner, Source. Group by status (Active, Deferred, Out of Scope). After the table, ask: "Confirm, adjust, or add?"
171
168
 
172
169
  ## Scope Assessment
173
170
 
@@ -179,7 +176,7 @@ Before moving to output, confirm the size estimate from your reflection still ho
179
176
 
180
177
  Before writing any files, **print the planned roadmap in chat** so the user can see and approve it. Print a markdown table with columns: Slice, Title, Risk, Depends, Demo. One row per slice. Below the table, print the milestone definition of done as a bullet list.
181
178
 
182
- Ask: "Ready to write the plan, or want to adjust?" Only proceed to writing files after the user confirms.
179
+ If the user raises a substantive objection, adjust the roadmap. Otherwise, present the roadmap and ask: "Ready to write, or want to adjust?" one gate, not two.
183
180
 
184
181
  ### Naming Convention
185
182
 
@@ -236,7 +233,7 @@ If a milestone has no dependencies, omit the frontmatter. The dependency chain f
236
233
 
237
234
  #### Phase 3: Sequential readiness gate for remaining milestones
238
235
 
239
- For each remaining milestone **one at a time, in sequence**, use `ask_user_questions` to assess readiness. Present three options:
236
+ For each remaining milestone **one at a time, in sequence**, decide the most likely readiness mode from the evidence you already have, then use `ask_user_questions` to let the user correct that recommendation. Present three options:
240
237
 
241
238
  - **"Discuss now"** — The user wants to conduct a focused discussion for this milestone in the current session, while the context from the broader discussion is still fresh. Proceed with a focused discussion for this milestone (reflection → investigation → questioning → depth verification). When the discussion concludes, write a full `CONTEXT.md`. Then move to the gate for the next milestone.
242
239
  - **"Write draft for later"** — This milestone has seed material from the current conversation but needs its own dedicated discussion in a future session. Write a `CONTEXT-DRAFT.md` capturing the seed material (what was discussed, key ideas, provisional scope, open questions). Mark it clearly as a draft, not a finalized context. **What happens downstream:** When auto-mode reaches this milestone, it pauses and notifies the user: "M00x has draft context — needs discussion. Run /gsd." The `/gsd` wizard shows a "Discuss from draft" option that seeds the new discussion with this draft, so nothing from the current conversation is lost. After the dedicated discussion produces a full CONTEXT.md, the draft file is automatically deleted.