@weavelogic/knowledge-graph-agent 0.1.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.
Files changed (74) hide show
  1. package/README.md +264 -0
  2. package/dist/cli/bin.d.ts +8 -0
  3. package/dist/cli/bin.d.ts.map +1 -0
  4. package/dist/cli/bin.js +20 -0
  5. package/dist/cli/bin.js.map +1 -0
  6. package/dist/cli/commands/claude.d.ts +11 -0
  7. package/dist/cli/commands/claude.d.ts.map +1 -0
  8. package/dist/cli/commands/claude.js +102 -0
  9. package/dist/cli/commands/claude.js.map +1 -0
  10. package/dist/cli/commands/docs.d.ts +11 -0
  11. package/dist/cli/commands/docs.d.ts.map +1 -0
  12. package/dist/cli/commands/docs.js +108 -0
  13. package/dist/cli/commands/docs.js.map +1 -0
  14. package/dist/cli/commands/graph.d.ts +11 -0
  15. package/dist/cli/commands/graph.d.ts.map +1 -0
  16. package/dist/cli/commands/graph.js +122 -0
  17. package/dist/cli/commands/graph.js.map +1 -0
  18. package/dist/cli/commands/init.d.ts +11 -0
  19. package/dist/cli/commands/init.d.ts.map +1 -0
  20. package/dist/cli/commands/init.js +80 -0
  21. package/dist/cli/commands/init.js.map +1 -0
  22. package/dist/cli/commands/search.d.ts +11 -0
  23. package/dist/cli/commands/search.d.ts.map +1 -0
  24. package/dist/cli/commands/search.js +80 -0
  25. package/dist/cli/commands/search.js.map +1 -0
  26. package/dist/cli/commands/stats.d.ts +11 -0
  27. package/dist/cli/commands/stats.d.ts.map +1 -0
  28. package/dist/cli/commands/stats.js +84 -0
  29. package/dist/cli/commands/stats.js.map +1 -0
  30. package/dist/cli/commands/sync.d.ts +11 -0
  31. package/dist/cli/commands/sync.d.ts.map +1 -0
  32. package/dist/cli/commands/sync.js +76 -0
  33. package/dist/cli/commands/sync.js.map +1 -0
  34. package/dist/cli/index.d.ts +11 -0
  35. package/dist/cli/index.d.ts.map +1 -0
  36. package/dist/cli/index.js +45 -0
  37. package/dist/cli/index.js.map +1 -0
  38. package/dist/core/database.d.ts +121 -0
  39. package/dist/core/database.d.ts.map +1 -0
  40. package/dist/core/database.js +470 -0
  41. package/dist/core/database.js.map +1 -0
  42. package/dist/core/graph.d.ts +109 -0
  43. package/dist/core/graph.d.ts.map +1 -0
  44. package/dist/core/graph.js +343 -0
  45. package/dist/core/graph.js.map +1 -0
  46. package/dist/core/security.d.ts +62 -0
  47. package/dist/core/security.d.ts.map +1 -0
  48. package/dist/core/security.js +31 -0
  49. package/dist/core/security.js.map +1 -0
  50. package/dist/core/types.d.ts +232 -0
  51. package/dist/core/types.d.ts.map +1 -0
  52. package/dist/core/types.js +37 -0
  53. package/dist/core/types.js.map +1 -0
  54. package/dist/generators/claude-md.d.ts +33 -0
  55. package/dist/generators/claude-md.d.ts.map +1 -0
  56. package/dist/generators/claude-md.js +410 -0
  57. package/dist/generators/claude-md.js.map +1 -0
  58. package/dist/generators/docs-init.d.ts +20 -0
  59. package/dist/generators/docs-init.d.ts.map +1 -0
  60. package/dist/generators/docs-init.js +625 -0
  61. package/dist/generators/docs-init.js.map +1 -0
  62. package/dist/generators/graph-generator.d.ts +41 -0
  63. package/dist/generators/graph-generator.d.ts.map +1 -0
  64. package/dist/generators/graph-generator.js +266 -0
  65. package/dist/generators/graph-generator.js.map +1 -0
  66. package/dist/index.d.ts +41 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +99 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/integrations/claude-flow.d.ts +62 -0
  71. package/dist/integrations/claude-flow.d.ts.map +1 -0
  72. package/dist/integrations/claude-flow.js +243 -0
  73. package/dist/integrations/claude-flow.js.map +1 -0
  74. package/package.json +77 -0
@@ -0,0 +1,45 @@
1
+ import { Command } from "commander";
2
+ import chalk from "chalk";
3
+ import { createInitCommand } from "./commands/init.js";
4
+ import { createGraphCommand } from "./commands/graph.js";
5
+ import { createDocsCommand } from "./commands/docs.js";
6
+ import { createClaudeCommand } from "./commands/claude.js";
7
+ import { createSyncCommand } from "./commands/sync.js";
8
+ import { createStatsCommand } from "./commands/stats.js";
9
+ import { createSearchCommand } from "./commands/search.js";
10
+ const VERSION = "0.1.0";
11
+ function createCLI() {
12
+ const program = new Command();
13
+ program.name("kg").description("Knowledge Graph Agent - Generate and manage knowledge graphs for Claude Code").version(VERSION, "-v, --version", "Display version number");
14
+ program.configureHelp({
15
+ sortSubcommands: true,
16
+ sortOptions: true
17
+ });
18
+ program.addCommand(createInitCommand());
19
+ program.addCommand(createGraphCommand());
20
+ program.addCommand(createDocsCommand());
21
+ program.addCommand(createClaudeCommand());
22
+ program.addCommand(createSyncCommand());
23
+ program.addCommand(createStatsCommand());
24
+ program.addCommand(createSearchCommand());
25
+ program.action(() => {
26
+ console.log(chalk.cyan.bold("\n Knowledge Graph Agent\n"));
27
+ console.log(chalk.gray(" Generate and manage knowledge graphs for Claude Code\n"));
28
+ console.log(chalk.white(" Quick Start:"));
29
+ console.log(chalk.gray(" $ kg init # Initialize knowledge graph"));
30
+ console.log(chalk.gray(" $ kg docs init # Initialize docs directory"));
31
+ console.log(chalk.gray(" $ kg graph # Generate knowledge graph"));
32
+ console.log(chalk.gray(" $ kg claude update # Update CLAUDE.md"));
33
+ console.log(chalk.gray(" $ kg sync # Sync with claude-flow\n"));
34
+ console.log(chalk.white(" Commands:"));
35
+ program.commands.forEach((cmd) => {
36
+ console.log(chalk.cyan(` ${cmd.name().padEnd(20)}`), chalk.gray(cmd.description() || ""));
37
+ });
38
+ console.log("\n Run", chalk.cyan("kg <command> --help"), "for more information\n");
39
+ });
40
+ return program;
41
+ }
42
+ export {
43
+ createCLI
44
+ };
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/cli/index.ts"],"sourcesContent":["/**\n * Knowledge Graph Agent CLI\n *\n * Main CLI setup and command registration.\n */\n\nimport { Command } from 'commander';\nimport chalk from 'chalk';\nimport { createInitCommand } from './commands/init.js';\nimport { createGraphCommand } from './commands/graph.js';\nimport { createDocsCommand } from './commands/docs.js';\nimport { createClaudeCommand } from './commands/claude.js';\nimport { createSyncCommand } from './commands/sync.js';\nimport { createStatsCommand } from './commands/stats.js';\nimport { createSearchCommand } from './commands/search.js';\n\n/**\n * CLI version\n */\nconst VERSION = '0.1.0';\n\n/**\n * Create and configure the CLI program\n */\nexport function createCLI(): Command {\n const program = new Command();\n\n program\n .name('kg')\n .description('Knowledge Graph Agent - Generate and manage knowledge graphs for Claude Code')\n .version(VERSION, '-v, --version', 'Display version number');\n\n // Configure help\n program.configureHelp({\n sortSubcommands: true,\n sortOptions: true,\n });\n\n // Add commands\n program.addCommand(createInitCommand());\n program.addCommand(createGraphCommand());\n program.addCommand(createDocsCommand());\n program.addCommand(createClaudeCommand());\n program.addCommand(createSyncCommand());\n program.addCommand(createStatsCommand());\n program.addCommand(createSearchCommand());\n\n // Default action (show help)\n program.action(() => {\n console.log(chalk.cyan.bold('\\n Knowledge Graph Agent\\n'));\n console.log(chalk.gray(' Generate and manage knowledge graphs for Claude Code\\n'));\n\n console.log(chalk.white(' Quick Start:'));\n console.log(chalk.gray(' $ kg init # Initialize knowledge graph'));\n console.log(chalk.gray(' $ kg docs init # Initialize docs directory'));\n console.log(chalk.gray(' $ kg graph # Generate knowledge graph'));\n console.log(chalk.gray(' $ kg claude update # Update CLAUDE.md'));\n console.log(chalk.gray(' $ kg sync # Sync with claude-flow\\n'));\n\n console.log(chalk.white(' Commands:'));\n program.commands.forEach(cmd => {\n console.log(chalk.cyan(` ${cmd.name().padEnd(20)}`), chalk.gray(cmd.description() || ''));\n });\n\n console.log('\\n Run', chalk.cyan('kg <command> --help'), 'for more information\\n');\n });\n\n return program;\n}\n"],"names":[],"mappings":";;;;;;;;;AAmBA,MAAM,UAAU;AAKT,SAAS,YAAqB;AACnC,QAAM,UAAU,IAAI,QAAA;AAEpB,UACG,KAAK,IAAI,EACT,YAAY,8EAA8E,EAC1F,QAAQ,SAAS,iBAAiB,wBAAwB;AAG7D,UAAQ,cAAc;AAAA,IACpB,iBAAiB;AAAA,IACjB,aAAa;AAAA,EAAA,CACd;AAGD,UAAQ,WAAW,mBAAmB;AACtC,UAAQ,WAAW,oBAAoB;AACvC,UAAQ,WAAW,mBAAmB;AACtC,UAAQ,WAAW,qBAAqB;AACxC,UAAQ,WAAW,mBAAmB;AACtC,UAAQ,WAAW,oBAAoB;AACvC,UAAQ,WAAW,qBAAqB;AAGxC,UAAQ,OAAO,MAAM;AACnB,YAAQ,IAAI,MAAM,KAAK,KAAK,6BAA6B,CAAC;AAC1D,YAAQ,IAAI,MAAM,KAAK,0DAA0D,CAAC;AAElF,YAAQ,IAAI,MAAM,MAAM,gBAAgB,CAAC;AACzC,YAAQ,IAAI,MAAM,KAAK,6DAA6D,CAAC;AACrF,YAAQ,IAAI,MAAM,KAAK,4DAA4D,CAAC;AACpF,YAAQ,IAAI,MAAM,KAAK,2DAA2D,CAAC;AACnF,YAAQ,IAAI,MAAM,KAAK,mDAAmD,CAAC;AAC3E,YAAQ,IAAI,MAAM,KAAK,0DAA0D,CAAC;AAElF,YAAQ,IAAI,MAAM,MAAM,aAAa,CAAC;AACtC,YAAQ,SAAS,QAAQ,CAAA,QAAO;AAC9B,cAAQ,IAAI,MAAM,KAAK,OAAO,IAAI,OAAO,OAAO,EAAE,CAAC,EAAE,GAAG,MAAM,KAAK,IAAI,YAAA,KAAiB,EAAE,CAAC;AAAA,IAC7F,CAAC;AAED,YAAQ,IAAI,WAAW,MAAM,KAAK,qBAAqB,GAAG,wBAAwB;AAAA,EACpF,CAAC;AAED,SAAO;AACT;"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Knowledge Graph Database
3
+ *
4
+ * SQLite database for persistent storage of knowledge graph data.
5
+ * Compatible with claude-flow database patterns.
6
+ */
7
+ import Database from 'better-sqlite3';
8
+ import type { KnowledgeNode, GraphEdge, GraphStats, NodeType, NodeStatus } from './types.js';
9
+ /**
10
+ * Knowledge Graph Database
11
+ */
12
+ export declare class KnowledgeGraphDatabase {
13
+ private db;
14
+ private dbPath;
15
+ constructor(dbPath: string);
16
+ /**
17
+ * Insert or update a node
18
+ */
19
+ upsertNode(node: KnowledgeNode): void;
20
+ /**
21
+ * Get node by ID
22
+ */
23
+ getNode(id: string): KnowledgeNode | null;
24
+ /**
25
+ * Get node by path
26
+ */
27
+ getNodeByPath(path: string): KnowledgeNode | null;
28
+ /**
29
+ * Get all nodes
30
+ */
31
+ getAllNodes(): KnowledgeNode[];
32
+ /**
33
+ * Get nodes by type
34
+ */
35
+ getNodesByType(type: NodeType): KnowledgeNode[];
36
+ /**
37
+ * Get nodes by status
38
+ */
39
+ getNodesByStatus(status: NodeStatus): KnowledgeNode[];
40
+ /**
41
+ * Get nodes by tag
42
+ */
43
+ getNodesByTag(tag: string): KnowledgeNode[];
44
+ /**
45
+ * Sanitize FTS5 query to prevent query injection
46
+ * Escapes special FTS5 operators and quotes terms
47
+ */
48
+ private sanitizeFtsQuery;
49
+ /**
50
+ * Search nodes by title or content
51
+ */
52
+ searchNodes(query: string, limit?: number): KnowledgeNode[];
53
+ /**
54
+ * Delete node
55
+ */
56
+ deleteNode(id: string): boolean;
57
+ /**
58
+ * Update tags for a node
59
+ */
60
+ private updateNodeTags;
61
+ /**
62
+ * Get tags for a node
63
+ */
64
+ getNodeTags(nodeId: string): string[];
65
+ /**
66
+ * Get all tags with counts
67
+ */
68
+ getAllTags(): Array<{
69
+ name: string;
70
+ count: number;
71
+ }>;
72
+ /**
73
+ * Add edge
74
+ */
75
+ addEdge(edge: GraphEdge): void;
76
+ /**
77
+ * Get outgoing edges for a node
78
+ */
79
+ getOutgoingEdges(nodeId: string): GraphEdge[];
80
+ /**
81
+ * Get incoming edges for a node
82
+ */
83
+ getIncomingEdges(nodeId: string): GraphEdge[];
84
+ /**
85
+ * Delete edges for a node
86
+ */
87
+ deleteNodeEdges(nodeId: string): void;
88
+ /**
89
+ * Get graph statistics
90
+ */
91
+ getStats(): GraphStats;
92
+ /**
93
+ * Get metadata value
94
+ */
95
+ getMetadata(key: string): string | null;
96
+ /**
97
+ * Set metadata value
98
+ */
99
+ setMetadata(key: string, value: string): void;
100
+ /**
101
+ * Convert database row to KnowledgeNode
102
+ */
103
+ private rowToNode;
104
+ /**
105
+ * Convert database row to GraphEdge
106
+ */
107
+ private rowToEdge;
108
+ /**
109
+ * Close database connection
110
+ */
111
+ close(): void;
112
+ /**
113
+ * Get raw database instance
114
+ */
115
+ getDatabase(): Database.Database;
116
+ }
117
+ /**
118
+ * Create knowledge graph database instance
119
+ */
120
+ export declare function createDatabase(dbPath: string): KnowledgeGraphDatabase;
121
+ //# sourceMappingURL=database.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/core/database.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,QAAQ,MAAM,gBAAgB,CAAC;AAGtC,OAAO,KAAK,EACV,aAAa,EACb,SAAS,EACT,UAAU,EACV,QAAQ,EACR,UAAU,EACX,MAAM,YAAY,CAAC;AAuGpB;;GAEG;AACH,qBAAa,sBAAsB;IACjC,OAAO,CAAC,EAAE,CAAoB;IAC9B,OAAO,CAAC,MAAM,CAAS;gBAEX,MAAM,EAAE,MAAM;IAuB1B;;OAEG;IACH,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAgCrC;;OAEG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAOzC;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI;IAOjD;;OAEG;IACH,WAAW,IAAI,aAAa,EAAE;IAM9B;;OAEG;IACH,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,aAAa,EAAE;IAM/C;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,aAAa,EAAE;IAMrD;;OAEG;IACH,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,aAAa,EAAE;IAY3C;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAiBxB;;OAEG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,aAAa,EAAE;IAsBvD;;OAEG;IACH,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAU/B;;OAEG;IACH,OAAO,CAAC,cAAc;IAqBtB;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE;IAWrC;;OAEG;IACH,UAAU,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAepD;;OAEG;IACH,OAAO,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI;IAQ9B;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAM7C;;OAEG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE;IAM7C;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAQrC;;OAEG;IACH,QAAQ,IAAI,UAAU;IA2DtB;;OAEG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAMvC;;OAEG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAY7C;;OAEG;IACH,OAAO,CAAC,SAAS;IA8BjB;;OAEG;IACH,OAAO,CAAC,SAAS;IAUjB;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,WAAW,IAAI,QAAQ,CAAC,QAAQ;CAGjC;AA2BD;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,sBAAsB,CAErE"}
@@ -0,0 +1,470 @@
1
+ import Database from "better-sqlite3";
2
+ import { existsSync, mkdirSync } from "fs";
3
+ import { dirname } from "path";
4
+ function safeJsonParse(str, fallback) {
5
+ if (!str) return fallback;
6
+ try {
7
+ return JSON.parse(str);
8
+ } catch {
9
+ return fallback;
10
+ }
11
+ }
12
+ const SCHEMA_SQL = `
13
+ -- Knowledge Graph Schema v1.0
14
+
15
+ -- Nodes table
16
+ CREATE TABLE IF NOT EXISTS nodes (
17
+ id TEXT PRIMARY KEY,
18
+ path TEXT NOT NULL UNIQUE,
19
+ filename TEXT NOT NULL,
20
+ title TEXT NOT NULL,
21
+ type TEXT NOT NULL CHECK(type IN ('concept', 'technical', 'feature', 'primitive', 'service', 'guide', 'standard', 'integration')),
22
+ status TEXT NOT NULL DEFAULT 'active' CHECK(status IN ('draft', 'active', 'deprecated', 'archived')),
23
+ content TEXT,
24
+ frontmatter TEXT,
25
+ word_count INTEGER DEFAULT 0,
26
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
27
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
28
+ );
29
+
30
+ -- Tags table
31
+ CREATE TABLE IF NOT EXISTS tags (
32
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
33
+ name TEXT NOT NULL UNIQUE
34
+ );
35
+
36
+ -- Node-Tags junction table
37
+ CREATE TABLE IF NOT EXISTS node_tags (
38
+ node_id TEXT NOT NULL,
39
+ tag_id INTEGER NOT NULL,
40
+ PRIMARY KEY (node_id, tag_id),
41
+ FOREIGN KEY (node_id) REFERENCES nodes(id) ON DELETE CASCADE,
42
+ FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
43
+ );
44
+
45
+ -- Edges table
46
+ CREATE TABLE IF NOT EXISTS edges (
47
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
48
+ source_id TEXT NOT NULL,
49
+ target_id TEXT NOT NULL,
50
+ type TEXT NOT NULL CHECK(type IN ('link', 'reference', 'parent', 'related')),
51
+ weight REAL DEFAULT 1.0,
52
+ context TEXT,
53
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
54
+ FOREIGN KEY (source_id) REFERENCES nodes(id) ON DELETE CASCADE
55
+ );
56
+
57
+ -- Metadata table
58
+ CREATE TABLE IF NOT EXISTS metadata (
59
+ key TEXT PRIMARY KEY,
60
+ value TEXT,
61
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
62
+ );
63
+
64
+ -- Indexes for performance
65
+ CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(type);
66
+ CREATE INDEX IF NOT EXISTS idx_nodes_status ON nodes(status);
67
+ CREATE INDEX IF NOT EXISTS idx_nodes_path ON nodes(path);
68
+ CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
69
+ CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
70
+ CREATE INDEX IF NOT EXISTS idx_node_tags_node ON node_tags(node_id);
71
+ CREATE INDEX IF NOT EXISTS idx_node_tags_tag ON node_tags(tag_id);
72
+
73
+ -- Full-text search
74
+ CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
75
+ title,
76
+ content,
77
+ content='nodes',
78
+ content_rowid='rowid'
79
+ );
80
+
81
+ -- Triggers for FTS sync
82
+ CREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes BEGIN
83
+ INSERT INTO nodes_fts(rowid, title, content) VALUES (NEW.rowid, NEW.title, NEW.content);
84
+ END;
85
+
86
+ CREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes BEGIN
87
+ INSERT INTO nodes_fts(nodes_fts, rowid, title, content) VALUES('delete', OLD.rowid, OLD.title, OLD.content);
88
+ END;
89
+
90
+ CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes BEGIN
91
+ INSERT INTO nodes_fts(nodes_fts, rowid, title, content) VALUES('delete', OLD.rowid, OLD.title, OLD.content);
92
+ INSERT INTO nodes_fts(rowid, title, content) VALUES (NEW.rowid, NEW.title, NEW.content);
93
+ END;
94
+
95
+ -- Initialize metadata
96
+ INSERT OR IGNORE INTO metadata (key, value) VALUES ('version', '1.0.0');
97
+ INSERT OR IGNORE INTO metadata (key, value) VALUES ('created', datetime('now'));
98
+ `;
99
+ class KnowledgeGraphDatabase {
100
+ db;
101
+ dbPath;
102
+ constructor(dbPath) {
103
+ this.dbPath = dbPath;
104
+ const dir = dirname(dbPath);
105
+ if (!existsSync(dir)) {
106
+ mkdirSync(dir, { recursive: true });
107
+ }
108
+ this.db = new Database(dbPath);
109
+ this.db.pragma("journal_mode = WAL");
110
+ this.db.pragma("foreign_keys = ON");
111
+ this.db.pragma("busy_timeout = 5000");
112
+ this.db.exec(SCHEMA_SQL);
113
+ }
114
+ // ========================================================================
115
+ // Node Operations
116
+ // ========================================================================
117
+ /**
118
+ * Insert or update a node
119
+ */
120
+ upsertNode(node) {
121
+ const stmt = this.db.prepare(`
122
+ INSERT INTO nodes (id, path, filename, title, type, status, content, frontmatter, word_count, updated_at)
123
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
124
+ ON CONFLICT(id) DO UPDATE SET
125
+ path = excluded.path,
126
+ filename = excluded.filename,
127
+ title = excluded.title,
128
+ type = excluded.type,
129
+ status = excluded.status,
130
+ content = excluded.content,
131
+ frontmatter = excluded.frontmatter,
132
+ word_count = excluded.word_count,
133
+ updated_at = datetime('now')
134
+ `);
135
+ stmt.run(
136
+ node.id,
137
+ node.path,
138
+ node.filename,
139
+ node.title,
140
+ node.type,
141
+ node.status,
142
+ node.content,
143
+ JSON.stringify(node.frontmatter),
144
+ node.wordCount
145
+ );
146
+ this.updateNodeTags(node.id, node.tags);
147
+ }
148
+ /**
149
+ * Get node by ID
150
+ */
151
+ getNode(id) {
152
+ const stmt = this.db.prepare("SELECT * FROM nodes WHERE id = ?");
153
+ const row = stmt.get(id);
154
+ if (!row) return null;
155
+ return this.rowToNode(row);
156
+ }
157
+ /**
158
+ * Get node by path
159
+ */
160
+ getNodeByPath(path) {
161
+ const stmt = this.db.prepare("SELECT * FROM nodes WHERE path = ?");
162
+ const row = stmt.get(path);
163
+ if (!row) return null;
164
+ return this.rowToNode(row);
165
+ }
166
+ /**
167
+ * Get all nodes
168
+ */
169
+ getAllNodes() {
170
+ const stmt = this.db.prepare("SELECT * FROM nodes ORDER BY title");
171
+ const rows = stmt.all();
172
+ return rows.map((row) => this.rowToNode(row));
173
+ }
174
+ /**
175
+ * Get nodes by type
176
+ */
177
+ getNodesByType(type) {
178
+ const stmt = this.db.prepare("SELECT * FROM nodes WHERE type = ? ORDER BY title");
179
+ const rows = stmt.all(type);
180
+ return rows.map((row) => this.rowToNode(row));
181
+ }
182
+ /**
183
+ * Get nodes by status
184
+ */
185
+ getNodesByStatus(status) {
186
+ const stmt = this.db.prepare("SELECT * FROM nodes WHERE status = ? ORDER BY title");
187
+ const rows = stmt.all(status);
188
+ return rows.map((row) => this.rowToNode(row));
189
+ }
190
+ /**
191
+ * Get nodes by tag
192
+ */
193
+ getNodesByTag(tag) {
194
+ const stmt = this.db.prepare(`
195
+ SELECT n.* FROM nodes n
196
+ JOIN node_tags nt ON n.id = nt.node_id
197
+ JOIN tags t ON nt.tag_id = t.id
198
+ WHERE t.name = ?
199
+ ORDER BY n.title
200
+ `);
201
+ const rows = stmt.all(tag);
202
+ return rows.map((row) => this.rowToNode(row));
203
+ }
204
+ /**
205
+ * Sanitize FTS5 query to prevent query injection
206
+ * Escapes special FTS5 operators and quotes terms
207
+ */
208
+ sanitizeFtsQuery(query) {
209
+ if (!query || typeof query !== "string") return "";
210
+ const sanitized = query.replace(/[*"():^\-]/g, " ").replace(/\b(AND|OR|NOT|NEAR)\b/gi, "").trim().split(/\s+/).filter((term) => term.length > 0 && term.length < 100).slice(0, 20).map((term) => `"${term.replace(/"/g, "")}"`).join(" ");
211
+ return sanitized;
212
+ }
213
+ /**
214
+ * Search nodes by title or content
215
+ */
216
+ searchNodes(query, limit = 50) {
217
+ const sanitizedQuery = this.sanitizeFtsQuery(query);
218
+ if (!sanitizedQuery) {
219
+ return [];
220
+ }
221
+ const safeLimit = Math.min(Math.max(1, limit), 100);
222
+ const stmt = this.db.prepare(`
223
+ SELECT n.* FROM nodes n
224
+ JOIN nodes_fts fts ON n.rowid = fts.rowid
225
+ WHERE nodes_fts MATCH ?
226
+ ORDER BY rank
227
+ LIMIT ?
228
+ `);
229
+ const rows = stmt.all(sanitizedQuery, safeLimit);
230
+ return rows.map((row) => this.rowToNode(row));
231
+ }
232
+ /**
233
+ * Delete node
234
+ */
235
+ deleteNode(id) {
236
+ const stmt = this.db.prepare("DELETE FROM nodes WHERE id = ?");
237
+ const result = stmt.run(id);
238
+ return result.changes > 0;
239
+ }
240
+ // ========================================================================
241
+ // Tag Operations
242
+ // ========================================================================
243
+ /**
244
+ * Update tags for a node
245
+ */
246
+ updateNodeTags(nodeId, tags) {
247
+ this.db.prepare("DELETE FROM node_tags WHERE node_id = ?").run(nodeId);
248
+ const getOrCreateTag = this.db.prepare(`
249
+ INSERT INTO tags (name) VALUES (?)
250
+ ON CONFLICT(name) DO UPDATE SET name = excluded.name
251
+ RETURNING id
252
+ `);
253
+ const insertNodeTag = this.db.prepare(
254
+ "INSERT OR IGNORE INTO node_tags (node_id, tag_id) VALUES (?, ?)"
255
+ );
256
+ for (const tag of tags) {
257
+ const result = getOrCreateTag.get(tag);
258
+ insertNodeTag.run(nodeId, result.id);
259
+ }
260
+ }
261
+ /**
262
+ * Get tags for a node
263
+ */
264
+ getNodeTags(nodeId) {
265
+ const stmt = this.db.prepare(`
266
+ SELECT t.name FROM tags t
267
+ JOIN node_tags nt ON t.id = nt.tag_id
268
+ WHERE nt.node_id = ?
269
+ ORDER BY t.name
270
+ `);
271
+ const rows = stmt.all(nodeId);
272
+ return rows.map((r) => r.name);
273
+ }
274
+ /**
275
+ * Get all tags with counts
276
+ */
277
+ getAllTags() {
278
+ const stmt = this.db.prepare(`
279
+ SELECT t.name, COUNT(nt.node_id) as count
280
+ FROM tags t
281
+ LEFT JOIN node_tags nt ON t.id = nt.tag_id
282
+ GROUP BY t.id
283
+ ORDER BY count DESC, t.name
284
+ `);
285
+ return stmt.all();
286
+ }
287
+ // ========================================================================
288
+ // Edge Operations
289
+ // ========================================================================
290
+ /**
291
+ * Add edge
292
+ */
293
+ addEdge(edge) {
294
+ const stmt = this.db.prepare(`
295
+ INSERT INTO edges (source_id, target_id, type, weight, context)
296
+ VALUES (?, ?, ?, ?, ?)
297
+ `);
298
+ stmt.run(edge.source, edge.target, edge.type, edge.weight, edge.context);
299
+ }
300
+ /**
301
+ * Get outgoing edges for a node
302
+ */
303
+ getOutgoingEdges(nodeId) {
304
+ const stmt = this.db.prepare("SELECT * FROM edges WHERE source_id = ?");
305
+ const rows = stmt.all(nodeId);
306
+ return rows.map((row) => this.rowToEdge(row));
307
+ }
308
+ /**
309
+ * Get incoming edges for a node
310
+ */
311
+ getIncomingEdges(nodeId) {
312
+ const stmt = this.db.prepare("SELECT * FROM edges WHERE target_id = ?");
313
+ const rows = stmt.all(nodeId);
314
+ return rows.map((row) => this.rowToEdge(row));
315
+ }
316
+ /**
317
+ * Delete edges for a node
318
+ */
319
+ deleteNodeEdges(nodeId) {
320
+ this.db.prepare("DELETE FROM edges WHERE source_id = ?").run(nodeId);
321
+ }
322
+ // ========================================================================
323
+ // Statistics
324
+ // ========================================================================
325
+ /**
326
+ * Get graph statistics
327
+ */
328
+ getStats() {
329
+ const totalNodes = this.db.prepare("SELECT COUNT(*) as count FROM nodes").get().count;
330
+ const totalEdges = this.db.prepare("SELECT COUNT(*) as count FROM edges").get().count;
331
+ const typeStats = this.db.prepare(`
332
+ SELECT type, COUNT(*) as count FROM nodes GROUP BY type
333
+ `).all();
334
+ const statusStats = this.db.prepare(`
335
+ SELECT status, COUNT(*) as count FROM nodes GROUP BY status
336
+ `).all();
337
+ const nodesByType = {
338
+ concept: 0,
339
+ technical: 0,
340
+ feature: 0,
341
+ primitive: 0,
342
+ service: 0,
343
+ guide: 0,
344
+ standard: 0,
345
+ integration: 0
346
+ };
347
+ for (const { type, count } of typeStats) {
348
+ nodesByType[type] = count;
349
+ }
350
+ const nodesByStatus = {
351
+ draft: 0,
352
+ active: 0,
353
+ deprecated: 0,
354
+ archived: 0
355
+ };
356
+ for (const { status, count } of statusStats) {
357
+ nodesByStatus[status] = count;
358
+ }
359
+ const orphanNodes = this.db.prepare(`
360
+ SELECT COUNT(*) as count FROM nodes n
361
+ WHERE NOT EXISTS (SELECT 1 FROM edges e WHERE e.source_id = n.id OR e.target_id = n.id)
362
+ `).get().count;
363
+ const avgLinksPerNode = totalNodes > 0 ? totalEdges / totalNodes : 0;
364
+ const mostConnected = this.db.prepare(`
365
+ SELECT n.id, (
366
+ (SELECT COUNT(*) FROM edges WHERE source_id = n.id) +
367
+ (SELECT COUNT(*) FROM edges WHERE target_id = n.id)
368
+ ) as connections
369
+ FROM nodes n
370
+ ORDER BY connections DESC
371
+ LIMIT 5
372
+ `).all();
373
+ return {
374
+ totalNodes,
375
+ totalEdges,
376
+ nodesByType,
377
+ nodesByStatus,
378
+ orphanNodes,
379
+ avgLinksPerNode: Math.round(avgLinksPerNode * 100) / 100,
380
+ mostConnected
381
+ };
382
+ }
383
+ // ========================================================================
384
+ // Metadata Operations
385
+ // ========================================================================
386
+ /**
387
+ * Get metadata value
388
+ */
389
+ getMetadata(key) {
390
+ const stmt = this.db.prepare("SELECT value FROM metadata WHERE key = ?");
391
+ const row = stmt.get(key);
392
+ return row?.value ?? null;
393
+ }
394
+ /**
395
+ * Set metadata value
396
+ */
397
+ setMetadata(key, value) {
398
+ const stmt = this.db.prepare(`
399
+ INSERT INTO metadata (key, value, updated_at) VALUES (?, ?, datetime('now'))
400
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = datetime('now')
401
+ `);
402
+ stmt.run(key, value);
403
+ }
404
+ // ========================================================================
405
+ // Utilities
406
+ // ========================================================================
407
+ /**
408
+ * Convert database row to KnowledgeNode
409
+ */
410
+ rowToNode(row) {
411
+ const tags = this.getNodeTags(row.id);
412
+ const outgoingEdges = this.getOutgoingEdges(row.id);
413
+ const incomingEdges = this.getIncomingEdges(row.id);
414
+ return {
415
+ id: row.id,
416
+ path: row.path,
417
+ filename: row.filename,
418
+ title: row.title,
419
+ type: row.type,
420
+ status: row.status,
421
+ content: row.content || "",
422
+ frontmatter: safeJsonParse(row.frontmatter, {}),
423
+ tags,
424
+ outgoingLinks: outgoingEdges.map((e) => ({
425
+ target: e.target,
426
+ type: "wikilink",
427
+ context: e.context
428
+ })),
429
+ incomingLinks: incomingEdges.map((e) => ({
430
+ target: e.source,
431
+ type: "backlink",
432
+ context: e.context
433
+ })),
434
+ wordCount: row.word_count,
435
+ lastModified: new Date(row.updated_at)
436
+ };
437
+ }
438
+ /**
439
+ * Convert database row to GraphEdge
440
+ */
441
+ rowToEdge(row) {
442
+ return {
443
+ source: row.source_id,
444
+ target: row.target_id,
445
+ type: row.type,
446
+ weight: row.weight,
447
+ context: row.context ?? void 0
448
+ };
449
+ }
450
+ /**
451
+ * Close database connection
452
+ */
453
+ close() {
454
+ this.db.close();
455
+ }
456
+ /**
457
+ * Get raw database instance
458
+ */
459
+ getDatabase() {
460
+ return this.db;
461
+ }
462
+ }
463
+ function createDatabase(dbPath) {
464
+ return new KnowledgeGraphDatabase(dbPath);
465
+ }
466
+ export {
467
+ KnowledgeGraphDatabase,
468
+ createDatabase
469
+ };
470
+ //# sourceMappingURL=database.js.map