@weavelogic/knowledge-graph-agent 0.7.4 → 0.8.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/_virtual/__vite-browser-external.js +2 -2
- package/dist/_virtual/__vite-browser-external.js.map +1 -1
- package/dist/_virtual/browser.js +2 -3
- package/dist/_virtual/browser.js.map +1 -1
- package/dist/_virtual/index10.js +2 -4
- package/dist/_virtual/index10.js.map +1 -1
- package/dist/_virtual/index11.js +2 -2
- package/dist/cli/commands/hive-mind/add-frontmatter.js +2 -2
- package/dist/cli/commands/hive-mind/add-frontmatter.js.map +1 -1
- package/dist/cli/commands/hive-mind/analyze-links.js +2 -2
- package/dist/cli/commands/hive-mind/analyze-links.js.map +1 -1
- package/dist/cli/commands/hive-mind/find-connections.js +2 -2
- package/dist/cli/commands/hive-mind/find-connections.js.map +1 -1
- package/dist/cli/commands/hive-mind/validate-names.js +2 -2
- package/dist/cli/commands/hive-mind/validate-names.js.map +1 -1
- package/dist/graphql/server.js +2 -2
- package/dist/graphql/server.js.map +1 -1
- package/dist/mcp-server/tools/audit/index.d.ts +4 -0
- package/dist/mcp-server/tools/audit/index.d.ts.map +1 -1
- package/dist/node_modules/@typescript-eslint/project-service/dist/index.js +1 -1
- package/dist/node_modules/debug/src/browser.js +1 -1
- package/dist/node_modules/fdir/dist/index.js +14 -14
- package/dist/node_modules/fdir/dist/index.js.map +1 -1
- package/dist/node_modules/tinyglobby/dist/index.js +14 -14
- package/dist/node_modules/tinyglobby/dist/index.js.map +1 -1
- package/dist/node_modules/ts-api-utils/lib/index.js +1 -1
- package/dist/node_modules/typescript/lib/typescript.js +24 -24
- package/dist/node_modules/typescript/lib/typescript.js.map +1 -1
- package/dist/vector/services/embedding-service.js +1 -7
- package/dist/vector/services/embedding-service.js.map +1 -1
- package/package.json +2 -1
- package/dist/_virtual/browser2.js +0 -5
- package/dist/_virtual/browser2.js.map +0 -1
- package/dist/_virtual/index12.js +0 -5
- package/dist/_virtual/index12.js.map +0 -1
- package/dist/_virtual/ort-web.min.js +0 -8
- package/dist/_virtual/ort-web.min.js.map +0 -1
- package/dist/_virtual/ort-web.min2.js +0 -5
- package/dist/_virtual/ort-web.min2.js.map +0 -1
- package/dist/node_modules/@huggingface/jinja/dist/index.js +0 -118
- package/dist/node_modules/@huggingface/jinja/dist/index.js.map +0 -1
- package/dist/node_modules/@xenova/transformers/src/backends/onnx.js +0 -24
- package/dist/node_modules/@xenova/transformers/src/backends/onnx.js.map +0 -1
- package/dist/node_modules/@xenova/transformers/src/configs.js +0 -52
- package/dist/node_modules/@xenova/transformers/src/configs.js.map +0 -1
- package/dist/node_modules/@xenova/transformers/src/env.js +0 -35
- package/dist/node_modules/@xenova/transformers/src/env.js.map +0 -1
- package/dist/node_modules/@xenova/transformers/src/models.js +0 -3852
- package/dist/node_modules/@xenova/transformers/src/models.js.map +0 -1
- package/dist/node_modules/@xenova/transformers/src/tokenizers.js +0 -144
- package/dist/node_modules/@xenova/transformers/src/tokenizers.js.map +0 -1
- package/dist/node_modules/@xenova/transformers/src/utils/core.js +0 -52
- package/dist/node_modules/@xenova/transformers/src/utils/core.js.map +0 -1
- package/dist/node_modules/@xenova/transformers/src/utils/generation.js +0 -623
- package/dist/node_modules/@xenova/transformers/src/utils/generation.js.map +0 -1
- package/dist/node_modules/@xenova/transformers/src/utils/hub.js +0 -395
- package/dist/node_modules/@xenova/transformers/src/utils/hub.js.map +0 -1
- package/dist/node_modules/@xenova/transformers/src/utils/image.js +0 -12
- package/dist/node_modules/@xenova/transformers/src/utils/image.js.map +0 -1
- package/dist/node_modules/@xenova/transformers/src/utils/maths.js +0 -89
- package/dist/node_modules/@xenova/transformers/src/utils/maths.js.map +0 -1
- package/dist/node_modules/@xenova/transformers/src/utils/tensor.js +0 -750
- package/dist/node_modules/@xenova/transformers/src/utils/tensor.js.map +0 -1
- package/dist/node_modules/onnxruntime-common/dist/lib/backend-impl.js +0 -67
- package/dist/node_modules/onnxruntime-common/dist/lib/backend-impl.js.map +0 -1
- package/dist/node_modules/onnxruntime-common/dist/lib/env-impl.js +0 -24
- package/dist/node_modules/onnxruntime-common/dist/lib/env-impl.js.map +0 -1
- package/dist/node_modules/onnxruntime-common/dist/lib/env.js +0 -6
- package/dist/node_modules/onnxruntime-common/dist/lib/env.js.map +0 -1
- package/dist/node_modules/onnxruntime-common/dist/lib/index.js +0 -11
- package/dist/node_modules/onnxruntime-common/dist/lib/index.js.map +0 -1
- package/dist/node_modules/onnxruntime-common/dist/lib/inference-session-impl.js +0 -162
- package/dist/node_modules/onnxruntime-common/dist/lib/inference-session-impl.js.map +0 -1
- package/dist/node_modules/onnxruntime-common/dist/lib/inference-session.js +0 -6
- package/dist/node_modules/onnxruntime-common/dist/lib/inference-session.js.map +0 -1
- package/dist/node_modules/onnxruntime-common/dist/lib/tensor-impl.js +0 -393
- package/dist/node_modules/onnxruntime-common/dist/lib/tensor-impl.js.map +0 -1
- package/dist/node_modules/onnxruntime-common/dist/lib/tensor.js +0 -6
- package/dist/node_modules/onnxruntime-common/dist/lib/tensor.js.map +0 -1
- package/dist/node_modules/onnxruntime-web/dist/ort-web.min.js +0 -12919
- package/dist/node_modules/onnxruntime-web/dist/ort-web.min.js.map +0 -1
- package/dist/node_modules/ws/browser.js +0 -16
- package/dist/node_modules/ws/browser.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"__vite-browser-external.js","sources":["../../__vite-browser-external"],"sourcesContent":["export default {}"],"names":[],"mappings":"AAAA,MAAA,
|
|
1
|
+
{"version":3,"file":"__vite-browser-external.js","sources":["../../__vite-browser-external"],"sourcesContent":["export default {}"],"names":[],"mappings":"AAAA,MAAA,wBAAe,CAAA;"}
|
package/dist/_virtual/browser.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"browser.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
package/dist/_virtual/index10.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
import * as index from "../node_modules/onnxruntime-common/dist/lib/index.js";
|
|
3
|
-
const require$$0 = /* @__PURE__ */ getAugmentedNamespace(index);
|
|
1
|
+
var dist = {};
|
|
4
2
|
export {
|
|
5
|
-
|
|
3
|
+
dist as __exports
|
|
6
4
|
};
|
|
7
5
|
//# sourceMappingURL=index10.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index10.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index10.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
package/dist/_virtual/index11.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Command } from "commander";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { writeFile, readFile } from "fs/promises";
|
|
5
|
-
import
|
|
5
|
+
import fg from "fast-glob";
|
|
6
6
|
import matter from "gray-matter";
|
|
7
7
|
import * as yaml from "js-yaml";
|
|
8
8
|
class FrontmatterEnricher {
|
|
@@ -11,7 +11,7 @@ class FrontmatterEnricher {
|
|
|
11
11
|
*/
|
|
12
12
|
async enrichVault(vaultPath, options = {}) {
|
|
13
13
|
const resolvedPath = path.resolve(vaultPath);
|
|
14
|
-
const files = await
|
|
14
|
+
const files = await fg("**/*.md", {
|
|
15
15
|
cwd: resolvedPath,
|
|
16
16
|
ignore: ["node_modules/**", ".git/**", "dist/**"],
|
|
17
17
|
absolute: false
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-frontmatter.js","sources":["../../../../src/cli/commands/hive-mind/add-frontmatter.ts"],"sourcesContent":["/**\n * Hive Mind - Frontmatter Enricher\n *\n * Adds or enriches YAML frontmatter in markdown files to improve discoverability\n * and enable better linking between documents.\n *\n * SPEC-003: Hive Mind Reconnection Tools\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport * as path from 'path';\nimport { readFile, writeFile } from 'fs/promises';\nimport { glob } from 'fast-glob';\nimport matter from 'gray-matter';\nimport * as yaml from 'js-yaml';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface EnrichOptions {\n overwrite?: boolean;\n tags?: boolean;\n aliases?: boolean;\n links?: boolean;\n dryRun?: boolean;\n output?: string;\n json?: boolean;\n verbose?: boolean;\n}\n\nexport interface FrontmatterTemplate {\n title: string;\n created: string;\n modified: string;\n tags: string[];\n aliases: string[];\n links: string[];\n type: string;\n status: string;\n description?: string;\n}\n\nexport interface EnrichedFile {\n file: string;\n added: string[];\n updated: string[];\n frontmatter: FrontmatterTemplate;\n}\n\nexport interface EnrichResult {\n enriched: EnrichedFile[];\n skipped: string[];\n errors: Array<{ file: string; error: string }>;\n statistics: {\n totalFiles: number;\n enrichedCount: number;\n skippedCount: number;\n errorCount: number;\n tagsAdded: number;\n aliasesAdded: number;\n linksExtracted: number;\n };\n}\n\n// ============================================================================\n// Frontmatter Enricher Class\n// ============================================================================\n\nexport class FrontmatterEnricher {\n /**\n * Enrich frontmatter for all files in a vault\n */\n async enrichVault(vaultPath: string, options: EnrichOptions = {}): Promise<EnrichResult> {\n const resolvedPath = path.resolve(vaultPath);\n\n // Find all markdown files\n const files = await glob('**/*.md', {\n cwd: resolvedPath,\n ignore: ['node_modules/**', '.git/**', 'dist/**'],\n absolute: false,\n });\n\n if (files.length === 0) {\n throw new Error(`No markdown files found in: ${resolvedPath}`);\n }\n\n const enriched: EnrichedFile[] = [];\n const skipped: string[] = [];\n const errors: Array<{ file: string; error: string }> = [];\n let tagsAdded = 0;\n let aliasesAdded = 0;\n let linksExtracted = 0;\n\n for (const file of files) {\n try {\n const result = await this.enrichFile(\n path.join(resolvedPath, file),\n file,\n options\n );\n\n if (result) {\n enriched.push(result);\n tagsAdded += result.frontmatter.tags.length;\n aliasesAdded += result.frontmatter.aliases.length;\n linksExtracted += result.frontmatter.links.length;\n } else {\n skipped.push(file);\n }\n } catch (error) {\n errors.push({\n file,\n error: error instanceof Error ? error.message : 'Unknown error',\n });\n }\n }\n\n return {\n enriched,\n skipped,\n errors,\n statistics: {\n totalFiles: files.length,\n enrichedCount: enriched.length,\n skippedCount: skipped.length,\n errorCount: errors.length,\n tagsAdded,\n aliasesAdded,\n linksExtracted,\n },\n };\n }\n\n /**\n * Enrich a single file's frontmatter\n */\n async enrichFile(\n filePath: string,\n relativePath: string,\n options: EnrichOptions\n ): Promise<EnrichedFile | null> {\n const content = await readFile(filePath, 'utf-8');\n const parsed = matter(content);\n\n const existingFm = parsed.data as Partial<FrontmatterTemplate>;\n const hasExistingFm = Object.keys(existingFm).length > 0;\n\n // Skip if has frontmatter and not overwriting\n if (hasExistingFm && !options.overwrite) {\n // Check if we need to add anything\n const needsTags = options.tags && (!existingFm.tags || existingFm.tags.length === 0);\n const needsAliases = options.aliases && (!existingFm.aliases || existingFm.aliases.length === 0);\n const needsLinks = options.links && (!existingFm.links || existingFm.links.length === 0);\n\n if (!needsTags && !needsAliases && !needsLinks && existingFm.title) {\n return null;\n }\n }\n\n // Extract metadata from content\n const metadata = this.extractMetadata(parsed.content, relativePath);\n\n // Build new frontmatter\n const added: string[] = [];\n const updated: string[] = [];\n const newFm: FrontmatterTemplate = {\n title: existingFm.title || metadata.title,\n created: existingFm.created || metadata.created,\n modified: new Date().toISOString().split('T')[0],\n tags: [],\n aliases: [],\n links: [],\n type: existingFm.type || metadata.type,\n status: existingFm.status || 'active',\n };\n\n // Track what was added/updated\n if (!existingFm.title) added.push('title');\n if (!existingFm.created) added.push('created');\n if (!existingFm.type) added.push('type');\n if (!existingFm.status) added.push('status');\n updated.push('modified');\n\n // Handle tags\n if (options.tags !== false) {\n const existingTags = existingFm.tags || [];\n const extractedTags = metadata.tags;\n const mergedTags = [...new Set([...existingTags, ...extractedTags])];\n newFm.tags = mergedTags;\n\n const newTagCount = mergedTags.length - existingTags.length;\n if (newTagCount > 0) added.push(`${newTagCount} tags`);\n }\n\n // Handle aliases\n if (options.aliases !== false) {\n const existingAliases = existingFm.aliases || [];\n const extractedAliases = metadata.aliases;\n const mergedAliases = [...new Set([...existingAliases, ...extractedAliases])];\n newFm.aliases = mergedAliases;\n\n const newAliasCount = mergedAliases.length - existingAliases.length;\n if (newAliasCount > 0) added.push(`${newAliasCount} aliases`);\n }\n\n // Handle links\n if (options.links !== false) {\n const existingLinks = existingFm.links || [];\n const extractedLinks = metadata.links;\n const mergedLinks = [...new Set([...existingLinks, ...extractedLinks])];\n newFm.links = mergedLinks;\n\n const newLinkCount = mergedLinks.length - existingLinks.length;\n if (newLinkCount > 0) added.push(`${newLinkCount} links`);\n }\n\n // Add description if missing\n if (!existingFm.description && metadata.description) {\n newFm.description = metadata.description;\n added.push('description');\n }\n\n // Write file if not dry run\n if (!options.dryRun) {\n const newContent = this.buildMarkdownWithFrontmatter(newFm, parsed.content);\n await writeFile(filePath, newContent);\n }\n\n return {\n file: relativePath,\n added,\n updated,\n frontmatter: newFm,\n };\n }\n\n /**\n * Extract metadata from content and filename\n */\n private extractMetadata(content: string, filename: string): {\n title: string;\n created: string;\n type: string;\n tags: string[];\n aliases: string[];\n links: string[];\n description?: string;\n } {\n const basename = path.basename(filename, '.md');\n\n // Extract title from first heading or filename\n let title = basename;\n const headingMatch = content.match(/^#\\s+(.+)$/m);\n if (headingMatch) {\n title = headingMatch[1].trim();\n }\n\n // Generate aliases from title variations\n const aliases = this.generateAliases(title, basename);\n\n // Extract tags from hashtags in content\n const tags = this.extractHashtags(content);\n\n // Infer type from content and filename\n const type = this.inferType(content, basename);\n\n // Extract wiki-links\n const links = this.extractWikiLinks(content);\n\n // Extract first paragraph as description\n const description = this.extractDescription(content);\n\n // Use current date for created\n const created = new Date().toISOString().split('T')[0];\n\n return { title, created, type, tags, aliases, links, description };\n }\n\n /**\n * Generate aliases from title\n */\n private generateAliases(title: string, filename: string): string[] {\n const aliases: string[] = [];\n\n // Add filename if different from title\n const cleanFilename = filename.replace(/-/g, ' ').toLowerCase();\n const cleanTitle = title.toLowerCase();\n if (cleanFilename !== cleanTitle) {\n // Convert filename to readable form\n const readableFilename = filename\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n if (readableFilename !== title) {\n aliases.push(readableFilename);\n }\n }\n\n // Add lowercase version if title has caps\n if (title !== title.toLowerCase() && !aliases.includes(title.toLowerCase())) {\n aliases.push(title.toLowerCase());\n }\n\n // Add acronym if title has multiple words\n const words = title.split(/\\s+/);\n if (words.length >= 2 && words.length <= 5) {\n const acronym = words\n .map(w => w[0])\n .filter(c => c && /[A-Z]/.test(c))\n .join('');\n if (acronym.length >= 2 && !aliases.includes(acronym)) {\n aliases.push(acronym);\n }\n }\n\n return aliases.slice(0, 5); // Limit to 5 aliases\n }\n\n /**\n * Extract hashtags from content\n */\n private extractHashtags(content: string): string[] {\n const tags = new Set<string>();\n\n // Match #tag patterns (but not #headings)\n const regex = /(?:^|\\s)#([a-zA-Z][a-zA-Z0-9_-]*)/g;\n let match;\n\n while ((match = regex.exec(content)) !== null) {\n const tag = match[1].toLowerCase();\n if (tag.length > 1 && tag.length < 30) {\n tags.add(tag);\n }\n }\n\n // Also infer tags from content keywords\n const inferredTags = this.inferTagsFromContent(content);\n for (const tag of inferredTags) {\n tags.add(tag);\n }\n\n return Array.from(tags).slice(0, 10); // Limit to 10 tags\n }\n\n /**\n * Infer tags from content analysis\n */\n private inferTagsFromContent(content: string): string[] {\n const tags: string[] = [];\n const lowerContent = content.toLowerCase();\n\n // Common topic patterns\n const patterns: Array<{ pattern: RegExp; tag: string }> = [\n { pattern: /\\b(api|endpoint|rest|graphql)\\b/i, tag: 'api' },\n { pattern: /\\b(database|sql|postgres|mysql|mongo)\\b/i, tag: 'database' },\n { pattern: /\\b(test|testing|jest|vitest|unittest)\\b/i, tag: 'testing' },\n { pattern: /\\b(docker|container|kubernetes|k8s)\\b/i, tag: 'devops' },\n { pattern: /\\b(security|auth|authentication|oauth)\\b/i, tag: 'security' },\n { pattern: /\\b(performance|optimization|cache)\\b/i, tag: 'performance' },\n { pattern: /\\b(react|vue|angular|frontend)\\b/i, tag: 'frontend' },\n { pattern: /\\b(node|express|backend|server)\\b/i, tag: 'backend' },\n { pattern: /\\b(typescript|javascript|python|rust)\\b/i, tag: 'programming' },\n { pattern: /\\b(guide|tutorial|howto|how-to)\\b/i, tag: 'guide' },\n { pattern: /\\b(architecture|design|pattern)\\b/i, tag: 'architecture' },\n { pattern: /\\b(config|configuration|setup)\\b/i, tag: 'configuration' },\n ];\n\n for (const { pattern, tag } of patterns) {\n if (pattern.test(lowerContent)) {\n tags.push(tag);\n }\n }\n\n return tags;\n }\n\n /**\n * Infer document type from content\n */\n private inferType(content: string, filename: string): string {\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check filename patterns\n if (lowerFilename.includes('readme') || lowerFilename.includes('index')) {\n return 'guide';\n }\n if (lowerFilename.includes('api') || lowerFilename.includes('endpoint')) {\n return 'technical';\n }\n if (lowerFilename.includes('standard') || lowerFilename.includes('convention')) {\n return 'standard';\n }\n\n // Check content patterns\n if (/```[a-z]+\\n/i.test(content)) {\n return 'technical';\n }\n if (/##\\s*installation|##\\s*getting started|##\\s*usage/i.test(content)) {\n return 'guide';\n }\n if (/##\\s*api|##\\s*endpoints|##\\s*methods/i.test(content)) {\n return 'technical';\n }\n if (/##\\s*overview|##\\s*introduction|##\\s*background/i.test(content)) {\n return 'concept';\n }\n\n return 'concept'; // Default type\n }\n\n /**\n * Extract wiki-links from content\n */\n private extractWikiLinks(content: string): string[] {\n const links = new Set<string>();\n const regex = /\\[\\[([^\\]|#]+)(?:#[^\\]|]*)?(?:\\|[^\\]]+)?\\]\\]/g;\n let match;\n\n while ((match = regex.exec(content)) !== null) {\n const link = match[1].trim();\n if (link) {\n links.add(link);\n }\n }\n\n return Array.from(links);\n }\n\n /**\n * Extract first paragraph as description\n */\n private extractDescription(content: string): string | undefined {\n // Skip headings and find first paragraph\n const lines = content.split('\\n');\n let foundContent = false;\n const paragraphLines: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Skip empty lines at start\n if (!foundContent && !trimmed) continue;\n\n // Skip headings\n if (trimmed.startsWith('#')) {\n if (foundContent) break;\n continue;\n }\n\n // Skip code blocks\n if (trimmed.startsWith('```')) continue;\n\n // Found content\n if (trimmed) {\n foundContent = true;\n paragraphLines.push(trimmed);\n } else if (foundContent) {\n // End of paragraph\n break;\n }\n }\n\n const description = paragraphLines.join(' ').slice(0, 200);\n return description || undefined;\n }\n\n /**\n * Build markdown content with frontmatter\n */\n private buildMarkdownWithFrontmatter(fm: FrontmatterTemplate, content: string): string {\n // Clean frontmatter - remove empty arrays and undefined values\n const cleanFm: Record<string, unknown> = {};\n\n if (fm.title) cleanFm.title = fm.title;\n if (fm.description) cleanFm.description = fm.description;\n if (fm.type) cleanFm.type = fm.type;\n if (fm.status) cleanFm.status = fm.status;\n if (fm.created) cleanFm.created = fm.created;\n if (fm.modified) cleanFm.modified = fm.modified;\n if (fm.tags && fm.tags.length > 0) cleanFm.tags = fm.tags;\n if (fm.aliases && fm.aliases.length > 0) cleanFm.aliases = fm.aliases;\n if (fm.links && fm.links.length > 0) cleanFm.links = fm.links;\n\n const yamlStr = yaml.dump(cleanFm, {\n lineWidth: -1,\n quotingType: '\"',\n forceQuotes: false,\n });\n\n return `---\\n${yamlStr}---\\n\\n${content.trim()}\\n`;\n }\n\n /**\n * Generate report\n */\n generateReport(result: EnrichResult): string {\n const lines: string[] = [];\n\n lines.push('# Frontmatter Enrichment Report\\n');\n lines.push(`Generated: ${new Date().toISOString()}\\n`);\n lines.push('');\n\n lines.push('## Summary\\n');\n lines.push(`| Metric | Value |`);\n lines.push(`|--------|-------|`);\n lines.push(`| Total Files | ${result.statistics.totalFiles} |`);\n lines.push(`| Enriched | ${result.statistics.enrichedCount} |`);\n lines.push(`| Skipped | ${result.statistics.skippedCount} |`);\n lines.push(`| Errors | ${result.statistics.errorCount} |`);\n lines.push(`| Tags Added | ${result.statistics.tagsAdded} |`);\n lines.push(`| Aliases Added | ${result.statistics.aliasesAdded} |`);\n lines.push(`| Links Extracted | ${result.statistics.linksExtracted} |`);\n lines.push('');\n\n if (result.enriched.length > 0) {\n lines.push('## Enriched Files\\n');\n for (const { file, added } of result.enriched.slice(0, 50)) {\n if (added.length > 0) {\n lines.push(`- \\`${file}\\`: ${added.join(', ')}`);\n }\n }\n if (result.enriched.length > 50) {\n lines.push(`\\n*... and ${result.enriched.length - 50} more*`);\n }\n lines.push('');\n }\n\n if (result.errors.length > 0) {\n lines.push('## Errors\\n');\n for (const { file, error } of result.errors) {\n lines.push(`- \\`${file}\\`: ${error}`);\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n }\n}\n\n// ============================================================================\n// CLI Command\n// ============================================================================\n\nexport function createAddFrontmatterCommand(): Command {\n const command = new Command('add-frontmatter')\n .description('Add/enrich YAML frontmatter in markdown files')\n .argument('<vault-path>', 'Path to Obsidian vault or docs directory')\n .option('--overwrite', 'Overwrite existing frontmatter fields')\n .option('--tags', 'Auto-generate tags from content (default: true)')\n .option('--no-tags', 'Skip tag generation')\n .option('--aliases', 'Generate aliases from title (default: true)')\n .option('--no-aliases', 'Skip alias generation')\n .option('--links', 'Extract wiki-links to frontmatter (default: true)')\n .option('--no-links', 'Skip link extraction')\n .option('--dry-run', 'Preview changes without writing files')\n .option('-o, --output <file>', 'Output file for report')\n .option('--json', 'Output as JSON')\n .option('-v, --verbose', 'Show detailed output')\n .action(async (vaultPath: string, options: EnrichOptions) => {\n const enricher = new FrontmatterEnricher();\n\n const mode = options.dryRun ? ' (dry run)' : '';\n console.log(chalk.cyan(`\\nEnriching frontmatter${mode}...\\n`));\n\n try {\n const result = await enricher.enrichVault(vaultPath, options);\n\n if (options.json) {\n if (options.output) {\n await writeFile(options.output, JSON.stringify(result, null, 2));\n console.log(chalk.green(`Results written to: ${options.output}`));\n } else {\n console.log(JSON.stringify(result, null, 2));\n }\n } else {\n // Display summary\n console.log(chalk.bold('Summary:'));\n console.log(chalk.white(` Total Files: ${result.statistics.totalFiles}`));\n console.log(chalk.green(` Enriched: ${result.statistics.enrichedCount}`));\n console.log(chalk.gray(` Skipped: ${result.statistics.skippedCount}`));\n if (result.statistics.errorCount > 0) {\n console.log(chalk.red(` Errors: ${result.statistics.errorCount}`));\n }\n console.log('');\n\n console.log(chalk.bold('Additions:'));\n console.log(chalk.white(` Tags Added: ${result.statistics.tagsAdded}`));\n console.log(chalk.white(` Aliases Added: ${result.statistics.aliasesAdded}`));\n console.log(chalk.white(` Links Extracted: ${result.statistics.linksExtracted}`));\n console.log('');\n\n if (options.verbose && result.enriched.length > 0) {\n console.log(chalk.bold('Enriched Files:'));\n for (const { file, added } of result.enriched.slice(0, 20)) {\n if (added.length > 0) {\n console.log(chalk.green(` ${file}`));\n console.log(chalk.gray(` Added: ${added.join(', ')}`));\n }\n }\n if (result.enriched.length > 20) {\n console.log(chalk.gray(` ... and ${result.enriched.length - 20} more`));\n }\n console.log('');\n }\n\n if (result.errors.length > 0) {\n console.log(chalk.bold(chalk.red('Errors:')));\n for (const { file, error } of result.errors.slice(0, 10)) {\n console.log(chalk.red(` ${file}: ${error}`));\n }\n if (result.errors.length > 10) {\n console.log(chalk.gray(` ... and ${result.errors.length - 10} more`));\n }\n console.log('');\n }\n\n // Write report if output specified\n if (options.output && !options.json) {\n const report = enricher.generateReport(result);\n await writeFile(options.output, report);\n console.log(chalk.green(`Report written to: ${options.output}`));\n }\n\n // Show dry-run notice\n if (options.dryRun) {\n console.log(chalk.yellow('Dry run complete - no files were modified.'));\n console.log(chalk.gray('Remove --dry-run to apply changes.\\n'));\n }\n }\n } catch (error) {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');\n process.exit(1);\n }\n });\n\n return command;\n}\n\nexport default createAddFrontmatterCommand;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"add-frontmatter.js","sources":["../../../../src/cli/commands/hive-mind/add-frontmatter.ts"],"sourcesContent":["/**\n * Hive Mind - Frontmatter Enricher\n *\n * Adds or enriches YAML frontmatter in markdown files to improve discoverability\n * and enable better linking between documents.\n *\n * SPEC-003: Hive Mind Reconnection Tools\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport * as path from 'path';\nimport { readFile, writeFile } from 'fs/promises';\nimport fg from 'fast-glob';\nimport matter from 'gray-matter';\nimport * as yaml from 'js-yaml';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface EnrichOptions {\n overwrite?: boolean;\n tags?: boolean;\n aliases?: boolean;\n links?: boolean;\n dryRun?: boolean;\n output?: string;\n json?: boolean;\n verbose?: boolean;\n}\n\nexport interface FrontmatterTemplate {\n title: string;\n created: string;\n modified: string;\n tags: string[];\n aliases: string[];\n links: string[];\n type: string;\n status: string;\n description?: string;\n}\n\nexport interface EnrichedFile {\n file: string;\n added: string[];\n updated: string[];\n frontmatter: FrontmatterTemplate;\n}\n\nexport interface EnrichResult {\n enriched: EnrichedFile[];\n skipped: string[];\n errors: Array<{ file: string; error: string }>;\n statistics: {\n totalFiles: number;\n enrichedCount: number;\n skippedCount: number;\n errorCount: number;\n tagsAdded: number;\n aliasesAdded: number;\n linksExtracted: number;\n };\n}\n\n// ============================================================================\n// Frontmatter Enricher Class\n// ============================================================================\n\nexport class FrontmatterEnricher {\n /**\n * Enrich frontmatter for all files in a vault\n */\n async enrichVault(vaultPath: string, options: EnrichOptions = {}): Promise<EnrichResult> {\n const resolvedPath = path.resolve(vaultPath);\n\n // Find all markdown files\n const files = await fg('**/*.md', {\n cwd: resolvedPath,\n ignore: ['node_modules/**', '.git/**', 'dist/**'],\n absolute: false,\n });\n\n if (files.length === 0) {\n throw new Error(`No markdown files found in: ${resolvedPath}`);\n }\n\n const enriched: EnrichedFile[] = [];\n const skipped: string[] = [];\n const errors: Array<{ file: string; error: string }> = [];\n let tagsAdded = 0;\n let aliasesAdded = 0;\n let linksExtracted = 0;\n\n for (const file of files) {\n try {\n const result = await this.enrichFile(\n path.join(resolvedPath, file),\n file,\n options\n );\n\n if (result) {\n enriched.push(result);\n tagsAdded += result.frontmatter.tags.length;\n aliasesAdded += result.frontmatter.aliases.length;\n linksExtracted += result.frontmatter.links.length;\n } else {\n skipped.push(file);\n }\n } catch (error) {\n errors.push({\n file,\n error: error instanceof Error ? error.message : 'Unknown error',\n });\n }\n }\n\n return {\n enriched,\n skipped,\n errors,\n statistics: {\n totalFiles: files.length,\n enrichedCount: enriched.length,\n skippedCount: skipped.length,\n errorCount: errors.length,\n tagsAdded,\n aliasesAdded,\n linksExtracted,\n },\n };\n }\n\n /**\n * Enrich a single file's frontmatter\n */\n async enrichFile(\n filePath: string,\n relativePath: string,\n options: EnrichOptions\n ): Promise<EnrichedFile | null> {\n const content = await readFile(filePath, 'utf-8');\n const parsed = matter(content);\n\n const existingFm = parsed.data as Partial<FrontmatterTemplate>;\n const hasExistingFm = Object.keys(existingFm).length > 0;\n\n // Skip if has frontmatter and not overwriting\n if (hasExistingFm && !options.overwrite) {\n // Check if we need to add anything\n const needsTags = options.tags && (!existingFm.tags || existingFm.tags.length === 0);\n const needsAliases = options.aliases && (!existingFm.aliases || existingFm.aliases.length === 0);\n const needsLinks = options.links && (!existingFm.links || existingFm.links.length === 0);\n\n if (!needsTags && !needsAliases && !needsLinks && existingFm.title) {\n return null;\n }\n }\n\n // Extract metadata from content\n const metadata = this.extractMetadata(parsed.content, relativePath);\n\n // Build new frontmatter\n const added: string[] = [];\n const updated: string[] = [];\n const newFm: FrontmatterTemplate = {\n title: existingFm.title || metadata.title,\n created: existingFm.created || metadata.created,\n modified: new Date().toISOString().split('T')[0],\n tags: [],\n aliases: [],\n links: [],\n type: existingFm.type || metadata.type,\n status: existingFm.status || 'active',\n };\n\n // Track what was added/updated\n if (!existingFm.title) added.push('title');\n if (!existingFm.created) added.push('created');\n if (!existingFm.type) added.push('type');\n if (!existingFm.status) added.push('status');\n updated.push('modified');\n\n // Handle tags\n if (options.tags !== false) {\n const existingTags = existingFm.tags || [];\n const extractedTags = metadata.tags;\n const mergedTags = [...new Set([...existingTags, ...extractedTags])];\n newFm.tags = mergedTags;\n\n const newTagCount = mergedTags.length - existingTags.length;\n if (newTagCount > 0) added.push(`${newTagCount} tags`);\n }\n\n // Handle aliases\n if (options.aliases !== false) {\n const existingAliases = existingFm.aliases || [];\n const extractedAliases = metadata.aliases;\n const mergedAliases = [...new Set([...existingAliases, ...extractedAliases])];\n newFm.aliases = mergedAliases;\n\n const newAliasCount = mergedAliases.length - existingAliases.length;\n if (newAliasCount > 0) added.push(`${newAliasCount} aliases`);\n }\n\n // Handle links\n if (options.links !== false) {\n const existingLinks = existingFm.links || [];\n const extractedLinks = metadata.links;\n const mergedLinks = [...new Set([...existingLinks, ...extractedLinks])];\n newFm.links = mergedLinks;\n\n const newLinkCount = mergedLinks.length - existingLinks.length;\n if (newLinkCount > 0) added.push(`${newLinkCount} links`);\n }\n\n // Add description if missing\n if (!existingFm.description && metadata.description) {\n newFm.description = metadata.description;\n added.push('description');\n }\n\n // Write file if not dry run\n if (!options.dryRun) {\n const newContent = this.buildMarkdownWithFrontmatter(newFm, parsed.content);\n await writeFile(filePath, newContent);\n }\n\n return {\n file: relativePath,\n added,\n updated,\n frontmatter: newFm,\n };\n }\n\n /**\n * Extract metadata from content and filename\n */\n private extractMetadata(content: string, filename: string): {\n title: string;\n created: string;\n type: string;\n tags: string[];\n aliases: string[];\n links: string[];\n description?: string;\n } {\n const basename = path.basename(filename, '.md');\n\n // Extract title from first heading or filename\n let title = basename;\n const headingMatch = content.match(/^#\\s+(.+)$/m);\n if (headingMatch) {\n title = headingMatch[1].trim();\n }\n\n // Generate aliases from title variations\n const aliases = this.generateAliases(title, basename);\n\n // Extract tags from hashtags in content\n const tags = this.extractHashtags(content);\n\n // Infer type from content and filename\n const type = this.inferType(content, basename);\n\n // Extract wiki-links\n const links = this.extractWikiLinks(content);\n\n // Extract first paragraph as description\n const description = this.extractDescription(content);\n\n // Use current date for created\n const created = new Date().toISOString().split('T')[0];\n\n return { title, created, type, tags, aliases, links, description };\n }\n\n /**\n * Generate aliases from title\n */\n private generateAliases(title: string, filename: string): string[] {\n const aliases: string[] = [];\n\n // Add filename if different from title\n const cleanFilename = filename.replace(/-/g, ' ').toLowerCase();\n const cleanTitle = title.toLowerCase();\n if (cleanFilename !== cleanTitle) {\n // Convert filename to readable form\n const readableFilename = filename\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, c => c.toUpperCase());\n if (readableFilename !== title) {\n aliases.push(readableFilename);\n }\n }\n\n // Add lowercase version if title has caps\n if (title !== title.toLowerCase() && !aliases.includes(title.toLowerCase())) {\n aliases.push(title.toLowerCase());\n }\n\n // Add acronym if title has multiple words\n const words = title.split(/\\s+/);\n if (words.length >= 2 && words.length <= 5) {\n const acronym = words\n .map(w => w[0])\n .filter(c => c && /[A-Z]/.test(c))\n .join('');\n if (acronym.length >= 2 && !aliases.includes(acronym)) {\n aliases.push(acronym);\n }\n }\n\n return aliases.slice(0, 5); // Limit to 5 aliases\n }\n\n /**\n * Extract hashtags from content\n */\n private extractHashtags(content: string): string[] {\n const tags = new Set<string>();\n\n // Match #tag patterns (but not #headings)\n const regex = /(?:^|\\s)#([a-zA-Z][a-zA-Z0-9_-]*)/g;\n let match;\n\n while ((match = regex.exec(content)) !== null) {\n const tag = match[1].toLowerCase();\n if (tag.length > 1 && tag.length < 30) {\n tags.add(tag);\n }\n }\n\n // Also infer tags from content keywords\n const inferredTags = this.inferTagsFromContent(content);\n for (const tag of inferredTags) {\n tags.add(tag);\n }\n\n return Array.from(tags).slice(0, 10); // Limit to 10 tags\n }\n\n /**\n * Infer tags from content analysis\n */\n private inferTagsFromContent(content: string): string[] {\n const tags: string[] = [];\n const lowerContent = content.toLowerCase();\n\n // Common topic patterns\n const patterns: Array<{ pattern: RegExp; tag: string }> = [\n { pattern: /\\b(api|endpoint|rest|graphql)\\b/i, tag: 'api' },\n { pattern: /\\b(database|sql|postgres|mysql|mongo)\\b/i, tag: 'database' },\n { pattern: /\\b(test|testing|jest|vitest|unittest)\\b/i, tag: 'testing' },\n { pattern: /\\b(docker|container|kubernetes|k8s)\\b/i, tag: 'devops' },\n { pattern: /\\b(security|auth|authentication|oauth)\\b/i, tag: 'security' },\n { pattern: /\\b(performance|optimization|cache)\\b/i, tag: 'performance' },\n { pattern: /\\b(react|vue|angular|frontend)\\b/i, tag: 'frontend' },\n { pattern: /\\b(node|express|backend|server)\\b/i, tag: 'backend' },\n { pattern: /\\b(typescript|javascript|python|rust)\\b/i, tag: 'programming' },\n { pattern: /\\b(guide|tutorial|howto|how-to)\\b/i, tag: 'guide' },\n { pattern: /\\b(architecture|design|pattern)\\b/i, tag: 'architecture' },\n { pattern: /\\b(config|configuration|setup)\\b/i, tag: 'configuration' },\n ];\n\n for (const { pattern, tag } of patterns) {\n if (pattern.test(lowerContent)) {\n tags.push(tag);\n }\n }\n\n return tags;\n }\n\n /**\n * Infer document type from content\n */\n private inferType(content: string, filename: string): string {\n const lowerContent = content.toLowerCase();\n const lowerFilename = filename.toLowerCase();\n\n // Check filename patterns\n if (lowerFilename.includes('readme') || lowerFilename.includes('index')) {\n return 'guide';\n }\n if (lowerFilename.includes('api') || lowerFilename.includes('endpoint')) {\n return 'technical';\n }\n if (lowerFilename.includes('standard') || lowerFilename.includes('convention')) {\n return 'standard';\n }\n\n // Check content patterns\n if (/```[a-z]+\\n/i.test(content)) {\n return 'technical';\n }\n if (/##\\s*installation|##\\s*getting started|##\\s*usage/i.test(content)) {\n return 'guide';\n }\n if (/##\\s*api|##\\s*endpoints|##\\s*methods/i.test(content)) {\n return 'technical';\n }\n if (/##\\s*overview|##\\s*introduction|##\\s*background/i.test(content)) {\n return 'concept';\n }\n\n return 'concept'; // Default type\n }\n\n /**\n * Extract wiki-links from content\n */\n private extractWikiLinks(content: string): string[] {\n const links = new Set<string>();\n const regex = /\\[\\[([^\\]|#]+)(?:#[^\\]|]*)?(?:\\|[^\\]]+)?\\]\\]/g;\n let match;\n\n while ((match = regex.exec(content)) !== null) {\n const link = match[1].trim();\n if (link) {\n links.add(link);\n }\n }\n\n return Array.from(links);\n }\n\n /**\n * Extract first paragraph as description\n */\n private extractDescription(content: string): string | undefined {\n // Skip headings and find first paragraph\n const lines = content.split('\\n');\n let foundContent = false;\n const paragraphLines: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n // Skip empty lines at start\n if (!foundContent && !trimmed) continue;\n\n // Skip headings\n if (trimmed.startsWith('#')) {\n if (foundContent) break;\n continue;\n }\n\n // Skip code blocks\n if (trimmed.startsWith('```')) continue;\n\n // Found content\n if (trimmed) {\n foundContent = true;\n paragraphLines.push(trimmed);\n } else if (foundContent) {\n // End of paragraph\n break;\n }\n }\n\n const description = paragraphLines.join(' ').slice(0, 200);\n return description || undefined;\n }\n\n /**\n * Build markdown content with frontmatter\n */\n private buildMarkdownWithFrontmatter(fm: FrontmatterTemplate, content: string): string {\n // Clean frontmatter - remove empty arrays and undefined values\n const cleanFm: Record<string, unknown> = {};\n\n if (fm.title) cleanFm.title = fm.title;\n if (fm.description) cleanFm.description = fm.description;\n if (fm.type) cleanFm.type = fm.type;\n if (fm.status) cleanFm.status = fm.status;\n if (fm.created) cleanFm.created = fm.created;\n if (fm.modified) cleanFm.modified = fm.modified;\n if (fm.tags && fm.tags.length > 0) cleanFm.tags = fm.tags;\n if (fm.aliases && fm.aliases.length > 0) cleanFm.aliases = fm.aliases;\n if (fm.links && fm.links.length > 0) cleanFm.links = fm.links;\n\n const yamlStr = yaml.dump(cleanFm, {\n lineWidth: -1,\n quotingType: '\"',\n forceQuotes: false,\n });\n\n return `---\\n${yamlStr}---\\n\\n${content.trim()}\\n`;\n }\n\n /**\n * Generate report\n */\n generateReport(result: EnrichResult): string {\n const lines: string[] = [];\n\n lines.push('# Frontmatter Enrichment Report\\n');\n lines.push(`Generated: ${new Date().toISOString()}\\n`);\n lines.push('');\n\n lines.push('## Summary\\n');\n lines.push(`| Metric | Value |`);\n lines.push(`|--------|-------|`);\n lines.push(`| Total Files | ${result.statistics.totalFiles} |`);\n lines.push(`| Enriched | ${result.statistics.enrichedCount} |`);\n lines.push(`| Skipped | ${result.statistics.skippedCount} |`);\n lines.push(`| Errors | ${result.statistics.errorCount} |`);\n lines.push(`| Tags Added | ${result.statistics.tagsAdded} |`);\n lines.push(`| Aliases Added | ${result.statistics.aliasesAdded} |`);\n lines.push(`| Links Extracted | ${result.statistics.linksExtracted} |`);\n lines.push('');\n\n if (result.enriched.length > 0) {\n lines.push('## Enriched Files\\n');\n for (const { file, added } of result.enriched.slice(0, 50)) {\n if (added.length > 0) {\n lines.push(`- \\`${file}\\`: ${added.join(', ')}`);\n }\n }\n if (result.enriched.length > 50) {\n lines.push(`\\n*... and ${result.enriched.length - 50} more*`);\n }\n lines.push('');\n }\n\n if (result.errors.length > 0) {\n lines.push('## Errors\\n');\n for (const { file, error } of result.errors) {\n lines.push(`- \\`${file}\\`: ${error}`);\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n }\n}\n\n// ============================================================================\n// CLI Command\n// ============================================================================\n\nexport function createAddFrontmatterCommand(): Command {\n const command = new Command('add-frontmatter')\n .description('Add/enrich YAML frontmatter in markdown files')\n .argument('<vault-path>', 'Path to Obsidian vault or docs directory')\n .option('--overwrite', 'Overwrite existing frontmatter fields')\n .option('--tags', 'Auto-generate tags from content (default: true)')\n .option('--no-tags', 'Skip tag generation')\n .option('--aliases', 'Generate aliases from title (default: true)')\n .option('--no-aliases', 'Skip alias generation')\n .option('--links', 'Extract wiki-links to frontmatter (default: true)')\n .option('--no-links', 'Skip link extraction')\n .option('--dry-run', 'Preview changes without writing files')\n .option('-o, --output <file>', 'Output file for report')\n .option('--json', 'Output as JSON')\n .option('-v, --verbose', 'Show detailed output')\n .action(async (vaultPath: string, options: EnrichOptions) => {\n const enricher = new FrontmatterEnricher();\n\n const mode = options.dryRun ? ' (dry run)' : '';\n console.log(chalk.cyan(`\\nEnriching frontmatter${mode}...\\n`));\n\n try {\n const result = await enricher.enrichVault(vaultPath, options);\n\n if (options.json) {\n if (options.output) {\n await writeFile(options.output, JSON.stringify(result, null, 2));\n console.log(chalk.green(`Results written to: ${options.output}`));\n } else {\n console.log(JSON.stringify(result, null, 2));\n }\n } else {\n // Display summary\n console.log(chalk.bold('Summary:'));\n console.log(chalk.white(` Total Files: ${result.statistics.totalFiles}`));\n console.log(chalk.green(` Enriched: ${result.statistics.enrichedCount}`));\n console.log(chalk.gray(` Skipped: ${result.statistics.skippedCount}`));\n if (result.statistics.errorCount > 0) {\n console.log(chalk.red(` Errors: ${result.statistics.errorCount}`));\n }\n console.log('');\n\n console.log(chalk.bold('Additions:'));\n console.log(chalk.white(` Tags Added: ${result.statistics.tagsAdded}`));\n console.log(chalk.white(` Aliases Added: ${result.statistics.aliasesAdded}`));\n console.log(chalk.white(` Links Extracted: ${result.statistics.linksExtracted}`));\n console.log('');\n\n if (options.verbose && result.enriched.length > 0) {\n console.log(chalk.bold('Enriched Files:'));\n for (const { file, added } of result.enriched.slice(0, 20)) {\n if (added.length > 0) {\n console.log(chalk.green(` ${file}`));\n console.log(chalk.gray(` Added: ${added.join(', ')}`));\n }\n }\n if (result.enriched.length > 20) {\n console.log(chalk.gray(` ... and ${result.enriched.length - 20} more`));\n }\n console.log('');\n }\n\n if (result.errors.length > 0) {\n console.log(chalk.bold(chalk.red('Errors:')));\n for (const { file, error } of result.errors.slice(0, 10)) {\n console.log(chalk.red(` ${file}: ${error}`));\n }\n if (result.errors.length > 10) {\n console.log(chalk.gray(` ... and ${result.errors.length - 10} more`));\n }\n console.log('');\n }\n\n // Write report if output specified\n if (options.output && !options.json) {\n const report = enricher.generateReport(result);\n await writeFile(options.output, report);\n console.log(chalk.green(`Report written to: ${options.output}`));\n }\n\n // Show dry-run notice\n if (options.dryRun) {\n console.log(chalk.yellow('Dry run complete - no files were modified.'));\n console.log(chalk.gray('Remove --dry-run to apply changes.\\n'));\n }\n }\n } catch (error) {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');\n process.exit(1);\n }\n });\n\n return command;\n}\n\nexport default createAddFrontmatterCommand;\n"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { Command } from "commander";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { writeFile, access, readFile } from "fs/promises";
|
|
5
|
-
import
|
|
5
|
+
import fg from "fast-glob";
|
|
6
6
|
import matter from "gray-matter";
|
|
7
7
|
class LinkAnalyzer {
|
|
8
8
|
fileMap = /* @__PURE__ */ new Map();
|
|
@@ -17,7 +17,7 @@ class LinkAnalyzer {
|
|
|
17
17
|
} catch {
|
|
18
18
|
throw new Error(`Vault path does not exist: ${resolvedPath}`);
|
|
19
19
|
}
|
|
20
|
-
const files = await
|
|
20
|
+
const files = await fg("**/*.md", {
|
|
21
21
|
cwd: resolvedPath,
|
|
22
22
|
ignore: ["node_modules/**", ".git/**", "dist/**"],
|
|
23
23
|
absolute: false
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyze-links.js","sources":["../../../../src/cli/commands/hive-mind/analyze-links.ts"],"sourcesContent":["/**\n * Hive Mind - Link Analyzer\n *\n * Analyzes wiki-links and markdown links in a vault to build an adjacency list\n * and identify orphan files.\n *\n * SPEC-003: Hive Mind Reconnection Tools\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport * as path from 'path';\nimport { readFile, access, writeFile } from 'fs/promises';\nimport { glob } from 'fast-glob';\nimport matter from 'gray-matter';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface AnalyzeOptions {\n output?: string;\n json?: boolean;\n verbose?: boolean;\n includeContent?: boolean;\n}\n\nexport interface BrokenLink {\n source: string;\n target: string;\n type: 'wikilink' | 'markdown';\n lineNumber?: number;\n}\n\nexport interface LinkAnalysisResult {\n totalFiles: number;\n filesWithLinks: number;\n orphanFiles: string[];\n orphanRate: number;\n linkDensity: number;\n adjacencyList: Map<string, string[]>;\n brokenLinks: BrokenLink[];\n statistics: {\n totalLinks: number;\n wikiLinks: number;\n markdownLinks: number;\n averageLinksPerFile: number;\n maxLinksInFile: number;\n maxLinksFile: string;\n isolatedClusters: number;\n };\n}\n\n// ============================================================================\n// Link Analyzer Class\n// ============================================================================\n\nexport class LinkAnalyzer {\n private fileMap: Map<string, string> = new Map();\n private allFiles: Set<string> = new Set();\n\n /**\n * Analyze a vault for links and build adjacency list\n */\n async analyzeVault(vaultPath: string, options: AnalyzeOptions = {}): Promise<LinkAnalysisResult> {\n const resolvedPath = path.resolve(vaultPath);\n\n // Check if path exists\n try {\n await access(resolvedPath);\n } catch {\n throw new Error(`Vault path does not exist: ${resolvedPath}`);\n }\n\n // Find all markdown files\n const files = await glob('**/*.md', {\n cwd: resolvedPath,\n ignore: ['node_modules/**', '.git/**', 'dist/**'],\n absolute: false,\n });\n\n if (files.length === 0) {\n throw new Error(`No markdown files found in: ${resolvedPath}`);\n }\n\n // Build file map for resolution (filename without extension -> full path)\n this.buildFileMap(files);\n\n const adjacencyList = new Map<string, string[]>();\n const brokenLinks: BrokenLink[] = [];\n const incomingLinks = new Map<string, Set<string>>();\n let totalWikiLinks = 0;\n let totalMarkdownLinks = 0;\n let maxLinksInFile = 0;\n let maxLinksFile = '';\n\n // Initialize adjacency list and incoming links\n for (const file of files) {\n adjacencyList.set(file, []);\n incomingLinks.set(file, new Set());\n }\n\n // Process each file\n for (const file of files) {\n const filePath = path.join(resolvedPath, file);\n const content = await readFile(filePath, 'utf-8');\n\n // Parse frontmatter to skip it for link counting\n const { content: bodyContent } = matter(content);\n\n // Extract links\n const wikiLinks = this.parseWikiLinks(bodyContent);\n const markdownLinks = this.parseMarkdownLinks(bodyContent);\n\n totalWikiLinks += wikiLinks.length;\n totalMarkdownLinks += markdownLinks.length;\n\n const fileLinks = adjacencyList.get(file) || [];\n const totalLinksInThisFile = wikiLinks.length + markdownLinks.length;\n\n if (totalLinksInThisFile > maxLinksInFile) {\n maxLinksInFile = totalLinksInThisFile;\n maxLinksFile = file;\n }\n\n // Process wiki links\n for (const link of wikiLinks) {\n const resolved = this.resolveLink(link, file);\n if (resolved && this.allFiles.has(resolved)) {\n fileLinks.push(resolved);\n incomingLinks.get(resolved)?.add(file);\n } else {\n brokenLinks.push({\n source: file,\n target: link,\n type: 'wikilink',\n });\n }\n }\n\n // Process markdown links\n for (const link of markdownLinks) {\n const resolved = this.resolveLink(link, file);\n if (resolved && this.allFiles.has(resolved)) {\n fileLinks.push(resolved);\n incomingLinks.get(resolved)?.add(file);\n } else if (!this.isExternalLink(link)) {\n brokenLinks.push({\n source: file,\n target: link,\n type: 'markdown',\n });\n }\n }\n\n adjacencyList.set(file, [...new Set(fileLinks)]); // Dedupe\n }\n\n // Find orphan files (no incoming AND no outgoing links)\n const orphanFiles: string[] = [];\n for (const file of files) {\n const outgoing = adjacencyList.get(file) || [];\n const incoming = incomingLinks.get(file) || new Set();\n if (outgoing.length === 0 && incoming.size === 0) {\n orphanFiles.push(file);\n }\n }\n\n // Calculate metrics\n const totalFiles = files.length;\n const filesWithLinks = files.filter(f => {\n const outgoing = adjacencyList.get(f) || [];\n const incoming = incomingLinks.get(f) || new Set();\n return outgoing.length > 0 || incoming.size > 0;\n }).length;\n\n const orphanRate = totalFiles > 0 ? (orphanFiles.length / totalFiles) * 100 : 0;\n const totalLinks = totalWikiLinks + totalMarkdownLinks;\n const linkDensity = totalFiles > 0 ? totalLinks / totalFiles : 0;\n const averageLinksPerFile = totalFiles > 0 ? totalLinks / totalFiles : 0;\n\n // Calculate isolated clusters using union-find\n const isolatedClusters = this.countClusters(adjacencyList, files);\n\n return {\n totalFiles,\n filesWithLinks,\n orphanFiles,\n orphanRate: Math.round(orphanRate * 100) / 100,\n linkDensity: Math.round(linkDensity * 100) / 100,\n adjacencyList,\n brokenLinks,\n statistics: {\n totalLinks,\n wikiLinks: totalWikiLinks,\n markdownLinks: totalMarkdownLinks,\n averageLinksPerFile: Math.round(averageLinksPerFile * 100) / 100,\n maxLinksInFile,\n maxLinksFile,\n isolatedClusters,\n },\n };\n }\n\n /**\n * Build a map from filename (without extension) to full path\n */\n private buildFileMap(files: string[]): void {\n this.fileMap.clear();\n this.allFiles.clear();\n\n for (const file of files) {\n this.allFiles.add(file);\n\n // Map by filename without extension\n const basename = path.basename(file, '.md');\n if (!this.fileMap.has(basename)) {\n this.fileMap.set(basename, file);\n }\n\n // Also map by full path without extension\n const pathWithoutExt = file.replace(/\\.md$/, '');\n this.fileMap.set(pathWithoutExt, file);\n }\n }\n\n /**\n * Parse [[wiki-links]] from content\n * Handles: [[link]], [[link|alias]], [[folder/link]], [[link#heading]]\n */\n parseWikiLinks(content: string): string[] {\n const links: string[] = [];\n // Match [[link]] and [[link|alias]] patterns\n const regex = /\\[\\[([^\\]|#]+)(?:#[^\\]|]*)?(?:\\|[^\\]]+)?\\]\\]/g;\n let match;\n\n while ((match = regex.exec(content)) !== null) {\n const link = match[1].trim();\n if (link) {\n links.push(link);\n }\n }\n\n return links;\n }\n\n /**\n * Parse [markdown](links) from content\n * Handles: [text](link.md), [text](./link.md), [text](../folder/link.md)\n */\n parseMarkdownLinks(content: string): string[] {\n const links: string[] = [];\n // Match [text](link) patterns, excluding images  and external links\n const regex = /(?<!!)\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\n let match;\n\n while ((match = regex.exec(content)) !== null) {\n const link = match[2].trim();\n // Skip external links, anchors, and non-md files\n if (link && !this.isExternalLink(link) && !link.startsWith('#')) {\n links.push(link);\n }\n }\n\n return links;\n }\n\n /**\n * Check if a link is external (http, https, etc.)\n */\n private isExternalLink(link: string): boolean {\n return /^(https?:|mailto:|tel:|ftp:)/i.test(link);\n }\n\n /**\n * Resolve a link to a file in the vault\n */\n private resolveLink(link: string, sourceFile: string): string | null {\n // Remove .md extension if present\n let cleanLink = link.replace(/\\.md$/, '');\n\n // Remove leading ./ or ../ and resolve relative paths\n if (cleanLink.startsWith('./') || cleanLink.startsWith('../')) {\n const sourceDir = path.dirname(sourceFile);\n const resolved = path.normalize(path.join(sourceDir, cleanLink));\n cleanLink = resolved;\n }\n\n // Try to find in file map\n const found = this.fileMap.get(cleanLink);\n if (found) return found;\n\n // Try with .md extension\n const withMd = cleanLink + '.md';\n if (this.allFiles.has(withMd)) return withMd;\n\n // Try just the basename (for simple wiki-links)\n const basename = path.basename(cleanLink);\n const byBasename = this.fileMap.get(basename);\n if (byBasename) return byBasename;\n\n return null;\n }\n\n /**\n * Count isolated clusters using union-find algorithm\n */\n private countClusters(adjacencyList: Map<string, string[]>, files: string[]): number {\n const parent = new Map<string, string>();\n const rank = new Map<string, number>();\n\n // Initialize each file as its own cluster\n for (const file of files) {\n parent.set(file, file);\n rank.set(file, 0);\n }\n\n const find = (x: string): string => {\n if (parent.get(x) !== x) {\n parent.set(x, find(parent.get(x)!));\n }\n return parent.get(x)!;\n };\n\n const union = (x: string, y: string): void => {\n const rootX = find(x);\n const rootY = find(y);\n if (rootX !== rootY) {\n const rankX = rank.get(rootX) || 0;\n const rankY = rank.get(rootY) || 0;\n if (rankX < rankY) {\n parent.set(rootX, rootY);\n } else if (rankX > rankY) {\n parent.set(rootY, rootX);\n } else {\n parent.set(rootY, rootX);\n rank.set(rootX, rankX + 1);\n }\n }\n };\n\n // Union connected files\n for (const [source, targets] of adjacencyList) {\n for (const target of targets) {\n if (parent.has(target)) {\n union(source, target);\n }\n }\n }\n\n // Count unique roots\n const roots = new Set<string>();\n for (const file of files) {\n roots.add(find(file));\n }\n\n return roots.size;\n }\n\n /**\n * Generate a detailed report\n */\n generateReport(result: LinkAnalysisResult): string {\n const lines: string[] = [];\n\n lines.push('# Link Analysis Report\\n');\n lines.push(`Generated: ${new Date().toISOString()}\\n`);\n\n lines.push('## Summary\\n');\n lines.push(`| Metric | Value |`);\n lines.push(`|--------|-------|`);\n lines.push(`| Total Files | ${result.totalFiles} |`);\n lines.push(`| Files with Links | ${result.filesWithLinks} |`);\n lines.push(`| Orphan Files | ${result.orphanFiles.length} |`);\n lines.push(`| Orphan Rate | ${result.orphanRate}% |`);\n lines.push(`| Link Density | ${result.linkDensity} |`);\n lines.push(`| Total Links | ${result.statistics.totalLinks} |`);\n lines.push(`| Wiki Links | ${result.statistics.wikiLinks} |`);\n lines.push(`| Markdown Links | ${result.statistics.markdownLinks} |`);\n lines.push(`| Avg Links/File | ${result.statistics.averageLinksPerFile} |`);\n lines.push(`| Max Links in File | ${result.statistics.maxLinksInFile} |`);\n lines.push(`| Most Linked File | ${result.statistics.maxLinksFile} |`);\n lines.push(`| Isolated Clusters | ${result.statistics.isolatedClusters} |`);\n lines.push('');\n\n if (result.orphanFiles.length > 0) {\n lines.push('## Orphan Files\\n');\n lines.push('Files with no incoming or outgoing links:\\n');\n for (const file of result.orphanFiles.slice(0, 50)) {\n lines.push(`- \\`${file}\\``);\n }\n if (result.orphanFiles.length > 50) {\n lines.push(`\\n... and ${result.orphanFiles.length - 50} more`);\n }\n lines.push('');\n }\n\n if (result.brokenLinks.length > 0) {\n lines.push('## Broken Links\\n');\n lines.push('Links that point to non-existent files:\\n');\n for (const broken of result.brokenLinks.slice(0, 50)) {\n lines.push(`- \\`${broken.source}\\` -> \\`${broken.target}\\` (${broken.type})`);\n }\n if (result.brokenLinks.length > 50) {\n lines.push(`\\n... and ${result.brokenLinks.length - 50} more`);\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n }\n}\n\n// ============================================================================\n// CLI Command\n// ============================================================================\n\nexport function createAnalyzeLinksCommand(): Command {\n const command = new Command('analyze-links')\n .description('Analyze wiki-links and markdown links in a vault')\n .argument('<vault-path>', 'Path to Obsidian vault or docs directory')\n .option('-o, --output <file>', 'Output file for results (JSON or Markdown)')\n .option('--json', 'Output as JSON')\n .option('-v, --verbose', 'Show detailed output')\n .action(async (vaultPath: string, options: AnalyzeOptions) => {\n const analyzer = new LinkAnalyzer();\n\n console.log(chalk.cyan('\\nAnalyzing links in vault...\\n'));\n\n try {\n const result = await analyzer.analyzeVault(vaultPath, options);\n\n if (options.json) {\n // Convert Map to Object for JSON serialization\n const jsonResult = {\n ...result,\n adjacencyList: Object.fromEntries(result.adjacencyList),\n };\n\n if (options.output) {\n await writeFile(options.output, JSON.stringify(jsonResult, null, 2));\n console.log(chalk.green(`Results written to: ${options.output}`));\n } else {\n console.log(JSON.stringify(jsonResult, null, 2));\n }\n } else {\n // Display summary\n console.log(chalk.bold('Summary:'));\n console.log(chalk.white(` Total Files: ${result.totalFiles}`));\n console.log(chalk.white(` Files with Links: ${result.filesWithLinks}`));\n console.log(chalk.white(` Orphan Files: ${chalk.yellow(result.orphanFiles.length.toString())}`));\n console.log(chalk.white(` Orphan Rate: ${chalk.yellow(result.orphanRate + '%')}`));\n console.log(chalk.white(` Link Density: ${result.linkDensity}`));\n console.log('');\n\n console.log(chalk.bold('Link Statistics:'));\n console.log(chalk.white(` Total Links: ${result.statistics.totalLinks}`));\n console.log(chalk.white(` Wiki Links: ${result.statistics.wikiLinks}`));\n console.log(chalk.white(` Markdown Links: ${result.statistics.markdownLinks}`));\n console.log(chalk.white(` Avg Links/File: ${result.statistics.averageLinksPerFile}`));\n console.log(chalk.white(` Isolated Clusters: ${result.statistics.isolatedClusters}`));\n console.log('');\n\n if (result.brokenLinks.length > 0) {\n console.log(chalk.bold(chalk.red(`Broken Links: ${result.brokenLinks.length}`)));\n if (options.verbose) {\n for (const broken of result.brokenLinks.slice(0, 10)) {\n console.log(chalk.red(` ${broken.source} -> ${broken.target}`));\n }\n if (result.brokenLinks.length > 10) {\n console.log(chalk.gray(` ... and ${result.brokenLinks.length - 10} more`));\n }\n }\n console.log('');\n }\n\n if (options.verbose && result.orphanFiles.length > 0) {\n console.log(chalk.bold(chalk.yellow('Orphan Files:')));\n for (const file of result.orphanFiles.slice(0, 10)) {\n console.log(chalk.yellow(` ${file}`));\n }\n if (result.orphanFiles.length > 10) {\n console.log(chalk.gray(` ... and ${result.orphanFiles.length - 10} more`));\n }\n console.log('');\n }\n\n // Write report if output specified\n if (options.output) {\n const report = analyzer.generateReport(result);\n await writeFile(options.output, report);\n console.log(chalk.green(`Report written to: ${options.output}`));\n }\n\n // Show target metrics comparison\n console.log(chalk.bold('Target Metrics:'));\n const orphanStatus = result.orphanRate < 10 ? chalk.green('PASS') : chalk.red('FAIL');\n const densityStatus = result.linkDensity > 5.0 ? chalk.green('PASS') : chalk.red('FAIL');\n console.log(chalk.white(` Orphan Rate: ${result.orphanRate}% (target: < 10%) ${orphanStatus}`));\n console.log(chalk.white(` Link Density: ${result.linkDensity} (target: > 5.0) ${densityStatus}`));\n console.log('');\n }\n } catch (error) {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');\n process.exit(1);\n }\n });\n\n return command;\n}\n\nexport default createAnalyzeLinksCommand;\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"analyze-links.js","sources":["../../../../src/cli/commands/hive-mind/analyze-links.ts"],"sourcesContent":["/**\n * Hive Mind - Link Analyzer\n *\n * Analyzes wiki-links and markdown links in a vault to build an adjacency list\n * and identify orphan files.\n *\n * SPEC-003: Hive Mind Reconnection Tools\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport * as path from 'path';\nimport { readFile, access, writeFile } from 'fs/promises';\nimport fg from 'fast-glob';\nimport matter from 'gray-matter';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface AnalyzeOptions {\n output?: string;\n json?: boolean;\n verbose?: boolean;\n includeContent?: boolean;\n}\n\nexport interface BrokenLink {\n source: string;\n target: string;\n type: 'wikilink' | 'markdown';\n lineNumber?: number;\n}\n\nexport interface LinkAnalysisResult {\n totalFiles: number;\n filesWithLinks: number;\n orphanFiles: string[];\n orphanRate: number;\n linkDensity: number;\n adjacencyList: Map<string, string[]>;\n brokenLinks: BrokenLink[];\n statistics: {\n totalLinks: number;\n wikiLinks: number;\n markdownLinks: number;\n averageLinksPerFile: number;\n maxLinksInFile: number;\n maxLinksFile: string;\n isolatedClusters: number;\n };\n}\n\n// ============================================================================\n// Link Analyzer Class\n// ============================================================================\n\nexport class LinkAnalyzer {\n private fileMap: Map<string, string> = new Map();\n private allFiles: Set<string> = new Set();\n\n /**\n * Analyze a vault for links and build adjacency list\n */\n async analyzeVault(vaultPath: string, options: AnalyzeOptions = {}): Promise<LinkAnalysisResult> {\n const resolvedPath = path.resolve(vaultPath);\n\n // Check if path exists\n try {\n await access(resolvedPath);\n } catch {\n throw new Error(`Vault path does not exist: ${resolvedPath}`);\n }\n\n // Find all markdown files\n const files = await fg('**/*.md', {\n cwd: resolvedPath,\n ignore: ['node_modules/**', '.git/**', 'dist/**'],\n absolute: false,\n });\n\n if (files.length === 0) {\n throw new Error(`No markdown files found in: ${resolvedPath}`);\n }\n\n // Build file map for resolution (filename without extension -> full path)\n this.buildFileMap(files);\n\n const adjacencyList = new Map<string, string[]>();\n const brokenLinks: BrokenLink[] = [];\n const incomingLinks = new Map<string, Set<string>>();\n let totalWikiLinks = 0;\n let totalMarkdownLinks = 0;\n let maxLinksInFile = 0;\n let maxLinksFile = '';\n\n // Initialize adjacency list and incoming links\n for (const file of files) {\n adjacencyList.set(file, []);\n incomingLinks.set(file, new Set());\n }\n\n // Process each file\n for (const file of files) {\n const filePath = path.join(resolvedPath, file);\n const content = await readFile(filePath, 'utf-8');\n\n // Parse frontmatter to skip it for link counting\n const { content: bodyContent } = matter(content);\n\n // Extract links\n const wikiLinks = this.parseWikiLinks(bodyContent);\n const markdownLinks = this.parseMarkdownLinks(bodyContent);\n\n totalWikiLinks += wikiLinks.length;\n totalMarkdownLinks += markdownLinks.length;\n\n const fileLinks = adjacencyList.get(file) || [];\n const totalLinksInThisFile = wikiLinks.length + markdownLinks.length;\n\n if (totalLinksInThisFile > maxLinksInFile) {\n maxLinksInFile = totalLinksInThisFile;\n maxLinksFile = file;\n }\n\n // Process wiki links\n for (const link of wikiLinks) {\n const resolved = this.resolveLink(link, file);\n if (resolved && this.allFiles.has(resolved)) {\n fileLinks.push(resolved);\n incomingLinks.get(resolved)?.add(file);\n } else {\n brokenLinks.push({\n source: file,\n target: link,\n type: 'wikilink',\n });\n }\n }\n\n // Process markdown links\n for (const link of markdownLinks) {\n const resolved = this.resolveLink(link, file);\n if (resolved && this.allFiles.has(resolved)) {\n fileLinks.push(resolved);\n incomingLinks.get(resolved)?.add(file);\n } else if (!this.isExternalLink(link)) {\n brokenLinks.push({\n source: file,\n target: link,\n type: 'markdown',\n });\n }\n }\n\n adjacencyList.set(file, [...new Set(fileLinks)]); // Dedupe\n }\n\n // Find orphan files (no incoming AND no outgoing links)\n const orphanFiles: string[] = [];\n for (const file of files) {\n const outgoing = adjacencyList.get(file) || [];\n const incoming = incomingLinks.get(file) || new Set();\n if (outgoing.length === 0 && incoming.size === 0) {\n orphanFiles.push(file);\n }\n }\n\n // Calculate metrics\n const totalFiles = files.length;\n const filesWithLinks = files.filter(f => {\n const outgoing = adjacencyList.get(f) || [];\n const incoming = incomingLinks.get(f) || new Set();\n return outgoing.length > 0 || incoming.size > 0;\n }).length;\n\n const orphanRate = totalFiles > 0 ? (orphanFiles.length / totalFiles) * 100 : 0;\n const totalLinks = totalWikiLinks + totalMarkdownLinks;\n const linkDensity = totalFiles > 0 ? totalLinks / totalFiles : 0;\n const averageLinksPerFile = totalFiles > 0 ? totalLinks / totalFiles : 0;\n\n // Calculate isolated clusters using union-find\n const isolatedClusters = this.countClusters(adjacencyList, files);\n\n return {\n totalFiles,\n filesWithLinks,\n orphanFiles,\n orphanRate: Math.round(orphanRate * 100) / 100,\n linkDensity: Math.round(linkDensity * 100) / 100,\n adjacencyList,\n brokenLinks,\n statistics: {\n totalLinks,\n wikiLinks: totalWikiLinks,\n markdownLinks: totalMarkdownLinks,\n averageLinksPerFile: Math.round(averageLinksPerFile * 100) / 100,\n maxLinksInFile,\n maxLinksFile,\n isolatedClusters,\n },\n };\n }\n\n /**\n * Build a map from filename (without extension) to full path\n */\n private buildFileMap(files: string[]): void {\n this.fileMap.clear();\n this.allFiles.clear();\n\n for (const file of files) {\n this.allFiles.add(file);\n\n // Map by filename without extension\n const basename = path.basename(file, '.md');\n if (!this.fileMap.has(basename)) {\n this.fileMap.set(basename, file);\n }\n\n // Also map by full path without extension\n const pathWithoutExt = file.replace(/\\.md$/, '');\n this.fileMap.set(pathWithoutExt, file);\n }\n }\n\n /**\n * Parse [[wiki-links]] from content\n * Handles: [[link]], [[link|alias]], [[folder/link]], [[link#heading]]\n */\n parseWikiLinks(content: string): string[] {\n const links: string[] = [];\n // Match [[link]] and [[link|alias]] patterns\n const regex = /\\[\\[([^\\]|#]+)(?:#[^\\]|]*)?(?:\\|[^\\]]+)?\\]\\]/g;\n let match;\n\n while ((match = regex.exec(content)) !== null) {\n const link = match[1].trim();\n if (link) {\n links.push(link);\n }\n }\n\n return links;\n }\n\n /**\n * Parse [markdown](links) from content\n * Handles: [text](link.md), [text](./link.md), [text](../folder/link.md)\n */\n parseMarkdownLinks(content: string): string[] {\n const links: string[] = [];\n // Match [text](link) patterns, excluding images  and external links\n const regex = /(?<!!)\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\n let match;\n\n while ((match = regex.exec(content)) !== null) {\n const link = match[2].trim();\n // Skip external links, anchors, and non-md files\n if (link && !this.isExternalLink(link) && !link.startsWith('#')) {\n links.push(link);\n }\n }\n\n return links;\n }\n\n /**\n * Check if a link is external (http, https, etc.)\n */\n private isExternalLink(link: string): boolean {\n return /^(https?:|mailto:|tel:|ftp:)/i.test(link);\n }\n\n /**\n * Resolve a link to a file in the vault\n */\n private resolveLink(link: string, sourceFile: string): string | null {\n // Remove .md extension if present\n let cleanLink = link.replace(/\\.md$/, '');\n\n // Remove leading ./ or ../ and resolve relative paths\n if (cleanLink.startsWith('./') || cleanLink.startsWith('../')) {\n const sourceDir = path.dirname(sourceFile);\n const resolved = path.normalize(path.join(sourceDir, cleanLink));\n cleanLink = resolved;\n }\n\n // Try to find in file map\n const found = this.fileMap.get(cleanLink);\n if (found) return found;\n\n // Try with .md extension\n const withMd = cleanLink + '.md';\n if (this.allFiles.has(withMd)) return withMd;\n\n // Try just the basename (for simple wiki-links)\n const basename = path.basename(cleanLink);\n const byBasename = this.fileMap.get(basename);\n if (byBasename) return byBasename;\n\n return null;\n }\n\n /**\n * Count isolated clusters using union-find algorithm\n */\n private countClusters(adjacencyList: Map<string, string[]>, files: string[]): number {\n const parent = new Map<string, string>();\n const rank = new Map<string, number>();\n\n // Initialize each file as its own cluster\n for (const file of files) {\n parent.set(file, file);\n rank.set(file, 0);\n }\n\n const find = (x: string): string => {\n if (parent.get(x) !== x) {\n parent.set(x, find(parent.get(x)!));\n }\n return parent.get(x)!;\n };\n\n const union = (x: string, y: string): void => {\n const rootX = find(x);\n const rootY = find(y);\n if (rootX !== rootY) {\n const rankX = rank.get(rootX) || 0;\n const rankY = rank.get(rootY) || 0;\n if (rankX < rankY) {\n parent.set(rootX, rootY);\n } else if (rankX > rankY) {\n parent.set(rootY, rootX);\n } else {\n parent.set(rootY, rootX);\n rank.set(rootX, rankX + 1);\n }\n }\n };\n\n // Union connected files\n for (const [source, targets] of adjacencyList) {\n for (const target of targets) {\n if (parent.has(target)) {\n union(source, target);\n }\n }\n }\n\n // Count unique roots\n const roots = new Set<string>();\n for (const file of files) {\n roots.add(find(file));\n }\n\n return roots.size;\n }\n\n /**\n * Generate a detailed report\n */\n generateReport(result: LinkAnalysisResult): string {\n const lines: string[] = [];\n\n lines.push('# Link Analysis Report\\n');\n lines.push(`Generated: ${new Date().toISOString()}\\n`);\n\n lines.push('## Summary\\n');\n lines.push(`| Metric | Value |`);\n lines.push(`|--------|-------|`);\n lines.push(`| Total Files | ${result.totalFiles} |`);\n lines.push(`| Files with Links | ${result.filesWithLinks} |`);\n lines.push(`| Orphan Files | ${result.orphanFiles.length} |`);\n lines.push(`| Orphan Rate | ${result.orphanRate}% |`);\n lines.push(`| Link Density | ${result.linkDensity} |`);\n lines.push(`| Total Links | ${result.statistics.totalLinks} |`);\n lines.push(`| Wiki Links | ${result.statistics.wikiLinks} |`);\n lines.push(`| Markdown Links | ${result.statistics.markdownLinks} |`);\n lines.push(`| Avg Links/File | ${result.statistics.averageLinksPerFile} |`);\n lines.push(`| Max Links in File | ${result.statistics.maxLinksInFile} |`);\n lines.push(`| Most Linked File | ${result.statistics.maxLinksFile} |`);\n lines.push(`| Isolated Clusters | ${result.statistics.isolatedClusters} |`);\n lines.push('');\n\n if (result.orphanFiles.length > 0) {\n lines.push('## Orphan Files\\n');\n lines.push('Files with no incoming or outgoing links:\\n');\n for (const file of result.orphanFiles.slice(0, 50)) {\n lines.push(`- \\`${file}\\``);\n }\n if (result.orphanFiles.length > 50) {\n lines.push(`\\n... and ${result.orphanFiles.length - 50} more`);\n }\n lines.push('');\n }\n\n if (result.brokenLinks.length > 0) {\n lines.push('## Broken Links\\n');\n lines.push('Links that point to non-existent files:\\n');\n for (const broken of result.brokenLinks.slice(0, 50)) {\n lines.push(`- \\`${broken.source}\\` -> \\`${broken.target}\\` (${broken.type})`);\n }\n if (result.brokenLinks.length > 50) {\n lines.push(`\\n... and ${result.brokenLinks.length - 50} more`);\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n }\n}\n\n// ============================================================================\n// CLI Command\n// ============================================================================\n\nexport function createAnalyzeLinksCommand(): Command {\n const command = new Command('analyze-links')\n .description('Analyze wiki-links and markdown links in a vault')\n .argument('<vault-path>', 'Path to Obsidian vault or docs directory')\n .option('-o, --output <file>', 'Output file for results (JSON or Markdown)')\n .option('--json', 'Output as JSON')\n .option('-v, --verbose', 'Show detailed output')\n .action(async (vaultPath: string, options: AnalyzeOptions) => {\n const analyzer = new LinkAnalyzer();\n\n console.log(chalk.cyan('\\nAnalyzing links in vault...\\n'));\n\n try {\n const result = await analyzer.analyzeVault(vaultPath, options);\n\n if (options.json) {\n // Convert Map to Object for JSON serialization\n const jsonResult = {\n ...result,\n adjacencyList: Object.fromEntries(result.adjacencyList),\n };\n\n if (options.output) {\n await writeFile(options.output, JSON.stringify(jsonResult, null, 2));\n console.log(chalk.green(`Results written to: ${options.output}`));\n } else {\n console.log(JSON.stringify(jsonResult, null, 2));\n }\n } else {\n // Display summary\n console.log(chalk.bold('Summary:'));\n console.log(chalk.white(` Total Files: ${result.totalFiles}`));\n console.log(chalk.white(` Files with Links: ${result.filesWithLinks}`));\n console.log(chalk.white(` Orphan Files: ${chalk.yellow(result.orphanFiles.length.toString())}`));\n console.log(chalk.white(` Orphan Rate: ${chalk.yellow(result.orphanRate + '%')}`));\n console.log(chalk.white(` Link Density: ${result.linkDensity}`));\n console.log('');\n\n console.log(chalk.bold('Link Statistics:'));\n console.log(chalk.white(` Total Links: ${result.statistics.totalLinks}`));\n console.log(chalk.white(` Wiki Links: ${result.statistics.wikiLinks}`));\n console.log(chalk.white(` Markdown Links: ${result.statistics.markdownLinks}`));\n console.log(chalk.white(` Avg Links/File: ${result.statistics.averageLinksPerFile}`));\n console.log(chalk.white(` Isolated Clusters: ${result.statistics.isolatedClusters}`));\n console.log('');\n\n if (result.brokenLinks.length > 0) {\n console.log(chalk.bold(chalk.red(`Broken Links: ${result.brokenLinks.length}`)));\n if (options.verbose) {\n for (const broken of result.brokenLinks.slice(0, 10)) {\n console.log(chalk.red(` ${broken.source} -> ${broken.target}`));\n }\n if (result.brokenLinks.length > 10) {\n console.log(chalk.gray(` ... and ${result.brokenLinks.length - 10} more`));\n }\n }\n console.log('');\n }\n\n if (options.verbose && result.orphanFiles.length > 0) {\n console.log(chalk.bold(chalk.yellow('Orphan Files:')));\n for (const file of result.orphanFiles.slice(0, 10)) {\n console.log(chalk.yellow(` ${file}`));\n }\n if (result.orphanFiles.length > 10) {\n console.log(chalk.gray(` ... and ${result.orphanFiles.length - 10} more`));\n }\n console.log('');\n }\n\n // Write report if output specified\n if (options.output) {\n const report = analyzer.generateReport(result);\n await writeFile(options.output, report);\n console.log(chalk.green(`Report written to: ${options.output}`));\n }\n\n // Show target metrics comparison\n console.log(chalk.bold('Target Metrics:'));\n const orphanStatus = result.orphanRate < 10 ? chalk.green('PASS') : chalk.red('FAIL');\n const densityStatus = result.linkDensity > 5.0 ? chalk.green('PASS') : chalk.red('FAIL');\n console.log(chalk.white(` Orphan Rate: ${result.orphanRate}% (target: < 10%) ${orphanStatus}`));\n console.log(chalk.white(` Link Density: ${result.linkDensity} (target: > 5.0) ${densityStatus}`));\n console.log('');\n }\n } catch (error) {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');\n process.exit(1);\n }\n });\n\n return command;\n}\n\nexport default createAnalyzeLinksCommand;\n"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { Command } from "commander";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { writeFile, readFile } from "fs/promises";
|
|
5
|
-
import
|
|
5
|
+
import fg from "fast-glob";
|
|
6
6
|
import matter from "gray-matter";
|
|
7
7
|
function tokenize(text) {
|
|
8
8
|
const noCode = text.replace(/```[\s\S]*?```/g, "");
|
|
@@ -85,7 +85,7 @@ class ConnectionFinder {
|
|
|
85
85
|
*/
|
|
86
86
|
async buildIndex(vaultPath) {
|
|
87
87
|
const resolvedPath = path.resolve(vaultPath);
|
|
88
|
-
const files = await
|
|
88
|
+
const files = await fg("**/*.md", {
|
|
89
89
|
cwd: resolvedPath,
|
|
90
90
|
ignore: ["node_modules/**", ".git/**", "dist/**"],
|
|
91
91
|
absolute: false
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-connections.js","sources":["../../../../src/cli/commands/hive-mind/find-connections.ts"],"sourcesContent":["/**\n * Hive Mind - Connection Finder\n *\n * Uses TF-IDF similarity to find potential connections between documents.\n * Suggests links for orphan files to reconnect the knowledge graph.\n *\n * SPEC-003: Hive Mind Reconnection Tools\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport * as path from 'path';\nimport { readFile, writeFile } from 'fs/promises';\nimport { glob } from 'fast-glob';\nimport matter from 'gray-matter';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface FindConnectionsOptions {\n threshold?: string;\n suggest?: boolean;\n limit?: string;\n output?: string;\n json?: boolean;\n verbose?: boolean;\n}\n\nexport interface SimilarityMatch {\n source: string;\n target: string;\n similarity: number;\n sharedTerms: string[];\n}\n\nexport interface DocumentVector {\n file: string;\n terms: Map<string, number>;\n magnitude: number;\n}\n\nexport interface ConnectionFinderResult {\n totalDocuments: number;\n suggestedConnections: SimilarityMatch[];\n orphanConnections: SimilarityMatch[];\n termCount: number;\n averageSimilarity: number;\n}\n\n// ============================================================================\n// TF-IDF Implementation\n// ============================================================================\n\n/**\n * Simple tokenizer - splits text into words\n */\nfunction tokenize(text: string): string[] {\n // Remove code blocks\n const noCode = text.replace(/```[\\s\\S]*?```/g, '');\n // Remove inline code\n const noInline = noCode.replace(/`[^`]+`/g, '');\n // Remove URLs\n const noUrls = noInline.replace(/https?:\\/\\/[^\\s]+/g, '');\n // Remove markdown links but keep text\n const noLinks = noUrls.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1');\n // Remove wiki links but keep text\n const noWiki = noLinks.replace(/\\[\\[([^\\]|]+)(?:\\|[^\\]]+)?\\]\\]/g, '$1');\n // Remove special characters and split\n const words = noWiki\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, ' ')\n .split(/\\s+/)\n .filter(word => word.length > 2 && word.length < 30);\n\n return words;\n}\n\n/**\n * Stopwords to filter out\n */\nconst STOPWORDS = new Set([\n 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', 'her',\n 'was', 'one', 'our', 'out', 'has', 'have', 'been', 'were', 'some', 'this',\n 'that', 'what', 'when', 'where', 'which', 'while', 'who', 'will', 'with',\n 'would', 'there', 'their', 'from', 'they', 'been', 'said', 'each', 'she',\n 'how', 'its', 'may', 'more', 'than', 'then', 'these', 'into', 'only',\n 'other', 'also', 'any', 'such', 'because', 'about', 'just', 'could', 'very',\n]);\n\n/**\n * Filter stopwords\n */\nfunction filterStopwords(words: string[]): string[] {\n return words.filter(word => !STOPWORDS.has(word));\n}\n\n// ============================================================================\n// Connection Finder Class\n// ============================================================================\n\nexport class ConnectionFinder {\n private documents: Map<string, string> = new Map();\n private documentVectors: Map<string, DocumentVector> = new Map();\n private documentFrequency: Map<string, number> = new Map();\n private totalDocuments = 0;\n\n /**\n * Build TF-IDF index from vault\n */\n async buildIndex(vaultPath: string): Promise<void> {\n const resolvedPath = path.resolve(vaultPath);\n\n // Find all markdown files\n const files = await glob('**/*.md', {\n cwd: resolvedPath,\n ignore: ['node_modules/**', '.git/**', 'dist/**'],\n absolute: false,\n });\n\n if (files.length === 0) {\n throw new Error(`No markdown files found in: ${resolvedPath}`);\n }\n\n this.documents.clear();\n this.documentVectors.clear();\n this.documentFrequency.clear();\n this.totalDocuments = files.length;\n\n // First pass: calculate document frequency\n const termDocuments = new Map<string, Set<string>>();\n\n for (const file of files) {\n const filePath = path.join(resolvedPath, file);\n const content = await readFile(filePath, 'utf-8');\n const { content: bodyContent, data: frontmatter } = matter(content);\n\n // Combine content with frontmatter fields\n const fullText = [\n frontmatter.title || '',\n frontmatter.description || '',\n Array.isArray(frontmatter.tags) ? frontmatter.tags.join(' ') : '',\n Array.isArray(frontmatter.aliases) ? frontmatter.aliases.join(' ') : '',\n bodyContent,\n ].join(' ');\n\n this.documents.set(file, fullText);\n\n const tokens = filterStopwords(tokenize(fullText));\n const uniqueTerms = new Set(tokens);\n\n for (const term of uniqueTerms) {\n if (!termDocuments.has(term)) {\n termDocuments.set(term, new Set());\n }\n termDocuments.get(term)!.add(file);\n }\n }\n\n // Store document frequency\n for (const [term, docs] of termDocuments) {\n this.documentFrequency.set(term, docs.size);\n }\n\n // Second pass: calculate TF-IDF vectors\n for (const file of files) {\n const content = this.documents.get(file)!;\n const tokens = filterStopwords(tokenize(content));\n\n // Calculate term frequency\n const termFreq = new Map<string, number>();\n for (const term of tokens) {\n termFreq.set(term, (termFreq.get(term) || 0) + 1);\n }\n\n // Calculate TF-IDF\n const tfidf = new Map<string, number>();\n const maxFreq = Math.max(...termFreq.values(), 1);\n\n for (const [term, freq] of termFreq) {\n const tf = freq / maxFreq; // Normalized TF\n const df = this.documentFrequency.get(term) || 1;\n const idf = Math.log(this.totalDocuments / df);\n tfidf.set(term, tf * idf);\n }\n\n // Calculate magnitude\n let magnitude = 0;\n for (const value of tfidf.values()) {\n magnitude += value * value;\n }\n magnitude = Math.sqrt(magnitude);\n\n this.documentVectors.set(file, {\n file,\n terms: tfidf,\n magnitude,\n });\n }\n }\n\n /**\n * Find similar documents to a source file\n */\n findSimilar(sourceFile: string, threshold = 0.3, limit = 10): SimilarityMatch[] {\n const sourceVector = this.documentVectors.get(sourceFile);\n if (!sourceVector) {\n return [];\n }\n\n const matches: SimilarityMatch[] = [];\n\n for (const [targetFile, targetVector] of this.documentVectors) {\n if (targetFile === sourceFile) continue;\n\n const similarity = this.cosineSimilarity(sourceVector, targetVector);\n\n if (similarity >= threshold) {\n const sharedTerms = this.findSharedTerms(sourceVector, targetVector);\n matches.push({\n source: sourceFile,\n target: targetFile,\n similarity: Math.round(similarity * 1000) / 1000,\n sharedTerms: sharedTerms.slice(0, 5),\n });\n }\n }\n\n // Sort by similarity descending\n matches.sort((a, b) => b.similarity - a.similarity);\n\n return matches.slice(0, limit);\n }\n\n /**\n * Calculate cosine similarity between two document vectors\n */\n private cosineSimilarity(a: DocumentVector, b: DocumentVector): number {\n if (a.magnitude === 0 || b.magnitude === 0) return 0;\n\n let dotProduct = 0;\n for (const [term, aValue] of a.terms) {\n const bValue = b.terms.get(term) || 0;\n dotProduct += aValue * bValue;\n }\n\n return dotProduct / (a.magnitude * b.magnitude);\n }\n\n /**\n * Find shared terms between two documents\n */\n private findSharedTerms(a: DocumentVector, b: DocumentVector): string[] {\n const shared: Array<{ term: string; score: number }> = [];\n\n for (const [term, aValue] of a.terms) {\n const bValue = b.terms.get(term);\n if (bValue && bValue > 0) {\n shared.push({ term, score: aValue * bValue });\n }\n }\n\n // Sort by combined score\n shared.sort((a, b) => b.score - a.score);\n\n return shared.map(s => s.term);\n }\n\n /**\n * Suggest connections for orphan files\n */\n async suggestConnections(\n vaultPath: string,\n orphanFiles: string[],\n threshold = 0.3,\n limit = 5\n ): Promise<SimilarityMatch[]> {\n // Build index if not already built\n if (this.documentVectors.size === 0) {\n await this.buildIndex(vaultPath);\n }\n\n const suggestions: SimilarityMatch[] = [];\n\n for (const orphan of orphanFiles) {\n const similar = this.findSimilar(orphan, threshold, limit);\n suggestions.push(...similar);\n }\n\n // Sort by similarity descending\n suggestions.sort((a, b) => b.similarity - a.similarity);\n\n return suggestions;\n }\n\n /**\n * Find all potential connections above threshold\n */\n async findAllConnections(\n vaultPath: string,\n threshold = 0.3,\n limit = 100\n ): Promise<ConnectionFinderResult> {\n // Build index if not already built\n if (this.documentVectors.size === 0) {\n await this.buildIndex(vaultPath);\n }\n\n const allConnections: SimilarityMatch[] = [];\n const processed = new Set<string>();\n\n for (const sourceFile of this.documentVectors.keys()) {\n const similar = this.findSimilar(sourceFile, threshold, 10);\n for (const match of similar) {\n // Avoid duplicates (A-B and B-A)\n const key = [match.source, match.target].sort().join('|');\n if (!processed.has(key)) {\n processed.add(key);\n allConnections.push(match);\n }\n }\n }\n\n // Sort by similarity descending\n allConnections.sort((a, b) => b.similarity - a.similarity);\n\n const topConnections = allConnections.slice(0, limit);\n const averageSimilarity = topConnections.length > 0\n ? topConnections.reduce((sum, c) => sum + c.similarity, 0) / topConnections.length\n : 0;\n\n return {\n totalDocuments: this.totalDocuments,\n suggestedConnections: topConnections,\n orphanConnections: [],\n termCount: this.documentFrequency.size,\n averageSimilarity: Math.round(averageSimilarity * 1000) / 1000,\n };\n }\n\n /**\n * Get index statistics\n */\n getStats(): { documents: number; terms: number } {\n return {\n documents: this.documentVectors.size,\n terms: this.documentFrequency.size,\n };\n }\n}\n\n// ============================================================================\n// CLI Command\n// ============================================================================\n\nexport function createFindConnectionsCommand(): Command {\n const command = new Command('find-connections')\n .description('Find potential connections using TF-IDF similarity')\n .argument('<vault-path>', 'Path to Obsidian vault or docs directory')\n .option('-t, --threshold <number>', 'Similarity threshold (0-1)', '0.3')\n .option('-l, --limit <number>', 'Maximum connections to return', '50')\n .option('--suggest', 'Focus on orphan files')\n .option('-o, --output <file>', 'Output file for results')\n .option('--json', 'Output as JSON')\n .option('-v, --verbose', 'Show detailed output')\n .action(async (vaultPath: string, options: FindConnectionsOptions) => {\n const finder = new ConnectionFinder();\n const threshold = parseFloat(options.threshold || '0.3');\n const limit = parseInt(options.limit || '50', 10);\n\n console.log(chalk.cyan('\\nBuilding TF-IDF index...\\n'));\n\n try {\n await finder.buildIndex(vaultPath);\n const stats = finder.getStats();\n\n console.log(chalk.white(` Documents indexed: ${stats.documents}`));\n console.log(chalk.white(` Unique terms: ${stats.terms}`));\n console.log('');\n\n const result = await finder.findAllConnections(vaultPath, threshold, limit);\n\n if (options.json) {\n if (options.output) {\n await writeFile(options.output, JSON.stringify(result, null, 2));\n console.log(chalk.green(`Results written to: ${options.output}`));\n } else {\n console.log(JSON.stringify(result, null, 2));\n }\n } else {\n console.log(chalk.bold('Potential Connections Found:\\n'));\n\n if (result.suggestedConnections.length === 0) {\n console.log(chalk.yellow(' No connections found above threshold.'));\n console.log(chalk.gray(' Try lowering the threshold with -t option.\\n'));\n } else {\n for (const conn of result.suggestedConnections.slice(0, options.verbose ? 50 : 20)) {\n const simColor = conn.similarity >= 0.5 ? chalk.green : conn.similarity >= 0.3 ? chalk.yellow : chalk.gray;\n console.log(\n simColor(` [${(conn.similarity * 100).toFixed(1)}%]`),\n chalk.white(conn.source),\n chalk.gray('->'),\n chalk.cyan(conn.target)\n );\n if (options.verbose && conn.sharedTerms.length > 0) {\n console.log(chalk.gray(` Terms: ${conn.sharedTerms.join(', ')}`));\n }\n }\n\n if (result.suggestedConnections.length > (options.verbose ? 50 : 20)) {\n console.log(\n chalk.gray(`\\n ... and ${result.suggestedConnections.length - (options.verbose ? 50 : 20)} more`)\n );\n }\n\n console.log('');\n console.log(chalk.bold('Summary:'));\n console.log(chalk.white(` Total Documents: ${result.totalDocuments}`));\n console.log(chalk.white(` Connections Found: ${result.suggestedConnections.length}`));\n console.log(chalk.white(` Average Similarity: ${(result.averageSimilarity * 100).toFixed(1)}%`));\n console.log('');\n }\n\n // Write to output file if specified\n if (options.output) {\n const reportLines = [\n '# Connection Suggestions\\n',\n `Generated: ${new Date().toISOString()}\\n`,\n `Threshold: ${threshold}\\n`,\n '',\n '## Suggested Links\\n',\n ];\n\n for (const conn of result.suggestedConnections) {\n reportLines.push(`- **${conn.source}** -> [[${conn.target}]]`);\n reportLines.push(` - Similarity: ${(conn.similarity * 100).toFixed(1)}%`);\n if (conn.sharedTerms.length > 0) {\n reportLines.push(` - Shared terms: ${conn.sharedTerms.join(', ')}`);\n }\n }\n\n await writeFile(options.output, reportLines.join('\\n'));\n console.log(chalk.green(`Report written to: ${options.output}`));\n }\n\n // Show next steps\n console.log(chalk.bold('Next Steps:'));\n console.log(chalk.gray(' 1. Review suggested connections'));\n console.log(chalk.gray(' 2. Add [[wiki-links]] to connect related documents'));\n console.log(chalk.gray(' 3. Run kg analyze-links to verify improvement'));\n console.log('');\n }\n } catch (error) {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');\n process.exit(1);\n }\n });\n\n return command;\n}\n\nexport default createFindConnectionsCommand;\n"],"names":["a","b"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"find-connections.js","sources":["../../../../src/cli/commands/hive-mind/find-connections.ts"],"sourcesContent":["/**\n * Hive Mind - Connection Finder\n *\n * Uses TF-IDF similarity to find potential connections between documents.\n * Suggests links for orphan files to reconnect the knowledge graph.\n *\n * SPEC-003: Hive Mind Reconnection Tools\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport * as path from 'path';\nimport { readFile, writeFile } from 'fs/promises';\nimport fg from 'fast-glob';\nimport matter from 'gray-matter';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface FindConnectionsOptions {\n threshold?: string;\n suggest?: boolean;\n limit?: string;\n output?: string;\n json?: boolean;\n verbose?: boolean;\n}\n\nexport interface SimilarityMatch {\n source: string;\n target: string;\n similarity: number;\n sharedTerms: string[];\n}\n\nexport interface DocumentVector {\n file: string;\n terms: Map<string, number>;\n magnitude: number;\n}\n\nexport interface ConnectionFinderResult {\n totalDocuments: number;\n suggestedConnections: SimilarityMatch[];\n orphanConnections: SimilarityMatch[];\n termCount: number;\n averageSimilarity: number;\n}\n\n// ============================================================================\n// TF-IDF Implementation\n// ============================================================================\n\n/**\n * Simple tokenizer - splits text into words\n */\nfunction tokenize(text: string): string[] {\n // Remove code blocks\n const noCode = text.replace(/```[\\s\\S]*?```/g, '');\n // Remove inline code\n const noInline = noCode.replace(/`[^`]+`/g, '');\n // Remove URLs\n const noUrls = noInline.replace(/https?:\\/\\/[^\\s]+/g, '');\n // Remove markdown links but keep text\n const noLinks = noUrls.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1');\n // Remove wiki links but keep text\n const noWiki = noLinks.replace(/\\[\\[([^\\]|]+)(?:\\|[^\\]]+)?\\]\\]/g, '$1');\n // Remove special characters and split\n const words = noWiki\n .toLowerCase()\n .replace(/[^a-z0-9\\s-]/g, ' ')\n .split(/\\s+/)\n .filter(word => word.length > 2 && word.length < 30);\n\n return words;\n}\n\n/**\n * Stopwords to filter out\n */\nconst STOPWORDS = new Set([\n 'the', 'and', 'for', 'are', 'but', 'not', 'you', 'all', 'can', 'had', 'her',\n 'was', 'one', 'our', 'out', 'has', 'have', 'been', 'were', 'some', 'this',\n 'that', 'what', 'when', 'where', 'which', 'while', 'who', 'will', 'with',\n 'would', 'there', 'their', 'from', 'they', 'been', 'said', 'each', 'she',\n 'how', 'its', 'may', 'more', 'than', 'then', 'these', 'into', 'only',\n 'other', 'also', 'any', 'such', 'because', 'about', 'just', 'could', 'very',\n]);\n\n/**\n * Filter stopwords\n */\nfunction filterStopwords(words: string[]): string[] {\n return words.filter(word => !STOPWORDS.has(word));\n}\n\n// ============================================================================\n// Connection Finder Class\n// ============================================================================\n\nexport class ConnectionFinder {\n private documents: Map<string, string> = new Map();\n private documentVectors: Map<string, DocumentVector> = new Map();\n private documentFrequency: Map<string, number> = new Map();\n private totalDocuments = 0;\n\n /**\n * Build TF-IDF index from vault\n */\n async buildIndex(vaultPath: string): Promise<void> {\n const resolvedPath = path.resolve(vaultPath);\n\n // Find all markdown files\n const files = await fg('**/*.md', {\n cwd: resolvedPath,\n ignore: ['node_modules/**', '.git/**', 'dist/**'],\n absolute: false,\n });\n\n if (files.length === 0) {\n throw new Error(`No markdown files found in: ${resolvedPath}`);\n }\n\n this.documents.clear();\n this.documentVectors.clear();\n this.documentFrequency.clear();\n this.totalDocuments = files.length;\n\n // First pass: calculate document frequency\n const termDocuments = new Map<string, Set<string>>();\n\n for (const file of files) {\n const filePath = path.join(resolvedPath, file);\n const content = await readFile(filePath, 'utf-8');\n const { content: bodyContent, data: frontmatter } = matter(content);\n\n // Combine content with frontmatter fields\n const fullText = [\n frontmatter.title || '',\n frontmatter.description || '',\n Array.isArray(frontmatter.tags) ? frontmatter.tags.join(' ') : '',\n Array.isArray(frontmatter.aliases) ? frontmatter.aliases.join(' ') : '',\n bodyContent,\n ].join(' ');\n\n this.documents.set(file, fullText);\n\n const tokens = filterStopwords(tokenize(fullText));\n const uniqueTerms = new Set(tokens);\n\n for (const term of uniqueTerms) {\n if (!termDocuments.has(term)) {\n termDocuments.set(term, new Set());\n }\n termDocuments.get(term)!.add(file);\n }\n }\n\n // Store document frequency\n for (const [term, docs] of termDocuments) {\n this.documentFrequency.set(term, docs.size);\n }\n\n // Second pass: calculate TF-IDF vectors\n for (const file of files) {\n const content = this.documents.get(file)!;\n const tokens = filterStopwords(tokenize(content));\n\n // Calculate term frequency\n const termFreq = new Map<string, number>();\n for (const term of tokens) {\n termFreq.set(term, (termFreq.get(term) || 0) + 1);\n }\n\n // Calculate TF-IDF\n const tfidf = new Map<string, number>();\n const maxFreq = Math.max(...termFreq.values(), 1);\n\n for (const [term, freq] of termFreq) {\n const tf = freq / maxFreq; // Normalized TF\n const df = this.documentFrequency.get(term) || 1;\n const idf = Math.log(this.totalDocuments / df);\n tfidf.set(term, tf * idf);\n }\n\n // Calculate magnitude\n let magnitude = 0;\n for (const value of tfidf.values()) {\n magnitude += value * value;\n }\n magnitude = Math.sqrt(magnitude);\n\n this.documentVectors.set(file, {\n file,\n terms: tfidf,\n magnitude,\n });\n }\n }\n\n /**\n * Find similar documents to a source file\n */\n findSimilar(sourceFile: string, threshold = 0.3, limit = 10): SimilarityMatch[] {\n const sourceVector = this.documentVectors.get(sourceFile);\n if (!sourceVector) {\n return [];\n }\n\n const matches: SimilarityMatch[] = [];\n\n for (const [targetFile, targetVector] of this.documentVectors) {\n if (targetFile === sourceFile) continue;\n\n const similarity = this.cosineSimilarity(sourceVector, targetVector);\n\n if (similarity >= threshold) {\n const sharedTerms = this.findSharedTerms(sourceVector, targetVector);\n matches.push({\n source: sourceFile,\n target: targetFile,\n similarity: Math.round(similarity * 1000) / 1000,\n sharedTerms: sharedTerms.slice(0, 5),\n });\n }\n }\n\n // Sort by similarity descending\n matches.sort((a, b) => b.similarity - a.similarity);\n\n return matches.slice(0, limit);\n }\n\n /**\n * Calculate cosine similarity between two document vectors\n */\n private cosineSimilarity(a: DocumentVector, b: DocumentVector): number {\n if (a.magnitude === 0 || b.magnitude === 0) return 0;\n\n let dotProduct = 0;\n for (const [term, aValue] of a.terms) {\n const bValue = b.terms.get(term) || 0;\n dotProduct += aValue * bValue;\n }\n\n return dotProduct / (a.magnitude * b.magnitude);\n }\n\n /**\n * Find shared terms between two documents\n */\n private findSharedTerms(a: DocumentVector, b: DocumentVector): string[] {\n const shared: Array<{ term: string; score: number }> = [];\n\n for (const [term, aValue] of a.terms) {\n const bValue = b.terms.get(term);\n if (bValue && bValue > 0) {\n shared.push({ term, score: aValue * bValue });\n }\n }\n\n // Sort by combined score\n shared.sort((a, b) => b.score - a.score);\n\n return shared.map(s => s.term);\n }\n\n /**\n * Suggest connections for orphan files\n */\n async suggestConnections(\n vaultPath: string,\n orphanFiles: string[],\n threshold = 0.3,\n limit = 5\n ): Promise<SimilarityMatch[]> {\n // Build index if not already built\n if (this.documentVectors.size === 0) {\n await this.buildIndex(vaultPath);\n }\n\n const suggestions: SimilarityMatch[] = [];\n\n for (const orphan of orphanFiles) {\n const similar = this.findSimilar(orphan, threshold, limit);\n suggestions.push(...similar);\n }\n\n // Sort by similarity descending\n suggestions.sort((a, b) => b.similarity - a.similarity);\n\n return suggestions;\n }\n\n /**\n * Find all potential connections above threshold\n */\n async findAllConnections(\n vaultPath: string,\n threshold = 0.3,\n limit = 100\n ): Promise<ConnectionFinderResult> {\n // Build index if not already built\n if (this.documentVectors.size === 0) {\n await this.buildIndex(vaultPath);\n }\n\n const allConnections: SimilarityMatch[] = [];\n const processed = new Set<string>();\n\n for (const sourceFile of this.documentVectors.keys()) {\n const similar = this.findSimilar(sourceFile, threshold, 10);\n for (const match of similar) {\n // Avoid duplicates (A-B and B-A)\n const key = [match.source, match.target].sort().join('|');\n if (!processed.has(key)) {\n processed.add(key);\n allConnections.push(match);\n }\n }\n }\n\n // Sort by similarity descending\n allConnections.sort((a, b) => b.similarity - a.similarity);\n\n const topConnections = allConnections.slice(0, limit);\n const averageSimilarity = topConnections.length > 0\n ? topConnections.reduce((sum, c) => sum + c.similarity, 0) / topConnections.length\n : 0;\n\n return {\n totalDocuments: this.totalDocuments,\n suggestedConnections: topConnections,\n orphanConnections: [],\n termCount: this.documentFrequency.size,\n averageSimilarity: Math.round(averageSimilarity * 1000) / 1000,\n };\n }\n\n /**\n * Get index statistics\n */\n getStats(): { documents: number; terms: number } {\n return {\n documents: this.documentVectors.size,\n terms: this.documentFrequency.size,\n };\n }\n}\n\n// ============================================================================\n// CLI Command\n// ============================================================================\n\nexport function createFindConnectionsCommand(): Command {\n const command = new Command('find-connections')\n .description('Find potential connections using TF-IDF similarity')\n .argument('<vault-path>', 'Path to Obsidian vault or docs directory')\n .option('-t, --threshold <number>', 'Similarity threshold (0-1)', '0.3')\n .option('-l, --limit <number>', 'Maximum connections to return', '50')\n .option('--suggest', 'Focus on orphan files')\n .option('-o, --output <file>', 'Output file for results')\n .option('--json', 'Output as JSON')\n .option('-v, --verbose', 'Show detailed output')\n .action(async (vaultPath: string, options: FindConnectionsOptions) => {\n const finder = new ConnectionFinder();\n const threshold = parseFloat(options.threshold || '0.3');\n const limit = parseInt(options.limit || '50', 10);\n\n console.log(chalk.cyan('\\nBuilding TF-IDF index...\\n'));\n\n try {\n await finder.buildIndex(vaultPath);\n const stats = finder.getStats();\n\n console.log(chalk.white(` Documents indexed: ${stats.documents}`));\n console.log(chalk.white(` Unique terms: ${stats.terms}`));\n console.log('');\n\n const result = await finder.findAllConnections(vaultPath, threshold, limit);\n\n if (options.json) {\n if (options.output) {\n await writeFile(options.output, JSON.stringify(result, null, 2));\n console.log(chalk.green(`Results written to: ${options.output}`));\n } else {\n console.log(JSON.stringify(result, null, 2));\n }\n } else {\n console.log(chalk.bold('Potential Connections Found:\\n'));\n\n if (result.suggestedConnections.length === 0) {\n console.log(chalk.yellow(' No connections found above threshold.'));\n console.log(chalk.gray(' Try lowering the threshold with -t option.\\n'));\n } else {\n for (const conn of result.suggestedConnections.slice(0, options.verbose ? 50 : 20)) {\n const simColor = conn.similarity >= 0.5 ? chalk.green : conn.similarity >= 0.3 ? chalk.yellow : chalk.gray;\n console.log(\n simColor(` [${(conn.similarity * 100).toFixed(1)}%]`),\n chalk.white(conn.source),\n chalk.gray('->'),\n chalk.cyan(conn.target)\n );\n if (options.verbose && conn.sharedTerms.length > 0) {\n console.log(chalk.gray(` Terms: ${conn.sharedTerms.join(', ')}`));\n }\n }\n\n if (result.suggestedConnections.length > (options.verbose ? 50 : 20)) {\n console.log(\n chalk.gray(`\\n ... and ${result.suggestedConnections.length - (options.verbose ? 50 : 20)} more`)\n );\n }\n\n console.log('');\n console.log(chalk.bold('Summary:'));\n console.log(chalk.white(` Total Documents: ${result.totalDocuments}`));\n console.log(chalk.white(` Connections Found: ${result.suggestedConnections.length}`));\n console.log(chalk.white(` Average Similarity: ${(result.averageSimilarity * 100).toFixed(1)}%`));\n console.log('');\n }\n\n // Write to output file if specified\n if (options.output) {\n const reportLines = [\n '# Connection Suggestions\\n',\n `Generated: ${new Date().toISOString()}\\n`,\n `Threshold: ${threshold}\\n`,\n '',\n '## Suggested Links\\n',\n ];\n\n for (const conn of result.suggestedConnections) {\n reportLines.push(`- **${conn.source}** -> [[${conn.target}]]`);\n reportLines.push(` - Similarity: ${(conn.similarity * 100).toFixed(1)}%`);\n if (conn.sharedTerms.length > 0) {\n reportLines.push(` - Shared terms: ${conn.sharedTerms.join(', ')}`);\n }\n }\n\n await writeFile(options.output, reportLines.join('\\n'));\n console.log(chalk.green(`Report written to: ${options.output}`));\n }\n\n // Show next steps\n console.log(chalk.bold('Next Steps:'));\n console.log(chalk.gray(' 1. Review suggested connections'));\n console.log(chalk.gray(' 2. Add [[wiki-links]] to connect related documents'));\n console.log(chalk.gray(' 3. Run kg analyze-links to verify improvement'));\n console.log('');\n }\n } catch (error) {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : 'Unknown error');\n process.exit(1);\n }\n });\n\n return command;\n}\n\nexport default createFindConnectionsCommand;\n"],"names":["a","b"],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@ import { Command } from "commander";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import * as path from "path";
|
|
4
4
|
import { writeFile, access, mkdir, rename } from "fs/promises";
|
|
5
|
-
import
|
|
5
|
+
import fg from "fast-glob";
|
|
6
6
|
const SCHEMAS = {
|
|
7
7
|
kebab: {
|
|
8
8
|
name: "kebab-case",
|
|
@@ -46,7 +46,7 @@ class NameValidator {
|
|
|
46
46
|
*/
|
|
47
47
|
async validateVault(vaultPath, options = {}) {
|
|
48
48
|
const resolvedPath = path.resolve(vaultPath);
|
|
49
|
-
const files = await
|
|
49
|
+
const files = await fg("**/*.md", {
|
|
50
50
|
cwd: resolvedPath,
|
|
51
51
|
ignore: ["node_modules/**", ".git/**", "dist/**"],
|
|
52
52
|
absolute: false
|