@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.
- package/README.md +264 -0
- package/dist/cli/bin.d.ts +8 -0
- package/dist/cli/bin.d.ts.map +1 -0
- package/dist/cli/bin.js +20 -0
- package/dist/cli/bin.js.map +1 -0
- package/dist/cli/commands/claude.d.ts +11 -0
- package/dist/cli/commands/claude.d.ts.map +1 -0
- package/dist/cli/commands/claude.js +102 -0
- package/dist/cli/commands/claude.js.map +1 -0
- package/dist/cli/commands/docs.d.ts +11 -0
- package/dist/cli/commands/docs.d.ts.map +1 -0
- package/dist/cli/commands/docs.js +108 -0
- package/dist/cli/commands/docs.js.map +1 -0
- package/dist/cli/commands/graph.d.ts +11 -0
- package/dist/cli/commands/graph.d.ts.map +1 -0
- package/dist/cli/commands/graph.js +122 -0
- package/dist/cli/commands/graph.js.map +1 -0
- package/dist/cli/commands/init.d.ts +11 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +80 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/search.d.ts +11 -0
- package/dist/cli/commands/search.d.ts.map +1 -0
- package/dist/cli/commands/search.js +80 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/commands/stats.d.ts +11 -0
- package/dist/cli/commands/stats.d.ts.map +1 -0
- package/dist/cli/commands/stats.js +84 -0
- package/dist/cli/commands/stats.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +11 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +76 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +45 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/database.d.ts +121 -0
- package/dist/core/database.d.ts.map +1 -0
- package/dist/core/database.js +470 -0
- package/dist/core/database.js.map +1 -0
- package/dist/core/graph.d.ts +109 -0
- package/dist/core/graph.d.ts.map +1 -0
- package/dist/core/graph.js +343 -0
- package/dist/core/graph.js.map +1 -0
- package/dist/core/security.d.ts +62 -0
- package/dist/core/security.d.ts.map +1 -0
- package/dist/core/security.js +31 -0
- package/dist/core/security.js.map +1 -0
- package/dist/core/types.d.ts +232 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +37 -0
- package/dist/core/types.js.map +1 -0
- package/dist/generators/claude-md.d.ts +33 -0
- package/dist/generators/claude-md.d.ts.map +1 -0
- package/dist/generators/claude-md.js +410 -0
- package/dist/generators/claude-md.js.map +1 -0
- package/dist/generators/docs-init.d.ts +20 -0
- package/dist/generators/docs-init.d.ts.map +1 -0
- package/dist/generators/docs-init.js +625 -0
- package/dist/generators/docs-init.js.map +1 -0
- package/dist/generators/graph-generator.d.ts +41 -0
- package/dist/generators/graph-generator.d.ts.map +1 -0
- package/dist/generators/graph-generator.js +266 -0
- package/dist/generators/graph-generator.js.map +1 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/claude-flow.d.ts +62 -0
- package/dist/integrations/claude-flow.d.ts.map +1 -0
- package/dist/integrations/claude-flow.js +243 -0
- package/dist/integrations/claude-flow.js.map +1 -0
- 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
|