gsd-pi 2.35.0 → 2.36.0-dev.d612764

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 (194) hide show
  1. package/README.md +3 -1
  2. package/dist/cli.js +7 -2
  3. package/dist/resource-loader.d.ts +1 -1
  4. package/dist/resource-loader.js +13 -1
  5. package/dist/resources/extensions/async-jobs/await-tool.js +0 -2
  6. package/dist/resources/extensions/async-jobs/job-manager.js +0 -6
  7. package/dist/resources/extensions/bg-shell/output-formatter.js +1 -19
  8. package/dist/resources/extensions/bg-shell/process-manager.js +0 -4
  9. package/dist/resources/extensions/bg-shell/types.js +0 -2
  10. package/dist/resources/extensions/cmux/index.js +321 -0
  11. package/dist/resources/extensions/context7/index.js +5 -0
  12. package/dist/resources/extensions/get-secrets-from-user.js +2 -30
  13. package/dist/resources/extensions/google-search/index.js +5 -0
  14. package/dist/resources/extensions/gsd/auto-dashboard.js +334 -104
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +43 -1
  16. package/dist/resources/extensions/gsd/auto-loop.js +28 -3
  17. package/dist/resources/extensions/gsd/auto-model-selection.js +15 -3
  18. package/dist/resources/extensions/gsd/auto-recovery.js +35 -0
  19. package/dist/resources/extensions/gsd/auto-start.js +35 -2
  20. package/dist/resources/extensions/gsd/auto.js +75 -4
  21. package/dist/resources/extensions/gsd/commands-cmux.js +120 -0
  22. package/dist/resources/extensions/gsd/commands-handlers.js +2 -2
  23. package/dist/resources/extensions/gsd/commands-inspect.js +10 -3
  24. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  25. package/dist/resources/extensions/gsd/commands-rate.js +31 -0
  26. package/dist/resources/extensions/gsd/commands.js +94 -2
  27. package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -0
  28. package/dist/resources/extensions/gsd/doctor-environment.js +26 -17
  29. package/dist/resources/extensions/gsd/files.js +11 -2
  30. package/dist/resources/extensions/gsd/gitignore.js +54 -7
  31. package/dist/resources/extensions/gsd/guided-flow.js +8 -2
  32. package/dist/resources/extensions/gsd/health-widget-core.js +96 -0
  33. package/dist/resources/extensions/gsd/health-widget.js +97 -46
  34. package/dist/resources/extensions/gsd/index.js +31 -33
  35. package/dist/resources/extensions/gsd/migrate-external.js +55 -2
  36. package/dist/resources/extensions/gsd/milestone-ids.js +3 -2
  37. package/dist/resources/extensions/gsd/notifications.js +10 -1
  38. package/dist/resources/extensions/gsd/paths.js +74 -7
  39. package/dist/resources/extensions/gsd/post-unit-hooks.js +4 -1
  40. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  41. package/dist/resources/extensions/gsd/preferences-validation.js +45 -1
  42. package/dist/resources/extensions/gsd/preferences.js +15 -0
  43. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  44. package/dist/resources/extensions/gsd/prompts/research-milestone.md +4 -3
  45. package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -2
  46. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  47. package/dist/resources/extensions/gsd/roadmap-mutations.js +55 -0
  48. package/dist/resources/extensions/gsd/session-lock.js +53 -2
  49. package/dist/resources/extensions/gsd/state.js +2 -1
  50. package/dist/resources/extensions/gsd/templates/plan.md +8 -0
  51. package/dist/resources/extensions/gsd/templates/preferences.md +6 -0
  52. package/dist/resources/extensions/gsd/worktree-resolver.js +12 -0
  53. package/dist/resources/extensions/remote-questions/remote-command.js +2 -22
  54. package/dist/resources/extensions/search-the-web/native-search.js +45 -4
  55. package/dist/resources/extensions/shared/mod.js +1 -1
  56. package/dist/resources/extensions/shared/sanitize.js +30 -0
  57. package/dist/resources/extensions/shared/terminal.js +5 -0
  58. package/dist/resources/extensions/subagent/index.js +186 -74
  59. package/dist/resources/skills/core-web-vitals/SKILL.md +1 -1
  60. package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
  61. package/dist/resources/skills/github-workflows/SKILL.md +0 -2
  62. package/dist/resources/skills/web-quality-audit/SKILL.md +0 -2
  63. package/package.json +2 -1
  64. package/packages/pi-agent-core/dist/agent.d.ts +10 -2
  65. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  66. package/packages/pi-agent-core/dist/agent.js +19 -8
  67. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  68. package/packages/pi-agent-core/src/agent.ts +31 -10
  69. package/packages/pi-ai/dist/providers/openai-responses.js +1 -1
  70. package/packages/pi-ai/dist/providers/openai-responses.js.map +1 -1
  71. package/packages/pi-ai/src/providers/openai-responses.ts +1 -1
  72. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  73. package/packages/pi-coding-agent/dist/core/agent-session.js +20 -4
  74. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  75. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/resource-loader.js +13 -2
  77. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  78. package/packages/pi-coding-agent/package.json +1 -1
  79. package/packages/pi-coding-agent/src/core/agent-session.ts +36 -12
  80. package/packages/pi-coding-agent/src/core/resource-loader.ts +13 -2
  81. package/packages/pi-tui/dist/terminal-image.d.ts.map +1 -1
  82. package/packages/pi-tui/dist/terminal-image.js +4 -0
  83. package/packages/pi-tui/dist/terminal-image.js.map +1 -1
  84. package/packages/pi-tui/src/terminal-image.ts +5 -0
  85. package/pkg/package.json +1 -1
  86. package/src/resources/extensions/async-jobs/await-tool.ts +0 -2
  87. package/src/resources/extensions/async-jobs/job-manager.ts +0 -7
  88. package/src/resources/extensions/bg-shell/output-formatter.ts +0 -17
  89. package/src/resources/extensions/bg-shell/process-manager.ts +0 -4
  90. package/src/resources/extensions/bg-shell/types.ts +0 -12
  91. package/src/resources/extensions/cmux/index.ts +384 -0
  92. package/src/resources/extensions/context7/index.ts +7 -0
  93. package/src/resources/extensions/get-secrets-from-user.ts +2 -35
  94. package/src/resources/extensions/google-search/index.ts +7 -0
  95. package/src/resources/extensions/gsd/auto-dashboard.ts +363 -116
  96. package/src/resources/extensions/gsd/auto-dispatch.ts +49 -1
  97. package/src/resources/extensions/gsd/auto-loop.ts +64 -2
  98. package/src/resources/extensions/gsd/auto-model-selection.ts +23 -2
  99. package/src/resources/extensions/gsd/auto-recovery.ts +39 -0
  100. package/src/resources/extensions/gsd/auto-start.ts +42 -2
  101. package/src/resources/extensions/gsd/auto.ts +82 -3
  102. package/src/resources/extensions/gsd/commands-cmux.ts +143 -0
  103. package/src/resources/extensions/gsd/commands-handlers.ts +2 -2
  104. package/src/resources/extensions/gsd/commands-inspect.ts +10 -3
  105. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  106. package/src/resources/extensions/gsd/commands-rate.ts +55 -0
  107. package/src/resources/extensions/gsd/commands.ts +97 -2
  108. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -0
  109. package/src/resources/extensions/gsd/doctor-environment.ts +26 -16
  110. package/src/resources/extensions/gsd/files.ts +12 -2
  111. package/src/resources/extensions/gsd/gitignore.ts +54 -7
  112. package/src/resources/extensions/gsd/guided-flow.ts +8 -2
  113. package/src/resources/extensions/gsd/health-widget-core.ts +129 -0
  114. package/src/resources/extensions/gsd/health-widget.ts +103 -59
  115. package/src/resources/extensions/gsd/index.ts +37 -32
  116. package/src/resources/extensions/gsd/migrate-external.ts +47 -2
  117. package/src/resources/extensions/gsd/milestone-ids.ts +3 -2
  118. package/src/resources/extensions/gsd/notifications.ts +10 -1
  119. package/src/resources/extensions/gsd/paths.ts +73 -7
  120. package/src/resources/extensions/gsd/post-unit-hooks.ts +5 -1
  121. package/src/resources/extensions/gsd/preferences-types.ts +13 -0
  122. package/src/resources/extensions/gsd/preferences-validation.ts +42 -1
  123. package/src/resources/extensions/gsd/preferences.ts +18 -1
  124. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  125. package/src/resources/extensions/gsd/prompts/research-milestone.md +4 -3
  126. package/src/resources/extensions/gsd/prompts/research-slice.md +3 -2
  127. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  128. package/src/resources/extensions/gsd/roadmap-mutations.ts +66 -0
  129. package/src/resources/extensions/gsd/session-lock.ts +59 -2
  130. package/src/resources/extensions/gsd/state.ts +2 -1
  131. package/src/resources/extensions/gsd/templates/plan.md +8 -0
  132. package/src/resources/extensions/gsd/templates/preferences.md +6 -0
  133. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +2 -0
  134. package/src/resources/extensions/gsd/tests/cmux.test.ts +98 -0
  135. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +46 -0
  136. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +20 -0
  137. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +214 -0
  138. package/src/resources/extensions/gsd/tests/health-widget.test.ts +158 -0
  139. package/src/resources/extensions/gsd/tests/paths.test.ts +113 -0
  140. package/src/resources/extensions/gsd/tests/preferences.test.ts +35 -2
  141. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +26 -0
  142. package/src/resources/extensions/gsd/tests/test-utils.ts +165 -0
  143. package/src/resources/extensions/gsd/tests/validate-directory.test.ts +15 -0
  144. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +7 -0
  145. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +32 -0
  146. package/src/resources/extensions/gsd/worktree-resolver.ts +11 -0
  147. package/src/resources/extensions/remote-questions/remote-command.ts +2 -23
  148. package/src/resources/extensions/search-the-web/native-search.ts +50 -4
  149. package/src/resources/extensions/shared/mod.ts +1 -1
  150. package/src/resources/extensions/shared/sanitize.ts +36 -0
  151. package/src/resources/extensions/shared/terminal.ts +5 -0
  152. package/src/resources/extensions/subagent/index.ts +242 -91
  153. package/src/resources/skills/core-web-vitals/SKILL.md +1 -1
  154. package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +1 -1
  155. package/src/resources/skills/github-workflows/SKILL.md +0 -2
  156. package/src/resources/skills/web-quality-audit/SKILL.md +0 -2
  157. package/dist/resources/extensions/shared/wizard-ui.js +0 -478
  158. package/dist/resources/skills/swiftui/SKILL.md +0 -208
  159. package/dist/resources/skills/swiftui/references/animations.md +0 -921
  160. package/dist/resources/skills/swiftui/references/architecture.md +0 -1561
  161. package/dist/resources/skills/swiftui/references/layout-system.md +0 -1186
  162. package/dist/resources/skills/swiftui/references/navigation.md +0 -1492
  163. package/dist/resources/skills/swiftui/references/networking-async.md +0 -214
  164. package/dist/resources/skills/swiftui/references/performance.md +0 -1706
  165. package/dist/resources/skills/swiftui/references/platform-integration.md +0 -204
  166. package/dist/resources/skills/swiftui/references/state-management.md +0 -1443
  167. package/dist/resources/skills/swiftui/references/swiftdata.md +0 -297
  168. package/dist/resources/skills/swiftui/references/testing-debugging.md +0 -247
  169. package/dist/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
  170. package/dist/resources/skills/swiftui/workflows/add-feature.md +0 -191
  171. package/dist/resources/skills/swiftui/workflows/build-new-app.md +0 -311
  172. package/dist/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
  173. package/dist/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
  174. package/dist/resources/skills/swiftui/workflows/ship-app.md +0 -203
  175. package/dist/resources/skills/swiftui/workflows/write-tests.md +0 -235
  176. package/src/resources/extensions/shared/wizard-ui.ts +0 -551
  177. package/src/resources/skills/swiftui/SKILL.md +0 -208
  178. package/src/resources/skills/swiftui/references/animations.md +0 -921
  179. package/src/resources/skills/swiftui/references/architecture.md +0 -1561
  180. package/src/resources/skills/swiftui/references/layout-system.md +0 -1186
  181. package/src/resources/skills/swiftui/references/navigation.md +0 -1492
  182. package/src/resources/skills/swiftui/references/networking-async.md +0 -214
  183. package/src/resources/skills/swiftui/references/performance.md +0 -1706
  184. package/src/resources/skills/swiftui/references/platform-integration.md +0 -204
  185. package/src/resources/skills/swiftui/references/state-management.md +0 -1443
  186. package/src/resources/skills/swiftui/references/swiftdata.md +0 -297
  187. package/src/resources/skills/swiftui/references/testing-debugging.md +0 -247
  188. package/src/resources/skills/swiftui/references/uikit-appkit-interop.md +0 -218
  189. package/src/resources/skills/swiftui/workflows/add-feature.md +0 -191
  190. package/src/resources/skills/swiftui/workflows/build-new-app.md +0 -311
  191. package/src/resources/skills/swiftui/workflows/debug-swiftui.md +0 -192
  192. package/src/resources/skills/swiftui/workflows/optimize-performance.md +0 -197
  193. package/src/resources/skills/swiftui/workflows/ship-app.md +0 -203
  194. package/src/resources/skills/swiftui/workflows/write-tests.md +0 -235
@@ -0,0 +1,31 @@
1
+ /**
2
+ * /gsd rate — Submit feedback on the last unit's model tier assignment.
3
+ * Feeds into the adaptive routing history so future dispatches improve.
4
+ */
5
+ import { loadLedgerFromDisk } from "./metrics.js";
6
+ import { recordFeedback, initRoutingHistory } from "./routing-history.js";
7
+ const VALID_RATINGS = new Set(["over", "under", "ok"]);
8
+ export async function handleRate(args, ctx, basePath) {
9
+ const rating = args.trim().toLowerCase();
10
+ if (!rating || !VALID_RATINGS.has(rating)) {
11
+ ctx.ui.notify("Usage: /gsd rate <over|ok|under>\n" +
12
+ " over — model was overpowered for that task (encourage cheaper)\n" +
13
+ " ok — model was appropriate\n" +
14
+ " under — model was too weak (encourage stronger)", "info");
15
+ return;
16
+ }
17
+ const ledger = loadLedgerFromDisk(basePath);
18
+ if (!ledger || ledger.units.length === 0) {
19
+ ctx.ui.notify("No completed units found — nothing to rate.", "warning");
20
+ return;
21
+ }
22
+ const lastUnit = ledger.units[ledger.units.length - 1];
23
+ const tier = lastUnit.tier;
24
+ if (!tier) {
25
+ ctx.ui.notify("Last unit has no tier data (dynamic routing was not active). Rating skipped.", "warning");
26
+ return;
27
+ }
28
+ initRoutingHistory(basePath);
29
+ recordFeedback(lastUnit.type, lastUnit.id, tier, rating);
30
+ ctx.ui.notify(`Recorded "${rating}" for ${lastUnit.type}/${lastUnit.id} at tier ${tier}.`, "info");
31
+ }
@@ -36,6 +36,8 @@ import { computeProgressScore, formatProgressLine } from "./progress-score.js";
36
36
  import { runEnvironmentChecks } from "./doctor-environment.js";
37
37
  import { handleLogs } from "./commands-logs.js";
38
38
  import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
39
+ import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
40
+ import { handleCmux } from "./commands-cmux.js";
39
41
  /** Resolve the effective project root, accounting for worktree paths. */
40
42
  export function projectRoot() {
41
43
  const cwd = process.cwd();
@@ -54,9 +56,41 @@ export function projectRoot() {
54
56
  }
55
57
  return root;
56
58
  }
59
+ /**
60
+ * Check if another process holds the auto-mode session lock.
61
+ * Returns the lock data if a remote session is alive, null otherwise.
62
+ */
63
+ function getRemoteAutoSession(basePath) {
64
+ const lockData = readSessionLockData(basePath);
65
+ if (!lockData)
66
+ return null;
67
+ if (lockData.pid === process.pid)
68
+ return null;
69
+ if (!isSessionLockProcessAlive(lockData))
70
+ return null;
71
+ return { pid: lockData.pid };
72
+ }
73
+ /**
74
+ * Show a steering menu when auto-mode is running in another process.
75
+ * Returns true if a remote session was detected (caller should return early).
76
+ */
77
+ function notifyRemoteAutoActive(ctx, basePath) {
78
+ const remote = getRemoteAutoSession(basePath);
79
+ if (!remote)
80
+ return false;
81
+ ctx.ui.notify(`Auto-mode is running in another process (PID ${remote.pid}).\n` +
82
+ `Use these commands to interact with it:\n` +
83
+ ` /gsd status — check progress\n` +
84
+ ` /gsd discuss — discuss architecture decisions\n` +
85
+ ` /gsd queue — queue the next milestone\n` +
86
+ ` /gsd steer — apply an override to active work\n` +
87
+ ` /gsd capture — fire-and-forget thought\n` +
88
+ ` /gsd stop — stop auto-mode`, "warning");
89
+ return true;
90
+ }
57
91
  export function registerGSDCommand(pi) {
58
92
  pi.registerCommand("gsd", {
59
- description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|update",
93
+ description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|update",
60
94
  getArgumentCompletions: (prefix) => {
61
95
  const subcommands = [
62
96
  { cmd: "help", desc: "Categorized command reference with descriptions" },
@@ -65,6 +99,7 @@ export function registerGSDCommand(pi) {
65
99
  { cmd: "stop", desc: "Stop auto mode gracefully" },
66
100
  { cmd: "pause", desc: "Pause auto-mode (preserves state, /gsd auto to resume)" },
67
101
  { cmd: "status", desc: "Progress dashboard" },
102
+ { cmd: "widget", desc: "Cycle widget: full → small → min → off" },
68
103
  { cmd: "visualize", desc: "Open 10-tab workflow visualizer (progress, timeline, deps, metrics, health, agent, changes, knowledge, captures, export)" },
69
104
  { cmd: "queue", desc: "Queue and reorder future milestones" },
70
105
  { cmd: "quick", desc: "Execute a quick task without full planning overhead" },
@@ -74,6 +109,7 @@ export function registerGSDCommand(pi) {
74
109
  { cmd: "triage", desc: "Manually trigger triage of pending captures" },
75
110
  { cmd: "dispatch", desc: "Dispatch a specific phase directly" },
76
111
  { cmd: "history", desc: "View execution history" },
112
+ { cmd: "rate", desc: "Rate last unit's model tier (over/ok/under) — improves adaptive routing" },
77
113
  { cmd: "undo", desc: "Revert last completed unit" },
78
114
  { cmd: "skip", desc: "Prevent a unit from auto-mode dispatch" },
79
115
  { cmd: "export", desc: "Export milestone/slice results" },
@@ -97,6 +133,7 @@ export function registerGSDCommand(pi) {
97
133
  { cmd: "knowledge", desc: "Add persistent project knowledge (rule, pattern, or lesson)" },
98
134
  { cmd: "new-milestone", desc: "Create a milestone from a specification document (headless)" },
99
135
  { cmd: "parallel", desc: "Parallel milestone orchestration (start, status, stop, merge)" },
136
+ { cmd: "cmux", desc: "Manage cmux integration (status, sidebar, notifications, splits)" },
100
137
  { cmd: "park", desc: "Park a milestone — skip without deleting" },
101
138
  { cmd: "unpark", desc: "Reactivate a parked milestone" },
102
139
  { cmd: "update", desc: "Update GSD to the latest version" },
@@ -148,6 +185,36 @@ export function registerGSDCommand(pi) {
148
185
  .filter((s) => s.cmd.startsWith(subPrefix))
149
186
  .map((s) => ({ value: `parallel ${s.cmd}`, label: s.cmd, description: s.desc }));
150
187
  }
188
+ if (parts[0] === "cmux") {
189
+ if (parts.length <= 2) {
190
+ const subPrefix = parts[1] ?? "";
191
+ const subs = [
192
+ { cmd: "status", desc: "Show cmux detection, prefs, and capabilities" },
193
+ { cmd: "on", desc: "Enable cmux integration" },
194
+ { cmd: "off", desc: "Disable cmux integration" },
195
+ { cmd: "notifications", desc: "Toggle cmux desktop notifications" },
196
+ { cmd: "sidebar", desc: "Toggle cmux sidebar metadata" },
197
+ { cmd: "splits", desc: "Toggle cmux visual subagent splits" },
198
+ { cmd: "browser", desc: "Toggle future browser integration flag" },
199
+ ];
200
+ return subs
201
+ .filter((s) => s.cmd.startsWith(subPrefix))
202
+ .map((s) => ({ value: `cmux ${s.cmd}`, label: s.cmd, description: s.desc }));
203
+ }
204
+ if (parts.length <= 3 && ["notifications", "sidebar", "splits", "browser"].includes(parts[1])) {
205
+ const togglePrefix = parts[2] ?? "";
206
+ return [
207
+ { cmd: "on", desc: "Enable this cmux area" },
208
+ { cmd: "off", desc: "Disable this cmux area" },
209
+ ]
210
+ .filter((item) => item.cmd.startsWith(togglePrefix))
211
+ .map((item) => ({
212
+ value: `cmux ${parts[1]} ${item.cmd}`,
213
+ label: item.cmd,
214
+ description: item.desc,
215
+ }));
216
+ }
217
+ }
151
218
  if (parts[0] === "setup" && parts.length <= 2) {
152
219
  const subPrefix = parts[1] ?? "";
153
220
  const subs = [
@@ -396,6 +463,18 @@ export async function handleGSDCommand(args, ctx, pi) {
396
463
  await handleStatus(ctx);
397
464
  return;
398
465
  }
466
+ if (trimmed === "widget" || trimmed.startsWith("widget ")) {
467
+ const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await import("./auto-dashboard.js");
468
+ const arg = trimmed.replace(/^widget\s*/, "").trim();
469
+ if (arg === "full" || arg === "small" || arg === "min" || arg === "off") {
470
+ setWidgetMode(arg);
471
+ }
472
+ else {
473
+ cycleWidgetMode();
474
+ }
475
+ ctx.ui.notify(`Widget: ${getWidgetMode()}`, "info");
476
+ return;
477
+ }
399
478
  if (trimmed === "visualize") {
400
479
  await handleVisualize(ctx);
401
480
  return;
@@ -412,6 +491,10 @@ export async function handleGSDCommand(args, ctx, pi) {
412
491
  await handlePrefs(trimmed.replace(/^prefs\s*/, "").trim(), ctx);
413
492
  return;
414
493
  }
494
+ if (trimmed === "cmux" || trimmed.startsWith("cmux ")) {
495
+ await handleCmux(trimmed.replace(/^cmux\s*/, "").trim(), ctx);
496
+ return;
497
+ }
415
498
  if (trimmed === "init") {
416
499
  const { detectProjectState } = await import("./detection.js");
417
500
  const { showProjectInit, handleReinit } = await import("./init-wizard.js");
@@ -459,6 +542,8 @@ export async function handleGSDCommand(args, ctx, pi) {
459
542
  await handleDryRun(ctx, projectRoot());
460
543
  return;
461
544
  }
545
+ if (notifyRemoteAutoActive(ctx, projectRoot()))
546
+ return;
462
547
  const verboseMode = trimmed.includes("--verbose");
463
548
  const debugMode = trimmed.includes("--debug");
464
549
  if (debugMode)
@@ -513,6 +598,11 @@ export async function handleGSDCommand(args, ctx, pi) {
513
598
  await handleUndo(trimmed.replace(/^undo\s*/, "").trim(), ctx, pi, projectRoot());
514
599
  return;
515
600
  }
601
+ if (trimmed === "rate" || trimmed.startsWith("rate ")) {
602
+ const { handleRate } = await import("./commands-rate.js");
603
+ await handleRate(trimmed.replace(/^rate\s*/, "").trim(), ctx, projectRoot());
604
+ return;
605
+ }
516
606
  if (trimmed.startsWith("skip ")) {
517
607
  await handleSkip(trimmed.replace(/^skip\s*/, "").trim(), ctx, projectRoot());
518
608
  return;
@@ -809,7 +899,8 @@ Examples:
809
899
  return;
810
900
  }
811
901
  if (trimmed === "") {
812
- // Bare /gsd defaults to step mode
902
+ if (notifyRemoteAutoActive(ctx, projectRoot()))
903
+ return;
813
904
  await startAuto(ctx, pi, projectRoot(), false, { step: true });
814
905
  return;
815
906
  }
@@ -858,6 +949,7 @@ function showHelp(ctx) {
858
949
  " /gsd setup Global setup status [llm|search|remote|keys|prefs]",
859
950
  " /gsd mode Set workflow mode (solo/team) [global|project]",
860
951
  " /gsd prefs Manage preferences [global|project|status|wizard|setup|import-claude]",
952
+ " /gsd cmux Manage cmux integration [status|on|off|notifications|sidebar|splits|browser]",
861
953
  " /gsd config Set API keys for external tools",
862
954
  " /gsd keys API key manager [list|add|remove|test|rotate|doctor]",
863
955
  " /gsd hooks Show post-unit hook configuration",
@@ -173,6 +173,13 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
173
173
  - `on_milestone`: boolean — notify when a milestone finishes. Default: `true`.
174
174
  - `on_attention`: boolean — notify when manual attention is needed. Default: `true`.
175
175
 
176
+ - `cmux`: configures cmux terminal integration when GSD is running inside a cmux workspace. Keys:
177
+ - `enabled`: boolean — master toggle for cmux integration. Default: `false`.
178
+ - `notifications`: boolean — route desktop notifications through cmux. Default: `true` when enabled.
179
+ - `sidebar`: boolean — publish status, progress, and log metadata to the cmux sidebar. Default: `true` when enabled.
180
+ - `splits`: boolean — run supported subagent work in visible cmux splits. Default: `false`.
181
+ - `browser`: boolean — reserve the future browser integration flag. Default: `false`.
182
+
176
183
  - `dynamic_routing`: configures the dynamic model router that adjusts model selection based on task complexity. Keys:
177
184
  - `enabled`: boolean — enable dynamic routing. Default: `false`.
178
185
  - `tier_models`: object — model overrides per complexity tier. Keys: `light`, `standard`, `heavy`. Values are model ID strings.
@@ -477,6 +484,24 @@ Disables per-unit completion notifications (noisy in long runs) while keeping er
477
484
 
478
485
  ---
479
486
 
487
+ ## cmux Example
488
+
489
+ ```yaml
490
+ ---
491
+ version: 1
492
+ cmux:
493
+ enabled: true
494
+ notifications: true
495
+ sidebar: true
496
+ splits: true
497
+ browser: false
498
+ ---
499
+ ```
500
+
501
+ Enables cmux-aware notifications, sidebar metadata, and visible subagent splits when GSD is running inside a cmux terminal.
502
+
503
+ ---
504
+
480
505
  ## Post-Unit Hooks Example
481
506
 
482
507
  ```yaml
@@ -148,27 +148,36 @@ function checkPortConflicts(basePath) {
148
148
  // Try to detect ports from package.json scripts
149
149
  const portsToCheck = new Set();
150
150
  const pkgPath = join(basePath, "package.json");
151
- if (existsSync(pkgPath)) {
152
- try {
153
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
154
- const scripts = pkg.scripts ?? {};
155
- const scriptText = Object.values(scripts).join(" ");
156
- // Look for --port NNNN, -p NNNN, PORT=NNNN, :NNNN patterns
157
- const portMatches = scriptText.matchAll(/(?:--port\s+|(?:^|[^a-z])PORT[=:]\s*|-p\s+|:)(\d{4,5})\b/gi);
158
- for (const m of portMatches) {
159
- const port = parseInt(m[1], 10);
160
- if (port >= 1024 && port <= 65535)
161
- portsToCheck.add(port);
162
- }
163
- }
164
- catch {
165
- // parse failed use defaults
151
+ if (!existsSync(pkgPath)) {
152
+ // No package.json — this isn't a Node.js project. Skip port checks
153
+ // entirely to avoid false positives from system services (e.g., macOS
154
+ // AirPlay Receiver on port 5000). (#1381)
155
+ return [];
156
+ }
157
+ try {
158
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
159
+ const scripts = pkg.scripts ?? {};
160
+ const scriptText = Object.values(scripts).join(" ");
161
+ // Look for --port NNNN, -p NNNN, PORT=NNNN, :NNNN patterns
162
+ const portMatches = scriptText.matchAll(/(?:--port\s+|(?:^|[^a-z])PORT[=:]\s*|-p\s+|:)(\d{4,5})\b/gi);
163
+ for (const m of portMatches) {
164
+ const port = parseInt(m[1], 10);
165
+ if (port >= 1024 && port <= 65535)
166
+ portsToCheck.add(port);
166
167
  }
167
168
  }
168
- // If no ports found in scripts, check common defaults
169
+ catch {
170
+ // parse failed — skip port checks rather than using defaults
171
+ return [];
172
+ }
173
+ // If no ports found in scripts, check common defaults.
174
+ // Filter out port 5000 on macOS — AirPlay Receiver uses it by default (#1381).
169
175
  if (portsToCheck.size === 0) {
170
- for (const p of DEFAULT_DEV_PORTS)
176
+ for (const p of DEFAULT_DEV_PORTS) {
177
+ if (p === 5000 && process.platform === "darwin")
178
+ continue;
171
179
  portsToCheck.add(p);
180
+ }
172
181
  }
173
182
  for (const port of portsToCheck) {
174
183
  const result = tryExec(`lsof -i :${port} -sTCP:LISTEN -t`, basePath);
@@ -509,7 +509,8 @@ export async function loadFile(path) {
509
509
  return await fs.readFile(path, 'utf-8');
510
510
  }
511
511
  catch (err) {
512
- if (err.code === 'ENOENT')
512
+ const code = err.code;
513
+ if (code === 'ENOENT' || code === 'EISDIR')
513
514
  return null;
514
515
  throw err;
515
516
  }
@@ -704,7 +705,7 @@ export async function inlinePriorMilestoneSummary(mid, base) {
704
705
  * Returns `null` when no manifest file exists (path resolution failure or
705
706
  * file not on disk) - callers can distinguish "no manifest" from "empty manifest".
706
707
  */
707
- export async function getManifestStatus(base, milestoneId) {
708
+ export async function getManifestStatus(base, milestoneId, projectRoot) {
708
709
  const resolvedPath = resolveMilestoneFile(base, milestoneId, 'SECRETS');
709
710
  if (!resolvedPath)
710
711
  return null;
@@ -713,8 +714,16 @@ export async function getManifestStatus(base, milestoneId) {
713
714
  return null;
714
715
  const manifest = parseSecretsManifest(content);
715
716
  const keys = manifest.entries.map(e => e.key);
717
+ // Check both the base path .env AND the project root .env (#1387).
718
+ // In worktree mode, base is the worktree path which may not have .env.
719
+ // The project root's .env is where the user actually defined their keys.
716
720
  const existingKeys = await checkExistingEnvKeys(keys, resolve(base, '.env'));
717
721
  const existingSet = new Set(existingKeys);
722
+ if (projectRoot && projectRoot !== base) {
723
+ const rootKeys = await checkExistingEnvKeys(keys, resolve(projectRoot, '.env'));
724
+ for (const k of rootKeys)
725
+ existingSet.add(k);
726
+ }
718
727
  const result = {
719
728
  pending: [],
720
729
  collected: [],
@@ -6,8 +6,8 @@
6
6
  * Both idempotent — non-destructive if already present.
7
7
  */
8
8
  import { join } from "node:path";
9
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
10
- import { nativeRmCached } from "./native-git-bridge.js";
9
+ import { existsSync, lstatSync, readFileSync, writeFileSync } from "node:fs";
10
+ import { nativeRmCached, nativeLsFiles } from "./native-git-bridge.js";
11
11
  import { gsdRoot } from "./paths.js";
12
12
  /**
13
13
  * GSD runtime patterns for git index cleanup.
@@ -67,12 +67,48 @@ const BASELINE_PATTERNS = [
67
67
  "tmp/",
68
68
  ];
69
69
  /**
70
- * Ensure basePath/.gitignore contains a blanket `.gsd/` ignore.
71
- * Creates the file if missing; appends `.gsd/` if not present.
70
+ * Check whether `.gsd/` contains files tracked by git.
71
+ * If so, the project intentionally keeps `.gsd/` in version control
72
+ * and we must NOT add `.gsd` to `.gitignore` or attempt migration.
73
+ *
74
+ * Returns true if git tracks at least one file under `.gsd/`.
75
+ * Returns false (safe to ignore) if:
76
+ * - Not a git repo
77
+ * - `.gsd/` is a symlink (external state, should be ignored)
78
+ * - `.gsd/` doesn't exist
79
+ * - No tracked files found under `.gsd/`
80
+ */
81
+ export function hasGitTrackedGsdFiles(basePath) {
82
+ const localGsd = join(basePath, ".gsd");
83
+ // If .gsd doesn't exist or is already a symlink, no tracked files concern
84
+ if (!existsSync(localGsd))
85
+ return false;
86
+ try {
87
+ if (lstatSync(localGsd).isSymbolicLink())
88
+ return false;
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ // Check if git tracks any files under .gsd/
94
+ try {
95
+ const tracked = nativeLsFiles(basePath, ".gsd");
96
+ return tracked.length > 0;
97
+ }
98
+ catch {
99
+ // Not a git repo or git not available — safe to proceed
100
+ return false;
101
+ }
102
+ }
103
+ /**
104
+ * Ensure basePath/.gitignore contains baseline ignore patterns.
105
+ * Creates the file if missing; appends missing patterns.
72
106
  * Returns true if the file was created or modified, false if already complete.
73
107
  *
74
- * `.gsd/` state is managed externally (symlinked to `~/.gsd/projects/<hash>/`),
75
- * so the entire directory is always gitignored.
108
+ * **Safety check:** If `.gsd/` contains git-tracked files (i.e., the project
109
+ * intentionally keeps `.gsd/` in version control), the `.gsd` ignore pattern
110
+ * is excluded to prevent data loss. Only the `.gsd` pattern is affected —
111
+ * all other baseline patterns are still applied normally.
76
112
  */
77
113
  export function ensureGitignore(basePath, options) {
78
114
  // If manage_gitignore is explicitly false, do not touch .gitignore at all
@@ -88,8 +124,14 @@ export function ensureGitignore(basePath, options) {
88
124
  .split("\n")
89
125
  .map((l) => l.trim())
90
126
  .filter((l) => l && !l.startsWith("#")));
127
+ // Determine which patterns to apply. If .gsd/ has tracked files,
128
+ // exclude the ".gsd" pattern to prevent deleting tracked state.
129
+ const gsdIsTracked = hasGitTrackedGsdFiles(basePath);
130
+ const patternsToApply = gsdIsTracked
131
+ ? BASELINE_PATTERNS.filter((p) => p !== ".gsd")
132
+ : BASELINE_PATTERNS;
91
133
  // Find patterns not yet present
92
- const missing = BASELINE_PATTERNS.filter((p) => !existingLines.has(p));
134
+ const missing = patternsToApply.filter((p) => !existingLines.has(p));
93
135
  if (missing.length === 0)
94
136
  return false;
95
137
  // Build the block to append
@@ -111,6 +153,11 @@ export function ensureGitignore(basePath, options) {
111
153
  * already in the index even after .gitignore is updated.
112
154
  *
113
155
  * Only removes from the index (`--cached`), never from disk. Idempotent.
156
+ *
157
+ * Note: These are strictly runtime/ephemeral paths (activity logs, lock files,
158
+ * metrics, STATE.md). They are always safe to untrack, even when the project
159
+ * intentionally keeps other `.gsd/` files (like PROJECT.md, milestones/) in
160
+ * version control.
114
161
  */
115
162
  export function untrackRuntimeFiles(basePath) {
116
163
  const runtimePaths = GSD_RUNTIME_PATTERNS;
@@ -17,6 +17,7 @@ import { resolveExpectedArtifactPath } from "./auto.js";
17
17
  import { gsdRoot, milestonesDir, resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile, relMilestoneFile, relSliceFile, } from "./paths.js";
18
18
  import { join } from "node:path";
19
19
  import { readFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from "node:fs";
20
+ import { readSessionLockData, isSessionLockProcessAlive } from "./session-lock.js";
20
21
  import { nativeIsRepo, nativeInit } from "./native-git-bridge.js";
21
22
  import { ensureGitignore, ensurePreferences, untrackRuntimeFiles } from "./gitignore.js";
22
23
  import { loadEffectiveGSDPreferences } from "./preferences.js";
@@ -154,7 +155,7 @@ function parseMilestoneSequenceFromProject(content) {
154
155
  * This is the only way the wizard triggers work — everything else is the LLM's job.
155
156
  */
156
157
  function dispatchWorkflow(pi, note, customType = "gsd-run") {
157
- const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
158
+ const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
158
159
  const workflow = readFileSync(workflowPath, "utf-8");
159
160
  pi.sendMessage({
160
161
  customType,
@@ -426,7 +427,12 @@ export async function showDiscuss(ctx, pi, basePath) {
426
427
  // If all pending slices are discussed, notify and exit instead of looping
427
428
  const allDiscussed = pendingSlices.every(s => discussedMap.get(s.id));
428
429
  if (allDiscussed) {
429
- ctx.ui.notify(`All ${pendingSlices.length} slices discussed. Run /gsd to start planning.`, "info");
430
+ const lockData = readSessionLockData(basePath);
431
+ const remoteAutoRunning = lockData && lockData.pid !== process.pid && isSessionLockProcessAlive(lockData);
432
+ const nextStep = remoteAutoRunning
433
+ ? "Auto-mode is already running — use /gsd status to check progress."
434
+ : "Run /gsd to start planning.";
435
+ ctx.ui.notify(`All ${pendingSlices.length} slices discussed. ${nextStep}`, "info");
430
436
  return;
431
437
  }
432
438
  // Find the first undiscussed slice to recommend
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Pure GSD health widget logic.
3
+ *
4
+ * Separates project-state detection and line rendering from the widget's
5
+ * runtime integrations so the regressions can be tested directly.
6
+ */
7
+ import { existsSync, readdirSync } from "node:fs";
8
+ import { gsdRoot } from "./paths.js";
9
+ import { join } from "node:path";
10
+ export function detectHealthWidgetProjectState(basePath) {
11
+ const root = gsdRoot(basePath);
12
+ if (!existsSync(root))
13
+ return "none";
14
+ // Lightweight milestone count — avoids the full detectProjectState() scan
15
+ // (CI markers, Makefile targets, etc.) that is unnecessary on the 60s refresh.
16
+ try {
17
+ const milestonesDir = join(root, "milestones");
18
+ if (existsSync(milestonesDir)) {
19
+ const entries = readdirSync(milestonesDir, { withFileTypes: true });
20
+ if (entries.some(e => e.isDirectory()))
21
+ return "active";
22
+ }
23
+ }
24
+ catch { /* non-fatal */ }
25
+ return "initialized";
26
+ }
27
+ function formatCost(n) {
28
+ return n >= 1 ? `$${n.toFixed(2)}` : `${(n * 100).toFixed(1)}¢`;
29
+ }
30
+ function formatProgress(progress) {
31
+ if (!progress)
32
+ return null;
33
+ const parts = [];
34
+ parts.push(`M ${progress.milestones.done}/${progress.milestones.total}`);
35
+ if (progress.slices)
36
+ parts.push(`S ${progress.slices.done}/${progress.slices.total}`);
37
+ if (progress.tasks)
38
+ parts.push(`T ${progress.tasks.done}/${progress.tasks.total}`);
39
+ return parts.length > 0 ? `Progress: ${parts.join(" · ")}` : null;
40
+ }
41
+ function formatEnvironmentSummary(errorCount, warningCount) {
42
+ if (errorCount <= 0 && warningCount <= 0)
43
+ return null;
44
+ const parts = [];
45
+ if (errorCount > 0)
46
+ parts.push(`${errorCount} error${errorCount > 1 ? "s" : ""}`);
47
+ if (warningCount > 0)
48
+ parts.push(`${warningCount} warning${warningCount > 1 ? "s" : ""}`);
49
+ return `Env: ${parts.join(", ")}`;
50
+ }
51
+ function formatBudgetSummary(data) {
52
+ if (data.budgetCeiling !== undefined && data.budgetCeiling > 0) {
53
+ const pct = Math.min(100, (data.budgetSpent / data.budgetCeiling) * 100);
54
+ return `Budget: ${formatCost(data.budgetSpent)}/${formatCost(data.budgetCeiling)} (${pct.toFixed(0)}%)`;
55
+ }
56
+ if (data.budgetSpent > 0) {
57
+ return `Spent: ${formatCost(data.budgetSpent)}`;
58
+ }
59
+ return null;
60
+ }
61
+ function buildExecutionHeadline(data) {
62
+ const status = data.executionStatus ?? "Active project";
63
+ const target = data.executionTarget ?? data.blocker ?? "loading status…";
64
+ return ` GSD ${status}${target ? ` - ${target}` : ""}`;
65
+ }
66
+ /**
67
+ * Build compact health lines for the widget.
68
+ * Returns a string array suitable for setWidget().
69
+ */
70
+ export function buildHealthLines(data) {
71
+ if (data.projectState === "none") {
72
+ return [" GSD No project loaded — run /gsd to start"];
73
+ }
74
+ if (data.projectState === "initialized") {
75
+ return [" GSD Project initialized — run /gsd to continue setup"];
76
+ }
77
+ const lines = [buildExecutionHeadline(data)];
78
+ const details = [];
79
+ const progress = formatProgress(data.progress);
80
+ if (progress)
81
+ details.push(progress);
82
+ if (data.providerIssue)
83
+ details.push(data.providerIssue);
84
+ const environment = formatEnvironmentSummary(data.environmentErrorCount, data.environmentWarningCount);
85
+ if (environment)
86
+ details.push(environment);
87
+ const budget = formatBudgetSummary(data);
88
+ if (budget)
89
+ details.push(budget);
90
+ if (data.eta)
91
+ details.push(data.eta);
92
+ if (details.length > 0) {
93
+ lines.push(` ${details.join(" │ ")}`);
94
+ }
95
+ return lines;
96
+ }