llm-wiki-kit 0.1.0 → 0.1.1

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 CHANGED
@@ -60,7 +60,7 @@ llm-wiki/
60
60
  └── procedures/
61
61
  ```
62
62
 
63
- `raw/` is the immutable or redacted evidence layer. `wiki/` is the LLM-maintained knowledge layer. `outputs/` stores live Q&A and requested reports. `.kit-state.json` records which runtime version last applied managed templates to the project.
63
+ `raw/` is the immutable or redacted evidence layer. `wiki/` is the LLM-maintained knowledge layer. `outputs/` stores live Q&A and requested reports. `.kit-state.json` records which runtime version last applied managed templates to the project. Text written by the kit is normalized to NFC so Korean filenames and content stay composed.
64
64
 
65
65
  ## Normal Use
66
66
 
@@ -71,6 +71,7 @@ The installed hooks:
71
71
  - inject relevant wiki context at session start and prompt submit time
72
72
  - record redacted turn summaries
73
73
  - capture decision points, debugging findings, changed files, and verification notes
74
+ - allow tool calls to proceed without secret/PII-based hook blocking
74
75
  - update `llm-wiki/outputs/questions/YYYY-MM-DD-live-qa.md`
75
76
  - promote useful answers into `llm-wiki/wiki/queries/`
76
77
  - promote decision-like turns into `llm-wiki/wiki/decisions/`
@@ -107,9 +108,10 @@ llm-wiki hook claude Stop
107
108
  ## Security Defaults
108
109
 
109
110
  - Full raw transcript capture is disabled by default.
110
- - `.env`, tokens, private keys, credentials, `raw_private/`, and `secrets/` are blocked or redacted.
111
+ - Tool calls are not blocked only because inputs look sensitive.
112
+ - Authentication values such as tokens, passwords, and private keys are redacted before durable summaries are written.
111
113
  - Hook payloads are stored only as redacted event envelopes.
112
- - Store customer or personal data only after explicit sanitization.
114
+ - Phone numbers, emails, dates, and business identifiers are preserved by default so the wiki remains useful for local work.
113
115
 
114
116
  ## Development Notes
115
117
 
@@ -38,6 +38,8 @@ Claude Code reads `CLAUDE.md`. For project compatibility, the kit creates a `CLA
38
38
 
39
39
  when no project `CLAUDE.md` exists. Existing `CLAUDE.md` files are not overwritten.
40
40
 
41
+ The hook records redacted turn summaries but does not deny tool calls only because an input looks sensitive.
42
+
41
43
  After installation or update, run:
42
44
 
43
45
  ```bash
@@ -31,7 +31,7 @@ Expected behavior:
31
31
 
32
32
  - `SessionStart` injects `llm-wiki/wiki/index.md`, recent log context, and operating rules.
33
33
  - `UserPromptSubmit` searches project wiki pages and injects the smallest useful context set.
34
- - `PreToolUse` blocks secret-looking paths or payloads.
34
+ - `PreToolUse` records redacted tool summaries without blocking tool calls.
35
35
  - `PostToolUse` records redacted tool summaries in a turn buffer.
36
36
  - `Stop` writes live Q&A and durable wiki pages.
37
37
 
@@ -6,14 +6,14 @@
6
6
 
7
7
  - inject context
8
8
  - record redacted summaries
9
- - block secret-looking tool access
9
+ - allow tool calls while redacting authentication values before storage
10
10
  - write live Q&A and wiki pages
11
11
 
12
12
  `strict` is reserved for future stronger enforcement:
13
13
 
14
14
  - fail closed when hooks are disabled
15
15
  - require successful stop/session-end recording
16
- - block raw/wiki boundary violations more aggressively
16
+ - enforce project-specific storage policies more aggressively
17
17
 
18
18
  ## Install
19
19
 
package/docs/security.md CHANGED
@@ -1,27 +1,14 @@
1
1
  # Security
2
2
 
3
- The default policy is conservative.
3
+ The default policy favors a useful local work wiki over aggressive blocking.
4
4
 
5
- Do not store:
5
+ The runtime does not deny tool calls only because an input looks sensitive. It preserves operational context by default, including phone numbers, emails, dates, and business identifiers.
6
6
 
7
- - credentials
8
- - tokens
9
- - private keys
10
- - `.env` contents
11
- - customer identifiers
12
- - personal data
13
- - contracts, invoices, or financial details
14
- - private raw transcripts unless the project explicitly opts in
7
+ Before writing durable summaries, the runtime redacts authentication values such as:
15
8
 
16
- The runtime redacts common secret patterns and blocks secret-looking tool paths such as:
17
-
18
- - `.env`
19
- - `*.pem`
20
- - `*.key`
21
- - `id_rsa`
22
- - `secrets/`
23
- - `credentials/`
24
- - `raw_private/`
25
-
26
- Full transcript capture is intentionally not implemented as a default. If a project needs it, add a project-local policy and a redaction path first.
9
+ - token-like values
10
+ - password-like assignments
11
+ - private key blocks
12
+ - bearer credentials
27
13
 
14
+ Hook payloads are stored as small event envelopes, not full raw transcripts. Full transcript capture is intentionally not implemented as a default. If a project needs it, add a project-local policy and a redaction path first.
@@ -39,6 +39,38 @@ llm-wiki install --workspace /path/to/project --profile standard
39
39
 
40
40
  Real registry-based update checks will work only after `npm view llm-wiki-kit version` succeeds.
41
41
 
42
+ ## npm install -g Fails With ECONNRESET
43
+
44
+ `ECONNRESET` means npm reached the network path but the connection was reset while reading from the registry. This is usually a proxy, firewall, TLS inspection, DNS, or WSL/network issue on the installing server. It is different from `E404`, which means the package or registry entry was not found.
45
+
46
+ First confirm the registry and package from the same shell:
47
+
48
+ ```bash
49
+ npm view llm-wiki-kit version --registry=https://registry.npmjs.org/
50
+ curl -Iv https://registry.npmjs.org/llm-wiki-kit
51
+ ```
52
+
53
+ When installing with `sudo`, check root's npm config too because it may not match the current user's config:
54
+
55
+ ```bash
56
+ npm config get registry
57
+ sudo npm config get registry
58
+ sudo npm config get proxy
59
+ sudo npm config get https-proxy
60
+ sudo npm ping --registry=https://registry.npmjs.org/
61
+ ```
62
+
63
+ If the server is behind an HTTP proxy, configure it for root npm:
64
+
65
+ ```bash
66
+ sudo npm config set proxy http://proxy-host:proxy-port
67
+ sudo npm config set https-proxy http://proxy-host:proxy-port
68
+ sudo npm config set registry https://registry.npmjs.org/
69
+ sudo npm install -g llm-wiki-kit@latest
70
+ ```
71
+
72
+ If the proxy performs TLS inspection, install the organization's CA and set `cafile` instead of disabling TLS verification. As a temporary diagnostic only, `sudo npm config set strict-ssl false` can confirm that the failure is CA-related, but do not keep that setting in production.
73
+
42
74
  ## npm install -g Fails With EACCES
43
75
 
44
76
  If npm tries to write under `/usr` and fails with `EACCES`, use sudo when the server policy allows system-wide global packages:
@@ -86,9 +118,9 @@ Check:
86
118
  - project `CLAUDE.md` exists or imports `@AGENTS.md`
87
119
  - the session was restarted after install
88
120
 
89
- ## A Secret Was Detected
121
+ ## Authentication Values Were Redacted
90
122
 
91
- The hook blocks or redacts secret-like content. Move sensitive material out of the project wiki path and use sanitized source notes.
123
+ The hook does not block tool calls only because inputs look sensitive. Durable summaries redact authentication values before writing, while ordinary work context such as dates, phone numbers, emails, and business identifiers is preserved by default.
92
124
 
93
125
  ## Duplicate Pages Appear
94
126
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-wiki-kit",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Hook-first living LLM Wiki runtime for Codex and Claude Code.",
5
5
  "type": "module",
6
6
  "files": [
package/src/cli.js CHANGED
@@ -27,6 +27,8 @@ function parseOptions(args) {
27
27
  options.check = true;
28
28
  } else if (arg === '--dry-run') {
29
29
  options.dryRun = true;
30
+ } else if (arg === '--replace-hooks') {
31
+ options.replaceHooks = true;
30
32
  } else if (arg === '--json') {
31
33
  options.json = true;
32
34
  } else {
package/src/fs-utils.js CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  } from 'fs/promises';
15
15
  import { dirname, join, parse, resolve } from 'path';
16
16
  import { PROJECT_MARKERS } from './constants.js';
17
+ import { normalizeForStorage } from './redaction.js';
17
18
 
18
19
  export function homeDir() {
19
20
  return process.env.HOME || process.env.USERPROFILE || process.cwd();
@@ -63,18 +64,18 @@ export async function readText(path, fallback = '') {
63
64
  export async function writeTextIfMissing(path, content) {
64
65
  if (await exists(path)) return false;
65
66
  await ensureDir(dirname(path));
66
- await writeFile(path, content, 'utf8');
67
+ await writeFile(path, normalizeForStorage(content), 'utf8');
67
68
  return true;
68
69
  }
69
70
 
70
71
  export async function writeText(path, content) {
71
72
  await ensureDir(dirname(path));
72
- await writeFile(path, content, 'utf8');
73
+ await writeFile(path, normalizeForStorage(content), 'utf8');
73
74
  }
74
75
 
75
76
  export async function appendText(path, content) {
76
77
  await ensureDir(dirname(path));
77
- await appendFile(path, content, 'utf8');
78
+ await appendFile(path, normalizeForStorage(content), 'utf8');
78
79
  }
79
80
 
80
81
  export async function readJson(path, fallback = null) {
@@ -87,7 +88,7 @@ export async function readJson(path, fallback = null) {
87
88
 
88
89
  export async function writeJson(path, value) {
89
90
  await ensureDir(dirname(path));
90
- await writeFile(path, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
91
+ await writeFile(path, normalizeForStorage(`${JSON.stringify(value, null, 2)}\n`), 'utf8');
91
92
  }
92
93
 
93
94
  export async function backupFile(path, label) {
package/src/hook.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { findProjectRoot } from './fs-utils.js';
2
2
  import { bootstrapProject, appendContextNote, appendLiveQa, appendSessionEnvelope, appendWikiLog, buildContextBrief, writeDecisionPage, writeQueryPage } from './project.js';
3
- import { extractPathsFromText, hasSecretLikeText, isSensitivePath, summarizeForStorage } from './redaction.js';
3
+ import { summarizeForStorage } from './redaction.js';
4
4
  import { buildEntryFromState, rememberQuestion, rememberTool } from './state.js';
5
5
 
6
6
  async function readStdinJson() {
@@ -27,11 +27,6 @@ function toolSummary(payload) {
27
27
  return `${toolName}: ${summarizeForStorage(input, 1200)}`;
28
28
  }
29
29
 
30
- function toolInputText(payload) {
31
- const input = payload.tool_input || payload.toolInput || payload.input || payload.arguments || payload.tool?.input || {};
32
- return typeof input === 'string' ? input : JSON.stringify(input);
33
- }
34
-
35
30
  function contextOutput(eventName, context) {
36
31
  if (!context) return {};
37
32
  return {
@@ -42,37 +37,6 @@ function contextOutput(eventName, context) {
42
37
  };
43
38
  }
44
39
 
45
- function blockOutput(provider, reason) {
46
- if (provider === 'claude') {
47
- return {
48
- permissionDecision: 'deny',
49
- permissionDecisionReason: reason,
50
- decision: 'block',
51
- reason,
52
- };
53
- }
54
- return {
55
- decision: 'block',
56
- reason,
57
- hookSpecificOutput: {
58
- hookEventName: 'PreToolUse',
59
- additionalContext: reason,
60
- },
61
- };
62
- }
63
-
64
- function shouldBlockTool(payload) {
65
- const input = toolInputText(payload);
66
- const paths = extractPathsFromText(input);
67
- if (paths.some(isSensitivePath)) {
68
- return 'llm-wiki-kit blocked access to a secret-looking path. Do not read or store .env, keys, token files, credentials, raw_private, or secrets.';
69
- }
70
- if (hasSecretLikeText(input)) {
71
- return 'llm-wiki-kit blocked a tool call containing secret-like text. Redact secrets before continuing.';
72
- }
73
- return null;
74
- }
75
-
76
40
  export async function handleHook(provider, explicitEvent) {
77
41
  const payload = await readStdinJson();
78
42
  payload.__provider = provider;
@@ -91,15 +55,10 @@ export async function handleHook(provider, explicitEvent) {
91
55
  const prompt = promptText(payload);
92
56
  await rememberQuestion(projectRoot, payload, prompt);
93
57
  const context = await buildContextBrief(projectRoot, eventName, prompt);
94
- const warning = hasSecretLikeText(prompt)
95
- ? '\n\nSecurity note: the prompt appears to contain secret-like text. Do not persist raw secret values; store only redacted summaries.'
96
- : '';
97
- return contextOutput(eventName, `${context}${warning}`);
58
+ return contextOutput(eventName, context);
98
59
  }
99
60
 
100
61
  if (eventName === 'PreToolUse') {
101
- const reason = shouldBlockTool(payload);
102
- if (reason) return blockOutput(provider, reason);
103
62
  await rememberTool(projectRoot, payload, `pre ${toolSummary(payload)}`);
104
63
  return {};
105
64
  }
package/src/project.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  writeTextIfMissing,
11
11
  } from './fs-utils.js';
12
12
  import { LLM_WIKI_DIRS } from './constants.js';
13
- import { redactText, summarizeForStorage } from './redaction.js';
13
+ import { normalizeForStorage, redactText, summarizeForStorage } from './redaction.js';
14
14
  import { gitignore, indexPage, llmWikiAgents, logPage, procedure, rootAgentsPolicy } from './templates.js';
15
15
  import { recordManagedTemplates } from './project-state.js';
16
16
 
@@ -79,13 +79,19 @@ export async function appendSessionEnvelope(projectRoot, eventName, payload) {
79
79
  }
80
80
 
81
81
  export function slugify(value, fallback = 'note') {
82
- const ascii = String(value || '')
83
- .normalize('NFKD')
82
+ const slug = normalizeForStorage(value || '')
83
+ .replace(/\[REDACTED:[^\]]+\]/g, '')
84
+ .replace(/\[TRUNCATED:[^\]]+\]/g, '')
84
85
  .replace(/[^\p{Letter}\p{Number}]+/gu, '-')
85
86
  .replace(/^-+|-+$/g, '')
86
87
  .toLowerCase()
87
- .slice(0, 80);
88
- return ascii || fallback;
88
+ .slice(0, 72)
89
+ .replace(/-+$/g, '');
90
+ return slug || fallback;
91
+ }
92
+
93
+ function hasCapturedQuestion(entry) {
94
+ return Boolean(entry.question && entry.question !== '(not captured)' && entry.question.trim().length >= 8);
89
95
  }
90
96
 
91
97
  export async function appendLiveQa(projectRoot, entry) {
@@ -117,7 +123,7 @@ export async function appendLiveQa(projectRoot, entry) {
117
123
  }
118
124
 
119
125
  export async function writeQueryPage(projectRoot, entry) {
120
- if (!entry.question || entry.question.length < 8) return null;
126
+ if (!hasCapturedQuestion(entry)) return null;
121
127
  const day = todayKst();
122
128
  const slug = slugify(entry.question, 'query');
123
129
  const path = join(projectRoot, 'llm-wiki', 'wiki', 'queries', `${day}-${slug}.md`);
@@ -128,6 +134,7 @@ export async function writeQueryPage(projectRoot, entry) {
128
134
  }
129
135
 
130
136
  export async function writeDecisionPage(projectRoot, entry) {
137
+ if (!hasCapturedQuestion(entry)) return null;
131
138
  const text = `${entry.question || ''}\n${entry.result || ''}`;
132
139
  if (!/(decision|decided|결정|선택|채택|확정)/i.test(text)) return null;
133
140
  const day = todayKst();
package/src/redaction.js CHANGED
@@ -1,4 +1,4 @@
1
- const SECRET_PATTERNS = [
1
+ const AUTH_PATTERNS = [
2
2
  {
3
3
  name: 'private-key',
4
4
  pattern: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
@@ -6,22 +6,16 @@ const SECRET_PATTERNS = [
6
6
  {
7
7
  name: 'env-assignment-secret',
8
8
  pattern: /\b([A-Z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API_KEY|PRIVATE_KEY|ACCESS_KEY)[A-Z0-9_]*)\s*=\s*([^\s"'`]{6,}|["'`][^"'`]{6,}["'`])/gi,
9
+ replacement: '$1=[REDACTED:env-assignment-secret]',
9
10
  },
10
11
  {
11
12
  name: 'bearer-token',
12
13
  pattern: /\bBearer\s+[A-Za-z0-9._~+/=-]{16,}/g,
14
+ replacement: 'Bearer [REDACTED:bearer-token]',
13
15
  },
14
16
  {
15
17
  name: 'generic-token',
16
- pattern: /\b(?:sk|pk|ghp|gho|github_pat|xoxb|xoxp|AKIA)[A-Za-z0-9._-]{16,}\b/g,
17
- },
18
- {
19
- name: 'email',
20
- pattern: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/gi,
21
- },
22
- {
23
- name: 'phone',
24
- pattern: /(?<![\w])(?!(?:\d{4}-\d{2}-\d{2})(?!\d))(?:\+?\d(?:[\d .()-]*\d){9,})(?![\w])/g,
18
+ pattern: /\b(?:sk|pk|ghp|gho|github_pat|npm_|xoxb|xoxp|AKIA)[A-Za-z0-9._-]{16,}\b/g,
25
19
  },
26
20
  ];
27
21
 
@@ -34,19 +28,19 @@ const SENSITIVE_PATH_PATTERNS = [
34
28
  ];
35
29
 
36
30
  export function redactText(value, maxLength = 6000) {
37
- let text = typeof value === 'string' ? value : JSON.stringify(value ?? '');
38
- for (const rule of SECRET_PATTERNS) {
39
- text = text.replace(rule.pattern, `[REDACTED:${rule.name}]`);
31
+ let text = normalizeForStorage(typeof value === 'string' ? value : JSON.stringify(value ?? ''));
32
+ for (const rule of AUTH_PATTERNS) {
33
+ text = text.replace(rule.pattern, rule.replacement || `[REDACTED:${rule.name}]`);
40
34
  }
41
35
  if (text.length > maxLength) {
42
36
  text = `${text.slice(0, maxLength)}\n[TRUNCATED:${text.length - maxLength}]`;
43
37
  }
44
- return text;
38
+ return normalizeForStorage(text);
45
39
  }
46
40
 
47
41
  export function hasSecretLikeText(value) {
48
- const text = typeof value === 'string' ? value : JSON.stringify(value ?? '');
49
- return SECRET_PATTERNS.some((rule) => {
42
+ const text = normalizeForStorage(typeof value === 'string' ? value : JSON.stringify(value ?? ''));
43
+ return AUTH_PATTERNS.some((rule) => {
50
44
  rule.pattern.lastIndex = 0;
51
45
  return rule.pattern.test(text);
52
46
  });
@@ -61,6 +55,10 @@ export function summarizeForStorage(value, maxLength = 1200) {
61
55
  return redactText(value, maxLength).replace(/\r/g, '').trim();
62
56
  }
63
57
 
58
+ export function normalizeForStorage(value) {
59
+ return String(value ?? '').normalize('NFC');
60
+ }
61
+
64
62
  export function extractPathsFromText(text) {
65
63
  if (!text || typeof text !== 'string') return [];
66
64
  const matches = text.match(/(?:^|\s)([.~\/A-Za-z0-9_@:-][^\s"'`<>|;]{1,220})/g) || [];
package/src/templates.js CHANGED
@@ -1,11 +1,11 @@
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- Never store credentials, tokens, private keys, \`.env\` contents, customer identifiers, or private personal data.\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- 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`;
5
5
  }
6
6
 
7
7
  export function llmWikiAgents() {
8
- return `# LLM Wiki Agent Rules\n\nGenerated by llm-wiki-kit ${runtimeVersion()}.\n\n## Purpose\nMaintain a living Markdown LLM Wiki from immutable source files and redacted Codex/Claude Code session events.\nThese rules supersede older OMX/OMC/\`omx_wiki/\` LLM Wiki rules for this project.\n\n## Directories\n- \`raw/\`: immutable or redacted source material. Never edit original source captures.\n- \`wiki/\`: AI-maintained knowledge pages.\n- \`outputs/\`: live Q&A summaries, reports, and generated briefs.\n- \`procedures/\`: detailed operating rules for ingest, query, lint, and security.\n\n## Core Rules\n- Never modify \`raw/\` source material except to append redacted session envelopes generated by hooks.\n- Do not state unsupported claims as facts.\n- Mark inference explicitly.\n- Add \`source_ids\` or file references for important claims.\n- Update \`wiki/index.md\` and \`wiki/log.md\` whenever new durable knowledge is created.\n- Preserve contradictions in \`Contradictions\` or \`Open Questions\` instead of overwriting them.\n- Ask before reading private or secret-looking files.\n\n## Page Format\nUse YAML frontmatter when creating wiki pages:\n\n\`\`\`yaml\n---\ntitle: \"\"\ntype: \"source | concept | entity | decision | architecture | debugging | context | query | session-log | convention\"\nsource_ids: []\nstatus: \"draft | reviewed | stale\"\nlast_updated: \"YYYY-MM-DD\"\nconfidence: \"high | medium | low\"\n---\n\`\`\`\n\n## Operations\n- ingest: read new raw files, create or update wiki pages, then update \`wiki/index.md\` and \`wiki/log.md\`.\n- query: start from \`wiki/index.md\`, read relevant wiki pages, answer with source references, and save reusable answers.\n- lint: find stale pages, orphan pages, broken wiki links, missing sources, duplicate concepts, contradictions, and missing links.\n`;
8
+ return `# LLM Wiki Agent Rules\n\nGenerated by llm-wiki-kit ${runtimeVersion()}.\n\n## Purpose\nMaintain a living Markdown LLM Wiki from immutable source files and redacted Codex/Claude Code session events.\nThese rules supersede older OMX/OMC/\`omx_wiki/\` LLM Wiki rules for this project.\n\n## Directories\n- \`raw/\`: immutable or redacted source material. Never edit original source captures.\n- \`wiki/\`: AI-maintained knowledge pages.\n- \`outputs/\`: live Q&A summaries, reports, and generated briefs.\n- \`procedures/\`: detailed operating rules for ingest, query, lint, and security.\n\n## Core Rules\n- Never modify \`raw/\` source material except to append redacted session envelopes generated by hooks.\n- Do not state unsupported claims as facts.\n- Mark inference explicitly.\n- Add \`source_ids\` or file references for important claims.\n- Update \`wiki/index.md\` and \`wiki/log.md\` whenever new durable knowledge is created.\n- Preserve contradictions in \`Contradictions\` or \`Open Questions\` instead of overwriting them.\n- Preserve useful work context and redact authentication values before writing durable notes.\n\n## Page Format\nUse YAML frontmatter when creating wiki pages:\n\n\`\`\`yaml\n---\ntitle: \"\"\ntype: \"source | concept | entity | decision | architecture | debugging | context | query | session-log | convention\"\nsource_ids: []\nstatus: \"draft | reviewed | stale\"\nlast_updated: \"YYYY-MM-DD\"\nconfidence: \"high | medium | low\"\n---\n\`\`\`\n\n## Operations\n- ingest: read new raw files, create or update wiki pages, then update \`wiki/index.md\` and \`wiki/log.md\`.\n- query: start from \`wiki/index.md\`, read relevant wiki pages, answer with source references, and save reusable answers.\n- lint: find stale pages, orphan pages, broken wiki links, missing sources, duplicate concepts, contradictions, and missing links.\n`;
9
9
  }
10
10
 
11
11
  export function indexPage() {
@@ -21,7 +21,7 @@ export function procedure(name) {
21
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 confidence.\n7. Update \`wiki/index.md\` and append to \`wiki/log.md\`.\n`,
22
22
  'query.md': `# Query Procedure\n\n1. Start from \`wiki/index.md\`.\n2. Search \`wiki/\` for relevant pages.\n3. Read the smallest useful set of pages first.\n4. Use raw sources only when exact evidence matters.\n5. Separate verified facts from inference.\n6. Save reusable answers into \`wiki/queries/\` and link them from related pages.\n`,
23
23
  'lint.md': `# Lint Procedure\n\nCheck for stale pages, orphan pages, broken wiki links, missing sources, duplicate concepts, unsupported claims, and unresolved contradictions. Prefer producing a review report before automatic edits.\n`,
24
- 'security.md': `# Security Procedure\n\n- Never store credentials, tokens, private keys, \`.env\` contents, customer identifiers, or private personal data.\n- Redact before writing hook payloads or summaries.\n- Full raw transcript capture is disabled by default and must be explicitly enabled by project policy.\n- If a file or prompt looks secret-bearing, do not persist it and ask for confirmation before reading further.\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 summaries.\n- Full raw transcript capture is disabled by default and must be explicitly enabled by project policy.\n`,
25
25
  };
26
26
  return procedures[name] || '';
27
27
  }