harper-kb 0.2.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/LICENSE +21 -0
- package/README.md +288 -0
- package/config.yaml +13 -0
- package/dist/core/embeddings.d.ts +31 -0
- package/dist/core/embeddings.d.ts.map +1 -0
- package/dist/core/embeddings.js +199 -0
- package/dist/core/embeddings.js.map +1 -0
- package/dist/core/entries.d.ts +101 -0
- package/dist/core/entries.d.ts.map +1 -0
- package/dist/core/entries.js +304 -0
- package/dist/core/entries.js.map +1 -0
- package/dist/core/history.d.ts +31 -0
- package/dist/core/history.d.ts.map +1 -0
- package/dist/core/history.js +119 -0
- package/dist/core/history.js.map +1 -0
- package/dist/core/knowledge-base.d.ts +49 -0
- package/dist/core/knowledge-base.d.ts.map +1 -0
- package/dist/core/knowledge-base.js +117 -0
- package/dist/core/knowledge-base.js.map +1 -0
- package/dist/core/search.d.ts +34 -0
- package/dist/core/search.d.ts.map +1 -0
- package/dist/core/search.js +327 -0
- package/dist/core/search.js.map +1 -0
- package/dist/core/tags.d.ts +39 -0
- package/dist/core/tags.d.ts.map +1 -0
- package/dist/core/tags.js +97 -0
- package/dist/core/tags.js.map +1 -0
- package/dist/core/triage.d.ts +61 -0
- package/dist/core/triage.d.ts.map +1 -0
- package/dist/core/triage.js +136 -0
- package/dist/core/triage.js.map +1 -0
- package/dist/core/webhook-endpoints.d.ts +46 -0
- package/dist/core/webhook-endpoints.d.ts.map +1 -0
- package/dist/core/webhook-endpoints.js +85 -0
- package/dist/core/webhook-endpoints.js.map +1 -0
- package/dist/hooks.d.ts +67 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +53 -0
- package/dist/hooks.js.map +1 -0
- package/dist/http-utils.d.ts +38 -0
- package/dist/http-utils.d.ts.map +1 -0
- package/dist/http-utils.js +133 -0
- package/dist/http-utils.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +78 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/protocol.d.ts +25 -0
- package/dist/mcp/protocol.d.ts.map +1 -0
- package/dist/mcp/protocol.js +105 -0
- package/dist/mcp/protocol.js.map +1 -0
- package/dist/mcp/server.d.ts +28 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +144 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +26 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +706 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/oauth/authorize.d.ts +28 -0
- package/dist/oauth/authorize.d.ts.map +1 -0
- package/dist/oauth/authorize.js +421 -0
- package/dist/oauth/authorize.js.map +1 -0
- package/dist/oauth/init.d.ts +18 -0
- package/dist/oauth/init.d.ts.map +1 -0
- package/dist/oauth/init.js +30 -0
- package/dist/oauth/init.js.map +1 -0
- package/dist/oauth/keys.d.ts +34 -0
- package/dist/oauth/keys.d.ts.map +1 -0
- package/dist/oauth/keys.js +101 -0
- package/dist/oauth/keys.js.map +1 -0
- package/dist/oauth/metadata.d.ts +23 -0
- package/dist/oauth/metadata.d.ts.map +1 -0
- package/dist/oauth/metadata.js +57 -0
- package/dist/oauth/metadata.js.map +1 -0
- package/dist/oauth/middleware.d.ts +23 -0
- package/dist/oauth/middleware.d.ts.map +1 -0
- package/dist/oauth/middleware.js +65 -0
- package/dist/oauth/middleware.js.map +1 -0
- package/dist/oauth/register.d.ts +15 -0
- package/dist/oauth/register.d.ts.map +1 -0
- package/dist/oauth/register.js +78 -0
- package/dist/oauth/register.js.map +1 -0
- package/dist/oauth/token.d.ts +16 -0
- package/dist/oauth/token.d.ts.map +1 -0
- package/dist/oauth/token.js +184 -0
- package/dist/oauth/token.js.map +1 -0
- package/dist/oauth/validate.d.ts +40 -0
- package/dist/oauth/validate.d.ts.map +1 -0
- package/dist/oauth/validate.js +61 -0
- package/dist/oauth/validate.js.map +1 -0
- package/dist/resources/HistoryResource.d.ts +41 -0
- package/dist/resources/HistoryResource.d.ts.map +1 -0
- package/dist/resources/HistoryResource.js +61 -0
- package/dist/resources/HistoryResource.js.map +1 -0
- package/dist/resources/KnowledgeBaseResource.d.ts +60 -0
- package/dist/resources/KnowledgeBaseResource.d.ts.map +1 -0
- package/dist/resources/KnowledgeBaseResource.js +118 -0
- package/dist/resources/KnowledgeBaseResource.js.map +1 -0
- package/dist/resources/KnowledgeEntryResource.d.ts +61 -0
- package/dist/resources/KnowledgeEntryResource.d.ts.map +1 -0
- package/dist/resources/KnowledgeEntryResource.js +191 -0
- package/dist/resources/KnowledgeEntryResource.js.map +1 -0
- package/dist/resources/MeResource.d.ts +31 -0
- package/dist/resources/MeResource.d.ts.map +1 -0
- package/dist/resources/MeResource.js +40 -0
- package/dist/resources/MeResource.js.map +1 -0
- package/dist/resources/QueryLogResource.d.ts +22 -0
- package/dist/resources/QueryLogResource.d.ts.map +1 -0
- package/dist/resources/QueryLogResource.js +66 -0
- package/dist/resources/QueryLogResource.js.map +1 -0
- package/dist/resources/ServiceKeyResource.d.ts +52 -0
- package/dist/resources/ServiceKeyResource.d.ts.map +1 -0
- package/dist/resources/ServiceKeyResource.js +151 -0
- package/dist/resources/ServiceKeyResource.js.map +1 -0
- package/dist/resources/TagResource.d.ts +27 -0
- package/dist/resources/TagResource.d.ts.map +1 -0
- package/dist/resources/TagResource.js +41 -0
- package/dist/resources/TagResource.js.map +1 -0
- package/dist/resources/TriageResource.d.ts +53 -0
- package/dist/resources/TriageResource.d.ts.map +1 -0
- package/dist/resources/TriageResource.js +120 -0
- package/dist/resources/TriageResource.js.map +1 -0
- package/dist/resources/WebhookEndpointResource.d.ts +63 -0
- package/dist/resources/WebhookEndpointResource.d.ts.map +1 -0
- package/dist/resources/WebhookEndpointResource.js +115 -0
- package/dist/resources/WebhookEndpointResource.js.map +1 -0
- package/dist/types.d.ts +378 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/webhooks/github.d.ts +25 -0
- package/dist/webhooks/github.d.ts.map +1 -0
- package/dist/webhooks/github.js +165 -0
- package/dist/webhooks/github.js.map +1 -0
- package/dist/webhooks/middleware.d.ts +19 -0
- package/dist/webhooks/middleware.d.ts.map +1 -0
- package/dist/webhooks/middleware.js +144 -0
- package/dist/webhooks/middleware.js.map +1 -0
- package/dist/webhooks/types.d.ts +18 -0
- package/dist/webhooks/types.d.ts.map +1 -0
- package/dist/webhooks/types.js +5 -0
- package/dist/webhooks/types.js.map +1 -0
- package/package.json +69 -0
- package/schema/knowledge.graphql +136 -0
- package/schema/oauth.graphql +45 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Entry Management
|
|
3
|
+
*
|
|
4
|
+
* CRUD operations for knowledge base entries. Handles embedding generation,
|
|
5
|
+
* tag synchronization, and relationship management.
|
|
6
|
+
*/
|
|
7
|
+
import crypto from 'node:crypto';
|
|
8
|
+
import { generateEmbedding } from "./embeddings.js";
|
|
9
|
+
import { syncTags } from "./tags.js";
|
|
10
|
+
import { logEdit } from "./history.js";
|
|
11
|
+
/**
|
|
12
|
+
* Strip embedding vectors from an entry to keep responses compact.
|
|
13
|
+
* Embeddings are large float arrays not useful in API responses.
|
|
14
|
+
*
|
|
15
|
+
* NOTE: Harper database records use non-enumerable properties, so
|
|
16
|
+
* object spread ({...record}) produces an empty object. We must
|
|
17
|
+
* explicitly read each field.
|
|
18
|
+
*/
|
|
19
|
+
export function stripEmbedding(entry) {
|
|
20
|
+
return {
|
|
21
|
+
id: entry.id,
|
|
22
|
+
kbId: entry.kbId,
|
|
23
|
+
title: entry.title,
|
|
24
|
+
content: entry.content,
|
|
25
|
+
tags: entry.tags,
|
|
26
|
+
appliesTo: entry.appliesTo,
|
|
27
|
+
source: entry.source,
|
|
28
|
+
sourceUrl: entry.sourceUrl,
|
|
29
|
+
references: entry.references,
|
|
30
|
+
confidence: entry.confidence,
|
|
31
|
+
addedBy: entry.addedBy,
|
|
32
|
+
reviewedBy: entry.reviewedBy,
|
|
33
|
+
supersedesId: entry.supersedesId,
|
|
34
|
+
supersededById: entry.supersededById,
|
|
35
|
+
siblingIds: entry.siblingIds,
|
|
36
|
+
relatedIds: entry.relatedIds,
|
|
37
|
+
metadata: entry.metadata,
|
|
38
|
+
deprecated: entry.deprecated,
|
|
39
|
+
createdAt: entry.createdAt,
|
|
40
|
+
updatedAt: entry.updatedAt,
|
|
41
|
+
// score is present on SearchResult objects
|
|
42
|
+
...(entry.score !== undefined ? { score: entry.score } : {}),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a new knowledge entry.
|
|
47
|
+
*
|
|
48
|
+
* Generates an embedding from title + content, synchronizes tags,
|
|
49
|
+
* and stores the entry. A UUID is generated if no id is provided.
|
|
50
|
+
*
|
|
51
|
+
* @param data - Entry data to create
|
|
52
|
+
* @returns The created knowledge entry
|
|
53
|
+
*/
|
|
54
|
+
export async function createEntry(data) {
|
|
55
|
+
const id = data.id || crypto.randomUUID();
|
|
56
|
+
// Generate embedding from title + content
|
|
57
|
+
const embeddingText = `${data.title}\n\n${data.content}`;
|
|
58
|
+
let embedding;
|
|
59
|
+
try {
|
|
60
|
+
embedding = await generateEmbedding(embeddingText);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
logger?.warn?.('Failed to generate embedding for new entry:', error.message);
|
|
64
|
+
}
|
|
65
|
+
const entry = {
|
|
66
|
+
id,
|
|
67
|
+
kbId: data.kbId,
|
|
68
|
+
title: data.title,
|
|
69
|
+
content: data.content,
|
|
70
|
+
tags: data.tags || [],
|
|
71
|
+
appliesTo: data.appliesTo,
|
|
72
|
+
source: data.source,
|
|
73
|
+
sourceUrl: data.sourceUrl,
|
|
74
|
+
references: data.references,
|
|
75
|
+
confidence: data.confidence || 'ai-generated',
|
|
76
|
+
addedBy: data.addedBy,
|
|
77
|
+
reviewedBy: data.reviewedBy,
|
|
78
|
+
metadata: data.metadata,
|
|
79
|
+
deprecated: data.deprecated ?? false,
|
|
80
|
+
embedding,
|
|
81
|
+
};
|
|
82
|
+
// Sync tag counts (no previous tags for new entries)
|
|
83
|
+
if (entry.tags.length > 0) {
|
|
84
|
+
await syncTags(data.kbId, entry.tags);
|
|
85
|
+
}
|
|
86
|
+
// Store the entry
|
|
87
|
+
await databases.kb.KnowledgeEntry.put(entry);
|
|
88
|
+
return entry;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get a knowledge entry by ID.
|
|
92
|
+
*
|
|
93
|
+
* @param id - Entry ID
|
|
94
|
+
* @returns The entry, or null if not found
|
|
95
|
+
*/
|
|
96
|
+
export async function getEntry(id) {
|
|
97
|
+
const entry = await databases.kb.KnowledgeEntry.get(id);
|
|
98
|
+
return entry;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Update an existing knowledge entry.
|
|
102
|
+
*
|
|
103
|
+
* Merges the update data with the existing entry. If title or content changed,
|
|
104
|
+
* regenerates the embedding. Synchronizes tag counts if tags changed.
|
|
105
|
+
* Optionally logs the edit to the history table.
|
|
106
|
+
*
|
|
107
|
+
* @param id - ID of the entry to update
|
|
108
|
+
* @param data - Fields to update
|
|
109
|
+
* @param options - Optional edit tracking metadata
|
|
110
|
+
* @returns The updated entry
|
|
111
|
+
* @throws Error if the entry does not exist
|
|
112
|
+
*/
|
|
113
|
+
export async function updateEntry(id, data, options) {
|
|
114
|
+
const existing = await databases.kb.KnowledgeEntry.get(id);
|
|
115
|
+
if (!existing) {
|
|
116
|
+
throw new Error(`Knowledge entry not found: ${id}`);
|
|
117
|
+
}
|
|
118
|
+
const existingEntry = existing;
|
|
119
|
+
const previousTags = existingEntry.tags || [];
|
|
120
|
+
// Merge updates
|
|
121
|
+
const updated = {
|
|
122
|
+
...existingEntry,
|
|
123
|
+
...data,
|
|
124
|
+
id, // Ensure ID is never overwritten
|
|
125
|
+
};
|
|
126
|
+
// Regenerate embedding if title or content changed
|
|
127
|
+
const titleChanged = data.title !== undefined && data.title !== existingEntry.title;
|
|
128
|
+
const contentChanged = data.content !== undefined && data.content !== existingEntry.content;
|
|
129
|
+
if (titleChanged || contentChanged) {
|
|
130
|
+
const embeddingText = `${updated.title}\n\n${updated.content}`;
|
|
131
|
+
try {
|
|
132
|
+
updated.embedding = await generateEmbedding(embeddingText);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
logger?.warn?.('Failed to regenerate embedding for entry update:', error.message);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
// Sync tag counts if tags changed
|
|
139
|
+
if (data.tags !== undefined) {
|
|
140
|
+
await syncTags(existingEntry.kbId, updated.tags, previousTags);
|
|
141
|
+
}
|
|
142
|
+
// Log the edit before overwriting
|
|
143
|
+
if (options?.editedBy) {
|
|
144
|
+
try {
|
|
145
|
+
await logEdit(existingEntry.kbId, id, existingEntry, updated, options.editedBy, options.editSummary);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
logger?.warn?.('Failed to log edit history:', error.message);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Store the updated entry
|
|
152
|
+
await databases.kb.KnowledgeEntry.put(updated);
|
|
153
|
+
return updated;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Mark an entry as deprecated.
|
|
157
|
+
*
|
|
158
|
+
* @param id - ID of the entry to deprecate
|
|
159
|
+
* @throws Error if the entry does not exist
|
|
160
|
+
*/
|
|
161
|
+
export async function deprecateEntry(id) {
|
|
162
|
+
const existing = await databases.kb.KnowledgeEntry.get(id);
|
|
163
|
+
if (!existing) {
|
|
164
|
+
throw new Error(`Knowledge entry not found: ${id}`);
|
|
165
|
+
}
|
|
166
|
+
await databases.kb.KnowledgeEntry.put({
|
|
167
|
+
...existing,
|
|
168
|
+
id,
|
|
169
|
+
deprecated: true,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Link a new entry as superseding an old entry.
|
|
174
|
+
*
|
|
175
|
+
* Sets newEntry.supersedesId = oldId and oldEntry.supersededById = newId.
|
|
176
|
+
*
|
|
177
|
+
* @param newId - ID of the new (superseding) entry
|
|
178
|
+
* @param oldId - ID of the old (superseded) entry
|
|
179
|
+
* @throws Error if either entry does not exist
|
|
180
|
+
*/
|
|
181
|
+
export async function linkSupersedes(newId, oldId) {
|
|
182
|
+
const newEntry = await databases.kb.KnowledgeEntry.get(newId);
|
|
183
|
+
const oldEntry = await databases.kb.KnowledgeEntry.get(oldId);
|
|
184
|
+
if (!newEntry) {
|
|
185
|
+
throw new Error(`New entry not found: ${newId}`);
|
|
186
|
+
}
|
|
187
|
+
if (!oldEntry) {
|
|
188
|
+
throw new Error(`Old entry not found: ${oldId}`);
|
|
189
|
+
}
|
|
190
|
+
await databases.kb.KnowledgeEntry.put({
|
|
191
|
+
...newEntry,
|
|
192
|
+
id: newId,
|
|
193
|
+
supersedesId: oldId,
|
|
194
|
+
});
|
|
195
|
+
await databases.kb.KnowledgeEntry.put({
|
|
196
|
+
...oldEntry,
|
|
197
|
+
id: oldId,
|
|
198
|
+
supersededById: newId,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Link multiple entries as siblings.
|
|
203
|
+
*
|
|
204
|
+
* For each entry, adds all other entry IDs to its siblingIds array (deduplicated).
|
|
205
|
+
*
|
|
206
|
+
* @param ids - IDs of entries to link as siblings
|
|
207
|
+
* @throws Error if any entry does not exist
|
|
208
|
+
*/
|
|
209
|
+
export async function linkSiblings(ids) {
|
|
210
|
+
if (ids.length < 2) {
|
|
211
|
+
return; // Need at least 2 entries to link
|
|
212
|
+
}
|
|
213
|
+
// Load all entries first to verify they exist
|
|
214
|
+
const entries = [];
|
|
215
|
+
for (const id of ids) {
|
|
216
|
+
const entry = await databases.kb.KnowledgeEntry.get(id);
|
|
217
|
+
if (!entry) {
|
|
218
|
+
throw new Error(`Entry not found: ${id}`);
|
|
219
|
+
}
|
|
220
|
+
entries.push(entry);
|
|
221
|
+
}
|
|
222
|
+
// Update each entry's siblingIds
|
|
223
|
+
for (let i = 0; i < ids.length; i++) {
|
|
224
|
+
const entry = entries[i];
|
|
225
|
+
const entryTyped = entry;
|
|
226
|
+
const existingSiblings = new Set(entryTyped.siblingIds || []);
|
|
227
|
+
// Add all other IDs (not itself)
|
|
228
|
+
for (const otherId of ids) {
|
|
229
|
+
if (otherId !== ids[i]) {
|
|
230
|
+
existingSiblings.add(otherId);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
await databases.kb.KnowledgeEntry.put({
|
|
234
|
+
...entry,
|
|
235
|
+
id: ids[i],
|
|
236
|
+
siblingIds: Array.from(existingSiblings),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Reindex embeddings for all entries missing them.
|
|
242
|
+
*
|
|
243
|
+
* Iterates every KnowledgeEntry, generates embeddings for any that
|
|
244
|
+
* don't have one yet, and writes them back. Returns counts of
|
|
245
|
+
* processed, updated, and failed entries.
|
|
246
|
+
*/
|
|
247
|
+
export async function reindexEmbeddings(kbId) {
|
|
248
|
+
let total = 0;
|
|
249
|
+
let updated = 0;
|
|
250
|
+
let failed = 0;
|
|
251
|
+
let skipped = 0;
|
|
252
|
+
for await (const record of databases.kb.KnowledgeEntry.search({
|
|
253
|
+
conditions: [{ attribute: 'kbId', comparator: 'equals', value: kbId }],
|
|
254
|
+
})) {
|
|
255
|
+
total++;
|
|
256
|
+
const entry = record;
|
|
257
|
+
// Skip entries that already have embeddings
|
|
258
|
+
if (entry.embedding && entry.embedding.length > 0) {
|
|
259
|
+
skipped++;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
const embeddingText = `${entry.title}\n\n${entry.content}`;
|
|
264
|
+
const embedding = await generateEmbedding(embeddingText);
|
|
265
|
+
await databases.kb.KnowledgeEntry.put({
|
|
266
|
+
...record,
|
|
267
|
+
id: entry.id,
|
|
268
|
+
embedding,
|
|
269
|
+
});
|
|
270
|
+
updated++;
|
|
271
|
+
logger?.info?.(`Reindexed embedding for entry: ${entry.id}`);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
failed++;
|
|
275
|
+
logger?.warn?.(`Failed to reindex entry ${entry.id}:`, error.message);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return { total, updated, failed, skipped };
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Link two entries as related.
|
|
282
|
+
*
|
|
283
|
+
* Adds relatedId to the entry's relatedIds array (deduplicated).
|
|
284
|
+
* This is a one-directional link; call twice for bidirectional.
|
|
285
|
+
*
|
|
286
|
+
* @param id - ID of the entry to add a related link to
|
|
287
|
+
* @param relatedId - ID of the related entry
|
|
288
|
+
* @throws Error if the entry does not exist
|
|
289
|
+
*/
|
|
290
|
+
export async function linkRelated(id, relatedId) {
|
|
291
|
+
const entry = await databases.kb.KnowledgeEntry.get(id);
|
|
292
|
+
if (!entry) {
|
|
293
|
+
throw new Error(`Entry not found: ${id}`);
|
|
294
|
+
}
|
|
295
|
+
const entryTyped = entry;
|
|
296
|
+
const existingRelated = new Set(entryTyped.relatedIds || []);
|
|
297
|
+
existingRelated.add(relatedId);
|
|
298
|
+
await databases.kb.KnowledgeEntry.put({
|
|
299
|
+
...entry,
|
|
300
|
+
id,
|
|
301
|
+
relatedIds: Array.from(existingRelated),
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=entries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entries.js","sourceRoot":"","sources":["../../src/core/entries.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit History Tracking
|
|
3
|
+
*
|
|
4
|
+
* Append-only audit log for knowledge entry edits.
|
|
5
|
+
* Records who changed what, when, and why — with a snapshot
|
|
6
|
+
* of the previous state for diffing.
|
|
7
|
+
*/
|
|
8
|
+
import type { KnowledgeEntry, KnowledgeEntryEdit } from '../types.ts';
|
|
9
|
+
/**
|
|
10
|
+
* Log an edit to a knowledge entry.
|
|
11
|
+
*
|
|
12
|
+
* Compares the previous and updated entries to determine which fields changed,
|
|
13
|
+
* then stores an edit record with the previous snapshot.
|
|
14
|
+
*
|
|
15
|
+
* @param entryId - ID of the entry that was edited
|
|
16
|
+
* @param previous - The entry state before the edit
|
|
17
|
+
* @param updated - The entry state after the edit
|
|
18
|
+
* @param editedBy - Username of who made the edit
|
|
19
|
+
* @param editSummary - Optional description of what changed and why
|
|
20
|
+
* @returns The created edit record
|
|
21
|
+
*/
|
|
22
|
+
export declare function logEdit(kbId: string, entryId: string, previous: KnowledgeEntry, updated: KnowledgeEntry, editedBy: string, editSummary?: string): Promise<KnowledgeEntryEdit>;
|
|
23
|
+
/**
|
|
24
|
+
* Get the edit history for a knowledge entry, newest first.
|
|
25
|
+
*
|
|
26
|
+
* @param entryId - ID of the entry to get history for
|
|
27
|
+
* @param limit - Maximum number of edits to return (default 50)
|
|
28
|
+
* @returns Array of edit records, newest first
|
|
29
|
+
*/
|
|
30
|
+
export declare function getHistory(entryId: string, limit?: number): Promise<KnowledgeEntryEdit[]>;
|
|
31
|
+
//# sourceMappingURL=history.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.d.ts","sourceRoot":"","sources":["../../src/core/history.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAEtE;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAC5B,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,cAAc,EACxB,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,kBAAkB,CAAC,CAuB7B;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAY3F"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edit History Tracking
|
|
3
|
+
*
|
|
4
|
+
* Append-only audit log for knowledge entry edits.
|
|
5
|
+
* Records who changed what, when, and why — with a snapshot
|
|
6
|
+
* of the previous state for diffing.
|
|
7
|
+
*/
|
|
8
|
+
import crypto from 'node:crypto';
|
|
9
|
+
/**
|
|
10
|
+
* Log an edit to a knowledge entry.
|
|
11
|
+
*
|
|
12
|
+
* Compares the previous and updated entries to determine which fields changed,
|
|
13
|
+
* then stores an edit record with the previous snapshot.
|
|
14
|
+
*
|
|
15
|
+
* @param entryId - ID of the entry that was edited
|
|
16
|
+
* @param previous - The entry state before the edit
|
|
17
|
+
* @param updated - The entry state after the edit
|
|
18
|
+
* @param editedBy - Username of who made the edit
|
|
19
|
+
* @param editSummary - Optional description of what changed and why
|
|
20
|
+
* @returns The created edit record
|
|
21
|
+
*/
|
|
22
|
+
export async function logEdit(kbId, entryId, previous, updated, editedBy, editSummary) {
|
|
23
|
+
// Determine which fields actually changed
|
|
24
|
+
const changedFields = detectChangedFields(previous, updated);
|
|
25
|
+
// Build a snapshot of only the previous values for changed fields
|
|
26
|
+
const previousSnapshot = {};
|
|
27
|
+
for (const field of changedFields) {
|
|
28
|
+
previousSnapshot[field] = previous[field];
|
|
29
|
+
}
|
|
30
|
+
const edit = {
|
|
31
|
+
id: crypto.randomUUID(),
|
|
32
|
+
kbId,
|
|
33
|
+
entryId,
|
|
34
|
+
editedBy,
|
|
35
|
+
editSummary,
|
|
36
|
+
previousSnapshot,
|
|
37
|
+
changedFields,
|
|
38
|
+
};
|
|
39
|
+
await databases.kb.KnowledgeEntryEdit.put(edit);
|
|
40
|
+
return edit;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Get the edit history for a knowledge entry, newest first.
|
|
44
|
+
*
|
|
45
|
+
* @param entryId - ID of the entry to get history for
|
|
46
|
+
* @param limit - Maximum number of edits to return (default 50)
|
|
47
|
+
* @returns Array of edit records, newest first
|
|
48
|
+
*/
|
|
49
|
+
export async function getHistory(entryId, limit = 50) {
|
|
50
|
+
const edits = [];
|
|
51
|
+
for await (const record of databases.kb.KnowledgeEntryEdit.search({
|
|
52
|
+
conditions: [{ attribute: 'entryId', comparator: 'equals', value: entryId }],
|
|
53
|
+
sort: { attribute: 'createdAt', descending: true },
|
|
54
|
+
limit,
|
|
55
|
+
})) {
|
|
56
|
+
edits.push(record);
|
|
57
|
+
}
|
|
58
|
+
return edits;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Compare two entry states and return the list of fields that differ.
|
|
62
|
+
* Ignores internal fields like embedding and updatedAt.
|
|
63
|
+
*/
|
|
64
|
+
function detectChangedFields(previous, updated) {
|
|
65
|
+
const trackableFields = [
|
|
66
|
+
'title',
|
|
67
|
+
'content',
|
|
68
|
+
'tags',
|
|
69
|
+
'appliesTo',
|
|
70
|
+
'source',
|
|
71
|
+
'sourceUrl',
|
|
72
|
+
'confidence',
|
|
73
|
+
'addedBy',
|
|
74
|
+
'reviewedBy',
|
|
75
|
+
'metadata',
|
|
76
|
+
'deprecated',
|
|
77
|
+
'supersedesId',
|
|
78
|
+
'supersededById',
|
|
79
|
+
'siblingIds',
|
|
80
|
+
'relatedIds',
|
|
81
|
+
];
|
|
82
|
+
const changed = [];
|
|
83
|
+
for (const field of trackableFields) {
|
|
84
|
+
const prev = previous[field];
|
|
85
|
+
const next = updated[field];
|
|
86
|
+
if (!deepEqual(prev, next)) {
|
|
87
|
+
changed.push(field);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return changed;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Simple deep equality check for JSON-serializable values.
|
|
94
|
+
*/
|
|
95
|
+
function deepEqual(a, b) {
|
|
96
|
+
if (a === b)
|
|
97
|
+
return true;
|
|
98
|
+
if (a == null || b == null)
|
|
99
|
+
return a === b;
|
|
100
|
+
if (typeof a !== typeof b)
|
|
101
|
+
return false;
|
|
102
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
103
|
+
if (a.length !== b.length)
|
|
104
|
+
return false;
|
|
105
|
+
return a.every((val, i) => deepEqual(val, b[i]));
|
|
106
|
+
}
|
|
107
|
+
if (typeof a === 'object' && typeof b === 'object') {
|
|
108
|
+
const aObj = a;
|
|
109
|
+
const bObj = b;
|
|
110
|
+
const keys = new Set([...Object.keys(aObj), ...Object.keys(bObj)]);
|
|
111
|
+
for (const key of keys) {
|
|
112
|
+
if (!deepEqual(aObj[key], bObj[key]))
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/core/history.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Base Management
|
|
3
|
+
*
|
|
4
|
+
* CRUD operations for the KnowledgeBase registry table.
|
|
5
|
+
* Each KB is a tenant — entries, tags, triage items, etc. are scoped to a KB.
|
|
6
|
+
*/
|
|
7
|
+
import type { KnowledgeBase, KnowledgeBaseInput, KnowledgeBaseUpdate } from '../types.ts';
|
|
8
|
+
/**
|
|
9
|
+
* Create a new knowledge base.
|
|
10
|
+
*
|
|
11
|
+
* @param data - KB data to create
|
|
12
|
+
* @returns The created knowledge base
|
|
13
|
+
* @throws Error if a KB with the same id already exists
|
|
14
|
+
*/
|
|
15
|
+
export declare function createKnowledgeBase(data: KnowledgeBaseInput): Promise<KnowledgeBase>;
|
|
16
|
+
/**
|
|
17
|
+
* Get a knowledge base by ID.
|
|
18
|
+
*
|
|
19
|
+
* @param id - KB identifier
|
|
20
|
+
* @returns The KB record, or null if not found
|
|
21
|
+
*/
|
|
22
|
+
export declare function getKnowledgeBase(id: string): Promise<KnowledgeBase | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Update an existing knowledge base.
|
|
25
|
+
*
|
|
26
|
+
* @param id - KB identifier
|
|
27
|
+
* @param data - Fields to update
|
|
28
|
+
* @returns The updated KB record
|
|
29
|
+
* @throws Error if the KB does not exist
|
|
30
|
+
*/
|
|
31
|
+
export declare function updateKnowledgeBase(id: string, data: KnowledgeBaseUpdate): Promise<KnowledgeBase>;
|
|
32
|
+
/**
|
|
33
|
+
* Delete a knowledge base.
|
|
34
|
+
*
|
|
35
|
+
* Only deletes the registry record — does NOT cascade to entries, tags, etc.
|
|
36
|
+
* The caller is responsible for cleanup or can leave orphaned data for
|
|
37
|
+
* eventual consistency.
|
|
38
|
+
*
|
|
39
|
+
* @param id - KB identifier
|
|
40
|
+
* @throws Error if the KB does not exist
|
|
41
|
+
*/
|
|
42
|
+
export declare function deleteKnowledgeBase(id: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* List all knowledge bases.
|
|
45
|
+
*
|
|
46
|
+
* @returns Array of KB records
|
|
47
|
+
*/
|
|
48
|
+
export declare function listKnowledgeBases(): Promise<KnowledgeBase[]>;
|
|
49
|
+
//# sourceMappingURL=knowledge-base.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge-base.d.ts","sourceRoot":"","sources":["../../src/core/knowledge-base.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE1F;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC,CAiB1F;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAahF;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,aAAa,CAAC,CAsBvG;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAMnE;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAcnE"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Base Management
|
|
3
|
+
*
|
|
4
|
+
* CRUD operations for the KnowledgeBase registry table.
|
|
5
|
+
* Each KB is a tenant — entries, tags, triage items, etc. are scoped to a KB.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Create a new knowledge base.
|
|
9
|
+
*
|
|
10
|
+
* @param data - KB data to create
|
|
11
|
+
* @returns The created knowledge base
|
|
12
|
+
* @throws Error if a KB with the same id already exists
|
|
13
|
+
*/
|
|
14
|
+
export async function createKnowledgeBase(data) {
|
|
15
|
+
const existing = await databases.kb.KnowledgeBase.get(data.id);
|
|
16
|
+
if (existing) {
|
|
17
|
+
throw new Error(`Knowledge base already exists: ${data.id}`);
|
|
18
|
+
}
|
|
19
|
+
const kb = {
|
|
20
|
+
id: data.id,
|
|
21
|
+
name: data.name,
|
|
22
|
+
description: data.description,
|
|
23
|
+
settings: data.settings,
|
|
24
|
+
createdBy: data.createdBy,
|
|
25
|
+
};
|
|
26
|
+
await databases.kb.KnowledgeBase.put(kb);
|
|
27
|
+
return kb;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get a knowledge base by ID.
|
|
31
|
+
*
|
|
32
|
+
* @param id - KB identifier
|
|
33
|
+
* @returns The KB record, or null if not found
|
|
34
|
+
*/
|
|
35
|
+
export async function getKnowledgeBase(id) {
|
|
36
|
+
const record = await databases.kb.KnowledgeBase.get(id);
|
|
37
|
+
if (!record)
|
|
38
|
+
return null;
|
|
39
|
+
return {
|
|
40
|
+
id: record.id,
|
|
41
|
+
name: record.name,
|
|
42
|
+
description: record.description,
|
|
43
|
+
settings: record.settings,
|
|
44
|
+
createdBy: record.createdBy,
|
|
45
|
+
createdAt: record.createdAt,
|
|
46
|
+
updatedAt: record.updatedAt,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Update an existing knowledge base.
|
|
51
|
+
*
|
|
52
|
+
* @param id - KB identifier
|
|
53
|
+
* @param data - Fields to update
|
|
54
|
+
* @returns The updated KB record
|
|
55
|
+
* @throws Error if the KB does not exist
|
|
56
|
+
*/
|
|
57
|
+
export async function updateKnowledgeBase(id, data) {
|
|
58
|
+
const existing = await databases.kb.KnowledgeBase.get(id);
|
|
59
|
+
if (!existing) {
|
|
60
|
+
throw new Error(`Knowledge base not found: ${id}`);
|
|
61
|
+
}
|
|
62
|
+
const updated = { ...existing, id };
|
|
63
|
+
if (data.name !== undefined)
|
|
64
|
+
updated.name = data.name;
|
|
65
|
+
if (data.description !== undefined)
|
|
66
|
+
updated.description = data.description;
|
|
67
|
+
if (data.settings !== undefined)
|
|
68
|
+
updated.settings = data.settings;
|
|
69
|
+
await databases.kb.KnowledgeBase.put(updated);
|
|
70
|
+
return {
|
|
71
|
+
id: updated.id,
|
|
72
|
+
name: updated.name,
|
|
73
|
+
description: updated.description,
|
|
74
|
+
settings: updated.settings,
|
|
75
|
+
createdBy: updated.createdBy,
|
|
76
|
+
createdAt: updated.createdAt,
|
|
77
|
+
updatedAt: updated.updatedAt,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Delete a knowledge base.
|
|
82
|
+
*
|
|
83
|
+
* Only deletes the registry record — does NOT cascade to entries, tags, etc.
|
|
84
|
+
* The caller is responsible for cleanup or can leave orphaned data for
|
|
85
|
+
* eventual consistency.
|
|
86
|
+
*
|
|
87
|
+
* @param id - KB identifier
|
|
88
|
+
* @throws Error if the KB does not exist
|
|
89
|
+
*/
|
|
90
|
+
export async function deleteKnowledgeBase(id) {
|
|
91
|
+
const existing = await databases.kb.KnowledgeBase.get(id);
|
|
92
|
+
if (!existing) {
|
|
93
|
+
throw new Error(`Knowledge base not found: ${id}`);
|
|
94
|
+
}
|
|
95
|
+
await databases.kb.KnowledgeBase.delete(id);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* List all knowledge bases.
|
|
99
|
+
*
|
|
100
|
+
* @returns Array of KB records
|
|
101
|
+
*/
|
|
102
|
+
export async function listKnowledgeBases() {
|
|
103
|
+
const results = [];
|
|
104
|
+
for await (const record of databases.kb.KnowledgeBase.search({})) {
|
|
105
|
+
results.push({
|
|
106
|
+
id: record.id,
|
|
107
|
+
name: record.name,
|
|
108
|
+
description: record.description,
|
|
109
|
+
settings: record.settings,
|
|
110
|
+
createdBy: record.createdBy,
|
|
111
|
+
createdAt: record.createdAt,
|
|
112
|
+
updatedAt: record.updatedAt,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
return results;
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=knowledge-base.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge-base.js","sourceRoot":"","sources":["../../src/core/knowledge-base.ts"],"names":[],"mappings":"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"}
|