opentology 0.3.4 → 0.3.6
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 +94 -2
- package/dist/lib/deep-scanner-treesitter.d.ts +1 -1
- package/dist/mcp/server.js +28 -4
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -64,6 +64,52 @@ After setup, every session follows this cycle:
|
|
|
64
64
|
|
|
65
65
|
OpenTology doesn't just add capabilities — it **shapes how the AI works** on your project.
|
|
66
66
|
|
|
67
|
+
### Why Graph over grep?
|
|
68
|
+
|
|
69
|
+
We ran a real experiment on this codebase: the same question answered with grep vs the knowledge graph.
|
|
70
|
+
|
|
71
|
+
**Q: "What breaks if I change `store-adapter.ts`?"**
|
|
72
|
+
|
|
73
|
+
| | grep / ripgrep | OpenTology |
|
|
74
|
+
|---|---|---|
|
|
75
|
+
| Calls needed | **6** (direct imports + 2nd-level deps + test files) | **1** (`context_impact`) |
|
|
76
|
+
| Files found | 7 (missed dead-code dependents) | 9 (complete, including dead code) |
|
|
77
|
+
| Impact severity | Not available | `HIGH` — returned automatically |
|
|
78
|
+
| Manual work | Trace each import, search consumers, assemble the list yourself | None — one call returns the full picture |
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
# grep: 6 separate searches to assemble the dependency chain
|
|
82
|
+
$ rg "import.*store-adapter" src/ # Step 1: direct imports (7 files)
|
|
83
|
+
$ rg "import.*store-factory" src/ # Step 2: who imports those?
|
|
84
|
+
$ rg "import.*reasoner" src/ # Step 3: ...and those?
|
|
85
|
+
$ rg "import.*visualizer" src/ # Step 4
|
|
86
|
+
$ rg "import.*embedded-adapter" src/ # Step 5
|
|
87
|
+
$ rg "store-adapter|store-factory" tests/ # Step 6: test files
|
|
88
|
+
|
|
89
|
+
# OpenTology: 1 call
|
|
90
|
+
$ opentology context impact src/lib/store-adapter.ts
|
|
91
|
+
# → Impact: HIGH | 9 dependents | 0 dependencies
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Q: "How does `embedded-adapter` reach `reasoner`?"**
|
|
95
|
+
|
|
96
|
+
| | grep | OpenTology |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| Calls needed | **4** + manual code reading | **1** SPARQL query |
|
|
99
|
+
| Result | Manual path assembly: adapter -> store-factory -> reasoner | Transitive closure (`dependsOn+`) returns all paths |
|
|
100
|
+
| Bonus | None | Returns all 14 modules that reach reasoner, including indirect paths |
|
|
101
|
+
|
|
102
|
+
```sparql
|
|
103
|
+
# One query finds all transitive paths
|
|
104
|
+
SELECT ?from ?to WHERE {
|
|
105
|
+
?from otx:dependsOn+ ?to .
|
|
106
|
+
FILTER(?to = <urn:module:src/lib/reasoner>)
|
|
107
|
+
}
|
|
108
|
+
# Returns all 14 modules that reach reasoner, including indirect paths
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**[See the interactive comparison at opentology.dev](https://opentology.dev)** — switch between Impact Analysis and Call Path Tracing scenarios.
|
|
112
|
+
|
|
67
113
|
### The Knowledge Graph
|
|
68
114
|
|
|
69
115
|
Everything lives in RDF named graphs with the `otx:` ontology:
|
|
@@ -93,7 +139,7 @@ SELECT ?title ?reason WHERE {
|
|
|
93
139
|
|
|
94
140
|
# What did we do last session?
|
|
95
141
|
SELECT ?title ?body ?next WHERE {
|
|
96
|
-
?s a otx:Session ; otx:title ?title ; otx:body ?body
|
|
142
|
+
?s a otx:Session ; otx:title ?title ; otx:date ?date ; otx:body ?body
|
|
97
143
|
OPTIONAL { ?s otx:nextTodo ?next }
|
|
98
144
|
} ORDER BY DESC(?date) LIMIT 1
|
|
99
145
|
```
|
|
@@ -346,6 +392,52 @@ opentology context scan # 3. 코드베이스를 지식 그래프로
|
|
|
346
392
|
|
|
347
393
|
OpenTology는 기능만 추가하는 것이 아니라, **AI가 프로젝트에서 일하는 방식 자체를 바꿉니다**.
|
|
348
394
|
|
|
395
|
+
### 왜 grep 대신 그래프인가?
|
|
396
|
+
|
|
397
|
+
이 코드베이스에서 실제 실험을 했습니다: 같은 질문을 grep과 지식 그래프로 각각 답해 봤습니다.
|
|
398
|
+
|
|
399
|
+
**Q: "`store-adapter.ts`를 바꾸면 뭐가 깨지나?"**
|
|
400
|
+
|
|
401
|
+
| | grep / ripgrep | OpenTology |
|
|
402
|
+
|---|---|---|
|
|
403
|
+
| 필요한 호출 수 | **6번** (직접 import + 2차 의존 추적 + 테스트 파일) | **1번** (`context_impact`) |
|
|
404
|
+
| 발견한 파일 수 | 7개 (dead code 의존성 누락) | 9개 (dead code 포함 완전한 결과) |
|
|
405
|
+
| 영향도 수준 | 제공 안 됨 | `HIGH` — 자동 반환 |
|
|
406
|
+
| 수작업 | 각 import를 추적하고, 소비자를 검색하고, 직접 목록 조합 | 없음 — 한 번의 호출로 전체 그림 |
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
# grep: 의존성 체인 조합에 6번의 검색
|
|
410
|
+
$ rg "import.*store-adapter" src/ # 1단계: 직접 import (7개 파일)
|
|
411
|
+
$ rg "import.*store-factory" src/ # 2단계: 그걸 누가 import?
|
|
412
|
+
$ rg "import.*reasoner" src/ # 3단계: ...그리고 그걸?
|
|
413
|
+
$ rg "import.*visualizer" src/ # 4단계
|
|
414
|
+
$ rg "import.*embedded-adapter" src/ # 5단계
|
|
415
|
+
$ rg "store-adapter|store-factory" tests/ # 6단계: 테스트 파일
|
|
416
|
+
|
|
417
|
+
# OpenTology: 1번의 호출
|
|
418
|
+
$ opentology context impact src/lib/store-adapter.ts
|
|
419
|
+
# → Impact: HIGH | 9개 의존 모듈 | 0개 종속성
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
**Q: "`embedded-adapter`에서 `reasoner`까지 어떻게 연결되나?"**
|
|
423
|
+
|
|
424
|
+
| | grep | OpenTology |
|
|
425
|
+
|---|---|---|
|
|
426
|
+
| 필요한 호출 수 | **4번** + 코드 직접 읽기 | **1번** SPARQL 쿼리 |
|
|
427
|
+
| 결과 | 수동 경로 조합: adapter → store-factory → reasoner | 전이 폐포(`dependsOn+`)로 모든 경로 반환 |
|
|
428
|
+
| 추가 발견 | 없음 | reasoner에 전이적으로 의존하는 14개 모듈 전체 목록 |
|
|
429
|
+
|
|
430
|
+
```sparql
|
|
431
|
+
-- 한 번의 쿼리로 모든 전이 경로 발견
|
|
432
|
+
SELECT ?from ?to WHERE {
|
|
433
|
+
?from otx:dependsOn+ ?to .
|
|
434
|
+
FILTER(?to = <urn:module:src/lib/reasoner>)
|
|
435
|
+
}
|
|
436
|
+
-- 간접 경로인 embedded-adapter 포함 14개 모듈 반환
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**[opentology.dev에서 인터랙티브 비교 보기](https://opentology.dev)** — Impact Analysis와 Call Path Tracing 시나리오를 전환해 보세요.
|
|
440
|
+
|
|
349
441
|
### 지식 그래프 구조
|
|
350
442
|
|
|
351
443
|
모든 데이터는 `otx:` 온톨로지를 사용하는 RDF named graph에 저장됩니다:
|
|
@@ -375,7 +467,7 @@ SELECT ?title ?reason WHERE {
|
|
|
375
467
|
|
|
376
468
|
# 지난 세션에서 뭘 했지?
|
|
377
469
|
SELECT ?title ?body ?next WHERE {
|
|
378
|
-
?s a otx:Session ; otx:title ?title ; otx:body ?body
|
|
470
|
+
?s a otx:Session ; otx:title ?title ; otx:date ?date ; otx:body ?body
|
|
379
471
|
OPTIONAL { ?s otx:nextTodo ?next }
|
|
380
472
|
} ORDER BY DESC(?date) LIMIT 1
|
|
381
473
|
```
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import type { LanguageExtractor, ExtractedSymbols } from './language-extractor.js';
|
|
8
8
|
type TSLanguage = import('web-tree-sitter').Language;
|
|
9
9
|
type TSTree = import('web-tree-sitter').Tree;
|
|
10
|
-
type TSNode = import('web-tree-sitter').
|
|
10
|
+
type TSNode = import('web-tree-sitter').SyntaxNode;
|
|
11
11
|
export type { TSNode, TSTree, TSLanguage };
|
|
12
12
|
export declare abstract class TreeSitterExtractor implements LanguageExtractor {
|
|
13
13
|
abstract readonly language: string;
|
package/dist/mcp/server.js
CHANGED
|
@@ -379,22 +379,46 @@ async function handleContextScan(args) {
|
|
|
379
379
|
}
|
|
380
380
|
// Auto-push triples server-side
|
|
381
381
|
let pushStats = null;
|
|
382
|
+
let moduleStats = null;
|
|
382
383
|
try {
|
|
383
384
|
const config = loadConfig();
|
|
384
385
|
const contextUri = `${config.graphUri}/context`;
|
|
385
386
|
const adapter = await createReadyAdapter(config);
|
|
387
|
+
// Push symbol triples
|
|
386
388
|
pushStats = await pushSymbolTriples(adapter, contextUri, scanResult);
|
|
389
|
+
// Also push module dependency graph (fixes #64)
|
|
390
|
+
const snapshot = await scanCodebase(process.cwd());
|
|
391
|
+
if (snapshot.dependencyGraph && snapshot.dependencyGraph.modules.length > 0) {
|
|
392
|
+
const dg = snapshot.dependencyGraph;
|
|
393
|
+
// Clear stale Module/dependsOn triples
|
|
394
|
+
await adapter.sparqlUpdate(`DELETE { GRAPH <${contextUri}> { ?s ?p ?o } } WHERE { GRAPH <${contextUri}> { ?s ?p ?o . { ?s a <https://opentology.dev/vocab#Module> } UNION { ?s <https://opentology.dev/vocab#dependsOn> ?o } } }`);
|
|
395
|
+
const sparqlTriples = [];
|
|
396
|
+
for (const mod of dg.modules) {
|
|
397
|
+
sparqlTriples.push(`<urn:module:${mod}> a <https://opentology.dev/vocab#Module> ; <https://opentology.dev/vocab#title> "${mod}" .`);
|
|
398
|
+
}
|
|
399
|
+
for (const edge of dg.edges) {
|
|
400
|
+
sparqlTriples.push(`<urn:module:${edge.from}> <https://opentology.dev/vocab#dependsOn> <urn:module:${edge.to}> .`);
|
|
401
|
+
}
|
|
402
|
+
await adapter.sparqlUpdate(`INSERT DATA { GRAPH <${contextUri}> {\n${sparqlTriples.join('\n')}\n} }`);
|
|
403
|
+
moduleStats = { modules: dg.modules.length, edges: dg.edges.length };
|
|
404
|
+
}
|
|
387
405
|
await persistGraph(adapter, config, contextUri);
|
|
388
406
|
}
|
|
389
407
|
catch {
|
|
390
408
|
// Non-fatal: push is best-effort
|
|
391
409
|
}
|
|
410
|
+
const hints = [];
|
|
411
|
+
if (pushStats)
|
|
412
|
+
hints.push(`Symbol triples: ${pushStats.triplesInserted} in ${pushStats.batchCount} batches`);
|
|
413
|
+
if (moduleStats)
|
|
414
|
+
hints.push(`Module triples: ${moduleStats.modules} modules, ${moduleStats.edges} edges`);
|
|
392
415
|
return {
|
|
393
416
|
...scanResult,
|
|
394
417
|
pushStats,
|
|
418
|
+
moduleStats,
|
|
395
419
|
_experimental: true,
|
|
396
|
-
_hint:
|
|
397
|
-
?
|
|
420
|
+
_hint: hints.length
|
|
421
|
+
? `${hints.join('. ')}. Query examples:\n- Classes: SELECT ?c ?name WHERE { ?c a otx:Class ; otx:title ?name }\n- Dependents: SELECT ?dep WHERE { ?dep otx:dependsOn <urn:module:src/lib/store-adapter> }\n- Call graph: SELECT ?caller ?callee WHERE { ?s a otx:MethodCall ; otx:callerSymbol ?caller ; otx:calleeSymbol ?callee }`
|
|
398
422
|
: 'Deep scan completed but triple push failed. Use push manually with the generated triples.',
|
|
399
423
|
};
|
|
400
424
|
}
|
|
@@ -415,7 +439,7 @@ async function handleContextScan(args) {
|
|
|
415
439
|
// Insert fresh triples
|
|
416
440
|
const sparqlTriples = [];
|
|
417
441
|
for (const mod of dg.modules) {
|
|
418
|
-
sparqlTriples.push(`<urn:module:${mod}> a <https://opentology.dev/vocab#Module> .`);
|
|
442
|
+
sparqlTriples.push(`<urn:module:${mod}> a <https://opentology.dev/vocab#Module> ; <https://opentology.dev/vocab#title> "${mod}" .`);
|
|
419
443
|
}
|
|
420
444
|
for (const edge of dg.edges) {
|
|
421
445
|
sparqlTriples.push(`<urn:module:${edge.from}> <https://opentology.dev/vocab#dependsOn> <urn:module:${edge.to}> .`);
|
|
@@ -626,7 +650,7 @@ async function handleContextInit(args) {
|
|
|
626
650
|
const adapter = await createReadyAdapter(config);
|
|
627
651
|
const sparqlTriples = [];
|
|
628
652
|
for (const mod of dg.modules) {
|
|
629
|
-
sparqlTriples.push(`<urn:module:${mod}> a <https://opentology.dev/vocab#Module> .`);
|
|
653
|
+
sparqlTriples.push(`<urn:module:${mod}> a <https://opentology.dev/vocab#Module> ; <https://opentology.dev/vocab#title> "${mod}" .`);
|
|
630
654
|
}
|
|
631
655
|
for (const edge of dg.edges) {
|
|
632
656
|
sparqlTriples.push(`<urn:module:${edge.from}> <https://opentology.dev/vocab#dependsOn> <urn:module:${edge.to}> .`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opentology",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Ontology-powered project memory for AI coding assistants — your codebase as a knowledge graph",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
"shacl-engine": "^1.1.0"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
+
"tree-sitter-wasms": ">=0.1.0",
|
|
54
55
|
"ts-morph": ">=21.0.0",
|
|
55
|
-
"web-tree-sitter": ">=0.24.0"
|
|
56
|
-
"tree-sitter-wasms": ">=0.1.0"
|
|
56
|
+
"web-tree-sitter": ">=0.24.0 <0.26.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependenciesMeta": {
|
|
59
59
|
"ts-morph": {
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
"tree-sitter-wasms": "^0.1.13",
|
|
73
73
|
"ts-morph": "^27.0.2",
|
|
74
74
|
"ts-node": "^10.9.2",
|
|
75
|
-
"web-tree-sitter": "^0.26.8",
|
|
76
75
|
"typescript": "^6.0.2",
|
|
77
|
-
"vitest": "^4.1.2"
|
|
76
|
+
"vitest": "^4.1.2",
|
|
77
|
+
"web-tree-sitter": "^0.24.0"
|
|
78
78
|
}
|
|
79
79
|
}
|