gsd-pi 2.80.0-dev.8b3a59f5c → 2.80.0-dev.b2a374d5b

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 (222) hide show
  1. package/dist/resources/.managed-resources-content-hash +1 -1
  2. package/dist/resources/GSD-WORKFLOW.md +2 -2
  3. package/dist/resources/extensions/gsd/auto/loop.js +32 -1
  4. package/dist/resources/extensions/gsd/auto/phases.js +37 -30
  5. package/dist/resources/extensions/gsd/auto-dispatch.js +10 -0
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +10 -10
  7. package/dist/resources/extensions/gsd/auto-prompts.js +111 -1
  8. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  9. package/dist/resources/extensions/gsd/auto.js +16 -3
  10. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +21 -0
  11. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  12. package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
  13. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  14. package/dist/resources/extensions/gsd/detection.js +106 -0
  15. package/dist/resources/extensions/gsd/guided-flow.js +63 -12
  16. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  17. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  18. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  19. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  20. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  21. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  22. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  23. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  24. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  25. package/dist/resources/extensions/gsd/quick.js +34 -2
  26. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  27. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  28. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  29. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  30. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  31. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  32. package/dist/web/standalone/.next/BUILD_ID +1 -1
  33. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  34. package/dist/web/standalone/.next/build-manifest.json +2 -2
  35. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  36. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/index.html +1 -1
  53. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  60. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  62. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  63. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  64. package/package.json +4 -2
  65. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/agent-session.js +3 -0
  67. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  68. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +32 -0
  69. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  70. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  71. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  73. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  75. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  76. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  77. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  78. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  79. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  80. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  81. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  83. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  85. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
  87. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  88. package/packages/pi-coding-agent/dist/core/extensions/runner.js +17 -1
  89. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +99 -0
  91. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  93. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  95. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  96. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  97. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  98. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  99. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
  100. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +25 -20
  102. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  103. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +2 -2
  104. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +2 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +26 -13
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  117. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +15 -5
  118. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  119. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
  120. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  121. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
  123. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  124. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
  126. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +21 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  129. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  130. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  131. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
  132. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  133. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  134. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  135. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  136. package/packages/pi-coding-agent/src/core/agent-session.ts +3 -0
  137. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +38 -0
  138. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  139. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  140. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  141. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  142. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +110 -0
  143. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -1
  144. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  145. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  146. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
  147. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +25 -20
  148. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +2 -2
  149. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
  150. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +2 -1
  151. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
  152. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +33 -13
  153. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
  154. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +24 -7
  155. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
  156. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
  157. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +25 -0
  158. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
  159. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  160. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  161. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  162. package/packages/pi-tui/dist/tui.js +17 -7
  163. package/packages/pi-tui/dist/tui.js.map +1 -1
  164. package/packages/pi-tui/src/tui.ts +19 -7
  165. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  166. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  167. package/pkg/dist/modes/interactive/theme/themes.js +36 -27
  168. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  169. package/src/resources/GSD-WORKFLOW.md +2 -2
  170. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  171. package/src/resources/extensions/gsd/auto/loop.ts +50 -8
  172. package/src/resources/extensions/gsd/auto/phases.ts +42 -28
  173. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
  174. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  175. package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
  176. package/src/resources/extensions/gsd/auto-start.ts +7 -6
  177. package/src/resources/extensions/gsd/auto.ts +23 -3
  178. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
  179. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  180. package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
  181. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  182. package/src/resources/extensions/gsd/detection.ts +128 -0
  183. package/src/resources/extensions/gsd/guided-flow.ts +66 -12
  184. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  185. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  186. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  187. package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  188. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  189. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  190. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  191. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  192. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  193. package/src/resources/extensions/gsd/quick.ts +37 -2
  194. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  195. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
  196. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  197. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
  198. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  199. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  200. package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
  201. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  202. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  203. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  204. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  205. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  206. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  207. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  208. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  209. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
  210. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +63 -2
  211. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  212. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
  213. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  214. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  215. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  216. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  217. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  218. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  219. package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
  220. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  221. /package/dist/web/standalone/.next/static/{k-MDjzPMF62Rg1-FR437h → FU0iL9dhdc2IBzEYBDOlY}/_buildManifest.js +0 -0
  222. /package/dist/web/standalone/.next/static/{k-MDjzPMF62Rg1-FR437h → FU0iL9dhdc2IBzEYBDOlY}/_ssgManifest.js +0 -0
@@ -13,6 +13,9 @@ import { parseRoadmap, parsePlan } from '../parsers-legacy.ts';
13
13
  import { parseSummary } from '../files.ts';
14
14
  import { deriveState } from '../state.ts';
15
15
  import { invalidateAllCaches } from '../cache.ts';
16
+ import { ensureDbOpen } from '../bootstrap/dynamic-tools.ts';
17
+ import { closeDatabase, getAllMilestones } from '../gsd-db.ts';
18
+ import { importWrittenMigrationToDb } from '../migrate/command.ts';
16
19
  import type {
17
20
  GSDProject,
18
21
  GSDMilestone,
@@ -250,6 +253,51 @@ test('Scenario 1: Incomplete project — write, parse, deriveState', async () =>
250
253
  }
251
254
  });
252
255
 
256
+ test('Scenario 1b: written migration imports into authoritative DB state', async () => {
257
+ const base = mkdtempSync(join(tmpdir(), 'gsd-writer-db-import-'));
258
+ try {
259
+ const project = buildIncompleteProject();
260
+ await writeGSDDirectory(project, base);
261
+
262
+ assert.equal(await ensureDbOpen(base), true, 'db import: ensureDbOpen creates authoritative DB');
263
+
264
+ invalidateAllCaches();
265
+ const before = await deriveState(base);
266
+ assert.equal(before.activeMilestone, null, 'db import: markdown-only migration is invisible before DB import');
267
+
268
+ const imported = await importWrittenMigrationToDb(base);
269
+ assert.deepStrictEqual(imported.hierarchy, { milestones: 1, slices: 2, tasks: 3 }, 'db import: hierarchy counts');
270
+
271
+ invalidateAllCaches();
272
+ const after = await deriveState(base);
273
+ assert.deepStrictEqual(after.phase, 'executing', 'db import: deriveState sees imported DB hierarchy');
274
+ assert.deepStrictEqual(after.activeMilestone?.id, 'M001', 'db import: active milestone');
275
+ assert.deepStrictEqual(after.activeSlice?.id, 'S02', 'db import: active slice');
276
+ assert.deepStrictEqual(after.activeTask?.id, 'T03', 'db import: active task');
277
+ } finally {
278
+ closeDatabase();
279
+ rmSync(base, { recursive: true, force: true });
280
+ }
281
+ });
282
+
283
+ test('Scenario 1c: DB import verification fails when preview counts do not match', async () => {
284
+ const base = mkdtempSync(join(tmpdir(), 'gsd-writer-db-check-'));
285
+ try {
286
+ const project = buildIncompleteProject();
287
+ await writeGSDDirectory(project, base);
288
+
289
+ const preview = generatePreview(project);
290
+ await assert.rejects(
291
+ () => importWrittenMigrationToDb(base, { ...preview, totalTasks: preview.totalTasks + 1 }),
292
+ /migration DB import verification failed: tasks 3\/4/,
293
+ );
294
+ assert.deepStrictEqual(getAllMilestones(), [], 'db import: failed verification rolls back hierarchy rewrite');
295
+ } finally {
296
+ closeDatabase();
297
+ rmSync(base, { recursive: true, force: true });
298
+ }
299
+ });
300
+
253
301
  // ─── Scenario 2: Fully complete project ────────────────────────────────
254
302
 
255
303
  test('Scenario 2: Fully complete project — deriveState phase', async () => {
@@ -0,0 +1,127 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { tmpdir } from "node:os";
5
+ import test from "node:test";
6
+
7
+ import { ensureDbOpen } from "../bootstrap/dynamic-tools.ts";
8
+ import {
9
+ _getAdapter,
10
+ closeDatabase,
11
+ getAllMilestones,
12
+ getSliceTasks,
13
+ } from "../gsd-db.ts";
14
+ import {
15
+ autoImportMarkdownHierarchyIfDbMismatch,
16
+ countMarkdownHierarchy,
17
+ } from "../migration-auto-check.ts";
18
+ import { writeGSDDirectory } from "../migrate/writer.ts";
19
+ import type { GSDProject } from "../migrate/types.ts";
20
+
21
+ function makeBase(): string {
22
+ return mkdtempSync(join(tmpdir(), "gsd-migration-auto-check-"));
23
+ }
24
+
25
+ function cleanup(base: string): void {
26
+ closeDatabase();
27
+ rmSync(base, { recursive: true, force: true });
28
+ }
29
+
30
+ function projectFixture(): GSDProject {
31
+ return {
32
+ projectContent: "# Legacy Project\n",
33
+ decisionsContent: "",
34
+ requirements: [],
35
+ milestones: [
36
+ {
37
+ id: "M001",
38
+ title: "Legacy Milestone",
39
+ vision: "Carry forward previous work",
40
+ successCriteria: ["Existing task is visible"],
41
+ research: null,
42
+ boundaryMap: [],
43
+ slices: [
44
+ {
45
+ id: "S01",
46
+ title: "Legacy Slice",
47
+ risk: "medium",
48
+ depends: [],
49
+ done: false,
50
+ demo: "Legacy slice demo",
51
+ goal: "Legacy slice demo",
52
+ research: null,
53
+ summary: null,
54
+ tasks: [
55
+ {
56
+ id: "T01",
57
+ title: "Legacy Task",
58
+ description: "Task carried from markdown",
59
+ done: false,
60
+ estimate: "",
61
+ files: ["src/index.ts"],
62
+ mustHaves: [],
63
+ summary: null,
64
+ },
65
+ ],
66
+ },
67
+ ],
68
+ },
69
+ ],
70
+ };
71
+ }
72
+
73
+ test("migration auto-check imports markdown hierarchy when DB is empty", async () => {
74
+ const base = makeBase();
75
+ try {
76
+ await writeGSDDirectory(projectFixture(), base);
77
+ assert.deepEqual(countMarkdownHierarchy(base), { milestones: 1, slices: 1, tasks: 1 });
78
+
79
+ assert.equal(await ensureDbOpen(base), true);
80
+ assert.equal(getAllMilestones().length, 0, "fresh authoritative DB starts empty");
81
+
82
+ const result = await autoImportMarkdownHierarchyIfDbMismatch(base);
83
+ assert.equal(result.action, "imported");
84
+ assert.equal(result.reason, "db-empty");
85
+ assert.deepEqual(result.afterDb, { milestones: 1, slices: 1, tasks: 1 });
86
+ assert.equal(getAllMilestones().length, 1);
87
+ assert.equal(getSliceTasks("M001", "S01").length, 1);
88
+ } finally {
89
+ cleanup(base);
90
+ }
91
+ });
92
+
93
+ test("migration auto-check repairs DB hierarchy count mismatch", async () => {
94
+ const base = makeBase();
95
+ try {
96
+ await writeGSDDirectory(projectFixture(), base);
97
+ await autoImportMarkdownHierarchyIfDbMismatch(base);
98
+
99
+ _getAdapter()!.prepare("DELETE FROM tasks WHERE milestone_id = ? AND slice_id = ? AND id = ?").run("M001", "S01", "T01");
100
+ assert.equal(getSliceTasks("M001", "S01").length, 0, "test fixture simulates stale DB task count");
101
+
102
+ const result = await autoImportMarkdownHierarchyIfDbMismatch(base);
103
+ assert.equal(result.action, "imported");
104
+ assert.equal(result.reason, "count-mismatch");
105
+ assert.deepEqual(result.beforeDb, { milestones: 1, slices: 1, tasks: 0 });
106
+ assert.deepEqual(result.afterDb, { milestones: 1, slices: 1, tasks: 1 });
107
+ assert.equal(getSliceTasks("M001", "S01").length, 1);
108
+ } finally {
109
+ cleanup(base);
110
+ }
111
+ });
112
+
113
+ test("migration auto-check leaves matching DB hierarchy alone", async () => {
114
+ const base = makeBase();
115
+ try {
116
+ await writeGSDDirectory(projectFixture(), base);
117
+ await autoImportMarkdownHierarchyIfDbMismatch(base);
118
+
119
+ const result = await autoImportMarkdownHierarchyIfDbMismatch(base);
120
+ assert.equal(result.action, "none");
121
+ assert.equal(result.reason, "in-sync");
122
+ assert.deepEqual(result.markdown, { milestones: 1, slices: 1, tasks: 1 });
123
+ assert.deepEqual(result.afterDb, { milestones: 1, slices: 1, tasks: 1 });
124
+ } finally {
125
+ cleanup(base);
126
+ }
127
+ });
@@ -172,6 +172,56 @@ test('handlePlanSlice rejects invalid payloads', async () => {
172
172
  }
173
173
  });
174
174
 
175
+ test('handlePlanSlice rejects absolute task IO paths outside the active worktree', async () => {
176
+ const base = makeTmpBase();
177
+ openDatabase(join(base, '.gsd', 'gsd.db'));
178
+
179
+ try {
180
+ seedParentSlice();
181
+ const outside = join(tmpdir(), 'outside-checkout', 'index.html');
182
+ const result = await handlePlanSlice({
183
+ ...validParams(),
184
+ tasks: [
185
+ {
186
+ ...validParams().tasks[0],
187
+ inputs: [outside],
188
+ expectedOutput: [outside],
189
+ },
190
+ ],
191
+ }, base);
192
+
193
+ assert.ok('error' in result);
194
+ assert.match(result.error, /validation failed: tasks\[0\]\.inputs contains absolute path outside working directory/);
195
+ assert.equal(getSliceTasks('M001', 'S02').length, 0, 'invalid planning IO must not persist tasks');
196
+ } finally {
197
+ cleanup(base);
198
+ }
199
+ });
200
+
201
+ test('handlePlanSlice accepts absolute task IO paths inside the active worktree', async () => {
202
+ const base = makeTmpBase();
203
+ openDatabase(join(base, '.gsd', 'gsd.db'));
204
+
205
+ try {
206
+ seedParentSlice();
207
+ const inside = join(base, 'index.html');
208
+ const result = await handlePlanSlice({
209
+ ...validParams(),
210
+ tasks: [
211
+ {
212
+ ...validParams().tasks[0],
213
+ inputs: [inside],
214
+ expectedOutput: [inside],
215
+ },
216
+ ],
217
+ }, base);
218
+
219
+ assert.ok(!('error' in result), `unexpected error: ${'error' in result ? result.error : ''}`);
220
+ } finally {
221
+ cleanup(base);
222
+ }
223
+ });
224
+
175
225
  test('handlePlanSlice rejects missing parent slice', async () => {
176
226
  const base = makeTmpBase();
177
227
  openDatabase(join(base, '.gsd', 'gsd.db'));
@@ -79,6 +79,27 @@ test('handlePlanTask rejects invalid payloads', async () => {
79
79
  }
80
80
  });
81
81
 
82
+ test('handlePlanTask rejects absolute task IO paths outside the active worktree', async () => {
83
+ const base = makeTmpBase();
84
+ openDatabase(join(base, '.gsd', 'gsd.db'));
85
+
86
+ try {
87
+ seedParent();
88
+ const outside = join(tmpdir(), 'outside-checkout', 'index.html');
89
+ const result = await handlePlanTask({
90
+ ...validParams(),
91
+ inputs: [outside],
92
+ expectedOutput: [outside],
93
+ }, base);
94
+
95
+ assert.ok('error' in result);
96
+ assert.match(result.error, /validation failed: inputs contains absolute path outside working directory/);
97
+ assert.equal(getTask('M001', 'S02', 'T02'), null, 'invalid planning IO must not persist the task');
98
+ } finally {
99
+ cleanup(base);
100
+ }
101
+ });
102
+
82
103
  test('handlePlanTask rejects missing parent slice', async () => {
83
104
  const base = makeTmpBase();
84
105
  openDatabase(join(base, '.gsd', 'gsd.db'));
@@ -0,0 +1,40 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { readFileSync, readdirSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { dirname } from "node:path";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+ const promptsDir = join(__dirname, "..", "prompts");
11
+
12
+ test("prompt templates do not reference legacy milestone-root .gsd paths", () => {
13
+ const offenders: string[] = [];
14
+ for (const file of readdirSync(promptsDir)) {
15
+ if (!file.endsWith(".md")) continue;
16
+ const content = readFileSync(join(promptsDir, file), "utf-8");
17
+ const legacyPatterns = [
18
+ /\.gsd\/\{\{(?:milestoneId|mid)\}\}\//g,
19
+ /\.gsd\/<milestone-id>\//g,
20
+ /\.gsd\/<ID>\//g,
21
+ ];
22
+ for (const pattern of legacyPatterns) {
23
+ if (pattern.test(content)) {
24
+ offenders.push(`${file}: ${pattern.source}`);
25
+ }
26
+ }
27
+ }
28
+
29
+ assert.deepEqual(
30
+ offenders,
31
+ [],
32
+ "Milestone artifacts must use .gsd/milestones/<MID>/..., not legacy .gsd/<MID>/...",
33
+ );
34
+ });
35
+
36
+ test("quick task prompt delegates commit policy to quick.ts", () => {
37
+ const content = readFileSync(join(promptsDir, "quick-task.md"), "utf-8");
38
+ assert.match(content, /\{\{commitInstruction\}\}/);
39
+ assert.doesNotMatch(content, /Stage only relevant files/);
40
+ });
@@ -0,0 +1,40 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdtempSync, mkdirSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ import { spawnSync } from "node:child_process";
7
+ import { buildQuickCommitInstruction } from "../quick.ts";
8
+
9
+ function git(cwd: string, args: string[]): void {
10
+ const result = spawnSync("git", args, { cwd, encoding: "utf-8" });
11
+ assert.equal(result.status, 0, result.stderr || result.stdout);
12
+ }
13
+
14
+ test("quick task commit instruction does not ask agents to stage external .gsd quick files", { skip: process.platform === "win32" }, () => {
15
+ const tempRoot = realpathSync(mkdtempSync(join(tmpdir(), "gsd-quick-ext-")));
16
+ const repo = join(tempRoot, "repo");
17
+ const externalGsd = join(tempRoot, "state");
18
+ mkdirSync(repo);
19
+ mkdirSync(externalGsd);
20
+
21
+ const previousCwd = process.cwd();
22
+ try {
23
+ git(repo, ["init"]);
24
+ git(repo, ["config", "user.email", "test@example.com"]);
25
+ git(repo, ["config", "user.name", "Test User"]);
26
+ writeFileSync(join(repo, "README.md"), "# Test\n", "utf-8");
27
+ git(repo, ["add", "README.md"]);
28
+ git(repo, ["commit", "-m", "init"]);
29
+ symlinkSync(externalGsd, join(repo, ".gsd"), "dir");
30
+
31
+ const instruction = buildQuickCommitInstruction(repo, join(repo, ".gsd"));
32
+
33
+ assert.match(instruction, /do not stage or commit `\.gsd\/quick\/\.\.\.`/);
34
+ assert.match(instruction, /nothing in the project repo to commit/);
35
+ assert.match(instruction, /Write the quick summary file directly/);
36
+ } finally {
37
+ process.chdir(previousCwd);
38
+ rmSync(tempRoot, { recursive: true, force: true });
39
+ }
40
+ });
@@ -0,0 +1,192 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { execFileSync } from "node:child_process";
4
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
+ import { join } from "node:path";
6
+ import { tmpdir } from "node:os";
7
+
8
+ import { buildCompleteMilestonePrompt, buildPlanMilestonePrompt } from "../auto-prompts.ts";
9
+
10
+ function git(cwd: string, args: string[]): string {
11
+ return execFileSync("git", args, {
12
+ cwd,
13
+ stdio: ["ignore", "pipe", "pipe"],
14
+ encoding: "utf-8",
15
+ env: { ...process.env, GIT_AUTHOR_NAME: "Test User", GIT_AUTHOR_EMAIL: "test@example.com", GIT_COMMITTER_NAME: "Test User", GIT_COMMITTER_EMAIL: "test@example.com" },
16
+ }).trim();
17
+ }
18
+
19
+ function makeRepo(files: Record<string, string>): string {
20
+ const base = mkdtempSync(join(tmpdir(), "gsd-right-size-"));
21
+ git(base, ["init", "-b", "main"]);
22
+ mkdirSync(join(base, ".gsd", "milestones", "M001"), { recursive: true });
23
+ writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-CONTEXT.md"), "# Context\n\nTest milestone.");
24
+ for (const [path, content] of Object.entries(files)) {
25
+ const abs = join(base, path);
26
+ mkdirSync(join(abs, ".."), { recursive: true });
27
+ writeFileSync(abs, content);
28
+ }
29
+ git(base, ["add", "."]);
30
+ git(base, ["commit", "-m", "init"]);
31
+ return base;
32
+ }
33
+
34
+ function writeCompleteMilestoneFiles(base: string, validation: string): void {
35
+ const dir = join(base, ".gsd", "milestones", "M001");
36
+ mkdirSync(join(dir, "slices", "S01"), { recursive: true });
37
+ writeFileSync(join(dir, "M001-ROADMAP.md"), "# M001\n\n## Slices\n- [x] **S01: One** `risk:low` `depends:[]`\n > Done\n");
38
+ writeFileSync(join(dir, "M001-VALIDATION.md"), validation);
39
+ writeFileSync(join(dir, "slices", "S01", "S01-SUMMARY.md"), "# S01 Summary\n\n**Verification:** passed\n");
40
+ }
41
+
42
+ function validationMetadata(): string {
43
+ return [
44
+ "validation_metadata:",
45
+ " covered_artifacts:",
46
+ " - `.gsd/milestones/M001/M001-VALIDATION.md`",
47
+ " - `.gsd/milestones/M001/M001-ROADMAP.md`",
48
+ " - `.gsd/milestones/M001/slices/S01/S01-SUMMARY.md`",
49
+ ].join("\n");
50
+ }
51
+
52
+ test("plan-milestone prompt includes tiny untyped project classification and one-slice guidance", async () => {
53
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
54
+ try {
55
+ const prompt = await buildPlanMilestonePrompt("M001", "Polish static page", base, "minimal");
56
+ assert.match(prompt, /\*\*Kind:\*\* untyped-existing/);
57
+ assert.match(prompt, /\*\*Content files:\*\* 1/);
58
+ assert.match(prompt, /`index\.html`/);
59
+ assert.match(prompt, /Prefer exactly one slice/);
60
+ } finally {
61
+ rmSync(base, { recursive: true, force: true });
62
+ }
63
+ });
64
+
65
+ test("plan-milestone prompt includes small untyped project 1-2 slice guidance", async () => {
66
+ const base = makeRepo({
67
+ "index.html": "html",
68
+ "README.md": "readme",
69
+ "styles.css": "body {}",
70
+ });
71
+ try {
72
+ const prompt = await buildPlanMilestonePrompt("M001", "Polish static files", base, "minimal");
73
+ assert.match(prompt, /\*\*Kind:\*\* untyped-existing/);
74
+ assert.match(prompt, /\*\*Content files:\*\* 3/);
75
+ assert.match(prompt, /Prefer 1-2 slices/);
76
+ } finally {
77
+ rmSync(base, { recursive: true, force: true });
78
+ }
79
+ });
80
+
81
+ test("plan-milestone prompt keeps normal guidance for typed projects", async () => {
82
+ const base = makeRepo({
83
+ "package.json": "{\"scripts\":{\"test\":\"node --test\"}}\n",
84
+ "src/index.js": "console.log('ok');\n",
85
+ });
86
+ try {
87
+ const prompt = await buildPlanMilestonePrompt("M001", "Update app", base, "minimal");
88
+ assert.match(prompt, /\*\*Kind:\*\* typed-existing/);
89
+ assert.match(prompt, /Use normal ecosystem-aware planning guidance/);
90
+ assert.doesNotMatch(prompt, /Prefer exactly one slice/);
91
+ } finally {
92
+ rmSync(base, { recursive: true, force: true });
93
+ }
94
+ });
95
+
96
+ test("workflow docs no longer contain blanket 4-10 slice guidance", () => {
97
+ const docs = readFileSync(join(process.cwd(), "src", "resources", "GSD-WORKFLOW.md"), "utf-8");
98
+ assert.doesNotMatch(docs, /4-10 slices/);
99
+ assert.match(docs, /1-10 slices/);
100
+ assert.match(docs, /single-file/);
101
+ });
102
+
103
+ test("prompt templates carry right-sized planning and closeout mode guidance", () => {
104
+ const planTemplate = readFileSync(join(process.cwd(), "src", "resources", "extensions", "gsd", "prompts", "plan-milestone.md"), "utf-8");
105
+ const completeTemplate = readFileSync(join(process.cwd(), "src", "resources", "extensions", "gsd", "prompts", "complete-milestone.md"), "utf-8");
106
+
107
+ assert.match(planTemplate, /Use 1-10 slices, sized to the work/);
108
+ assert.match(planTemplate, /tiny\/single-file\/static work should usually be one slice/);
109
+ assert.match(planTemplate, /untyped-existing/);
110
+ assert.match(completeTemplate, /Closeout Review Mode/);
111
+ assert.match(completeTemplate, /passing validation artifact is present/);
112
+ assert.doesNotMatch(completeTemplate, /^### Delegate Review Work/m);
113
+ });
114
+
115
+ test("complete-milestone prompt trusts passing validation artifact", async () => {
116
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
117
+ try {
118
+ writeCompleteMilestoneFiles(base, `---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\n${validationMetadata()}\n\nAll checks passed.`);
119
+ const prompt = await buildCompleteMilestonePrompt("M001", "Polish static page", base, "minimal");
120
+ assert.match(prompt, /Passing Validation Artifact/);
121
+ assert.match(prompt, /Treat it as authoritative/);
122
+ assert.match(prompt, /Do not delegate fresh reviewer\/security\/tester audits/);
123
+ assert.match(prompt, /All checks passed/);
124
+ } finally {
125
+ rmSync(base, { recursive: true, force: true });
126
+ }
127
+ });
128
+
129
+ test("complete-milestone prompt trusts centralized markdown body pass verdict", async () => {
130
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
131
+ try {
132
+ writeCompleteMilestoneFiles(base, `# Validation\n\n**Verdict:** PASS\n\n${validationMetadata()}\n\nAll checks passed.`);
133
+ const prompt = await buildCompleteMilestonePrompt("M001", "Polish static page", base, "minimal");
134
+ assert.match(prompt, /Passing Validation Artifact/);
135
+ assert.match(prompt, /Treat it as authoritative/);
136
+ assert.match(prompt, /Do not delegate fresh reviewer\/security\/tester audits/);
137
+ } finally {
138
+ rmSync(base, { recursive: true, force: true });
139
+ }
140
+ });
141
+
142
+ test("complete-milestone prompt does not trust stale pass validation without metadata", async () => {
143
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
144
+ try {
145
+ writeCompleteMilestoneFiles(base, "---\nverdict: pass\nremediation_round: 0\n---\n\n# Validation\nAll checks passed.");
146
+ const prompt = await buildCompleteMilestonePrompt("M001", "Polish static page", base, "minimal");
147
+ assert.match(prompt, /Validation Requires Attention/);
148
+ assert.match(prompt, /missing freshness metadata/);
149
+ assert.doesNotMatch(prompt, /Passing Validation Artifact/);
150
+ } finally {
151
+ rmSync(base, { recursive: true, force: true });
152
+ }
153
+ });
154
+
155
+ test("complete-milestone prompt does not trust pass validation missing current summary coverage", async () => {
156
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
157
+ try {
158
+ writeCompleteMilestoneFiles(base, [
159
+ "---",
160
+ "verdict: pass",
161
+ "remediation_round: 0",
162
+ "---",
163
+ "",
164
+ "# Validation",
165
+ "validation_metadata:",
166
+ " covered_artifacts:",
167
+ " - `.gsd/milestones/M001/M001-VALIDATION.md`",
168
+ " - `.gsd/milestones/M001/M001-ROADMAP.md`",
169
+ "",
170
+ "All checks passed.",
171
+ ].join("\n"));
172
+ const prompt = await buildCompleteMilestonePrompt("M001", "Polish static page", base, "minimal");
173
+ assert.match(prompt, /Validation Requires Attention/);
174
+ assert.match(prompt, /does not cover current milestone artifacts/);
175
+ assert.doesNotMatch(prompt, /Passing Validation Artifact/);
176
+ } finally {
177
+ rmSync(base, { recursive: true, force: true });
178
+ }
179
+ });
180
+
181
+ test("complete-milestone prompt keeps deeper review path without passing validation", async () => {
182
+ const base = makeRepo({ "index.html": "<!doctype html>\n<title>Test</title>\n" });
183
+ try {
184
+ writeCompleteMilestoneFiles(base, "---\nverdict: needs-attention\nremediation_round: 0\n---\n\n# Validation\nFix gaps.");
185
+ const prompt = await buildCompleteMilestonePrompt("M001", "Polish static page", base, "minimal");
186
+ assert.match(prompt, /Validation Requires Attention/);
187
+ assert.match(prompt, /verdict `needs-attention`/);
188
+ assert.match(prompt, /Use `subagent` for review work needing fresh context/i);
189
+ } finally {
190
+ rmSync(base, { recursive: true, force: true });
191
+ }
192
+ });
@@ -144,6 +144,18 @@ test("safety-harness-bug2-race: bash evidence survives mid-unit reset between to
144
144
  assert.ok(bash[0].outputSnippet.includes("found"), "output snippet captured");
145
145
  });
146
146
 
147
+ test("safety-harness: gsd_exec counts as execution evidence", () => {
148
+ resetEvidence();
149
+
150
+ recordToolCall("tc-exec-1", "gsd_exec", { command: "grep -n render index.html" });
151
+ recordToolResult("tc-exec-1", "gsd_exec", "Command exited with code 0\n1:render\n", false);
152
+
153
+ const bash = getEvidence().filter((e): e is BashEvidence => e.kind === "bash");
154
+ assert.equal(bash.length, 1, "gsd_exec must be tracked as execution evidence");
155
+ assert.equal(bash[0].command, "grep -n render index.html");
156
+ assert.equal(bash[0].exitCode, 0);
157
+ });
158
+
147
159
  // ─── Bug 3: git diff HEAD~1 scope check ─────────────────────────────────────
148
160
 
149
161
  test("safety-harness-bug3: validateFileChanges works on initial commit (no HEAD~1)", (t) => {
@@ -237,3 +249,20 @@ test("safety-harness-bug3: validateFileChanges works on merge commit", (t) => {
237
249
  // Must produce a valid result without throwing
238
250
  assert.ok(audit !== null, "audit must be produced for merge commit repo");
239
251
  });
252
+
253
+ test("safety-harness: planned changed file avoids unexpected-file warning", (t) => {
254
+ const base = mkdtempSync(join(tmpdir(), "gsd-planned-file-"));
255
+ t.after(() => rmSync(base, { recursive: true, force: true }));
256
+
257
+ execFileSync("git", ["init"], { cwd: base });
258
+ execFileSync("git", ["config", "user.email", "test@example.com"], { cwd: base });
259
+ execFileSync("git", ["config", "user.name", "Test User"], { cwd: base });
260
+ writeFileSync(join(base, "index.html"), "<main></main>\n");
261
+ execFileSync("git", ["add", "index.html"], { cwd: base });
262
+ execFileSync("git", ["commit", "-m", "add static app"], { cwd: base });
263
+
264
+ const audit = validateFileChanges(base, [], ["index.html"]);
265
+ assert.ok(audit !== null, "audit must be produced");
266
+ assert.deepEqual(audit!.unexpectedFiles, [], "planned index.html must not be unexpected");
267
+ assert.deepEqual(audit!.missingFiles, [], "planned index.html must not be missing");
268
+ });
@@ -47,6 +47,9 @@ test("guided-flow complete branch offers a chooser for next milestone or status"
47
47
  const branchChunk = guidedFlowSource.slice(branchIdx, nextBranchIdx === -1 ? branchIdx + 1600 : nextBranchIdx);
48
48
 
49
49
  assert.match(branchChunk, /showNextAction\(/, "complete branch should present a chooser");
50
+ assert.match(branchChunk, /id:\s*"quick_task"/, "complete branch should offer quick task before milestone planning");
51
+ assert.match(branchChunk, /Do a small bounded task without opening a milestone/, "quick task action should explain that it avoids milestones");
52
+ assert.match(branchChunk, /recommended:\s*true/, "quick task action should be the recommended complete-state action");
50
53
  assert.match(branchChunk, /findMilestoneIds\(basePath\)/, "complete branch should compute the next milestone id");
51
54
  assert.match(
52
55
  branchChunk,
@@ -56,6 +59,41 @@ test("guided-flow complete branch offers a chooser for next milestone or status"
56
59
  assert.match(branchChunk, /dispatchWorkflow\(pi, await prepareAndBuildDiscussPrompt\(/, "complete branch should dispatch the prepared discuss prompt");
57
60
  });
58
61
 
62
+ test("guided-flow quick task choices prompt for text and route through /gsd quick", () => {
63
+ const guidedFlowSource = readFileSync(join(import.meta.dirname, "..", "guided-flow.ts"), "utf-8");
64
+
65
+ assert.match(
66
+ guidedFlowSource,
67
+ /async function runQuickTaskChoice\([\s\S]*ctx\.ui\.input\("Quick task"[\s\S]*await import\("\.\/quick\.js"\)[\s\S]*await handleQuick\(task,\s*ctx,\s*pi\)/,
68
+ "quick task chooser should prompt for task text and route through handleQuick",
69
+ );
70
+
71
+ const notifyOnlyPattern = /if \(choice === "quick_task"\) \{\s*ctx\.ui\.notify\("Run \/gsd quick <task>/;
72
+ assert.doesNotMatch(
73
+ guidedFlowSource,
74
+ notifyOnlyPattern,
75
+ "quick task chooser must not merely print instructions after selection",
76
+ );
77
+
78
+ const quickChoiceCalls = guidedFlowSource.match(/if \(choice === "quick_task"\) \{\s*await runQuickTaskChoice\(ctx,\s*pi\);/g) ?? [];
79
+ assert.equal(quickChoiceCalls.length, 3, "all guided-flow quick task choices should dispatch the helper");
80
+ });
81
+
82
+ test("dispatcher routes multi-word freeform /gsd input through /gsd do", () => {
83
+ const dispatcherSource = readFileSync(join(import.meta.dirname, "..", "commands", "dispatcher.ts"), "utf-8");
84
+
85
+ assert.match(
86
+ dispatcherSource,
87
+ /if\s*\(trimmed\.includes\(" "\)\)\s*\{[\s\S]*handleDo\(trimmed,\s*ctx,\s*pi\)/,
88
+ "dispatcher should treat multi-word unknown input as natural-language /gsd do work",
89
+ );
90
+ assert.match(
91
+ dispatcherSource,
92
+ /Unknown: \/gsd/,
93
+ "single-token unknown commands should still report the normal unknown-command warning",
94
+ );
95
+ });
96
+
59
97
  test("guided-flow needs-discussion skip branch opens the project DB before reserving a new milestone", () => {
60
98
  const guidedFlowSource = readFileSync(join(import.meta.dirname, "..", "guided-flow.ts"), "utf-8");
61
99
  const laterDbOpenIdx = guidedFlowSource.indexOf("// Ensure DB is open before querying slices (#2560).");