gsd-pi 2.44.0-dev.62b5d6c → 2.44.0-dev.a5271fc

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/README.md +30 -12
  2. package/dist/resources/extensions/gsd/auto-start.js +10 -0
  3. package/dist/resources/extensions/gsd/bootstrap/system-context.js +46 -12
  4. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +5 -0
  5. package/dist/resources/extensions/gsd/preferences.js +9 -1
  6. package/dist/resources/extensions/gsd/state.js +19 -2
  7. package/dist/web/standalone/.next/BUILD_ID +1 -1
  8. package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
  9. package/dist/web/standalone/.next/build-manifest.json +2 -2
  10. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  11. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  12. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  17. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  20. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/index.html +1 -1
  28. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
  35. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  36. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  37. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  38. package/package.json +1 -1
  39. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
  40. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  41. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
  42. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  43. package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
  44. package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
  45. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
  46. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  47. package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
  48. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  49. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
  50. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  51. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
  52. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
  53. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
  54. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
  55. package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
  56. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
  57. package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
  58. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
  59. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
  60. package/src/resources/extensions/gsd/auto-start.ts +14 -0
  61. package/src/resources/extensions/gsd/bootstrap/system-context.ts +48 -11
  62. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
  63. package/src/resources/extensions/gsd/preferences.ts +11 -1
  64. package/src/resources/extensions/gsd/state.ts +19 -1
  65. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
  66. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
  67. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
  68. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
  69. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
  70. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
  71. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
  72. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
  73. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
  74. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
  75. package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
  76. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
  77. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
  78. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
  79. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
  80. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
  81. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
  82. package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
  83. package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
  84. package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
  85. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
  86. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
  87. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
  88. package/src/resources/extensions/gsd/tests/db-writer.test.ts +390 -420
  89. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
  90. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
  91. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +183 -181
  92. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
  93. package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
  94. package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
  95. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
  96. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
  97. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
  98. package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
  99. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
  100. package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
  101. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
  102. package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
  103. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
  104. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
  105. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
  106. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
  107. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
  108. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
  109. package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
  110. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
  111. package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
  112. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
  113. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
  114. package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
  115. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
  116. package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
  117. package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
  118. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
  119. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
  120. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
  121. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
  122. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
  123. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
  124. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
  125. package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
  126. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
  127. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
  128. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
  129. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
  130. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
  131. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
  132. package/src/resources/extensions/gsd/tests/knowledge.test.ts +89 -0
  133. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
  134. package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
  135. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
  136. package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
  137. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
  138. package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
  139. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
  140. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
  141. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
  142. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
  143. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
  144. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
  145. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
  146. package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
  147. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
  148. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
  149. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
  150. package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
  151. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
  152. package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
  153. package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
  154. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
  155. package/src/resources/extensions/gsd/tests/preferences.test.ts +27 -0
  156. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
  157. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
  158. package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
  159. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
  160. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
  161. package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
  162. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
  163. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
  164. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
  165. package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
  166. package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
  167. package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
  168. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
  169. package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
  170. package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
  171. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
  172. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
  173. package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
  174. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
  175. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
  176. package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
  177. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
  178. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +9 -11
  179. package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
  180. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
  181. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
  182. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
  183. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
  184. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
  185. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
  186. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
  187. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
  188. package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
  189. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
  190. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
  191. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
  192. package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
  193. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
  194. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
  195. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
  196. package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
  197. /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → JyimLR2pZuvKEzv26gI3w}/_buildManifest.js +0 -0
  198. /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → JyimLR2pZuvKEzv26gI3w}/_ssgManifest.js +0 -0
@@ -162,7 +162,7 @@ describe("continue-here", () => {
162
162
  });
163
163
 
164
164
  describe("continueHereFired runtime record field", () => {
165
- it("AutoUnitRuntimeRecord includes continueHereFired with default false", async () => {
165
+ it("AutoUnitRuntimeRecord includes continueHereFired with default false", async (t) => {
166
166
  // Import writeUnitRuntimeRecord to verify the field is present and defaults
167
167
  const { writeUnitRuntimeRecord, readUnitRuntimeRecord, clearUnitRuntimeRecord } = await import("../unit-runtime.js");
168
168
  const fs = await import("node:fs");
@@ -171,87 +171,83 @@ describe("continue-here", () => {
171
171
 
172
172
  // Use a temp directory as basePath
173
173
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "continue-here-test-"));
174
- try {
175
- const record = writeUnitRuntimeRecord(tmpDir, "execute-task", "M007/S02/T02", Date.now(), {
176
- phase: "dispatched",
177
- wrapupWarningSent: false,
178
- });
179
-
180
- assert.equal(record.continueHereFired, false, "default continueHereFired should be false");
181
-
182
- // Verify it persists to disk
183
- const read = readUnitRuntimeRecord(tmpDir, "execute-task", "M007/S02/T02");
184
- assert.ok(read, "record should be readable");
185
- assert.equal(read!.continueHereFired, false);
186
-
187
- // Update to true
188
- const updated = writeUnitRuntimeRecord(tmpDir, "execute-task", "M007/S02/T02", Date.now(), {
189
- continueHereFired: true,
190
- });
191
- assert.equal(updated.continueHereFired, true, "updated continueHereFired should be true");
192
-
193
- // Verify persistence
194
- const readUpdated = readUnitRuntimeRecord(tmpDir, "execute-task", "M007/S02/T02");
195
- assert.equal(readUpdated!.continueHereFired, true, "persisted continueHereFired should be true");
196
-
197
- // Clean up
198
- clearUnitRuntimeRecord(tmpDir, "execute-task", "M007/S02/T02");
199
- } finally {
200
- fs.rmSync(tmpDir, { recursive: true, force: true });
201
- }
174
+ t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
175
+
176
+ const record = writeUnitRuntimeRecord(tmpDir, "execute-task", "M007/S02/T02", Date.now(), {
177
+ phase: "dispatched",
178
+ wrapupWarningSent: false,
179
+ });
180
+
181
+ assert.equal(record.continueHereFired, false, "default continueHereFired should be false");
182
+
183
+ // Verify it persists to disk
184
+ const read = readUnitRuntimeRecord(tmpDir, "execute-task", "M007/S02/T02");
185
+ assert.ok(read, "record should be readable");
186
+ assert.equal(read!.continueHereFired, false);
187
+
188
+ // Update to true
189
+ const updated = writeUnitRuntimeRecord(tmpDir, "execute-task", "M007/S02/T02", Date.now(), {
190
+ continueHereFired: true,
191
+ });
192
+ assert.equal(updated.continueHereFired, true, "updated continueHereFired should be true");
193
+
194
+ // Verify persistence
195
+ const readUpdated = readUnitRuntimeRecord(tmpDir, "execute-task", "M007/S02/T02");
196
+ assert.equal(readUpdated!.continueHereFired, true, "persisted continueHereFired should be true");
197
+
198
+ // Clean up
199
+ clearUnitRuntimeRecord(tmpDir, "execute-task", "M007/S02/T02");
202
200
  });
203
201
  });
204
202
 
205
203
  describe("context-pressure monitor integration", () => {
206
- it("should fire wrap-up when context >= threshold and mark continueHereFired", async () => {
204
+ it("should fire wrap-up when context >= threshold and mark continueHereFired", async (t) => {
207
205
  const { writeUnitRuntimeRecord, readUnitRuntimeRecord, clearUnitRuntimeRecord } = await import("../unit-runtime.js");
208
206
  const fs = await import("node:fs");
209
207
  const path = await import("node:path");
210
208
  const os = await import("node:os");
211
209
 
212
210
  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "continue-here-monitor-"));
213
- try {
214
- // Simulate the monitor's one-shot logic:
215
- // 1. Write initial runtime record (continueHereFired=false)
216
- const startedAt = Date.now();
217
- writeUnitRuntimeRecord(tmpDir, "execute-task", "M001/S01/T01", startedAt, {
218
- phase: "dispatched",
219
- wrapupWarningSent: false,
220
- });
221
-
222
- const budget = computeBudgets(128_000);
223
- const threshold = budget.continueThresholdPercent;
224
-
225
- // Simulate the monitor poll: context at 75% (above threshold)
226
- const contextPercent = 75;
227
- const runtime = readUnitRuntimeRecord(tmpDir, "execute-task", "M001/S01/T01");
228
- assert.ok(runtime, "runtime record should exist");
229
- assert.equal(runtime!.continueHereFired, false, "initially false");
230
-
231
- // Check: should fire
232
- const shouldFire = !runtime!.continueHereFired
233
- && contextPercent >= threshold;
234
- assert.ok(shouldFire, "should fire when context >= threshold and not yet fired");
235
-
236
- // Mark as fired (what the monitor does)
237
- writeUnitRuntimeRecord(tmpDir, "execute-task", "M001/S01/T01", startedAt, {
238
- continueHereFired: true,
239
- });
240
-
241
- // Verify one-shot: second poll should NOT fire
242
- const runtime2 = readUnitRuntimeRecord(tmpDir, "execute-task", "M001/S01/T01");
243
- assert.ok(runtime2, "runtime record should still exist");
244
- assert.equal(runtime2!.continueHereFired, true, "should be marked as fired");
245
-
246
- const shouldFireAgain = !runtime2!.continueHereFired
247
- && contextPercent >= threshold;
248
- assert.equal(shouldFireAgain, false, "must not fire again — one-shot guard");
249
-
250
- // Clean up
251
- clearUnitRuntimeRecord(tmpDir, "execute-task", "M001/S01/T01");
252
- } finally {
253
- fs.rmSync(tmpDir, { recursive: true, force: true });
254
- }
211
+ t.after(() => fs.rmSync(tmpDir, { recursive: true, force: true }));
212
+
213
+ // Simulate the monitor's one-shot logic:
214
+ // 1. Write initial runtime record (continueHereFired=false)
215
+ const startedAt = Date.now();
216
+ writeUnitRuntimeRecord(tmpDir, "execute-task", "M001/S01/T01", startedAt, {
217
+ phase: "dispatched",
218
+ wrapupWarningSent: false,
219
+ });
220
+
221
+ const budget = computeBudgets(128_000);
222
+ const threshold = budget.continueThresholdPercent;
223
+
224
+ // Simulate the monitor poll: context at 75% (above threshold)
225
+ const contextPercent = 75;
226
+ const runtime = readUnitRuntimeRecord(tmpDir, "execute-task", "M001/S01/T01");
227
+ assert.ok(runtime, "runtime record should exist");
228
+ assert.equal(runtime!.continueHereFired, false, "initially false");
229
+
230
+ // Check: should fire
231
+ const shouldFire = !runtime!.continueHereFired
232
+ && contextPercent >= threshold;
233
+ assert.ok(shouldFire, "should fire when context >= threshold and not yet fired");
234
+
235
+ // Mark as fired (what the monitor does)
236
+ writeUnitRuntimeRecord(tmpDir, "execute-task", "M001/S01/T01", startedAt, {
237
+ continueHereFired: true,
238
+ });
239
+
240
+ // Verify one-shot: second poll should NOT fire
241
+ const runtime2 = readUnitRuntimeRecord(tmpDir, "execute-task", "M001/S01/T01");
242
+ assert.ok(runtime2, "runtime record should still exist");
243
+ assert.equal(runtime2!.continueHereFired, true, "should be marked as fired");
244
+
245
+ const shouldFireAgain = !runtime2!.continueHereFired
246
+ && contextPercent >= threshold;
247
+ assert.equal(shouldFireAgain, false, "must not fire again — one-shot guard");
248
+
249
+ // Clean up
250
+ clearUnitRuntimeRecord(tmpDir, "execute-task", "M001/S01/T01");
255
251
  });
256
252
 
257
253
  it("should not fire when context is below threshold", () => {
@@ -7,11 +7,12 @@
7
7
  * That failure confirms the test runs against real code. (T01 state)
8
8
  */
9
9
 
10
+ import { describe, test } from "node:test";
11
+ import assert from "node:assert/strict";
10
12
  import {
11
13
  type SliceAggregate,
12
14
  formatCostProjection,
13
15
  } from "../metrics.js";
14
- import { createTestContext } from './test-helpers.ts';
15
16
 
16
17
  // ─── Test helpers ─────────────────────────────────────────────────────────────
17
18
 
@@ -25,110 +26,95 @@ function makeSliceAggregate(sliceId: string, cost: number): SliceAggregate {
25
26
  };
26
27
  }
27
28
 
28
- const { assertEq, assertTrue, report } = createTestContext();
29
29
  // ─── formatCostProjection ─────────────────────────────────────────────────────
30
30
 
31
- console.log("\n=== formatCostProjection ===");
32
-
33
- // 1. Zero completed slices → empty result
34
- {
35
- const result = formatCostProjection([], 3);
36
- assertEq(result.length, 0, "zero slices → empty array");
37
- }
38
-
39
- // 2. One slice → suppressed (need ≥2 to project reliably)
40
- {
41
- const result = formatCostProjection([makeSliceAggregate("M001/S01", 0.10)], 3);
42
- assertEq(result.length, 0, "one slice → suppressed (no projection shown)");
43
- }
44
-
45
- // 3. Two slices → projection shown (result.length > 0)
46
- {
47
- const slices = [
48
- makeSliceAggregate("M001/S01", 0.10),
49
- makeSliceAggregate("M001/S02", 0.10),
50
- ];
51
- const result = formatCostProjection(slices, 5);
52
- assertTrue(result.length > 0, "two slices projection shown");
53
- }
54
-
55
- // 4. Two-slice result: result[0] contains "$" (cost is formatted)
56
- {
57
- const slices = [
58
- makeSliceAggregate("M001/S01", 0.10),
59
- makeSliceAggregate("M001/S02", 0.10),
60
- ];
61
- const result = formatCostProjection(slices, 5);
62
- assertTrue(result.length > 0 && result[0].includes("$"), "projection line contains \"$\"");
63
- }
64
-
65
- // 5. Budget ceiling hit: total $0.20 >= ceiling $0.05 → line contains "ceiling"
66
- {
67
- const slices = [
68
- makeSliceAggregate("M001/S01", 0.10),
69
- makeSliceAggregate("M001/S02", 0.10),
70
- ];
71
- const result = formatCostProjection(slices, 5, 0.05);
72
- const hasCeilingLine = result.some(
73
- line => line.toLowerCase().includes("ceiling")
74
- );
75
- assertTrue(hasCeilingLine, "ceiling warning appears when total ($0.20) >= ceiling ($0.05)");
76
- }
77
-
78
- // 6. Budget ceiling not hit: total $0.20 < ceiling $100.00 → no ceiling line
79
- {
80
- const slices = [
81
- makeSliceAggregate("M001/S01", 0.10),
82
- makeSliceAggregate("M001/S02", 0.10),
83
- ];
84
- const result = formatCostProjection(slices, 5, 100.00);
85
- const hasCeilingLine = result.some(
86
- line => line.toLowerCase().includes("ceiling")
87
- );
88
- assertTrue(!hasCeilingLine, "no ceiling warning when total ($0.20) < ceiling ($100.00)");
89
- }
90
-
91
- // 7. No ceiling arg → no ceiling line
92
- {
93
- const slices = [
94
- makeSliceAggregate("M001/S01", 0.10),
95
- makeSliceAggregate("M001/S02", 0.10),
96
- ];
97
- const result = formatCostProjection(slices, 5);
98
- const hasCeilingLine = result.some(
99
- line => line.toLowerCase().includes("ceiling")
100
- );
101
- assertTrue(!hasCeilingLine, "no ceiling warning when no ceiling is set");
102
- }
103
-
104
- // 8. Rounding: avg $0.10 × 5 remaining = $0.50 → result[0] contains "$0.50"
105
- {
106
- const slices = [
107
- makeSliceAggregate("M001/S01", 0.10),
108
- makeSliceAggregate("M001/S02", 0.10),
109
- ];
110
- const result = formatCostProjection(slices, 5);
111
- const hasRoundedCost = result.some(line => line.includes("$0.50"));
112
- assertTrue(hasRoundedCost, "projected cost $0.50 (avg $0.10 × 5 remaining) appears in output");
113
- }
114
-
115
- // 9. Bare milestone entries excluded from average:
116
- // makeSliceAggregate('M001', 5.00) has no "/" in sliceId → excluded from avg calc.
117
- // Only M001/S01 ($0.10) and M001/S02 ($0.10) count → avg $0.10 × 3 remaining = $0.30
118
- {
119
- const slices = [
120
- makeSliceAggregate("M001", 5.00), // bare milestone — must be excluded
121
- makeSliceAggregate("M001/S01", 0.10),
122
- makeSliceAggregate("M001/S02", 0.10),
123
- ];
124
- const result = formatCostProjection(slices, 3);
125
- const hasCorrectProjection = result.some(line => line.includes("$0.30"));
126
- assertTrue(
127
- hasCorrectProjection,
128
- "bare milestone entry excluded from avg: projection shows $0.30 (avg $0.10 × 3), not $1.83 (including $5.00 entry)"
129
- );
130
- }
131
-
132
- // ─── Summary ──────────────────────────────────────────────────────────────────
133
-
134
- report();
31
+ describe("formatCostProjection", () => {
32
+
33
+ test("zero completed slices → empty result", () => {
34
+ const result = formatCostProjection([], 3);
35
+ assert.strictEqual(result.length, 0, "zero slices → empty array");
36
+ });
37
+
38
+ test("one slice → suppressed (need ≥2 to project reliably)", () => {
39
+ const result = formatCostProjection([makeSliceAggregate("M001/S01", 0.10)], 3);
40
+ assert.strictEqual(result.length, 0, "one slice → suppressed (no projection shown)");
41
+ });
42
+
43
+ test("two slices → projection shown", () => {
44
+ const slices = [
45
+ makeSliceAggregate("M001/S01", 0.10),
46
+ makeSliceAggregate("M001/S02", 0.10),
47
+ ];
48
+ const result = formatCostProjection(slices, 5);
49
+ assert.ok(result.length > 0, "two slices → projection shown");
50
+ });
51
+
52
+ test("two-slice result contains $ (cost is formatted)", () => {
53
+ const slices = [
54
+ makeSliceAggregate("M001/S01", 0.10),
55
+ makeSliceAggregate("M001/S02", 0.10),
56
+ ];
57
+ const result = formatCostProjection(slices, 5);
58
+ assert.ok(result.length > 0 && result[0].includes("$"), "projection line contains \"$\"");
59
+ });
60
+
61
+ test("budget ceiling hit: total >= ceiling → line contains ceiling", () => {
62
+ const slices = [
63
+ makeSliceAggregate("M001/S01", 0.10),
64
+ makeSliceAggregate("M001/S02", 0.10),
65
+ ];
66
+ const result = formatCostProjection(slices, 5, 0.05);
67
+ const hasCeilingLine = result.some(
68
+ line => line.toLowerCase().includes("ceiling")
69
+ );
70
+ assert.ok(hasCeilingLine, "ceiling warning appears when total ($0.20) >= ceiling ($0.05)");
71
+ });
72
+
73
+ test("budget ceiling not hit: total < ceiling → no ceiling line", () => {
74
+ const slices = [
75
+ makeSliceAggregate("M001/S01", 0.10),
76
+ makeSliceAggregate("M001/S02", 0.10),
77
+ ];
78
+ const result = formatCostProjection(slices, 5, 100.00);
79
+ const hasCeilingLine = result.some(
80
+ line => line.toLowerCase().includes("ceiling")
81
+ );
82
+ assert.ok(!hasCeilingLine, "no ceiling warning when total ($0.20) < ceiling ($100.00)");
83
+ });
84
+
85
+ test("no ceiling arg → no ceiling line", () => {
86
+ const slices = [
87
+ makeSliceAggregate("M001/S01", 0.10),
88
+ makeSliceAggregate("M001/S02", 0.10),
89
+ ];
90
+ const result = formatCostProjection(slices, 5);
91
+ const hasCeilingLine = result.some(
92
+ line => line.toLowerCase().includes("ceiling")
93
+ );
94
+ assert.ok(!hasCeilingLine, "no ceiling warning when no ceiling is set");
95
+ });
96
+
97
+ test("rounding: avg $0.10 × 5 remaining = $0.50", () => {
98
+ const slices = [
99
+ makeSliceAggregate("M001/S01", 0.10),
100
+ makeSliceAggregate("M001/S02", 0.10),
101
+ ];
102
+ const result = formatCostProjection(slices, 5);
103
+ const hasRoundedCost = result.some(line => line.includes("$0.50"));
104
+ assert.ok(hasRoundedCost, "projected cost $0.50 (avg $0.10 × 5 remaining) appears in output");
105
+ });
106
+
107
+ test("bare milestone entries excluded from average", () => {
108
+ const slices = [
109
+ makeSliceAggregate("M001", 5.00), // bare milestone — must be excluded
110
+ makeSliceAggregate("M001/S01", 0.10),
111
+ makeSliceAggregate("M001/S02", 0.10),
112
+ ];
113
+ const result = formatCostProjection(slices, 3);
114
+ const hasCorrectProjection = result.some(line => line.includes("$0.30"));
115
+ assert.ok(
116
+ hasCorrectProjection,
117
+ "bare milestone entry excluded from avg: projection shows $0.30 (avg $0.10 × 3), not $1.83 (including $5.00 entry)"
118
+ );
119
+ });
120
+ });
@@ -26,53 +26,45 @@ function cleanup(base: string): void {
26
26
 
27
27
  // ─── writeLock / readCrashLock ────────────────────────────────────────────
28
28
 
29
- test("writeLock creates lock file and readCrashLock reads it", () => {
29
+ test("writeLock creates lock file and readCrashLock reads it", (t) => {
30
30
  const base = makeTmpBase();
31
- try {
32
- writeLock(base, "execute-task", "M001/S01/T01", 3, "/tmp/session.jsonl");
33
- const lock = readCrashLock(base);
34
- assert.ok(lock, "lock should exist");
35
- assert.equal(lock!.unitType, "execute-task");
36
- assert.equal(lock!.unitId, "M001/S01/T01");
37
- assert.equal(lock!.completedUnits, 3);
38
- assert.equal(lock!.sessionFile, "/tmp/session.jsonl");
39
- assert.equal(lock!.pid, process.pid);
40
- } finally {
41
- cleanup(base);
42
- }
31
+ t.after(() => cleanup(base));
32
+
33
+ writeLock(base, "execute-task", "M001/S01/T01", 3, "/tmp/session.jsonl");
34
+ const lock = readCrashLock(base);
35
+ assert.ok(lock, "lock should exist");
36
+ assert.equal(lock!.unitType, "execute-task");
37
+ assert.equal(lock!.unitId, "M001/S01/T01");
38
+ assert.equal(lock!.completedUnits, 3);
39
+ assert.equal(lock!.sessionFile, "/tmp/session.jsonl");
40
+ assert.equal(lock!.pid, process.pid);
43
41
  });
44
42
 
45
- test("readCrashLock returns null when no lock exists", () => {
43
+ test("readCrashLock returns null when no lock exists", (t) => {
46
44
  const base = makeTmpBase();
47
- try {
48
- const lock = readCrashLock(base);
49
- assert.equal(lock, null);
50
- } finally {
51
- cleanup(base);
52
- }
45
+ t.after(() => cleanup(base));
46
+
47
+ const lock = readCrashLock(base);
48
+ assert.equal(lock, null);
53
49
  });
54
50
 
55
51
  // ─── clearLock ────────────────────────────────────────────────────────────
56
52
 
57
- test("clearLock removes existing lock file", () => {
53
+ test("clearLock removes existing lock file", (t) => {
58
54
  const base = makeTmpBase();
59
- try {
60
- writeLock(base, "plan-slice", "M001/S01", 0);
61
- assert.ok(readCrashLock(base), "lock should exist before clear");
62
- clearLock(base);
63
- assert.equal(readCrashLock(base), null, "lock should be gone after clear");
64
- } finally {
65
- cleanup(base);
66
- }
55
+ t.after(() => cleanup(base));
56
+
57
+ writeLock(base, "plan-slice", "M001/S01", 0);
58
+ assert.ok(readCrashLock(base), "lock should exist before clear");
59
+ clearLock(base);
60
+ assert.equal(readCrashLock(base), null, "lock should be gone after clear");
67
61
  });
68
62
 
69
- test("clearLock is safe when no lock exists", () => {
63
+ test("clearLock is safe when no lock exists", (t) => {
70
64
  const base = makeTmpBase();
71
- try {
72
- assert.doesNotThrow(() => clearLock(base));
73
- } finally {
74
- cleanup(base);
75
- }
65
+ t.after(() => cleanup(base));
66
+
67
+ assert.doesNotThrow(() => clearLock(base));
76
68
  });
77
69
 
78
70
  // ─── isLockProcessAlive ──────────────────────────────────────────────────