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

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 (77) 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/phases.js +37 -30
  4. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
  5. package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
  6. package/dist/resources/extensions/gsd/auto.js +9 -1
  7. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  8. package/dist/resources/extensions/gsd/detection.js +106 -0
  9. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  10. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  11. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  12. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  13. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  14. package/dist/web/standalone/.next/BUILD_ID +1 -1
  15. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  16. package/dist/web/standalone/.next/build-manifest.json +2 -2
  17. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  18. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/index.html +1 -1
  35. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  42. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  44. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  45. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  46. package/package.json +1 -1
  47. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +30 -0
  48. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  49. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  50. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +2 -0
  51. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  52. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +36 -0
  53. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +2 -0
  54. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  55. package/src/resources/GSD-WORKFLOW.md +2 -2
  56. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  57. package/src/resources/extensions/gsd/auto/phases.ts +42 -28
  58. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  59. package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
  60. package/src/resources/extensions/gsd/auto.ts +12 -1
  61. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  62. package/src/resources/extensions/gsd/detection.ts +128 -0
  63. package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  64. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  65. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  66. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
  67. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  68. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  69. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  70. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  71. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +46 -2
  72. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  73. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  74. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  75. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  76. /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → mPZbi5BH9dwokaPZlrYuQ}/_buildManifest.js +0 -0
  77. /package/dist/web/standalone/.next/static/{TCSim36ZpcPu2WgeoC45g → mPZbi5BH9dwokaPZlrYuQ}/_ssgManifest.js +0 -0
@@ -3,6 +3,8 @@ import assert from "node:assert/strict";
3
3
  import { readFileSync } from "node:fs";
4
4
  import { resolve } from "node:path";
5
5
 
6
+ import { _withDetachedAutoKeepaliveForTest } from "../auto.ts";
7
+
6
8
  const gsdDir = resolve(import.meta.dirname, "..");
7
9
 
8
10
  function readGsdFile(relativePath: string): string {
@@ -102,8 +104,8 @@ test("startAutoDetached reports failures asynchronously (#3733)", () => {
102
104
  "auto.ts should export startAutoDetached",
103
105
  );
104
106
  assert.ok(
105
- autoSrc.includes("void startAuto(ctx, pi, base, verboseMode, options).catch"),
106
- "startAutoDetached should launch startAuto without awaiting it",
107
+ autoSrc.includes("void withDetachedAutoKeepalive(startAuto(ctx, pi, base, verboseMode, options)).catch"),
108
+ "startAutoDetached should launch startAuto without awaiting it and keep the process alive",
107
109
  );
108
110
  assert.ok(
109
111
  autoSrc.includes("ctx.ui.notify(`Auto-start failed: ${message}`, \"error\")"),
@@ -111,6 +113,48 @@ test("startAutoDetached reports failures asynchronously (#3733)", () => {
111
113
  );
112
114
  });
113
115
 
116
+ test("detached auto-start keeps a ref'ed handle until the run settles", async () => {
117
+ const originalSetInterval = globalThis.setInterval;
118
+ const originalClearInterval = globalThis.clearInterval;
119
+ let intervalCreated = false;
120
+ let intervalCleared = false;
121
+ let createdHandle: NodeJS.Timeout | undefined;
122
+ let resolveRun!: () => void;
123
+ const run = new Promise<void>((resolve) => {
124
+ resolveRun = resolve;
125
+ });
126
+
127
+ globalThis.setInterval = ((handler: TimerHandler, timeout?: number, ...args: unknown[]) => {
128
+ intervalCreated = true;
129
+ assert.equal(timeout, 30_000);
130
+ void handler;
131
+ void args;
132
+ createdHandle = originalSetInterval(() => {}, 1_000_000);
133
+ assert.equal(createdHandle.hasRef(), true, "detached auto keepalive must be ref'ed");
134
+ return createdHandle;
135
+ }) as unknown as typeof setInterval;
136
+
137
+ globalThis.clearInterval = ((handle?: NodeJS.Timeout | number | string) => {
138
+ if (handle === createdHandle) intervalCleared = true;
139
+ return originalClearInterval(handle);
140
+ }) as unknown as typeof clearInterval;
141
+
142
+ try {
143
+ const heldRun = _withDetachedAutoKeepaliveForTest(run);
144
+ assert.equal(intervalCreated, true, "keepalive interval should start immediately");
145
+ assert.equal(intervalCleared, false, "keepalive should remain active while auto-mode is running");
146
+
147
+ resolveRun();
148
+ await heldRun;
149
+
150
+ assert.equal(intervalCleared, true, "keepalive interval should clear when auto-mode settles");
151
+ } finally {
152
+ if (createdHandle) originalClearInterval(createdHandle);
153
+ globalThis.setInterval = originalSetInterval;
154
+ globalThis.clearInterval = originalClearInterval;
155
+ }
156
+ });
157
+
114
158
  test("detached auto-start preserves milestone lock across pause/stop cleanup (#3733)", () => {
115
159
  const autoSrc = readGsdFile("auto.ts");
116
160
  const sessionSrc = readGsdFile("auto/session.ts");
@@ -9,12 +9,12 @@
9
9
 
10
10
  import { describe, test, beforeEach, afterEach } from "node:test";
11
11
  import assert from "node:assert/strict";
12
- import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readdirSync } from "node:fs";
12
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, writeFileSync, rmSync, readdirSync } from "node:fs";
13
13
  import { join } from "node:path";
14
14
  import { tmpdir } from "node:os";
15
15
  import { execSync } from "node:child_process";
16
16
 
17
- import { PROJECT_FILES } from "../detection.js";
17
+ import { PROJECT_FILES, classifyProject } from "../detection.js";
18
18
 
19
19
  // ─── Helpers ─────────────────────────────────────────────────────────────────
20
20
 
@@ -30,6 +30,14 @@ function createGitRepo(): string {
30
30
  return dir;
31
31
  }
32
32
 
33
+ function createEmptyGitRepo(): string {
34
+ const dir = mkdtempSync(join(tmpdir(), "wt-dispatch-test-empty-"));
35
+ execSync("git init", { cwd: dir, stdio: "ignore" });
36
+ execSync("git config user.email test@test.com", { cwd: dir, stdio: "ignore" });
37
+ execSync("git config user.name Test", { cwd: dir, stdio: "ignore" });
38
+ return dir;
39
+ }
40
+
33
41
  /**
34
42
  * Simulate the health check logic from auto/phases.ts.
35
43
  *
@@ -64,8 +72,6 @@ function hasXcodeBundle(basePath: string): boolean {
64
72
  } catch { return false; }
65
73
  }
66
74
 
67
- import { existsSync } from "node:fs";
68
-
69
75
  // ─── Tests ───────────────────────────────────────────────────────────────────
70
76
 
71
77
  test("PROJECT_FILES is exported and contains expected multi-ecosystem entries", () => {
@@ -80,6 +86,21 @@ test("PROJECT_FILES is exported and contains expected multi-ecosystem entries",
80
86
  assert.ok(PROJECT_FILES.includes("Package.swift"), "includes Swift marker");
81
87
  });
82
88
 
89
+ test("runUnitPhase fails closed when classification returns invalid-repo", () => {
90
+ const source = readFileSync(join(process.cwd(), "src/resources/extensions/gsd/auto/phases.ts"), "utf-8");
91
+ const invalidRepoBranch = source.slice(
92
+ source.indexOf('projectClassification.kind === "invalid-repo"'),
93
+ source.indexOf('projectClassification.kind === "greenfield"'),
94
+ );
95
+
96
+ assert.match(invalidRepoBranch, /projectClassification\.reason === "missing \.git" && hasGit/);
97
+ assert.match(invalidRepoBranch, /project classification could not confirm \.git/);
98
+ assert.match(invalidRepoBranch, /ctx\.ui\.notify\(msg,\s*"error"\)/);
99
+ assert.match(invalidRepoBranch, /await deps\.stopAuto\(ctx,\s*pi,\s*msg\)/);
100
+ assert.match(invalidRepoBranch, /return \{ action: "break", reason: "worktree-invalid" \}/);
101
+ assert.match(invalidRepoBranch, /classified as invalid-repo/);
102
+ });
103
+
83
104
  describe("health check with git repo", () => {
84
105
  let dir: string;
85
106
  beforeEach(() => { dir = createGitRepo(); });
@@ -132,8 +153,18 @@ describe("health check with git repo", () => {
132
153
  });
133
154
 
134
155
  test("health check passes for empty git repo (greenfield project)", () => {
135
- assert.ok(wouldPassHealthCheck(dir, existsSync), "empty git repo should pass health check (greenfield)");
136
- assert.ok(!hasRecognizedProjectFiles(dir, existsSync), "empty git repo has no recognized project files");
156
+ const empty = createEmptyGitRepo();
157
+ try {
158
+ assert.ok(wouldPassHealthCheck(empty, existsSync), "empty git repo should pass health check (greenfield)");
159
+ assert.equal(classifyProject(empty).kind, "greenfield");
160
+ } finally {
161
+ rmSync(empty, { recursive: true, force: true });
162
+ }
163
+ });
164
+
165
+ test("health check classifies README-only repo as untyped existing, not greenfield", () => {
166
+ assert.ok(wouldPassHealthCheck(dir, existsSync), "README-only repo should pass health check");
167
+ assert.equal(classifyProject(dir).kind, "untyped-existing");
137
168
  });
138
169
  });
139
170
 
@@ -235,4 +235,11 @@ describe("removeWorktree — missing worktree", () => {
235
235
  "should not throw when worktree does not exist",
236
236
  );
237
237
  });
238
+
239
+ test("deleteBranch is quiet when the branch is already gone", () => {
240
+ assert.doesNotThrow(
241
+ () => removeWorktree(base, "nonexistent", { deleteBranch: true }),
242
+ "missing branch should be treated as already cleaned up",
243
+ );
244
+ });
238
245
  });
@@ -56,11 +56,11 @@ test("#2616: findNestedGitDirs ignores .git files (worktree pointers)", (t) => {
56
56
  );
57
57
  });
58
58
 
59
- test("#2616: findNestedGitDirs skips excluded directories (node_modules, .gsd, target)", (t) => {
59
+ test("#2616: findNestedGitDirs skips excluded directories (node_modules, .gsd, .bg-shell, target)", (t) => {
60
60
  const root = makeRoot(t);
61
61
 
62
62
  // All three of these contain a .git *directory*, but the scan must skip them.
63
- for (const excluded of ["node_modules", ".gsd", "target"]) {
63
+ for (const excluded of ["node_modules", ".gsd", ".bg-shell", "target"]) {
64
64
  const inside = join(root, excluded, "vendored-pkg");
65
65
  mkdirSync(join(inside, ".git"), { recursive: true });
66
66
  }
@@ -73,6 +73,13 @@ test("#2616: findNestedGitDirs skips excluded directories (node_modules, .gsd, t
73
73
  );
74
74
  });
75
75
 
76
+ test("#2616: findNestedGitDirs ignores normal missing child .git probes", (t) => {
77
+ const root = makeRoot(t);
78
+ mkdirSync(join(root, "plain-dir"), { recursive: true });
79
+
80
+ assert.deepEqual(findNestedGitDirs(root), []);
81
+ });
82
+
76
83
  test("#2616: findNestedGitDirs finds deeply nested repos", (t) => {
77
84
  const root = makeRoot(t);
78
85
  const deep = join(root, "a", "b", "c", "scaffolded");
@@ -70,6 +70,15 @@ export interface WorktreeDiffSummary {
70
70
  removed: string[];
71
71
  }
72
72
 
73
+ function deleteBranchIfPresent(basePath: string, branch: string, warningPrefix: string): void {
74
+ try {
75
+ if (!nativeBranchExists(basePath, branch)) return;
76
+ nativeBranchDelete(basePath, branch, true);
77
+ } catch (e) {
78
+ logWarning("worktree", `${warningPrefix}: ${(e as Error).message}`);
79
+ }
80
+ }
81
+
73
82
  // ─── Path Helpers ──────────────────────────────────────────────────────────
74
83
 
75
84
  function normalizePathForComparison(path: string): string {
@@ -408,7 +417,7 @@ export function listWorktrees(basePath: string): WorktreeInfo[] {
408
417
 
409
418
  /** Directories to skip when scanning for nested .git dirs. */
410
419
  const NESTED_GIT_SKIP_DIRS = new Set([
411
- ".git", ".gsd", "node_modules", ".next", ".nuxt", "dist", "build",
420
+ ".git", ".gsd", ".bg-shell", "node_modules", ".next", ".nuxt", "dist", "build",
412
421
  "__pycache__", ".tox", ".venv", "venv", "target", "vendor",
413
422
  ]);
414
423
 
@@ -462,7 +471,9 @@ export function findNestedGitDirs(rootPath: string): string[] {
462
471
  continue;
463
472
  }
464
473
  } catch (e) {
465
- logWarning("worktree", `existsSync/.git check failed for ${fullPath}: ${(e as Error).message}`);
474
+ if ((e as NodeJS.ErrnoException).code !== "ENOENT") {
475
+ logWarning("worktree", `existsSync/.git check failed for ${fullPath}: ${(e as Error).message}`);
476
+ }
466
477
  }
467
478
 
468
479
  walk(fullPath, depth + 1);
@@ -535,7 +546,7 @@ export function removeWorktree(
535
546
  if (!existsSync(wtPath)) {
536
547
  nativeWorktreePrune(basePath);
537
548
  if (deleteBranch) {
538
- try { nativeBranchDelete(basePath, branch, true); } catch (e) { logWarning("worktree", `nativeBranchDelete failed: ${(e as Error).message}`); }
549
+ deleteBranchIfPresent(basePath, branch, "nativeBranchDelete failed");
539
550
  }
540
551
  return;
541
552
  }
@@ -670,7 +681,7 @@ export function removeWorktree(
670
681
  nativeWorktreePrune(basePath);
671
682
 
672
683
  if (deleteBranch) {
673
- try { nativeBranchDelete(basePath, branch, true); } catch (e) { logWarning("worktree", `final branch delete failed: ${(e as Error).message}`); }
684
+ deleteBranchIfPresent(basePath, branch, "final branch delete failed");
674
685
  }
675
686
  }
676
687