gsd-pi 2.80.0-dev.0aaa4fc17 → 2.80.0-dev.1486c1899

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 (166) hide show
  1. package/README.md +4 -2
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/gsd/auto/phases.js +22 -15
  4. package/dist/resources/extensions/gsd/auto/resolve.js +17 -0
  5. package/dist/resources/extensions/gsd/auto/run-unit.js +13 -1
  6. package/dist/resources/extensions/gsd/auto-prompts.js +13 -1
  7. package/dist/resources/extensions/gsd/auto-recovery.js +43 -1
  8. package/dist/resources/extensions/gsd/auto-supervisor.js +8 -1
  9. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +2 -2
  10. package/dist/resources/extensions/gsd/auto.js +52 -4
  11. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +21 -2
  12. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +27 -20
  13. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +21 -0
  14. package/dist/resources/extensions/gsd/db/unit-dispatches.js +39 -0
  15. package/dist/resources/extensions/gsd/guided-flow.js +19 -4
  16. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  17. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  18. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  19. package/dist/resources/extensions/gsd/quick.js +34 -2
  20. package/dist/resources/extensions/gsd/tools/context-mode-tool-result.js +15 -0
  21. package/dist/resources/extensions/gsd/tools/exec-search-tool.js +5 -0
  22. package/dist/resources/extensions/gsd/tools/exec-tool.js +3 -15
  23. package/dist/resources/extensions/gsd/tools/resume-tool.js +5 -0
  24. package/dist/resources/extensions/gsd/unit-context-composer.js +12 -3
  25. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  26. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  27. package/dist/web/standalone/.next/BUILD_ID +1 -1
  28. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  29. package/dist/web/standalone/.next/build-manifest.json +2 -2
  30. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  31. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.html +1 -1
  48. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  55. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  57. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  58. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  59. package/package.json +3 -3
  60. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  61. package/packages/mcp-server/dist/workflow-tools.js +22 -17
  62. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  63. package/packages/mcp-server/src/workflow-tools.test.ts +75 -2
  64. package/packages/mcp-server/src/workflow-tools.ts +30 -16
  65. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  66. package/packages/native/tsconfig.tsbuildinfo +1 -1
  67. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +32 -0
  68. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  69. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/agent-session.js +8 -0
  71. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +3 -1
  73. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  75. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  77. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  78. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  79. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  80. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  81. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +1 -0
  83. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  84. package/packages/pi-coding-agent/dist/core/extensions/runner.js +3 -0
  85. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  86. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +2 -0
  87. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  89. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  91. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  92. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  93. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  94. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  95. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +3 -0
  98. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +13 -5
  101. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  102. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
  103. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  104. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
  106. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  107. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +36 -0
  108. package/packages/pi-coding-agent/src/core/agent-session.ts +8 -0
  109. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +3 -1
  110. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  111. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  112. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +2 -0
  113. package/packages/pi-coding-agent/src/core/extensions/runner.ts +3 -0
  114. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  115. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  116. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +4 -0
  117. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
  118. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +22 -7
  119. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
  120. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  121. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  122. package/packages/pi-tui/dist/tui.js +18 -8
  123. package/packages/pi-tui/dist/tui.js.map +1 -1
  124. package/packages/pi-tui/src/tui.ts +20 -8
  125. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  126. package/src/resources/extensions/gsd/auto/phases.ts +28 -20
  127. package/src/resources/extensions/gsd/auto/resolve.ts +23 -1
  128. package/src/resources/extensions/gsd/auto/run-unit.ts +18 -1
  129. package/src/resources/extensions/gsd/auto-prompts.ts +17 -1
  130. package/src/resources/extensions/gsd/auto-recovery.ts +54 -0
  131. package/src/resources/extensions/gsd/auto-supervisor.ts +7 -0
  132. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +2 -2
  133. package/src/resources/extensions/gsd/auto.ts +60 -3
  134. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +21 -1
  135. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +27 -19
  136. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
  137. package/src/resources/extensions/gsd/db/unit-dispatches.ts +41 -0
  138. package/src/resources/extensions/gsd/guided-flow.ts +32 -4
  139. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  140. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  141. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  142. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  143. package/src/resources/extensions/gsd/quick.ts +37 -2
  144. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +71 -0
  145. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +14 -1
  146. package/src/resources/extensions/gsd/tests/compaction-snapshot.test.ts +11 -0
  147. package/src/resources/extensions/gsd/tests/exec-history.test.ts +15 -0
  148. package/src/resources/extensions/gsd/tests/exec-sandbox.test.ts +65 -0
  149. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +234 -0
  150. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  151. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  152. package/src/resources/extensions/gsd/tests/signal-handlers.test.ts +27 -0
  153. package/src/resources/extensions/gsd/tests/stalled-tool-recovery.test.ts +49 -1
  154. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +55 -0
  155. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  156. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +136 -4
  157. package/src/resources/extensions/gsd/tests/unit-dispatches.test.ts +30 -0
  158. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +30 -0
  159. package/src/resources/extensions/gsd/tools/context-mode-tool-result.ts +25 -0
  160. package/src/resources/extensions/gsd/tools/exec-search-tool.ts +7 -7
  161. package/src/resources/extensions/gsd/tools/exec-tool.ts +4 -23
  162. package/src/resources/extensions/gsd/tools/resume-tool.ts +7 -7
  163. package/src/resources/extensions/gsd/unit-context-composer.ts +19 -4
  164. package/src/resources/extensions/gsd/unit-runtime.ts +11 -0
  165. /package/dist/web/standalone/.next/static/{YRK7-Es93MwpIn3VL35K0 → O-2rnxSJki_W-2fkK3zIj}/_buildManifest.js +0 -0
  166. /package/dist/web/standalone/.next/static/{YRK7-Es93MwpIn3VL35K0 → O-2rnxSJki_W-2fkK3zIj}/_ssgManifest.js +0 -0
@@ -21,12 +21,18 @@ import type {
21
21
  ComputedArtifactRegistry,
22
22
  UnitContextManifest,
23
23
  } from "../unit-context-manifest.ts";
24
- import { UNIT_MANIFESTS } from "../unit-context-manifest.ts";
25
- import { buildReassessRoadmapPrompt } from "../auto-prompts.ts";
24
+ import { KNOWN_UNIT_TYPES, UNIT_MANIFESTS } from "../unit-context-manifest.ts";
25
+ import {
26
+ buildExecuteTaskPrompt,
27
+ buildGateEvaluatePrompt,
28
+ buildReassessRoadmapPrompt,
29
+ buildWorkflowPreferencesPrompt,
30
+ } from "../auto-prompts.ts";
26
31
  import { invalidateAllCaches } from "../cache.ts";
27
32
  import {
28
33
  openDatabase,
29
34
  closeDatabase,
35
+ insertGateRow,
30
36
  insertMilestone,
31
37
  upsertMilestonePlanning,
32
38
  insertSlice,
@@ -121,7 +127,7 @@ test("Context Mode composer: standalone output starts with heading and includes
121
127
  assert.ok(out.startsWith("## Context Mode"));
122
128
  assert.match(out, /execution lane/i);
123
129
  assert.match(out, /`gsd_exec`/);
124
- assert.match(out, /noisy commands/);
130
+ assert.match(out, /builds, tests, and diagnostics/);
125
131
  assert.match(out, /`gsd_exec_search`/);
126
132
  assert.match(out, /before reruns/);
127
133
  assert.match(out, /`gsd_resume`/);
@@ -136,7 +142,45 @@ test("Context Mode composer: nested output is compact single sentence", () => {
136
142
  assert.match(out, /`gsd_exec`/);
137
143
  assert.match(out, /`gsd_exec_search`/);
138
144
  assert.match(out, /`gsd_resume`/);
139
- assert.ok(out.length < 180, `nested guidance should stay compact, got ${out.length} chars`);
145
+ assert.ok(out.length < 240, `nested guidance should stay compact, got ${out.length} chars`);
146
+ });
147
+
148
+ const laneLabelByMode: Record<string, string> = {
149
+ interview: "interview",
150
+ research: "research",
151
+ planning: "planning",
152
+ execution: "execution",
153
+ verification: "verification",
154
+ orchestration: "orchestration",
155
+ docs: "documentation",
156
+ };
157
+
158
+ test("Context Mode composer: every known eligible unit renders its configured lane and required tools", () => {
159
+ for (const unitType of KNOWN_UNIT_TYPES) {
160
+ const manifest = UNIT_MANIFESTS[unitType];
161
+ assert.ok(manifest, `missing manifest for ${unitType}`);
162
+ const out = composeContextModeInstructions(unitType, { enabled: true, renderMode: "standalone" });
163
+ if (manifest.contextMode === "none") {
164
+ assert.strictEqual(out, "", `${unitType} should not render Context Mode`);
165
+ continue;
166
+ }
167
+ assert.ok(out.startsWith("## Context Mode"), `${unitType} should render standalone Context Mode heading`);
168
+ assert.match(out, new RegExp(`Lane: \\*\\*${laneLabelByMode[manifest.contextMode]} lane\\*\\*\\.`, "i"));
169
+ assert.match(out, /`gsd_exec`/, `${unitType} should mention gsd_exec`);
170
+ assert.match(out, /`gsd_exec_search`/, `${unitType} should mention gsd_exec_search`);
171
+ assert.match(out, /`gsd_resume`/, `${unitType} should mention gsd_resume`);
172
+ }
173
+ });
174
+
175
+ test("Context Mode composer: workflow-preferences and research-decision render no Context Mode block", () => {
176
+ assert.strictEqual(
177
+ composeContextModeInstructions("workflow-preferences", { enabled: true, renderMode: "standalone" }),
178
+ "",
179
+ );
180
+ assert.strictEqual(
181
+ composeContextModeInstructions("research-decision", { enabled: true, renderMode: "standalone" }),
182
+ "",
183
+ );
140
184
  });
141
185
 
142
186
  // ─── Integration: migrated buildReassessRoadmapPrompt ─────────────────────
@@ -220,6 +264,94 @@ test("#4782 phase 2: buildReassessRoadmapPrompt emits composer-shaped context wi
220
264
  assert.ok(!prompt.includes("Slice Context (from discussion)"));
221
265
  });
222
266
 
267
+ test("Context Mode resume injection: eligible prompts include one bounded snapshot block above inlined context", async (t) => {
268
+ const base = makeFixtureBase();
269
+ t.after(() => cleanup(base));
270
+ invalidateAllCaches();
271
+
272
+ seed(base, "M001");
273
+ writeArtifacts(base);
274
+ writeFileSync(
275
+ join(base, ".gsd", "last-snapshot.md"),
276
+ "# GSD context snapshot\n\nResume evidence.\n",
277
+ "utf-8",
278
+ );
279
+
280
+ const prompt = await buildReassessRoadmapPrompt("M001", "Test", "S01", base);
281
+
282
+ assert.equal(prompt.match(/## Context Snapshot/g)?.length, 1);
283
+ assert.match(prompt, /Source: `\.gsd\/last-snapshot\.md`/);
284
+ assert.match(prompt, /Resume evidence/);
285
+ assert.ok(prompt.indexOf("## Context Mode") < prompt.indexOf("## Context Snapshot"));
286
+ assert.ok(prompt.indexOf("## Context Snapshot") < prompt.indexOf("## Inlined Context"));
287
+ });
288
+
289
+ test("Context Mode resume injection: missing snapshot does not add an empty block", async (t) => {
290
+ const base = makeFixtureBase();
291
+ t.after(() => cleanup(base));
292
+ invalidateAllCaches();
293
+
294
+ seed(base, "M001");
295
+ writeArtifacts(base);
296
+
297
+ const prompt = await buildReassessRoadmapPrompt("M001", "Test", "S01", base);
298
+
299
+ assert.match(prompt, /## Context Mode/);
300
+ assert.doesNotMatch(prompt, /## Context Snapshot/);
301
+ });
302
+
303
+ test("Context Mode resume injection: disabled mode suppresses guidance and snapshot reads", async (t) => {
304
+ const base = makeFixtureBase();
305
+ t.after(() => cleanup(base));
306
+ invalidateAllCaches();
307
+
308
+ seed(base, "M001");
309
+ writeArtifacts(base);
310
+ writeFileSync(join(base, ".gsd", "PREFERENCES.md"), "---\ncontext_mode:\n enabled: false\n---\n", "utf-8");
311
+ writeFileSync(join(base, ".gsd", "last-snapshot.md"), "# GSD context snapshot\n\nDo not inject.\n", "utf-8");
312
+
313
+ const prompt = await buildReassessRoadmapPrompt("M001", "Test", "S01", base);
314
+
315
+ assert.doesNotMatch(prompt, /## Context Mode/);
316
+ assert.doesNotMatch(prompt, /## Context Snapshot/);
317
+ assert.doesNotMatch(prompt, /Do not inject/);
318
+ });
319
+
320
+ test("Context Mode resume injection: none-mode units do not inject snapshots", async () => {
321
+ const base = makeFixtureBase();
322
+ try {
323
+ writeFileSync(join(base, ".gsd", "last-snapshot.md"), "# GSD context snapshot\n\nNo lane.\n", "utf-8");
324
+ const prompt = await buildWorkflowPreferencesPrompt(base);
325
+ assert.doesNotMatch(prompt, /## Context Mode/);
326
+ assert.doesNotMatch(prompt, /## Context Snapshot/);
327
+ assert.doesNotMatch(prompt, /No lane/);
328
+ } finally {
329
+ cleanup(base);
330
+ }
331
+ });
332
+
333
+ test("Context Mode prompt suppression: disabled inlined, phase-anchor, and nested prompts omit Context Mode", async (t) => {
334
+ const base = makeFixtureBase();
335
+ t.after(() => cleanup(base));
336
+ invalidateAllCaches();
337
+
338
+ seed(base, "M001");
339
+ writeArtifacts(base);
340
+ insertGateRow({ milestoneId: "M001", sliceId: "S01", gateId: "Q3", scope: "slice" });
341
+ writeFileSync(join(base, ".gsd", "PREFERENCES.md"), "---\ncontext_mode:\n enabled: false\n---\n", "utf-8");
342
+ writeFileSync(join(base, ".gsd", "last-snapshot.md"), "# GSD context snapshot\n\nDo not inject.\n", "utf-8");
343
+
344
+ const inlinedPrompt = await buildReassessRoadmapPrompt("M001", "Test", "S01", base);
345
+ assert.doesNotMatch(inlinedPrompt, /## Context Mode|Context Mode \(|## Context Snapshot/);
346
+
347
+ const phaseAnchorPrompt = await buildExecuteTaskPrompt("M001", "S01", "First", "T01", "Task", base);
348
+ assert.doesNotMatch(phaseAnchorPrompt, /## Context Mode|Context Mode \(|## Context Snapshot/);
349
+
350
+ const nestedPrompt = await buildGateEvaluatePrompt("M001", "Test", "S01", "First", base);
351
+ assert.match(nestedPrompt, /Use this as the prompt for a `subagent` call/);
352
+ assert.doesNotMatch(nestedPrompt, /## Context Mode|Context Mode \(|## Context Snapshot/);
353
+ });
354
+
223
355
  // ─── v2 surface (#4924) ───────────────────────────────────────────────────
224
356
 
225
357
  const fakeBase: BaseResolverContext = {
@@ -21,6 +21,7 @@ import {
21
21
  markFailed,
22
22
  markStuck,
23
23
  markCanceled,
24
+ markLatestActiveForWorkerCanceled,
24
25
  getRecentForUnit,
25
26
  getLatestForUnit,
26
27
  } from "../db/unit-dispatches.ts";
@@ -196,6 +197,35 @@ test("markStuck and markCanceled set their respective statuses", (t) => {
196
197
  assert.equal(getLatestForUnit("M001/S01/T01")!.status, "canceled");
197
198
  });
198
199
 
200
+ test("markLatestActiveForWorkerCanceled cancels only the latest active dispatch for a worker", (t) => {
201
+ const base = makeBase();
202
+ t.after(() => cleanup(base));
203
+ const { workerId, leaseToken } = setup(base);
204
+
205
+ const first = recordDispatchClaim({
206
+ traceId: "tc-1", workerId, milestoneLeaseToken: leaseToken,
207
+ milestoneId: "M001", unitType: "plan-slice", unitId: "M001/S01",
208
+ });
209
+ assert.equal(first.ok, true);
210
+ if (!first.ok) return;
211
+ markCompleted(first.dispatchId);
212
+
213
+ const second = recordDispatchClaim({
214
+ traceId: "tc-2", workerId, milestoneLeaseToken: leaseToken,
215
+ milestoneId: "M001", unitType: "run-task", unitId: "M001/S01/T01",
216
+ });
217
+ assert.equal(second.ok, true);
218
+ if (!second.ok) return;
219
+ markRunning(second.dispatchId);
220
+
221
+ assert.equal(markLatestActiveForWorkerCanceled(workerId, "signal-exit"), true);
222
+ assert.equal(getLatestForUnit("M001/S01")!.status, "completed");
223
+ const latest = getLatestForUnit("M001/S01/T01")!;
224
+ assert.equal(latest.status, "canceled");
225
+ assert.equal(latest.exit_reason, "signal-exit");
226
+ assert.equal(markLatestActiveForWorkerCanceled(workerId, "signal-exit"), false);
227
+ });
228
+
199
229
  test("terminal transitions do not overwrite an already terminal dispatch", (t) => {
200
230
  const base = makeBase();
201
231
  t.after(() => cleanup(base));
@@ -9,6 +9,7 @@ import {
9
9
  readUnitRuntimeRecord,
10
10
  writeUnitRuntimeRecord,
11
11
  } from "../unit-runtime.ts";
12
+ import { closeDatabase, insertMilestone, insertSlice, insertTask, openDatabase } from "../gsd-db.ts";
12
13
  import { clearPathCache } from '../paths.ts';
13
14
  import { test } from 'node:test';
14
15
  import assert from 'node:assert/strict';
@@ -72,6 +73,35 @@ console.log("\n=== runtime record cleanup ===");
72
73
  assert.deepStrictEqual(loaded, null, "record removed");
73
74
  }
74
75
 
76
+ console.log("\n=== execute-task durability trusts closed DB task status ===");
77
+ {
78
+ const dbBase = mkdtempSync(join(tmpdir(), "gsd-unit-runtime-db-test-"));
79
+ mkdirSync(join(dbBase, ".gsd", "milestones", "M300", "slices", "S01", "tasks"), { recursive: true });
80
+ try {
81
+ openDatabase(join(dbBase, ".gsd", "gsd.db"));
82
+ insertMilestone({ id: "M300", title: "DB Milestone", status: "active" });
83
+ insertSlice({ id: "S01", milestoneId: "M300", title: "DB Slice", status: "in_progress" });
84
+ insertTask({ id: "T01", milestoneId: "M300", sliceId: "S01", title: "DB Task", status: "complete" });
85
+ writeFileSync(
86
+ join(dbBase, ".gsd", "milestones", "M300", "slices", "S01", "S01-PLAN.md"),
87
+ "# S01\n\n## Tasks\n\n- [ ] **T01: DB Task** `est:10m`\n",
88
+ "utf-8",
89
+ );
90
+ writeFileSync(join(dbBase, ".gsd", "STATE.md"), "## Next Action\nExecute T01 for S01: DB task\n", "utf-8");
91
+
92
+ const status = await inspectExecuteTaskDurability(dbBase, "M300/S01/T01");
93
+ assert.ok(status !== null, "db-complete: status exists");
94
+ assert.equal(status!.dbComplete, true, "db-complete: closed DB status is captured");
95
+ assert.equal(status!.summaryExists, false, "db-complete: summary can still be missing");
96
+ assert.equal(status!.taskChecked, false, "db-complete: checkbox can still be unchecked");
97
+ assert.equal(status!.nextActionAdvanced, false, "db-complete: next action can still point at task");
98
+ assert.equal(formatExecuteTaskRecoveryStatus(status!), "DB task status is closed");
99
+ } finally {
100
+ closeDatabase();
101
+ rmSync(dbBase, { recursive: true, force: true });
102
+ }
103
+ }
104
+
75
105
  console.log("\n=== hook unit type sanitization (slash in unitType) ===");
76
106
  {
77
107
  // Hook units have unitType like "hook/code-review" with a slash
@@ -0,0 +1,25 @@
1
+ // Project/App: GSD-2
2
+ // File Purpose: Shared Context Mode tool result helpers.
3
+
4
+ export interface ToolExecutionResult {
5
+ content: Array<{ type: "text"; text: string }>;
6
+ details: Record<string, unknown>;
7
+ isError?: boolean;
8
+ }
9
+
10
+ export type ContextModeToolName = "gsd_exec" | "gsd_exec_search" | "gsd_resume";
11
+
12
+ export function contextModeDisabledResult(operation: ContextModeToolName): ToolExecutionResult {
13
+ return {
14
+ content: [
15
+ {
16
+ type: "text",
17
+ text:
18
+ `${operation} is disabled by \`context_mode.enabled: false\` in preferences. ` +
19
+ "Remove that override or set it to true to re-enable Context Mode tools.",
20
+ },
21
+ ],
22
+ details: { operation, error: "context_mode_disabled" },
23
+ isError: true,
24
+ };
25
+ }
@@ -4,6 +4,8 @@
4
4
  // re-discover past runs without re-executing. Read-only; no DB writes.
5
5
 
6
6
  import { searchExecHistory, type ExecSearchOptions } from "../exec-history.js";
7
+ import { isContextModeEnabled, type ContextModeConfig } from "../preferences-types.js";
8
+ import { contextModeDisabledResult, type ToolExecutionResult } from "./context-mode-tool-result.js";
7
9
 
8
10
  export interface ExecSearchToolParams {
9
11
  query?: string;
@@ -12,16 +14,14 @@ export interface ExecSearchToolParams {
12
14
  limit?: number;
13
15
  }
14
16
 
15
- export interface ToolExecutionResult {
16
- content: Array<{ type: "text"; text: string }>;
17
- details: Record<string, unknown>;
18
- isError?: boolean;
19
- }
20
-
21
17
  export function executeExecSearch(
22
18
  params: ExecSearchToolParams,
23
- opts: { baseDir: string },
19
+ opts: { baseDir: string; preferences?: { context_mode?: ContextModeConfig } | null },
24
20
  ): ToolExecutionResult {
21
+ if (!isContextModeEnabled(opts.preferences)) {
22
+ return contextModeDisabledResult("gsd_exec_search");
23
+ }
24
+
25
25
  const searchOpts: ExecSearchOptions = {
26
26
  query: typeof params.query === "string" ? params.query : undefined,
27
27
  runtime: params.runtime,
@@ -12,6 +12,7 @@ import {
12
12
  type ExecSandboxResult,
13
13
  } from "../exec-sandbox.js";
14
14
  import { isContextModeEnabled, type ContextModeConfig } from "../preferences-types.js";
15
+ import { contextModeDisabledResult, type ToolExecutionResult } from "./context-mode-tool-result.js";
15
16
 
16
17
  export interface ExecToolParams {
17
18
  runtime: ExecSandboxRequest["runtime"];
@@ -20,17 +21,12 @@ export interface ExecToolParams {
20
21
  timeout_ms?: number;
21
22
  }
22
23
 
23
- export interface ToolExecutionResult {
24
- content: Array<{ type: "text"; text: string }>;
25
- details: Record<string, unknown>;
26
- isError?: boolean;
27
- }
28
-
29
24
  export interface ExecToolDeps {
30
25
  baseDir: string;
31
26
  preferences: { context_mode?: ContextModeConfig } | null;
32
27
  /** Optional override for testing. */
33
28
  run?: (req: ExecSandboxRequest, opts: ExecSandboxOptions) => Promise<ExecSandboxResult>;
29
+ env?: NodeJS.ProcessEnv;
34
30
  now?: () => Date;
35
31
  generateId?: () => string;
36
32
  }
@@ -77,21 +73,6 @@ function isEnabled(prefs: ExecToolDeps["preferences"]): boolean {
77
73
  return isContextModeEnabled(prefs);
78
74
  }
79
75
 
80
- function disabledResult(): ToolExecutionResult {
81
- return {
82
- content: [
83
- {
84
- type: "text",
85
- text:
86
- "gsd_exec is disabled by `context_mode.enabled: false` in preferences. Remove that " +
87
- "override (or set it to true) to re-enable sandboxed tool-output execution.",
88
- },
89
- ],
90
- details: { operation: "gsd_exec", error: "context_mode_disabled" },
91
- isError: true,
92
- };
93
- }
94
-
95
76
  function paramError(message: string): ToolExecutionResult {
96
77
  return {
97
78
  content: [{ type: "text", text: `Error: ${message}` }],
@@ -104,7 +85,7 @@ export async function executeGsdExec(
104
85
  params: ExecToolParams,
105
86
  deps: ExecToolDeps,
106
87
  ): Promise<ToolExecutionResult> {
107
- if (!isEnabled(deps.preferences)) return disabledResult();
88
+ if (!isEnabled(deps.preferences)) return contextModeDisabledResult("gsd_exec");
108
89
 
109
90
  const runtime = params.runtime;
110
91
  if (runtime !== "bash" && runtime !== "node" && runtime !== "python") {
@@ -121,7 +102,7 @@ export async function executeGsdExec(
121
102
  const opts = buildExecOptions(
122
103
  deps.baseDir,
123
104
  deps.preferences?.context_mode,
124
- { now: deps.now, generateId: deps.generateId },
105
+ { env: deps.env, now: deps.now, generateId: deps.generateId },
125
106
  );
126
107
  const run = deps.run ?? runExecSandbox;
127
108
 
@@ -3,22 +3,22 @@
3
3
  // re-deriving project memory state.
4
4
 
5
5
  import { readCompactionSnapshot } from "../compaction-snapshot.js";
6
+ import { isContextModeEnabled, type ContextModeConfig } from "../preferences-types.js";
7
+ import { contextModeDisabledResult, type ToolExecutionResult } from "./context-mode-tool-result.js";
6
8
 
7
9
  export interface ResumeToolParams {
8
10
  /** Ignored — reserved for future variant (e.g. dated snapshots). */
9
11
  _variant?: string;
10
12
  }
11
13
 
12
- export interface ToolExecutionResult {
13
- content: Array<{ type: "text"; text: string }>;
14
- details: Record<string, unknown>;
15
- isError?: boolean;
16
- }
17
-
18
14
  export function executeResume(
19
15
  _params: ResumeToolParams,
20
- opts: { baseDir: string },
16
+ opts: { baseDir: string; preferences?: { context_mode?: ContextModeConfig } | null },
21
17
  ): ToolExecutionResult {
18
+ if (!isContextModeEnabled(opts.preferences)) {
19
+ return contextModeDisabledResult("gsd_resume");
20
+ }
21
+
22
22
  const snapshot = readCompactionSnapshot(opts.baseDir);
23
23
  if (snapshot == null) {
24
24
  return {
@@ -112,8 +112,22 @@ const CONTEXT_MODE_LANE_LABELS: Record<Exclude<ContextModePolicy, "none">, strin
112
112
  docs: "documentation",
113
113
  };
114
114
 
115
- const CONTEXT_MODE_GUIDANCE =
116
- "Use `gsd_exec` for noisy commands, `gsd_exec_search` before reruns, and `gsd_resume` after compaction or resume.";
115
+ const CONTEXT_MODE_GUIDANCE_BY_LANE: Record<Exclude<ContextModePolicy, "none">, string> = {
116
+ interview:
117
+ "Use `gsd_resume` to restore prior discussion, `gsd_exec` for noisy discovery, and `gsd_exec_search` before repeating scans.",
118
+ research:
119
+ "Use `gsd_exec` for noisy research scans, `gsd_exec_search` before reruns, and `gsd_resume` to restore prior findings.",
120
+ planning:
121
+ "Use `gsd_resume` for planning continuity, `gsd_exec` for noisy checks, and `gsd_exec_search` before rerunning diagnostics.",
122
+ execution:
123
+ "Use `gsd_exec` for builds, tests, and diagnostics, `gsd_exec_search` before reruns, and `gsd_resume` after compaction or resume.",
124
+ verification:
125
+ "Use `gsd_exec` for verification commands, `gsd_exec_search` to reuse prior evidence, and `gsd_resume` after compaction or resume.",
126
+ orchestration:
127
+ "Use `gsd_resume` before resuming orchestration, `gsd_exec_search` to reuse prior runs, and `gsd_exec` for noisy coordination checks.",
128
+ docs:
129
+ "Use `gsd_resume` for prior context, `gsd_exec_search` for saved evidence, and `gsd_exec` for noisy doc validation commands.",
130
+ };
117
131
 
118
132
  /**
119
133
  * Render the Context Mode instruction lane for a unit type. Unknown unit
@@ -129,15 +143,16 @@ export function composeContextModeInstructions(
129
143
  if (!manifest || manifest.contextMode === "none") return "";
130
144
 
131
145
  const lane = CONTEXT_MODE_LANE_LABELS[manifest.contextMode];
146
+ const guidance = CONTEXT_MODE_GUIDANCE_BY_LANE[manifest.contextMode];
132
147
  if (opts.renderMode === "nested") {
133
- return `Context Mode (${lane} lane): ${CONTEXT_MODE_GUIDANCE}`;
148
+ return `Context Mode (${lane} lane): ${guidance}`;
134
149
  }
135
150
 
136
151
  return [
137
152
  "## Context Mode",
138
153
  "",
139
154
  `Lane: **${lane} lane**.`,
140
- CONTEXT_MODE_GUIDANCE,
155
+ guidance,
141
156
  ].join("\n");
142
157
  }
143
158
 
@@ -10,6 +10,8 @@ import {
10
10
  } from "./paths.js";
11
11
  import { loadFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
12
12
  import { parseUnitId } from "./unit-id.js";
13
+ import { getTask, isDbAvailable, refreshOpenDatabaseFromDisk } from "./gsd-db.js";
14
+ import { isClosedStatus } from "./status-guards.js";
13
15
 
14
16
  // Per-record advisory lock — prevents read-modify-write races between
15
17
  // concurrent writers updating disjoint fields of the same runtime record.
@@ -92,6 +94,7 @@ export interface ExecuteTaskRecoveryStatus {
92
94
  summaryExists: boolean;
93
95
  taskChecked: boolean;
94
96
  nextActionAdvanced: boolean;
97
+ dbComplete: boolean;
95
98
  mustHaveCount: number;
96
99
  mustHavesMentionedInSummary: number;
97
100
  }
@@ -213,6 +216,12 @@ export async function inspectExecuteTaskDurability(
213
216
  const escapedTid = tid.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
214
217
  const taskChecked = !!planContent && new RegExp(`^- \\[[xX]\\] \\*\\*${escapedTid}:`, "m").test(planContent);
215
218
  const nextActionAdvanced = !new RegExp(`Execute ${tid}\\b`).test(stateContent);
219
+ let dbComplete = false;
220
+ if (isDbAvailable()) {
221
+ refreshOpenDatabaseFromDisk();
222
+ const task = getTask(mid, sid, tid);
223
+ dbComplete = !!task && isClosedStatus(task.status);
224
+ }
216
225
 
217
226
  // Must-have coverage: load task plan and count mentions in summary
218
227
  let mustHaveCount = 0;
@@ -239,12 +248,14 @@ export async function inspectExecuteTaskDurability(
239
248
  summaryExists,
240
249
  taskChecked,
241
250
  nextActionAdvanced,
251
+ dbComplete,
242
252
  mustHaveCount,
243
253
  mustHavesMentionedInSummary,
244
254
  };
245
255
  }
246
256
 
247
257
  export function formatExecuteTaskRecoveryStatus(status: ExecuteTaskRecoveryStatus): string {
258
+ if (status.dbComplete) return "DB task status is closed";
248
259
  const missing = [] as string[];
249
260
  if (!status.summaryExists) missing.push(`summary missing (${status.summaryPath})`);
250
261
  if (!status.taskChecked) missing.push(`task checkbox unchecked in ${status.planPath}`);