gsd-pi 2.80.0-dev.c5c38454b → 2.80.0-dev.e146beb20

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 (120) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/GSD-WORKFLOW.md +2 -2
  3. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  4. package/dist/resources/extensions/gsd/auto/phases.js +37 -30
  5. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
  6. package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
  7. package/dist/resources/extensions/gsd/auto.js +9 -1
  8. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  9. package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
  10. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  11. package/dist/resources/extensions/gsd/detection.js +106 -0
  12. package/dist/resources/extensions/gsd/guided-flow.js +47 -10
  13. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  14. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  15. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  16. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  17. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  18. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  19. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  20. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  21. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  22. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  23. package/dist/web/standalone/.next/BUILD_ID +1 -1
  24. package/dist/web/standalone/.next/app-path-routes-manifest.json +16 -16
  25. package/dist/web/standalone/.next/build-manifest.json +2 -2
  26. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  27. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.html +1 -1
  44. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app-paths-manifest.json +16 -16
  51. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  53. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  54. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  55. package/package.json +1 -1
  56. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +30 -0
  57. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  58. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  59. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  60. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  61. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  62. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  63. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  64. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  65. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  66. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  67. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +2 -0
  68. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  70. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  71. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
  72. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  73. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  74. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  75. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  76. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +36 -0
  77. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  78. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  79. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +2 -0
  80. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
  81. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  82. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  83. package/src/resources/GSD-WORKFLOW.md +2 -2
  84. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  85. package/src/resources/extensions/gsd/auto/loop.ts +50 -8
  86. package/src/resources/extensions/gsd/auto/phases.ts +42 -28
  87. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  88. package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
  89. package/src/resources/extensions/gsd/auto.ts +12 -1
  90. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  91. package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
  92. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  93. package/src/resources/extensions/gsd/detection.ts +128 -0
  94. package/src/resources/extensions/gsd/guided-flow.ts +47 -10
  95. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  96. package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  97. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  98. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  99. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  100. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
  101. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  102. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
  103. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  104. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  105. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  106. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  107. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  108. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  109. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
  110. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +46 -2
  111. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
  112. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  113. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  114. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  115. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  116. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  117. package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
  118. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  119. /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → y73quA-XdLo9n41nxphjW}/_buildManifest.js +0 -0
  120. /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → y73quA-XdLo9n41nxphjW}/_ssgManifest.js +0 -0
@@ -5,6 +5,7 @@
5
5
  * Used by init-wizard.ts and guided-flow.ts to determine what onboarding
6
6
  * flow to show when entering a project directory.
7
7
  */
8
+ import { execFileSync } from "node:child_process";
8
9
  import { existsSync, openSync, readSync, closeSync, readdirSync, readFileSync, statSync } from "node:fs";
9
10
  import { dirname, join, parse as parsePath } from "node:path";
10
11
  import { homedir } from "node:os";
@@ -171,6 +172,7 @@ const TEST_MARKERS = [
171
172
  const RECURSIVE_SCAN_IGNORED_DIRS = new Set([
172
173
  ".git",
173
174
  ".gsd",
175
+ ".bg-shell",
174
176
  ".planning",
175
177
  ".plans",
176
178
  ".claude",
@@ -194,6 +196,7 @@ const RECURSIVE_SCAN_IGNORED_DIRS = new Set([
194
196
  "DerivedData",
195
197
  "out",
196
198
  ]);
199
+ const PROJECT_CONTENT_EXCLUDE_DIRS = RECURSIVE_SCAN_IGNORED_DIRS;
197
200
  /** Project file markers safe to detect recursively via suffix matching. */
198
201
  const ROOT_ONLY_PROJECT_FILES = new Set([
199
202
  ".github/workflows",
@@ -429,6 +432,109 @@ export function detectProjectSignals(basePath) {
429
432
  verificationCommands,
430
433
  };
431
434
  }
435
+ function normalizeGitPath(file) {
436
+ return file.replaceAll("\\", "/").replace(/^\.\//, "");
437
+ }
438
+ function isProjectContentFile(file) {
439
+ const normalized = normalizeGitPath(file);
440
+ if (!normalized || normalized.endsWith("/"))
441
+ return false;
442
+ if (normalized === ".gitignore" || normalized === ".gitattributes")
443
+ return false;
444
+ const parts = normalized.split("/");
445
+ if (parts.some((part) => PROJECT_CONTENT_EXCLUDE_DIRS.has(part)))
446
+ return false;
447
+ if (normalized.endsWith(".DS_Store"))
448
+ return false;
449
+ return true;
450
+ }
451
+ function runGitLines(basePath, args) {
452
+ try {
453
+ const output = execFileSync("git", args, {
454
+ cwd: basePath,
455
+ stdio: ["ignore", "pipe", "ignore"],
456
+ encoding: "utf-8",
457
+ }).trim();
458
+ return output ? output.split("\n").map((line) => line.trim()).filter(Boolean) : [];
459
+ }
460
+ catch {
461
+ return [];
462
+ }
463
+ }
464
+ function listTrackedProjectFiles(basePath) {
465
+ return runGitLines(basePath, ["ls-files"])
466
+ .map(normalizeGitPath)
467
+ .filter(isProjectContentFile);
468
+ }
469
+ function listUntrackedProjectFiles(basePath) {
470
+ return runGitLines(basePath, ["ls-files", "--others", "--exclude-standard"])
471
+ .map(normalizeGitPath)
472
+ .filter(isProjectContentFile);
473
+ }
474
+ function hasKnownProjectMarkers(basePath, signals) {
475
+ if (signals.detectedFiles.length > 0)
476
+ return true;
477
+ if (signals.xcodePlatforms.length > 0)
478
+ return true;
479
+ return false;
480
+ }
481
+ /**
482
+ * Classify repo presence separately from ecosystem/tooling markers.
483
+ *
484
+ * Known project files identify tooling. Git-tracked/non-ignored content
485
+ * identifies whether this is an existing project at all. This keeps small
486
+ * static or documentation repos from being mislabeled as greenfield.
487
+ */
488
+ export function classifyProject(basePath) {
489
+ const signals = detectProjectSignals(basePath);
490
+ const markers = [...signals.detectedFiles];
491
+ if (!signals.isGitRepo) {
492
+ return {
493
+ kind: "invalid-repo",
494
+ signals,
495
+ trackedFiles: [],
496
+ untrackedFiles: [],
497
+ contentFiles: [],
498
+ markers,
499
+ reason: "missing .git",
500
+ };
501
+ }
502
+ const trackedFiles = listTrackedProjectFiles(basePath);
503
+ const untrackedFiles = listUntrackedProjectFiles(basePath);
504
+ const contentFiles = [...new Set([...trackedFiles, ...untrackedFiles])];
505
+ const hasMarkers = hasKnownProjectMarkers(basePath, signals);
506
+ if (hasMarkers) {
507
+ return {
508
+ kind: "typed-existing",
509
+ signals,
510
+ trackedFiles,
511
+ untrackedFiles,
512
+ contentFiles,
513
+ markers,
514
+ reason: markers.length > 0 ? `detected markers: ${markers.join(", ")}` : "detected project structure",
515
+ };
516
+ }
517
+ if (contentFiles.length > 0) {
518
+ return {
519
+ kind: "untyped-existing",
520
+ signals,
521
+ trackedFiles,
522
+ untrackedFiles,
523
+ contentFiles,
524
+ markers,
525
+ reason: "project content exists but no recognized tooling markers were found",
526
+ };
527
+ }
528
+ return {
529
+ kind: "greenfield",
530
+ signals,
531
+ trackedFiles,
532
+ untrackedFiles,
533
+ contentFiles,
534
+ markers,
535
+ reason: "no tracked or non-ignored project content",
536
+ };
537
+ }
432
538
  // ─── Xcode Platform Detection ───────────────────────────────────────────────────
433
539
  /** Known SDKROOT values → canonical platform names. */
434
540
  const SDKROOT_MAP = {
@@ -16,7 +16,7 @@ import { invalidateAllCaches } from "./cache.js";
16
16
  import { startAutoDetached } from "./auto.js";
17
17
  import { clearLock } from "./crash-recovery.js";
18
18
  import { assessInterruptedSession, formatInterruptedSessionRunningMessage, formatInterruptedSessionSummary, } from "./interrupted-session.js";
19
- import { listUnitRuntimeRecords, clearUnitRuntimeRecord } from "./unit-runtime.js";
19
+ import { listUnitRuntimeRecords, clearUnitRuntimeRecord, isInFlightRuntimePhase } from "./unit-runtime.js";
20
20
  import { resolveExpectedArtifactPath } from "./auto.js";
21
21
  import { gsdHome } from "./gsd-home.js";
22
22
  import { gsdRoot, milestonesDir, resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile, relMilestoneFile, relSliceFile, clearPathCache, } from "./paths.js";
@@ -71,6 +71,19 @@ export function verifyExpectedArtifactForScope(scope, unitType, unitId) {
71
71
  export function resolveExpectedArtifactPathForScope(scope, unitType, unitId) {
72
72
  return resolveExpectedArtifactPath(unitType, unitId, scope.workspace.projectRoot);
73
73
  }
74
+ async function runQuickTaskChoice(ctx, pi) {
75
+ if (!ctx.hasUI) {
76
+ ctx.ui.notify("Run /gsd quick <task> for small bounded work, or /gsd do <task> for natural-language routing.", "info");
77
+ return;
78
+ }
79
+ const task = (await ctx.ui.input("Quick task", "Describe the small task to run with /gsd quick"))?.trim();
80
+ if (!task) {
81
+ ctx.ui.notify("Quick task cancelled.", "info");
82
+ return;
83
+ }
84
+ const { handleQuick } = await import("./quick.js");
85
+ await handleQuick(task, ctx, pi);
86
+ }
74
87
  /**
75
88
  * Scope-based overload of isGhostMilestone.
76
89
  * Binds basePath and milestoneId from the scope, ensuring path resolution
@@ -1469,8 +1482,8 @@ function selfHealRuntimeRecords(basePath, ctx) {
1469
1482
  cleared++;
1470
1483
  continue;
1471
1484
  }
1472
- // Clear records stuck in dispatched or timeout phase (process died mid-unit)
1473
- if (phase === "dispatched" || phase === "timeout") {
1485
+ // Clear records stuck in an in-flight phase (process died mid-unit).
1486
+ if (isInFlightRuntimePhase(phase)) {
1474
1487
  clearUnitRuntimeRecord(basePath, unitType, unitId);
1475
1488
  cleared++;
1476
1489
  }
@@ -1780,16 +1793,24 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1780
1793
  title: "GSD — Get Shit Done",
1781
1794
  summary: ["No active milestone."],
1782
1795
  actions: [
1796
+ {
1797
+ id: "quick_task",
1798
+ label: "Quick task",
1799
+ description: "For small bounded work, run /gsd quick <task> or /gsd do <task>.",
1800
+ recommended: true,
1801
+ },
1783
1802
  {
1784
1803
  id: "new_milestone",
1785
1804
  label: "Create next milestone",
1786
- description: "Define what to build next.",
1787
- recommended: true,
1805
+ description: "Define a larger body of work with planning artifacts.",
1788
1806
  },
1789
1807
  ],
1790
1808
  notYetMessage: "Run /gsd when ready.",
1791
1809
  });
1792
- if (choice === "new_milestone") {
1810
+ if (choice === "quick_task") {
1811
+ await runQuickTaskChoice(ctx, pi);
1812
+ }
1813
+ else if (choice === "new_milestone") {
1793
1814
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId: nextId, step: stepMode });
1794
1815
  await dispatchWorkflow(pi, await prepareAndBuildDiscussPrompt(ctx, pi, nextId, `New milestone ${nextId}.`, basePath), "gsd-run", ctx, "discuss-milestone");
1795
1816
  }
@@ -1809,11 +1830,16 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1809
1830
  title: `GSD — ${milestoneId}: ${milestoneTitle}`,
1810
1831
  summary: ["All milestones complete."],
1811
1832
  actions: [
1833
+ {
1834
+ id: "quick_task",
1835
+ label: "Quick task",
1836
+ description: "Do a small bounded task without opening a milestone.",
1837
+ recommended: true,
1838
+ },
1812
1839
  {
1813
1840
  id: "new_milestone",
1814
1841
  label: "Start new milestone",
1815
1842
  description: "Define and plan the next milestone.",
1816
- recommended: true,
1817
1843
  },
1818
1844
  {
1819
1845
  id: "status",
@@ -1823,7 +1849,10 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1823
1849
  ],
1824
1850
  notYetMessage: "Run /gsd when ready.",
1825
1851
  });
1826
- if (choice === "new_milestone") {
1852
+ if (choice === "quick_task") {
1853
+ await runQuickTaskChoice(ctx, pi);
1854
+ }
1855
+ else if (choice === "new_milestone") {
1827
1856
  const milestoneIds = findMilestoneIds(basePath);
1828
1857
  const uniqueMilestoneIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
1829
1858
  const nextId = nextMilestoneIdReserved(milestoneIds, uniqueMilestoneIds, basePath);
@@ -1916,13 +1945,18 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1916
1945
  const contextFile = resolveMilestoneFile(basePath, milestoneId, "CONTEXT");
1917
1946
  const hasContext = !!(contextFile && await loadFile(contextFile));
1918
1947
  const actions = [
1948
+ {
1949
+ id: "quick_task",
1950
+ label: "Quick task instead",
1951
+ description: "Use this when the work is small and should not become a milestone.",
1952
+ recommended: true,
1953
+ },
1919
1954
  {
1920
1955
  id: "plan",
1921
1956
  label: "Create roadmap",
1922
1957
  description: hasContext
1923
1958
  ? "Context captured. Decompose into slices with a boundary map."
1924
1959
  : "Decompose the milestone into slices with a boundary map.",
1925
- recommended: true,
1926
1960
  },
1927
1961
  ...(!hasContext ? [{
1928
1962
  id: "discuss",
@@ -1946,7 +1980,10 @@ export async function showSmartEntry(ctx, pi, basePath, options) {
1946
1980
  actions,
1947
1981
  notYetMessage: "Run /gsd when ready.",
1948
1982
  });
1949
- if (choice === "plan") {
1983
+ if (choice === "quick_task") {
1984
+ await runQuickTaskChoice(ctx, pi);
1985
+ }
1986
+ else if (choice === "plan") {
1950
1987
  setPendingAutoStart(basePath, { ctx, pi, basePath, milestoneId, step: stepMode });
1951
1988
  await dispatchWorkflow(pi, await buildPlanMilestonePrompt(milestoneId, milestoneTitle, basePath), "gsd-run", ctx, "plan-milestone");
1952
1989
  }
@@ -0,0 +1,26 @@
1
+ import { isAbsolute, relative, resolve } from "node:path";
2
+ import { normalizePlannedFileReference } from "./files.js";
3
+ function isInsideBase(basePath, candidate) {
4
+ const base = resolve(basePath);
5
+ const abs = resolve(candidate);
6
+ const rel = relative(base, abs);
7
+ return rel === "" || (!!rel && !rel.startsWith("..") && !isAbsolute(rel));
8
+ }
9
+ /**
10
+ * Planning IO fields are execution contracts. Absolute paths are only safe when
11
+ * they stay inside the active working directory; in worktree mode, an absolute
12
+ * path to the original checkout makes executors edit the wrong tree.
13
+ */
14
+ export function validatePlanningPathScope(basePath, fields) {
15
+ for (const { field, values } of fields) {
16
+ for (const raw of values) {
17
+ const candidate = normalizePlannedFileReference(raw);
18
+ if (!isAbsolute(candidate))
19
+ continue;
20
+ if (isInsideBase(basePath, candidate))
21
+ continue;
22
+ return `${field} contains absolute path outside working directory: ${candidate}. Use a path relative to ${basePath}.`;
23
+ }
24
+ }
25
+ return null;
26
+ }
@@ -16,15 +16,14 @@ Start with what the excerpts give you. Read full files when the section heads si
16
16
 
17
17
  **On-demand Read ordering:** Complete all slice SUMMARY Reads you need for cross-slice synthesis, the Decision Re-evaluation table, and LEARNINGS **before** calling `gsd_complete_milestone` (step 12). Once that tool runs, the milestone is marked complete in the DB, so it must be the final persistent milestone-closeout write.
18
18
 
19
- ### Delegate Review Work
19
+ ### Closeout Review Mode
20
20
 
21
- Use `subagent` for review work needing fresh context, before drafting LEARNINGS:
21
+ The inlined context includes a validation status block.
22
22
 
23
- - Cross-slice integrations or new public APIs -> **reviewer** with milestone diff and roadmap.
24
- - Auth, network, parsing, file IO, shell exec, or crypto -> **security** audit.
25
- - Significant tests added or changed -> **tester** coverage check against success criteria.
23
+ - If it says a passing validation artifact is present, treat that artifact as authoritative for success criteria, requirement coverage, verification classes, and cross-slice integration. Do not delegate fresh reviewer/security/tester audits unless the validation artifact is internally inconsistent with the inlined summaries.
24
+ - If validation is missing, stale, non-pass, or internally inconsistent, use `subagent` for review work needing fresh context before drafting LEARNINGS: cross-slice integrations or new public APIs -> **reviewer**; auth, network, parsing, file IO, shell exec, or crypto -> **security**; significant tests added or changed -> **tester**.
26
25
 
27
- Subagents report only; they do not write user source. Fold findings into Decision Re-evaluation and LEARNINGS before completion.
26
+ Subagents report only; they do not write user source. Fold any findings into Decision Re-evaluation and LEARNINGS before completion.
28
27
 
29
28
  {{inlinedContext}}
30
29
 
@@ -33,8 +32,8 @@ Subagents report only; they do not write user source. Fold findings into Decisio
33
32
  1. Use the **Milestone Summary** output template from the inlined context above
34
33
  2. {{skillActivation}}
35
34
  3. **Verify code changes exist.** Compare milestone work against the integration branch (`main`, `master`, or recorded branch), using merge-base as older revision and `HEAD` as newer. If the diff lists non-`.gsd/` files, pass. If `HEAD` equals the integration branch/merge-base, treat it as a self-diff retry: inspect milestone-scoped commit evidence (`GSD-Unit: {{milestoneId}}` or production `GSD-Task: Sxx/Tyy` trailers touching `.gsd/milestones/{{milestoneId}}/`) and verify those commits touched non-`.gsd/` files. Record **verification failure** only when neither source shows implementation files.
36
- 4. Verify every **success criterion** from `{{roadmapPath}}` with evidence from summaries, tests, or observable behavior. Record unmet criteria as **verification failure**.
37
- 5. Verify **definition of done**: all slices `[x]`, summaries exist, and integrations work. Record unmet items as **verification failure**.
35
+ 4. Verify every **success criterion** from `{{roadmapPath}}`. If passing validation is present, summarize the validation evidence instead of re-auditing it; otherwise verify with evidence from summaries, tests, or observable behavior. Record unmet criteria as **verification failure**.
36
+ 5. Verify **definition of done**: all slices `[x]`, summaries exist, and integrations work. If passing validation is present, trust its integration/verification verdict unless inconsistent with current artifacts. Record unmet items as **verification failure**.
38
37
  6. If the roadmap includes a **Horizontal Checklist**, verify each item and note unchecked items in the summary.
39
38
  7. Fill the **Decision Re-evaluation** table: compare each key `.gsd/DECISIONS.md` decision from this milestone with what shipped, and flag decisions to revisit.
40
39
  8. Validate **requirement status transitions**. For each changed requirement, confirm evidence supports the new status. Requirements may move between Active, Validated, Deferred, Blocked, or Out of Scope only with proof.
@@ -48,7 +48,7 @@ Narrate decomposition reasoning in complete sentences: grouping, risk order, ver
48
48
  Then:
49
49
  1. Use the **Roadmap** output template from the inlined context above
50
50
  2. {{skillActivation}}
51
- 3. Create only as many demoable vertical slices as the work genuinely needs.
51
+ 3. Create only as many demoable vertical slices as the work genuinely needs. Use 1-10 slices, sized to the work; tiny/single-file/static work should usually be one slice.
52
52
  4. Order by risk, high-risk first.
53
53
  5. Call `gsd_plan_milestone` to persist milestone fields, slice rows, and **Horizontal Checklist** through the DB-backed path. Fill checklist concerns considered during planning: requirements, decisions, shutdown, revenue, auth, shared resources, reconnection. Omit for trivial milestones. Do **not** write `{{outputPath}}`, `ROADMAP.md`, or other planning artifacts manually; the tool owns rendering and persistence.
54
54
  6. If planning produced structural decisions (slice ordering, technology choices, scope exclusions), call `gsd_decision_save` for each; the tool assigns IDs and regenerates `.gsd/DECISIONS.md`.
@@ -78,6 +78,8 @@ Apply these when decomposing and ordering slices:
78
78
  - Ship features, not proofs; use clearly marked realistic stubs only when necessary.
79
79
  - **Dependency format is comma-separated, never range syntax.** Write `depends:[S01,S02,S03]`, not `depends:[S01-S03]`.
80
80
  - Roadmap ambition must match the milestone; right-size decomposition.
81
+ - Missing ecosystem markers are not a reason to over-plan. If Project Classification says `untyped-existing`, treat the listed content files as the project surface and use generic file-level workflow guidance.
82
+ - For `untyped-existing` projects with 1-2 content files, prefer exactly one slice unless the request clearly spans multiple independent user-visible capabilities. For 3-5 content files, prefer 1-2 slices.
81
83
 
82
84
  ## Progressive Planning (ADR-011)
83
85
 
@@ -39,7 +39,7 @@ If slice research is inlined, trust it. Explore enough code to confirm paths, bo
39
39
  5. Define slice verification before tasks. Non-trivial slices need real tests or executable assertions; boundary contracts need contract-exercising checks. Tests must not read .gitignore/gitignored paths such as `.gsd/`, `.planning/`, or `.audits/`.
40
40
  6. Include Threat Surface (Q3), Requirement Impact (Q4), proof level, observability, integration closure, Failure Modes (Q5), Load Profile (Q6), and Negative Tests (Q7) only where applicable.
41
41
  7. Right-size tasks. Simple slices can be one task; split only when context, ownership, or verification boundaries justify it.
42
- 8. Each task needs a concrete title, Why / Files / Do / Verify / Done when, plus task-plan description, steps, must-haves, verification, inputs, and expected output. Inputs and Expected Output must include concrete backtick-wrapped paths; each task needs at least one output path.
42
+ 8. Each task needs a concrete title, Why / Files / Do / Verify / Done when, plus task-plan description, steps, must-haves, verification, inputs, and expected output. Inputs and Expected Output must include concrete backtick-wrapped paths; each task needs at least one output path. Use paths relative to `{{workingDirectory}}`; do not put absolute paths to the original checkout or any directory outside `{{workingDirectory}}` in `files`, `inputs`, `expectedOutput`, or verification commands.
43
43
  9. Persist with `gsd_plan_slice` using goal, successCriteria, optional proofLevel/integrationClosure/observabilityImpact, and tasks. `gsd_plan_slice` handles task persistence transactionally and renders `{{outputPath}}` plus task plans; do not call `gsd_plan_task`. The DB-backed tool is the canonical write path. Do **not** rely on direct `PLAN.md` writes as the source of truth.
44
44
  10. Self-audit before finishing: goal/demo closure, requirement coverage, locked decisions, concrete paths, dependency order, wiring, scope size, proof truthfulness, feature completeness, and quality gates. Quality gates: non-trivial slices/tasks include specific Q3-Q7 coverage where applicable.
45
45
  11. If planning creates structural decisions, append them to `.gsd/DECISIONS.md`.
@@ -14,6 +14,14 @@
14
14
  import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, unlinkSync, } from "node:fs";
15
15
  import { join, dirname } from "node:path";
16
16
  import { randomBytes } from "node:crypto";
17
+ const EXECUTION_TOOL_NAMES = new Set([
18
+ "bash",
19
+ "Bash",
20
+ "gsd_exec",
21
+ "gsd_exec_search",
22
+ "mcp__gsd-workflow__gsd_exec",
23
+ "mcp__gsd-workflow__gsd_exec_search",
24
+ ]);
17
25
  // ─── Module State ───────────────────────────────────────────────────────────
18
26
  let unitEvidence = [];
19
27
  // ─── Public API ─────────────────────────────────────────────────────────────
@@ -129,11 +137,11 @@ export function clearEvidenceFromDisk(basePath, milestoneId, sliceId, taskId) {
129
137
  * Exit codes and output are filled in by recordToolResult after execution.
130
138
  */
131
139
  export function recordToolCall(toolCallId, toolName, input) {
132
- if (toolName === "bash" || toolName === "Bash") {
140
+ if (EXECUTION_TOOL_NAMES.has(toolName)) {
133
141
  unitEvidence.push({
134
142
  kind: "bash",
135
143
  toolCallId,
136
- command: String(input.command ?? ""),
144
+ command: String(input.command ?? input.cmd ?? input.query ?? ""),
137
145
  exitCode: -1,
138
146
  outputSnippet: "",
139
147
  timestamp: Date.now(),
@@ -8,6 +8,7 @@ import { renderAllProjections } from "../workflow-projections.js";
8
8
  import { writeManifest } from "../workflow-manifest.js";
9
9
  import { appendEvent } from "../workflow-events.js";
10
10
  import { logWarning } from "../workflow-logger.js";
11
+ import { validatePlanningPathScope } from "../planning-path-scope.js";
11
12
  function validateTasks(value) {
12
13
  if (!Array.isArray(value) || value.length === 0) {
13
14
  throw new Error("tasks must be a non-empty array");
@@ -91,6 +92,14 @@ export async function handlePlanSlice(rawParams, basePath) {
91
92
  catch (err) {
92
93
  return { error: `validation failed: ${err.message}` };
93
94
  }
95
+ const pathScopeError = validatePlanningPathScope(basePath, params.tasks.flatMap((task, index) => [
96
+ { field: `tasks[${index}].files`, values: task.files },
97
+ { field: `tasks[${index}].inputs`, values: task.inputs },
98
+ { field: `tasks[${index}].expectedOutput`, values: task.expectedOutput },
99
+ ]));
100
+ if (pathScopeError) {
101
+ return { error: `validation failed: ${pathScopeError}` };
102
+ }
94
103
  // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
95
104
  // Guards must be inside the transaction so the state they check cannot
96
105
  // change between the read and the write (#2723).
@@ -8,6 +8,7 @@ import { renderAllProjections } from "../workflow-projections.js";
8
8
  import { writeManifest } from "../workflow-manifest.js";
9
9
  import { appendEvent } from "../workflow-events.js";
10
10
  import { logWarning } from "../workflow-logger.js";
11
+ import { validatePlanningPathScope } from "../planning-path-scope.js";
11
12
  function validateParams(params) {
12
13
  if (!isNonEmptyString(params?.milestoneId))
13
14
  throw new Error("milestoneId is required");
@@ -41,6 +42,14 @@ export async function handlePlanTask(rawParams, basePath) {
41
42
  catch (err) {
42
43
  return { error: `validation failed: ${err.message}` };
43
44
  }
45
+ const pathScopeError = validatePlanningPathScope(basePath, [
46
+ { field: "files", values: params.files },
47
+ { field: "inputs", values: params.inputs },
48
+ { field: "expectedOutput", values: params.expectedOutput },
49
+ ]);
50
+ if (pathScopeError) {
51
+ return { error: `validation failed: ${pathScopeError}` };
52
+ }
44
53
  // ── Guards + DB writes inside a single transaction (prevents TOCTOU) ───
45
54
  // Guards must be inside the transaction so the state they check cannot
46
55
  // change between the read and the write (#2723).
@@ -67,6 +67,17 @@ function withRecordLock(recordPath, fn) {
67
67
  catch { /* best-effort */ }
68
68
  }
69
69
  }
70
+ export const IN_FLIGHT_RUNTIME_PHASES = new Set([
71
+ "dispatched",
72
+ "wrapup-warning-sent",
73
+ "timeout",
74
+ "finalize-timeout",
75
+ "crashed",
76
+ "paused",
77
+ ]);
78
+ export function isInFlightRuntimePhase(phase) {
79
+ return IN_FLIGHT_RUNTIME_PHASES.has(phase);
80
+ }
70
81
  function runtimeDir(basePath) {
71
82
  return join(gsdRoot(basePath), "runtime", "units");
72
83
  }
@@ -22,6 +22,16 @@ import { logWarning } from "./workflow-logger.js";
22
22
  import { nativeBranchDelete, nativeBranchExists, nativeBranchForceReset, nativeCommit, nativeDetectMainBranch, nativeDiffContent, nativeDiffNameStatus, nativeDiffNumstat, nativeGetCurrentBranch, nativeIsAncestor, nativeLogOneline, nativeMergeSquash, nativeWorktreeAdd, nativeWorktreeList, nativeWorktreePrune, nativeWorktreeRemove, } from "./native-git-bridge.js";
23
23
  import { emitCanonicalRootRedirect } from "./worktree-telemetry.js";
24
24
  import { isGsdWorktreePath, normalizeWorktreePathForCompare, resolveWorktreeProjectRoot, } from "./worktree-root.js";
25
+ function deleteBranchIfPresent(basePath, branch, warningPrefix) {
26
+ try {
27
+ if (!nativeBranchExists(basePath, branch))
28
+ return;
29
+ nativeBranchDelete(basePath, branch, true);
30
+ }
31
+ catch (e) {
32
+ logWarning("worktree", `${warningPrefix}: ${e.message}`);
33
+ }
34
+ }
25
35
  // ─── Path Helpers ──────────────────────────────────────────────────────────
26
36
  function normalizePathForComparison(path) {
27
37
  const normalized = path
@@ -311,7 +321,7 @@ export function listWorktrees(basePath) {
311
321
  // of their object database, causing permanent silent data loss.
312
322
  /** Directories to skip when scanning for nested .git dirs. */
313
323
  const NESTED_GIT_SKIP_DIRS = new Set([
314
- ".git", ".gsd", "node_modules", ".next", ".nuxt", "dist", "build",
324
+ ".git", ".gsd", ".bg-shell", "node_modules", ".next", ".nuxt", "dist", "build",
315
325
  "__pycache__", ".tox", ".venv", "venv", "target", "vendor",
316
326
  ]);
317
327
  /**
@@ -364,7 +374,9 @@ export function findNestedGitDirs(rootPath) {
364
374
  }
365
375
  }
366
376
  catch (e) {
367
- logWarning("worktree", `existsSync/.git check failed for ${fullPath}: ${e.message}`);
377
+ if (e.code !== "ENOENT") {
378
+ logWarning("worktree", `existsSync/.git check failed for ${fullPath}: ${e.message}`);
379
+ }
368
380
  }
369
381
  walk(fullPath, depth + 1);
370
382
  }
@@ -430,12 +442,7 @@ export function removeWorktree(basePath, name, opts = {}) {
430
442
  if (!existsSync(wtPath)) {
431
443
  nativeWorktreePrune(basePath);
432
444
  if (deleteBranch) {
433
- try {
434
- nativeBranchDelete(basePath, branch, true);
435
- }
436
- catch (e) {
437
- logWarning("worktree", `nativeBranchDelete failed: ${e.message}`);
438
- }
445
+ deleteBranchIfPresent(basePath, branch, "nativeBranchDelete failed");
439
446
  }
440
447
  return;
441
448
  }
@@ -549,12 +556,7 @@ export function removeWorktree(basePath, name, opts = {}) {
549
556
  // Prune stale entries so git knows the worktree is gone
550
557
  nativeWorktreePrune(basePath);
551
558
  if (deleteBranch) {
552
- try {
553
- nativeBranchDelete(basePath, branch, true);
554
- }
555
- catch (e) {
556
- logWarning("worktree", `final branch delete failed: ${e.message}`);
557
- }
559
+ deleteBranchIfPresent(basePath, branch, "final branch delete failed");
558
560
  }
559
561
  }
560
562
  /**