opentology 0.4.0 → 0.4.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.d.ts +2 -0
- package/dist/commands/context.js +29 -0
- package/dist/lib/ask-engine.d.ts +31 -0
- package/dist/lib/ask-engine.js +111 -0
- package/dist/lib/context-sync.js +1 -3
- package/dist/lib/deep-scan-triples.js +2 -3
- package/dist/lib/git-utils.d.ts +6 -0
- package/dist/lib/git-utils.js +34 -0
- package/dist/lib/persist.d.ts +10 -0
- package/dist/lib/persist.js +29 -0
- package/dist/lib/scan-state.d.ts +8 -0
- package/dist/lib/scan-state.js +32 -0
- package/dist/lib/snapshot.js +1 -1
- package/dist/lib/sparql-utils.d.ts +5 -0
- package/dist/lib/sparql-utils.js +16 -6
- package/dist/mcp/handlers/context.d.ts +33 -0
- package/dist/mcp/handlers/context.js +713 -0
- package/dist/mcp/handlers/rdf.d.ts +18 -0
- package/dist/mcp/handlers/rdf.js +182 -0
- package/dist/mcp/handlers/schema.d.ts +25 -0
- package/dist/mcp/handlers/schema.js +55 -0
- package/dist/mcp/handlers/system.d.ts +22 -0
- package/dist/mcp/handlers/system.js +135 -0
- package/dist/mcp/server.d.ts +1 -37
- package/dist/mcp/server.js +78 -1085
- package/dist/templates/builtin-predicates.d.ts +1 -0
- package/dist/templates/builtin-predicates.js +33 -0
- package/dist/templates/claude-md-context.js +32 -113
- package/dist/templates/otx-ontology.d.ts +1 -1
- package/dist/templates/otx-ontology.js +10 -0
- package/dist/templates/post-edit-hook.d.ts +1 -0
- package/dist/templates/post-edit-hook.js +78 -0
- package/dist/templates/slash-commands.js +158 -3
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { handleContextScan } from '../mcp/server.js';
|
|
2
|
+
export function registerContext(program) {
|
|
3
|
+
const ctx = program.command('context').description('Context management commands');
|
|
4
|
+
ctx
|
|
5
|
+
.command('scan')
|
|
6
|
+
.description('Scan the codebase and update the knowledge graph')
|
|
7
|
+
.option('--depth <depth>', 'Scan depth: "module" or "symbol"', 'symbol')
|
|
8
|
+
.option('--incremental', 'Only scan files changed since the last scan', false)
|
|
9
|
+
.option('--max-files <n>', 'Maximum files to scan (symbol depth)', parseInt)
|
|
10
|
+
.option('--max-symbols <n>', 'Maximum symbols to extract (symbol depth)', parseInt)
|
|
11
|
+
.option('--timeout <ms>', 'Timeout in milliseconds (symbol depth)', parseInt)
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
try {
|
|
14
|
+
const result = await handleContextScan({
|
|
15
|
+
depth: opts.depth,
|
|
16
|
+
incremental: opts.incremental,
|
|
17
|
+
maxFiles: opts.maxFiles,
|
|
18
|
+
maxSymbols: opts.maxSymbols,
|
|
19
|
+
timeoutMs: opts.timeout,
|
|
20
|
+
});
|
|
21
|
+
console.log(JSON.stringify(result, null, 2));
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
25
|
+
console.error(`context scan failed: ${msg}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { StoreAdapter } from './store-adapter.js';
|
|
2
|
+
export interface AskInput {
|
|
3
|
+
predicate: string;
|
|
4
|
+
context: Record<string, string>;
|
|
5
|
+
record?: boolean;
|
|
6
|
+
graph?: string;
|
|
7
|
+
graphUri?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AskOutput {
|
|
10
|
+
answer: boolean | null;
|
|
11
|
+
reason?: string;
|
|
12
|
+
missing?: string[];
|
|
13
|
+
evaluationUri?: string;
|
|
14
|
+
bindings?: Array<Record<string, string>>;
|
|
15
|
+
}
|
|
16
|
+
interface ResolvedPredicate {
|
|
17
|
+
uri: string;
|
|
18
|
+
title: string;
|
|
19
|
+
sparqlTemplate: string;
|
|
20
|
+
requiredParams: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare function resolvePredicate(adapter: StoreAdapter, predicateName: string, contextGraphUri: string): Promise<ResolvedPredicate | null>;
|
|
23
|
+
export declare function validateContext(predicate: ResolvedPredicate, context: Record<string, string>): string[];
|
|
24
|
+
export declare function bindTemplate(template: string, context: Record<string, string>, graphUri: string): string;
|
|
25
|
+
export declare function evaluate(adapter: StoreAdapter, sparql: string): Promise<{
|
|
26
|
+
answer: boolean;
|
|
27
|
+
bindings?: Array<Record<string, string>>;
|
|
28
|
+
}>;
|
|
29
|
+
export declare function recordEvaluation(adapter: StoreAdapter, graphUri: string, predicateUri: string, context: Record<string, string>, answer: boolean | null, sessionUri?: string): Promise<string>;
|
|
30
|
+
export declare function ask(adapter: StoreAdapter, contextGraphUri: string, input: AskInput): Promise<AskOutput>;
|
|
31
|
+
export {};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// ── Resolve ──
|
|
2
|
+
export async function resolvePredicate(adapter, predicateName, contextGraphUri) {
|
|
3
|
+
const sparql = `
|
|
4
|
+
PREFIX otx: <https://opentology.dev/vocab#>
|
|
5
|
+
SELECT ?uri ?title ?template ?param WHERE {
|
|
6
|
+
GRAPH <${contextGraphUri}> {
|
|
7
|
+
?uri a otx:Predicate ;
|
|
8
|
+
otx:title ?title ;
|
|
9
|
+
otx:sparqlTemplate ?template .
|
|
10
|
+
OPTIONAL { ?uri otx:requiredParam ?param }
|
|
11
|
+
FILTER(?title = "${predicateName}" || ?uri = <urn:predicate:${predicateName}>)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
`;
|
|
15
|
+
const results = await adapter.sparqlQuery(sparql);
|
|
16
|
+
const bindings = results.results.bindings;
|
|
17
|
+
if (bindings.length === 0)
|
|
18
|
+
return null;
|
|
19
|
+
const first = bindings[0];
|
|
20
|
+
const requiredParams = bindings
|
|
21
|
+
.map((b) => b.param?.value)
|
|
22
|
+
.filter((v) => !!v);
|
|
23
|
+
return {
|
|
24
|
+
uri: first.uri.value,
|
|
25
|
+
title: first.title.value,
|
|
26
|
+
sparqlTemplate: first.template.value,
|
|
27
|
+
requiredParams: [...new Set(requiredParams)],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// ── Validate ──
|
|
31
|
+
export function validateContext(predicate, context) {
|
|
32
|
+
return predicate.requiredParams.filter((p) => !(p in context));
|
|
33
|
+
}
|
|
34
|
+
// ── Evaluate ──
|
|
35
|
+
export function bindTemplate(template, context, graphUri) {
|
|
36
|
+
let bound = template;
|
|
37
|
+
bound = bound.replace(/\{\{graphUri\}\}/g, graphUri);
|
|
38
|
+
for (const [key, value] of Object.entries(context)) {
|
|
39
|
+
bound = bound.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
|
|
40
|
+
}
|
|
41
|
+
return bound;
|
|
42
|
+
}
|
|
43
|
+
export async function evaluate(adapter, sparql) {
|
|
44
|
+
const trimmed = sparql.trim();
|
|
45
|
+
// ASK query → boolean
|
|
46
|
+
if (/^(PREFIX\s+\S+:\s+<[^>]+>\s+)*ASK\b/i.test(trimmed)) {
|
|
47
|
+
const answer = await adapter.askQuery(trimmed);
|
|
48
|
+
return { answer };
|
|
49
|
+
}
|
|
50
|
+
// SELECT query → has results?
|
|
51
|
+
const results = await adapter.sparqlQuery(trimmed);
|
|
52
|
+
const bindings = results.results.bindings.map((b) => {
|
|
53
|
+
const row = {};
|
|
54
|
+
for (const [k, v] of Object.entries(b)) {
|
|
55
|
+
row[k] = v.value;
|
|
56
|
+
}
|
|
57
|
+
return row;
|
|
58
|
+
});
|
|
59
|
+
return { answer: bindings.length > 0, bindings: bindings.length > 0 ? bindings : undefined };
|
|
60
|
+
}
|
|
61
|
+
// ── Record ──
|
|
62
|
+
export async function recordEvaluation(adapter, graphUri, predicateUri, context, answer, sessionUri) {
|
|
63
|
+
const now = new Date().toISOString().slice(0, 10);
|
|
64
|
+
const seq = Date.now() % 100000;
|
|
65
|
+
const evalUri = `urn:evaluation:${now}-${seq}`;
|
|
66
|
+
const inputJson = JSON.stringify(context).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
67
|
+
let turtle = `
|
|
68
|
+
@prefix otx: <https://opentology.dev/vocab#> .
|
|
69
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
70
|
+
|
|
71
|
+
<${evalUri}> a otx:Evaluation ;
|
|
72
|
+
otx:predicate <${predicateUri}> ;
|
|
73
|
+
otx:input "${inputJson}" ;
|
|
74
|
+
otx:result "${String(answer)}" ;
|
|
75
|
+
otx:date "${now}"^^xsd:date .
|
|
76
|
+
`;
|
|
77
|
+
if (sessionUri) {
|
|
78
|
+
turtle += `<${evalUri}> otx:createdIn <${sessionUri}> .\n`;
|
|
79
|
+
}
|
|
80
|
+
await adapter.insertTurtle(graphUri, turtle);
|
|
81
|
+
return evalUri;
|
|
82
|
+
}
|
|
83
|
+
// ── Main ask() ──
|
|
84
|
+
export async function ask(adapter, contextGraphUri, input) {
|
|
85
|
+
// 1. Resolve predicate
|
|
86
|
+
const predicate = await resolvePredicate(adapter, input.predicate, contextGraphUri);
|
|
87
|
+
if (!predicate) {
|
|
88
|
+
return { answer: null, reason: `Unknown predicate: "${input.predicate}"` };
|
|
89
|
+
}
|
|
90
|
+
// 2. Validate required params
|
|
91
|
+
const missing = validateContext(predicate, input.context);
|
|
92
|
+
if (missing.length > 0) {
|
|
93
|
+
return { answer: null, missing, reason: `Missing required parameters: ${missing.join(', ')}` };
|
|
94
|
+
}
|
|
95
|
+
// 3. Bind template and evaluate
|
|
96
|
+
const boundSparql = bindTemplate(predicate.sparqlTemplate, input.context, contextGraphUri);
|
|
97
|
+
const { answer, bindings } = await evaluate(adapter, boundSparql);
|
|
98
|
+
// 4. Optionally record
|
|
99
|
+
let evaluationUri;
|
|
100
|
+
if (input.record !== false) {
|
|
101
|
+
evaluationUri = await recordEvaluation(adapter, contextGraphUri, predicate.uri, input.context, answer);
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
answer,
|
|
105
|
+
reason: answer
|
|
106
|
+
? `Predicate "${predicate.title}" evaluated to true`
|
|
107
|
+
: `Predicate "${predicate.title}" evaluated to false`,
|
|
108
|
+
evaluationUri,
|
|
109
|
+
bindings,
|
|
110
|
+
};
|
|
111
|
+
}
|
package/dist/lib/context-sync.js
CHANGED
|
@@ -4,6 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { addTrackedFile, saveConfig } from './config.js';
|
|
5
5
|
import { createReadyAdapter } from './store-factory.js';
|
|
6
6
|
import { extractDependencyGraph } from './codebase-scanner.js';
|
|
7
|
+
import { escapeTurtleLiteral as escapeTurtle } from './sparql-utils.js';
|
|
7
8
|
const OTX = 'https://opentology.dev/vocab#';
|
|
8
9
|
const XSD = 'http://www.w3.org/2001/XMLSchema#';
|
|
9
10
|
/**
|
|
@@ -224,6 +225,3 @@ export async function syncContext(config, projectRoot) {
|
|
|
224
225
|
saveConfig(config);
|
|
225
226
|
return { sessionsRecovered, modulesUpdated, moduleStats, actions };
|
|
226
227
|
}
|
|
227
|
-
function escapeTurtle(s) {
|
|
228
|
-
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
|
|
229
|
-
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Converts DeepScanResult into OTX-compliant SPARQL INSERT DATA triples
|
|
3
3
|
* with batched insert and scoped delete-then-insert strategy.
|
|
4
4
|
*/
|
|
5
|
+
import { escapeTurtleLiteral } from './sparql-utils.js';
|
|
5
6
|
const OTX = 'https://opentology.dev/vocab#';
|
|
6
7
|
function encodeSegment(s) {
|
|
7
8
|
return encodeURIComponent(s);
|
|
@@ -12,9 +13,7 @@ function symbolUri(filePath, kind, name) {
|
|
|
12
13
|
function moduleUri(filePath) {
|
|
13
14
|
return `urn:module:${filePath}`;
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
17
|
-
}
|
|
16
|
+
const esc = escapeTurtleLiteral;
|
|
18
17
|
// ── Triple generation ───────────────────────────────────────────
|
|
19
18
|
export function generateSymbolTriples(result) {
|
|
20
19
|
const triples = [];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns absolute paths of source files changed since the given git ref.
|
|
3
|
+
* Includes both committed changes (sinceRef..HEAD) and staged changes.
|
|
4
|
+
* Returns [] on any git error (graceful fallback).
|
|
5
|
+
*/
|
|
6
|
+
export declare function getChangedSourceFiles(rootDir: string, sinceRef: string): string[];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { extname, join } from 'node:path';
|
|
4
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
5
|
+
'.ts', '.js', '.tsx', '.jsx',
|
|
6
|
+
'.py', '.go', '.rs', '.java', '.swift',
|
|
7
|
+
]);
|
|
8
|
+
/**
|
|
9
|
+
* Returns absolute paths of source files changed since the given git ref.
|
|
10
|
+
* Includes both committed changes (sinceRef..HEAD) and staged changes.
|
|
11
|
+
* Returns [] on any git error (graceful fallback).
|
|
12
|
+
*/
|
|
13
|
+
export function getChangedSourceFiles(rootDir, sinceRef) {
|
|
14
|
+
try {
|
|
15
|
+
// Verify sinceRef is reachable (guards against shallow clones or rebased history)
|
|
16
|
+
execFileSync('git', ['cat-file', '-e', sinceRef], {
|
|
17
|
+
cwd: rootDir, timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'],
|
|
18
|
+
});
|
|
19
|
+
const committed = execFileSync('git', ['diff', '--name-only', sinceRef, 'HEAD'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
20
|
+
const staged = execFileSync('git', ['diff', '--name-only', '--cached'], { cwd: rootDir, encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
21
|
+
const relative = [
|
|
22
|
+
...committed.split('\n').filter(Boolean),
|
|
23
|
+
...staged.split('\n').filter(Boolean),
|
|
24
|
+
];
|
|
25
|
+
const unique = [...new Set(relative)];
|
|
26
|
+
return unique
|
|
27
|
+
.filter(f => SOURCE_EXTENSIONS.has(extname(f).toLowerCase()))
|
|
28
|
+
.map(f => join(rootDir, f))
|
|
29
|
+
.filter(f => existsSync(f));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { StoreAdapter } from './store-adapter.js';
|
|
2
|
+
import type { OpenTologyConfig } from './config.js';
|
|
3
|
+
export declare const MAX_TRIPLES_PER_PUSH = 100;
|
|
4
|
+
export declare function assertTripleLimit(tripleCount: number): void;
|
|
5
|
+
/**
|
|
6
|
+
* Persist a named graph to a .ttl file in embedded mode.
|
|
7
|
+
* Exports the full graph, writes to .opentology/data/{slug}.ttl, and tracks
|
|
8
|
+
* the file in config — mirroring CLI push behavior.
|
|
9
|
+
*/
|
|
10
|
+
export declare function persistGraph(adapter: StoreAdapter, config: OpenTologyConfig, graphUri: string): Promise<void>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { addTrackedFile, saveConfig } from './config.js';
|
|
4
|
+
export const MAX_TRIPLES_PER_PUSH = 100;
|
|
5
|
+
export function assertTripleLimit(tripleCount) {
|
|
6
|
+
if (tripleCount > MAX_TRIPLES_PER_PUSH) {
|
|
7
|
+
throw new Error(`Too many triples (${tripleCount}). Maximum is ${MAX_TRIPLES_PER_PUSH} per push. Split your data into smaller batches.`);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Persist a named graph to a .ttl file in embedded mode.
|
|
12
|
+
* Exports the full graph, writes to .opentology/data/{slug}.ttl, and tracks
|
|
13
|
+
* the file in config — mirroring CLI push behavior.
|
|
14
|
+
*/
|
|
15
|
+
export async function persistGraph(adapter, config, graphUri) {
|
|
16
|
+
if (config.mode !== 'embedded')
|
|
17
|
+
return;
|
|
18
|
+
const exported = await adapter.exportGraph(graphUri);
|
|
19
|
+
if (!exported.trim())
|
|
20
|
+
return;
|
|
21
|
+
// Derive a filename slug from the graph URI
|
|
22
|
+
const slug = graphUri.replace(/[^a-zA-Z0-9-]/g, '_').replace(/_+/g, '_');
|
|
23
|
+
const dataDir = join(process.cwd(), '.opentology', 'data');
|
|
24
|
+
const filePath = join(dataDir, `${slug}.ttl`);
|
|
25
|
+
mkdirSync(dataDir, { recursive: true });
|
|
26
|
+
writeFileSync(filePath, exported, 'utf-8');
|
|
27
|
+
addTrackedFile(config, graphUri, filePath);
|
|
28
|
+
saveConfig(config);
|
|
29
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export interface ScanState {
|
|
2
|
+
lastScanRef: string;
|
|
3
|
+
lastScanAt: string;
|
|
4
|
+
scannedFiles: string[];
|
|
5
|
+
}
|
|
6
|
+
export declare function readScanState(rootDir: string): ScanState | null;
|
|
7
|
+
export declare function writeScanState(rootDir: string, state: ScanState): void;
|
|
8
|
+
export declare function getCurrentGitRef(rootDir: string): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
const STATE_FILE = '.opentology/last-scan.json';
|
|
5
|
+
export function readScanState(rootDir) {
|
|
6
|
+
const stateFile = join(rootDir, STATE_FILE);
|
|
7
|
+
if (!existsSync(stateFile))
|
|
8
|
+
return null;
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(readFileSync(stateFile, 'utf-8'));
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function writeScanState(rootDir, state) {
|
|
17
|
+
const stateFile = join(rootDir, STATE_FILE);
|
|
18
|
+
writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
export function getCurrentGitRef(rootDir) {
|
|
21
|
+
try {
|
|
22
|
+
return execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
23
|
+
cwd: rootDir,
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
timeout: 5000,
|
|
26
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
27
|
+
}).trim();
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return 'HEAD';
|
|
31
|
+
}
|
|
32
|
+
}
|
package/dist/lib/snapshot.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync, statSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { persistGraph } from '
|
|
3
|
+
import { persistGraph } from './persist.js';
|
|
4
4
|
const DEFAULT_RETENTION = 5;
|
|
5
5
|
/**
|
|
6
6
|
* Derive a filesystem-safe slug from a graph URI.
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { Quad } from 'n3';
|
|
2
2
|
export declare const WELL_KNOWN_PREFIXES: Record<string, string>;
|
|
3
|
+
/**
|
|
4
|
+
* Escape a string for use inside a Turtle double-quoted literal.
|
|
5
|
+
* Handles backslash, quotes, control characters, and non-BMP Unicode.
|
|
6
|
+
*/
|
|
7
|
+
export declare function escapeTurtleLiteral(s: string): string;
|
|
3
8
|
export declare function termToSparql(term: Quad['subject'] | Quad['predicate'] | Quad['object']): string;
|
|
4
9
|
export declare function parseTurtle(turtle: string): Promise<Quad[]>;
|
|
5
10
|
export declare function serializeQuadsToTurtle(quads: Quad[]): Promise<string>;
|
package/dist/lib/sparql-utils.js
CHANGED
|
@@ -13,6 +13,21 @@ export const WELL_KNOWN_PREFIXES = {
|
|
|
13
13
|
'http://www.w3.org/ns/prov#': 'prov',
|
|
14
14
|
};
|
|
15
15
|
// ── Term / Turtle helpers ─────────────────────────────────────────────
|
|
16
|
+
/**
|
|
17
|
+
* Escape a string for use inside a Turtle double-quoted literal.
|
|
18
|
+
* Handles backslash, quotes, control characters, and non-BMP Unicode.
|
|
19
|
+
*/
|
|
20
|
+
export function escapeTurtleLiteral(s) {
|
|
21
|
+
return s
|
|
22
|
+
.replace(/\\/g, '\\\\')
|
|
23
|
+
.replace(/"/g, '\\"')
|
|
24
|
+
.replace(/\n/g, '\\n')
|
|
25
|
+
.replace(/\r/g, '\\r')
|
|
26
|
+
.replace(/\t/g, '\\t')
|
|
27
|
+
.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, (ch) => {
|
|
28
|
+
return '\\u' + ch.charCodeAt(0).toString(16).padStart(4, '0');
|
|
29
|
+
});
|
|
30
|
+
}
|
|
16
31
|
export function termToSparql(term) {
|
|
17
32
|
switch (term.termType) {
|
|
18
33
|
case 'NamedNode':
|
|
@@ -20,12 +35,7 @@ export function termToSparql(term) {
|
|
|
20
35
|
case 'BlankNode':
|
|
21
36
|
return `_:${term.value}`;
|
|
22
37
|
case 'Literal': {
|
|
23
|
-
const escaped = term.value
|
|
24
|
-
.replace(/\\/g, '\\\\')
|
|
25
|
-
.replace(/"/g, '\\"')
|
|
26
|
-
.replace(/\n/g, '\\n')
|
|
27
|
-
.replace(/\r/g, '\\r')
|
|
28
|
-
.replace(/\t/g, '\\t');
|
|
38
|
+
const escaped = escapeTurtleLiteral(term.value);
|
|
29
39
|
if (term.language) {
|
|
30
40
|
return `"${escaped}"@${term.language}`;
|
|
31
41
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface ContextLoadOutput {
|
|
2
|
+
projectId: string;
|
|
3
|
+
graphUri: string;
|
|
4
|
+
sessions: Array<{
|
|
5
|
+
uri: string;
|
|
6
|
+
title: string;
|
|
7
|
+
date: string;
|
|
8
|
+
nextTodo?: string;
|
|
9
|
+
}>;
|
|
10
|
+
openIssues: Array<{
|
|
11
|
+
uri: string;
|
|
12
|
+
title: string;
|
|
13
|
+
date: string;
|
|
14
|
+
}>;
|
|
15
|
+
recentDecisions: Array<{
|
|
16
|
+
uri: string;
|
|
17
|
+
title: string;
|
|
18
|
+
date: string;
|
|
19
|
+
reason?: string;
|
|
20
|
+
}>;
|
|
21
|
+
meta: {
|
|
22
|
+
contextTripleCount: number;
|
|
23
|
+
sessionsTripleCount: number;
|
|
24
|
+
loadedAt: string;
|
|
25
|
+
};
|
|
26
|
+
warnings?: string[];
|
|
27
|
+
}
|
|
28
|
+
export declare function handleContextScan(args: Record<string, unknown>): Promise<unknown>;
|
|
29
|
+
export declare function handleContextInit(args: Record<string, unknown>): Promise<unknown>;
|
|
30
|
+
export declare function handleContextLoad(): Promise<ContextLoadOutput>;
|
|
31
|
+
export declare function handleContextSearch(args: Record<string, unknown>): Promise<unknown>;
|
|
32
|
+
export declare function handleContextImpact(args: Record<string, unknown>): Promise<unknown>;
|
|
33
|
+
export declare function handleContextStatus(): Promise<unknown>;
|