opentology 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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +609 -0
  3. package/dist/commands/context.d.ts +29 -0
  4. package/dist/commands/context.js +369 -0
  5. package/dist/commands/delete.d.ts +2 -0
  6. package/dist/commands/delete.js +46 -0
  7. package/dist/commands/diff.d.ts +2 -0
  8. package/dist/commands/diff.js +43 -0
  9. package/dist/commands/drop.d.ts +2 -0
  10. package/dist/commands/drop.js +41 -0
  11. package/dist/commands/graph.d.ts +2 -0
  12. package/dist/commands/graph.js +130 -0
  13. package/dist/commands/infer.d.ts +2 -0
  14. package/dist/commands/infer.js +47 -0
  15. package/dist/commands/init.d.ts +2 -0
  16. package/dist/commands/init.js +53 -0
  17. package/dist/commands/mcp.d.ts +2 -0
  18. package/dist/commands/mcp.js +9 -0
  19. package/dist/commands/prefix.d.ts +2 -0
  20. package/dist/commands/prefix.js +73 -0
  21. package/dist/commands/pull.d.ts +2 -0
  22. package/dist/commands/pull.js +43 -0
  23. package/dist/commands/push.d.ts +2 -0
  24. package/dist/commands/push.js +79 -0
  25. package/dist/commands/query.d.ts +2 -0
  26. package/dist/commands/query.js +119 -0
  27. package/dist/commands/shapes.d.ts +2 -0
  28. package/dist/commands/shapes.js +67 -0
  29. package/dist/commands/status.d.ts +2 -0
  30. package/dist/commands/status.js +47 -0
  31. package/dist/commands/validate.d.ts +2 -0
  32. package/dist/commands/validate.js +46 -0
  33. package/dist/index.d.ts +2 -0
  34. package/dist/index.js +38 -0
  35. package/dist/lib/codebase-scanner.d.ts +41 -0
  36. package/dist/lib/codebase-scanner.js +360 -0
  37. package/dist/lib/config.d.ts +16 -0
  38. package/dist/lib/config.js +70 -0
  39. package/dist/lib/embedded-adapter.d.ts +45 -0
  40. package/dist/lib/embedded-adapter.js +202 -0
  41. package/dist/lib/http-adapter.d.ts +41 -0
  42. package/dist/lib/http-adapter.js +169 -0
  43. package/dist/lib/oxigraph.d.ts +62 -0
  44. package/dist/lib/oxigraph.js +323 -0
  45. package/dist/lib/reasoner.d.ts +19 -0
  46. package/dist/lib/reasoner.js +310 -0
  47. package/dist/lib/shacl.d.ts +22 -0
  48. package/dist/lib/shacl.js +105 -0
  49. package/dist/lib/sparql-utils.d.ts +28 -0
  50. package/dist/lib/sparql-utils.js +217 -0
  51. package/dist/lib/store-adapter.d.ts +50 -0
  52. package/dist/lib/store-adapter.js +1 -0
  53. package/dist/lib/store-factory.d.ts +9 -0
  54. package/dist/lib/store-factory.js +71 -0
  55. package/dist/lib/validator.d.ts +10 -0
  56. package/dist/lib/validator.js +40 -0
  57. package/dist/mcp/server.d.ts +3 -0
  58. package/dist/mcp/server.js +1020 -0
  59. package/dist/templates/claude-md-context.d.ts +4 -0
  60. package/dist/templates/claude-md-context.js +104 -0
  61. package/dist/templates/otx-ontology.d.ts +2 -0
  62. package/dist/templates/otx-ontology.js +31 -0
  63. package/dist/templates/session-start-hook.d.ts +1 -0
  64. package/dist/templates/session-start-hook.js +94 -0
  65. package/dist/templates/slash-commands.d.ts +5 -0
  66. package/dist/templates/slash-commands.js +108 -0
  67. package/package.json +58 -0
@@ -0,0 +1,105 @@
1
+ import rdf from 'rdf-ext';
2
+ import { Parser } from 'n3';
3
+ import { Validator } from 'shacl-engine';
4
+ import { readFileSync, existsSync, readdirSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ /**
7
+ * Discover .ttl shape files in the given directory (defaults to shapes/ in cwd).
8
+ */
9
+ export function discoverShapes(dir) {
10
+ const shapesDir = dir ?? join(process.cwd(), 'shapes');
11
+ if (!existsSync(shapesDir)) {
12
+ return [];
13
+ }
14
+ return readdirSync(shapesDir)
15
+ .filter((f) => f.endsWith('.ttl'))
16
+ .map((f) => join(shapesDir, f));
17
+ }
18
+ /**
19
+ * Check whether the shapes directory exists and contains at least one .ttl file.
20
+ */
21
+ export function hasShapes(shapesDir) {
22
+ return discoverShapes(shapesDir).length > 0;
23
+ }
24
+ /**
25
+ * Parse a Turtle string into an rdf-ext Dataset using n3.
26
+ */
27
+ function parseTurtleToDataset(turtle) {
28
+ return new Promise((resolve, reject) => {
29
+ const parser = new Parser();
30
+ const quads = [];
31
+ parser.parse(turtle, (err, quad) => {
32
+ if (err) {
33
+ reject(err);
34
+ return;
35
+ }
36
+ if (quad) {
37
+ quads.push(quad);
38
+ }
39
+ else {
40
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
41
+ resolve(rdf.dataset(quads));
42
+ }
43
+ });
44
+ });
45
+ }
46
+ /**
47
+ * Load and combine multiple shape .ttl files into a single rdf-ext Dataset.
48
+ */
49
+ async function loadShapes(shapePaths) {
50
+ const allQuads = [];
51
+ for (const shapePath of shapePaths) {
52
+ const turtle = readFileSync(shapePath, 'utf-8');
53
+ const dataset = await parseTurtleToDataset(turtle);
54
+ for (const quad of dataset) {
55
+ allQuads.push(quad);
56
+ }
57
+ }
58
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
59
+ return rdf.dataset(allQuads);
60
+ }
61
+ /**
62
+ * Extract a string value from a SHACL result message, which may be a Literal object or a plain string.
63
+ */
64
+ function extractMessageValue(msg) {
65
+ if (!msg)
66
+ return '';
67
+ if (Array.isArray(msg)) {
68
+ return msg.map(extractMessageValue).join('; ');
69
+ }
70
+ if (typeof msg === 'object' && msg !== null && 'value' in msg) {
71
+ return String(msg.value);
72
+ }
73
+ return String(msg);
74
+ }
75
+ /**
76
+ * Extract the local name from a URI (e.g. "http://...#Violation" → "Violation").
77
+ */
78
+ function extractLocalName(uri) {
79
+ const hashIdx = uri.lastIndexOf('#');
80
+ if (hashIdx !== -1)
81
+ return uri.slice(hashIdx + 1);
82
+ const slashIdx = uri.lastIndexOf('/');
83
+ if (slashIdx !== -1)
84
+ return uri.slice(slashIdx + 1);
85
+ return uri;
86
+ }
87
+ /**
88
+ * Validate a Turtle data string against the given SHACL shape files.
89
+ */
90
+ export async function validateWithShacl(dataTurtle, shapePaths) {
91
+ const dataDataset = await parseTurtleToDataset(dataTurtle);
92
+ const shapesDataset = await loadShapes(shapePaths);
93
+ const validator = new Validator(shapesDataset, { factory: rdf });
94
+ const report = await validator.validate({ dataset: dataDataset });
95
+ const violations = (report.results ?? []).map((result) => ({
96
+ focusNode: result.focusNode?.value ?? '',
97
+ path: result.path?.value ?? null,
98
+ message: extractMessageValue(result.message),
99
+ severity: extractLocalName(result.severity?.value ?? ''),
100
+ }));
101
+ return {
102
+ conforms: Boolean(report.conforms),
103
+ violations,
104
+ };
105
+ }
@@ -0,0 +1,28 @@
1
+ import type { Quad } from 'n3';
2
+ export declare const WELL_KNOWN_PREFIXES: Record<string, string>;
3
+ export declare function termToSparql(term: Quad['subject'] | Quad['predicate'] | Quad['object']): string;
4
+ export declare function parseTurtle(turtle: string): Promise<Quad[]>;
5
+ export declare function serializeQuadsToTurtle(quads: Quad[]): Promise<string>;
6
+ export declare function extractPrefixes(uris: string[]): Record<string, string>;
7
+ /**
8
+ * Returns true if the query already manages its own graph scoping.
9
+ * Matches GRAPH, FROM NAMED, or FROM < (case-insensitive).
10
+ */
11
+ export declare function hasGraphScope(sparql: string): boolean;
12
+ /**
13
+ * Wraps the WHERE body of a SPARQL SELECT/CONSTRUCT/ASK/DESCRIBE query so
14
+ * that all triple patterns are scoped to `graphUri`.
15
+ *
16
+ * Handles:
17
+ * SELECT ... WHERE { ... } -> standard form
18
+ * SELECT ... { ... } -> shorthand (no WHERE keyword)
19
+ *
20
+ * Returns null if the outermost `{ ... }` block cannot be located safely.
21
+ */
22
+ export declare function autoScopeQuery(sparql: string, graphUri: string): string | null;
23
+ /**
24
+ * Auto-scope a SPARQL query with UNION over asserted + inference graphs.
25
+ * Returns null if brace matching fails.
26
+ */
27
+ export declare function autoScopeQueryWithInference(sparql: string, graphUri: string, inferenceGraphUri: string): string | null;
28
+ export declare function getInferenceGraphUri(graphUri: string): string;
@@ -0,0 +1,217 @@
1
+ import { Parser, Writer } from 'n3';
2
+ // ── Well-known prefixes ───────────────────────────────────────────────
3
+ export const WELL_KNOWN_PREFIXES = {
4
+ 'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf',
5
+ 'http://www.w3.org/2000/01/rdf-schema#': 'rdfs',
6
+ 'http://www.w3.org/2002/07/owl#': 'owl',
7
+ 'http://schema.org/': 'schema',
8
+ 'http://xmlns.com/foaf/0.1/': 'foaf',
9
+ 'http://www.w3.org/2001/XMLSchema#': 'xsd',
10
+ 'http://purl.org/dc/elements/1.1/': 'dc',
11
+ 'http://purl.org/dc/terms/': 'dcterms',
12
+ 'http://www.w3.org/2004/02/skos/core#': 'skos',
13
+ 'http://www.w3.org/ns/prov#': 'prov',
14
+ };
15
+ // ── Term / Turtle helpers ─────────────────────────────────────────────
16
+ export function termToSparql(term) {
17
+ switch (term.termType) {
18
+ case 'NamedNode':
19
+ return `<${term.value}>`;
20
+ case 'BlankNode':
21
+ return `_:${term.value}`;
22
+ case 'Literal': {
23
+ const escaped = term.value
24
+ .replace(/\\/g, '\\\\')
25
+ .replace(/"/g, '\\"')
26
+ .replace(/\n/g, '\\n')
27
+ .replace(/\r/g, '\\r')
28
+ .replace(/\t/g, '\\t');
29
+ if (term.language) {
30
+ return `"${escaped}"@${term.language}`;
31
+ }
32
+ if (term.datatype && term.datatype.value !== 'http://www.w3.org/2001/XMLSchema#string') {
33
+ return `"${escaped}"^^<${term.datatype.value}>`;
34
+ }
35
+ return `"${escaped}"`;
36
+ }
37
+ default:
38
+ throw new Error(`Unsupported term type: ${term.termType}`);
39
+ }
40
+ }
41
+ export function parseTurtle(turtle) {
42
+ return new Promise((resolve, reject) => {
43
+ const parser = new Parser();
44
+ const quads = [];
45
+ parser.parse(turtle, (err, quad) => {
46
+ if (err) {
47
+ reject(new Error(`Failed to parse Turtle: ${err.message}`));
48
+ return;
49
+ }
50
+ if (quad) {
51
+ quads.push(quad);
52
+ }
53
+ else {
54
+ resolve(quads);
55
+ }
56
+ });
57
+ });
58
+ }
59
+ export function serializeQuadsToTurtle(quads) {
60
+ return new Promise((resolve, reject) => {
61
+ const writer = new Writer({ format: 'Turtle' });
62
+ writer.addQuads(quads);
63
+ writer.end((err, result) => {
64
+ if (err) {
65
+ reject(new Error(`Failed to serialize Turtle: ${err.message}`));
66
+ }
67
+ else {
68
+ resolve(result);
69
+ }
70
+ });
71
+ });
72
+ }
73
+ // ── Prefix extraction ─────────────────────────────────────────────────
74
+ export function extractPrefixes(uris) {
75
+ const prefixes = {};
76
+ for (const uri of uris) {
77
+ // Check well-known prefixes first
78
+ for (const [ns, prefix] of Object.entries(WELL_KNOWN_PREFIXES)) {
79
+ if (uri.startsWith(ns) && !(prefix in Object.values(prefixes))) {
80
+ prefixes[prefix] = ns;
81
+ break;
82
+ }
83
+ }
84
+ // If not matched by well-known, derive from URI structure
85
+ if (!Object.values(prefixes).some((ns) => uri.startsWith(ns))) {
86
+ // Try hash-based namespace (e.g. http://example.org/ontology#)
87
+ const hashIdx = uri.lastIndexOf('#');
88
+ if (hashIdx !== -1) {
89
+ const ns = uri.slice(0, hashIdx + 1);
90
+ if (!Object.values(prefixes).includes(ns)) {
91
+ // Derive a short prefix from the last path segment before the hash
92
+ const pathSegments = ns.replace(/#$/, '').split('/').filter(Boolean);
93
+ const candidate = pathSegments[pathSegments.length - 1]
94
+ ?.toLowerCase()
95
+ .replace(/[^a-z0-9]/g, '')
96
+ .slice(0, 8);
97
+ if (candidate && !(candidate in prefixes)) {
98
+ prefixes[candidate] = ns;
99
+ }
100
+ }
101
+ }
102
+ else {
103
+ // Slash-based namespace (e.g. http://example.org/ontology/)
104
+ const slashIdx = uri.lastIndexOf('/');
105
+ if (slashIdx !== -1) {
106
+ const ns = uri.slice(0, slashIdx + 1);
107
+ if (!Object.values(prefixes).includes(ns)) {
108
+ const pathSegments = ns.replace(/\/$/, '').split('/').filter(Boolean);
109
+ const candidate = pathSegments[pathSegments.length - 1]
110
+ ?.toLowerCase()
111
+ .replace(/[^a-z0-9]/g, '')
112
+ .slice(0, 8);
113
+ if (candidate && !(candidate in prefixes)) {
114
+ prefixes[candidate] = ns;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ }
121
+ return prefixes;
122
+ }
123
+ // ── Query scoping ─────────────────────────────────────────────────────
124
+ /**
125
+ * Returns true if the query already manages its own graph scoping.
126
+ * Matches GRAPH, FROM NAMED, or FROM < (case-insensitive).
127
+ */
128
+ export function hasGraphScope(sparql) {
129
+ return /\bGRAPH\b|\bFROM\s+NAMED\b|\bFROM\s*</i.test(sparql);
130
+ }
131
+ /**
132
+ * Wraps the WHERE body of a SPARQL SELECT/CONSTRUCT/ASK/DESCRIBE query so
133
+ * that all triple patterns are scoped to `graphUri`.
134
+ *
135
+ * Handles:
136
+ * SELECT ... WHERE { ... } -> standard form
137
+ * SELECT ... { ... } -> shorthand (no WHERE keyword)
138
+ *
139
+ * Returns null if the outermost `{ ... }` block cannot be located safely.
140
+ */
141
+ export function autoScopeQuery(sparql, graphUri) {
142
+ // Find the first `{` that opens the body.
143
+ // We look for WHERE { or, as a fallback, any { after the projection clause.
144
+ const whereMatch = sparql.match(/\bWHERE\s*\{/i);
145
+ let braceStart;
146
+ if (whereMatch && whereMatch.index !== undefined) {
147
+ // Position of `{` inside `WHERE {`
148
+ braceStart = whereMatch.index + whereMatch[0].length - 1;
149
+ }
150
+ else {
151
+ // Shorthand: find the first `{`
152
+ const firstBrace = sparql.indexOf('{');
153
+ if (firstBrace === -1)
154
+ return null;
155
+ braceStart = firstBrace;
156
+ }
157
+ // Walk forward to find the matching closing `}` (respecting nesting).
158
+ let depth = 0;
159
+ let braceEnd = -1;
160
+ for (let i = braceStart; i < sparql.length; i++) {
161
+ if (sparql[i] === '{')
162
+ depth++;
163
+ else if (sparql[i] === '}') {
164
+ depth--;
165
+ if (depth === 0) {
166
+ braceEnd = i;
167
+ break;
168
+ }
169
+ }
170
+ }
171
+ if (braceEnd === -1)
172
+ return null;
173
+ const before = sparql.slice(0, braceStart + 1); // up to and including `{`
174
+ const inner = sparql.slice(braceStart + 1, braceEnd); // content between `{ ... }`
175
+ const after = sparql.slice(braceEnd); // from `}` onwards
176
+ return `${before} GRAPH <${graphUri}> {${inner}} ${after}`;
177
+ }
178
+ /**
179
+ * Auto-scope a SPARQL query with UNION over asserted + inference graphs.
180
+ * Returns null if brace matching fails.
181
+ */
182
+ export function autoScopeQueryWithInference(sparql, graphUri, inferenceGraphUri) {
183
+ const whereMatch = sparql.match(/\bWHERE\s*\{/i);
184
+ let braceStart;
185
+ if (whereMatch && whereMatch.index !== undefined) {
186
+ braceStart = whereMatch.index + whereMatch[0].length - 1;
187
+ }
188
+ else {
189
+ const firstBrace = sparql.indexOf('{');
190
+ if (firstBrace === -1)
191
+ return null;
192
+ braceStart = firstBrace;
193
+ }
194
+ let depth = 0;
195
+ let braceEnd = -1;
196
+ for (let i = braceStart; i < sparql.length; i++) {
197
+ if (sparql[i] === '{')
198
+ depth++;
199
+ else if (sparql[i] === '}') {
200
+ depth--;
201
+ if (depth === 0) {
202
+ braceEnd = i;
203
+ break;
204
+ }
205
+ }
206
+ }
207
+ if (braceEnd === -1)
208
+ return null;
209
+ const before = sparql.slice(0, braceStart + 1);
210
+ const inner = sparql.slice(braceStart + 1, braceEnd);
211
+ const after = sparql.slice(braceEnd);
212
+ return `${before} GRAPH ?__g {${inner}} FILTER(?__g = <${graphUri}> || ?__g = <${inferenceGraphUri}>) ${after}`;
213
+ }
214
+ // ── Inference graph URI ───────────────────────────────────────────────
215
+ export function getInferenceGraphUri(graphUri) {
216
+ return `${graphUri}/inferred`;
217
+ }
@@ -0,0 +1,50 @@
1
+ export interface SparqlResults {
2
+ head: {
3
+ vars: string[];
4
+ };
5
+ results: {
6
+ bindings: Array<Record<string, {
7
+ type: string;
8
+ value: string;
9
+ datatype?: string;
10
+ }>>;
11
+ };
12
+ }
13
+ export interface StoreAdapter {
14
+ sparqlQuery(query: string): Promise<SparqlResults>;
15
+ askQuery(query: string): Promise<boolean>;
16
+ sparqlUpdate(update: string): Promise<void>;
17
+ constructQuery(query: string): Promise<string>;
18
+ insertTurtle(graphUri: string, turtle: string): Promise<void>;
19
+ dropGraph(graphUri: string): Promise<void>;
20
+ deleteTriples(graphUri: string, options: {
21
+ turtle?: string;
22
+ where?: string;
23
+ }): Promise<void>;
24
+ getGraphTripleCount(graphUri: string): Promise<number>;
25
+ exportGraph(graphUri: string): Promise<string>;
26
+ diffGraph(graphUri: string, localTurtle: string): Promise<{
27
+ added: string[];
28
+ removed: string[];
29
+ unchanged: number;
30
+ }>;
31
+ getSchemaOverview(graphUri: string): Promise<{
32
+ prefixes: Record<string, string>;
33
+ classes: string[];
34
+ properties: string[];
35
+ tripleCount: number;
36
+ }>;
37
+ getClassDetails(graphUri: string, classUri: string): Promise<{
38
+ classUri: string;
39
+ instanceCount: number;
40
+ properties: Array<{
41
+ property: string;
42
+ count: number;
43
+ }>;
44
+ sampleTriples: Array<{
45
+ s: string;
46
+ p: string;
47
+ o: string;
48
+ }>;
49
+ }>;
50
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { StoreAdapter } from './store-adapter.js';
2
+ import type { OpenTologyConfig } from './config.js';
3
+ export declare function createAdapter(config: OpenTologyConfig): StoreAdapter;
4
+ /**
5
+ * Reset the cached embedded adapter. Useful for tests or after config-level
6
+ * changes that invalidate the entire store (e.g. project re-init).
7
+ */
8
+ export declare function resetAdapterCache(): void;
9
+ export declare function createReadyAdapter(config: OpenTologyConfig): Promise<StoreAdapter>;
@@ -0,0 +1,71 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { getTrackedFiles } from './config.js';
4
+ import { HttpAdapter } from './http-adapter.js';
5
+ import { EmbeddedAdapter } from './embedded-adapter.js';
6
+ import { materializeInferences } from './reasoner.js';
7
+ // Singleton cache for embedded mode — keeps data alive across MCP tool calls.
8
+ let cachedAdapter = null;
9
+ let loadedFileKeys = new Set();
10
+ export function createAdapter(config) {
11
+ if (config.mode === 'embedded') {
12
+ return new EmbeddedAdapter();
13
+ }
14
+ return new HttpAdapter(config.endpoint ?? 'http://localhost:7878');
15
+ }
16
+ /**
17
+ * Reset the cached embedded adapter. Useful for tests or after config-level
18
+ * changes that invalidate the entire store (e.g. project re-init).
19
+ */
20
+ export function resetAdapterCache() {
21
+ cachedAdapter = null;
22
+ loadedFileKeys = new Set();
23
+ }
24
+ export async function createReadyAdapter(config) {
25
+ if (config.mode !== 'embedded') {
26
+ return new HttpAdapter(config.endpoint ?? 'http://localhost:7878');
27
+ }
28
+ // Reuse cached adapter so data persists across MCP tool calls.
29
+ if (!cachedAdapter) {
30
+ cachedAdapter = new EmbeddedAdapter();
31
+ loadedFileKeys = new Set();
32
+ }
33
+ const adapter = cachedAdapter;
34
+ // Incrementally load only newly-tracked files into the existing store.
35
+ const allGraphs = [config.graphUri, ...Object.keys(config.graphs ?? {}).map(k => config.graphs[k])];
36
+ let newFilesLoaded = false;
37
+ for (const graphUri of allGraphs) {
38
+ const files = getTrackedFiles(config, graphUri);
39
+ for (const f of files) {
40
+ const key = `${graphUri}::${resolve(f)}`;
41
+ if (!loadedFileKeys.has(key)) {
42
+ try {
43
+ const content = readFileSync(resolve(f), 'utf-8');
44
+ adapter.loadTurtleIntoGraph(content, graphUri);
45
+ loadedFileKeys.add(key);
46
+ newFilesLoaded = true;
47
+ }
48
+ catch {
49
+ // File may have been deleted — skip silently
50
+ }
51
+ }
52
+ }
53
+ }
54
+ // Re-materialize inferences only when new file data was loaded.
55
+ if (newFilesLoaded) {
56
+ const allGraphUris = new Set();
57
+ allGraphUris.add(config.graphUri);
58
+ if (config.graphs) {
59
+ for (const uri of Object.values(config.graphs)) {
60
+ allGraphUris.add(uri);
61
+ }
62
+ }
63
+ for (const graphUri of allGraphUris) {
64
+ const count = await adapter.getGraphTripleCount(graphUri);
65
+ if (count > 0) {
66
+ await materializeInferences(adapter, graphUri);
67
+ }
68
+ }
69
+ }
70
+ return adapter;
71
+ }
@@ -0,0 +1,10 @@
1
+ export type ValidationResult = {
2
+ valid: true;
3
+ tripleCount: number;
4
+ prefixes: Record<string, string>;
5
+ } | {
6
+ valid: false;
7
+ error: string;
8
+ };
9
+ export declare function validateTurtle(content: string): Promise<ValidationResult>;
10
+ export declare function validateTurtleFile(filePath: string): Promise<ValidationResult>;
@@ -0,0 +1,40 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { Parser } from 'n3';
3
+ export async function validateTurtle(content) {
4
+ return new Promise((resolve) => {
5
+ const parser = new Parser();
6
+ const quads = [];
7
+ const prefixes = {};
8
+ parser.parse(content, (err, quad, prefixMap) => {
9
+ if (err) {
10
+ resolve({ valid: false, error: err.message });
11
+ return;
12
+ }
13
+ if (quad) {
14
+ quads.push(quad);
15
+ }
16
+ else {
17
+ // Done — prefixMap is provided as the third argument on completion
18
+ if (prefixMap) {
19
+ for (const [prefix, iri] of Object.entries(prefixMap)) {
20
+ prefixes[prefix] = iri.value ?? String(iri);
21
+ }
22
+ }
23
+ resolve({ valid: true, tripleCount: quads.length, prefixes });
24
+ }
25
+ });
26
+ });
27
+ }
28
+ export async function validateTurtleFile(filePath) {
29
+ let content;
30
+ try {
31
+ content = readFileSync(filePath, 'utf-8');
32
+ }
33
+ catch (err) {
34
+ return {
35
+ valid: false,
36
+ error: `Could not read file '${filePath}': ${err.message}`,
37
+ };
38
+ }
39
+ return validateTurtle(content);
40
+ }
@@ -0,0 +1,3 @@
1
+ export declare const MAX_TRIPLES_PER_PUSH = 100;
2
+ export declare function assertTripleLimit(tripleCount: number): void;
3
+ export declare function startMcpServer(): Promise<void>;