llm-wiki-kit 0.1.5 → 0.1.6
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 +32 -2
- package/docs/concepts.md +12 -2
- package/docs/integrations/claude-code.md +2 -0
- package/docs/integrations/codex.md +2 -2
- package/docs/operations.md +92 -3
- package/docs/research/baseline.md +3 -1
- package/docs/security.md +2 -0
- package/docs/troubleshooting.md +30 -1
- package/package.json +4 -1
- package/src/cli.js +48 -3
- package/src/consolidate.js +203 -0
- package/src/install.js +4 -2
- package/src/project-state.js +76 -11
- package/src/project.js +11 -55
- package/src/templates.js +90 -7
- package/src/update.js +10 -4
- package/src/wiki-lint.js +271 -0
- package/src/wiki-model.js +263 -0
- package/src/wiki-search.js +243 -0
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
|
-
|
|
82
|
-
|
|
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');
|
package/src/project-state.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { dirname, join, relative } from 'path';
|
|
2
2
|
import { backupFile, ensureDir, exists, readJson, readText, sha256, writeJson, writeText } from './fs-utils.js';
|
|
3
3
|
import { runtimeVersion } from './version.js';
|
|
4
|
-
import { llmWikiAgents, procedure, rootAgentsPolicy } from './templates.js';
|
|
4
|
+
import { llmWikiAgents, memoryPage, procedure, rootAgentsPolicy } from './templates.js';
|
|
5
5
|
|
|
6
6
|
export const PROJECT_STATE_SCHEMA_VERSION = 1;
|
|
7
7
|
export const PROJECT_STATE_FILE = '.kit-state.json';
|
|
@@ -27,7 +27,12 @@ function templateDescriptors() {
|
|
|
27
27
|
path: join('llm-wiki', 'AGENTS.md'),
|
|
28
28
|
content: llmWikiAgents(),
|
|
29
29
|
mode: 'generated-file',
|
|
30
|
-
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'wiki-memory',
|
|
33
|
+
path: join('llm-wiki', 'wiki', 'memory.md'),
|
|
34
|
+
content: memoryPage(),
|
|
35
|
+
mode: 'create-only',
|
|
31
36
|
},
|
|
32
37
|
...PROCEDURE_NAMES.map((name) => ({
|
|
33
38
|
id: `procedure-${name.replace(/\.md$/, '')}`,
|
|
@@ -81,32 +86,45 @@ function replaceMarkedBlock(current, replacement) {
|
|
|
81
86
|
return `${current.slice(0, start)}${replacement.trimStart().trimEnd()}${current.slice(afterEnd)}`;
|
|
82
87
|
}
|
|
83
88
|
|
|
84
|
-
function
|
|
85
|
-
|
|
89
|
+
function markerStatus(current) {
|
|
90
|
+
const start = current.indexOf(ROOT_POLICY_START);
|
|
91
|
+
const end = current.indexOf(ROOT_POLICY_END);
|
|
92
|
+
if (start === -1 && end === -1) return 'missing';
|
|
93
|
+
if (start === -1 || end === -1 || end < start) return 'malformed';
|
|
94
|
+
return 'complete';
|
|
86
95
|
}
|
|
87
96
|
|
|
88
97
|
function isRecordedManaged(state, descriptor, currentText) {
|
|
89
98
|
const recorded = state.managedTemplates?.[descriptor.id];
|
|
90
99
|
if (!recorded) return false;
|
|
100
|
+
if (descriptor.mode === 'create-only') return true;
|
|
91
101
|
if (descriptor.mode === 'marker') return true;
|
|
92
|
-
return
|
|
102
|
+
return Boolean(recorded.hash) && recorded.hash === templateHash(currentText);
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
function isKnownGeneratedContent(text, descriptor) {
|
|
106
|
+
if (descriptor.mode === 'create-only') return false;
|
|
96
107
|
if (descriptor.mode === 'marker') return replaceMarkedBlock(text, descriptor.content) !== null;
|
|
97
|
-
return templateHash(text) === templateHash(descriptor.content)
|
|
108
|
+
return templateHash(text) === templateHash(descriptor.content);
|
|
98
109
|
}
|
|
99
110
|
|
|
100
|
-
function canPatchDescriptor(state, descriptor, currentText) {
|
|
111
|
+
function canPatchDescriptor(state, descriptor, currentText, fileExists = true) {
|
|
112
|
+
if (descriptor.mode === 'create-only') return !fileExists;
|
|
101
113
|
if (descriptor.mode === 'marker') return replaceMarkedBlock(currentText, descriptor.content) !== null;
|
|
102
114
|
return isRecordedManaged(state, descriptor, currentText) || isKnownGeneratedContent(currentText, descriptor);
|
|
103
115
|
}
|
|
104
116
|
|
|
105
|
-
function desiredTextForDescriptor(descriptor, currentText) {
|
|
117
|
+
function desiredTextForDescriptor(descriptor, currentText, fileExists = true) {
|
|
118
|
+
if (descriptor.mode === 'create-only') return fileExists ? currentText : descriptor.content;
|
|
106
119
|
if (descriptor.mode === 'marker') return replaceMarkedBlock(currentText, descriptor.content);
|
|
107
120
|
return descriptor.content;
|
|
108
121
|
}
|
|
109
122
|
|
|
123
|
+
async function createOnlyParentExists(projectRoot, descriptor) {
|
|
124
|
+
if (descriptor.mode !== 'create-only') return true;
|
|
125
|
+
return exists(dirname(join(projectRoot, descriptor.path)));
|
|
126
|
+
}
|
|
127
|
+
|
|
110
128
|
export async function inspectProjectState(projectRoot) {
|
|
111
129
|
const state = await readProjectState(projectRoot);
|
|
112
130
|
const runtime = runtimeVersion();
|
|
@@ -116,12 +134,18 @@ export async function inspectProjectState(projectRoot) {
|
|
|
116
134
|
const absolutePath = join(projectRoot, descriptor.path);
|
|
117
135
|
const fileExists = await exists(absolutePath);
|
|
118
136
|
const currentText = fileExists ? await readText(absolutePath) : '';
|
|
119
|
-
const desiredText =
|
|
137
|
+
const desiredText = desiredTextForDescriptor(descriptor, currentText, fileExists);
|
|
120
138
|
const desiredHash = templateHash(desiredText || descriptor.content);
|
|
121
139
|
const currentHash = fileExists ? templateHash(currentText) : null;
|
|
122
140
|
const recorded = state.managedTemplates[descriptor.id] || null;
|
|
123
|
-
const
|
|
124
|
-
const
|
|
141
|
+
const markerState = descriptor.mode === 'marker' && fileExists ? markerStatus(currentText) : null;
|
|
142
|
+
const parentExists = await createOnlyParentExists(projectRoot, descriptor);
|
|
143
|
+
const patchable = descriptor.mode === 'create-only' && !fileExists && !parentExists
|
|
144
|
+
? false
|
|
145
|
+
: (markerState === 'malformed' || canPatchDescriptor(state, descriptor, currentText, fileExists));
|
|
146
|
+
const current = descriptor.mode === 'create-only'
|
|
147
|
+
? fileExists
|
|
148
|
+
: fileExists && desiredText !== null && currentHash === templateHash(desiredText);
|
|
125
149
|
|
|
126
150
|
files.push({
|
|
127
151
|
id: descriptor.id,
|
|
@@ -133,6 +157,7 @@ export async function inspectProjectState(projectRoot) {
|
|
|
133
157
|
desiredHash,
|
|
134
158
|
recordedHash: recorded?.hash || null,
|
|
135
159
|
recordedVersion: recorded?.version || null,
|
|
160
|
+
reason: markerState === 'malformed' ? 'malformed-marker' : null,
|
|
136
161
|
});
|
|
137
162
|
}
|
|
138
163
|
|
|
@@ -155,6 +180,14 @@ export async function recordManagedTemplates(projectRoot) {
|
|
|
155
180
|
const absolutePath = join(projectRoot, descriptor.path);
|
|
156
181
|
if (!(await exists(absolutePath))) continue;
|
|
157
182
|
const currentText = await readText(absolutePath);
|
|
183
|
+
if (descriptor.mode === 'create-only') {
|
|
184
|
+
state.managedTemplates[descriptor.id] = {
|
|
185
|
+
path: descriptor.path,
|
|
186
|
+
version: runtime,
|
|
187
|
+
hash: templateHash(currentText),
|
|
188
|
+
};
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
158
191
|
if (!canPatchDescriptor(state, descriptor, currentText)) continue;
|
|
159
192
|
state.managedTemplates[descriptor.id] = {
|
|
160
193
|
path: descriptor.path,
|
|
@@ -177,11 +210,43 @@ export async function applyProjectTemplateUpdate(projectRoot, options = {}) {
|
|
|
177
210
|
for (const descriptor of templateDescriptors()) {
|
|
178
211
|
const absolutePath = join(projectRoot, descriptor.path);
|
|
179
212
|
if (!(await exists(absolutePath))) {
|
|
213
|
+
if (descriptor.mode === 'create-only') {
|
|
214
|
+
if (!(await createOnlyParentExists(projectRoot, descriptor))) {
|
|
215
|
+
skipped.push({ id: descriptor.id, path: descriptor.path, reason: 'missing-wiki' });
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (!options.dryRun) {
|
|
219
|
+
await ensureDir(dirname(absolutePath));
|
|
220
|
+
await writeText(absolutePath, descriptor.content, 'utf8');
|
|
221
|
+
}
|
|
222
|
+
changed.push({ id: descriptor.id, path: descriptor.path });
|
|
223
|
+
state.managedTemplates[descriptor.id] = {
|
|
224
|
+
path: descriptor.path,
|
|
225
|
+
version: runtime,
|
|
226
|
+
hash: templateHash(descriptor.content),
|
|
227
|
+
};
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
180
230
|
skipped.push({ id: descriptor.id, path: descriptor.path, reason: 'missing' });
|
|
181
231
|
continue;
|
|
182
232
|
}
|
|
183
233
|
|
|
184
234
|
const currentText = await readText(absolutePath);
|
|
235
|
+
if (descriptor.mode === 'marker' && markerStatus(currentText) === 'malformed') {
|
|
236
|
+
skipped.push({ id: descriptor.id, path: descriptor.path, reason: 'malformed-marker' });
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (descriptor.mode === 'create-only') {
|
|
241
|
+
unchanged.push({ id: descriptor.id, path: descriptor.path });
|
|
242
|
+
state.managedTemplates[descriptor.id] = {
|
|
243
|
+
path: descriptor.path,
|
|
244
|
+
version: runtime,
|
|
245
|
+
hash: templateHash(currentText),
|
|
246
|
+
};
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
185
250
|
if (!canPatchDescriptor(state, descriptor, currentText)) {
|
|
186
251
|
skipped.push({ id: descriptor.id, path: descriptor.path, reason: 'not-managed' });
|
|
187
252
|
continue;
|
package/src/project.js
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
appendText,
|
|
4
4
|
ensureDir,
|
|
5
5
|
exists,
|
|
6
|
-
listMarkdownFiles,
|
|
7
6
|
readText,
|
|
8
7
|
timeKst,
|
|
9
8
|
todayKst,
|
|
@@ -11,8 +10,9 @@ import {
|
|
|
11
10
|
} from './fs-utils.js';
|
|
12
11
|
import { LLM_WIKI_DIRS } from './constants.js';
|
|
13
12
|
import { normalizeForStorage, redactText, summarizeForStorage } from './redaction.js';
|
|
14
|
-
import { gitignore, indexPage, llmWikiAgents, logPage, procedure, rootAgentsPolicy } from './templates.js';
|
|
13
|
+
import { gitignore, indexPage, llmWikiAgents, logPage, memoryPage, procedure, rootAgentsPolicy } from './templates.js';
|
|
15
14
|
import { recordManagedTemplates } from './project-state.js';
|
|
15
|
+
import { buildContextPack, formatContextPack, searchWiki as searchWikiWithIndex } from './wiki-search.js';
|
|
16
16
|
|
|
17
17
|
export async function bootstrapProject(projectRoot, options = {}) {
|
|
18
18
|
if (process.env.LLM_WIKI_KIT_DISABLE_BOOTSTRAP === '1') return { created: false };
|
|
@@ -28,6 +28,7 @@ export async function bootstrapProject(projectRoot, options = {}) {
|
|
|
28
28
|
|
|
29
29
|
await maybeCreate(join(base, 'AGENTS.md'), llmWikiAgents());
|
|
30
30
|
await maybeCreate(join(base, 'wiki', 'index.md'), indexPage());
|
|
31
|
+
await maybeCreate(join(base, 'wiki', 'memory.md'), memoryPage());
|
|
31
32
|
await maybeCreate(join(base, 'wiki', 'log.md'), logPage());
|
|
32
33
|
await maybeCreate(join(base, '.gitignore'), gitignore());
|
|
33
34
|
for (const name of ['ingest.md', 'query.md', 'lint.md', 'security.md']) {
|
|
@@ -128,7 +129,7 @@ export async function writeQueryPage(projectRoot, entry) {
|
|
|
128
129
|
const slug = slugify(entry.question, 'query');
|
|
129
130
|
const path = join(projectRoot, 'llm-wiki', 'wiki', 'queries', `${day}-${slug}.md`);
|
|
130
131
|
if (await exists(path)) return path;
|
|
131
|
-
const content = `---\ntitle: "${entry.topic || slug}"\ntype: "query"\nsource_ids: []\nstatus: "draft"\nlast_updated: "${day}"\nconfidence: "medium"\n---\n\n# ${entry.topic || entry.question.slice(0, 80)}\n\n## Question\n${entry.question}\n\n## Answer Summary\n${entry.result || '(not captured)'}\n\n## Work Notes\n${entry.work || '(not captured)'}\n\n## Verification\n${entry.verification || '(not captured)'}\n\n## Related Pages\n- [[index]]\n\n## Change Log\n- ${day}: Captured automatically by llm-wiki-kit hook.\n`;
|
|
132
|
+
const content = `---\ntitle: "${entry.topic || slug}"\ntype: "query"\nsource_ids: []\nstatus: "draft"\nlast_updated: "${day}"\nconfidence: "medium"\nmemory_type: "episodic"\nimportance: 2\nlast_verified: "unknown"\nsupersedes: []\nsuperseded_by: []\n---\n\n# ${entry.topic || entry.question.slice(0, 80)}\n\n## Question\n${entry.question}\n\n## Answer Summary\n${entry.result || '(not captured)'}\n\n## Work Notes\n${entry.work || '(not captured)'}\n\n## Verification\n${entry.verification || '(not captured)'}\n\n## Related Pages\n- [[index]]\n\n## Change Log\n- ${day}: Captured automatically by llm-wiki-kit hook.\n`;
|
|
132
133
|
await writeTextIfMissing(path, redactText(content, 12000));
|
|
133
134
|
return path;
|
|
134
135
|
}
|
|
@@ -141,7 +142,7 @@ export async function writeDecisionPage(projectRoot, entry) {
|
|
|
141
142
|
const slug = slugify(entry.topic || entry.question || 'decision', 'decision');
|
|
142
143
|
const path = join(projectRoot, 'llm-wiki', 'wiki', 'decisions', `${day}-${slug}.md`);
|
|
143
144
|
if (await exists(path)) return path;
|
|
144
|
-
const content = `---\ntitle: "${entry.topic || slug}"\ntype: "decision"\nsource_ids: []\nstatus: "draft"\nlast_updated: "${day}"\nconfidence: "medium"\n---\n\n# ${entry.topic || 'Decision'}\n\n## Decision\n${entry.result || '(captured from assistant response; review needed)'}\n\n## Context\n${entry.question || '(not captured)'}\n\n## Evidence\n${entry.work || '(not captured)'}\n\n## Verification\n${entry.verification || '(not captured)'}\n\n## Open Questions\n${entry.followUp || '(none captured)'}\n\n## Change Log\n- ${day}: Captured automatically by llm-wiki-kit hook.\n`;
|
|
145
|
+
const content = `---\ntitle: "${entry.topic || slug}"\ntype: "decision"\nsource_ids: []\nstatus: "draft"\nlast_updated: "${day}"\nconfidence: "medium"\nmemory_type: "semantic"\nimportance: 4\nlast_verified: "unknown"\nsupersedes: []\nsuperseded_by: []\n---\n\n# ${entry.topic || 'Decision'}\n\n## Decision\n${entry.result || '(captured from assistant response; review needed)'}\n\n## Context\n${entry.question || '(not captured)'}\n\n## Evidence\n${entry.work || '(not captured)'}\n\n## Verification\n${entry.verification || '(not captured)'}\n\n## Open Questions\n${entry.followUp || '(none captured)'}\n\n## Change Log\n- ${day}: Captured automatically by llm-wiki-kit hook.\n`;
|
|
145
146
|
await writeTextIfMissing(path, redactText(content, 12000));
|
|
146
147
|
return path;
|
|
147
148
|
}
|
|
@@ -153,59 +154,14 @@ export async function appendContextNote(projectRoot, eventName, text) {
|
|
|
153
154
|
await appendText(path, block);
|
|
154
155
|
}
|
|
155
156
|
|
|
156
|
-
function tokenize(text) {
|
|
157
|
-
return String(text || '')
|
|
158
|
-
.toLowerCase()
|
|
159
|
-
.replace(/[^\p{Letter}\p{Number}]+/gu, ' ')
|
|
160
|
-
.split(/\s+/)
|
|
161
|
-
.filter((token) => token.length >= 2)
|
|
162
|
-
.slice(0, 80);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
157
|
export async function searchWiki(projectRoot, query, limit = 5) {
|
|
166
|
-
|
|
167
|
-
const files = await listMarkdownFiles(wikiRoot, 400);
|
|
168
|
-
const terms = tokenize(query);
|
|
169
|
-
if (terms.length === 0) return [];
|
|
170
|
-
const scored = [];
|
|
171
|
-
for (const file of files) {
|
|
172
|
-
const raw = await readText(file);
|
|
173
|
-
const content = raw.slice(0, 50000);
|
|
174
|
-
const lower = content.toLowerCase();
|
|
175
|
-
let score = 0;
|
|
176
|
-
for (const term of terms) {
|
|
177
|
-
if (lower.includes(term)) score += term.length > 3 ? 2 : 1;
|
|
178
|
-
}
|
|
179
|
-
if (score > 0) {
|
|
180
|
-
const rel = relative(join(projectRoot, 'llm-wiki'), file);
|
|
181
|
-
scored.push({
|
|
182
|
-
score,
|
|
183
|
-
path: rel,
|
|
184
|
-
snippet: content.replace(/\s+/g, ' ').slice(0, 350),
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
158
|
+
return searchWikiWithIndex(projectRoot, query, typeof limit === 'number' ? { limit } : limit);
|
|
189
159
|
}
|
|
190
160
|
|
|
191
161
|
export async function buildContextBrief(projectRoot, eventName, query = '') {
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
'- Treat chat memory as temporary; update project Markdown when knowledge should persist.',
|
|
198
|
-
'- Preserve raw/wiki separation. Do not store secrets, tokens, .env contents, private keys, or personal/customer identifiers.',
|
|
199
|
-
'- Prefer updating existing wiki pages over creating duplicate pages.',
|
|
200
|
-
'',
|
|
201
|
-
'Index excerpt:',
|
|
202
|
-
index.slice(0, 1200).trim(),
|
|
203
|
-
];
|
|
204
|
-
if (hits.length > 0) {
|
|
205
|
-
parts.push('', 'Relevant wiki pages:', ...hits.map((hit) => `- ${hit.path}: ${hit.snippet}`));
|
|
206
|
-
}
|
|
207
|
-
if (eventName === 'SessionStart') {
|
|
208
|
-
parts.push('', 'Recent log excerpt:', log.slice(-1000).trim());
|
|
209
|
-
}
|
|
210
|
-
return parts.join('\n').trim();
|
|
162
|
+
const pack = await buildContextPack(projectRoot, query, {
|
|
163
|
+
includeLog: eventName === 'SessionStart',
|
|
164
|
+
limit: 5,
|
|
165
|
+
});
|
|
166
|
+
return formatContextPack(pack);
|
|
211
167
|
}
|
package/src/templates.js
CHANGED
|
@@ -1,15 +1,98 @@
|
|
|
1
1
|
import { runtimeVersion } from './version.js';
|
|
2
2
|
|
|
3
3
|
export function rootAgentsPolicy() {
|
|
4
|
-
return `\n<!-- llm-wiki-kit:start -->\n## LLM Wiki Policy\n\nThis repository uses llm-wiki-kit as a hook-first living Markdown wiki for Codex and Claude Code.\n\n- This block supersedes older OMX/OMC/\`omx_wiki/\` LLM Wiki instructions for this repository.\n- Treat chat memory as temporary; durable project knowledge belongs in \`llm-wiki/\`.\n- \`llm-wiki/raw/\` stores immutable or redacted source material. Do not edit raw source files.\n- \`llm-wiki/wiki/\` stores LLM-maintained knowledge pages such as decisions, architecture, debugging, context, concepts, and queries.\n- Before non-trivial work, use the injected LLM Wiki context and consult \`llm-wiki/wiki/index.md\` when present.\n- Capture reusable decisions, debugging findings, verification commands, and open questions into the wiki.\n- Preserve useful work context, but do not store raw authentication values such as tokens, passwords, or private keys.\n- Mark inference explicitly and preserve contradictions instead of silently overwriting them.\n\n<!-- llm-wiki-kit:end -->\n`;
|
|
4
|
+
return `\n<!-- llm-wiki-kit:start -->\n## LLM Wiki Policy\n\nThis repository uses llm-wiki-kit as a hook-first living Markdown wiki for Codex and Claude Code.\n\n- This block supersedes older OMX/OMC/\`omx_wiki/\` LLM Wiki instructions for this repository.\n- Treat chat memory as temporary; durable project knowledge belongs in \`llm-wiki/\`.\n- \`llm-wiki/raw/\` stores immutable or redacted source material. Do not edit raw source files.\n- \`llm-wiki/wiki/\` stores LLM-maintained knowledge pages such as decisions, architecture, debugging, context, concepts, and queries.\n- \`llm-wiki/wiki/memory.md\` is the short hot index for the most reusable current facts; keep it concise and link to deeper pages.\n- Before non-trivial work, use the injected LLM Wiki context and consult \`llm-wiki/wiki/memory.md\` and \`llm-wiki/wiki/index.md\` when present.\n- Capture reusable decisions, debugging findings, verification commands, and open questions into the wiki.\n- Preserve useful work context, but do not store raw authentication values such as tokens, passwords, or private keys.\n- Mark inference explicitly and preserve contradictions instead of silently overwriting them.\n\n<!-- llm-wiki-kit:end -->\n`;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
7
|
export function llmWikiAgents() {
|
|
8
|
-
return `# LLM Wiki Agent Rules
|
|
8
|
+
return `# LLM Wiki Agent Rules
|
|
9
|
+
|
|
10
|
+
Generated by llm-wiki-kit ${runtimeVersion()}.
|
|
11
|
+
|
|
12
|
+
## Purpose
|
|
13
|
+
Maintain a living Markdown LLM Wiki from immutable source files and redacted Codex/Claude Code session events.
|
|
14
|
+
These rules supersede older OMX/OMC/\`omx_wiki/\` LLM Wiki rules for this project.
|
|
15
|
+
|
|
16
|
+
## Directories
|
|
17
|
+
- \`raw/\`: immutable or redacted source material. Never edit original source captures.
|
|
18
|
+
- \`wiki/\`: AI-maintained knowledge pages. \`wiki/memory.md\` is the short hot index injected into hook context.
|
|
19
|
+
- \`outputs/\`: live Q&A summaries, reports, and generated briefs.
|
|
20
|
+
- \`procedures/\`: detailed operating rules for ingest, query, lint, and security.
|
|
21
|
+
|
|
22
|
+
## Core Rules
|
|
23
|
+
- Never modify \`raw/\` source material except to append redacted session envelopes generated by hooks.
|
|
24
|
+
- Do not state unsupported claims as facts.
|
|
25
|
+
- Mark inference explicitly.
|
|
26
|
+
- Add \`source_ids\` or file references for important claims.
|
|
27
|
+
- Update \`wiki/memory.md\`, \`wiki/index.md\`, and \`wiki/log.md\` whenever new durable knowledge changes the active project map.
|
|
28
|
+
- Preserve contradictions in \`Contradictions\` or \`Open Questions\` instead of overwriting them.
|
|
29
|
+
- Preserve useful work context and redact authentication values before writing durable notes.
|
|
30
|
+
|
|
31
|
+
## Page Format
|
|
32
|
+
Use YAML frontmatter when creating wiki pages:
|
|
33
|
+
|
|
34
|
+
\`\`\`yaml
|
|
35
|
+
---
|
|
36
|
+
title: ""
|
|
37
|
+
type: "source | concept | entity | decision | architecture | debugging | context | query | session-log | convention"
|
|
38
|
+
source_ids: []
|
|
39
|
+
status: "draft | reviewed | stale"
|
|
40
|
+
last_updated: "YYYY-MM-DD"
|
|
41
|
+
confidence: "high | medium | low"
|
|
42
|
+
memory_type: "semantic | episodic | procedural"
|
|
43
|
+
importance: 1
|
|
44
|
+
last_verified: "YYYY-MM-DD | unknown"
|
|
45
|
+
supersedes: []
|
|
46
|
+
superseded_by: []
|
|
47
|
+
---
|
|
48
|
+
\`\`\`
|
|
49
|
+
|
|
50
|
+
## Operations
|
|
51
|
+
- ingest: read \`wiki/memory.md\` and \`wiki/index.md\`, ingest new raw files, create or update wiki pages, then update the active map and \`wiki/log.md\`.
|
|
52
|
+
- query: start from \`llm-wiki context "<query>"\` or \`wiki/memory.md\`, read relevant wiki pages, answer with source references, and save reusable answers.
|
|
53
|
+
- lint: run \`llm-wiki lint --workspace <project>\` to find stale pages, orphan pages, broken wiki/Markdown links, unsafe source IDs, secret-like content, missing sources, duplicate concepts, contradictions, and missing links.
|
|
54
|
+
- consolidate: run \`llm-wiki consolidate --workspace <project>\` after meaningful wiki growth to refresh generated memory/index blocks without overwriting hand-written notes. Default query/context/session-log pages are excluded unless explicitly durable.
|
|
55
|
+
`;
|
|
9
56
|
}
|
|
10
57
|
|
|
11
58
|
export function indexPage() {
|
|
12
|
-
return `# LLM Wiki Index\n\nGenerated by llm-wiki-kit.\n\n## Overview\n\nThis is the navigation map for the project living wiki. Keep it short and useful.\n\n## Main Areas\n\n- [Sources](sources/) - source summaries and source IDs.\n- [Concepts](concepts/) - reusable concepts and terminology.\n- [Entities](entities/) - people, systems, modules, vendors, services, and tools.\n- [Decisions](decisions/) - decisions and rationale.\n- [Architecture](architecture/) - system structure and data/control flow.\n- [Debugging](debugging/) - root causes, fixes, verification evidence.\n- [Context](context/) - session continuity and project memory.\n- [Queries](queries/) - durable answers to useful questions.\n\n## Operating Notes\n\n- Start from this index before broad project questions.\n- Read 3-7 relevant pages first; inspect raw sources only when precision matters.\n- Add links using \`[[page-or-topic]]\` when pages become related.\n`;
|
|
59
|
+
return `# LLM Wiki Index\n\nGenerated by llm-wiki-kit.\n\n## Overview\n\nThis is the navigation map for the project living wiki. Keep it short and useful.\n\n## Main Areas\n\n- [Memory](memory.md) - short hot index and durable entry points.\n- [Sources](sources/) - source summaries and source IDs.\n- [Concepts](concepts/) - reusable concepts and terminology.\n- [Entities](entities/) - people, systems, modules, vendors, services, and tools.\n- [Decisions](decisions/) - decisions and rationale.\n- [Architecture](architecture/) - system structure and data/control flow.\n- [Debugging](debugging/) - root causes, fixes, verification evidence.\n- [Context](context/) - session continuity and project memory.\n- [Queries](queries/) - durable answers to useful questions.\n\n## Operating Notes\n\n- Start from memory and this index before broad project questions.\n- Read 3-7 relevant pages first; inspect raw sources only when precision matters.\n- Add links using \`[[page-or-topic]]\` when pages become related.\n\n<!-- llm-wiki-kit:index-start -->\n## Generated Page Map\n\nRun \`llm-wiki consolidate --workspace <project>\` to refresh this generated block.\n<!-- llm-wiki-kit:index-end -->\n`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function memoryPage() {
|
|
63
|
+
return `---
|
|
64
|
+
title: "LLM Wiki Memory"
|
|
65
|
+
type: "context"
|
|
66
|
+
source_ids: []
|
|
67
|
+
status: "draft"
|
|
68
|
+
last_updated: "unknown"
|
|
69
|
+
confidence: "medium"
|
|
70
|
+
memory_type: "semantic"
|
|
71
|
+
importance: 5
|
|
72
|
+
last_verified: "unknown"
|
|
73
|
+
supersedes: []
|
|
74
|
+
superseded_by: []
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
# LLM Wiki Memory
|
|
78
|
+
|
|
79
|
+
Short hot index for project memory. Keep this page compact enough for hook context. Link to deeper pages instead of copying long notes.
|
|
80
|
+
|
|
81
|
+
## Current Focus
|
|
82
|
+
|
|
83
|
+
- Add the current project focus here when it becomes durable.
|
|
84
|
+
|
|
85
|
+
## Durable Entry Points
|
|
86
|
+
|
|
87
|
+
- [Index](index.md)
|
|
88
|
+
- [Log](log.md)
|
|
89
|
+
|
|
90
|
+
<!-- llm-wiki-kit:memory-start -->
|
|
91
|
+
## Generated Memory Map
|
|
92
|
+
|
|
93
|
+
Run \`llm-wiki consolidate --workspace <project>\` to refresh this generated block.
|
|
94
|
+
<!-- llm-wiki-kit:memory-end -->
|
|
95
|
+
`;
|
|
13
96
|
}
|
|
14
97
|
|
|
15
98
|
export function logPage() {
|
|
@@ -18,10 +101,10 @@ export function logPage() {
|
|
|
18
101
|
|
|
19
102
|
export function procedure(name) {
|
|
20
103
|
const procedures = {
|
|
21
|
-
'ingest.md': `# Ingest Procedure\n\n1. Read \`wiki/index.md\` first.\n2. Inspect new material under \`raw/inbox/\` or \`raw/sources/\`.\n3. Create or update \`wiki/sources/<slug>.md\` for each source.\n4. Update related concept, entity, decision, architecture, debugging, or context pages.\n5. Prefer updating existing pages over creating duplicates.\n6. Add source references and
|
|
22
|
-
'query.md': `# Query Procedure\n\n1. Start from \`wiki/index.md\`.\n2. Search \`wiki/\` for relevant pages.\
|
|
23
|
-
'lint.md': `# Lint Procedure\n\
|
|
24
|
-
'security.md': `# Security Procedure\n\n- Preserve useful work context for the local project wiki.\n- Do not block reads or tool calls only because they look sensitive.\n- Redact authentication values such as tokens, passwords, and private keys before writing hook payloads or
|
|
104
|
+
'ingest.md': `# Ingest Procedure\n\n1. Read \`wiki/memory.md\` and \`wiki/index.md\` first.\n2. Inspect new material under \`raw/inbox/\` or \`raw/sources/\`.\n3. Create or update \`wiki/sources/<slug>.md\` for each source.\n4. Update related concept, entity, decision, architecture, debugging, or context pages.\n5. Prefer updating existing pages over creating duplicates.\n6. Add source references, confidence, memory type, importance, and verification status.\n7. Update \`wiki/memory.md\` or run \`llm-wiki consolidate\` when durable entry points change.\n8. Update \`wiki/index.md\` and append to \`wiki/log.md\`.\n`,
|
|
105
|
+
'query.md': `# Query Procedure\n\n1. Start from \`llm-wiki context \"<query>\"\` or read \`wiki/memory.md\` and \`wiki/index.md\`.\n2. Use \`--limit\` or \`--no-expand\` when you need a narrower context pack.\n3. Search \`wiki/\` for relevant pages.\n4. Read the smallest useful set of pages first.\n5. Use raw sources only when exact evidence matters.\n6. Separate verified facts from inference.\n7. Save reusable answers into \`wiki/queries/\` and link them from related pages.\n`,
|
|
106
|
+
'lint.md': `# Lint Procedure\n\nRun \`llm-wiki lint --workspace <project>\` to check for stale pages, orphan pages, broken wiki/Markdown links, unsafe source IDs, secret-like content, missing sources, duplicate concepts, unsupported claims, and unresolved contradictions. Prefer producing a review report before automatic edits.\n`,
|
|
107
|
+
'security.md': `# Security Procedure\n\n- Preserve useful work context for the local project wiki.\n- Do not block reads or tool calls only because they look sensitive.\n- Redact authentication values such as tokens, passwords, bearer credentials, and private keys before writing hook payloads, summaries, or context packs.\n- Run \`llm-wiki lint --workspace <project>\` when sensitive material might have entered wiki pages; secret-like content is reported as an error.\n- Full raw transcript capture is disabled by default and must be explicitly enabled by project policy.\n`,
|
|
25
108
|
};
|
|
26
109
|
return procedures[name] || '';
|
|
27
110
|
}
|
package/src/update.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawnSync } from 'child_process';
|
|
2
|
-
import { resolve } from 'path';
|
|
3
|
-
import {
|
|
2
|
+
import { join, resolve } from 'path';
|
|
3
|
+
import { exists } from './fs-utils.js';
|
|
4
|
+
import { appendWikiLog } from './project.js';
|
|
4
5
|
import { install } from './install.js';
|
|
5
6
|
import { applyProjectTemplateUpdate, inspectProjectState } from './project-state.js';
|
|
6
7
|
import { knownProjectRoots } from './projects.js';
|
|
@@ -34,6 +35,10 @@ function assertCommandOk(result, label) {
|
|
|
34
35
|
throw new Error(`${label} failed: ${detail}`);
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
async function hasProjectWiki(projectRoot) {
|
|
39
|
+
return exists(join(projectRoot, 'llm-wiki', 'wiki'));
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
export function parseRegistryVersion(output) {
|
|
38
43
|
return String(output || '').trim().split(/\s+/).at(-1) || '';
|
|
39
44
|
}
|
|
@@ -60,6 +65,7 @@ export async function postUpdate(options = {}) {
|
|
|
60
65
|
...options,
|
|
61
66
|
workspace,
|
|
62
67
|
replaceHooks: true,
|
|
68
|
+
noProject: true,
|
|
63
69
|
});
|
|
64
70
|
|
|
65
71
|
if (options.all) {
|
|
@@ -69,7 +75,7 @@ export async function postUpdate(options = {}) {
|
|
|
69
75
|
const projectResult = options.noProject
|
|
70
76
|
? null
|
|
71
77
|
: await applyProjectTemplateUpdate(projectRoot);
|
|
72
|
-
if (!options.noProject) {
|
|
78
|
+
if (!options.noProject && await hasProjectWiki(projectRoot)) {
|
|
73
79
|
await appendWikiLog(projectRoot, `llm-wiki-kit post-update applied runtime ${runtimeVersion()}; changed templates: ${projectResult.changed.length}`);
|
|
74
80
|
}
|
|
75
81
|
projects.push({
|
|
@@ -89,7 +95,7 @@ export async function postUpdate(options = {}) {
|
|
|
89
95
|
? null
|
|
90
96
|
: await applyProjectTemplateUpdate(workspace);
|
|
91
97
|
|
|
92
|
-
if (!options.noProject) {
|
|
98
|
+
if (!options.noProject && await hasProjectWiki(workspace)) {
|
|
93
99
|
await appendWikiLog(workspace, `llm-wiki-kit post-update applied runtime ${runtimeVersion()}; changed templates: ${projectResult.changed.length}`);
|
|
94
100
|
}
|
|
95
101
|
|