gsd-pi 2.74.0-dev.2b524c3 → 2.74.0-dev.b741afb

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 (159) hide show
  1. package/dist/cli.js +85 -0
  2. package/dist/headless-query.js +4 -1
  3. package/dist/help-text.js +23 -0
  4. package/dist/resources/extensions/gsd/auto/detect-stuck.js +11 -4
  5. package/dist/resources/extensions/gsd/auto/phases.js +45 -1
  6. package/dist/resources/extensions/gsd/auto-post-unit.js +52 -56
  7. package/dist/resources/extensions/gsd/auto-prompts.js +12 -0
  8. package/dist/resources/extensions/gsd/auto.js +8 -2
  9. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +21 -8
  10. package/dist/resources/extensions/gsd/commands/catalog.js +26 -1
  11. package/dist/resources/extensions/gsd/commands/handlers/ops.js +20 -0
  12. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +68 -9
  13. package/dist/resources/extensions/gsd/commands-add-tests.js +111 -0
  14. package/dist/resources/extensions/gsd/commands-backlog.js +140 -0
  15. package/dist/resources/extensions/gsd/commands-do.js +79 -0
  16. package/dist/resources/extensions/gsd/commands-maintenance.js +6 -6
  17. package/dist/resources/extensions/gsd/commands-pr-branch.js +180 -0
  18. package/dist/resources/extensions/gsd/commands-session-report.js +82 -0
  19. package/dist/resources/extensions/gsd/commands-ship.js +187 -0
  20. package/dist/resources/extensions/gsd/db-writer.js +3 -5
  21. package/dist/resources/extensions/gsd/graph-context.js +66 -0
  22. package/dist/resources/extensions/gsd/gsd-db.js +321 -0
  23. package/dist/resources/extensions/gsd/index.js +15 -2
  24. package/dist/resources/extensions/gsd/md-importer.js +3 -4
  25. package/dist/resources/extensions/gsd/memory-store.js +19 -51
  26. package/dist/resources/extensions/gsd/milestone-validation-gates.js +13 -12
  27. package/dist/resources/extensions/gsd/native-git-bridge.js +7 -4
  28. package/dist/resources/extensions/gsd/prompts/add-tests.md +35 -0
  29. package/dist/resources/extensions/gsd/state.js +5 -1
  30. package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -0
  31. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +3 -14
  32. package/dist/resources/extensions/gsd/triage-resolution.js +2 -5
  33. package/dist/resources/extensions/gsd/workflow-manifest.js +8 -69
  34. package/dist/resources/extensions/gsd/workflow-migration.js +21 -22
  35. package/dist/resources/extensions/gsd/workflow-projections.js +4 -1
  36. package/dist/resources/extensions/gsd/workflow-reconcile.js +14 -11
  37. package/dist/tsconfig.extensions.tsbuildinfo +1 -0
  38. package/dist/web/standalone/.next/BUILD_ID +1 -1
  39. package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
  40. package/dist/web/standalone/.next/build-manifest.json +2 -2
  41. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  42. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/index.html +1 -1
  59. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
  66. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  68. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  69. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  70. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  71. package/package.json +3 -2
  72. package/packages/daemon/package.json +2 -2
  73. package/packages/mcp-server/dist/index.d.ts +3 -0
  74. package/packages/mcp-server/dist/index.d.ts.map +1 -1
  75. package/packages/mcp-server/dist/index.js +3 -0
  76. package/packages/mcp-server/dist/index.js.map +1 -1
  77. package/packages/mcp-server/dist/readers/graph.d.ts +87 -0
  78. package/packages/mcp-server/dist/readers/graph.d.ts.map +1 -0
  79. package/packages/mcp-server/dist/readers/graph.js +548 -0
  80. package/packages/mcp-server/dist/readers/graph.js.map +1 -0
  81. package/packages/mcp-server/dist/readers/index.d.ts +2 -0
  82. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -1
  83. package/packages/mcp-server/dist/readers/index.js +1 -0
  84. package/packages/mcp-server/dist/readers/index.js.map +1 -1
  85. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  86. package/packages/mcp-server/dist/server.js +65 -0
  87. package/packages/mcp-server/dist/server.js.map +1 -1
  88. package/packages/mcp-server/package.json +2 -2
  89. package/packages/mcp-server/src/index.ts +15 -0
  90. package/packages/mcp-server/src/readers/graph.test.ts +426 -0
  91. package/packages/mcp-server/src/readers/graph.ts +708 -0
  92. package/packages/mcp-server/src/readers/index.ts +12 -0
  93. package/packages/mcp-server/src/server.ts +83 -0
  94. package/packages/mcp-server/tsconfig.json +1 -0
  95. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -0
  96. package/packages/native/package.json +2 -2
  97. package/packages/native/tsconfig.tsbuildinfo +1 -0
  98. package/packages/pi-agent-core/package.json +1 -1
  99. package/packages/pi-agent-core/tsconfig.json +1 -0
  100. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -0
  101. package/packages/pi-ai/package.json +1 -1
  102. package/packages/pi-ai/tsconfig.json +1 -0
  103. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -0
  104. package/packages/pi-coding-agent/tsconfig.json +1 -0
  105. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -0
  106. package/packages/pi-tui/package.json +1 -1
  107. package/packages/pi-tui/tsconfig.json +1 -0
  108. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -0
  109. package/packages/rpc-client/package.json +1 -1
  110. package/packages/rpc-client/tsconfig.json +1 -0
  111. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -0
  112. package/src/resources/extensions/gsd/auto/detect-stuck.ts +12 -4
  113. package/src/resources/extensions/gsd/auto/loop-deps.ts +6 -0
  114. package/src/resources/extensions/gsd/auto/phases.ts +68 -1
  115. package/src/resources/extensions/gsd/auto-post-unit.ts +60 -57
  116. package/src/resources/extensions/gsd/auto-prompts.ts +13 -0
  117. package/src/resources/extensions/gsd/auto.ts +7 -0
  118. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -8
  119. package/src/resources/extensions/gsd/commands/catalog.ts +26 -1
  120. package/src/resources/extensions/gsd/commands/handlers/ops.ts +20 -0
  121. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +74 -9
  122. package/src/resources/extensions/gsd/commands-add-tests.ts +137 -0
  123. package/src/resources/extensions/gsd/commands-backlog.ts +182 -0
  124. package/src/resources/extensions/gsd/commands-do.ts +109 -0
  125. package/src/resources/extensions/gsd/commands-maintenance.ts +6 -6
  126. package/src/resources/extensions/gsd/commands-pr-branch.ts +234 -0
  127. package/src/resources/extensions/gsd/commands-session-report.ts +101 -0
  128. package/src/resources/extensions/gsd/commands-ship.ts +219 -0
  129. package/src/resources/extensions/gsd/db-writer.ts +3 -5
  130. package/src/resources/extensions/gsd/graph-context.ts +85 -0
  131. package/src/resources/extensions/gsd/gsd-db.ts +467 -0
  132. package/src/resources/extensions/gsd/index.ts +18 -2
  133. package/src/resources/extensions/gsd/md-importer.ts +3 -5
  134. package/src/resources/extensions/gsd/memory-store.ts +31 -62
  135. package/src/resources/extensions/gsd/milestone-validation-gates.ts +13 -14
  136. package/src/resources/extensions/gsd/native-git-bridge.ts +11 -12
  137. package/src/resources/extensions/gsd/prompts/add-tests.md +35 -0
  138. package/src/resources/extensions/gsd/state.ts +9 -2
  139. package/src/resources/extensions/gsd/tests/commands-backlog.test.ts +158 -0
  140. package/src/resources/extensions/gsd/tests/commands-do.test.ts +127 -0
  141. package/src/resources/extensions/gsd/tests/commands-pr-branch.test.ts +68 -0
  142. package/src/resources/extensions/gsd/tests/commands-session-report.test.ts +82 -0
  143. package/src/resources/extensions/gsd/tests/commands-ship.test.ts +71 -0
  144. package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +14 -0
  145. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +154 -0
  146. package/src/resources/extensions/gsd/tests/graph-context.test.ts +337 -0
  147. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +68 -1
  148. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +140 -0
  149. package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +180 -0
  150. package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +223 -0
  151. package/src/resources/extensions/gsd/tools/complete-slice.ts +19 -0
  152. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +3 -11
  153. package/src/resources/extensions/gsd/triage-resolution.ts +2 -7
  154. package/src/resources/extensions/gsd/workflow-manifest.ts +9 -104
  155. package/src/resources/extensions/gsd/workflow-migration.ts +21 -29
  156. package/src/resources/extensions/gsd/workflow-projections.ts +8 -1
  157. package/src/resources/extensions/gsd/workflow-reconcile.ts +15 -15
  158. /package/dist/web/standalone/.next/static/{YzIEI9sxJy4t5xgClF08g → XnHY5eXUsTCFmNodWHetD}/_buildManifest.js +0 -0
  159. /package/dist/web/standalone/.next/static/{YzIEI9sxJy4t5xgClF08g → XnHY5eXUsTCFmNodWHetD}/_ssgManifest.js +0 -0
@@ -0,0 +1,548 @@
1
+ // GSD MCP Server — knowledge graph reader
2
+ // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
3
+ /**
4
+ * Knowledge Graph for GSD projects.
5
+ *
6
+ * Parses .gsd/ artifacts (STATE.md, milestone ROADMAPs, slice PLANs,
7
+ * KNOWLEDGE.md) into a graph of nodes and edges. Parse errors in any
8
+ * single artifact are caught and never propagate — the artifact is skipped
9
+ * and the rest of the graph is returned.
10
+ *
11
+ * writeGraph() is atomic: writes to graph.tmp.json then renames to graph.json.
12
+ */
13
+ import { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from 'node:fs';
14
+ import { join, resolve } from 'node:path';
15
+ import { resolveGsdRoot, findMilestoneIds, resolveMilestoneDir, findSliceIds, resolveSliceDir } from './paths.js';
16
+ // ---------------------------------------------------------------------------
17
+ // Graph file paths
18
+ // ---------------------------------------------------------------------------
19
+ function graphsDir(gsdRoot) {
20
+ return join(gsdRoot, 'graphs');
21
+ }
22
+ function graphJsonPath(gsdRoot) {
23
+ return join(graphsDir(gsdRoot), 'graph.json');
24
+ }
25
+ function graphTmpPath(gsdRoot) {
26
+ return join(graphsDir(gsdRoot), 'graph.tmp.json');
27
+ }
28
+ function snapshotPath(gsdRoot) {
29
+ return join(graphsDir(gsdRoot), '.last-build-snapshot.json');
30
+ }
31
+ // ---------------------------------------------------------------------------
32
+ // Parsers — each returns nodes/edges and never throws
33
+ // ---------------------------------------------------------------------------
34
+ /**
35
+ * Parse STATE.md for active milestone and phase concepts.
36
+ */
37
+ function parseStateFile(gsdRoot, nodes, _edges) {
38
+ const statePath = join(gsdRoot, 'STATE.md');
39
+ if (!existsSync(statePath))
40
+ return;
41
+ let content;
42
+ try {
43
+ content = readFileSync(statePath, 'utf-8');
44
+ }
45
+ catch {
46
+ return;
47
+ }
48
+ // Extract active milestone
49
+ const activeMilestoneMatch = content.match(/\*\*Active Milestone:\*\*\s+([A-Z]\d+):\s+(.+)/i);
50
+ if (activeMilestoneMatch) {
51
+ const [, milestoneId, title] = activeMilestoneMatch;
52
+ const id = `milestone:${milestoneId}`;
53
+ if (!nodes.some((n) => n.id === id)) {
54
+ nodes.push({
55
+ id,
56
+ label: `${milestoneId}: ${title.trim()}`,
57
+ type: 'milestone',
58
+ description: `Active milestone: ${milestoneId}`,
59
+ confidence: 'EXTRACTED',
60
+ sourceFile: 'STATE.md',
61
+ });
62
+ }
63
+ }
64
+ // Extract phase as concept
65
+ const phaseMatch = content.match(/\*\*Phase:\*\*\s+(\S+)/i);
66
+ if (phaseMatch) {
67
+ const phase = phaseMatch[1].trim();
68
+ nodes.push({
69
+ id: `concept:phase:${phase}`,
70
+ label: `Phase: ${phase}`,
71
+ type: 'concept',
72
+ confidence: 'EXTRACTED',
73
+ sourceFile: 'STATE.md',
74
+ });
75
+ }
76
+ }
77
+ /**
78
+ * Parse KNOWLEDGE.md for rules, patterns, and lessons.
79
+ */
80
+ function parseKnowledgeFile(gsdRoot, nodes, _edges) {
81
+ const knowledgePath = join(gsdRoot, 'KNOWLEDGE.md');
82
+ if (!existsSync(knowledgePath))
83
+ return;
84
+ let content;
85
+ try {
86
+ content = readFileSync(knowledgePath, 'utf-8');
87
+ }
88
+ catch {
89
+ return;
90
+ }
91
+ // Parse Rules table
92
+ const rulesMatch = content.match(/## Rules\s*\n([\s\S]*?)(?=\n## |$)/i);
93
+ if (rulesMatch) {
94
+ for (const line of rulesMatch[1].split('\n')) {
95
+ if (!line.includes('|'))
96
+ continue;
97
+ const cells = line.split('|').map((c) => c.trim()).filter(Boolean);
98
+ if (cells.length < 3)
99
+ continue;
100
+ if (cells[0].startsWith('#') || cells[0].startsWith('-'))
101
+ continue;
102
+ const id = cells[0];
103
+ if (!/^K\d+$/i.test(id))
104
+ continue;
105
+ nodes.push({
106
+ id: `rule:${id}`,
107
+ label: id,
108
+ type: 'rule',
109
+ description: cells[2] ?? '',
110
+ confidence: 'EXTRACTED',
111
+ sourceFile: 'KNOWLEDGE.md',
112
+ });
113
+ }
114
+ }
115
+ // Parse Patterns table
116
+ const patternsMatch = content.match(/## Patterns\s*\n([\s\S]*?)(?=\n## |$)/i);
117
+ if (patternsMatch) {
118
+ for (const line of patternsMatch[1].split('\n')) {
119
+ if (!line.includes('|'))
120
+ continue;
121
+ const cells = line.split('|').map((c) => c.trim()).filter(Boolean);
122
+ if (cells.length < 2)
123
+ continue;
124
+ if (cells[0].startsWith('#') || cells[0].startsWith('-'))
125
+ continue;
126
+ const id = cells[0];
127
+ if (!/^P\d+$/i.test(id))
128
+ continue;
129
+ nodes.push({
130
+ id: `pattern:${id}`,
131
+ label: id,
132
+ type: 'pattern',
133
+ description: cells[1] ?? '',
134
+ confidence: 'EXTRACTED',
135
+ sourceFile: 'KNOWLEDGE.md',
136
+ });
137
+ }
138
+ }
139
+ // Parse Lessons Learned table
140
+ const lessonsMatch = content.match(/## Lessons Learned\s*\n([\s\S]*?)(?=\n## |$)/i);
141
+ if (lessonsMatch) {
142
+ for (const line of lessonsMatch[1].split('\n')) {
143
+ if (!line.includes('|'))
144
+ continue;
145
+ const cells = line.split('|').map((c) => c.trim()).filter(Boolean);
146
+ if (cells.length < 2)
147
+ continue;
148
+ if (cells[0].startsWith('#') || cells[0].startsWith('-'))
149
+ continue;
150
+ const id = cells[0];
151
+ if (!/^L\d+$/i.test(id))
152
+ continue;
153
+ nodes.push({
154
+ id: `lesson:${id}`,
155
+ label: id,
156
+ type: 'lesson',
157
+ description: cells[1] ?? '',
158
+ confidence: 'EXTRACTED',
159
+ sourceFile: 'KNOWLEDGE.md',
160
+ });
161
+ }
162
+ }
163
+ }
164
+ /**
165
+ * Parse milestone ROADMAP.md files for milestones and slices.
166
+ */
167
+ function parseMilestoneFiles(gsdRoot, nodes, edges) {
168
+ const milestoneIds = findMilestoneIds(gsdRoot);
169
+ for (const milestoneId of milestoneIds) {
170
+ try {
171
+ parseSingleMilestone(gsdRoot, milestoneId, nodes, edges);
172
+ }
173
+ catch {
174
+ // Skip this milestone on any error
175
+ }
176
+ }
177
+ }
178
+ function parseSingleMilestone(gsdRoot, milestoneId, nodes, edges) {
179
+ const mDir = resolveMilestoneDir(gsdRoot, milestoneId);
180
+ if (!mDir)
181
+ return;
182
+ const milestoneNodeId = `milestone:${milestoneId}`;
183
+ // Try to read the roadmap file
184
+ const roadmapPath = join(mDir, `${milestoneId}-ROADMAP.md`);
185
+ let roadmapContent = null;
186
+ if (existsSync(roadmapPath)) {
187
+ try {
188
+ roadmapContent = readFileSync(roadmapPath, 'utf-8');
189
+ }
190
+ catch {
191
+ // Skip
192
+ }
193
+ }
194
+ // Extract milestone title from roadmap
195
+ let milestoneTitle = milestoneId;
196
+ if (roadmapContent) {
197
+ const titleMatch = roadmapContent.match(/^#\s+[A-Z]\d+:\s+(.+)/m);
198
+ if (titleMatch)
199
+ milestoneTitle = `${milestoneId}: ${titleMatch[1].trim()}`;
200
+ }
201
+ // Ensure milestone node exists
202
+ if (!nodes.some((n) => n.id === milestoneNodeId)) {
203
+ nodes.push({
204
+ id: milestoneNodeId,
205
+ label: milestoneTitle,
206
+ type: 'milestone',
207
+ confidence: 'EXTRACTED',
208
+ sourceFile: roadmapContent ? `milestones/${milestoneId}/${milestoneId}-ROADMAP.md` : undefined,
209
+ });
210
+ }
211
+ // Parse slices from roadmap table or filesystem
212
+ const sliceIds = findSliceIds(gsdRoot, milestoneId);
213
+ for (const sliceId of sliceIds) {
214
+ try {
215
+ parseSingleSlice(gsdRoot, milestoneId, sliceId, milestoneNodeId, nodes, edges);
216
+ }
217
+ catch {
218
+ // Skip this slice on any error
219
+ }
220
+ }
221
+ }
222
+ function parseSingleSlice(gsdRoot, milestoneId, sliceId, milestoneNodeId, nodes, edges) {
223
+ const sDir = resolveSliceDir(gsdRoot, milestoneId, sliceId);
224
+ if (!sDir)
225
+ return;
226
+ const sliceNodeId = `slice:${milestoneId}:${sliceId}`;
227
+ // Try to read the slice plan
228
+ const planPath = join(sDir, `${sliceId}-PLAN.md`);
229
+ let sliceTitle = `${milestoneId}/${sliceId}`;
230
+ let planContent = null;
231
+ if (existsSync(planPath)) {
232
+ try {
233
+ planContent = readFileSync(planPath, 'utf-8');
234
+ const titleMatch = planContent.match(/^#\s+[A-Z]\d+:\s+(.+)/m);
235
+ if (titleMatch)
236
+ sliceTitle = `${sliceId}: ${titleMatch[1].trim()}`;
237
+ }
238
+ catch {
239
+ // Use default title
240
+ }
241
+ }
242
+ nodes.push({
243
+ id: sliceNodeId,
244
+ label: sliceTitle,
245
+ type: 'slice',
246
+ confidence: 'EXTRACTED',
247
+ sourceFile: planContent ? `milestones/${milestoneId}/slices/${sliceId}/${sliceId}-PLAN.md` : undefined,
248
+ });
249
+ // Edge: milestone contains slice
250
+ edges.push({
251
+ from: milestoneNodeId,
252
+ to: sliceNodeId,
253
+ type: 'contains',
254
+ confidence: 'EXTRACTED',
255
+ });
256
+ // Parse tasks from the slice plan
257
+ if (planContent) {
258
+ parseTasksFromPlan(planContent, milestoneId, sliceId, sliceNodeId, nodes, edges);
259
+ }
260
+ }
261
+ function parseTasksFromPlan(content, milestoneId, sliceId, sliceNodeId, nodes, edges) {
262
+ // Match lines like: - [ ] **T01: Title** — description
263
+ const taskPattern = /[-*]\s+\[[ x]\]\s+\*\*(T\d+):\s*([^*]+)\*\*/g;
264
+ let match;
265
+ while ((match = taskPattern.exec(content)) !== null) {
266
+ const [, taskId, taskTitle] = match;
267
+ const taskNodeId = `task:${milestoneId}:${sliceId}:${taskId}`;
268
+ nodes.push({
269
+ id: taskNodeId,
270
+ label: `${taskId}: ${taskTitle.trim()}`,
271
+ type: 'task',
272
+ confidence: 'EXTRACTED',
273
+ });
274
+ edges.push({
275
+ from: sliceNodeId,
276
+ to: taskNodeId,
277
+ type: 'contains',
278
+ confidence: 'EXTRACTED',
279
+ });
280
+ }
281
+ }
282
+ // ---------------------------------------------------------------------------
283
+ // buildGraph
284
+ // ---------------------------------------------------------------------------
285
+ /**
286
+ * Build a KnowledgeGraph by parsing all .gsd/ artifacts.
287
+ *
288
+ * Parse errors in any single artifact are caught — the artifact is skipped
289
+ * and never causes buildGraph() to throw.
290
+ */
291
+ export async function buildGraph(projectDir) {
292
+ const gsdRoot = resolveGsdRoot(resolve(projectDir));
293
+ const nodes = [];
294
+ const edges = [];
295
+ // Each parser is wrapped so a crash in one never stops others
296
+ const parsers = [
297
+ parseStateFile,
298
+ parseKnowledgeFile,
299
+ parseMilestoneFiles,
300
+ ];
301
+ for (const parser of parsers) {
302
+ try {
303
+ parser(gsdRoot, nodes, edges);
304
+ }
305
+ catch {
306
+ // Parsing error — skip this artifact, mark as ambiguous
307
+ nodes.push({
308
+ id: `error:${parser.name}:${Date.now()}`,
309
+ label: `Parse error in ${parser.name}`,
310
+ type: 'concept',
311
+ confidence: 'AMBIGUOUS',
312
+ });
313
+ }
314
+ }
315
+ // Deduplicate nodes by id (keep first occurrence)
316
+ const seen = new Set();
317
+ const dedupedNodes = nodes.filter((n) => {
318
+ if (seen.has(n.id))
319
+ return false;
320
+ seen.add(n.id);
321
+ return true;
322
+ });
323
+ return {
324
+ nodes: dedupedNodes,
325
+ edges,
326
+ builtAt: new Date().toISOString(),
327
+ };
328
+ }
329
+ // ---------------------------------------------------------------------------
330
+ // writeGraph — atomic write via tmp + rename
331
+ // ---------------------------------------------------------------------------
332
+ /**
333
+ * Write the graph to .gsd/graphs/graph.json atomically.
334
+ *
335
+ * Writes to graph.tmp.json first, then renames to graph.json.
336
+ * Creates the graphs/ directory if it does not exist.
337
+ */
338
+ export async function writeGraph(gsdRoot, graph) {
339
+ const dir = graphsDir(gsdRoot);
340
+ mkdirSync(dir, { recursive: true });
341
+ const tmp = graphTmpPath(gsdRoot);
342
+ const final = graphJsonPath(gsdRoot);
343
+ writeFileSync(tmp, JSON.stringify(graph, null, 2), 'utf-8');
344
+ renameSync(tmp, final);
345
+ }
346
+ // ---------------------------------------------------------------------------
347
+ // writeSnapshot
348
+ // ---------------------------------------------------------------------------
349
+ /**
350
+ * Copy the current graph.json to .last-build-snapshot.json.
351
+ * Adds a snapshotAt timestamp to the copy.
352
+ */
353
+ export async function writeSnapshot(gsdRoot) {
354
+ const src = graphJsonPath(gsdRoot);
355
+ if (!existsSync(src))
356
+ return;
357
+ const dir = graphsDir(gsdRoot);
358
+ mkdirSync(dir, { recursive: true });
359
+ const raw = readFileSync(src, 'utf-8');
360
+ let graph;
361
+ try {
362
+ graph = JSON.parse(raw);
363
+ }
364
+ catch {
365
+ return;
366
+ }
367
+ const snapshot = { ...graph, snapshotAt: new Date().toISOString() };
368
+ writeFileSync(snapshotPath(gsdRoot), JSON.stringify(snapshot, null, 2), 'utf-8');
369
+ }
370
+ // ---------------------------------------------------------------------------
371
+ // graphStatus
372
+ // ---------------------------------------------------------------------------
373
+ /**
374
+ * Return status of the graph: whether it exists, its age, and whether it is stale.
375
+ * Stale means builtAt is older than 24 hours.
376
+ */
377
+ export async function graphStatus(projectDir) {
378
+ const gsdRoot = resolveGsdRoot(resolve(projectDir));
379
+ const graphPath = graphJsonPath(gsdRoot);
380
+ if (!existsSync(graphPath)) {
381
+ return { exists: false };
382
+ }
383
+ try {
384
+ const raw = readFileSync(graphPath, 'utf-8');
385
+ const graph = JSON.parse(raw);
386
+ const builtAt = graph.builtAt;
387
+ const ageMs = Date.now() - new Date(builtAt).getTime();
388
+ const ageHours = ageMs / (1000 * 60 * 60);
389
+ const stale = ageHours > 24;
390
+ return {
391
+ exists: true,
392
+ lastBuild: builtAt,
393
+ nodeCount: graph.nodes.length,
394
+ edgeCount: graph.edges.length,
395
+ stale,
396
+ ageHours,
397
+ };
398
+ }
399
+ catch {
400
+ return { exists: false };
401
+ }
402
+ }
403
+ // ---------------------------------------------------------------------------
404
+ // applyBudget — trim edges to stay within token budget
405
+ // ---------------------------------------------------------------------------
406
+ /**
407
+ * Given a set of seed node IDs and the full graph, apply BFS to collect
408
+ * reachable nodes and edges. Trims AMBIGUOUS edges first, then INFERRED,
409
+ * stopping when the estimated token count drops within budget.
410
+ *
411
+ * Budget is a rough token estimate: 1 node ≈ 20 tokens, 1 edge ≈ 10 tokens.
412
+ */
413
+ function applyBudget(graph, seedIds, budget) {
414
+ // BFS to collect reachable nodes (start from seeds)
415
+ const reachable = new Set(seedIds);
416
+ const queue = [...seedIds];
417
+ while (queue.length > 0) {
418
+ const current = queue.shift();
419
+ for (const edge of graph.edges) {
420
+ if (edge.from === current && !reachable.has(edge.to)) {
421
+ reachable.add(edge.to);
422
+ queue.push(edge.to);
423
+ }
424
+ }
425
+ }
426
+ let resultNodes = graph.nodes.filter((n) => reachable.has(n.id));
427
+ let resultEdges = graph.edges.filter((e) => reachable.has(e.from) && reachable.has(e.to));
428
+ // Estimate tokens and trim if over budget
429
+ // Trim AMBIGUOUS edges first, then INFERRED
430
+ const estimate = () => resultNodes.length * 20 + resultEdges.length * 10;
431
+ if (estimate() > budget) {
432
+ resultEdges = resultEdges.filter((e) => e.confidence !== 'AMBIGUOUS');
433
+ }
434
+ if (estimate() > budget) {
435
+ resultEdges = resultEdges.filter((e) => e.confidence !== 'INFERRED');
436
+ }
437
+ if (estimate() > budget) {
438
+ // Hard trim — keep only seed nodes and their EXTRACTED edges
439
+ const seedNodes = resultNodes.filter((n) => seedIds.has(n.id));
440
+ const seedEdges = resultEdges.filter((e) => seedIds.has(e.from) && e.confidence === 'EXTRACTED');
441
+ return { nodes: seedNodes, edges: seedEdges };
442
+ }
443
+ return { nodes: resultNodes, edges: resultEdges };
444
+ }
445
+ // ---------------------------------------------------------------------------
446
+ // graphQuery
447
+ // ---------------------------------------------------------------------------
448
+ /**
449
+ * Query the graph for nodes matching a term (case-insensitive on label + description).
450
+ * BFS from seed nodes, applying budget trimming.
451
+ *
452
+ * Reads from the pre-built graph.json. Falls back to an empty result if no
453
+ * graph exists.
454
+ */
455
+ export async function graphQuery(projectDir, term, budget = 4000) {
456
+ const gsdRoot = resolveGsdRoot(resolve(projectDir));
457
+ const graphPath = graphJsonPath(gsdRoot);
458
+ if (!existsSync(graphPath)) {
459
+ return { nodes: [], edges: [], term, budget };
460
+ }
461
+ let graph;
462
+ try {
463
+ const raw = readFileSync(graphPath, 'utf-8');
464
+ graph = JSON.parse(raw);
465
+ }
466
+ catch {
467
+ return { nodes: [], edges: [], term, budget };
468
+ }
469
+ if (!term || term.trim() === '') {
470
+ // Empty term — return empty result
471
+ return { nodes: [], edges: [], term, budget };
472
+ }
473
+ const lower = term.toLowerCase();
474
+ // Find seed nodes that match the term
475
+ const seedIds = new Set(graph.nodes
476
+ .filter((n) => {
477
+ const labelMatch = n.label.toLowerCase().includes(lower);
478
+ const descMatch = n.description?.toLowerCase().includes(lower) ?? false;
479
+ return labelMatch || descMatch;
480
+ })
481
+ .map((n) => n.id));
482
+ if (seedIds.size === 0) {
483
+ return { nodes: [], edges: [], term, budget };
484
+ }
485
+ const result = applyBudget(graph, seedIds, budget);
486
+ return { ...result, term, budget };
487
+ }
488
+ // ---------------------------------------------------------------------------
489
+ // graphDiff
490
+ // ---------------------------------------------------------------------------
491
+ /**
492
+ * Compare the current graph.json with .last-build-snapshot.json.
493
+ * Returns added/removed/changed nodes and added/removed edges.
494
+ *
495
+ * If no snapshot exists, returns empty diff arrays.
496
+ */
497
+ export async function graphDiff(projectDir) {
498
+ const gsdRoot = resolveGsdRoot(resolve(projectDir));
499
+ const empty = {
500
+ nodes: { added: [], removed: [], changed: [] },
501
+ edges: { added: [], removed: [] },
502
+ };
503
+ const graphPath = graphJsonPath(gsdRoot);
504
+ const snap = snapshotPath(gsdRoot);
505
+ if (!existsSync(graphPath))
506
+ return empty;
507
+ if (!existsSync(snap))
508
+ return empty;
509
+ let current;
510
+ let snapshot;
511
+ try {
512
+ current = JSON.parse(readFileSync(graphPath, 'utf-8'));
513
+ }
514
+ catch {
515
+ return empty;
516
+ }
517
+ try {
518
+ snapshot = JSON.parse(readFileSync(snap, 'utf-8'));
519
+ }
520
+ catch {
521
+ return empty;
522
+ }
523
+ const currentNodeIds = new Set(current.nodes.map((n) => n.id));
524
+ const snapshotNodeIds = new Set(snapshot.nodes.map((n) => n.id));
525
+ const added = current.nodes.filter((n) => !snapshotNodeIds.has(n.id)).map((n) => n.id);
526
+ const removed = snapshot.nodes.filter((n) => !currentNodeIds.has(n.id)).map((n) => n.id);
527
+ // Changed: same id but different label or description
528
+ const snapshotNodeMap = new Map(snapshot.nodes.map((n) => [n.id, n]));
529
+ const changed = current.nodes
530
+ .filter((n) => {
531
+ const snap = snapshotNodeMap.get(n.id);
532
+ if (!snap)
533
+ return false;
534
+ return n.label !== snap.label || n.description !== snap.description;
535
+ })
536
+ .map((n) => n.id);
537
+ // Edges — compare by string key "from->to:type"
538
+ const edgeKey = (e) => `${e.from}->${e.to}:${e.type}`;
539
+ const currentEdgeKeys = new Set(current.edges.map(edgeKey));
540
+ const snapshotEdgeKeys = new Set(snapshot.edges.map(edgeKey));
541
+ const edgesAdded = current.edges.filter((e) => !snapshotEdgeKeys.has(edgeKey(e))).map(edgeKey);
542
+ const edgesRemoved = snapshot.edges.filter((e) => !currentEdgeKeys.has(edgeKey(e))).map(edgeKey);
543
+ return {
544
+ nodes: { added, removed, changed },
545
+ edges: { added: edgesAdded, removed: edgesRemoved },
546
+ };
547
+ }
548
+ //# sourceMappingURL=graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph.js","sourceRoot":"","sources":["../../src/readers/graph.ts"],"names":[],"mappings":"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","sourcesContent":["// GSD MCP Server — knowledge graph reader\n// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>\n\n/**\n * Knowledge Graph for GSD projects.\n *\n * Parses .gsd/ artifacts (STATE.md, milestone ROADMAPs, slice PLANs,\n * KNOWLEDGE.md) into a graph of nodes and edges. Parse errors in any\n * single artifact are caught and never propagate — the artifact is skipped\n * and the rest of the graph is returned.\n *\n * writeGraph() is atomic: writes to graph.tmp.json then renames to graph.json.\n */\n\nimport { readFileSync, writeFileSync, renameSync, existsSync, mkdirSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\nimport { resolveGsdRoot, findMilestoneIds, resolveMilestoneDir, findSliceIds, resolveSliceDir } from './paths.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport type NodeType =\n | 'milestone'\n | 'slice'\n | 'task'\n | 'rule'\n | 'pattern'\n | 'lesson'\n | 'concept';\n\nexport type EdgeType =\n | 'contains'\n | 'depends_on'\n | 'relates_to'\n | 'implements';\n\nexport type ConfidenceTier = 'EXTRACTED' | 'INFERRED' | 'AMBIGUOUS';\n\nexport interface GraphNode {\n id: string;\n label: string;\n type: NodeType;\n description?: string;\n confidence: ConfidenceTier;\n sourceFile?: string;\n}\n\nexport interface GraphEdge {\n from: string;\n to: string;\n type: EdgeType;\n confidence: ConfidenceTier;\n}\n\nexport interface KnowledgeGraph {\n nodes: GraphNode[];\n edges: GraphEdge[];\n builtAt: string;\n}\n\nexport interface GraphStatusResult {\n exists: boolean;\n lastBuild?: string;\n nodeCount?: number;\n edgeCount?: number;\n stale?: boolean;\n ageHours?: number;\n}\n\nexport interface GraphQueryResult {\n nodes: GraphNode[];\n edges: GraphEdge[];\n term: string;\n budget: number;\n}\n\nexport interface GraphDiffResult {\n nodes: {\n added: string[];\n removed: string[];\n changed: string[];\n };\n edges: {\n added: string[];\n removed: string[];\n };\n}\n\n// ---------------------------------------------------------------------------\n// Graph file paths\n// ---------------------------------------------------------------------------\n\nfunction graphsDir(gsdRoot: string): string {\n return join(gsdRoot, 'graphs');\n}\n\nfunction graphJsonPath(gsdRoot: string): string {\n return join(graphsDir(gsdRoot), 'graph.json');\n}\n\nfunction graphTmpPath(gsdRoot: string): string {\n return join(graphsDir(gsdRoot), 'graph.tmp.json');\n}\n\nfunction snapshotPath(gsdRoot: string): string {\n return join(graphsDir(gsdRoot), '.last-build-snapshot.json');\n}\n\n// ---------------------------------------------------------------------------\n// Parsers — each returns nodes/edges and never throws\n// ---------------------------------------------------------------------------\n\n/**\n * Parse STATE.md for active milestone and phase concepts.\n */\nfunction parseStateFile(gsdRoot: string, nodes: GraphNode[], _edges: GraphEdge[]): void {\n const statePath = join(gsdRoot, 'STATE.md');\n if (!existsSync(statePath)) return;\n\n let content: string;\n try {\n content = readFileSync(statePath, 'utf-8');\n } catch {\n return;\n }\n\n // Extract active milestone\n const activeMilestoneMatch = content.match(/\\*\\*Active Milestone:\\*\\*\\s+([A-Z]\\d+):\\s+(.+)/i);\n if (activeMilestoneMatch) {\n const [, milestoneId, title] = activeMilestoneMatch;\n const id = `milestone:${milestoneId}`;\n if (!nodes.some((n) => n.id === id)) {\n nodes.push({\n id,\n label: `${milestoneId}: ${title.trim()}`,\n type: 'milestone',\n description: `Active milestone: ${milestoneId}`,\n confidence: 'EXTRACTED',\n sourceFile: 'STATE.md',\n });\n }\n }\n\n // Extract phase as concept\n const phaseMatch = content.match(/\\*\\*Phase:\\*\\*\\s+(\\S+)/i);\n if (phaseMatch) {\n const phase = phaseMatch[1].trim();\n nodes.push({\n id: `concept:phase:${phase}`,\n label: `Phase: ${phase}`,\n type: 'concept',\n confidence: 'EXTRACTED',\n sourceFile: 'STATE.md',\n });\n }\n}\n\n/**\n * Parse KNOWLEDGE.md for rules, patterns, and lessons.\n */\nfunction parseKnowledgeFile(gsdRoot: string, nodes: GraphNode[], _edges: GraphEdge[]): void {\n const knowledgePath = join(gsdRoot, 'KNOWLEDGE.md');\n if (!existsSync(knowledgePath)) return;\n\n let content: string;\n try {\n content = readFileSync(knowledgePath, 'utf-8');\n } catch {\n return;\n }\n\n // Parse Rules table\n const rulesMatch = content.match(/## Rules\\s*\\n([\\s\\S]*?)(?=\\n## |$)/i);\n if (rulesMatch) {\n for (const line of rulesMatch[1].split('\\n')) {\n if (!line.includes('|')) continue;\n const cells = line.split('|').map((c) => c.trim()).filter(Boolean);\n if (cells.length < 3) continue;\n if (cells[0].startsWith('#') || cells[0].startsWith('-')) continue;\n const id = cells[0];\n if (!/^K\\d+$/i.test(id)) continue;\n nodes.push({\n id: `rule:${id}`,\n label: id,\n type: 'rule',\n description: cells[2] ?? '',\n confidence: 'EXTRACTED',\n sourceFile: 'KNOWLEDGE.md',\n });\n }\n }\n\n // Parse Patterns table\n const patternsMatch = content.match(/## Patterns\\s*\\n([\\s\\S]*?)(?=\\n## |$)/i);\n if (patternsMatch) {\n for (const line of patternsMatch[1].split('\\n')) {\n if (!line.includes('|')) continue;\n const cells = line.split('|').map((c) => c.trim()).filter(Boolean);\n if (cells.length < 2) continue;\n if (cells[0].startsWith('#') || cells[0].startsWith('-')) continue;\n const id = cells[0];\n if (!/^P\\d+$/i.test(id)) continue;\n nodes.push({\n id: `pattern:${id}`,\n label: id,\n type: 'pattern',\n description: cells[1] ?? '',\n confidence: 'EXTRACTED',\n sourceFile: 'KNOWLEDGE.md',\n });\n }\n }\n\n // Parse Lessons Learned table\n const lessonsMatch = content.match(/## Lessons Learned\\s*\\n([\\s\\S]*?)(?=\\n## |$)/i);\n if (lessonsMatch) {\n for (const line of lessonsMatch[1].split('\\n')) {\n if (!line.includes('|')) continue;\n const cells = line.split('|').map((c) => c.trim()).filter(Boolean);\n if (cells.length < 2) continue;\n if (cells[0].startsWith('#') || cells[0].startsWith('-')) continue;\n const id = cells[0];\n if (!/^L\\d+$/i.test(id)) continue;\n nodes.push({\n id: `lesson:${id}`,\n label: id,\n type: 'lesson',\n description: cells[1] ?? '',\n confidence: 'EXTRACTED',\n sourceFile: 'KNOWLEDGE.md',\n });\n }\n }\n}\n\n/**\n * Parse milestone ROADMAP.md files for milestones and slices.\n */\nfunction parseMilestoneFiles(\n gsdRoot: string,\n nodes: GraphNode[],\n edges: GraphEdge[],\n): void {\n const milestoneIds = findMilestoneIds(gsdRoot);\n\n for (const milestoneId of milestoneIds) {\n try {\n parseSingleMilestone(gsdRoot, milestoneId, nodes, edges);\n } catch {\n // Skip this milestone on any error\n }\n }\n}\n\nfunction parseSingleMilestone(\n gsdRoot: string,\n milestoneId: string,\n nodes: GraphNode[],\n edges: GraphEdge[],\n): void {\n const mDir = resolveMilestoneDir(gsdRoot, milestoneId);\n if (!mDir) return;\n\n const milestoneNodeId = `milestone:${milestoneId}`;\n\n // Try to read the roadmap file\n const roadmapPath = join(mDir, `${milestoneId}-ROADMAP.md`);\n let roadmapContent: string | null = null;\n if (existsSync(roadmapPath)) {\n try {\n roadmapContent = readFileSync(roadmapPath, 'utf-8');\n } catch {\n // Skip\n }\n }\n\n // Extract milestone title from roadmap\n let milestoneTitle = milestoneId;\n if (roadmapContent) {\n const titleMatch = roadmapContent.match(/^#\\s+[A-Z]\\d+:\\s+(.+)/m);\n if (titleMatch) milestoneTitle = `${milestoneId}: ${titleMatch[1].trim()}`;\n }\n\n // Ensure milestone node exists\n if (!nodes.some((n) => n.id === milestoneNodeId)) {\n nodes.push({\n id: milestoneNodeId,\n label: milestoneTitle,\n type: 'milestone',\n confidence: 'EXTRACTED',\n sourceFile: roadmapContent ? `milestones/${milestoneId}/${milestoneId}-ROADMAP.md` : undefined,\n });\n }\n\n // Parse slices from roadmap table or filesystem\n const sliceIds = findSliceIds(gsdRoot, milestoneId);\n for (const sliceId of sliceIds) {\n try {\n parseSingleSlice(gsdRoot, milestoneId, sliceId, milestoneNodeId, nodes, edges);\n } catch {\n // Skip this slice on any error\n }\n }\n}\n\nfunction parseSingleSlice(\n gsdRoot: string,\n milestoneId: string,\n sliceId: string,\n milestoneNodeId: string,\n nodes: GraphNode[],\n edges: GraphEdge[],\n): void {\n const sDir = resolveSliceDir(gsdRoot, milestoneId, sliceId);\n if (!sDir) return;\n\n const sliceNodeId = `slice:${milestoneId}:${sliceId}`;\n\n // Try to read the slice plan\n const planPath = join(sDir, `${sliceId}-PLAN.md`);\n let sliceTitle = `${milestoneId}/${sliceId}`;\n let planContent: string | null = null;\n\n if (existsSync(planPath)) {\n try {\n planContent = readFileSync(planPath, 'utf-8');\n const titleMatch = planContent.match(/^#\\s+[A-Z]\\d+:\\s+(.+)/m);\n if (titleMatch) sliceTitle = `${sliceId}: ${titleMatch[1].trim()}`;\n } catch {\n // Use default title\n }\n }\n\n nodes.push({\n id: sliceNodeId,\n label: sliceTitle,\n type: 'slice',\n confidence: 'EXTRACTED',\n sourceFile: planContent ? `milestones/${milestoneId}/slices/${sliceId}/${sliceId}-PLAN.md` : undefined,\n });\n\n // Edge: milestone contains slice\n edges.push({\n from: milestoneNodeId,\n to: sliceNodeId,\n type: 'contains',\n confidence: 'EXTRACTED',\n });\n\n // Parse tasks from the slice plan\n if (planContent) {\n parseTasksFromPlan(planContent, milestoneId, sliceId, sliceNodeId, nodes, edges);\n }\n}\n\nfunction parseTasksFromPlan(\n content: string,\n milestoneId: string,\n sliceId: string,\n sliceNodeId: string,\n nodes: GraphNode[],\n edges: GraphEdge[],\n): void {\n // Match lines like: - [ ] **T01: Title** — description\n const taskPattern = /[-*]\\s+\\[[ x]\\]\\s+\\*\\*(T\\d+):\\s*([^*]+)\\*\\*/g;\n let match: RegExpExecArray | null;\n\n while ((match = taskPattern.exec(content)) !== null) {\n const [, taskId, taskTitle] = match;\n const taskNodeId = `task:${milestoneId}:${sliceId}:${taskId}`;\n\n nodes.push({\n id: taskNodeId,\n label: `${taskId}: ${taskTitle.trim()}`,\n type: 'task',\n confidence: 'EXTRACTED',\n });\n\n edges.push({\n from: sliceNodeId,\n to: taskNodeId,\n type: 'contains',\n confidence: 'EXTRACTED',\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// buildGraph\n// ---------------------------------------------------------------------------\n\n/**\n * Build a KnowledgeGraph by parsing all .gsd/ artifacts.\n *\n * Parse errors in any single artifact are caught — the artifact is skipped\n * and never causes buildGraph() to throw.\n */\nexport async function buildGraph(projectDir: string): Promise<KnowledgeGraph> {\n const gsdRoot = resolveGsdRoot(resolve(projectDir));\n\n const nodes: GraphNode[] = [];\n const edges: GraphEdge[] = [];\n\n // Each parser is wrapped so a crash in one never stops others\n const parsers: Array<(g: string, n: GraphNode[], e: GraphEdge[]) => void> = [\n parseStateFile,\n parseKnowledgeFile,\n parseMilestoneFiles,\n ];\n\n for (const parser of parsers) {\n try {\n parser(gsdRoot, nodes, edges);\n } catch {\n // Parsing error — skip this artifact, mark as ambiguous\n nodes.push({\n id: `error:${parser.name}:${Date.now()}`,\n label: `Parse error in ${parser.name}`,\n type: 'concept',\n confidence: 'AMBIGUOUS',\n });\n }\n }\n\n // Deduplicate nodes by id (keep first occurrence)\n const seen = new Set<string>();\n const dedupedNodes = nodes.filter((n) => {\n if (seen.has(n.id)) return false;\n seen.add(n.id);\n return true;\n });\n\n return {\n nodes: dedupedNodes,\n edges,\n builtAt: new Date().toISOString(),\n };\n}\n\n// ---------------------------------------------------------------------------\n// writeGraph — atomic write via tmp + rename\n// ---------------------------------------------------------------------------\n\n/**\n * Write the graph to .gsd/graphs/graph.json atomically.\n *\n * Writes to graph.tmp.json first, then renames to graph.json.\n * Creates the graphs/ directory if it does not exist.\n */\nexport async function writeGraph(gsdRoot: string, graph: KnowledgeGraph): Promise<void> {\n const dir = graphsDir(gsdRoot);\n mkdirSync(dir, { recursive: true });\n\n const tmp = graphTmpPath(gsdRoot);\n const final = graphJsonPath(gsdRoot);\n\n writeFileSync(tmp, JSON.stringify(graph, null, 2), 'utf-8');\n renameSync(tmp, final);\n}\n\n// ---------------------------------------------------------------------------\n// writeSnapshot\n// ---------------------------------------------------------------------------\n\n/**\n * Copy the current graph.json to .last-build-snapshot.json.\n * Adds a snapshotAt timestamp to the copy.\n */\nexport async function writeSnapshot(gsdRoot: string): Promise<void> {\n const src = graphJsonPath(gsdRoot);\n if (!existsSync(src)) return;\n\n const dir = graphsDir(gsdRoot);\n mkdirSync(dir, { recursive: true });\n\n const raw = readFileSync(src, 'utf-8');\n let graph: KnowledgeGraph;\n try {\n graph = JSON.parse(raw) as KnowledgeGraph;\n } catch {\n return;\n }\n const snapshot = { ...graph, snapshotAt: new Date().toISOString() };\n\n writeFileSync(snapshotPath(gsdRoot), JSON.stringify(snapshot, null, 2), 'utf-8');\n}\n\n// ---------------------------------------------------------------------------\n// graphStatus\n// ---------------------------------------------------------------------------\n\n/**\n * Return status of the graph: whether it exists, its age, and whether it is stale.\n * Stale means builtAt is older than 24 hours.\n */\nexport async function graphStatus(projectDir: string): Promise<GraphStatusResult> {\n const gsdRoot = resolveGsdRoot(resolve(projectDir));\n const graphPath = graphJsonPath(gsdRoot);\n\n if (!existsSync(graphPath)) {\n return { exists: false };\n }\n\n try {\n const raw = readFileSync(graphPath, 'utf-8');\n const graph = JSON.parse(raw) as KnowledgeGraph;\n\n const builtAt = graph.builtAt;\n const ageMs = Date.now() - new Date(builtAt).getTime();\n const ageHours = ageMs / (1000 * 60 * 60);\n const stale = ageHours > 24;\n\n return {\n exists: true,\n lastBuild: builtAt,\n nodeCount: graph.nodes.length,\n edgeCount: graph.edges.length,\n stale,\n ageHours,\n };\n } catch {\n return { exists: false };\n }\n}\n\n// ---------------------------------------------------------------------------\n// applyBudget — trim edges to stay within token budget\n// ---------------------------------------------------------------------------\n\n/**\n * Given a set of seed node IDs and the full graph, apply BFS to collect\n * reachable nodes and edges. Trims AMBIGUOUS edges first, then INFERRED,\n * stopping when the estimated token count drops within budget.\n *\n * Budget is a rough token estimate: 1 node ≈ 20 tokens, 1 edge ≈ 10 tokens.\n */\nfunction applyBudget(\n graph: KnowledgeGraph,\n seedIds: Set<string>,\n budget: number,\n): { nodes: GraphNode[]; edges: GraphEdge[] } {\n // BFS to collect reachable nodes (start from seeds)\n const reachable = new Set<string>(seedIds);\n const queue = [...seedIds];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n for (const edge of graph.edges) {\n if (edge.from === current && !reachable.has(edge.to)) {\n reachable.add(edge.to);\n queue.push(edge.to);\n }\n }\n }\n\n let resultNodes = graph.nodes.filter((n) => reachable.has(n.id));\n let resultEdges = graph.edges.filter(\n (e) => reachable.has(e.from) && reachable.has(e.to),\n );\n\n // Estimate tokens and trim if over budget\n // Trim AMBIGUOUS edges first, then INFERRED\n const estimate = (): number =>\n resultNodes.length * 20 + resultEdges.length * 10;\n\n if (estimate() > budget) {\n resultEdges = resultEdges.filter((e) => e.confidence !== 'AMBIGUOUS');\n }\n if (estimate() > budget) {\n resultEdges = resultEdges.filter((e) => e.confidence !== 'INFERRED');\n }\n if (estimate() > budget) {\n // Hard trim — keep only seed nodes and their EXTRACTED edges\n const seedNodes = resultNodes.filter((n) => seedIds.has(n.id));\n const seedEdges = resultEdges.filter(\n (e) => seedIds.has(e.from) && e.confidence === 'EXTRACTED',\n );\n return { nodes: seedNodes, edges: seedEdges };\n }\n\n return { nodes: resultNodes, edges: resultEdges };\n}\n\n// ---------------------------------------------------------------------------\n// graphQuery\n// ---------------------------------------------------------------------------\n\n/**\n * Query the graph for nodes matching a term (case-insensitive on label + description).\n * BFS from seed nodes, applying budget trimming.\n *\n * Reads from the pre-built graph.json. Falls back to an empty result if no\n * graph exists.\n */\nexport async function graphQuery(\n projectDir: string,\n term: string,\n budget = 4000,\n): Promise<GraphQueryResult> {\n const gsdRoot = resolveGsdRoot(resolve(projectDir));\n const graphPath = graphJsonPath(gsdRoot);\n\n if (!existsSync(graphPath)) {\n return { nodes: [], edges: [], term, budget };\n }\n\n let graph: KnowledgeGraph;\n try {\n const raw = readFileSync(graphPath, 'utf-8');\n graph = JSON.parse(raw) as KnowledgeGraph;\n } catch {\n return { nodes: [], edges: [], term, budget };\n }\n\n if (!term || term.trim() === '') {\n // Empty term — return empty result\n return { nodes: [], edges: [], term, budget };\n }\n\n const lower = term.toLowerCase();\n\n // Find seed nodes that match the term\n const seedIds = new Set<string>(\n graph.nodes\n .filter((n) => {\n const labelMatch = n.label.toLowerCase().includes(lower);\n const descMatch = n.description?.toLowerCase().includes(lower) ?? false;\n return labelMatch || descMatch;\n })\n .map((n) => n.id),\n );\n\n if (seedIds.size === 0) {\n return { nodes: [], edges: [], term, budget };\n }\n\n const result = applyBudget(graph, seedIds, budget);\n return { ...result, term, budget };\n}\n\n// ---------------------------------------------------------------------------\n// graphDiff\n// ---------------------------------------------------------------------------\n\n/**\n * Compare the current graph.json with .last-build-snapshot.json.\n * Returns added/removed/changed nodes and added/removed edges.\n *\n * If no snapshot exists, returns empty diff arrays.\n */\nexport async function graphDiff(projectDir: string): Promise<GraphDiffResult> {\n const gsdRoot = resolveGsdRoot(resolve(projectDir));\n const empty: GraphDiffResult = {\n nodes: { added: [], removed: [], changed: [] },\n edges: { added: [], removed: [] },\n };\n\n const graphPath = graphJsonPath(gsdRoot);\n const snap = snapshotPath(gsdRoot);\n\n if (!existsSync(graphPath)) return empty;\n if (!existsSync(snap)) return empty;\n\n let current: KnowledgeGraph;\n let snapshot: KnowledgeGraph;\n\n try {\n current = JSON.parse(readFileSync(graphPath, 'utf-8')) as KnowledgeGraph;\n } catch {\n return empty;\n }\n\n try {\n snapshot = JSON.parse(readFileSync(snap, 'utf-8')) as KnowledgeGraph;\n } catch {\n return empty;\n }\n\n const currentNodeIds = new Set(current.nodes.map((n) => n.id));\n const snapshotNodeIds = new Set(snapshot.nodes.map((n) => n.id));\n\n const added = current.nodes.filter((n) => !snapshotNodeIds.has(n.id)).map((n) => n.id);\n const removed = snapshot.nodes.filter((n) => !currentNodeIds.has(n.id)).map((n) => n.id);\n\n // Changed: same id but different label or description\n const snapshotNodeMap = new Map(snapshot.nodes.map((n) => [n.id, n]));\n const changed = current.nodes\n .filter((n) => {\n const snap = snapshotNodeMap.get(n.id);\n if (!snap) return false;\n return n.label !== snap.label || n.description !== snap.description;\n })\n .map((n) => n.id);\n\n // Edges — compare by string key \"from->to:type\"\n const edgeKey = (e: GraphEdge): string => `${e.from}->${e.to}:${e.type}`;\n const currentEdgeKeys = new Set(current.edges.map(edgeKey));\n const snapshotEdgeKeys = new Set(snapshot.edges.map(edgeKey));\n\n const edgesAdded = current.edges.filter((e) => !snapshotEdgeKeys.has(edgeKey(e))).map(edgeKey);\n const edgesRemoved = snapshot.edges.filter((e) => !currentEdgeKeys.has(edgeKey(e))).map(edgeKey);\n\n return {\n nodes: { added, removed, changed },\n edges: { added: edgesAdded, removed: edgesRemoved },\n };\n}\n"]}
@@ -11,4 +11,6 @@ export { readKnowledge } from './knowledge.js';
11
11
  export type { KnowledgeResult, KnowledgeEntry } from './knowledge.js';
12
12
  export { runDoctorLite } from './doctor-lite.js';
13
13
  export type { DoctorResult, DoctorIssue } from './doctor-lite.js';
14
+ export { buildGraph, writeGraph, writeSnapshot, graphStatus, graphQuery, graphDiff } from './graph.js';
15
+ export type { NodeType, EdgeType, ConfidenceTier, GraphNode, GraphEdge, KnowledgeGraph, GraphStatusResult, GraphQueryResult, GraphDiffResult, } from './graph.js';
14
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/readers/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/readers/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACvG,YAAY,EACV,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,SAAS,EACT,SAAS,EACT,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,GAChB,MAAM,YAAY,CAAC"}
@@ -7,4 +7,5 @@ export { readHistory } from './metrics.js';
7
7
  export { readCaptures } from './captures.js';
8
8
  export { readKnowledge } from './knowledge.js';
9
9
  export { runDoctorLite } from './doctor-lite.js';
10
+ export { buildGraph, writeGraph, writeSnapshot, graphStatus, graphQuery, graphDiff } from './graph.js';
10
11
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/readers/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,4DAA4D;AAE5D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["// GSD MCP Server — readers barrel export\n// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>\n\nexport { resolveGsdRoot, resolveRootFile } from './paths.js';\nexport { readProgress } from './state.js';\nexport type { ProgressResult } from './state.js';\nexport { readRoadmap } from './roadmap.js';\nexport type { RoadmapResult, MilestoneInfo, SliceInfo, TaskInfo } from './roadmap.js';\nexport { readHistory } from './metrics.js';\nexport type { HistoryResult, MetricsUnit } from './metrics.js';\nexport { readCaptures } from './captures.js';\nexport type { CapturesResult, CaptureEntry } from './captures.js';\nexport { readKnowledge } from './knowledge.js';\nexport type { KnowledgeResult, KnowledgeEntry } from './knowledge.js';\nexport { runDoctorLite } from './doctor-lite.js';\nexport type { DoctorResult, DoctorIssue } from './doctor-lite.js';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/readers/index.ts"],"names":[],"mappings":"AAAA,yCAAyC;AACzC,4DAA4D;AAE5D,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAE/C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC","sourcesContent":["// GSD MCP Server — readers barrel export\n// Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>\n\nexport { resolveGsdRoot, resolveRootFile } from './paths.js';\nexport { readProgress } from './state.js';\nexport type { ProgressResult } from './state.js';\nexport { readRoadmap } from './roadmap.js';\nexport type { RoadmapResult, MilestoneInfo, SliceInfo, TaskInfo } from './roadmap.js';\nexport { readHistory } from './metrics.js';\nexport type { HistoryResult, MetricsUnit } from './metrics.js';\nexport { readCaptures } from './captures.js';\nexport type { CapturesResult, CaptureEntry } from './captures.js';\nexport { readKnowledge } from './knowledge.js';\nexport type { KnowledgeResult, KnowledgeEntry } from './knowledge.js';\nexport { runDoctorLite } from './doctor-lite.js';\nexport type { DoctorResult, DoctorIssue } from './doctor-lite.js';\nexport { buildGraph, writeGraph, writeSnapshot, graphStatus, graphQuery, graphDiff } from './graph.js';\nexport type {\n NodeType,\n EdgeType,\n ConfidenceTier,\n GraphNode,\n GraphEdge,\n KnowledgeGraph,\n GraphStatusResult,\n GraphQueryResult,\n GraphDiffResult,\n} from './graph.js';\n"]}