opentology 0.3.7 → 0.3.8

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.
@@ -8,8 +8,13 @@ export declare function batchTriples(triples: string[], batchSize?: number): str
8
8
  import type { StoreAdapter as FullStoreAdapter } from './store-adapter.js';
9
9
  type StoreAdapter = Pick<FullStoreAdapter, 'sparqlUpdate'>;
10
10
  export declare function deleteExistingSymbols(adapter: StoreAdapter, graphUri: string, modulePaths: string[]): Promise<void>;
11
- export declare function pushSymbolTriples(adapter: StoreAdapter, graphUri: string, result: DeepScanResult): Promise<{
11
+ export interface PushResult {
12
12
  triplesInserted: number;
13
+ triplesFailed: number;
13
14
  batchCount: number;
14
- }>;
15
+ batchesFailed: number;
16
+ errors: string[];
17
+ retryHint: string | null;
18
+ }
19
+ export declare function pushSymbolTriples(adapter: StoreAdapter, graphUri: string, result: DeepScanResult): Promise<PushResult>;
15
20
  export {};
@@ -84,7 +84,7 @@ export function generateSymbolTriples(result) {
84
84
  return triples;
85
85
  }
86
86
  // ── Batching ────────────────────────────────────────────────────
87
- export function batchTriples(triples, batchSize = 100) {
87
+ export function batchTriples(triples, batchSize = 25) {
88
88
  const batches = [];
89
89
  for (let i = 0; i < triples.length; i += batchSize) {
90
90
  batches.push(triples.slice(i, i + batchSize));
@@ -110,11 +110,28 @@ export async function pushSymbolTriples(adapter, graphUri, result) {
110
110
  modulePaths.add(f.filePath);
111
111
  // Delete existing symbols for these modules
112
112
  await deleteExistingSymbols(adapter, graphUri, [...modulePaths]);
113
- // Generate and batch-insert
113
+ // Generate and batch-insert with per-batch error handling
114
114
  const triples = generateSymbolTriples(result);
115
- const batches = batchTriples(triples, 100);
116
- for (const batch of batches) {
117
- await adapter.sparqlUpdate(`INSERT DATA { GRAPH <${graphUri}> {\n${batch.join('\n')}\n} }`);
115
+ const batches = batchTriples(triples, 25);
116
+ let inserted = 0;
117
+ let failed = 0;
118
+ let batchesFailed = 0;
119
+ const errors = [];
120
+ for (let i = 0; i < batches.length; i++) {
121
+ const batch = batches[i];
122
+ try {
123
+ await adapter.sparqlUpdate(`INSERT DATA { GRAPH <${graphUri}> {\n${batch.join('\n')}\n} }`);
124
+ inserted += batch.length;
125
+ }
126
+ catch (err) {
127
+ batchesFailed++;
128
+ failed += batch.length;
129
+ const msg = err instanceof Error ? err.message : String(err);
130
+ errors.push(`Batch ${i + 1}/${batches.length} failed (${batch.length} triples): ${msg}`);
131
+ }
118
132
  }
119
- return { triplesInserted: triples.length, batchCount: batches.length };
133
+ const retryHint = batchesFailed > 0
134
+ ? `${batchesFailed}/${batches.length} batches failed (${failed} triples lost). Re-run context_scan with depth="symbol" to retry. If failures persist, try reducing maxSymbols or disabling includeMethodCalls.`
135
+ : null;
136
+ return { triplesInserted: inserted, triplesFailed: failed, batchCount: batches.length, batchesFailed, errors, retryHint };
120
137
  }
@@ -12,6 +12,7 @@ export type { TSNode, TSTree, TSLanguage };
12
12
  export declare abstract class TreeSitterExtractor implements LanguageExtractor {
13
13
  abstract readonly language: string;
14
14
  abstract readonly extensions: string[];
15
+ abstract readonly dependencyModel: import('./language-extractor.js').DependencyModel;
15
16
  /** WASM file name, e.g. 'tree-sitter-python.wasm' */
16
17
  protected abstract readonly wasmName: string;
17
18
  protected lang: TSLanguage | null;
@@ -6,6 +6,7 @@ import type { LanguageExtractor, ExtractedSymbols } from './language-extractor.j
6
6
  export declare class TypeScriptExtractor implements LanguageExtractor {
7
7
  readonly language = "typescript";
8
8
  readonly extensions: string[];
9
+ readonly dependencyModel: "file-based";
9
10
  private tsMorph;
10
11
  isAvailable(): Promise<boolean>;
11
12
  extract(_filePaths: string[], rootDir: string, options: {
@@ -156,6 +156,7 @@ export class TypeScriptExtractor {
156
156
  constructor() {
157
157
  this.language = 'typescript';
158
158
  this.extensions = ['.ts', '.tsx', '.js', '.jsx'];
159
+ this.dependencyModel = 'file-based';
159
160
  this.tsMorph = null;
160
161
  }
161
162
  async isAvailable() {
@@ -2,6 +2,7 @@
2
2
  * Deep scanner — language-agnostic orchestrator for symbol-level codebase analysis.
3
3
  * Delegates to LanguageExtractor implementations (ts-morph, tree-sitter, etc.).
4
4
  */
5
+ import type { DependencyModel } from './language-extractor.js';
5
6
  export interface DeepScanOptions {
6
7
  maxFiles?: number;
7
8
  maxSymbols?: number;
@@ -48,6 +49,12 @@ export interface MethodCallInfo {
48
49
  caller: string;
49
50
  callee: string;
50
51
  }
52
+ export interface LanguageHint {
53
+ language: string;
54
+ dependencyModel: DependencyModel;
55
+ moduleScanApplicable: boolean;
56
+ recommendation: string;
57
+ }
51
58
  export interface UnsupportedFileGroup {
52
59
  extension: string;
53
60
  language: string;
@@ -61,6 +68,7 @@ export interface DeepScanResult {
61
68
  functions: FunctionInfo[];
62
69
  methodCalls: MethodCallInfo[];
63
70
  unsupportedFiles: UnsupportedFileGroup[];
71
+ languageHints: LanguageHint[];
64
72
  fileCount: number;
65
73
  symbolCount: number;
66
74
  scanDurationMs: number;
@@ -103,6 +103,20 @@ async function discoverUnsupportedFiles(rootDir, supportedExtensions) {
103
103
  .filter(g => g.count > 0)
104
104
  .sort((a, b) => b.count - a.count);
105
105
  }
106
+ // ── Language hint generation ────────────────────────────────────
107
+ const DEPENDENCY_MODEL_RECOMMENDATIONS = {
108
+ 'file-based': 'Module-level dependency graph (depth="module") is applicable.',
109
+ 'package-based': 'This language uses package-based imports — module-level scan is not applicable. Use depth="symbol" for class/method/call-level analysis.',
110
+ 'framework-based': 'This language uses framework-level imports — module-level scan is not applicable. Use depth="symbol" for class/method/call-level analysis.',
111
+ };
112
+ function buildLanguageHints(extractors) {
113
+ return extractors.map(ext => ({
114
+ language: ext.language,
115
+ dependencyModel: ext.dependencyModel,
116
+ moduleScanApplicable: ext.dependencyModel === 'file-based',
117
+ recommendation: DEPENDENCY_MODEL_RECOMMENDATIONS[ext.dependencyModel],
118
+ }));
119
+ }
106
120
  // ── Main entry point ────────────────────────────────────────────
107
121
  export async function deepScan(rootDir, options) {
108
122
  const maxFiles = options?.maxFiles ?? 500;
@@ -144,6 +158,7 @@ export async function deepScan(rootDir, options) {
144
158
  functions: [],
145
159
  methodCalls: [],
146
160
  unsupportedFiles: [],
161
+ languageHints: buildLanguageHints(available),
147
162
  fileCount: total,
148
163
  symbolCount: 0,
149
164
  scanDurationMs: Date.now() - start,
@@ -218,6 +233,7 @@ export async function deepScan(rootDir, options) {
218
233
  functions,
219
234
  methodCalls,
220
235
  unsupportedFiles,
236
+ languageHints: buildLanguageHints(available),
221
237
  fileCount: files.length,
222
238
  symbolCount,
223
239
  scanDurationMs: Date.now() - start,
@@ -8,6 +8,7 @@ import type { ExtractedSymbols } from '../language-extractor.js';
8
8
  export declare class GoExtractor extends TreeSitterExtractor {
9
9
  readonly language = "go";
10
10
  readonly extensions: string[];
11
+ readonly dependencyModel: "package-based";
11
12
  protected readonly wasmName = "tree-sitter-go.wasm";
12
13
  protected extractFromTree(tree: TSTree, relPath: string, _source: string, includeMethodCalls: boolean): ExtractedSymbols;
13
14
  private extractStructFields;
@@ -8,6 +8,7 @@ export class GoExtractor extends TreeSitterExtractor {
8
8
  super(...arguments);
9
9
  this.language = 'go';
10
10
  this.extensions = ['.go'];
11
+ this.dependencyModel = 'package-based';
11
12
  this.wasmName = 'tree-sitter-go.wasm';
12
13
  }
13
14
  extractFromTree(tree, relPath, _source, includeMethodCalls) {
@@ -8,6 +8,7 @@ import type { ExtractedSymbols } from '../language-extractor.js';
8
8
  export declare class JavaExtractor extends TreeSitterExtractor {
9
9
  readonly language = "java";
10
10
  readonly extensions: string[];
11
+ readonly dependencyModel: "package-based";
11
12
  protected readonly wasmName = "tree-sitter-java.wasm";
12
13
  protected extractFromTree(tree: TSTree, relPath: string, _source: string, includeMethodCalls: boolean): ExtractedSymbols;
13
14
  private getSuperclass;
@@ -8,6 +8,7 @@ export class JavaExtractor extends TreeSitterExtractor {
8
8
  super(...arguments);
9
9
  this.language = 'java';
10
10
  this.extensions = ['.java'];
11
+ this.dependencyModel = 'package-based';
11
12
  this.wasmName = 'tree-sitter-java.wasm';
12
13
  }
13
14
  extractFromTree(tree, relPath, _source, includeMethodCalls) {
@@ -8,6 +8,7 @@ import type { ExtractedSymbols } from '../language-extractor.js';
8
8
  export declare class PythonExtractor extends TreeSitterExtractor {
9
9
  readonly language = "python";
10
10
  readonly extensions: string[];
11
+ readonly dependencyModel: "file-based";
11
12
  protected readonly wasmName = "tree-sitter-python.wasm";
12
13
  protected extractFromTree(tree: TSTree, relPath: string, _source: string, includeMethodCalls: boolean): ExtractedSymbols;
13
14
  private getBaseClasses;
@@ -9,6 +9,7 @@ export class PythonExtractor extends TreeSitterExtractor {
9
9
  super(...arguments);
10
10
  this.language = 'python';
11
11
  this.extensions = ['.py'];
12
+ this.dependencyModel = 'file-based';
12
13
  this.wasmName = 'tree-sitter-python.wasm';
13
14
  }
14
15
  extractFromTree(tree, relPath, _source, includeMethodCalls) {
@@ -8,6 +8,7 @@ import type { ExtractedSymbols } from '../language-extractor.js';
8
8
  export declare class RustExtractor extends TreeSitterExtractor {
9
9
  readonly language = "rust";
10
10
  readonly extensions: string[];
11
+ readonly dependencyModel: "file-based";
11
12
  protected readonly wasmName = "tree-sitter-rust.wasm";
12
13
  protected extractFromTree(tree: TSTree, relPath: string, _source: string, includeMethodCalls: boolean): ExtractedSymbols;
13
14
  private extractParams;
@@ -8,6 +8,7 @@ export class RustExtractor extends TreeSitterExtractor {
8
8
  super(...arguments);
9
9
  this.language = 'rust';
10
10
  this.extensions = ['.rs'];
11
+ this.dependencyModel = 'file-based';
11
12
  this.wasmName = 'tree-sitter-rust.wasm';
12
13
  }
13
14
  extractFromTree(tree, relPath, _source, includeMethodCalls) {
@@ -8,6 +8,7 @@ import type { ExtractedSymbols } from '../language-extractor.js';
8
8
  export declare class SwiftExtractor extends TreeSitterExtractor {
9
9
  readonly language = "swift";
10
10
  readonly extensions: string[];
11
+ readonly dependencyModel: "framework-based";
11
12
  protected readonly wasmName = "tree-sitter-swift.wasm";
12
13
  protected extractFromTree(tree: TSTree, relPath: string, _source: string, includeMethodCalls: boolean): ExtractedSymbols;
13
14
  private extractClassLike;
@@ -8,6 +8,7 @@ export class SwiftExtractor extends TreeSitterExtractor {
8
8
  super(...arguments);
9
9
  this.language = 'swift';
10
10
  this.extensions = ['.swift'];
11
+ this.dependencyModel = 'framework-based';
11
12
  this.wasmName = 'tree-sitter-swift.wasm';
12
13
  }
13
14
  extractFromTree(tree, relPath, _source, includeMethodCalls) {
@@ -9,11 +9,15 @@ export interface ExtractedSymbols {
9
9
  functions: FunctionInfo[];
10
10
  methodCalls: MethodCallInfo[];
11
11
  }
12
+ /** How a language resolves inter-file dependencies. */
13
+ export type DependencyModel = 'file-based' | 'package-based' | 'framework-based';
12
14
  export interface LanguageExtractor {
13
15
  /** Language identifier, e.g. 'typescript', 'python', 'go' */
14
16
  readonly language: string;
15
17
  /** File extensions handled by this extractor, e.g. ['.ts', '.tsx'] */
16
18
  readonly extensions: string[];
19
+ /** How this language resolves dependencies between source files. */
20
+ readonly dependencyModel: DependencyModel;
17
21
  /** Check whether the extractor's dependencies are available at runtime. */
18
22
  isAvailable(): Promise<boolean>;
19
23
  /**
@@ -381,12 +381,16 @@ async function handleContextScan(args) {
381
381
  // Auto-push triples server-side
382
382
  let pushStats = null;
383
383
  let moduleStats = null;
384
+ const pushWarnings = [];
384
385
  try {
385
386
  const config = loadConfig();
386
387
  const contextUri = `${config.graphUri}/context`;
387
388
  const adapter = await createReadyAdapter(config);
388
389
  // Push symbol triples
389
390
  pushStats = await pushSymbolTriples(adapter, contextUri, scanResult);
391
+ if (pushStats.errors.length > 0) {
392
+ pushWarnings.push(...pushStats.errors);
393
+ }
390
394
  // Also push module dependency graph (fixes #64)
391
395
  const snapshot = await scanCodebase(process.cwd());
392
396
  if (snapshot.dependencyGraph && snapshot.dependencyGraph.modules.length > 0) {
@@ -405,14 +409,20 @@ async function handleContextScan(args) {
405
409
  }
406
410
  await persistGraph(adapter, config, contextUri);
407
411
  }
408
- catch {
409
- // Non-fatal: push is best-effort
412
+ catch (err) {
413
+ const msg = err instanceof Error ? err.message : String(err);
414
+ pushWarnings.push(`Push failed: ${msg}`);
410
415
  }
411
416
  const hints = [];
412
- if (pushStats)
413
- hints.push(`Symbol triples: ${pushStats.triplesInserted} in ${pushStats.batchCount} batches`);
417
+ if (pushStats) {
418
+ hints.push(`Symbol triples: ${pushStats.triplesInserted} inserted, ${pushStats.triplesFailed} failed, ${pushStats.batchCount} batches`);
419
+ if (pushStats.retryHint)
420
+ hints.push(pushStats.retryHint);
421
+ }
414
422
  if (moduleStats)
415
423
  hints.push(`Module triples: ${moduleStats.modules} modules, ${moduleStats.edges} edges`);
424
+ if (pushWarnings.length > 0)
425
+ hints.push(...pushWarnings);
416
426
  // Build compact summary — do NOT return full symbol arrays (#66)
417
427
  const compact = {
418
428
  deepScanAvailable: true,
@@ -424,6 +434,7 @@ async function handleContextScan(args) {
424
434
  files: scanResult.fileCount,
425
435
  symbols: scanResult.symbolCount,
426
436
  },
437
+ languageHints: scanResult.languageHints,
427
438
  scanDurationMs: scanResult.scanDurationMs,
428
439
  capped: scanResult.capped,
429
440
  warnings: scanResult.warnings,
@@ -512,9 +523,42 @@ async function handleContextScan(args) {
512
523
  catch {
513
524
  // Non-fatal: module triple push is best-effort
514
525
  }
526
+ // Generate language hints for module scan — helps LLM understand when module scan isn't applicable
527
+ const moduleLanguageHints = [];
528
+ const fileExtensionMap = {
529
+ '.ts': { lang: 'typescript', model: 'file-based' },
530
+ '.tsx': { lang: 'typescript', model: 'file-based' },
531
+ '.js': { lang: 'javascript', model: 'file-based' },
532
+ '.jsx': { lang: 'javascript', model: 'file-based' },
533
+ '.py': { lang: 'python', model: 'file-based' },
534
+ '.rs': { lang: 'rust', model: 'file-based' },
535
+ '.go': { lang: 'go', model: 'package-based' },
536
+ '.java': { lang: 'java', model: 'package-based' },
537
+ '.swift': { lang: 'swift', model: 'framework-based' },
538
+ };
539
+ if (snapshot.dependencyGraph) {
540
+ const detectedLangs = new Set();
541
+ for (const mod of snapshot.dependencyGraph.modules) {
542
+ const ext = '.' + mod.split('.').pop();
543
+ const info = fileExtensionMap[ext];
544
+ if (info && !detectedLangs.has(info.lang)) {
545
+ detectedLangs.add(info.lang);
546
+ const applicable = info.model === 'file-based';
547
+ moduleLanguageHints.push({
548
+ language: info.lang,
549
+ dependencyModel: info.model,
550
+ moduleScanApplicable: applicable,
551
+ recommendation: applicable
552
+ ? 'Module-level dependency graph (depth="module") is applicable.'
553
+ : `This language uses ${info.model} imports — module-level scan is not applicable. Use depth="symbol" for class/method/call-level analysis.`,
554
+ });
555
+ }
556
+ }
557
+ }
515
558
  return {
516
559
  codebaseSnapshot: snapshot,
517
560
  moduleStats,
561
+ languageHints: moduleLanguageHints.length > 0 ? moduleLanguageHints : undefined,
518
562
  _hint: moduleStats
519
563
  ? `Module triples auto-pushed: ${moduleStats.modules} modules, ${moduleStats.edges} edges. Query with: SELECT ?m WHERE { ?m a otx:Module }`
520
564
  : 'No dependency graph auto-extracted or push failed. Inspect key source files manually.',
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "opentology",
3
- "version": "0.3.7",
3
+ "version": "0.3.8",
4
4
  "description": "Ontology-powered project memory for AI coding assistants — your codebase as a knowledge graph",
5
5
  "type": "module",
6
6
  "bin": {
7
- "opentology": "./dist/index.js"
7
+ "opentology": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist/"