@weavelogic/knowledge-graph-agent 0.10.7 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/analyze.d.ts.map +1 -1
- package/dist/cli/commands/analyze.js +102 -0
- package/dist/cli/commands/analyze.js.map +1 -1
- package/dist/cultivation/migration-orchestrator.d.ts +195 -0
- package/dist/cultivation/migration-orchestrator.d.ts.map +1 -0
- package/dist/cultivation/migration-orchestrator.js +797 -0
- package/dist/cultivation/migration-orchestrator.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync, statSync } from "fs";
|
|
2
|
+
import { join, dirname, basename, relative } from "path";
|
|
3
|
+
import { GoogleGenerativeAI } from "../node_modules/@google/generative-ai/dist/index.js";
|
|
4
|
+
class MigrationOrchestrator {
|
|
5
|
+
projectRoot;
|
|
6
|
+
docsPath;
|
|
7
|
+
analysisDir;
|
|
8
|
+
verbose;
|
|
9
|
+
dryRun;
|
|
10
|
+
useVectorSearch;
|
|
11
|
+
maxAgents;
|
|
12
|
+
geminiClient = null;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.projectRoot = options.projectRoot;
|
|
15
|
+
this.docsPath = options.docsPath;
|
|
16
|
+
this.analysisDir = options.analysisDir;
|
|
17
|
+
this.verbose = options.verbose ?? false;
|
|
18
|
+
this.dryRun = options.dryRun ?? false;
|
|
19
|
+
this.useVectorSearch = options.useVectorSearch ?? false;
|
|
20
|
+
this.maxAgents = options.maxAgents ?? 8;
|
|
21
|
+
const apiKey = process.env.GOOGLE_GEMINI_API_KEY || process.env.GOOGLE_AI_API_KEY || process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
22
|
+
if (apiKey) {
|
|
23
|
+
this.geminiClient = new GoogleGenerativeAI(apiKey);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Check availability status
|
|
28
|
+
*/
|
|
29
|
+
async getAvailabilityStatus() {
|
|
30
|
+
if (this.geminiClient) {
|
|
31
|
+
return { available: true, reason: "Using Gemini API" };
|
|
32
|
+
}
|
|
33
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
34
|
+
return { available: true, reason: "Using Anthropic API" };
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
available: false,
|
|
38
|
+
reason: "No API key found. Set GOOGLE_GEMINI_API_KEY or ANTHROPIC_API_KEY"
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run the migration process
|
|
43
|
+
*/
|
|
44
|
+
async migrate() {
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
const result = {
|
|
47
|
+
success: false,
|
|
48
|
+
agentsUsed: 0,
|
|
49
|
+
documentsCreated: 0,
|
|
50
|
+
documentsUpdated: 0,
|
|
51
|
+
connectionsAdded: 0,
|
|
52
|
+
questionsAnswered: 0,
|
|
53
|
+
gapsFilled: 0,
|
|
54
|
+
errors: [],
|
|
55
|
+
warnings: [],
|
|
56
|
+
duration: 0
|
|
57
|
+
};
|
|
58
|
+
try {
|
|
59
|
+
this.log("info", "Starting migration orchestration", {
|
|
60
|
+
analysisDir: this.analysisDir,
|
|
61
|
+
docsPath: this.docsPath
|
|
62
|
+
});
|
|
63
|
+
const analysis = await this.parseAnalysisFiles();
|
|
64
|
+
this.log("info", "Parsed analysis files", {
|
|
65
|
+
gaps: analysis.gaps.length,
|
|
66
|
+
questions: analysis.questions.length,
|
|
67
|
+
connections: analysis.connections.length
|
|
68
|
+
});
|
|
69
|
+
const docsContext = await this.loadDocsContext();
|
|
70
|
+
this.log("info", "Loaded documentation context", {
|
|
71
|
+
totalDocs: docsContext.size,
|
|
72
|
+
keyDocs: Array.from(docsContext.keys()).slice(0, 5)
|
|
73
|
+
});
|
|
74
|
+
const agents = this.createMigrationAgents(analysis, docsContext);
|
|
75
|
+
this.log("info", "Created migration agents", { agents: agents.length });
|
|
76
|
+
for (const agent of agents) {
|
|
77
|
+
try {
|
|
78
|
+
const agentResult = await this.executeAgent(agent, analysis, docsContext);
|
|
79
|
+
result.agentsUsed++;
|
|
80
|
+
if (agentResult.documentsCreated) {
|
|
81
|
+
result.documentsCreated += agentResult.documentsCreated;
|
|
82
|
+
}
|
|
83
|
+
if (agentResult.documentsUpdated) {
|
|
84
|
+
result.documentsUpdated += agentResult.documentsUpdated;
|
|
85
|
+
}
|
|
86
|
+
if (agentResult.gapsFilled) {
|
|
87
|
+
result.gapsFilled += agentResult.gapsFilled;
|
|
88
|
+
}
|
|
89
|
+
if (agentResult.questionsAnswered) {
|
|
90
|
+
result.questionsAnswered += agentResult.questionsAnswered;
|
|
91
|
+
}
|
|
92
|
+
if (agentResult.connectionsAdded) {
|
|
93
|
+
result.connectionsAdded += agentResult.connectionsAdded;
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
97
|
+
result.errors.push(`Agent ${agent.name} failed: ${msg}`);
|
|
98
|
+
this.log("error", `Agent ${agent.name} failed`, { error: msg });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (result.documentsCreated > 0 || result.connectionsAdded > 0) {
|
|
102
|
+
await this.updateMOCFiles(analysis.connections);
|
|
103
|
+
}
|
|
104
|
+
result.success = result.errors.length === 0;
|
|
105
|
+
result.duration = Date.now() - startTime;
|
|
106
|
+
this.log("info", "Migration complete", {
|
|
107
|
+
success: result.success,
|
|
108
|
+
documentsCreated: result.documentsCreated,
|
|
109
|
+
gapsFilled: result.gapsFilled,
|
|
110
|
+
duration: result.duration
|
|
111
|
+
});
|
|
112
|
+
return result;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
115
|
+
result.errors.push(`Migration failed: ${msg}`);
|
|
116
|
+
result.duration = Date.now() - startTime;
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Parse all analysis files from the analysis directory
|
|
122
|
+
*/
|
|
123
|
+
async parseAnalysisFiles() {
|
|
124
|
+
const analysisPath = join(this.projectRoot, this.docsPath, this.analysisDir);
|
|
125
|
+
const result = {
|
|
126
|
+
vision: { purpose: "", goals: [], recommendations: [] },
|
|
127
|
+
gaps: [],
|
|
128
|
+
questions: [],
|
|
129
|
+
connections: []
|
|
130
|
+
};
|
|
131
|
+
if (!existsSync(analysisPath)) {
|
|
132
|
+
throw new Error(`Analysis directory not found: ${analysisPath}`);
|
|
133
|
+
}
|
|
134
|
+
const visionFile = join(analysisPath, "vision-synthesis.md");
|
|
135
|
+
if (existsSync(visionFile)) {
|
|
136
|
+
result.vision = this.parseVisionFile(readFileSync(visionFile, "utf-8"));
|
|
137
|
+
}
|
|
138
|
+
const gapsFile = join(analysisPath, "documentation-gaps.md");
|
|
139
|
+
if (existsSync(gapsFile)) {
|
|
140
|
+
result.gaps = this.parseGapsFile(readFileSync(gapsFile, "utf-8"));
|
|
141
|
+
}
|
|
142
|
+
const questionsFile = join(analysisPath, "research-questions.md");
|
|
143
|
+
if (existsSync(questionsFile)) {
|
|
144
|
+
result.questions = this.parseQuestionsFile(readFileSync(questionsFile, "utf-8"));
|
|
145
|
+
}
|
|
146
|
+
const connectionsFile = join(analysisPath, "knowledge-connections.md");
|
|
147
|
+
if (existsSync(connectionsFile)) {
|
|
148
|
+
result.connections = this.parseConnectionsFile(readFileSync(connectionsFile, "utf-8"));
|
|
149
|
+
}
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Parse vision synthesis file
|
|
154
|
+
*/
|
|
155
|
+
parseVisionFile(content) {
|
|
156
|
+
const vision = {
|
|
157
|
+
purpose: "",
|
|
158
|
+
goals: [],
|
|
159
|
+
recommendations: []
|
|
160
|
+
};
|
|
161
|
+
const purposeMatch = content.match(/## (?:Core Purpose|Problem Statement)[^#]*?\n([\s\S]*?)(?=\n##|\n\*\*|$)/i);
|
|
162
|
+
if (purposeMatch) {
|
|
163
|
+
vision.purpose = purposeMatch[1].trim().slice(0, 500);
|
|
164
|
+
}
|
|
165
|
+
const goalsMatch = content.match(/## (?:Key Success Metrics|Goals)[^#]*?\n([\s\S]*?)(?=\n##|$)/i);
|
|
166
|
+
if (goalsMatch) {
|
|
167
|
+
const goalLines = goalsMatch[1].match(/\*\s+\*\*[^*]+\*\*/g) || [];
|
|
168
|
+
vision.goals = goalLines.map((g) => g.replace(/\*+/g, "").trim()).slice(0, 10);
|
|
169
|
+
}
|
|
170
|
+
const recsMatch = content.match(/## (?:Actionable Recommendations|Recommendations)[^#]*?\n([\s\S]*?)(?=\n##|```|$)/i);
|
|
171
|
+
if (recsMatch) {
|
|
172
|
+
const recLines = recsMatch[1].match(/\d+\.\s+\*\*[^*]+\*\*[^*\n]*/g) || [];
|
|
173
|
+
vision.recommendations = recLines.map((r) => r.replace(/\d+\.\s*\*\*|\*\*/g, "").trim()).slice(0, 10);
|
|
174
|
+
}
|
|
175
|
+
return vision;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Parse documentation gaps file
|
|
179
|
+
*/
|
|
180
|
+
parseGapsFile(content) {
|
|
181
|
+
const gaps = [];
|
|
182
|
+
const sections = content.split(/###\s+\d+\.\s+/);
|
|
183
|
+
for (const section of sections.slice(1)) {
|
|
184
|
+
const titleMatch = section.match(/^([^\n]+)/);
|
|
185
|
+
const title = titleMatch ? titleMatch[1].trim() : "Unknown Section";
|
|
186
|
+
const obsMatches = section.matchAll(/\*\*Observation:\*\*\s*([^\n]+)/g);
|
|
187
|
+
for (const match of obsMatches) {
|
|
188
|
+
const observation = match[1].trim();
|
|
189
|
+
const recMatch = section.match(/\*\*Recommendation:\*\*\s*([^\n]+)/);
|
|
190
|
+
const recommendation = recMatch ? recMatch[1].trim() : "";
|
|
191
|
+
const wikiLinks = observation.match(/\[\[[^\]]+\]\]/g) || [];
|
|
192
|
+
const relatedDocs = wikiLinks.map((l) => l.replace(/\[\[|\]\]/g, ""));
|
|
193
|
+
gaps.push({
|
|
194
|
+
section: title,
|
|
195
|
+
description: observation,
|
|
196
|
+
recommendation,
|
|
197
|
+
priority: this.inferPriority(observation, recommendation),
|
|
198
|
+
relatedDocs
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
const findingMatches = section.matchAll(/\*\*Finding:\*\*\s*([^\n]+)/g);
|
|
202
|
+
for (const match of findingMatches) {
|
|
203
|
+
const finding = match[1].trim();
|
|
204
|
+
const wikiLinks = finding.match(/\[\[[^\]]+\]\]/g) || [];
|
|
205
|
+
gaps.push({
|
|
206
|
+
section: title,
|
|
207
|
+
description: finding,
|
|
208
|
+
recommendation: "",
|
|
209
|
+
priority: "medium",
|
|
210
|
+
relatedDocs: wikiLinks.map((l) => l.replace(/\[\[|\]\]/g, ""))
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return gaps;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Parse research questions file
|
|
218
|
+
*/
|
|
219
|
+
parseQuestionsFile(content) {
|
|
220
|
+
const questions = [];
|
|
221
|
+
const questionMatches = content.matchAll(/(?:Question:|####\s+\d+\.\d+\s+)([^\n]+)\n([\s\S]*?)(?=\n(?:Question:|####|\*\*Importance|\*\*Suggested|###|$))/g);
|
|
222
|
+
for (const match of questionMatches) {
|
|
223
|
+
const questionText = match[1].replace(/^Question:\s*/, "").trim();
|
|
224
|
+
const context = match[2].trim();
|
|
225
|
+
const importanceMatch = content.match(new RegExp(`${questionText.slice(0, 50)}[\\s\\S]*?\\*\\*Importance:\\*\\*\\s*([^\\n]+)`));
|
|
226
|
+
const importance = importanceMatch ? importanceMatch[1].trim() : "";
|
|
227
|
+
const resourcesMatch = content.match(new RegExp(`${questionText.slice(0, 50)}[\\s\\S]*?\\*\\*Suggested Resources:\\*\\*\\s*([^\\n]+)`));
|
|
228
|
+
const resources = resourcesMatch ? resourcesMatch[1].split(",").map((r) => r.trim()) : [];
|
|
229
|
+
const categoryMatch = content.match(new RegExp(`### \\d+\\. ([^\\n]+)[\\s\\S]*?${questionText.slice(0, 30)}`));
|
|
230
|
+
const category = categoryMatch ? categoryMatch[1].trim() : "General";
|
|
231
|
+
questions.push({
|
|
232
|
+
question: questionText,
|
|
233
|
+
context,
|
|
234
|
+
importance,
|
|
235
|
+
suggestedResources: resources,
|
|
236
|
+
category
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return questions;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Parse knowledge connections file
|
|
243
|
+
*/
|
|
244
|
+
parseConnectionsFile(content) {
|
|
245
|
+
const connections = [];
|
|
246
|
+
const relationshipMatches = content.matchAll(/\[([^\]]+)\]\s*--([A-Z_-]+)-->\s*\[([^\]]+)\](?::\s*([^\n]+))?/g);
|
|
247
|
+
for (const match of relationshipMatches) {
|
|
248
|
+
connections.push({
|
|
249
|
+
source: match[1].trim(),
|
|
250
|
+
target: match[3].trim(),
|
|
251
|
+
relationship: match[2].trim(),
|
|
252
|
+
reason: match[4]?.trim() || ""
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
const wikiMatches = content.matchAll(/\[\[([^\]]+)\]\]\s*(?:to|→|->|--)\s*\[\[([^\]]+)\]\]/g);
|
|
256
|
+
for (const match of wikiMatches) {
|
|
257
|
+
connections.push({
|
|
258
|
+
source: match[1].trim(),
|
|
259
|
+
target: match[2].trim(),
|
|
260
|
+
relationship: "RELATED-TO",
|
|
261
|
+
reason: ""
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return connections;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Load all documentation as context
|
|
268
|
+
*/
|
|
269
|
+
async loadDocsContext() {
|
|
270
|
+
const context = /* @__PURE__ */ new Map();
|
|
271
|
+
const docsDir = join(this.projectRoot, this.docsPath);
|
|
272
|
+
if (!existsSync(docsDir)) {
|
|
273
|
+
return context;
|
|
274
|
+
}
|
|
275
|
+
const loadDir = (dir) => {
|
|
276
|
+
const entries = readdirSync(dir);
|
|
277
|
+
for (const entry of entries) {
|
|
278
|
+
const fullPath = join(dir, entry);
|
|
279
|
+
const stat = statSync(fullPath);
|
|
280
|
+
if (stat.isDirectory() && !entry.startsWith(".") && entry !== "analysis") {
|
|
281
|
+
loadDir(fullPath);
|
|
282
|
+
} else if (entry.endsWith(".md")) {
|
|
283
|
+
const relativePath = relative(docsDir, fullPath);
|
|
284
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
285
|
+
context.set(relativePath, content.slice(0, 15e3));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
loadDir(docsDir);
|
|
290
|
+
return context;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Create migration agents based on analysis
|
|
294
|
+
*/
|
|
295
|
+
createMigrationAgents(analysis, docsContext) {
|
|
296
|
+
const agents = [];
|
|
297
|
+
const highPriorityGaps = analysis.gaps.filter((g) => g.priority === "high");
|
|
298
|
+
if (highPriorityGaps.length > 0) {
|
|
299
|
+
agents.push({
|
|
300
|
+
name: "Gap Filler - High Priority",
|
|
301
|
+
type: "gap-filler",
|
|
302
|
+
task: "Fill high-priority documentation gaps with comprehensive content",
|
|
303
|
+
context: this.buildGapFillerContext(highPriorityGaps, docsContext),
|
|
304
|
+
outputFile: "gap-implementations.md"
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
const mediumPriorityGaps = analysis.gaps.filter((g) => g.priority === "medium");
|
|
308
|
+
if (mediumPriorityGaps.length > 0) {
|
|
309
|
+
agents.push({
|
|
310
|
+
name: "Gap Filler - Medium Priority",
|
|
311
|
+
type: "gap-filler",
|
|
312
|
+
task: "Fill medium-priority documentation gaps",
|
|
313
|
+
context: this.buildGapFillerContext(mediumPriorityGaps, docsContext)
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const questionsByCategory = /* @__PURE__ */ new Map();
|
|
317
|
+
for (const q of analysis.questions) {
|
|
318
|
+
const cat = q.category || "General";
|
|
319
|
+
if (!questionsByCategory.has(cat)) {
|
|
320
|
+
questionsByCategory.set(cat, []);
|
|
321
|
+
}
|
|
322
|
+
questionsByCategory.get(cat).push(q);
|
|
323
|
+
}
|
|
324
|
+
for (const [category, questions] of questionsByCategory) {
|
|
325
|
+
if (questions.length > 0) {
|
|
326
|
+
agents.push({
|
|
327
|
+
name: `Researcher - ${category}`,
|
|
328
|
+
type: "researcher",
|
|
329
|
+
task: `Research and answer questions about ${category}`,
|
|
330
|
+
context: this.buildResearcherContext(questions, docsContext),
|
|
331
|
+
outputFile: `research-${category.toLowerCase().replace(/\s+/g, "-")}.md`
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
const mocGaps = analysis.gaps.filter(
|
|
336
|
+
(g) => g.description.toLowerCase().includes("moc") || g.description.toLowerCase().includes("stub")
|
|
337
|
+
);
|
|
338
|
+
if (mocGaps.length > 0) {
|
|
339
|
+
agents.push({
|
|
340
|
+
name: "MOC Builder",
|
|
341
|
+
type: "moc-builder",
|
|
342
|
+
task: "Populate empty MOC (Map of Content) files with proper structure and links",
|
|
343
|
+
context: this.buildMOCBuilderContext(mocGaps, docsContext)
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (analysis.connections.length > 0) {
|
|
347
|
+
agents.push({
|
|
348
|
+
name: "Connection Builder",
|
|
349
|
+
type: "connector",
|
|
350
|
+
task: "Build knowledge graph connections by adding wiki-links to documents",
|
|
351
|
+
context: this.buildConnectorContext(analysis.connections, docsContext)
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
agents.push({
|
|
355
|
+
name: "Documentation Integrator",
|
|
356
|
+
type: "integrator",
|
|
357
|
+
task: "Ensure all new documentation is consistent and properly integrated",
|
|
358
|
+
context: this.buildIntegratorContext(analysis, docsContext),
|
|
359
|
+
outputFile: "integration-summary.md"
|
|
360
|
+
});
|
|
361
|
+
return agents.slice(0, this.maxAgents);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Build context for gap filler agent
|
|
365
|
+
*/
|
|
366
|
+
buildGapFillerContext(gaps, docsContext) {
|
|
367
|
+
let context = "## Documentation Gaps to Fill\n\n";
|
|
368
|
+
for (const gap of gaps) {
|
|
369
|
+
context += `### ${gap.section}
|
|
370
|
+
`;
|
|
371
|
+
context += `**Issue:** ${gap.description}
|
|
372
|
+
`;
|
|
373
|
+
if (gap.recommendation) {
|
|
374
|
+
context += `**Recommendation:** ${gap.recommendation}
|
|
375
|
+
`;
|
|
376
|
+
}
|
|
377
|
+
context += `**Priority:** ${gap.priority}
|
|
378
|
+
`;
|
|
379
|
+
for (const relatedDoc of gap.relatedDocs.slice(0, 2)) {
|
|
380
|
+
const docKey = Array.from(docsContext.keys()).find(
|
|
381
|
+
(k) => k.toLowerCase().includes(relatedDoc.toLowerCase().replace(/\s+/g, "-"))
|
|
382
|
+
);
|
|
383
|
+
if (docKey) {
|
|
384
|
+
context += `
|
|
385
|
+
**Related: ${relatedDoc}**
|
|
386
|
+
`;
|
|
387
|
+
context += docsContext.get(docKey)?.slice(0, 2e3) + "\n";
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
context += "\n---\n\n";
|
|
391
|
+
}
|
|
392
|
+
context += "\n## Instructions\n";
|
|
393
|
+
context += "For each gap, create comprehensive documentation that:\n";
|
|
394
|
+
context += "1. Addresses the specific issue identified\n";
|
|
395
|
+
context += "2. Follows the existing documentation style\n";
|
|
396
|
+
context += "3. Includes proper wiki-links [[like-this]]\n";
|
|
397
|
+
context += "4. Has appropriate frontmatter (title, type, tags)\n";
|
|
398
|
+
context += "5. Integrates with existing documentation structure\n";
|
|
399
|
+
return context;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Build context for researcher agent
|
|
403
|
+
*/
|
|
404
|
+
buildResearcherContext(questions, docsContext) {
|
|
405
|
+
let context = "## Research Questions to Answer\n\n";
|
|
406
|
+
for (const q of questions) {
|
|
407
|
+
context += `### Question
|
|
408
|
+
${q.question}
|
|
409
|
+
|
|
410
|
+
`;
|
|
411
|
+
if (q.importance) {
|
|
412
|
+
context += `**Importance:** ${q.importance}
|
|
413
|
+
`;
|
|
414
|
+
}
|
|
415
|
+
if (q.context) {
|
|
416
|
+
context += `**Context:** ${q.context}
|
|
417
|
+
`;
|
|
418
|
+
}
|
|
419
|
+
if (q.suggestedResources.length > 0) {
|
|
420
|
+
context += `**Resources:** ${q.suggestedResources.join(", ")}
|
|
421
|
+
`;
|
|
422
|
+
}
|
|
423
|
+
context += "\n---\n\n";
|
|
424
|
+
}
|
|
425
|
+
context += "\n## Available Documentation Context\n\n";
|
|
426
|
+
const relevantDocs = this.findRelevantDocs(
|
|
427
|
+
questions.map((q) => q.question).join(" "),
|
|
428
|
+
docsContext,
|
|
429
|
+
5
|
|
430
|
+
);
|
|
431
|
+
for (const [path, content] of relevantDocs) {
|
|
432
|
+
context += `### ${path}
|
|
433
|
+
`;
|
|
434
|
+
context += content.slice(0, 3e3) + "\n\n";
|
|
435
|
+
}
|
|
436
|
+
context += "\n## Instructions\n";
|
|
437
|
+
context += "For each research question:\n";
|
|
438
|
+
context += "1. Analyze the available documentation\n";
|
|
439
|
+
context += "2. Synthesize a well-researched answer\n";
|
|
440
|
+
context += "3. Cite sources using [[wiki-links]]\n";
|
|
441
|
+
context += "4. Identify any remaining unknowns\n";
|
|
442
|
+
context += "5. Suggest best practices based on the knowledge graph\n";
|
|
443
|
+
return context;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Build context for MOC builder agent
|
|
447
|
+
*/
|
|
448
|
+
buildMOCBuilderContext(gaps, docsContext) {
|
|
449
|
+
let context = "## MOC Files to Populate\n\n";
|
|
450
|
+
const mocFiles = Array.from(docsContext.keys()).filter(
|
|
451
|
+
(k) => k.includes("_MOC.md") || k.includes("MOC.md")
|
|
452
|
+
);
|
|
453
|
+
context += "### Current MOC Files\n";
|
|
454
|
+
for (const mocFile of mocFiles) {
|
|
455
|
+
const content = docsContext.get(mocFile) || "";
|
|
456
|
+
const isEmpty = content.length < 200 || content.includes("stub");
|
|
457
|
+
context += `- ${mocFile} ${isEmpty ? "(EMPTY/STUB)" : "(has content)"}
|
|
458
|
+
`;
|
|
459
|
+
}
|
|
460
|
+
context += "\n### Gap Analysis Related to MOCs\n";
|
|
461
|
+
for (const gap of gaps) {
|
|
462
|
+
context += `- ${gap.section}: ${gap.description}
|
|
463
|
+
`;
|
|
464
|
+
if (gap.recommendation) {
|
|
465
|
+
context += ` Recommendation: ${gap.recommendation}
|
|
466
|
+
`;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
context += "\n### Documentation Structure\n";
|
|
470
|
+
const directories = /* @__PURE__ */ new Set();
|
|
471
|
+
for (const path of docsContext.keys()) {
|
|
472
|
+
const dir = dirname(path);
|
|
473
|
+
if (dir !== ".") {
|
|
474
|
+
directories.add(dir);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
for (const dir of directories) {
|
|
478
|
+
const docsInDir = Array.from(docsContext.keys()).filter((k) => dirname(k) === dir);
|
|
479
|
+
context += `- ${dir}/ (${docsInDir.length} docs)
|
|
480
|
+
`;
|
|
481
|
+
}
|
|
482
|
+
context += "\n## Instructions\n";
|
|
483
|
+
context += "For each empty/stub MOC file:\n";
|
|
484
|
+
context += "1. Create a proper introduction for the section\n";
|
|
485
|
+
context += "2. List all documents in that directory with [[wiki-links]]\n";
|
|
486
|
+
context += "3. Organize by subcategory if applicable\n";
|
|
487
|
+
context += "4. Add brief descriptions for each linked document\n";
|
|
488
|
+
context += "5. Include navigation links to parent/sibling MOCs\n";
|
|
489
|
+
return context;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Build context for connector agent
|
|
493
|
+
*/
|
|
494
|
+
buildConnectorContext(connections, docsContext) {
|
|
495
|
+
let context = "## Suggested Knowledge Graph Connections\n\n";
|
|
496
|
+
for (const conn of connections) {
|
|
497
|
+
context += `- [${conn.source}] --${conn.relationship}--> [${conn.target}]`;
|
|
498
|
+
if (conn.reason) {
|
|
499
|
+
context += `: ${conn.reason}`;
|
|
500
|
+
}
|
|
501
|
+
context += "\n";
|
|
502
|
+
}
|
|
503
|
+
context += "\n## Existing Documents\n";
|
|
504
|
+
for (const [path] of Array.from(docsContext.entries()).slice(0, 30)) {
|
|
505
|
+
context += `- [[${path.replace(".md", "")}]]
|
|
506
|
+
`;
|
|
507
|
+
}
|
|
508
|
+
context += "\n## Instructions\n";
|
|
509
|
+
context += "For each suggested connection:\n";
|
|
510
|
+
context += "1. Find the source document\n";
|
|
511
|
+
context += "2. Add appropriate wiki-link [[target]] to the source\n";
|
|
512
|
+
context += "3. Consider adding reciprocal links where appropriate\n";
|
|
513
|
+
context += '4. Use "See also" or "Related" sections for connections\n';
|
|
514
|
+
context += "5. Ensure the link context is meaningful\n";
|
|
515
|
+
return context;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Build context for integrator agent
|
|
519
|
+
*/
|
|
520
|
+
buildIntegratorContext(analysis, docsContext) {
|
|
521
|
+
let context = "## Integration Context\n\n";
|
|
522
|
+
context += "### Project Vision\n";
|
|
523
|
+
context += analysis.vision.purpose + "\n\n";
|
|
524
|
+
context += "### Goals\n";
|
|
525
|
+
for (const goal of analysis.vision.goals) {
|
|
526
|
+
context += `- ${goal}
|
|
527
|
+
`;
|
|
528
|
+
}
|
|
529
|
+
context += "\n### Key Recommendations\n";
|
|
530
|
+
for (const rec of analysis.vision.recommendations) {
|
|
531
|
+
context += `- ${rec}
|
|
532
|
+
`;
|
|
533
|
+
}
|
|
534
|
+
context += "\n### Statistics\n";
|
|
535
|
+
context += `- Total documents: ${docsContext.size}
|
|
536
|
+
`;
|
|
537
|
+
context += `- Gaps identified: ${analysis.gaps.length}
|
|
538
|
+
`;
|
|
539
|
+
context += `- Questions to answer: ${analysis.questions.length}
|
|
540
|
+
`;
|
|
541
|
+
context += `- Connections to build: ${analysis.connections.length}
|
|
542
|
+
`;
|
|
543
|
+
context += "\n## Instructions\n";
|
|
544
|
+
context += "Create an integration summary that:\n";
|
|
545
|
+
context += "1. Lists all changes made during migration\n";
|
|
546
|
+
context += "2. Highlights any remaining gaps\n";
|
|
547
|
+
context += "3. Suggests next steps for documentation improvement\n";
|
|
548
|
+
context += "4. Provides a quality assessment\n";
|
|
549
|
+
return context;
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Execute a single migration agent
|
|
553
|
+
*/
|
|
554
|
+
async executeAgent(agent, analysis, docsContext) {
|
|
555
|
+
this.log("info", `Executing agent: ${agent.name}`, { type: agent.type });
|
|
556
|
+
const prompt = this.buildAgentPrompt(agent);
|
|
557
|
+
const response = await this.callAI(prompt);
|
|
558
|
+
if (!response) {
|
|
559
|
+
throw new Error("No response from AI");
|
|
560
|
+
}
|
|
561
|
+
const result = await this.processAgentResponse(agent, response, docsContext);
|
|
562
|
+
this.log("info", `Agent ${agent.name} completed`, result);
|
|
563
|
+
return result;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Build prompt for agent
|
|
567
|
+
*/
|
|
568
|
+
buildAgentPrompt(agent) {
|
|
569
|
+
return `# ${agent.name}
|
|
570
|
+
|
|
571
|
+
## Task
|
|
572
|
+
${agent.task}
|
|
573
|
+
|
|
574
|
+
${agent.context}
|
|
575
|
+
|
|
576
|
+
## Output Format
|
|
577
|
+
Provide your response in markdown format with clear sections.
|
|
578
|
+
For each document to create or update, use this format:
|
|
579
|
+
|
|
580
|
+
\`\`\`document
|
|
581
|
+
---
|
|
582
|
+
path: relative/path/to/file.md
|
|
583
|
+
action: create|update
|
|
584
|
+
---
|
|
585
|
+
# Document Title
|
|
586
|
+
|
|
587
|
+
Document content here with [[wiki-links]] to other documents.
|
|
588
|
+
\`\`\`
|
|
589
|
+
|
|
590
|
+
For research answers, use:
|
|
591
|
+
\`\`\`answer
|
|
592
|
+
## Question
|
|
593
|
+
The original question
|
|
594
|
+
|
|
595
|
+
## Answer
|
|
596
|
+
Your researched answer with [[citations]]
|
|
597
|
+
|
|
598
|
+
## Best Practices
|
|
599
|
+
- Recommendation 1
|
|
600
|
+
- Recommendation 2
|
|
601
|
+
|
|
602
|
+
## Remaining Unknowns
|
|
603
|
+
- Any unresolved items
|
|
604
|
+
\`\`\`
|
|
605
|
+
`;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Call AI (Gemini or fallback)
|
|
609
|
+
*/
|
|
610
|
+
async callAI(prompt) {
|
|
611
|
+
if (this.geminiClient) {
|
|
612
|
+
try {
|
|
613
|
+
const model = this.geminiClient.getGenerativeModel({ model: "gemini-2.0-flash" });
|
|
614
|
+
const result = await model.generateContent(prompt);
|
|
615
|
+
return result.response.text();
|
|
616
|
+
} catch (error) {
|
|
617
|
+
this.log("error", "Gemini API call failed", { error: String(error) });
|
|
618
|
+
return null;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
622
|
+
try {
|
|
623
|
+
const { default: Anthropic } = await import("@anthropic-ai/sdk");
|
|
624
|
+
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
|
|
625
|
+
const message = await client.messages.create({
|
|
626
|
+
model: "claude-sonnet-4-20250514",
|
|
627
|
+
max_tokens: 8e3,
|
|
628
|
+
messages: [{ role: "user", content: prompt }]
|
|
629
|
+
});
|
|
630
|
+
const textBlock = message.content.find((b) => b.type === "text");
|
|
631
|
+
return textBlock ? textBlock.text : null;
|
|
632
|
+
} catch (error) {
|
|
633
|
+
this.log("error", "Anthropic API call failed", { error: String(error) });
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
return null;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Process agent response and create/update documents
|
|
641
|
+
*/
|
|
642
|
+
async processAgentResponse(agent, response, docsContext) {
|
|
643
|
+
const result = {
|
|
644
|
+
documentsCreated: 0,
|
|
645
|
+
documentsUpdated: 0,
|
|
646
|
+
gapsFilled: 0,
|
|
647
|
+
questionsAnswered: 0,
|
|
648
|
+
connectionsAdded: 0
|
|
649
|
+
};
|
|
650
|
+
const documentMatches = response.matchAll(/```document\n---\npath:\s*([^\n]+)\naction:\s*(\w+)\n---\n([\s\S]*?)```/g);
|
|
651
|
+
for (const match of documentMatches) {
|
|
652
|
+
const path = match[1].trim();
|
|
653
|
+
const action = match[2].trim();
|
|
654
|
+
const content = match[3].trim();
|
|
655
|
+
if (this.dryRun) {
|
|
656
|
+
this.log("info", `[DRY RUN] Would ${action}: ${path}`);
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
const fullPath = join(this.projectRoot, this.docsPath, path);
|
|
660
|
+
const dir = dirname(fullPath);
|
|
661
|
+
if (!existsSync(dir)) {
|
|
662
|
+
mkdirSync(dir, { recursive: true });
|
|
663
|
+
}
|
|
664
|
+
const finalContent = this.addFrontmatter(content, path, agent.type);
|
|
665
|
+
writeFileSync(fullPath, finalContent, "utf-8");
|
|
666
|
+
if (action === "create") {
|
|
667
|
+
result.documentsCreated++;
|
|
668
|
+
if (agent.type === "gap-filler") {
|
|
669
|
+
result.gapsFilled++;
|
|
670
|
+
}
|
|
671
|
+
} else {
|
|
672
|
+
result.documentsUpdated++;
|
|
673
|
+
}
|
|
674
|
+
const wikiLinks = content.match(/\[\[[^\]]+\]\]/g) || [];
|
|
675
|
+
result.connectionsAdded += wikiLinks.length;
|
|
676
|
+
}
|
|
677
|
+
const answerMatches = response.matchAll(/```answer\n([\s\S]*?)```/g);
|
|
678
|
+
for (const match of answerMatches) {
|
|
679
|
+
result.questionsAnswered++;
|
|
680
|
+
if (agent.outputFile && !this.dryRun) {
|
|
681
|
+
const outputPath = join(this.projectRoot, this.docsPath, "analysis", agent.outputFile);
|
|
682
|
+
const existing = existsSync(outputPath) ? readFileSync(outputPath, "utf-8") : "";
|
|
683
|
+
const newContent = existing + "\n\n---\n\n" + match[1].trim();
|
|
684
|
+
writeFileSync(outputPath, newContent, "utf-8");
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
if (agent.outputFile && result.documentsCreated === 0 && !this.dryRun) {
|
|
688
|
+
const outputPath = join(this.projectRoot, this.docsPath, "analysis", agent.outputFile);
|
|
689
|
+
const frontmatter = `---
|
|
690
|
+
title: "${agent.name}"
|
|
691
|
+
type: migration-output
|
|
692
|
+
generator: migration-orchestrator
|
|
693
|
+
agent: ${agent.type}
|
|
694
|
+
created: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
695
|
+
---
|
|
696
|
+
|
|
697
|
+
# ${agent.name}
|
|
698
|
+
|
|
699
|
+
> Generated by MigrationOrchestrator
|
|
700
|
+
|
|
701
|
+
`;
|
|
702
|
+
writeFileSync(outputPath, frontmatter + response, "utf-8");
|
|
703
|
+
result.documentsCreated++;
|
|
704
|
+
}
|
|
705
|
+
return result;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Add frontmatter to document if not present
|
|
709
|
+
*/
|
|
710
|
+
addFrontmatter(content, path, agentType) {
|
|
711
|
+
if (content.startsWith("---")) {
|
|
712
|
+
return content;
|
|
713
|
+
}
|
|
714
|
+
const title = basename(path, ".md").replace(/-/g, " ").replace(/_/g, " ");
|
|
715
|
+
const type = this.inferDocType(path);
|
|
716
|
+
return `---
|
|
717
|
+
title: "${title}"
|
|
718
|
+
type: ${type}
|
|
719
|
+
generator: migration-orchestrator
|
|
720
|
+
agent: ${agentType}
|
|
721
|
+
created: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
722
|
+
---
|
|
723
|
+
|
|
724
|
+
${content}`;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Infer document type from path
|
|
728
|
+
*/
|
|
729
|
+
inferDocType(path) {
|
|
730
|
+
if (path.includes("concepts")) return "concept";
|
|
731
|
+
if (path.includes("components")) return "component";
|
|
732
|
+
if (path.includes("services")) return "service";
|
|
733
|
+
if (path.includes("features")) return "feature";
|
|
734
|
+
if (path.includes("guides")) return "guide";
|
|
735
|
+
if (path.includes("references")) return "reference";
|
|
736
|
+
if (path.includes("standards")) return "standard";
|
|
737
|
+
if (path.includes("integrations")) return "integration";
|
|
738
|
+
if (path.includes("MOC")) return "moc";
|
|
739
|
+
return "document";
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Update MOC files with new connections
|
|
743
|
+
*/
|
|
744
|
+
async updateMOCFiles(connections) {
|
|
745
|
+
this.log("info", "MOC files updated with new connections", { count: connections.length });
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Find relevant docs based on query
|
|
749
|
+
*/
|
|
750
|
+
findRelevantDocs(query, docsContext, limit) {
|
|
751
|
+
const relevant = /* @__PURE__ */ new Map();
|
|
752
|
+
const queryWords = query.toLowerCase().split(/\s+/).filter((w) => w.length > 3);
|
|
753
|
+
const scored = Array.from(docsContext.entries()).map(([path, content]) => {
|
|
754
|
+
let score = 0;
|
|
755
|
+
const lowerContent = content.toLowerCase();
|
|
756
|
+
for (const word of queryWords) {
|
|
757
|
+
if (lowerContent.includes(word)) {
|
|
758
|
+
score++;
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
return { path, content, score };
|
|
762
|
+
});
|
|
763
|
+
scored.sort((a, b) => b.score - a.score);
|
|
764
|
+
for (const item of scored.slice(0, limit)) {
|
|
765
|
+
if (item.score > 0) {
|
|
766
|
+
relevant.set(item.path, item.content);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return relevant;
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* Infer priority from description
|
|
773
|
+
*/
|
|
774
|
+
inferPriority(description, recommendation) {
|
|
775
|
+
const text = (description + " " + recommendation).toLowerCase();
|
|
776
|
+
if (text.includes("critical") || text.includes("missing") || text.includes("empty") || text.includes("stub") || text.includes("required")) {
|
|
777
|
+
return "high";
|
|
778
|
+
}
|
|
779
|
+
if (text.includes("should") || text.includes("recommend") || text.includes("consider")) {
|
|
780
|
+
return "medium";
|
|
781
|
+
}
|
|
782
|
+
return "low";
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Log message
|
|
786
|
+
*/
|
|
787
|
+
log(level, message, data) {
|
|
788
|
+
if (!this.verbose && level === "info") return;
|
|
789
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().split("T")[1].split(".")[0];
|
|
790
|
+
const prefix = level === "error" ? "❌" : level === "warn" ? "⚠️" : "📋";
|
|
791
|
+
console.log(`[${timestamp}] ${prefix} [migration] ${message}`, data ? JSON.stringify(data) : "");
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
export {
|
|
795
|
+
MigrationOrchestrator
|
|
796
|
+
};
|
|
797
|
+
//# sourceMappingURL=migration-orchestrator.js.map
|