gsd-pi 2.19.0 → 2.20.0

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 (249) hide show
  1. package/README.md +5 -1
  2. package/dist/cli.js +3 -3
  3. package/dist/onboarding.d.ts +3 -1
  4. package/dist/onboarding.js +77 -3
  5. package/dist/remote-questions-config.d.ts +1 -1
  6. package/dist/resources/extensions/google-search/index.ts +164 -47
  7. package/dist/resources/extensions/gsd/auto-prompts.ts +103 -24
  8. package/dist/resources/extensions/gsd/auto-worktree.ts +93 -9
  9. package/dist/resources/extensions/gsd/auto.ts +424 -30
  10. package/dist/resources/extensions/gsd/commands.ts +518 -36
  11. package/dist/resources/extensions/gsd/context-budget.ts +243 -0
  12. package/dist/resources/extensions/gsd/context-store.ts +195 -0
  13. package/dist/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  14. package/dist/resources/extensions/gsd/db-writer.ts +341 -0
  15. package/dist/resources/extensions/gsd/debug-logger.ts +178 -0
  16. package/dist/resources/extensions/gsd/dispatch-guard.ts +0 -1
  17. package/dist/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  18. package/dist/resources/extensions/gsd/doctor-proactive.ts +286 -0
  19. package/dist/resources/extensions/gsd/doctor.ts +283 -2
  20. package/dist/resources/extensions/gsd/export.ts +81 -2
  21. package/dist/resources/extensions/gsd/files.ts +39 -9
  22. package/dist/resources/extensions/gsd/git-service.ts +6 -0
  23. package/dist/resources/extensions/gsd/gsd-db.ts +752 -0
  24. package/dist/resources/extensions/gsd/guided-flow.ts +26 -1
  25. package/dist/resources/extensions/gsd/history.ts +0 -1
  26. package/dist/resources/extensions/gsd/index.ts +277 -1
  27. package/dist/resources/extensions/gsd/md-importer.ts +526 -0
  28. package/dist/resources/extensions/gsd/metrics.ts +39 -3
  29. package/dist/resources/extensions/gsd/notifications.ts +0 -1
  30. package/dist/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  31. package/dist/resources/extensions/gsd/preferences.ts +125 -150
  32. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -5
  33. package/dist/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  34. package/dist/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  35. package/dist/resources/extensions/gsd/prompts/quick-task.md +48 -0
  36. package/dist/resources/extensions/gsd/prompts/system.md +2 -1
  37. package/dist/resources/extensions/gsd/quick.ts +156 -0
  38. package/dist/resources/extensions/gsd/skill-discovery.ts +5 -3
  39. package/dist/resources/extensions/gsd/skill-health.ts +417 -0
  40. package/dist/resources/extensions/gsd/skill-telemetry.ts +127 -0
  41. package/dist/resources/extensions/gsd/state.ts +30 -0
  42. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  43. package/dist/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  44. package/dist/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  45. package/dist/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  46. package/dist/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  47. package/dist/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  48. package/dist/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  49. package/dist/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  50. package/dist/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  51. package/dist/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  52. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  53. package/dist/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  54. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  55. package/dist/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  56. package/dist/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  57. package/dist/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  58. package/dist/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  59. package/dist/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  60. package/dist/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  61. package/dist/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  62. package/dist/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  63. package/dist/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  64. package/dist/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  65. package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  66. package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  67. package/dist/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  68. package/dist/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  69. package/dist/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  70. package/dist/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  71. package/dist/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  72. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  73. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  74. package/dist/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  75. package/dist/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  76. package/dist/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  77. package/dist/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  78. package/dist/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  79. package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  80. package/dist/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  81. package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  82. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  83. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  84. package/dist/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  85. package/dist/resources/extensions/gsd/types.ts +29 -0
  86. package/dist/resources/extensions/gsd/undo.ts +0 -1
  87. package/dist/resources/extensions/gsd/unit-runtime.ts +5 -1
  88. package/dist/resources/extensions/gsd/visualizer-data.ts +352 -1
  89. package/dist/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  90. package/dist/resources/extensions/gsd/visualizer-views.ts +464 -2
  91. package/dist/resources/extensions/gsd/worktree-command.ts +18 -0
  92. package/dist/resources/extensions/gsd/worktree-manager.ts +11 -4
  93. package/dist/resources/extensions/remote-questions/config.ts +4 -2
  94. package/dist/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  95. package/dist/resources/extensions/remote-questions/format.ts +154 -8
  96. package/dist/resources/extensions/remote-questions/manager.ts +9 -7
  97. package/dist/resources/extensions/remote-questions/remote-command.ts +100 -4
  98. package/dist/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  99. package/dist/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  100. package/dist/resources/extensions/remote-questions/types.ts +2 -1
  101. package/dist/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  102. package/dist/resources/extensions/voice/index.ts +4 -3
  103. package/package.json +1 -1
  104. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  105. package/packages/pi-coding-agent/dist/core/agent-session.js +12 -1
  106. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  107. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  108. package/packages/pi-coding-agent/dist/core/extensions/loader.js +5 -0
  109. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  110. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts +6 -0
  111. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  112. package/packages/pi-coding-agent/dist/core/lsp/client.js +25 -0
  113. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  114. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts +2 -0
  115. package/packages/pi-coding-agent/dist/core/lsp/index.d.ts.map +1 -1
  116. package/packages/pi-coding-agent/dist/core/lsp/index.js +106 -3
  117. package/packages/pi-coding-agent/dist/core/lsp/index.js.map +1 -1
  118. package/packages/pi-coding-agent/dist/core/lsp/lsp.md +6 -0
  119. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts +35 -0
  120. package/packages/pi-coding-agent/dist/core/lsp/types.d.ts.map +1 -1
  121. package/packages/pi-coding-agent/dist/core/lsp/types.js +6 -0
  122. package/packages/pi-coding-agent/dist/core/lsp/types.js.map +1 -1
  123. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts +3 -1
  124. package/packages/pi-coding-agent/dist/core/lsp/utils.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/lsp/utils.js +45 -0
  126. package/packages/pi-coding-agent/dist/core/lsp/utils.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +6 -0
  128. package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
  129. package/packages/pi-coding-agent/dist/core/settings-manager.js +43 -11
  130. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  131. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  132. package/packages/pi-coding-agent/dist/core/system-prompt.js +7 -1
  133. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  134. package/packages/pi-coding-agent/dist/core/tools/edit.d.ts.map +1 -1
  135. package/packages/pi-coding-agent/dist/core/tools/edit.js +5 -0
  136. package/packages/pi-coding-agent/dist/core/tools/edit.js.map +1 -1
  137. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +2 -0
  138. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  139. package/packages/pi-coding-agent/dist/core/tools/write.d.ts.map +1 -1
  140. package/packages/pi-coding-agent/dist/core/tools/write.js +5 -0
  141. package/packages/pi-coding-agent/dist/core/tools/write.js.map +1 -1
  142. package/packages/pi-coding-agent/src/core/agent-session.ts +13 -1
  143. package/packages/pi-coding-agent/src/core/extensions/loader.ts +6 -0
  144. package/packages/pi-coding-agent/src/core/lsp/client.ts +26 -0
  145. package/packages/pi-coding-agent/src/core/lsp/index.ts +157 -2
  146. package/packages/pi-coding-agent/src/core/lsp/lsp.md +6 -0
  147. package/packages/pi-coding-agent/src/core/lsp/types.ts +53 -0
  148. package/packages/pi-coding-agent/src/core/lsp/utils.ts +56 -0
  149. package/packages/pi-coding-agent/src/core/settings-manager.ts +41 -11
  150. package/packages/pi-coding-agent/src/core/system-prompt.ts +7 -1
  151. package/packages/pi-coding-agent/src/core/tools/edit.ts +3 -0
  152. package/packages/pi-coding-agent/src/core/tools/write.ts +3 -0
  153. package/src/resources/extensions/google-search/index.ts +164 -47
  154. package/src/resources/extensions/gsd/auto-prompts.ts +103 -24
  155. package/src/resources/extensions/gsd/auto-worktree.ts +93 -9
  156. package/src/resources/extensions/gsd/auto.ts +424 -30
  157. package/src/resources/extensions/gsd/commands.ts +518 -36
  158. package/src/resources/extensions/gsd/context-budget.ts +243 -0
  159. package/src/resources/extensions/gsd/context-store.ts +195 -0
  160. package/src/resources/extensions/gsd/dashboard-overlay.ts +41 -3
  161. package/src/resources/extensions/gsd/db-writer.ts +341 -0
  162. package/src/resources/extensions/gsd/debug-logger.ts +178 -0
  163. package/src/resources/extensions/gsd/dispatch-guard.ts +0 -1
  164. package/src/resources/extensions/gsd/docs/preferences-reference.md +54 -0
  165. package/src/resources/extensions/gsd/doctor-proactive.ts +286 -0
  166. package/src/resources/extensions/gsd/doctor.ts +283 -2
  167. package/src/resources/extensions/gsd/export.ts +81 -2
  168. package/src/resources/extensions/gsd/files.ts +39 -9
  169. package/src/resources/extensions/gsd/git-service.ts +6 -0
  170. package/src/resources/extensions/gsd/gsd-db.ts +752 -0
  171. package/src/resources/extensions/gsd/guided-flow.ts +26 -1
  172. package/src/resources/extensions/gsd/history.ts +0 -1
  173. package/src/resources/extensions/gsd/index.ts +277 -1
  174. package/src/resources/extensions/gsd/md-importer.ts +526 -0
  175. package/src/resources/extensions/gsd/metrics.ts +39 -3
  176. package/src/resources/extensions/gsd/notifications.ts +0 -1
  177. package/src/resources/extensions/gsd/post-unit-hooks.ts +70 -1
  178. package/src/resources/extensions/gsd/preferences.ts +125 -150
  179. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -5
  180. package/src/resources/extensions/gsd/prompts/heal-skill.md +45 -0
  181. package/src/resources/extensions/gsd/prompts/plan-slice.md +5 -1
  182. package/src/resources/extensions/gsd/prompts/quick-task.md +48 -0
  183. package/src/resources/extensions/gsd/prompts/system.md +2 -1
  184. package/src/resources/extensions/gsd/quick.ts +156 -0
  185. package/src/resources/extensions/gsd/skill-discovery.ts +5 -3
  186. package/src/resources/extensions/gsd/skill-health.ts +417 -0
  187. package/src/resources/extensions/gsd/skill-telemetry.ts +127 -0
  188. package/src/resources/extensions/gsd/state.ts +30 -0
  189. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  190. package/src/resources/extensions/gsd/tests/context-budget.test.ts +283 -0
  191. package/src/resources/extensions/gsd/tests/context-compression.test.ts +1 -1
  192. package/src/resources/extensions/gsd/tests/context-store.test.ts +462 -0
  193. package/src/resources/extensions/gsd/tests/continue-here.test.ts +204 -0
  194. package/src/resources/extensions/gsd/tests/dashboard-budget.test.ts +346 -0
  195. package/src/resources/extensions/gsd/tests/db-writer.test.ts +602 -0
  196. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +185 -0
  197. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +406 -0
  198. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -1
  199. package/src/resources/extensions/gsd/tests/dist-redirect.mjs +22 -0
  200. package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +244 -0
  201. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +303 -0
  202. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +353 -0
  203. package/src/resources/extensions/gsd/tests/gsd-inspect.test.ts +125 -0
  204. package/src/resources/extensions/gsd/tests/gsd-tools.test.ts +326 -0
  205. package/src/resources/extensions/gsd/tests/integration-edge.test.ts +228 -0
  206. package/src/resources/extensions/gsd/tests/integration-lifecycle.test.ts +277 -0
  207. package/src/resources/extensions/gsd/tests/md-importer.test.ts +411 -0
  208. package/src/resources/extensions/gsd/tests/metrics.test.ts +197 -0
  209. package/src/resources/extensions/gsd/tests/model-isolation.test.ts +99 -0
  210. package/src/resources/extensions/gsd/tests/parsers.test.ts +40 -0
  211. package/src/resources/extensions/gsd/tests/post-unit-hooks.test.ts +41 -1
  212. package/src/resources/extensions/gsd/tests/preferences-git.test.ts +0 -1
  213. package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +0 -1
  214. package/src/resources/extensions/gsd/tests/preferences-mode.test.ts +110 -0
  215. package/src/resources/extensions/gsd/tests/preferences-models.test.ts +0 -1
  216. package/src/resources/extensions/gsd/tests/prompt-budget-enforcement.test.ts +464 -0
  217. package/src/resources/extensions/gsd/tests/prompt-db.test.ts +385 -0
  218. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +262 -1
  219. package/src/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +17 -29
  220. package/src/resources/extensions/gsd/tests/resolve-ts.mjs +2 -8
  221. package/src/resources/extensions/gsd/tests/skill-lifecycle.test.ts +126 -0
  222. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +31 -8
  223. package/src/resources/extensions/gsd/tests/token-savings.test.ts +366 -0
  224. package/src/resources/extensions/gsd/tests/unit-runtime.test.ts +25 -1
  225. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +145 -0
  226. package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +92 -0
  227. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +120 -0
  228. package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +228 -5
  229. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  230. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  231. package/src/resources/extensions/gsd/tests/worktree-post-create-hook.test.ts +165 -0
  232. package/src/resources/extensions/gsd/types.ts +29 -0
  233. package/src/resources/extensions/gsd/undo.ts +0 -1
  234. package/src/resources/extensions/gsd/unit-runtime.ts +5 -1
  235. package/src/resources/extensions/gsd/visualizer-data.ts +352 -1
  236. package/src/resources/extensions/gsd/visualizer-overlay.ts +166 -22
  237. package/src/resources/extensions/gsd/visualizer-views.ts +464 -2
  238. package/src/resources/extensions/gsd/worktree-command.ts +18 -0
  239. package/src/resources/extensions/gsd/worktree-manager.ts +11 -4
  240. package/src/resources/extensions/remote-questions/config.ts +4 -2
  241. package/src/resources/extensions/remote-questions/discord-adapter.ts +2 -4
  242. package/src/resources/extensions/remote-questions/format.ts +154 -8
  243. package/src/resources/extensions/remote-questions/manager.ts +9 -7
  244. package/src/resources/extensions/remote-questions/remote-command.ts +100 -4
  245. package/src/resources/extensions/remote-questions/slack-adapter.ts +58 -2
  246. package/src/resources/extensions/remote-questions/telegram-adapter.ts +161 -0
  247. package/src/resources/extensions/remote-questions/types.ts +2 -1
  248. package/src/resources/extensions/ttsr/ttsr-manager.ts +26 -0
  249. package/src/resources/extensions/voice/index.ts +4 -3
@@ -0,0 +1,341 @@
1
+ // GSD DB Writer — Markdown generators + DB-first write helpers
2
+ //
3
+ // The missing DB→markdown direction. S03 established markdown→DB (md-importer.ts).
4
+ // This module generates DECISIONS.md and REQUIREMENTS.md from DB state,
5
+ // computes next decision IDs, and provides write helpers that upsert to DB
6
+ // then regenerate the corresponding markdown file.
7
+ //
8
+ // Critical invariant: generated markdown must round-trip through
9
+ // parseDecisionsTable() and parseRequirementsSections() with field fidelity.
10
+
11
+ import { join, resolve } from 'node:path';
12
+ import type { Decision, Requirement } from './types.js';
13
+ import { resolveGsdRootFile } from './paths.js';
14
+ import { saveFile } from './files.js';
15
+
16
+ // ─── Markdown Generators ──────────────────────────────────────────────────
17
+
18
+ /**
19
+ * Generate full DECISIONS.md content from an array of Decision objects.
20
+ * Produces the canonical format: H1 header, HTML comment block, table header,
21
+ * separator, and one data row per decision.
22
+ *
23
+ * Column order: #, When, Scope, Decision, Choice, Rationale, Revisable?
24
+ */
25
+ export function generateDecisionsMd(decisions: Decision[]): string {
26
+ const lines: string[] = [];
27
+
28
+ lines.push('# Decisions Register');
29
+ lines.push('');
30
+ lines.push('<!-- Append-only. Never edit or remove existing rows.');
31
+ lines.push(' To reverse a decision, add a new row that supersedes it.');
32
+ lines.push(' Read this file at the start of any planning or research phase. -->');
33
+ lines.push('');
34
+ lines.push('| # | When | Scope | Decision | Choice | Rationale | Revisable? |');
35
+ lines.push('|---|------|-------|----------|--------|-----------|------------|');
36
+
37
+ for (const d of decisions) {
38
+ // Escape pipe characters within cell values to preserve table structure
39
+ const cells = [
40
+ d.id,
41
+ d.when_context,
42
+ d.scope,
43
+ d.decision,
44
+ d.choice,
45
+ d.rationale,
46
+ d.revisable,
47
+ ].map(cell => (cell ?? '').replace(/\|/g, '\\|'));
48
+
49
+ lines.push(`| ${cells.join(' | ')} |`);
50
+ }
51
+
52
+ return lines.join('\n') + '\n';
53
+ }
54
+
55
+ // ─── Requirements Markdown Generator ──────────────────────────────────────
56
+
57
+ /** Status values that map to specific sections, in display order. */
58
+ const STATUS_SECTION_MAP: Array<{ status: string; heading: string }> = [
59
+ { status: 'active', heading: 'Active' },
60
+ { status: 'validated', heading: 'Validated' },
61
+ { status: 'deferred', heading: 'Deferred' },
62
+ { status: 'out-of-scope', heading: 'Out of Scope' },
63
+ ];
64
+
65
+ /**
66
+ * Generate full REQUIREMENTS.md content from an array of Requirement objects.
67
+ * Groups requirements by status into sections (## Active, ## Validated, etc.),
68
+ * each containing ### RXXX — Description headings with bullet fields.
69
+ * Only emits sections that have content. Appends Traceability table and
70
+ * Coverage Summary at the bottom.
71
+ */
72
+ export function generateRequirementsMd(requirements: Requirement[]): string {
73
+ const lines: string[] = [];
74
+
75
+ lines.push('# Requirements');
76
+ lines.push('');
77
+ lines.push('This file is the explicit capability and coverage contract for the project.');
78
+ lines.push('');
79
+
80
+ // Group by status
81
+ const byStatus = new Map<string, Requirement[]>();
82
+ for (const r of requirements) {
83
+ const status = (r.status || 'active').toLowerCase();
84
+ if (!byStatus.has(status)) byStatus.set(status, []);
85
+ byStatus.get(status)!.push(r);
86
+ }
87
+
88
+ // Emit sections in canonical order
89
+ for (const { status, heading } of STATUS_SECTION_MAP) {
90
+ const reqs = byStatus.get(status);
91
+ if (!reqs || reqs.length === 0) continue;
92
+
93
+ lines.push(`## ${heading}`);
94
+ lines.push('');
95
+
96
+ for (const r of reqs) {
97
+ lines.push(`### ${r.id} — ${r.description || 'Untitled'}`);
98
+
99
+ // Emit bullet fields — only those with content
100
+ if (r.class) lines.push(`- Class: ${r.class}`);
101
+ if (r.status) lines.push(`- Status: ${r.status}`);
102
+ if (r.description) lines.push(`- Description: ${r.description}`);
103
+ if (r.why) lines.push(`- Why it matters: ${r.why}`);
104
+ if (r.source) lines.push(`- Source: ${r.source}`);
105
+ if (r.primary_owner) lines.push(`- Primary owning slice: ${r.primary_owner}`);
106
+ if (r.supporting_slices) lines.push(`- Supporting slices: ${r.supporting_slices}`);
107
+ if (r.validation) lines.push(`- Validation: ${r.validation}`);
108
+ if (r.notes) lines.push(`- Notes: ${r.notes}`);
109
+ lines.push('');
110
+ }
111
+ }
112
+
113
+ // Traceability table
114
+ lines.push('## Traceability');
115
+ lines.push('');
116
+ lines.push('| ID | Class | Status | Primary owner | Supporting | Proof |');
117
+ lines.push('|---|---|---|---|---|---|');
118
+
119
+ for (const r of requirements) {
120
+ const proof = r.validation || 'unmapped';
121
+ lines.push(
122
+ `| ${r.id} | ${r.class || ''} | ${r.status || ''} | ${r.primary_owner || 'none'} | ${r.supporting_slices || 'none'} | ${proof} |`,
123
+ );
124
+ }
125
+
126
+ lines.push('');
127
+
128
+ // Coverage Summary
129
+ const activeCount = byStatus.get('active')?.length ?? 0;
130
+ const validatedReqs = byStatus.get('validated') ?? [];
131
+ const validatedIds = validatedReqs.map(r => r.id).join(', ');
132
+
133
+ lines.push('## Coverage Summary');
134
+ lines.push('');
135
+ lines.push(`- Active requirements: ${activeCount}`);
136
+ lines.push(`- Mapped to slices: ${activeCount}`);
137
+ lines.push(`- Validated: ${validatedReqs.length}${validatedIds ? ` (${validatedIds})` : ''}`);
138
+ lines.push(`- Unmapped active requirements: 0`);
139
+
140
+ return lines.join('\n') + '\n';
141
+ }
142
+
143
+ // ─── Next Decision ID ─────────────────────────────────────────────────────
144
+
145
+ /**
146
+ * Compute the next decision ID from the current DB state.
147
+ * Queries MAX(CAST(SUBSTR(id, 2) AS INTEGER)) from decisions table.
148
+ * Returns D001 if no decisions exist. Zero-pads to 3 digits.
149
+ */
150
+ export async function nextDecisionId(): Promise<string> {
151
+ try {
152
+ const db = await import('./gsd-db.js');
153
+ const adapter = db._getAdapter();
154
+ if (!adapter) return 'D001';
155
+
156
+ const row = adapter
157
+ .prepare('SELECT MAX(CAST(SUBSTR(id, 2) AS INTEGER)) as max_num FROM decisions')
158
+ .get();
159
+
160
+ const maxNum = row ? (row['max_num'] as number | null) : null;
161
+ if (maxNum == null || isNaN(maxNum)) return 'D001';
162
+
163
+ const next = maxNum + 1;
164
+ return `D${String(next).padStart(3, '0')}`;
165
+ } catch (err) {
166
+ process.stderr.write(`gsd-db: nextDecisionId failed: ${(err as Error).message}\n`);
167
+ return 'D001';
168
+ }
169
+ }
170
+
171
+ // ─── Save Decision to DB + Regenerate Markdown ────────────────────────────
172
+
173
+ export interface SaveDecisionFields {
174
+ scope: string;
175
+ decision: string;
176
+ choice: string;
177
+ rationale: string;
178
+ revisable?: string;
179
+ when_context?: string;
180
+ }
181
+
182
+ /**
183
+ * Save a new decision to DB and regenerate DECISIONS.md.
184
+ * Auto-assigns the next ID via nextDecisionId().
185
+ * Returns the assigned ID.
186
+ */
187
+ export async function saveDecisionToDb(
188
+ fields: SaveDecisionFields,
189
+ basePath: string,
190
+ ): Promise<{ id: string }> {
191
+ try {
192
+ const db = await import('./gsd-db.js');
193
+
194
+ const id = await nextDecisionId();
195
+
196
+ db.upsertDecision({
197
+ id,
198
+ when_context: fields.when_context ?? '',
199
+ scope: fields.scope,
200
+ decision: fields.decision,
201
+ choice: fields.choice,
202
+ rationale: fields.rationale,
203
+ revisable: fields.revisable ?? 'Yes',
204
+ superseded_by: null,
205
+ });
206
+
207
+ // Fetch all decisions (including superseded for the full register)
208
+ const adapter = db._getAdapter();
209
+ let allDecisions: Decision[] = [];
210
+ if (adapter) {
211
+ const rows = adapter.prepare('SELECT * FROM decisions ORDER BY seq').all();
212
+ allDecisions = rows.map(row => ({
213
+ seq: row['seq'] as number,
214
+ id: row['id'] as string,
215
+ when_context: row['when_context'] as string,
216
+ scope: row['scope'] as string,
217
+ decision: row['decision'] as string,
218
+ choice: row['choice'] as string,
219
+ rationale: row['rationale'] as string,
220
+ revisable: row['revisable'] as string,
221
+ superseded_by: (row['superseded_by'] as string) ?? null,
222
+ }));
223
+ }
224
+
225
+ const md = generateDecisionsMd(allDecisions);
226
+ const filePath = resolveGsdRootFile(basePath, 'DECISIONS');
227
+ await saveFile(filePath, md);
228
+
229
+ return { id };
230
+ } catch (err) {
231
+ process.stderr.write(`gsd-db: saveDecisionToDb failed: ${(err as Error).message}\n`);
232
+ throw err;
233
+ }
234
+ }
235
+
236
+ // ─── Update Requirement in DB + Regenerate Markdown ───────────────────────
237
+
238
+ /**
239
+ * Update a requirement in DB and regenerate REQUIREMENTS.md.
240
+ * Fetches existing requirement, merges updates, upserts, then regenerates.
241
+ */
242
+ export async function updateRequirementInDb(
243
+ id: string,
244
+ updates: Partial<Requirement>,
245
+ basePath: string,
246
+ ): Promise<void> {
247
+ try {
248
+ const db = await import('./gsd-db.js');
249
+
250
+ const existing = db.getRequirementById(id);
251
+ if (!existing) {
252
+ throw new Error(`Requirement ${id} not found`);
253
+ }
254
+
255
+ // Merge updates into existing
256
+ const merged: Requirement = {
257
+ ...existing,
258
+ ...updates,
259
+ id: existing.id, // ID cannot be changed
260
+ };
261
+
262
+ db.upsertRequirement(merged);
263
+
264
+ // Fetch ALL requirements (including superseded) for full file regeneration
265
+ const adapter = db._getAdapter();
266
+ let allRequirements: Requirement[] = [];
267
+ if (adapter) {
268
+ const rows = adapter.prepare('SELECT * FROM requirements ORDER BY id').all();
269
+ allRequirements = rows.map(row => ({
270
+ id: row['id'] as string,
271
+ class: row['class'] as string,
272
+ status: row['status'] as string,
273
+ description: row['description'] as string,
274
+ why: row['why'] as string,
275
+ source: row['source'] as string,
276
+ primary_owner: row['primary_owner'] as string,
277
+ supporting_slices: row['supporting_slices'] as string,
278
+ validation: row['validation'] as string,
279
+ notes: row['notes'] as string,
280
+ full_content: row['full_content'] as string,
281
+ superseded_by: (row['superseded_by'] as string) ?? null,
282
+ }));
283
+ }
284
+
285
+ // Filter to non-superseded for the markdown file
286
+ // (superseded requirements don't appear in section headings)
287
+ const nonSuperseded = allRequirements.filter(r => r.superseded_by == null);
288
+
289
+ const md = generateRequirementsMd(nonSuperseded);
290
+ const filePath = resolveGsdRootFile(basePath, 'REQUIREMENTS');
291
+ await saveFile(filePath, md);
292
+ } catch (err) {
293
+ process.stderr.write(`gsd-db: updateRequirementInDb failed: ${(err as Error).message}\n`);
294
+ throw err;
295
+ }
296
+ }
297
+
298
+ // ─── Save Artifact to DB + Disk ───────────────────────────────────────────
299
+
300
+ export interface SaveArtifactOpts {
301
+ path: string;
302
+ artifact_type: string;
303
+ content: string;
304
+ milestone_id?: string;
305
+ slice_id?: string;
306
+ task_id?: string;
307
+ }
308
+
309
+ /**
310
+ * Save an artifact to DB and write the corresponding markdown file to disk.
311
+ * The path is relative to .gsd/ (e.g. "milestones/M001/slices/S06/tasks/T01-SUMMARY.md").
312
+ * The full file path is computed as basePath + '.gsd/' + path.
313
+ */
314
+ export async function saveArtifactToDb(
315
+ opts: SaveArtifactOpts,
316
+ basePath: string,
317
+ ): Promise<void> {
318
+ try {
319
+ const db = await import('./gsd-db.js');
320
+
321
+ db.insertArtifact({
322
+ path: opts.path,
323
+ artifact_type: opts.artifact_type,
324
+ milestone_id: opts.milestone_id ?? null,
325
+ slice_id: opts.slice_id ?? null,
326
+ task_id: opts.task_id ?? null,
327
+ full_content: opts.content,
328
+ });
329
+
330
+ // Write the file to disk (guard against path traversal)
331
+ const gsdDir = resolve(basePath, '.gsd');
332
+ const fullPath = resolve(basePath, '.gsd', opts.path);
333
+ if (!fullPath.startsWith(gsdDir)) {
334
+ throw new Error(`saveArtifactToDb: path escapes .gsd/ directory: ${opts.path}`);
335
+ }
336
+ await saveFile(fullPath, opts.content);
337
+ } catch (err) {
338
+ process.stderr.write(`gsd-db: saveArtifactToDb failed: ${(err as Error).message}\n`);
339
+ throw err;
340
+ }
341
+ }
@@ -0,0 +1,178 @@
1
+ // GSD Extension — Debug Logger
2
+ // Structured JSONL debug logging for diagnosing stuck/slow GSD sessions.
3
+ // Zero overhead when disabled — all public functions are no-ops.
4
+
5
+ import { appendFileSync, mkdirSync, readdirSync, unlinkSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { gsdRoot } from './paths.js';
8
+
9
+ // ─── State ────────────────────────────────────────────────────────────────────
10
+
11
+ let _enabled = false;
12
+ let _logPath: string | null = null;
13
+ let _startTime = 0;
14
+
15
+ /** Rolling counters for the debug summary written on stop. */
16
+ const _counters = {
17
+ deriveStateCalls: 0,
18
+ deriveStateTotalMs: 0,
19
+ ttsrChecks: 0,
20
+ ttsrTotalMs: 0,
21
+ ttsrPeakBuffer: 0,
22
+ parseRoadmapCalls: 0,
23
+ parseRoadmapTotalMs: 0,
24
+ parsePlanCalls: 0,
25
+ parsePlanTotalMs: 0,
26
+ dispatches: 0,
27
+ renders: 0,
28
+ };
29
+
30
+ /** Max debug log files to keep. Older ones are pruned on enable. */
31
+ const MAX_DEBUG_LOGS = 5;
32
+
33
+ // ─── Public API ───────────────────────────────────────────────────────────────
34
+
35
+ /**
36
+ * Enable debug logging. Creates the log file and prunes old logs.
37
+ * Can be activated via `--debug` flag or `GSD_DEBUG=1` env var.
38
+ */
39
+ export function enableDebug(basePath: string): void {
40
+ const debugDir = join(gsdRoot(basePath), 'debug');
41
+ mkdirSync(debugDir, { recursive: true });
42
+
43
+ // Prune old debug logs
44
+ try {
45
+ const files = readdirSync(debugDir)
46
+ .filter(f => f.startsWith('debug-') && f.endsWith('.log'))
47
+ .sort();
48
+ while (files.length >= MAX_DEBUG_LOGS) {
49
+ const oldest = files.shift()!;
50
+ try { unlinkSync(join(debugDir, oldest)); } catch { /* ignore */ }
51
+ }
52
+ } catch { /* non-fatal */ }
53
+
54
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
55
+ _logPath = join(debugDir, `debug-${timestamp}.log`);
56
+ _startTime = Date.now();
57
+ _enabled = true;
58
+
59
+ // Reset counters
60
+ for (const key of Object.keys(_counters) as (keyof typeof _counters)[]) {
61
+ _counters[key] = 0;
62
+ }
63
+ }
64
+
65
+ /** Disable debug logging and return the log file path (if any). */
66
+ export function disableDebug(): string | null {
67
+ const path = _logPath;
68
+ _enabled = false;
69
+ _logPath = null;
70
+ _startTime = 0;
71
+ return path;
72
+ }
73
+
74
+ /** Check if debug mode is active. */
75
+ export function isDebugEnabled(): boolean {
76
+ return _enabled;
77
+ }
78
+
79
+ /** Return the current log file path (or null). */
80
+ export function getDebugLogPath(): string | null {
81
+ return _logPath;
82
+ }
83
+
84
+ /**
85
+ * Log a structured debug event. No-op when debug is disabled.
86
+ *
87
+ * Each event is one JSON line: `{ ts, event, ...data }`
88
+ */
89
+ export function debugLog(event: string, data?: Record<string, unknown>): void {
90
+ if (!_enabled || !_logPath) return;
91
+
92
+ const entry = {
93
+ ts: new Date().toISOString(),
94
+ event,
95
+ ...data,
96
+ };
97
+
98
+ try {
99
+ appendFileSync(_logPath, JSON.stringify(entry) + '\n');
100
+ } catch {
101
+ // Silently ignore write failures — debug logging must never break GSD
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Start a timer for a named operation. Returns a stop function that logs
107
+ * the elapsed time and optional result data.
108
+ *
109
+ * Usage:
110
+ * ```ts
111
+ * const stop = debugTime('derive-state');
112
+ * const result = await deriveState(base);
113
+ * stop({ phase: result.phase });
114
+ * ```
115
+ */
116
+ export function debugTime(event: string): (data?: Record<string, unknown>) => void {
117
+ if (!_enabled) return _noop;
118
+
119
+ const start = performance.now();
120
+ return (data?: Record<string, unknown>) => {
121
+ const elapsed_ms = Math.round((performance.now() - start) * 100) / 100;
122
+ debugLog(event, { elapsed_ms, ...data });
123
+ };
124
+ }
125
+
126
+ // ─── Counter Helpers ──────────────────────────────────────────────────────────
127
+
128
+ /** Increment a debug counter (used by instrumentation points). */
129
+ export function debugCount(counter: keyof typeof _counters, value = 1): void {
130
+ if (!_enabled) return;
131
+ _counters[counter] += value;
132
+ }
133
+
134
+ /** Record a peak value (only updates if new value is higher). */
135
+ export function debugPeak(counter: keyof typeof _counters, value: number): void {
136
+ if (!_enabled) return;
137
+ if (value > _counters[counter]) {
138
+ _counters[counter] = value;
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Write the debug summary and disable logging. Call this when auto-mode stops.
144
+ * Returns the log file path for user notification.
145
+ */
146
+ export function writeDebugSummary(): string | null {
147
+ if (!_enabled || !_logPath) return null;
148
+
149
+ const totalElapsed_ms = Date.now() - _startTime;
150
+ const avgDeriveState_ms = _counters.deriveStateCalls > 0
151
+ ? Math.round((_counters.deriveStateTotalMs / _counters.deriveStateCalls) * 100) / 100
152
+ : 0;
153
+ const avgTtsrCheck_ms = _counters.ttsrChecks > 0
154
+ ? Math.round((_counters.ttsrTotalMs / _counters.ttsrChecks) * 100) / 100
155
+ : 0;
156
+
157
+ debugLog('debug-summary', {
158
+ totalElapsed_ms,
159
+ dispatches: _counters.dispatches,
160
+ deriveStateCalls: _counters.deriveStateCalls,
161
+ avgDeriveState_ms,
162
+ parseRoadmapCalls: _counters.parseRoadmapCalls,
163
+ avgParseRoadmap_ms: _counters.parseRoadmapCalls > 0
164
+ ? Math.round((_counters.parseRoadmapTotalMs / _counters.parseRoadmapCalls) * 100) / 100
165
+ : 0,
166
+ parsePlanCalls: _counters.parsePlanCalls,
167
+ ttsrChecks: _counters.ttsrChecks,
168
+ avgTtsrCheck_ms,
169
+ ttsrPeakBuffer: _counters.ttsrPeakBuffer,
170
+ renders: _counters.renders,
171
+ });
172
+
173
+ return disableDebug();
174
+ }
175
+
176
+ // ─── Internal ─────────────────────────────────────────────────────────────────
177
+
178
+ function _noop(_data?: Record<string, unknown>): void { /* no-op */ }
@@ -1,5 +1,4 @@
1
1
  // GSD Dispatch Guard — prevents out-of-order slice dispatch
2
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
2
 
4
3
  import { readFileSync } from "node:fs";
5
4
  import { readdirSync } from "node:fs";
@@ -72,6 +72,20 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
72
72
 
73
73
  - `version`: schema version. Start at `1`.
74
74
 
75
+ - `mode`: workflow mode — `"solo"` or `"team"`. Sets sensible defaults for git and project settings based on your workflow. Mode defaults are the lowest priority layer — any explicit preference overrides them. Omit to configure everything manually.
76
+
77
+ | Setting | `solo` | `team` |
78
+ |---|---|---|
79
+ | `git.auto_push` | `true` | `false` |
80
+ | `git.push_branches` | `false` | `true` |
81
+ | `git.pre_merge_check` | `false` | `true` |
82
+ | `git.merge_strategy` | `"squash"` | `"squash"` |
83
+ | `git.isolation` | `"worktree"` | `"worktree"` |
84
+ | `git.commit_docs` | `true` | `true` |
85
+ | `unique_milestone_ids` | `false` | `true` |
86
+
87
+ Quick setup: `/gsd mode` (global) or `/gsd mode project` (project-level).
88
+
75
89
  - `always_use_skills`: skills GSD should use whenever they are relevant.
76
90
 
77
91
  - `prefer_skills`: soft defaults GSD should prefer when relevant.
@@ -111,6 +125,7 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
111
125
  - `merge_strategy`: `"squash"` or `"merge"` — controls how worktree branches are merged back. `"squash"` combines all commits into one; `"merge"` preserves individual commits. Default: `"squash"`.
112
126
  - `isolation`: `"worktree"` or `"branch"` — controls auto-mode git isolation strategy. `"worktree"` creates a milestone worktree for isolated work; `"branch"` works directly in the project root (useful for submodule-heavy repos). Default: `"worktree"`.
113
127
  - `commit_docs`: boolean — when `false`, prevents GSD from committing `.gsd/` planning artifacts to git. The `.gsd/` folder is added to `.gitignore` and kept local-only. Useful for teams where only some members use GSD, or when company policy requires a clean repository. Default: `true`.
128
+ - `worktree_post_create`: string — script to run after a worktree is created (both auto-mode and manual `/worktree`). Receives `SOURCE_DIR` and `WORKTREE_DIR` as environment variables. Can be absolute or relative to project root. Runs with 30-second timeout. Failure is non-fatal (logged as warning). Default: none.
114
129
 
115
130
  - `unique_milestone_ids`: boolean — when `true`, generates milestone IDs in `M{seq}-{rand6}` format (e.g. `M001-eh88as`) instead of plain sequential `M001`. Prevents ID collisions in team workflows where multiple contributors create milestones concurrently. Both formats coexist — existing `M001`-style milestones remain valid. Default: `false`.
116
131
 
@@ -189,6 +204,45 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
189
204
 
190
205
  ---
191
206
 
207
+ ## Workflow Mode Examples
208
+
209
+ **Solo developer — auto-push, simple IDs:**
210
+
211
+ ```yaml
212
+ ---
213
+ version: 1
214
+ mode: solo
215
+ ---
216
+ ```
217
+
218
+ Equivalent to setting `git.auto_push: true`, `git.push_branches: false`, `git.pre_merge_check: false`, `git.merge_strategy: squash`, `git.isolation: worktree`, `git.commit_docs: true`, `unique_milestone_ids: false`.
219
+
220
+ **Team — unique IDs, push branches, pre-merge checks:**
221
+
222
+ ```yaml
223
+ ---
224
+ version: 1
225
+ mode: team
226
+ ---
227
+ ```
228
+
229
+ Equivalent to setting `git.auto_push: false`, `git.push_branches: true`, `git.pre_merge_check: true`, `git.merge_strategy: squash`, `git.isolation: worktree`, `git.commit_docs: true`, `unique_milestone_ids: true`.
230
+
231
+ **Mode with overrides — team mode but with auto-push:**
232
+
233
+ ```yaml
234
+ ---
235
+ version: 1
236
+ mode: team
237
+ git:
238
+ auto_push: true
239
+ ---
240
+ ```
241
+
242
+ Gets all team defaults except `auto_push`, which is explicitly overridden to `true`. Any explicit setting always wins over the mode default.
243
+
244
+ ---
245
+
192
246
  ## Minimal Example
193
247
 
194
248
  The cleanest preferences file only specifies what you actually want: