opentology 0.2.8 → 0.3.1
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/dist/commands/context.js +14 -0
- package/dist/commands/diff.js +7 -1
- package/dist/commands/drop.js +2 -0
- package/dist/commands/rollback.d.ts +2 -0
- package/dist/commands/rollback.js +75 -0
- package/dist/index.js +2 -0
- package/dist/lib/deep-scan-triples.js +7 -5
- package/dist/lib/embedded-adapter.d.ts +4 -1
- package/dist/lib/embedded-adapter.js +12 -4
- package/dist/lib/http-adapter.d.ts +4 -1
- package/dist/lib/http-adapter.js +12 -4
- package/dist/lib/snapshot.d.ts +40 -0
- package/dist/lib/snapshot.js +136 -0
- package/dist/lib/store-adapter.d.ts +4 -1
- package/dist/mcp/server.js +94 -5
- package/dist/templates/claude-md-context.js +47 -1
- package/dist/templates/otx-ontology.d.ts +1 -1
- package/dist/templates/otx-ontology.js +4 -1
- package/package.json +1 -1
package/dist/commands/context.js
CHANGED
|
@@ -217,6 +217,20 @@ export function registerContext(program) {
|
|
|
217
217
|
}
|
|
218
218
|
settings.hooks = hooks;
|
|
219
219
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
220
|
+
// Ensure .opentology/snapshots/ is in .gitignore
|
|
221
|
+
const gitignorePath = join(process.cwd(), '.gitignore');
|
|
222
|
+
const snapshotIgnore = '.opentology/snapshots/';
|
|
223
|
+
if (existsSync(gitignorePath)) {
|
|
224
|
+
const gitignoreContent = readFileSync(gitignorePath, 'utf-8');
|
|
225
|
+
if (!gitignoreContent.includes(snapshotIgnore)) {
|
|
226
|
+
writeFileSync(gitignorePath, gitignoreContent.trimEnd() + '\n' + snapshotIgnore + '\n', 'utf-8');
|
|
227
|
+
console.log(pc.green(' Added .opentology/snapshots/ to .gitignore'));
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
writeFileSync(gitignorePath, snapshotIgnore + '\n', 'utf-8');
|
|
232
|
+
console.log(pc.green(' Created .gitignore with .opentology/snapshots/'));
|
|
233
|
+
}
|
|
220
234
|
console.log('');
|
|
221
235
|
console.log(pc.dim('Consider adding .opentology/hooks/ to version control so team members share the hook.'));
|
|
222
236
|
}
|
package/dist/commands/diff.js
CHANGED
|
@@ -27,7 +27,13 @@ export function registerDiff(program) {
|
|
|
27
27
|
for (const triple of result.removed) {
|
|
28
28
|
console.log(pc.red(`- ${triple}`));
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
const addedLabel = result.truncated && result.addedCount > result.added.length
|
|
31
|
+
? `${result.added.length}/${result.addedCount} added (truncated)`
|
|
32
|
+
: `${result.addedCount} added`;
|
|
33
|
+
const removedLabel = result.truncated && result.removedCount > result.removed.length
|
|
34
|
+
? `${result.removed.length}/${result.removedCount} removed (truncated)`
|
|
35
|
+
: `${result.removedCount} removed`;
|
|
36
|
+
console.log(`\n${addedLabel}, ${removedLabel}, ${result.unchanged} unchanged`);
|
|
31
37
|
}
|
|
32
38
|
catch (err) {
|
|
33
39
|
const message = err.message;
|
package/dist/commands/drop.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import pc from 'picocolors';
|
|
2
2
|
import { loadConfig, resolveGraphUri, saveConfig } from '../lib/config.js';
|
|
3
3
|
import { createReadyAdapter } from '../lib/store-factory.js';
|
|
4
|
+
import { snapshotGraph } from '../lib/snapshot.js';
|
|
4
5
|
export function registerDrop(program) {
|
|
5
6
|
program
|
|
6
7
|
.command('drop')
|
|
@@ -23,6 +24,7 @@ export function registerDrop(program) {
|
|
|
23
24
|
}
|
|
24
25
|
try {
|
|
25
26
|
const adapter = await createReadyAdapter(config);
|
|
27
|
+
await snapshotGraph(adapter, config, graphUri);
|
|
26
28
|
await adapter.dropGraph(graphUri);
|
|
27
29
|
// In embedded mode, clear tracked files for this graph
|
|
28
30
|
if (config.mode === 'embedded') {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import pc from 'picocolors';
|
|
2
|
+
import { loadConfig, resolveGraphUri } from '../lib/config.js';
|
|
3
|
+
import { createReadyAdapter } from '../lib/store-factory.js';
|
|
4
|
+
import { listSnapshots, restoreSnapshot } from '../lib/snapshot.js';
|
|
5
|
+
import { createInterface } from 'node:readline';
|
|
6
|
+
function ask(question) {
|
|
7
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
rl.question(question, (answer) => {
|
|
10
|
+
rl.close();
|
|
11
|
+
resolve(answer.trim().toLowerCase());
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function registerRollback(program) {
|
|
16
|
+
program
|
|
17
|
+
.command('rollback')
|
|
18
|
+
.description('List or restore graph snapshots')
|
|
19
|
+
.option('--list', 'List available snapshots')
|
|
20
|
+
.option('--to <timestamp>', 'Restore to a specific snapshot timestamp')
|
|
21
|
+
.option('--graph <name>', 'Target a specific named graph')
|
|
22
|
+
.action(async (opts) => {
|
|
23
|
+
let config;
|
|
24
|
+
try {
|
|
25
|
+
config = loadConfig();
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
console.error(`Error: ${err.message}`);
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
const graphUri = opts.graph ? resolveGraphUri(config, opts.graph) : config.graphUri;
|
|
32
|
+
if (opts.list) {
|
|
33
|
+
const snapshots = listSnapshots(graphUri);
|
|
34
|
+
if (snapshots.length === 0) {
|
|
35
|
+
console.log(pc.dim('No snapshots found for this graph.'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
console.log(pc.bold(`Snapshots for ${graphUri}:`));
|
|
39
|
+
console.log('');
|
|
40
|
+
for (const s of snapshots) {
|
|
41
|
+
const sizeKb = (s.sizeBytes / 1024).toFixed(1);
|
|
42
|
+
const label = s.isPreRollback ? pc.yellow(' (pre-rollback)') : '';
|
|
43
|
+
console.log(` ${pc.dim(s.timestamp)} ${sizeKb} KB${label}`);
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const adapter = await createReadyAdapter(config);
|
|
49
|
+
if (opts.to) {
|
|
50
|
+
await restoreSnapshot(adapter, config, graphUri, opts.to);
|
|
51
|
+
console.log(pc.green(`Restored graph to snapshot: ${opts.to}`));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// No flags: restore latest snapshot with confirmation
|
|
55
|
+
const snapshots = listSnapshots(graphUri).filter((s) => !s.isPreRollback);
|
|
56
|
+
if (snapshots.length === 0) {
|
|
57
|
+
console.log(pc.dim('No snapshots found for this graph.'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const latest = snapshots[0];
|
|
61
|
+
const answer = await ask(`Restore to latest snapshot ${pc.bold(latest.timestamp)}? [y/N] `);
|
|
62
|
+
if (answer === 'y' || answer === 'yes') {
|
|
63
|
+
await restoreSnapshot(adapter, config, graphUri, latest.timestamp);
|
|
64
|
+
console.log(pc.green(`Restored graph to snapshot: ${latest.timestamp}`));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
console.log(pc.dim('Rollback cancelled.'));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error(pc.red(`Error: ${err.message}`));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { registerPrefix } from './commands/prefix.js';
|
|
|
17
17
|
import { registerContext } from './commands/context.js';
|
|
18
18
|
import { registerViz } from './commands/viz.js';
|
|
19
19
|
import { registerDoctor } from './commands/doctor.js';
|
|
20
|
+
import { registerRollback } from './commands/rollback.js';
|
|
20
21
|
const program = new Command();
|
|
21
22
|
program
|
|
22
23
|
.name('opentology')
|
|
@@ -39,4 +40,5 @@ registerPrefix(program);
|
|
|
39
40
|
registerContext(program);
|
|
40
41
|
registerViz(program);
|
|
41
42
|
registerDoctor(program);
|
|
43
|
+
registerRollback(program);
|
|
42
44
|
program.parse(process.argv);
|
|
@@ -75,11 +75,11 @@ export function generateSymbolTriples(result) {
|
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
for (const call of result.methodCalls) {
|
|
78
|
-
|
|
79
|
-
triples.push(
|
|
80
|
-
|
|
81
|
-
triples.push(
|
|
82
|
-
triples.push(
|
|
78
|
+
const callUri = `urn:call:${encodeSegment(call.caller)}--${encodeSegment(call.callee)}`;
|
|
79
|
+
triples.push(`<${callUri}> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <${OTX}MethodCall> .`);
|
|
80
|
+
triples.push(`<${callUri}> <${OTX}callerSymbol> "${esc(call.caller)}" .`);
|
|
81
|
+
triples.push(`<${callUri}> <${OTX}calleeSymbol> "${esc(call.callee)}" .`);
|
|
82
|
+
triples.push(`<${callUri}> <${OTX}title> "${esc(call.caller)} -> ${esc(call.callee)}" .`);
|
|
83
83
|
}
|
|
84
84
|
return triples;
|
|
85
85
|
}
|
|
@@ -96,6 +96,8 @@ export async function deleteExistingSymbols(adapter, graphUri, modulePaths) {
|
|
|
96
96
|
const modUri = moduleUri(modPath);
|
|
97
97
|
await adapter.sparqlUpdate(`DELETE WHERE { GRAPH <${graphUri}> { ?s <${OTX}definedIn> <${modUri}> . ?s ?p ?o } }`);
|
|
98
98
|
}
|
|
99
|
+
// Clean up MethodCall triples (no definedIn link, so delete all and re-insert)
|
|
100
|
+
await adapter.sparqlUpdate(`DELETE WHERE { GRAPH <${graphUri}> { ?s a <${OTX}MethodCall> . ?s ?p ?o } }`);
|
|
99
101
|
}
|
|
100
102
|
export async function pushSymbolTriples(adapter, graphUri, result) {
|
|
101
103
|
// Collect all module paths for scoped delete
|
|
@@ -18,10 +18,13 @@ export declare class EmbeddedAdapter implements StoreAdapter {
|
|
|
18
18
|
turtle?: string;
|
|
19
19
|
where?: string;
|
|
20
20
|
}): Promise<void>;
|
|
21
|
-
diffGraph(graphUri: string, localTurtle: string): Promise<{
|
|
21
|
+
diffGraph(graphUri: string, localTurtle: string, limit?: number): Promise<{
|
|
22
22
|
added: string[];
|
|
23
23
|
removed: string[];
|
|
24
24
|
unchanged: number;
|
|
25
|
+
addedCount: number;
|
|
26
|
+
removedCount: number;
|
|
27
|
+
truncated: boolean;
|
|
25
28
|
}>;
|
|
26
29
|
getSchemaOverview(graphUri: string): Promise<{
|
|
27
30
|
prefixes: Record<string, string>;
|
|
@@ -148,7 +148,7 @@ export class EmbeddedAdapter {
|
|
|
148
148
|
throw new Error('deleteTriples: either options.turtle or options.where must be provided');
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
|
-
async diffGraph(graphUri, localTurtle) {
|
|
151
|
+
async diffGraph(graphUri, localTurtle, limit = 50) {
|
|
152
152
|
const localQuads = await parseTurtle(localTurtle);
|
|
153
153
|
const localSet = new Set(localQuads.map((q) => `${termToSparql(q.subject)} ${termToSparql(q.predicate)} ${termToSparql(q.object)}`));
|
|
154
154
|
// Get remote quads from the store
|
|
@@ -158,10 +158,18 @@ export class EmbeddedAdapter {
|
|
|
158
158
|
const n3q = wasmQuadToN3Quad(q);
|
|
159
159
|
remoteSet.add(`${termToSparql(n3q.subject)} ${termToSparql(n3q.predicate)} ${termToSparql(n3q.object)}`);
|
|
160
160
|
}
|
|
161
|
-
const
|
|
162
|
-
const
|
|
161
|
+
const allAdded = [...localSet].filter((t) => !remoteSet.has(t));
|
|
162
|
+
const allRemoved = [...remoteSet].filter((t) => !localSet.has(t));
|
|
163
163
|
const unchanged = [...localSet].filter((t) => remoteSet.has(t)).length;
|
|
164
|
-
|
|
164
|
+
const truncated = allAdded.length > limit || allRemoved.length > limit;
|
|
165
|
+
return {
|
|
166
|
+
added: allAdded.slice(0, limit),
|
|
167
|
+
removed: allRemoved.slice(0, limit),
|
|
168
|
+
unchanged,
|
|
169
|
+
addedCount: allAdded.length,
|
|
170
|
+
removedCount: allRemoved.length,
|
|
171
|
+
truncated,
|
|
172
|
+
};
|
|
165
173
|
}
|
|
166
174
|
async getSchemaOverview(graphUri) {
|
|
167
175
|
const tripleCount = await this.getGraphTripleCount(graphUri);
|
|
@@ -14,10 +14,13 @@ export declare class HttpAdapter implements StoreAdapter {
|
|
|
14
14
|
turtle?: string;
|
|
15
15
|
where?: string;
|
|
16
16
|
}): Promise<void>;
|
|
17
|
-
diffGraph(graphUri: string, localTurtle: string): Promise<{
|
|
17
|
+
diffGraph(graphUri: string, localTurtle: string, limit?: number): Promise<{
|
|
18
18
|
added: string[];
|
|
19
19
|
removed: string[];
|
|
20
20
|
unchanged: number;
|
|
21
|
+
addedCount: number;
|
|
22
|
+
removedCount: number;
|
|
23
|
+
truncated: boolean;
|
|
21
24
|
}>;
|
|
22
25
|
getSchemaOverview(graphUri: string): Promise<{
|
|
23
26
|
prefixes: Record<string, string>;
|
package/dist/lib/http-adapter.js
CHANGED
|
@@ -115,7 +115,7 @@ export class HttpAdapter {
|
|
|
115
115
|
throw new Error('deleteTriples: either options.turtle or options.where must be provided');
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
|
-
async diffGraph(graphUri, localTurtle) {
|
|
118
|
+
async diffGraph(graphUri, localTurtle, limit = 50) {
|
|
119
119
|
const localQuads = await parseTurtle(localTurtle);
|
|
120
120
|
const localSet = new Set(localQuads.map((q) => `${termToSparql(q.subject)} ${termToSparql(q.predicate)} ${termToSparql(q.object)}`));
|
|
121
121
|
// Fetch remote quads via CONSTRUCT
|
|
@@ -125,10 +125,18 @@ export class HttpAdapter {
|
|
|
125
125
|
? await parseTurtle(remoteTurtle)
|
|
126
126
|
: [];
|
|
127
127
|
const remoteSet = new Set(remoteQuads.map((q) => `${termToSparql(q.subject)} ${termToSparql(q.predicate)} ${termToSparql(q.object)}`));
|
|
128
|
-
const
|
|
129
|
-
const
|
|
128
|
+
const allAdded = [...localSet].filter((t) => !remoteSet.has(t));
|
|
129
|
+
const allRemoved = [...remoteSet].filter((t) => !localSet.has(t));
|
|
130
130
|
const unchanged = [...localSet].filter((t) => remoteSet.has(t)).length;
|
|
131
|
-
|
|
131
|
+
const truncated = allAdded.length > limit || allRemoved.length > limit;
|
|
132
|
+
return {
|
|
133
|
+
added: allAdded.slice(0, limit),
|
|
134
|
+
removed: allRemoved.slice(0, limit),
|
|
135
|
+
unchanged,
|
|
136
|
+
addedCount: allAdded.length,
|
|
137
|
+
removedCount: allRemoved.length,
|
|
138
|
+
truncated,
|
|
139
|
+
};
|
|
132
140
|
}
|
|
133
141
|
async getSchemaOverview(graphUri) {
|
|
134
142
|
const tripleCount = await this.getGraphTripleCount(graphUri);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { StoreAdapter } from './store-adapter.js';
|
|
2
|
+
import type { OpenTologyConfig } from './config.js';
|
|
3
|
+
/**
|
|
4
|
+
* Derive a filesystem-safe slug from a graph URI.
|
|
5
|
+
* Reuses the same pattern as persistGraph().
|
|
6
|
+
*/
|
|
7
|
+
export declare function graphSlug(graphUri: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Generate an ISO timestamp suitable for filenames.
|
|
10
|
+
* Format: 2026-04-05T14-30-22-123Z (colons replaced with dashes, ms included).
|
|
11
|
+
*/
|
|
12
|
+
export declare function toFilenameTimestamp(date?: Date): string;
|
|
13
|
+
/**
|
|
14
|
+
* Snapshot a named graph before a destructive operation.
|
|
15
|
+
* No-op if not embedded mode or if the graph is empty.
|
|
16
|
+
* Automatically prunes old snapshots after saving.
|
|
17
|
+
*/
|
|
18
|
+
export declare function snapshotGraph(adapter: StoreAdapter, config: OpenTologyConfig, graphUri: string, retention?: number): Promise<string | null>;
|
|
19
|
+
export interface SnapshotInfo {
|
|
20
|
+
timestamp: string;
|
|
21
|
+
path: string;
|
|
22
|
+
sizeBytes: number;
|
|
23
|
+
isPreRollback: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* List snapshots for a graph, sorted by timestamp descending (newest first).
|
|
27
|
+
*/
|
|
28
|
+
export declare function listSnapshots(graphUri: string): SnapshotInfo[];
|
|
29
|
+
/**
|
|
30
|
+
* Restore a graph to a specific snapshot.
|
|
31
|
+
* 1. Saves current state as {timestamp}_pre-rollback.ttl (snapshot-before-rollback)
|
|
32
|
+
* 2. Drops the graph and loads the snapshot
|
|
33
|
+
* 3. Persists the restored state to .opentology/data/
|
|
34
|
+
*/
|
|
35
|
+
export declare function restoreSnapshot(adapter: StoreAdapter, config: OpenTologyConfig, graphUri: string, timestamp: string): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Prune old snapshots, keeping only the most recent `keep` count.
|
|
38
|
+
* Pre-rollback snapshots are pruned separately (keep 2).
|
|
39
|
+
*/
|
|
40
|
+
export declare function pruneSnapshots(graphUri: string, keep?: number): Promise<number>;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync, statSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { persistGraph } from '../mcp/server.js';
|
|
4
|
+
const DEFAULT_RETENTION = 5;
|
|
5
|
+
/**
|
|
6
|
+
* Derive a filesystem-safe slug from a graph URI.
|
|
7
|
+
* Reuses the same pattern as persistGraph().
|
|
8
|
+
*/
|
|
9
|
+
export function graphSlug(graphUri) {
|
|
10
|
+
return graphUri.replace(/[^a-zA-Z0-9-]/g, '_').replace(/_+/g, '_');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate an ISO timestamp suitable for filenames.
|
|
14
|
+
* Format: 2026-04-05T14-30-22-123Z (colons replaced with dashes, ms included).
|
|
15
|
+
*/
|
|
16
|
+
export function toFilenameTimestamp(date = new Date()) {
|
|
17
|
+
return date.toISOString().replace(/:/g, '-').replace(/\./g, '-');
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Parse a filename timestamp back to a Date.
|
|
21
|
+
*/
|
|
22
|
+
function parseFilenameTimestamp(filename) {
|
|
23
|
+
// Remove .ttl suffix and _pre-rollback suffix
|
|
24
|
+
const base = filename.replace(/\.ttl$/, '').replace(/_pre-rollback$/, '');
|
|
25
|
+
// Reverse the filename encoding: dashes back to colons/dots
|
|
26
|
+
// Format: 2026-04-05T14-30-22-123Z → 2026-04-05T14:30:22.123Z
|
|
27
|
+
const parts = base.match(/^(\d{4}-\d{2}-\d{2}T)(\d{2})-(\d{2})-(\d{2})-(\d{3}Z)$/);
|
|
28
|
+
if (!parts)
|
|
29
|
+
return null;
|
|
30
|
+
const iso = `${parts[1]}${parts[2]}:${parts[3]}:${parts[4]}.${parts[5]}`;
|
|
31
|
+
const d = new Date(iso);
|
|
32
|
+
return isNaN(d.getTime()) ? null : d;
|
|
33
|
+
}
|
|
34
|
+
function snapshotDir(graphUri) {
|
|
35
|
+
return join(process.cwd(), '.opentology', 'snapshots', graphSlug(graphUri));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Snapshot a named graph before a destructive operation.
|
|
39
|
+
* No-op if not embedded mode or if the graph is empty.
|
|
40
|
+
* Automatically prunes old snapshots after saving.
|
|
41
|
+
*/
|
|
42
|
+
export async function snapshotGraph(adapter, config, graphUri, retention = DEFAULT_RETENTION) {
|
|
43
|
+
if (config.mode !== 'embedded')
|
|
44
|
+
return null;
|
|
45
|
+
const exported = await adapter.exportGraph(graphUri);
|
|
46
|
+
if (!exported.trim())
|
|
47
|
+
return null;
|
|
48
|
+
const dir = snapshotDir(graphUri);
|
|
49
|
+
mkdirSync(dir, { recursive: true });
|
|
50
|
+
const timestamp = toFilenameTimestamp();
|
|
51
|
+
const filename = `${timestamp}.ttl`;
|
|
52
|
+
const filePath = join(dir, filename);
|
|
53
|
+
writeFileSync(filePath, exported, 'utf-8');
|
|
54
|
+
// Verify the file was written
|
|
55
|
+
if (!existsSync(filePath)) {
|
|
56
|
+
throw new Error(`Snapshot write failed: ${filePath}`);
|
|
57
|
+
}
|
|
58
|
+
// Prune old snapshots (create → verify → prune order)
|
|
59
|
+
await pruneSnapshots(graphUri, retention);
|
|
60
|
+
return filePath;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* List snapshots for a graph, sorted by timestamp descending (newest first).
|
|
64
|
+
*/
|
|
65
|
+
export function listSnapshots(graphUri) {
|
|
66
|
+
const dir = snapshotDir(graphUri);
|
|
67
|
+
if (!existsSync(dir))
|
|
68
|
+
return [];
|
|
69
|
+
const files = readdirSync(dir).filter((f) => f.endsWith('.ttl'));
|
|
70
|
+
return files
|
|
71
|
+
.map((f) => {
|
|
72
|
+
const fullPath = join(dir, f);
|
|
73
|
+
const stat = statSync(fullPath);
|
|
74
|
+
return {
|
|
75
|
+
timestamp: f.replace(/\.ttl$/, ''),
|
|
76
|
+
path: fullPath,
|
|
77
|
+
sizeBytes: stat.size,
|
|
78
|
+
isPreRollback: f.includes('_pre-rollback'),
|
|
79
|
+
};
|
|
80
|
+
})
|
|
81
|
+
.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Restore a graph to a specific snapshot.
|
|
85
|
+
* 1. Saves current state as {timestamp}_pre-rollback.ttl (snapshot-before-rollback)
|
|
86
|
+
* 2. Drops the graph and loads the snapshot
|
|
87
|
+
* 3. Persists the restored state to .opentology/data/
|
|
88
|
+
*/
|
|
89
|
+
export async function restoreSnapshot(adapter, config, graphUri, timestamp) {
|
|
90
|
+
const dir = snapshotDir(graphUri);
|
|
91
|
+
const snapshotPath = join(dir, `${timestamp}.ttl`);
|
|
92
|
+
if (!existsSync(snapshotPath)) {
|
|
93
|
+
throw new Error(`Snapshot not found: ${timestamp}`);
|
|
94
|
+
}
|
|
95
|
+
// Step 1: Snapshot-before-rollback — save current state
|
|
96
|
+
const currentExport = await adapter.exportGraph(graphUri);
|
|
97
|
+
if (currentExport.trim()) {
|
|
98
|
+
mkdirSync(dir, { recursive: true });
|
|
99
|
+
const preRollbackTimestamp = toFilenameTimestamp();
|
|
100
|
+
const preRollbackPath = join(dir, `${preRollbackTimestamp}_pre-rollback.ttl`);
|
|
101
|
+
writeFileSync(preRollbackPath, currentExport, 'utf-8');
|
|
102
|
+
}
|
|
103
|
+
// Step 2: Drop and restore
|
|
104
|
+
const snapshotTurtle = readFileSync(snapshotPath, 'utf-8');
|
|
105
|
+
await adapter.dropGraph(graphUri);
|
|
106
|
+
await adapter.insertTurtle(graphUri, snapshotTurtle);
|
|
107
|
+
// Step 3: Persist restored state
|
|
108
|
+
await persistGraph(adapter, config, graphUri);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Prune old snapshots, keeping only the most recent `keep` count.
|
|
112
|
+
* Pre-rollback snapshots are pruned separately (keep 2).
|
|
113
|
+
*/
|
|
114
|
+
export async function pruneSnapshots(graphUri, keep = DEFAULT_RETENTION) {
|
|
115
|
+
const dir = snapshotDir(graphUri);
|
|
116
|
+
if (!existsSync(dir))
|
|
117
|
+
return 0;
|
|
118
|
+
const files = readdirSync(dir).filter((f) => f.endsWith('.ttl'));
|
|
119
|
+
// Separate regular snapshots and pre-rollback snapshots
|
|
120
|
+
const regular = files.filter((f) => !f.includes('_pre-rollback')).sort();
|
|
121
|
+
const preRollback = files.filter((f) => f.includes('_pre-rollback')).sort();
|
|
122
|
+
let deleted = 0;
|
|
123
|
+
// Prune regular snapshots
|
|
124
|
+
while (regular.length > keep) {
|
|
125
|
+
const oldest = regular.shift();
|
|
126
|
+
unlinkSync(join(dir, oldest));
|
|
127
|
+
deleted++;
|
|
128
|
+
}
|
|
129
|
+
// Prune pre-rollback snapshots (keep max 2)
|
|
130
|
+
while (preRollback.length > 2) {
|
|
131
|
+
const oldest = preRollback.shift();
|
|
132
|
+
unlinkSync(join(dir, oldest));
|
|
133
|
+
deleted++;
|
|
134
|
+
}
|
|
135
|
+
return deleted;
|
|
136
|
+
}
|
|
@@ -34,10 +34,13 @@ export interface StoreAdapter {
|
|
|
34
34
|
}): Promise<void>;
|
|
35
35
|
getGraphTripleCount(graphUri: string): Promise<number>;
|
|
36
36
|
exportGraph(graphUri: string): Promise<string>;
|
|
37
|
-
diffGraph(graphUri: string, localTurtle: string): Promise<{
|
|
37
|
+
diffGraph(graphUri: string, localTurtle: string, limit?: number): Promise<{
|
|
38
38
|
added: string[];
|
|
39
39
|
removed: string[];
|
|
40
40
|
unchanged: number;
|
|
41
|
+
addedCount: number;
|
|
42
|
+
removedCount: number;
|
|
43
|
+
truncated: boolean;
|
|
41
44
|
}>;
|
|
42
45
|
getSchemaOverview(graphUri: string): Promise<{
|
|
43
46
|
prefixes: Record<string, string>;
|
package/dist/mcp/server.js
CHANGED
|
@@ -6,6 +6,7 @@ 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
8
|
import { normalizeModuleUri } from '../lib/module-uri.js';
|
|
9
|
+
import { snapshotGraph, listSnapshots, restoreSnapshot } from '../lib/snapshot.js';
|
|
9
10
|
import { hasGraphScope, autoScopeQuery, getInferenceGraphUri } from '../lib/sparql-utils.js';
|
|
10
11
|
import { validateTurtle } from '../lib/validator.js';
|
|
11
12
|
import { discoverShapes, validateWithShacl, hasShapes } from '../lib/shacl.js';
|
|
@@ -120,6 +121,7 @@ async function handlePush(args) {
|
|
|
120
121
|
});
|
|
121
122
|
const adapter = await createReadyAdapter(config);
|
|
122
123
|
if (replace) {
|
|
124
|
+
await snapshotGraph(adapter, config, graphUri);
|
|
123
125
|
await adapter.dropGraph(graphUri);
|
|
124
126
|
}
|
|
125
127
|
await adapter.insertTurtle(graphUri, content);
|
|
@@ -220,6 +222,7 @@ async function handleDrop(args) {
|
|
|
220
222
|
graph: args.graph,
|
|
221
223
|
});
|
|
222
224
|
const adapter = await createReadyAdapter(config);
|
|
225
|
+
await snapshotGraph(adapter, config, graphUri);
|
|
223
226
|
await adapter.dropGraph(graphUri);
|
|
224
227
|
await persistGraph(adapter, config, graphUri);
|
|
225
228
|
return { success: true, graphUri };
|
|
@@ -235,21 +238,44 @@ async function handleDelete(args) {
|
|
|
235
238
|
graph: args.graph,
|
|
236
239
|
});
|
|
237
240
|
const adapter = await createReadyAdapter(config);
|
|
241
|
+
await snapshotGraph(adapter, config, graphUri);
|
|
238
242
|
await adapter.deleteTriples(graphUri, { turtle: content, where });
|
|
239
243
|
await persistGraph(adapter, config, graphUri);
|
|
240
244
|
return { success: true };
|
|
241
245
|
}
|
|
246
|
+
async function handleRollback(args) {
|
|
247
|
+
const action = args.action;
|
|
248
|
+
const { config, graphUri } = resolveConfig({
|
|
249
|
+
graphUri: args.graphUri,
|
|
250
|
+
graph: args.graph,
|
|
251
|
+
});
|
|
252
|
+
if (action === 'list') {
|
|
253
|
+
const snapshots = listSnapshots(graphUri);
|
|
254
|
+
return { graphUri, snapshots };
|
|
255
|
+
}
|
|
256
|
+
if (action === 'restore') {
|
|
257
|
+
const to = args.to;
|
|
258
|
+
if (!to) {
|
|
259
|
+
throw new Error('timestamp (to) is required for restore action');
|
|
260
|
+
}
|
|
261
|
+
const adapter = await createReadyAdapter(config);
|
|
262
|
+
await restoreSnapshot(adapter, config, graphUri, to);
|
|
263
|
+
return { success: true, graphUri, restoredTo: to };
|
|
264
|
+
}
|
|
265
|
+
throw new Error(`Unknown rollback action: ${action}. Use 'list' or 'restore'.`);
|
|
266
|
+
}
|
|
242
267
|
async function handleDiff(args) {
|
|
243
268
|
const content = args.content;
|
|
244
269
|
if (!content) {
|
|
245
270
|
throw new Error('content (Turtle) is required');
|
|
246
271
|
}
|
|
272
|
+
const limit = typeof args.limit === 'number' ? args.limit : 50;
|
|
247
273
|
const { config, graphUri } = resolveConfig({
|
|
248
274
|
graphUri: args.graphUri,
|
|
249
275
|
graph: args.graph,
|
|
250
276
|
});
|
|
251
277
|
const adapter = await createReadyAdapter(config);
|
|
252
|
-
return await adapter.diffGraph(graphUri, content);
|
|
278
|
+
return await adapter.diffGraph(graphUri, content, limit);
|
|
253
279
|
}
|
|
254
280
|
async function handleGraphList(_args) {
|
|
255
281
|
const config = loadConfig();
|
|
@@ -310,6 +336,7 @@ async function handleGraphDrop(args) {
|
|
|
310
336
|
const config = loadConfig();
|
|
311
337
|
const graphUri = resolveGraphUri(config, name);
|
|
312
338
|
const adapter = await createReadyAdapter(config);
|
|
339
|
+
await snapshotGraph(adapter, config, graphUri);
|
|
313
340
|
await adapter.dropGraph(graphUri);
|
|
314
341
|
await persistGraph(adapter, config, graphUri);
|
|
315
342
|
const graphs = config.graphs ?? {};
|
|
@@ -326,10 +353,13 @@ async function handleInfer(args) {
|
|
|
326
353
|
});
|
|
327
354
|
const adapter = await createReadyAdapter(config);
|
|
328
355
|
if (clear) {
|
|
356
|
+
await snapshotGraph(adapter, config, graphUri);
|
|
329
357
|
await clearInferences(adapter, graphUri);
|
|
358
|
+
await persistGraph(adapter, config, graphUri);
|
|
330
359
|
return { success: true, cleared: true };
|
|
331
360
|
}
|
|
332
361
|
const result = await materializeInferences(adapter, graphUri);
|
|
362
|
+
await persistGraph(adapter, config, graphUri);
|
|
333
363
|
return result;
|
|
334
364
|
}
|
|
335
365
|
async function handleContextScan(args) {
|
|
@@ -362,7 +392,7 @@ async function handleContextScan(args) {
|
|
|
362
392
|
pushStats,
|
|
363
393
|
_experimental: true,
|
|
364
394
|
_hint: pushStats
|
|
365
|
-
? `Symbol triples pushed: ${pushStats.triplesInserted} triples in ${pushStats.batchCount} batches. Query
|
|
395
|
+
? `Symbol triples pushed: ${pushStats.triplesInserted} triples in ${pushStats.batchCount} batches. Query examples:\n- Classes: SELECT ?c ?name WHERE { ?c a otx:Class ; otx:title ?name }\n- Functions in a module: SELECT ?f ?name WHERE { ?f a otx:Function ; otx:title ?name ; otx:definedIn <urn:module:src/mcp/server> }\n- Call graph: SELECT ?caller ?callee WHERE { ?s a otx:MethodCall ; otx:callerSymbol ?caller ; otx:calleeSymbol ?callee }`
|
|
366
396
|
: 'Deep scan completed but triple push failed. Use push manually with the generated triples.',
|
|
367
397
|
};
|
|
368
398
|
}
|
|
@@ -376,6 +406,8 @@ async function handleContextScan(args) {
|
|
|
376
406
|
const adapter = await createReadyAdapter(config);
|
|
377
407
|
if (snapshot.dependencyGraph && snapshot.dependencyGraph.modules.length > 0) {
|
|
378
408
|
const dg = snapshot.dependencyGraph;
|
|
409
|
+
// Snapshot before destructive scan
|
|
410
|
+
await snapshotGraph(adapter, config, contextUri);
|
|
379
411
|
// Scoped delete: clear stale module triples before re-insert
|
|
380
412
|
await adapter.sparqlUpdate(`DELETE WHERE { GRAPH <${contextUri}> { ?m a <https://opentology.dev/vocab#Module> . ?m ?p ?o } }`);
|
|
381
413
|
// Insert fresh triples
|
|
@@ -525,6 +557,20 @@ async function handleContextInit(args) {
|
|
|
525
557
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
|
|
526
558
|
actions.push('Auto-registered hooks in .claude/settings.json');
|
|
527
559
|
}
|
|
560
|
+
// Ensure .opentology/snapshots/ is in .gitignore
|
|
561
|
+
const gitignorePath = join(process.cwd(), '.gitignore');
|
|
562
|
+
const snapshotIgnore = '.opentology/snapshots/';
|
|
563
|
+
if (existsSync(gitignorePath)) {
|
|
564
|
+
const gitignoreContent = readFileSync(gitignorePath, 'utf-8');
|
|
565
|
+
if (!gitignoreContent.includes(snapshotIgnore)) {
|
|
566
|
+
writeFileSync(gitignorePath, gitignoreContent.trimEnd() + '\n' + snapshotIgnore + '\n', 'utf-8');
|
|
567
|
+
actions.push('Added .opentology/snapshots/ to .gitignore');
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
writeFileSync(gitignorePath, snapshotIgnore + '\n', 'utf-8');
|
|
572
|
+
actions.push('Created .gitignore with .opentology/snapshots/');
|
|
573
|
+
}
|
|
528
574
|
// Auto-push Module triples from dependency graph
|
|
529
575
|
let moduleStats = null;
|
|
530
576
|
try {
|
|
@@ -560,6 +606,7 @@ async function handleContextInit(args) {
|
|
|
560
606
|
moduleStats,
|
|
561
607
|
dependencyHint,
|
|
562
608
|
hooksAutoInstalled: hooksChanged,
|
|
609
|
+
scanSuggestion: 'Run context_scan to populate the knowledge graph. Use depth="module" for file-level dependencies, or depth="symbol" (with includeMethodCalls=true) for class/function/call-level analysis. The symbol scan enables queries like "which functions call persistGraph?" from the graph.',
|
|
563
610
|
};
|
|
564
611
|
}
|
|
565
612
|
async function handleContextLoad() {
|
|
@@ -1043,7 +1090,7 @@ export async function startMcpServer() {
|
|
|
1043
1090
|
},
|
|
1044
1091
|
{
|
|
1045
1092
|
name: 'diff',
|
|
1046
|
-
description: 'Compare local Turtle content against the remote graph. Returns added triples (in local but not remote), removed triples (in remote but not local), and count of unchanged triples.',
|
|
1093
|
+
description: 'Compare local Turtle content against the remote graph. Returns added triples (in local but not remote), removed triples (in remote but not local), and count of unchanged triples. Output is limited to avoid blowing up LLM context windows.',
|
|
1047
1094
|
inputSchema: {
|
|
1048
1095
|
type: 'object',
|
|
1049
1096
|
properties: {
|
|
@@ -1051,6 +1098,10 @@ export async function startMcpServer() {
|
|
|
1051
1098
|
type: 'string',
|
|
1052
1099
|
description: 'Turtle (RDF) content to compare against the remote graph',
|
|
1053
1100
|
},
|
|
1101
|
+
limit: {
|
|
1102
|
+
type: 'number',
|
|
1103
|
+
description: 'Maximum number of added/removed triples to return (default: 50). Total counts are always included.',
|
|
1104
|
+
},
|
|
1054
1105
|
graph: {
|
|
1055
1106
|
type: 'string',
|
|
1056
1107
|
description: 'Logical graph name (as created by graph_create). Resolves to a graph URI via config.',
|
|
@@ -1262,6 +1313,33 @@ export async function startMcpServer() {
|
|
|
1262
1313
|
properties: {},
|
|
1263
1314
|
},
|
|
1264
1315
|
},
|
|
1316
|
+
{
|
|
1317
|
+
name: 'rollback',
|
|
1318
|
+
description: 'List or restore graph snapshots. Snapshots are automatically created before destructive operations (drop, delete, scan, etc.). Use action=list to see available snapshots, action=restore with a timestamp to restore.',
|
|
1319
|
+
inputSchema: {
|
|
1320
|
+
type: 'object',
|
|
1321
|
+
properties: {
|
|
1322
|
+
action: {
|
|
1323
|
+
type: 'string',
|
|
1324
|
+
enum: ['list', 'restore'],
|
|
1325
|
+
description: 'Action: list available snapshots or restore a specific one',
|
|
1326
|
+
},
|
|
1327
|
+
to: {
|
|
1328
|
+
type: 'string',
|
|
1329
|
+
description: 'Timestamp of the snapshot to restore (required for action=restore)',
|
|
1330
|
+
},
|
|
1331
|
+
graph: {
|
|
1332
|
+
type: 'string',
|
|
1333
|
+
description: 'Logical graph name (as created by graph_create). Resolves to a graph URI via config.',
|
|
1334
|
+
},
|
|
1335
|
+
graphUri: {
|
|
1336
|
+
type: 'string',
|
|
1337
|
+
description: 'Named graph URI (uses config default if omitted)',
|
|
1338
|
+
},
|
|
1339
|
+
},
|
|
1340
|
+
required: ['action'],
|
|
1341
|
+
},
|
|
1342
|
+
},
|
|
1265
1343
|
],
|
|
1266
1344
|
}));
|
|
1267
1345
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
@@ -1335,8 +1413,19 @@ export async function startMcpServer() {
|
|
|
1335
1413
|
case 'context_impact':
|
|
1336
1414
|
result = await handleContextImpact(args);
|
|
1337
1415
|
break;
|
|
1338
|
-
case 'context_sync':
|
|
1339
|
-
|
|
1416
|
+
case 'context_sync': {
|
|
1417
|
+
const syncConfig = loadConfig();
|
|
1418
|
+
const syncContextUri = `${syncConfig.graphUri}/context`;
|
|
1419
|
+
const syncSessionsUri = `${syncConfig.graphUri}/sessions`;
|
|
1420
|
+
const syncAdapter = await createReadyAdapter(syncConfig);
|
|
1421
|
+
await snapshotGraph(syncAdapter, syncConfig, syncContextUri);
|
|
1422
|
+
result = await syncContext(syncConfig, process.cwd());
|
|
1423
|
+
await persistGraph(syncAdapter, syncConfig, syncContextUri);
|
|
1424
|
+
await persistGraph(syncAdapter, syncConfig, syncSessionsUri);
|
|
1425
|
+
break;
|
|
1426
|
+
}
|
|
1427
|
+
case 'rollback':
|
|
1428
|
+
result = await handleRollback(args);
|
|
1340
1429
|
break;
|
|
1341
1430
|
case 'doctor':
|
|
1342
1431
|
result = await runDoctor();
|
|
@@ -26,6 +26,12 @@ This project uses OpenTology as its project context graph.
|
|
|
26
26
|
| \`otx:Knowledge\` | Reusable knowledge |
|
|
27
27
|
| \`otx:Session\` | Session logs |
|
|
28
28
|
| \`otx:Pattern\` | Recurring patterns/conventions |
|
|
29
|
+
| \`otx:Module\` | Source file module |
|
|
30
|
+
| \`otx:Class\` | Class definition (symbol scan) |
|
|
31
|
+
| \`otx:Interface\` | Interface definition (symbol scan) |
|
|
32
|
+
| \`otx:Function\` | Function definition (symbol scan) |
|
|
33
|
+
| \`otx:Method\` | Method definition (symbol scan) |
|
|
34
|
+
| \`otx:MethodCall\` | Call relationship between symbols (symbol scan) |
|
|
29
35
|
|
|
30
36
|
| Property | Range | Description |
|
|
31
37
|
|----------|-------|-------------|
|
|
@@ -36,6 +42,11 @@ This project uses OpenTology as its project context graph.
|
|
|
36
42
|
| \`otx:reason\` | string | Decision rationale |
|
|
37
43
|
| \`otx:nextTodo\` | string | Next action item |
|
|
38
44
|
| \`otx:relatedTo\` | resource | Related entity |
|
|
45
|
+
| \`otx:dependsOn\` | Module | Module import dependency |
|
|
46
|
+
| \`otx:definedIn\` | Module | Which module a symbol belongs to |
|
|
47
|
+
| \`otx:callerSymbol\` | string | Caller in a MethodCall |
|
|
48
|
+
| \`otx:calleeSymbol\` | string | Callee in a MethodCall |
|
|
49
|
+
| \`otx:calls\` | resource | Call relationship |
|
|
39
50
|
|
|
40
51
|
### When to Record
|
|
41
52
|
|
|
@@ -107,13 +118,48 @@ If impact is **high**, inform the user of affected modules and get confirmation
|
|
|
107
118
|
|
|
108
119
|
#### Searching the Knowledge Graph
|
|
109
120
|
|
|
110
|
-
Use \`query\` with SPARQL to find anything in the project graph:
|
|
121
|
+
Use \`query\` with SPARQL to find anything in the project graph. **Always query the graph before reading source files** when investigating code structure, dependencies, or call relationships:
|
|
122
|
+
|
|
111
123
|
- **Decisions**: \`?s a otx:Decision\` — why architectural choices were made
|
|
112
124
|
- **Issues**: \`?s a otx:Issue ; otx:status "open"\` — known bugs and their status
|
|
113
125
|
- **Knowledge**: \`?s a otx:Knowledge\` — reusable patterns and lessons learned
|
|
114
126
|
- **Sessions**: query the sessions graph for past work logs and next TODOs
|
|
115
127
|
- **Modules**: \`?s a otx:Module\` — all scanned source modules and their dependencies (\`otx:dependsOn\`)
|
|
116
128
|
- **Symbols**: \`?s a otx:Class\`, \`otx:Interface\`, \`otx:Function\`, \`otx:Method\` — code-level entities (available after symbol-depth scan)
|
|
129
|
+
- **Call graph**: \`?s a otx:MethodCall\` — who calls whom (available after symbol scan with \`includeMethodCalls=true\`)
|
|
130
|
+
|
|
131
|
+
\`\`\`sparql
|
|
132
|
+
# Functions in a specific module
|
|
133
|
+
PREFIX otx: <https://opentology.dev/vocab#>
|
|
134
|
+
SELECT ?name WHERE {
|
|
135
|
+
GRAPH <${contextUri}> {
|
|
136
|
+
?f a otx:Function ; otx:title ?name ; otx:definedIn <urn:module:src/mcp/server> .
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
# Who calls a specific function?
|
|
141
|
+
PREFIX otx: <https://opentology.dev/vocab#>
|
|
142
|
+
SELECT ?caller WHERE {
|
|
143
|
+
GRAPH <${contextUri}> {
|
|
144
|
+
?s a otx:MethodCall ; otx:callerSymbol ?caller ; otx:calleeSymbol ?callee .
|
|
145
|
+
FILTER(CONTAINS(?callee, "persistGraph"))
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# Module dependency chain (what depends on a module?)
|
|
150
|
+
PREFIX otx: <https://opentology.dev/vocab#>
|
|
151
|
+
SELECT ?dependent WHERE {
|
|
152
|
+
GRAPH <${contextUri}> {
|
|
153
|
+
?dependent otx:dependsOn+ <urn:module:src/lib/store-adapter> .
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
\`\`\`
|
|
157
|
+
|
|
158
|
+
#### Post-Edit Graph Update
|
|
159
|
+
|
|
160
|
+
After significant code changes (new files, renamed functions, changed dependencies), run \`context_scan\` to keep the knowledge graph in sync:
|
|
161
|
+
- \`depth="module"\` — fast, updates file-level imports
|
|
162
|
+
- \`depth="symbol"\` with \`includeMethodCalls=true\` — thorough, updates class/function/call graph
|
|
117
163
|
|
|
118
164
|
#### Other Useful Tools
|
|
119
165
|
|
|
@@ -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:
|
|
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 .\notx:MethodCall 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 .\notx:callerSymbol a owl:ObjectProperty ; rdfs:domain otx:MethodCall .\notx:calleeSymbol a owl:ObjectProperty ; rdfs:domain otx:MethodCall .\notx:returns a owl:DatatypeProperty ; rdfs:range xsd:string .\notx:paramType a owl:DatatypeProperty ; rdfs:range xsd:string .\n";
|
|
@@ -30,12 +30,15 @@ otx:Class a owl:Class .
|
|
|
30
30
|
otx:Interface a owl:Class .
|
|
31
31
|
otx:Function a owl:Class .
|
|
32
32
|
otx:Method a owl:Class .
|
|
33
|
+
otx:MethodCall a owl:Class .
|
|
33
34
|
|
|
34
35
|
otx:definedIn a owl:ObjectProperty ; rdfs:range otx:Module .
|
|
35
36
|
otx:extends a owl:ObjectProperty ; rdfs:domain otx:Class ; rdfs:range otx:Class .
|
|
36
37
|
otx:implements a owl:ObjectProperty ; rdfs:domain otx:Class ; rdfs:range otx:Interface .
|
|
37
38
|
otx:hasMethod a owl:ObjectProperty ; rdfs:domain otx:Class ; rdfs:range otx:Method .
|
|
38
|
-
otx:calls a owl:ObjectProperty
|
|
39
|
+
otx:calls a owl:ObjectProperty .
|
|
40
|
+
otx:callerSymbol a owl:ObjectProperty ; rdfs:domain otx:MethodCall .
|
|
41
|
+
otx:calleeSymbol a owl:ObjectProperty ; rdfs:domain otx:MethodCall .
|
|
39
42
|
otx:returns a owl:DatatypeProperty ; rdfs:range xsd:string .
|
|
40
43
|
otx:paramType a owl:DatatypeProperty ; rdfs:range xsd:string .
|
|
41
44
|
`;
|