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,47 @@
1
+ import pc from 'picocolors';
2
+ import { loadConfig, resolveGraphUri } from '../lib/config.js';
3
+ import { createReadyAdapter } from '../lib/store-factory.js';
4
+ import { materializeInferences, clearInferences } from '../lib/reasoner.js';
5
+ export function registerInfer(program) {
6
+ program
7
+ .command('infer')
8
+ .description('Run RDFS inference on the project graph')
9
+ .option('--clear', 'Clear the inference graph')
10
+ .option('--graph <name>', 'Target a specific named graph')
11
+ .action(async (opts) => {
12
+ let config;
13
+ try {
14
+ config = loadConfig();
15
+ }
16
+ catch (err) {
17
+ console.error(`Error: ${err.message}`);
18
+ process.exit(1);
19
+ }
20
+ const graphUri = opts.graph ? resolveGraphUri(config, opts.graph) : config.graphUri;
21
+ try {
22
+ const adapter = await createReadyAdapter(config);
23
+ if (opts.clear) {
24
+ await clearInferences(adapter, graphUri);
25
+ console.log(pc.green('Cleared inference graph'));
26
+ return;
27
+ }
28
+ const result = await materializeInferences(adapter, graphUri);
29
+ console.log(pc.green(`Inferred ${pc.cyan(String(result.inferredCount))} triples`));
30
+ const activeRules = Object.entries(result.rules).filter(([, n]) => n > 0);
31
+ if (activeRules.length > 0) {
32
+ const breakdown = activeRules.map(([rule, n]) => `${rule}: ${pc.cyan(String(n))}`).join(', ');
33
+ console.log(breakdown);
34
+ }
35
+ }
36
+ catch (err) {
37
+ const message = err.message;
38
+ if (message.includes('fetch failed') || message.includes('ECONNREFUSED')) {
39
+ console.error(`Cannot connect to Oxigraph at ${config.endpoint ?? 'unknown'}. Is it running? Start with: docker compose up -d`);
40
+ }
41
+ else {
42
+ console.error(`Error: ${message}`);
43
+ }
44
+ process.exit(1);
45
+ }
46
+ });
47
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerInit(program: Command): void;
@@ -0,0 +1,53 @@
1
+ import { basename } from 'node:path';
2
+ import pc from 'picocolors';
3
+ import { configExists, saveConfig } from '../lib/config.js';
4
+ const DEFAULT_PREFIXES = {
5
+ rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
6
+ rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
7
+ owl: 'http://www.w3.org/2002/07/owl#',
8
+ xsd: 'http://www.w3.org/2001/XMLSchema#',
9
+ schema: 'http://schema.org/',
10
+ };
11
+ function slugify(name) {
12
+ return name
13
+ .toLowerCase()
14
+ .replace(/[^a-z0-9]+/g, '-')
15
+ .replace(/^-|-$/g, '');
16
+ }
17
+ export function registerInit(program) {
18
+ program
19
+ .command('init [projectId]')
20
+ .description('Initialize a new OpenTology project')
21
+ .option('--embedded', 'Use embedded mode (no server needed)')
22
+ .action((projectId, opts) => {
23
+ if (configExists()) {
24
+ console.error('Error: .opentology.json already exists in this directory.');
25
+ console.error('Remove it first if you want to re-initialize.');
26
+ process.exit(1);
27
+ }
28
+ const id = projectId || slugify(basename(process.cwd()));
29
+ if (!id) {
30
+ console.error('Error: Could not determine project ID. Please provide one explicitly.');
31
+ process.exit(1);
32
+ }
33
+ const graphUri = `https://opentology.dev/${id}`;
34
+ if (opts.embedded) {
35
+ saveConfig({ projectId: id, mode: 'embedded', graphUri, prefixes: DEFAULT_PREFIXES });
36
+ console.log(pc.green(`Initialized OpenTology project.`));
37
+ console.log(` Project ID: ${id}`);
38
+ console.log(` Mode: embedded (no server needed)`);
39
+ console.log(` Graph URI: ${graphUri}`);
40
+ console.log(`\nConfig saved to .opentology.json`);
41
+ }
42
+ else {
43
+ const endpoint = 'http://localhost:7878';
44
+ saveConfig({ projectId: id, mode: 'http', endpoint, graphUri, prefixes: DEFAULT_PREFIXES });
45
+ console.log(pc.green(`Initialized OpenTology project.`));
46
+ console.log(` Project ID: ${id}`);
47
+ console.log(` Mode: http`);
48
+ console.log(` Endpoint: ${endpoint}`);
49
+ console.log(` Graph URI: ${graphUri}`);
50
+ console.log(`\nConfig saved to .opentology.json`);
51
+ }
52
+ });
53
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerMcp(program: Command): void;
@@ -0,0 +1,9 @@
1
+ import { startMcpServer } from '../mcp/server.js';
2
+ export function registerMcp(program) {
3
+ program
4
+ .command('mcp')
5
+ .description('Start MCP server for AI agent integration')
6
+ .action(async () => {
7
+ await startMcpServer();
8
+ });
9
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerPrefix(program: Command): void;
@@ -0,0 +1,73 @@
1
+ import pc from 'picocolors';
2
+ import { loadConfig, saveConfig } from '../lib/config.js';
3
+ export function registerPrefix(program) {
4
+ const prefix = program
5
+ .command('prefix')
6
+ .description('Manage project-level SPARQL prefix declarations');
7
+ prefix
8
+ .command('list')
9
+ .description('Show all registered prefixes')
10
+ .action(() => {
11
+ let config;
12
+ try {
13
+ config = loadConfig();
14
+ }
15
+ catch (err) {
16
+ console.error(`Error: ${err.message}`);
17
+ process.exit(1);
18
+ }
19
+ const prefixes = config.prefixes ?? {};
20
+ const entries = Object.entries(prefixes);
21
+ if (entries.length === 0) {
22
+ console.log('No prefixes registered. Use `opentology prefix add <name> <uri>` to add one.');
23
+ return;
24
+ }
25
+ const maxName = Math.max(...entries.map(([n]) => n.length), 6);
26
+ console.log(`${'PREFIX'.padEnd(maxName)} URI`);
27
+ console.log(`${'-'.repeat(maxName)} ${'-'.repeat(40)}`);
28
+ for (const [name, uri] of entries) {
29
+ console.log(`${pc.cyan(name.padEnd(maxName))} ${uri}`);
30
+ }
31
+ });
32
+ prefix
33
+ .command('add <name> <uri>')
34
+ .description('Add a prefix mapping')
35
+ .action((name, uri) => {
36
+ let config;
37
+ try {
38
+ config = loadConfig();
39
+ }
40
+ catch (err) {
41
+ console.error(`Error: ${err.message}`);
42
+ process.exit(1);
43
+ }
44
+ if (!config.prefixes)
45
+ config.prefixes = {};
46
+ config.prefixes[name] = uri;
47
+ saveConfig(config);
48
+ console.log(pc.green(`Added prefix ${pc.cyan(name)}: → ${uri}`));
49
+ });
50
+ prefix
51
+ .command('remove <name>')
52
+ .description('Remove a prefix mapping')
53
+ .action((name) => {
54
+ let config;
55
+ try {
56
+ config = loadConfig();
57
+ }
58
+ catch (err) {
59
+ console.error(`Error: ${err.message}`);
60
+ process.exit(1);
61
+ }
62
+ if (!config.prefixes?.[name]) {
63
+ console.error(`Error: prefix '${name}' is not registered.`);
64
+ process.exit(1);
65
+ }
66
+ delete config.prefixes[name];
67
+ if (Object.keys(config.prefixes).length === 0) {
68
+ config.prefixes = undefined;
69
+ }
70
+ saveConfig(config);
71
+ console.log(pc.green(`Removed prefix ${pc.cyan(name)}`));
72
+ });
73
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerPull(program: Command): void;
@@ -0,0 +1,43 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ import { loadConfig, resolveGraphUri } from '../lib/config.js';
3
+ import { createReadyAdapter } from '../lib/store-factory.js';
4
+ export function registerPull(program) {
5
+ program
6
+ .command('pull [output]')
7
+ .description('Export graph from triplestore as Turtle')
8
+ .option('--graph <name>', 'Target a specific named graph')
9
+ .action(async (output, opts) => {
10
+ let config;
11
+ try {
12
+ config = loadConfig();
13
+ }
14
+ catch (err) {
15
+ console.error(`Error: ${err.message}`);
16
+ process.exit(1);
17
+ }
18
+ const graphUri = opts.graph ? resolveGraphUri(config, opts.graph) : config.graphUri;
19
+ try {
20
+ const adapter = await createReadyAdapter(config);
21
+ const turtle = await adapter.exportGraph(graphUri);
22
+ const count = await adapter.getGraphTripleCount(graphUri);
23
+ if (output) {
24
+ writeFileSync(output, turtle, 'utf-8');
25
+ console.log(`Exported ${count} triples to ${output}`);
26
+ }
27
+ else if (process.stdout.isTTY) {
28
+ // Interactive terminal — write to default file
29
+ const filename = `${config.projectId}.ttl`;
30
+ writeFileSync(filename, turtle, 'utf-8');
31
+ console.log(`Exported ${count} triples to ${filename}`);
32
+ }
33
+ else {
34
+ // Piped — write turtle to stdout
35
+ process.stdout.write(turtle);
36
+ }
37
+ }
38
+ catch (err) {
39
+ console.error(`Error: ${err.message}`);
40
+ process.exit(1);
41
+ }
42
+ });
43
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerPush(program: Command): void;
@@ -0,0 +1,79 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import pc from 'picocolors';
3
+ import { loadConfig, resolveGraphUri, saveConfig, addTrackedFile } from '../lib/config.js';
4
+ import { createReadyAdapter } from '../lib/store-factory.js';
5
+ import { validateTurtleFile } from '../lib/validator.js';
6
+ import { discoverShapes, validateWithShacl, hasShapes } from '../lib/shacl.js';
7
+ import { materializeInferences } from '../lib/reasoner.js';
8
+ export function registerPush(program) {
9
+ program
10
+ .command('push <file>')
11
+ .description('Push a Turtle file to the triplestore')
12
+ .option('--replace', 'Replace entire graph contents (drop + push)')
13
+ .option('--no-shacl', 'Skip SHACL validation')
14
+ .option('--no-infer', 'Skip RDFS inference after push')
15
+ .option('--graph <name>', 'Target a specific named graph')
16
+ .action(async (file, opts) => {
17
+ let config;
18
+ try {
19
+ config = loadConfig();
20
+ }
21
+ catch (err) {
22
+ console.error(`Error: ${err.message}`);
23
+ process.exit(1);
24
+ }
25
+ const graphUri = opts.graph ? resolveGraphUri(config, opts.graph) : config.graphUri;
26
+ try {
27
+ const result = await validateTurtleFile(file);
28
+ if (!result.valid) {
29
+ console.error(`Validation failed: ${result.error}`);
30
+ process.exit(1);
31
+ }
32
+ const turtle = readFileSync(file, 'utf-8');
33
+ // Auto-validate against SHACL when shapes exist (unless --no-shacl)
34
+ if (opts.shacl !== false && hasShapes()) {
35
+ const shapePaths = discoverShapes();
36
+ const report = await validateWithShacl(turtle, shapePaths);
37
+ if (!report.conforms) {
38
+ for (const v of report.violations) {
39
+ console.error(pc.red(`SHACL Violation: ${v.focusNode} — ${v.message} (path: ${v.path})`));
40
+ }
41
+ process.exit(1);
42
+ }
43
+ console.log('SHACL: conforms');
44
+ }
45
+ const adapter = await createReadyAdapter(config);
46
+ if (opts.replace) {
47
+ await adapter.dropGraph(graphUri);
48
+ }
49
+ await adapter.insertTurtle(graphUri, turtle);
50
+ // In embedded mode, track the file
51
+ if (config.mode === 'embedded') {
52
+ addTrackedFile(config, graphUri, file);
53
+ saveConfig(config);
54
+ }
55
+ if (opts.replace) {
56
+ console.log(pc.green(`Replaced graph with ${result.tripleCount} triples`));
57
+ }
58
+ else {
59
+ console.log(pc.green(`Pushed ${result.tripleCount} triples to ${graphUri}`));
60
+ }
61
+ if (opts.infer !== false) {
62
+ const inference = await materializeInferences(adapter, graphUri);
63
+ if (inference.inferredCount > 0) {
64
+ console.log(pc.green(`Inferred ${pc.cyan(String(inference.inferredCount))} additional triples`));
65
+ }
66
+ }
67
+ }
68
+ catch (err) {
69
+ const message = err.message;
70
+ if (message.includes('fetch failed') || message.includes('ECONNREFUSED')) {
71
+ console.error(`Cannot connect to Oxigraph at ${config.endpoint ?? 'unknown'}. Is it running? Start with: docker compose up -d`);
72
+ }
73
+ else {
74
+ console.error(`Error: ${message}`);
75
+ }
76
+ process.exit(1);
77
+ }
78
+ });
79
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerQuery(program: Command): void;
@@ -0,0 +1,119 @@
1
+ import { loadConfig, resolveGraphUri } from '../lib/config.js';
2
+ import { createReadyAdapter } from '../lib/store-factory.js';
3
+ import { hasGraphScope, autoScopeQuery } from '../lib/sparql-utils.js';
4
+ function injectPrefixes(sparql, prefixes) {
5
+ const lines = Object.entries(prefixes)
6
+ .filter(([prefix]) => {
7
+ // Skip if the query already declares this prefix
8
+ const re = new RegExp(`PREFIX\\s+${prefix}\\s*:`, 'i');
9
+ return !re.test(sparql);
10
+ })
11
+ .map(([prefix, uri]) => `PREFIX ${prefix}: <${uri}>`);
12
+ if (lines.length === 0)
13
+ return sparql;
14
+ return lines.join('\n') + '\n' + sparql;
15
+ }
16
+ function formatTable(vars, bindings) {
17
+ if (bindings.length === 0) {
18
+ return '(no results)';
19
+ }
20
+ // Calculate column widths
21
+ const widths = {};
22
+ for (const v of vars) {
23
+ widths[v] = v.length;
24
+ }
25
+ for (const row of bindings) {
26
+ for (const v of vars) {
27
+ const val = row[v]?.value ?? '';
28
+ widths[v] = Math.max(widths[v], val.length);
29
+ }
30
+ }
31
+ // Build header
32
+ const header = vars.map(v => v.padEnd(widths[v])).join(' ');
33
+ const separator = vars.map(v => '-'.repeat(widths[v])).join(' ');
34
+ // Build rows
35
+ const rows = bindings.map(row => vars.map(v => (row[v]?.value ?? '').padEnd(widths[v])).join(' '));
36
+ return [header, separator, ...rows].join('\n');
37
+ }
38
+ function formatCsv(vars, bindings) {
39
+ const header = vars.join(',');
40
+ const rows = bindings.map(row => vars.map(v => {
41
+ const val = row[v]?.value ?? '';
42
+ // Escape values containing commas, quotes, or newlines
43
+ if (val.includes(',') || val.includes('"') || val.includes('\n')) {
44
+ return `"${val.replace(/"/g, '""')}"`;
45
+ }
46
+ return val;
47
+ }).join(','));
48
+ return [header, ...rows].join('\n');
49
+ }
50
+ export function registerQuery(program) {
51
+ program
52
+ .command('query <sparql>')
53
+ .description('Run a SPARQL query against the triplestore')
54
+ .option('--format <type>', 'Output format: table, json, csv', 'table')
55
+ .option('--json', 'Output raw JSON (alias for --format json)')
56
+ .option('--raw', 'Skip automatic Named Graph scoping')
57
+ .option('--graph <name>', 'Target a specific named graph')
58
+ .action(async (sparql, options) => {
59
+ let config;
60
+ try {
61
+ config = loadConfig();
62
+ }
63
+ catch (err) {
64
+ console.error(`Error: ${err.message}`);
65
+ process.exit(1);
66
+ }
67
+ const graphUri = options.graph ? resolveGraphUri(config, options.graph) : config.graphUri;
68
+ // Resolve format: --json flag overrides --format
69
+ const format = options.json ? 'json' : (options.format || 'table');
70
+ // Inject project-level PREFIX declarations from config
71
+ let effectiveSparql = sparql;
72
+ if (config.prefixes) {
73
+ effectiveSparql = injectPrefixes(effectiveSparql, config.prefixes);
74
+ }
75
+ // Auto-scope the query to the project's Named Graph unless the user
76
+ // has already specified graph scoping or passed --raw.
77
+ if (!options.raw && !hasGraphScope(effectiveSparql)) {
78
+ const scoped = autoScopeQuery(effectiveSparql, graphUri);
79
+ if (scoped !== null) {
80
+ effectiveSparql = scoped;
81
+ }
82
+ else {
83
+ // Transformation failed — run as-is and warn.
84
+ console.warn(`Warning: could not auto-scope query. Add GRAPH <${graphUri}> manually or use --raw.`);
85
+ }
86
+ }
87
+ try {
88
+ const adapter = await createReadyAdapter(config);
89
+ const results = await adapter.sparqlQuery(effectiveSparql);
90
+ switch (format) {
91
+ case 'json':
92
+ console.log(JSON.stringify(results, null, 2));
93
+ break;
94
+ case 'csv':
95
+ console.log(formatCsv(results.head.vars, results.results.bindings));
96
+ break;
97
+ default: {
98
+ const output = formatTable(results.head.vars, results.results.bindings);
99
+ console.log(output);
100
+ if (results.results.bindings.length === 0) {
101
+ console.log(`\nHint: use GRAPH <${graphUri}> in your WHERE clause`);
102
+ }
103
+ break;
104
+ }
105
+ }
106
+ }
107
+ catch (err) {
108
+ const message = err.message;
109
+ console.error(`Error: ${message}`);
110
+ if (message.includes('fetch failed') || message.includes('ECONNREFUSED')) {
111
+ console.error(`Cannot connect to triplestore at ${config.endpoint ?? 'unknown'}. Is it running? Start with: docker compose up -d`);
112
+ }
113
+ if (message.toLowerCase().includes('parse') && sparql.includes('\\!')) {
114
+ console.error(`Hint: if your query contains !=, your shell may have escaped the '!' character. Try wrapping the query in $'...' or use FILTER(?x NOT IN (<uri>)) instead.`);
115
+ }
116
+ process.exit(1);
117
+ }
118
+ });
119
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerShapes(program: Command): void;
@@ -0,0 +1,67 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { basename } from 'node:path';
3
+ import { Parser } from 'n3';
4
+ import { discoverShapes } from '../lib/shacl.js';
5
+ /**
6
+ * Parse a Turtle file and extract sh:targetClass values.
7
+ */
8
+ function extractTargetClasses(turtle) {
9
+ const classes = [];
10
+ const parser = new Parser();
11
+ const quads = parser.parse(turtle);
12
+ for (const quad of quads) {
13
+ if (quad.predicate.value === 'http://www.w3.org/ns/shacl#targetClass') {
14
+ classes.push(quad.object.value);
15
+ }
16
+ }
17
+ return classes;
18
+ }
19
+ export function registerShapes(program) {
20
+ const shapes = program
21
+ .command('shapes')
22
+ .description('List and inspect SHACL shapes');
23
+ shapes
24
+ .command('list', { isDefault: true })
25
+ .description('List all shapes in shapes/ directory')
26
+ .action(() => {
27
+ const shapePaths = discoverShapes();
28
+ if (shapePaths.length === 0) {
29
+ console.log('No shapes found in shapes/ directory');
30
+ return;
31
+ }
32
+ console.log('Shapes:');
33
+ console.log('');
34
+ console.log(padRight('File', 30) + padRight('Target Classes', 50));
35
+ console.log(padRight('—', 30, '—') + padRight('—', 50, '—'));
36
+ for (const shapePath of shapePaths) {
37
+ const name = basename(shapePath);
38
+ try {
39
+ const content = readFileSync(shapePath, 'utf-8');
40
+ const targets = extractTargetClasses(content);
41
+ const targetStr = targets.length > 0 ? targets.join(', ') : '(none)';
42
+ console.log(padRight(name, 30) + padRight(targetStr, 50));
43
+ }
44
+ catch {
45
+ console.log(padRight(name, 30) + padRight('(parse error)', 50));
46
+ }
47
+ }
48
+ });
49
+ shapes
50
+ .command('show <file>')
51
+ .description('Display a shape file\'s contents')
52
+ .action((file) => {
53
+ const shapePaths = discoverShapes();
54
+ const match = shapePaths.find((p) => basename(p) === file || p === file);
55
+ if (!match) {
56
+ console.error(`Shape file not found: ${file}`);
57
+ process.exit(1);
58
+ }
59
+ const content = readFileSync(match, 'utf-8');
60
+ console.log(content);
61
+ });
62
+ }
63
+ function padRight(str, len, fill = ' ') {
64
+ if (str.length >= len)
65
+ return str;
66
+ return str + fill.repeat(len - str.length);
67
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerStatus(program: Command): void;
@@ -0,0 +1,47 @@
1
+ import pc from 'picocolors';
2
+ import { loadConfig, resolveGraphUri, getTrackedFiles } from '../lib/config.js';
3
+ import { createReadyAdapter } from '../lib/store-factory.js';
4
+ import { getInferenceGraphUri } from '../lib/sparql-utils.js';
5
+ export function registerStatus(program) {
6
+ program
7
+ .command('status')
8
+ .description('Show project status and triplestore info')
9
+ .option('--graph <name>', 'Target a specific named graph')
10
+ .action(async (opts) => {
11
+ let config;
12
+ try {
13
+ config = loadConfig();
14
+ }
15
+ catch (err) {
16
+ console.error(`Error: ${err.message}`);
17
+ process.exit(1);
18
+ }
19
+ const graphUri = opts.graph ? resolveGraphUri(config, opts.graph) : config.graphUri;
20
+ console.log(`${pc.cyan('Project:')} ${config.projectId}`);
21
+ console.log(`${pc.cyan('Graph URI:')} ${graphUri}`);
22
+ console.log(`${pc.cyan('Mode:')} ${config.mode}`);
23
+ if (config.endpoint) {
24
+ console.log(`${pc.cyan('Endpoint:')} ${config.endpoint}`);
25
+ }
26
+ if (opts.graph) {
27
+ console.log(`${pc.cyan('Graph:')} ${opts.graph}`);
28
+ }
29
+ if (config.mode === 'embedded') {
30
+ const trackedCount = getTrackedFiles(config, graphUri).length;
31
+ console.log(`${pc.cyan('Tracked files:')} ${trackedCount}`);
32
+ }
33
+ try {
34
+ const adapter = await createReadyAdapter(config);
35
+ const inferenceGraphUri = getInferenceGraphUri(graphUri);
36
+ const assertedCount = await adapter.getGraphTripleCount(graphUri);
37
+ const inferredCount = await adapter.getGraphTripleCount(inferenceGraphUri);
38
+ const totalCount = assertedCount + inferredCount;
39
+ console.log(`${pc.cyan('Triples (asserted):')} ${assertedCount}`);
40
+ console.log(`${pc.cyan('Triples (inferred):')} ${inferredCount}`);
41
+ console.log(`${pc.cyan('Triples (total):')} ${totalCount}`);
42
+ }
43
+ catch {
44
+ console.log(`${pc.cyan('Triples:')} Cannot connect to triplestore. Is it running?`);
45
+ }
46
+ });
47
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare function registerValidate(program: Command): void;
@@ -0,0 +1,46 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import pc from 'picocolors';
3
+ import { validateTurtleFile } from '../lib/validator.js';
4
+ import { discoverShapes, validateWithShacl } from '../lib/shacl.js';
5
+ export function registerValidate(program) {
6
+ program
7
+ .command('validate <file>')
8
+ .description('Validate a local Turtle/RDF file')
9
+ .option('--shacl', 'Also validate against SHACL shapes in shapes/ directory')
10
+ .action(async (file, opts) => {
11
+ try {
12
+ const result = await validateTurtleFile(file);
13
+ if (result.valid) {
14
+ const prefixList = Object.keys(result.prefixes).join(', ') || '(none)';
15
+ console.log(pc.green(`Valid — ${result.tripleCount} triples, prefixes: ${prefixList}`));
16
+ }
17
+ else {
18
+ console.error(pc.red(`Validation failed: ${result.error}`));
19
+ process.exit(1);
20
+ }
21
+ if (opts.shacl) {
22
+ const shapePaths = discoverShapes();
23
+ if (shapePaths.length === 0) {
24
+ console.log('SHACL: no shapes found in shapes/ directory');
25
+ }
26
+ else {
27
+ const content = readFileSync(file, 'utf-8');
28
+ const report = await validateWithShacl(content, shapePaths);
29
+ if (report.conforms) {
30
+ console.log(pc.green('SHACL: conforms'));
31
+ }
32
+ else {
33
+ for (const v of report.violations) {
34
+ console.error(pc.yellow(`SHACL Violation: ${v.focusNode} — ${v.message} (path: ${v.path})`));
35
+ }
36
+ process.exit(1);
37
+ }
38
+ }
39
+ }
40
+ }
41
+ catch (err) {
42
+ console.error(`Error: ${err.message}`);
43
+ process.exit(1);
44
+ }
45
+ });
46
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};