gsd-pi 2.45.0-dev.6b9da3e → 2.45.0-dev.fdcf73c

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 (109) hide show
  1. package/dist/help-text.js +1 -1
  2. package/dist/loader.js +34 -0
  3. package/dist/resources/extensions/gsd/auto/phases.js +16 -10
  4. package/dist/resources/extensions/gsd/auto/run-unit.js +6 -3
  5. package/dist/resources/extensions/gsd/auto-worktree.js +5 -4
  6. package/dist/resources/extensions/gsd/auto.js +4 -5
  7. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +15 -12
  8. package/dist/resources/extensions/gsd/db-writer.js +9 -9
  9. package/dist/resources/extensions/gsd/doctor-checks.js +1 -1
  10. package/dist/resources/extensions/gsd/doctor.js +2 -2
  11. package/dist/resources/extensions/gsd/gsd-db.js +5 -1
  12. package/dist/resources/extensions/gsd/preferences-types.js +2 -2
  13. package/dist/resources/extensions/gsd/preferences.js +8 -4
  14. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +20 -7
  15. package/dist/resources/extensions/gsd/tools/complete-milestone.js +4 -0
  16. package/dist/resources/extensions/gsd/workflow-logger.js +138 -0
  17. package/dist/resources/extensions/gsd/worktree-manager.js +4 -3
  18. package/dist/resources/extensions/gsd/worktree-resolver.js +37 -0
  19. package/dist/resources/extensions/voice/index.js +11 -16
  20. package/dist/resources/extensions/voice/linux-ready.js +67 -0
  21. package/dist/web/standalone/.next/BUILD_ID +1 -1
  22. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  23. package/dist/web/standalone/.next/build-manifest.json +2 -2
  24. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  25. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  26. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/index.html +1 -1
  42. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  49. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  50. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  51. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  52. package/package.json +2 -1
  53. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
  54. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +2 -0
  55. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  56. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -1
  57. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  58. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  59. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts +4 -0
  60. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.d.ts.map +1 -1
  61. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js +10 -5
  62. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.js.map +1 -1
  63. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts +2 -0
  64. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.d.ts.map +1 -0
  65. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js +185 -0
  66. package/packages/pi-coding-agent/dist/core/lifecycle-hooks.test.js.map +1 -0
  67. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +239 -10
  68. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +2 -1
  70. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  71. package/packages/pi-coding-agent/dist/core/model-registry.js +20 -2
  72. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  73. package/packages/pi-coding-agent/dist/core/package-commands.test.js +206 -195
  74. package/packages/pi-coding-agent/dist/core/package-commands.test.js.map +1 -1
  75. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +2 -0
  76. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -1
  77. package/packages/pi-coding-agent/src/core/lifecycle-hooks.test.ts +227 -0
  78. package/packages/pi-coding-agent/src/core/lifecycle-hooks.ts +11 -5
  79. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +297 -11
  80. package/packages/pi-coding-agent/src/core/model-registry.ts +30 -3
  81. package/packages/pi-coding-agent/src/core/package-commands.test.ts +227 -205
  82. package/src/resources/extensions/gsd/auto/phases.ts +16 -12
  83. package/src/resources/extensions/gsd/auto/run-unit.ts +6 -3
  84. package/src/resources/extensions/gsd/auto-worktree.ts +8 -5
  85. package/src/resources/extensions/gsd/auto.ts +3 -3
  86. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +15 -12
  87. package/src/resources/extensions/gsd/db-writer.ts +9 -17
  88. package/src/resources/extensions/gsd/doctor-checks.ts +1 -1
  89. package/src/resources/extensions/gsd/doctor.ts +2 -2
  90. package/src/resources/extensions/gsd/gsd-db.ts +5 -1
  91. package/src/resources/extensions/gsd/journal.ts +6 -1
  92. package/src/resources/extensions/gsd/preferences-types.ts +2 -2
  93. package/src/resources/extensions/gsd/preferences.ts +7 -3
  94. package/src/resources/extensions/gsd/prompts/complete-milestone.md +20 -7
  95. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +96 -0
  96. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -1
  97. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +42 -3
  98. package/src/resources/extensions/gsd/tests/preferences.test.ts +7 -9
  99. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +275 -0
  100. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +220 -0
  101. package/src/resources/extensions/gsd/tools/complete-milestone.ts +6 -0
  102. package/src/resources/extensions/gsd/workflow-logger.ts +193 -0
  103. package/src/resources/extensions/gsd/worktree-manager.ts +4 -9
  104. package/src/resources/extensions/gsd/worktree-resolver.ts +37 -0
  105. package/src/resources/extensions/voice/index.ts +11 -21
  106. package/src/resources/extensions/voice/linux-ready.ts +87 -0
  107. package/src/resources/extensions/voice/tests/linux-ready.test.ts +124 -0
  108. /package/dist/web/standalone/.next/static/{rzO54ZboyINyEt7cVM_uS → zWYDSwB-terOjfhmWzqk1}/_buildManifest.js +0 -0
  109. /package/dist/web/standalone/.next/static/{rzO54ZboyINyEt7cVM_uS → zWYDSwB-terOjfhmWzqk1}/_ssgManifest.js +0 -0
@@ -1,5 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
- import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import { Writable } from "node:stream";
@@ -25,216 +25,238 @@ function writePackage(root: string, files: Record<string, string>): void {
25
25
  }
26
26
  }
27
27
 
28
+ function createTestDirs(prefix: string, t: { after: (fn: () => void) => void }) {
29
+ const root = mkdtempSync(join(tmpdir(), `pi-lifecycle-${prefix}-`));
30
+ t.after(() => rmSync(root, { recursive: true, force: true }));
31
+ const cwd = join(root, "cwd");
32
+ const agentDir = join(root, "agent");
33
+ const extensionDir = join(root, `ext-${prefix}`);
34
+ mkdirSync(cwd, { recursive: true });
35
+ mkdirSync(agentDir, { recursive: true });
36
+ mkdirSync(extensionDir, { recursive: true });
37
+ return { root, cwd, agentDir, extensionDir };
38
+ }
39
+
28
40
  describe("runPackageCommand lifecycle hooks", () => {
29
- it("executes registered beforeInstall and afterInstall handlers for local packages", async () => {
30
- const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-install-"));
31
- const cwd = join(root, "cwd");
32
- const agentDir = join(root, "agent");
33
- const extensionDir = join(root, "ext-registered");
34
- mkdirSync(cwd, { recursive: true });
35
- mkdirSync(agentDir, { recursive: true });
36
- mkdirSync(extensionDir, { recursive: true });
37
-
38
- try {
39
- writePackage(extensionDir, {
40
- "package.json": JSON.stringify({
41
- name: "ext-registered",
42
- type: "module",
43
- pi: { extensions: ["./index.js"] },
44
- }),
45
- "index.js": `
46
- import { writeFileSync } from "node:fs";
47
- import { join } from "node:path";
48
- export default function (pi) {
49
- pi.registerBeforeInstall((ctx) => {
50
- writeFileSync(join(ctx.installedPath, "before-install-ran.txt"), "ok", "utf-8");
51
- });
52
- pi.registerAfterInstall((ctx) => {
53
- writeFileSync(join(ctx.installedPath, "after-install-ran.txt"), "ok", "utf-8");
54
- });
55
- }
56
- `,
57
- });
58
-
59
- const stdout = createCaptureStream();
60
- const stderr = createCaptureStream();
61
- const result = await runPackageCommand({
62
- appName: "pi",
63
- args: ["install", extensionDir],
64
- cwd,
65
- agentDir,
66
- stdout: stdout.stream,
67
- stderr: stderr.stream,
68
- });
69
-
70
- assert.equal(result.handled, true);
71
- assert.equal(result.exitCode, 0);
72
- assert.equal(readFileSync(join(extensionDir, "before-install-ran.txt"), "utf-8"), "ok");
73
- assert.equal(readFileSync(join(extensionDir, "after-install-ran.txt"), "utf-8"), "ok");
74
- assert.ok(stdout.getOutput().includes(`Installed ${extensionDir}`));
75
- } finally {
76
- rmSync(root, { recursive: true, force: true });
77
- }
41
+ it("executes registered beforeInstall and afterInstall handlers for local packages", async (t) => {
42
+ const { cwd, agentDir, extensionDir } = createTestDirs("install", t);
43
+
44
+ writePackage(extensionDir, {
45
+ "package.json": JSON.stringify({
46
+ name: "ext-registered",
47
+ type: "module",
48
+ pi: { extensions: ["./index.js"] },
49
+ }),
50
+ "index.js": [
51
+ 'import { writeFileSync } from "node:fs";',
52
+ 'import { join } from "node:path";',
53
+ "export default function (pi) {",
54
+ " pi.registerBeforeInstall((ctx) => {",
55
+ ' writeFileSync(join(ctx.installedPath, "before-install-ran.txt"), "ok", "utf-8");',
56
+ " });",
57
+ " pi.registerAfterInstall((ctx) => {",
58
+ ' writeFileSync(join(ctx.installedPath, "after-install-ran.txt"), "ok", "utf-8");',
59
+ " });",
60
+ "}",
61
+ ].join("\n"),
62
+ });
63
+
64
+ const stdout = createCaptureStream();
65
+ const stderr = createCaptureStream();
66
+ const result = await runPackageCommand({
67
+ appName: "pi",
68
+ args: ["install", extensionDir],
69
+ cwd,
70
+ agentDir,
71
+ stdout: stdout.stream,
72
+ stderr: stderr.stream,
73
+ });
74
+
75
+ assert.equal(result.handled, true);
76
+ assert.equal(result.exitCode, 0);
77
+ assert.equal(readFileSync(join(extensionDir, "before-install-ran.txt"), "utf-8"), "ok");
78
+ assert.equal(readFileSync(join(extensionDir, "after-install-ran.txt"), "utf-8"), "ok");
79
+ assert.ok(stdout.getOutput().includes(`Installed ${extensionDir}`));
78
80
  });
79
81
 
80
- it("runs legacy named lifecycle hooks when no registered hooks exist", async () => {
81
- const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-legacy-"));
82
- const cwd = join(root, "cwd");
83
- const agentDir = join(root, "agent");
84
- const extensionDir = join(root, "ext-legacy");
85
- mkdirSync(cwd, { recursive: true });
86
- mkdirSync(agentDir, { recursive: true });
87
- mkdirSync(extensionDir, { recursive: true });
88
-
89
- try {
90
- writePackage(extensionDir, {
91
- "package.json": JSON.stringify({
92
- name: "ext-legacy",
93
- type: "module",
94
- pi: { extensions: ["./index.js"] },
95
- }),
96
- "index.js": `
97
- import { writeFileSync } from "node:fs";
98
- import { join } from "node:path";
99
- export default function () {}
100
- export async function beforeInstall(ctx) {
101
- writeFileSync(join(ctx.installedPath, "legacy-before-install.txt"), "ok", "utf-8");
102
- }
103
- export async function afterInstall(ctx) {
104
- writeFileSync(join(ctx.installedPath, "legacy-after-install.txt"), "ok", "utf-8");
105
- }
106
- export async function beforeRemove(ctx) {
107
- writeFileSync(join(ctx.installedPath, "legacy-before-remove.txt"), "ok", "utf-8");
108
- }
109
- export async function afterRemove(ctx) {
110
- writeFileSync(join(ctx.installedPath, "legacy-after-remove.txt"), "ok", "utf-8");
111
- }
112
- `,
113
- });
114
-
115
- const stdout = createCaptureStream();
116
- const stderr = createCaptureStream();
117
- const installResult = await runPackageCommand({
118
- appName: "pi",
119
- args: ["install", extensionDir],
120
- cwd,
121
- agentDir,
122
- stdout: stdout.stream,
123
- stderr: stderr.stream,
124
- });
125
-
126
- assert.equal(installResult.handled, true);
127
- assert.equal(installResult.exitCode, 0);
128
- assert.equal(readFileSync(join(extensionDir, "legacy-before-install.txt"), "utf-8"), "ok");
129
- assert.equal(readFileSync(join(extensionDir, "legacy-after-install.txt"), "utf-8"), "ok");
130
-
131
- const removeResult = await runPackageCommand({
132
- appName: "pi",
133
- args: ["remove", extensionDir],
134
- cwd,
135
- agentDir,
136
- stdout: stdout.stream,
137
- stderr: stderr.stream,
138
- });
139
-
140
- assert.equal(removeResult.handled, true);
141
- assert.equal(removeResult.exitCode, 0);
142
- assert.equal(readFileSync(join(extensionDir, "legacy-before-remove.txt"), "utf-8"), "ok");
143
- assert.equal(readFileSync(join(extensionDir, "legacy-after-remove.txt"), "utf-8"), "ok");
144
- } finally {
145
- rmSync(root, { recursive: true, force: true });
146
- }
82
+ it("runs legacy named lifecycle hooks when no registered hooks exist", async (t) => {
83
+ const { cwd, agentDir, extensionDir } = createTestDirs("legacy", t);
84
+
85
+ writePackage(extensionDir, {
86
+ "package.json": JSON.stringify({
87
+ name: "ext-legacy",
88
+ type: "module",
89
+ pi: { extensions: ["./index.js"] },
90
+ }),
91
+ "index.js": [
92
+ 'import { writeFileSync } from "node:fs";',
93
+ 'import { join } from "node:path";',
94
+ "export default function () {}",
95
+ "export async function beforeInstall(ctx) {",
96
+ ' writeFileSync(join(ctx.installedPath, "legacy-before-install.txt"), "ok", "utf-8");',
97
+ "}",
98
+ "export async function afterInstall(ctx) {",
99
+ ' writeFileSync(join(ctx.installedPath, "legacy-after-install.txt"), "ok", "utf-8");',
100
+ "}",
101
+ "export async function beforeRemove(ctx) {",
102
+ ' writeFileSync(join(ctx.installedPath, "legacy-before-remove.txt"), "ok", "utf-8");',
103
+ "}",
104
+ "export async function afterRemove(ctx) {",
105
+ ' writeFileSync(join(ctx.installedPath, "legacy-after-remove.txt"), "ok", "utf-8");',
106
+ "}",
107
+ ].join("\n"),
108
+ });
109
+
110
+ const stdout = createCaptureStream();
111
+ const stderr = createCaptureStream();
112
+ const installResult = await runPackageCommand({
113
+ appName: "pi",
114
+ args: ["install", extensionDir],
115
+ cwd,
116
+ agentDir,
117
+ stdout: stdout.stream,
118
+ stderr: stderr.stream,
119
+ });
120
+
121
+ assert.equal(installResult.handled, true);
122
+ assert.equal(installResult.exitCode, 0);
123
+ assert.equal(readFileSync(join(extensionDir, "legacy-before-install.txt"), "utf-8"), "ok");
124
+ assert.equal(readFileSync(join(extensionDir, "legacy-after-install.txt"), "utf-8"), "ok");
125
+
126
+ const removeResult = await runPackageCommand({
127
+ appName: "pi",
128
+ args: ["remove", extensionDir],
129
+ cwd,
130
+ agentDir,
131
+ stdout: stdout.stream,
132
+ stderr: stderr.stream,
133
+ });
134
+
135
+ assert.equal(removeResult.handled, true);
136
+ assert.equal(removeResult.exitCode, 0);
137
+ assert.equal(readFileSync(join(extensionDir, "legacy-before-remove.txt"), "utf-8"), "ok");
138
+ assert.equal(readFileSync(join(extensionDir, "legacy-after-remove.txt"), "utf-8"), "ok");
147
139
  });
148
140
 
149
- it("skips lifecycle phases with no hooks declared", async () => {
150
- const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-skip-"));
151
- const cwd = join(root, "cwd");
152
- const agentDir = join(root, "agent");
153
- const extensionDir = join(root, "ext-empty");
154
- mkdirSync(cwd, { recursive: true });
155
- mkdirSync(agentDir, { recursive: true });
156
- mkdirSync(extensionDir, { recursive: true });
157
-
158
- try {
159
- writePackage(extensionDir, {
160
- "package.json": JSON.stringify({
161
- name: "ext-empty",
162
- type: "module",
163
- pi: { extensions: ["./index.js"] },
164
- }),
165
- "index.js": `export default function () {}`,
166
- });
167
-
168
- const stdout = createCaptureStream();
169
- const stderr = createCaptureStream();
170
- const installResult = await runPackageCommand({
171
- appName: "pi",
172
- args: ["install", extensionDir],
173
- cwd,
174
- agentDir,
175
- stdout: stdout.stream,
176
- stderr: stderr.stream,
177
- });
178
- assert.equal(installResult.handled, true);
179
- assert.equal(installResult.exitCode, 0);
180
-
181
- const removeResult = await runPackageCommand({
182
- appName: "pi",
183
- args: ["remove", extensionDir],
184
- cwd,
185
- agentDir,
186
- stdout: stdout.stream,
187
- stderr: stderr.stream,
188
- });
189
- assert.equal(removeResult.handled, true);
190
- assert.equal(removeResult.exitCode, 0);
191
- assert.equal(stderr.getOutput().includes("Hook failed"), false);
192
- } finally {
193
- rmSync(root, { recursive: true, force: true });
194
- }
141
+ it("skips lifecycle phases with no hooks declared", async (t) => {
142
+ const { cwd, agentDir, extensionDir } = createTestDirs("skip", t);
143
+
144
+ writePackage(extensionDir, {
145
+ "package.json": JSON.stringify({
146
+ name: "ext-empty",
147
+ type: "module",
148
+ pi: { extensions: ["./index.js"] },
149
+ }),
150
+ "index.js": "export default function () {}",
151
+ });
152
+
153
+ const stdout = createCaptureStream();
154
+ const stderr = createCaptureStream();
155
+ const installResult = await runPackageCommand({
156
+ appName: "pi",
157
+ args: ["install", extensionDir],
158
+ cwd,
159
+ agentDir,
160
+ stdout: stdout.stream,
161
+ stderr: stderr.stream,
162
+ });
163
+ assert.equal(installResult.handled, true);
164
+ assert.equal(installResult.exitCode, 0);
165
+
166
+ const removeResult = await runPackageCommand({
167
+ appName: "pi",
168
+ args: ["remove", extensionDir],
169
+ cwd,
170
+ agentDir,
171
+ stdout: stdout.stream,
172
+ stderr: stderr.stream,
173
+ });
174
+ assert.equal(removeResult.handled, true);
175
+ assert.equal(removeResult.exitCode, 0);
176
+ assert.equal(stderr.getOutput().includes("Hook failed"), false);
177
+ });
178
+
179
+ it("fails install when manifest runtime dependency is missing", async (t) => {
180
+ const { cwd, agentDir, extensionDir } = createTestDirs("deps", t);
181
+
182
+ writePackage(extensionDir, {
183
+ "package.json": JSON.stringify({
184
+ name: "ext-runtime-deps",
185
+ type: "module",
186
+ pi: { extensions: ["./index.js"] },
187
+ }),
188
+ "index.js": "export default function () {}",
189
+ "extension-manifest.json": JSON.stringify({
190
+ id: "ext-runtime-deps",
191
+ name: "Runtime Dep Test",
192
+ version: "1.0.0",
193
+ dependencies: { runtime: ["__definitely_missing_command_for_test__"] },
194
+ }),
195
+ });
196
+
197
+ const stdout = createCaptureStream();
198
+ const stderr = createCaptureStream();
199
+ const result = await runPackageCommand({
200
+ appName: "pi",
201
+ args: ["install", extensionDir],
202
+ cwd,
203
+ agentDir,
204
+ stdout: stdout.stream,
205
+ stderr: stderr.stream,
206
+ });
207
+
208
+ assert.equal(result.handled, true);
209
+ assert.equal(result.exitCode, 1);
210
+ assert.ok(stderr.getOutput().includes("Missing runtime dependencies"));
195
211
  });
196
212
 
197
- it("fails install when manifest runtime dependency is missing", async () => {
198
- const root = mkdtempSync(join(tmpdir(), "pi-lifecycle-deps-"));
199
- const cwd = join(root, "cwd");
200
- const agentDir = join(root, "agent");
201
- const extensionDir = join(root, "ext-runtime-deps");
202
- mkdirSync(cwd, { recursive: true });
203
- mkdirSync(agentDir, { recursive: true });
204
- mkdirSync(extensionDir, { recursive: true });
205
-
206
- try {
207
- writePackage(extensionDir, {
208
- "package.json": JSON.stringify({
209
- name: "ext-runtime-deps",
210
- type: "module",
211
- pi: { extensions: ["./index.js"] },
212
- }),
213
- "index.js": `export default function () {}`,
214
- "extension-manifest.json": JSON.stringify({
215
- id: "ext-runtime-deps",
216
- name: "Runtime Dep Test",
217
- version: "1.0.0",
218
- dependencies: { runtime: ["__definitely_missing_command_for_test__"] },
219
- }),
220
- });
221
-
222
- const stdout = createCaptureStream();
223
- const stderr = createCaptureStream();
224
- const result = await runPackageCommand({
225
- appName: "pi",
226
- args: ["install", extensionDir],
227
- cwd,
228
- agentDir,
229
- stdout: stdout.stream,
230
- stderr: stderr.stream,
231
- });
232
-
233
- assert.equal(result.handled, true);
234
- assert.equal(result.exitCode, 1);
235
- assert.ok(stderr.getOutput().includes("Missing runtime dependencies"));
236
- } finally {
237
- rmSync(root, { recursive: true, force: true });
238
- }
213
+ it("afterRemove hook receives installedPath even when directory is deleted", async (t) => {
214
+ const { cwd, agentDir, extensionDir } = createTestDirs("after-remove", t);
215
+
216
+ writePackage(extensionDir, {
217
+ "package.json": JSON.stringify({
218
+ name: "ext-after-remove",
219
+ type: "module",
220
+ pi: { extensions: ["./index.js"] },
221
+ }),
222
+ "index.js": [
223
+ 'import { writeFileSync, existsSync } from "node:fs";',
224
+ 'import { join } from "node:path";',
225
+ "export default function () {}",
226
+ "export async function afterRemove(ctx) {",
227
+ ' const marker = join(ctx.cwd, "after-remove-marker.json");',
228
+ " writeFileSync(marker, JSON.stringify({",
229
+ " receivedPath: ctx.installedPath,",
230
+ " pathExisted: existsSync(ctx.installedPath),",
231
+ ' }), "utf-8");',
232
+ "}",
233
+ ].join("\n"),
234
+ });
235
+
236
+ const stdout = createCaptureStream();
237
+ const stderr = createCaptureStream();
238
+
239
+ await runPackageCommand({
240
+ appName: "pi",
241
+ args: ["install", extensionDir],
242
+ cwd,
243
+ agentDir,
244
+ stdout: stdout.stream,
245
+ stderr: stderr.stream,
246
+ });
247
+
248
+ await runPackageCommand({
249
+ appName: "pi",
250
+ args: ["remove", extensionDir],
251
+ cwd,
252
+ agentDir,
253
+ stdout: stdout.stream,
254
+ stderr: stderr.stream,
255
+ });
256
+
257
+ const markerPath = join(cwd, "after-remove-marker.json");
258
+ assert.ok(existsSync(markerPath), "afterRemove hook must have executed and written marker");
259
+ const marker = JSON.parse(readFileSync(markerPath, "utf-8"));
260
+ assert.equal(typeof marker.receivedPath, "string", "hook must receive installedPath as string");
239
261
  });
240
262
  });
@@ -30,6 +30,7 @@ import { PROJECT_FILES } from "../detection.js";
30
30
  import { MergeConflictError } from "../git-service.js";
31
31
  import { join } from "node:path";
32
32
  import { existsSync, cpSync } from "node:fs";
33
+ import { logWarning, logError } from "../workflow-logger.js";
33
34
 
34
35
  // ─── generateMilestoneReport ──────────────────────────────────────────────────
35
36
 
@@ -164,8 +165,8 @@ export async function runPreDispatch(
164
165
  debugLog("autoLoop", { phase: "exit", reason: "health-gate-failed" });
165
166
  return { action: "break", reason: "health-gate-failed" };
166
167
  }
167
- } catch {
168
- // Non-fatal
168
+ } catch (e) {
169
+ logWarning("engine", "Pre-dispatch health gate threw unexpectedly", { error: String(e) });
169
170
  }
170
171
 
171
172
  // Sync project root artifacts into worktree
@@ -247,7 +248,8 @@ export async function runPreDispatch(
247
248
  await deps.stopAuto(ctx, pi, `Merge conflict on milestone ${s.currentMilestoneId}`);
248
249
  return { action: "break", reason: "merge-conflict" };
249
250
  }
250
- // Non-conflict errors — log and continue
251
+ // Non-conflict merge errors — log and continue
252
+ logWarning("engine", "Milestone merge failed with non-conflict error", { milestone: s.currentMilestoneId!, error: String(mergeErr) });
251
253
  }
252
254
 
253
255
  // PR creation (auto_pr) is handled inside mergeMilestoneToMain (#2302)
@@ -290,7 +292,9 @@ export async function runPreDispatch(
290
292
  cpSync(completedKeysPath, archivePath);
291
293
  }
292
294
  atomicWriteSync(completedKeysPath, JSON.stringify([], null, 2));
293
- } catch { /* non-fatal */ }
295
+ } catch (e) {
296
+ logWarning("engine", "Failed to archive completed-units on milestone transition", { error: String(e) });
297
+ }
294
298
 
295
299
  // Rebuild STATE.md immediately so it reflects the new active milestone.
296
300
  // This bypasses the 30-second throttle in the normal rebuild path —
@@ -298,8 +302,8 @@ export async function runPreDispatch(
298
302
  // immediate write.
299
303
  try {
300
304
  await deps.rebuildState(s.basePath);
301
- } catch {
302
- // Non-fatal — STATE.md will be rebuilt on the next regular cycle
305
+ } catch (e) {
306
+ logWarning("engine", "STATE.md rebuild failed after milestone transition", { error: String(e) });
303
307
  }
304
308
  }
305
309
 
@@ -919,8 +923,8 @@ export async function runUnitPhase(
919
923
  (decisionsContent?.length ?? 0) +
920
924
  (requirementsContent?.length ?? 0) +
921
925
  (projectContent?.length ?? 0);
922
- } catch {
923
- // Non-fatal
926
+ } catch (e) {
927
+ logWarning("engine", "Baseline char count measurement failed", { error: String(e) });
924
928
  }
925
929
  }
926
930
 
@@ -930,9 +934,7 @@ export async function runUnitPhase(
930
934
  } catch (reorderErr) {
931
935
  const msg =
932
936
  reorderErr instanceof Error ? reorderErr.message : String(reorderErr);
933
- process.stderr.write(
934
- `[gsd] prompt reorder failed (non-fatal): ${msg}\n`,
935
- );
937
+ logWarning("engine", "Prompt reorder failed", { error: msg });
936
938
  }
937
939
 
938
940
  // Select and apply model (with tier escalation on retry — normal units only)
@@ -1135,7 +1137,9 @@ export async function runUnitPhase(
1135
1137
  const completedKeysPath = join(gsdRoot(s.basePath), "completed-units.json");
1136
1138
  const keys = s.completedUnits.map((u) => `${u.type}/${u.id}`);
1137
1139
  atomicWriteSync(completedKeysPath, JSON.stringify(keys, null, 2));
1138
- } catch { /* non-fatal: disk flush failure */ }
1140
+ } catch (e) {
1141
+ logWarning("engine", "Failed to flush completed-units to disk", { error: String(e) });
1142
+ }
1139
1143
 
1140
1144
  deps.clearUnitRuntimeRecord(s.basePath, unitType, unitId);
1141
1145
  s.unitDispatchCount.delete(`${unitType}/${unitId}`);
@@ -11,6 +11,7 @@ import { NEW_SESSION_TIMEOUT_MS } from "./session.js";
11
11
  import type { UnitResult } from "./types.js";
12
12
  import { _setCurrentResolve, _setSessionSwitchInFlight } from "./resolve.js";
13
13
  import { debugLog } from "../debug-logger.js";
14
+ import { logWarning, logError } from "../workflow-logger.js";
14
15
 
15
16
  /**
16
17
  * Execute a single unit: create a new session, send the prompt, and await
@@ -85,7 +86,9 @@ export async function runUnit(
85
86
  if (process.cwd() !== s.basePath) {
86
87
  process.chdir(s.basePath);
87
88
  }
88
- } catch { /* non-fatal — chdir may fail if dir was removed */ }
89
+ } catch (e) {
90
+ logWarning("engine", "Failed to chdir to basePath before dispatch", { basePath: s.basePath, error: String(e) });
91
+ }
89
92
 
90
93
  // ── Send the prompt ──
91
94
  debugLog("runUnit", { phase: "send-message", unitType, unitId });
@@ -115,8 +118,8 @@ export async function runUnit(
115
118
  if (typeof cmdCtxAny?.clearQueue === "function") {
116
119
  (cmdCtxAny.clearQueue as () => unknown)();
117
120
  }
118
- } catch {
119
- // Non-fatal clearQueue may not be available in all contexts
121
+ } catch (e) {
122
+ logWarning("engine", "clearQueue failed after unit completion", { error: String(e) });
120
123
  }
121
124
 
122
125
  return result;
@@ -42,6 +42,7 @@ import {
42
42
  } from "./worktree.js";
43
43
  import { MergeConflictError, readIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
44
44
  import { debugLog } from "./debug-logger.js";
45
+ import { logWarning } from "./workflow-logger.js";
45
46
  import { loadEffectiveGSDPreferences } from "./preferences.js";
46
47
  import {
47
48
  nativeGetCurrentBranch,
@@ -700,7 +701,7 @@ export function createAutoWorktree(
700
701
  const hookError = runWorktreePostCreateHook(basePath, info.path);
701
702
  if (hookError) {
702
703
  // Non-fatal — log but don't prevent worktree usage
703
- console.error(`[GSD] ${hookError}`);
704
+ logWarning("reconcile", hookError, { worktree: info.name });
704
705
  }
705
706
 
706
707
  const previousCwd = process.cwd();
@@ -793,10 +794,12 @@ export function teardownAutoWorktree(
793
794
  // backslashes (#1436), leaving ~1 GB+ orphaned directories.
794
795
  const wtDir = worktreePath(originalBasePath, milestoneId);
795
796
  if (existsSync(wtDir)) {
796
- console.error(
797
- `[GSD] WARNING: Worktree directory still exists after teardown: ${wtDir}\n` +
798
- ` This is likely an orphaned directory consuming disk space.\n` +
799
- ` Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`,
797
+ logWarning(
798
+ "reconcile",
799
+ `Worktree directory still exists after teardown: ${wtDir}. ` +
800
+ `This is likely an orphaned directory consuming disk space. ` +
801
+ `Remove it manually with: rm -rf "${wtDir.replaceAll("\\", "/")}"`,
802
+ { worktree: milestoneId },
800
803
  );
801
804
  // Attempt a direct filesystem removal as a fallback
802
805
  try {
@@ -250,9 +250,9 @@ const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
250
250
 
251
251
  export function shouldUseWorktreeIsolation(): boolean {
252
252
  const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
253
- if (prefs?.isolation === "none") return false;
254
- if (prefs?.isolation === "branch") return false;
255
- return true; // default: worktree
253
+ if (prefs?.isolation === "worktree") return true;
254
+ // Default is false — worktree isolation requires explicit opt-in
255
+ return false;
256
256
  }
257
257
 
258
258
  /** Crash recovery prompt — set by startAuto, consumed by the main loop */