llm-wiki-kit 0.1.5 → 0.1.7

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.
@@ -0,0 +1,203 @@
1
+ import { join } from 'path';
2
+ import { appendWikiLog } from './project.js';
3
+ import { backupFile, exists, readText, writeText } from './fs-utils.js';
4
+ import { hasSecretLikeText, isSensitivePath, normalizeForStorage } from './redaction.js';
5
+ import { indexPage, memoryPage } from './templates.js';
6
+ import { collectWikiPages } from './wiki-model.js';
7
+ import { runLint } from './wiki-lint.js';
8
+
9
+ export const MEMORY_START = '<!-- llm-wiki-kit:memory-start -->';
10
+ export const MEMORY_END = '<!-- llm-wiki-kit:memory-end -->';
11
+ export const INDEX_START = '<!-- llm-wiki-kit:index-start -->';
12
+ export const INDEX_END = '<!-- llm-wiki-kit:index-end -->';
13
+
14
+ function replaceOrAppendBlock(content, startMarker, endMarker, block) {
15
+ const replacement = `${startMarker}\n${block.trim()}\n${endMarker}`;
16
+ const firstStart = content.indexOf(startMarker);
17
+ const firstEnd = content.indexOf(endMarker);
18
+ if (firstStart === -1 && firstEnd === -1) {
19
+ return { next: `${content.trimEnd()}\n\n${replacement}\n`, malformed: false };
20
+ }
21
+ const lastStart = content.lastIndexOf(startMarker);
22
+ const lastEnd = content.lastIndexOf(endMarker);
23
+ if (firstStart === -1 || firstEnd === -1 || firstEnd < firstStart || lastStart > lastEnd) {
24
+ return { next: content, malformed: true };
25
+ }
26
+ return {
27
+ next: `${content.slice(0, firstStart)}${replacement}${content.slice(lastEnd + endMarker.length)}`,
28
+ malformed: false,
29
+ };
30
+ }
31
+
32
+ function pageTarget(page) {
33
+ return page.rel.replace(/^wiki\//, '').replace(/\.md$/i, '');
34
+ }
35
+
36
+ function pageReference(page) {
37
+ return `[[${pageTarget(page)}|${page.title}]]`;
38
+ }
39
+
40
+ function importance(page) {
41
+ const value = Number(page.frontmatter.importance || 0);
42
+ return Number.isFinite(value) ? value : 0;
43
+ }
44
+
45
+ function isPromotableEpisodicPage(page) {
46
+ return ['semantic', 'procedural'].includes(page.memoryType) && importance(page) >= 4;
47
+ }
48
+
49
+ function isGeneratedBlockCandidate(page) {
50
+ if (['wiki/index.md', 'wiki/log.md', 'wiki/memory.md'].includes(page.rel)) return false;
51
+ if (isSensitivePath(page.rel) || isSensitivePath(page.title)) return false;
52
+ if (hasSecretLikeText(`${page.rel}\n${page.title}\n${page.content}`)) return false;
53
+ const type = String(page.type || '').toLowerCase();
54
+ if (
55
+ type === 'query' ||
56
+ type === 'context' ||
57
+ type === 'session-log' ||
58
+ page.rel.startsWith('wiki/queries/') ||
59
+ page.rel.startsWith('wiki/context/')
60
+ ) {
61
+ return isPromotableEpisodicPage(page);
62
+ }
63
+ return true;
64
+ }
65
+
66
+ function sortedPages(pages) {
67
+ return pages
68
+ .filter(isGeneratedBlockCandidate)
69
+ .sort((a, b) => {
70
+ const importanceDiff = importance(b) - importance(a);
71
+ if (importanceDiff !== 0) return importanceDiff;
72
+ const typeDiff = String(a.type || '').localeCompare(String(b.type || ''));
73
+ if (typeDiff !== 0) return typeDiff;
74
+ return a.rel.localeCompare(b.rel);
75
+ });
76
+ }
77
+
78
+ function buildGeneratedMemoryBlock(pages) {
79
+ const candidates = sortedPages(pages).slice(0, 25);
80
+ const lines = [
81
+ '## Generated Memory Map',
82
+ '',
83
+ `- Pages indexed: ${sortedPages(pages).length}`,
84
+ ];
85
+ if (candidates.length === 0) {
86
+ lines.push('- No durable pages found yet.');
87
+ return lines.join('\n');
88
+ }
89
+ lines.push('', '### High-Value Pages');
90
+ for (const page of candidates) {
91
+ const metadata = [
92
+ page.type || 'unknown',
93
+ page.memoryType ? `memory:${page.memoryType}` : '',
94
+ importance(page) > 0 ? `importance:${importance(page)}` : '',
95
+ page.confidence ? `confidence:${page.confidence}` : '',
96
+ ].filter(Boolean).join(', ');
97
+ lines.push(`- ${pageReference(page)}${metadata ? ` - ${metadata}` : ''}`);
98
+ }
99
+ return lines.join('\n');
100
+ }
101
+
102
+ function buildGeneratedIndexBlock(pages) {
103
+ const groups = new Map();
104
+ const candidates = sortedPages(pages);
105
+ for (const page of candidates) {
106
+ const type = page.type || 'uncategorized';
107
+ if (!groups.has(type)) groups.set(type, []);
108
+ groups.get(type).push(page);
109
+ }
110
+
111
+ const lines = [
112
+ '## Generated Page Map',
113
+ '',
114
+ `- Pages indexed: ${candidates.length}`,
115
+ ];
116
+ if (groups.size === 0) {
117
+ lines.push('- No durable pages found yet.');
118
+ return lines.join('\n');
119
+ }
120
+ for (const type of [...groups.keys()].sort()) {
121
+ lines.push('', `### ${type}`);
122
+ for (const page of groups.get(type).slice(0, 50)) {
123
+ lines.push(`- ${pageReference(page)}`);
124
+ }
125
+ }
126
+ return lines.join('\n');
127
+ }
128
+
129
+ async function updateMarkedFile(projectRoot, rel, initialContent, startMarker, endMarker, block, options) {
130
+ const absolutePath = join(projectRoot, rel);
131
+ const current = await readText(absolutePath, initialContent);
132
+ const { next, malformed } = replaceOrAppendBlock(current || initialContent, startMarker, endMarker, block);
133
+ if (malformed) return { skipped: { path: rel, reason: 'malformed-marker' } };
134
+ if (normalizeForStorage(current) === normalizeForStorage(next)) return null;
135
+ if (!options.dryRun) {
136
+ if (await exists(absolutePath)) await backupFile(absolutePath, rel);
137
+ await writeText(absolutePath, next);
138
+ }
139
+ return { changed: { path: rel } };
140
+ }
141
+
142
+ export async function runConsolidate(projectRoot, options = {}) {
143
+ const lintResult = await runLint(projectRoot, { maxFiles: options.maxFiles || 1000 });
144
+ const pages = await collectWikiPages(projectRoot, { maxFiles: options.maxFiles || 1000 });
145
+ const changed = [];
146
+ const skipped = [];
147
+
148
+ const memoryChange = await updateMarkedFile(
149
+ projectRoot,
150
+ join('llm-wiki', 'wiki', 'memory.md'),
151
+ memoryPage(),
152
+ MEMORY_START,
153
+ MEMORY_END,
154
+ buildGeneratedMemoryBlock(pages),
155
+ options,
156
+ );
157
+ if (memoryChange?.changed) changed.push(memoryChange.changed);
158
+ if (memoryChange?.skipped) skipped.push(memoryChange.skipped);
159
+
160
+ const indexChange = await updateMarkedFile(
161
+ projectRoot,
162
+ join('llm-wiki', 'wiki', 'index.md'),
163
+ indexPage(),
164
+ INDEX_START,
165
+ INDEX_END,
166
+ buildGeneratedIndexBlock(pages),
167
+ options,
168
+ );
169
+ if (indexChange?.changed) changed.push(indexChange.changed);
170
+ if (indexChange?.skipped) skipped.push(indexChange.skipped);
171
+
172
+ const finalLintResult = options.dryRun || changed.length === 0
173
+ ? lintResult
174
+ : await runLint(projectRoot, { maxFiles: options.maxFiles || 1000 });
175
+
176
+ if (changed.length > 0 && !options.dryRun) {
177
+ await appendWikiLog(projectRoot, `consolidate: refreshed ${changed.map((item) => item.path).join(', ')}; lint errors=${finalLintResult.errorCount}; warnings=${finalLintResult.warningCount}`);
178
+ }
179
+
180
+ return {
181
+ workspace: projectRoot,
182
+ dryRun: Boolean(options.dryRun),
183
+ changed,
184
+ skipped,
185
+ lint: {
186
+ ok: finalLintResult.ok,
187
+ errorCount: finalLintResult.errorCount,
188
+ warningCount: finalLintResult.warningCount,
189
+ },
190
+ };
191
+ }
192
+
193
+ export function formatConsolidateResult(result) {
194
+ return [
195
+ 'llm-wiki consolidate',
196
+ `- workspace: ${result.workspace}`,
197
+ `- dry run: ${result.dryRun ? 'yes' : 'no'}`,
198
+ `- changed: ${result.changed.length ? result.changed.map((item) => item.path).join(', ') : 'none'}`,
199
+ `- skipped: ${result.skipped?.length ? result.skipped.map((item) => `${item.path} (${item.reason})`).join(', ') : 'none'}`,
200
+ `- lint errors: ${result.lint.errorCount}`,
201
+ `- lint warnings: ${result.lint.warningCount}`,
202
+ ].join('\n');
203
+ }
package/src/hook.js CHANGED
@@ -3,7 +3,8 @@ import { bootstrapProject, appendContextNote, appendLiveQa, appendSessionEnvelop
3
3
  import { applyProjectTemplateUpdate, inspectProjectState } from './project-state.js';
4
4
  import { recordProject } from './projects.js';
5
5
  import { summarizeForStorage } from './redaction.js';
6
- import { buildEntryFromState, rememberQuestion, rememberTool } from './state.js';
6
+ import { buildEntryFromState, clearTurnState, rememberQuestion, rememberTool } from './state.js';
7
+ import { relative } from 'path';
7
8
 
8
9
  async function readStdinJson() {
9
10
  const chunks = [];
@@ -45,7 +46,9 @@ async function autoUpdateManagedProject(projectRoot, eventName) {
45
46
  const state = await inspectProjectState(projectRoot);
46
47
  if (state.runtimeUpToDateWithProject && state.managedFilesCurrent) return;
47
48
  const result = await applyProjectTemplateUpdate(projectRoot);
48
- await appendWikiLog(projectRoot, `llm-wiki-kit auto-update applied runtime ${result.runtimeVersion}; changed templates: ${result.changed.length}; skipped templates: ${result.skipped.length}`);
49
+ if (result.changed.length > 0) {
50
+ await appendWikiLog(projectRoot, `llm-wiki-kit auto-update applied runtime ${result.runtimeVersion}; changed templates: ${result.changed.length}; skipped templates: ${result.skipped.length}`);
51
+ }
49
52
  }
50
53
 
51
54
  export async function handleHook(provider, explicitEvent) {
@@ -96,7 +99,10 @@ export async function handleHook(provider, explicitEvent) {
96
99
  await appendLiveQa(projectRoot, entry);
97
100
  const queryPath = await writeQueryPage(projectRoot, entry);
98
101
  const decisionPath = await writeDecisionPage(projectRoot, entry);
99
- await appendWikiLog(projectRoot, `captured ${eventName}${queryPath ? `; query=${queryPath}` : ''}${decisionPath ? `; decision=${decisionPath}` : ''}`);
102
+ await appendWikiLog(projectRoot, `captured ${eventName}${queryPath ? `; query=${relative(projectRoot, queryPath)}` : ''}${decisionPath ? `; decision=${relative(projectRoot, decisionPath)}` : ''}`);
103
+ }
104
+ if (eventName === 'Stop' || eventName === 'SessionEnd') {
105
+ await clearTurnState(projectRoot, payload).catch(() => {});
100
106
  }
101
107
  return {};
102
108
  }
package/src/install.js CHANGED
@@ -78,8 +78,10 @@ export async function install(options = {}) {
78
78
  await backupFile(localBinPath, 'local-bin-llm-wiki');
79
79
  }
80
80
  await safeSymlink(binPath, localBinPath);
81
- await bootstrapProject(workspace, { profile: options.profile || 'standard', recordState: true });
82
- await recordProject(workspace, 'install');
81
+ if (!options.noProject) {
82
+ await bootstrapProject(workspace, { profile: options.profile || 'standard', recordState: true });
83
+ await recordProject(workspace, 'install');
84
+ }
83
85
 
84
86
  const codexHooksPath = join(homeDir(), '.codex', 'hooks.json');
85
87
  const claudeSettingsPath = join(homeDir(), '.claude', 'settings.json');