gsd-pi 2.37.1 → 2.38.0-dev.63ad7e5

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/README.md +1 -1
  2. package/dist/app-paths.js +1 -1
  3. package/dist/cli.js +9 -0
  4. package/dist/extension-discovery.d.ts +5 -3
  5. package/dist/extension-discovery.js +14 -9
  6. package/dist/extension-registry.js +2 -2
  7. package/dist/onboarding.js +1 -0
  8. package/dist/remote-questions-config.js +2 -2
  9. package/dist/resources/extensions/browser-tools/package.json +3 -1
  10. package/dist/resources/extensions/cmux/index.js +55 -1
  11. package/dist/resources/extensions/context7/package.json +1 -1
  12. package/dist/resources/extensions/env-utils.js +29 -0
  13. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  14. package/dist/resources/extensions/google-search/package.json +3 -1
  15. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  16. package/dist/resources/extensions/gsd/auto-dispatch.js +74 -9
  17. package/dist/resources/extensions/gsd/auto-loop.js +68 -97
  18. package/dist/resources/extensions/gsd/auto-post-unit.js +87 -69
  19. package/dist/resources/extensions/gsd/auto-prompts.js +98 -33
  20. package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
  21. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  22. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  23. package/dist/resources/extensions/gsd/auto.js +143 -96
  24. package/dist/resources/extensions/gsd/captures.js +9 -1
  25. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  26. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  27. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  28. package/dist/resources/extensions/gsd/commands.js +22 -2
  29. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  30. package/dist/resources/extensions/gsd/detection.js +1 -2
  31. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  32. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  33. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  34. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  35. package/dist/resources/extensions/gsd/doctor-providers.js +62 -12
  36. package/dist/resources/extensions/gsd/doctor.js +184 -11
  37. package/dist/resources/extensions/gsd/export.js +1 -1
  38. package/dist/resources/extensions/gsd/files.js +43 -2
  39. package/dist/resources/extensions/gsd/forensics.js +1 -1
  40. package/dist/resources/extensions/gsd/index.js +2 -1
  41. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  42. package/dist/resources/extensions/gsd/observability-validator.js +24 -0
  43. package/dist/resources/extensions/gsd/package.json +1 -1
  44. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  45. package/dist/resources/extensions/gsd/preferences-types.js +2 -2
  46. package/dist/resources/extensions/gsd/preferences-validation.js +43 -11
  47. package/dist/resources/extensions/gsd/preferences.js +5 -5
  48. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  49. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
  50. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  51. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  52. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  54. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  55. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  56. package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
  57. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  58. package/dist/resources/extensions/gsd/reactive-graph.js +227 -0
  59. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  60. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  61. package/dist/resources/extensions/gsd/state.js +1 -1
  62. package/dist/resources/extensions/gsd/templates/task-plan.md +11 -3
  63. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  64. package/dist/resources/extensions/gsd/worktree.js +35 -16
  65. package/dist/resources/extensions/remote-questions/status.js +2 -1
  66. package/dist/resources/extensions/remote-questions/store.js +2 -1
  67. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  68. package/dist/resources/extensions/subagent/index.js +12 -3
  69. package/dist/resources/extensions/subagent/isolation.js +2 -1
  70. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  71. package/dist/resources/extensions/universal-config/package.json +1 -1
  72. package/dist/welcome-screen.d.ts +12 -0
  73. package/dist/welcome-screen.js +53 -0
  74. package/package.json +2 -1
  75. package/packages/pi-ai/dist/env-api-keys.js +13 -0
  76. package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
  77. package/packages/pi-ai/dist/models.generated.d.ts +172 -0
  78. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  79. package/packages/pi-ai/dist/models.generated.js +172 -0
  80. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  81. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
  82. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
  83. package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
  84. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
  85. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
  86. package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
  87. package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
  88. package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
  89. package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
  90. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  91. package/packages/pi-ai/dist/providers/anthropic.js +47 -764
  92. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  93. package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
  94. package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
  95. package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
  96. package/packages/pi-ai/dist/types.d.ts +2 -2
  97. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  98. package/packages/pi-ai/dist/types.js.map +1 -1
  99. package/packages/pi-ai/package.json +1 -0
  100. package/packages/pi-ai/src/env-api-keys.ts +14 -0
  101. package/packages/pi-ai/src/models.generated.ts +172 -0
  102. package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
  103. package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
  104. package/packages/pi-ai/src/providers/anthropic.ts +76 -868
  105. package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
  106. package/packages/pi-ai/src/types.ts +2 -0
  107. package/packages/pi-coding-agent/dist/core/model-resolver.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
  109. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  111. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  112. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  113. package/packages/pi-coding-agent/package.json +1 -1
  114. package/packages/pi-coding-agent/src/core/model-resolver.ts +1 -0
  115. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  116. package/pkg/package.json +1 -1
  117. package/src/resources/extensions/cmux/index.ts +57 -1
  118. package/src/resources/extensions/env-utils.ts +31 -0
  119. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  120. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  121. package/src/resources/extensions/gsd/auto-dispatch.ts +99 -8
  122. package/src/resources/extensions/gsd/auto-loop.ts +88 -133
  123. package/src/resources/extensions/gsd/auto-post-unit.ts +64 -40
  124. package/src/resources/extensions/gsd/auto-prompts.ts +132 -36
  125. package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
  126. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  127. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  128. package/src/resources/extensions/gsd/auto.ts +139 -101
  129. package/src/resources/extensions/gsd/captures.ts +10 -1
  130. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  131. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  132. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  133. package/src/resources/extensions/gsd/commands.ts +24 -2
  134. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  135. package/src/resources/extensions/gsd/detection.ts +2 -2
  136. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  137. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  138. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  139. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  140. package/src/resources/extensions/gsd/doctor-providers.ts +64 -10
  141. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  142. package/src/resources/extensions/gsd/doctor.ts +177 -13
  143. package/src/resources/extensions/gsd/export.ts +1 -1
  144. package/src/resources/extensions/gsd/files.ts +47 -2
  145. package/src/resources/extensions/gsd/forensics.ts +1 -1
  146. package/src/resources/extensions/gsd/index.ts +3 -1
  147. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  148. package/src/resources/extensions/gsd/observability-validator.ts +27 -0
  149. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  150. package/src/resources/extensions/gsd/preferences-types.ts +5 -5
  151. package/src/resources/extensions/gsd/preferences-validation.ts +42 -11
  152. package/src/resources/extensions/gsd/preferences.ts +5 -5
  153. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  154. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
  155. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  156. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  157. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  158. package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -1
  159. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  160. package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
  161. package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
  162. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  163. package/src/resources/extensions/gsd/reactive-graph.ts +289 -0
  164. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  165. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  166. package/src/resources/extensions/gsd/state.ts +1 -1
  167. package/src/resources/extensions/gsd/templates/task-plan.md +11 -3
  168. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  169. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +11 -31
  170. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  171. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  172. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +191 -3
  173. package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
  174. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  175. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  176. package/src/resources/extensions/gsd/tests/reactive-executor.test.ts +511 -0
  177. package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -0
  178. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  179. package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
  180. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  181. package/src/resources/extensions/gsd/types.ts +43 -1
  182. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  183. package/src/resources/extensions/gsd/worktree.ts +35 -15
  184. package/src/resources/extensions/remote-questions/status.ts +3 -1
  185. package/src/resources/extensions/remote-questions/store.ts +3 -1
  186. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  187. package/src/resources/extensions/subagent/index.ts +12 -3
  188. package/src/resources/extensions/subagent/isolation.ts +3 -1
  189. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  190. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  191. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  192. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  193. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  194. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  195. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  196. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  197. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  198. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  199. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  200. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  201. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -1,7 +1,7 @@
1
- import { existsSync, mkdirSync } from "node:fs";
1
+ import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
4
- import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
4
+ import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath } from "./paths.js";
5
5
  import { deriveState, isMilestoneComplete } from "./state.js";
6
6
  import { invalidateAllCaches } from "./cache.js";
7
7
  import { loadEffectiveGSDPreferences } from "./preferences.js";
@@ -9,7 +9,7 @@ import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
9
9
  import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
10
10
  import { checkEnvironmentHealth } from "./doctor-environment.js";
11
11
  import { runProviderChecks } from "./doctor-providers.js";
12
- export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt } from "./doctor-format.js";
12
+ export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt, formatDoctorReportJson } from "./doctor-format.js";
13
13
  export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport } from "./doctor-environment.js";
14
14
  export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport } from "./progress-score.js";
15
15
  /**
@@ -324,10 +324,68 @@ export async function selectDoctorScope(basePath, requestedScope) {
324
324
  }
325
325
  return state.registry[0]?.id;
326
326
  }
327
+ // ── Helper: circular dependency detection ──────────────────────────────────
328
+ function detectCircularDependencies(slices) {
329
+ const known = new Set(slices.map(s => s.id));
330
+ const adj = new Map();
331
+ for (const s of slices)
332
+ adj.set(s.id, s.depends.filter(d => known.has(d)));
333
+ const state = new Map();
334
+ for (const s of slices)
335
+ state.set(s.id, "unvisited");
336
+ const cycles = [];
337
+ function dfs(id, path) {
338
+ const st = state.get(id);
339
+ if (st === "done")
340
+ return;
341
+ if (st === "visiting") {
342
+ cycles.push([...path.slice(path.indexOf(id)), id]);
343
+ return;
344
+ }
345
+ state.set(id, "visiting");
346
+ for (const dep of adj.get(id) ?? [])
347
+ dfs(dep, [...path, id]);
348
+ state.set(id, "done");
349
+ }
350
+ for (const s of slices)
351
+ if (state.get(s.id) === "unvisited")
352
+ dfs(s.id, []);
353
+ return cycles;
354
+ }
355
+ async function appendDoctorHistory(basePath, report) {
356
+ try {
357
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
358
+ const entry = JSON.stringify({
359
+ ts: new Date().toISOString(),
360
+ ok: report.ok,
361
+ errors: report.issues.filter(i => i.severity === "error").length,
362
+ warnings: report.issues.filter(i => i.severity === "warning").length,
363
+ fixes: report.fixesApplied.length,
364
+ codes: [...new Set(report.issues.map(i => i.code))],
365
+ });
366
+ const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
367
+ await saveFile(historyPath, existing + entry + "\n");
368
+ }
369
+ catch { /* non-fatal */ }
370
+ }
371
+ /** Read the last N doctor history entries. Returns most-recent-first. */
372
+ export async function readDoctorHistory(basePath, lastN = 50) {
373
+ try {
374
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
375
+ if (!existsSync(historyPath))
376
+ return [];
377
+ const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
378
+ return lines.slice(-lastN).reverse().map(l => JSON.parse(l));
379
+ }
380
+ catch {
381
+ return [];
382
+ }
383
+ }
327
384
  export async function runGSDDoctor(basePath, options) {
328
385
  const issues = [];
329
386
  const fixesApplied = [];
330
387
  const fix = options?.fix === true;
388
+ const dryRun = options?.dryRun === true;
331
389
  const fixLevel = options?.fixLevel ?? "all";
332
390
  // Issue codes that represent completion state transitions — creating summary
333
391
  // stubs, marking slices/milestones done in the roadmap. These belong to the
@@ -336,12 +394,18 @@ export async function runGSDDoctor(basePath, options) {
336
394
  // detected and reported but never auto-fixed.
337
395
  /** Whether a given issue code should be auto-fixed at the current fixLevel. */
338
396
  const shouldFix = (code) => {
339
- if (!fix)
397
+ if (!fix || dryRun)
340
398
  return false;
341
399
  if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))
342
400
  return false;
343
401
  return true;
344
402
  };
403
+ /** Log a dry-run "would fix" entry when fix=true but dryRun=true. */
404
+ const dryRunCanFix = (code, message) => {
405
+ if (dryRun && fix && !(fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))) {
406
+ fixesApplied.push(`[dry-run] would fix: ${message}`);
407
+ }
408
+ };
345
409
  const prefs = loadEffectiveGSDPreferences();
346
410
  if (prefs) {
347
411
  const prefIssues = validatePreferenceShape(prefs.preferences);
@@ -357,18 +421,30 @@ export async function runGSDDoctor(basePath, options) {
357
421
  });
358
422
  }
359
423
  }
360
- // Git health checks (orphaned worktrees, stale branches, corrupt merge state, tracked runtime files)
424
+ // Git health checks timed
425
+ const t0git = Date.now();
361
426
  const isolationMode = options?.isolationMode ??
362
427
  (prefs?.preferences?.git?.isolation === "none" ? "none" :
363
428
  prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
364
429
  await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
365
- // Runtime health checks (crash locks, completed-units, hook state, activity logs, STATE.md, gitignore)
430
+ const gitMs = Date.now() - t0git;
431
+ // Runtime health checks — timed
432
+ const t0runtime = Date.now();
366
433
  await checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix);
367
- // Environment health checks (#1221: missing tools, port conflicts, stale deps, disk space)
368
- await checkEnvironmentHealth(basePath, issues, { includeRemote: !options?.scope });
434
+ const runtimeMs = Date.now() - t0runtime;
435
+ // Environment health checks timed
436
+ const t0env = Date.now();
437
+ await checkEnvironmentHealth(basePath, issues, {
438
+ includeRemote: !options?.scope,
439
+ includeBuild: options?.includeBuild,
440
+ includeTests: options?.includeTests,
441
+ });
442
+ const envMs = Date.now() - t0env;
369
443
  const milestonesPath = milestonesDir(basePath);
370
444
  if (!existsSync(milestonesPath)) {
371
- return { ok: issues.every(issue => issue.severity !== "error"), basePath, issues, fixesApplied };
445
+ const report = { ok: issues.every(i => i.severity !== "error"), basePath, issues, fixesApplied, timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: 0 } };
446
+ await appendDoctorHistory(basePath, report);
447
+ return report;
372
448
  }
373
449
  const requirementsPath = resolveGsdRootFile(basePath, "REQUIREMENTS");
374
450
  const requirementsContent = await loadFile(requirementsPath);
@@ -432,6 +508,46 @@ export async function runGSDDoctor(basePath, options) {
432
508
  if (!roadmapContent)
433
509
  continue;
434
510
  const roadmap = parseRoadmap(roadmapContent);
511
+ // ── Circular dependency detection ──────────────────────────────────────
512
+ for (const cycle of detectCircularDependencies(roadmap.slices)) {
513
+ issues.push({
514
+ severity: "error",
515
+ code: "circular_slice_dependency",
516
+ scope: "milestone",
517
+ unitId: milestoneId,
518
+ message: `Circular dependency detected: ${cycle.join(" → ")}`,
519
+ file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
520
+ fixable: false,
521
+ });
522
+ }
523
+ // ── Orphaned slice directories ─────────────────────────────────────────
524
+ try {
525
+ const slicesDir = join(milestonePath, "slices");
526
+ if (existsSync(slicesDir)) {
527
+ const knownSliceIds = new Set(roadmap.slices.map(s => s.id));
528
+ for (const entry of readdirSync(slicesDir)) {
529
+ try {
530
+ if (!lstatSync(join(slicesDir, entry)).isDirectory())
531
+ continue;
532
+ }
533
+ catch {
534
+ continue;
535
+ }
536
+ if (!knownSliceIds.has(entry)) {
537
+ issues.push({
538
+ severity: "warning",
539
+ code: "orphaned_slice_directory",
540
+ scope: "milestone",
541
+ unitId: milestoneId,
542
+ message: `Directory "${entry}" exists in ${milestoneId}/slices/ but is not referenced in the roadmap`,
543
+ file: `${relMilestonePath(basePath, milestoneId)}/slices/${entry}`,
544
+ fixable: false,
545
+ });
546
+ }
547
+ }
548
+ }
549
+ }
550
+ catch { /* non-fatal */ }
435
551
  for (const slice of roadmap.slices) {
436
552
  const unitId = `${milestoneId}/${slice.id}`;
437
553
  if (options?.scope && !matchesScope(unitId, options.scope) && options.scope !== milestoneId)
@@ -502,6 +618,34 @@ export async function runGSDDoctor(basePath, options) {
502
618
  }
503
619
  continue;
504
620
  }
621
+ // ── Duplicate task IDs ───────────────────────────────────────────────
622
+ const taskIdCounts = new Map();
623
+ for (const task of plan.tasks)
624
+ taskIdCounts.set(task.id, (taskIdCounts.get(task.id) ?? 0) + 1);
625
+ for (const [taskId, count] of taskIdCounts) {
626
+ if (count > 1) {
627
+ issues.push({ severity: "error", code: "duplicate_task_id", scope: "slice", unitId,
628
+ message: `Task ID "${taskId}" appears ${count} times in ${slice.id}-PLAN.md — duplicate IDs cause dispatch failures`,
629
+ file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"), fixable: false });
630
+ }
631
+ }
632
+ // ── Task files on disk not in plan ────────────────────────────────────
633
+ try {
634
+ if (tasksDir) {
635
+ const planTaskIds = new Set(plan.tasks.map(t => t.id));
636
+ for (const f of readdirSync(tasksDir)) {
637
+ if (!f.endsWith("-SUMMARY.md"))
638
+ continue;
639
+ const diskTaskId = f.replace(/-SUMMARY\.md$/, "");
640
+ if (!planTaskIds.has(diskTaskId)) {
641
+ issues.push({ severity: "info", code: "task_file_not_in_plan", scope: "slice", unitId,
642
+ message: `Task summary "${f}" exists on disk but "${diskTaskId}" is not in ${slice.id}-PLAN.md`,
643
+ file: relTaskFile(basePath, milestoneId, slice.id, diskTaskId, "SUMMARY"), fixable: false });
644
+ }
645
+ }
646
+ }
647
+ }
648
+ catch { /* non-fatal */ }
505
649
  let allTasksDone = plan.tasks.length > 0;
506
650
  for (const task of plan.tasks) {
507
651
  const taskUnitId = `${unitId}/${task.id}`;
@@ -517,6 +661,7 @@ export async function runGSDDoctor(basePath, options) {
517
661
  file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"),
518
662
  fixable: true,
519
663
  });
664
+ dryRunCanFix("task_done_missing_summary", `create stub summary for ${taskUnitId}`);
520
665
  if (shouldFix("task_done_missing_summary")) {
521
666
  const stubPath = join(basePath, ".gsd", "milestones", milestoneId, "slices", slice.id, "tasks", `${task.id}-SUMMARY.md`);
522
667
  const stubContent = [
@@ -575,6 +720,22 @@ export async function runGSDDoctor(basePath, options) {
575
720
  }
576
721
  }
577
722
  }
723
+ // ── Future timestamp check ─────────────────────────────────────
724
+ if (task.done && hasSummary && summaryPath) {
725
+ try {
726
+ const rawSummary = await loadFile(summaryPath);
727
+ const m = rawSummary?.match(/^completed_at:\s*(.+)$/m);
728
+ if (m) {
729
+ const ts = new Date(m[1].trim());
730
+ if (!isNaN(ts.getTime()) && ts.getTime() > Date.now() + 24 * 60 * 60 * 1000) {
731
+ issues.push({ severity: "warning", code: "future_timestamp", scope: "task", unitId: taskUnitId,
732
+ message: `Task ${task.id} has completed_at "${m[1].trim()}" which is more than 24h in the future`,
733
+ file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"), fixable: false });
734
+ }
735
+ }
736
+ }
737
+ catch { /* non-fatal */ }
738
+ }
578
739
  allTasksDone = allTasksDone && task.done;
579
740
  }
580
741
  // Blocker-without-replan detection
@@ -604,6 +765,12 @@ export async function runGSDDoctor(basePath, options) {
604
765
  }
605
766
  }
606
767
  }
768
+ // ── Stale REPLAN: exists but all tasks done ────────────────────────
769
+ if (replanPath && allTasksDone) {
770
+ issues.push({ severity: "info", code: "stale_replan_file", scope: "slice", unitId,
771
+ message: `${slice.id} has a REPLAN.md but all tasks are done — REPLAN.md may be stale`,
772
+ file: relSliceFile(basePath, milestoneId, slice.id, "REPLAN"), fixable: false });
773
+ }
607
774
  const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
608
775
  const sliceUatPath = join(slicePath, `${slice.id}-UAT.md`);
609
776
  const hasSliceSummary = !!(sliceSummaryPath && await loadFile(sliceSummaryPath));
@@ -618,6 +785,7 @@ export async function runGSDDoctor(basePath, options) {
618
785
  file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
619
786
  fixable: true,
620
787
  });
788
+ dryRunCanFix("all_tasks_done_missing_slice_summary", `create placeholder summary for ${unitId}`);
621
789
  if (shouldFix("all_tasks_done_missing_slice_summary"))
622
790
  await ensureSliceSummaryStub(basePath, milestoneId, slice.id, fixesApplied);
623
791
  }
@@ -631,6 +799,7 @@ export async function runGSDDoctor(basePath, options) {
631
799
  file: `${relSlicePath(basePath, milestoneId, slice.id)}/${slice.id}-UAT.md`,
632
800
  fixable: true,
633
801
  });
802
+ dryRunCanFix("all_tasks_done_missing_slice_uat", `create placeholder UAT for ${unitId}`);
634
803
  if (shouldFix("all_tasks_done_missing_slice_uat"))
635
804
  await ensureSliceUatStub(basePath, milestoneId, slice.id, fixesApplied);
636
805
  }
@@ -644,6 +813,7 @@ export async function runGSDDoctor(basePath, options) {
644
813
  file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
645
814
  fixable: true,
646
815
  });
816
+ dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
647
817
  if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary" && issue.unitId === unitId))) {
648
818
  await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
649
819
  }
@@ -696,13 +866,16 @@ export async function runGSDDoctor(basePath, options) {
696
866
  });
697
867
  }
698
868
  }
699
- if (fix && fixesApplied.length > 0) {
869
+ if (fix && !dryRun && fixesApplied.length > 0) {
700
870
  await updateStateFile(basePath, fixesApplied);
701
871
  }
702
- return {
872
+ const report = {
703
873
  ok: issues.every(issue => issue.severity !== "error"),
704
874
  basePath,
705
875
  issues,
706
876
  fixesApplied,
877
+ timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: Math.max(0, Date.now() - t0env - envMs) },
707
878
  };
879
+ await appendDoctorHistory(basePath, report);
880
+ return report;
708
881
  }
@@ -5,7 +5,7 @@ import { join, basename } from "node:path";
5
5
  import { exec } from "node:child_process";
6
6
  import { getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice, aggregateByModel, formatCost, formatTokenCount, loadLedgerFromDisk, } from "./metrics.js";
7
7
  import { gsdRoot } from "./paths.js";
8
- import { formatDuration, fileLink } from "../shared/mod.js";
8
+ import { formatDuration, fileLink } from "../shared/format-utils.js";
9
9
  import { getErrorMessage } from "./error-utils.js";
10
10
  /**
11
11
  * Open a file in the user's default browser.
@@ -6,8 +6,8 @@ import { promises as fs } from 'node:fs';
6
6
  import { resolve } from 'node:path';
7
7
  import { atomicWriteAsync } from './atomic-write.js';
8
8
  import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
9
- import { findMilestoneIds } from './guided-flow.js';
10
- import { checkExistingEnvKeys } from '../get-secrets-from-user.js';
9
+ import { findMilestoneIds } from './milestone-ids.js';
10
+ import { checkExistingEnvKeys } from '../env-utils.js';
11
11
  import { parseRoadmapSlices } from './roadmap-slices.js';
12
12
  import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativeParseSummaryFile, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
13
13
  import { debugTime, debugCount } from './debug-logger.js';
@@ -629,6 +629,47 @@ export function countMustHavesMentionedInSummary(mustHaves, summaryContent) {
629
629
  }
630
630
  return count;
631
631
  }
632
+ // ─── Task Plan IO Extractor ────────────────────────────────────────────────
633
+ /**
634
+ * Extract input and output file paths from a task plan's `## Inputs` and
635
+ * `## Expected Output` sections. Looks for backtick-wrapped file paths on
636
+ * each line (e.g. `` `src/foo.ts` ``).
637
+ *
638
+ * Returns empty arrays for missing/empty sections — callers should treat
639
+ * tasks with no IO as ambiguous (sequential fallback trigger).
640
+ */
641
+ export function parseTaskPlanIO(content) {
642
+ const backtickPathRegex = /`([^`]+)`/g;
643
+ function extractPaths(sectionText) {
644
+ if (!sectionText)
645
+ return [];
646
+ const paths = [];
647
+ for (const line of sectionText.split("\n")) {
648
+ const trimmed = line.trim();
649
+ if (!trimmed || trimmed.startsWith("#"))
650
+ continue;
651
+ let match;
652
+ backtickPathRegex.lastIndex = 0;
653
+ while ((match = backtickPathRegex.exec(trimmed)) !== null) {
654
+ const candidate = match[1];
655
+ // Filter out things that look like code tokens rather than file paths
656
+ // (e.g. `true`, `false`, `npm run test`). A file path has at least one
657
+ // dot or slash.
658
+ if (candidate.includes("/") || candidate.includes(".")) {
659
+ paths.push(candidate);
660
+ }
661
+ }
662
+ }
663
+ return paths;
664
+ }
665
+ const [, body] = splitFrontmatter(content);
666
+ const inputSection = extractSection(body, "Inputs");
667
+ const outputSection = extractSection(body, "Expected Output");
668
+ return {
669
+ inputFiles: extractPaths(inputSection),
670
+ outputFiles: extractPaths(outputSection),
671
+ };
672
+ }
632
673
  /**
633
674
  * Extract the UAT type from a UAT file's raw content.
634
675
  *
@@ -21,7 +21,7 @@ import { deriveState } from "./state.js";
21
21
  import { isAutoActive } from "./auto.js";
22
22
  import { loadPrompt } from "./prompt-loader.js";
23
23
  import { gsdRoot } from "./paths.js";
24
- import { formatDuration } from "../shared/mod.js";
24
+ import { formatDuration } from "../shared/format-utils.js";
25
25
  import { getAutoWorktreePath } from "./auto-worktree.js";
26
26
  // ─── Entry Point ──────────────────────────────────────────────────────────────
27
27
  export async function handleForensics(args, ctx, pi) {
@@ -41,6 +41,7 @@ import { join } from "node:path";
41
41
  import { existsSync, readFileSync } from "node:fs";
42
42
  import { homedir } from "node:os";
43
43
  import { shortcutDesc } from "../shared/mod.js";
44
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
44
45
  import { Text } from "@gsd/pi-tui";
45
46
  import { pauseAutoForProviderError, classifyProviderError } from "./provider-error-pause.js";
46
47
  import { toPosixPath } from "../shared/mod.js";
@@ -52,7 +53,7 @@ import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../cmux/index.js"
52
53
  // Pi core natively supports AGENTS.md (with CLAUDE.md fallback) per directory.
53
54
  function warnDeprecatedAgentInstructions() {
54
55
  const paths = [
55
- join(homedir(), ".gsd", "agent-instructions.md"),
56
+ join(gsdHome, "agent-instructions.md"),
56
57
  join(process.cwd(), ".gsd", "agent-instructions.md"),
57
58
  ];
58
59
  for (const p of paths) {
@@ -2,7 +2,7 @@
2
2
  // Pure functions that take file content (string) and return typed data.
3
3
  // Zero Pi dependencies — uses only exported helpers from files.ts.
4
4
  import { splitFrontmatter, parseFrontmatterMap, extractBoldField } from '../files.js';
5
- import { normalizeStringArray } from '../../shared/mod.js';
5
+ import { normalizeStringArray } from '../../shared/format-utils.js';
6
6
  // Re-export PlanningProjectMeta — not in types.ts yet, use string for project field
7
7
  // Actually PlanningProjectMeta isn't in types.ts — project is stored as string | null.
8
8
  // We'll keep parseOldProject returning a simple shape.
@@ -209,6 +209,30 @@ export function validateTaskPlanContent(file, content) {
209
209
  }
210
210
  }
211
211
  }
212
+ // Rule: Inputs and Expected Output should contain backtick-wrapped file paths
213
+ const inputsSection = getSection(content, "Inputs", 2);
214
+ const outputSection = getSection(content, "Expected Output", 2);
215
+ const backtickPathPattern = /`[^`]*[./][^`]*`/;
216
+ if (outputSection === null || !backtickPathPattern.test(outputSection)) {
217
+ issues.push({
218
+ severity: "warning",
219
+ scope: "task-plan",
220
+ file,
221
+ ruleId: "missing_output_file_paths",
222
+ message: "Task plan `## Expected Output` is missing or has no backtick-wrapped file paths.",
223
+ suggestion: "List concrete output file paths in backticks (e.g. `src/types.ts`). These are machine-parsed to derive task dependencies.",
224
+ });
225
+ }
226
+ if (inputsSection !== null && inputsSection.trim().length > 0 && !backtickPathPattern.test(inputsSection)) {
227
+ issues.push({
228
+ severity: "info",
229
+ scope: "task-plan",
230
+ file,
231
+ ruleId: "missing_input_file_paths",
232
+ message: "Task plan `## Inputs` has content but no backtick-wrapped file paths.",
233
+ suggestion: "List input file paths in backticks (e.g. `src/config.json`). These are machine-parsed to derive task dependencies.",
234
+ });
235
+ }
212
236
  // ── Observability rules (gated by runtime relevance) ──
213
237
  const relevant = textSuggestsObservabilityRelevant(content);
214
238
  if (!relevant)
@@ -5,7 +5,7 @@
5
5
  "type": "module",
6
6
  "pi": {
7
7
  "extensions": [
8
- "./index.ts"
8
+ "./index.js"
9
9
  ]
10
10
  }
11
11
  }
@@ -260,18 +260,6 @@ export function resolveInlineLevel() {
260
260
  case "quality": return "full";
261
261
  }
262
262
  }
263
- /**
264
- * Resolve the compression strategy from the active token profile.
265
- * budget/balanced -> "compress", quality -> "truncate".
266
- * Explicit preference always wins.
267
- */
268
- export function resolveCompressionStrategy() {
269
- const prefs = loadEffectiveGSDPreferences();
270
- if (prefs?.preferences.compression_strategy)
271
- return prefs.preferences.compression_strategy;
272
- const profile = resolveEffectiveProfile();
273
- return profile === "quality" ? "truncate" : "compress";
274
- }
275
263
  /**
276
264
  * Resolve the context selection mode from the active token profile.
277
265
  * budget -> "smart", balanced/quality -> "full".
@@ -62,14 +62,14 @@ export const KNOWN_PREFERENCE_KEYS = new Set([
62
62
  "verification_auto_fix",
63
63
  "verification_max_retries",
64
64
  "search_provider",
65
- "compression_strategy",
66
65
  "context_selection",
67
66
  "widget_mode",
67
+ "reactive_execution",
68
68
  ]);
69
69
  /** Canonical list of all dispatch unit types. */
70
70
  export const KNOWN_UNIT_TYPES = [
71
71
  "research-milestone", "plan-milestone", "research-slice", "plan-slice",
72
- "execute-task", "complete-slice", "replan-slice", "reassess-roadmap",
72
+ "execute-task", "reactive-execute", "complete-slice", "replan-slice", "reassess-roadmap",
73
73
  "run-uat", "complete-milestone",
74
74
  ];
75
75
  export const SKILL_ACTIONS = new Set(["use", "prefer", "avoid"]);
@@ -6,7 +6,7 @@
6
6
  * together with any errors and warnings.
7
7
  */
8
8
  import { VALID_BRANCH_NAME } from "./git-service.js";
9
- import { normalizeStringArray } from "../shared/mod.js";
9
+ import { normalizeStringArray } from "../shared/format-utils.js";
10
10
  import { KNOWN_PREFERENCE_KEYS, KNOWN_UNIT_TYPES, SKILL_ACTIONS, } from "./preferences-types.js";
11
11
  const VALID_TOKEN_PROFILES = new Set(["budget", "balanced", "quality"]);
12
12
  export function validatePreferences(preferences) {
@@ -500,6 +500,48 @@ export function validatePreferences(preferences) {
500
500
  validated.parallel = parallel;
501
501
  }
502
502
  }
503
+ // ─── Reactive Execution ─────────────────────────────────────────────────
504
+ if (preferences.reactive_execution !== undefined) {
505
+ if (typeof preferences.reactive_execution === "object" && preferences.reactive_execution !== null) {
506
+ const re = preferences.reactive_execution;
507
+ const validRe = {};
508
+ if (re.enabled !== undefined) {
509
+ if (typeof re.enabled === "boolean")
510
+ validRe.enabled = re.enabled;
511
+ else
512
+ errors.push("reactive_execution.enabled must be a boolean");
513
+ }
514
+ if (re.max_parallel !== undefined) {
515
+ const mp = typeof re.max_parallel === "number" ? re.max_parallel : Number(re.max_parallel);
516
+ if (Number.isFinite(mp) && mp >= 1 && mp <= 8) {
517
+ validRe.max_parallel = Math.floor(mp);
518
+ }
519
+ else {
520
+ errors.push("reactive_execution.max_parallel must be a number between 1 and 8");
521
+ }
522
+ }
523
+ if (re.isolation_mode !== undefined) {
524
+ if (re.isolation_mode === "same-tree") {
525
+ validRe.isolation_mode = "same-tree";
526
+ }
527
+ else {
528
+ errors.push('reactive_execution.isolation_mode must be "same-tree"');
529
+ }
530
+ }
531
+ const knownReKeys = new Set(["enabled", "max_parallel", "isolation_mode"]);
532
+ for (const key of Object.keys(re)) {
533
+ if (!knownReKeys.has(key)) {
534
+ warnings.push(`unknown reactive_execution key "${key}" — ignored`);
535
+ }
536
+ }
537
+ if (Object.keys(validRe).length > 0) {
538
+ validated.reactive_execution = validRe;
539
+ }
540
+ }
541
+ else {
542
+ errors.push("reactive_execution must be an object");
543
+ }
544
+ }
503
545
  // ─── Verification Preferences ───────────────────────────────────────────
504
546
  if (preferences.verification_commands !== undefined) {
505
547
  if (Array.isArray(preferences.verification_commands)) {
@@ -665,16 +707,6 @@ export function validatePreferences(preferences) {
665
707
  errors.push("auto_report must be a boolean");
666
708
  }
667
709
  }
668
- // ─── Compression Strategy ───────────────────────────────────────────
669
- if (preferences.compression_strategy !== undefined) {
670
- const validStrategies = new Set(["truncate", "compress"]);
671
- if (typeof preferences.compression_strategy === "string" && validStrategies.has(preferences.compression_strategy)) {
672
- validated.compression_strategy = preferences.compression_strategy;
673
- }
674
- else {
675
- errors.push(`compression_strategy must be one of: truncate, compress`);
676
- }
677
- }
678
710
  // ─── Context Selection ──────────────────────────────────────────────
679
711
  if (preferences.context_selection !== undefined) {
680
712
  const validModes = new Set(["full", "smart"]);
@@ -12,9 +12,10 @@
12
12
  import { existsSync, readFileSync } from "node:fs";
13
13
  import { homedir } from "node:os";
14
14
  import { join } from "node:path";
15
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
15
16
  import { gsdRoot } from "./paths.js";
16
17
  import { parse as parseYaml } from "yaml";
17
- import { normalizeStringArray } from "../shared/mod.js";
18
+ import { normalizeStringArray } from "../shared/format-utils.js";
18
19
  import { resolveProfileDefaults as _resolveProfileDefaults } from "./preferences-models.js";
19
20
  import { MODE_DEFAULTS, } from "./preferences-types.js";
20
21
  import { validatePreferences } from "./preferences-validation.js";
@@ -24,16 +25,16 @@ export { validatePreferences } from "./preferences-validation.js";
24
25
  // ─── Re-exports: skills ─────────────────────────────────────────────────────
25
26
  export { resolveAllSkillReferences, resolveSkillDiscoveryMode, resolveSkillStalenessDays, } from "./preferences-skills.js";
26
27
  // ─── Re-exports: models ─────────────────────────────────────────────────────
27
- export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel, isTransientNetworkError, validateModelId, updatePreferencesModels, resolveDynamicRoutingConfig, resolveAutoSupervisorConfig, resolveProfileDefaults, resolveEffectiveProfile, resolveInlineLevel, resolveCompressionStrategy, resolveContextSelection, resolveSearchProviderFromPreferences, } from "./preferences-models.js";
28
+ export { resolveModelForUnit, resolveModelWithFallbacksForUnit, getNextFallbackModel, isTransientNetworkError, validateModelId, updatePreferencesModels, resolveDynamicRoutingConfig, resolveAutoSupervisorConfig, resolveProfileDefaults, resolveEffectiveProfile, resolveInlineLevel, resolveContextSelection, resolveSearchProviderFromPreferences, } from "./preferences-models.js";
28
29
  // ─── Path Constants & Getters ───────────────────────────────────────────────
29
- const GLOBAL_PREFERENCES_PATH = join(homedir(), ".gsd", "preferences.md");
30
+ const GLOBAL_PREFERENCES_PATH = join(gsdHome, "preferences.md");
30
31
  const LEGACY_GLOBAL_PREFERENCES_PATH = join(homedir(), ".pi", "agent", "gsd-preferences.md");
31
32
  function projectPreferencesPath() {
32
33
  return join(gsdRoot(process.cwd()), "preferences.md");
33
34
  }
34
35
  // Bootstrap in gitignore.ts historically created PREFERENCES.md (uppercase) by mistake.
35
36
  // Check uppercase as a fallback so those files aren't silently ignored.
36
- const GLOBAL_PREFERENCES_PATH_UPPERCASE = join(homedir(), ".gsd", "PREFERENCES.md");
37
+ const GLOBAL_PREFERENCES_PATH_UPPERCASE = join(gsdHome, "PREFERENCES.md");
37
38
  function projectPreferencesPathUppercase() {
38
39
  return join(gsdRoot(process.cwd()), "PREFERENCES.md");
39
40
  }
@@ -199,7 +200,6 @@ function mergePreferences(base, override) {
199
200
  verification_auto_fix: override.verification_auto_fix ?? base.verification_auto_fix,
200
201
  verification_max_retries: override.verification_max_retries ?? base.verification_max_retries,
201
202
  search_provider: override.search_provider ?? base.search_provider,
202
- compression_strategy: override.compression_strategy ?? base.compression_strategy,
203
203
  context_selection: override.context_selection ?? base.context_selection,
204
204
  auto_visualize: override.auto_visualize ?? base.auto_visualize,
205
205
  auto_report: override.auto_report ?? base.auto_report,