gsd-pi 2.16.0 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/dist/resources/extensions/gsd/auto-dashboard.ts +4 -0
  2. package/dist/resources/extensions/gsd/auto-dispatch.ts +9 -3
  3. package/dist/resources/extensions/gsd/auto-prompts.ts +71 -41
  4. package/dist/resources/extensions/gsd/auto-recovery.ts +7 -2
  5. package/dist/resources/extensions/gsd/auto.ts +54 -15
  6. package/dist/resources/extensions/gsd/commands.ts +20 -2
  7. package/dist/resources/extensions/gsd/complexity.ts +236 -0
  8. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  9. package/dist/resources/extensions/gsd/files.ts +6 -2
  10. package/dist/resources/extensions/gsd/git-service.ts +19 -8
  11. package/dist/resources/extensions/gsd/gitignore.ts +41 -2
  12. package/dist/resources/extensions/gsd/guided-flow.ts +10 -6
  13. package/dist/resources/extensions/gsd/metrics.ts +44 -0
  14. package/dist/resources/extensions/gsd/native-git-bridge.ts +5 -0
  15. package/dist/resources/extensions/gsd/native-parser-bridge.ts +5 -0
  16. package/dist/resources/extensions/gsd/preferences.ts +122 -1
  17. package/dist/resources/extensions/gsd/routing-history.ts +290 -0
  18. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +50 -0
  19. package/dist/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
  20. package/dist/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
  21. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
  22. package/dist/resources/extensions/gsd/tests/git-service.test.ts +132 -0
  23. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +28 -0
  24. package/dist/resources/extensions/gsd/tests/routing-history.test.ts +87 -0
  25. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
  26. package/dist/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
  27. package/dist/resources/extensions/gsd/types.ts +28 -0
  28. package/dist/resources/extensions/gsd/worktree.ts +2 -2
  29. package/package.json +1 -1
  30. package/packages/pi-ai/dist/models.generated.d.ts +493 -13
  31. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  32. package/packages/pi-ai/dist/models.generated.js +422 -62
  33. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  34. package/packages/pi-ai/dist/providers/google-shared.d.ts +12 -0
  35. package/packages/pi-ai/dist/providers/google-shared.d.ts.map +1 -1
  36. package/packages/pi-ai/dist/providers/google-shared.js +9 -22
  37. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  38. package/packages/pi-ai/dist/providers/google-shared.test.d.ts +2 -0
  39. package/packages/pi-ai/dist/providers/google-shared.test.d.ts.map +1 -0
  40. package/packages/pi-ai/dist/providers/google-shared.test.js +125 -0
  41. package/packages/pi-ai/dist/providers/google-shared.test.js.map +1 -0
  42. package/packages/pi-ai/src/models.generated.ts +422 -62
  43. package/packages/pi-ai/src/providers/google-shared.test.ts +137 -0
  44. package/packages/pi-ai/src/providers/google-shared.ts +10 -19
  45. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts +7 -7
  46. package/packages/pi-coding-agent/dist/core/tools/edit-diff.d.ts.map +1 -1
  47. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js +209 -13
  48. package/packages/pi-coding-agent/dist/core/tools/edit-diff.js.map +1 -1
  49. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts +2 -0
  50. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.d.ts.map +1 -0
  51. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +67 -0
  52. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -0
  53. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  54. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js +10 -0
  55. package/packages/pi-coding-agent/dist/modes/interactive/theme/theme.js.map +1 -1
  56. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +85 -0
  57. package/packages/pi-coding-agent/src/core/tools/edit-diff.ts +245 -17
  58. package/packages/pi-coding-agent/src/modes/interactive/theme/theme.ts +13 -0
  59. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  60. package/pkg/dist/modes/interactive/theme/theme.js +10 -0
  61. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -1
  62. package/src/resources/extensions/gsd/auto-dashboard.ts +4 -0
  63. package/src/resources/extensions/gsd/auto-dispatch.ts +9 -3
  64. package/src/resources/extensions/gsd/auto-prompts.ts +71 -41
  65. package/src/resources/extensions/gsd/auto-recovery.ts +7 -2
  66. package/src/resources/extensions/gsd/auto.ts +54 -15
  67. package/src/resources/extensions/gsd/commands.ts +20 -2
  68. package/src/resources/extensions/gsd/complexity.ts +236 -0
  69. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -0
  70. package/src/resources/extensions/gsd/files.ts +6 -2
  71. package/src/resources/extensions/gsd/git-service.ts +19 -8
  72. package/src/resources/extensions/gsd/gitignore.ts +41 -2
  73. package/src/resources/extensions/gsd/guided-flow.ts +10 -6
  74. package/src/resources/extensions/gsd/metrics.ts +44 -0
  75. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -0
  76. package/src/resources/extensions/gsd/native-parser-bridge.ts +5 -0
  77. package/src/resources/extensions/gsd/preferences.ts +122 -1
  78. package/src/resources/extensions/gsd/routing-history.ts +290 -0
  79. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +50 -0
  80. package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +220 -0
  81. package/src/resources/extensions/gsd/tests/complexity-routing.test.ts +294 -0
  82. package/src/resources/extensions/gsd/tests/context-compression.test.ts +180 -0
  83. package/src/resources/extensions/gsd/tests/git-service.test.ts +132 -0
  84. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +28 -0
  85. package/src/resources/extensions/gsd/tests/routing-history.test.ts +87 -0
  86. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +130 -0
  87. package/src/resources/extensions/gsd/tests/token-profile.test.ts +263 -0
  88. package/src/resources/extensions/gsd/types.ts +28 -0
  89. package/src/resources/extensions/gsd/worktree.ts +2 -2
@@ -35,6 +35,10 @@ export interface AutoDashboardData {
35
35
  /** Running cost and token totals from metrics ledger */
36
36
  totalCost: number;
37
37
  totalTokens: number;
38
+ /** Projected remaining cost based on unit-type averages (undefined if insufficient data) */
39
+ projectedRemainingCost?: number;
40
+ /** Whether token profile has been auto-downgraded due to budget prediction */
41
+ profileDowngraded?: boolean;
38
42
  }
39
43
 
40
44
  // ─── Unit Description Helpers ─────────────────────────────────────────────────
@@ -122,7 +122,9 @@ const DISPATCH_RULES: DispatchRule[] = [
122
122
  },
123
123
  {
124
124
  name: "reassess-roadmap (post-completion)",
125
- match: async ({ state, mid, midTitle, basePath }) => {
125
+ match: async ({ state, mid, midTitle, basePath, prefs }) => {
126
+ // Phase skip: skip reassess when preference or profile says so
127
+ if (prefs?.phases?.skip_reassess) return null;
126
128
  const needsReassess = await checkNeedsReassessment(basePath, mid, state);
127
129
  if (!needsReassess) return null;
128
130
  return {
@@ -160,8 +162,10 @@ const DISPATCH_RULES: DispatchRule[] = [
160
162
  },
161
163
  {
162
164
  name: "pre-planning (no research) → research-milestone",
163
- match: async ({ state, mid, midTitle, basePath }) => {
165
+ match: async ({ state, mid, midTitle, basePath, prefs }) => {
164
166
  if (state.phase !== "pre-planning") return null;
167
+ // Phase skip: skip research when preference or profile says so
168
+ if (prefs?.phases?.skip_research) return null;
165
169
  const researchFile = resolveMilestoneFile(basePath, mid, "RESEARCH");
166
170
  if (researchFile) return null; // has research, fall through
167
171
  return {
@@ -186,8 +190,10 @@ const DISPATCH_RULES: DispatchRule[] = [
186
190
  },
187
191
  {
188
192
  name: "planning (no research, not S01) → research-slice",
189
- match: async ({ state, mid, midTitle, basePath }) => {
193
+ match: async ({ state, mid, midTitle, basePath, prefs }) => {
190
194
  if (state.phase !== "planning") return null;
195
+ // Phase skip: skip research when preference or profile says so
196
+ if (prefs?.phases?.skip_research || prefs?.phases?.skip_slice_research) return null;
191
197
  const sid = state.activeSlice!.id;
192
198
  const sTitle = state.activeSlice!.title;
193
199
  const researchFile = resolveSliceFile(basePath, mid, sid, "RESEARCH");
@@ -15,8 +15,8 @@ import {
15
15
  relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath,
16
16
  resolveGsdRootFile, relGsdRootFile,
17
17
  } from "./paths.js";
18
- import { resolveSkillDiscoveryMode } from "./preferences.js";
19
- import type { GSDState } from "./types.js";
18
+ import { resolveSkillDiscoveryMode, resolveInlineLevel } from "./preferences.js";
19
+ import type { GSDState, InlineLevel } from "./types.js";
20
20
  import type { GSDPreferences } from "./preferences.js";
21
21
  import { join } from "node:path";
22
22
  import { existsSync } from "node:fs";
@@ -393,7 +393,8 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string
393
393
  });
394
394
  }
395
395
 
396
- export async function buildPlanMilestonePrompt(mid: string, midTitle: string, base: string): Promise<string> {
396
+ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, base: string, level?: InlineLevel): Promise<string> {
397
+ const inlineLevel = level ?? resolveInlineLevel();
397
398
  const contextPath = resolveMilestoneFile(base, mid, "CONTEXT");
398
399
  const contextRel = relMilestoneFile(base, mid, "CONTEXT");
399
400
  const researchPath = resolveMilestoneFile(base, mid, "RESEARCH");
@@ -406,17 +407,23 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
406
407
  const { inlinePriorMilestoneSummary } = await import("./files.js");
407
408
  const priorSummaryInline = await inlinePriorMilestoneSummary(mid, base);
408
409
  if (priorSummaryInline) inlined.push(priorSummaryInline);
409
- const projectInline = await inlineGsdRootFile(base, "project.md", "Project");
410
+ const projectInline = inlineLevel !== "minimal" ? await inlineGsdRootFile(base, "project.md", "Project") : null;
410
411
  if (projectInline) inlined.push(projectInline);
411
- const requirementsInline = await inlineGsdRootFile(base, "requirements.md", "Requirements");
412
+ const requirementsInline = inlineLevel !== "minimal" ? await inlineGsdRootFile(base, "requirements.md", "Requirements") : null;
412
413
  if (requirementsInline) inlined.push(requirementsInline);
413
- const decisionsInline = await inlineGsdRootFile(base, "decisions.md", "Decisions");
414
+ const decisionsInline = inlineLevel !== "minimal" ? await inlineGsdRootFile(base, "decisions.md", "Decisions") : null;
414
415
  if (decisionsInline) inlined.push(decisionsInline);
415
416
  inlined.push(inlineTemplate("roadmap", "Roadmap"));
416
- inlined.push(inlineTemplate("decisions", "Decisions"));
417
- inlined.push(inlineTemplate("plan", "Slice Plan"));
418
- inlined.push(inlineTemplate("task-plan", "Task Plan"));
419
- inlined.push(inlineTemplate("secrets-manifest", "Secrets Manifest"));
417
+ if (inlineLevel === "full") {
418
+ inlined.push(inlineTemplate("decisions", "Decisions"));
419
+ inlined.push(inlineTemplate("plan", "Slice Plan"));
420
+ inlined.push(inlineTemplate("task-plan", "Task Plan"));
421
+ inlined.push(inlineTemplate("secrets-manifest", "Secrets Manifest"));
422
+ } else if (inlineLevel === "standard") {
423
+ inlined.push(inlineTemplate("decisions", "Decisions"));
424
+ inlined.push(inlineTemplate("plan", "Slice Plan"));
425
+ inlined.push(inlineTemplate("task-plan", "Task Plan"));
426
+ }
420
427
 
421
428
  const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
422
429
 
@@ -479,8 +486,9 @@ export async function buildResearchSlicePrompt(
479
486
  }
480
487
 
481
488
  export async function buildPlanSlicePrompt(
482
- mid: string, _midTitle: string, sid: string, sTitle: string, base: string,
489
+ mid: string, _midTitle: string, sid: string, sTitle: string, base: string, level?: InlineLevel,
483
490
  ): Promise<string> {
491
+ const inlineLevel = level ?? resolveInlineLevel();
484
492
  const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
485
493
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
486
494
  const researchPath = resolveSliceFile(base, mid, sid, "RESEARCH");
@@ -490,12 +498,16 @@ export async function buildPlanSlicePrompt(
490
498
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
491
499
  const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research");
492
500
  if (researchInline) inlined.push(researchInline);
493
- const decisionsInline = await inlineGsdRootFile(base, "decisions.md", "Decisions");
494
- if (decisionsInline) inlined.push(decisionsInline);
495
- const requirementsInline = await inlineGsdRootFile(base, "requirements.md", "Requirements");
496
- if (requirementsInline) inlined.push(requirementsInline);
501
+ if (inlineLevel !== "minimal") {
502
+ const decisionsInline = await inlineGsdRootFile(base, "decisions.md", "Decisions");
503
+ if (decisionsInline) inlined.push(decisionsInline);
504
+ const requirementsInline = await inlineGsdRootFile(base, "requirements.md", "Requirements");
505
+ if (requirementsInline) inlined.push(requirementsInline);
506
+ }
497
507
  inlined.push(inlineTemplate("plan", "Slice Plan"));
498
- inlined.push(inlineTemplate("task-plan", "Task Plan"));
508
+ if (inlineLevel === "full") {
509
+ inlined.push(inlineTemplate("task-plan", "Task Plan"));
510
+ }
499
511
 
500
512
  const depContent = await inlineDependencySummaries(mid, sid, base);
501
513
  const planActiveOverrides = await loadActiveOverrides(base);
@@ -519,8 +531,9 @@ export async function buildPlanSlicePrompt(
519
531
 
520
532
  export async function buildExecuteTaskPrompt(
521
533
  mid: string, sid: string, sTitle: string,
522
- tid: string, tTitle: string, base: string,
534
+ tid: string, tTitle: string, base: string, level?: InlineLevel,
523
535
  ): Promise<string> {
536
+ const inlineLevel = level ?? resolveInlineLevel();
524
537
 
525
538
  const priorSummaries = await getPriorTaskSummaryPaths(mid, sid, tid, base);
526
539
  const priorLines = priorSummaries.length > 0
@@ -560,11 +573,17 @@ export async function buildExecuteTaskPrompt(
560
573
  legacyContinuePath ? `${relSlicePath(base, mid, sid)}/continue.md` : null,
561
574
  );
562
575
 
563
- const carryForwardSection = await buildCarryForwardSection(priorSummaries, base);
564
- const inlinedTemplates = [
565
- inlineTemplate("task-summary", "Task Summary"),
566
- inlineTemplate("decisions", "Decisions"),
567
- ].join("\n\n---\n\n");
576
+ // For minimal inline level, only carry forward the most recent prior summary
577
+ const effectivePriorSummaries = inlineLevel === "minimal" && priorSummaries.length > 1
578
+ ? priorSummaries.slice(-1)
579
+ : priorSummaries;
580
+ const carryForwardSection = await buildCarryForwardSection(effectivePriorSummaries, base);
581
+ const inlinedTemplates = inlineLevel === "minimal"
582
+ ? inlineTemplate("task-summary", "Task Summary")
583
+ : [
584
+ inlineTemplate("task-summary", "Task Summary"),
585
+ inlineTemplate("decisions", "Decisions"),
586
+ ].join("\n\n---\n\n");
568
587
 
569
588
  const taskSummaryPath = `${relSlicePath(base, mid, sid)}/tasks/${tid}-SUMMARY.md`;
570
589
 
@@ -589,8 +608,9 @@ export async function buildExecuteTaskPrompt(
589
608
  }
590
609
 
591
610
  export async function buildCompleteSlicePrompt(
592
- mid: string, _midTitle: string, sid: string, sTitle: string, base: string,
611
+ mid: string, _midTitle: string, sid: string, sTitle: string, base: string, level?: InlineLevel,
593
612
  ): Promise<string> {
613
+ const inlineLevel = level ?? resolveInlineLevel();
594
614
 
595
615
  const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
596
616
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
@@ -600,8 +620,10 @@ export async function buildCompleteSlicePrompt(
600
620
  const inlined: string[] = [];
601
621
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
602
622
  inlined.push(await inlineFile(slicePlanPath, slicePlanRel, "Slice Plan"));
603
- const requirementsInline = await inlineGsdRootFile(base, "requirements.md", "Requirements");
604
- if (requirementsInline) inlined.push(requirementsInline);
623
+ if (inlineLevel !== "minimal") {
624
+ const requirementsInline = await inlineGsdRootFile(base, "requirements.md", "Requirements");
625
+ if (requirementsInline) inlined.push(requirementsInline);
626
+ }
605
627
 
606
628
  // Inline all task summaries for this slice
607
629
  const tDir = resolveTasksDir(base, mid, sid);
@@ -618,7 +640,9 @@ export async function buildCompleteSlicePrompt(
618
640
  }
619
641
  }
620
642
  inlined.push(inlineTemplate("slice-summary", "Slice Summary"));
621
- inlined.push(inlineTemplate("uat", "UAT"));
643
+ if (inlineLevel !== "minimal") {
644
+ inlined.push(inlineTemplate("uat", "UAT"));
645
+ }
622
646
  const completeActiveOverrides = await loadActiveOverrides(base);
623
647
  const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
624
648
  if (completeOverridesInline) inlined.unshift(completeOverridesInline);
@@ -641,8 +665,9 @@ export async function buildCompleteSlicePrompt(
641
665
  }
642
666
 
643
667
  export async function buildCompleteMilestonePrompt(
644
- mid: string, midTitle: string, base: string,
668
+ mid: string, midTitle: string, base: string, level?: InlineLevel,
645
669
  ): Promise<string> {
670
+ const inlineLevel = level ?? resolveInlineLevel();
646
671
  const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
647
672
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
648
673
 
@@ -663,13 +688,15 @@ export async function buildCompleteMilestonePrompt(
663
688
  }
664
689
  }
665
690
 
666
- // Inline root GSD files
667
- const requirementsInline = await inlineGsdRootFile(base, "requirements.md", "Requirements");
668
- if (requirementsInline) inlined.push(requirementsInline);
669
- const decisionsInline = await inlineGsdRootFile(base, "decisions.md", "Decisions");
670
- if (decisionsInline) inlined.push(decisionsInline);
671
- const projectInline = await inlineGsdRootFile(base, "project.md", "Project");
672
- if (projectInline) inlined.push(projectInline);
691
+ // Inline root GSD files (skip for minimal — completion can read these if needed)
692
+ if (inlineLevel !== "minimal") {
693
+ const requirementsInline = await inlineGsdRootFile(base, "requirements.md", "Requirements");
694
+ if (requirementsInline) inlined.push(requirementsInline);
695
+ const decisionsInline = await inlineGsdRootFile(base, "decisions.md", "Decisions");
696
+ if (decisionsInline) inlined.push(decisionsInline);
697
+ const projectInline = await inlineGsdRootFile(base, "project.md", "Project");
698
+ if (projectInline) inlined.push(projectInline);
699
+ }
673
700
  // Inline milestone context file (milestone-level, not GSD root)
674
701
  const contextPath = resolveMilestoneFile(base, mid, "CONTEXT");
675
702
  const contextRel = relMilestoneFile(base, mid, "CONTEXT");
@@ -779,8 +806,9 @@ export async function buildRunUatPrompt(
779
806
  }
780
807
 
781
808
  export async function buildReassessRoadmapPrompt(
782
- mid: string, midTitle: string, completedSliceId: string, base: string,
809
+ mid: string, midTitle: string, completedSliceId: string, base: string, level?: InlineLevel,
783
810
  ): Promise<string> {
811
+ const inlineLevel = level ?? resolveInlineLevel();
784
812
  const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
785
813
  const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
786
814
  const summaryPath = resolveSliceFile(base, mid, completedSliceId, "SUMMARY");
@@ -789,12 +817,14 @@ export async function buildReassessRoadmapPrompt(
789
817
  const inlined: string[] = [];
790
818
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Current Roadmap"));
791
819
  inlined.push(await inlineFile(summaryPath, summaryRel, `${completedSliceId} Summary`));
792
- const projectInline = await inlineGsdRootFile(base, "project.md", "Project");
793
- if (projectInline) inlined.push(projectInline);
794
- const requirementsInline = await inlineGsdRootFile(base, "requirements.md", "Requirements");
795
- if (requirementsInline) inlined.push(requirementsInline);
796
- const decisionsInline = await inlineGsdRootFile(base, "decisions.md", "Decisions");
797
- if (decisionsInline) inlined.push(decisionsInline);
820
+ if (inlineLevel !== "minimal") {
821
+ const projectInline = await inlineGsdRootFile(base, "project.md", "Project");
822
+ if (projectInline) inlined.push(projectInline);
823
+ const requirementsInline = await inlineGsdRootFile(base, "requirements.md", "Requirements");
824
+ if (requirementsInline) inlined.push(requirementsInline);
825
+ const decisionsInline = await inlineGsdRootFile(base, "decisions.md", "Decisions");
826
+ if (decisionsInline) inlined.push(decisionsInline);
827
+ }
798
828
 
799
829
  const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
800
830
 
@@ -11,6 +11,7 @@ import type { ExtensionContext } from "@gsd/pi-coding-agent";
11
11
  import {
12
12
  clearUnitRuntimeRecord,
13
13
  } from "./unit-runtime.js";
14
+ import { clearParseCache } from "./files.js";
14
15
  import {
15
16
  nativeConflictFiles,
16
17
  nativeCommit,
@@ -107,9 +108,13 @@ export function verifyExpectedArtifact(unitType: string, unitId: string, base: s
107
108
  // is managed by the hook engine, not the artifact verification system.
108
109
  if (unitType.startsWith("hook/")) return true;
109
110
 
110
- // Clear stale directory listing cache so artifact checks see fresh disk state (#431).
111
- // Moved after hook check to avoid unnecessary cache clears for hook units.
111
+ // Clear stale directory listing cache AND parse cache so artifact checks see
112
+ // fresh disk state (#431). The parse cache must also be cleared because
113
+ // cacheKey() uses length + first/last 100 chars — when a checkbox changes
114
+ // from [ ] to [x], the key collides with the pre-edit version, returning
115
+ // stale parsed results (e.g., slice.done = false when it's actually true).
112
116
  clearPathCache();
117
+ clearParseCache();
113
118
 
114
119
  if (unitType === "rewrite-docs") {
115
120
  const overridesPath = resolveGsdRootFile(base, "OVERRIDES");
@@ -293,6 +293,41 @@ export function isAutoPaused(): boolean {
293
293
  return paused;
294
294
  }
295
295
 
296
+ /**
297
+ * Return the base path to use for the auto.lock file.
298
+ * Always uses the original project root (not the worktree) so that
299
+ * a second terminal can discover and stop a running auto-mode session.
300
+ */
301
+ function lockBase(): string {
302
+ return originalBasePath || basePath;
303
+ }
304
+
305
+ /**
306
+ * Attempt to stop a running auto-mode session from a different process.
307
+ * Reads the lock file at the project root, checks if the PID is alive,
308
+ * and sends SIGTERM to gracefully stop it.
309
+ *
310
+ * Returns true if a remote session was found and signaled, false otherwise.
311
+ */
312
+ export function stopAutoRemote(projectRoot: string): { found: boolean; pid?: number; error?: string } {
313
+ const lock = readCrashLock(projectRoot);
314
+ if (!lock) return { found: false };
315
+
316
+ if (!isLockProcessAlive(lock)) {
317
+ // Stale lock — clean it up
318
+ clearLock(projectRoot);
319
+ return { found: false };
320
+ }
321
+
322
+ // Send SIGTERM — the auto-mode process has a handler that clears the lock and exits
323
+ try {
324
+ process.kill(lock.pid, "SIGTERM");
325
+ return { found: true, pid: lock.pid };
326
+ } catch (err) {
327
+ return { found: false, error: (err as Error).message };
328
+ }
329
+ }
330
+
296
331
  export function isStepMode(): boolean {
297
332
  return stepMode;
298
333
  }
@@ -371,7 +406,7 @@ function startDispatchGapWatchdog(ctx: ExtensionContext, pi: ExtensionAPI): void
371
406
  export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI): Promise<void> {
372
407
  if (!active && !paused) return;
373
408
  clearUnitTimeout();
374
- if (basePath) clearLock(basePath);
409
+ if (lockBase()) clearLock(lockBase());
375
410
  clearSkillSnapshot();
376
411
  _dispatching = false;
377
412
  _skipDepth = 0;
@@ -454,7 +489,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI): Promi
454
489
  export async function pauseAuto(ctx?: ExtensionContext, _pi?: ExtensionAPI): Promise<void> {
455
490
  if (!active) return;
456
491
  clearUnitTimeout();
457
- if (basePath) clearLock(basePath);
492
+ if (lockBase()) clearLock(lockBase());
458
493
 
459
494
  // Remove SIGTERM handler registered at auto-mode start
460
495
  deregisterSigtermHandler();
@@ -527,8 +562,8 @@ export async function startAuto(
527
562
  }
528
563
  }
529
564
 
530
- // Re-register SIGTERM handler for the resumed session
531
- registerSigtermHandler(basePath);
565
+ // Re-register SIGTERM handler for the resumed session (use original base for lock)
566
+ registerSigtermHandler(lockBase());
532
567
 
533
568
  ctx.ui.setStatus("gsd-auto", stepMode ? "next" : "auto");
534
569
  ctx.ui.setFooter(hideFooter);
@@ -557,17 +592,21 @@ export async function startAuto(
557
592
  }
558
593
 
559
594
  // Ensure .gitignore has baseline patterns
560
- ensureGitignore(base);
595
+ const commitDocs = loadEffectiveGSDPreferences()?.preferences?.git?.commit_docs;
596
+ ensureGitignore(base, { commitDocs });
561
597
  untrackRuntimeFiles(base);
562
598
 
563
599
  // Bootstrap .gsd/ if it doesn't exist
564
600
  const gsdDir = join(base, ".gsd");
565
601
  if (!existsSync(gsdDir)) {
566
602
  mkdirSync(join(gsdDir, "milestones"), { recursive: true });
567
- try {
568
- nativeAddPaths(base, [".gsd", ".gitignore"]);
569
- nativeCommit(base, "chore: init gsd");
570
- } catch { /* nothing to commit */ }
603
+ // Only commit .gsd/ init when commit_docs is not explicitly false
604
+ if (commitDocs !== false) {
605
+ try {
606
+ nativeAddPaths(base, [".gsd", ".gitignore"]);
607
+ nativeCommit(base, "chore: init gsd");
608
+ } catch { /* nothing to commit */ }
609
+ }
571
610
  }
572
611
 
573
612
  // Initialize GitServiceImpl — basePath is set and git repo confirmed
@@ -658,7 +697,7 @@ export async function startAuto(
658
697
  // of the repo's default (main/master). Idempotent when the branch is the
659
698
  // same; updates the record when started from a different branch (#300).
660
699
  if (currentMilestoneId) {
661
- captureIntegrationBranch(base, currentMilestoneId);
700
+ captureIntegrationBranch(base, currentMilestoneId, { commitDocs });
662
701
  setActiveMilestoneId(base, currentMilestoneId);
663
702
  }
664
703
 
@@ -695,8 +734,8 @@ export async function startAuto(
695
734
  gitService = new GitServiceImpl(basePath, loadEffectiveGSDPreferences()?.preferences?.git ?? {});
696
735
  ctx.ui.notify(`Created auto-worktree at ${wtPath}`, "info");
697
736
  }
698
- // Re-register SIGTERM handler with the new basePath
699
- registerSigtermHandler(basePath);
737
+ // Re-register SIGTERM handler with the original basePath (lock lives there)
738
+ registerSigtermHandler(originalBasePath);
700
739
  } catch (err) {
701
740
  // Worktree creation is non-fatal — continue in the project root.
702
741
  ctx.ui.notify(
@@ -952,7 +991,7 @@ export async function handleAgentEnd(
952
991
  return;
953
992
  }
954
993
  const sessionFile = ctx.sessionManager.getSessionFile();
955
- writeLock(basePath, hookUnit.unitType, hookUnit.unitId, completedUnits.length, sessionFile);
994
+ writeLock(lockBase(), hookUnit.unitType, hookUnit.unitId, completedUnits.length, sessionFile);
956
995
  // Persist hook state so cycle counts survive crashes
957
996
  persistHookState(basePath);
958
997
 
@@ -1211,7 +1250,7 @@ async function dispatchNextUnit(
1211
1250
  unitRecoveryCount.clear();
1212
1251
  unitLifetimeDispatches.clear();
1213
1252
  // Capture integration branch for the new milestone and update git service
1214
- captureIntegrationBranch(originalBasePath || basePath, mid);
1253
+ captureIntegrationBranch(originalBasePath || basePath, mid, { commitDocs: loadEffectiveGSDPreferences()?.preferences?.git?.commit_docs });
1215
1254
  }
1216
1255
  if (mid) {
1217
1256
  currentMilestoneId = mid;
@@ -1758,7 +1797,7 @@ async function dispatchNextUnit(
1758
1797
  // Pi appends entries incrementally via appendFileSync, so on crash the
1759
1798
  // session file survives with every tool call up to the crash point.
1760
1799
  const sessionFile = ctx.sessionManager.getSessionFile();
1761
- writeLock(basePath, unitType, unitId, completedUnits.length, sessionFile);
1800
+ writeLock(lockBase(), unitType, unitId, completedUnits.length, sessionFile);
1762
1801
 
1763
1802
  // On crash recovery, prepend the full recovery briefing
1764
1803
  // On retry (stuck detection), prepend deep diagnostic from last attempt
@@ -12,7 +12,7 @@ import { fileURLToPath } from "node:url";
12
12
  import { deriveState } from "./state.js";
13
13
  import { GSDDashboardOverlay } from "./dashboard-overlay.js";
14
14
  import { showQueue, showDiscuss } from "./guided-flow.js";
15
- import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode } from "./auto.js";
15
+ import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote } from "./auto.js";
16
16
  import {
17
17
  getGlobalGSDPreferencesPath,
18
18
  getLegacyGlobalGSDPreferencesPath,
@@ -178,7 +178,15 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
178
178
 
179
179
  if (trimmed === "stop") {
180
180
  if (!isAutoActive() && !isAutoPaused()) {
181
- ctx.ui.notify("Auto-mode is not running.", "info");
181
+ // Not running in this process — check for a remote auto-mode session
182
+ const result = stopAutoRemote(process.cwd());
183
+ if (result.found) {
184
+ ctx.ui.notify(`Sent stop signal to auto-mode session (PID ${result.pid}). It will shut down gracefully.`, "info");
185
+ } else if (result.error) {
186
+ ctx.ui.notify(`Failed to stop remote auto-mode: ${result.error}`, "error");
187
+ } else {
188
+ ctx.ui.notify("Auto-mode is not running.", "info");
189
+ }
182
190
  return;
183
191
  }
184
192
  await stopAuto(ctx, pi);
@@ -502,6 +510,16 @@ async function handlePrefsWizard(
502
510
  delete git.main_branch;
503
511
  }
504
512
  }
513
+ // ─── Git commit_docs ────────────────────────────────────────────────────
514
+ const currentCommitDocs = git.commit_docs;
515
+ const commitDocsChoice = await ctx.ui.select(
516
+ `Track .gsd/ planning docs in git${currentCommitDocs !== undefined ? ` (current: ${currentCommitDocs})` : ""}:`,
517
+ ["true", "false", "(keep current)"],
518
+ );
519
+ if (commitDocsChoice && commitDocsChoice !== "(keep current)") {
520
+ git.commit_docs = commitDocsChoice === "true";
521
+ }
522
+
505
523
  if (Object.keys(git).length > 0) {
506
524
  prefs.git = git;
507
525
  }