fluffy-context 0.3.1 → 0.6.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/README.md +61 -5
- package/dist/src/agent/api.d.ts +5 -1
- package/dist/src/agent/api.js +50 -10
- package/dist/src/agent/index.d.ts +1 -1
- package/dist/src/agent/types.d.ts +21 -0
- package/dist/src/cli/main.js +177 -9
- package/dist/src/cognition/feedback.d.ts +9 -0
- package/dist/src/cognition/feedback.js +30 -0
- package/dist/src/cognition/git-observer.d.ts +13 -0
- package/dist/src/cognition/git-observer.js +61 -0
- package/dist/src/cognition/journal.d.ts +24 -0
- package/dist/src/cognition/journal.js +113 -0
- package/dist/src/cognition/migration-v1.d.ts +7 -0
- package/dist/src/cognition/migration-v1.js +201 -0
- package/dist/src/cognition/orient.d.ts +13 -0
- package/dist/src/cognition/orient.js +94 -0
- package/dist/src/cognition/projections.d.ts +7 -0
- package/dist/src/cognition/projections.js +130 -0
- package/dist/src/cognition/readiness.d.ts +2 -0
- package/dist/src/cognition/readiness.js +81 -0
- package/dist/src/cognition/retrieval.d.ts +26 -0
- package/dist/src/cognition/retrieval.js +273 -0
- package/dist/src/cognition/runtime.d.ts +12 -0
- package/dist/src/cognition/runtime.js +295 -0
- package/dist/src/cognition/types.d.ts +170 -0
- package/dist/src/cognition/types.js +1 -0
- package/dist/src/cognition/v1-bridge.d.ts +7 -0
- package/dist/src/cognition/v1-bridge.js +82 -0
- package/dist/src/git/git-adapter.d.ts +7 -0
- package/dist/src/git/git-adapter.js +16 -6
- package/dist/src/hooks/claude-code.js +53 -0
- package/dist/src/runtime/diagnostics.js +6 -0
- package/dist/src/runtime/knowledge.d.ts +2 -0
- package/dist/src/runtime/knowledge.js +68 -0
- package/dist/src/runtime/notes.js +8 -1
- package/dist/src/runtime/runtime.d.ts +1 -0
- package/dist/src/runtime/runtime.js +28 -0
- package/dist/src/runtime/types.d.ts +19 -0
- package/dist/src/storage/layout.d.ts +19 -0
- package/dist/src/storage/layout.js +57 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { access, readdir } from 'node:fs/promises';
|
|
2
|
+
import { resolveProjectRoot } from '../project/project-resolver.js';
|
|
3
|
+
import { contextMetadataPath, contextsRoot, cognitionManifestPath, manifestPath, snapshotPath, snapshotsDirectory } from '../storage/layout.js';
|
|
4
|
+
import { readJson } from '../storage/json-store.js';
|
|
5
|
+
import { listDeadends, listKnowledge } from '../runtime/knowledge.js';
|
|
6
|
+
import { listNotes } from '../runtime/notes.js';
|
|
7
|
+
import { rebuildSnapshot } from '../runtime/runtime.js';
|
|
8
|
+
async function exists(filePath) {
|
|
9
|
+
try {
|
|
10
|
+
await access(filePath);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function migrationReadiness(startPath) {
|
|
18
|
+
const projectRoot = await resolveProjectRoot(startPath);
|
|
19
|
+
const counts = { contexts: 0, snapshots: 0, notes: 0, knowledge: 0, deadends: 0 };
|
|
20
|
+
const issues = [];
|
|
21
|
+
const v1Present = await exists(manifestPath(projectRoot));
|
|
22
|
+
const v2Present = await exists(cognitionManifestPath(projectRoot));
|
|
23
|
+
if (!v1Present) {
|
|
24
|
+
issues.push('v1 manifest is missing');
|
|
25
|
+
return { projectRoot, eligible: false, v1Present, v2Present, counts, issues };
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const manifest = await readJson(manifestPath(projectRoot));
|
|
29
|
+
if (manifest.schemaVersion !== 1 || manifest.layoutVersion !== 'compact-json-v1' || manifest.projectRoot !== projectRoot) {
|
|
30
|
+
issues.push('v1 manifest is invalid');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
issues.push(`v1 manifest: ${error instanceof Error ? error.message : 'invalid'}`);
|
|
35
|
+
}
|
|
36
|
+
let contextIds = [];
|
|
37
|
+
try {
|
|
38
|
+
contextIds = await readdir(contextsRoot(projectRoot));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
issues.push(`v1 contexts: ${error instanceof Error ? error.message : 'unavailable'}`);
|
|
42
|
+
}
|
|
43
|
+
for (const contextId of contextIds) {
|
|
44
|
+
try {
|
|
45
|
+
const context = await readJson(contextMetadataPath(projectRoot, contextId));
|
|
46
|
+
if (context.schemaVersion !== 1 || context.id !== contextId || context.projectRoot !== projectRoot) {
|
|
47
|
+
throw new Error('invalid context metadata');
|
|
48
|
+
}
|
|
49
|
+
counts.contexts += 1;
|
|
50
|
+
const snapshots = (await readdir(snapshotsDirectory(projectRoot, contextId))).filter((entry) => entry.endsWith('.json'));
|
|
51
|
+
counts.snapshots += snapshots.length;
|
|
52
|
+
for (const snapshotFile of snapshots) {
|
|
53
|
+
const snapshotId = snapshotFile.slice(0, -'.json'.length);
|
|
54
|
+
await readJson(snapshotPath(projectRoot, contextId, snapshotId));
|
|
55
|
+
await rebuildSnapshot(projectRoot, contextId, snapshotId);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
issues.push(`context ${contextId}: ${error instanceof Error ? error.message : 'invalid'}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
counts.notes = (await listNotes(projectRoot, { limit: Number.MAX_SAFE_INTEGER, maxChars: Number.MAX_SAFE_INTEGER })).length;
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
issues.push(`notes: ${error instanceof Error ? error.message : 'invalid'}`);
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
counts.knowledge = (await listKnowledge(projectRoot, true)).length;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
issues.push(`knowledge: ${error instanceof Error ? error.message : 'invalid'}`);
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
counts.deadends = (await listDeadends(projectRoot, true)).length;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
issues.push(`deadends: ${error instanceof Error ? error.message : 'invalid'}`);
|
|
79
|
+
}
|
|
80
|
+
return { projectRoot, eligible: issues.length === 0, v1Present, v2Present, counts, issues };
|
|
81
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { DeadendDiscoveryResult, KnowledgeDiscoveryResult } from '../runtime/types.js';
|
|
2
|
+
import type { CognitionEvent, CognitionRetrievalProjection } from './types.js';
|
|
3
|
+
export declare function normalized(value: string): string;
|
|
4
|
+
export declare function queryTerms(query: string): string[];
|
|
5
|
+
export declare function buildRetrievalProjection(events: CognitionEvent[]): CognitionRetrievalProjection;
|
|
6
|
+
export interface RetrievalExclusions {
|
|
7
|
+
status_not_verified: number;
|
|
8
|
+
applicability_mismatch: number;
|
|
9
|
+
scope_mismatch: number;
|
|
10
|
+
query_no_match: number;
|
|
11
|
+
result_limit: number;
|
|
12
|
+
}
|
|
13
|
+
export interface V2Discovery<T> {
|
|
14
|
+
result: T;
|
|
15
|
+
exclusions: RetrievalExclusions;
|
|
16
|
+
}
|
|
17
|
+
export declare function discoverV2Knowledge(index: CognitionRetrievalProjection, query: string, current: CognitionEvent['git'], options: {
|
|
18
|
+
scope?: string;
|
|
19
|
+
limit: number;
|
|
20
|
+
maxChars: number;
|
|
21
|
+
}): V2Discovery<KnowledgeDiscoveryResult>;
|
|
22
|
+
export declare function discoverV2Deadends(index: CognitionRetrievalProjection, query: string, current: CognitionEvent['git'], options: {
|
|
23
|
+
scope?: string;
|
|
24
|
+
limit: number;
|
|
25
|
+
maxChars: number;
|
|
26
|
+
}): V2Discovery<DeadendDiscoveryResult>;
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
const aliasGroups = [
|
|
2
|
+
['投保人认证', '投保人身份认证', '身份核验', '实名认证', 'applicant authentication', 'identity verification'],
|
|
3
|
+
['短信验证码', '验证码', 'sms verification', 'otp'],
|
|
4
|
+
['证件ocr', '证件识别', 'identity ocr', 'document ocr'],
|
|
5
|
+
['人脸识别', '人脸认证', 'face recognition', 'facial verification'],
|
|
6
|
+
];
|
|
7
|
+
export function normalized(value) {
|
|
8
|
+
return value.trim().toLocaleLowerCase();
|
|
9
|
+
}
|
|
10
|
+
export function queryTerms(query) {
|
|
11
|
+
const clean = normalized(query);
|
|
12
|
+
const terms = new Set(clean.split(/\s+/).filter(Boolean));
|
|
13
|
+
for (const group of aliasGroups) {
|
|
14
|
+
if (group.some((alias) => clean.includes(normalized(alias)))) {
|
|
15
|
+
for (const alias of group)
|
|
16
|
+
terms.add(normalized(alias));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return [...terms].sort();
|
|
20
|
+
}
|
|
21
|
+
function strings(record, entity) {
|
|
22
|
+
return entity === 'knowledge'
|
|
23
|
+
? {
|
|
24
|
+
statement: typeof record.statement === 'string' ? record.statement : '',
|
|
25
|
+
kind: typeof record.kind === 'string' ? record.kind : '',
|
|
26
|
+
scope: typeof record.scope === 'string' ? record.scope : '',
|
|
27
|
+
}
|
|
28
|
+
: {
|
|
29
|
+
attempt: typeof record.attempt === 'string' ? record.attempt : '',
|
|
30
|
+
reason: typeof record.reason === 'string' ? record.reason : '',
|
|
31
|
+
scope: typeof record.scope === 'string' ? record.scope : '',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function defaultApplicability(event) {
|
|
35
|
+
if (event.git.ref === null && event.git.branch === null)
|
|
36
|
+
return { project: true, branchMode: 'global' };
|
|
37
|
+
return {
|
|
38
|
+
project: true,
|
|
39
|
+
branchMode: 'exact',
|
|
40
|
+
branches: [event.git.ref ?? event.git.branch ?? 'detached'],
|
|
41
|
+
sourceCommit: event.git.head,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function applicability(record, event) {
|
|
45
|
+
const value = record.applicability;
|
|
46
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
47
|
+
const candidate = value;
|
|
48
|
+
if (candidate.project === true && (candidate.branchMode === 'global' || candidate.branchMode === 'exact')) {
|
|
49
|
+
return {
|
|
50
|
+
project: true,
|
|
51
|
+
branchMode: candidate.branchMode,
|
|
52
|
+
...(Array.isArray(candidate.branches) ? { branches: candidate.branches.filter((item) => typeof item === 'string').sort() } : {}),
|
|
53
|
+
...(Array.isArray(candidate.paths) ? { paths: candidate.paths.filter((item) => typeof item === 'string').sort() } : {}),
|
|
54
|
+
...(typeof candidate.sourceCommit === 'string' || candidate.sourceCommit === null ? { sourceCommit: candidate.sourceCommit } : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return defaultApplicability(event);
|
|
59
|
+
}
|
|
60
|
+
function recordPayload(event) {
|
|
61
|
+
const payload = event.payload;
|
|
62
|
+
return payload.record && typeof payload.record === 'object' && !Array.isArray(payload.record)
|
|
63
|
+
? payload.record
|
|
64
|
+
: null;
|
|
65
|
+
}
|
|
66
|
+
function lifecycleEntity(event, record) {
|
|
67
|
+
if (event.type.startsWith('knowledge.') && typeof record.knowledgeId === 'string')
|
|
68
|
+
return { entity: 'knowledge', entityId: record.knowledgeId };
|
|
69
|
+
if (event.type.startsWith('deadend.') && typeof record.deadendId === 'string')
|
|
70
|
+
return { entity: 'deadend', entityId: record.deadendId };
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
function lifecycleEvent(event) {
|
|
74
|
+
return [
|
|
75
|
+
'knowledge.proposed', 'knowledge.verified', 'knowledge.deprecated', 'knowledge.rejected',
|
|
76
|
+
'deadend.proposed', 'deadend.verified', 'deadend.obsoleted', 'deadend.rejected',
|
|
77
|
+
].includes(event.type);
|
|
78
|
+
}
|
|
79
|
+
export function buildRetrievalProjection(events) {
|
|
80
|
+
const entries = new Map();
|
|
81
|
+
const feedback = new Map();
|
|
82
|
+
for (const event of events) {
|
|
83
|
+
if (event.type === 'record.used') {
|
|
84
|
+
const payload = event.payload;
|
|
85
|
+
const entity = payload.entity === 'knowledge' || payload.entity === 'deadend' ? payload.entity : null;
|
|
86
|
+
const entityId = typeof payload.entityId === 'string' ? payload.entityId : null;
|
|
87
|
+
if (!entity || !entityId)
|
|
88
|
+
continue;
|
|
89
|
+
const key = `${entity}:${entityId}`;
|
|
90
|
+
const prior = feedback.get(key) ?? { usedCount: 0, latestUsedAt: event.occurredAt };
|
|
91
|
+
feedback.set(key, { usedCount: prior.usedCount + 1, latestUsedAt: event.occurredAt });
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (!lifecycleEvent(event))
|
|
95
|
+
continue;
|
|
96
|
+
const record = recordPayload(event);
|
|
97
|
+
if (!record)
|
|
98
|
+
continue;
|
|
99
|
+
const identity = lifecycleEntity(event, record);
|
|
100
|
+
if (!identity)
|
|
101
|
+
continue;
|
|
102
|
+
const fields = strings(record, identity.entity);
|
|
103
|
+
const normalizedTerms = [...new Set(Object.values(fields).flatMap((value) => {
|
|
104
|
+
const normalizedValue = normalized(value);
|
|
105
|
+
return normalizedValue ? [normalizedValue, ...normalizedValue.split(/\s+/).filter(Boolean)] : [];
|
|
106
|
+
}))].sort();
|
|
107
|
+
const key = `${identity.entity}:${identity.entityId}`;
|
|
108
|
+
entries.set(key, {
|
|
109
|
+
entity: identity.entity,
|
|
110
|
+
entityId: identity.entityId,
|
|
111
|
+
status: typeof record.status === 'string' ? record.status : 'candidate',
|
|
112
|
+
record,
|
|
113
|
+
applicability: applicability(record, event),
|
|
114
|
+
searchable: Object.fromEntries(Object.entries(fields).map(([field, value]) => [field, normalized(value)])),
|
|
115
|
+
normalizedTerms,
|
|
116
|
+
provenance: {
|
|
117
|
+
eventId: event.eventId,
|
|
118
|
+
eventType: event.type,
|
|
119
|
+
occurredAt: event.occurredAt,
|
|
120
|
+
git: event.git,
|
|
121
|
+
...(typeof event.payload.anchorProvenance === 'string' ? { anchorProvenance: event.payload.anchorProvenance } : {}),
|
|
122
|
+
},
|
|
123
|
+
feedback: { usedCount: 0, latestUsedAt: null },
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
for (const [key, entry] of entries) {
|
|
127
|
+
const value = feedback.get(key);
|
|
128
|
+
if (value)
|
|
129
|
+
entry.feedback = value;
|
|
130
|
+
}
|
|
131
|
+
const ordered = Object.fromEntries([...entries.entries()].sort(([left], [right]) => left.localeCompare(right)));
|
|
132
|
+
const terms = new Map();
|
|
133
|
+
for (const [key, entry] of Object.entries(ordered)) {
|
|
134
|
+
for (const term of entry.normalizedTerms)
|
|
135
|
+
terms.set(term, [...(terms.get(term) ?? []), key]);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
schemaVersion: 2,
|
|
139
|
+
entries: ordered,
|
|
140
|
+
terms: Object.fromEntries([...terms.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([term, ids]) => [term, ids.sort()])),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function emptyExclusions() {
|
|
144
|
+
return { status_not_verified: 0, applicability_mismatch: 0, scope_mismatch: 0, query_no_match: 0, result_limit: 0 };
|
|
145
|
+
}
|
|
146
|
+
function applicable(entry, current) {
|
|
147
|
+
if (entry.applicability.branchMode === 'global')
|
|
148
|
+
return true;
|
|
149
|
+
const anchor = entry.provenance.git;
|
|
150
|
+
return anchor.ref === current.ref
|
|
151
|
+
&& anchor.branch === current.branch
|
|
152
|
+
&& (anchor.worktreeId === null || anchor.worktreeId === current.worktreeId);
|
|
153
|
+
}
|
|
154
|
+
function match(entry, terms, entity) {
|
|
155
|
+
const fields = new Set();
|
|
156
|
+
const matched = new Set();
|
|
157
|
+
let score = 0;
|
|
158
|
+
const phrase = terms.join(' ');
|
|
159
|
+
const statementField = entity === 'knowledge' ? 'statement' : 'attempt';
|
|
160
|
+
if (entry.searchable[statementField]?.includes(phrase))
|
|
161
|
+
score += entity === 'knowledge' ? 20 : 0;
|
|
162
|
+
for (const term of terms) {
|
|
163
|
+
for (const [field, value] of Object.entries(entry.searchable)) {
|
|
164
|
+
if (!value.includes(term))
|
|
165
|
+
continue;
|
|
166
|
+
fields.add(field);
|
|
167
|
+
matched.add(term);
|
|
168
|
+
score += field === statementField ? 5 : field === 'scope' ? 4 : 3;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (entry.searchable.scope === phrase)
|
|
172
|
+
score += 10;
|
|
173
|
+
return { score, matchedTerms: [...matched].sort(), matchedFields: [...fields].sort() };
|
|
174
|
+
}
|
|
175
|
+
function limitText(value, maxChars) {
|
|
176
|
+
return value.length <= maxChars ? value : `${value.slice(0, Math.max(0, maxChars - 1))}…`;
|
|
177
|
+
}
|
|
178
|
+
export function discoverV2Knowledge(index, query, current, options) {
|
|
179
|
+
const exclusions = emptyExclusions();
|
|
180
|
+
const terms = queryTerms(query);
|
|
181
|
+
const hits = Object.values(index.entries).flatMap((entry) => {
|
|
182
|
+
if (entry.entity !== 'knowledge')
|
|
183
|
+
return [];
|
|
184
|
+
if (entry.status !== 'verified') {
|
|
185
|
+
exclusions.status_not_verified += 1;
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
if (!applicable(entry, current)) {
|
|
189
|
+
exclusions.applicability_mismatch += 1;
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
const knowledge = entry.record;
|
|
193
|
+
if (options.scope && normalized(knowledge.scope) !== normalized(options.scope)) {
|
|
194
|
+
exclusions.scope_mismatch += 1;
|
|
195
|
+
return [];
|
|
196
|
+
}
|
|
197
|
+
const matched = match(entry, terms, 'knowledge');
|
|
198
|
+
if (matched.matchedTerms.length === 0) {
|
|
199
|
+
exclusions.query_no_match += 1;
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
return [{ entry, knowledge, ...matched }];
|
|
203
|
+
}).sort((left, right) => right.score - left.score
|
|
204
|
+
|| Math.min(right.entry.feedback.usedCount, 5) - Math.min(left.entry.feedback.usedCount, 5)
|
|
205
|
+
|| right.knowledge.confidence - left.knowledge.confidence
|
|
206
|
+
|| right.entry.provenance.occurredAt.localeCompare(left.entry.provenance.occurredAt)
|
|
207
|
+
|| left.knowledge.knowledgeId.localeCompare(right.knowledge.knowledgeId));
|
|
208
|
+
const selected = hits.slice(0, options.limit);
|
|
209
|
+
exclusions.result_limit = hits.length - selected.length;
|
|
210
|
+
return {
|
|
211
|
+
result: {
|
|
212
|
+
query,
|
|
213
|
+
total: hits.length,
|
|
214
|
+
truncated: hits.length > selected.length,
|
|
215
|
+
hits: selected.map(({ knowledge, score, matchedTerms, matchedFields, entry }) => ({
|
|
216
|
+
knowledge: { ...knowledge, statement: limitText(knowledge.statement, options.maxChars), supportingEvidence: knowledge.supportingEvidence.map((value) => limitText(value, options.maxChars)) },
|
|
217
|
+
score,
|
|
218
|
+
matchedTerms,
|
|
219
|
+
matchedFields,
|
|
220
|
+
why: [...matchedFields.map((field) => `matched ${field}`), ...(entry.feedback.usedCount > 0 ? ['prior recorded use'] : [])],
|
|
221
|
+
})),
|
|
222
|
+
duplicateIds: [],
|
|
223
|
+
possibleConflicts: [],
|
|
224
|
+
},
|
|
225
|
+
exclusions,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
export function discoverV2Deadends(index, query, current, options) {
|
|
229
|
+
const exclusions = emptyExclusions();
|
|
230
|
+
const terms = queryTerms(query);
|
|
231
|
+
const hits = Object.values(index.entries).flatMap((entry) => {
|
|
232
|
+
if (entry.entity !== 'deadend')
|
|
233
|
+
return [];
|
|
234
|
+
if (entry.status !== 'verified') {
|
|
235
|
+
exclusions.status_not_verified += 1;
|
|
236
|
+
return [];
|
|
237
|
+
}
|
|
238
|
+
if (!applicable(entry, current)) {
|
|
239
|
+
exclusions.applicability_mismatch += 1;
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
const deadend = entry.record;
|
|
243
|
+
if (options.scope && normalized(deadend.scope) !== normalized(options.scope)) {
|
|
244
|
+
exclusions.scope_mismatch += 1;
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
const matched = match(entry, terms, 'deadend');
|
|
248
|
+
if (matched.matchedTerms.length === 0) {
|
|
249
|
+
exclusions.query_no_match += 1;
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
return [{ entry, deadend, ...matched }];
|
|
253
|
+
}).sort((left, right) => right.score - left.score
|
|
254
|
+
|| Math.min(right.entry.feedback.usedCount, 5) - Math.min(left.entry.feedback.usedCount, 5)
|
|
255
|
+
|| right.entry.provenance.occurredAt.localeCompare(left.entry.provenance.occurredAt)
|
|
256
|
+
|| left.deadend.deadendId.localeCompare(right.deadend.deadendId));
|
|
257
|
+
const selected = hits.slice(0, options.limit);
|
|
258
|
+
exclusions.result_limit = hits.length - selected.length;
|
|
259
|
+
return {
|
|
260
|
+
result: {
|
|
261
|
+
query,
|
|
262
|
+
total: hits.length,
|
|
263
|
+
truncated: hits.length > selected.length,
|
|
264
|
+
hits: selected.map(({ deadend, score, matchedTerms, matchedFields }) => ({
|
|
265
|
+
deadend: { ...deadend, attempt: limitText(deadend.attempt, options.maxChars), reason: limitText(deadend.reason, options.maxChars), observedEvidence: deadend.observedEvidence.map((value) => limitText(value, options.maxChars)) },
|
|
266
|
+
score,
|
|
267
|
+
matchedTerms,
|
|
268
|
+
matchedFields,
|
|
269
|
+
})),
|
|
270
|
+
},
|
|
271
|
+
exclusions,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RuntimeLease, RuntimeStatus } from './types.js';
|
|
2
|
+
export interface RuntimeServer {
|
|
3
|
+
lease: RuntimeLease;
|
|
4
|
+
close(reason?: string): Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
export declare function serveRuntime(startPath?: string): Promise<RuntimeServer>;
|
|
7
|
+
export declare function runtimeStatus(startPath?: string): Promise<RuntimeStatus>;
|
|
8
|
+
export declare function enableRuntime(startPath?: string): Promise<RuntimeStatus>;
|
|
9
|
+
export declare function disableRuntime(startPath?: string): Promise<RuntimeStatus>;
|
|
10
|
+
export declare function startRuntime(startPath?: string): Promise<RuntimeStatus>;
|
|
11
|
+
export declare function stopRuntime(startPath?: string): Promise<RuntimeStatus>;
|
|
12
|
+
export declare function observeRuntime(startPath?: string): Promise<boolean>;
|