@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.
@@ -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