opentology 0.2.2 → 0.2.4

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.
@@ -11,6 +11,7 @@ import { generateContextSection, updateClaudeMd } from '../templates/claude-md-c
11
11
  import { generateHookScript } from '../templates/session-start-hook.js';
12
12
  import { generatePreEditHookScript } from '../templates/pre-edit-hook.js';
13
13
  import { generateSlashCommands } from '../templates/slash-commands.js';
14
+ import { normalizeModuleUri } from '../lib/module-uri.js';
14
15
  function ask(question) {
15
16
  const rl = createInterface({ input: process.stdin, output: process.stdout });
16
17
  return new Promise((resolve) => {
@@ -177,9 +178,16 @@ export function registerContext(program) {
177
178
  const sessionStartCmd = 'node .opentology/hooks/session-start.mjs';
178
179
  if (!hooks.SessionStart)
179
180
  hooks.SessionStart = [];
180
- const hasSessionHook = hooks.SessionStart.some((h) => h.command === sessionStartCmd);
181
+ const hasSessionHook = hooks.SessionStart.some((h) => {
182
+ const entry = h;
183
+ const entryHooks = entry.hooks;
184
+ return entryHooks?.some((hook) => hook.command === sessionStartCmd);
185
+ });
181
186
  if (!hasSessionHook) {
182
- hooks.SessionStart.push({ type: 'command', command: sessionStartCmd });
187
+ hooks.SessionStart.push({
188
+ matcher: '',
189
+ hooks: [{ type: 'command', command: sessionStartCmd }],
190
+ });
183
191
  console.log(pc.green(' Registered SessionStart hook in .claude/settings.json'));
184
192
  }
185
193
  // Ask about PreToolUse impact hook
@@ -191,12 +199,15 @@ export function registerContext(program) {
191
199
  const preEditCmd = 'node .opentology/hooks/pre-edit.mjs';
192
200
  if (!hooks.PreToolUse)
193
201
  hooks.PreToolUse = [];
194
- const hasPreEditHook = hooks.PreToolUse.some((h) => h.command === preEditCmd);
202
+ const hasPreEditHook = hooks.PreToolUse.some((h) => {
203
+ const entry = h;
204
+ const entryHooks = entry.hooks;
205
+ return entryHooks?.some((hook) => hook.command === preEditCmd);
206
+ });
195
207
  if (!hasPreEditHook) {
196
208
  hooks.PreToolUse.push({
197
- type: 'command',
198
- command: preEditCmd,
199
- matcher: { tool_name: 'Edit|Write' },
209
+ matcher: 'Edit|Write',
210
+ hooks: [{ type: 'command', command: preEditCmd }],
200
211
  });
201
212
  }
202
213
  console.log(pc.green(' Registered PreToolUse impact hook in .claude/settings.json'));
@@ -444,7 +455,7 @@ export function registerContext(program) {
444
455
  }
445
456
  const contextUri = graphs['context'];
446
457
  const OTX = 'https://opentology.dev/vocab#';
447
- const moduleUriStr = `urn:module:${opts.file}`;
458
+ const moduleUriStr = normalizeModuleUri(opts.file);
448
459
  try {
449
460
  const adapter = await createReadyAdapter(config);
450
461
  // Dependents (reverse deps)
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import { registerDoctor } from './commands/doctor.js';
20
20
  const program = new Command();
21
21
  program
22
22
  .name('opentology')
23
- .version('0.2.2')
23
+ .version('0.2.4')
24
24
  .description('CLI-managed RDF/SPARQL infrastructure — Supabase for RDF');
25
25
  registerInit(program);
26
26
  registerValidate(program);
@@ -2,6 +2,7 @@ import { readFile, readdir } from 'node:fs/promises';
2
2
  import { join, relative } from 'node:path';
3
3
  import { execFile } from 'node:child_process';
4
4
  import { promisify } from 'node:util';
5
+ import { stripSourceExtension } from './module-uri.js';
5
6
  const execFileAsync = promisify(execFile);
6
7
  const DEFAULT_MAX_BYTES = 15360;
7
8
  const HARDCODED_EXCLUDES = new Set(['.git', 'node_modules', 'dist', 'build', '.opentology', '.claude', '.omc']);
@@ -212,7 +213,7 @@ export async function extractDependencyGraph(rootDir, gitFiles) {
212
213
  const moduleSet = new Set();
213
214
  const edges = [];
214
215
  for (const filePath of sourceFiles) {
215
- const moduleName = filePath.replace(/\.[tj]sx?$/, '');
216
+ const moduleName = stripSourceExtension(filePath);
216
217
  moduleSet.add(moduleName);
217
218
  let content;
218
219
  try {
@@ -5,11 +5,11 @@
5
5
  import type { DeepScanResult } from './deep-scanner.js';
6
6
  export declare function generateSymbolTriples(result: DeepScanResult): string[];
7
7
  export declare function batchTriples(triples: string[], batchSize?: number): string[][];
8
- export interface StoreAdapter {
9
- sparqlUpdate(query: string): Promise<void>;
10
- }
8
+ import type { StoreAdapter as FullStoreAdapter } from './store-adapter.js';
9
+ type StoreAdapter = Pick<FullStoreAdapter, 'sparqlUpdate'>;
11
10
  export declare function deleteExistingSymbols(adapter: StoreAdapter, graphUri: string, modulePaths: string[]): Promise<void>;
12
11
  export declare function pushSymbolTriples(adapter: StoreAdapter, graphUri: string, result: DeepScanResult): Promise<{
13
12
  triplesInserted: number;
14
13
  batchCount: number;
15
14
  }>;
15
+ export {};
@@ -75,8 +75,6 @@ export function generateSymbolTriples(result) {
75
75
  }
76
76
  }
77
77
  for (const call of result.methodCalls) {
78
- const callerParts = call.caller.split('.');
79
- const calleeParts = call.callee.split('.');
80
78
  // Method calls reference by class.method pattern — generate a simple triple
81
79
  triples.push(`<urn:call:${encodeSegment(call.caller)}> <${OTX}calls> <urn:call:${encodeSegment(call.callee)}> .`);
82
80
  // Store caller/callee names for queryability
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared module URI normalization — single source of truth for
3
+ * converting file paths to urn:module: URIs.
4
+ */
5
+ export declare function stripSourceExtension(filePath: string): string;
6
+ export declare function normalizeModuleUri(filePath: string): string;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shared module URI normalization — single source of truth for
3
+ * converting file paths to urn:module: URIs.
4
+ */
5
+ const SOURCE_EXT_RE = /\.[tj]sx?$/;
6
+ export function stripSourceExtension(filePath) {
7
+ return filePath.replace(SOURCE_EXT_RE, '');
8
+ }
9
+ export function normalizeModuleUri(filePath) {
10
+ return `urn:module:${stripSourceExtension(filePath)}`;
11
+ }
@@ -1,19 +1,8 @@
1
1
  import type { StoreAdapter } from './store-adapter.js';
2
- interface Triple {
3
- s: string;
4
- p: string;
5
- o: string;
6
- isLiteral: boolean;
7
- }
8
2
  export interface InferenceResult {
9
3
  assertedCount: number;
10
4
  inferredCount: number;
11
5
  rules: Record<string, number>;
12
6
  }
13
- export declare function computeInferences(triples: Triple[]): {
14
- inferred: Triple[];
15
- rules: Record<string, number>;
16
- };
17
7
  export declare function clearInferences(adapter: StoreAdapter, graphUri: string): Promise<void>;
18
8
  export declare function materializeInferences(adapter: StoreAdapter, graphUri: string): Promise<InferenceResult>;
19
- export {};
@@ -68,7 +68,7 @@ function transitiveClosure(pairs) {
68
68
  return result;
69
69
  }
70
70
  // ── Pure inference engine ──────────────────────────────────────────────
71
- export function computeInferences(triples) {
71
+ function computeInferences(triples) {
72
72
  const assertedSet = new Set(triples.map(tripleKey));
73
73
  const inferredMap = new Map(); // tripleKey → rule name
74
74
  const rules = {};
@@ -1,10 +1,10 @@
1
- export interface ShaclViolation {
1
+ interface ShaclViolation {
2
2
  focusNode: string;
3
3
  path: string | null;
4
4
  message: string;
5
5
  severity: string;
6
6
  }
7
- export interface ShaclReport {
7
+ interface ShaclReport {
8
8
  conforms: boolean;
9
9
  violations: ShaclViolation[];
10
10
  }
@@ -20,3 +20,4 @@ export declare function hasShapes(shapesDir?: string): boolean;
20
20
  * Validate a Turtle data string against the given SHACL shape files.
21
21
  */
22
22
  export declare function validateWithShacl(dataTurtle: string, shapePaths: string[]): Promise<ShaclReport>;
23
+ export {};
@@ -20,9 +20,4 @@ export declare function hasGraphScope(sparql: string): boolean;
20
20
  * Returns null if the outermost `{ ... }` block cannot be located safely.
21
21
  */
22
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
23
  export declare function getInferenceGraphUri(graphUri: string): string;
@@ -175,42 +175,6 @@ export function autoScopeQuery(sparql, graphUri) {
175
175
  const after = sparql.slice(braceEnd); // from `}` onwards
176
176
  return `${before} GRAPH <${graphUri}> {${inner}} ${after}`;
177
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
178
  // ── Inference graph URI ───────────────────────────────────────────────
215
179
  export function getInferenceGraphUri(graphUri) {
216
180
  return `${graphUri}/inferred`;
@@ -1,6 +1,5 @@
1
1
  import type { StoreAdapter } from './store-adapter.js';
2
2
  import type { OpenTologyConfig } from './config.js';
3
- export declare function createAdapter(config: OpenTologyConfig): StoreAdapter;
4
3
  /**
5
4
  * Reset the cached embedded adapter. Useful for tests or after config-level
6
5
  * changes that invalidate the entire store (e.g. project re-init).
@@ -7,12 +7,6 @@ import { materializeInferences } from './reasoner.js';
7
7
  // Singleton cache for embedded mode — keeps data alive across MCP tool calls.
8
8
  let cachedAdapter = null;
9
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
10
  /**
17
11
  * Reset the cached embedded adapter. Useful for tests or after config-level
18
12
  * changes that invalidate the entire store (e.g. project re-init).
@@ -5,6 +5,7 @@ import { loadConfig, saveConfig, configExists, resolveGraphUri } from '../lib/co
5
5
  import { deepScan } from '../lib/deep-scanner.js';
6
6
  import { pushSymbolTriples } from '../lib/deep-scan-triples.js';
7
7
  import { createReadyAdapter } from '../lib/store-factory.js';
8
+ import { normalizeModuleUri } from '../lib/module-uri.js';
8
9
  import { hasGraphScope, autoScopeQuery, getInferenceGraphUri } from '../lib/sparql-utils.js';
9
10
  import { validateTurtle } from '../lib/validator.js';
10
11
  import { discoverShapes, validateWithShacl, hasShapes } from '../lib/shacl.js';
@@ -627,7 +628,7 @@ async function handleContextImpact(args) {
627
628
  const contextUri = `${config.graphUri}/context`;
628
629
  const adapter = await createReadyAdapter(config);
629
630
  const OTX = 'https://opentology.dev/vocab#';
630
- const moduleUriStr = `urn:module:${filePath}`;
631
+ const moduleUriStr = normalizeModuleUri(filePath);
631
632
  // 1. Modules that depend on this file (dependents / reverse deps)
632
633
  const dependentsQuery = `
633
634
  SELECT ?dependent WHERE {
@@ -1,4 +1,2 @@
1
- export declare const MARKER_BEGIN = "<!-- OPENTOLOGY:CONTEXT:BEGIN -->";
2
- export declare const MARKER_END = "<!-- OPENTOLOGY:CONTEXT:END -->";
3
1
  export declare function generateContextSection(projectId: string, graphUri: string): string;
4
2
  export declare function updateClaudeMd(filePath: string, section: string): void;
@@ -1,6 +1,6 @@
1
1
  import { readFileSync, writeFileSync, existsSync } from 'node:fs';
2
- export const MARKER_BEGIN = '<!-- OPENTOLOGY:CONTEXT:BEGIN -->';
3
- export const MARKER_END = '<!-- OPENTOLOGY:CONTEXT:END -->';
2
+ const MARKER_BEGIN = '<!-- OPENTOLOGY:CONTEXT:BEGIN -->';
3
+ const MARKER_END = '<!-- OPENTOLOGY:CONTEXT:END -->';
4
4
  export function generateContextSection(projectId, graphUri) {
5
5
  const contextUri = `${graphUri}/context`;
6
6
  const sessionsUri = `${graphUri}/sessions`;
@@ -1,2 +1 @@
1
1
  export declare const OTX_BOOTSTRAP_TURTLE = "@prefix otx: <https://opentology.dev/vocab#> .\n@prefix owl: <http://www.w3.org/2002/07/owl#> .\n@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .\n@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .\n\notx:Project a owl:Class .\notx:Decision a owl:Class .\notx:Issue a owl:Class .\notx:Knowledge a owl:Class .\notx:Session a owl:Class .\notx:Pattern a owl:Class .\notx:Module a owl:Class .\n\notx:title a owl:DatatypeProperty ; rdfs:range xsd:string .\notx:date a owl:DatatypeProperty ; rdfs:range xsd:date .\notx:body a owl:DatatypeProperty ; rdfs:range xsd:string .\notx:status a owl:DatatypeProperty ; rdfs:range xsd:string .\notx:reason a owl:DatatypeProperty ; rdfs:range xsd:string .\notx:cause a owl:DatatypeProperty ; rdfs:range xsd:string .\notx:solution a owl:DatatypeProperty ; rdfs:range xsd:string .\notx:nextTodo a owl:DatatypeProperty ; rdfs:range xsd:string .\notx:relatedTo a owl:ObjectProperty .\notx:project a owl:ObjectProperty .\notx:dependsOn a owl:ObjectProperty ; rdfs:domain otx:Module ; rdfs:range otx:Module .\notx:stack a owl:DatatypeProperty ; rdfs:range xsd:string .\notx:alternative a owl:DatatypeProperty ; rdfs:range xsd:string .\n\notx:Class a owl:Class .\notx:Interface a owl:Class .\notx:Function a owl:Class .\notx:Method a owl:Class .\n\notx:definedIn a owl:ObjectProperty ; rdfs:range otx:Module .\notx:extends a owl:ObjectProperty ; rdfs:domain otx:Class ; rdfs:range otx:Class .\notx:implements a owl:ObjectProperty ; rdfs:domain otx:Class ; rdfs:range otx:Interface .\notx:hasMethod a owl:ObjectProperty ; rdfs:domain otx:Class ; rdfs:range otx:Method .\notx:calls a owl:ObjectProperty ; rdfs:domain otx:Method ; rdfs:range otx:Method .\notx:returns a owl:DatatypeProperty ; rdfs:range xsd:string .\notx:paramType a owl:DatatypeProperty ; rdfs:range xsd:string .\n";
2
- export declare function buildAskQuery(contextGraphUri: string): string;
@@ -39,6 +39,3 @@ otx:calls a owl:ObjectProperty ; rdfs:domain otx:Method ; rdfs:range otx:Method
39
39
  otx:returns a owl:DatatypeProperty ; rdfs:range xsd:string .
40
40
  otx:paramType a owl:DatatypeProperty ; rdfs:range xsd:string .
41
41
  `;
42
- export function buildAskQuery(contextGraphUri) {
43
- return `ASK { GRAPH <${contextGraphUri}> { <https://opentology.dev/vocab#Decision> a <http://www.w3.org/2002/07/owl#Class> } }`;
44
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opentology",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "CLI-managed RDF/SPARQL infrastructure — Supabase for RDF",
5
5
  "type": "module",
6
6
  "bin": {