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,526 @@
1
+ // GSD Markdown Importer
2
+ // Parses DECISIONS.md, REQUIREMENTS.md, and hierarchy artifacts from a .gsd/ tree,
3
+ // then upserts everything into the SQLite database.
4
+ //
5
+ // Exports: parseDecisionsTable, parseRequirementsSections, migrateFromMarkdown
6
+
7
+ import { readFileSync, readdirSync, existsSync } from 'node:fs';
8
+ import { join, relative } from 'node:path';
9
+ import type { Decision, Requirement } from './types.js';
10
+ import {
11
+ upsertDecision,
12
+ upsertRequirement,
13
+ insertArtifact,
14
+ openDatabase,
15
+ transaction,
16
+ _getAdapter,
17
+ } from './gsd-db.js';
18
+ import {
19
+ resolveGsdRootFile,
20
+ milestonesDir,
21
+ resolveTaskFiles,
22
+ } from './paths.js';
23
+ import { findMilestoneIds } from './guided-flow.js';
24
+
25
+ // ─── DECISIONS.md Parser ───────────────────────────────────────────────────
26
+
27
+ /**
28
+ * Parse a DECISIONS.md markdown table into Decision objects (without seq).
29
+ * Detects `(amends DXXX)` in the Decision column to build supersession info.
30
+ * Returns parsed rows with superseded_by set to null; callers handle chaining.
31
+ */
32
+ export function parseDecisionsTable(content: string): Omit<Decision, 'seq'>[] {
33
+ const lines = content.split('\n');
34
+ const results: Omit<Decision, 'seq'>[] = [];
35
+
36
+ // Map from amended ID → amending ID for supersession
37
+ const amendsMap = new Map<string, string>();
38
+
39
+ for (const line of lines) {
40
+ // Skip non-table lines, header, and separator
41
+ if (!line.trim().startsWith('|')) continue;
42
+ const trimmed = line.trim();
43
+ // Skip separator rows like |---|---|...|
44
+ if (/^\|[\s-|]+\|$/.test(trimmed)) continue;
45
+
46
+ // Split on | and strip leading/trailing empty cells
47
+ const cells = trimmed.split('|').map(c => c.trim());
48
+ // Remove first and last empty strings from leading/trailing |
49
+ if (cells.length > 0 && cells[0] === '') cells.shift();
50
+ if (cells.length > 0 && cells[cells.length - 1] === '') cells.pop();
51
+
52
+ if (cells.length < 7) continue;
53
+
54
+ const id = cells[0].trim();
55
+ // Skip header row
56
+ if (id === '#' || id.toLowerCase() === 'id') continue;
57
+ // Must look like a decision ID (D followed by digits)
58
+ if (!/^D\d+/.test(id)) continue;
59
+
60
+ const when_context = cells[1].trim();
61
+ const scope = cells[2].trim();
62
+ const decisionText = cells[3].trim();
63
+ const choice = cells[4].trim();
64
+ const rationale = cells[5].trim();
65
+ const revisable = cells[6].trim();
66
+
67
+ // Detect (amends DXXX) in the Decision column
68
+ const amendsMatch = decisionText.match(/\(amends\s+(D\d+)\)/i);
69
+ if (amendsMatch) {
70
+ amendsMap.set(amendsMatch[1], id);
71
+ }
72
+
73
+ results.push({
74
+ id,
75
+ when_context,
76
+ scope,
77
+ decision: decisionText,
78
+ choice,
79
+ rationale,
80
+ revisable,
81
+ superseded_by: null,
82
+ });
83
+ }
84
+
85
+ // Apply supersession: if D010 amends D001, set D001.superseded_by = D010
86
+ // Handle chains: if D020 amends D010 and D010 amends D001,
87
+ // D001.superseded_by = D010, D010.superseded_by = D020
88
+ for (const row of results) {
89
+ if (amendsMap.has(row.id)) {
90
+ row.superseded_by = amendsMap.get(row.id)!;
91
+ }
92
+ }
93
+
94
+ return results;
95
+ }
96
+
97
+ // ─── REQUIREMENTS.md Parser ────────────────────────────────────────────────
98
+
99
+ const STATUS_SECTIONS: Record<string, string> = {
100
+ '## active': 'active',
101
+ '## validated': 'validated',
102
+ '## deferred': 'deferred',
103
+ '## out of scope': 'out-of-scope',
104
+ };
105
+
106
+ /**
107
+ * Parse REQUIREMENTS.md into Requirement objects.
108
+ * Finds section headings (## Active, ## Validated, ## Deferred, ## Out of Scope),
109
+ * then within each section finds ### RXXX — Title blocks and extracts bullet fields.
110
+ */
111
+ export function parseRequirementsSections(content: string): Requirement[] {
112
+ const lines = content.split('\n');
113
+ const results: Requirement[] = [];
114
+
115
+ let currentSectionStatus: string | null = null;
116
+ let currentReq: Partial<Requirement> | null = null;
117
+ let currentFullContentLines: string[] = [];
118
+
119
+ function flushReq(): void {
120
+ if (currentReq && currentReq.id) {
121
+ currentReq.full_content = currentFullContentLines.join('\n').trim();
122
+ results.push({
123
+ id: currentReq.id!,
124
+ class: currentReq.class ?? '',
125
+ status: currentReq.status ?? currentSectionStatus ?? '',
126
+ description: currentReq.description ?? '',
127
+ why: currentReq.why ?? '',
128
+ source: currentReq.source ?? '',
129
+ primary_owner: currentReq.primary_owner ?? '',
130
+ supporting_slices: currentReq.supporting_slices ?? '',
131
+ validation: currentReq.validation ?? '',
132
+ notes: currentReq.notes ?? '',
133
+ full_content: currentReq.full_content ?? '',
134
+ superseded_by: currentReq.superseded_by ?? null,
135
+ });
136
+ }
137
+ currentReq = null;
138
+ currentFullContentLines = [];
139
+ }
140
+
141
+ for (let i = 0; i < lines.length; i++) {
142
+ const line = lines[i];
143
+ const lineLower = line.trim().toLowerCase();
144
+
145
+ // Check for section heading (## Active, ## Validated, etc.)
146
+ if (lineLower.startsWith('## ')) {
147
+ flushReq();
148
+ const matchedSection = Object.entries(STATUS_SECTIONS).find(
149
+ ([prefix]) => lineLower === prefix || lineLower.startsWith(prefix + ' ')
150
+ );
151
+ if (matchedSection) {
152
+ currentSectionStatus = matchedSection[1];
153
+ } else {
154
+ // Sections like ## Traceability, ## Coverage Summary — stop parsing requirements
155
+ currentSectionStatus = null;
156
+ }
157
+ continue;
158
+ }
159
+
160
+ // Check for requirement heading (### RXXX — Title)
161
+ const reqMatch = line.match(/^###\s+(R\d+)\s*[—–-]\s*(.+)/);
162
+ if (reqMatch) {
163
+ flushReq();
164
+ if (currentSectionStatus !== null) {
165
+ currentReq = {
166
+ id: reqMatch[1],
167
+ status: currentSectionStatus,
168
+ };
169
+ currentFullContentLines = [line];
170
+ }
171
+ continue;
172
+ }
173
+
174
+ // If we're inside a requirement block, collect content and extract bullets
175
+ if (currentReq && currentSectionStatus !== null) {
176
+ currentFullContentLines.push(line);
177
+
178
+ // Extract field bullets: "- Field: value" or "- Field name: value"
179
+ const bulletMatch = line.match(/^-\s+(.+?):\s+(.*)/);
180
+ if (bulletMatch) {
181
+ const fieldName = bulletMatch[1].trim().toLowerCase();
182
+ const value = bulletMatch[2].trim();
183
+
184
+ switch (fieldName) {
185
+ case 'class':
186
+ currentReq.class = value;
187
+ break;
188
+ case 'status':
189
+ // Bullet status takes precedence over section heading
190
+ currentReq.status = value;
191
+ break;
192
+ case 'description':
193
+ currentReq.description = value;
194
+ break;
195
+ case 'why it matters':
196
+ case 'why':
197
+ currentReq.why = value;
198
+ break;
199
+ case 'source':
200
+ currentReq.source = value;
201
+ break;
202
+ case 'primary owning slice':
203
+ case 'primary owner':
204
+ case 'primary_owner':
205
+ currentReq.primary_owner = value;
206
+ break;
207
+ case 'supporting slices':
208
+ case 'supporting_slices':
209
+ currentReq.supporting_slices = value;
210
+ break;
211
+ case 'validation':
212
+ case 'validated by':
213
+ currentReq.validation = value;
214
+ break;
215
+ case 'notes':
216
+ currentReq.notes = value;
217
+ break;
218
+ case 'proof':
219
+ // In validated section, "Proof:" serves as notes
220
+ currentReq.notes = value;
221
+ break;
222
+ }
223
+ }
224
+ }
225
+ }
226
+
227
+ flushReq();
228
+
229
+ // Deduplicate by ID: if a requirement appears in both Active and Validated sections,
230
+ // keep the fuller entry (typically Active) and merge in any non-empty fields from later entries.
231
+ const deduped = new Map<string, Requirement>();
232
+ for (const req of results) {
233
+ const existing = deduped.get(req.id);
234
+ if (!existing) {
235
+ deduped.set(req.id, req);
236
+ } else {
237
+ // Merge: non-empty fields from later entry override empty fields in existing
238
+ for (const key of Object.keys(req) as (keyof Requirement)[]) {
239
+ if (key === 'id' || key === 'superseded_by') continue;
240
+ const val = req[key];
241
+ if (val && val !== '' && (!existing[key] || existing[key] === '')) {
242
+ (existing as unknown as Record<string, unknown>)[key] = val;
243
+ }
244
+ }
245
+ }
246
+ }
247
+
248
+ return Array.from(deduped.values());
249
+ }
250
+
251
+ // ─── Import Functions ──────────────────────────────────────────────────────
252
+
253
+ /**
254
+ * Import decisions from DECISIONS.md into the database.
255
+ * Handles supersession chains.
256
+ */
257
+ function importDecisions(gsdDir: string): number {
258
+ const filePath = resolveGsdRootFile(gsdDir, 'DECISIONS');
259
+ if (!existsSync(filePath)) return 0;
260
+
261
+ const content = readFileSync(filePath, 'utf-8');
262
+ const decisions = parseDecisionsTable(content);
263
+
264
+ for (const d of decisions) {
265
+ upsertDecision(d);
266
+ }
267
+
268
+ return decisions.length;
269
+ }
270
+
271
+ /**
272
+ * Import requirements from REQUIREMENTS.md into the database.
273
+ */
274
+ function importRequirements(gsdDir: string): number {
275
+ const filePath = resolveGsdRootFile(gsdDir, 'REQUIREMENTS');
276
+ if (!existsSync(filePath)) return 0;
277
+
278
+ const content = readFileSync(filePath, 'utf-8');
279
+ const requirements = parseRequirementsSections(content);
280
+
281
+ for (const r of requirements) {
282
+ upsertRequirement(r);
283
+ }
284
+
285
+ return requirements.length;
286
+ }
287
+
288
+ // ─── Hierarchy Artifact Walker ─────────────────────────────────────────────
289
+
290
+ /** Artifact suffixes to look for at each hierarchy level */
291
+ const MILESTONE_SUFFIXES = ['ROADMAP', 'CONTEXT', 'RESEARCH', 'ASSESSMENT'];
292
+ const SLICE_SUFFIXES = ['PLAN', 'SUMMARY', 'RESEARCH', 'CONTEXT', 'ASSESSMENT', 'UAT'];
293
+ const TASK_SUFFIXES = ['PLAN', 'SUMMARY', 'CONTINUE', 'CONTEXT', 'RESEARCH'];
294
+
295
+ /**
296
+ * Import hierarchy artifacts (roadmaps, plans, summaries, etc.) from the .gsd/ tree.
297
+ * Walks milestones → slices → tasks directories.
298
+ */
299
+ function importHierarchyArtifacts(gsdDir: string): number {
300
+ let count = 0;
301
+ const gsdPath = join(gsdDir, '.gsd');
302
+
303
+ // Root-level artifacts: PROJECT.md, QUEUE.md
304
+ const rootFiles = ['PROJECT.md', 'QUEUE.md', 'SECRETS-MANIFEST.md'];
305
+ for (const fileName of rootFiles) {
306
+ const filePath = join(gsdPath, fileName);
307
+ if (existsSync(filePath)) {
308
+ const content = readFileSync(filePath, 'utf-8');
309
+ const artifactType = fileName.replace('.md', '').replace('-', '_');
310
+ insertArtifact({
311
+ path: fileName,
312
+ artifact_type: artifactType,
313
+ milestone_id: null,
314
+ slice_id: null,
315
+ task_id: null,
316
+ full_content: content,
317
+ });
318
+ count++;
319
+ }
320
+ }
321
+
322
+ // Walk milestones
323
+ const milestoneIds = findMilestoneIds(gsdDir);
324
+ const msDir = milestonesDir(gsdDir);
325
+
326
+ for (const milestoneId of milestoneIds) {
327
+ // Find the actual milestone directory name (handles legacy naming)
328
+ const milestoneDirName = findDirByPrefix(msDir, milestoneId);
329
+ if (!milestoneDirName) continue;
330
+ const milestoneFullPath = join(msDir, milestoneDirName);
331
+
332
+ // Milestone-level files
333
+ count += importFilesAtLevel(
334
+ milestoneFullPath,
335
+ milestoneId,
336
+ MILESTONE_SUFFIXES,
337
+ `milestones/${milestoneDirName}`,
338
+ milestoneId,
339
+ null,
340
+ null,
341
+ );
342
+
343
+ // Walk slices
344
+ const slicesDir = join(milestoneFullPath, 'slices');
345
+ if (!existsSync(slicesDir)) continue;
346
+
347
+ const sliceDirs = readdirSync(slicesDir, { withFileTypes: true })
348
+ .filter(d => d.isDirectory() && /^S\d+/.test(d.name))
349
+ .map(d => d.name)
350
+ .sort();
351
+
352
+ for (const sliceDirName of sliceDirs) {
353
+ const sliceId = sliceDirName.match(/^(S\d+)/)?.[1] ?? sliceDirName;
354
+ const sliceFullPath = join(slicesDir, sliceDirName);
355
+
356
+ // Slice-level files
357
+ count += importFilesAtLevel(
358
+ sliceFullPath,
359
+ sliceId,
360
+ SLICE_SUFFIXES,
361
+ `milestones/${milestoneDirName}/slices/${sliceDirName}`,
362
+ milestoneId,
363
+ sliceId,
364
+ null,
365
+ );
366
+
367
+ // Walk tasks
368
+ const tasksDir = join(sliceFullPath, 'tasks');
369
+ if (!existsSync(tasksDir)) continue;
370
+
371
+ for (const suffix of TASK_SUFFIXES) {
372
+ const taskFiles = resolveTaskFiles(tasksDir, suffix);
373
+ for (const taskFileName of taskFiles) {
374
+ const taskId = taskFileName.match(/^(T\d+)/)?.[1] ?? null;
375
+ const taskFilePath = join(tasksDir, taskFileName);
376
+ if (!existsSync(taskFilePath)) continue;
377
+
378
+ const content = readFileSync(taskFilePath, 'utf-8');
379
+ const relPath = `milestones/${milestoneDirName}/slices/${sliceDirName}/tasks/${taskFileName}`;
380
+
381
+ insertArtifact({
382
+ path: relPath,
383
+ artifact_type: suffix,
384
+ milestone_id: milestoneId,
385
+ slice_id: sliceId,
386
+ task_id: taskId,
387
+ full_content: content,
388
+ });
389
+ count++;
390
+ }
391
+ }
392
+ }
393
+ }
394
+
395
+ return count;
396
+ }
397
+
398
+ /**
399
+ * Import files at a specific hierarchy level (milestone or slice).
400
+ */
401
+ function importFilesAtLevel(
402
+ dirPath: string,
403
+ idPrefix: string,
404
+ suffixes: string[],
405
+ relativeBase: string,
406
+ milestoneId: string,
407
+ sliceId: string | null,
408
+ taskId: string | null,
409
+ ): number {
410
+ let count = 0;
411
+
412
+ for (const suffix of suffixes) {
413
+ // Try ID-SUFFIX.md pattern (e.g., M001-ROADMAP.md, S01-PLAN.md)
414
+ const fileName = findFileByPrefixAndSuffix(dirPath, idPrefix, suffix);
415
+ if (!fileName) continue;
416
+
417
+ const filePath = join(dirPath, fileName);
418
+ if (!existsSync(filePath)) continue;
419
+
420
+ const content = readFileSync(filePath, 'utf-8');
421
+ const relPath = `${relativeBase}/${fileName}`;
422
+
423
+ insertArtifact({
424
+ path: relPath,
425
+ artifact_type: suffix,
426
+ milestone_id: milestoneId,
427
+ slice_id: sliceId,
428
+ task_id: taskId,
429
+ full_content: content,
430
+ });
431
+ count++;
432
+ }
433
+
434
+ return count;
435
+ }
436
+
437
+ /**
438
+ * Find a directory by ID prefix within a parent directory.
439
+ */
440
+ function findDirByPrefix(parentDir: string, idPrefix: string): string | null {
441
+ if (!existsSync(parentDir)) return null;
442
+ try {
443
+ const entries = readdirSync(parentDir, { withFileTypes: true });
444
+ // Exact match first
445
+ const exact = entries.find(e => e.isDirectory() && e.name === idPrefix);
446
+ if (exact) return exact.name;
447
+ // Prefix match for legacy
448
+ const prefixed = entries.find(e => e.isDirectory() && e.name.startsWith(idPrefix + '-'));
449
+ return prefixed ? prefixed.name : null;
450
+ } catch {
451
+ return null;
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Find a file by ID prefix and suffix within a directory.
457
+ * Matches ID-SUFFIX.md or ID-*-SUFFIX.md patterns.
458
+ */
459
+ function findFileByPrefixAndSuffix(dir: string, idPrefix: string, suffix: string): string | null {
460
+ if (!existsSync(dir)) return null;
461
+ try {
462
+ const entries = readdirSync(dir);
463
+ // Direct: ID-SUFFIX.md
464
+ const target = `${idPrefix}-${suffix}.md`.toUpperCase();
465
+ const direct = entries.find(e => e.toUpperCase() === target);
466
+ if (direct) return direct;
467
+ // Legacy: ID-DESCRIPTOR-SUFFIX.md
468
+ const pattern = new RegExp(`^${idPrefix}-.*-${suffix}\\.md$`, 'i');
469
+ const match = entries.find(e => pattern.test(e));
470
+ return match ?? null;
471
+ } catch {
472
+ return null;
473
+ }
474
+ }
475
+
476
+ // ─── Orchestrator ──────────────────────────────────────────────────────────
477
+
478
+ /**
479
+ * Import all markdown artifacts from a .gsd/ directory into the database.
480
+ * Opens the DB if not already open. Wraps all imports in a single transaction.
481
+ * Returns counts of imported items for logging.
482
+ *
483
+ * Missing files are skipped gracefully — no errors produced.
484
+ */
485
+ export function migrateFromMarkdown(gsdDir: string): {
486
+ decisions: number;
487
+ requirements: number;
488
+ artifacts: number;
489
+ } {
490
+ const dbPath = join(gsdDir, '.gsd', 'gsd.db');
491
+
492
+ // Open DB if not already open
493
+ if (!_getAdapter()) {
494
+ openDatabase(dbPath);
495
+ }
496
+
497
+ let decisions = 0;
498
+ let requirements = 0;
499
+ let artifacts = 0;
500
+
501
+ transaction(() => {
502
+ try {
503
+ decisions = importDecisions(gsdDir);
504
+ } catch (err) {
505
+ process.stderr.write(`gsd-migrate: skipping decisions import: ${(err as Error).message}\n`);
506
+ }
507
+
508
+ try {
509
+ requirements = importRequirements(gsdDir);
510
+ } catch (err) {
511
+ process.stderr.write(`gsd-migrate: skipping requirements import: ${(err as Error).message}\n`);
512
+ }
513
+
514
+ try {
515
+ artifacts = importHierarchyArtifacts(gsdDir);
516
+ } catch (err) {
517
+ process.stderr.write(`gsd-migrate: skipping artifacts import: ${(err as Error).message}\n`);
518
+ }
519
+ });
520
+
521
+ process.stderr.write(
522
+ `gsd-migrate: imported ${decisions} decisions, ${requirements} requirements, ${artifacts} artifacts\n`,
523
+ );
524
+
525
+ return { decisions, requirements, artifacts };
526
+ }
@@ -17,6 +17,7 @@ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
17
17
  import { join } from "node:path";
18
18
  import type { ExtensionContext } from "@gsd/pi-coding-agent";
19
19
  import { gsdRoot } from "./paths.js";
20
+ import { getAndClearSkills } from "./skill-telemetry.js";
20
21
 
21
22
  // ─── Types ────────────────────────────────────────────────────────────────────
22
23
 
@@ -39,8 +40,22 @@ export interface UnitMetrics {
39
40
  toolCalls: number;
40
41
  assistantMessages: number;
41
42
  userMessages: number;
43
+ // Budget fields (optional — absent in pre-M009 metrics data)
44
+ contextWindowTokens?: number;
45
+ truncationSections?: number;
46
+ continueHereFired?: boolean;
47
+ promptCharCount?: number;
48
+ baselineCharCount?: number;
42
49
  tier?: string; // complexity tier (light/standard/heavy) if dynamic routing active
43
50
  modelDowngraded?: boolean; // true if dynamic routing used a cheaper model
51
+ skills?: string[]; // skill names available/loaded during this unit (#599)
52
+ }
53
+
54
+ /** Budget state passed to snapshotUnitMetrics for persistence in the metrics ledger. */
55
+ export interface BudgetInfo {
56
+ contextWindowTokens?: number;
57
+ truncationSections?: number;
58
+ continueHereFired?: boolean;
44
59
  }
45
60
 
46
61
  export interface MetricsLedger {
@@ -106,7 +121,7 @@ export function snapshotUnitMetrics(
106
121
  unitId: string,
107
122
  startedAt: number,
108
123
  model: string,
109
- extras?: { tier?: string; modelDowngraded?: boolean },
124
+ opts?: { tier?: string; modelDowngraded?: boolean; contextWindowTokens?: number; truncationSections?: number; continueHereFired?: boolean; promptCharCount?: number; baselineCharCount?: number },
110
125
  ): UnitMetrics | null {
111
126
  if (!ledger) return null;
112
127
 
@@ -159,10 +174,21 @@ export function snapshotUnitMetrics(
159
174
  toolCalls,
160
175
  assistantMessages,
161
176
  userMessages,
162
- ...(extras?.tier ? { tier: extras.tier } : {}),
163
- ...(extras?.modelDowngraded !== undefined ? { modelDowngraded: extras.modelDowngraded } : {}),
177
+ ...(opts?.tier ? { tier: opts.tier } : {}),
178
+ ...(opts?.modelDowngraded !== undefined ? { modelDowngraded: opts.modelDowngraded } : {}),
179
+ ...(opts?.contextWindowTokens !== undefined ? { contextWindowTokens: opts.contextWindowTokens } : {}),
180
+ ...(opts?.truncationSections !== undefined ? { truncationSections: opts.truncationSections } : {}),
181
+ ...(opts?.continueHereFired !== undefined ? { continueHereFired: opts.continueHereFired } : {}),
182
+ ...(opts?.promptCharCount != null ? { promptCharCount: opts.promptCharCount } : {}),
183
+ ...(opts?.baselineCharCount != null ? { baselineCharCount: opts.baselineCharCount } : {}),
164
184
  };
165
185
 
186
+ // Auto-capture skill telemetry (#599)
187
+ const skills = getAndClearSkills();
188
+ if (skills.length > 0) {
189
+ unit.skills = skills;
190
+ }
191
+
166
192
  ledger.units.push(unit);
167
193
  saveLedger(basePath, ledger);
168
194
 
@@ -199,6 +225,7 @@ export interface ModelAggregate {
199
225
  units: number;
200
226
  tokens: TokenCounts;
201
227
  cost: number;
228
+ contextWindowTokens?: number;
202
229
  }
203
230
 
204
231
  export interface ProjectTotals {
@@ -209,6 +236,8 @@ export interface ProjectTotals {
209
236
  toolCalls: number;
210
237
  assistantMessages: number;
211
238
  userMessages: number;
239
+ totalTruncationSections: number;
240
+ continueHereFiredCount: number;
212
241
  }
213
242
 
214
243
  function emptyTokens(): TokenCounts {
@@ -274,6 +303,9 @@ export function aggregateByModel(units: UnitMetrics[]): ModelAggregate[] {
274
303
  agg.units++;
275
304
  agg.tokens = addTokens(agg.tokens, u.tokens);
276
305
  agg.cost += u.cost;
306
+ if (u.contextWindowTokens !== undefined && agg.contextWindowTokens === undefined) {
307
+ agg.contextWindowTokens = u.contextWindowTokens;
308
+ }
277
309
  }
278
310
  return Array.from(map.values()).sort((a, b) => b.cost - a.cost);
279
311
  }
@@ -287,6 +319,8 @@ export function getProjectTotals(units: UnitMetrics[]): ProjectTotals {
287
319
  toolCalls: 0,
288
320
  assistantMessages: 0,
289
321
  userMessages: 0,
322
+ totalTruncationSections: 0,
323
+ continueHereFiredCount: 0,
290
324
  };
291
325
  for (const u of units) {
292
326
  totals.tokens = addTokens(totals.tokens, u.tokens);
@@ -295,6 +329,8 @@ export function getProjectTotals(units: UnitMetrics[]): ProjectTotals {
295
329
  totals.toolCalls += u.toolCalls;
296
330
  totals.assistantMessages += u.assistantMessages;
297
331
  totals.userMessages += u.userMessages;
332
+ totals.totalTruncationSections += u.truncationSections ?? 0;
333
+ if (u.continueHereFired) totals.continueHereFiredCount++;
298
334
  }
299
335
  return totals;
300
336
  }
@@ -1,6 +1,5 @@
1
1
  // GSD Extension — Desktop Notification Helper
2
2
  // Cross-platform desktop notifications for auto-mode events.
3
- // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
4
3
 
5
4
  import { execFileSync } from "node:child_process";
6
5
  import type { NotificationPreferences } from "./types.js";