harper-knowledge 0.1.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 +276 -0
- package/config.yaml +17 -0
- package/dist/core/embeddings.d.ts +29 -0
- package/dist/core/embeddings.js +199 -0
- package/dist/core/entries.d.ts +85 -0
- package/dist/core/entries.js +235 -0
- package/dist/core/history.d.ts +30 -0
- package/dist/core/history.js +119 -0
- package/dist/core/search.d.ts +23 -0
- package/dist/core/search.js +306 -0
- package/dist/core/tags.d.ts +32 -0
- package/dist/core/tags.js +76 -0
- package/dist/core/triage.d.ts +55 -0
- package/dist/core/triage.js +126 -0
- package/dist/http-utils.d.ts +37 -0
- package/dist/http-utils.js +132 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +76 -0
- package/dist/mcp/server.d.ts +24 -0
- package/dist/mcp/server.js +124 -0
- package/dist/mcp/tools.d.ts +13 -0
- package/dist/mcp/tools.js +497 -0
- package/dist/oauth/authorize.d.ts +27 -0
- package/dist/oauth/authorize.js +438 -0
- package/dist/oauth/github.d.ts +28 -0
- package/dist/oauth/github.js +62 -0
- package/dist/oauth/keys.d.ts +33 -0
- package/dist/oauth/keys.js +100 -0
- package/dist/oauth/metadata.d.ts +21 -0
- package/dist/oauth/metadata.js +55 -0
- package/dist/oauth/middleware.d.ts +22 -0
- package/dist/oauth/middleware.js +64 -0
- package/dist/oauth/register.d.ts +14 -0
- package/dist/oauth/register.js +83 -0
- package/dist/oauth/token.d.ts +15 -0
- package/dist/oauth/token.js +178 -0
- package/dist/oauth/validate.d.ts +30 -0
- package/dist/oauth/validate.js +52 -0
- package/dist/resources/HistoryResource.d.ts +38 -0
- package/dist/resources/HistoryResource.js +38 -0
- package/dist/resources/KnowledgeEntryResource.d.ts +64 -0
- package/dist/resources/KnowledgeEntryResource.js +157 -0
- package/dist/resources/QueryLogResource.d.ts +20 -0
- package/dist/resources/QueryLogResource.js +57 -0
- package/dist/resources/ServiceKeyResource.d.ts +51 -0
- package/dist/resources/ServiceKeyResource.js +132 -0
- package/dist/resources/TagResource.d.ts +25 -0
- package/dist/resources/TagResource.js +32 -0
- package/dist/resources/TriageResource.d.ts +51 -0
- package/dist/resources/TriageResource.js +107 -0
- package/dist/types.d.ts +317 -0
- package/dist/types.js +7 -0
- package/dist/webhooks/datadog.d.ts +26 -0
- package/dist/webhooks/datadog.js +120 -0
- package/dist/webhooks/github.d.ts +24 -0
- package/dist/webhooks/github.js +167 -0
- package/dist/webhooks/middleware.d.ts +14 -0
- package/dist/webhooks/middleware.js +161 -0
- package/dist/webhooks/types.d.ts +17 -0
- package/dist/webhooks/types.js +4 -0
- package/package.json +72 -0
- package/schema/knowledge.graphql +134 -0
- package/web/index.html +735 -0
- package/web/js/app.js +461 -0
- package/web/js/detail.js +223 -0
- package/web/js/editor.js +303 -0
- package/web/js/search.js +238 -0
- package/web/js/triage.js +305 -0
|
@@ -0,0 +1,235 @@
|
|
|
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
|
+
export function stripEmbedding(entry) {
|
|
16
|
+
const { embedding: _embedding, ...rest } = entry;
|
|
17
|
+
return rest;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a new knowledge entry.
|
|
21
|
+
*
|
|
22
|
+
* Generates an embedding from title + content, synchronizes tags,
|
|
23
|
+
* and stores the entry. A UUID is generated if no id is provided.
|
|
24
|
+
*
|
|
25
|
+
* @param data - Entry data to create
|
|
26
|
+
* @returns The created knowledge entry
|
|
27
|
+
*/
|
|
28
|
+
export async function createEntry(data) {
|
|
29
|
+
const id = data.id || crypto.randomUUID();
|
|
30
|
+
// Generate embedding from title + content
|
|
31
|
+
const embeddingText = `${data.title}\n\n${data.content}`;
|
|
32
|
+
let embedding;
|
|
33
|
+
try {
|
|
34
|
+
embedding = await generateEmbedding(embeddingText);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
logger?.warn?.("Failed to generate embedding for new entry:", error.message);
|
|
38
|
+
}
|
|
39
|
+
const entry = {
|
|
40
|
+
id,
|
|
41
|
+
title: data.title,
|
|
42
|
+
content: data.content,
|
|
43
|
+
tags: data.tags || [],
|
|
44
|
+
appliesTo: data.appliesTo,
|
|
45
|
+
source: data.source,
|
|
46
|
+
sourceUrl: data.sourceUrl,
|
|
47
|
+
confidence: data.confidence || "ai-generated",
|
|
48
|
+
addedBy: data.addedBy,
|
|
49
|
+
reviewedBy: data.reviewedBy,
|
|
50
|
+
customerContext: data.customerContext,
|
|
51
|
+
deprecated: data.deprecated ?? false,
|
|
52
|
+
embedding,
|
|
53
|
+
};
|
|
54
|
+
// Sync tag counts (no previous tags for new entries)
|
|
55
|
+
if (entry.tags.length > 0) {
|
|
56
|
+
await syncTags(entry.tags);
|
|
57
|
+
}
|
|
58
|
+
// Store the entry
|
|
59
|
+
await databases.kb.KnowledgeEntry.put(entry);
|
|
60
|
+
return entry;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get a knowledge entry by ID.
|
|
64
|
+
*
|
|
65
|
+
* @param id - Entry ID
|
|
66
|
+
* @returns The entry, or null if not found
|
|
67
|
+
*/
|
|
68
|
+
export async function getEntry(id) {
|
|
69
|
+
const entry = await databases.kb.KnowledgeEntry.get(id);
|
|
70
|
+
return entry;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Update an existing knowledge entry.
|
|
74
|
+
*
|
|
75
|
+
* Merges the update data with the existing entry. If title or content changed,
|
|
76
|
+
* regenerates the embedding. Synchronizes tag counts if tags changed.
|
|
77
|
+
* Optionally logs the edit to the history table.
|
|
78
|
+
*
|
|
79
|
+
* @param id - ID of the entry to update
|
|
80
|
+
* @param data - Fields to update
|
|
81
|
+
* @param options - Optional edit tracking metadata
|
|
82
|
+
* @returns The updated entry
|
|
83
|
+
* @throws Error if the entry does not exist
|
|
84
|
+
*/
|
|
85
|
+
export async function updateEntry(id, data, options) {
|
|
86
|
+
const existing = await databases.kb.KnowledgeEntry.get(id);
|
|
87
|
+
if (!existing) {
|
|
88
|
+
throw new Error(`Knowledge entry not found: ${id}`);
|
|
89
|
+
}
|
|
90
|
+
const existingEntry = existing;
|
|
91
|
+
const previousTags = existingEntry.tags || [];
|
|
92
|
+
// Merge updates
|
|
93
|
+
const updated = {
|
|
94
|
+
...existingEntry,
|
|
95
|
+
...data,
|
|
96
|
+
id, // Ensure ID is never overwritten
|
|
97
|
+
};
|
|
98
|
+
// Regenerate embedding if title or content changed
|
|
99
|
+
const titleChanged = data.title !== undefined && data.title !== existingEntry.title;
|
|
100
|
+
const contentChanged = data.content !== undefined && data.content !== existingEntry.content;
|
|
101
|
+
if (titleChanged || contentChanged) {
|
|
102
|
+
const embeddingText = `${updated.title}\n\n${updated.content}`;
|
|
103
|
+
try {
|
|
104
|
+
updated.embedding = await generateEmbedding(embeddingText);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
logger?.warn?.("Failed to regenerate embedding for entry update:", error.message);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Sync tag counts if tags changed
|
|
111
|
+
if (data.tags !== undefined) {
|
|
112
|
+
await syncTags(updated.tags, previousTags);
|
|
113
|
+
}
|
|
114
|
+
// Log the edit before overwriting
|
|
115
|
+
if (options?.editedBy) {
|
|
116
|
+
try {
|
|
117
|
+
await logEdit(id, existingEntry, updated, options.editedBy, options.editSummary);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
logger?.warn?.("Failed to log edit history:", error.message);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Store the updated entry
|
|
124
|
+
await databases.kb.KnowledgeEntry.put(updated);
|
|
125
|
+
return updated;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Mark an entry as deprecated.
|
|
129
|
+
*
|
|
130
|
+
* @param id - ID of the entry to deprecate
|
|
131
|
+
* @throws Error if the entry does not exist
|
|
132
|
+
*/
|
|
133
|
+
export async function deprecateEntry(id) {
|
|
134
|
+
const existing = await databases.kb.KnowledgeEntry.get(id);
|
|
135
|
+
if (!existing) {
|
|
136
|
+
throw new Error(`Knowledge entry not found: ${id}`);
|
|
137
|
+
}
|
|
138
|
+
await databases.kb.KnowledgeEntry.put({
|
|
139
|
+
...existing,
|
|
140
|
+
id,
|
|
141
|
+
deprecated: true,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Link a new entry as superseding an old entry.
|
|
146
|
+
*
|
|
147
|
+
* Sets newEntry.supersedesId = oldId and oldEntry.supersededById = newId.
|
|
148
|
+
*
|
|
149
|
+
* @param newId - ID of the new (superseding) entry
|
|
150
|
+
* @param oldId - ID of the old (superseded) entry
|
|
151
|
+
* @throws Error if either entry does not exist
|
|
152
|
+
*/
|
|
153
|
+
export async function linkSupersedes(newId, oldId) {
|
|
154
|
+
const newEntry = await databases.kb.KnowledgeEntry.get(newId);
|
|
155
|
+
const oldEntry = await databases.kb.KnowledgeEntry.get(oldId);
|
|
156
|
+
if (!newEntry) {
|
|
157
|
+
throw new Error(`New entry not found: ${newId}`);
|
|
158
|
+
}
|
|
159
|
+
if (!oldEntry) {
|
|
160
|
+
throw new Error(`Old entry not found: ${oldId}`);
|
|
161
|
+
}
|
|
162
|
+
await databases.kb.KnowledgeEntry.put({
|
|
163
|
+
...newEntry,
|
|
164
|
+
id: newId,
|
|
165
|
+
supersedesId: oldId,
|
|
166
|
+
});
|
|
167
|
+
await databases.kb.KnowledgeEntry.put({
|
|
168
|
+
...oldEntry,
|
|
169
|
+
id: oldId,
|
|
170
|
+
supersededById: newId,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Link multiple entries as siblings.
|
|
175
|
+
*
|
|
176
|
+
* For each entry, adds all other entry IDs to its siblingIds array (deduplicated).
|
|
177
|
+
*
|
|
178
|
+
* @param ids - IDs of entries to link as siblings
|
|
179
|
+
* @throws Error if any entry does not exist
|
|
180
|
+
*/
|
|
181
|
+
export async function linkSiblings(ids) {
|
|
182
|
+
if (ids.length < 2) {
|
|
183
|
+
return; // Need at least 2 entries to link
|
|
184
|
+
}
|
|
185
|
+
// Load all entries first to verify they exist
|
|
186
|
+
const entries = [];
|
|
187
|
+
for (const id of ids) {
|
|
188
|
+
const entry = await databases.kb.KnowledgeEntry.get(id);
|
|
189
|
+
if (!entry) {
|
|
190
|
+
throw new Error(`Entry not found: ${id}`);
|
|
191
|
+
}
|
|
192
|
+
entries.push(entry);
|
|
193
|
+
}
|
|
194
|
+
// Update each entry's siblingIds
|
|
195
|
+
for (let i = 0; i < ids.length; i++) {
|
|
196
|
+
const entry = entries[i];
|
|
197
|
+
const entryTyped = entry;
|
|
198
|
+
const existingSiblings = new Set(entryTyped.siblingIds || []);
|
|
199
|
+
// Add all other IDs (not itself)
|
|
200
|
+
for (const otherId of ids) {
|
|
201
|
+
if (otherId !== ids[i]) {
|
|
202
|
+
existingSiblings.add(otherId);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
await databases.kb.KnowledgeEntry.put({
|
|
206
|
+
...entry,
|
|
207
|
+
id: ids[i],
|
|
208
|
+
siblingIds: Array.from(existingSiblings),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Link two entries as related.
|
|
214
|
+
*
|
|
215
|
+
* Adds relatedId to the entry's relatedIds array (deduplicated).
|
|
216
|
+
* This is a one-directional link; call twice for bidirectional.
|
|
217
|
+
*
|
|
218
|
+
* @param id - ID of the entry to add a related link to
|
|
219
|
+
* @param relatedId - ID of the related entry
|
|
220
|
+
* @throws Error if the entry does not exist
|
|
221
|
+
*/
|
|
222
|
+
export async function linkRelated(id, relatedId) {
|
|
223
|
+
const entry = await databases.kb.KnowledgeEntry.get(id);
|
|
224
|
+
if (!entry) {
|
|
225
|
+
throw new Error(`Entry not found: ${id}`);
|
|
226
|
+
}
|
|
227
|
+
const entryTyped = entry;
|
|
228
|
+
const existingRelated = new Set(entryTyped.relatedIds || []);
|
|
229
|
+
existingRelated.add(relatedId);
|
|
230
|
+
await databases.kb.KnowledgeEntry.put({
|
|
231
|
+
...entry,
|
|
232
|
+
id,
|
|
233
|
+
relatedIds: Array.from(existingRelated),
|
|
234
|
+
});
|
|
235
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
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(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[]>;
|
|
@@ -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(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
|
+
entryId,
|
|
33
|
+
editedBy,
|
|
34
|
+
editSummary,
|
|
35
|
+
previousSnapshot,
|
|
36
|
+
changedFields,
|
|
37
|
+
};
|
|
38
|
+
await databases.kb.KnowledgeEntryEdit.put(edit);
|
|
39
|
+
return edit;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get the edit history for a knowledge entry, newest first.
|
|
43
|
+
*
|
|
44
|
+
* @param entryId - ID of the entry to get history for
|
|
45
|
+
* @param limit - Maximum number of edits to return (default 50)
|
|
46
|
+
* @returns Array of edit records, newest first
|
|
47
|
+
*/
|
|
48
|
+
export async function getHistory(entryId, limit = 50) {
|
|
49
|
+
const edits = [];
|
|
50
|
+
for await (const record of databases.kb.KnowledgeEntryEdit.search({
|
|
51
|
+
conditions: [
|
|
52
|
+
{ attribute: "entryId", comparator: "equals", value: entryId },
|
|
53
|
+
],
|
|
54
|
+
sort: { attribute: "createdAt", descending: true },
|
|
55
|
+
limit,
|
|
56
|
+
})) {
|
|
57
|
+
edits.push(record);
|
|
58
|
+
}
|
|
59
|
+
return edits;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Compare two entry states and return the list of fields that differ.
|
|
63
|
+
* Ignores internal fields like embedding and updatedAt.
|
|
64
|
+
*/
|
|
65
|
+
function detectChangedFields(previous, updated) {
|
|
66
|
+
const trackableFields = [
|
|
67
|
+
"title",
|
|
68
|
+
"content",
|
|
69
|
+
"tags",
|
|
70
|
+
"appliesTo",
|
|
71
|
+
"source",
|
|
72
|
+
"sourceUrl",
|
|
73
|
+
"confidence",
|
|
74
|
+
"addedBy",
|
|
75
|
+
"reviewedBy",
|
|
76
|
+
"customerContext",
|
|
77
|
+
"deprecated",
|
|
78
|
+
"supersedesId",
|
|
79
|
+
"supersededById",
|
|
80
|
+
"siblingIds",
|
|
81
|
+
"relatedIds",
|
|
82
|
+
];
|
|
83
|
+
const changed = [];
|
|
84
|
+
for (const field of trackableFields) {
|
|
85
|
+
const prev = previous[field];
|
|
86
|
+
const next = updated[field];
|
|
87
|
+
if (!deepEqual(prev, next)) {
|
|
88
|
+
changed.push(field);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return changed;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Simple deep equality check for JSON-serializable values.
|
|
95
|
+
*/
|
|
96
|
+
function deepEqual(a, b) {
|
|
97
|
+
if (a === b)
|
|
98
|
+
return true;
|
|
99
|
+
if (a == null || b == null)
|
|
100
|
+
return a === b;
|
|
101
|
+
if (typeof a !== typeof b)
|
|
102
|
+
return false;
|
|
103
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
104
|
+
if (a.length !== b.length)
|
|
105
|
+
return false;
|
|
106
|
+
return a.every((val, i) => deepEqual(val, b[i]));
|
|
107
|
+
}
|
|
108
|
+
if (typeof a === "object" && typeof b === "object") {
|
|
109
|
+
const aObj = a;
|
|
110
|
+
const bObj = b;
|
|
111
|
+
const keys = new Set([...Object.keys(aObj), ...Object.keys(bObj)]);
|
|
112
|
+
for (const key of keys) {
|
|
113
|
+
if (!deepEqual(aObj[key], bObj[key]))
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Base Search
|
|
3
|
+
*
|
|
4
|
+
* Supports keyword, semantic (vector), and hybrid search modes.
|
|
5
|
+
* Applies applicability context filtering to boost/demote results.
|
|
6
|
+
* Logs all queries to the QueryLog table for analytics.
|
|
7
|
+
*/
|
|
8
|
+
import type { SearchParams, SearchResult, ApplicabilityContext } from "../types.ts";
|
|
9
|
+
/**
|
|
10
|
+
* Search the knowledge base.
|
|
11
|
+
*
|
|
12
|
+
* @param params - Search parameters including query, mode, tags, limit, context
|
|
13
|
+
* @returns Scored and sorted search results
|
|
14
|
+
*/
|
|
15
|
+
export declare function search(params: SearchParams): Promise<SearchResult[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Filter and re-score results based on applicability context.
|
|
18
|
+
*
|
|
19
|
+
* If the caller provides their environment context (Harper version, storage engine,
|
|
20
|
+
* Node version, platform), results that match get a score boost, while results
|
|
21
|
+
* that specify a different scope get a score penalty (but are NOT hidden).
|
|
22
|
+
*/
|
|
23
|
+
export declare function filterByApplicability(results: SearchResult[], context: ApplicabilityContext): SearchResult[];
|