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 +5 -3
- package/docs/integrations/claude-code.md +2 -0
- package/docs/integrations/codex.md +1 -1
- package/docs/operations.md +2 -2
- package/docs/security.md +8 -21
- package/docs/troubleshooting.md +34 -2
- package/package.json +1 -1
- package/src/cli.js +2 -0
- package/src/fs-utils.js +5 -4
- package/src/hook.js +2 -43
- package/src/project.js +13 -6
- package/src/redaction.js +14 -16
- package/src/templates.js +3 -3
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
|
-
-
|
|
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
|
-
-
|
|
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`
|
|
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
|
|
package/docs/operations.md
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
- inject context
|
|
8
8
|
- record redacted summaries
|
|
9
|
-
-
|
|
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
|
-
-
|
|
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
|
|
3
|
+
The default policy favors a useful local work wiki over aggressive blocking.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
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.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -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
|
-
##
|
|
121
|
+
## Authentication Values Were Redacted
|
|
90
122
|
|
|
91
|
-
The hook
|
|
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
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
|
|
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 {
|
|
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
|
-
|
|
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
|
|
83
|
-
.
|
|
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,
|
|
88
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
}
|