gsd-pi 2.60.0-dev.2580e65 → 2.60.0-dev.d9052f5

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 (196) hide show
  1. package/dist/resources/extensions/ask-user-questions.js +4 -7
  2. package/dist/resources/extensions/gsd/auto/phases.js +7 -15
  3. package/dist/resources/extensions/gsd/auto-dashboard.js +8 -21
  4. package/dist/resources/extensions/gsd/auto-dispatch.js +3 -6
  5. package/dist/resources/extensions/gsd/auto-model-selection.js +9 -58
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +2 -3
  7. package/dist/resources/extensions/gsd/auto-prompts.js +20 -36
  8. package/dist/resources/extensions/gsd/auto-recovery.js +18 -37
  9. package/dist/resources/extensions/gsd/auto-start.js +5 -9
  10. package/dist/resources/extensions/gsd/auto-timers.js +5 -11
  11. package/dist/resources/extensions/gsd/auto-unit-closeout.js +3 -5
  12. package/dist/resources/extensions/gsd/auto-verification.js +2 -3
  13. package/dist/resources/extensions/gsd/auto-worktree.js +55 -120
  14. package/dist/resources/extensions/gsd/auto.js +17 -39
  15. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +3 -6
  16. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -2
  17. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +10 -4
  18. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +1 -2
  19. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +0 -7
  20. package/dist/resources/extensions/gsd/bootstrap/system-context.js +10 -11
  21. package/dist/resources/extensions/gsd/commands/catalog.js +0 -2
  22. package/dist/resources/extensions/gsd/commands-codebase.js +21 -48
  23. package/dist/resources/extensions/gsd/commands-inspect.js +1 -2
  24. package/dist/resources/extensions/gsd/commands-maintenance.js +19 -32
  25. package/dist/resources/extensions/gsd/complexity-classifier.js +4 -8
  26. package/dist/resources/extensions/gsd/custom-verification.js +2 -3
  27. package/dist/resources/extensions/gsd/gsd-db.js +13 -33
  28. package/dist/resources/extensions/gsd/guided-flow.js +9 -19
  29. package/dist/resources/extensions/gsd/init-wizard.js +0 -12
  30. package/dist/resources/extensions/gsd/markdown-renderer.js +9 -11
  31. package/dist/resources/extensions/gsd/md-importer.js +4 -5
  32. package/dist/resources/extensions/gsd/milestone-actions.js +2 -3
  33. package/dist/resources/extensions/gsd/milestone-ids.js +1 -2
  34. package/dist/resources/extensions/gsd/model-router.js +121 -156
  35. package/dist/resources/extensions/gsd/parallel-merge.js +3 -5
  36. package/dist/resources/extensions/gsd/parallel-orchestrator.js +14 -26
  37. package/dist/resources/extensions/gsd/preferences-types.js +0 -1
  38. package/dist/resources/extensions/gsd/preferences-validation.js +0 -45
  39. package/dist/resources/extensions/gsd/preferences.js +3 -15
  40. package/dist/resources/extensions/gsd/prompt-loader.js +2 -3
  41. package/dist/resources/extensions/gsd/prompts/rethink.md +1 -1
  42. package/dist/resources/extensions/gsd/rule-registry.js +6 -7
  43. package/dist/resources/extensions/gsd/safe-fs.js +8 -6
  44. package/dist/resources/extensions/gsd/tools/complete-milestone.js +2 -3
  45. package/dist/resources/extensions/gsd/tools/complete-slice.js +2 -3
  46. package/dist/resources/extensions/gsd/tools/complete-task.js +2 -3
  47. package/dist/resources/extensions/gsd/tools/plan-milestone.js +2 -3
  48. package/dist/resources/extensions/gsd/tools/plan-slice.js +2 -3
  49. package/dist/resources/extensions/gsd/tools/plan-task.js +1 -2
  50. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +4 -4
  51. package/dist/resources/extensions/gsd/tools/reopen-slice.js +1 -2
  52. package/dist/resources/extensions/gsd/tools/reopen-task.js +1 -2
  53. package/dist/resources/extensions/gsd/tools/replan-slice.js +1 -2
  54. package/dist/resources/extensions/gsd/tools/validate-milestone.js +1 -2
  55. package/dist/resources/extensions/gsd/triage-resolution.js +4 -11
  56. package/dist/resources/extensions/gsd/workflow-events.js +1 -2
  57. package/dist/resources/extensions/gsd/workflow-logger.js +4 -37
  58. package/dist/resources/extensions/gsd/workflow-migration.js +12 -14
  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 +14 -26
  62. package/dist/resources/extensions/shared/interview-ui.js +1 -3
  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 +0 -5
  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 +1 -2
  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 +0 -16
  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 +0 -26
  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 +1 -6
  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/src/core/extensions/loader.ts +0 -6
  110. package/packages/pi-coding-agent/src/core/extensions/runner.ts +0 -19
  111. package/packages/pi-coding-agent/src/core/extensions/types.ts +0 -26
  112. package/packages/pi-coding-agent/src/core/lsp/config.ts +1 -7
  113. package/packages/pi-coding-agent/src/core/lsp/defaults.json +2 -2
  114. package/src/resources/extensions/ask-user-questions.ts +3 -7
  115. package/src/resources/extensions/gsd/auto/phases.ts +7 -17
  116. package/src/resources/extensions/gsd/auto-dashboard.ts +8 -22
  117. package/src/resources/extensions/gsd/auto-dispatch.ts +3 -7
  118. package/src/resources/extensions/gsd/auto-model-selection.ts +15 -77
  119. package/src/resources/extensions/gsd/auto-post-unit.ts +4 -4
  120. package/src/resources/extensions/gsd/auto-prompts.ts +20 -37
  121. package/src/resources/extensions/gsd/auto-recovery.ts +18 -38
  122. package/src/resources/extensions/gsd/auto-start.ts +9 -10
  123. package/src/resources/extensions/gsd/auto-timers.ts +5 -12
  124. package/src/resources/extensions/gsd/auto-unit-closeout.ts +2 -6
  125. package/src/resources/extensions/gsd/auto-verification.ts +6 -3
  126. package/src/resources/extensions/gsd/auto-worktree.ts +55 -121
  127. package/src/resources/extensions/gsd/auto.ts +17 -40
  128. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +3 -4
  129. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +2 -2
  130. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -4
  131. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +1 -2
  132. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +0 -8
  133. package/src/resources/extensions/gsd/bootstrap/system-context.ts +10 -11
  134. package/src/resources/extensions/gsd/commands/catalog.ts +0 -2
  135. package/src/resources/extensions/gsd/commands-codebase.ts +20 -52
  136. package/src/resources/extensions/gsd/commands-inspect.ts +1 -2
  137. package/src/resources/extensions/gsd/commands-maintenance.ts +19 -28
  138. package/src/resources/extensions/gsd/complexity-classifier.ts +4 -9
  139. package/src/resources/extensions/gsd/custom-verification.ts +2 -3
  140. package/src/resources/extensions/gsd/gsd-db.ts +14 -12
  141. package/src/resources/extensions/gsd/guided-flow.ts +8 -9
  142. package/src/resources/extensions/gsd/init-wizard.ts +0 -12
  143. package/src/resources/extensions/gsd/markdown-renderer.ts +17 -11
  144. package/src/resources/extensions/gsd/md-importer.ts +4 -5
  145. package/src/resources/extensions/gsd/milestone-actions.ts +2 -3
  146. package/src/resources/extensions/gsd/milestone-ids.ts +1 -2
  147. package/src/resources/extensions/gsd/model-router.ts +173 -199
  148. package/src/resources/extensions/gsd/parallel-merge.ts +3 -5
  149. package/src/resources/extensions/gsd/parallel-orchestrator.ts +14 -18
  150. package/src/resources/extensions/gsd/preferences-types.ts +0 -13
  151. package/src/resources/extensions/gsd/preferences-validation.ts +0 -45
  152. package/src/resources/extensions/gsd/preferences.ts +3 -16
  153. package/src/resources/extensions/gsd/prompt-loader.ts +2 -3
  154. package/src/resources/extensions/gsd/prompts/rethink.md +1 -1
  155. package/src/resources/extensions/gsd/rule-registry.ts +6 -7
  156. package/src/resources/extensions/gsd/safe-fs.ts +5 -6
  157. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +0 -63
  158. package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +2 -27
  159. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +4 -4
  160. package/src/resources/extensions/gsd/tests/model-router.test.ts +3 -403
  161. package/src/resources/extensions/gsd/tests/preferences.test.ts +0 -62
  162. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +0 -21
  163. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +6 -6
  164. package/src/resources/extensions/gsd/tools/complete-milestone.ts +6 -3
  165. package/src/resources/extensions/gsd/tools/complete-slice.ts +6 -3
  166. package/src/resources/extensions/gsd/tools/complete-task.ts +6 -3
  167. package/src/resources/extensions/gsd/tools/plan-milestone.ts +6 -3
  168. package/src/resources/extensions/gsd/tools/plan-slice.ts +6 -3
  169. package/src/resources/extensions/gsd/tools/plan-task.ts +3 -2
  170. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +6 -4
  171. package/src/resources/extensions/gsd/tools/reopen-slice.ts +3 -2
  172. package/src/resources/extensions/gsd/tools/reopen-task.ts +3 -2
  173. package/src/resources/extensions/gsd/tools/replan-slice.ts +3 -2
  174. package/src/resources/extensions/gsd/tools/validate-milestone.ts +3 -2
  175. package/src/resources/extensions/gsd/triage-resolution.ts +4 -11
  176. package/src/resources/extensions/gsd/types.ts +0 -1
  177. package/src/resources/extensions/gsd/workflow-events.ts +1 -2
  178. package/src/resources/extensions/gsd/workflow-logger.ts +5 -52
  179. package/src/resources/extensions/gsd/workflow-migration.ts +12 -14
  180. package/src/resources/extensions/gsd/workflow-projections.ts +2 -2
  181. package/src/resources/extensions/gsd/workflow-reconcile.ts +2 -2
  182. package/src/resources/extensions/gsd/worktree-manager.ts +14 -16
  183. package/src/resources/extensions/shared/interview-ui.ts +1 -3
  184. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts +0 -2
  185. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.d.ts.map +0 -1
  186. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js +0 -47
  187. package/packages/pi-coding-agent/dist/core/lsp/lsp-legacy-alias.test.js.map +0 -1
  188. package/packages/pi-coding-agent/src/core/lsp/lsp-legacy-alias.test.ts +0 -70
  189. package/src/resources/extensions/gsd/tests/capability-router.test.ts +0 -347
  190. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +0 -1188
  191. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +0 -841
  192. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +0 -284
  193. package/src/resources/extensions/gsd/tests/workflow-logger-audit.test.ts +0 -120
  194. package/src/resources/extensions/shared/tests/interview-notes-loop.test.ts +0 -144
  195. /package/dist/web/standalone/.next/static/{ogyMN7M-3bGGuRY08L5HR → JVkoVYumy0cDhOQISEYdG}/_buildManifest.js +0 -0
  196. /package/dist/web/standalone/.next/static/{ogyMN7M-3bGGuRY08L5HR → JVkoVYumy0cDhOQISEYdG}/_ssgManifest.js +0 -0
@@ -1,4 +1,4 @@
1
- import test, { describe } from "node:test";
1
+ import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
 
4
4
  import {
@@ -7,8 +7,6 @@ import {
7
7
  defaultRoutingConfig,
8
8
  scoreModel,
9
9
  computeTaskRequirements,
10
- scoreEligibleModels,
11
- getEligibleModels,
12
10
  MODEL_CAPABILITY_PROFILES,
13
11
  } from "../model-router.js";
14
12
  import type { DynamicRoutingConfig, RoutingDecision, ModelCapabilities } from "../model-router.js";
@@ -213,9 +211,9 @@ test("#2192: known model is still downgraded normally", () => {
213
211
 
214
212
  // ─── Capability Scoring (ADR-004 Phase 2) ───────────────────────────────────
215
213
 
216
- test("defaultRoutingConfig includes capability_routing: true", () => {
214
+ test("defaultRoutingConfig includes capability_routing: false", () => {
217
215
  const config = defaultRoutingConfig();
218
- assert.equal(config.capability_routing, true);
216
+ assert.equal(config.capability_routing, false);
219
217
  });
220
218
 
221
219
  test("scoreModel computes weighted average of capability × requirement", () => {
@@ -358,401 +356,3 @@ test("#2885: heavy openai-codex model downgrades to light for light task", () =>
358
356
  // Should pick a light-tier model
359
357
  assert.notEqual(result.modelId, "gpt-5.4", "should not use the heavy model for light task");
360
358
  });
361
- // ─── scoreModel ──────────────────────────────────────────────────────────────
362
-
363
- describe("scoreModel", () => {
364
- const sonnetProfile: ModelCapabilities = MODEL_CAPABILITY_PROFILES["claude-sonnet-4-6"]!;
365
-
366
- test("produces correct weighted average for two dimensions (coding:0.9, instruction:0.7)", () => {
367
- // (0.9*85 + 0.7*85) / (0.9+0.7) = (76.5+59.5)/1.6 = 136/1.6 = 85.0
368
- const score = scoreModel(sonnetProfile, { coding: 0.9, instruction: 0.7 });
369
- assert.ok(Math.abs(score - 85.0) < 0.01, `Expected ~85.0, got ${score}`);
370
- });
371
-
372
- test("returns 50 when requirements is empty", () => {
373
- const score = scoreModel(sonnetProfile, {});
374
- assert.equal(score, 50);
375
- });
376
-
377
- test("returns correct score for single dimension coding:1.0", () => {
378
- // coding=90 for claude-opus-4-6
379
- const opusProfile = MODEL_CAPABILITY_PROFILES["claude-opus-4-6"]!;
380
- const score = scoreModel(opusProfile, { coding: 1.0 });
381
- assert.equal(score, 95);
382
- });
383
-
384
- test("handles all 7 dimensions correctly", () => {
385
- // Uniform weight 1.0 on every dim → average of all dim values
386
- const profile: ModelCapabilities = {
387
- coding: 60, debugging: 60, research: 60, reasoning: 60,
388
- speed: 60, longContext: 60, instruction: 60,
389
- };
390
- const reqs: Partial<Record<keyof ModelCapabilities, number>> = {
391
- coding: 1.0, debugging: 1.0, research: 1.0, reasoning: 1.0,
392
- speed: 1.0, longContext: 1.0, instruction: 1.0,
393
- };
394
- const score = scoreModel(profile, reqs);
395
- assert.equal(score, 60);
396
- });
397
- });
398
-
399
- // ─── computeTaskRequirements ─────────────────────────────────────────────────
400
-
401
- describe("computeTaskRequirements", () => {
402
- test("execute-task with no metadata returns base vector", () => {
403
- const req = computeTaskRequirements("execute-task", undefined);
404
- assert.deepStrictEqual(req, { coding: 0.9, instruction: 0.7, speed: 0.3 });
405
- });
406
-
407
- test("execute-task with tags:['docs'] adjusts requirements", () => {
408
- const req = computeTaskRequirements("execute-task", { tags: ["docs"] });
409
- assert.equal(req.instruction, 0.9);
410
- assert.equal(req.coding, 0.3);
411
- assert.equal(req.speed, 0.7);
412
- });
413
-
414
- test("execute-task with tags:['config'] adjusts requirements", () => {
415
- const req = computeTaskRequirements("execute-task", { tags: ["config"] });
416
- assert.equal(req.instruction, 0.9);
417
- });
418
-
419
- test("execute-task with complexityKeywords:['concurrency'] boosts debugging and reasoning", () => {
420
- const req = computeTaskRequirements("execute-task", { complexityKeywords: ["concurrency"] });
421
- assert.equal(req.debugging, 0.9);
422
- assert.equal(req.reasoning, 0.8);
423
- });
424
-
425
- test("execute-task with complexityKeywords:['migration'] boosts reasoning and coding", () => {
426
- const req = computeTaskRequirements("execute-task", { complexityKeywords: ["migration"] });
427
- assert.equal(req.reasoning, 0.9);
428
- assert.equal(req.coding, 0.8);
429
- });
430
-
431
- test("execute-task with fileCount:8 boosts coding and reasoning", () => {
432
- const req = computeTaskRequirements("execute-task", { fileCount: 8 });
433
- assert.equal(req.coding, 0.9);
434
- assert.equal(req.reasoning, 0.7);
435
- });
436
-
437
- test("execute-task with estimatedLines:600 boosts coding and reasoning", () => {
438
- const req = computeTaskRequirements("execute-task", { estimatedLines: 600 });
439
- assert.equal(req.coding, 0.9);
440
- assert.equal(req.reasoning, 0.7);
441
- });
442
-
443
- test("research-milestone returns correct base vector", () => {
444
- const req = computeTaskRequirements("research-milestone");
445
- assert.deepStrictEqual(req, { research: 0.9, longContext: 0.7, reasoning: 0.5 });
446
- });
447
-
448
- test("plan-slice returns correct base vector", () => {
449
- const req = computeTaskRequirements("plan-slice");
450
- assert.deepStrictEqual(req, { reasoning: 0.9, coding: 0.5 });
451
- });
452
-
453
- test("unknown-unit-type returns default reasoning requirement", () => {
454
- const req = computeTaskRequirements("unknown-unit-type");
455
- assert.deepStrictEqual(req, { reasoning: 0.5 });
456
- });
457
-
458
- test("non-execute-task with metadata ignores metadata refinements", () => {
459
- // research-milestone should return the same vector regardless of metadata
460
- const reqWithMeta = computeTaskRequirements("research-milestone", { tags: ["docs"], fileCount: 10 });
461
- const reqWithout = computeTaskRequirements("research-milestone");
462
- assert.deepStrictEqual(reqWithMeta, reqWithout);
463
- });
464
- });
465
-
466
- // ─── scoreEligibleModels ─────────────────────────────────────────────────────
467
-
468
- describe("scoreEligibleModels", () => {
469
- test("ranks models by score descending when scores differ by more than 2", () => {
470
- // research: heavily weights research dimension. gemini-2.5-pro has 85 research vs sonnet's 75
471
- const requirements = { research: 0.9, longContext: 0.7, reasoning: 0.5 };
472
- const results = scoreEligibleModels(["claude-sonnet-4-6", "gemini-2.5-pro"], requirements);
473
- assert.equal(results.length, 2);
474
- assert.ok(results[0].score >= results[1].score, "Should be sorted by score descending");
475
- });
476
-
477
- test("within 2-point threshold, prefers cheaper model", () => {
478
- // Use models without built-in profiles (both get score 50) so tie-break applies
479
- // Then use known models with equal scores: force this via single unknown model pair
480
- const requirements = { coding: 1.0 };
481
- // model-a and model-b are both unknown → score=50, cost=Infinity → lexicographic
482
- const results = scoreEligibleModels(["model-z", "model-a"], requirements);
483
- // Both unknown: score=50 (within 2), cost=Infinity (equal) → lex: model-a first
484
- assert.equal(results[0].modelId, "model-a");
485
- });
486
-
487
- test("single model returns array of one", () => {
488
- const results = scoreEligibleModels(["claude-sonnet-4-6"], { coding: 0.9 });
489
- assert.equal(results.length, 1);
490
- assert.equal(results[0].modelId, "claude-sonnet-4-6");
491
- });
492
-
493
- test("unknown model with no profile gets score of 50", () => {
494
- const results = scoreEligibleModels(["totally-unknown-model"], { coding: 1.0 });
495
- assert.equal(results[0].score, 50);
496
- });
497
-
498
- test("capabilityOverrides deep-merges with built-in profile", () => {
499
- const requirements = { coding: 1.0 };
500
- // Override sonnet's coding to 30 — gpt-4o (coding=80) should win
501
- const results = scoreEligibleModels(
502
- ["claude-sonnet-4-6", "gpt-4o"],
503
- requirements,
504
- { "claude-sonnet-4-6": { coding: 30 } },
505
- );
506
- assert.equal(results[0].modelId, "gpt-4o", "gpt-4o should rank first after coding override");
507
- });
508
- });
509
-
510
- // ─── getEligibleModels ───────────────────────────────────────────────────────
511
-
512
- describe("getEligibleModels", () => {
513
- const ALL_MODELS = [
514
- "claude-opus-4-6", // heavy
515
- "claude-sonnet-4-6", // standard
516
- "claude-haiku-4-5", // light
517
- "gpt-4o-mini", // light
518
- "gpt-4o", // standard
519
- ];
520
-
521
- test("returns light-tier models from available list sorted by cost", () => {
522
- const config: DynamicRoutingConfig = defaultRoutingConfig();
523
- const result = getEligibleModels("light", ALL_MODELS, config);
524
- assert.ok(result.length >= 1);
525
- for (const id of result) {
526
- assert.ok(
527
- ["claude-haiku-4-5", "gpt-4o-mini"].includes(id),
528
- `Expected light-tier model, got ${id}`,
529
- );
530
- }
531
- });
532
-
533
- test("returns standard-tier models from available list sorted by cost", () => {
534
- const config: DynamicRoutingConfig = defaultRoutingConfig();
535
- const result = getEligibleModels("standard", ALL_MODELS, config);
536
- assert.ok(result.length >= 1);
537
- for (const id of result) {
538
- assert.ok(
539
- ["claude-sonnet-4-6", "gpt-4o"].includes(id),
540
- `Expected standard-tier model, got ${id}`,
541
- );
542
- }
543
- });
544
-
545
- test("tier_models pinned model returns single-element array", () => {
546
- const config: DynamicRoutingConfig = {
547
- ...defaultRoutingConfig(),
548
- tier_models: { light: "gpt-4o-mini" },
549
- };
550
- const result = getEligibleModels("light", ALL_MODELS, config);
551
- assert.deepStrictEqual(result, ["gpt-4o-mini"]);
552
- });
553
-
554
- test("empty available list returns empty array", () => {
555
- const config: DynamicRoutingConfig = defaultRoutingConfig();
556
- const result = getEligibleModels("light", [], config);
557
- assert.equal(result.length, 0);
558
- });
559
-
560
- test("unknown models classified as standard appear in standard tier results", () => {
561
- const config: DynamicRoutingConfig = defaultRoutingConfig();
562
- // unknown-model-xyz has no entry → defaults to standard tier
563
- const result = getEligibleModels("standard", ["unknown-model-xyz"], config);
564
- assert.ok(result.includes("unknown-model-xyz"), "Unknown model should appear in standard tier");
565
- });
566
- });
567
-
568
- // ─── capability-aware routing integration ────────────────────────────────────
569
-
570
- describe("capability-aware routing integration", () => {
571
- // All standard-tier models available alongside heavy (opus)
572
- const MULTI_MODEL_AVAILABLE = [
573
- "claude-opus-4-6",
574
- "claude-sonnet-4-6",
575
- "gpt-4o",
576
- "gemini-2.5-pro",
577
- "claude-haiku-4-5",
578
- "gpt-4o-mini",
579
- ];
580
-
581
- // 1. Full pipeline with capability scoring active
582
- test("full pipeline with capability_routing: true returns capability-scored decision", () => {
583
- const config: DynamicRoutingConfig = { ...defaultRoutingConfig(), enabled: true, capability_routing: true };
584
- // Configured primary is opus (heavy) — standard tier should trigger capability scoring
585
- const result = resolveModelForComplexity(
586
- { tier: "standard", reason: "test", downgraded: false },
587
- { primary: "claude-opus-4-6", fallbacks: [] },
588
- config,
589
- MULTI_MODEL_AVAILABLE,
590
- "execute-task",
591
- { tags: [], complexityKeywords: [], fileCount: 3, estimatedLines: 100, codeBlockCount: 0 },
592
- );
593
- assert.equal(result.selectionMethod, "capability-scored", "should use capability scoring when enabled with multiple eligible models");
594
- assert.ok(result.capabilityScores !== undefined, "capabilityScores should be populated");
595
- assert.ok(Object.keys(result.capabilityScores!).length > 1, "should have scores for multiple models");
596
- assert.equal(result.wasDowngraded, true, "should be downgraded from opus");
597
- });
598
-
599
- // 2. capability_routing: false falls back to tier-only
600
- test("capability_routing: false skips scoring and uses tier-only", () => {
601
- const config: DynamicRoutingConfig = { ...defaultRoutingConfig(), enabled: true, capability_routing: false };
602
- const result = resolveModelForComplexity(
603
- { tier: "standard", reason: "test", downgraded: false },
604
- { primary: "claude-opus-4-6", fallbacks: [] },
605
- config,
606
- MULTI_MODEL_AVAILABLE,
607
- "execute-task",
608
- undefined,
609
- );
610
- assert.equal(result.selectionMethod, "tier-only", "capability_routing: false should use tier-only");
611
- assert.equal(result.capabilityScores, undefined, "capabilityScores should be undefined for tier-only");
612
- });
613
-
614
- // 3. Single eligible model skips scoring
615
- test("single eligible model skips capability scoring and uses tier-only", () => {
616
- const config: DynamicRoutingConfig = {
617
- ...defaultRoutingConfig(),
618
- enabled: true,
619
- capability_routing: true,
620
- tier_models: { standard: "claude-sonnet-4-6" },
621
- };
622
- // Pin to single standard model — eligible.length === 1 → skips STEP 2
623
- const result = resolveModelForComplexity(
624
- { tier: "standard", reason: "test", downgraded: false },
625
- { primary: "claude-opus-4-6", fallbacks: [] },
626
- config,
627
- MULTI_MODEL_AVAILABLE,
628
- "execute-task",
629
- undefined,
630
- );
631
- // Single pinned model → tier-only (no scoring needed)
632
- assert.equal(result.selectionMethod, "tier-only", "single eligible model should use tier-only");
633
- assert.equal(result.modelId, "claude-sonnet-4-6", "should use the pinned model");
634
- });
635
-
636
- // 4. Unknown model with no profile gets uniform 50s and competes
637
- test("unknown model with no profile gets uniform score of 50 and can compete", () => {
638
- const unknownModel = "unknown-future-model-xyz";
639
- const config: DynamicRoutingConfig = { ...defaultRoutingConfig(), enabled: true, capability_routing: true };
640
- // Add unknown model to available list at standard tier (unknown → standard per D-15)
641
- // scoring should still work with score=50 for the unknown model
642
- const requirements = { coding: 0.9, instruction: 0.7, speed: 0.3 };
643
- const scored = scoreEligibleModels([unknownModel, "claude-sonnet-4-6"], requirements);
644
- const unknownEntry = scored.find(s => s.modelId === unknownModel);
645
- assert.ok(unknownEntry !== undefined, "unknown model should be in scored results");
646
- // Unknown model gets uniform 50s: (0.9*50 + 0.7*50 + 0.3*50) / (0.9+0.7+0.3) ≈ 50
647
- assert.ok(Math.abs(unknownEntry!.score - 50) < 0.01, `expected score ~50, got ${unknownEntry!.score}`);
648
- });
649
-
650
- // 5. Capability overrides change scoring outcome
651
- test("capabilityOverrides boost a model above another for same task", () => {
652
- // sonnet: coding=85, gpt-4o: coding=80. Override gpt-4o coding to 99 → gpt-4o should win.
653
- const requirements = { coding: 1.0 };
654
- const overrides = { "gpt-4o": { coding: 99 } };
655
- const scored = scoreEligibleModels(["claude-sonnet-4-6", "gpt-4o"], requirements, overrides);
656
- assert.equal(scored[0].modelId, "gpt-4o", "overridden model should win for coding-heavy task");
657
- assert.ok(scored[0].score > 90, `expected score > 90 after override, got ${scored[0].score}`);
658
- });
659
-
660
- // 5b. Capability overrides pass through resolveModelForComplexity to scoreEligibleModels
661
- test("resolveModelForComplexity passes capabilityOverrides to scoring step", () => {
662
- const config: DynamicRoutingConfig = { ...defaultRoutingConfig(), enabled: true, capability_routing: true };
663
- // sonnet coding=85, gpt-4o coding=80. Override gpt-4o coding to 99 → gpt-4o should win.
664
- const overrides: Record<string, Partial<ModelCapabilities>> = { "gpt-4o": { coding: 99 } };
665
- const result = resolveModelForComplexity(
666
- { tier: "standard", reason: "test", downgraded: false },
667
- { primary: "claude-opus-4-6", fallbacks: [] },
668
- config,
669
- ["claude-opus-4-6", "claude-sonnet-4-6", "gpt-4o"],
670
- "execute-task",
671
- undefined,
672
- overrides,
673
- );
674
- assert.equal(result.selectionMethod, "capability-scored");
675
- assert.equal(result.modelId, "gpt-4o", "gpt-4o should win with coding override");
676
- });
677
-
678
- // 6. Regression: existing routing guards unchanged
679
- test("regression: routing-disabled passthrough still returns tier-only", () => {
680
- const config: DynamicRoutingConfig = { ...defaultRoutingConfig(), enabled: false };
681
- const result = resolveModelForComplexity(
682
- { tier: "light", reason: "test", downgraded: false },
683
- { primary: "claude-opus-4-6", fallbacks: [] },
684
- config,
685
- MULTI_MODEL_AVAILABLE,
686
- "execute-task",
687
- undefined,
688
- );
689
- assert.equal(result.selectionMethod, "tier-only");
690
- assert.equal(result.wasDowngraded, false);
691
- assert.equal(result.modelId, "claude-opus-4-6");
692
- });
693
-
694
- test("regression: unknown-model bypass returns tier-only and does not downgrade", () => {
695
- const config: DynamicRoutingConfig = { ...defaultRoutingConfig(), enabled: true };
696
- const result = resolveModelForComplexity(
697
- { tier: "light", reason: "test", downgraded: false },
698
- { primary: "totally-unknown-custom-model", fallbacks: [] },
699
- config,
700
- ["totally-unknown-custom-model", ...MULTI_MODEL_AVAILABLE],
701
- "execute-task",
702
- undefined,
703
- );
704
- assert.equal(result.selectionMethod, "tier-only");
705
- assert.equal(result.wasDowngraded, false);
706
- assert.equal(result.modelId, "totally-unknown-custom-model");
707
- });
708
-
709
- test("regression: no-downgrade-needed path returns tier-only", () => {
710
- const config: DynamicRoutingConfig = { ...defaultRoutingConfig(), enabled: true, capability_routing: true };
711
- // Configured model is sonnet (standard), requesting standard → no downgrade needed
712
- const result = resolveModelForComplexity(
713
- { tier: "standard", reason: "test", downgraded: false },
714
- { primary: "claude-sonnet-4-6", fallbacks: [] },
715
- config,
716
- MULTI_MODEL_AVAILABLE,
717
- "execute-task",
718
- undefined,
719
- );
720
- assert.equal(result.selectionMethod, "tier-only");
721
- assert.equal(result.wasDowngraded, false);
722
- assert.equal(result.modelId, "claude-sonnet-4-6");
723
- });
724
- });
725
-
726
- // ─── getModelTier unknown default ────────────────────────────────────────────
727
-
728
- describe("getModelTier unknown default", () => {
729
- test("unknown model returns standard tier (not heavy) via downgrade behavior", () => {
730
- // We can verify this indirectly: resolveModelForComplexity for a standard classification
731
- // with an unknown primary model should NOT downgrade (because unknown → standard, not heavy)
732
- const config = { ...defaultRoutingConfig(), enabled: true };
733
- // Use "unknown-model-xyz" as primary — its tier will be "standard" per D-15
734
- // Classification is "heavy" → tier >= standard → no downgrade
735
- // But unknown models use the isKnownModel() guard, so they pass through anyway
736
- // Test the positive: an unknown model is NOT treated as heavy
737
- const result = resolveModelForComplexity(
738
- makeClassification("standard"),
739
- { primary: "claude-sonnet-4-6", fallbacks: [] },
740
- config,
741
- ["claude-sonnet-4-6", "claude-haiku-4-5", "gpt-4o-mini"],
742
- );
743
- // standard classification with standard model (sonnet) → no downgrade
744
- assert.equal(result.wasDowngraded, false, "standard model should not downgrade for standard task");
745
- assert.equal(result.modelId, "claude-sonnet-4-6");
746
- });
747
-
748
- test("unknown model in getEligibleModels defaults to standard tier", () => {
749
- // Per D-15: getModelTier returns "standard" for unknown models
750
- const config: DynamicRoutingConfig = defaultRoutingConfig();
751
- const standardModels = getEligibleModels("standard", ["totally-unknown-model-abc"], config);
752
- const lightModels = getEligibleModels("light", ["totally-unknown-model-abc"], config);
753
- const heavyModels = getEligibleModels("heavy", ["totally-unknown-model-abc"], config);
754
- assert.ok(standardModels.includes("totally-unknown-model-abc"), "Unknown model should be in standard tier");
755
- assert.equal(lightModels.length, 0, "Unknown model should NOT be in light tier");
756
- assert.equal(heavyModels.length, 0, "Unknown model should NOT be in heavy tier");
757
- });
758
- });
@@ -461,65 +461,3 @@ test("experimental.rtk defaults to off in new project preferences", () => {
461
461
  assert.notEqual(prefs, null);
462
462
  assert.equal(prefs!.experimental?.rtk, undefined);
463
463
  });
464
-
465
- // ── Codebase Map Preferences ─────────────────────────────────────────────────
466
-
467
- test("codebase preferences validate and pass through correctly", () => {
468
- const result = validatePreferences({
469
- codebase: {
470
- exclude_patterns: ["docs/", "fixtures/"],
471
- max_files: 1000,
472
- collapse_threshold: 15,
473
- },
474
- });
475
- assert.equal(result.errors.length, 0);
476
- assert.deepEqual(result.preferences.codebase?.exclude_patterns, ["docs/", "fixtures/"]);
477
- assert.equal(result.preferences.codebase?.max_files, 1000);
478
- assert.equal(result.preferences.codebase?.collapse_threshold, 15);
479
- });
480
-
481
- test("codebase preferences reject invalid types", () => {
482
- const result = validatePreferences({
483
- codebase: {
484
- exclude_patterns: "not-an-array" as any,
485
- max_files: -5,
486
- collapse_threshold: 0,
487
- },
488
- });
489
- assert.ok(result.errors.some(e => e.includes("exclude_patterns must be an array")));
490
- assert.ok(result.errors.some(e => e.includes("max_files must be a positive")));
491
- assert.ok(result.errors.some(e => e.includes("collapse_threshold must be a positive")));
492
- });
493
-
494
- test("codebase preferences warn on unknown keys", () => {
495
- const result = validatePreferences({
496
- codebase: {
497
- exclude_patterns: ["docs/"],
498
- unknown_key: true,
499
- } as any,
500
- });
501
- assert.equal(result.errors.length, 0);
502
- assert.ok(result.warnings.some(w => w.includes('unknown codebase key "unknown_key"')));
503
- assert.deepEqual(result.preferences.codebase?.exclude_patterns, ["docs/"]);
504
- });
505
-
506
- test("codebase preferences parse from markdown frontmatter", () => {
507
- const content = [
508
- "---",
509
- "version: 1",
510
- "codebase:",
511
- " exclude_patterns:",
512
- ' - "docs/"',
513
- ' - ".cache/"',
514
- " max_files: 800",
515
- " collapse_threshold: 10",
516
- "---",
517
- ].join("\n");
518
- const prefs = parsePreferencesMarkdown(content);
519
- assert.notEqual(prefs, null);
520
- const result = validatePreferences(prefs!);
521
- assert.equal(result.errors.length, 0);
522
- assert.deepEqual(result.preferences.codebase?.exclude_patterns, ["docs/", ".cache/"]);
523
- assert.equal(result.preferences.codebase?.max_files, 800);
524
- assert.equal(result.preferences.codebase?.collapse_threshold, 10);
525
- });
@@ -739,27 +739,6 @@ test("config source-level: hydration skips api_key entries with empty keys", ()
739
739
  );
740
740
  });
741
741
 
742
- test("ask-user-questions source-level: tryRemoteQuestions is called before the hasUI guard", () => {
743
- // Regression test for #3480 — remote questions were silently skipped in interactive
744
- // mode because tryRemoteQuestions was gated behind `if (!ctx.hasUI)`.
745
- // The fix moved the remote call before that guard so configured channels
746
- // (Telegram/Slack/Discord) fire regardless of UI availability.
747
- const src = readFileSync(
748
- join(__dirname, "..", "..", "ask-user-questions.ts"),
749
- "utf-8",
750
- );
751
-
752
- const remoteCallIdx = src.indexOf("tryRemoteQuestions(params.questions");
753
- const hasUIGuardIdx = src.indexOf("if (!ctx.hasUI)");
754
-
755
- assert.ok(remoteCallIdx !== -1, "tryRemoteQuestions call should exist in ask-user-questions.ts");
756
- assert.ok(hasUIGuardIdx !== -1, "!ctx.hasUI guard should exist in ask-user-questions.ts");
757
- assert.ok(
758
- remoteCallIdx < hasUIGuardIdx,
759
- "tryRemoteQuestions must be called before the !ctx.hasUI guard — otherwise remote questions are skipped in interactive mode",
760
- );
761
- });
762
-
763
742
  test("config source-level: removeProviderToken uses auth.remove not auth.set with empty key", () => {
764
743
  const commandSrc = readFileSync(
765
744
  join(__dirname, "..", "..", "remote-questions", "remote-command.ts"),
@@ -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
- logError("engine", "audit test entry");
243
+ logWarning("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, "error");
249
+ assert.equal(entry.severity, "warn");
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
- logError("engine", "post-reset entry");
257
+ logWarning("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
- logError("engine", "audit test entry");
296
+ logWarning("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, "error");
302
+ assert.equal(entry.severity, "warn");
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
- logError("engine", "post-reset entry");
310
+ logWarning("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,7 +23,6 @@ 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";
27
26
 
28
27
  export interface CompleteMilestoneParams {
29
28
  milestoneId: string;
@@ -192,7 +191,9 @@ export async function handleCompleteMilestone(
192
191
  await saveFile(summaryPath, summaryMd);
193
192
  } catch (renderErr) {
194
193
  // Disk render failed — roll back DB status so state stays consistent
195
- logWarning("tool", `complete_milestone — disk render failed, rolling back DB status: ${(renderErr as Error).message}`);
194
+ process.stderr.write(
195
+ `gsd-db: complete_milestone — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
196
+ );
196
197
  updateMilestoneStatus(params.milestoneId, 'active', null);
197
198
  invalidateStateCache();
198
199
  return { error: `disk render failed: ${(renderErr as Error).message}` };
@@ -216,7 +217,9 @@ export async function handleCompleteMilestone(
216
217
  trigger_reason: params.triggerReason,
217
218
  });
218
219
  } catch (hookErr) {
219
- logWarning("tool", `complete-milestone post-mutation hook warning: ${(hookErr as Error).message}`);
220
+ process.stderr.write(
221
+ `gsd: complete-milestone post-mutation hook warning: ${(hookErr as Error).message}\n`,
222
+ );
220
223
  }
221
224
 
222
225
  return {
@@ -30,7 +30,6 @@ 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";
34
33
 
35
34
  export interface CompleteSliceResult {
36
35
  sliceId: string;
@@ -298,7 +297,9 @@ export async function handleCompleteSlice(
298
297
  }
299
298
  } catch (renderErr) {
300
299
  // Disk render failed — roll back DB status so state stays consistent
301
- logWarning("tool", `complete_slice — disk render failed, rolling back DB status: ${(renderErr as Error).message}`);
300
+ process.stderr.write(
301
+ `gsd-db: complete_slice — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
302
+ );
302
303
  updateSliceStatus(params.milestoneId, params.sliceId, 'pending');
303
304
  invalidateStateCache();
304
305
  return { error: `disk render failed: ${(renderErr as Error).message}` };
@@ -325,7 +326,9 @@ export async function handleCompleteSlice(
325
326
  trigger_reason: params.triggerReason,
326
327
  });
327
328
  } catch (hookErr) {
328
- logWarning("tool", `complete-slice post-mutation hook warning: ${(hookErr as Error).message}`);
329
+ process.stderr.write(
330
+ `gsd: complete-slice post-mutation hook warning: ${(hookErr as Error).message}\n`,
331
+ );
329
332
  }
330
333
 
331
334
  return {
@@ -33,7 +33,6 @@ 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";
37
36
 
38
37
  export interface CompleteTaskResult {
39
38
  taskId: string;
@@ -211,7 +210,9 @@ export async function handleCompleteTask(
211
210
  }
212
211
  } catch (renderErr) {
213
212
  // Disk render failed — roll back DB status so state stays consistent
214
- logWarning("tool", `complete_task — disk render failed, rolling back DB status: ${(renderErr as Error).message}`);
213
+ process.stderr.write(
214
+ `gsd-db: complete_task — disk render failed, rolling back DB status: ${(renderErr as Error).message}\n`,
215
+ );
215
216
  // Delete orphaned verification_evidence rows first (FK constraint
216
217
  // references tasks, so evidence must go before status change).
217
218
  // Without this, retries accumulate duplicate evidence rows (#2724).
@@ -242,7 +243,9 @@ export async function handleCompleteTask(
242
243
  trigger_reason: params.triggerReason,
243
244
  });
244
245
  } catch (hookErr) {
245
- logWarning("tool", `complete-task post-mutation hook warning: ${(hookErr as Error).message}`);
246
+ process.stderr.write(
247
+ `gsd: complete-task post-mutation hook warning: ${(hookErr as Error).message}\n`,
248
+ );
246
249
  }
247
250
 
248
251
  return {