gsd-pi 2.37.1-dev.d3ace49 → 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 (150) hide show
  1. package/dist/app-paths.js +1 -1
  2. package/dist/cli.js +9 -0
  3. package/dist/extension-discovery.d.ts +5 -3
  4. package/dist/extension-discovery.js +14 -9
  5. package/dist/extension-registry.js +2 -2
  6. package/dist/remote-questions-config.js +2 -2
  7. package/dist/resources/extensions/browser-tools/package.json +3 -1
  8. package/dist/resources/extensions/cmux/index.js +55 -1
  9. package/dist/resources/extensions/context7/package.json +1 -1
  10. package/dist/resources/extensions/env-utils.js +29 -0
  11. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  12. package/dist/resources/extensions/google-search/package.json +3 -1
  13. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  14. package/dist/resources/extensions/gsd/auto-dispatch.js +7 -8
  15. package/dist/resources/extensions/gsd/auto-loop.js +68 -97
  16. package/dist/resources/extensions/gsd/auto-post-unit.js +75 -71
  17. package/dist/resources/extensions/gsd/auto-prompts.js +7 -31
  18. package/dist/resources/extensions/gsd/auto-start.js +13 -2
  19. package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
  20. package/dist/resources/extensions/gsd/auto.js +143 -96
  21. package/dist/resources/extensions/gsd/captures.js +9 -1
  22. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  23. package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
  24. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  25. package/dist/resources/extensions/gsd/commands.js +22 -2
  26. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  27. package/dist/resources/extensions/gsd/detection.js +1 -2
  28. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  29. package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
  30. package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
  31. package/dist/resources/extensions/gsd/doctor-format.js +15 -0
  32. package/dist/resources/extensions/gsd/doctor-providers.js +27 -11
  33. package/dist/resources/extensions/gsd/doctor.js +184 -11
  34. package/dist/resources/extensions/gsd/export.js +1 -1
  35. package/dist/resources/extensions/gsd/files.js +2 -2
  36. package/dist/resources/extensions/gsd/forensics.js +1 -1
  37. package/dist/resources/extensions/gsd/index.js +2 -1
  38. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  39. package/dist/resources/extensions/gsd/package.json +1 -1
  40. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  41. package/dist/resources/extensions/gsd/preferences-types.js +0 -1
  42. package/dist/resources/extensions/gsd/preferences-validation.js +1 -11
  43. package/dist/resources/extensions/gsd/preferences.js +5 -5
  44. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  45. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
  46. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  47. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  48. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  49. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  50. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  51. package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
  52. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  53. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  54. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  55. package/dist/resources/extensions/gsd/state.js +1 -1
  56. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  57. package/dist/resources/extensions/gsd/worktree.js +35 -16
  58. package/dist/resources/extensions/remote-questions/status.js +2 -1
  59. package/dist/resources/extensions/remote-questions/store.js +2 -1
  60. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  61. package/dist/resources/extensions/subagent/index.js +12 -3
  62. package/dist/resources/extensions/subagent/isolation.js +2 -1
  63. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  64. package/dist/resources/extensions/universal-config/package.json +1 -1
  65. package/dist/welcome-screen.d.ts +12 -0
  66. package/dist/welcome-screen.js +53 -0
  67. package/package.json +1 -1
  68. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
  70. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  71. package/packages/pi-coding-agent/package.json +1 -1
  72. package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
  73. package/pkg/package.json +1 -1
  74. package/src/resources/extensions/cmux/index.ts +57 -1
  75. package/src/resources/extensions/env-utils.ts +31 -0
  76. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  77. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  78. package/src/resources/extensions/gsd/auto-dispatch.ts +6 -8
  79. package/src/resources/extensions/gsd/auto-loop.ts +88 -133
  80. package/src/resources/extensions/gsd/auto-post-unit.ts +52 -42
  81. package/src/resources/extensions/gsd/auto-prompts.ts +7 -33
  82. package/src/resources/extensions/gsd/auto-start.ts +18 -2
  83. package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
  84. package/src/resources/extensions/gsd/auto.ts +139 -101
  85. package/src/resources/extensions/gsd/captures.ts +10 -1
  86. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  87. package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
  88. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  89. package/src/resources/extensions/gsd/commands.ts +24 -2
  90. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  91. package/src/resources/extensions/gsd/detection.ts +2 -2
  92. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  93. package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
  94. package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
  95. package/src/resources/extensions/gsd/doctor-format.ts +20 -0
  96. package/src/resources/extensions/gsd/doctor-providers.ts +26 -9
  97. package/src/resources/extensions/gsd/doctor-types.ts +16 -1
  98. package/src/resources/extensions/gsd/doctor.ts +177 -13
  99. package/src/resources/extensions/gsd/export.ts +1 -1
  100. package/src/resources/extensions/gsd/files.ts +2 -2
  101. package/src/resources/extensions/gsd/forensics.ts +1 -1
  102. package/src/resources/extensions/gsd/index.ts +3 -1
  103. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  104. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  105. package/src/resources/extensions/gsd/preferences-types.ts +0 -4
  106. package/src/resources/extensions/gsd/preferences-validation.ts +1 -11
  107. package/src/resources/extensions/gsd/preferences.ts +5 -5
  108. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  109. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
  110. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  111. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  112. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  113. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  114. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  115. package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
  116. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  117. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  118. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  119. package/src/resources/extensions/gsd/state.ts +1 -1
  120. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  121. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +11 -31
  122. package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
  123. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
  124. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  125. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  126. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  127. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  128. package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
  129. package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
  130. package/src/resources/extensions/gsd/types.ts +0 -1
  131. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  132. package/src/resources/extensions/gsd/worktree.ts +35 -15
  133. package/src/resources/extensions/remote-questions/status.ts +3 -1
  134. package/src/resources/extensions/remote-questions/store.ts +3 -1
  135. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  136. package/src/resources/extensions/subagent/index.ts +12 -3
  137. package/src/resources/extensions/subagent/isolation.ts +3 -1
  138. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  139. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  140. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  141. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  142. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  143. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  144. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  145. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  146. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  147. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  148. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  149. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  150. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -1,14 +1,15 @@
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
 
4
4
  import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
5
- import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
5
+ import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath } from "./paths.js";
6
6
  import { deriveState, isMilestoneComplete } from "./state.js";
7
7
  import { invalidateAllCaches } from "./cache.js";
8
8
  import { loadEffectiveGSDPreferences, type GSDPreferences } from "./preferences.js";
9
9
 
10
- import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
10
+ import type { DoctorIssue, DoctorIssueCode, DoctorReport } from "./doctor-types.js";
11
11
  import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
12
+ import type { RoadmapSliceEntry } from "./types.js";
12
13
  import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
13
14
  import { checkEnvironmentHealth } from "./doctor-environment.js";
14
15
  import { runProviderChecks } from "./doctor-providers.js";
@@ -17,7 +18,7 @@ import { runProviderChecks } from "./doctor-providers.js";
17
18
  // All public types and functions from extracted modules are re-exported here
18
19
  // so that existing imports from "./doctor.js" continue to work unchanged.
19
20
  export type { DoctorSeverity, DoctorIssueCode, DoctorIssue, DoctorReport, DoctorSummary } from "./doctor-types.js";
20
- export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt } from "./doctor-format.js";
21
+ export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt, formatDoctorReportJson } from "./doctor-format.js";
21
22
  export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport, type EnvironmentCheckResult } from "./doctor-environment.js";
22
23
  export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport, type ProgressScore, type ProgressLevel } from "./progress-score.js";
23
24
 
@@ -350,10 +351,60 @@ export async function selectDoctorScope(basePath: string, requestedScope?: strin
350
351
  return state.registry[0]?.id;
351
352
  }
352
353
 
353
- export async function runGSDDoctor(basePath: string, options?: { fix?: boolean; scope?: string; fixLevel?: "task" | "all"; isolationMode?: "none" | "worktree" | "branch" }): Promise<import("./doctor-types.js").DoctorReport> {
354
+ // ── Helper: circular dependency detection ──────────────────────────────────
355
+ function detectCircularDependencies(slices: RoadmapSliceEntry[]): string[][] {
356
+ const known = new Set(slices.map(s => s.id));
357
+ const adj = new Map<string, string[]>();
358
+ for (const s of slices) adj.set(s.id, s.depends.filter(d => known.has(d)));
359
+ const state = new Map<string, "unvisited" | "visiting" | "done">();
360
+ for (const s of slices) state.set(s.id, "unvisited");
361
+ const cycles: string[][] = [];
362
+ function dfs(id: string, path: string[]): void {
363
+ const st = state.get(id);
364
+ if (st === "done") return;
365
+ if (st === "visiting") { cycles.push([...path.slice(path.indexOf(id)), id]); return; }
366
+ state.set(id, "visiting");
367
+ for (const dep of adj.get(id) ?? []) dfs(dep, [...path, id]);
368
+ state.set(id, "done");
369
+ }
370
+ for (const s of slices) if (state.get(s.id) === "unvisited") dfs(s.id, []);
371
+ return cycles;
372
+ }
373
+
374
+ // ── Helper: doctor run history ──────────────────────────────────────────────
375
+ interface DoctorHistoryEntry { ts: string; ok: boolean; errors: number; warnings: number; fixes: number; codes: string[] }
376
+
377
+ async function appendDoctorHistory(basePath: string, report: DoctorReport): Promise<void> {
378
+ try {
379
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
380
+ const entry = JSON.stringify({
381
+ ts: new Date().toISOString(),
382
+ ok: report.ok,
383
+ errors: report.issues.filter(i => i.severity === "error").length,
384
+ warnings: report.issues.filter(i => i.severity === "warning").length,
385
+ fixes: report.fixesApplied.length,
386
+ codes: [...new Set(report.issues.map(i => i.code))],
387
+ } satisfies DoctorHistoryEntry);
388
+ const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
389
+ await saveFile(historyPath, existing + entry + "\n");
390
+ } catch { /* non-fatal */ }
391
+ }
392
+
393
+ /** Read the last N doctor history entries. Returns most-recent-first. */
394
+ export async function readDoctorHistory(basePath: string, lastN = 50): Promise<DoctorHistoryEntry[]> {
395
+ try {
396
+ const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
397
+ if (!existsSync(historyPath)) return [];
398
+ const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
399
+ return lines.slice(-lastN).reverse().map(l => JSON.parse(l) as DoctorHistoryEntry);
400
+ } catch { return []; }
401
+ }
402
+
403
+ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean; dryRun?: boolean; scope?: string; fixLevel?: "task" | "all"; isolationMode?: "none" | "worktree" | "branch"; includeBuild?: boolean; includeTests?: boolean }): Promise<DoctorReport> {
354
404
  const issues: DoctorIssue[] = [];
355
405
  const fixesApplied: string[] = [];
356
406
  const fix = options?.fix === true;
407
+ const dryRun = options?.dryRun === true;
357
408
  const fixLevel = options?.fixLevel ?? "all";
358
409
 
359
410
  // Issue codes that represent completion state transitions — creating summary
@@ -364,11 +415,18 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
364
415
 
365
416
  /** Whether a given issue code should be auto-fixed at the current fixLevel. */
366
417
  const shouldFix = (code: DoctorIssueCode): boolean => {
367
- if (!fix) return false;
418
+ if (!fix || dryRun) return false;
368
419
  if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code)) return false;
369
420
  return true;
370
421
  };
371
422
 
423
+ /** Log a dry-run "would fix" entry when fix=true but dryRun=true. */
424
+ const dryRunCanFix = (code: DoctorIssueCode, message: string): void => {
425
+ if (dryRun && fix && !(fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))) {
426
+ fixesApplied.push(`[dry-run] would fix: ${message}`);
427
+ }
428
+ };
429
+
372
430
  const prefs = loadEffectiveGSDPreferences();
373
431
  if (prefs) {
374
432
  const prefIssues = validatePreferenceShape(prefs.preferences);
@@ -385,21 +443,33 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
385
443
  }
386
444
  }
387
445
 
388
- // Git health checks (orphaned worktrees, stale branches, corrupt merge state, tracked runtime files)
446
+ // Git health checks timed
447
+ const t0git = Date.now();
389
448
  const isolationMode: "none" | "worktree" | "branch" = options?.isolationMode ??
390
449
  (prefs?.preferences?.git?.isolation === "none" ? "none" :
391
450
  prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
392
451
  await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
452
+ const gitMs = Date.now() - t0git;
393
453
 
394
- // Runtime health checks (crash locks, completed-units, hook state, activity logs, STATE.md, gitignore)
454
+ // Runtime health checks timed
455
+ const t0runtime = Date.now();
395
456
  await checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix);
457
+ const runtimeMs = Date.now() - t0runtime;
396
458
 
397
- // Environment health checks (#1221: missing tools, port conflicts, stale deps, disk space)
398
- await checkEnvironmentHealth(basePath, issues, { includeRemote: !options?.scope });
459
+ // Environment health checks timed
460
+ const t0env = Date.now();
461
+ await checkEnvironmentHealth(basePath, issues, {
462
+ includeRemote: !options?.scope,
463
+ includeBuild: options?.includeBuild,
464
+ includeTests: options?.includeTests,
465
+ });
466
+ const envMs = Date.now() - t0env;
399
467
 
400
468
  const milestonesPath = milestonesDir(basePath);
401
469
  if (!existsSync(milestonesPath)) {
402
- return { ok: issues.every(issue => issue.severity !== "error"), basePath, issues, fixesApplied };
470
+ const report: DoctorReport = { ok: issues.every(i => i.severity !== "error"), basePath, issues, fixesApplied, timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: 0 } };
471
+ await appendDoctorHistory(basePath, report);
472
+ return report;
403
473
  }
404
474
 
405
475
  const requirementsPath = resolveGsdRootFile(basePath, "REQUIREMENTS");
@@ -465,6 +535,43 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
465
535
  if (!roadmapContent) continue;
466
536
  const roadmap = parseRoadmap(roadmapContent);
467
537
 
538
+ // ── Circular dependency detection ──────────────────────────────────────
539
+ for (const cycle of detectCircularDependencies(roadmap.slices)) {
540
+ issues.push({
541
+ severity: "error",
542
+ code: "circular_slice_dependency",
543
+ scope: "milestone",
544
+ unitId: milestoneId,
545
+ message: `Circular dependency detected: ${cycle.join(" → ")}`,
546
+ file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
547
+ fixable: false,
548
+ });
549
+ }
550
+
551
+ // ── Orphaned slice directories ─────────────────────────────────────────
552
+ try {
553
+ const slicesDir = join(milestonePath, "slices");
554
+ if (existsSync(slicesDir)) {
555
+ const knownSliceIds = new Set(roadmap.slices.map(s => s.id));
556
+ for (const entry of readdirSync(slicesDir)) {
557
+ try {
558
+ if (!lstatSync(join(slicesDir, entry)).isDirectory()) continue;
559
+ } catch { continue; }
560
+ if (!knownSliceIds.has(entry)) {
561
+ issues.push({
562
+ severity: "warning",
563
+ code: "orphaned_slice_directory",
564
+ scope: "milestone",
565
+ unitId: milestoneId,
566
+ message: `Directory "${entry}" exists in ${milestoneId}/slices/ but is not referenced in the roadmap`,
567
+ file: `${relMilestonePath(basePath, milestoneId)}/slices/${entry}`,
568
+ fixable: false,
569
+ });
570
+ }
571
+ }
572
+ }
573
+ } catch { /* non-fatal */ }
574
+
468
575
  for (const slice of roadmap.slices) {
469
576
  const unitId = `${milestoneId}/${slice.id}`;
470
577
  if (options?.scope && !matchesScope(unitId, options.scope) && options.scope !== milestoneId) continue;
@@ -539,6 +646,33 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
539
646
  continue;
540
647
  }
541
648
 
649
+ // ── Duplicate task IDs ───────────────────────────────────────────────
650
+ const taskIdCounts = new Map<string, number>();
651
+ for (const task of plan.tasks) taskIdCounts.set(task.id, (taskIdCounts.get(task.id) ?? 0) + 1);
652
+ for (const [taskId, count] of taskIdCounts) {
653
+ if (count > 1) {
654
+ issues.push({ severity: "error", code: "duplicate_task_id", scope: "slice", unitId,
655
+ message: `Task ID "${taskId}" appears ${count} times in ${slice.id}-PLAN.md — duplicate IDs cause dispatch failures`,
656
+ file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"), fixable: false });
657
+ }
658
+ }
659
+
660
+ // ── Task files on disk not in plan ────────────────────────────────────
661
+ try {
662
+ if (tasksDir) {
663
+ const planTaskIds = new Set(plan.tasks.map(t => t.id));
664
+ for (const f of readdirSync(tasksDir)) {
665
+ if (!f.endsWith("-SUMMARY.md")) continue;
666
+ const diskTaskId = f.replace(/-SUMMARY\.md$/, "");
667
+ if (!planTaskIds.has(diskTaskId)) {
668
+ issues.push({ severity: "info", code: "task_file_not_in_plan", scope: "slice", unitId,
669
+ message: `Task summary "${f}" exists on disk but "${diskTaskId}" is not in ${slice.id}-PLAN.md`,
670
+ file: relTaskFile(basePath, milestoneId, slice.id, diskTaskId, "SUMMARY"), fixable: false });
671
+ }
672
+ }
673
+ }
674
+ } catch { /* non-fatal */ }
675
+
542
676
  let allTasksDone = plan.tasks.length > 0;
543
677
  for (const task of plan.tasks) {
544
678
  const taskUnitId = `${unitId}/${task.id}`;
@@ -555,6 +689,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
555
689
  file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"),
556
690
  fixable: true,
557
691
  });
692
+ dryRunCanFix("task_done_missing_summary", `create stub summary for ${taskUnitId}`);
558
693
  if (shouldFix("task_done_missing_summary")) {
559
694
  const stubPath = join(
560
695
  basePath, ".gsd", "milestones", milestoneId, "slices", slice.id, "tasks",
@@ -618,6 +753,22 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
618
753
  }
619
754
  }
620
755
 
756
+ // ── Future timestamp check ─────────────────────────────────────
757
+ if (task.done && hasSummary && summaryPath) {
758
+ try {
759
+ const rawSummary = await loadFile(summaryPath);
760
+ const m = rawSummary?.match(/^completed_at:\s*(.+)$/m);
761
+ if (m) {
762
+ const ts = new Date(m[1].trim());
763
+ if (!isNaN(ts.getTime()) && ts.getTime() > Date.now() + 24 * 60 * 60 * 1000) {
764
+ issues.push({ severity: "warning", code: "future_timestamp", scope: "task", unitId: taskUnitId,
765
+ message: `Task ${task.id} has completed_at "${m[1].trim()}" which is more than 24h in the future`,
766
+ file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"), fixable: false });
767
+ }
768
+ }
769
+ } catch { /* non-fatal */ }
770
+ }
771
+
621
772
  allTasksDone = allTasksDone && task.done;
622
773
  }
623
774
 
@@ -646,6 +797,13 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
646
797
  }
647
798
  }
648
799
 
800
+ // ── Stale REPLAN: exists but all tasks done ────────────────────────
801
+ if (replanPath && allTasksDone) {
802
+ issues.push({ severity: "info", code: "stale_replan_file", scope: "slice", unitId,
803
+ message: `${slice.id} has a REPLAN.md but all tasks are done — REPLAN.md may be stale`,
804
+ file: relSliceFile(basePath, milestoneId, slice.id, "REPLAN"), fixable: false });
805
+ }
806
+
649
807
  const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
650
808
  const sliceUatPath = join(slicePath, `${slice.id}-UAT.md`);
651
809
  const hasSliceSummary = !!(sliceSummaryPath && await loadFile(sliceSummaryPath));
@@ -661,6 +819,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
661
819
  file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
662
820
  fixable: true,
663
821
  });
822
+ dryRunCanFix("all_tasks_done_missing_slice_summary", `create placeholder summary for ${unitId}`);
664
823
  if (shouldFix("all_tasks_done_missing_slice_summary")) await ensureSliceSummaryStub(basePath, milestoneId, slice.id, fixesApplied);
665
824
  }
666
825
 
@@ -674,6 +833,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
674
833
  file: `${relSlicePath(basePath, milestoneId, slice.id)}/${slice.id}-UAT.md`,
675
834
  fixable: true,
676
835
  });
836
+ dryRunCanFix("all_tasks_done_missing_slice_uat", `create placeholder UAT for ${unitId}`);
677
837
  if (shouldFix("all_tasks_done_missing_slice_uat")) await ensureSliceUatStub(basePath, milestoneId, slice.id, fixesApplied);
678
838
  }
679
839
 
@@ -687,6 +847,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
687
847
  file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
688
848
  fixable: true,
689
849
  });
850
+ dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
690
851
  if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary" && issue.unitId === unitId))) {
691
852
  await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
692
853
  }
@@ -744,14 +905,17 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
744
905
  }
745
906
  }
746
907
 
747
- if (fix && fixesApplied.length > 0) {
908
+ if (fix && !dryRun && fixesApplied.length > 0) {
748
909
  await updateStateFile(basePath, fixesApplied);
749
910
  }
750
911
 
751
- return {
912
+ const report: DoctorReport = {
752
913
  ok: issues.every(issue => issue.severity !== "error"),
753
914
  basePath,
754
915
  issues,
755
916
  fixesApplied,
917
+ timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: Math.max(0, Date.now() - t0env - envMs) },
756
918
  };
919
+ await appendDoctorHistory(basePath, report);
920
+ return report;
757
921
  }
@@ -11,7 +11,7 @@ import {
11
11
  } from "./metrics.js";
12
12
  import type { UnitMetrics } from "./metrics.js";
13
13
  import { gsdRoot } from "./paths.js";
14
- import { formatDuration, fileLink } from "../shared/mod.js";
14
+ import { formatDuration, fileLink } from "../shared/format-utils.js";
15
15
  import { getErrorMessage } from "./error-utils.js";
16
16
 
17
17
  /**
@@ -7,7 +7,7 @@ import { promises as fs } from 'node:fs';
7
7
  import { resolve } from 'node:path';
8
8
  import { atomicWriteAsync } from './atomic-write.js';
9
9
  import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
10
- import { milestoneIdSort, findMilestoneIds } from './guided-flow.js';
10
+ import { milestoneIdSort, findMilestoneIds } from './milestone-ids.js';
11
11
 
12
12
  import type {
13
13
  Roadmap, BoundaryMapEntry,
@@ -20,7 +20,7 @@ import type {
20
20
  ManifestStatus,
21
21
  } from './types.js';
22
22
 
23
- import { checkExistingEnvKeys } from '../get-secrets-from-user.js';
23
+ import { checkExistingEnvKeys } from '../env-utils.js';
24
24
  import { parseRoadmapSlices } from './roadmap-slices.js';
25
25
  import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativeParseSummaryFile, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
26
26
  import { debugTime, debugCount } from './debug-logger.js';
@@ -27,7 +27,7 @@ import { deriveState } from "./state.js";
27
27
  import { isAutoActive } from "./auto.js";
28
28
  import { loadPrompt } from "./prompt-loader.js";
29
29
  import { gsdRoot } from "./paths.js";
30
- import { formatDuration } from "../shared/mod.js";
30
+ import { formatDuration } from "../shared/format-utils.js";
31
31
  import { getAutoWorktreePath } from "./auto-worktree.js";
32
32
 
33
33
  // ─── Types ────────────────────────────────────────────────────────────────────
@@ -60,6 +60,8 @@ import { join } from "node:path";
60
60
  import { existsSync, readFileSync } from "node:fs";
61
61
  import { homedir } from "node:os";
62
62
  import { shortcutDesc } from "../shared/mod.js";
63
+
64
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
63
65
  import { Text } from "@gsd/pi-tui";
64
66
  import { pauseAutoForProviderError, classifyProviderError } from "./provider-error-pause.js";
65
67
  import { toPosixPath } from "../shared/mod.js";
@@ -73,7 +75,7 @@ import { markCmuxPromptShown, shouldPromptToEnableCmux } from "../cmux/index.js"
73
75
 
74
76
  function warnDeprecatedAgentInstructions(): void {
75
77
  const paths = [
76
- join(homedir(), ".gsd", "agent-instructions.md"),
78
+ join(gsdHome, "agent-instructions.md"),
77
79
  join(process.cwd(), ".gsd", "agent-instructions.md"),
78
80
  ];
79
81
  for (const p of paths) {
@@ -3,7 +3,7 @@
3
3
  // Zero Pi dependencies — uses only exported helpers from files.ts.
4
4
 
5
5
  import { splitFrontmatter, parseFrontmatterMap, extractBoldField } from '../files.js';
6
- import { normalizeStringArray } from '../../shared/mod.js';
6
+ import { normalizeStringArray } from '../../shared/format-utils.js';
7
7
 
8
8
  import type {
9
9
  PlanningRoadmap,
@@ -295,18 +295,6 @@ export function resolveInlineLevel(): InlineLevel {
295
295
  }
296
296
  }
297
297
 
298
- /**
299
- * Resolve the compression strategy from the active token profile.
300
- * budget/balanced -> "compress", quality -> "truncate".
301
- * Explicit preference always wins.
302
- */
303
- export function resolveCompressionStrategy(): import("./types.js").CompressionStrategy {
304
- const prefs = loadEffectiveGSDPreferences();
305
- if (prefs?.preferences.compression_strategy) return prefs.preferences.compression_strategy;
306
- const profile = resolveEffectiveProfile();
307
- return profile === "quality" ? "truncate" : "compress";
308
- }
309
-
310
298
  /**
311
299
  * Resolve the context selection mode from the active token profile.
312
300
  * budget -> "smart", balanced/quality -> "full".
@@ -16,7 +16,6 @@ import type {
16
16
  InlineLevel,
17
17
  PhaseSkipPreferences,
18
18
  ParallelConfig,
19
- CompressionStrategy,
20
19
  ContextSelectionMode,
21
20
  ReactiveExecutionConfig,
22
21
  } from "./types.js";
@@ -84,7 +83,6 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
84
83
  "verification_auto_fix",
85
84
  "verification_max_retries",
86
85
  "search_provider",
87
- "compression_strategy",
88
86
  "context_selection",
89
87
  "widget_mode",
90
88
  "reactive_execution",
@@ -211,8 +209,6 @@ export interface GSDPreferences {
211
209
  verification_max_retries?: number;
212
210
  /** Search provider preference. "brave"/"tavily"/"ollama" force that backend and disable native Anthropic search. "native" forces native only. "auto" = current default behavior. */
213
211
  search_provider?: "brave" | "tavily" | "ollama" | "native" | "auto";
214
- /** Compression strategy for context that exceeds budget. "truncate" (default) drops sections, "compress" applies heuristic compression first. */
215
- compression_strategy?: CompressionStrategy;
216
212
  /** Context selection mode for file inlining. "full" inlines entire files, "smart" uses semantic chunking. Default derived from token profile. */
217
213
  context_selection?: ContextSelectionMode;
218
214
  /** Default widget display mode for auto-mode dashboard. "full" | "small" | "min" | "off". Default: "full". */
@@ -10,7 +10,7 @@ import type { GitPreferences } from "./git-service.js";
10
10
  import type { PostUnitHookConfig, PreDispatchHookConfig, TokenProfile, PhaseSkipPreferences } from "./types.js";
11
11
  import type { DynamicRoutingConfig } from "./model-router.js";
12
12
  import { VALID_BRANCH_NAME } from "./git-service.js";
13
- import { normalizeStringArray } from "../shared/mod.js";
13
+ import { normalizeStringArray } from "../shared/format-utils.js";
14
14
 
15
15
  import {
16
16
  KNOWN_PREFERENCE_KEYS,
@@ -686,16 +686,6 @@ export function validatePreferences(preferences: GSDPreferences): {
686
686
  }
687
687
  }
688
688
 
689
- // ─── Compression Strategy ───────────────────────────────────────────
690
- if (preferences.compression_strategy !== undefined) {
691
- const validStrategies = new Set(["truncate", "compress"]);
692
- if (typeof preferences.compression_strategy === "string" && validStrategies.has(preferences.compression_strategy)) {
693
- validated.compression_strategy = preferences.compression_strategy as GSDPreferences["compression_strategy"];
694
- } else {
695
- errors.push(`compression_strategy must be one of: truncate, compress`);
696
- }
697
- }
698
-
699
689
  // ─── Context Selection ──────────────────────────────────────────────
700
690
  if (preferences.context_selection !== undefined) {
701
691
  const validModes = new Set(["full", "smart"]);
@@ -13,11 +13,13 @@
13
13
  import { existsSync, readFileSync } from "node:fs";
14
14
  import { homedir } from "node:os";
15
15
  import { join } from "node:path";
16
+
17
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
16
18
  import { gsdRoot } from "./paths.js";
17
19
  import { parse as parseYaml } from "yaml";
18
20
  import type { PostUnitHookConfig, PreDispatchHookConfig, TokenProfile } from "./types.js";
19
21
  import type { DynamicRoutingConfig } from "./model-router.js";
20
- import { normalizeStringArray } from "../shared/mod.js";
22
+ import { normalizeStringArray } from "../shared/format-utils.js";
21
23
  import { resolveProfileDefaults as _resolveProfileDefaults } from "./preferences-models.js";
22
24
 
23
25
  import {
@@ -75,21 +77,20 @@ export {
75
77
  resolveProfileDefaults,
76
78
  resolveEffectiveProfile,
77
79
  resolveInlineLevel,
78
- resolveCompressionStrategy,
79
80
  resolveContextSelection,
80
81
  resolveSearchProviderFromPreferences,
81
82
  } from "./preferences-models.js";
82
83
 
83
84
  // ─── Path Constants & Getters ───────────────────────────────────────────────
84
85
 
85
- const GLOBAL_PREFERENCES_PATH = join(homedir(), ".gsd", "preferences.md");
86
+ const GLOBAL_PREFERENCES_PATH = join(gsdHome, "preferences.md");
86
87
  const LEGACY_GLOBAL_PREFERENCES_PATH = join(homedir(), ".pi", "agent", "gsd-preferences.md");
87
88
  function projectPreferencesPath(): string {
88
89
  return join(gsdRoot(process.cwd()), "preferences.md");
89
90
  }
90
91
  // Bootstrap in gitignore.ts historically created PREFERENCES.md (uppercase) by mistake.
91
92
  // Check uppercase as a fallback so those files aren't silently ignored.
92
- const GLOBAL_PREFERENCES_PATH_UPPERCASE = join(homedir(), ".gsd", "PREFERENCES.md");
93
+ const GLOBAL_PREFERENCES_PATH_UPPERCASE = join(gsdHome, "PREFERENCES.md");
93
94
  function projectPreferencesPathUppercase(): string {
94
95
  return join(gsdRoot(process.cwd()), "PREFERENCES.md");
95
96
  }
@@ -267,7 +268,6 @@ function mergePreferences(base: GSDPreferences, override: GSDPreferences): GSDPr
267
268
  verification_auto_fix: override.verification_auto_fix ?? base.verification_auto_fix,
268
269
  verification_max_retries: override.verification_max_retries ?? base.verification_max_retries,
269
270
  search_provider: override.search_provider ?? base.search_provider,
270
- compression_strategy: override.compression_strategy ?? base.compression_strategy,
271
271
  context_selection: override.context_selection ?? base.context_selection,
272
272
  auto_visualize: override.auto_visualize ?? base.auto_visualize,
273
273
  auto_report: override.auto_report ?? base.auto_report,
@@ -11,7 +11,7 @@ After the user describes their idea, **do not ask questions yet**. First, prove
11
11
  1. Summarize what you understood in your own words — concretely, not abstractly.
12
12
  2. Give an honest size read: roughly how many milestones, roughly how many slices in the first one. Base this on the actual work involved, not a classification label. A config change might be 1 milestone with 1 slice. A social network might be 5 milestones with 8+ slices each. Use your judgment.
13
13
  3. Include scope honesty — a bullet list of the major capabilities you're hearing: "Here's what I'm hearing: [bullet list of major capabilities]."
14
- 4. Ask: "Does that capture it? If not, tell me what I missed." — plain text, not `ask_user_questions`. Let them correct freely.
14
+ 4. Invite correction in one plain sentence: "Here's my read. Correct anything important I missed." — plain text, not `ask_user_questions`.
15
15
 
16
16
  This prevents runaway questioning by forcing comprehension proof before anything else. Do not skip this step. Do not combine it with the first question round.
17
17
 
@@ -21,7 +21,7 @@ After reflection is confirmed, decide the approach based on the actual scope —
21
21
 
22
22
  **If the work spans multiple milestones:** Before drilling into details, map the full landscape:
23
23
  1. Propose a milestone sequence — names, one-line intents, rough dependencies
24
- 2. Present this to the user for confirmation or adjustment
24
+ 2. Present this as the working milestone sequence. Adjust it if the user objects, sharpens it, or adds constraints; otherwise keep moving.
25
25
  3. Only then begin the deep Q&A — and scope the Q&A to the full vision, not just M001
26
26
 
27
27
  **If the work fits in a single milestone:** Proceed directly to questioning.
@@ -48,7 +48,7 @@ You are a thinking partner, not an interviewer.
48
48
 
49
49
  **Challenge vagueness, make abstract concrete.** When the user says something abstract ("it should be smart" / "it needs to handle edge cases" / "good UX"), push for specifics. What does "smart" mean in practice? Which edge cases? What does good UX look like for this specific interaction?
50
50
 
51
- **Questions must be about the experience, not the implementation.** Never ask "what auth provider?" ask "when someone logs in, what should that feel like?" Never ask "what database?" ask "when they come back tomorrow, what should they see?" Implementation is your job. Understanding what they want to experience is the discussion's job.
51
+ **Lead with experience, but ask implementation when it materially matters.** Default questions should target the experience and outcome. But when implementation choices materially change scope, proof, compliance, integration, deployment, or irreversible architecture, ask them directly instead of forcing a fake UX phrasing.
52
52
 
53
53
  **Freeform rule:** When the user selects "Other" or clearly wants to explain something freely, stop using `ask_user_questions` and switch to plain text follow-ups. Let them talk. Resume structured questions when appropriate.
54
54
 
@@ -105,16 +105,13 @@ Example flow:
105
105
 
106
106
  If they clarify, absorb the correction and re-verify.
107
107
 
108
- ## Wrap-up Gate
109
-
110
- Only after the depth checklist is fully satisfied and you genuinely understand the work, offer to proceed.
108
+ The depth verification is the required write-gate. Do **not** add another meta "ready to proceed?" checkpoint immediately after it unless there is still material ambiguity.
111
109
 
112
- The wrap-up gate must include a scope reflection:
113
- "Here's what I'm planning to build: [list of capabilities with rough complexity]. Does this match your vision, or did I miss something?"
110
+ ## Wrap-up Gate
114
111
 
115
- Then offer options: "Ready to confirm requirements and milestone plan (Recommended)", "I have more to discuss"
112
+ Once the depth checklist is fully satisfied, move directly into requirements and roadmap preview. Do not insert a separate "are you ready to continue?" gate unless the user explicitly wants to keep brainstorming or you still see material ambiguity.
116
113
 
117
- If the user wants to keep going, keep asking. If they're ready, proceed.
114
+ If you need a final scope reflection, fold it into the depth summary or roadmap preview rather than asking for permission twice.
118
115
 
119
116
  ## Focused Research
120
117
 
@@ -165,9 +162,9 @@ Rules:
165
162
 
166
163
  For multi-milestone projects, requirements should span the full vision. Requirements owned by later milestones get provisional ownership. The full requirement set captures the user's complete vision — milestones are the sequencing strategy, not the scope boundary.
167
164
 
168
- If the project is new or has no `REQUIREMENTS.md`, confirm candidate requirements with the user before writing the roadmap.
165
+ If the project is new or has no `REQUIREMENTS.md`, surface candidate requirements in chat before writing the roadmap. Ask for correction only on material omissions, wrong ownership, or wrong scope. If the user has already been specific and raises no substantive objection, treat the requirement set as confirmed and continue.
169
166
 
170
- **Print the requirements in chat before asking for confirmation.** Do not say "here are the requirements" and then only write them to a file. The user must see them in the terminal. Print a markdown table with columns: ID, Title, Status, Owner, Source. Group by status (Active, Deferred, Out of Scope). After the table, ask: "Confirm, adjust, or add?"
167
+ **Print the requirements in chat before writing the roadmap.** Do not say "here are the requirements" and then only write them to a file. The user must see them in the terminal. Print a markdown table with columns: ID, Title, Status, Owner, Source. Group by status (Active, Deferred, Out of Scope). After the table, ask: "Confirm, adjust, or add?"
171
168
 
172
169
  ## Scope Assessment
173
170
 
@@ -179,7 +176,7 @@ Before moving to output, confirm the size estimate from your reflection still ho
179
176
 
180
177
  Before writing any files, **print the planned roadmap in chat** so the user can see and approve it. Print a markdown table with columns: Slice, Title, Risk, Depends, Demo. One row per slice. Below the table, print the milestone definition of done as a bullet list.
181
178
 
182
- Ask: "Ready to write the plan, or want to adjust?" Only proceed to writing files after the user confirms.
179
+ If the user raises a substantive objection, adjust the roadmap. Otherwise, present the roadmap and ask: "Ready to write, or want to adjust?" one gate, not two.
183
180
 
184
181
  ### Naming Convention
185
182
 
@@ -236,7 +233,7 @@ If a milestone has no dependencies, omit the frontmatter. The dependency chain f
236
233
 
237
234
  #### Phase 3: Sequential readiness gate for remaining milestones
238
235
 
239
- For each remaining milestone **one at a time, in sequence**, use `ask_user_questions` to assess readiness. Present three options:
236
+ For each remaining milestone **one at a time, in sequence**, decide the most likely readiness mode from the evidence you already have, then use `ask_user_questions` to let the user correct that recommendation. Present three options:
240
237
 
241
238
  - **"Discuss now"** — The user wants to conduct a focused discussion for this milestone in the current session, while the context from the broader discussion is still fresh. Proceed with a focused discussion for this milestone (reflection → investigation → questioning → depth verification). When the discussion concludes, write a full `CONTEXT.md`. Then move to the gate for the next milestone.
242
239
  - **"Write draft for later"** — This milestone has seed material from the current conversation but needs its own dedicated discussion in a future session. Write a `CONTEXT-DRAFT.md` capturing the seed material (what was discussed, key ideas, provisional scope, open questions). Mark it clearly as a draft, not a finalized context. **What happens downstream:** When auto-mode reaches this milestone, it pauses and notifies the user: "M00x has draft context — needs discussion. Run /gsd." The `/gsd` wizard shows a "Discuss from draft" option that seeds the new discussion with this draft, so nothing from the current conversation is lost. After the dedicated discussion produces a full CONTEXT.md, the draft file is automatically deleted.
@@ -6,7 +6,7 @@ You are executing GSD auto-mode.
6
6
 
7
7
  Your working directory is `{{workingDirectory}}`. All file reads, writes, and shell commands MUST operate relative to this directory. Do NOT `cd` to any other directory.
8
8
 
9
- A researcher explored the codebase and a planner decomposed the work — you are the executor. The task plan below is your authoritative contract. It contains the specific files, steps, and verification you need. Don't re-research or re-plan build what the plan says, verify it works, and document what happened.
9
+ A researcher explored the codebase and a planner decomposed the work — you are the executor. The task plan below is your authoritative contract for the slice goal and verification bar, but it is not a substitute for local reality. Verify the referenced files and surrounding code before changing them. Do not do broad re-research or spontaneous re-planning. Small factual corrections, file-path fixes, and local implementation adaptations are part of execution. Escalate to `blocker_discovered: true` only when the slice contract or downstream task graph no longer holds.
10
10
 
11
11
  {{overridesSection}}
12
12
 
@@ -27,7 +27,7 @@ A researcher explored the codebase and a planner decomposed the work — you are
27
27
  Then:
28
28
  0. Narrate step transitions, key implementation decisions, and verification outcomes as you work. Keep it terse — one line between tool-call clusters, not between every call — but write complete sentences in user-facing prose, not shorthand notes or scratchpad fragments.
29
29
  1. **Load relevant skills before writing code.** Check the `GSD Skill Preferences` block in system context and the `<available_skills>` catalog in your system prompt. For each skill that matches this task's technology stack (e.g., React, Next.js, accessibility, component design), `read` its SKILL.md file now. Skills contain implementation rules and patterns that should guide your code. If no skills match this task, skip this step.
30
- 2. Execute the steps in the inlined task plan
30
+ 2. Execute the steps in the inlined task plan, adapting minor local mismatches when the surrounding code differs from the planner's snapshot
31
31
  3. Build the real thing. If the task plan says "create login endpoint", build an endpoint that actually authenticates against a real store, not one that returns a hardcoded success response. If the task plan says "create dashboard page", build a page that renders real data from the API, not a component with hardcoded props. Stubs and mocks are for tests, not for the shipped feature.
32
32
  4. Write or update tests as part of execution — tests are verification, not an afterthought. If the slice plan defines test files in its Verification section and this is the first task, create them (they should initially fail).
33
33
  5. When implementing non-trivial runtime behavior (async flows, API boundaries, background processes, error paths), add or preserve agent-usable observability. Skip this for simple changes where it doesn't apply.