llm-wiki-kit 0.2.8 → 0.2.10
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 +7 -6
- package/docs/concepts.md +4 -3
- package/docs/integrations/claude-code.md +2 -2
- package/docs/integrations/codex.md +3 -3
- package/docs/manual.md +23 -7
- package/docs/operations.md +23 -4
- package/package.json +1 -1
- package/src/cli.js +4 -2
- package/src/compact-capture.js +107 -29
- package/src/consolidate.js +62 -29
- package/src/hook.js +25 -9
- package/src/maintenance.js +97 -2
- package/src/state.js +16 -0
- package/src/templates.js +13 -4
- package/src/wiki-lint.js +47 -9
- package/src/wiki-search.js +84 -19
- package/src/wiki-visibility.js +76 -0
package/src/wiki-search.js
CHANGED
|
@@ -4,10 +4,20 @@ import { redactText } from './redaction.js';
|
|
|
4
4
|
import {
|
|
5
5
|
buildWikiGraph,
|
|
6
6
|
collectWikiPages,
|
|
7
|
+
MEMORY_BYTE_LIMIT,
|
|
8
|
+
MEMORY_LINE_LIMIT,
|
|
7
9
|
pageLookup,
|
|
8
10
|
parseFrontmatter,
|
|
9
11
|
readMemoryExcerpt,
|
|
10
12
|
} from './wiki-model.js';
|
|
13
|
+
import {
|
|
14
|
+
isArchivedPage,
|
|
15
|
+
isDefaultSearchCandidate,
|
|
16
|
+
isEpisodicLayerPage,
|
|
17
|
+
isStalePage,
|
|
18
|
+
isSupersededPage,
|
|
19
|
+
searchWeight,
|
|
20
|
+
} from './wiki-visibility.js';
|
|
11
21
|
|
|
12
22
|
const DEFAULT_LIMIT = 5;
|
|
13
23
|
const SNIPPET_CHARS = 350;
|
|
@@ -65,6 +75,7 @@ function resultRecord(page, score, fields = {}) {
|
|
|
65
75
|
title: page.title,
|
|
66
76
|
type: page.type || '',
|
|
67
77
|
memoryType: page.memoryType || '',
|
|
78
|
+
status: page.status || '',
|
|
68
79
|
score,
|
|
69
80
|
directScore: fields.directScore || 0,
|
|
70
81
|
linkScore: fields.linkScore || 0,
|
|
@@ -81,22 +92,35 @@ function sortHits(a, b) {
|
|
|
81
92
|
return a.path.localeCompare(b.path);
|
|
82
93
|
}
|
|
83
94
|
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
return Number.isFinite(value) ? value : 0;
|
|
95
|
+
export async function searchWiki(projectRoot, query, options = {}) {
|
|
96
|
+
return (await performSearch(projectRoot, query, options)).hits;
|
|
87
97
|
}
|
|
88
98
|
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
function searchVisibility(pages, options = {}) {
|
|
100
|
+
const metadata = {
|
|
101
|
+
totalPages: pages.length,
|
|
102
|
+
searchedPages: 0,
|
|
103
|
+
hiddenEpisodicPages: 0,
|
|
104
|
+
hiddenArchivedPages: 0,
|
|
105
|
+
hiddenSupersededPages: 0,
|
|
106
|
+
stalePagesSearched: 0,
|
|
107
|
+
includeEpisodic: Boolean(options.includeEpisodic),
|
|
108
|
+
includeArchived: Boolean(options.includeArchived),
|
|
109
|
+
};
|
|
110
|
+
const visible = [];
|
|
111
|
+
for (const page of pages) {
|
|
112
|
+
const hiddenArchived = !options.includeArchived && isArchivedPage(page);
|
|
113
|
+
const hiddenSuperseded = !options.includeArchived && isSupersededPage(page);
|
|
114
|
+
const hiddenEpisodic = !options.includeEpisodic && isEpisodicLayerPage(page) && !isDefaultSearchCandidate(page, options);
|
|
115
|
+
if (hiddenArchived) metadata.hiddenArchivedPages += 1;
|
|
116
|
+
if (hiddenSuperseded) metadata.hiddenSupersededPages += 1;
|
|
117
|
+
if (hiddenEpisodic) metadata.hiddenEpisodicPages += 1;
|
|
118
|
+
if (!isDefaultSearchCandidate(page, options)) continue;
|
|
119
|
+
if (isStalePage(page)) metadata.stalePagesSearched += 1;
|
|
120
|
+
visible.push(page);
|
|
94
121
|
}
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export async function searchWiki(projectRoot, query, options = {}) {
|
|
99
|
-
return (await performSearch(projectRoot, query, options)).hits;
|
|
122
|
+
metadata.searchedPages = visible.length;
|
|
123
|
+
return { visible, metadata };
|
|
100
124
|
}
|
|
101
125
|
|
|
102
126
|
async function performSearch(projectRoot, query, options = {}) {
|
|
@@ -109,8 +133,21 @@ async function performSearch(projectRoot, query, options = {}) {
|
|
|
109
133
|
}
|
|
110
134
|
|
|
111
135
|
let pages = [];
|
|
136
|
+
let metadata = {
|
|
137
|
+
totalPages: 0,
|
|
138
|
+
searchedPages: 0,
|
|
139
|
+
hiddenEpisodicPages: 0,
|
|
140
|
+
hiddenArchivedPages: 0,
|
|
141
|
+
hiddenSupersededPages: 0,
|
|
142
|
+
stalePagesSearched: 0,
|
|
143
|
+
includeEpisodic: Boolean(opts.includeEpisodic),
|
|
144
|
+
includeArchived: Boolean(opts.includeArchived),
|
|
145
|
+
};
|
|
112
146
|
try {
|
|
113
|
-
|
|
147
|
+
const collected = await collectWikiPages(projectRoot, opts);
|
|
148
|
+
const visibility = searchVisibility(collected, opts);
|
|
149
|
+
pages = visibility.visible;
|
|
150
|
+
metadata = visibility.metadata;
|
|
114
151
|
} catch {
|
|
115
152
|
return { hits: [], search: 'missing-wiki' };
|
|
116
153
|
}
|
|
@@ -144,7 +181,7 @@ async function performSearch(projectRoot, query, options = {}) {
|
|
|
144
181
|
const page = byPath.get(item.id);
|
|
145
182
|
if (!page) continue;
|
|
146
183
|
const subScore = substringScore(page, terms);
|
|
147
|
-
const score = item.score + subScore;
|
|
184
|
+
const score = (item.score + subScore) * searchWeight(page);
|
|
148
185
|
hits.set(page.rel, resultRecord(page, score, {
|
|
149
186
|
directScore: score,
|
|
150
187
|
matchedTerms: item.terms || terms,
|
|
@@ -154,7 +191,7 @@ async function performSearch(projectRoot, query, options = {}) {
|
|
|
154
191
|
}
|
|
155
192
|
|
|
156
193
|
for (const page of pages) {
|
|
157
|
-
const score = substringScore(page, terms);
|
|
194
|
+
const score = substringScore(page, terms) * searchWeight(page);
|
|
158
195
|
if (score <= 0 || hits.has(page.rel)) continue;
|
|
159
196
|
hits.set(page.rel, resultRecord(page, score, {
|
|
160
197
|
directScore: score,
|
|
@@ -175,7 +212,7 @@ async function performSearch(projectRoot, query, options = {}) {
|
|
|
175
212
|
if (hits.has(neighborPath)) continue;
|
|
176
213
|
const page = byPath.get(neighborPath);
|
|
177
214
|
if (!page) continue;
|
|
178
|
-
const linkScore = seed.score * 0.2;
|
|
215
|
+
const linkScore = seed.score * 0.2 * searchWeight(page);
|
|
179
216
|
hits.set(neighborPath, resultRecord(page, linkScore, {
|
|
180
217
|
linkScore,
|
|
181
218
|
source: 'linked',
|
|
@@ -190,6 +227,7 @@ async function performSearch(projectRoot, query, options = {}) {
|
|
|
190
227
|
return {
|
|
191
228
|
hits: [...hits.values()].sort(sortHits).slice(0, limit),
|
|
192
229
|
search,
|
|
230
|
+
metadata,
|
|
193
231
|
};
|
|
194
232
|
}
|
|
195
233
|
|
|
@@ -282,11 +320,12 @@ export async function buildContextPack(projectRoot, query, options = {}) {
|
|
|
282
320
|
const limit = Number(options.limit || DEFAULT_LIMIT);
|
|
283
321
|
const expand = options.expand !== false;
|
|
284
322
|
const memoryExcerpt = await readMemoryExcerpt(projectRoot);
|
|
285
|
-
const
|
|
323
|
+
const rawIndexExcerpt = await readText(join(projectRoot, 'llm-wiki', 'wiki', 'index.md'));
|
|
324
|
+
const indexExcerpt = rawIndexExcerpt.slice(0, 1200).trim();
|
|
286
325
|
const logExcerpt = options.includeLog
|
|
287
326
|
? (await readText(join(projectRoot, 'llm-wiki', 'wiki', 'log.md'))).slice(-1000).trim()
|
|
288
327
|
: '';
|
|
289
|
-
const result = query ? await performSearch(projectRoot, query, { ...options, limit, expand }) : { hits: [], search: 'none' };
|
|
328
|
+
const result = query ? await performSearch(projectRoot, query, { ...options, limit, expand }) : { hits: [], search: 'none', metadata: null };
|
|
290
329
|
return {
|
|
291
330
|
workspace: projectRoot,
|
|
292
331
|
query: redactText(query || '', 1000),
|
|
@@ -297,6 +336,32 @@ export async function buildContextPack(projectRoot, query, options = {}) {
|
|
|
297
336
|
indexExcerpt: redactText(indexExcerpt, 2000),
|
|
298
337
|
logExcerpt: redactText(logExcerpt, 2000),
|
|
299
338
|
hits: result.hits.map(redactHit),
|
|
339
|
+
metadata: {
|
|
340
|
+
search: result.metadata,
|
|
341
|
+
budgets: {
|
|
342
|
+
memory: {
|
|
343
|
+
maxLines: MEMORY_LINE_LIMIT,
|
|
344
|
+
maxBytes: MEMORY_BYTE_LIMIT,
|
|
345
|
+
returnedBytes: Buffer.byteLength(memoryExcerpt.trim(), 'utf8'),
|
|
346
|
+
redactionMaxChars: 30000,
|
|
347
|
+
},
|
|
348
|
+
index: {
|
|
349
|
+
maxChars: 1200,
|
|
350
|
+
sourceChars: rawIndexExcerpt.length,
|
|
351
|
+
returnedChars: indexExcerpt.length,
|
|
352
|
+
redactionMaxChars: 2000,
|
|
353
|
+
},
|
|
354
|
+
hits: {
|
|
355
|
+
requestedLimit: limit,
|
|
356
|
+
returned: result.hits.length,
|
|
357
|
+
snippetChars: SNIPPET_CHARS,
|
|
358
|
+
hookSnippetChars: HOOK_SNIPPET_CHARS,
|
|
359
|
+
expand,
|
|
360
|
+
includeEpisodic: Boolean(options.includeEpisodic),
|
|
361
|
+
includeArchived: Boolean(options.includeArchived),
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
},
|
|
300
365
|
};
|
|
301
366
|
}
|
|
302
367
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export const DURABLE_MEMORY_TYPES = new Set(['semantic', 'procedural']);
|
|
2
|
+
|
|
3
|
+
export function frontmatterValues(value) {
|
|
4
|
+
if (Array.isArray(value)) return value.map((item) => String(item || '').trim()).filter(Boolean);
|
|
5
|
+
const text = String(value || '').trim();
|
|
6
|
+
if (!text || text === '[]') return [];
|
|
7
|
+
return [text];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function pageImportance(page) {
|
|
11
|
+
const value = Number(page?.frontmatter?.importance || 0);
|
|
12
|
+
return Number.isFinite(value) ? value : 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isArchivedPage(page) {
|
|
16
|
+
return String(page?.status || '').toLowerCase() === 'archived';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function isStalePage(page) {
|
|
20
|
+
return String(page?.status || '').toLowerCase() === 'stale';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function supersededBy(page) {
|
|
24
|
+
return frontmatterValues(page?.frontmatter?.superseded_by);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function supersedes(page) {
|
|
28
|
+
return frontmatterValues(page?.frontmatter?.supersedes);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isSupersededPage(page) {
|
|
32
|
+
return supersededBy(page).length > 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function hasSupersession(page) {
|
|
36
|
+
return supersededBy(page).length > 0 || supersedes(page).length > 0;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function isEpisodicLayerPage(page) {
|
|
40
|
+
const type = String(page?.type || '').toLowerCase();
|
|
41
|
+
const rel = String(page?.rel || '');
|
|
42
|
+
return (
|
|
43
|
+
type === 'query' ||
|
|
44
|
+
type === 'context' ||
|
|
45
|
+
type === 'session-log' ||
|
|
46
|
+
rel.startsWith('wiki/queries/') ||
|
|
47
|
+
rel.startsWith('wiki/context/')
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isPromotedDurablePage(page) {
|
|
52
|
+
return DURABLE_MEMORY_TYPES.has(String(page?.memoryType || '').toLowerCase()) && pageImportance(page) >= 4;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function includeArchivedPages(options = {}) {
|
|
56
|
+
return Boolean(options.includeArchived);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function includeEpisodicPages(options = {}) {
|
|
60
|
+
return Boolean(options.includeEpisodic);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isDefaultSearchCandidate(page, options = {}) {
|
|
64
|
+
if (!includeArchivedPages(options) && (isArchivedPage(page) || isSupersededPage(page))) return false;
|
|
65
|
+
if (includeEpisodicPages(options)) return true;
|
|
66
|
+
if (isEpisodicLayerPage(page)) return isPromotedDurablePage(page);
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function searchWeight(page) {
|
|
71
|
+
let weight = 1;
|
|
72
|
+
if (DURABLE_MEMORY_TYPES.has(String(page?.memoryType || '').toLowerCase())) weight *= 1.2;
|
|
73
|
+
if (pageImportance(page) >= 4) weight *= 1.1;
|
|
74
|
+
if (isStalePage(page)) weight *= 0.35;
|
|
75
|
+
return weight;
|
|
76
|
+
}
|