@opengsd/gsd-pi 1.1.1-dev.b2556262 → 1.2.0-dev.844675c9

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 (211) hide show
  1. package/dist/project-sessions.js +4 -2
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +17 -9
  4. package/dist/resources/extensions/gsd/auto/contracts.js +8 -1
  5. package/dist/resources/extensions/gsd/auto/orchestrator.js +659 -57
  6. package/dist/resources/extensions/gsd/auto-prompts.js +110 -1
  7. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  8. package/dist/resources/extensions/gsd/auto-tool-tracking.js +5 -0
  9. package/dist/resources/extensions/gsd/auto-unit-tool-scope.js +29 -0
  10. package/dist/resources/extensions/gsd/auto-worktree.js +24 -17
  11. package/dist/resources/extensions/gsd/auto.js +62 -464
  12. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +4 -1
  13. package/dist/resources/extensions/gsd/debug-logger.js +10 -0
  14. package/dist/resources/extensions/gsd/doctor-proactive.js +7 -2
  15. package/dist/resources/extensions/gsd/guided-flow.js +2 -2
  16. package/dist/resources/extensions/gsd/markdown-renderer.js +31 -32
  17. package/dist/resources/extensions/gsd/mcp-filter.js +6 -0
  18. package/dist/resources/extensions/gsd/native-git-bridge.js +45 -0
  19. package/dist/resources/extensions/gsd/prompts/discuss.md +6 -7
  20. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
  21. package/dist/resources/extensions/gsd/prompts/guided-discuss-project.md +3 -5
  22. package/dist/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
  23. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
  24. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  25. package/dist/resources/extensions/gsd/prompts/research-milestone.md +2 -2
  26. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
  27. package/dist/resources/extensions/gsd/schemas/parsers.js +6 -1
  28. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +21 -1
  29. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +169 -20
  30. package/dist/resources/extensions/gsd/user-input-boundary.js +42 -4
  31. package/dist/tsconfig.extensions.tsbuildinfo +1 -0
  32. package/dist/web/standalone/.next/BUILD_ID +1 -1
  33. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  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/api/boot/route.js.nft.json +1 -1
  53. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  54. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  55. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  56. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  57. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  58. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  59. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  60. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  61. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  62. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  64. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  65. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  66. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  67. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  68. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  69. package/dist/web/standalone/.next/server/app/api/mcp-connections/route.js.nft.json +1 -1
  70. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  71. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  72. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  73. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  74. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  75. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  76. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  77. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  78. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  79. package/dist/web/standalone/.next/server/app/api/shutdown/route.js.nft.json +1 -1
  80. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  81. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  82. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  83. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  84. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  85. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  86. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  87. package/dist/web/standalone/.next/server/app/index.html +1 -1
  88. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  95. package/dist/web/standalone/.next/server/chunks/5047.js +2 -0
  96. package/dist/web/standalone/.next/server/chunks/5124.js +1 -0
  97. package/dist/web/standalone/.next/server/chunks/8357.js +2 -2
  98. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  100. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  101. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  102. package/dist/web/standalone/node_modules/@gsd/native/package.json +1 -1
  103. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  104. package/dist/web/standalone/node_modules/postcss/lib/container.js +26 -18
  105. package/dist/web/standalone/node_modules/postcss/lib/css-syntax-error.js +47 -14
  106. package/dist/web/standalone/node_modules/postcss/lib/declaration.js +4 -4
  107. package/dist/web/standalone/node_modules/postcss/lib/fromJSON.js +3 -3
  108. package/dist/web/standalone/node_modules/postcss/lib/input.js +54 -29
  109. package/dist/web/standalone/node_modules/postcss/lib/lazy-result.js +47 -37
  110. package/dist/web/standalone/node_modules/postcss/lib/map-generator.js +26 -9
  111. package/dist/web/standalone/node_modules/postcss/lib/no-work-result.js +57 -55
  112. package/dist/web/standalone/node_modules/postcss/lib/node.js +99 -31
  113. package/dist/web/standalone/node_modules/postcss/lib/parse.js +1 -1
  114. package/dist/web/standalone/node_modules/postcss/lib/parser.js +10 -9
  115. package/dist/web/standalone/node_modules/postcss/lib/postcss.js +12 -12
  116. package/dist/web/standalone/node_modules/postcss/lib/previous-map.js +30 -11
  117. package/dist/web/standalone/node_modules/postcss/lib/processor.js +7 -7
  118. package/dist/web/standalone/node_modules/postcss/lib/result.js +5 -5
  119. package/dist/web/standalone/node_modules/postcss/lib/rule.js +6 -6
  120. package/dist/web/standalone/node_modules/postcss/lib/stringifier.js +69 -28
  121. package/dist/web/standalone/node_modules/postcss/lib/tokenize.js +6 -2
  122. package/dist/web/standalone/node_modules/postcss/package.json +48 -48
  123. package/package.json +16 -11
  124. package/packages/cloud-mcp-gateway/package.json +2 -2
  125. package/packages/contracts/package.json +1 -1
  126. package/packages/daemon/package.json +4 -4
  127. package/packages/gsd-agent-core/package.json +5 -5
  128. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +1 -1
  129. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  130. package/packages/gsd-agent-modes/package.json +7 -7
  131. package/packages/mcp-server/package.json +3 -3
  132. package/packages/native/package.json +1 -1
  133. package/packages/pi-agent-core/package.json +1 -1
  134. package/packages/pi-ai/dist/models.generated.d.ts +0 -34
  135. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  136. package/packages/pi-ai/dist/models.generated.js +12 -46
  137. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  138. package/packages/pi-ai/package.json +1 -1
  139. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/auth-storage.js +11 -3
  141. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  142. package/packages/pi-coding-agent/package.json +7 -7
  143. package/packages/pi-tui/package.json +2 -2
  144. package/packages/rpc-client/package.json +2 -2
  145. package/pkg/package.json +1 -1
  146. package/scripts/install/deps.js +10 -0
  147. package/scripts/link-workspace-packages.cjs +7 -40
  148. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +18 -8
  149. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +2 -2
  150. package/src/resources/extensions/gsd/auto/contracts.ts +8 -119
  151. package/src/resources/extensions/gsd/auto/orchestrator.ts +794 -58
  152. package/src/resources/extensions/gsd/auto-prompts.ts +114 -1
  153. package/src/resources/extensions/gsd/auto-runtime-state.ts +4 -0
  154. package/src/resources/extensions/gsd/auto-tool-tracking.ts +5 -0
  155. package/src/resources/extensions/gsd/auto-unit-tool-scope.ts +33 -0
  156. package/src/resources/extensions/gsd/auto-worktree.ts +24 -16
  157. package/src/resources/extensions/gsd/auto.ts +81 -500
  158. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +4 -0
  159. package/src/resources/extensions/gsd/debug-logger.ts +11 -0
  160. package/src/resources/extensions/gsd/doctor-proactive.ts +8 -2
  161. package/src/resources/extensions/gsd/guided-flow.ts +2 -2
  162. package/src/resources/extensions/gsd/markdown-renderer.ts +38 -19
  163. package/src/resources/extensions/gsd/mcp-filter.ts +7 -0
  164. package/src/resources/extensions/gsd/native-git-bridge.ts +48 -0
  165. package/src/resources/extensions/gsd/prompts/discuss.md +6 -7
  166. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +5 -7
  167. package/src/resources/extensions/gsd/prompts/guided-discuss-project.md +3 -5
  168. package/src/resources/extensions/gsd/prompts/guided-discuss-requirements.md +1 -2
  169. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +5 -6
  170. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  171. package/src/resources/extensions/gsd/prompts/research-milestone.md +2 -2
  172. package/src/resources/extensions/gsd/prompts/validate-milestone.md +5 -3
  173. package/src/resources/extensions/gsd/schemas/parsers.ts +6 -1
  174. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +31 -10
  175. package/src/resources/extensions/gsd/tests/artifact-db-drift-memo.test.ts +66 -0
  176. package/src/resources/extensions/gsd/tests/auto-dispatch-baseline-harness.test.ts +53 -0
  177. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +590 -855
  178. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +38 -10
  179. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +15 -0
  180. package/src/resources/extensions/gsd/tests/execute-summary-save-empty-project.test.ts +64 -1
  181. package/src/resources/extensions/gsd/tests/integration/merge-strategy-regular.test.ts +157 -0
  182. package/src/resources/extensions/gsd/tests/markdown-renderer-parse-cache.test.ts +75 -0
  183. package/src/resources/extensions/gsd/tests/native-merge-regular.test.ts +139 -0
  184. package/src/resources/extensions/gsd/tests/orchestrator-legacy-parity.test.ts +127 -0
  185. package/src/resources/extensions/gsd/tests/parse-project-milestone-bridge.test.ts +77 -0
  186. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +4 -2
  187. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +29 -2
  188. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +65 -0
  189. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +19 -5
  190. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +38 -0
  191. package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +62 -0
  192. package/src/resources/extensions/gsd/tests/worktree-safety.test.ts +24 -0
  193. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +15 -3
  194. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +183 -21
  195. package/src/resources/extensions/gsd/user-input-boundary.ts +37 -5
  196. package/dist/web/standalone/.next/server/chunks/678.js +0 -2
  197. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts +0 -21
  198. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.d.ts.map +0 -1
  199. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js +0 -213
  200. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/gsd-widget-prototype.js.map +0 -1
  201. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts +0 -28
  202. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.d.ts.map +0 -1
  203. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js +0 -249
  204. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-density-prototype.js.map +0 -1
  205. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts +0 -19
  206. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.d.ts.map +0 -1
  207. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js +0 -797
  208. package/packages/gsd-agent-modes/dist/modes/interactive/components/__prototype__/transcript-design-prototype.js.map +0 -1
  209. package/scripts/ensure-workspace-builds.cjs +0 -129
  210. /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → Qbr81pQ-pbQXP4bq2VXLv}/_buildManifest.js +0 -0
  211. /package/dist/web/standalone/.next/static/{tJOKQbQRO-9MiFDO8DIDS → Qbr81pQ-pbQXP4bq2VXLv}/_ssgManifest.js +0 -0
@@ -3,7 +3,7 @@
3
3
  import { ensureDbOpen } from "../bootstrap/dynamic-tools.js";
4
4
  import { sanitizeCompleteMilestoneParams } from "../bootstrap/sanitize-complete-milestone.js";
5
5
  import { loadWriteGateSnapshot, shouldBlockContextArtifactSaveInSnapshot, shouldBlockRootArtifactSaveInSnapshot } from "../bootstrap/write-gate.js";
6
- import { getActiveRequirements, insertMilestone, getMilestone, getSliceStatusSummary, getSliceTaskCounts, insertAssessment, insertGateRun, readTransaction, saveGateResult, upsertQualityGate, } from "../gsd-db.js";
6
+ import { getActiveRequirements, getAllMilestones, getMilestone, getSliceStatusSummary, getSliceTaskCounts, insertMilestone, insertAssessment, insertGateRun, readTransaction, saveGateResult, upsertQualityGate, } from "../gsd-db.js";
7
7
  import { GATE_REGISTRY } from "../gate-registry.js";
8
8
  import { generateRequirementsMd, saveArtifactToDb } from "../db-writer.js";
9
9
  import { clearPathCache, relSliceFile, resolveGsdPathContract, resolveMilestoneFile, resolveSliceFile } from "../paths.js";
@@ -74,6 +74,103 @@ function registerProjectMilestoneSequence(content) {
74
74
  }
75
75
  return registered;
76
76
  }
77
+ /**
78
+ * Best-effort recovery of the human one-liner for each milestone id from a
79
+ * (possibly malformed) Milestone Sequence body. Deliberately lenient: tolerates
80
+ * any separator the canonical MILESTONE_LINE_RE rejects (en-dash, " : ", a
81
+ * missing checkbox, etc.) so a model formatting slip does not discard the prose.
82
+ */
83
+ function recoverMilestoneTails(sequenceBody) {
84
+ const out = new Map();
85
+ const lenient = /^\s*(?:-\s*)?(?:\[[ xX]\]\s*)?(M\d{3})\b\s*[:.\-–—]*\s*(.*)$/;
86
+ for (const rawLine of sequenceBody.split("\n")) {
87
+ const m = rawLine.match(lenient);
88
+ if (m)
89
+ out.set(m[1], m[2].trim());
90
+ }
91
+ return out;
92
+ }
93
+ function firstSentence(text) {
94
+ const trimmed = text.trim();
95
+ if (!trimmed)
96
+ return "";
97
+ const idx = trimmed.search(/[.!?](\s|$)/);
98
+ return (idx >= 0 ? trimmed.slice(0, idx + 1) : trimmed).trim();
99
+ }
100
+ /** Render one canonical, parseable milestone line for the given DB row. */
101
+ function renderMilestoneLine(m, recoveredTail) {
102
+ const done = m.status === "complete";
103
+ let oneLiner = recoveredTail;
104
+ // The recovered tail often still carries the title (e.g. "Foo — bar" or
105
+ // "Foo : bar"). Strip a leading repetition of the title, then any separator.
106
+ if (oneLiner.toLowerCase().startsWith(m.title.toLowerCase())) {
107
+ oneLiner = oneLiner.slice(m.title.length).replace(/^\s*[:.\-–—]+\s*/, "").trim();
108
+ }
109
+ else {
110
+ const sep = oneLiner.match(/\s+(?:—|–|--|-|:)\s+/);
111
+ if (sep && sep.index !== undefined)
112
+ oneLiner = oneLiner.slice(sep.index + sep[0].length).trim();
113
+ }
114
+ // MILESTONE_LINE_RE requires non-empty prose after the separator.
115
+ if (!oneLiner)
116
+ oneLiner = firstSentence(m.vision) || (done ? "Completed." : "Planned.");
117
+ return `- [${done ? "x" : " "}] ${m.id}: ${m.title} — ${oneLiner}`;
118
+ }
119
+ /**
120
+ * Rebuild the "## Milestone Sequence" section from authoritative DB rows when a
121
+ * model-authored PROJECT.md projection parsed to zero milestone lines but the DB
122
+ * already holds milestones. The DB is the source of truth (markdown is a
123
+ * projection), so this repairs the projection rather than failing the save.
124
+ * Preserves a leading HTML comment in the section and recovers one-liners
125
+ * best-effort. The returned content parses cleanly under MILESTONE_LINE_RE.
126
+ */
127
+ function rebuildMilestoneSequenceSection(content, milestones) {
128
+ const lines = content.split("\n");
129
+ const headerIdx = lines.findIndex(l => /^##\s+Milestone Sequence\s*$/.test(l));
130
+ const canonicalLines = (() => {
131
+ // Recover tails from the existing (malformed) body when the section exists.
132
+ let body = "";
133
+ if (headerIdx !== -1) {
134
+ let end = headerIdx + 1;
135
+ while (end < lines.length && !/^##\s+/.test(lines[end]))
136
+ end++;
137
+ body = lines.slice(headerIdx + 1, end).join("\n");
138
+ }
139
+ const tails = recoverMilestoneTails(body);
140
+ return milestones.map(m => renderMilestoneLine(m, tails.get(m.id) ?? ""));
141
+ })();
142
+ if (headerIdx === -1) {
143
+ // No section at all — append a fresh, canonical one.
144
+ const sep = content.endsWith("\n") ? "" : "\n";
145
+ return `${content}${sep}\n## Milestone Sequence\n\n${canonicalLines.join("\n")}\n`;
146
+ }
147
+ let bodyEnd = headerIdx + 1;
148
+ while (bodyEnd < lines.length && !/^##\s+/.test(lines[bodyEnd]))
149
+ bodyEnd++;
150
+ const existingBody = lines.slice(headerIdx + 1, bodyEnd);
151
+ // Preserve a contiguous leading HTML comment block (the "Check off…" hint).
152
+ let i = 0;
153
+ while (i < existingBody.length && existingBody[i].trim() === "")
154
+ i++;
155
+ const preserved = [];
156
+ if (i < existingBody.length && existingBody[i].trim().startsWith("<!--")) {
157
+ while (i < existingBody.length) {
158
+ preserved.push(existingBody[i]);
159
+ const closed = existingBody[i].includes("-->");
160
+ i++;
161
+ if (closed)
162
+ break;
163
+ }
164
+ }
165
+ return [
166
+ ...lines.slice(0, headerIdx + 1),
167
+ "",
168
+ ...(preserved.length ? [...preserved, ""] : []),
169
+ ...canonicalLines,
170
+ "",
171
+ ...lines.slice(bodyEnd),
172
+ ].join("\n");
173
+ }
77
174
  async function mirrorArtifactToActiveWorktreeProjection(basePath, relativePath, content) {
78
175
  const contract = resolveGsdPathContract(basePath);
79
176
  if (!contract.worktreeGsd)
@@ -192,6 +289,7 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
192
289
  }, basePath);
193
290
  await mirrorArtifactToActiveWorktreeProjection(basePath, relativePath, contentToSave);
194
291
  let registeredMilestones = [];
292
+ let milestoneSequenceSelfHealed = false;
195
293
  if (params.artifact_type === "PROJECT") {
196
294
  try {
197
295
  registeredMilestones = registerProjectMilestoneSequence(contentToSave);
@@ -227,28 +325,78 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
227
325
  };
228
326
  }
229
327
  if (registeredMilestones.length === 0) {
230
- logError("tool", `gsd_summary_save: PROJECT.md saved to ${relativePath} but parsed zero milestones — registration produced no DB rows`, {
328
+ const existingMilestones = getAllMilestones();
329
+ if (existingMilestones.length === 0) {
330
+ // Genuine first-save failure: no milestones parsed AND none in the DB.
331
+ // /gsd really would report "No Active Milestone" — hard-fail so the
332
+ // caller rewrites the sequence before proceeding.
333
+ logError("tool", `gsd_summary_save: PROJECT.md saved to ${relativePath} but parsed zero milestones — registration produced no DB rows`, {
334
+ tool: "gsd_summary_save",
335
+ });
336
+ // PROJECT.md was persisted; invalidate so subsequent reads see the new
337
+ // artifacts row even though no milestones registered.
338
+ invalidateStateCache();
339
+ return {
340
+ content: [{
341
+ type: "text",
342
+ text: `Error: PROJECT.md was saved to ${relativePath} but contains zero parseable milestone lines, ` +
343
+ `so no milestones were registered in the DB. /gsd will report "No Active Milestone". ` +
344
+ `Rewrite PROJECT.md so the "Milestone Sequence" section uses canonical lines: ` +
345
+ `\`- [ ] M001: <Title> — <One-liner>\` (em-dash, double-dash \`--\`, or single-dash \`-\` separator), then re-call gsd_summary_save(PROJECT).`,
346
+ }],
347
+ details: {
348
+ operation: "save_summary",
349
+ path: relativePath,
350
+ artifact_type: params.artifact_type,
351
+ error: "milestone_registration_empty_parse",
352
+ },
353
+ isError: true,
354
+ };
355
+ }
356
+ // Existing DB rows mean this is projection drift, not data loss. Rebuild
357
+ // the section from DB state and re-persist a parseable projection.
358
+ logWarning("tool", `gsd_summary_save: PROJECT.md parsed zero milestone lines but DB has ${existingMilestones.length} — rebuilding Milestone Sequence from DB (projection self-heal)`, {
231
359
  tool: "gsd_summary_save",
360
+ path: relativePath,
232
361
  });
233
- // PROJECT.md was persisted; invalidate so subsequent reads see the new
234
- // artifacts row even though no milestones registered.
235
- invalidateStateCache();
236
- return {
237
- content: [{
238
- type: "text",
239
- text: `Error: PROJECT.md was saved to ${relativePath} but contains zero parseable milestone lines, ` +
240
- `so no milestones were registered in the DB. /gsd will report "No Active Milestone". ` +
241
- `Rewrite PROJECT.md so the "Milestone Sequence" section uses canonical lines: ` +
242
- `\`- [ ] M001: <Title> — <One-liner>\` (em-dash, double-dash \`--\`, or single-dash \`-\` separator), then re-call gsd_summary_save(PROJECT).`,
243
- }],
244
- details: {
245
- operation: "save_summary",
362
+ try {
363
+ const healed = rebuildMilestoneSequenceSection(contentToSave, existingMilestones);
364
+ await saveArtifactToDb({ path: relativePath, artifact_type: params.artifact_type, content: healed }, basePath);
365
+ await mirrorArtifactToActiveWorktreeProjection(basePath, relativePath, healed);
366
+ const healedRegisteredMilestones = registerProjectMilestoneSequence(healed);
367
+ if (healedRegisteredMilestones.length === 0) {
368
+ throw new Error("self-healed PROJECT.md still parsed zero milestone lines");
369
+ }
370
+ registeredMilestones = healedRegisteredMilestones;
371
+ milestoneSequenceSelfHealed = true;
372
+ }
373
+ catch (healErr) {
374
+ const msg = healErr instanceof Error ? healErr.message : String(healErr);
375
+ logError("tool", `gsd_summary_save: Milestone Sequence self-heal failed: ${msg}`, {
376
+ tool: "gsd_summary_save",
246
377
  path: relativePath,
247
- artifact_type: params.artifact_type,
248
- error: "milestone_registration_empty_parse",
249
- },
250
- isError: true,
251
- };
378
+ error: msg,
379
+ });
380
+ invalidateStateCache();
381
+ return {
382
+ content: [{
383
+ type: "text",
384
+ text: `Error: PROJECT.md was saved to ${relativePath} but contains zero parseable milestone lines, ` +
385
+ `and automatic DB-backed Milestone Sequence repair failed: ${msg}. ` +
386
+ `Rewrite PROJECT.md so the "Milestone Sequence" section uses canonical lines: ` +
387
+ `\`- [ ] M001: <Title> — <One-liner>\`, then re-call gsd_summary_save(PROJECT).`,
388
+ }],
389
+ details: {
390
+ operation: "save_summary",
391
+ path: relativePath,
392
+ artifact_type: params.artifact_type,
393
+ error: "milestone_sequence_self_heal_failed",
394
+ self_heal_error: msg,
395
+ },
396
+ isError: true,
397
+ };
398
+ }
399
+ invalidateStateCache();
252
400
  }
253
401
  }
254
402
  if (params.artifact_type === "CONTEXT" && !params.task_id) {
@@ -271,6 +419,7 @@ export async function executeSummarySave(params, basePath = process.cwd()) {
271
419
  artifact_type: params.artifact_type,
272
420
  content_source: contentSource,
273
421
  ...(registeredMilestones.length > 0 ? { registeredMilestones } : {}),
422
+ ...(milestoneSequenceSelfHealed ? { milestoneSequenceSelfHealed: true } : {}),
274
423
  },
275
424
  };
276
425
  }
@@ -10,6 +10,26 @@ const APPROVAL_QUESTION_RE = /\b(?:confirm|confirmation|approve|approval|approve
10
10
  const APPROVAL_RIGHT_QUESTION_RE = /\b(?:does|do|is|are|was|were|did)\b[^\n?]{0,120}\bright\b/i;
11
11
  const APPROVAL_CHANGE_QUESTION_RE = /\b(?:anything\s+else|anything|something)\s+to\s+(?:adjust|add|remove|reclassify)\b/i;
12
12
  const RESEARCH_DECISION_QUESTION_RE = /\b(?:research|skip)\b/i;
13
+ function extractVisibleTextFromMessage(msg) {
14
+ if (!msg || typeof msg !== "object")
15
+ return "";
16
+ const content = msg.content;
17
+ if (typeof content === "string")
18
+ return content;
19
+ if (!Array.isArray(content))
20
+ return "";
21
+ const parts = [];
22
+ for (const block of content) {
23
+ if (!block || typeof block !== "object")
24
+ continue;
25
+ const typed = block;
26
+ if (typed.type === "text" && typeof typed.text === "string") {
27
+ parts.push(typed.text);
28
+ }
29
+ // thinking blocks intentionally excluded — they are internal reasoning, not user-visible
30
+ }
31
+ return parts.join("\n");
32
+ }
13
33
  function extractTextFromMessage(msg) {
14
34
  if (!msg || typeof msg !== "object")
15
35
  return "";
@@ -26,6 +46,9 @@ function extractTextFromMessage(msg) {
26
46
  if (typed.type === "text" && typeof typed.text === "string") {
27
47
  parts.push(typed.text);
28
48
  }
49
+ if (typed.type === "thinking" && typeof typed.thinking === "string") {
50
+ parts.push(typed.thinking);
51
+ }
29
52
  }
30
53
  return parts.join("\n");
31
54
  }
@@ -44,6 +67,21 @@ export function lastAssistantText(messages) {
44
67
  }
45
68
  return "";
46
69
  }
70
+ function lastAssistantVisibleText(messages) {
71
+ if (!Array.isArray(messages))
72
+ return "";
73
+ for (let i = messages.length - 1; i >= 0; i--) {
74
+ const msg = messages[i];
75
+ if (!msg || typeof msg !== "object")
76
+ continue;
77
+ if (msg.role !== "assistant")
78
+ continue;
79
+ const text = extractVisibleTextFromMessage(msg).trim();
80
+ if (text)
81
+ return text;
82
+ }
83
+ return "";
84
+ }
47
85
  function anyMessageMatches(messages, pattern) {
48
86
  if (!Array.isArray(messages))
49
87
  return false;
@@ -52,7 +90,7 @@ function anyMessageMatches(messages, pattern) {
52
90
  return false;
53
91
  if (msg.role === "user")
54
92
  return false;
55
- return pattern.test(extractTextFromMessage(msg));
93
+ return pattern.test(extractVisibleTextFromMessage(msg));
56
94
  });
57
95
  }
58
96
  function hasApprovalQuestion(text) {
@@ -117,7 +155,7 @@ export function isAwaitingUserInput(messages) {
117
155
  return true;
118
156
  if (anyMessageMatches(messages, REMOTE_QUESTION_FAILURE_RE))
119
157
  return true;
120
- const text = lastAssistantText(messages);
158
+ const text = lastAssistantVisibleText(messages);
121
159
  if (!text)
122
160
  return false;
123
161
  if (APPROVAL_WAIT_RE.test(text))
@@ -132,7 +170,7 @@ export function isAwaitingApprovalBoundary(messages) {
132
170
  return true;
133
171
  if (anyMessageMatches(messages, REMOTE_QUESTION_FAILURE_RE))
134
172
  return true;
135
- const text = lastAssistantText(messages);
173
+ const text = lastAssistantVisibleText(messages);
136
174
  if (!text)
137
175
  return false;
138
176
  if (APPROVAL_WAIT_RE.test(text))
@@ -146,7 +184,7 @@ export function shouldPauseForUserApprovalQuestion(unitType, messages) {
146
184
  return true;
147
185
  if (anyMessageMatches(messages, REMOTE_QUESTION_FAILURE_RE))
148
186
  return true;
149
- const text = lastAssistantText(messages);
187
+ const text = lastAssistantVisibleText(messages);
150
188
  if (!text)
151
189
  return false;
152
190
  if (APPROVAL_WAIT_RE.test(text))