gsd-pi 2.59.0-dev.d77b3dd → 2.60.0-dev.2580e65

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 (198) hide show
  1. package/dist/resources/extensions/ask-user-questions.js +7 -4
  2. package/dist/resources/extensions/gsd/auto/phases.js +15 -7
  3. package/dist/resources/extensions/gsd/auto-dashboard.js +21 -8
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +6 -3
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +58 -9
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +3 -2
  7. package/dist/resources/extensions/gsd/auto-prompts.js +36 -20
  8. package/dist/resources/extensions/gsd/auto-recovery.js +37 -18
  9. package/dist/resources/extensions/gsd/auto-start.js +9 -5
  10. package/dist/resources/extensions/gsd/auto-timers.js +11 -5
  11. package/dist/resources/extensions/gsd/auto-unit-closeout.js +5 -3
  12. package/dist/resources/extensions/gsd/auto-verification.js +3 -2
  13. package/dist/resources/extensions/gsd/auto-worktree.js +120 -55
  14. package/dist/resources/extensions/gsd/auto.js +39 -17
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -3
  16. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -2
  17. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +4 -10
  18. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +2 -1
  19. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -0
  20. package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -10
  21. package/dist/resources/extensions/gsd/commands/catalog.js +2 -0
  22. package/dist/resources/extensions/gsd/commands-codebase.js +48 -21
  23. package/dist/resources/extensions/gsd/commands-inspect.js +2 -1
  24. package/dist/resources/extensions/gsd/commands-maintenance.js +32 -19
  25. package/dist/resources/extensions/gsd/complexity-classifier.js +8 -4
  26. package/dist/resources/extensions/gsd/custom-verification.js +3 -2
  27. package/dist/resources/extensions/gsd/gsd-db.js +33 -13
  28. package/dist/resources/extensions/gsd/guided-flow.js +19 -9
  29. package/dist/resources/extensions/gsd/init-wizard.js +12 -0
  30. package/dist/resources/extensions/gsd/markdown-renderer.js +11 -9
  31. package/dist/resources/extensions/gsd/md-importer.js +5 -4
  32. package/dist/resources/extensions/gsd/milestone-actions.js +3 -2
  33. package/dist/resources/extensions/gsd/milestone-ids.js +2 -1
  34. package/dist/resources/extensions/gsd/model-router.js +156 -121
  35. package/dist/resources/extensions/gsd/parallel-merge.js +5 -3
  36. package/dist/resources/extensions/gsd/parallel-orchestrator.js +26 -14
  37. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  38. package/dist/resources/extensions/gsd/preferences-validation.js +45 -0
  39. package/dist/resources/extensions/gsd/preferences.js +15 -3
  40. package/dist/resources/extensions/gsd/prompt-loader.js +3 -2
  41. package/dist/resources/extensions/gsd/prompts/rethink.md +1 -1
  42. package/dist/resources/extensions/gsd/rule-registry.js +7 -6
  43. package/dist/resources/extensions/gsd/safe-fs.js +6 -8
  44. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -2
  45. package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -2
  46. package/dist/resources/extensions/gsd/tools/complete-task.js +3 -2
  47. package/dist/resources/extensions/gsd/tools/plan-milestone.js +3 -2
  48. package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -2
  49. package/dist/resources/extensions/gsd/tools/plan-task.js +2 -1
  50. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +4 -4
  51. package/dist/resources/extensions/gsd/tools/reopen-slice.js +2 -1
  52. package/dist/resources/extensions/gsd/tools/reopen-task.js +2 -1
  53. package/dist/resources/extensions/gsd/tools/replan-slice.js +2 -1
  54. package/dist/resources/extensions/gsd/tools/validate-milestone.js +2 -1
  55. package/dist/resources/extensions/gsd/triage-resolution.js +11 -4
  56. package/dist/resources/extensions/gsd/workflow-events.js +2 -1
  57. package/dist/resources/extensions/gsd/workflow-logger.js +37 -4
  58. package/dist/resources/extensions/gsd/workflow-migration.js +14 -12
  59. package/dist/resources/extensions/gsd/workflow-projections.js +2 -2
  60. package/dist/resources/extensions/gsd/workflow-reconcile.js +2 -2
  61. package/dist/resources/extensions/gsd/worktree-manager.js +26 -14
  62. package/dist/resources/extensions/shared/interview-ui.js +3 -1
  63. package/dist/web/standalone/.next/BUILD_ID +1 -1
  64. package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
  65. package/dist/web/standalone/.next/build-manifest.json +2 -2
  66. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  67. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  68. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.html +1 -1
  84. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
  91. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  92. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  93. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  94. package/package.json +1 -1
  95. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  97. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -1
  99. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/extensions/runner.js +16 -0
  101. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  102. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +26 -0
  103. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  104. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/lsp/config.d.ts.map +1 -1
  106. package/packages/pi-coding-agent/dist/core/lsp/config.js +6 -1
  107. package/packages/pi-coding-agent/dist/core/lsp/config.js.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/lsp/defaults.json +2 -2
  109. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts +2 -0
  110. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts.map +1 -0
  111. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js +47 -0
  112. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js.map +1 -0
  113. package/packages/pi-coding-agent/package.json +1 -1
  114. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  115. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -0
  116. package/packages/pi-coding-agent/src/core/extensions/types.ts +26 -0
  117. package/packages/pi-coding-agent/src/core/lsp/config.ts +7 -1
  118. package/packages/pi-coding-agent/src/core/lsp/defaults.json +2 -2
  119. package/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts +70 -0
  120. package/pkg/package.json +1 -1
  121. package/src/resources/extensions/ask-user-questions.ts +7 -3
  122. package/src/resources/extensions/gsd/auto/phases.ts +17 -7
  123. package/src/resources/extensions/gsd/auto-dashboard.ts +22 -8
  124. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -3
  125. package/src/resources/extensions/gsd/auto-model-selection.ts +77 -15
  126. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -4
  127. package/src/resources/extensions/gsd/auto-prompts.ts +37 -20
  128. package/src/resources/extensions/gsd/auto-recovery.ts +38 -18
  129. package/src/resources/extensions/gsd/auto-start.ts +10 -9
  130. package/src/resources/extensions/gsd/auto-timers.ts +12 -5
  131. package/src/resources/extensions/gsd/auto-unit-closeout.ts +6 -2
  132. package/src/resources/extensions/gsd/auto-verification.ts +3 -6
  133. package/src/resources/extensions/gsd/auto-worktree.ts +121 -55
  134. package/src/resources/extensions/gsd/auto.ts +40 -17
  135. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +4 -3
  136. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +2 -2
  137. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +4 -16
  138. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +2 -1
  139. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
  140. package/src/resources/extensions/gsd/bootstrap/system-context.ts +11 -10
  141. package/src/resources/extensions/gsd/commands/catalog.ts +2 -0
  142. package/src/resources/extensions/gsd/commands-codebase.ts +52 -20
  143. package/src/resources/extensions/gsd/commands-inspect.ts +2 -1
  144. package/src/resources/extensions/gsd/commands-maintenance.ts +28 -19
  145. package/src/resources/extensions/gsd/complexity-classifier.ts +9 -4
  146. package/src/resources/extensions/gsd/custom-verification.ts +3 -2
  147. package/src/resources/extensions/gsd/gsd-db.ts +12 -14
  148. package/src/resources/extensions/gsd/guided-flow.ts +9 -8
  149. package/src/resources/extensions/gsd/init-wizard.ts +12 -0
  150. package/src/resources/extensions/gsd/markdown-renderer.ts +11 -17
  151. package/src/resources/extensions/gsd/md-importer.ts +5 -4
  152. package/src/resources/extensions/gsd/milestone-actions.ts +3 -2
  153. package/src/resources/extensions/gsd/milestone-ids.ts +2 -1
  154. package/src/resources/extensions/gsd/model-router.ts +199 -173
  155. package/src/resources/extensions/gsd/parallel-merge.ts +5 -3
  156. package/src/resources/extensions/gsd/parallel-orchestrator.ts +18 -14
  157. package/src/resources/extensions/gsd/preferences-types.ts +13 -0
  158. package/src/resources/extensions/gsd/preferences-validation.ts +45 -0
  159. package/src/resources/extensions/gsd/preferences.ts +16 -3
  160. package/src/resources/extensions/gsd/prompt-loader.ts +3 -2
  161. package/src/resources/extensions/gsd/prompts/rethink.md +1 -1
  162. package/src/resources/extensions/gsd/rule-registry.ts +7 -6
  163. package/src/resources/extensions/gsd/safe-fs.ts +6 -5
  164. package/src/resources/extensions/gsd/tests/capability-router.test.ts +347 -0
  165. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +63 -0
  166. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +27 -2
  167. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  168. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +1188 -0
  169. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +841 -0
  170. package/src/resources/extensions/gsd/tests/model-router.test.ts +403 -3
  171. package/src/resources/extensions/gsd/tests/preferences.test.ts +62 -0
  172. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +21 -0
  173. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +284 -0
  174. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +120 -0
  175. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +6 -6
  176. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -6
  177. package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -6
  178. package/src/resources/extensions/gsd/tools/complete-task.ts +3 -6
  179. package/src/resources/extensions/gsd/tools/plan-milestone.ts +3 -6
  180. package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -6
  181. package/src/resources/extensions/gsd/tools/plan-task.ts +2 -3
  182. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +4 -6
  183. package/src/resources/extensions/gsd/tools/reopen-slice.ts +2 -3
  184. package/src/resources/extensions/gsd/tools/reopen-task.ts +2 -3
  185. package/src/resources/extensions/gsd/tools/replan-slice.ts +2 -3
  186. package/src/resources/extensions/gsd/tools/validate-milestone.ts +2 -3
  187. package/src/resources/extensions/gsd/triage-resolution.ts +11 -4
  188. package/src/resources/extensions/gsd/types.ts +1 -0
  189. package/src/resources/extensions/gsd/workflow-events.ts +2 -1
  190. package/src/resources/extensions/gsd/workflow-logger.ts +52 -5
  191. package/src/resources/extensions/gsd/workflow-migration.ts +14 -12
  192. package/src/resources/extensions/gsd/workflow-projections.ts +2 -2
  193. package/src/resources/extensions/gsd/workflow-reconcile.ts +2 -2
  194. package/src/resources/extensions/gsd/worktree-manager.ts +16 -14
  195. package/src/resources/extensions/shared/interview-ui.ts +3 -1
  196. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +144 -0
  197. /package/dist/web/standalone/.next/static/{t_cBZAENjaOJIRST3dw08 → ogyMN7M-3bGGuRY08L5HR}/_buildManifest.js +0 -0
  198. /package/dist/web/standalone/.next/static/{t_cBZAENjaOJIRST3dw08 → ogyMN7M-3bGGuRY08L5HR}/_ssgManifest.js +0 -0
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Verify that catch blocks across GSD source files use the centralized
3
+ * workflow-logger (logWarning/logError) instead of raw process.stderr.write,
4
+ * console.error, or being completely empty (#3348, #3345).
5
+ *
6
+ * Two tests:
7
+ * 1. Auto-mode files must have zero empty catch blocks (fully migrated).
8
+ * 2. All GSD files must not use raw stderr/console in catch blocks.
9
+ */
10
+
11
+ import { describe, test } from "node:test";
12
+ import assert from "node:assert/strict";
13
+ import { readFileSync, readdirSync, statSync } from "node:fs";
14
+ import { join, dirname, relative } from "node:path";
15
+ import { fileURLToPath } from "node:url";
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const gsdDir = join(__dirname, "..");
19
+
20
+ /** Files exempt from the raw-stderr/console check */
21
+ const EXEMPT_FILES = new Set([
22
+ "workflow-logger.ts", // The logger itself
23
+ "debug-logger.ts", // Separate opt-in debug system
24
+ ]);
25
+
26
+ /**
27
+ * Files that have been fully migrated to workflow-logger and must not
28
+ * regress to empty catch blocks. Covers auto-mode, tools, bootstrap,
29
+ * and core infrastructure files.
30
+ */
31
+ const MIGRATED_FILES = new Set([
32
+ // auto-mode (detected dynamically below)
33
+ // tools/
34
+ "tools/complete-task.ts",
35
+ "tools/complete-slice.ts",
36
+ "tools/complete-milestone.ts",
37
+ "tools/plan-milestone.ts",
38
+ "tools/plan-slice.ts",
39
+ "tools/plan-task.ts",
40
+ "tools/reassess-roadmap.ts",
41
+ "tools/reopen-task.ts",
42
+ "tools/reopen-slice.ts",
43
+ "tools/replan-slice.ts",
44
+ "tools/validate-milestone.ts",
45
+ // bootstrap/
46
+ "bootstrap/agent-end-recovery.ts",
47
+ "bootstrap/system-context.ts",
48
+ "bootstrap/db-tools.ts",
49
+ "bootstrap/dynamic-tools.ts",
50
+ "bootstrap/journal-tools.ts",
51
+ // core infrastructure
52
+ "gsd-db.ts",
53
+ "workflow-logger.ts",
54
+ "workflow-reconcile.ts",
55
+ "workflow-migration.ts",
56
+ "workflow-projections.ts",
57
+ "workflow-events.ts",
58
+ "worktree-manager.ts",
59
+ "parallel-orchestrator.ts",
60
+ "parallel-merge.ts",
61
+ "guided-flow.ts",
62
+ "preferences.ts",
63
+ "commands-maintenance.ts",
64
+ "commands-inspect.ts",
65
+ "safe-fs.ts",
66
+ "markdown-renderer.ts",
67
+ "md-importer.ts",
68
+ "milestone-actions.ts",
69
+ "milestone-ids.ts",
70
+ "rule-registry.ts",
71
+ "custom-verification.ts",
72
+ "prompt-loader.ts",
73
+ "auto-verification.ts",
74
+ ]);
75
+
76
+ /** Patterns that indicate a catch block already uses workflow-logger */
77
+ const LOGGER_PATTERNS = [
78
+ /logWarning\s*\(/,
79
+ /logError\s*\(/,
80
+ ];
81
+
82
+ function getAutoModeFiles(): string[] {
83
+ const files: string[] = [];
84
+
85
+ // Top-level auto*.ts files
86
+ for (const f of readdirSync(gsdDir)) {
87
+ if (f.startsWith("auto") && f.endsWith(".ts") && !f.endsWith(".test.ts")) {
88
+ files.push(join(gsdDir, f));
89
+ }
90
+ }
91
+
92
+ // auto/ subdirectory
93
+ const autoSubDir = join(gsdDir, "auto");
94
+ for (const f of readdirSync(autoSubDir)) {
95
+ if (f.endsWith(".ts") && !f.endsWith(".test.ts")) {
96
+ files.push(join(autoSubDir, f));
97
+ }
98
+ }
99
+
100
+ return files;
101
+ }
102
+
103
+ function getGsdSourceFiles(): string[] {
104
+ const files: string[] = [];
105
+
106
+ function walk(dir: string): void {
107
+ for (const entry of readdirSync(dir)) {
108
+ const full = join(dir, entry);
109
+ if (entry === "tests" || entry === "node_modules") continue;
110
+ try {
111
+ const st = statSync(full);
112
+ if (st.isDirectory()) {
113
+ walk(full);
114
+ } else if (entry.endsWith(".ts") && !entry.endsWith(".test.ts") && !entry.endsWith(".d.ts")) {
115
+ files.push(full);
116
+ }
117
+ } catch {
118
+ continue;
119
+ }
120
+ }
121
+ }
122
+
123
+ walk(gsdDir);
124
+ return files;
125
+ }
126
+
127
+ /**
128
+ * Scan a file for empty catch blocks — catches whose body contains
129
+ * only whitespace and/or comments but no executable statements.
130
+ */
131
+ function findEmptyCatches(filePath: string): Array<{ line: number; text: string }> {
132
+ const content = readFileSync(filePath, "utf-8");
133
+ const lines = content.split("\n");
134
+ const results: Array<{ line: number; text: string }> = [];
135
+
136
+ for (let i = 0; i < lines.length; i++) {
137
+ const line = lines[i];
138
+
139
+ // Match catch block opening
140
+ if (!/\}\s*catch\s*(\([^)]*\))?\s*\{/.test(line)) continue;
141
+
142
+ // Inline single-line catch: } catch { ... }
143
+ const inlineMatch = line.match(/\}\s*catch\s*(\([^)]*\))?\s*\{(.*)\}\s*;?\s*$/);
144
+ if (inlineMatch) {
145
+ const body = inlineMatch[2].trim();
146
+ const stripped = body.replace(/\/\*.*?\*\//g, "").replace(/\/\/.*/g, "").trim();
147
+ if (!stripped) {
148
+ results.push({ line: i + 1, text: line.trim() });
149
+ }
150
+ continue;
151
+ }
152
+
153
+ // Multi-line catch — scan until matching }
154
+ let j = i + 1;
155
+ let depth = 1;
156
+ const bodyLines: string[] = [];
157
+ while (j < lines.length && depth > 0) {
158
+ for (const ch of lines[j]) {
159
+ if (ch === "{") depth++;
160
+ else if (ch === "}") depth--;
161
+ }
162
+ bodyLines.push(lines[j].trim());
163
+ j++;
164
+ }
165
+
166
+ const meaningful = bodyLines.slice(0, -1).filter(
167
+ (l) => l && !l.startsWith("//") && !l.startsWith("/*") && !l.startsWith("*") && l !== "}",
168
+ );
169
+
170
+ if (meaningful.length === 0) {
171
+ results.push({ line: i + 1, text: line.trim() });
172
+ }
173
+ }
174
+
175
+ return results;
176
+ }
177
+
178
+ /**
179
+ * Scan a file for catch blocks that use raw process.stderr.write or
180
+ * console.error/warn instead of workflow-logger.
181
+ */
182
+ function findRawStderrCatches(filePath: string): Array<{ line: number; text: string }> {
183
+ const content = readFileSync(filePath, "utf-8");
184
+ const lines = content.split("\n");
185
+ const results: Array<{ line: number; text: string }> = [];
186
+
187
+ for (let i = 0; i < lines.length; i++) {
188
+ const line = lines[i];
189
+ if (!/\}\s*catch\s*(\([^)]*\))?\s*\{/.test(line)) continue;
190
+
191
+ // Inline single-line catch
192
+ const inlineMatch = line.match(/\}\s*catch\s*(\([^)]*\))?\s*\{(.*)\}\s*;?\s*$/);
193
+ if (inlineMatch) {
194
+ const body = inlineMatch[2];
195
+ if (!LOGGER_PATTERNS.some((p) => p.test(body))) {
196
+ if (/process\.stderr\.write/.test(body) || /console\.(error|warn)/.test(body)) {
197
+ results.push({ line: i + 1, text: line.trim() });
198
+ }
199
+ }
200
+ continue;
201
+ }
202
+
203
+ // Multi-line catch
204
+ let j = i + 1;
205
+ let depth = 1;
206
+ const bodyLines: string[] = [];
207
+ while (j < lines.length && depth > 0) {
208
+ for (const ch of lines[j]) {
209
+ if (ch === "{") depth++;
210
+ else if (ch === "}") depth--;
211
+ }
212
+ bodyLines.push(lines[j]);
213
+ j++;
214
+ }
215
+
216
+ const bodyText = bodyLines.slice(0, -1).join("\n");
217
+ if (!LOGGER_PATTERNS.some((p) => p.test(bodyText))) {
218
+ if (/process\.stderr\.write/.test(bodyText) || /console\.(error|warn)/.test(bodyText)) {
219
+ results.push({ line: i + 1, text: line.trim() });
220
+ }
221
+ }
222
+ }
223
+
224
+ return results;
225
+ }
226
+
227
+ describe("workflow-logger coverage (#3348)", () => {
228
+ test("no empty catch blocks remain in migrated files", () => {
229
+ // Combine auto-mode files + explicitly migrated files
230
+ const autoFiles = getAutoModeFiles();
231
+ const allFiles = getGsdSourceFiles();
232
+ const migratedPaths = new Set(autoFiles);
233
+ for (const file of allFiles) {
234
+ const rel = relative(gsdDir, file);
235
+ if (MIGRATED_FILES.has(rel)) {
236
+ migratedPaths.add(file);
237
+ }
238
+ }
239
+
240
+ assert.ok(migratedPaths.size > 0, "should find migrated source files");
241
+
242
+ const violations: string[] = [];
243
+ for (const file of migratedPaths) {
244
+ const rel = relative(gsdDir, file);
245
+ const basename = rel.split("/").pop()!;
246
+ // gsd-db.ts has intentionally silent provider probes
247
+ if (basename === "gsd-db.ts" || basename === "session-lock.ts") continue;
248
+
249
+ const empties = findEmptyCatches(file);
250
+ for (const empty of empties) {
251
+ violations.push(`${rel}:${empty.line} — ${empty.text}`);
252
+ }
253
+ }
254
+
255
+ assert.equal(
256
+ violations.length,
257
+ 0,
258
+ `Found ${violations.length} empty catch block(s) in migrated files:\n${violations.join("\n")}`,
259
+ );
260
+ });
261
+
262
+ test("catch blocks use workflow-logger instead of raw stderr/console", () => {
263
+ const files = getGsdSourceFiles();
264
+ assert.ok(files.length > 0, "should find GSD source files");
265
+
266
+ const violations: string[] = [];
267
+ for (const file of files) {
268
+ const rel = relative(gsdDir, file);
269
+ const basename = rel.split("/").pop()!;
270
+ if (EXEMPT_FILES.has(basename)) continue;
271
+
272
+ const issues = findRawStderrCatches(file);
273
+ for (const issue of issues) {
274
+ violations.push(`${rel}:${issue.line} — ${issue.text}`);
275
+ }
276
+ }
277
+
278
+ assert.equal(
279
+ violations.length,
280
+ 0,
281
+ `Found ${violations.length} catch block(s) using raw stderr/console instead of workflow-logger:\n${violations.join("\n")}`,
282
+ );
283
+ });
284
+ });
@@ -0,0 +1,120 @@
1
+ // GSD Extension — Workflow Logger Audit Persistence Tests
2
+ // Validates error-only persistence, sanitization, and warning ephemeral behavior.
3
+
4
+ import { describe, test, beforeEach, afterEach } from "node:test";
5
+ import assert from "node:assert/strict";
6
+ import { mkdtempSync, mkdirSync, readFileSync, existsSync, rmSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import { tmpdir } from "node:os";
9
+
10
+ import {
11
+ logWarning,
12
+ logError,
13
+ setLogBasePath,
14
+ _resetLogs,
15
+ peekLogs,
16
+ drainLogs,
17
+ } from "../workflow-logger.ts";
18
+
19
+ function createTempProject(): string {
20
+ const tmp = mkdtempSync(join(tmpdir(), "gsd-wflog-test-"));
21
+ mkdirSync(join(tmp, ".gsd"), { recursive: true });
22
+ return tmp;
23
+ }
24
+
25
+ function readAuditLines(basePath: string): Record<string, unknown>[] {
26
+ const auditPath = join(basePath, ".gsd", "audit-log.jsonl");
27
+ if (!existsSync(auditPath)) return [];
28
+ const content = readFileSync(auditPath, "utf-8").trim();
29
+ if (!content) return [];
30
+ return content.split("\n").map((line) => JSON.parse(line));
31
+ }
32
+
33
+ describe("workflow-logger audit persistence", () => {
34
+ let tmp: string;
35
+
36
+ beforeEach(() => {
37
+ tmp = createTempProject();
38
+ _resetLogs();
39
+ setLogBasePath(tmp);
40
+ });
41
+
42
+ afterEach(() => {
43
+ _resetLogs();
44
+ setLogBasePath(null as unknown as string);
45
+ rmSync(tmp, { recursive: true, force: true });
46
+ });
47
+
48
+ test("logError persists to audit-log.jsonl", () => {
49
+ logError("engine", "something broke");
50
+ const lines = readAuditLines(tmp);
51
+ assert.equal(lines.length, 1);
52
+ assert.equal(lines[0].severity, "error");
53
+ assert.equal(lines[0].component, "engine");
54
+ });
55
+
56
+ test("logWarning does NOT persist to audit-log.jsonl", () => {
57
+ logWarning("engine", "something fishy");
58
+ const lines = readAuditLines(tmp);
59
+ assert.equal(lines.length, 0, "warnings must not be persisted to audit log");
60
+ });
61
+
62
+ test("logWarning still appears in in-memory buffer", () => {
63
+ logWarning("recovery", "probe miss");
64
+ const entries = peekLogs();
65
+ assert.equal(entries.length, 1);
66
+ assert.equal(entries[0].severity, "warn");
67
+ assert.equal(entries[0].component, "recovery");
68
+ });
69
+
70
+ test("persisted error messages are truncated at 200 chars", () => {
71
+ const longMessage = "x".repeat(300);
72
+ logError("engine", longMessage);
73
+ const lines = readAuditLines(tmp);
74
+ assert.equal(lines.length, 1);
75
+ const msg = lines[0].message as string;
76
+ assert.ok(msg.length <= 215, `message should be truncated, got ${msg.length} chars`);
77
+ assert.ok(msg.endsWith("…[truncated]"));
78
+ });
79
+
80
+ test("persisted errors have context filtered to safe allowlist", () => {
81
+ logError("tool", "tool failed", {
82
+ fn: "saveDecisionToDb",
83
+ tool: "gsd_decision_save",
84
+ error: "SQLITE_BUSY: database is locked",
85
+ file: "/home/user/project/gsd.db",
86
+ });
87
+ const lines = readAuditLines(tmp);
88
+ assert.equal(lines.length, 1);
89
+ const ctx = lines[0].context as Record<string, string>;
90
+ assert.ok(ctx, "context should exist");
91
+ assert.equal(ctx.fn, "saveDecisionToDb");
92
+ assert.equal(ctx.tool, "gsd_decision_save");
93
+ assert.equal(ctx.error, undefined, "error key must be stripped from persisted context");
94
+ assert.equal(ctx.file, undefined, "file key must be stripped from persisted context");
95
+ });
96
+
97
+ test("persisted errors omit context when no safe keys present", () => {
98
+ logError("bootstrap", "ensureDbOpen failed", {
99
+ error: "ENOENT",
100
+ cwd: "/home/user/project",
101
+ });
102
+ const lines = readAuditLines(tmp);
103
+ assert.equal(lines.length, 1);
104
+ assert.equal(lines[0].context, undefined, "context should be omitted when no safe keys match");
105
+ });
106
+
107
+ test("mixed warnings and errors only persist errors", () => {
108
+ logWarning("recovery", "main not found");
109
+ logWarning("recovery", "master not found");
110
+ logError("engine", "fatal failure");
111
+ logWarning("prompt", "cache miss");
112
+
113
+ const lines = readAuditLines(tmp);
114
+ assert.equal(lines.length, 1, "only the error should be persisted");
115
+ assert.equal(lines[0].severity, "error");
116
+
117
+ const buffered = drainLogs();
118
+ assert.equal(buffered.length, 4, "all entries should be in the in-memory buffer");
119
+ });
120
+ });
@@ -240,13 +240,13 @@ describe("workflow-logger", () => {
240
240
 
241
241
  test("writes entry to .gsd/audit-log.jsonl after setLogBasePath", () => {
242
242
  setLogBasePath(dir);
243
- logWarning("engine", "audit test entry");
243
+ logError("engine", "audit test entry");
244
244
 
245
245
  const auditPath = join(dir, ".gsd", "audit-log.jsonl");
246
246
  assert.ok(existsSync(auditPath), "audit-log.jsonl should exist");
247
247
  const content = readFileSync(auditPath, "utf-8");
248
248
  const entry = JSON.parse(content.trim());
249
- assert.equal(entry.severity, "warn");
249
+ assert.equal(entry.severity, "error");
250
250
  assert.equal(entry.component, "engine");
251
251
  assert.equal(entry.message, "audit test entry");
252
252
  });
@@ -254,7 +254,7 @@ describe("workflow-logger", () => {
254
254
  test("_resetLogs does not clear the audit base path", () => {
255
255
  setLogBasePath(dir);
256
256
  _resetLogs();
257
- logWarning("engine", "post-reset entry");
257
+ logError("engine", "post-reset entry");
258
258
 
259
259
  const auditPath = join(dir, ".gsd", "audit-log.jsonl");
260
260
  assert.ok(existsSync(auditPath), "audit-log.jsonl should exist after _resetLogs");
@@ -293,13 +293,13 @@ describe("workflow-logger", () => {
293
293
 
294
294
  test("writes entry to .gsd/audit-log.jsonl after setLogBasePath", () => {
295
295
  setLogBasePath(dir);
296
- logWarning("engine", "audit test entry");
296
+ logError("engine", "audit test entry");
297
297
 
298
298
  const auditPath = join(dir, ".gsd", "audit-log.jsonl");
299
299
  assert.ok(existsSync(auditPath), "audit-log.jsonl should exist");
300
300
  const content = readFileSync(auditPath, "utf-8");
301
301
  const entry = JSON.parse(content.trim());
302
- assert.equal(entry.severity, "warn");
302
+ assert.equal(entry.severity, "error");
303
303
  assert.equal(entry.component, "engine");
304
304
  assert.equal(entry.message, "audit test entry");
305
305
  });
@@ -307,7 +307,7 @@ describe("workflow-logger", () => {
307
307
  test("_resetLogs does not clear the audit base path", () => {
308
308
  setLogBasePath(dir);
309
309
  _resetLogs();
310
- logWarning("engine", "post-reset entry");
310
+ logError("engine", "post-reset entry");
311
311
 
312
312
  const auditPath = join(dir, ".gsd", "audit-log.jsonl");
313
313
  assert.ok(existsSync(auditPath), "audit-log.jsonl should exist after _resetLogs");
@@ -23,6 +23,7 @@ import { invalidateStateCache } from "../state.js";
23
23
  import { renderAllProjections } from "../workflow-projections.js";
24
24
  import { writeManifest } from "../workflow-manifest.js";
25
25
  import { appendEvent } from "../workflow-events.js";
26
+ import { logWarning } from "../workflow-logger.js";
26
27
 
27
28
  export interface CompleteMilestoneParams {
28
29
  milestoneId: string;
@@ -191,9 +192,7 @@ export async function handleCompleteMilestone(
191
192
  await saveFile(summaryPath, summaryMd);
192
193
  } catch (renderErr) {
193
194
  // Disk render failed — roll back DB status so state stays consistent
194
- process.stderr.write(
195
- `gsd-db: complete_milestone — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
196
- );
195
+ logWarning("tool", `complete_milestone — disk render failed, rolling back DB status: ${(renderErr as Error).message}`);
197
196
  updateMilestoneStatus(params.milestoneId, 'active', null);
198
197
  invalidateStateCache();
199
198
  return { error: `disk render failed: ${(renderErr as Error).message}` };
@@ -217,9 +216,7 @@ export async function handleCompleteMilestone(
217
216
  trigger_reason: params.triggerReason,
218
217
  });
219
218
  } catch (hookErr) {
220
- process.stderr.write(
221
- `gsd: complete-milestone post-mutation hook warning: ${(hookErr as Error).message}\n`,
222
- );
219
+ logWarning("tool", `complete-milestone post-mutation hook warning: ${(hookErr as Error).message}`);
223
220
  }
224
221
 
225
222
  return {
@@ -30,6 +30,7 @@ import { renderRoadmapCheckboxes } from "../markdown-renderer.js";
30
30
  import { renderAllProjections } from "../workflow-projections.js";
31
31
  import { writeManifest } from "../workflow-manifest.js";
32
32
  import { appendEvent } from "../workflow-events.js";
33
+ import { logWarning } from "../workflow-logger.js";
33
34
 
34
35
  export interface CompleteSliceResult {
35
36
  sliceId: string;
@@ -297,9 +298,7 @@ export async function handleCompleteSlice(
297
298
  }
298
299
  } catch (renderErr) {
299
300
  // Disk render failed — roll back DB status so state stays consistent
300
- process.stderr.write(
301
- `gsd-db: complete_slice — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
302
- );
301
+ logWarning("tool", `complete_slice — disk render failed, rolling back DB status: ${(renderErr as Error).message}`);
303
302
  updateSliceStatus(params.milestoneId, params.sliceId, 'pending');
304
303
  invalidateStateCache();
305
304
  return { error: `disk render failed: ${(renderErr as Error).message}` };
@@ -326,9 +325,7 @@ export async function handleCompleteSlice(
326
325
  trigger_reason: params.triggerReason,
327
326
  });
328
327
  } catch (hookErr) {
329
- process.stderr.write(
330
- `gsd: complete-slice post-mutation hook warning: ${(hookErr as Error).message}\n`,
331
- );
328
+ logWarning("tool", `complete-slice post-mutation hook warning: ${(hookErr as Error).message}`);
332
329
  }
333
330
 
334
331
  return {
@@ -33,6 +33,7 @@ import { renderPlanCheckboxes } from "../markdown-renderer.js";
33
33
  import { renderAllProjections, renderSummaryContent } from "../workflow-projections.js";
34
34
  import { writeManifest } from "../workflow-manifest.js";
35
35
  import { appendEvent } from "../workflow-events.js";
36
+ import { logWarning } from "../workflow-logger.js";
36
37
 
37
38
  export interface CompleteTaskResult {
38
39
  taskId: string;
@@ -210,9 +211,7 @@ export async function handleCompleteTask(
210
211
  }
211
212
  } catch (renderErr) {
212
213
  // Disk render failed — roll back DB status so state stays consistent
213
- process.stderr.write(
214
- `gsd-db: complete_task — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
215
- );
214
+ logWarning("tool", `complete_task — disk render failed, rolling back DB status: ${(renderErr as Error).message}`);
216
215
  // Delete orphaned verification_evidence rows first (FK constraint
217
216
  // references tasks, so evidence must go before status change).
218
217
  // Without this, retries accumulate duplicate evidence rows (#2724).
@@ -243,9 +242,7 @@ export async function handleCompleteTask(
243
242
  trigger_reason: params.triggerReason,
244
243
  });
245
244
  } catch (hookErr) {
246
- process.stderr.write(
247
- `gsd: complete-task post-mutation hook warning: ${(hookErr as Error).message}\n`,
248
- );
245
+ logWarning("tool", `complete-task post-mutation hook warning: ${(hookErr as Error).message}`);
249
246
  }
250
247
 
251
248
  return {
@@ -15,6 +15,7 @@ import { renderRoadmapFromDb } from "../markdown-renderer.js";
15
15
  import { renderAllProjections } from "../workflow-projections.js";
16
16
  import { writeManifest } from "../workflow-manifest.js";
17
17
  import { appendEvent } from "../workflow-events.js";
18
+ import { logWarning } from "../workflow-logger.js";
18
19
 
19
20
  export interface PlanMilestoneSliceInput {
20
21
  sliceId: string;
@@ -269,9 +270,7 @@ export async function handlePlanMilestone(
269
270
  const renderResult = await renderRoadmapFromDb(basePath, params.milestoneId);
270
271
  roadmapPath = renderResult.roadmapPath;
271
272
  } catch (renderErr) {
272
- process.stderr.write(
273
- `gsd-db: plan_milestone — render failed (DB rows preserved for debugging): ${(renderErr as Error).message}\n`,
274
- );
273
+ logWarning("tool", `plan_milestone — render failed (DB rows preserved for debugging): ${(renderErr as Error).message}`);
275
274
  invalidateStateCache();
276
275
  return { error: `render failed: ${(renderErr as Error).message}` };
277
276
  }
@@ -292,9 +291,7 @@ export async function handlePlanMilestone(
292
291
  trigger_reason: params.triggerReason,
293
292
  });
294
293
  } catch (hookErr) {
295
- process.stderr.write(
296
- `gsd: plan-milestone post-mutation hook warning: ${(hookErr as Error).message}\n`,
297
- );
294
+ logWarning("tool", `plan-milestone post-mutation hook warning: ${(hookErr as Error).message}`);
298
295
  }
299
296
 
300
297
  return {
@@ -16,6 +16,7 @@ import { renderPlanFromDb } from "../markdown-renderer.js";
16
16
  import { renderAllProjections } from "../workflow-projections.js";
17
17
  import { writeManifest } from "../workflow-manifest.js";
18
18
  import { appendEvent } from "../workflow-events.js";
19
+ import { logWarning } from "../workflow-logger.js";
19
20
 
20
21
  export interface PlanSliceTaskInput {
21
22
  taskId: string;
@@ -229,9 +230,7 @@ export async function handlePlanSlice(
229
230
  trigger_reason: params.triggerReason,
230
231
  });
231
232
  } catch (hookErr) {
232
- process.stderr.write(
233
- `gsd: plan-slice post-mutation hook warning: ${(hookErr as Error).message}\n`,
234
- );
233
+ logWarning("tool", `plan-slice post-mutation hook warning: ${(hookErr as Error).message}`);
235
234
  }
236
235
 
237
236
  return {
@@ -241,9 +240,7 @@ export async function handlePlanSlice(
241
240
  taskPlanPaths: renderResult.taskPlanPaths,
242
241
  };
243
242
  } catch (renderErr) {
244
- process.stderr.write(
245
- `gsd-db: plan_slice — render failed (DB rows preserved for debugging): ${(renderErr as Error).message}\n`,
246
- );
243
+ logWarning("tool", `plan_slice — render failed (DB rows preserved for debugging): ${(renderErr as Error).message}`);
247
244
  invalidateStateCache();
248
245
  return { error: `render failed: ${(renderErr as Error).message}` };
249
246
  }
@@ -7,6 +7,7 @@ import { renderTaskPlanFromDb } from "../markdown-renderer.js";
7
7
  import { renderAllProjections } from "../workflow-projections.js";
8
8
  import { writeManifest } from "../workflow-manifest.js";
9
9
  import { appendEvent } from "../workflow-events.js";
10
+ import { logWarning } from "../workflow-logger.js";
10
11
 
11
12
  export interface PlanTaskParams {
12
13
  milestoneId: string;
@@ -135,9 +136,7 @@ export async function handlePlanTask(
135
136
  trigger_reason: params.triggerReason,
136
137
  });
137
138
  } catch (hookErr) {
138
- process.stderr.write(
139
- `gsd: plan-task post-mutation hook warning: ${(hookErr as Error).message}\n`,
140
- );
139
+ logWarning("tool", `plan-task post-mutation hook warning: ${(hookErr as Error).message}`);
141
140
  }
142
141
 
143
142
  return {
@@ -19,6 +19,7 @@ import { renderRoadmapFromDb, renderAssessmentFromDb } from "../markdown-rendere
19
19
  import { renderAllProjections } from "../workflow-projections.js";
20
20
  import { writeManifest } from "../workflow-manifest.js";
21
21
  import { appendEvent } from "../workflow-events.js";
22
+ import { logWarning } from "../workflow-logger.js";
22
23
 
23
24
  export interface SliceChangeInput {
24
25
  sliceId: string;
@@ -248,9 +249,8 @@ export async function handleReassessRoadmap(
248
249
  );
249
250
  try {
250
251
  if (existsSync(validationFile)) unlinkSync(validationFile);
251
- } catch {
252
- // Best-effort: DB row is already deleted, so state derivation
253
- // will not see the file-based verdict as authoritative.
252
+ } catch (e) {
253
+ logWarning("tool", `validation file cleanup failed: ${(e as Error).message}`);
254
254
  }
255
255
  }
256
256
 
@@ -271,9 +271,7 @@ export async function handleReassessRoadmap(
271
271
  trigger_reason: params.triggerReason,
272
272
  });
273
273
  } catch (hookErr) {
274
- process.stderr.write(
275
- `gsd: reassess-roadmap post-mutation hook warning: ${(hookErr as Error).message}\n`,
276
- );
274
+ logWarning("tool", `reassess-roadmap post-mutation hook warning: ${(hookErr as Error).message}`);
277
275
  }
278
276
 
279
277
  return {
@@ -24,6 +24,7 @@ import { isClosedStatus } from "../status-guards.js";
24
24
  import { renderAllProjections } from "../workflow-projections.js";
25
25
  import { writeManifest } from "../workflow-manifest.js";
26
26
  import { appendEvent } from "../workflow-events.js";
27
+ import { logWarning } from "../workflow-logger.js";
27
28
 
28
29
  export interface ReopenSliceParams {
29
30
  milestoneId: string;
@@ -113,9 +114,7 @@ export async function handleReopenSlice(
113
114
  trigger_reason: params.triggerReason,
114
115
  });
115
116
  } catch (hookErr) {
116
- process.stderr.write(
117
- `gsd: reopen-slice post-mutation hook warning: ${(hookErr as Error).message}\n`,
118
- );
117
+ logWarning("tool", `reopen-slice post-mutation hook warning: ${(hookErr as Error).message}`);
119
118
  }
120
119
 
121
120
  return {