gsd-pi 2.80.0-dev.96f4d89d5 → 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 (239) 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-recovery.js +154 -8
  9. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  10. package/dist/resources/extensions/gsd/auto.js +16 -3
  11. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
  12. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +129 -1
  13. package/dist/resources/extensions/gsd/clean-root-preflight.js +42 -4
  14. package/dist/resources/extensions/gsd/commands/dispatcher.js +5 -0
  15. package/dist/resources/extensions/gsd/commands-extract-learnings.js +17 -12
  16. package/dist/resources/extensions/gsd/crash-recovery.js +56 -10
  17. package/dist/resources/extensions/gsd/db-base-schema.js +14 -0
  18. package/dist/resources/extensions/gsd/db-migration-steps.js +16 -0
  19. package/dist/resources/extensions/gsd/detection.js +106 -0
  20. package/dist/resources/extensions/gsd/gsd-db.js +102 -2
  21. package/dist/resources/extensions/gsd/guided-flow.js +63 -12
  22. package/dist/resources/extensions/gsd/migrate/command.js +40 -1
  23. package/dist/resources/extensions/gsd/migration-auto-check.js +87 -0
  24. package/dist/resources/extensions/gsd/planning-path-scope.js +26 -0
  25. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  26. package/dist/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  28. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  29. package/dist/resources/extensions/gsd/prompts/quick-task.md +1 -5
  30. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  31. package/dist/resources/extensions/gsd/quick.js +34 -2
  32. package/dist/resources/extensions/gsd/safety/evidence-collector.js +10 -2
  33. package/dist/resources/extensions/gsd/tools/plan-slice.js +9 -0
  34. package/dist/resources/extensions/gsd/tools/plan-task.js +9 -0
  35. package/dist/resources/extensions/gsd/unit-runtime.js +11 -0
  36. package/dist/resources/extensions/gsd/worktree-manager.js +16 -14
  37. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  38. package/dist/web/standalone/.next/BUILD_ID +1 -1
  39. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  40. package/dist/web/standalone/.next/build-manifest.json +2 -2
  41. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  42. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.html +1 -1
  59. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  66. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  68. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  69. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  70. package/package.json +4 -2
  71. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  72. package/packages/pi-coding-agent/dist/core/agent-session.js +3 -0
  73. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  74. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +76 -0
  75. package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
  76. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +11 -0
  77. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  78. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +9 -0
  79. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  80. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts +2 -0
  81. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.d.ts.map +1 -0
  82. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js +103 -0
  83. package/packages/pi-coding-agent/dist/core/compaction-threshold.test.js.map +1 -0
  84. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts +15 -0
  85. package/packages/pi-coding-agent/dist/core/db-snapshot.d.ts.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/db-snapshot.js +66 -0
  87. package/packages/pi-coding-agent/dist/core/db-snapshot.js.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts +2 -0
  89. package/packages/pi-coding-agent/dist/core/db-snapshot.test.d.ts.map +1 -0
  90. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js +24 -0
  91. package/packages/pi-coding-agent/dist/core/db-snapshot.test.js.map +1 -0
  92. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +3 -0
  93. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/extensions/runner.js +17 -1
  95. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js +99 -0
  97. package/packages/pi-coding-agent/dist/core/extensions/runner.test.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  99. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  101. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +20 -0
  102. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  103. package/packages/pi-coding-agent/dist/core/settings-manager.js +25 -0
  104. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  105. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js +6 -4
  106. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +50 -19
  108. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  109. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js +2 -2
  110. package/packages/pi-coding-agent/dist/modes/interactive/components/adaptive-layout.js.map +1 -1
  111. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/modes/interactive/components/assistant-message.js.map +1 -1
  113. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.d.ts.map +1 -1
  114. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js +2 -1
  115. package/packages/pi-coding-agent/dist/modes/interactive/components/chat-frame.js.map +1 -1
  116. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +7 -6
  117. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +13 -0
  119. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  120. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +77 -13
  121. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  122. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  123. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +68 -2
  124. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  125. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js +53 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.test.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +3 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  131. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js +36 -27
  132. package/packages/pi-coding-agent/dist/modes/interactive/theme/themes.js.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js +21 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/tui-mode.test.js.map +1 -1
  135. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts +2 -0
  136. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.d.ts.map +1 -0
  137. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js +10 -0
  138. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage-safety-guard.test.js.map +1 -0
  139. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js +3 -2
  141. package/packages/pi-coding-agent/dist/resources/extensions/memory/storage.js.map +1 -1
  142. package/packages/pi-coding-agent/src/core/agent-session.ts +3 -0
  143. package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +89 -0
  144. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +18 -0
  145. package/packages/pi-coding-agent/src/core/compaction-threshold.test.ts +121 -0
  146. package/packages/pi-coding-agent/src/core/db-snapshot.test.ts +32 -0
  147. package/packages/pi-coding-agent/src/core/db-snapshot.ts +66 -0
  148. package/packages/pi-coding-agent/src/core/extensions/runner.test.ts +110 -0
  149. package/packages/pi-coding-agent/src/core/extensions/runner.ts +19 -1
  150. package/packages/pi-coding-agent/src/core/extensions/types.ts +7 -0
  151. package/packages/pi-coding-agent/src/core/settings-manager.ts +39 -1
  152. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/chat-frame-compaction-tone.test.ts +7 -5
  153. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +68 -19
  154. package/packages/pi-coding-agent/src/modes/interactive/components/adaptive-layout.ts +2 -2
  155. package/packages/pi-coding-agent/src/modes/interactive/components/assistant-message.ts +1 -0
  156. package/packages/pi-coding-agent/src/modes/interactive/components/chat-frame.ts +2 -1
  157. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +10 -9
  158. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +88 -13
  159. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.test.ts +56 -0
  160. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +91 -3
  161. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +3 -0
  162. package/packages/pi-coding-agent/src/modes/interactive/theme/themes.ts +36 -27
  163. package/packages/pi-coding-agent/src/modes/interactive/tui-mode.test.ts +25 -0
  164. package/packages/pi-coding-agent/src/resources/extensions/memory/storage-safety-guard.test.ts +14 -0
  165. package/packages/pi-coding-agent/src/resources/extensions/memory/storage.ts +3 -2
  166. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  167. package/packages/pi-tui/dist/tui.d.ts.map +1 -1
  168. package/packages/pi-tui/dist/tui.js +17 -7
  169. package/packages/pi-tui/dist/tui.js.map +1 -1
  170. package/packages/pi-tui/src/tui.ts +19 -7
  171. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  172. package/pkg/dist/modes/interactive/theme/themes.d.ts.map +1 -1
  173. package/pkg/dist/modes/interactive/theme/themes.js +36 -27
  174. package/pkg/dist/modes/interactive/theme/themes.js.map +1 -1
  175. package/src/resources/GSD-WORKFLOW.md +2 -2
  176. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  177. package/src/resources/extensions/gsd/auto/loop.ts +50 -8
  178. package/src/resources/extensions/gsd/auto/phases.ts +42 -28
  179. package/src/resources/extensions/gsd/auto-dispatch.ts +17 -0
  180. package/src/resources/extensions/gsd/auto-post-unit.ts +10 -10
  181. package/src/resources/extensions/gsd/auto-prompts.ts +116 -1
  182. package/src/resources/extensions/gsd/auto-recovery.ts +153 -7
  183. package/src/resources/extensions/gsd/auto-start.ts +7 -6
  184. package/src/resources/extensions/gsd/auto.ts +23 -3
  185. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +39 -1
  186. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +135 -1
  187. package/src/resources/extensions/gsd/clean-root-preflight.ts +41 -3
  188. package/src/resources/extensions/gsd/commands/dispatcher.ts +6 -0
  189. package/src/resources/extensions/gsd/commands-extract-learnings.ts +17 -12
  190. package/src/resources/extensions/gsd/crash-recovery.ts +67 -10
  191. package/src/resources/extensions/gsd/db-base-schema.ts +15 -0
  192. package/src/resources/extensions/gsd/db-migration-steps.ts +17 -0
  193. package/src/resources/extensions/gsd/detection.ts +128 -0
  194. package/src/resources/extensions/gsd/gsd-db.ts +119 -1
  195. package/src/resources/extensions/gsd/guided-flow.ts +66 -12
  196. package/src/resources/extensions/gsd/migrate/command.ts +47 -1
  197. package/src/resources/extensions/gsd/migration-auto-check.ts +129 -0
  198. package/src/resources/extensions/gsd/planning-path-scope.ts +35 -0
  199. package/src/resources/extensions/gsd/prompts/complete-milestone.md +7 -8
  200. package/src/resources/extensions/gsd/prompts/parallel-research-slices.md +1 -1
  201. package/src/resources/extensions/gsd/prompts/plan-milestone.md +3 -1
  202. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  203. package/src/resources/extensions/gsd/prompts/quick-task.md +1 -5
  204. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -2
  205. package/src/resources/extensions/gsd/quick.ts +37 -2
  206. package/src/resources/extensions/gsd/safety/evidence-collector.ts +11 -2
  207. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -1
  208. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +170 -1
  209. package/src/resources/extensions/gsd/tests/clean-root-preflight.test.ts +88 -2
  210. package/src/resources/extensions/gsd/tests/commands-extract-learnings.test.ts +9 -0
  211. package/src/resources/extensions/gsd/tests/crash-handler-secondary.test.ts +55 -0
  212. package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +22 -0
  213. package/src/resources/extensions/gsd/tests/db-schema-metadata.test.ts +2 -2
  214. package/src/resources/extensions/gsd/tests/detection.test.ts +140 -0
  215. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +44 -0
  216. package/src/resources/extensions/gsd/tests/has-pending-deep-stage.test.ts +33 -1
  217. package/src/resources/extensions/gsd/tests/migrate-writer-integration.test.ts +48 -0
  218. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +127 -0
  219. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +50 -0
  220. package/src/resources/extensions/gsd/tests/plan-task.test.ts +21 -0
  221. package/src/resources/extensions/gsd/tests/prompt-path-audit.test.ts +40 -0
  222. package/src/resources/extensions/gsd/tests/quick-external-gsd.test.ts +40 -0
  223. package/src/resources/extensions/gsd/tests/right-sized-workflow-prompts.test.ts +192 -0
  224. package/src/resources/extensions/gsd/tests/safety-harness-false-positives.test.ts +29 -0
  225. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +38 -0
  226. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +63 -2
  227. package/src/resources/extensions/gsd/tests/status-db-open.test.ts +9 -0
  228. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +7 -0
  229. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +1 -1
  230. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +37 -6
  231. package/src/resources/extensions/gsd/tests/worktree-manager.test.ts +7 -0
  232. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +9 -2
  233. package/src/resources/extensions/gsd/tests/worktree-write-gate.test.ts +179 -0
  234. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -0
  235. package/src/resources/extensions/gsd/tools/plan-task.ts +10 -0
  236. package/src/resources/extensions/gsd/unit-runtime.ts +14 -0
  237. package/src/resources/extensions/gsd/worktree-manager.ts +15 -4
  238. /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_buildManifest.js +0 -0
  239. /package/dist/web/standalone/.next/static/{y7d--cnc6Cibuva-Lzzw_ → FU0iL9dhdc2IBzEYBDOlY}/_ssgManifest.js +0 -0
@@ -0,0 +1,35 @@
1
+ import { isAbsolute, relative, resolve } from "node:path";
2
+ import { normalizePlannedFileReference } from "./files.js";
3
+
4
+ export interface PlanningPathScopeField {
5
+ field: string;
6
+ values: string[];
7
+ }
8
+
9
+ function isInsideBase(basePath: string, candidate: string): boolean {
10
+ const base = resolve(basePath);
11
+ const abs = resolve(candidate);
12
+ const rel = relative(base, abs);
13
+ return rel === "" || (!!rel && !rel.startsWith("..") && !isAbsolute(rel));
14
+ }
15
+
16
+ /**
17
+ * Planning IO fields are execution contracts. Absolute paths are only safe when
18
+ * they stay inside the active working directory; in worktree mode, an absolute
19
+ * path to the original checkout makes executors edit the wrong tree.
20
+ */
21
+ export function validatePlanningPathScope(
22
+ basePath: string,
23
+ fields: PlanningPathScopeField[],
24
+ ): string | null {
25
+ for (const { field, values } of fields) {
26
+ for (const raw of values) {
27
+ const candidate = normalizePlannedFileReference(raw);
28
+ if (!isAbsolute(candidate)) continue;
29
+ if (isInsideBase(basePath, candidate)) continue;
30
+ return `${field} contains absolute path outside working directory: ${candidate}. Use a path relative to ${basePath}.`;
31
+ }
32
+ }
33
+
34
+ return null;
35
+ }
@@ -16,15 +16,14 @@ Start with what the excerpts give you. Read full files when the section heads si
16
16
 
17
17
  **On-demand Read ordering:** Complete all slice SUMMARY Reads you need for cross-slice synthesis, the Decision Re-evaluation table, and LEARNINGS **before** calling `gsd_complete_milestone` (step 12). Once that tool runs, the milestone is marked complete in the DB, so it must be the final persistent milestone-closeout write.
18
18
 
19
- ### Delegate Review Work
19
+ ### Closeout Review Mode
20
20
 
21
- Use `subagent` for review work needing fresh context, before drafting LEARNINGS:
21
+ The inlined context includes a validation status block.
22
22
 
23
- - Cross-slice integrations or new public APIs -> **reviewer** with milestone diff and roadmap.
24
- - Auth, network, parsing, file IO, shell exec, or crypto -> **security** audit.
25
- - Significant tests added or changed -> **tester** coverage check against success criteria.
23
+ - If it says a passing validation artifact is present, treat that artifact as authoritative for success criteria, requirement coverage, verification classes, and cross-slice integration. Do not delegate fresh reviewer/security/tester audits unless the validation artifact is internally inconsistent with the inlined summaries.
24
+ - If validation is missing, stale, non-pass, or internally inconsistent, use `subagent` for review work needing fresh context before drafting LEARNINGS: cross-slice integrations or new public APIs -> **reviewer**; auth, network, parsing, file IO, shell exec, or crypto -> **security**; significant tests added or changed -> **tester**.
26
25
 
27
- Subagents report only; they do not write user source. Fold findings into Decision Re-evaluation and LEARNINGS before completion.
26
+ Subagents report only; they do not write user source. Fold any findings into Decision Re-evaluation and LEARNINGS before completion.
28
27
 
29
28
  {{inlinedContext}}
30
29
 
@@ -33,8 +32,8 @@ Subagents report only; they do not write user source. Fold findings into Decisio
33
32
  1. Use the **Milestone Summary** output template from the inlined context above
34
33
  2. {{skillActivation}}
35
34
  3. **Verify code changes exist.** Compare milestone work against the integration branch (`main`, `master`, or recorded branch), using merge-base as older revision and `HEAD` as newer. If the diff lists non-`.gsd/` files, pass. If `HEAD` equals the integration branch/merge-base, treat it as a self-diff retry: inspect milestone-scoped commit evidence (`GSD-Unit: {{milestoneId}}` or production `GSD-Task: Sxx/Tyy` trailers touching `.gsd/milestones/{{milestoneId}}/`) and verify those commits touched non-`.gsd/` files. Record **verification failure** only when neither source shows implementation files.
36
- 4. Verify every **success criterion** from `{{roadmapPath}}` with evidence from summaries, tests, or observable behavior. Record unmet criteria as **verification failure**.
37
- 5. Verify **definition of done**: all slices `[x]`, summaries exist, and integrations work. Record unmet items as **verification failure**.
35
+ 4. Verify every **success criterion** from `{{roadmapPath}}`. If passing validation is present, summarize the validation evidence instead of re-auditing it; otherwise verify with evidence from summaries, tests, or observable behavior. Record unmet criteria as **verification failure**.
36
+ 5. Verify **definition of done**: all slices `[x]`, summaries exist, and integrations work. If passing validation is present, trust its integration/verification verdict unless inconsistent with current artifacts. Record unmet items as **verification failure**.
38
37
  6. If the roadmap includes a **Horizontal Checklist**, verify each item and note unchecked items in the summary.
39
38
  7. Fill the **Decision Re-evaluation** table: compare each key `.gsd/DECISIONS.md` decision from this milestone with what shipped, and flag decisions to revisit.
40
39
  8. Validate **requirement status transitions**. For each changed requirement, confirm evidence supports the new status. Requirements may move between Active, Validated, Deferred, Blocked, or Out of Scope only with proof.
@@ -16,7 +16,7 @@ Dispatch ALL slices simultaneously using the `subagent` tool in **parallel mode*
16
16
 
17
17
  1. Call `subagent` with `tasks: [...]` containing one entry per slice below
18
18
  2. Wait for ALL subagents to complete
19
- 3. Verify each slice's RESEARCH file was written (check the `.gsd/{{mid}}/` directory)
19
+ 3. Verify each slice's RESEARCH file was written (check `.gsd/milestones/{{mid}}/slices/<slice-id>/`)
20
20
  4. If a subagent failed to write its RESEARCH file, retry it **once** individually
21
21
  5. If it fails a second time, write a partial RESEARCH file for that slice with a `## BLOCKER` section explaining the failure — do NOT retry again
22
22
  6. Report which slices completed research and which (if any) needed a blocker note
@@ -48,7 +48,7 @@ Narrate decomposition reasoning in complete sentences: grouping, risk order, ver
48
48
  Then:
49
49
  1. Use the **Roadmap** output template from the inlined context above
50
50
  2. {{skillActivation}}
51
- 3. Create only as many demoable vertical slices as the work genuinely needs.
51
+ 3. Create only as many demoable vertical slices as the work genuinely needs. Use 1-10 slices, sized to the work; tiny/single-file/static work should usually be one slice.
52
52
  4. Order by risk, high-risk first.
53
53
  5. Call `gsd_plan_milestone` to persist milestone fields, slice rows, and **Horizontal Checklist** through the DB-backed path. Fill checklist concerns considered during planning: requirements, decisions, shutdown, revenue, auth, shared resources, reconnection. Omit for trivial milestones. Do **not** write `{{outputPath}}`, `ROADMAP.md`, or other planning artifacts manually; the tool owns rendering and persistence.
54
54
  6. If planning produced structural decisions (slice ordering, technology choices, scope exclusions), call `gsd_decision_save` for each; the tool assigns IDs and regenerates `.gsd/DECISIONS.md`.
@@ -78,6 +78,8 @@ Apply these when decomposing and ordering slices:
78
78
  - Ship features, not proofs; use clearly marked realistic stubs only when necessary.
79
79
  - **Dependency format is comma-separated, never range syntax.** Write `depends:[S01,S02,S03]`, not `depends:[S01-S03]`.
80
80
  - Roadmap ambition must match the milestone; right-size decomposition.
81
+ - Missing ecosystem markers are not a reason to over-plan. If Project Classification says `untyped-existing`, treat the listed content files as the project surface and use generic file-level workflow guidance.
82
+ - For `untyped-existing` projects with 1-2 content files, prefer exactly one slice unless the request clearly spans multiple independent user-visible capabilities. For 3-5 content files, prefer 1-2 slices.
81
83
 
82
84
  ## Progressive Planning (ADR-011)
83
85
 
@@ -39,7 +39,7 @@ If slice research is inlined, trust it. Explore enough code to confirm paths, bo
39
39
  5. Define slice verification before tasks. Non-trivial slices need real tests or executable assertions; boundary contracts need contract-exercising checks. Tests must not read .gitignore/gitignored paths such as `.gsd/`, `.planning/`, or `.audits/`.
40
40
  6. Include Threat Surface (Q3), Requirement Impact (Q4), proof level, observability, integration closure, Failure Modes (Q5), Load Profile (Q6), and Negative Tests (Q7) only where applicable.
41
41
  7. Right-size tasks. Simple slices can be one task; split only when context, ownership, or verification boundaries justify it.
42
- 8. Each task needs a concrete title, Why / Files / Do / Verify / Done when, plus task-plan description, steps, must-haves, verification, inputs, and expected output. Inputs and Expected Output must include concrete backtick-wrapped paths; each task needs at least one output path.
42
+ 8. Each task needs a concrete title, Why / Files / Do / Verify / Done when, plus task-plan description, steps, must-haves, verification, inputs, and expected output. Inputs and Expected Output must include concrete backtick-wrapped paths; each task needs at least one output path. Use paths relative to `{{workingDirectory}}`; do not put absolute paths to the original checkout or any directory outside `{{workingDirectory}}` in `files`, `inputs`, `expectedOutput`, or verification commands.
43
43
  9. Persist with `gsd_plan_slice` using goal, successCriteria, optional proofLevel/integrationClosure/observabilityImpact, and tasks. `gsd_plan_slice` handles task persistence transactionally and renders `{{outputPath}}` plus task plans; do not call `gsd_plan_task`. The DB-backed tool is the canonical write path. Do **not** rely on direct `PLAN.md` writes as the source of truth.
44
44
  10. Self-audit before finishing: goal/demo closure, requirement coverage, locked decisions, concrete paths, dependency order, wiring, scope size, proof truthfulness, feature completeness, and quality gates. Quality gates: non-trivial slices/tasks include specific Q3-Q7 coverage where applicable.
45
45
  11. If planning creates structural decisions, append them to `.gsd/DECISIONS.md`.
@@ -17,11 +17,7 @@ You are executing a GSD quick task — a lightweight, focused unit of work outsi
17
17
  5. Verify your work:
18
18
  - Run tests if applicable.
19
19
  - Verify both happy path and failure modes for non-trivial changes.
20
- 6. Commit your changes atomically:
21
- - Use conventional commit messages (feat:, fix:, refactor:, etc.)
22
- - Stage only relevant files — never commit secrets or runtime files.
23
- - Commit logical units separately if the task involves distinct changes.
24
- - Quick tasks run outside the auto-mode lifecycle — there is no system auto-commit, so commit directly here.
20
+ 6. {{commitInstruction}}
25
21
  7. Write a brief summary to `{{summaryPath}}`:
26
22
  - Quick tasks operate outside the milestone/slice/task DB structure, so `gsd_summary_save` (which requires a `milestone_id`) cannot be used here. Write the file directly.
27
23
 
@@ -27,13 +27,13 @@ Roadmap, slice summaries, assessments, requirements, decisions, and project cont
27
27
  Call `subagent` with `tasks: [...]` containing ALL THREE reviewers simultaneously:
28
28
 
29
29
  **Reviewer A - Requirements Coverage**
30
- Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directory: {{workingDirectory}}. Read `.gsd/{{milestoneId}}/REQUIREMENTS.md` or equivalent. For each requirement, check slice SUMMARY files in `.gsd/{{milestoneId}}/` and mark COVERED, PARTIAL, or MISSING. Output table: Requirement | Status | Evidence. End with one-line verdict: PASS if all covered, NEEDS-ATTENTION if partials exist, FAIL if any missing."
30
+ Prompt: "Review milestone {{milestoneId}} requirements coverage. Working directory: {{workingDirectory}}. Read `.gsd/REQUIREMENTS.md` or use the inlined requirements context. For each requirement, check slice SUMMARY files under `.gsd/milestones/{{milestoneId}}/slices/` and mark COVERED, PARTIAL, or MISSING. Output table: Requirement | Status | Evidence. End with one-line verdict: PASS if all covered, NEEDS-ATTENTION if partials exist, FAIL if any missing."
31
31
 
32
32
  **Reviewer B - Cross-Slice Integration**
33
33
  Prompt: "Review milestone {{milestoneId}} cross-slice integration. Working directory: {{workingDirectory}}. Read `{{roadmapPath}}` and find the boundary map (produces/consumes contracts). For each boundary, confirm producer SUMMARY produced the artifact and consumer SUMMARY consumed it. Output table: Boundary | Producer Summary | Consumer Summary | Status. End with one-line verdict: PASS if all boundaries honored, NEEDS-ATTENTION if any gaps."
34
34
 
35
35
  **Reviewer C - Assessment & Acceptance Criteria**
36
- Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/{{milestoneId}}/CONTEXT.md` for criteria. Check slice ASSESSMENT files. Verify each criterion maps to a passing assessment or clear SUMMARY evidence. Then review inlined milestone verification classes. For each non-empty planned class, output table: Class | Planned Check | Evidence | Verdict. Use the exact class names `Contract`, `Integration`, `Operational`, and `UAT` whenever those classes are present. If no verification classes were planned, say that explicitly. Output sections `Acceptance Criteria` with checklist `[ ] Criterion | Evidence`, and `Verification Classes` with the table. End with one-line verdict: PASS if all criteria and classes are covered, NEEDS-ATTENTION if gaps exist."
36
+ Prompt: "Review milestone {{milestoneId}} assessment evidence and acceptance criteria. Working directory: {{workingDirectory}}. Read `.gsd/milestones/{{milestoneId}}/{{milestoneId}}-CONTEXT.md` for criteria. Check slice SUMMARY/UAT files under `.gsd/milestones/{{milestoneId}}/slices/`. Verify each criterion maps to passing evidence. Then review inlined milestone verification classes. For each non-empty planned class, output table: Class | Planned Check | Evidence | Verdict. Use the exact class names `Contract`, `Integration`, `Operational`, and `UAT` whenever those classes are present. If no verification classes were planned, say that explicitly. Output sections `Acceptance Criteria` with checklist `[ ] Criterion | Evidence`, and `Verification Classes` with the table. End with one-line verdict: PASS if all criteria and classes are covered, NEEDS-ATTENTION if gaps exist."
37
37
 
38
38
  ### Step 2 - Synthesize Findings
39
39
 
@@ -10,8 +10,8 @@
10
10
  */
11
11
 
12
12
  import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
13
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
14
- import { join } from "node:path";
13
+ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, writeFileSync } from "node:fs";
14
+ import { isAbsolute, join, relative } from "node:path";
15
15
  import { loadPrompt } from "./prompt-loader.js";
16
16
  import { gsdRoot } from "./paths.js";
17
17
  import { GitServiceImpl, runGit } from "./git-service.js";
@@ -77,6 +77,40 @@ function ensureQuickDir(basePath: string, taskNum: number, slug: string): string
77
77
  return taskDir;
78
78
  }
79
79
 
80
+ function isPathInside(parent: string, child: string): boolean {
81
+ const rel = relative(parent, child);
82
+ return rel === "" || (!!rel && !rel.startsWith("..") && !isAbsolute(rel));
83
+ }
84
+
85
+ function isExternalGsdRoot(basePath: string, root: string): boolean {
86
+ try {
87
+ return !isPathInside(realpathSync(basePath), realpathSync(root));
88
+ } catch {
89
+ return !isPathInside(basePath, root);
90
+ }
91
+ }
92
+
93
+ export function buildQuickCommitInstruction(basePath: string, root: string): string {
94
+ const externalState = isExternalGsdRoot(basePath, root);
95
+ if (externalState) {
96
+ return [
97
+ "Commit repo changes atomically, but do not stage or commit `.gsd/quick/...`:",
98
+ " - `.gsd/` resolves outside this git repository, so Git cannot stage quick-task summary files from the project repo.",
99
+ " - Write the quick summary file directly at the requested path; that file is persisted by GSD external state.",
100
+ " - Stage and commit only implementation/test/docs files that live inside the repository.",
101
+ " - If the task only writes quick-task research/summary files and no repository files changed, do not run `git commit`; report that there was nothing in the project repo to commit.",
102
+ ].join("\n");
103
+ }
104
+
105
+ return [
106
+ "Commit your changes atomically:",
107
+ " - Use conventional commit messages (feat:, fix:, refactor:, etc.)",
108
+ " - Stage only relevant files — never commit secrets or runtime files.",
109
+ " - Commit logical units separately if the task involves distinct changes.",
110
+ " - Quick tasks run outside the auto-mode lifecycle — there is no system auto-commit, so commit directly here.",
111
+ ].join("\n");
112
+ }
113
+
80
114
  function quickReturnStatePath(basePath: string): string {
81
115
  return join(gsdRoot(basePath), "runtime", "quick-return.json");
82
116
  }
@@ -246,6 +280,7 @@ export async function handleQuick(
246
280
  taskDir: taskDirRel,
247
281
  branch: actualBranch,
248
282
  summaryPath,
283
+ commitInstruction: buildQuickCommitInstruction(basePath, root),
249
284
  date,
250
285
  taskNum: String(taskNum),
251
286
  slug,
@@ -50,6 +50,15 @@ export interface FileEditEvidence {
50
50
 
51
51
  export type EvidenceEntry = BashEvidence | FileWriteEvidence | FileEditEvidence;
52
52
 
53
+ const EXECUTION_TOOL_NAMES = new Set([
54
+ "bash",
55
+ "Bash",
56
+ "gsd_exec",
57
+ "gsd_exec_search",
58
+ "mcp__gsd-workflow__gsd_exec",
59
+ "mcp__gsd-workflow__gsd_exec_search",
60
+ ]);
61
+
53
62
  // ─── Module State ───────────────────────────────────────────────────────────
54
63
 
55
64
  let unitEvidence: EvidenceEntry[] = [];
@@ -188,11 +197,11 @@ export function clearEvidenceFromDisk(
188
197
  * Exit codes and output are filled in by recordToolResult after execution.
189
198
  */
190
199
  export function recordToolCall(toolCallId: string, toolName: string, input: Record<string, unknown>): void {
191
- if (toolName === "bash" || toolName === "Bash") {
200
+ if (EXECUTION_TOOL_NAMES.has(toolName)) {
192
201
  unitEvidence.push({
193
202
  kind: "bash",
194
203
  toolCallId,
195
- command: String(input.command ?? ""),
204
+ command: String(input.command ?? input.cmd ?? input.query ?? ""),
196
205
  exitCode: -1,
197
206
  outputSnippet: "",
198
207
  timestamp: Date.now(),
@@ -2556,7 +2556,7 @@ test("autoLoop warns but proceeds for greenfield project (no project files) (#18
2556
2556
  "should not stop with health check failure for greenfield project",
2557
2557
  );
2558
2558
  const greenfieldWarning = notifications.find(
2559
- (n) => n.includes("no recognized project files") && n.includes("greenfield"),
2559
+ (n) => n.includes("no project content yet") && n.includes("greenfield"),
2560
2560
  );
2561
2561
  assert.ok(
2562
2562
  greenfieldWarning,
@@ -7,11 +7,12 @@ import { randomUUID } from "node:crypto";
7
7
 
8
8
  import { verifyExpectedArtifact, hasImplementationArtifacts, resolveExpectedArtifactPath, diagnoseExpectedArtifact, buildLoopRemediationSteps, writeBlockerPlaceholder } from "../auto-recovery.ts";
9
9
  import { resolveMilestoneFile } from "../paths.ts";
10
- import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertGateRow, insertTask } from "../gsd-db.ts";
10
+ import { openDatabase, closeDatabase, insertMilestone, insertSlice, insertGateRow, insertTask, getMilestoneCommitAttributionShas } from "../gsd-db.ts";
11
11
  import { clearParseCache } from "../files.ts";
12
12
  import { parseRoadmap } from "../parsers-legacy.ts";
13
13
  import { invalidateAllCaches } from "../cache.ts";
14
14
  import { deriveState, invalidateStateCache } from "../state.ts";
15
+ import { writeIntegrationBranch } from "../git-service.ts";
15
16
 
16
17
  const tmpDirs: string[] = [];
17
18
 
@@ -735,6 +736,174 @@ test("hasImplementationArtifacts rejects milestone-scoped main history with only
735
736
  }
736
737
  });
737
738
 
739
+ test("hasImplementationArtifacts finds integration implementation-only commits when milestone branch diff is .gsd-only", () => {
740
+ const base = makeGitBase();
741
+ try {
742
+ mkdirSync(join(base, "src"), { recursive: true });
743
+ writeFileSync(join(base, "src", "feature.ts"), "export function feature() {}\n");
744
+ execFileSync("git", ["add", "src/feature.ts"], { cwd: base, stdio: "ignore" });
745
+ execFileSync("git", ["commit", "-m", "feat: add milestone feature\n\nGSD-Task: S01/T01"], { cwd: base, stdio: "ignore" });
746
+
747
+ mkdirSync(join(base, ".gsd"), { recursive: true });
748
+ openDatabase(join(base, ".gsd", "gsd.db"));
749
+ insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
750
+ insertSlice({
751
+ id: "S01",
752
+ milestoneId: "M001",
753
+ title: "Slice One",
754
+ status: "complete",
755
+ risk: "low",
756
+ depends: [],
757
+ });
758
+ insertTask({
759
+ id: "T01",
760
+ sliceId: "S01",
761
+ milestoneId: "M001",
762
+ title: "Task One",
763
+ status: "complete",
764
+ });
765
+
766
+ execFileSync("git", ["checkout", "-b", "milestone/M001"], { cwd: base, stdio: "ignore" });
767
+ writeIntegrationBranch(base, "M001", "main");
768
+ writeFileSync(join(base, ".gsd", "milestones", "M001", "M001-SUMMARY.md"), "# Milestone Summary\nDone.");
769
+ execFileSync("git", ["add", "."], { cwd: base, stdio: "ignore" });
770
+ execFileSync("git", ["commit", "-m", "chore: auto-commit after complete-milestone\n\nGSD-Unit: M001"], { cwd: base, stdio: "ignore" });
771
+
772
+ const result = hasImplementationArtifacts(base, "M001");
773
+ assert.equal(
774
+ result,
775
+ "present",
776
+ ".gsd-only milestone closeout diffs should still honor implementation commits already on the integration branch",
777
+ );
778
+ } finally {
779
+ cleanup(base);
780
+ }
781
+ });
782
+
783
+ test("hasImplementationArtifacts backfills untagged main implementation commits from completed task file hints", () => {
784
+ const base = makeGitBase();
785
+ try {
786
+ mkdirSync(join(base, ".gsd"), { recursive: true });
787
+ openDatabase(join(base, ".gsd", "gsd.db"));
788
+ insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
789
+ insertSlice({
790
+ id: "S01",
791
+ milestoneId: "M001",
792
+ title: "Slice One",
793
+ status: "complete",
794
+ risk: "low",
795
+ depends: [],
796
+ });
797
+ insertTask({
798
+ id: "T01",
799
+ sliceId: "S01",
800
+ milestoneId: "M001",
801
+ title: "Task One",
802
+ status: "complete",
803
+ keyFiles: ["index.html", "style.css", "app.js"],
804
+ planning: { files: ["index.html", "style.css", "app.js"] },
805
+ });
806
+
807
+ writeFileSync(join(base, "index.html"), "<main></main>\n");
808
+ writeFileSync(join(base, "style.css"), "main { display: block; }\n");
809
+ writeFileSync(join(base, "app.js"), "document.body.dataset.ready = 'true';\n");
810
+ execFileSync("git", ["add", "index.html", "style.css", "app.js"], { cwd: base, stdio: "ignore" });
811
+ execFileSync("git", ["commit", "-m", "feat: add to-do app with CRUD and localStorage persistence"], { cwd: base, stdio: "ignore" });
812
+ const commitSha = execFileSync("git", ["rev-parse", "HEAD"], { cwd: base, encoding: "utf-8" }).trim();
813
+
814
+ const result = hasImplementationArtifacts(base, "M001");
815
+ assert.equal(
816
+ result,
817
+ "present",
818
+ "completed task file hints should repair prior untagged implementation commits on main",
819
+ );
820
+ assert.deepEqual(getMilestoneCommitAttributionShas("M001"), [commitSha]);
821
+ } finally {
822
+ cleanup(base);
823
+ }
824
+ });
825
+
826
+ test("hasImplementationArtifacts does not backfill untagged commits before milestone creation", () => {
827
+ const base = makeGitBase();
828
+ try {
829
+ writeFileSync(join(base, "app.js"), "document.body.dataset.ready = 'old';\n");
830
+ execFileSync("git", ["add", "app.js"], { cwd: base, stdio: "ignore" });
831
+ execFileSync("git", ["commit", "-m", "feat: old app work"], {
832
+ cwd: base,
833
+ stdio: "ignore",
834
+ env: {
835
+ ...process.env,
836
+ GIT_AUTHOR_DATE: "2020-01-01T00:00:00Z",
837
+ GIT_COMMITTER_DATE: "2020-01-01T00:00:00Z",
838
+ },
839
+ });
840
+
841
+ mkdirSync(join(base, ".gsd"), { recursive: true });
842
+ openDatabase(join(base, ".gsd", "gsd.db"));
843
+ insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
844
+ insertSlice({
845
+ id: "S01",
846
+ milestoneId: "M001",
847
+ title: "Slice One",
848
+ status: "complete",
849
+ risk: "low",
850
+ depends: [],
851
+ });
852
+ insertTask({
853
+ id: "T01",
854
+ sliceId: "S01",
855
+ milestoneId: "M001",
856
+ title: "Task One",
857
+ status: "complete",
858
+ keyFiles: ["app.js"],
859
+ planning: { files: ["app.js"] },
860
+ });
861
+
862
+ const result = hasImplementationArtifacts(base, "M001");
863
+ assert.equal(result, "absent", "pre-milestone commits must not be attributed to the milestone");
864
+ assert.deepEqual(getMilestoneCommitAttributionShas("M001"), []);
865
+ } finally {
866
+ cleanup(base);
867
+ }
868
+ });
869
+
870
+ test("hasImplementationArtifacts does not backfill unrelated untagged implementation commits", () => {
871
+ const base = makeGitBase();
872
+ try {
873
+ mkdirSync(join(base, ".gsd"), { recursive: true });
874
+ openDatabase(join(base, ".gsd", "gsd.db"));
875
+ insertMilestone({ id: "M001", title: "Milestone One", status: "active" });
876
+ insertSlice({
877
+ id: "S01",
878
+ milestoneId: "M001",
879
+ title: "Slice One",
880
+ status: "complete",
881
+ risk: "low",
882
+ depends: [],
883
+ });
884
+ insertTask({
885
+ id: "T01",
886
+ sliceId: "S01",
887
+ milestoneId: "M001",
888
+ title: "Task One",
889
+ status: "complete",
890
+ keyFiles: ["src/expected.ts"],
891
+ planning: { files: ["src/expected.ts"] },
892
+ });
893
+
894
+ mkdirSync(join(base, "src"), { recursive: true });
895
+ writeFileSync(join(base, "src", "unrelated.ts"), "export const unrelated = true;\n");
896
+ execFileSync("git", ["add", "src/unrelated.ts"], { cwd: base, stdio: "ignore" });
897
+ execFileSync("git", ["commit", "-m", "feat: unrelated work"], { cwd: base, stdio: "ignore" });
898
+
899
+ const result = hasImplementationArtifacts(base, "M001");
900
+ assert.equal(result, "absent", "backfill must require overlap with completed task file hints");
901
+ assert.deepEqual(getMilestoneCommitAttributionShas("M001"), []);
902
+ } finally {
903
+ cleanup(base);
904
+ }
905
+ });
906
+
738
907
  test("hasImplementationArtifacts treats empty non-integration branch diff as absent (#4699)", () => {
739
908
  const base = makeGitBase();
740
909
  try {
@@ -131,7 +131,7 @@ test("postflightPopStash — restores stashed changes and emits info notificatio
131
131
  run('git commit -m "simulate merge"', repo);
132
132
 
133
133
  const postNotifications: Array<{ msg: string; level: string }> = [];
134
- postflightPopStash(repo, "M004", (msg, level) => {
134
+ postflightPopStash(repo, "M004", preflight.stashMarker, (msg, level) => {
135
135
  postNotifications.push({ msg, level });
136
136
  });
137
137
 
@@ -171,7 +171,7 @@ test("preflight + merge + postflight round-trip preserves uncommitted changes",
171
171
  run('git commit -m "feat: add feature"', repo);
172
172
 
173
173
  // Postflight: pop stash
174
- postflightPopStash(repo, "M005", () => {});
174
+ postflightPopStash(repo, "M005", preflight.stashMarker, () => {});
175
175
 
176
176
  // README.md must still have our local content
177
177
  const restored = readFileSync(join(repo, "README.md"), "utf-8");
@@ -184,3 +184,89 @@ test("preflight + merge + postflight round-trip preserves uncommitted changes",
184
184
  try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
185
185
  }
186
186
  });
187
+
188
+ test("postflightPopStash conflict warning names the exact stash ref", () => {
189
+ const repo = createTempRepo();
190
+ try {
191
+ writeFileSync(join(repo, "README.md"), "# local work\n");
192
+ const preflight = preflightCleanRoot(repo, "M005C", () => {});
193
+ assert.equal(preflight.stashPushed, true, "must have stashed");
194
+
195
+ writeFileSync(join(repo, "README.md"), "# merged work\n");
196
+ run("git add README.md", repo);
197
+ run('git commit -m "simulate conflicting merge"', repo);
198
+
199
+ const notifications: Array<{ msg: string; level: string }> = [];
200
+ postflightPopStash(repo, "M005C", preflight.stashMarker, (msg, level) => {
201
+ notifications.push({ msg, level });
202
+ });
203
+
204
+ const warning = notifications.find((n) => n.level === "warning")?.msg ?? "";
205
+ assert.match(warning, /git stash pop stash@\{\d+\}/);
206
+ assert.match(warning, /git stash apply stash@\{\d+\}/);
207
+ } finally {
208
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
209
+ }
210
+ });
211
+
212
+ test("postflightPopStash restores the matching GSD stash, not stash@{0}", () => {
213
+ const repo = createTempRepo();
214
+ try {
215
+ writeFileSync(join(repo, "README.md"), "# target stash\n");
216
+ const preflight = preflightCleanRoot(repo, "M006", () => {});
217
+ assert.equal(preflight.stashPushed, true, "must have stashed target change");
218
+
219
+ writeFileSync(join(repo, "other.txt"), "other stash\n");
220
+ run('git stash push --include-untracked -m "unrelated newer stash"', repo);
221
+
222
+ postflightPopStash(repo, "M006", preflight.stashMarker, () => {});
223
+
224
+ const content = readFileSync(join(repo, "README.md"), "utf-8");
225
+ assert.equal(content.replace(/\r\n/g, "\n"), "# target stash\n");
226
+ const stashList = run("git stash list", repo);
227
+ assert.ok(stashList.includes("unrelated newer stash"), "unrelated newer stash must remain");
228
+ assert.ok(!stashList.includes("gsd-preflight-stash [gsd-preflight-stash:M006"), "target stash should be consumed");
229
+ } finally {
230
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
231
+ }
232
+ });
233
+
234
+ test("postflightPopStash restores the exact preflight marker when another same-milestone stash exists", () => {
235
+ const repo = createTempRepo();
236
+ try {
237
+ writeFileSync(join(repo, "README.md"), "# target stash\n");
238
+ const preflight = preflightCleanRoot(repo, "M007", () => {});
239
+ assert.equal(preflight.stashPushed, true, "must have stashed target change");
240
+ assert.ok(preflight.stashMarker, "preflight must expose exact stash marker");
241
+
242
+ writeFileSync(join(repo, "same-milestone.txt"), "newer same milestone stash\n");
243
+ run('git stash push --include-untracked -m "gsd-preflight-stash [gsd-preflight-stash:M007:other]"', repo);
244
+
245
+ postflightPopStash(repo, "M007", preflight.stashMarker, () => {});
246
+
247
+ const content = readFileSync(join(repo, "README.md"), "utf-8");
248
+ assert.equal(content.replace(/\r\n/g, "\n"), "# target stash\n");
249
+ const stashList = run("git stash list", repo);
250
+ assert.ok(stashList.includes("gsd-preflight-stash:M007:other"), "newer same-milestone stash must remain");
251
+ assert.ok(!stashList.includes(preflight.stashMarker), "exact target stash should be consumed");
252
+ } finally {
253
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
254
+ }
255
+ });
256
+
257
+ test("postflightPopStash falls back to milestone marker prefix when exact marker is unavailable", () => {
258
+ const repo = createTempRepo();
259
+ try {
260
+ writeFileSync(join(repo, "README.md"), "# fallback stash\n");
261
+ run('git stash push --include-untracked -m "gsd-preflight-stash [gsd-preflight-stash:M008:fallback]"', repo);
262
+
263
+ postflightPopStash(repo, "M008", undefined, () => {});
264
+
265
+ const content = readFileSync(join(repo, "README.md"), "utf-8");
266
+ assert.equal(content.replace(/\r\n/g, "\n"), "# fallback stash\n");
267
+ const stashList = run("git stash list", repo);
268
+ assert.ok(!stashList.includes("gsd-preflight-stash:M008:fallback"), "fallback stash should be consumed");
269
+ } finally {
270
+ try { rmSync(repo, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 }); } catch { /* ignore */ }
271
+ }
272
+ });
@@ -1,3 +1,4 @@
1
+ // GSD2 commands-extract-learnings tests
1
2
  import { describe, it, beforeEach, afterEach } from "node:test";
2
3
  import assert from "node:assert/strict";
3
4
  import { mkdirSync, writeFileSync, rmSync, readFileSync } from "node:fs";
@@ -484,6 +485,14 @@ describe("buildExtractionStepsBlock", () => {
484
485
  assert.ok(/skip/i.test(block));
485
486
  });
486
487
 
488
+ it("limits duplicate checks to one milestone-scoped memory query", () => {
489
+ const block = buildExtractionStepsBlock(ctx);
490
+ const memoryQueryMatches = block.match(/memory_query/g) ?? [];
491
+ assert.equal(memoryQueryMatches.length, 1);
492
+ assert.ok(block.includes("Do not re-read milestone artefacts or repeat memory queries category-by-category"));
493
+ assert.ok(!block.includes("Before each `capture_thought` call, optionally call `memory_query`"));
494
+ });
495
+
487
496
  it("instructs capture_thought as the sole persistence path for Patterns, Lessons, and Decisions (ADR-013 step 6 cutover)", () => {
488
497
  const block = buildExtractionStepsBlock(ctx);
489
498
  // Each of the three persistence steps must call capture_thought.
@@ -232,4 +232,59 @@ describe('emitCrashRecoveredUnitEnd (#3348)', () => {
232
232
  rmSync(base, { recursive: true, force: true });
233
233
  }
234
234
  });
235
+
236
+ test('emitOpenUnitEndForUnit closes the latest open start with error context', async () => {
237
+ const base = makeTmpBase();
238
+ try {
239
+ const { emitJournalEvent, queryJournal } = await import('../journal.ts');
240
+ const { emitOpenUnitEndForUnit } = await import('../crash-recovery.ts');
241
+
242
+ const firstFlowId = randomUUID();
243
+ const secondFlowId = randomUUID();
244
+ emitJournalEvent(base, {
245
+ ts: new Date().toISOString(),
246
+ flowId: firstFlowId,
247
+ seq: 1,
248
+ eventType: 'unit-start',
249
+ data: { unitType: 'execute-task', unitId: 'M008/S04/T02' },
250
+ });
251
+ emitJournalEvent(base, {
252
+ ts: new Date().toISOString(),
253
+ flowId: firstFlowId,
254
+ seq: 2,
255
+ eventType: 'unit-end',
256
+ data: { unitType: 'execute-task', unitId: 'M008/S04/T02', status: 'completed' },
257
+ causedBy: { flowId: firstFlowId, seq: 1 },
258
+ });
259
+ emitJournalEvent(base, {
260
+ ts: new Date().toISOString(),
261
+ flowId: secondFlowId,
262
+ seq: 3,
263
+ eventType: 'unit-start',
264
+ data: { unitType: 'execute-task', unitId: 'M008/S04/T02' },
265
+ });
266
+
267
+ const emitted = emitOpenUnitEndForUnit(
268
+ base,
269
+ 'execute-task',
270
+ 'M008/S04/T02',
271
+ 'cancelled',
272
+ { message: 'runUnitPhase exploded', category: 'unit-exception', isTransient: false },
273
+ );
274
+
275
+ assert.equal(emitted, true, 'open unit should be closed');
276
+ const ends = queryJournal(base).filter((e) => e.eventType === 'unit-end');
277
+ assert.equal(ends.length, 2, 'should preserve existing end and add one new end');
278
+ const newEnd = ends.find((e) => e.causedBy?.flowId === secondFlowId);
279
+ assert.ok(newEnd, 'new end should close the latest open start');
280
+ assert.equal(newEnd!.data?.status, 'cancelled');
281
+ assert.deepEqual(newEnd!.data?.errorContext, {
282
+ message: 'runUnitPhase exploded',
283
+ category: 'unit-exception',
284
+ isTransient: false,
285
+ });
286
+ } finally {
287
+ rmSync(base, { recursive: true, force: true });
288
+ }
289
+ });
235
290
  });