gsd-pi 2.80.0-dev.2be7caf18 → 2.80.0-dev.3065f7bfa

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 (268) hide show
  1. package/README.md +1 -0
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/phases.js +178 -108
  4. package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
  5. package/dist/resources/extensions/gsd/auto/run-unit.js +20 -30
  6. package/dist/resources/extensions/gsd/auto/session.js +8 -0
  7. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +5 -32
  8. package/dist/resources/extensions/gsd/auto-dispatch.js +16 -0
  9. package/dist/resources/extensions/gsd/auto-post-unit.js +17 -4
  10. package/dist/resources/extensions/gsd/auto-prompts.js +90 -15
  11. package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
  12. package/dist/resources/extensions/gsd/auto-start.js +197 -6
  13. package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
  14. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
  15. package/dist/resources/extensions/gsd/auto-worktree.js +111 -1
  16. package/dist/resources/extensions/gsd/auto.js +79 -24
  17. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +42 -2
  18. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +49 -36
  19. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +15 -5
  20. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +9 -3
  21. package/dist/resources/extensions/gsd/bootstrap/journal-tools.js +7 -1
  22. package/dist/resources/extensions/gsd/bootstrap/memory-tools.js +9 -3
  23. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +8 -2
  24. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +238 -28
  25. package/dist/resources/extensions/gsd/bootstrap/system-context.js +82 -23
  26. package/dist/resources/extensions/gsd/clean-root-preflight.js +24 -6
  27. package/dist/resources/extensions/gsd/commands-handlers.js +23 -9
  28. package/dist/resources/extensions/gsd/context-budget.js +37 -2
  29. package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
  30. package/dist/resources/extensions/gsd/db-base-schema.js +4 -2
  31. package/dist/resources/extensions/gsd/db-migration-steps.js +6 -0
  32. package/dist/resources/extensions/gsd/ecosystem/gsd-extension-api.js +2 -0
  33. package/dist/resources/extensions/gsd/git-service.js +36 -4
  34. package/dist/resources/extensions/gsd/gsd-db.js +46 -13
  35. package/dist/resources/extensions/gsd/guided-flow.js +66 -32
  36. package/dist/resources/extensions/gsd/memory-store.js +69 -12
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +32 -8
  38. package/dist/resources/extensions/gsd/orphan-stash-audit.js +101 -0
  39. package/dist/resources/extensions/gsd/parallel-orchestrator.js +13 -3
  40. package/dist/resources/extensions/gsd/pre-execution-checks.js +22 -0
  41. package/dist/resources/extensions/gsd/prompt-loader.js +28 -2
  42. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
  43. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  44. package/dist/resources/extensions/gsd/prompts/execute-task.md +4 -2
  45. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  47. package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -0
  48. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +1 -1
  49. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  50. package/dist/resources/extensions/gsd/workflow-protocol.js +131 -0
  51. package/dist/resources/extensions/gsd/worktree-resolver.js +68 -21
  52. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  53. package/dist/web/standalone/.next/BUILD_ID +1 -1
  54. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  55. package/dist/web/standalone/.next/build-manifest.json +2 -2
  56. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  57. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.html +1 -1
  74. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  81. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  83. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  84. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  85. package/package.json +3 -3
  86. package/packages/mcp-server/src/workflow-tools.test.ts +9 -2
  87. package/packages/native/tsconfig.tsbuildinfo +1 -1
  88. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  89. package/packages/pi-agent-core/dist/agent-loop.js +4 -1
  90. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  91. package/packages/pi-agent-core/dist/agent.d.ts +5 -0
  92. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  93. package/packages/pi-agent-core/dist/agent.js +2 -0
  94. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  95. package/packages/pi-agent-core/dist/index.d.ts +1 -0
  96. package/packages/pi-agent-core/dist/index.d.ts.map +1 -1
  97. package/packages/pi-agent-core/dist/index.js +2 -0
  98. package/packages/pi-agent-core/dist/index.js.map +1 -1
  99. package/packages/pi-agent-core/dist/token-audit.d.ts +47 -0
  100. package/packages/pi-agent-core/dist/token-audit.d.ts.map +1 -0
  101. package/packages/pi-agent-core/dist/token-audit.js +221 -0
  102. package/packages/pi-agent-core/dist/token-audit.js.map +1 -0
  103. package/packages/pi-agent-core/dist/types.d.ts +9 -0
  104. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  105. package/packages/pi-agent-core/dist/types.js.map +1 -1
  106. package/packages/pi-agent-core/src/agent-loop.test.ts +128 -0
  107. package/packages/pi-agent-core/src/agent-loop.ts +4 -1
  108. package/packages/pi-agent-core/src/agent.ts +8 -0
  109. package/packages/pi-agent-core/src/index.ts +2 -0
  110. package/packages/pi-agent-core/src/token-audit.test.ts +189 -0
  111. package/packages/pi-agent-core/src/token-audit.ts +287 -0
  112. package/packages/pi-agent-core/src/types.ts +14 -0
  113. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  114. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
  115. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +18 -0
  117. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
  119. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/core/agent-session.js +41 -7
  121. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/extensions/loader.js +8 -0
  124. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -0
  126. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -6
  128. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +3 -3
  130. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +32 -1
  132. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js +2 -0
  135. package/packages/pi-coding-agent/dist/core/hooks-runner.test.js.map +1 -1
  136. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts +2 -0
  137. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.d.ts.map +1 -0
  138. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js +46 -0
  139. package/packages/pi-coding-agent/dist/core/sdk-tool-filter.test.js.map +1 -0
  140. package/packages/pi-coding-agent/dist/core/sdk.d.ts +10 -2
  141. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  142. package/packages/pi-coding-agent/dist/core/sdk.js +74 -2
  143. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  144. package/packages/pi-coding-agent/dist/core/skill-tool.test.js +22 -0
  145. package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -1
  146. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +6 -7
  147. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  148. package/packages/pi-coding-agent/dist/core/system-prompt.js +2 -3
  149. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  150. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
  151. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +25 -0
  152. package/packages/pi-coding-agent/src/core/agent-session.ts +45 -7
  153. package/packages/pi-coding-agent/src/core/extensions/loader.ts +10 -0
  154. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +3 -3
  155. package/packages/pi-coding-agent/src/core/extensions/runner.ts +5 -5
  156. package/packages/pi-coding-agent/src/core/extensions/types.ts +35 -1
  157. package/packages/pi-coding-agent/src/core/hooks-runner.test.ts +2 -0
  158. package/packages/pi-coding-agent/src/core/sdk-tool-filter.test.ts +60 -0
  159. package/packages/pi-coding-agent/src/core/sdk.ts +85 -3
  160. package/packages/pi-coding-agent/src/core/skill-tool.test.ts +28 -0
  161. package/packages/pi-coding-agent/src/core/system-prompt.ts +8 -10
  162. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  163. package/src/resources/extensions/gsd/auto/loop-deps.ts +2 -2
  164. package/src/resources/extensions/gsd/auto/phases.ts +251 -172
  165. package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
  166. package/src/resources/extensions/gsd/auto/run-unit.ts +25 -30
  167. package/src/resources/extensions/gsd/auto/session.ts +8 -0
  168. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +8 -34
  169. package/src/resources/extensions/gsd/auto-dispatch.ts +16 -0
  170. package/src/resources/extensions/gsd/auto-post-unit.ts +18 -4
  171. package/src/resources/extensions/gsd/auto-prompts.ts +95 -14
  172. package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
  173. package/src/resources/extensions/gsd/auto-start.ts +230 -9
  174. package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
  175. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
  176. package/src/resources/extensions/gsd/auto-worktree.ts +123 -0
  177. package/src/resources/extensions/gsd/auto.ts +87 -19
  178. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +49 -1
  179. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +50 -36
  180. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +16 -5
  181. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +10 -3
  182. package/src/resources/extensions/gsd/bootstrap/journal-tools.ts +8 -1
  183. package/src/resources/extensions/gsd/bootstrap/memory-tools.ts +10 -3
  184. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +9 -2
  185. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +283 -28
  186. package/src/resources/extensions/gsd/bootstrap/system-context.ts +90 -22
  187. package/src/resources/extensions/gsd/clean-root-preflight.ts +32 -7
  188. package/src/resources/extensions/gsd/commands-handlers.ts +34 -15
  189. package/src/resources/extensions/gsd/context-budget.ts +44 -2
  190. package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
  191. package/src/resources/extensions/gsd/db-base-schema.ts +4 -2
  192. package/src/resources/extensions/gsd/db-migration-steps.ts +8 -0
  193. package/src/resources/extensions/gsd/ecosystem/gsd-extension-api.ts +3 -0
  194. package/src/resources/extensions/gsd/git-service.ts +46 -8
  195. package/src/resources/extensions/gsd/gsd-db.ts +50 -13
  196. package/src/resources/extensions/gsd/guided-flow.ts +84 -39
  197. package/src/resources/extensions/gsd/memory-store.ts +77 -12
  198. package/src/resources/extensions/gsd/native-git-bridge.ts +39 -6
  199. package/src/resources/extensions/gsd/orphan-stash-audit.ts +117 -0
  200. package/src/resources/extensions/gsd/parallel-orchestrator.ts +13 -3
  201. package/src/resources/extensions/gsd/pre-execution-checks.ts +23 -0
  202. package/src/resources/extensions/gsd/prompt-loader.ts +27 -2
  203. package/src/resources/extensions/gsd/prompts/complete-milestone.md +16 -13
  204. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  205. package/src/resources/extensions/gsd/prompts/execute-task.md +4 -2
  206. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  207. package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
  208. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +2 -2
  209. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +417 -10
  210. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +56 -13
  211. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
  212. package/src/resources/extensions/gsd/tests/auto-wrapup-inflight-guard.test.ts +168 -6
  213. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +15 -6
  214. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +3 -1
  215. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +31 -0
  216. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +3 -2
  217. package/src/resources/extensions/gsd/tests/context-budget.test.ts +10 -1
  218. package/src/resources/extensions/gsd/tests/context-store.test.ts +7 -1
  219. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +5 -1
  220. package/src/resources/extensions/gsd/tests/dispatch-rule-coverage.test.ts +313 -0
  221. package/src/resources/extensions/gsd/tests/execute-task-rendering.test.ts +5 -2
  222. package/src/resources/extensions/gsd/tests/fast-forward-reused-milestone-branch.test.ts +219 -0
  223. package/src/resources/extensions/gsd/tests/finalize-survivor-branch.test.ts +132 -0
  224. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +54 -0
  225. package/src/resources/extensions/gsd/tests/isolation-none-branch-guard.test.ts +6 -3
  226. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +239 -1
  227. package/src/resources/extensions/gsd/tests/journal-query-tool.test.ts +32 -0
  228. package/src/resources/extensions/gsd/tests/knowledge.test.ts +47 -0
  229. package/src/resources/extensions/gsd/tests/memory-decay-factor.test.ts +90 -0
  230. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +1 -0
  231. package/src/resources/extensions/gsd/tests/milestone-merge-stash-restore.test.ts +242 -0
  232. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +34 -2
  233. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +3 -0
  234. package/src/resources/extensions/gsd/tests/orphan-merge-bootstrap.test.ts +133 -0
  235. package/src/resources/extensions/gsd/tests/orphan-stash-audit.test.ts +201 -0
  236. package/src/resources/extensions/gsd/tests/parallel-orchestrator-fast-forward.test.ts +113 -0
  237. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +45 -5
  238. package/src/resources/extensions/gsd/tests/prompt-duplication-cuts.test.ts +230 -0
  239. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +19 -0
  240. package/src/resources/extensions/gsd/tests/query-tools-db-open.test.ts +3 -3
  241. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +38 -17
  242. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +156 -0
  243. package/src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts +96 -0
  244. package/src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts +106 -0
  245. package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
  246. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
  247. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +38 -0
  248. package/src/resources/extensions/gsd/tests/state-corruption-2945.test.ts +1 -0
  249. package/src/resources/extensions/gsd/tests/system-context-memory.test.ts +112 -0
  250. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +7 -9
  251. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +291 -0
  252. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
  253. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
  254. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +5 -4
  255. package/src/resources/extensions/gsd/tests/workflow-protocol-excerpt.test.ts +99 -0
  256. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +3 -0
  257. package/src/resources/extensions/gsd/tests/worktree-journal-events.test.ts +1 -0
  258. package/src/resources/extensions/gsd/tests/worktree-path-injection.test.ts +22 -19
  259. package/src/resources/extensions/gsd/tests/worktree-project-root-degrade.test.ts +66 -0
  260. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +167 -4
  261. package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -0
  262. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +1 -1
  263. package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
  264. package/src/resources/extensions/gsd/workflow-protocol.ts +160 -0
  265. package/src/resources/extensions/gsd/worktree-resolver.ts +85 -19
  266. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +0 -97
  267. /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_buildManifest.js +0 -0
  268. /package/dist/web/standalone/.next/static/{3EDDd9ULaybSupPoA_vf- → O2WvnXROue6l7nAp_4dhC}/_ssgManifest.js +0 -0
@@ -0,0 +1,156 @@
1
+ // gsd-2 / V27 + V28 schema migration regression tests
2
+ //
3
+ // Same bug class as #4591 (schema-v21-sequence): a migration block can be
4
+ // added but the SCHEMA_VERSION constant left unchanged, causing fresh-install
5
+ // + upgrade paths to silently skip the column add. This file pins both V27
6
+ // (artifacts.content_hash) and V28 (memories.last_hit_at) at the schema and
7
+ // write-path level on fresh-install DBs.
8
+
9
+ import test from "node:test";
10
+ import assert from "node:assert/strict";
11
+ import * as fs from "node:fs";
12
+ import * as path from "node:path";
13
+ import * as os from "node:os";
14
+
15
+ import {
16
+ openDatabase,
17
+ closeDatabase,
18
+ _getAdapter,
19
+ insertArtifact,
20
+ insertMemoryRow,
21
+ incrementMemoryHitCount,
22
+ SCHEMA_VERSION,
23
+ } from "../gsd-db.ts";
24
+
25
+ function makeTmp(): string {
26
+ return fs.mkdtempSync(path.join(os.tmpdir(), "gsd-v27v28-"));
27
+ }
28
+
29
+ function cleanup(base: string): void {
30
+ try { closeDatabase(); } catch { /* noop */ }
31
+ try { fs.rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
32
+ }
33
+
34
+ test("SCHEMA_VERSION constant is at least 28 (V28 migration committed)", () => {
35
+ assert.ok(
36
+ SCHEMA_VERSION >= 28,
37
+ `SCHEMA_VERSION must be ≥ 28 after V28 migration; got ${SCHEMA_VERSION}`,
38
+ );
39
+ });
40
+
41
+ test("fresh-install DB has artifacts.content_hash column (V27)", () => {
42
+ const base = makeTmp();
43
+ const dbPath = path.join(base, "gsd.db");
44
+ try {
45
+ openDatabase(dbPath);
46
+ const db = _getAdapter()!;
47
+ const cols = db.prepare("PRAGMA table_info(artifacts)").all() as Array<Record<string, unknown>>;
48
+ const colNames = new Set(cols.map((c) => c["name"] as string));
49
+ assert.ok(
50
+ colNames.has("content_hash"),
51
+ "V27 must add content_hash column to artifacts on fresh install",
52
+ );
53
+ } finally {
54
+ cleanup(base);
55
+ }
56
+ });
57
+
58
+ test("fresh-install DB has memories.last_hit_at column (V28)", () => {
59
+ const base = makeTmp();
60
+ const dbPath = path.join(base, "gsd.db");
61
+ try {
62
+ openDatabase(dbPath);
63
+ const db = _getAdapter()!;
64
+ const cols = db.prepare("PRAGMA table_info(memories)").all() as Array<Record<string, unknown>>;
65
+ const colNames = new Set(cols.map((c) => c["name"] as string));
66
+ assert.ok(
67
+ colNames.has("last_hit_at"),
68
+ "V28 must add last_hit_at column to memories on fresh install",
69
+ );
70
+ } finally {
71
+ cleanup(base);
72
+ }
73
+ });
74
+
75
+ test("fresh-install DB stamps SCHEMA_VERSION (≥28) in schema_version table", () => {
76
+ const base = makeTmp();
77
+ const dbPath = path.join(base, "gsd.db");
78
+ try {
79
+ openDatabase(dbPath);
80
+ const db = _getAdapter()!;
81
+ const row = db.prepare("SELECT MAX(version) as v FROM schema_version").get() as Record<string, unknown> | undefined;
82
+ const max = (row?.["v"] as number) ?? 0;
83
+ assert.ok(max >= 28, `fresh install must record schema_version ≥ 28; got ${max}`);
84
+ } finally {
85
+ cleanup(base);
86
+ }
87
+ });
88
+
89
+ test("insertArtifact populates content_hash with SHA-256 of full_content (V27 write-path)", () => {
90
+ const base = makeTmp();
91
+ const dbPath = path.join(base, "gsd.db");
92
+ try {
93
+ openDatabase(dbPath);
94
+ insertArtifact({
95
+ path: "M001/PROJECT.md",
96
+ artifact_type: "PROJECT",
97
+ milestone_id: "M001",
98
+ slice_id: null,
99
+ task_id: null,
100
+ full_content: "hello world",
101
+ });
102
+
103
+ const db = _getAdapter()!;
104
+ const row = db
105
+ .prepare("SELECT content_hash FROM artifacts WHERE path = :p")
106
+ .get({ ":p": "M001/PROJECT.md" }) as Record<string, unknown> | undefined;
107
+ const hash = row?.["content_hash"] as string | null | undefined;
108
+
109
+ // SHA-256 of "hello world" hex-encoded:
110
+ const expected = "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9";
111
+ assert.equal(hash, expected, "content_hash must be SHA-256 hex of full_content");
112
+ } finally {
113
+ cleanup(base);
114
+ }
115
+ });
116
+
117
+ test("incrementMemoryHitCount sets last_hit_at alongside hit_count (V28 write-path)", () => {
118
+ const base = makeTmp();
119
+ const dbPath = path.join(base, "gsd.db");
120
+ try {
121
+ openDatabase(dbPath);
122
+
123
+ const created = "2026-01-01T00:00:00.000Z";
124
+ insertMemoryRow({
125
+ id: "MEM001",
126
+ category: "gotcha",
127
+ content: "test memory",
128
+ confidence: 0.9,
129
+ sourceUnitType: null,
130
+ sourceUnitId: null,
131
+ createdAt: created,
132
+ updatedAt: created,
133
+ scope: "project",
134
+ tags: [],
135
+ structuredFields: null,
136
+ });
137
+
138
+ // Before increment: last_hit_at should be NULL
139
+ const db = _getAdapter()!;
140
+ const before = db
141
+ .prepare("SELECT last_hit_at FROM memories WHERE id = :id")
142
+ .get({ ":id": "MEM001" }) as Record<string, unknown>;
143
+ assert.equal(before["last_hit_at"], null, "last_hit_at starts NULL on fresh insert");
144
+
145
+ const hitTime = "2026-02-01T00:00:00.000Z";
146
+ incrementMemoryHitCount("MEM001", hitTime);
147
+
148
+ const after = db
149
+ .prepare("SELECT hit_count, last_hit_at FROM memories WHERE id = :id")
150
+ .get({ ":id": "MEM001" }) as Record<string, unknown>;
151
+ assert.equal(after["hit_count"], 1, "hit_count increments");
152
+ assert.equal(after["last_hit_at"], hitTime, "last_hit_at set to provided timestamp");
153
+ } finally {
154
+ cleanup(base);
155
+ }
156
+ });
@@ -0,0 +1,96 @@
1
+ // GSD-2 + src/resources/extensions/gsd/tests/select-resumable-milestone.test.ts
2
+ // Regression: bootstrap must rederive `currentMilestoneId` from an unmerged
3
+ // completed milestone branch when in-memory state was lost across a process
4
+ // restart (#5538-followup).
5
+
6
+ import test from "node:test";
7
+ import assert from "node:assert/strict";
8
+
9
+ import { _selectResumableMilestone } from "../auto-start.js";
10
+
11
+ const ALL_COMPLETE = (_id: string) => true;
12
+ const NEVER_COMPLETE = (_id: string) => false;
13
+ const HAS_COMMITS = (_branch: string) => 1;
14
+ const NO_COMMITS = (_branch: string) => 0;
15
+
16
+ test("returns null when no branches exist", () => {
17
+ const result = _selectResumableMilestone([], new Set(), ALL_COMPLETE, HAS_COMMITS);
18
+ assert.equal(result, null);
19
+ });
20
+
21
+ test("returns null when every milestone branch is already merged", () => {
22
+ const branches = ["milestone/M001", "milestone/M002"];
23
+ const merged = new Set(branches);
24
+ const result = _selectResumableMilestone(branches, merged, ALL_COMPLETE, HAS_COMMITS);
25
+ assert.equal(result, null);
26
+ });
27
+
28
+ test("returns null when no candidate milestones are complete", () => {
29
+ const branches = ["milestone/M002"];
30
+ const result = _selectResumableMilestone(branches, new Set(), NEVER_COMPLETE, HAS_COMMITS);
31
+ assert.equal(result, null);
32
+ });
33
+
34
+ test("returns null when no branch has commits ahead", () => {
35
+ const branches = ["milestone/M002"];
36
+ const result = _selectResumableMilestone(branches, new Set(), ALL_COMPLETE, NO_COMMITS);
37
+ assert.equal(result, null);
38
+ });
39
+
40
+ test("returns the unmerged completed milestone (regression: M002 stranded after restart)", () => {
41
+ // Repro of test12345 state: M001 ✅ merged, M002 ✅ unmerged, M003 🔄.
42
+ // Bootstrap must seed currentMilestoneId = "M002" so the loop's transition
43
+ // guard fires when the next iteration sees mid="M003".
44
+ const branches = ["milestone/M002", "milestone/M003"];
45
+ const merged = new Set<string>(); // none merged from this list
46
+ const isComplete = (id: string) => id === "M002"; // M003 still in progress
47
+ const result = _selectResumableMilestone(branches, merged, isComplete, HAS_COMMITS);
48
+ assert.equal(result, "M002");
49
+ });
50
+
51
+ test("picks the lex-greatest milestone when multiple candidates exist", () => {
52
+ // M001, M002 both unmerged complete -> pick M002 (the most recent).
53
+ const branches = ["milestone/M001", "milestone/M002"];
54
+ const result = _selectResumableMilestone(
55
+ branches,
56
+ new Set(),
57
+ ALL_COMPLETE,
58
+ HAS_COMMITS,
59
+ );
60
+ assert.equal(result, "M002");
61
+ });
62
+
63
+ test("ignores branch names that don't follow the milestone/ prefix", () => {
64
+ const branches = ["feature/something", "milestone/M002", "fix/bug-1"];
65
+ const result = _selectResumableMilestone(
66
+ branches,
67
+ new Set(),
68
+ ALL_COMPLETE,
69
+ HAS_COMMITS,
70
+ );
71
+ assert.equal(result, "M002");
72
+ });
73
+
74
+ test("isComplete callback throwing skips that milestone but does not crash", () => {
75
+ const branches = ["milestone/M001", "milestone/M002"];
76
+ const isComplete = (id: string) => {
77
+ if (id === "M001") throw new Error("db unavailable for M001");
78
+ return true;
79
+ };
80
+ // Implementation choice: a thrown isComplete is propagated. Wrapping in
81
+ // try/catch happens at the production wrapper level (findUnmergedCompletedMilestone).
82
+ // Verify the helper itself surfaces the error so callers see real failures.
83
+ assert.throws(() => _selectResumableMilestone(branches, new Set(), isComplete, HAS_COMMITS));
84
+ });
85
+
86
+ test("commitsAhead callback throwing for one branch falls through to others", () => {
87
+ // Production wrapper: commits-ahead failures should not abort the search.
88
+ // The helper catches throws from commitsAhead per-branch and treats as 0.
89
+ const branches = ["milestone/M001", "milestone/M002"];
90
+ const commitsAhead = (branch: string) => {
91
+ if (branch === "milestone/M001") throw new Error("rev-walk failed");
92
+ return 5;
93
+ };
94
+ const result = _selectResumableMilestone(branches, new Set(), ALL_COMPLETE, commitsAhead);
95
+ assert.equal(result, "M002");
96
+ });
@@ -0,0 +1,106 @@
1
+ // GSD-2 + src/resources/extensions/gsd/tests/session-switch-abort-misclassification.test.ts
2
+ // Regression: session-transition aborts must not be classified as user cancellations.
3
+
4
+ import test from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ import { _handleSessionSwitchAgentEnd } from "../bootstrap/agent-end-recovery.js";
8
+ import type { ErrorContext } from "../auto/types.js";
9
+
10
+ test("user-abort message during session-switch is dropped (not propagated as cancellation)", () => {
11
+ // The Anthropic SDK emits this exact string when newSession() aborts an
12
+ // in-flight stream during a unit-to-unit session transition. Before the fix
13
+ // this was misclassified as a user cancellation and killed auto-mode with
14
+ // "Auto-mode stopped — Unit aborted: Claude Code process aborted by user".
15
+ let cancelledWith: ErrorContext | null = null;
16
+ const resolveCancelled = (ctx: ErrorContext) => {
17
+ cancelledWith = ctx;
18
+ return true;
19
+ };
20
+
21
+ _handleSessionSwitchAgentEnd(
22
+ { stopReason: "error", errorMessage: "Claude Code process aborted by user" },
23
+ resolveCancelled,
24
+ );
25
+ assert.equal(cancelledWith, null, "SDK user-abort during session-switch must not propagate cancellation");
26
+
27
+ _handleSessionSwitchAgentEnd(
28
+ { stopReason: "error", errorMessage: "Request aborted by user" },
29
+ resolveCancelled,
30
+ );
31
+ assert.equal(cancelledWith, null, "proxy user-abort during session-switch must not propagate cancellation");
32
+ });
33
+
34
+ test("genuine stopReason='aborted' with errorMessage during session-switch still propagates", () => {
35
+ // Regression guard for prior behavior: genuine aborts with diagnostic content
36
+ // continue to surface as cancellations so transient-pause recovery can run.
37
+ let cancelledWith: { message: string; category: string; isTransient?: boolean } | null = null;
38
+ const resolveCancelled = (ctx: ErrorContext) => {
39
+ cancelledWith = ctx;
40
+ return true;
41
+ };
42
+
43
+ _handleSessionSwitchAgentEnd(
44
+ {
45
+ stopReason: "aborted",
46
+ errorMessage: "stream torn down mid-flight",
47
+ content: [{ type: "text", text: "partial output" }],
48
+ },
49
+ resolveCancelled,
50
+ );
51
+
52
+ assert.deepEqual(cancelledWith, {
53
+ message: "stream torn down mid-flight",
54
+ category: "aborted",
55
+ isTransient: true,
56
+ });
57
+ });
58
+
59
+ test("empty-content aborted during session-switch is silently ignored", () => {
60
+ // Empty-content aborted is a non-fatal LLM stop; we must not pause/cancel.
61
+ let cancelledWith: unknown = null;
62
+ const resolveCancelled = (ctx: ErrorContext) => {
63
+ cancelledWith = ctx;
64
+ return true;
65
+ };
66
+
67
+ _handleSessionSwitchAgentEnd(
68
+ { stopReason: "aborted", content: [] },
69
+ resolveCancelled,
70
+ );
71
+
72
+ assert.equal(cancelledWith, null);
73
+ });
74
+
75
+ test("non-abort errors during session-switch are not propagated through this helper", () => {
76
+ // Real provider errors (rate-limit, network, unsupported-model) are handled
77
+ // by the post-switch retry pipeline — not by the in-flight switch handler.
78
+ let cancelledWith: unknown = null;
79
+ const resolveCancelled = (ctx: ErrorContext) => {
80
+ cancelledWith = ctx;
81
+ return true;
82
+ };
83
+
84
+ _handleSessionSwitchAgentEnd(
85
+ { stopReason: "error", errorMessage: "rate limit exceeded" },
86
+ resolveCancelled,
87
+ );
88
+
89
+ assert.equal(cancelledWith, null);
90
+ });
91
+
92
+ test("malformed lastMsg is rejected gracefully", () => {
93
+ let calls = 0;
94
+ const resolveCancelled = (_ctx: unknown) => {
95
+ calls += 1;
96
+ return true;
97
+ };
98
+
99
+ _handleSessionSwitchAgentEnd(undefined, resolveCancelled as never);
100
+ _handleSessionSwitchAgentEnd(null, resolveCancelled as never);
101
+ _handleSessionSwitchAgentEnd("not an object", resolveCancelled as never);
102
+ _handleSessionSwitchAgentEnd({}, resolveCancelled as never);
103
+ _handleSessionSwitchAgentEnd({ stopReason: "completed" }, resolveCancelled as never);
104
+
105
+ assert.equal(calls, 0, "malformed or non-abort lastMsg must not invoke cancellation");
106
+ });
@@ -101,3 +101,30 @@ test("registerSigtermHandler deregisters previous handler from all signals", ()
101
101
  // Clean up
102
102
  deregisterSigtermHandler(handler2);
103
103
  });
104
+
105
+ test("registered signal handler runs best-effort cleanup before exiting", () => {
106
+ let cleanupCalled = false;
107
+ let exitCode: number | string | null | undefined;
108
+ const originalExit = process.exit;
109
+ const handler = registerSigtermHandler(
110
+ "/tmp/test-signal-cleanup",
111
+ null,
112
+ () => {
113
+ cleanupCalled = true;
114
+ },
115
+ );
116
+
117
+ (process as any).exit = ((code?: number | string | null) => {
118
+ exitCode = code;
119
+ throw new Error("process.exit intercepted");
120
+ }) as never;
121
+
122
+ try {
123
+ assert.throws(() => handler(), /process\.exit intercepted/);
124
+ assert.equal(cleanupCalled, true);
125
+ assert.equal(exitCode, 0);
126
+ } finally {
127
+ (process as any).exit = originalExit;
128
+ deregisterSigtermHandler(handler);
129
+ }
130
+ });
@@ -15,10 +15,11 @@
15
15
  * (the fix) and verifies it does not crash.
16
16
  */
17
17
 
18
- import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
18
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
19
19
  import { join } from "node:path";
20
20
  import { tmpdir } from "node:os";
21
21
  import { recoverTimedOutUnit, type RecoveryContext } from "../auto-timeout-recovery.ts";
22
+ import { closeDatabase, insertMilestone, insertSlice, insertTask, openDatabase } from "../gsd-db.ts";
22
23
  import { test } from 'node:test';
23
24
  import assert from 'node:assert/strict';
24
25
 
@@ -39,6 +40,14 @@ function makeMockPi() {
39
40
  } as any;
40
41
  }
41
42
 
43
+ function makeRecordingPi() {
44
+ const messages: unknown[] = [];
45
+ return {
46
+ messages,
47
+ sendMessage: (message: unknown) => { messages.push(message); },
48
+ } as any;
49
+ }
50
+
42
51
  // ═══ #1855: empty RecoveryContext (basePath undefined) crashes ════════════════
43
52
 
44
53
  {
@@ -63,6 +72,45 @@ function makeMockPi() {
63
72
  assert.ok(crashed, "should crash when basePath is undefined (reproduces #1855)");
64
73
  }
65
74
 
75
+ // ═══ DB-complete execute-task recovery advances without steering ═════════════
76
+
77
+ {
78
+ console.log("\n=== execute-task timeout recovery trusts closed DB status ===");
79
+ const base = mkdtempSync(join(tmpdir(), "gsd-timeout-db-complete-"));
80
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
81
+
82
+ try {
83
+ openDatabase(join(base, ".gsd", "gsd.db"));
84
+ insertMilestone({ id: "M001", title: "Milestone", status: "active" });
85
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "in_progress" });
86
+ insertTask({ id: "T01", milestoneId: "M001", sliceId: "S01", title: "Task", status: "complete" });
87
+ writeFileSync(
88
+ join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-PLAN.md"),
89
+ "# S01\n\n## Tasks\n\n- [ ] **T01: Task** `est:10m`\n",
90
+ "utf-8",
91
+ );
92
+ writeFileSync(join(base, ".gsd", "STATE.md"), "## Next Action\nExecute T01 for S01: Task\n", "utf-8");
93
+
94
+ const ctx = makeMockCtx();
95
+ const pi = makeRecordingPi();
96
+ const result = await recoverTimedOutUnit(ctx, pi, "execute-task", "M001/S01/T01", "idle", {
97
+ basePath: base,
98
+ verbose: false,
99
+ currentUnitStartedAt: Date.now(),
100
+ unitRecoveryCount: new Map(),
101
+ });
102
+
103
+ assert.equal(result, "recovered", "db-complete task should recover immediately");
104
+ assert.equal(pi.messages.length, 0, "db-complete task should not send steering recovery");
105
+ const runtime = JSON.parse(readFileSync(join(base, ".gsd", "runtime", "units", "execute-task-M001-S01-T01.json"), "utf-8"));
106
+ assert.equal(runtime.phase, "finalized", "db-complete task should be finalized");
107
+ assert.equal(runtime.recovery.dbComplete, true, "runtime recovery should record DB completion");
108
+ } finally {
109
+ closeDatabase();
110
+ rmSync(base, { recursive: true, force: true });
111
+ }
112
+ }
113
+
66
114
  // ═══ #1855: valid RecoveryContext does not crash ═════════════════════════════
67
115
 
68
116
  {
@@ -4,6 +4,7 @@ import { readFileSync } from "node:fs";
4
4
  import { resolve } from "node:path";
5
5
 
6
6
  import { _withDetachedAutoKeepaliveForTest } from "../auto.ts";
7
+ import { _scheduleAutoStartAfterIdleForTest } from "../guided-flow.ts";
7
8
 
8
9
  const gsdDir = resolve(import.meta.dirname, "..");
9
10
 
@@ -202,3 +203,40 @@ test("detached auto-start preserves milestone lock across pause/stop cleanup (#3
202
203
  "AutoSession should track the detached milestone lock explicitly",
203
204
  );
204
205
  });
206
+
207
+ test("discussion auto-start waits for the current command context to become idle", async () => {
208
+ let releaseIdle!: () => void;
209
+ const idle = new Promise<void>((resolveIdle) => {
210
+ releaseIdle = resolveIdle;
211
+ });
212
+ const launches: unknown[][] = [];
213
+ const ctx = {
214
+ waitForIdle: () => idle,
215
+ ui: {
216
+ notify: () => {},
217
+ },
218
+ } as any;
219
+
220
+ _scheduleAutoStartAfterIdleForTest(
221
+ ctx,
222
+ {} as any,
223
+ "/tmp/gsd-auto-start-idle-test",
224
+ false,
225
+ { step: true },
226
+ (...args: unknown[]) => {
227
+ launches.push(args);
228
+ },
229
+ );
230
+
231
+ await Promise.resolve();
232
+ assert.equal(launches.length, 0, "auto-start must not launch before waitForIdle resolves");
233
+
234
+ releaseIdle();
235
+ await Promise.resolve();
236
+ assert.equal(launches.length, 0, "auto-start should defer launch to the next timer turn");
237
+
238
+ await new Promise((resolveTimer) => setTimeout(resolveTimer, 0));
239
+ assert.equal(launches.length, 1);
240
+ assert.equal(launches[0][2], "/tmp/gsd-auto-start-idle-test");
241
+ assert.deepEqual(launches[0][4], { step: true });
242
+ });
@@ -291,6 +291,7 @@ describe("#2945 Bug 3: mergeAndExit must teardown worktree after successful merg
291
291
  getAutoWorktreePath: () => null,
292
292
  autoCommitCurrentBranch: () => {},
293
293
  getCurrentBranch: () => "main",
294
+ checkoutBranch: () => {},
294
295
  autoWorktreeBranch: () => "gsd/M001",
295
296
  resolveMilestoneFile: () => "/mock/roadmap.md",
296
297
  readFileSync: () => "# Roadmap content",
@@ -0,0 +1,112 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: System context memory gating regression tests.
3
+
4
+ import test from "node:test";
5
+ import assert from "node:assert/strict";
6
+
7
+ import { buildContextMessage, isLowEntropyResumePrompt, loadMemoryBlock } from "../bootstrap/system-context.ts";
8
+ import { closeDatabase, openDatabase } from "../gsd-db.ts";
9
+ import { createMemory } from "../memory-store.ts";
10
+
11
+ test("buildContextMessage marks hidden guided context when memory is supplied", () => {
12
+ const message = buildContextMessage({
13
+ memoryBlock: "\n\n[MEMORY]\n\n- keep this",
14
+ injection: "[GSD Guided Execute Context]\nUse the task plan.",
15
+ forensicsInjection: null,
16
+ });
17
+
18
+ assert.ok(message, "expected hidden context message");
19
+ assert.equal(message.customType, "gsd-guided-context");
20
+ assert.equal(message.display, false);
21
+ assert.match(message.content, /\[GSD Context Metadata\]\n- Memory supplied: yes/);
22
+ assert.ok(
23
+ message.content.indexOf("Memory supplied: yes") < message.content.indexOf("[GSD Guided Execute Context]"),
24
+ "memory marker should appear before guided context",
25
+ );
26
+ });
27
+
28
+ test("buildContextMessage caps hidden context by default", () => {
29
+ const original = process.env.PI_GSD_CONTEXT_MAX_CHARS;
30
+ delete process.env.PI_GSD_CONTEXT_MAX_CHARS;
31
+ try {
32
+ const message = buildContextMessage({
33
+ memoryBlock: "",
34
+ injection: `[GSD Guided Execute Context]\n${"large context\n".repeat(500)}`,
35
+ forensicsInjection: null,
36
+ });
37
+
38
+ assert.ok(message, "expected hidden context message");
39
+ assert.equal(message.customType, "gsd-guided-context");
40
+ assert.ok(message.content.length <= 4000);
41
+ assert.match(message.content, /\[GSD Context Truncated\]/);
42
+ } finally {
43
+ if (original === undefined) delete process.env.PI_GSD_CONTEXT_MAX_CHARS;
44
+ else process.env.PI_GSD_CONTEXT_MAX_CHARS = original;
45
+ }
46
+ });
47
+
48
+ test("buildContextMessage supports explicit context cap override", () => {
49
+ const original = process.env.PI_GSD_CONTEXT_MAX_CHARS;
50
+ process.env.PI_GSD_CONTEXT_MAX_CHARS = "1200";
51
+ try {
52
+ const message = buildContextMessage({
53
+ memoryBlock: "",
54
+ injection: `[GSD Guided Execute Context]\n${"large context\n".repeat(200)}`,
55
+ forensicsInjection: null,
56
+ });
57
+
58
+ assert.ok(message, "expected hidden context message");
59
+ assert.equal(message.customType, "gsd-guided-context");
60
+ assert.ok(message.content.length <= 1200);
61
+ assert.match(message.content, /\[GSD Context Truncated\]/);
62
+ } finally {
63
+ if (original === undefined) delete process.env.PI_GSD_CONTEXT_MAX_CHARS;
64
+ else process.env.PI_GSD_CONTEXT_MAX_CHARS = original;
65
+ }
66
+ });
67
+
68
+ test("buildContextMessage does not add memory marker when only guided context is supplied", () => {
69
+ const message = buildContextMessage({
70
+ memoryBlock: "",
71
+ injection: "[GSD Guided Execute Context]\nUse the task plan.",
72
+ forensicsInjection: null,
73
+ });
74
+
75
+ assert.ok(message, "expected guided context message");
76
+ assert.equal(message.customType, "gsd-guided-context");
77
+ assert.doesNotMatch(message.content, /Memory supplied: yes/);
78
+ });
79
+
80
+ test("loadMemoryBlock keeps critical memories while gating prompt-relevant query hits", async () => {
81
+ closeDatabase();
82
+ assert.equal(openDatabase(":memory:"), true);
83
+ try {
84
+ createMemory({
85
+ category: "gotcha",
86
+ content: "Always preserve critical resume safety context.",
87
+ confidence: 0.95,
88
+ });
89
+ createMemory({
90
+ category: "preference",
91
+ content: "React dashboard preference should only appear for a React prompt query.",
92
+ confidence: 0.95,
93
+ });
94
+
95
+ const withPromptRelevant = await loadMemoryBlock("React dashboard", { includePromptRelevant: true });
96
+ assert.match(withPromptRelevant, /critical resume safety context/);
97
+ assert.match(withPromptRelevant, /React dashboard preference/);
98
+
99
+ const withoutPromptRelevant = await loadMemoryBlock("React dashboard", { includePromptRelevant: false });
100
+ assert.match(withoutPromptRelevant, /critical resume safety context/);
101
+ assert.doesNotMatch(withoutPromptRelevant, /React dashboard preference/);
102
+ } finally {
103
+ closeDatabase();
104
+ }
105
+ });
106
+
107
+ test("isLowEntropyResumePrompt identifies bare resume prompts only", () => {
108
+ assert.equal(isLowEntropyResumePrompt("continue"), true);
109
+ assert.equal(isLowEntropyResumePrompt("Go ahead."), true);
110
+ assert.equal(isLowEntropyResumePrompt("run the tests"), false);
111
+ assert.equal(isLowEntropyResumePrompt("/gsd auto"), false);
112
+ });
@@ -1,9 +1,5 @@
1
- // GSD bootstrap + system-context-message-routing.test — regression coverage
2
- // for #5019. `memoryBlock` is FTS-queried against the user prompt and changes
3
- // per call; embedding it in the cached system prefix invalidates Anthropic
4
- // prompt-cache hits on every request. The fix routes memory through the
5
- // existing context-message channel (volatile user-message suffix) and combines
6
- // it with any active guided-execute or forensics injection.
1
+ // Project/App: GSD-2
2
+ // File Purpose: Regression coverage for volatile system-context message routing.
7
3
 
8
4
  import { describe, test } from "node:test";
9
5
  import assert from "node:assert/strict";
@@ -11,6 +7,8 @@ import assert from "node:assert/strict";
11
7
  import { buildContextMessage } from "../bootstrap/system-context.ts";
12
8
 
13
9
  describe("buildContextMessage (#5019 — memory routing)", () => {
10
+ const markedMemory = "[GSD Context Metadata]\n- Memory supplied: yes\n\n[MEMORY]\nrule one";
11
+
14
12
  test("returns null when nothing to inject", () => {
15
13
  const result = buildContextMessage({
16
14
  memoryBlock: "",
@@ -37,7 +35,7 @@ describe("buildContextMessage (#5019 — memory routing)", () => {
37
35
  });
38
36
  assert.ok(result, "expected a context message");
39
37
  assert.equal(result.customType, "gsd-memory");
40
- assert.equal(result.content, "[MEMORY]\nrule one\nrule two");
38
+ assert.equal(result.content, "[GSD Context Metadata]\n- Memory supplied: yes\n\n[MEMORY]\nrule one\nrule two");
41
39
  assert.equal(result.display, false);
42
40
  });
43
41
 
@@ -71,7 +69,7 @@ describe("buildContextMessage (#5019 — memory routing)", () => {
71
69
  });
72
70
  assert.ok(result);
73
71
  assert.equal(result.customType, "gsd-guided-context");
74
- assert.equal(result.content, "[MEMORY]\nrule one\n\n[GUIDED]\nexecute T01");
72
+ assert.equal(result.content, `${markedMemory}\n\n[GUIDED]\nexecute T01`);
75
73
  });
76
74
 
77
75
  test("memory + forensics: memory prepended, customType is gsd-forensics", () => {
@@ -82,7 +80,7 @@ describe("buildContextMessage (#5019 — memory routing)", () => {
82
80
  });
83
81
  assert.ok(result);
84
82
  assert.equal(result.customType, "gsd-forensics");
85
- assert.equal(result.content, "[MEMORY]\nrule one\n\n[FORENSICS]\ninvestigation context");
83
+ assert.equal(result.content, `${markedMemory}\n\n[FORENSICS]\ninvestigation context`);
86
84
  });
87
85
 
88
86
  test("guided takes precedence over forensics when both are somehow present", () => {