gsd-pi 2.41.0-dev.cac69f9 → 2.42.0-dev.1df898f

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 (263) hide show
  1. package/README.md +23 -0
  2. package/dist/cli.js +18 -3
  3. package/dist/loader.js +3 -1
  4. package/dist/resource-loader.js +39 -6
  5. package/dist/resources/extensions/async-jobs/async-bash-tool.js +52 -4
  6. package/dist/resources/extensions/async-jobs/await-tool.js +5 -0
  7. package/dist/resources/extensions/async-jobs/index.js +2 -0
  8. package/dist/resources/extensions/gsd/auto/loop.js +80 -0
  9. package/dist/resources/extensions/gsd/auto/phases.js +3 -5
  10. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  11. package/dist/resources/extensions/gsd/auto-dashboard.js +2 -0
  12. package/dist/resources/extensions/gsd/auto-prompts.js +3 -16
  13. package/dist/resources/extensions/gsd/auto-start.js +8 -11
  14. package/dist/resources/extensions/gsd/auto.js +28 -1
  15. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +11 -5
  16. package/dist/resources/extensions/gsd/bootstrap/tool-call-loop-guard.js +7 -2
  17. package/dist/resources/extensions/gsd/commands/catalog.js +32 -0
  18. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +146 -0
  19. package/dist/resources/extensions/gsd/context-injector.js +74 -0
  20. package/dist/resources/extensions/gsd/custom-execution-policy.js +47 -0
  21. package/dist/resources/extensions/gsd/custom-verification.js +145 -0
  22. package/dist/resources/extensions/gsd/custom-workflow-engine.js +164 -0
  23. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -0
  24. package/dist/resources/extensions/gsd/definition-loader.js +352 -0
  25. package/dist/resources/extensions/gsd/detection.js +19 -0
  26. package/dist/resources/extensions/gsd/dev-execution-policy.js +24 -0
  27. package/dist/resources/extensions/gsd/dev-workflow-engine.js +82 -0
  28. package/dist/resources/extensions/gsd/doctor-checks.js +31 -1
  29. package/dist/resources/extensions/gsd/doctor-providers.js +10 -0
  30. package/dist/resources/extensions/gsd/engine-resolver.js +40 -0
  31. package/dist/resources/extensions/gsd/engine-types.js +8 -0
  32. package/dist/resources/extensions/gsd/execution-policy.js +8 -0
  33. package/dist/resources/extensions/gsd/forensics.js +84 -0
  34. package/dist/resources/extensions/gsd/git-constants.js +1 -0
  35. package/dist/resources/extensions/gsd/git-service.js +1 -1
  36. package/dist/resources/extensions/gsd/graph.js +225 -0
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +1 -0
  38. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  39. package/dist/resources/extensions/gsd/preferences.js +59 -8
  40. package/dist/resources/extensions/gsd/prompts/forensics.md +12 -5
  41. package/dist/resources/extensions/gsd/repo-identity.js +46 -5
  42. package/dist/resources/extensions/gsd/run-manager.js +134 -0
  43. package/dist/resources/extensions/gsd/service-tier.js +13 -4
  44. package/dist/resources/extensions/gsd/session-lock.js +2 -2
  45. package/dist/resources/extensions/gsd/workflow-engine.js +7 -0
  46. package/dist/resources/extensions/gsd/worktree-resolver.js +2 -2
  47. package/dist/resources/extensions/gsd/worktree.js +2 -2
  48. package/dist/resources/extensions/mcp-client/index.js +2 -1
  49. package/dist/resources/extensions/search-the-web/tool-search.js +3 -3
  50. package/dist/resources/skills/create-workflow/SKILL.md +103 -0
  51. package/dist/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  52. package/dist/resources/skills/create-workflow/references/verification-policies.md +76 -0
  53. package/dist/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  54. package/dist/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  55. package/dist/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  56. package/dist/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  57. package/dist/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  58. package/dist/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  59. package/dist/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  60. package/dist/web/standalone/.next/BUILD_ID +1 -1
  61. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  62. package/dist/web/standalone/.next/build-manifest.json +2 -2
  63. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  64. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  65. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/index.html +1 -1
  82. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  89. package/dist/web/standalone/.next/server/chunks/229.js +2 -2
  90. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  91. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  92. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  93. package/dist/web-mode.d.ts +2 -0
  94. package/dist/web-mode.js +40 -4
  95. package/package.json +1 -1
  96. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  97. package/packages/pi-agent-core/dist/agent.js +2 -0
  98. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  99. package/packages/pi-agent-core/dist/types.d.ts +6 -0
  100. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  101. package/packages/pi-agent-core/dist/types.js.map +1 -1
  102. package/packages/pi-agent-core/src/agent.test.ts +53 -0
  103. package/packages/pi-agent-core/src/agent.ts +3 -0
  104. package/packages/pi-agent-core/src/types.ts +6 -0
  105. package/packages/pi-agent-core/tsconfig.json +1 -1
  106. package/packages/pi-ai/dist/models.d.ts +5 -3
  107. package/packages/pi-ai/dist/models.d.ts.map +1 -1
  108. package/packages/pi-ai/dist/models.generated.d.ts +801 -1468
  109. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  110. package/packages/pi-ai/dist/models.generated.js +1135 -1588
  111. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  112. package/packages/pi-ai/dist/models.js.map +1 -1
  113. package/packages/pi-ai/dist/utils/oauth/github-copilot.d.ts.map +1 -1
  114. package/packages/pi-ai/dist/utils/oauth/github-copilot.js +60 -2
  115. package/packages/pi-ai/dist/utils/oauth/github-copilot.js.map +1 -1
  116. package/packages/pi-ai/scripts/generate-models.ts +1543 -0
  117. package/packages/pi-ai/src/models.generated.ts +1140 -1593
  118. package/packages/pi-ai/src/models.ts +7 -4
  119. package/packages/pi-ai/src/utils/oauth/github-copilot.ts +74 -2
  120. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -1
  122. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +7 -0
  124. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/auth-storage.js +29 -2
  126. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +60 -0
  128. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/core/extensions/loader.js +18 -0
  131. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/lsp/client.js +23 -0
  134. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/model-registry.js +2 -0
  137. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  138. package/packages/pi-coding-agent/dist/core/package-manager.d.ts +6 -0
  139. package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/package-manager.js +63 -11
  141. package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +9 -0
  143. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/resource-loader.js +20 -6
  145. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/system-prompt.js +6 -5
  148. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js +3 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/extension-editor.js.map +1 -1
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +9 -6
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  156. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -10
  157. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  158. package/packages/pi-coding-agent/package.json +1 -1
  159. package/packages/pi-coding-agent/src/core/agent-session.ts +7 -1
  160. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +68 -0
  161. package/packages/pi-coding-agent/src/core/auth-storage.ts +30 -2
  162. package/packages/pi-coding-agent/src/core/extensions/loader.ts +18 -0
  163. package/packages/pi-coding-agent/src/core/lsp/client.ts +29 -0
  164. package/packages/pi-coding-agent/src/core/model-registry.ts +3 -0
  165. package/packages/pi-coding-agent/src/core/package-manager.ts +99 -58
  166. package/packages/pi-coding-agent/src/core/resource-loader.ts +24 -6
  167. package/packages/pi-coding-agent/src/core/system-prompt.ts +6 -5
  168. package/packages/pi-coding-agent/src/modes/interactive/components/extension-editor.ts +3 -0
  169. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +10 -6
  170. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +31 -11
  171. package/pkg/package.json +1 -1
  172. package/src/resources/extensions/async-jobs/async-bash-timeout.test.ts +122 -0
  173. package/src/resources/extensions/async-jobs/async-bash-tool.ts +40 -4
  174. package/src/resources/extensions/async-jobs/await-tool.test.ts +47 -0
  175. package/src/resources/extensions/async-jobs/await-tool.ts +5 -0
  176. package/src/resources/extensions/async-jobs/index.ts +1 -0
  177. package/src/resources/extensions/async-jobs/job-manager.ts +2 -0
  178. package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -1
  179. package/src/resources/extensions/gsd/auto/loop.ts +91 -0
  180. package/src/resources/extensions/gsd/auto/phases.ts +3 -5
  181. package/src/resources/extensions/gsd/auto/session.ts +6 -0
  182. package/src/resources/extensions/gsd/auto-dashboard.ts +2 -0
  183. package/src/resources/extensions/gsd/auto-prompts.ts +2 -18
  184. package/src/resources/extensions/gsd/auto-start.ts +7 -10
  185. package/src/resources/extensions/gsd/auto.ts +31 -1
  186. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +13 -5
  187. package/src/resources/extensions/gsd/bootstrap/tool-call-loop-guard.ts +9 -2
  188. package/src/resources/extensions/gsd/commands/catalog.ts +32 -0
  189. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +164 -0
  190. package/src/resources/extensions/gsd/context-injector.ts +100 -0
  191. package/src/resources/extensions/gsd/custom-execution-policy.ts +73 -0
  192. package/src/resources/extensions/gsd/custom-verification.ts +180 -0
  193. package/src/resources/extensions/gsd/custom-workflow-engine.ts +216 -0
  194. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -0
  195. package/src/resources/extensions/gsd/definition-loader.ts +462 -0
  196. package/src/resources/extensions/gsd/detection.ts +19 -0
  197. package/src/resources/extensions/gsd/dev-execution-policy.ts +51 -0
  198. package/src/resources/extensions/gsd/dev-workflow-engine.ts +110 -0
  199. package/src/resources/extensions/gsd/doctor-checks.ts +32 -1
  200. package/src/resources/extensions/gsd/doctor-providers.ts +13 -0
  201. package/src/resources/extensions/gsd/doctor-types.ts +1 -0
  202. package/src/resources/extensions/gsd/engine-resolver.ts +57 -0
  203. package/src/resources/extensions/gsd/engine-types.ts +71 -0
  204. package/src/resources/extensions/gsd/execution-policy.ts +43 -0
  205. package/src/resources/extensions/gsd/forensics.ts +92 -0
  206. package/src/resources/extensions/gsd/git-constants.ts +1 -0
  207. package/src/resources/extensions/gsd/git-service.ts +0 -1
  208. package/src/resources/extensions/gsd/gitignore.ts +1 -1
  209. package/src/resources/extensions/gsd/graph.ts +312 -0
  210. package/src/resources/extensions/gsd/native-git-bridge.ts +1 -0
  211. package/src/resources/extensions/gsd/preferences-types.ts +3 -0
  212. package/src/resources/extensions/gsd/preferences.ts +62 -6
  213. package/src/resources/extensions/gsd/prompts/forensics.md +12 -5
  214. package/src/resources/extensions/gsd/repo-identity.ts +48 -5
  215. package/src/resources/extensions/gsd/run-manager.ts +180 -0
  216. package/src/resources/extensions/gsd/service-tier.ts +17 -4
  217. package/src/resources/extensions/gsd/session-lock.ts +2 -2
  218. package/src/resources/extensions/gsd/tests/activity-log.test.ts +31 -69
  219. package/src/resources/extensions/gsd/tests/bundled-workflow-defs.test.ts +180 -0
  220. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +283 -0
  221. package/src/resources/extensions/gsd/tests/context-injector.test.ts +313 -0
  222. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +540 -0
  223. package/src/resources/extensions/gsd/tests/custom-verification.test.ts +382 -0
  224. package/src/resources/extensions/gsd/tests/custom-workflow-engine.test.ts +339 -0
  225. package/src/resources/extensions/gsd/tests/dashboard-custom-engine.test.ts +87 -0
  226. package/src/resources/extensions/gsd/tests/definition-loader.test.ts +778 -0
  227. package/src/resources/extensions/gsd/tests/dev-engine-wrapper.test.ts +318 -0
  228. package/src/resources/extensions/gsd/tests/e2e-workflow-pipeline-integration.test.ts +476 -0
  229. package/src/resources/extensions/gsd/tests/engine-interfaces-contract.test.ts +271 -0
  230. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +48 -0
  231. package/src/resources/extensions/gsd/tests/forensics-issue-routing.test.ts +43 -0
  232. package/src/resources/extensions/gsd/tests/git-locale.test.ts +133 -0
  233. package/src/resources/extensions/gsd/tests/git-service.test.ts +44 -0
  234. package/src/resources/extensions/gsd/tests/graph-operations.test.ts +599 -0
  235. package/src/resources/extensions/gsd/tests/iterate-engine-integration.test.ts +429 -0
  236. package/src/resources/extensions/gsd/tests/journal.test.ts +82 -127
  237. package/src/resources/extensions/gsd/tests/manifest-status.test.ts +73 -82
  238. package/src/resources/extensions/gsd/tests/run-manager.test.ts +229 -0
  239. package/src/resources/extensions/gsd/tests/service-tier.test.ts +30 -1
  240. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +56 -3
  241. package/src/resources/extensions/gsd/tests/symlink-numbered-variants.test.ts +151 -0
  242. package/src/resources/extensions/gsd/tests/tool-call-loop-guard.test.ts +45 -0
  243. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +156 -263
  244. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -78
  245. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +81 -74
  246. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +1 -2
  247. package/src/resources/extensions/gsd/workflow-engine.ts +38 -0
  248. package/src/resources/extensions/gsd/worktree-resolver.ts +2 -3
  249. package/src/resources/extensions/gsd/worktree.ts +2 -2
  250. package/src/resources/extensions/mcp-client/index.ts +5 -1
  251. package/src/resources/extensions/search-the-web/tool-search.ts +3 -3
  252. package/src/resources/skills/create-workflow/SKILL.md +103 -0
  253. package/src/resources/skills/create-workflow/references/feature-patterns.md +128 -0
  254. package/src/resources/skills/create-workflow/references/verification-policies.md +76 -0
  255. package/src/resources/skills/create-workflow/references/yaml-schema-v1.md +46 -0
  256. package/src/resources/skills/create-workflow/templates/blog-post-pipeline.yaml +60 -0
  257. package/src/resources/skills/create-workflow/templates/code-audit.yaml +60 -0
  258. package/src/resources/skills/create-workflow/templates/release-checklist.yaml +66 -0
  259. package/src/resources/skills/create-workflow/templates/workflow-definition.yaml +32 -0
  260. package/src/resources/skills/create-workflow/workflows/create-from-scratch.md +104 -0
  261. package/src/resources/skills/create-workflow/workflows/create-from-template.md +72 -0
  262. /package/dist/web/standalone/.next/static/{EnGUNqHeGbE0tuuUkTJVA → qw8qDHXOTLUXBq1vEknSz}/_buildManifest.js +0 -0
  263. /package/dist/web/standalone/.next/static/{EnGUNqHeGbE0tuuUkTJVA → qw8qDHXOTLUXBq1vEknSz}/_ssgManifest.js +0 -0
@@ -1,4 +1,4 @@
1
- import test from "node:test";
1
+ import { describe, test, beforeEach, afterEach } from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import {
4
4
  mkdirSync,
@@ -46,9 +46,12 @@ function makeEntry(overrides: Partial<JournalEntry> = {}): JournalEntry {
46
46
 
47
47
  // ─── emitJournalEvent ─────────────────────────────────────────────────────────
48
48
 
49
- test("emitJournalEvent creates journal directory and JSONL file", () => {
50
- const base = makeTmpBase();
51
- try {
49
+ describe("emitJournalEvent", () => {
50
+ let base: string;
51
+ beforeEach(() => { base = makeTmpBase(); });
52
+ afterEach(() => { cleanup(base); });
53
+
54
+ test("creates journal directory and JSONL file", () => {
52
55
  const entry = makeEntry();
53
56
  emitJournalEvent(base, entry);
54
57
 
@@ -61,14 +64,9 @@ test("emitJournalEvent creates journal directory and JSONL file", () => {
61
64
  assert.equal(parsed.flowId, entry.flowId);
62
65
  assert.equal(parsed.seq, entry.seq);
63
66
  assert.equal(parsed.eventType, entry.eventType);
64
- } finally {
65
- cleanup(base);
66
- }
67
- });
67
+ });
68
68
 
69
- test("emitJournalEvent appends multiple lines to the same file", () => {
70
- const base = makeTmpBase();
71
- try {
69
+ test("appends multiple lines to the same file", () => {
72
70
  emitJournalEvent(base, makeEntry({ seq: 0 }));
73
71
  emitJournalEvent(base, makeEntry({ seq: 1, eventType: "dispatch-match" }));
74
72
  emitJournalEvent(base, makeEntry({ seq: 2, eventType: "unit-start" }));
@@ -82,26 +80,9 @@ test("emitJournalEvent appends multiple lines to the same file", () => {
82
80
  assert.equal(parsed[1].seq, 1);
83
81
  assert.equal(parsed[2].seq, 2);
84
82
  assert.equal(parsed[1].eventType, "dispatch-match");
85
- } finally {
86
- cleanup(base);
87
- }
88
- });
89
-
90
- test("emitJournalEvent auto-creates nonexistent parent directory", () => {
91
- const base = join(tmpdir(), `gsd-journal-test-${randomUUID()}`);
92
- // Don't create .gsd/ — emitJournalEvent should handle it via mkdirSync recursive
93
- try {
94
- emitJournalEvent(base, makeEntry());
95
- const filePath = join(base, ".gsd", "journal", "2025-03-21.jsonl");
96
- assert.ok(existsSync(filePath), "File should exist even when parent dirs did not");
97
- } finally {
98
- cleanup(base);
99
- }
100
- });
83
+ });
101
84
 
102
- test("emitJournalEvent preserves optional fields (rule, causedBy, data)", () => {
103
- const base = makeTmpBase();
104
- try {
85
+ test("preserves optional fields (rule, causedBy, data)", () => {
105
86
  const entry = makeEntry({
106
87
  rule: "my-dispatch-rule",
107
88
  causedBy: { flowId: "flow-prior", seq: 3 },
@@ -115,24 +96,12 @@ test("emitJournalEvent preserves optional fields (rule, causedBy, data)", () =>
115
96
  assert.deepEqual(parsed.causedBy, { flowId: "flow-prior", seq: 3 });
116
97
  assert.equal(parsed.data.unitId, "M001/S01/T01");
117
98
  assert.equal(parsed.data.status, "ok");
118
- } finally {
119
- cleanup(base);
120
- }
121
- });
122
-
123
- test("emitJournalEvent silently catches write errors (no throw)", () => {
124
- // Use a path that can't be created — null bytes in path
125
- assert.doesNotThrow(() => {
126
- emitJournalEvent("/dev/null/impossible\0path", makeEntry());
127
99
  });
128
- });
129
100
 
130
- test("emitJournalEvent silently catches read-only directory errors", () => {
131
- const base = makeTmpBase();
132
- const journalDir = join(base, ".gsd", "journal");
133
- mkdirSync(journalDir, { recursive: true });
101
+ test("silently catches read-only directory errors", () => {
102
+ const journalDir = join(base, ".gsd", "journal");
103
+ mkdirSync(journalDir, { recursive: true });
134
104
 
135
- try {
136
105
  // Make the journal directory read-only
137
106
  chmodSync(journalDir, 0o444);
138
107
 
@@ -140,22 +109,46 @@ test("emitJournalEvent silently catches read-only directory errors", () => {
140
109
  assert.doesNotThrow(() => {
141
110
  emitJournalEvent(base, makeEntry());
142
111
  });
143
- } finally {
112
+
144
113
  // Restore permissions for cleanup
145
114
  try {
146
115
  chmodSync(journalDir, 0o755);
147
116
  } catch {
148
117
  /* */
149
118
  }
150
- cleanup(base);
151
- }
119
+ });
120
+ });
121
+
122
+ describe("emitJournalEvent — auto-creates parent directory", () => {
123
+ let base: string;
124
+ beforeEach(() => {
125
+ base = join(tmpdir(), `gsd-journal-test-${randomUUID()}`);
126
+ // Don't create .gsd/ — emitJournalEvent should handle it via mkdirSync recursive
127
+ });
128
+ afterEach(() => { cleanup(base); });
129
+
130
+ test("auto-creates nonexistent parent directory", () => {
131
+ emitJournalEvent(base, makeEntry());
132
+ const filePath = join(base, ".gsd", "journal", "2025-03-21.jsonl");
133
+ assert.ok(existsSync(filePath), "File should exist even when parent dirs did not");
134
+ });
135
+ });
136
+
137
+ test("emitJournalEvent silently catches write errors (no throw)", () => {
138
+ // Use a path that can't be created — null bytes in path
139
+ assert.doesNotThrow(() => {
140
+ emitJournalEvent("/dev/null/impossible\0path", makeEntry());
141
+ });
152
142
  });
153
143
 
154
144
  // ─── Daily Rotation ───────────────────────────────────────────────────────────
155
145
 
156
- test("daily rotation: events with different dates go to different files", () => {
157
- const base = makeTmpBase();
158
- try {
146
+ describe("daily rotation", () => {
147
+ let base: string;
148
+ beforeEach(() => { base = makeTmpBase(); });
149
+ afterEach(() => { cleanup(base); });
150
+
151
+ test("events with different dates go to different files", () => {
159
152
  emitJournalEvent(base, makeEntry({ ts: "2025-03-20T23:59:59.000Z" }));
160
153
  emitJournalEvent(base, makeEntry({ ts: "2025-03-21T00:00:01.000Z" }));
161
154
  emitJournalEvent(base, makeEntry({ ts: "2025-03-22T12:00:00.000Z" }));
@@ -172,16 +165,17 @@ test("daily rotation: events with different dates go to different files", () =>
172
165
  .split("\n");
173
166
  assert.equal(lines.length, 1, `${date}.jsonl should have 1 line`);
174
167
  }
175
- } finally {
176
- cleanup(base);
177
- }
168
+ });
178
169
  });
179
170
 
180
171
  // ─── queryJournal ─────────────────────────────────────────────────────────────
181
172
 
182
- test("queryJournal returns all entries when no filters provided", () => {
183
- const base = makeTmpBase();
184
- try {
173
+ describe("queryJournal", () => {
174
+ let base: string;
175
+ beforeEach(() => { base = makeTmpBase(); });
176
+ afterEach(() => { cleanup(base); });
177
+
178
+ test("returns all entries when no filters provided", () => {
185
179
  emitJournalEvent(base, makeEntry({ seq: 0 }));
186
180
  emitJournalEvent(base, makeEntry({ seq: 1, eventType: "dispatch-match" }));
187
181
 
@@ -189,14 +183,9 @@ test("queryJournal returns all entries when no filters provided", () => {
189
183
  assert.equal(results.length, 2);
190
184
  assert.equal(results[0].seq, 0);
191
185
  assert.equal(results[1].seq, 1);
192
- } finally {
193
- cleanup(base);
194
- }
195
- });
186
+ });
196
187
 
197
- test("queryJournal filters by flowId", () => {
198
- const base = makeTmpBase();
199
- try {
188
+ test("filters by flowId", () => {
200
189
  emitJournalEvent(base, makeEntry({ flowId: "flow-aaa", seq: 0 }));
201
190
  emitJournalEvent(base, makeEntry({ flowId: "flow-bbb", seq: 1 }));
202
191
  emitJournalEvent(base, makeEntry({ flowId: "flow-aaa", seq: 2 }));
@@ -204,14 +193,9 @@ test("queryJournal filters by flowId", () => {
204
193
  const results = queryJournal(base, { flowId: "flow-aaa" });
205
194
  assert.equal(results.length, 2);
206
195
  assert.ok(results.every(e => e.flowId === "flow-aaa"));
207
- } finally {
208
- cleanup(base);
209
- }
210
- });
196
+ });
211
197
 
212
- test("queryJournal filters by eventType", () => {
213
- const base = makeTmpBase();
214
- try {
198
+ test("filters by eventType", () => {
215
199
  emitJournalEvent(base, makeEntry({ eventType: "iteration-start", seq: 0 }));
216
200
  emitJournalEvent(base, makeEntry({ eventType: "dispatch-match", seq: 1 }));
217
201
  emitJournalEvent(base, makeEntry({ eventType: "unit-start", seq: 2 }));
@@ -220,14 +204,9 @@ test("queryJournal filters by eventType", () => {
220
204
  const results = queryJournal(base, { eventType: "dispatch-match" });
221
205
  assert.equal(results.length, 2);
222
206
  assert.ok(results.every(e => e.eventType === "dispatch-match"));
223
- } finally {
224
- cleanup(base);
225
- }
226
- });
207
+ });
227
208
 
228
- test("queryJournal filters by unitId (from data.unitId)", () => {
229
- const base = makeTmpBase();
230
- try {
209
+ test("filters by unitId (from data.unitId)", () => {
231
210
  emitJournalEvent(
232
211
  base,
233
212
  makeEntry({ seq: 0, data: { unitId: "M001/S01/T01" } }),
@@ -249,14 +228,9 @@ test("queryJournal filters by unitId (from data.unitId)", () => {
249
228
  e => (e.data as Record<string, unknown>)?.unitId === "M001/S01/T01",
250
229
  ),
251
230
  );
252
- } finally {
253
- cleanup(base);
254
- }
255
- });
231
+ });
256
232
 
257
- test("queryJournal filters by time range (after/before)", () => {
258
- const base = makeTmpBase();
259
- try {
233
+ test("filters by time range (after/before)", () => {
260
234
  emitJournalEvent(base, makeEntry({ ts: "2025-03-20T08:00:00.000Z", seq: 0 }));
261
235
  emitJournalEvent(base, makeEntry({ ts: "2025-03-21T10:00:00.000Z", seq: 1 }));
262
236
  emitJournalEvent(base, makeEntry({ ts: "2025-03-21T15:00:00.000Z", seq: 2 }));
@@ -276,14 +250,9 @@ test("queryJournal filters by time range (after/before)", () => {
276
250
  before: "2025-03-21T23:59:59.000Z",
277
251
  });
278
252
  assert.equal(rangeResults.length, 2, "2 entries within 2025-03-21");
279
- } finally {
280
- cleanup(base);
281
- }
282
- });
253
+ });
283
254
 
284
- test("queryJournal combines multiple filters", () => {
285
- const base = makeTmpBase();
286
- try {
255
+ test("combines multiple filters", () => {
287
256
  emitJournalEvent(
288
257
  base,
289
258
  makeEntry({ flowId: "flow-aaa", eventType: "unit-start", seq: 0 }),
@@ -304,25 +273,9 @@ test("queryJournal combines multiple filters", () => {
304
273
  assert.equal(results.length, 1);
305
274
  assert.equal(results[0].flowId, "flow-aaa");
306
275
  assert.equal(results[0].eventType, "unit-start");
307
- } finally {
308
- cleanup(base);
309
- }
310
- });
311
-
312
- test("queryJournal on nonexistent directory returns empty array", () => {
313
- const base = join(tmpdir(), `gsd-journal-test-${randomUUID()}`);
314
- // Don't create anything
315
- try {
316
- const results = queryJournal(base);
317
- assert.deepEqual(results, []);
318
- } finally {
319
- cleanup(base);
320
- }
321
- });
276
+ });
322
277
 
323
- test("queryJournal skips malformed JSON lines gracefully", () => {
324
- const base = makeTmpBase();
325
- try {
278
+ test("skips malformed JSON lines gracefully", () => {
326
279
  const journalDir = join(base, ".gsd", "journal");
327
280
  mkdirSync(journalDir, { recursive: true });
328
281
 
@@ -335,14 +288,9 @@ test("queryJournal skips malformed JSON lines gracefully", () => {
335
288
  assert.equal(results.length, 2, "Should skip the malformed line");
336
289
  assert.equal(results[0].seq, 0);
337
290
  assert.equal(results[1].seq, 1);
338
- } finally {
339
- cleanup(base);
340
- }
341
- });
291
+ });
342
292
 
343
- test("queryJournal reads across multiple daily files", () => {
344
- const base = makeTmpBase();
345
- try {
293
+ test("reads across multiple daily files", () => {
346
294
  emitJournalEvent(base, makeEntry({ ts: "2025-03-20T12:00:00.000Z", seq: 0 }));
347
295
  emitJournalEvent(base, makeEntry({ ts: "2025-03-21T12:00:00.000Z", seq: 1 }));
348
296
  emitJournalEvent(base, makeEntry({ ts: "2025-03-22T12:00:00.000Z", seq: 2 }));
@@ -353,14 +301,9 @@ test("queryJournal reads across multiple daily files", () => {
353
301
  assert.equal(results[0].ts, "2025-03-20T12:00:00.000Z");
354
302
  assert.equal(results[1].ts, "2025-03-21T12:00:00.000Z");
355
303
  assert.equal(results[2].ts, "2025-03-22T12:00:00.000Z");
356
- } finally {
357
- cleanup(base);
358
- }
359
- });
304
+ });
360
305
 
361
- test("queryJournal filters by rule", () => {
362
- const base = makeTmpBase();
363
- try {
306
+ test("filters by rule", () => {
364
307
  emitJournalEvent(
365
308
  base,
366
309
  makeEntry({ seq: 0, eventType: "dispatch-match", rule: "dispatch-task" }),
@@ -380,7 +323,19 @@ test("queryJournal filters by rule", () => {
380
323
  results.every(e => e.rule === "dispatch-task"),
381
324
  "All results should have rule === 'dispatch-task'",
382
325
  );
383
- } finally {
384
- cleanup(base);
385
- }
326
+ });
327
+ });
328
+
329
+ describe("queryJournal — nonexistent directory", () => {
330
+ let base: string;
331
+ beforeEach(() => {
332
+ base = join(tmpdir(), `gsd-journal-test-${randomUUID()}`);
333
+ // Don't create anything
334
+ });
335
+ afterEach(() => { cleanup(base); });
336
+
337
+ test("on nonexistent directory returns empty array", () => {
338
+ const results = queryJournal(base);
339
+ assert.deepEqual(results, []);
340
+ });
386
341
  });
@@ -8,7 +8,7 @@
8
8
  * Uses temp directories with real .gsd/milestones/M001/ structure.
9
9
  */
10
10
 
11
- import test from 'node:test';
11
+ import { describe, test, beforeEach, afterEach } from 'node:test';
12
12
  import assert from 'node:assert/strict';
13
13
  import { mkdirSync, writeFileSync, rmSync } from 'node:fs';
14
14
  import { join } from 'node:path';
@@ -30,12 +30,21 @@ function writeManifest(base: string, content: string): void {
30
30
 
31
31
  // ─── Mixed statuses ──────────────────────────────────────────────────────────
32
32
 
33
- test('getManifestStatus: mixed statuses — categorizes entries correctly', async () => {
34
- const tmp = makeTempDir('manifest-mixed');
35
- const savedVal = process.env.GSD_TEST_EXISTING_KEY_001;
36
- try {
33
+ describe('getManifestStatus: mixed statuses', () => {
34
+ let tmp: string;
35
+ let savedVal: string | undefined;
36
+ beforeEach(() => {
37
+ tmp = makeTempDir('manifest-mixed');
38
+ savedVal = process.env.GSD_TEST_EXISTING_KEY_001;
37
39
  process.env.GSD_TEST_EXISTING_KEY_001 = 'some-value';
40
+ });
41
+ afterEach(() => {
42
+ delete process.env.GSD_TEST_EXISTING_KEY_001;
43
+ if (savedVal !== undefined) process.env.GSD_TEST_EXISTING_KEY_001 = savedVal;
44
+ rmSync(tmp, { recursive: true, force: true });
45
+ });
38
46
 
47
+ test('categorizes entries correctly', async () => {
39
48
  writeManifest(tmp, `# Secrets Manifest
40
49
 
41
50
  **Milestone:** M001
@@ -80,18 +89,17 @@ test('getManifestStatus: mixed statuses — categorizes entries correctly', asyn
80
89
  assert.deepStrictEqual(result!.collected, ['COLLECTED_KEY']);
81
90
  assert.deepStrictEqual(result!.skipped, ['SKIPPED_KEY']);
82
91
  assert.deepStrictEqual(result!.existing, ['GSD_TEST_EXISTING_KEY_001']);
83
- } finally {
84
- delete process.env.GSD_TEST_EXISTING_KEY_001;
85
- if (savedVal !== undefined) process.env.GSD_TEST_EXISTING_KEY_001 = savedVal;
86
- rmSync(tmp, { recursive: true, force: true });
87
- }
92
+ });
88
93
  });
89
94
 
90
95
  // ─── All pending ─────────────────────────────────────────────────────────────
91
96
 
92
- test('getManifestStatus: all pending 3 pending entries, none in env', async () => {
93
- const tmp = makeTempDir('manifest-pending');
94
- try {
97
+ describe('getManifestStatus: simple temp dir tests', () => {
98
+ let tmp: string;
99
+ beforeEach(() => { tmp = makeTempDir('manifest-test'); });
100
+ afterEach(() => { rmSync(tmp, { recursive: true, force: true }); });
101
+
102
+ test('all pending — 3 pending entries, none in env', async () => {
95
103
  // Ensure none of these are in process.env
96
104
  delete process.env.PEND_A;
97
105
  delete process.env.PEND_B;
@@ -133,16 +141,11 @@ test('getManifestStatus: all pending — 3 pending entries, none in env', async
133
141
  assert.deepStrictEqual(result!.collected, []);
134
142
  assert.deepStrictEqual(result!.skipped, []);
135
143
  assert.deepStrictEqual(result!.existing, []);
136
- } finally {
137
- rmSync(tmp, { recursive: true, force: true });
138
- }
139
- });
144
+ });
140
145
 
141
- // ─── All collected ───────────────────────────────────────────────────────────
146
+ // ─── All collected ───────────────────────────────────────────────────────────
142
147
 
143
- test('getManifestStatus: all collected — 2 collected entries, none in env', async () => {
144
- const tmp = makeTempDir('manifest-collected');
145
- try {
148
+ test('all collected — 2 collected entries, none in env', async () => {
146
149
  delete process.env.COLL_X;
147
150
  delete process.env.COLL_Y;
148
151
 
@@ -174,64 +177,19 @@ test('getManifestStatus: all collected — 2 collected entries, none in env', as
174
177
  assert.deepStrictEqual(result!.collected, ['COLL_X', 'COLL_Y']);
175
178
  assert.deepStrictEqual(result!.skipped, []);
176
179
  assert.deepStrictEqual(result!.existing, []);
177
- } finally {
178
- rmSync(tmp, { recursive: true, force: true });
179
- }
180
- });
181
-
182
- // ─── Key in env overrides manifest status ────────────────────────────────────
183
-
184
- test('getManifestStatus: key in env overrides manifest status — collected key in env goes to existing', async () => {
185
- const tmp = makeTempDir('manifest-override');
186
- const savedVal = process.env.GSD_TEST_OVERRIDE_KEY;
187
- try {
188
- process.env.GSD_TEST_OVERRIDE_KEY = 'already-here';
189
-
190
- writeManifest(tmp, `# Secrets Manifest
191
-
192
- **Milestone:** M001
193
- **Generated:** 2025-06-20T10:00:00Z
194
-
195
- ### GSD_TEST_OVERRIDE_KEY
196
-
197
- **Service:** Override
198
- **Status:** collected
199
- **Destination:** dotenv
200
-
201
- 1. Was collected but now in env
202
- `);
203
-
204
- const result = await getManifestStatus(tmp, 'M001');
205
- assert.notStrictEqual(result, null);
206
- assert.deepStrictEqual(result!.pending, []);
207
- assert.deepStrictEqual(result!.collected, []);
208
- assert.deepStrictEqual(result!.skipped, []);
209
- assert.deepStrictEqual(result!.existing, ['GSD_TEST_OVERRIDE_KEY']);
210
- } finally {
211
- delete process.env.GSD_TEST_OVERRIDE_KEY;
212
- if (savedVal !== undefined) process.env.GSD_TEST_OVERRIDE_KEY = savedVal;
213
- rmSync(tmp, { recursive: true, force: true });
214
- }
215
- });
180
+ });
216
181
 
217
- // ─── Missing manifest ────────────────────────────────────────────────────────
182
+ // ─── Missing manifest ────────────────────────────────────────────────────────
218
183
 
219
- test('getManifestStatus: missing manifest — returns null', async () => {
220
- const tmp = makeTempDir('manifest-missing');
221
- try {
184
+ test('missing manifest — returns null', async () => {
222
185
  // No .gsd directory at all
223
186
  const result = await getManifestStatus(tmp, 'M001');
224
187
  assert.strictEqual(result, null);
225
- } finally {
226
- rmSync(tmp, { recursive: true, force: true });
227
- }
228
- });
188
+ });
229
189
 
230
- // ─── Empty manifest (no entries) ─────────────────────────────────────────────
190
+ // ─── Empty manifest (no entries) ─────────────────────────────────────────────
231
191
 
232
- test('getManifestStatus: empty manifest — exists but no H3 sections', async () => {
233
- const tmp = makeTempDir('manifest-empty');
234
- try {
192
+ test('empty manifest — exists but no H3 sections', async () => {
235
193
  writeManifest(tmp, `# Secrets Manifest
236
194
 
237
195
  **Milestone:** M001
@@ -244,16 +202,11 @@ test('getManifestStatus: empty manifest — exists but no H3 sections', async ()
244
202
  assert.deepStrictEqual(result!.collected, []);
245
203
  assert.deepStrictEqual(result!.skipped, []);
246
204
  assert.deepStrictEqual(result!.existing, []);
247
- } finally {
248
- rmSync(tmp, { recursive: true, force: true });
249
- }
250
- });
205
+ });
251
206
 
252
- // ─── Env via .env file (not just process.env) ────────────────────────────────
207
+ // ─── Env via .env file (not just process.env) ────────────────────────────────
253
208
 
254
- test('getManifestStatus: key in .env file counts as existing', async () => {
255
- const tmp = makeTempDir('manifest-dotenv');
256
- try {
209
+ test('key in .env file counts as existing', async () => {
257
210
  delete process.env.DOTENV_ONLY_KEY;
258
211
 
259
212
  writeManifest(tmp, `# Secrets Manifest
@@ -277,7 +230,45 @@ test('getManifestStatus: key in .env file counts as existing', async () => {
277
230
  assert.notStrictEqual(result, null);
278
231
  assert.deepStrictEqual(result!.existing, ['DOTENV_ONLY_KEY']);
279
232
  assert.deepStrictEqual(result!.pending, []);
280
- } finally {
233
+ });
234
+ });
235
+
236
+ // ─── Key in env overrides manifest status ────────────────────────────────────
237
+
238
+ describe('getManifestStatus: key in env overrides manifest status', () => {
239
+ let tmp: string;
240
+ let savedVal: string | undefined;
241
+ beforeEach(() => {
242
+ tmp = makeTempDir('manifest-override');
243
+ savedVal = process.env.GSD_TEST_OVERRIDE_KEY;
244
+ process.env.GSD_TEST_OVERRIDE_KEY = 'already-here';
245
+ });
246
+ afterEach(() => {
247
+ delete process.env.GSD_TEST_OVERRIDE_KEY;
248
+ if (savedVal !== undefined) process.env.GSD_TEST_OVERRIDE_KEY = savedVal;
281
249
  rmSync(tmp, { recursive: true, force: true });
282
- }
250
+ });
251
+
252
+ test('collected key in env goes to existing', async () => {
253
+ writeManifest(tmp, `# Secrets Manifest
254
+
255
+ **Milestone:** M001
256
+ **Generated:** 2025-06-20T10:00:00Z
257
+
258
+ ### GSD_TEST_OVERRIDE_KEY
259
+
260
+ **Service:** Override
261
+ **Status:** collected
262
+ **Destination:** dotenv
263
+
264
+ 1. Was collected but now in env
265
+ `);
266
+
267
+ const result = await getManifestStatus(tmp, 'M001');
268
+ assert.notStrictEqual(result, null);
269
+ assert.deepStrictEqual(result!.pending, []);
270
+ assert.deepStrictEqual(result!.collected, []);
271
+ assert.deepStrictEqual(result!.skipped, []);
272
+ assert.deepStrictEqual(result!.existing, ['GSD_TEST_OVERRIDE_KEY']);
273
+ });
283
274
  });