gsd-pi 2.54.0-dev.e1efc1a → 2.55.0-dev.9ec7cdf

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 (201) hide show
  1. package/dist/cli.js +19 -19
  2. package/dist/headless-ui.d.ts +27 -1
  3. package/dist/headless-ui.js +203 -13
  4. package/dist/headless.js +60 -3
  5. package/dist/resources/extensions/bg-shell/bg-shell-lifecycle.js +2 -2
  6. package/dist/resources/extensions/bg-shell/utilities.js +34 -5
  7. package/dist/resources/extensions/gsd/auto/phases.js +19 -3
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
  9. package/dist/resources/extensions/gsd/auto-model-selection.js +17 -1
  10. package/dist/resources/extensions/gsd/auto-prompts.js +9 -0
  11. package/dist/resources/extensions/gsd/auto-start.js +12 -5
  12. package/dist/resources/extensions/gsd/auto-worktree.js +39 -14
  13. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +5 -1
  14. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +18 -0
  15. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +18 -5
  16. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +20 -0
  17. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  18. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +15 -1
  19. package/dist/resources/extensions/gsd/crash-recovery.js +2 -2
  20. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +413 -0
  21. package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -1
  22. package/dist/resources/extensions/gsd/session-lock.js +46 -12
  23. package/dist/resources/extensions/gsd/skill-health.js +2 -2
  24. package/dist/resources/extensions/gsd/visualizer-overlay.js +3 -3
  25. package/dist/resources/extensions/shared/format-utils.js +1 -1
  26. package/dist/resources/extensions/subagent/worker-registry.js +2 -1
  27. package/dist/web/standalone/.next/BUILD_ID +1 -1
  28. package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
  29. package/dist/web/standalone/.next/build-manifest.json +3 -3
  30. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  31. package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
  32. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  34. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/index.html +1 -1
  94. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  95. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  96. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  97. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
  102. package/dist/web/standalone/.next/server/chunks/2229.js +1 -1
  103. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  106. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  107. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  108. package/dist/web/standalone/.next/static/chunks/6502.2305d0afd2385711.js +9 -0
  109. package/dist/web/standalone/.next/static/chunks/app/{page-b950e4e384cc62b3.js → page-0c485498795110d6.js} +1 -1
  110. package/dist/web/standalone/.next/static/chunks/{webpack-bca0e732db0dcec3.js → webpack-4332cbd5dd1be584.js} +1 -1
  111. package/package.json +6 -4
  112. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -1
  113. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +2 -0
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +14 -2
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -1
  119. package/packages/pi-coding-agent/package.json +1 -1
  120. package/packages/pi-coding-agent/src/core/model-registry.ts +1 -1
  121. package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +16 -2
  122. package/pkg/package.json +1 -1
  123. package/scripts/ensure-workspace-builds.cjs +45 -41
  124. package/src/resources/extensions/bg-shell/bg-shell-lifecycle.ts +2 -2
  125. package/src/resources/extensions/bg-shell/utilities.ts +39 -4
  126. package/src/resources/extensions/gsd/auto/phases.ts +25 -4
  127. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
  128. package/src/resources/extensions/gsd/auto-model-selection.ts +21 -1
  129. package/src/resources/extensions/gsd/auto-prompts.ts +15 -0
  130. package/src/resources/extensions/gsd/auto-start.ts +13 -5
  131. package/src/resources/extensions/gsd/auto-worktree.ts +46 -13
  132. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +5 -4
  133. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +53 -0
  134. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +19 -6
  135. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +24 -0
  136. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  137. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +19 -1
  138. package/src/resources/extensions/gsd/crash-recovery.ts +2 -3
  139. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +497 -0
  140. package/src/resources/extensions/gsd/parallel-orchestrator.ts +6 -1
  141. package/src/resources/extensions/gsd/session-lock.ts +46 -12
  142. package/src/resources/extensions/gsd/skill-health.ts +2 -2
  143. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +139 -0
  144. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +28 -0
  145. package/src/resources/extensions/gsd/tests/{all-milestones-complete-merge.test.ts → integration/all-milestones-complete-merge.test.ts} +3 -3
  146. package/src/resources/extensions/gsd/tests/{atomic-task-closeout.test.ts → integration/atomic-task-closeout.test.ts} +1 -1
  147. package/src/resources/extensions/gsd/tests/{auto-preflight.test.ts → integration/auto-preflight.test.ts} +1 -1
  148. package/src/resources/extensions/gsd/tests/{auto-recovery.test.ts → integration/auto-recovery.test.ts} +7 -7
  149. package/src/resources/extensions/gsd/tests/{auto-secrets-gate.test.ts → integration/auto-secrets-gate.test.ts} +2 -2
  150. package/src/resources/extensions/gsd/tests/{auto-stash-merge.test.ts → integration/auto-stash-merge.test.ts} +3 -3
  151. package/src/resources/extensions/gsd/tests/{auto-worktree-milestone-merge.test.ts → integration/auto-worktree-milestone-merge.test.ts} +4 -4
  152. package/src/resources/extensions/gsd/tests/{auto-worktree.test.ts → integration/auto-worktree.test.ts} +5 -5
  153. package/src/resources/extensions/gsd/tests/{continue-here.test.ts → integration/continue-here.test.ts} +3 -3
  154. package/src/resources/extensions/gsd/tests/{doctor-completion-deferral.test.ts → integration/doctor-completion-deferral.test.ts} +1 -1
  155. package/src/resources/extensions/gsd/tests/{doctor-delimiter-fix.test.ts → integration/doctor-delimiter-fix.test.ts} +1 -1
  156. package/src/resources/extensions/gsd/tests/{doctor-enhancements.test.ts → integration/doctor-enhancements.test.ts} +3 -3
  157. package/src/resources/extensions/gsd/tests/{doctor-environment-worktree.test.ts → integration/doctor-environment-worktree.test.ts} +1 -1
  158. package/src/resources/extensions/gsd/tests/{doctor-environment.test.ts → integration/doctor-environment.test.ts} +1 -1
  159. package/src/resources/extensions/gsd/tests/{doctor-fixlevel.test.ts → integration/doctor-fixlevel.test.ts} +2 -2
  160. package/src/resources/extensions/gsd/tests/{doctor-git.test.ts → integration/doctor-git.test.ts} +1 -1
  161. package/src/resources/extensions/gsd/tests/{doctor-proactive.test.ts → integration/doctor-proactive.test.ts} +1 -1
  162. package/src/resources/extensions/gsd/tests/{doctor-roadmap-summary-atomicity.test.ts → integration/doctor-roadmap-summary-atomicity.test.ts} +1 -1
  163. package/src/resources/extensions/gsd/tests/{doctor-runtime.test.ts → integration/doctor-runtime.test.ts} +1 -1
  164. package/src/resources/extensions/gsd/tests/{doctor.test.ts → integration/doctor.test.ts} +1 -1
  165. package/src/resources/extensions/gsd/tests/{e2e-workflow-pipeline-integration.test.ts → integration/e2e-workflow-pipeline-integration.test.ts} +5 -5
  166. package/src/resources/extensions/gsd/tests/{feature-branch-lifecycle-integration.test.ts → integration/feature-branch-lifecycle-integration.test.ts} +4 -4
  167. package/src/resources/extensions/gsd/tests/{git-locale.test.ts → integration/git-locale.test.ts} +4 -4
  168. package/src/resources/extensions/gsd/tests/{git-self-heal.test.ts → integration/git-self-heal.test.ts} +1 -1
  169. package/src/resources/extensions/gsd/tests/{git-service.test.ts → integration/git-service.test.ts} +4 -4
  170. package/src/resources/extensions/gsd/tests/{gitignore-tracked-gsd.test.ts → integration/gitignore-tracked-gsd.test.ts} +2 -2
  171. package/src/resources/extensions/gsd/tests/{idle-recovery.test.ts → integration/idle-recovery.test.ts} +3 -3
  172. package/src/resources/extensions/gsd/tests/{inherited-repo-home-dir.test.ts → integration/inherited-repo-home-dir.test.ts} +1 -1
  173. package/src/resources/extensions/gsd/tests/{integration-lifecycle.test.ts → integration/integration-lifecycle.test.ts} +4 -4
  174. package/src/resources/extensions/gsd/tests/{integration-mixed-milestones.test.ts → integration/integration-mixed-milestones.test.ts} +6 -6
  175. package/src/resources/extensions/gsd/tests/{integration-proof.test.ts → integration/integration-proof.test.ts} +12 -12
  176. package/src/resources/extensions/gsd/tests/{migrate-command.test.ts → integration/migrate-command.test.ts} +2 -2
  177. package/src/resources/extensions/gsd/tests/{milestone-transition-worktree.test.ts → integration/milestone-transition-worktree.test.ts} +3 -3
  178. package/src/resources/extensions/gsd/tests/{parallel-merge.test.ts → integration/parallel-merge.test.ts} +3 -3
  179. package/src/resources/extensions/gsd/tests/{parallel-workers-multi-milestone-e2e.test.ts → integration/parallel-workers-multi-milestone-e2e.test.ts} +3 -3
  180. package/src/resources/extensions/gsd/tests/{paths.test.ts → integration/paths.test.ts} +1 -1
  181. package/src/resources/extensions/gsd/tests/{plugin-importer-live.test.ts → integration/plugin-importer-live.test.ts} +2 -2
  182. package/src/resources/extensions/gsd/tests/{queue-completed-milestone-perf.test.ts → integration/queue-completed-milestone-perf.test.ts} +3 -3
  183. package/src/resources/extensions/gsd/tests/{queue-reorder-e2e.test.ts → integration/queue-reorder-e2e.test.ts} +5 -5
  184. package/src/resources/extensions/gsd/tests/{quick-branch-lifecycle.test.ts → integration/quick-branch-lifecycle.test.ts} +5 -5
  185. package/src/resources/extensions/gsd/tests/{run-uat.test.ts → integration/run-uat.test.ts} +4 -4
  186. package/src/resources/extensions/gsd/tests/{token-savings.test.ts → integration/token-savings.test.ts} +3 -3
  187. package/src/resources/extensions/gsd/tests/{worktree-e2e.test.ts → integration/worktree-e2e.test.ts} +4 -4
  188. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +55 -0
  189. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +60 -0
  190. package/src/resources/extensions/gsd/tests/parallel-worker-lock-contention.test.ts +226 -0
  191. package/src/resources/extensions/gsd/tests/plan-milestone-queue-context.test.ts +48 -0
  192. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +61 -19
  193. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +98 -0
  194. package/src/resources/extensions/gsd/tests/register-extension-guard.test.ts +59 -0
  195. package/src/resources/extensions/gsd/tests/worktree-preferences-sync.test.ts +49 -24
  196. package/src/resources/extensions/gsd/visualizer-overlay.ts +3 -3
  197. package/src/resources/extensions/shared/format-utils.ts +1 -1
  198. package/src/resources/extensions/subagent/worker-registry.ts +2 -1
  199. package/dist/web/standalone/.next/static/chunks/4024.87fd909ae0110f50.js +0 -9
  200. /package/dist/web/standalone/.next/static/{nISuDzAIpGYC-DVTvs4Po → k92jvAf8IfV4dZE3nnrAr}/_buildManifest.js +0 -0
  201. /package/dist/web/standalone/.next/static/{nISuDzAIpGYC-DVTvs4Po → k92jvAf8IfV4dZE3nnrAr}/_ssgManifest.js +0 -0
@@ -21,7 +21,7 @@ import { join, dirname } from "node:path";
21
21
  import { fileURLToPath } from "node:url";
22
22
  import { gsdRoot } from "./paths.js";
23
23
  import { createWorktree, worktreePath } from "./worktree-manager.js";
24
- import { autoWorktreeBranch, runWorktreePostCreateHook } from "./auto-worktree.js";
24
+ import { autoWorktreeBranch, runWorktreePostCreateHook, syncGsdStateToWorktree } from "./auto-worktree.js";
25
25
  import { nativeBranchExists } from "./native-git-bridge.js";
26
26
  import { readIntegrationBranch } from "./git-service.js";
27
27
  import { resolveParallelConfig } from "./preferences.js";
@@ -507,6 +507,11 @@ function createMilestoneWorktree(basePath: string, milestoneId: string): string
507
507
  // Run post-create hook if configured
508
508
  runWorktreePostCreateHook(basePath, info.path);
509
509
 
510
+ // Copy .gsd/ planning artifacts (milestones, CONTEXT, ROADMAP, etc.) from the
511
+ // project root into the worktree. Without this, workers for newly-planned
512
+ // milestones can't find their roadmap and exit immediately (#2184 Bug 4).
513
+ syncGsdStateToWorktree(basePath, info.path);
514
+
510
515
  return info.path;
511
516
  }
512
517
 
@@ -83,10 +83,31 @@ let _lockAcquiredAt: number = 0;
83
83
 
84
84
  const LOCK_FILE = "auto.lock";
85
85
 
86
+ /**
87
+ * Derive the effective lock file name for the current process.
88
+ * In parallel worker mode (GSD_PARALLEL_WORKER + GSD_MILESTONE_LOCK),
89
+ * each worker uses a per-milestone lock file (`auto-<milestoneId>.lock`)
90
+ * to avoid contending on the shared `.gsd/auto.lock` (#2184).
91
+ */
92
+ export function effectiveLockFile(): string {
93
+ const mid = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : null;
94
+ return mid ? `auto-${mid}.lock` : LOCK_FILE;
95
+ }
96
+
97
+ /**
98
+ * Derive the OS-level lock target directory for the current process.
99
+ * In parallel worker mode, uses `.gsd/parallel/<milestoneId>/` instead of
100
+ * `.gsd/` so workers don't contend on the same proper-lockfile directory (#2184).
101
+ */
102
+ export function effectiveLockTarget(gsdDir: string): string {
103
+ const mid = process.env.GSD_PARALLEL_WORKER ? process.env.GSD_MILESTONE_LOCK : null;
104
+ return mid ? join(gsdDir, "parallel", mid) : gsdDir;
105
+ }
106
+
86
107
  function lockPath(basePath: string): string {
87
108
  // If we have a snapshotted path from acquisition, use it for consistency
88
109
  if (_snapshotLockPath) return _snapshotLockPath;
89
- return join(gsdRoot(basePath), LOCK_FILE);
110
+ return join(gsdRoot(basePath), effectiveLockFile());
90
111
  }
91
112
 
92
113
  // ─── Stray Lock Cleanup ─────────────────────────────────────────────────────
@@ -265,14 +286,16 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
265
286
  }
266
287
 
267
288
  const gsdDir = gsdRoot(basePath);
289
+ const lockTarget = effectiveLockTarget(gsdDir);
268
290
 
269
291
  try {
270
- // Try to acquire an exclusive OS-level lock on the lock file.
271
- // We lock the directory (gsdRoot) since proper-lockfile works best
272
- // on directories, and the lock file itself may not exist yet.
273
- mkdirSync(gsdDir, { recursive: true });
292
+ // Try to acquire an exclusive OS-level lock on the lock target.
293
+ // We lock a directory since proper-lockfile works best on directories,
294
+ // and the lock file itself may not exist yet.
295
+ // In parallel worker mode, lockTarget is .gsd/parallel/<MID>/ (#2184).
296
+ mkdirSync(lockTarget, { recursive: true });
274
297
 
275
- const release = lockfile.lockSync(gsdDir, {
298
+ const release = lockfile.lockSync(lockTarget, {
276
299
  realpath: false,
277
300
  stale: 1_800_000, // 30 minutes — safe for laptop sleep / long event loop stalls
278
301
  update: 10_000, // Update lock mtime every 10s to prove liveness
@@ -283,7 +306,7 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
283
306
 
284
307
  // Safety net: clean up lock dir on process exit if _releaseFunction
285
308
  // wasn't called (e.g., normal exit after clean completion) (#1245).
286
- ensureExitHandler(gsdDir);
309
+ ensureExitHandler(lockTarget);
287
310
 
288
311
  // Write the informational lock data
289
312
  atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
@@ -298,12 +321,12 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
298
321
  // If no lock file or no alive process, try to clean up and re-acquire (#1245)
299
322
  if (!existingData || (existingPid && !isPidAlive(existingPid))) {
300
323
  try {
301
- const lockDir = join(gsdDir + ".lock");
324
+ const lockDir = join(lockTarget + ".lock");
302
325
  if (existsSync(lockDir)) rmSync(lockDir, { recursive: true, force: true });
303
326
  if (existsSync(lp)) unlinkSync(lp);
304
327
 
305
328
  // Retry acquisition after cleanup
306
- const release = lockfile.lockSync(gsdDir, {
329
+ const release = lockfile.lockSync(lockTarget, {
307
330
  realpath: false,
308
331
  stale: 1_800_000, // 30 minutes — match primary lock settings
309
332
  update: 10_000,
@@ -312,7 +335,7 @@ export function acquireSessionLock(basePath: string): SessionLockResult {
312
335
  assignLockState(basePath, release, lp);
313
336
 
314
337
  // Safety net — uses centralized handler to avoid double-registration
315
- ensureExitHandler(gsdDir);
338
+ ensureExitHandler(lockTarget);
316
339
 
317
340
  atomicWriteSync(lp, JSON.stringify(lockData, null, 2));
318
341
  return { acquired: true };
@@ -483,13 +506,24 @@ export function releaseSessionLock(basePath: string): void {
483
506
  // Non-fatal
484
507
  }
485
508
 
486
- // Remove the proper-lockfile directory (.gsd.lock/) for the current path
509
+ // Remove the proper-lockfile directory for the current lock target.
510
+ // In parallel worker mode, this is .gsd/parallel/<MID>.lock/ (#2184).
511
+ const gsdDir = gsdRoot(basePath);
512
+ const lockTarget = effectiveLockTarget(gsdDir);
487
513
  try {
488
- const lockDir = join(gsdRoot(basePath) + ".lock");
514
+ const lockDir = join(lockTarget + ".lock");
489
515
  if (existsSync(lockDir)) rmSync(lockDir, { recursive: true, force: true });
490
516
  } catch {
491
517
  // Non-fatal
492
518
  }
519
+ // Also clean the per-milestone parallel directory itself if it exists
520
+ if (lockTarget !== gsdDir) {
521
+ try {
522
+ if (existsSync(lockTarget)) rmSync(lockTarget, { recursive: true, force: true });
523
+ } catch {
524
+ // Non-fatal
525
+ }
526
+ }
493
527
 
494
528
  // Clean ALL registered lock paths (#1578) — lock files accumulate across
495
529
  // main project .gsd/, worktree .gsd/, and projects registry paths.
@@ -13,7 +13,7 @@
13
13
  * research identified as critical for skill quality.
14
14
  */
15
15
 
16
- import { existsSync, readFileSync, readdirSync } from "node:fs";
16
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
17
17
  import { join } from "node:path";
18
18
  import { homedir } from "node:os";
19
19
  import type { UnitMetrics, MetricsLedger } from "./metrics.js";
@@ -210,7 +210,7 @@ export function formatSkillDetail(basePath: string, skillName: string): string {
210
210
  // Check for SKILL.md existence
211
211
  const skillPath = join(homedir(), ".agents", "skills", skillName, "SKILL.md");
212
212
  if (existsSync(skillPath)) {
213
- const stat = require("node:fs").statSync(skillPath);
213
+ const stat = statSync(skillPath);
214
214
  lines.push("");
215
215
  lines.push(`SKILL.md: ${skillPath}`);
216
216
  lines.push(`Last modified: ${stat.mtime.toISOString().slice(0, 10)}`);
@@ -0,0 +1,139 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+
7
+ import { resolvePreferredModelConfig } from "../auto-model-selection.js";
8
+
9
+ function makeTempDir(prefix: string): string {
10
+ return mkdtempSync(join(tmpdir(), prefix));
11
+ }
12
+
13
+ test("resolvePreferredModelConfig synthesizes heavy routing ceiling when models section is absent", () => {
14
+ const originalCwd = process.cwd();
15
+ const originalGsdHome = process.env.GSD_HOME;
16
+ const tempProject = makeTempDir("gsd-routing-project-");
17
+ const tempGsdHome = makeTempDir("gsd-routing-home-");
18
+
19
+ try {
20
+ mkdirSync(join(tempProject, ".gsd"), { recursive: true });
21
+ writeFileSync(
22
+ join(tempProject, ".gsd", "PREFERENCES.md"),
23
+ [
24
+ "---",
25
+ "dynamic_routing:",
26
+ " enabled: true",
27
+ " tier_models:",
28
+ " light: claude-haiku-4-5",
29
+ " standard: claude-sonnet-4-6",
30
+ " heavy: claude-opus-4-6",
31
+ "---",
32
+ ].join("\n"),
33
+ "utf-8",
34
+ );
35
+ process.env.GSD_HOME = tempGsdHome;
36
+ process.chdir(tempProject);
37
+
38
+ const config = resolvePreferredModelConfig("plan-slice", {
39
+ provider: "anthropic",
40
+ id: "claude-sonnet-4-6",
41
+ });
42
+
43
+ assert.deepEqual(config, {
44
+ primary: "claude-opus-4-6",
45
+ fallbacks: [],
46
+ });
47
+ } finally {
48
+ process.chdir(originalCwd);
49
+ if (originalGsdHome === undefined) delete process.env.GSD_HOME;
50
+ else process.env.GSD_HOME = originalGsdHome;
51
+ rmSync(tempProject, { recursive: true, force: true });
52
+ rmSync(tempGsdHome, { recursive: true, force: true });
53
+ }
54
+ });
55
+
56
+ test("resolvePreferredModelConfig falls back to auto start model when heavy tier is absent", () => {
57
+ const originalCwd = process.cwd();
58
+ const originalGsdHome = process.env.GSD_HOME;
59
+ const tempProject = makeTempDir("gsd-routing-project-");
60
+ const tempGsdHome = makeTempDir("gsd-routing-home-");
61
+
62
+ try {
63
+ mkdirSync(join(tempProject, ".gsd"), { recursive: true });
64
+ writeFileSync(
65
+ join(tempProject, ".gsd", "PREFERENCES.md"),
66
+ [
67
+ "---",
68
+ "dynamic_routing:",
69
+ " enabled: true",
70
+ " tier_models:",
71
+ " light: claude-haiku-4-5",
72
+ " standard: claude-sonnet-4-6",
73
+ "---",
74
+ ].join("\n"),
75
+ "utf-8",
76
+ );
77
+ process.env.GSD_HOME = tempGsdHome;
78
+ process.chdir(tempProject);
79
+
80
+ const config = resolvePreferredModelConfig("execute-task", {
81
+ provider: "openai",
82
+ id: "gpt-5.4",
83
+ });
84
+
85
+ assert.deepEqual(config, {
86
+ primary: "openai/gpt-5.4",
87
+ fallbacks: [],
88
+ });
89
+ } finally {
90
+ process.chdir(originalCwd);
91
+ if (originalGsdHome === undefined) delete process.env.GSD_HOME;
92
+ else process.env.GSD_HOME = originalGsdHome;
93
+ rmSync(tempProject, { recursive: true, force: true });
94
+ rmSync(tempGsdHome, { recursive: true, force: true });
95
+ }
96
+ });
97
+
98
+ test("resolvePreferredModelConfig keeps explicit phase models as the ceiling", () => {
99
+ const originalCwd = process.cwd();
100
+ const originalGsdHome = process.env.GSD_HOME;
101
+ const tempProject = makeTempDir("gsd-routing-project-");
102
+ const tempGsdHome = makeTempDir("gsd-routing-home-");
103
+
104
+ try {
105
+ mkdirSync(join(tempProject, ".gsd"), { recursive: true });
106
+ writeFileSync(
107
+ join(tempProject, ".gsd", "PREFERENCES.md"),
108
+ [
109
+ "---",
110
+ "models:",
111
+ " planning: claude-sonnet-4-6",
112
+ "dynamic_routing:",
113
+ " enabled: true",
114
+ " tier_models:",
115
+ " heavy: claude-opus-4-6",
116
+ "---",
117
+ ].join("\n"),
118
+ "utf-8",
119
+ );
120
+ process.env.GSD_HOME = tempGsdHome;
121
+ process.chdir(tempProject);
122
+
123
+ const config = resolvePreferredModelConfig("plan-slice", {
124
+ provider: "anthropic",
125
+ id: "claude-opus-4-6",
126
+ });
127
+
128
+ assert.deepEqual(config, {
129
+ primary: "claude-sonnet-4-6",
130
+ fallbacks: [],
131
+ });
132
+ } finally {
133
+ process.chdir(originalCwd);
134
+ if (originalGsdHome === undefined) delete process.env.GSD_HOME;
135
+ else process.env.GSD_HOME = originalGsdHome;
136
+ rmSync(tempProject, { recursive: true, force: true });
137
+ rmSync(tempGsdHome, { recursive: true, force: true });
138
+ }
139
+ });
@@ -0,0 +1,28 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+
6
+ const sourcePath = join(import.meta.dirname, "..", "auto-start.ts");
7
+ const source = readFileSync(sourcePath, "utf-8");
8
+
9
+ test("bootstrapAutoSession snapshots ctx.model before guided-flow entry (#2829)", () => {
10
+ const snapshotIdx = source.indexOf("const startModelSnapshot = ctx.model");
11
+ assert.ok(snapshotIdx > -1, "auto-start.ts should snapshot ctx.model at bootstrap start");
12
+
13
+ const firstDiscussIdx = source.indexOf('await showSmartEntry(ctx, pi, base, { step: requestedStepMode });');
14
+ assert.ok(firstDiscussIdx > -1, "auto-start.ts should route through showSmartEntry during guided flow");
15
+
16
+ assert.ok(
17
+ snapshotIdx < firstDiscussIdx,
18
+ "auto-start.ts must capture the start model before guided-flow can mutate ctx.model",
19
+ );
20
+ });
21
+
22
+ test("bootstrapAutoSession restores autoModeStartModel from the early snapshot (#2829)", () => {
23
+ const assignmentIdx = source.indexOf("s.autoModeStartModel = {");
24
+ assert.ok(assignmentIdx > -1, "auto-start.ts should assign autoModeStartModel");
25
+
26
+ const snapshotRefIdx = source.indexOf("provider: startModelSnapshot.provider", assignmentIdx);
27
+ assert.ok(snapshotRefIdx > -1, "autoModeStartModel should be restored from startModelSnapshot");
28
+ });
@@ -31,7 +31,7 @@ import {
31
31
  isInAutoWorktree,
32
32
  getAutoWorktreeOriginalBase,
33
33
  mergeMilestoneToMain,
34
- } from "../auto-worktree.ts";
34
+ } from "../../auto-worktree.ts";
35
35
 
36
36
  const __dirname = dirname(fileURLToPath(import.meta.url));
37
37
 
@@ -78,9 +78,9 @@ function createMilestoneArtifacts(dir: string, mid: string): void {
78
78
  // ─── Source-level: verify the merge code exists in the "all complete" path ────
79
79
 
80
80
  test("auto-loop 'all milestones complete' path merges before stopping (#962)", () => {
81
- const loopSrc = readFileSync(join(__dirname, "..", "auto", "phases.ts"), "utf-8");
81
+ const loopSrc = readFileSync(join(__dirname, "../..", "auto", "phases.ts"), "utf-8");
82
82
  const resolverSrc = readFileSync(
83
- join(__dirname, "..", "worktree-resolver.ts"),
83
+ join(__dirname, "../..", "worktree-resolver.ts"),
84
84
  "utf-8",
85
85
  );
86
86
 
@@ -9,7 +9,7 @@ import { join } from "node:path";
9
9
  import { tmpdir } from "node:os";
10
10
  import test from "node:test";
11
11
  import assert from "node:assert/strict";
12
- import { runGSDDoctor } from "../doctor.ts";
12
+ import { runGSDDoctor } from "../../doctor.ts";
13
13
 
14
14
  function makeTmp(name: string): string {
15
15
  const dir = join(tmpdir(), `atomic-closeout-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
@@ -4,7 +4,7 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
 
7
- import { runGSDDoctor, selectDoctorScope, filterDoctorIssues } from "../doctor.js";
7
+ import { runGSDDoctor, selectDoctorScope, filterDoctorIssues } from "../../doctor.js";
8
8
 
9
9
  test("auto-preflight scopes to active milestone, ignoring historical", async (t) => {
10
10
  const tmpBase = mkdtempSync(join(tmpdir(), "gsd-auto-preflight-test-"));
@@ -11,19 +11,19 @@ import {
11
11
  diagnoseExpectedArtifact,
12
12
  buildLoopRemediationSteps,
13
13
  hasImplementationArtifacts,
14
- } from "../auto-recovery.ts";
15
- import { parseRoadmap, parsePlan } from "../parsers-legacy.ts";
16
- import { parseTaskPlanFile, clearParseCache } from "../files.ts";
17
- import { invalidateAllCaches } from "../cache.ts";
18
- import { deriveState, invalidateStateCache } from "../state.ts";
14
+ } from "../../auto-recovery.ts";
15
+ import { parseRoadmap, parsePlan } from "../../parsers-legacy.ts";
16
+ import { parseTaskPlanFile, clearParseCache } from "../../files.ts";
17
+ import { invalidateAllCaches } from "../../cache.ts";
18
+ import { deriveState, invalidateStateCache } from "../../state.ts";
19
19
  import {
20
20
  openDatabase,
21
21
  closeDatabase,
22
22
  insertMilestone,
23
23
  insertSlice,
24
24
  insertTask,
25
- } from "../gsd-db.ts";
26
- import { renderPlanFromDb } from "../markdown-renderer.ts";
25
+ } from "../../gsd-db.ts";
26
+ import { renderPlanFromDb } from "../../markdown-renderer.ts";
27
27
 
28
28
  function makeTmpBase(): string {
29
29
  const base = join(tmpdir(), `gsd-test-${randomUUID()}`);
@@ -16,8 +16,8 @@ import assert from 'node:assert/strict';
16
16
  import { mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
17
17
  import { join } from 'node:path';
18
18
  import { tmpdir } from 'node:os';
19
- import { getManifestStatus } from '../files.ts';
20
- import { collectSecretsFromManifest } from '../../get-secrets-from-user.ts';
19
+ import { getManifestStatus } from '../../files.ts';
20
+ import { collectSecretsFromManifest } from '../../../get-secrets-from-user.ts';
21
21
 
22
22
  function makeTempDir(prefix: string): string {
23
23
  const dir = join(tmpdir(), `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
@@ -12,8 +12,8 @@ import { join } from "node:path";
12
12
  import { tmpdir } from "node:os";
13
13
  import { execSync } from "node:child_process";
14
14
 
15
- import { createAutoWorktree, mergeMilestoneToMain } from "../auto-worktree.ts";
16
- import { nativeMergeSquash } from "../native-git-bridge.ts";
15
+ import { createAutoWorktree, mergeMilestoneToMain } from "../../auto-worktree.ts";
16
+ import { nativeMergeSquash } from "../../native-git-bridge.ts";
17
17
 
18
18
  function run(cmd: string, cwd: string): string {
19
19
  return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
@@ -88,7 +88,7 @@ test("#2151 bug 1: auto-stash unblocks merge when unrelated files are dirty", ()
88
88
  });
89
89
 
90
90
  test("#2151 bug 2: nativeMergeSquash returns dirty filenames", async () => {
91
- const { nativeMergeSquash } = await import("../native-git-bridge.ts");
91
+ const { nativeMergeSquash } = await import("../../native-git-bridge.ts");
92
92
  const repo = createTempRepo();
93
93
  try {
94
94
  run("git checkout -b milestone/M210", repo);
@@ -21,9 +21,9 @@ import {
21
21
  createAutoWorktree,
22
22
  mergeMilestoneToMain,
23
23
  getAutoWorktreeOriginalBase,
24
- } from "../auto-worktree.ts";
25
- import { getSliceBranchName } from "../worktree.ts";
26
- import { nativeMergeSquash } from "../native-git-bridge.ts";
24
+ } from "../../auto-worktree.ts";
25
+ import { getSliceBranchName } from "../../worktree.ts";
26
+ import { nativeMergeSquash } from "../../native-git-bridge.ts";
27
27
 
28
28
  function run(cmd: string, cwd: string): string {
29
29
  // Safe: all inputs are hardcoded test strings, not user input
@@ -329,7 +329,7 @@ describe("auto-worktree-milestone-merge", { timeout: 300_000 }, () => {
329
329
  });
330
330
 
331
331
  test("#1738 bug 1: nativeMergeSquash detects dirty working tree", async () => {
332
- const { nativeMergeSquash } = await import("../native-git-bridge.ts");
332
+ const { nativeMergeSquash } = await import("../../native-git-bridge.ts");
333
333
  const repo = freshRepo();
334
334
 
335
335
  run("git checkout -b milestone/M070", repo);
@@ -20,7 +20,7 @@ import {
20
20
  enterAutoWorktree,
21
21
  getAutoWorktreeOriginalBase,
22
22
  getActiveAutoWorktreeContext,
23
- } from "../auto-worktree.ts";
23
+ } from "../../auto-worktree.ts";
24
24
 
25
25
  // Note: execSync is used intentionally in tests for git operations with
26
26
  // controlled, hardcoded inputs (no user input). This is safe and matches
@@ -150,7 +150,7 @@ describe("auto-worktree lifecycle", () => {
150
150
  run("git commit -m \"add milestone\"", tempDir);
151
151
 
152
152
  // Import createWorktree directly for manual worktree
153
- const { createWorktree } = await import("../worktree-manager.ts");
153
+ const { createWorktree } = await import("../../worktree-manager.ts");
154
154
 
155
155
  // Create manual worktree (uses worktree/<name> branch)
156
156
  const manualWt = createWorktree(tempDir, "feature-x");
@@ -164,7 +164,7 @@ describe("auto-worktree lifecycle", () => {
164
164
 
165
165
  // Cleanup both
166
166
  teardownAutoWorktree(tempDir, "M003");
167
- const { removeWorktree } = await import("../worktree-manager.ts");
167
+ const { removeWorktree } = await import("../../worktree-manager.ts");
168
168
  removeWorktree(tempDir, "feature-x");
169
169
  });
170
170
 
@@ -190,7 +190,7 @@ describe("auto-worktree lifecycle", () => {
190
190
  run("git add .", tempDir);
191
191
  run("git commit -m \"add milestone\"", tempDir);
192
192
 
193
- const { GitServiceImpl } = await import("../git-service.ts");
193
+ const { GitServiceImpl } = await import("../../git-service.ts");
194
194
 
195
195
  // Create worktree
196
196
  const wtPath = createAutoWorktree(tempDir, "M005");
@@ -215,7 +215,7 @@ describe("auto-worktree lifecycle", () => {
215
215
  run("git commit -m \"add milestone\"", tempDir);
216
216
 
217
217
  // Simulate a crash leaving a stale directory with no .git file.
218
- const { worktreePath } = await import("../worktree-manager.ts");
218
+ const { worktreePath } = await import("../../worktree-manager.ts");
219
219
  const staleDir = worktreePath(tempDir, "M010");
220
220
  mkdirSync(staleDir, { recursive: true });
221
221
  writeFileSync(join(staleDir, "orphan.txt"), "stale leftover\n");
@@ -12,7 +12,7 @@
12
12
  import { describe, it } from "node:test";
13
13
  import assert from "node:assert/strict";
14
14
 
15
- import { computeBudgets } from "../context-budget.js";
15
+ import { computeBudgets } from "../../context-budget.js";
16
16
 
17
17
  // ─── Pure threshold / pipeline tests ──────────────────────────────────────────
18
18
  // These test the budget engine outputs that the continue-here monitor relies on.
@@ -164,7 +164,7 @@ describe("continue-here", () => {
164
164
  describe("continueHereFired runtime record field", () => {
165
165
  it("AutoUnitRuntimeRecord includes continueHereFired with default false", async (t) => {
166
166
  // Import writeUnitRuntimeRecord to verify the field is present and defaults
167
- const { writeUnitRuntimeRecord, readUnitRuntimeRecord, clearUnitRuntimeRecord } = await import("../unit-runtime.js");
167
+ const { writeUnitRuntimeRecord, readUnitRuntimeRecord, clearUnitRuntimeRecord } = await import("../../unit-runtime.js");
168
168
  const fs = await import("node:fs");
169
169
  const path = await import("node:path");
170
170
  const os = await import("node:os");
@@ -202,7 +202,7 @@ describe("continue-here", () => {
202
202
 
203
203
  describe("context-pressure monitor integration", () => {
204
204
  it("should fire wrap-up when context >= threshold and mark continueHereFired", async (t) => {
205
- const { writeUnitRuntimeRecord, readUnitRuntimeRecord, clearUnitRuntimeRecord } = await import("../unit-runtime.js");
205
+ const { writeUnitRuntimeRecord, readUnitRuntimeRecord, clearUnitRuntimeRecord } = await import("../../unit-runtime.js");
206
206
  const fs = await import("node:fs");
207
207
  const path = await import("node:path");
208
208
  const os = await import("node:os");
@@ -10,7 +10,7 @@ import { join } from "node:path";
10
10
  import { tmpdir } from "node:os";
11
11
  import test from "node:test";
12
12
  import assert from "node:assert/strict";
13
- import { runGSDDoctor } from "../doctor.ts";
13
+ import { runGSDDoctor } from "../../doctor.ts";
14
14
 
15
15
  function makeTmp(name: string): string {
16
16
  const dir = join(tmpdir(), `doctor-deferral-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
@@ -10,7 +10,7 @@ import assert from "node:assert/strict";
10
10
  import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
11
11
  import { join } from "node:path";
12
12
  import { tmpdir } from "node:os";
13
- import { runGSDDoctor } from "../doctor.js";
13
+ import { runGSDDoctor } from "../../doctor.js";
14
14
 
15
15
  test("doctor fix=true sanitizes em-dash in milestone title", async (t) => {
16
16
  const tmpBase = mkdtempSync(join(tmpdir(), "gsd-doctor-delim-"));
@@ -4,8 +4,8 @@ import { mkdtempSync, mkdirSync, rmSync, writeFileSync, existsSync } from "node:
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
 
7
- import { runGSDDoctor } from "../doctor.js";
8
- import { formatDoctorReportJson } from "../doctor-format.js";
7
+ import { runGSDDoctor } from "../../doctor.js";
8
+ import { formatDoctorReportJson } from "../../doctor-format.js";
9
9
  // ── Helpers ─────────────────────────────────────────────────────────────────
10
10
 
11
11
  function makeBase(): { base: string; gsd: string; mDir: string } {
@@ -230,7 +230,7 @@ describe('doctor-enhancements', async () => {
230
230
  const historyPath = join(gsd, "doctor-history.jsonl");
231
231
  assert.ok(existsSync(historyPath), "doctor-history.jsonl is created after run");
232
232
 
233
- const { readDoctorHistory } = await import("../doctor.js");
233
+ const { readDoctorHistory } = await import("../../doctor.js");
234
234
  const history = await readDoctorHistory(base);
235
235
  assert.ok(history.length >= 1, "history has at least one entry");
236
236
  assert.ok(typeof history[0]?.ts === "string", "history entry has ts field");
@@ -20,7 +20,7 @@ import {
20
20
  runEnvironmentChecks,
21
21
  environmentResultsToDoctorIssues,
22
22
  checkEnvironmentHealth,
23
- } from "../doctor-environment.ts";
23
+ } from "../../doctor-environment.ts";
24
24
  /** Create a directory tree with files. */
25
25
  function createDir(files: Record<string, string> = {}): string {
26
26
  const dir = mkdtempSync(join(tmpdir(), "gsd-wt-env-"));
@@ -26,7 +26,7 @@ import {
26
26
  formatEnvironmentReport,
27
27
  checkEnvironmentHealth,
28
28
  type EnvironmentCheckResult,
29
- } from "../doctor-environment.ts";
29
+ } from "../../doctor-environment.ts";
30
30
  function createProjectDir(files: Record<string, string> = {}): string {
31
31
  const dir = mkdtempSync(join(tmpdir(), "gsd-env-test-"));
32
32
  for (const [name, content] of Object.entries(files)) {
@@ -14,8 +14,8 @@ import { join } from "node:path";
14
14
  import { tmpdir } from "node:os";
15
15
  import test from "node:test";
16
16
  import assert from "node:assert/strict";
17
- import { runGSDDoctor } from "../doctor.ts";
18
- import { closeDatabase } from "../gsd-db.ts";
17
+ import { runGSDDoctor } from "../../doctor.ts";
18
+ import { closeDatabase } from "../../gsd-db.ts";
19
19
 
20
20
  function makeTmp(name: string): string {
21
21
  const dir = join(tmpdir(), `doctor-fixlevel-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
@@ -15,7 +15,7 @@ import { join } from "node:path";
15
15
  import { tmpdir } from "node:os";
16
16
  import { execSync } from "node:child_process";
17
17
 
18
- import { runGSDDoctor } from "../doctor.ts";
18
+ import { runGSDDoctor } from "../../doctor.ts";
19
19
  function run(cmd: string, cwd: string): string {
20
20
  return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
21
21
  }
@@ -23,7 +23,7 @@ import {
23
23
  checkHealEscalation,
24
24
  resetProactiveHealing,
25
25
  formatHealthSummary,
26
- } from "../doctor-proactive.ts";
26
+ } from "../../doctor-proactive.ts";
27
27
  function run(cmd: string, cwd: string): string {
28
28
  return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
29
29
  }
@@ -12,7 +12,7 @@ import { join } from "node:path";
12
12
  import { tmpdir } from "node:os";
13
13
  import test from "node:test";
14
14
  import assert from "node:assert/strict";
15
- import { runGSDDoctor } from "../doctor.ts";
15
+ import { runGSDDoctor } from "../../doctor.ts";
16
16
 
17
17
  function makeTmp(name: string): string {
18
18
  const dir = join(tmpdir(), `doctor-roadmap-summary-${name}-${Date.now()}-${Math.random().toString(36).slice(2)}`);
@@ -14,7 +14,7 @@ import { join } from "node:path";
14
14
  import { tmpdir } from "node:os";
15
15
  import { execSync } from "node:child_process";
16
16
 
17
- import { runGSDDoctor } from "../doctor.ts";
17
+ import { runGSDDoctor } from "../../doctor.ts";
18
18
  function run(cmd: string, cwd: string): string {
19
19
  return execSync(cmd, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" }).trim();
20
20
  }
@@ -4,7 +4,7 @@ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync, existsSync
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
 
7
- import { formatDoctorReport, runGSDDoctor, summarizeDoctorIssues, filterDoctorIssues, selectDoctorScope, validateTitle } from "../doctor.js";
7
+ import { formatDoctorReport, runGSDDoctor, summarizeDoctorIssues, filterDoctorIssues, selectDoctorScope, validateTitle } from "../../doctor.js";
8
8
  const tmpBase = mkdtempSync(join(tmpdir(), "gsd-doctor-test-"));
9
9
  const gsd = join(tmpBase, ".gsd");
10
10
  const mDir = join(gsd, "milestones", "M001");