@iflow-mcp/andrewhopper-facts-server 1.0.0
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/build/index.d.ts +2 -0
- package/build/index.js +341 -0
- package/build/sdk/server.d.ts +13 -0
- package/build/sdk/server.js +31 -0
- package/build/sdk/stdio.d.ts +8 -0
- package/build/sdk/stdio.js +34 -0
- package/build/sdk/types.d.ts +30 -0
- package/build/sdk/types.js +1 -0
- package/build/storage.d.ts +17 -0
- package/build/storage.js +193 -0
- package/build/types.d.ts +75 -0
- package/build/types.js +41 -0
- package/build/validation.d.ts +9 -0
- package/build/validation.js +52 -0
- package/package.json +1 -0
- package/prisma/facts.db +0 -0
- package/prisma/migrations/20250223115817_init/migration.sql +32 -0
- package/prisma/migrations/20250223171046_add_fact_category/migration.sql +54 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +43 -0
- package/prisma/setup_vector.sql +9 -0
- package/src/index.ts +387 -0
- package/src/storage.ts +244 -0
- package/src/types.ts +112 -0
- package/src/validation.ts +70 -0
- package/tsconfig.json +21 -0
package/build/index.d.ts
ADDED
package/build/index.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js';
|
|
5
|
+
import { PrismaStorageProvider } from './storage.js';
|
|
6
|
+
import { validateCriteria } from './validation.js';
|
|
7
|
+
import { StrictnessLevel, FactCategory } from './types.js';
|
|
8
|
+
class FactsServer {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.storage = new PrismaStorageProvider();
|
|
11
|
+
this.server = new Server({
|
|
12
|
+
name: 'facts-server',
|
|
13
|
+
version: '0.1.0',
|
|
14
|
+
}, {
|
|
15
|
+
capabilities: {
|
|
16
|
+
tools: {
|
|
17
|
+
get_all_facts: {
|
|
18
|
+
description: 'Get all facts in the system',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
get_fact: {
|
|
25
|
+
description: 'Get a fact by ID',
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
id: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'The ID of the fact to retrieve'
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
required: ['id']
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
search_facts: {
|
|
38
|
+
description: 'Search facts by type, strictness, and version',
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {
|
|
42
|
+
type: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'Filter by fact type'
|
|
45
|
+
},
|
|
46
|
+
strictness: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
enum: Object.values(StrictnessLevel),
|
|
49
|
+
description: 'Filter by strictness level'
|
|
50
|
+
},
|
|
51
|
+
version: {
|
|
52
|
+
type: 'string',
|
|
53
|
+
description: 'Filter by version compatibility'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
set_fact: {
|
|
59
|
+
description: 'Create or update a fact',
|
|
60
|
+
inputSchema: {
|
|
61
|
+
type: 'object',
|
|
62
|
+
properties: {
|
|
63
|
+
id: { type: 'string' },
|
|
64
|
+
content: { type: 'string' },
|
|
65
|
+
strictness: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
enum: Object.values(StrictnessLevel)
|
|
68
|
+
},
|
|
69
|
+
type: { type: 'string' },
|
|
70
|
+
category: {
|
|
71
|
+
type: 'string',
|
|
72
|
+
enum: Object.values(FactCategory),
|
|
73
|
+
description: 'The category this fact belongs to'
|
|
74
|
+
},
|
|
75
|
+
minVersion: { type: 'string' },
|
|
76
|
+
maxVersion: { type: 'string' },
|
|
77
|
+
conditions: {
|
|
78
|
+
type: 'array',
|
|
79
|
+
items: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
factId: { type: 'string' },
|
|
83
|
+
type: {
|
|
84
|
+
type: 'string',
|
|
85
|
+
enum: ['REQUIRES', 'CONFLICTS_WITH']
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
required: ['factId', 'type']
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
acceptanceCriteria: {
|
|
92
|
+
type: 'array',
|
|
93
|
+
items: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
id: { type: 'string' },
|
|
97
|
+
description: { type: 'string' },
|
|
98
|
+
validationType: {
|
|
99
|
+
type: 'string',
|
|
100
|
+
enum: ['MANUAL', 'AUTOMATED']
|
|
101
|
+
},
|
|
102
|
+
validationScript: { type: 'string' }
|
|
103
|
+
},
|
|
104
|
+
required: ['id', 'description', 'validationType']
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
required: ['id', 'content', 'strictness', 'type', 'category', 'minVersion', 'maxVersion']
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
validate_criteria: {
|
|
112
|
+
description: 'Validate content against fact acceptance criteria',
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
factId: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
description: 'ID of the fact to validate against'
|
|
119
|
+
},
|
|
120
|
+
content: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
description: 'Content to validate'
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
required: ['factId', 'content']
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
this.setupHandlers();
|
|
132
|
+
}
|
|
133
|
+
setupHandlers() {
|
|
134
|
+
// Handler for listing available tools
|
|
135
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
136
|
+
tools: [
|
|
137
|
+
{
|
|
138
|
+
name: 'get_all_facts',
|
|
139
|
+
description: 'Get all facts in the system',
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: 'object',
|
|
142
|
+
properties: {}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: 'get_fact',
|
|
147
|
+
description: 'Get a fact by ID',
|
|
148
|
+
inputSchema: {
|
|
149
|
+
type: 'object',
|
|
150
|
+
properties: {
|
|
151
|
+
id: {
|
|
152
|
+
type: 'string',
|
|
153
|
+
description: 'The ID of the fact to retrieve'
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
required: ['id']
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'search_facts',
|
|
161
|
+
description: 'Search facts by type, strictness, and version',
|
|
162
|
+
inputSchema: {
|
|
163
|
+
type: 'object',
|
|
164
|
+
properties: {
|
|
165
|
+
type: {
|
|
166
|
+
type: 'string',
|
|
167
|
+
description: 'Filter by fact type'
|
|
168
|
+
},
|
|
169
|
+
strictness: {
|
|
170
|
+
type: 'string',
|
|
171
|
+
enum: Object.values(StrictnessLevel),
|
|
172
|
+
description: 'Filter by strictness level'
|
|
173
|
+
},
|
|
174
|
+
version: {
|
|
175
|
+
type: 'string',
|
|
176
|
+
description: 'Filter by version compatibility'
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'set_fact',
|
|
183
|
+
description: 'Create or update a fact',
|
|
184
|
+
inputSchema: {
|
|
185
|
+
type: 'object',
|
|
186
|
+
properties: {
|
|
187
|
+
id: { type: 'string' },
|
|
188
|
+
content: { type: 'string' },
|
|
189
|
+
strictness: {
|
|
190
|
+
type: 'string',
|
|
191
|
+
enum: Object.values(StrictnessLevel)
|
|
192
|
+
},
|
|
193
|
+
type: { type: 'string' },
|
|
194
|
+
category: {
|
|
195
|
+
type: 'string',
|
|
196
|
+
enum: Object.values(FactCategory),
|
|
197
|
+
description: 'The category this fact belongs to'
|
|
198
|
+
},
|
|
199
|
+
minVersion: { type: 'string' },
|
|
200
|
+
maxVersion: { type: 'string' },
|
|
201
|
+
conditions: {
|
|
202
|
+
type: 'array',
|
|
203
|
+
items: {
|
|
204
|
+
type: 'object',
|
|
205
|
+
properties: {
|
|
206
|
+
factId: { type: 'string' },
|
|
207
|
+
type: {
|
|
208
|
+
type: 'string',
|
|
209
|
+
enum: ['REQUIRES', 'CONFLICTS_WITH']
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
required: ['factId', 'type']
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
acceptanceCriteria: {
|
|
216
|
+
type: 'array',
|
|
217
|
+
items: {
|
|
218
|
+
type: 'object',
|
|
219
|
+
properties: {
|
|
220
|
+
id: { type: 'string' },
|
|
221
|
+
description: { type: 'string' },
|
|
222
|
+
validationType: {
|
|
223
|
+
type: 'string',
|
|
224
|
+
enum: ['MANUAL', 'AUTOMATED']
|
|
225
|
+
},
|
|
226
|
+
validationScript: { type: 'string' }
|
|
227
|
+
},
|
|
228
|
+
required: ['id', 'description', 'validationType']
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
required: ['id', 'content', 'strictness', 'type', 'category', 'minVersion', 'maxVersion']
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: 'validate_criteria',
|
|
237
|
+
description: 'Validate content against fact acceptance criteria',
|
|
238
|
+
inputSchema: {
|
|
239
|
+
type: 'object',
|
|
240
|
+
properties: {
|
|
241
|
+
factId: {
|
|
242
|
+
type: 'string',
|
|
243
|
+
description: 'ID of the fact to validate against'
|
|
244
|
+
},
|
|
245
|
+
content: {
|
|
246
|
+
type: 'string',
|
|
247
|
+
description: 'Content to validate'
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
required: ['factId', 'content']
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
}));
|
|
255
|
+
// Handler for executing tools
|
|
256
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
257
|
+
switch (request.params.name) {
|
|
258
|
+
case 'get_all_facts': {
|
|
259
|
+
const facts = await this.storage.searchFacts({});
|
|
260
|
+
return {
|
|
261
|
+
content: [{ type: 'text', text: JSON.stringify(facts, null, 2) }]
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
case 'get_fact': {
|
|
265
|
+
const args = request.params.arguments;
|
|
266
|
+
const fact = await this.storage.getFact(args.id);
|
|
267
|
+
if (!fact) {
|
|
268
|
+
return {
|
|
269
|
+
content: [{ type: 'text', text: `Fact not found: ${args.id}` }],
|
|
270
|
+
isError: true
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return {
|
|
274
|
+
content: [{ type: 'text', text: JSON.stringify(fact, null, 2) }]
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
case 'search_facts': {
|
|
278
|
+
const args = request.params.arguments;
|
|
279
|
+
const facts = await this.storage.searchFacts(args);
|
|
280
|
+
return {
|
|
281
|
+
content: [{ type: 'text', text: JSON.stringify(facts, null, 2) }]
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
case 'set_fact': {
|
|
285
|
+
const args = request.params.arguments;
|
|
286
|
+
try {
|
|
287
|
+
await this.storage.setFact(args.id, args.content, args.strictness, args.type, args.category, args.minVersion, args.maxVersion, args.conditions || [], args.acceptanceCriteria || []);
|
|
288
|
+
return {
|
|
289
|
+
content: [{ type: 'text', text: `Fact ${args.id} saved successfully` }]
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
return {
|
|
294
|
+
content: [{ type: 'text', text: `Error saving fact: ${error instanceof Error ? error.message : 'Unknown error'}` }],
|
|
295
|
+
isError: true
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
case 'validate_criteria': {
|
|
300
|
+
const args = request.params.arguments;
|
|
301
|
+
const fact = await this.storage.getFact(args.factId);
|
|
302
|
+
if (!fact) {
|
|
303
|
+
return {
|
|
304
|
+
content: [{ type: 'text', text: `Fact not found: ${args.factId}` }],
|
|
305
|
+
isError: true
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
try {
|
|
309
|
+
const results = await validateCriteria(args.content, fact.acceptanceCriteria);
|
|
310
|
+
return {
|
|
311
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }]
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
return {
|
|
316
|
+
content: [{ type: 'text', text: `Validation error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
|
|
317
|
+
isError: true
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
async run() {
|
|
326
|
+
const transport = new StdioServerTransport();
|
|
327
|
+
await this.server.connect(transport);
|
|
328
|
+
console.error('Facts MCP server running on stdio');
|
|
329
|
+
// Handle cleanup on shutdown
|
|
330
|
+
process.on('SIGINT', async () => {
|
|
331
|
+
await this.storage.close();
|
|
332
|
+
process.exit(0);
|
|
333
|
+
});
|
|
334
|
+
process.on('SIGTERM', async () => {
|
|
335
|
+
await this.storage.close();
|
|
336
|
+
process.exit(0);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
const server = new FactsServer();
|
|
341
|
+
server.run().catch(console.error);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ServerRequest, ServerResponse, ServerInfo, ServerCapabilities, ServerTransport } from './types.js';
|
|
2
|
+
export declare class Server {
|
|
3
|
+
private info;
|
|
4
|
+
private capabilities;
|
|
5
|
+
private transport;
|
|
6
|
+
private handlers;
|
|
7
|
+
constructor(info: ServerInfo, capabilities: {
|
|
8
|
+
capabilities: ServerCapabilities;
|
|
9
|
+
});
|
|
10
|
+
connect(transport: ServerTransport): Promise<void>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
setHandler(method: string, handler: (request: ServerRequest) => Promise<ServerResponse>): void;
|
|
13
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export class Server {
|
|
2
|
+
constructor(info, capabilities) {
|
|
3
|
+
this.info = info;
|
|
4
|
+
this.capabilities = capabilities;
|
|
5
|
+
this.transport = null;
|
|
6
|
+
this.handlers = new Map();
|
|
7
|
+
}
|
|
8
|
+
async connect(transport) {
|
|
9
|
+
this.transport = transport;
|
|
10
|
+
transport.onRequest(async (request) => {
|
|
11
|
+
const handler = this.handlers.get(request.method);
|
|
12
|
+
if (!handler) {
|
|
13
|
+
return {
|
|
14
|
+
content: [{ type: 'text', text: `Unknown method: ${request.method}` }],
|
|
15
|
+
isError: true
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
return handler(request);
|
|
19
|
+
});
|
|
20
|
+
await transport.connect();
|
|
21
|
+
}
|
|
22
|
+
async close() {
|
|
23
|
+
if (this.transport) {
|
|
24
|
+
await this.transport.disconnect();
|
|
25
|
+
this.transport = null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
setHandler(method, handler) {
|
|
29
|
+
this.handlers.set(method, handler);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ServerRequest, ServerResponse, ServerTransport } from './types.js';
|
|
2
|
+
export declare class StdioServerTransport implements ServerTransport {
|
|
3
|
+
private requestHandler;
|
|
4
|
+
private readline;
|
|
5
|
+
connect(): Promise<void>;
|
|
6
|
+
disconnect(): Promise<void>;
|
|
7
|
+
onRequest(handler: (request: ServerRequest) => Promise<ServerResponse>): void;
|
|
8
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { createInterface } from 'readline';
|
|
2
|
+
export class StdioServerTransport {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.requestHandler = null;
|
|
5
|
+
this.readline = createInterface({
|
|
6
|
+
input: process.stdin,
|
|
7
|
+
output: process.stdout
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
async connect() {
|
|
11
|
+
this.readline.on('line', async (line) => {
|
|
12
|
+
try {
|
|
13
|
+
const request = JSON.parse(line);
|
|
14
|
+
if (this.requestHandler) {
|
|
15
|
+
const response = await this.requestHandler(request);
|
|
16
|
+
console.log(JSON.stringify(response));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
console.error('Error processing request:', error);
|
|
21
|
+
console.log(JSON.stringify({
|
|
22
|
+
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}` }],
|
|
23
|
+
isError: true
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async disconnect() {
|
|
29
|
+
this.readline.close();
|
|
30
|
+
}
|
|
31
|
+
onRequest(handler) {
|
|
32
|
+
this.requestHandler = handler;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface ServerRequest {
|
|
2
|
+
method: string;
|
|
3
|
+
params?: Record<string, unknown>;
|
|
4
|
+
}
|
|
5
|
+
export interface ServerResponse {
|
|
6
|
+
content: Array<{
|
|
7
|
+
type: string;
|
|
8
|
+
text: string;
|
|
9
|
+
}>;
|
|
10
|
+
isError?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface ServerCapabilities {
|
|
13
|
+
tools?: Record<string, {
|
|
14
|
+
description: string;
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: string;
|
|
17
|
+
properties: Record<string, unknown>;
|
|
18
|
+
required?: string[];
|
|
19
|
+
};
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
export interface ServerInfo {
|
|
23
|
+
name: string;
|
|
24
|
+
version: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ServerTransport {
|
|
27
|
+
connect(): Promise<void>;
|
|
28
|
+
disconnect(): Promise<void>;
|
|
29
|
+
onRequest(handler: (request: ServerRequest) => Promise<ServerResponse>): void;
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { StorageProvider, StorageSearchResult, StrictnessLevel, FactCategory, Condition, AcceptanceCriterion } from './types.js';
|
|
2
|
+
export declare class PrismaStorageProvider implements StorageProvider {
|
|
3
|
+
private prisma;
|
|
4
|
+
constructor();
|
|
5
|
+
setFact(id: string, content: string, strictness: StrictnessLevel, type: string, category: FactCategory, minVersion: string, maxVersion: string, conditions: Condition[], acceptanceCriteria: AcceptanceCriterion[], contentEmbedding?: string): Promise<void>;
|
|
6
|
+
getFact(id: string): Promise<StorageSearchResult | null>;
|
|
7
|
+
searchFacts(options: {
|
|
8
|
+
type?: string;
|
|
9
|
+
category?: FactCategory;
|
|
10
|
+
strictness?: StrictnessLevel;
|
|
11
|
+
version?: string;
|
|
12
|
+
embedding?: string;
|
|
13
|
+
similarityThreshold?: number;
|
|
14
|
+
}): Promise<StorageSearchResult[]>;
|
|
15
|
+
deleteFact(id: string): Promise<boolean>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
}
|
package/build/storage.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { PrismaClient } from '@prisma/client';
|
|
2
|
+
export class PrismaStorageProvider {
|
|
3
|
+
constructor() {
|
|
4
|
+
this.prisma = new PrismaClient();
|
|
5
|
+
}
|
|
6
|
+
async setFact(id, content, strictness, type, category, minVersion, maxVersion, conditions, acceptanceCriteria, contentEmbedding) {
|
|
7
|
+
const data = {
|
|
8
|
+
id,
|
|
9
|
+
content,
|
|
10
|
+
strictness,
|
|
11
|
+
type,
|
|
12
|
+
category,
|
|
13
|
+
minVersion,
|
|
14
|
+
maxVersion,
|
|
15
|
+
content_embedding: contentEmbedding,
|
|
16
|
+
conditions: {
|
|
17
|
+
create: conditions.map(condition => ({
|
|
18
|
+
type: condition.type,
|
|
19
|
+
targetId: condition.factId
|
|
20
|
+
}))
|
|
21
|
+
},
|
|
22
|
+
acceptanceCriteria: {
|
|
23
|
+
create: acceptanceCriteria.map(criterion => ({
|
|
24
|
+
id: criterion.id,
|
|
25
|
+
description: criterion.description,
|
|
26
|
+
validationType: criterion.validationType,
|
|
27
|
+
validationScript: criterion.validationScript
|
|
28
|
+
}))
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
await this.prisma.fact.upsert({
|
|
32
|
+
where: { id },
|
|
33
|
+
create: data,
|
|
34
|
+
update: {
|
|
35
|
+
...data,
|
|
36
|
+
conditions: {
|
|
37
|
+
deleteMany: {},
|
|
38
|
+
create: conditions.map(condition => ({
|
|
39
|
+
type: condition.type,
|
|
40
|
+
targetId: condition.factId
|
|
41
|
+
}))
|
|
42
|
+
},
|
|
43
|
+
acceptanceCriteria: {
|
|
44
|
+
deleteMany: {},
|
|
45
|
+
create: acceptanceCriteria.map(criterion => ({
|
|
46
|
+
id: criterion.id,
|
|
47
|
+
description: criterion.description,
|
|
48
|
+
validationType: criterion.validationType,
|
|
49
|
+
validationScript: criterion.validationScript
|
|
50
|
+
}))
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async getFact(id) {
|
|
56
|
+
const fact = await this.prisma.fact.findUnique({
|
|
57
|
+
where: { id },
|
|
58
|
+
include: {
|
|
59
|
+
conditions: true,
|
|
60
|
+
acceptanceCriteria: true
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
if (!fact) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
id: fact.id,
|
|
68
|
+
content: fact.content,
|
|
69
|
+
strictness: fact.strictness,
|
|
70
|
+
type: fact.type,
|
|
71
|
+
category: fact.category,
|
|
72
|
+
minVersion: fact.minVersion,
|
|
73
|
+
maxVersion: fact.maxVersion,
|
|
74
|
+
conditions: fact.conditions.map(c => ({
|
|
75
|
+
factId: c.targetId,
|
|
76
|
+
type: c.type
|
|
77
|
+
})),
|
|
78
|
+
acceptanceCriteria: fact.acceptanceCriteria.map(ac => ({
|
|
79
|
+
id: ac.id,
|
|
80
|
+
description: ac.description,
|
|
81
|
+
validationType: ac.validationType,
|
|
82
|
+
validationScript: ac.validationScript || undefined
|
|
83
|
+
})),
|
|
84
|
+
createdAt: fact.createdAt.toISOString(),
|
|
85
|
+
updatedAt: fact.updatedAt.toISOString(),
|
|
86
|
+
applicable: fact.applicable
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async searchFacts(options) {
|
|
90
|
+
let facts;
|
|
91
|
+
if (options.embedding) {
|
|
92
|
+
// Use raw query for vector similarity search
|
|
93
|
+
const threshold = options.similarityThreshold || 0.8;
|
|
94
|
+
const whereConditions = [];
|
|
95
|
+
const params = [options.embedding, threshold];
|
|
96
|
+
if (options.type) {
|
|
97
|
+
whereConditions.push('f.type = ?');
|
|
98
|
+
params.push(options.type);
|
|
99
|
+
}
|
|
100
|
+
if (options.category) {
|
|
101
|
+
whereConditions.push('f.category = ?');
|
|
102
|
+
params.push(options.category);
|
|
103
|
+
}
|
|
104
|
+
if (options.strictness) {
|
|
105
|
+
whereConditions.push('f.strictness = ?');
|
|
106
|
+
params.push(options.strictness);
|
|
107
|
+
}
|
|
108
|
+
if (options.version) {
|
|
109
|
+
whereConditions.push('f.minVersion <= ? AND f.maxVersion >= ?');
|
|
110
|
+
params.push(options.version, options.version);
|
|
111
|
+
}
|
|
112
|
+
const whereClause = whereConditions.length
|
|
113
|
+
? 'AND ' + whereConditions.join(' AND ')
|
|
114
|
+
: '';
|
|
115
|
+
const rawResults = await this.prisma.$queryRawUnsafe(`SELECT f.*,
|
|
116
|
+
vector_similarity(f.content_embedding, ?) as similarity
|
|
117
|
+
FROM "Fact" f
|
|
118
|
+
WHERE vector_similarity(f.content_embedding, ?) > ?
|
|
119
|
+
${whereClause}
|
|
120
|
+
ORDER BY similarity DESC`, options.embedding, options.embedding, threshold, ...params);
|
|
121
|
+
// Fetch full fact data including relations
|
|
122
|
+
const factPromises = rawResults.map(result => this.prisma.fact.findUnique({
|
|
123
|
+
where: { id: result.id },
|
|
124
|
+
include: {
|
|
125
|
+
conditions: true,
|
|
126
|
+
acceptanceCriteria: true
|
|
127
|
+
}
|
|
128
|
+
}));
|
|
129
|
+
facts = (await Promise.all(factPromises)).filter((fact) => fact !== null);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const where = {};
|
|
133
|
+
if (options.type) {
|
|
134
|
+
where.type = options.type;
|
|
135
|
+
}
|
|
136
|
+
if (options.category) {
|
|
137
|
+
where.category = options.category;
|
|
138
|
+
}
|
|
139
|
+
if (options.strictness) {
|
|
140
|
+
where.strictness = options.strictness;
|
|
141
|
+
}
|
|
142
|
+
if (options.version) {
|
|
143
|
+
where.AND = [
|
|
144
|
+
{ minVersion: { lte: options.version } },
|
|
145
|
+
{ maxVersion: { gte: options.version } }
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
facts = await this.prisma.fact.findMany({
|
|
149
|
+
where,
|
|
150
|
+
include: {
|
|
151
|
+
conditions: true,
|
|
152
|
+
acceptanceCriteria: true
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return facts.map(fact => ({
|
|
157
|
+
id: fact.id,
|
|
158
|
+
content: fact.content,
|
|
159
|
+
strictness: fact.strictness,
|
|
160
|
+
type: fact.type,
|
|
161
|
+
category: fact.category,
|
|
162
|
+
minVersion: fact.minVersion,
|
|
163
|
+
maxVersion: fact.maxVersion,
|
|
164
|
+
conditions: fact.conditions.map(c => ({
|
|
165
|
+
factId: c.targetId,
|
|
166
|
+
type: c.type
|
|
167
|
+
})),
|
|
168
|
+
acceptanceCriteria: fact.acceptanceCriteria.map(ac => ({
|
|
169
|
+
id: ac.id,
|
|
170
|
+
description: ac.description,
|
|
171
|
+
validationType: ac.validationType,
|
|
172
|
+
validationScript: ac.validationScript || undefined
|
|
173
|
+
})),
|
|
174
|
+
createdAt: fact.createdAt.toISOString(),
|
|
175
|
+
updatedAt: fact.updatedAt.toISOString(),
|
|
176
|
+
applicable: fact.applicable
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
async deleteFact(id) {
|
|
180
|
+
try {
|
|
181
|
+
await this.prisma.fact.delete({
|
|
182
|
+
where: { id }
|
|
183
|
+
});
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async close() {
|
|
191
|
+
await this.prisma.$disconnect();
|
|
192
|
+
}
|
|
193
|
+
}
|