llm-wiki-kit 0.2.9 → 0.2.11
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 +10 -7
- package/docs/concepts.md +10 -8
- package/docs/integrations/claude-code.md +1 -1
- package/docs/integrations/codex.md +2 -2
- package/docs/manual.md +37 -8
- package/docs/operations.md +25 -3
- package/package.json +1 -1
- package/src/capture-policy.js +19 -19
- package/src/cli.js +19 -2
- package/src/consolidate.js +62 -29
- package/src/live-qa.js +321 -0
- package/src/maintenance.js +97 -2
- package/src/project.js +2 -26
- package/src/templates.js +19 -10
- package/src/wiki-lint.js +96 -9
- package/src/wiki-search.js +84 -19
- package/src/wiki-visibility.js +76 -0
package/src/wiki-lint.js
CHANGED
|
@@ -4,14 +4,24 @@ import { exists, readText } from './fs-utils.js';
|
|
|
4
4
|
import { maintenanceSummary } from './maintenance.js';
|
|
5
5
|
import { inspectProjectState } from './project-state.js';
|
|
6
6
|
import { hasSecretLikeText } from './redaction.js';
|
|
7
|
+
import { liveQaMaxBytes, liveQaMaxLines } from './live-qa.js';
|
|
7
8
|
import {
|
|
8
9
|
buildAliasMap,
|
|
9
10
|
buildWikiGraph,
|
|
10
11
|
collectWikiPages,
|
|
12
|
+
DEFAULT_MAX_WIKI_FILES,
|
|
13
|
+
MEMORY_BYTE_LIMIT,
|
|
11
14
|
normalizeTarget,
|
|
12
15
|
resolveWikiLink,
|
|
13
16
|
wikiRoot,
|
|
14
17
|
} from './wiki-model.js';
|
|
18
|
+
import {
|
|
19
|
+
hasSupersession,
|
|
20
|
+
isArchivedPage,
|
|
21
|
+
isEpisodicLayerPage,
|
|
22
|
+
isPromotedDurablePage,
|
|
23
|
+
isStalePage,
|
|
24
|
+
} from './wiki-visibility.js';
|
|
15
25
|
|
|
16
26
|
const VALID_TYPES = new Set([
|
|
17
27
|
'source',
|
|
@@ -29,11 +39,60 @@ const VALID_STATUS = new Set(['draft', 'reviewed', 'stale', 'archived']);
|
|
|
29
39
|
const VALID_CONFIDENCE = new Set(['high', 'medium', 'low']);
|
|
30
40
|
const VALID_MEMORY_TYPES = new Set(['semantic', 'episodic', 'procedural']);
|
|
31
41
|
const CORE_PAGES = new Set(['wiki/index.md', 'wiki/log.md', 'wiki/memory.md']);
|
|
42
|
+
const MEMORY_NEAR_BUDGET_BYTES = 20 * 1024;
|
|
43
|
+
const HIDDEN_PAGE_WARNING_THRESHOLD = 50;
|
|
44
|
+
const SEARCH_CAP_WARNING_RATIO = 0.8;
|
|
45
|
+
const ARCHIVED_LIVE_QA_MARKER = '<!-- llm-wiki-kit:archived-live-qa -->';
|
|
32
46
|
|
|
33
47
|
function issue(severity, code, path, message) {
|
|
34
48
|
return { severity, code, path, message };
|
|
35
49
|
}
|
|
36
50
|
|
|
51
|
+
function countLines(text) {
|
|
52
|
+
if (!text) return 0;
|
|
53
|
+
return String(text).split(/\r?\n/).length;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function liveQaOversized(text) {
|
|
57
|
+
return countLines(text) > liveQaMaxLines() || Buffer.byteLength(text || '', 'utf8') > liveQaMaxBytes();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function liveQaGrowthIssues(projectRoot) {
|
|
61
|
+
const issues = [];
|
|
62
|
+
const root = join(projectRoot, 'llm-wiki', 'outputs', 'questions');
|
|
63
|
+
let entries = [];
|
|
64
|
+
try {
|
|
65
|
+
entries = await readdir(root, { withFileTypes: true });
|
|
66
|
+
} catch {
|
|
67
|
+
return issues;
|
|
68
|
+
}
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const full = join(root, entry.name);
|
|
71
|
+
if (entry.isFile() && /^\d{4}-\d{2}-\d{2}-live-qa\.md$/.test(entry.name)) {
|
|
72
|
+
const text = await readText(full, '');
|
|
73
|
+
if (!text.includes(ARCHIVED_LIVE_QA_MARKER) && liveQaOversized(text)) {
|
|
74
|
+
issues.push(issue('warning', 'live-qa-legacy-oversized', `outputs/questions/${entry.name}`, 'legacy daily live Q&A file is oversized; run archive-questions to split it into chunks'));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (entry.isDirectory() && /^\d{4}-\d{2}-\d{2}$/.test(entry.name)) {
|
|
78
|
+
let chunkEntries = [];
|
|
79
|
+
try {
|
|
80
|
+
chunkEntries = await readdir(full, { withFileTypes: true });
|
|
81
|
+
} catch {
|
|
82
|
+
chunkEntries = [];
|
|
83
|
+
}
|
|
84
|
+
for (const chunk of chunkEntries) {
|
|
85
|
+
if (!chunk.isFile() || !/^live-qa-\d+\.md$/.test(chunk.name)) continue;
|
|
86
|
+
const text = await readText(join(full, chunk.name), '');
|
|
87
|
+
if (liveQaOversized(text)) {
|
|
88
|
+
issues.push(issue('warning', 'live-qa-chunk-oversized', `outputs/questions/${entry.name}/${chunk.name}`, 'live Q&A chunk exceeds the configured rollover budget'));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return issues;
|
|
94
|
+
}
|
|
95
|
+
|
|
37
96
|
function isDateLike(value) {
|
|
38
97
|
return value === 'unknown' || /^\d{4}-\d{2}-\d{2}$/.test(String(value || ''));
|
|
39
98
|
}
|
|
@@ -125,8 +184,9 @@ export async function runLint(projectRoot, options = {}) {
|
|
|
125
184
|
return lintResult(projectRoot, [], issues);
|
|
126
185
|
}
|
|
127
186
|
|
|
187
|
+
const maxFiles = options.maxFiles || 1000;
|
|
128
188
|
const pages = await collectWikiPages(projectRoot, {
|
|
129
|
-
maxFiles
|
|
189
|
+
maxFiles,
|
|
130
190
|
maxChars: options.maxChars || 75000,
|
|
131
191
|
});
|
|
132
192
|
const byRel = new Map(pages.map((page) => [page.rel, page]));
|
|
@@ -226,9 +286,23 @@ export async function runLint(projectRoot, options = {}) {
|
|
|
226
286
|
}
|
|
227
287
|
|
|
228
288
|
const memoryText = await readText(join(projectRoot, 'llm-wiki', 'wiki', 'memory.md'), '');
|
|
229
|
-
|
|
230
|
-
|
|
289
|
+
const memoryBytes = Buffer.byteLength(memoryText, 'utf8');
|
|
290
|
+
if (memoryBytes > MEMORY_BYTE_LIMIT) {
|
|
291
|
+
issues.push(issue('warning', 'memory-too-large', 'wiki/memory.md', `memory.md is oversized (${memoryBytes} bytes) and larger than the hook excerpt budget`));
|
|
292
|
+
} else if (memoryBytes >= MEMORY_NEAR_BUDGET_BYTES) {
|
|
293
|
+
issues.push(issue('warning', 'memory-near-budget', 'wiki/memory.md', `memory.md is near the hook excerpt budget (${memoryBytes} bytes)`));
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const searchCap = options.maxFiles || DEFAULT_MAX_WIKI_FILES;
|
|
297
|
+
if (pages.length >= Math.floor(searchCap * SEARCH_CAP_WARNING_RATIO)) {
|
|
298
|
+
issues.push(issue('warning', 'wiki-page-count-near-search-cap', 'llm-wiki/wiki', `wiki page count ${pages.length} is near the default search cap ${searchCap}`));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const hiddenEpisodicPages = pages.filter((page) => isEpisodicLayerPage(page) && !isPromotedDurablePage(page));
|
|
302
|
+
if (hiddenEpisodicPages.length >= HIDDEN_PAGE_WARNING_THRESHOLD) {
|
|
303
|
+
issues.push(issue('warning', 'hidden-episodic-growth', 'llm-wiki/wiki', `${hiddenEpisodicPages.length} default-hidden episodic/context/session pages may add wiki growth and search noise`));
|
|
231
304
|
}
|
|
305
|
+
|
|
232
306
|
for (const page of pages.filter(isOrphanCandidate)) {
|
|
233
307
|
const backlinks = graph.backlinks.get(page.rel);
|
|
234
308
|
if (!backlinks || backlinks.size === 0) {
|
|
@@ -236,6 +310,15 @@ export async function runLint(projectRoot, options = {}) {
|
|
|
236
310
|
}
|
|
237
311
|
}
|
|
238
312
|
|
|
313
|
+
for (const page of pages) {
|
|
314
|
+
if (!isArchivedPage(page) && !isStalePage(page)) continue;
|
|
315
|
+
const backlinks = graph.backlinks.get(page.rel);
|
|
316
|
+
const outlinks = graph.outlinks.get(page.rel);
|
|
317
|
+
if (!hasSupersession(page) && (!backlinks || backlinks.size === 0) && (!outlinks || outlinks.size === 0)) {
|
|
318
|
+
issues.push(issue('warning', 'stale-archived-discoverability', page.rel, 'stale/archived page has no supersession metadata or wiki links for discoverability'));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
239
322
|
const projectState = await inspectProjectState(projectRoot).catch(() => null);
|
|
240
323
|
for (const file of projectState?.managedFiles || []) {
|
|
241
324
|
if (file.needsAttention) {
|
|
@@ -245,14 +328,18 @@ export async function runLint(projectRoot, options = {}) {
|
|
|
245
328
|
}
|
|
246
329
|
}
|
|
247
330
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
331
|
+
if (!options.skipMaintenance) {
|
|
332
|
+
const maintenance = await maintenanceSummary(projectRoot, { skipLint: true }).catch(() => null);
|
|
333
|
+
if (maintenance?.tooManyPending) {
|
|
334
|
+
issues.push(issue('warning', 'maintenance-too-many-pending', 'outputs/maintenance/queue.md', `maintenance queue has ${maintenance.pendingCount} pending items; review and merge durable items into wiki pages`));
|
|
335
|
+
}
|
|
336
|
+
if ((maintenance?.stalePending || []).length > 0) {
|
|
337
|
+
issues.push(issue('warning', 'maintenance-stale-pending', 'outputs/maintenance/queue.md', `${maintenance.stalePending.length} pending maintenance item(s) are older than ${maintenance.stalePendingDays} days`));
|
|
338
|
+
}
|
|
254
339
|
}
|
|
255
340
|
|
|
341
|
+
issues.push(...await liveQaGrowthIssues(projectRoot));
|
|
342
|
+
|
|
256
343
|
return lintResult(projectRoot, pages, issues);
|
|
257
344
|
}
|
|
258
345
|
|
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
|
+
}
|