gsd-pi 2.23.0 → 2.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.js +12 -3
  3. package/dist/headless.d.ts +4 -0
  4. package/dist/headless.js +118 -10
  5. package/dist/help-text.js +22 -7
  6. package/dist/models-resolver.d.ts +0 -11
  7. package/dist/models-resolver.js +0 -15
  8. package/dist/resource-loader.d.ts +0 -1
  9. package/dist/resource-loader.js +64 -18
  10. package/dist/resources/GSD-WORKFLOW.md +12 -9
  11. package/dist/resources/extensions/bg-shell/overlay.ts +18 -17
  12. package/dist/resources/extensions/get-secrets-from-user.ts +5 -23
  13. package/dist/resources/extensions/gsd/activity-log.ts +5 -3
  14. package/dist/resources/extensions/gsd/auto-dispatch.ts +51 -2
  15. package/dist/resources/extensions/gsd/auto-prompts.ts +87 -0
  16. package/dist/resources/extensions/gsd/auto-recovery.ts +41 -2
  17. package/dist/resources/extensions/gsd/auto-worktree.ts +134 -4
  18. package/dist/resources/extensions/gsd/auto.ts +307 -77
  19. package/dist/resources/extensions/gsd/cache.ts +3 -1
  20. package/dist/resources/extensions/gsd/commands.ts +176 -10
  21. package/dist/resources/extensions/gsd/complexity.ts +1 -0
  22. package/dist/resources/extensions/gsd/dashboard-overlay.ts +38 -0
  23. package/dist/resources/extensions/gsd/doctor.ts +58 -11
  24. package/dist/resources/extensions/gsd/exit-command.ts +2 -2
  25. package/dist/resources/extensions/gsd/git-service.ts +74 -14
  26. package/dist/resources/extensions/gsd/gitignore.ts +1 -0
  27. package/dist/resources/extensions/gsd/gsd-db.ts +78 -1
  28. package/dist/resources/extensions/gsd/guided-flow.ts +109 -12
  29. package/dist/resources/extensions/gsd/index.ts +48 -2
  30. package/dist/resources/extensions/gsd/memory-extractor.ts +352 -0
  31. package/dist/resources/extensions/gsd/memory-store.ts +441 -0
  32. package/dist/resources/extensions/gsd/migrate/command.ts +2 -2
  33. package/dist/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  34. package/dist/resources/extensions/gsd/parallel-merge.ts +156 -0
  35. package/dist/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  36. package/dist/resources/extensions/gsd/preferences.ts +65 -1
  37. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  39. package/dist/resources/extensions/gsd/prompts/discuss.md +4 -4
  40. package/dist/resources/extensions/gsd/prompts/execute-task.md +1 -1
  41. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  42. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  43. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  44. package/dist/resources/extensions/gsd/prompts/queue.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  47. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
  48. package/dist/resources/extensions/gsd/provider-error-pause.ts +29 -2
  49. package/dist/resources/extensions/gsd/session-status-io.ts +197 -0
  50. package/dist/resources/extensions/gsd/state.ts +72 -30
  51. package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  52. package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  53. package/dist/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  54. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
  55. package/dist/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  56. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  57. package/dist/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  58. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  59. package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  60. package/dist/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  61. package/dist/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  62. package/dist/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  63. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  64. package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  65. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  66. package/dist/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  67. package/dist/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  68. package/dist/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  69. package/dist/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  70. package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  71. package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  72. package/dist/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  73. package/dist/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  74. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  75. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  76. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  77. package/dist/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  78. package/dist/resources/extensions/gsd/triage-ui.ts +1 -1
  79. package/dist/resources/extensions/gsd/types.ts +15 -1
  80. package/dist/resources/extensions/gsd/visualizer-data.ts +291 -10
  81. package/dist/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  82. package/dist/resources/extensions/gsd/visualizer-views.ts +462 -48
  83. package/dist/resources/extensions/gsd/worktree.ts +9 -2
  84. package/dist/resources/extensions/search-the-web/native-search.ts +15 -5
  85. package/dist/resources/extensions/subagent/index.ts +5 -0
  86. package/dist/resources/extensions/subagent/worker-registry.ts +99 -0
  87. package/dist/update-check.d.ts +9 -0
  88. package/dist/update-check.js +97 -0
  89. package/package.json +6 -1
  90. package/packages/pi-agent-core/dist/agent-loop.js +2 -0
  91. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  92. package/packages/pi-agent-core/src/agent-loop.ts +2 -0
  93. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  94. package/packages/pi-ai/dist/providers/anthropic.js +55 -7
  95. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  96. package/packages/pi-ai/dist/providers/azure-openai-responses.d.ts.map +1 -1
  97. package/packages/pi-ai/dist/providers/azure-openai-responses.js +12 -4
  98. package/packages/pi-ai/dist/providers/azure-openai-responses.js.map +1 -1
  99. package/packages/pi-ai/dist/providers/google-vertex.d.ts.map +1 -1
  100. package/packages/pi-ai/dist/providers/google-vertex.js +21 -9
  101. package/packages/pi-ai/dist/providers/google-vertex.js.map +1 -1
  102. package/packages/pi-ai/dist/providers/mistral.js +3 -0
  103. package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
  104. package/packages/pi-ai/dist/providers/openai-completions.d.ts.map +1 -1
  105. package/packages/pi-ai/dist/providers/openai-completions.js +12 -4
  106. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  107. package/packages/pi-ai/dist/providers/openai-responses.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/providers/openai-responses.js +12 -4
  109. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  110. package/packages/pi-ai/dist/types.d.ts +23 -1
  111. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  112. package/packages/pi-ai/dist/types.js.map +1 -1
  113. package/packages/pi-ai/src/providers/anthropic.ts +59 -9
  114. package/packages/pi-ai/src/providers/azure-openai-responses.ts +16 -4
  115. package/packages/pi-ai/src/providers/google-vertex.ts +32 -17
  116. package/packages/pi-ai/src/providers/mistral.ts +3 -0
  117. package/packages/pi-ai/src/providers/openai-completions.ts +16 -4
  118. package/packages/pi-ai/src/providers/openai-responses.ts +16 -4
  119. package/packages/pi-ai/src/types.ts +19 -1
  120. package/packages/pi-coding-agent/dist/core/agent-session.js +1 -1
  121. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  123. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +72 -0
  130. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  131. package/packages/pi-coding-agent/src/core/agent-session.ts +1 -1
  132. package/packages/pi-coding-agent/src/core/settings-manager.ts +2 -2
  133. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -0
  134. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +84 -0
  135. package/scripts/postinstall.js +7 -109
  136. package/src/resources/GSD-WORKFLOW.md +12 -9
  137. package/src/resources/extensions/bg-shell/overlay.ts +18 -17
  138. package/src/resources/extensions/get-secrets-from-user.ts +5 -23
  139. package/src/resources/extensions/gsd/activity-log.ts +5 -3
  140. package/src/resources/extensions/gsd/auto-dispatch.ts +51 -2
  141. package/src/resources/extensions/gsd/auto-prompts.ts +87 -0
  142. package/src/resources/extensions/gsd/auto-recovery.ts +41 -2
  143. package/src/resources/extensions/gsd/auto-worktree.ts +134 -4
  144. package/src/resources/extensions/gsd/auto.ts +307 -77
  145. package/src/resources/extensions/gsd/cache.ts +3 -1
  146. package/src/resources/extensions/gsd/commands.ts +176 -10
  147. package/src/resources/extensions/gsd/complexity.ts +1 -0
  148. package/src/resources/extensions/gsd/dashboard-overlay.ts +38 -0
  149. package/src/resources/extensions/gsd/doctor.ts +58 -11
  150. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  151. package/src/resources/extensions/gsd/git-service.ts +74 -14
  152. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  153. package/src/resources/extensions/gsd/gsd-db.ts +78 -1
  154. package/src/resources/extensions/gsd/guided-flow.ts +109 -12
  155. package/src/resources/extensions/gsd/index.ts +48 -2
  156. package/src/resources/extensions/gsd/memory-extractor.ts +352 -0
  157. package/src/resources/extensions/gsd/memory-store.ts +441 -0
  158. package/src/resources/extensions/gsd/migrate/command.ts +2 -2
  159. package/src/resources/extensions/gsd/parallel-eligibility.ts +233 -0
  160. package/src/resources/extensions/gsd/parallel-merge.ts +156 -0
  161. package/src/resources/extensions/gsd/parallel-orchestrator.ts +496 -0
  162. package/src/resources/extensions/gsd/preferences.ts +65 -1
  163. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  164. package/src/resources/extensions/gsd/prompts/discuss-headless.md +86 -0
  165. package/src/resources/extensions/gsd/prompts/discuss.md +4 -4
  166. package/src/resources/extensions/gsd/prompts/execute-task.md +1 -1
  167. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  168. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  169. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  170. package/src/resources/extensions/gsd/prompts/queue.md +1 -1
  171. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  172. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/validate-milestone.md +40 -61
  174. package/src/resources/extensions/gsd/provider-error-pause.ts +29 -2
  175. package/src/resources/extensions/gsd/session-status-io.ts +197 -0
  176. package/src/resources/extensions/gsd/state.ts +72 -30
  177. package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +81 -0
  178. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +20 -3
  179. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +1 -0
  180. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +256 -2
  181. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +34 -0
  182. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  183. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +8 -1
  184. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +9 -15
  185. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +9 -0
  186. package/src/resources/extensions/gsd/tests/derive-state-draft.test.ts +8 -0
  187. package/src/resources/extensions/gsd/tests/derive-state.test.ts +14 -0
  188. package/src/resources/extensions/gsd/tests/git-service.test.ts +70 -4
  189. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +2 -2
  190. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +8 -0
  191. package/src/resources/extensions/gsd/tests/md-importer.test.ts +2 -3
  192. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +180 -0
  193. package/src/resources/extensions/gsd/tests/memory-store.test.ts +345 -0
  194. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +5 -5
  195. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +656 -0
  196. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +354 -0
  197. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +1 -0
  198. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +1 -1
  199. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +316 -0
  200. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +147 -2
  201. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +88 -10
  202. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +314 -87
  203. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +148 -0
  204. package/src/resources/extensions/gsd/triage-ui.ts +1 -1
  205. package/src/resources/extensions/gsd/types.ts +15 -1
  206. package/src/resources/extensions/gsd/visualizer-data.ts +291 -10
  207. package/src/resources/extensions/gsd/visualizer-overlay.ts +237 -28
  208. package/src/resources/extensions/gsd/visualizer-views.ts +462 -48
  209. package/src/resources/extensions/gsd/worktree.ts +9 -2
  210. package/src/resources/extensions/search-the-web/native-search.ts +15 -5
  211. package/src/resources/extensions/subagent/index.ts +5 -0
  212. package/src/resources/extensions/subagent/worker-registry.ts +99 -0
package/README.md CHANGED
@@ -36,7 +36,8 @@ Full documentation is available in the [`docs/`](./docs/) directory:
36
36
  - **[Skills](./docs/skills.md)** — bundled skills, discovery, custom authoring
37
37
  - **[Commands Reference](./docs/commands.md)** — all commands and keyboard shortcuts
38
38
  - **[Architecture](./docs/architecture.md)** — system design and dispatch pipeline
39
- - **[Troubleshooting](./docs/troubleshooting.md)** — common issues, doctor, recovery
39
+ - **[Troubleshooting](./docs/troubleshooting.md)** — common issues, doctor, forensics, recovery
40
+ - **[VS Code Extension](./vscode-extension/README.md)** — chat participant, sidebar dashboard, RPC integration
40
41
  - **[Migration from v1](./docs/migration.md)** — `.planning` → `.gsd` migration
41
42
 
42
43
  ---
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import { loadStoredEnvKeys } from './wizard.js';
8
8
  import { getPiDefaultModelAndProvider, migratePiCredentials } from './pi-migration.js';
9
9
  import { shouldRunOnboarding, runOnboarding } from './onboarding.js';
10
10
  import chalk from 'chalk';
11
- import { checkForUpdates } from './update-check.js';
11
+ import { checkForUpdates, checkAndPromptForUpdates } from './update-check.js';
12
12
  import { printHelp, printSubcommandHelp } from './help-text.js';
13
13
  function exitIfManagedResourcesAreNewer(currentAgentDir) {
14
14
  const currentVersion = process.env.GSD_VERSION || '0.0.0';
@@ -169,9 +169,18 @@ if (!isPrintMode && shouldRunOnboarding(authStorage, settingsManager.getDefaultP
169
169
  process.stdin.setRawMode(false);
170
170
  process.stdin.pause();
171
171
  }
172
- // Non-blocking update check — runs at most once per 24h, fire-and-forget
172
+ // Update check — interactive prompt when stdin is a TTY, passive banner otherwise
173
173
  if (!isPrintMode) {
174
- checkForUpdates().catch(() => { });
174
+ if (process.stdin.isTTY) {
175
+ const updated = await checkAndPromptForUpdates().catch(() => false);
176
+ if (updated) {
177
+ // User chose to update — exit so they relaunch with the new version
178
+ process.exit(0);
179
+ }
180
+ }
181
+ else {
182
+ checkForUpdates().catch(() => { });
183
+ }
175
184
  }
176
185
  // Warn if terminal is too narrow for readable output
177
186
  if (!isPrintMode && process.stdout.columns && process.stdout.columns < 40) {
@@ -16,6 +16,10 @@ export interface HeadlessOptions {
16
16
  model?: string;
17
17
  command: string;
18
18
  commandArgs: string[];
19
+ context?: string;
20
+ contextText?: string;
21
+ auto?: boolean;
22
+ verbose?: boolean;
19
23
  }
20
24
  export declare function parseHeadlessArgs(argv: string[]): HeadlessOptions;
21
25
  export declare function runHeadless(options: HeadlessOptions): Promise<void>;
package/dist/headless.js CHANGED
@@ -10,8 +10,8 @@
10
10
  * 1 — error or timeout
11
11
  * 2 — blocked (command reported a blocker)
12
12
  */
13
- import { existsSync } from 'node:fs';
14
- import { join } from 'node:path';
13
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs';
14
+ import { join, resolve } from 'node:path';
15
15
  // RpcClient is not in @gsd/pi-coding-agent's public exports — import from dist directly.
16
16
  // This relative path resolves correctly from both src/ (via tsx) and dist/ (compiled).
17
17
  import { RpcClient } from '../packages/pi-coding-agent/dist/modes/rpc/rpc-client.js';
@@ -46,6 +46,18 @@ export function parseHeadlessArgs(argv) {
46
46
  // --model can also be passed from the main CLI; headless-specific takes precedence
47
47
  options.model = args[++i];
48
48
  }
49
+ else if (arg === '--context' && i + 1 < args.length) {
50
+ options.context = args[++i];
51
+ }
52
+ else if (arg === '--context-text' && i + 1 < args.length) {
53
+ options.contextText = args[++i];
54
+ }
55
+ else if (arg === '--auto') {
56
+ options.auto = true;
57
+ }
58
+ else if (arg === '--verbose') {
59
+ options.verbose = true;
60
+ }
49
61
  }
50
62
  else if (!positionalStarted) {
51
63
  positionalStarted = true;
@@ -99,18 +111,23 @@ function handleExtensionUIRequest(event, writeToStdin) {
99
111
  // ---------------------------------------------------------------------------
100
112
  // Progress Formatter
101
113
  // ---------------------------------------------------------------------------
102
- function formatProgress(event) {
114
+ function formatProgress(event, verbose) {
103
115
  const type = String(event.type ?? '');
104
116
  switch (type) {
105
117
  case 'tool_execution_start':
106
- return `[tool] ${event.toolName ?? 'unknown'}`;
118
+ if (verbose)
119
+ return ` [tool] ${event.toolName ?? 'unknown'}`;
120
+ return null;
107
121
  case 'agent_start':
108
- return '[agent] Session started';
122
+ return '[agent] Session started';
109
123
  case 'agent_end':
110
- return '[agent] Session ended';
124
+ return '[agent] Session ended';
111
125
  case 'extension_ui_request':
112
126
  if (event.method === 'notify') {
113
- return `[gsd] ${event.message ?? ''}`;
127
+ return `[gsd] ${event.message ?? ''}`;
128
+ }
129
+ if (event.method === 'setStatus') {
130
+ return `[status] ${event.message ?? ''}`;
114
131
  }
115
132
  return null;
116
133
  default:
@@ -133,6 +150,11 @@ function isBlockedNotification(event) {
133
150
  return false;
134
151
  return String(event.message ?? '').toLowerCase().includes('blocked');
135
152
  }
153
+ function isMilestoneReadyNotification(event) {
154
+ if (event.type !== 'extension_ui_request' || event.method !== 'notify')
155
+ return false;
156
+ return /milestone\s+m\d+.*ready/i.test(String(event.message ?? ''));
157
+ }
136
158
  // ---------------------------------------------------------------------------
137
159
  // Quick Command Detection
138
160
  // ---------------------------------------------------------------------------
@@ -148,11 +170,69 @@ function isQuickCommand(command) {
148
170
  // ---------------------------------------------------------------------------
149
171
  // Main Orchestrator
150
172
  // ---------------------------------------------------------------------------
173
+ // ---------------------------------------------------------------------------
174
+ // Context Loading (new-milestone)
175
+ // ---------------------------------------------------------------------------
176
+ async function readStdin() {
177
+ const chunks = [];
178
+ for await (const chunk of process.stdin) {
179
+ chunks.push(chunk);
180
+ }
181
+ return Buffer.concat(chunks).toString('utf-8');
182
+ }
183
+ async function loadContext(options) {
184
+ if (options.contextText)
185
+ return options.contextText;
186
+ if (options.context === '-') {
187
+ return readStdin();
188
+ }
189
+ if (options.context) {
190
+ return readFileSync(resolve(options.context), 'utf-8');
191
+ }
192
+ throw new Error('No context provided. Use --context <file> or --context-text <text>');
193
+ }
194
+ /**
195
+ * Bootstrap .gsd/ directory structure for headless new-milestone.
196
+ * Mirrors the bootstrap logic from guided-flow.ts showSmartEntry().
197
+ */
198
+ function bootstrapGsdProject(basePath) {
199
+ const gsdDir = join(basePath, '.gsd');
200
+ mkdirSync(join(gsdDir, 'milestones'), { recursive: true });
201
+ mkdirSync(join(gsdDir, 'runtime'), { recursive: true });
202
+ }
151
203
  export async function runHeadless(options) {
152
204
  const startTime = Date.now();
153
- // Validate .gsd/ directory
205
+ const isNewMilestone = options.command === 'new-milestone';
206
+ // For new-milestone, load context and bootstrap .gsd/ before spawning RPC child
207
+ if (isNewMilestone) {
208
+ if (!options.context && !options.contextText) {
209
+ process.stderr.write('[headless] Error: new-milestone requires --context <file> or --context-text <text>\n');
210
+ process.exit(1);
211
+ }
212
+ let contextContent;
213
+ try {
214
+ contextContent = await loadContext(options);
215
+ }
216
+ catch (err) {
217
+ process.stderr.write(`[headless] Error loading context: ${err instanceof Error ? err.message : String(err)}\n`);
218
+ process.exit(1);
219
+ }
220
+ // Bootstrap .gsd/ if needed
221
+ const gsdDir = join(process.cwd(), '.gsd');
222
+ if (!existsSync(gsdDir)) {
223
+ if (!options.json) {
224
+ process.stderr.write('[headless] Bootstrapping .gsd/ project structure...\n');
225
+ }
226
+ bootstrapGsdProject(process.cwd());
227
+ }
228
+ // Write context to temp file for the RPC child to read
229
+ const runtimeDir = join(gsdDir, 'runtime');
230
+ mkdirSync(runtimeDir, { recursive: true });
231
+ writeFileSync(join(runtimeDir, 'headless-context.md'), contextContent, 'utf-8');
232
+ }
233
+ // Validate .gsd/ directory (skip for new-milestone since we just bootstrapped it)
154
234
  const gsdDir = join(process.cwd(), '.gsd');
155
- if (!existsSync(gsdDir)) {
235
+ if (!isNewMilestone && !existsSync(gsdDir)) {
156
236
  process.stderr.write('[headless] Error: No .gsd/ directory found in current directory.\n');
157
237
  process.stderr.write("[headless] Run 'gsd' interactively first to initialize a project.\n");
158
238
  process.exit(1);
@@ -178,6 +258,7 @@ export async function runHeadless(options) {
178
258
  let blocked = false;
179
259
  let completed = false;
180
260
  let exitCode = 0;
261
+ let milestoneReady = false; // tracks "Milestone X ready." for auto-chaining
181
262
  const recentEvents = [];
182
263
  function trackEvent(event) {
183
264
  totalEvents++;
@@ -231,7 +312,7 @@ export async function runHeadless(options) {
231
312
  }
232
313
  else {
233
314
  // Progress output to stderr
234
- const line = formatProgress(eventObj);
315
+ const line = formatProgress(eventObj, !!options.verbose);
235
316
  if (line)
236
317
  process.stderr.write(line + '\n');
237
318
  }
@@ -241,6 +322,10 @@ export async function runHeadless(options) {
241
322
  if (isBlockedNotification(eventObj)) {
242
323
  blocked = true;
243
324
  }
325
+ // Detect "Milestone X ready." for auto-mode chaining
326
+ if (isMilestoneReadyNotification(eventObj)) {
327
+ milestoneReady = true;
328
+ }
244
329
  if (isTerminalNotification(eventObj)) {
245
330
  completed = true;
246
331
  }
@@ -319,6 +404,29 @@ export async function runHeadless(options) {
319
404
  if (exitCode === 0 || exitCode === 2) {
320
405
  await completionPromise;
321
406
  }
407
+ // Auto-mode chaining: if --auto and milestone creation succeeded, send /gsd auto
408
+ if (isNewMilestone && options.auto && milestoneReady && !blocked && exitCode === 0) {
409
+ if (!options.json) {
410
+ process.stderr.write('[headless] Milestone ready — chaining into auto-mode...\n');
411
+ }
412
+ // Reset completion state for the auto-mode phase
413
+ completed = false;
414
+ milestoneReady = false;
415
+ blocked = false;
416
+ const autoCompletionPromise = new Promise((resolve) => {
417
+ resolveCompletion = resolve;
418
+ });
419
+ try {
420
+ await client.prompt('/gsd auto');
421
+ }
422
+ catch (err) {
423
+ process.stderr.write(`[headless] Error: Failed to start auto-mode: ${err instanceof Error ? err.message : String(err)}\n`);
424
+ exitCode = 1;
425
+ }
426
+ if (exitCode === 0 || exitCode === 2) {
427
+ await autoCompletionPromise;
428
+ }
429
+ }
322
430
  // Cleanup
323
431
  clearTimeout(timeoutTimer);
324
432
  if (idleTimer)
package/dist/help-text.js CHANGED
@@ -35,15 +35,30 @@ const SUBCOMMAND_HELP = {
35
35
  'Run /gsd commands without the TUI. Default command: auto',
36
36
  '',
37
37
  'Flags:',
38
- ' --timeout N Overall timeout in ms (default: 300000)',
39
- ' --json JSONL event stream to stdout',
40
- ' --model ID Override model',
38
+ ' --timeout N Overall timeout in ms (default: 300000)',
39
+ ' --json JSONL event stream to stdout',
40
+ ' --model ID Override model',
41
+ '',
42
+ 'Commands:',
43
+ ' auto Run all queued units continuously (default)',
44
+ ' next Run one unit',
45
+ ' status Show progress dashboard',
46
+ ' new-milestone Create a milestone from a specification document',
47
+ '',
48
+ 'new-milestone flags:',
49
+ ' --context <path> Path to spec/PRD file (use \'-\' for stdin)',
50
+ ' --context-text <txt> Inline specification text',
51
+ ' --auto Start auto-mode after milestone creation',
52
+ ' --verbose Show tool calls in progress output',
41
53
  '',
42
54
  'Examples:',
43
- ' gsd headless Run /gsd auto',
44
- ' gsd headless next Run one unit',
45
- ' gsd headless --json status Machine-readable status',
46
- ' gsd headless --timeout 60000 With 1-minute timeout',
55
+ ' gsd headless Run /gsd auto',
56
+ ' gsd headless next Run one unit',
57
+ ' gsd headless --json status Machine-readable status',
58
+ ' gsd headless --timeout 60000 With 1-minute timeout',
59
+ ' gsd headless new-milestone --context spec.md Create milestone from file',
60
+ ' cat spec.md | gsd headless new-milestone --context - From stdin',
61
+ ' gsd headless new-milestone --context spec.md --auto Create + auto-execute',
47
62
  '',
48
63
  'Exit codes: 0 = complete, 1 = error/timeout, 2 = blocked',
49
64
  ].join('\n'),
@@ -19,14 +19,3 @@
19
19
  * @returns The path to use for models.json
20
20
  */
21
21
  export declare function resolveModelsJsonPath(): string;
22
- /**
23
- * Check if both GSD and PI models.json files exist.
24
- */
25
- export declare function hasBothModelsFiles(): boolean;
26
- /**
27
- * Get the paths to both models.json files.
28
- */
29
- export declare function getModelsPaths(): {
30
- gsd: string;
31
- pi: string;
32
- };
@@ -33,18 +33,3 @@ export function resolveModelsJsonPath() {
33
33
  }
34
34
  return GSD_MODELS_PATH;
35
35
  }
36
- /**
37
- * Check if both GSD and PI models.json files exist.
38
- */
39
- export function hasBothModelsFiles() {
40
- return existsSync(GSD_MODELS_PATH) && existsSync(PI_MODELS_PATH);
41
- }
42
- /**
43
- * Get the paths to both models.json files.
44
- */
45
- export function getModelsPaths() {
46
- return {
47
- gsd: GSD_MODELS_PATH,
48
- pi: PI_MODELS_PATH,
49
- };
50
- }
@@ -1,7 +1,6 @@
1
1
  import { DefaultResourceLoader } from '@gsd/pi-coding-agent';
2
2
  export declare function discoverExtensionEntryPaths(extensionsDir: string): string[];
3
3
  export declare function readManagedResourceVersion(agentDir: string): string | null;
4
- export declare function readManagedResourceSyncedAt(agentDir: string): number | null;
5
4
  export declare function getNewerManagedResourceVersion(agentDir: string, currentVersion: string): string | null;
6
5
  /**
7
6
  * Syncs all bundled resources to agentDir (~/.gsd/agent/) on every launch.
@@ -1,6 +1,6 @@
1
1
  import { DefaultResourceLoader } from '@gsd/pi-coding-agent';
2
2
  import { homedir } from 'node:os';
3
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
3
+ import { chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
4
4
  import { dirname, join, relative, resolve } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { compareSemver } from './update-check.js';
@@ -97,15 +97,6 @@ export function readManagedResourceVersion(agentDir) {
97
97
  return null;
98
98
  }
99
99
  }
100
- export function readManagedResourceSyncedAt(agentDir) {
101
- try {
102
- const manifest = JSON.parse(readFileSync(getManagedResourceManifestPath(agentDir), 'utf-8'));
103
- return typeof manifest?.syncedAt === 'number' ? manifest.syncedAt : null;
104
- }
105
- catch {
106
- return null;
107
- }
108
- }
109
100
  export function getNewerManagedResourceVersion(agentDir, currentVersion) {
110
101
  const managedVersion = readManagedResourceVersion(agentDir);
111
102
  if (!managedVersion) {
@@ -113,6 +104,38 @@ export function getNewerManagedResourceVersion(agentDir, currentVersion) {
113
104
  }
114
105
  return compareSemver(managedVersion, currentVersion) > 0 ? managedVersion : null;
115
106
  }
107
+ /**
108
+ * Recursively makes all files and directories under dirPath owner-writable.
109
+ *
110
+ * Files copied from the Nix store inherit read-only modes (0444/0555).
111
+ * Calling this before cpSync prevents overwrite failures on subsequent upgrades,
112
+ * and calling it after ensures the next run can overwrite the copies too.
113
+ *
114
+ * Preserves existing permission bits (including executability) and only adds
115
+ * owner-write (and for directories, owner-exec) without widening group/other
116
+ * permissions.
117
+ */
118
+ function makeTreeWritable(dirPath) {
119
+ if (!existsSync(dirPath))
120
+ return;
121
+ const stats = statSync(dirPath);
122
+ const isDir = stats.isDirectory();
123
+ const currentMode = stats.mode & 0o777;
124
+ // Ensure owner-write; for directories also ensure owner-exec so they remain traversable.
125
+ let newMode = currentMode | 0o200;
126
+ if (isDir) {
127
+ newMode |= 0o100;
128
+ }
129
+ if (newMode !== currentMode) {
130
+ chmodSync(dirPath, newMode);
131
+ }
132
+ if (isDir) {
133
+ for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
134
+ const entryPath = join(dirPath, entry.name);
135
+ makeTreeWritable(entryPath);
136
+ }
137
+ }
138
+ }
116
139
  /**
117
140
  * Syncs all bundled resources to agentDir (~/.gsd/agent/) on every launch.
118
141
  *
@@ -130,26 +153,49 @@ export function getNewerManagedResourceVersion(agentDir, currentVersion) {
130
153
  */
131
154
  export function initResources(agentDir) {
132
155
  mkdirSync(agentDir, { recursive: true });
133
- // Skip resource sync when versions match saves ~128ms of cpSync per launch
134
- const currentVersion = getBundledGsdVersion();
135
- const managedVersion = readManagedResourceVersion(agentDir);
136
- if (managedVersion && managedVersion === currentVersion) {
137
- return;
138
- }
139
- // Sync extensions — overwrite so updates land on next launch
156
+ // Sync extensions clean bundled subdirs first to remove stale leftover files,
157
+ // then overwrite so updates land on next launch. Only bundled subdirs are removed;
158
+ // user-created extension directories are preserved.
140
159
  const destExtensions = join(agentDir, 'extensions');
160
+ makeTreeWritable(destExtensions);
161
+ for (const entry of readdirSync(bundledExtensionsDir, { withFileTypes: true })) {
162
+ if (entry.isDirectory()) {
163
+ const target = join(destExtensions, entry.name);
164
+ if (existsSync(target))
165
+ rmSync(target, { recursive: true, force: true });
166
+ }
167
+ }
141
168
  cpSync(bundledExtensionsDir, destExtensions, { recursive: true, force: true });
169
+ makeTreeWritable(destExtensions);
142
170
  // Sync agents
143
171
  const destAgents = join(agentDir, 'agents');
144
172
  const srcAgents = join(resourcesDir, 'agents');
145
173
  if (existsSync(srcAgents)) {
174
+ makeTreeWritable(destAgents);
175
+ for (const entry of readdirSync(srcAgents, { withFileTypes: true })) {
176
+ if (entry.isDirectory()) {
177
+ const target = join(destAgents, entry.name);
178
+ if (existsSync(target))
179
+ rmSync(target, { recursive: true, force: true });
180
+ }
181
+ }
146
182
  cpSync(srcAgents, destAgents, { recursive: true, force: true });
183
+ makeTreeWritable(destAgents);
147
184
  }
148
- // Sync skills — overwrite so updates land on next launch
185
+ // Sync skills
149
186
  const destSkills = join(agentDir, 'skills');
150
187
  const srcSkills = join(resourcesDir, 'skills');
151
188
  if (existsSync(srcSkills)) {
189
+ makeTreeWritable(destSkills);
190
+ for (const entry of readdirSync(srcSkills, { withFileTypes: true })) {
191
+ if (entry.isDirectory()) {
192
+ const target = join(destSkills, entry.name);
193
+ if (existsSync(target))
194
+ rmSync(target, { recursive: true, force: true });
195
+ }
196
+ }
152
197
  cpSync(srcSkills, destSkills, { recursive: true, force: true });
198
+ makeTreeWritable(destSkills);
153
199
  }
154
200
  writeManagedResourceManifest(agentDir);
155
201
  }
@@ -565,25 +565,28 @@ One commit per slice. Individually revertable. Reads like a changelog.
565
565
 
566
566
  ```
567
567
  gsd/M001/S01:
568
- test(S01): round-trip tests passing
568
+ test(S01/T03): round-trip tests passing
569
569
  feat(S01/T03): file writer with round-trip fidelity
570
- chore(S01/T03): auto-commit after task
571
570
  feat(S01/T02): markdown parser for plan files
572
- chore(S01/T02): auto-commit after task
573
571
  feat(S01/T01): core types and interfaces
574
- chore(S01/T01): auto-commit after task
572
+ docs(S01): add slice plan
575
573
  ```
576
574
 
577
575
  ### Commit Conventions
578
576
 
579
577
  | When | Format | Example |
580
578
  |------|--------|---------|
581
- | Auto-commit (dirty state) | `chore(S01/T02): auto-commit after task` | Automatic save of work in progress |
582
- | After task verified | `feat(S01/T02): <what was built>` | The real work |
583
- | Plan/docs committed | `docs(S01): add slice plan` | Bundled with first task |
584
- | Slice squash to main | `type(M001/S01): <slice title>` | Type inferred from title (`feat`, `fix`, `docs`, etc.) |
579
+ | Task completed | `{type}(S01/T02): <one-liner from summary>` | Type inferred from title (`feat`, `fix`, `test`, etc.) |
580
+ | Plan/docs committed | `docs(S01): add slice plan` | Planning artifacts |
581
+ | Slice squash to main | `type(M001/S01): <slice title>` | Type inferred from title |
582
+ | State rebuild | `chore(S01/T02): auto-commit after state-rebuild` | Bookkeeping only |
585
583
 
586
- Commit types: `feat`, `fix`, `test`, `refactor`, `docs`, `chore`
584
+ The system reads the task summary after execution and builds a meaningful commit message:
585
+ - **Subject**: `{type}({sliceId}/{taskId}): {one-liner}` — the one-liner from the summary frontmatter
586
+ - **Type**: Inferred from the task title and one-liner (`feat`, `fix`, `test`, `refactor`, `docs`, `perf`, `chore`)
587
+ - **Body**: Key files from the summary frontmatter (up to 8 files listed)
588
+
589
+ Commit types: `feat`, `fix`, `test`, `refactor`, `docs`, `perf`, `chore`
587
590
 
588
591
  ### Squash Merge Message
589
592
 
@@ -328,12 +328,9 @@ export class BgManagerOverlay {
328
328
  return this.box(inner, width);
329
329
  }
330
330
 
331
- private renderOutput(width: number): string[] {
331
+ private processStatusHeader(p: typeof this.viewingProcess, activeTab: "output" | "events"): { statusIcon: string; headerLine: string } {
332
332
  const th = this.theme;
333
- const p = this.viewingProcess;
334
- if (!p) return [""];
335
- const inner: string[] = [];
336
-
333
+ if (!p) return { statusIcon: "", headerLine: "" };
337
334
  const statusIcon = p.alive
338
335
  ? (p.status === "ready" ? th.fg("success", "●")
339
336
  : p.status === "error" ? th.fg("error", "●")
@@ -343,9 +340,21 @@ export class BgManagerOverlay {
343
340
  const uptime = th.fg("dim", formatUptime(Date.now() - p.startedAt));
344
341
  const typeTag = th.fg("dim", `[${p.processType}]`);
345
342
  const portInfo = p.ports.length > 0 ? th.fg("dim", ` :${p.ports.join(",")}`) : "";
346
- const tabIndicator = th.fg("accent", "[Output]") + " " + th.fg("dim", "Events");
343
+ const tabIndicator = activeTab === "output"
344
+ ? th.fg("accent", "[Output]") + " " + th.fg("dim", "Events")
345
+ : th.fg("dim", "Output") + " " + th.fg("accent", "[Events]");
346
+ const headerLine = `${statusIcon} ${name} ${typeTag} ${uptime}${portInfo} ${tabIndicator}`;
347
+ return { statusIcon, headerLine };
348
+ }
349
+
350
+ private renderOutput(width: number): string[] {
351
+ const th = this.theme;
352
+ const p = this.viewingProcess;
353
+ if (!p) return [""];
354
+ const inner: string[] = [];
347
355
 
348
- inner.push(`${statusIcon} ${name} ${typeTag} ${uptime}${portInfo} ${tabIndicator}`);
356
+ const { headerLine } = this.processStatusHeader(p, "output");
357
+ inner.push(headerLine);
349
358
  inner.push("");
350
359
 
351
360
  // Unified buffer is already chronologically interleaved
@@ -384,16 +393,8 @@ export class BgManagerOverlay {
384
393
  if (!p) return [""];
385
394
  const inner: string[] = [];
386
395
 
387
- const statusIcon = p.alive
388
- ? (p.status === "ready" ? th.fg("success", "●")
389
- : p.status === "error" ? th.fg("error", "●")
390
- : th.fg("warning", "●"))
391
- : th.fg("dim", "○");
392
- const name = th.fg("muted", p.label);
393
- const uptime = th.fg("dim", formatUptime(Date.now() - p.startedAt));
394
- const tabIndicator = th.fg("dim", "Output") + " " + th.fg("accent", "[Events]");
395
-
396
- inner.push(`${statusIcon} ${name} ${uptime} ${tabIndicator}`);
396
+ const { headerLine } = this.processStatusHeader(p, "events");
397
+ inner.push(headerLine);
397
398
  inner.push("");
398
399
 
399
400
  if (p.events.length === 0) {
@@ -369,32 +369,14 @@ async function applySecrets(
369
369
  }
370
370
  }
371
371
 
372
- if (destination === "vercel" && opts.exec) {
372
+ if ((destination === "vercel" || destination === "convex") && opts.exec) {
373
373
  const env = opts.environment ?? "development";
374
374
  for (const { key, value } of provided) {
375
+ const cmd = destination === "vercel"
376
+ ? `printf %s ${shellEscapeSingle(value)} | vercel env add ${key} ${env}`
377
+ : `npx convex env set ${key} ${shellEscapeSingle(value)}`;
375
378
  try {
376
- const result = await opts.exec("sh", [
377
- "-c",
378
- `printf %s ${shellEscapeSingle(value)} | vercel env add ${key} ${env}`,
379
- ]);
380
- if (result.code !== 0) {
381
- errors.push(`${key}: ${result.stderr.slice(0, 200)}`);
382
- } else {
383
- applied.push(key);
384
- }
385
- } catch (err: any) {
386
- errors.push(`${key}: ${err.message}`);
387
- }
388
- }
389
- }
390
-
391
- if (destination === "convex" && opts.exec) {
392
- for (const { key, value } of provided) {
393
- try {
394
- const result = await opts.exec("sh", [
395
- "-c",
396
- `npx convex env set ${key} ${shellEscapeSingle(value)}`,
397
- ]);
379
+ const result = await opts.exec("sh", ["-c", cmd]);
398
380
  if (result.code !== 0) {
399
381
  errors.push(`${key}: ${result.stderr.slice(0, 200)}`);
400
382
  } else {
@@ -103,10 +103,10 @@ export function saveActivityLog(
103
103
  basePath: string,
104
104
  unitType: string,
105
105
  unitId: string,
106
- ): void {
106
+ ): string | null {
107
107
  try {
108
108
  const entries = ctx.sessionManager.getEntries();
109
- if (!entries || entries.length === 0) return;
109
+ if (!entries || entries.length === 0) return null;
110
110
 
111
111
  const activityDir = join(gsdRoot(basePath), "activity");
112
112
  mkdirSync(activityDir, { recursive: true });
@@ -116,7 +116,7 @@ export function saveActivityLog(
116
116
  const unitKey = `${unitType}\0${safeUnitId}`;
117
117
  // Use lightweight fingerprint instead of serializing all entries (#611)
118
118
  const key = snapshotKey(unitType, safeUnitId, entries);
119
- if (state.lastSnapshotKeyByUnit.get(unitKey) === key) return;
119
+ if (state.lastSnapshotKeyByUnit.get(unitKey) === key) return null;
120
120
 
121
121
  const filePath = nextActivityFilePath(activityDir, state, unitType, safeUnitId);
122
122
  // Stream entries to disk line-by-line instead of building one massive string (#611).
@@ -131,9 +131,11 @@ export function saveActivityLog(
131
131
  }
132
132
  state.nextSeq += 1;
133
133
  state.lastSnapshotKeyByUnit.set(unitKey, key);
134
+ return filePath;
134
135
  } catch (e) {
135
136
  // Don't let logging failures break auto-mode
136
137
  void e;
138
+ return null;
137
139
  }
138
140
  }
139
141