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.
- package/dist/cli.js +85 -0
- package/dist/headless-query.js +4 -1
- package/dist/help-text.js +23 -0
- package/dist/resources/extensions/gsd/auto/detect-stuck.js +11 -4
- package/dist/resources/extensions/gsd/auto/phases.js +45 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +52 -56
- package/dist/resources/extensions/gsd/auto-prompts.js +12 -0
- package/dist/resources/extensions/gsd/auto.js +8 -2
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +21 -8
- package/dist/resources/extensions/gsd/commands/catalog.js +26 -1
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +20 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +68 -9
- package/dist/resources/extensions/gsd/commands-add-tests.js +111 -0
- package/dist/resources/extensions/gsd/commands-backlog.js +140 -0
- package/dist/resources/extensions/gsd/commands-do.js +79 -0
- package/dist/resources/extensions/gsd/commands-maintenance.js +6 -6
- package/dist/resources/extensions/gsd/commands-pr-branch.js +180 -0
- package/dist/resources/extensions/gsd/commands-session-report.js +82 -0
- package/dist/resources/extensions/gsd/commands-ship.js +187 -0
- package/dist/resources/extensions/gsd/db-writer.js +3 -5
- package/dist/resources/extensions/gsd/graph-context.js +66 -0
- package/dist/resources/extensions/gsd/gsd-db.js +321 -0
- package/dist/resources/extensions/gsd/index.js +15 -2
- package/dist/resources/extensions/gsd/md-importer.js +3 -4
- package/dist/resources/extensions/gsd/memory-store.js +19 -51
- package/dist/resources/extensions/gsd/milestone-validation-gates.js +13 -12
- package/dist/resources/extensions/gsd/native-git-bridge.js +7 -4
- package/dist/resources/extensions/gsd/prompts/add-tests.md +35 -0
- package/dist/resources/extensions/gsd/state.js +5 -1
- package/dist/resources/extensions/gsd/tools/complete-slice.js +15 -0
- package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +3 -14
- package/dist/resources/extensions/gsd/triage-resolution.js +2 -5
- package/dist/resources/extensions/gsd/workflow-manifest.js +8 -69
- package/dist/resources/extensions/gsd/workflow-migration.js +21 -22
- package/dist/resources/extensions/gsd/workflow-projections.js +4 -1
- package/dist/resources/extensions/gsd/workflow-reconcile.js +14 -11
- package/dist/tsconfig.extensions.tsbuildinfo +1 -0
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +7 -7
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +7 -7
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +3 -2
- package/packages/daemon/package.json +2 -2
- package/packages/mcp-server/dist/index.d.ts +3 -0
- package/packages/mcp-server/dist/index.d.ts.map +1 -1
- package/packages/mcp-server/dist/index.js +3 -0
- package/packages/mcp-server/dist/index.js.map +1 -1
- package/packages/mcp-server/dist/readers/graph.d.ts +87 -0
- package/packages/mcp-server/dist/readers/graph.d.ts.map +1 -0
- package/packages/mcp-server/dist/readers/graph.js +548 -0
- package/packages/mcp-server/dist/readers/graph.js.map +1 -0
- package/packages/mcp-server/dist/readers/index.d.ts +2 -0
- package/packages/mcp-server/dist/readers/index.d.ts.map +1 -1
- package/packages/mcp-server/dist/readers/index.js +1 -0
- package/packages/mcp-server/dist/readers/index.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +65 -0
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/package.json +2 -2
- package/packages/mcp-server/src/index.ts +15 -0
- package/packages/mcp-server/src/readers/graph.test.ts +426 -0
- package/packages/mcp-server/src/readers/graph.ts +708 -0
- package/packages/mcp-server/src/readers/index.ts +12 -0
- package/packages/mcp-server/src/server.ts +83 -0
- package/packages/mcp-server/tsconfig.json +1 -0
- package/packages/mcp-server/tsconfig.tsbuildinfo +1 -0
- package/packages/native/package.json +2 -2
- package/packages/native/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-agent-core/package.json +1 -1
- package/packages/pi-agent-core/tsconfig.json +1 -0
- package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-ai/package.json +1 -1
- package/packages/pi-ai/tsconfig.json +1 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-coding-agent/tsconfig.json +1 -0
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -0
- package/packages/pi-tui/package.json +1 -1
- package/packages/pi-tui/tsconfig.json +1 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -0
- package/packages/rpc-client/package.json +1 -1
- package/packages/rpc-client/tsconfig.json +1 -0
- package/packages/rpc-client/tsconfig.tsbuildinfo +1 -0
- package/src/resources/extensions/gsd/auto/detect-stuck.ts +12 -4
- package/src/resources/extensions/gsd/auto/loop-deps.ts +6 -0
- package/src/resources/extensions/gsd/auto/phases.ts +68 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +60 -57
- package/src/resources/extensions/gsd/auto-prompts.ts +13 -0
- package/src/resources/extensions/gsd/auto.ts +7 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +24 -8
- package/src/resources/extensions/gsd/commands/catalog.ts +26 -1
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +20 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +74 -9
- package/src/resources/extensions/gsd/commands-add-tests.ts +137 -0
- package/src/resources/extensions/gsd/commands-backlog.ts +182 -0
- package/src/resources/extensions/gsd/commands-do.ts +109 -0
- package/src/resources/extensions/gsd/commands-maintenance.ts +6 -6
- package/src/resources/extensions/gsd/commands-pr-branch.ts +234 -0
- package/src/resources/extensions/gsd/commands-session-report.ts +101 -0
- package/src/resources/extensions/gsd/commands-ship.ts +219 -0
- package/src/resources/extensions/gsd/db-writer.ts +3 -5
- package/src/resources/extensions/gsd/graph-context.ts +85 -0
- package/src/resources/extensions/gsd/gsd-db.ts +467 -0
- package/src/resources/extensions/gsd/index.ts +18 -2
- package/src/resources/extensions/gsd/md-importer.ts +3 -5
- package/src/resources/extensions/gsd/memory-store.ts +31 -62
- package/src/resources/extensions/gsd/milestone-validation-gates.ts +13 -14
- package/src/resources/extensions/gsd/native-git-bridge.ts +11 -12
- package/src/resources/extensions/gsd/prompts/add-tests.md +35 -0
- package/src/resources/extensions/gsd/state.ts +9 -2
- package/src/resources/extensions/gsd/tests/commands-backlog.test.ts +158 -0
- package/src/resources/extensions/gsd/tests/commands-do.test.ts +127 -0
- package/src/resources/extensions/gsd/tests/commands-pr-branch.test.ts +68 -0
- package/src/resources/extensions/gsd/tests/commands-session-report.test.ts +82 -0
- package/src/resources/extensions/gsd/tests/commands-ship.test.ts +71 -0
- package/src/resources/extensions/gsd/tests/commands-workflow-custom.test.ts +14 -0
- package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +154 -0
- package/src/resources/extensions/gsd/tests/graph-context.test.ts +337 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +68 -1
- package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +140 -0
- package/src/resources/extensions/gsd/tests/single-writer-invariant.test.ts +180 -0
- package/src/resources/extensions/gsd/tests/workflow-logger-wiring.test.ts +223 -0
- package/src/resources/extensions/gsd/tools/complete-slice.ts +19 -0
- package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +3 -11
- package/src/resources/extensions/gsd/triage-resolution.ts +2 -7
- package/src/resources/extensions/gsd/workflow-manifest.ts +9 -104
- package/src/resources/extensions/gsd/workflow-migration.ts +21 -29
- package/src/resources/extensions/gsd/workflow-projections.ts +8 -1
- package/src/resources/extensions/gsd/workflow-reconcile.ts +15 -15
- /package/dist/web/standalone/.next/static/{YzIEI9sxJy4t5xgClF08g → XnHY5eXUsTCFmNodWHetD}/_buildManifest.js +0 -0
- /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"]}
|