gsd-pi 2.78.1-dev.e9d88a536 → 2.78.1-dev.eccf86e27

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 (212) hide show
  1. package/README.md +5 -7
  2. package/dist/help-text.js +1 -1
  3. package/dist/resource-loader.js +6 -1
  4. package/dist/resources/.managed-resources-content-hash +1 -1
  5. package/dist/resources/extensions/gsd/auto/detect-stuck.js +41 -5
  6. package/dist/resources/extensions/gsd/auto/loop.js +235 -36
  7. package/dist/resources/extensions/gsd/auto/phases.js +14 -7
  8. package/dist/resources/extensions/gsd/auto/session.js +36 -0
  9. package/dist/resources/extensions/gsd/auto-dispatch.js +49 -4
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +26 -12
  11. package/dist/resources/extensions/gsd/auto-worktree.js +185 -201
  12. package/dist/resources/extensions/gsd/auto.js +139 -49
  13. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +1 -1
  14. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +26 -20
  15. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +67 -55
  16. package/dist/resources/extensions/gsd/crash-recovery.js +160 -47
  17. package/dist/resources/extensions/gsd/db/auto-workers.js +227 -0
  18. package/dist/resources/extensions/gsd/db/command-queue.js +105 -0
  19. package/dist/resources/extensions/gsd/db/milestone-leases.js +210 -0
  20. package/dist/resources/extensions/gsd/db/runtime-kv.js +91 -0
  21. package/dist/resources/extensions/gsd/db/unit-dispatches.js +322 -0
  22. package/dist/resources/extensions/gsd/db-writer.js +96 -16
  23. package/dist/resources/extensions/gsd/delegation-policy.js +155 -0
  24. package/dist/resources/extensions/gsd/docs/COORDINATION.md +42 -0
  25. package/dist/resources/extensions/gsd/doctor-proactive.js +4 -0
  26. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +22 -6
  27. package/dist/resources/extensions/gsd/doctor.js +12 -2
  28. package/dist/resources/extensions/gsd/gsd-db.js +355 -3
  29. package/dist/resources/extensions/gsd/guided-flow-queue.js +1 -1
  30. package/dist/resources/extensions/gsd/guided-flow.js +116 -26
  31. package/dist/resources/extensions/gsd/interrupted-session.js +18 -15
  32. package/dist/resources/extensions/gsd/metrics.js +287 -1
  33. package/dist/resources/extensions/gsd/paths.js +79 -8
  34. package/dist/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  35. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -3
  36. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  37. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  38. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  39. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  40. package/dist/resources/extensions/gsd/state.js +21 -6
  41. package/dist/resources/extensions/gsd/templates/project.md +10 -0
  42. package/dist/resources/extensions/gsd/workflow-mcp.js +2 -2
  43. package/dist/resources/extensions/gsd/workspace.js +59 -0
  44. package/dist/resources/extensions/gsd/worktree-resolver.js +79 -2
  45. package/dist/resources/extensions/gsd/write-intercept.js +3 -3
  46. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  47. package/dist/web/standalone/.next/BUILD_ID +1 -1
  48. package/dist/web/standalone/.next/app-path-routes-manifest.json +14 -14
  49. package/dist/web/standalone/.next/build-manifest.json +2 -2
  50. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  51. package/dist/web/standalone/.next/required-server-files.json +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.html +1 -1
  69. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app-paths-manifest.json +14 -14
  76. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  78. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  79. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  80. package/dist/web/standalone/server.js +1 -1
  81. package/package.json +1 -1
  82. package/packages/mcp-server/README.md +2 -11
  83. package/packages/mcp-server/dist/remote-questions.d.ts +27 -0
  84. package/packages/mcp-server/dist/remote-questions.d.ts.map +1 -1
  85. package/packages/mcp-server/dist/remote-questions.js +28 -0
  86. package/packages/mcp-server/dist/remote-questions.js.map +1 -1
  87. package/packages/mcp-server/dist/server.d.ts +28 -0
  88. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  89. package/packages/mcp-server/dist/server.js +94 -4
  90. package/packages/mcp-server/dist/server.js.map +1 -1
  91. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  92. package/packages/mcp-server/src/mcp-server.test.ts +226 -0
  93. package/packages/mcp-server/src/remote-questions.test.ts +103 -0
  94. package/packages/mcp-server/src/remote-questions.ts +35 -0
  95. package/packages/mcp-server/src/server.ts +129 -6
  96. package/packages/mcp-server/src/workflow-tools.ts +1 -1
  97. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  98. package/src/resources/extensions/gsd/auto/detect-stuck.ts +37 -5
  99. package/src/resources/extensions/gsd/auto/loop.ts +263 -41
  100. package/src/resources/extensions/gsd/auto/phases.ts +15 -7
  101. package/src/resources/extensions/gsd/auto/session.ts +40 -0
  102. package/src/resources/extensions/gsd/auto-dispatch.ts +63 -4
  103. package/src/resources/extensions/gsd/auto-post-unit.ts +27 -12
  104. package/src/resources/extensions/gsd/auto-worktree.ts +218 -225
  105. package/src/resources/extensions/gsd/auto.ts +166 -43
  106. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +1 -1
  107. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +26 -21
  108. package/src/resources/extensions/gsd/bootstrap/tests/write-gate-basepath.test.ts +103 -0
  109. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +80 -55
  110. package/src/resources/extensions/gsd/crash-recovery.ts +177 -43
  111. package/src/resources/extensions/gsd/db/auto-workers.ts +273 -0
  112. package/src/resources/extensions/gsd/db/command-queue.ts +149 -0
  113. package/src/resources/extensions/gsd/db/milestone-leases.ts +274 -0
  114. package/src/resources/extensions/gsd/db/runtime-kv.ts +127 -0
  115. package/src/resources/extensions/gsd/db/unit-dispatches.ts +446 -0
  116. package/src/resources/extensions/gsd/db-writer.ts +113 -17
  117. package/src/resources/extensions/gsd/delegation-policy.ts +197 -0
  118. package/src/resources/extensions/gsd/docs/COORDINATION.md +42 -0
  119. package/src/resources/extensions/gsd/doctor-proactive.ts +4 -0
  120. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +24 -6
  121. package/src/resources/extensions/gsd/doctor.ts +10 -2
  122. package/src/resources/extensions/gsd/gsd-db.ts +354 -3
  123. package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -1
  124. package/src/resources/extensions/gsd/guided-flow.ts +152 -26
  125. package/src/resources/extensions/gsd/interrupted-session.ts +19 -12
  126. package/src/resources/extensions/gsd/metrics.ts +321 -1
  127. package/src/resources/extensions/gsd/paths.ts +67 -8
  128. package/src/resources/extensions/gsd/prompts/complete-slice.md +4 -4
  129. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -3
  130. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +8 -1
  131. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +22 -7
  132. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +6 -2
  133. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -1
  134. package/src/resources/extensions/gsd/state.ts +44 -6
  135. package/src/resources/extensions/gsd/templates/project.md +10 -0
  136. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +14 -14
  137. package/src/resources/extensions/gsd/tests/auto-loop-no-copy-artifacts.test.ts +72 -0
  138. package/src/resources/extensions/gsd/tests/auto-loop-symlink-worktree.test.ts +190 -0
  139. package/src/resources/extensions/gsd/tests/auto-session-scope.test.ts +331 -0
  140. package/src/resources/extensions/gsd/tests/auto-workers.test.ts +105 -0
  141. package/src/resources/extensions/gsd/tests/auto-worktree-registry.test.ts +176 -0
  142. package/src/resources/extensions/gsd/tests/command-queue.test.ts +141 -0
  143. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +203 -0
  144. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +169 -59
  145. package/src/resources/extensions/gsd/tests/db-writer-path-containment.test.ts +152 -0
  146. package/src/resources/extensions/gsd/tests/db-writer-root-artifact.test.ts +221 -0
  147. package/src/resources/extensions/gsd/tests/db-writer-scope.test.ts +230 -0
  148. package/src/resources/extensions/gsd/tests/delegation-policy.test.ts +151 -0
  149. package/src/resources/extensions/gsd/tests/detect-stuck-respects-retry.test.ts +173 -0
  150. package/src/resources/extensions/gsd/tests/dispatch-backgroundable-annotation.test.ts +55 -0
  151. package/src/resources/extensions/gsd/tests/draft-promotion.test.ts +3 -23
  152. package/src/resources/extensions/gsd/tests/gate-1b-orphan-discrimination.test.ts +193 -0
  153. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound-corrections.test.ts +246 -0
  154. package/src/resources/extensions/gsd/tests/gate-1b-recovery-bound.test.ts +218 -0
  155. package/src/resources/extensions/gsd/tests/gsd-db-failed-open-restore.test.ts +117 -0
  156. package/src/resources/extensions/gsd/tests/gsd-db-workspace-scope.test.ts +226 -0
  157. package/src/resources/extensions/gsd/tests/gsd-root-canonical.test.ts +66 -0
  158. package/src/resources/extensions/gsd/tests/gsd-root-home-guard.test.ts +68 -5
  159. package/src/resources/extensions/gsd/tests/guided-flow-prompt-consolidation.test.ts +4 -4
  160. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +22 -12
  161. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +24 -10
  162. package/src/resources/extensions/gsd/tests/integration/doctor-runtime.test.ts +35 -23
  163. package/src/resources/extensions/gsd/tests/integration/workspace-collapse-integration.test.ts +369 -0
  164. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +72 -25
  165. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +72 -25
  166. package/src/resources/extensions/gsd/tests/memory-pressure-stuck-state.test.ts +9 -6
  167. package/src/resources/extensions/gsd/tests/metrics-atomic-merge.test.ts +222 -0
  168. package/src/resources/extensions/gsd/tests/metrics-lock-hardening.test.ts +400 -0
  169. package/src/resources/extensions/gsd/tests/metrics-lock-not-acquired.test.ts +141 -0
  170. package/src/resources/extensions/gsd/tests/metrics-lock-retry-sleep.test.ts +287 -0
  171. package/src/resources/extensions/gsd/tests/metrics-prune-cache-invalidation.test.ts +149 -0
  172. package/src/resources/extensions/gsd/tests/metrics-scope.test.ts +378 -0
  173. package/src/resources/extensions/gsd/tests/milestone-leases.test.ts +152 -0
  174. package/src/resources/extensions/gsd/tests/originalbase-path-comparison.test.ts +329 -0
  175. package/src/resources/extensions/gsd/tests/parallel-milestone-isolation.test.ts +106 -0
  176. package/src/resources/extensions/gsd/tests/path-cache-decoupled.test.ts +209 -0
  177. package/src/resources/extensions/gsd/tests/path-normalization-unified.test.ts +175 -0
  178. package/src/resources/extensions/gsd/tests/paths-cache.test.ts +170 -0
  179. package/src/resources/extensions/gsd/tests/paused-session-via-db.test.ts +119 -0
  180. package/src/resources/extensions/gsd/tests/pending-autostart-scope.test.ts +120 -0
  181. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +58 -0
  182. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +3 -17
  183. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +150 -7
  184. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +138 -16
  185. package/src/resources/extensions/gsd/tests/resume-missing-worktree-warning.test.ts +209 -0
  186. package/src/resources/extensions/gsd/tests/runtime-kv.test.ts +120 -0
  187. package/src/resources/extensions/gsd/tests/skipped-validation-completion.test.ts +133 -28
  188. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +17 -0
  189. package/src/resources/extensions/gsd/tests/stuck-state-via-db.test.ts +134 -0
  190. package/src/resources/extensions/gsd/tests/sync-layer-scope.test.ts +434 -0
  191. package/src/resources/extensions/gsd/tests/teardown-chdir-failure-clears-registry.test.ts +162 -0
  192. package/src/resources/extensions/gsd/tests/teardown-cleanup-parity.test.ts +98 -0
  193. package/src/resources/extensions/gsd/tests/teardown-failure-clears-registry.test.ts +186 -0
  194. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +1 -1
  195. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +247 -0
  196. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +41 -1
  197. package/src/resources/extensions/gsd/tests/validator-scope-parity.test.ts +239 -0
  198. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +2 -2
  199. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +9 -15
  200. package/src/resources/extensions/gsd/tests/workspace.test.ts +196 -0
  201. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +35 -35
  202. package/src/resources/extensions/gsd/tests/write-gate.test.ts +94 -71
  203. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +1 -1
  204. package/src/resources/extensions/gsd/workflow-mcp.ts +2 -2
  205. package/src/resources/extensions/gsd/workspace.ts +95 -0
  206. package/src/resources/extensions/gsd/worktree-resolver.ts +78 -2
  207. package/src/resources/extensions/gsd/write-intercept.ts +3 -3
  208. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +0 -213
  209. package/src/resources/extensions/gsd/tests/auto-stale-lock-self-kill.test.ts +0 -87
  210. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +0 -159
  211. /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_buildManifest.js +0 -0
  212. /package/dist/web/standalone/.next/static/{oZGTPvJBQX_IDKKnuV8Bt → Y5UeGFkXTYM9WIQOWHkot}/_ssgManifest.js +0 -0
@@ -0,0 +1,120 @@
1
+ // gsd-2 + runtime_kv non-correctness-critical key-value storage tests (Phase C)
2
+
3
+ import test from "node:test";
4
+ import assert from "node:assert/strict";
5
+ import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
6
+ import { join } from "node:path";
7
+ import { tmpdir } from "node:os";
8
+
9
+ import { openDatabase, closeDatabase, _getAdapter } from "../gsd-db.ts";
10
+ import {
11
+ setRuntimeKv,
12
+ getRuntimeKv,
13
+ deleteRuntimeKv,
14
+ listRuntimeKv,
15
+ } from "../db/runtime-kv.ts";
16
+
17
+ function makeBase(): string {
18
+ const base = mkdtempSync(join(tmpdir(), "gsd-runtime-kv-"));
19
+ mkdirSync(join(base, ".gsd"), { recursive: true });
20
+ return base;
21
+ }
22
+
23
+ function cleanup(base: string): void {
24
+ try { closeDatabase(); } catch { /* noop */ }
25
+ try { rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
26
+ }
27
+
28
+ test("set + get round-trip preserves the value", (t) => {
29
+ const base = makeBase();
30
+ t.after(() => cleanup(base));
31
+ openDatabase(join(base, ".gsd", "gsd.db"));
32
+
33
+ setRuntimeKv("global", "", "ui_cursor", { row: 5, col: 10 });
34
+ const got = getRuntimeKv<{ row: number; col: number }>("global", "", "ui_cursor");
35
+ assert.deepEqual(got, { row: 5, col: 10 });
36
+ });
37
+
38
+ test("get returns null for missing keys", (t) => {
39
+ const base = makeBase();
40
+ t.after(() => cleanup(base));
41
+ openDatabase(join(base, ".gsd", "gsd.db"));
42
+
43
+ assert.equal(getRuntimeKv("global", "", "missing"), null);
44
+ });
45
+
46
+ test("set on existing key updates the value (idempotent upsert)", (t) => {
47
+ const base = makeBase();
48
+ t.after(() => cleanup(base));
49
+ openDatabase(join(base, ".gsd", "gsd.db"));
50
+
51
+ setRuntimeKv("worker", "w1", "counter", 1);
52
+ setRuntimeKv("worker", "w1", "counter", 42);
53
+ assert.equal(getRuntimeKv("worker", "w1", "counter"), 42);
54
+ });
55
+
56
+ test("scope partitioning: same key under different scopes is independent", (t) => {
57
+ const base = makeBase();
58
+ t.after(() => cleanup(base));
59
+ openDatabase(join(base, ".gsd", "gsd.db"));
60
+
61
+ setRuntimeKv("global", "", "k", "global-value");
62
+ setRuntimeKv("worker", "w1", "k", "worker-value");
63
+ setRuntimeKv("milestone", "M001", "k", "milestone-value");
64
+
65
+ assert.equal(getRuntimeKv("global", "", "k"), "global-value");
66
+ assert.equal(getRuntimeKv("worker", "w1", "k"), "worker-value");
67
+ assert.equal(getRuntimeKv("milestone", "M001", "k"), "milestone-value");
68
+ });
69
+
70
+ test("scope_id partitioning: same scope+key under different scope_ids is independent", (t) => {
71
+ const base = makeBase();
72
+ t.after(() => cleanup(base));
73
+ openDatabase(join(base, ".gsd", "gsd.db"));
74
+
75
+ setRuntimeKv("worker", "w1", "k", "v1");
76
+ setRuntimeKv("worker", "w2", "k", "v2");
77
+ assert.equal(getRuntimeKv("worker", "w1", "k"), "v1");
78
+ assert.equal(getRuntimeKv("worker", "w2", "k"), "v2");
79
+ });
80
+
81
+ test("delete removes the row; subsequent get returns null", (t) => {
82
+ const base = makeBase();
83
+ t.after(() => cleanup(base));
84
+ openDatabase(join(base, ".gsd", "gsd.db"));
85
+
86
+ setRuntimeKv("worker", "w1", "k", "value");
87
+ deleteRuntimeKv("worker", "w1", "k");
88
+ assert.equal(getRuntimeKv("worker", "w1", "k"), null);
89
+ });
90
+
91
+ test("list returns all rows for a scope+scope_id, ordered by key", (t) => {
92
+ const base = makeBase();
93
+ t.after(() => cleanup(base));
94
+ openDatabase(join(base, ".gsd", "gsd.db"));
95
+
96
+ setRuntimeKv("milestone", "M001", "alpha", 1);
97
+ setRuntimeKv("milestone", "M001", "gamma", 3);
98
+ setRuntimeKv("milestone", "M001", "beta", 2);
99
+ setRuntimeKv("milestone", "M002", "ignored", "different-scope");
100
+
101
+ const rows = listRuntimeKv("milestone", "M001");
102
+ assert.equal(rows.length, 3);
103
+ assert.deepEqual(rows.map(r => r.key), ["alpha", "beta", "gamma"]);
104
+ });
105
+
106
+ test("malformed JSON in storage returns null without throwing", (t) => {
107
+ const base = makeBase();
108
+ t.after(() => cleanup(base));
109
+ openDatabase(join(base, ".gsd", "gsd.db"));
110
+
111
+ // Inject a malformed value directly (bypassing setRuntimeKv's JSON.stringify).
112
+ setRuntimeKv("global", "", "k", "valid");
113
+ // Then poison the row via raw SQL.
114
+ const db = _getAdapter()!;
115
+ db.prepare(
116
+ `UPDATE runtime_kv SET value_json = '{not json' WHERE scope = 'global' AND scope_id = '' AND key = 'k'`,
117
+ ).run();
118
+
119
+ assert.equal(getRuntimeKv("global", "", "k"), null);
120
+ });
@@ -1,39 +1,144 @@
1
1
  /**
2
2
  * Regression test for #3698 — allow milestone completion when validation
3
- * was skipped by preference
4
- *
5
- * When validation is skipped due to user preference (e.g. budget profile),
6
- * auto-dispatch should recognize the "skipped by preference" pattern and
7
- * allow completion instead of treating it as a missing validation.
3
+ * was skipped by preference.
8
4
  */
9
5
 
10
- import { describe, test } from 'node:test';
11
- import assert from 'node:assert/strict';
12
- import { readFileSync } from 'node:fs';
13
- import { fileURLToPath } from 'node:url';
14
- import { dirname, join } from 'node:path';
6
+ import test from "node:test";
7
+ import assert from "node:assert/strict";
8
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { tmpdir } from "node:os";
15
11
 
16
- const __filename = fileURLToPath(import.meta.url);
17
- const __dirname = dirname(__filename);
12
+ import { DISPATCH_RULES, type DispatchContext } from "../auto-dispatch.ts";
13
+ import {
14
+ closeDatabase,
15
+ insertMilestone,
16
+ insertSlice,
17
+ openDatabase,
18
+ upsertMilestonePlanning,
19
+ } from "../gsd-db.ts";
20
+ import { invalidateAllCaches } from "../cache.ts";
21
+ import type { GSDState } from "../types.ts";
18
22
 
19
- const autoDispatchSrc = readFileSync(
20
- join(__dirname, '..', 'auto-dispatch.ts'),
21
- 'utf-8',
22
- );
23
+ const COMPLETE_RULE = "completing-milestone → complete-milestone";
23
24
 
24
- describe('skipped validation completion (#3698)', () => {
25
- test('skippedByPreference regex detection exists', () => {
26
- assert.match(autoDispatchSrc, /skippedByPreference/,
27
- 'skippedByPreference variable should exist in auto-dispatch.ts');
28
- });
25
+ function makeBase(): string {
26
+ const base = mkdtempSync(join(tmpdir(), "gsd-skipped-validation-"));
27
+ mkdirSync(join(base, ".gsd", "milestones", "M001", "slices", "S01", "tasks"), { recursive: true });
28
+ writeFileSync(join(base, "app.js"), "export const shipped = true;\n");
29
+ return base;
30
+ }
29
31
 
30
- test('regex matches skip-by-preference patterns', () => {
31
- assert.match(autoDispatchSrc, /skip\(\?:ped\)\?\[\\s\\-\]\+\(\?:by\|per\|due to\)/,
32
- 'should have regex matching "skipped by/per/due to" patterns');
33
- });
32
+ function cleanup(base: string): void {
33
+ try { closeDatabase(); } catch { /* noop */ }
34
+ invalidateAllCaches();
35
+ rmSync(base, { recursive: true, force: true });
36
+ }
34
37
 
35
- test('skippedByPreference feeds into operational check', () => {
36
- assert.match(autoDispatchSrc, /hasOperationalCheck\s*=\s*skippedByPreference/,
37
- 'skippedByPreference should be part of hasOperationalCheck');
38
+ function seedMilestone(base: string): void {
39
+ openDatabase(join(base, ".gsd", "gsd.db"));
40
+ insertMilestone({
41
+ id: "M001",
42
+ title: "Preference-skipped validation milestone",
43
+ status: "active",
44
+ depends_on: [],
45
+ });
46
+ upsertMilestonePlanning("M001", {
47
+ title: "Preference-skipped validation milestone",
48
+ status: "active",
49
+ vision: "Ship a small implementation with a documented validation skip.",
50
+ successCriteria: ["Completion remains unblocked when validation was intentionally skipped."],
51
+ keyRisks: [],
52
+ proofStrategy: [],
53
+ verificationContract: "",
54
+ verificationIntegration: "",
55
+ verificationOperational: "Smoke-test the shipped workflow before completion.",
56
+ verificationUat: "",
57
+ definitionOfDone: [],
58
+ requirementCoverage: "",
59
+ boundaryMapMarkdown: "",
38
60
  });
61
+ insertSlice({
62
+ id: "S01",
63
+ milestoneId: "M001",
64
+ title: "First",
65
+ status: "done",
66
+ risk: "low",
67
+ depends: [],
68
+ demo: "",
69
+ sequence: 1,
70
+ });
71
+ }
72
+
73
+ function writeFixtureFiles(base: string): void {
74
+ const milestoneDir = join(base, ".gsd", "milestones", "M001");
75
+ writeFileSync(
76
+ join(milestoneDir, "M001-ROADMAP.md"),
77
+ [
78
+ "# M001",
79
+ "## Slices",
80
+ "- [x] **S01: First** `risk:low` `depends:[]`",
81
+ ].join("\n"),
82
+ );
83
+ writeFileSync(
84
+ join(milestoneDir, "slices", "S01", "S01-SUMMARY.md"),
85
+ "# S01\n\nImplemented the shipped workflow.\n",
86
+ );
87
+ writeFileSync(
88
+ join(milestoneDir, "M001-VALIDATION.md"),
89
+ [
90
+ "---",
91
+ "verdict: pass",
92
+ "skip_validation: true",
93
+ "skip_validation_reason: preference",
94
+ "remediation_round: 0",
95
+ "---",
96
+ "",
97
+ "# Milestone Validation (skipped)",
98
+ "",
99
+ "Milestone validation was skipped by preference.",
100
+ ].join("\n"),
101
+ );
102
+ }
103
+
104
+ function findRule(name: string) {
105
+ const rule = DISPATCH_RULES.find(candidate => candidate.name === name);
106
+ assert.ok(rule, `rule "${name}" must exist`);
107
+ return rule!;
108
+ }
109
+
110
+ function makeCtx(base: string): DispatchContext {
111
+ const state: GSDState = {
112
+ phase: "completing-milestone",
113
+ activeMilestone: { id: "M001", title: "Preference-skipped validation milestone" },
114
+ activeSlice: null,
115
+ activeTask: null,
116
+ recentDecisions: [],
117
+ blockers: [],
118
+ nextAction: "",
119
+ registry: [{ id: "M001", title: "Preference-skipped validation milestone", status: "active" }],
120
+ };
121
+ return {
122
+ basePath: base,
123
+ mid: "M001",
124
+ midTitle: "Preference-skipped validation milestone",
125
+ state,
126
+ prefs: undefined,
127
+ };
128
+ }
129
+
130
+ test("#3698: completing-milestone dispatch accepts skipped validation fixture", async (t) => {
131
+ const base = makeBase();
132
+ t.after(() => cleanup(base));
133
+
134
+ seedMilestone(base);
135
+ writeFixtureFiles(base);
136
+
137
+ const result = await findRule(COMPLETE_RULE).match(makeCtx(base));
138
+
139
+ assert.ok(result, "rule must return a result");
140
+ assert.strictEqual(result!.action, "dispatch", "skipped validation should still allow completion dispatch");
141
+ if (result!.action === "dispatch") {
142
+ assert.strictEqual(result.unitType, "complete-milestone");
143
+ }
39
144
  });
@@ -0,0 +1,17 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ test("skipped validation DB persistence stays atomic", () => {
10
+ const source = readFileSync(join(__dirname, "..", "auto-dispatch.ts"), "utf-8");
11
+
12
+ assert.match(
13
+ source,
14
+ /if \(isDbAvailable\(\)\) \{\s+transaction\(\(\) => \{\s+insertAssessment\([\s\S]*?insertMilestoneValidationGates\(/,
15
+ "skipped validation DB writes must remain inside a single transaction",
16
+ );
17
+ });
@@ -0,0 +1,134 @@
1
+ // gsd-2 + Stuck-state DB-migration regression (Phase C)
2
+ //
3
+ // stuck-state.json file IO has been deleted. The auto-loop now reconstructs
4
+ // recentUnits from unit_dispatches (Phase B ledger) and persists
5
+ // stuckRecoveryAttempts in runtime_kv (stable project scope, soft state).
6
+ //
7
+ // This test verifies the round-trip via the db modules directly: write
8
+ // dispatch rows + a runtime_kv counter, then confirm the same data shape
9
+ // the loop expects is returned.
10
+
11
+ import test from "node:test";
12
+ import assert from "node:assert/strict";
13
+ import { mkdtempSync, mkdirSync, rmSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { tmpdir } from "node:os";
16
+
17
+ import {
18
+ openDatabase,
19
+ closeDatabase,
20
+ insertMilestone,
21
+ } from "../gsd-db.ts";
22
+ import { registerAutoWorker } from "../db/auto-workers.ts";
23
+ import { claimMilestoneLease } from "../db/milestone-leases.ts";
24
+ import {
25
+ recordDispatchClaim,
26
+ getRecentUnitKeysForWorker,
27
+ } from "../db/unit-dispatches.ts";
28
+ import { setRuntimeKv, getRuntimeKv } from "../db/runtime-kv.ts";
29
+
30
+ function makeBase(): string {
31
+ const base = mkdtempSync(join(tmpdir(), "gsd-stuck-state-db-"));
32
+ mkdirSync(join(base, ".gsd"), { recursive: true });
33
+ return base;
34
+ }
35
+
36
+ function cleanup(base: string): void {
37
+ try { closeDatabase(); } catch { /* noop */ }
38
+ try { rmSync(base, { recursive: true, force: true }); } catch { /* noop */ }
39
+ }
40
+
41
+ test("getRecentUnitKeysForWorker reconstructs the recentUnits sliding window", (t) => {
42
+ const base = makeBase();
43
+ t.after(() => cleanup(base));
44
+ openDatabase(join(base, ".gsd", "gsd.db"));
45
+ insertMilestone({ id: "M001", title: "T", status: "active" });
46
+ const worker = registerAutoWorker({ projectRootRealpath: base });
47
+ const lease = claimMilestoneLease(worker, "M001");
48
+ assert.equal(lease.ok, true);
49
+ if (!lease.ok) return;
50
+
51
+ // Record three dispatches in chronological order. Each must transition
52
+ // out of 'claimed' before the next one with the same unit_id can claim
53
+ // (partial unique index). We use distinct unit IDs so all three coexist.
54
+ const claims: number[] = [];
55
+ for (const id of ["U1", "U2", "U3"]) {
56
+ const c = recordDispatchClaim({
57
+ traceId: id, workerId: worker, milestoneLeaseToken: lease.token,
58
+ milestoneId: "M001", unitType: "plan-slice", unitId: id,
59
+ });
60
+ assert.equal(c.ok, true);
61
+ if (c.ok) claims.push(c.dispatchId);
62
+ }
63
+
64
+ // The loader should return them oldest-first to match the in-memory
65
+ // window semantics that detect-stuck.ts expects.
66
+ const window = getRecentUnitKeysForWorker(worker, 20);
67
+ assert.deepEqual(window.map(w => w.key), ["U1", "U2", "U3"]);
68
+ });
69
+
70
+ test("getRecentUnitKeysForWorker honors the limit parameter", (t) => {
71
+ const base = makeBase();
72
+ t.after(() => cleanup(base));
73
+ openDatabase(join(base, ".gsd", "gsd.db"));
74
+ insertMilestone({ id: "M001", title: "T", status: "active" });
75
+ const worker = registerAutoWorker({ projectRootRealpath: base });
76
+ const lease = claimMilestoneLease(worker, "M001");
77
+ assert.equal(lease.ok, true);
78
+ if (!lease.ok) return;
79
+
80
+ for (let i = 0; i < 25; i++) {
81
+ const c = recordDispatchClaim({
82
+ traceId: `t${i}`, workerId: worker, milestoneLeaseToken: lease.token,
83
+ milestoneId: "M001", unitType: "plan-slice", unitId: `U${i}`,
84
+ });
85
+ assert.equal(c.ok, true);
86
+ }
87
+
88
+ const win20 = getRecentUnitKeysForWorker(worker, 20);
89
+ assert.equal(win20.length, 20);
90
+ // Most recent 20 are U5..U24 (chronological), oldest-first → U5..U24.
91
+ assert.equal(win20[0].key, "U5");
92
+ assert.equal(win20[19].key, "U24");
93
+ });
94
+
95
+ test("stuckRecoveryAttempts round-trips via runtime_kv (stable project scope)", (t) => {
96
+ const base = makeBase();
97
+ t.after(() => cleanup(base));
98
+ openDatabase(join(base, ".gsd", "gsd.db"));
99
+ registerAutoWorker({ projectRootRealpath: base });
100
+
101
+ setRuntimeKv("global", base, "stuck_recovery_attempts", 3);
102
+ assert.equal(getRuntimeKv<number>("global", base, "stuck_recovery_attempts"), 3);
103
+ setRuntimeKv("global", base, "stuck_recovery_attempts", 7);
104
+ assert.equal(getRuntimeKv<number>("global", base, "stuck_recovery_attempts"), 7);
105
+ });
106
+
107
+ test("getRecentUnitKeysForWorker filters by worker_id (no cross-worker bleed)", (t) => {
108
+ const base = makeBase();
109
+ t.after(() => cleanup(base));
110
+ openDatabase(join(base, ".gsd", "gsd.db"));
111
+ insertMilestone({ id: "M001", title: "T", status: "active" });
112
+ insertMilestone({ id: "M002", title: "U", status: "active" });
113
+ const w1 = registerAutoWorker({ projectRootRealpath: base });
114
+ const w2 = registerAutoWorker({ projectRootRealpath: base });
115
+ const lease1 = claimMilestoneLease(w1, "M001");
116
+ const lease2 = claimMilestoneLease(w2, "M002");
117
+ assert.equal(lease1.ok, true);
118
+ assert.equal(lease2.ok, true);
119
+ if (!lease1.ok || !lease2.ok) return;
120
+
121
+ recordDispatchClaim({
122
+ traceId: "ta", workerId: w1, milestoneLeaseToken: lease1.token,
123
+ milestoneId: "M001", unitType: "plan-slice", unitId: "for-w1",
124
+ });
125
+ recordDispatchClaim({
126
+ traceId: "tb", workerId: w2, milestoneLeaseToken: lease2.token,
127
+ milestoneId: "M002", unitType: "plan-slice", unitId: "for-w2",
128
+ });
129
+
130
+ const w1Window = getRecentUnitKeysForWorker(w1, 20);
131
+ const w2Window = getRecentUnitKeysForWorker(w2, 20);
132
+ assert.deepEqual(w1Window.map(w => w.key), ["for-w1"]);
133
+ assert.deepEqual(w2Window.map(w => w.key), ["for-w2"]);
134
+ });