llm-wiki-kit 0.2.6 โ 0.2.8
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 +6 -3
- package/docs/integrations/claude-code.md +4 -3
- package/docs/integrations/codex.md +7 -5
- package/docs/manual.md +13 -5
- package/docs/operations.md +6 -4
- package/docs/security.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/package.json +1 -1
- package/src/capture-policy.js +0 -7
- package/src/compact-capture.js +214 -0
- package/src/hook.js +7 -4
- package/src/install.js +43 -10
- package/src/maintenance.js +5 -5
- package/src/project.js +6 -2
- package/src/update-notice.js +6 -7
- package/src/wiki-search.js +118 -0
package/README.md
CHANGED
|
@@ -86,10 +86,11 @@ Use Claude Code or Codex normally.
|
|
|
86
86
|
|
|
87
87
|
The installed hooks:
|
|
88
88
|
|
|
89
|
-
- inject `wiki/memory.md`, `wiki/index.md`,
|
|
89
|
+
- inject functional compact context at session start, instructions loaded, prompt submit, and post-compact time. The hook still uses `wiki/memory.md`, `wiki/index.md`, relevant wiki search results, maintenance signals, and update status; it formats only the useful parts so user-visible hook context does not look like a raw debug dump.
|
|
90
90
|
- remove Codex-facing legacy `oh-my-codex:wiki`/`omx_wiki` surfaces at session start so `llm-wiki/` remains the active wiki implementation
|
|
91
91
|
- record small redacted raw event envelopes and per-turn state
|
|
92
92
|
- capture decision points, debugging findings, changed files, and verification notes
|
|
93
|
+
- before compaction, classify the current turn and save a redacted checkpoint for meaningful or durable work; durable candidates also get a maintenance queue item
|
|
93
94
|
- allow tool calls to proceed without secret/PII-based hook blocking
|
|
94
95
|
- update `llm-wiki/outputs/questions/YYYY-MM-DD-live-qa.md` only for meaningful work turns
|
|
95
96
|
- avoid automatic `wiki/queries/` and `wiki/decisions/` promotion in the default answer-first mode
|
|
@@ -100,6 +101,7 @@ The installed hooks:
|
|
|
100
101
|
|
|
101
102
|
If you need to think about saving every answer manually, the setup has failed.
|
|
102
103
|
If wiki maintenance delays the actual answer, the setup is being used wrong. The default capture mode is `LLM_WIKI_KIT_CAPTURE_MODE=answer-first`; the old eager query/decision capture path remains only as deprecated compatibility mode via `LLM_WIKI_KIT_CAPTURE_MODE=legacy-eager`.
|
|
104
|
+
Pre-compact preservation uses `LLM_WIKI_KIT_PRECOMPACT_ENFORCEMENT=limited|soft|off` with default `limited`. It reads only a bounded transcript tail controlled by `LLM_WIKI_KIT_PRECOMPACT_TRANSCRIPT_TAIL_BYTES`; the raw transcript path and authentication values are redacted before any checkpoint is written.
|
|
103
105
|
|
|
104
106
|
## Operational Commands
|
|
105
107
|
|
|
@@ -119,11 +121,11 @@ Most users should not need these during daily Claude Code/Codex work. They exist
|
|
|
119
121
|
|
|
120
122
|
`llm-wiki update` upgrades the global npm package when npm has a newer target, reinstalls the hook entries, and reapplies safe managed template updates across known project roots. If the installed runtime already satisfies the registry target, it skips `npm install -g` and still runs post-update maintenance. Use `--current-only` when you intentionally want to update only the supplied workspace. Existing wiki content is not overwritten. The command prints step progress to stderr, including registry lookup, npm install, post-update, and project discovery. Use `--timeout-ms <ms>` to bound external commands and `--max-dirs <n>` to bound project discovery under large or slow roots such as WSL `/mnt/*` trees.
|
|
121
123
|
|
|
122
|
-
Installed npm runtimes also perform a cached update notice check from hooks while the user works. This does not install anything automatically. When a newer npm release is detected, the
|
|
124
|
+
Installed npm runtimes also perform a cached update notice check from hooks while the user works. This does not install anything automatically. When a newer npm release is detected, Codex and Claude receive the same passive runtime update status in hook context: current runtime, npm registry target, and the manual command to use when the user asks for update or maintenance work. It is not an instruction to interrupt the current answer or to sell the update to the user. The cache is scoped to the npm command used for lookup so test/fake npm checks do not leak into normal hook sessions. Set `LLM_WIKI_KIT_UPDATE_NOTICE=0` only when diagnosing or suppressing that status block.
|
|
123
125
|
|
|
124
126
|
`llm-wiki post-update --workspace <project>` reapplies the current runtime's hook entries and safe managed template updates without running `npm install -g`. Use `post-update --all --workspace <search-root>` to reapply templates across discovered project roots.
|
|
125
127
|
|
|
126
|
-
`llm-wiki context "<query>"` prints the
|
|
128
|
+
`llm-wiki context "<query>"` prints the full debug view of the layered context sources used by hooks. Hook injection may render those sources as functional compact context for Codex and Claude, but this CLI stays verbose so maintainers can inspect retrieval, snippets, memory, index, and expansion behavior. Daily use should rely on hook injection. By default, episodic `wiki/queries/` pages are excluded from search unless they were promoted with `memory_type: semantic` or `procedural` and `importance >= 4`; use `--include-episodic` only when debugging old automatic query records.
|
|
127
129
|
|
|
128
130
|
`llm-wiki lint` checks wiki health and detects outdated managed rules from older kit versions. Agents may use it before/after meaningful wiki maintenance.
|
|
129
131
|
|
|
@@ -171,6 +173,7 @@ llm-wiki hook claude Stop
|
|
|
171
173
|
## Security Defaults
|
|
172
174
|
|
|
173
175
|
- Full raw transcript capture is disabled by default.
|
|
176
|
+
- PreCompact may read a small bounded transcript tail to create a redacted checkpoint, but it does not store the full transcript or raw `transcript_path`.
|
|
174
177
|
- Tool calls are not blocked only because inputs look sensitive.
|
|
175
178
|
- Authentication values such as tokens, passwords, and private keys are redacted before durable summaries are written.
|
|
176
179
|
- Hook payloads are stored only as redacted event envelopes.
|
|
@@ -42,13 +42,14 @@ when no project `CLAUDE.md` exists. Existing `CLAUDE.md` files are not overwritt
|
|
|
42
42
|
|
|
43
43
|
The hook records redacted turn summaries but does not deny tool calls only because an input looks sensitive. Hook payloads are stored as small redacted event envelopes rather than full transcripts, and context output is redacted field by field before it is returned to Claude Code.
|
|
44
44
|
|
|
45
|
-
At `SessionStart`/`InstructionsLoaded`, the hook first attempts a safe managed-template refresh, recovers stale turn state into `outputs/maintenance/queue.md`, performs a cached npm update notice check for npm installs, then injects `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`,
|
|
45
|
+
At `SessionStart`/`InstructionsLoaded`, the hook first attempts a safe managed-template refresh, recovers stale turn state into `outputs/maintenance/queue.md`, performs a cached npm update notice check for npm installs, then injects functional compact context. The context still uses `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`, relevant wiki/search state, operating rules, maintenance signals, passive runtime update status, and managed-template cleanup notes; the hook formats those signals so they are usable if shown in the Claude Code UI. At `UserPromptSubmit`, it recovers stale turn state, searches wiki pages with MiniSearch or substring fallback, expands one-hop wikilinks, redacts context fields, performs the same cached update notice check, and injects the smallest useful functional compact context set. Update notice cache is scoped by npm command, and maintenance reminders are shown only when the prompt is wiki/maintenance related or matches a queue topic.
|
|
46
46
|
|
|
47
|
-
`PostToolUse` and `PostToolBatch` record redacted tool summaries in the same turn buffer. `PreCompact`
|
|
47
|
+
`PostToolUse` and `PostToolBatch` record redacted tool summaries in the same turn buffer. `PreCompact` classifies the current turn before compaction: simple turns record only a context note, meaningful work writes a live Q&A checkpoint, and durable candidates write both a checkpoint and a maintenance queue item. The checkpoint can include only a bounded redacted transcript tail, never the full raw transcript or raw `transcript_path`. `PostCompact` stores the redacted compact summary as a context note and returns fresh wiki context. In the default `answer-first` mode, `SubagentStop` does not create live Q&A, query, decision, or maintenance files. `Stop` and `SessionEnd` append live Q&A only for meaningful work turns and do not auto-create `wiki/queries/` or `wiki/decisions/`. If the user explicitly asked to record or document durable knowledge and no durable wiki update is detected, `Stop`/`SessionEnd` queue a pending maintenance item for agent review. `Stop` and `SessionEnd` then clear the per-session turn buffer; `SubagentStop` does not.
|
|
48
48
|
|
|
49
49
|
Set `LLM_WIKI_KIT_AUTO_PROJECT_UPDATE=0` only while diagnosing automatic managed-template refresh behavior.
|
|
50
|
-
Set `LLM_WIKI_KIT_UPDATE_NOTICE=0` only while suppressing the cached
|
|
50
|
+
Set `LLM_WIKI_KIT_UPDATE_NOTICE=0` only while suppressing the cached passive runtime update status.
|
|
51
51
|
Set `LLM_WIKI_KIT_CAPTURE_MODE=legacy-eager` only as deprecated compatibility mode for the old eager query/decision capture behavior.
|
|
52
|
+
Set `LLM_WIKI_KIT_PRECOMPACT_ENFORCEMENT=limited|soft|off` to tune pre-compact failure handling; default `limited` blocks only when checkpoint/queue preservation fails on providers with block support. `LLM_WIKI_KIT_PRECOMPACT_TRANSCRIPT_TAIL_BYTES` controls the bounded tail size.
|
|
52
53
|
|
|
53
54
|
After installation or update, run:
|
|
54
55
|
|
|
@@ -29,20 +29,22 @@ Handled events:
|
|
|
29
29
|
|
|
30
30
|
Expected behavior:
|
|
31
31
|
|
|
32
|
-
- `SessionStart` first attempts a safe managed-template refresh, removes Codex-facing legacy `oh-my-codex:wiki`/`omx_wiki` surfaces when they reappear, recovers stale turn state into `outputs/maintenance/queue.md`, performs a cached npm update notice check for npm installs, then injects `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`,
|
|
33
|
-
- `UserPromptSubmit` recovers stale turn state, searches project wiki pages with MiniSearch or substring fallback, expands one-hop wikilinks, redacts context fields, performs the same cached update notice check, and injects the smallest useful context set. Update notice cache is scoped by npm command, and maintenance reminders are shown only when the prompt is wiki/maintenance related or matches a queue topic.
|
|
32
|
+
- `SessionStart` first attempts a safe managed-template refresh, removes Codex-facing legacy `oh-my-codex:wiki`/`omx_wiki` surfaces when they reappear, recovers stale turn state into `outputs/maintenance/queue.md`, performs a cached npm update notice check for npm installs, then injects functional compact context. The context still uses `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`, relevant wiki/search state, operating rules, maintenance signals, passive runtime update status, and managed-template cleanup notes; the hook formats those signals so they are usable if shown in the Codex UI.
|
|
33
|
+
- `UserPromptSubmit` recovers stale turn state, searches project wiki pages with MiniSearch or substring fallback, expands one-hop wikilinks, redacts context fields, performs the same cached update notice check, and injects the smallest useful functional compact context set. Update notice cache is scoped by npm command, and maintenance reminders are shown only when the prompt is wiki/maintenance related or matches a queue topic.
|
|
34
34
|
- `PreToolUse` records redacted tool summaries without blocking tool calls.
|
|
35
35
|
- `PostToolUse` records redacted tool summaries in a turn buffer.
|
|
36
|
-
- `PreCompact`
|
|
36
|
+
- `PreCompact` classifies the current turn before compaction. Simple turns record only a context note; meaningful work writes a live Q&A checkpoint; durable candidates write both a checkpoint and a maintenance queue item. The checkpoint can include only a bounded redacted transcript tail, never the full raw transcript or raw `transcript_path`.
|
|
37
|
+
- `PostCompact` stores the redacted compact summary as a context note and returns fresh wiki context.
|
|
37
38
|
- In the default `answer-first` mode, `SubagentStop` does not create live Q&A, query, decision, or maintenance files. `Stop` appends live Q&A only for meaningful work turns and does not auto-create `wiki/queries/` or `wiki/decisions/`.
|
|
38
39
|
- If the user explicitly asked to record or document durable knowledge and no durable wiki update is detected, `Stop` queues a pending maintenance item for agent review.
|
|
39
40
|
- `Stop` clears the per-session turn buffer after recording. `SubagentStop` leaves the parent turn buffer available for the final stop event.
|
|
40
41
|
|
|
41
|
-
Hook payloads are stored as small redacted event envelopes rather than full transcripts. Context output is also redacted field by field before it is returned to Codex.
|
|
42
|
+
Hook payloads are stored as small redacted event envelopes rather than full transcripts. Context output is also redacted field by field before it is returned to Codex. Functional compact context is a presentation policy, not a feature reduction: Codex still receives the wiki memory, search, maintenance, and passive update signals needed for the hook workflow.
|
|
42
43
|
|
|
43
44
|
Set `LLM_WIKI_KIT_AUTO_PROJECT_UPDATE=0` only while diagnosing automatic managed-template refresh behavior.
|
|
44
|
-
Set `LLM_WIKI_KIT_UPDATE_NOTICE=0` only while suppressing the cached
|
|
45
|
+
Set `LLM_WIKI_KIT_UPDATE_NOTICE=0` only while suppressing the cached passive runtime update status.
|
|
45
46
|
Set `LLM_WIKI_KIT_CAPTURE_MODE=legacy-eager` only as deprecated compatibility mode for the old eager query/decision capture behavior.
|
|
47
|
+
Set `LLM_WIKI_KIT_PRECOMPACT_ENFORCEMENT=limited|soft|off` to tune pre-compact failure handling; default `limited` blocks only on supported providers when checkpoint/queue preservation fails. `LLM_WIKI_KIT_PRECOMPACT_TRANSCRIPT_TAIL_BYTES` controls the bounded tail size.
|
|
46
48
|
|
|
47
49
|
Run these after install:
|
|
48
50
|
|
package/docs/manual.md
CHANGED
|
@@ -50,7 +50,7 @@ llm-wiki/
|
|
|
50
50
|
โโโ procedures/
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
`wiki/memory.md`๋ hook context์ ๋ค์ด๊ฐ๋ ์งง์ hot index๋ค. ๊ธด ์ค๋ช
์ ๋ณต์ฌํ์ง ๋ง๊ณ ์ค์ํ entry point๋ง ๋๋ค.
|
|
53
|
+
`wiki/memory.md`๋ hook context์ ๋ค์ด๊ฐ๋ ์งง์ hot index๋ค. hook์ ๊ธฐ๋ฅ์ ์ ์งํ๋ ์ฌ์ฉ์ ํ๋ฉด์ ๋ณด์ผ ์ ์๋ ๋ถ๋ถ์ functional compact context๋ก ์ ์ ํ๋ฏ๋ก, ๊ธด ์ค๋ช
์ ๋ณต์ฌํ์ง ๋ง๊ณ ์ค์ํ entry point๋ง ๋๋ค.
|
|
54
54
|
|
|
55
55
|
`wiki/index.md`๋ wiki ์ ์ฒด์ navigation map์ด๋ค. agent๋ ๋์ ์ง๋ฌธ์ ๋ฐ์ผ๋ฉด ์ฌ๊ธฐ์ `memory.md`์์ ์์ํ๋ค.
|
|
56
56
|
|
|
@@ -62,7 +62,8 @@ llm-wiki/
|
|
|
62
62
|
|
|
63
63
|
์ค์น๋ hook์ ๋ค์ ์ผ์ ์๋์ผ๋ก ์ํํ๋ค.
|
|
64
64
|
|
|
65
|
-
- session start, prompt submit, post compact ์์ ์ `wiki/memory.md`, `wiki/index.md`, ๊ด๋ จ wiki ๊ฒ์
|
|
65
|
+
- session start, prompt submit, post compact ์์ ์ functional compact context๋ฅผ ์ฃผ์
ํ๋ค. `wiki/memory.md`, `wiki/index.md`, ๊ด๋ จ wiki ๊ฒ์ ๊ฒฐ๊ณผ, maintenance signal, update status๋ ๊ณ์ ์ฌ์ฉํ๋ ์ฌ์ฉ์ ํ๋ฉด์ ๋ณด์ผ ์ ์๋ hook context๋ ํ์ํ ์ ๋ณด ์ค์ฌ์ผ๋ก ์ ์ ํ๋ค.
|
|
66
|
+
- pre compact ์์ ์๋ ํ์ฌ turn์ ๋ถ๋ฅํ๊ณ , simple turn์ context note๋ง ๋จ๊ธฐ๋ฉฐ, meaningful/durable turn์ redacted live Q&A checkpoint์ ํ์ํ maintenance queue ํ๋ณด๋ฅผ ๋จ๊ธด๋ค.
|
|
66
67
|
- prompt/tool/result summary๋ฅผ redactionํ ๋ค turn buffer์ ๊ธฐ๋กํ๋ค.
|
|
67
68
|
- ์๋ฏธ ์๋ ์์
turn๋ง `outputs/questions/YYYY-MM-DD-live-qa.md`์ live Q&A๋ก ๋จ๊ธด๋ค.
|
|
68
69
|
- ๊ธฐ๋ณธ answer-first mode์์๋ `wiki/queries/`์ `wiki/decisions/`๋ฅผ ๋งค turn ์๋ ์์ฑํ์ง ์๋๋ค.
|
|
@@ -71,6 +72,8 @@ llm-wiki/
|
|
|
71
72
|
- ์ runtime์ด ์ค๋๋ project rules/templates๋ฅผ ๋ฐ๊ฒฌํ๋ฉด ์์ ํ๊ฒ ๊ฐฑ์ ๊ฐ๋ฅํ managed file๋ง refreshํ๋ค.
|
|
72
73
|
- `SessionStart`/`InstructionsLoaded`์์ Codex-facing legacy `oh-my-codex:wiki`/`omx_wiki` ์ค์ ๊ณผ ์คํฌ ํ๋ฉด์ด ๋์ด์๋ฌ๋์ง ํ์ธํ๊ณ ์ ๊ฑฐํ๋ค. wiki ๊ธฐ๋ฅ์ `llm-wiki/`๊ฐ ๋จ์ผ ํ์ฑ ๊ตฌํ์ด๋ค.
|
|
73
74
|
|
|
75
|
+
hook UX ์ ์ฑ
์ ๊ธฐ๋ฅ ๋ณด์กด์ด ์ฐ์ ์ด๋ค. Codex์ Claude Code ๋ชจ๋ ๊ฐ์ ๊ธฐ๋ฅ ์ ํธ๋ฅผ ๋ฐ์ผ๋ฉฐ, ์ฐจ์ด๋ raw dump๊ฐ ์๋๋ผ ์ฝ๊ธฐ ์ฌ์ด functional compact formatting์ผ๋ก ๋ณด์ฌ์ฃผ๋ ๊ฒ์ด๋ค.
|
|
76
|
+
|
|
74
77
|
๋งค๋ฒ ์ ์ฅ ์ฌ๋ถ๋ฅผ ์ฌ์ฉ์๊ฐ ๊ณ ๋ฏผํด์ผ ํ๋ค๋ฉด ์๋ชป ์ฐ๊ณ ์๋ ๊ฒ์ด๋ค. ๋ต๋ณ๋ณด๋ค wiki maintenance๊ฐ ๋จผ์ ์ค๋ฉด ์ ๋๋ค. ํ์ฌ ์ฌ์ฉ์ ์์ฒญ์ ๋จผ์ ์ฒ๋ฆฌํ๊ณ , ์ค๋ ์ธ ์ง์๋ง ํ์ํ ๋งํผ ์ ๋ฆฌํ๋ค.
|
|
75
78
|
|
|
76
79
|
## Capture Modes
|
|
@@ -161,7 +164,7 @@ llm-wiki update --workspace /path/to/search-root --timeout-ms 120000 --max-dirs
|
|
|
161
164
|
|
|
162
165
|
`update`๋ source checkout์์ self-updateํ์ง ์๋๋ค. source checkout ๊ฐ๋ฐ ์ค์๋ npm package๋ local tarball๋ก global installํ ๋ค update behavior๋ฅผ ํ
์คํธํ๋ค.
|
|
163
166
|
|
|
164
|
-
global npm runtime์ผ๋ก ์ค์น๋ ๊ฒฝ์ฐ hook์ ์ฌ์ฉ ํ๋ฆ ์ค cached update notice check๋ฅผ ์ํํ๋ค. ์๋ ์ค์น๋ ํ์ง ์๋๋ค. ์ npm release๊ฐ ๊ฐ์ง๋๋ฉด hook context๊ฐ
|
|
167
|
+
global npm runtime์ผ๋ก ์ค์น๋ ๊ฒฝ์ฐ hook์ ์ฌ์ฉ ํ๋ฆ ์ค cached update notice check๋ฅผ ์ํํ๋ค. ์๋ ์ค์น๋ ํ์ง ์๋๋ค. ์ npm release๊ฐ ๊ฐ์ง๋๋ฉด hook context์๋ passive runtime update status๊ฐ ๋ค์ด๊ฐ๋ค. ์ด block์ ํ์ฌ runtime, npm registry target, ์
๋ฐ์ดํธ/์ ๋น ์์ฒญ์ด ์์ ๋ ์ธ ์ ์๋ `llm-wiki update --workspace <project-or-search-root>` ๋ช
๋ น์ ์งง๊ฒ ๋ณด์ฌ์ฃผ๋ ์ํ ์ ๋ณด๋ค. ํ์ฌ ๋ต๋ณ์ ์ค๋จํ๊ฑฐ๋ ์
๋ฐ์ดํธ ๊ถ์ ๋ฅผ ํ๋ผ๋ ์ง์๋ฌธ์ด ์๋๋ค. cache๋ lookup์ ์ฌ์ฉํ npm command๋ณ๋ก ๋ถ๋ฆฌ๋์ด test/fake npm ๊ฒฐ๊ณผ๊ฐ ์ผ๋ฐ hook session์ ์์ด์ง ์๋๋ค. `LLM_WIKI_KIT_UPDATE_NOTICE=0`์ผ๋ก ์ด status block๋ง ๋ ์ ์๋ค.
|
|
165
168
|
|
|
166
169
|
### `llm-wiki post-update`
|
|
167
170
|
|
|
@@ -260,7 +263,7 @@ older OMX/OMC/`omx_wiki/` material์ migration input์ผ ๋ฟ์ด๋ค. ํ์ฌ proje
|
|
|
260
263
|
|
|
261
264
|
### `llm-wiki context`
|
|
262
265
|
|
|
263
|
-
hook์ด
|
|
266
|
+
hook์ด ์ฌ์ฉํ๋ layered context source๋ฅผ full debug ํํ๋ก ์๋ ํ์ธํ๋ค. Codex/Claude hook์ ๊ฐ์ source๋ฅผ functional compact context๋ก ์ ์ ํด ์ฃผ์
ํ ์ ์์ง๋ง, ์ด CLI๋ retrieval๊ณผ formatting ๋ฌธ์ ๋ฅผ ์ ๊ฒํ ์ ์๋๋ก verbose debug ์ถ๋ ฅ์ ์ ์งํ๋ค.
|
|
264
267
|
|
|
265
268
|
```bash
|
|
266
269
|
llm-wiki context "auth architecture" --workspace /path/to/project
|
|
@@ -382,7 +385,11 @@ Claude Code handled events:
|
|
|
382
385
|
- `Stop`
|
|
383
386
|
- `SessionEnd`
|
|
384
387
|
|
|
385
|
-
Hook payload๋ full transcript๊ฐ ์๋๋ผ ์์ redacted event envelope๋ค. context output๋ field๋ณ redaction์ ๊ฑฐ์น๋ค.
|
|
388
|
+
Hook payload๋ full transcript๊ฐ ์๋๋ผ ์์ redacted event envelope๋ค. context output๋ field๋ณ redaction์ ๊ฑฐ์น๋ค. `PreCompact`๋ checkpoint ์์ฑ์ ์ํด ์์ bounded transcript tail๋ง ์ฝ์ ์ ์๊ณ , ์ ์ฅ ์ ์ธ์ฆ๊ฐ๊ณผ raw `transcript_path`๋ฅผ redactionํ๋ค.
|
|
389
|
+
|
|
390
|
+
Context๋ฅผ ๋ฐํํ๋ Codex/Claude hook event๋ ๊ธฐ๋ฅ์ ์ ๊ฑฐํ์ง ์๋๋ค. memory hot index, navigation index, relevant wiki hits, link expansion, maintenance signal, passive runtime update status๋ฅผ ๊ณ์ ์ฌ์ฉํ ์ ์๊ณ , ์ฌ์ฉ์ ํ๋ฉด์ ๋ณด์ผ ์ ์๋ `additionalContext`๋ง functional compact context๋ก ์ ์ ํ๋ค. `llm-wiki context` CLI์ full debug ์ถ๋ ฅ์ ์ด hook formatting ์ ์ฑ
๊ณผ ๋ณ๋๋ก ์ ์งํ๋ค.
|
|
391
|
+
|
|
392
|
+
Pre-compact ๋ณด์กด์ ๊ธฐ๋ณธ `LLM_WIKI_KIT_PRECOMPACT_ENFORCEMENT=limited`๋ก ๋์ํ๋ค. `limited`๋ checkpoint/queue ์ ์ฅ ์คํจ ์ block์ ์ง์ํ๋ provider์์๋ง compact block์ ์๋ํ๊ณ , ๊ทธ ์ธ์๋ failure context๋ฅผ ๋จ๊ธด๋ค. `soft`๋ blockํ์ง ์๊ณ , `off`๋ ์คํจ output์ ์ต์ ํ๋ค. `LLM_WIKI_KIT_PRECOMPACT_TRANSCRIPT_TAIL_BYTES`๋ก transcript tail byte ํ๋๋ฅผ ์กฐ์ ํ๋ค.
|
|
386
393
|
|
|
387
394
|
## Security Defaults
|
|
388
395
|
|
|
@@ -392,6 +399,7 @@ Hook payload๋ full transcript๊ฐ ์๋๋ผ ์์ redacted event envelope๋ค.
|
|
|
392
399
|
- token, password, bearer credential, private key, `.env` ์๋ฌธ ๊ฐ์ authentication value๋ durable summary์ ์ฐ๊ธฐ ์ redactionํ๋ค.
|
|
393
400
|
- phone number, email, date, business identifier๋ local work context๋ก ์ ์ฉํ ์ ์์ด ๊ธฐ๋ณธ ๋ณด์กดํ๋ค.
|
|
394
401
|
- full raw transcript capture๋ ๊ธฐ๋ณธ ๊ธฐ๋ฅ์ด ์๋๋ค.
|
|
402
|
+
- `PreCompact` checkpoint๋ bounded redacted transcript tail๋ง ์ฌ์ฉํ ์ ์์ผ๋ฉฐ full transcript์ raw `transcript_path`๋ ์ ์ฅํ์ง ์๋๋ค.
|
|
395
403
|
- `llm-wiki lint`๋ wiki ์์ secret-like content๋ฅผ error๋ก ๋ณด๊ณ ํ๋ค.
|
|
396
404
|
|
|
397
405
|
๋ฏผ๊ฐํ raw command output, log, screenshot, transcript๋ฅผ ์ ์ฅํด์ผ ํ ๋๋ ๋จผ์ redactionํ๋ค. ์ธ์ฆ๊ฐ์ wiki, report, live Q&A, command history์ ๋จ๊ธฐ์ง ์๋๋ค.
|
package/docs/operations.md
CHANGED
|
@@ -118,7 +118,7 @@ llm-wiki maintenance --workspace /path/to/project
|
|
|
118
118
|
|
|
119
119
|
`update --check [--to <version-or-tag>]` is online and asks npm for the target version. It reports `update available` only when that registry target is newer than the installed version, so it does not suggest downgrades.
|
|
120
120
|
|
|
121
|
-
Installed npm runtimes also run a cached hook-side update notice check while the user works. It never installs automatically. If npm has a newer release, `SessionStart`/`InstructionsLoaded`/`UserPromptSubmit` context
|
|
121
|
+
Installed npm runtimes also run a cached hook-side update notice check while the user works. It never installs automatically. If npm has a newer release, `SessionStart`/`InstructionsLoaded`/`UserPromptSubmit` context includes a passive runtime update status for both Codex and Claude: current runtime, npm registry target, and the manual `llm-wiki update --workspace <project-or-search-root>` command to use when the user asks for update or maintenance work. It is not an active instruction to interrupt the current answer or tell the user to update. The cache is scoped to the npm command used for lookup so test/fake npm checks do not leak into normal hook sessions. Set `LLM_WIKI_KIT_UPDATE_NOTICE=0` to suppress this status block while diagnosing.
|
|
122
122
|
|
|
123
123
|
`projects --workspace <search-root>` lists discovered project roots that have `llm-wiki/.kit-state.json` or an older `llm-wiki/wiki/index.md`, reports whether their managed templates are current, and prints the update commands for the search root.
|
|
124
124
|
|
|
@@ -141,13 +141,15 @@ After a plain `npm install -g llm-wiki-kit@latest`, existing hooks keep working
|
|
|
141
141
|
|
|
142
142
|
## Context And Wiki Maintenance
|
|
143
143
|
|
|
144
|
-
Daily use should be Claude Code/Codex first. The user should not need to run a chain of `llm-wiki` commands while working. Hooks inject context automatically, but the current user answer takes priority over wiki cleanup. The active agent updates durable wiki pages when reusable project knowledge appears and the turn's importance or user consent justifies persistence.
|
|
144
|
+
Daily use should be Claude Code/Codex first. The user should not need to run a chain of `llm-wiki` commands while working. Hooks inject context automatically, but the current user answer takes priority over wiki cleanup. The active agent updates durable wiki pages when reusable project knowledge appears and the turn's importance or user consent justifies persistence. Hook context policy is function-first: memory, search, maintenance, and update signals remain available, while user-visible context is formatted as functional compact context instead of a raw dump.
|
|
145
145
|
|
|
146
|
-
In the default `LLM_WIKI_KIT_CAPTURE_MODE=answer-first` mode, `Stop` and `SessionEnd` append live Q&A only for meaningful work turns. They do not auto-create `wiki/queries/` or `wiki/decisions/`. If the user explicitly asked for recording/documentation and no durable wiki update is detected, a pending cleanup candidate is written to `llm-wiki/outputs/maintenance/queue.md`. `SessionStart` and `UserPromptSubmit` also recover stale per-turn state into the same queue when the previous stop hook did not complete. `SessionStart` injects a one-item queue summary; `UserPromptSubmit` injects a soft reminder only when the prompt is wiki/maintenance related or matches a queue topic. This is a recovery and reminder layer, not a full transcript capture path.
|
|
146
|
+
In the default `LLM_WIKI_KIT_CAPTURE_MODE=answer-first` mode, `Stop` and `SessionEnd` append live Q&A only for meaningful work turns. They do not auto-create `wiki/queries/` or `wiki/decisions/`. If the user explicitly asked for recording/documentation and no durable wiki update is detected, a pending cleanup candidate is written to `llm-wiki/outputs/maintenance/queue.md`. `PreCompact` performs the same answer-first classification before context compaction: simple turns get only a context note, archive-worthy turns get a live Q&A checkpoint, and durable candidates get a checkpoint plus queue item. `SessionStart` and `UserPromptSubmit` also recover stale per-turn state into the same queue when the previous stop hook did not complete. `SessionStart` injects a one-item queue summary; `UserPromptSubmit` injects a soft reminder only when the prompt is wiki/maintenance related or matches a queue topic. This is a recovery and reminder layer, not a full transcript capture path.
|
|
147
|
+
|
|
148
|
+
Pre-compact preservation defaults to `LLM_WIKI_KIT_PRECOMPACT_ENFORCEMENT=limited`. In that mode, checkpoint/queue write failures can block compact only on providers that support hook blocking; otherwise the hook returns best-effort failure context. `soft` never blocks, and `off` suppresses failure output. `LLM_WIKI_KIT_PRECOMPACT_TRANSCRIPT_TAIL_BYTES` controls the small bounded transcript tail used for checkpoint context. Authentication values and the raw transcript path are redacted before storage.
|
|
147
149
|
|
|
148
150
|
`LLM_WIKI_KIT_CAPTURE_MODE=legacy-eager` keeps the old eager live Q&A/query/decision/maintenance behavior for compatibility only. New projects should rely on the answer-first default.
|
|
149
151
|
|
|
150
|
-
`llm-wiki context "<query>"` is the manual debug form of
|
|
152
|
+
`llm-wiki context "<query>"` is the manual full-debug form of the context sources used by hook injection. Hook output for Codex and Claude may be functionally compact, but this command remains verbose so maintainers can inspect all source layers. It reads:
|
|
151
153
|
|
|
152
154
|
- `llm-wiki/wiki/memory.md` as the short hot index
|
|
153
155
|
- `llm-wiki/wiki/index.md` as the navigation map
|
package/docs/security.md
CHANGED
|
@@ -13,4 +13,4 @@ Before writing durable summaries, the runtime redacts authentication values such
|
|
|
13
13
|
|
|
14
14
|
Manual and hook context output also runs through redaction before returning excerpts or search hits. `llm-wiki lint` reports remaining secret-like wiki content as an error so it can be removed or rewritten before it becomes reusable project memory.
|
|
15
15
|
|
|
16
|
-
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
|
|
16
|
+
Hook payloads are stored as small event envelopes, not full raw transcripts. Full transcript capture is intentionally not implemented as a default. `PreCompact` may read a small bounded transcript tail for a redacted checkpoint, but it does not store the raw transcript path or full transcript. If a project needs raw transcript capture, add a project-local policy and a redaction path first.
|
package/docs/troubleshooting.md
CHANGED
|
@@ -242,7 +242,7 @@ If the queue is always empty during ordinary Q&A, that is normal. If you expecte
|
|
|
242
242
|
|
|
243
243
|
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.
|
|
244
244
|
|
|
245
|
-
Hook payloads are stored as small redacted event envelopes rather than full transcripts. Manual and hook context output is redacted before wiki excerpts or search hits are returned.
|
|
245
|
+
Hook payloads are stored as small redacted event envelopes rather than full transcripts. `PreCompact` checkpoints may include a bounded redacted transcript tail, but the full transcript and raw `transcript_path` are not stored. Manual and hook context output is redacted before wiki excerpts or search hits are returned.
|
|
246
246
|
|
|
247
247
|
## Duplicate Pages Appear
|
|
248
248
|
|
package/package.json
CHANGED
package/src/capture-policy.js
CHANGED
|
@@ -153,12 +153,5 @@ export function formatDurableCaptureGuidance(query) {
|
|
|
153
153
|
'- ์ ๋ฌธ์๋ ๊ด๋ จ ๊ธฐ์กด ๋ฌธ์๊ฐ ์์ ๋๋ง ๋ง๋ค๊ณ , credentials/private data๋ ์ ์ฅํ์ง ์๋๋ค.',
|
|
154
154
|
].join('\n');
|
|
155
155
|
}
|
|
156
|
-
if (hasDurableKeyword(text)) {
|
|
157
|
-
return [
|
|
158
|
-
'LLM Wiki ์ ์ฅ ํ๋ณด ์๋ฆผ:',
|
|
159
|
-
'- ํ์ฌ ๋ต๋ณ์ ๋จผ์ ํ๋ค.',
|
|
160
|
-
'- ๋ค์ ์ธ์
์๋ ์ฌ์ฌ์ฉํ ๋งํ ๋ด์ฉ(๊ฒฐ์ , ์ ์ฑ
, ๋๋ฒ๊น
๊ฒฐ๊ณผ, ์ด์ ์ ์ฐจ, ๊ฒ์ฆ๋ ์ํ ๋ฑ)์ด ์์ผ๋ฉด ๋ต๋ณ ๋์์ ์งง๊ฒ wiki ๋ฌธ์ํ ์ฌ๋ถ๋ฅผ ๋ฌผ์ด๋ณธ๋ค.',
|
|
161
|
-
].join('\n');
|
|
162
|
-
}
|
|
163
156
|
return '';
|
|
164
157
|
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { open, stat } from 'fs/promises';
|
|
2
|
+
import { relative } from 'path';
|
|
3
|
+
import { classifyTurn } from './capture-policy.js';
|
|
4
|
+
import { recordMaintenanceForEntry } from './maintenance.js';
|
|
5
|
+
import { appendContextNote, appendLiveQa, appendWikiLog } from './project.js';
|
|
6
|
+
import { redactText, summarizeForStorage } from './redaction.js';
|
|
7
|
+
import { buildEntryFromState } from './state.js';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_PRECOMPACT_TAIL_BYTES = 32 * 1024;
|
|
10
|
+
const MAX_PRECOMPACT_TAIL_BYTES = 256 * 1024;
|
|
11
|
+
const ENFORCEMENT_MODES = new Set(['limited', 'soft', 'off']);
|
|
12
|
+
|
|
13
|
+
function contextOutput(eventName, context) {
|
|
14
|
+
if (!context) return {};
|
|
15
|
+
return {
|
|
16
|
+
hookSpecificOutput: {
|
|
17
|
+
hookEventName: eventName,
|
|
18
|
+
additionalContext: context,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function providerSupportsBlock(provider) {
|
|
24
|
+
return String(provider || '').toLowerCase() === 'claude';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function preCompactEnforcementMode(env = process.env) {
|
|
28
|
+
const value = String(env.LLM_WIKI_KIT_PRECOMPACT_ENFORCEMENT || 'limited').toLowerCase();
|
|
29
|
+
return ENFORCEMENT_MODES.has(value) ? value : 'limited';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function preCompactTranscriptTailBytes(env = process.env) {
|
|
33
|
+
const raw = env.LLM_WIKI_KIT_PRECOMPACT_TRANSCRIPT_TAIL_BYTES;
|
|
34
|
+
if (raw === undefined || raw === '') return DEFAULT_PRECOMPACT_TAIL_BYTES;
|
|
35
|
+
const parsed = Number(raw);
|
|
36
|
+
if (!Number.isFinite(parsed) || parsed < 0) return DEFAULT_PRECOMPACT_TAIL_BYTES;
|
|
37
|
+
return Math.min(Math.floor(parsed), MAX_PRECOMPACT_TAIL_BYTES);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function compactSummaryText(payload) {
|
|
41
|
+
return summarizeForStorage(
|
|
42
|
+
payload.compact_summary ||
|
|
43
|
+
payload.compactSummary ||
|
|
44
|
+
payload.summary ||
|
|
45
|
+
payload.message ||
|
|
46
|
+
'',
|
|
47
|
+
4000
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function transcriptPath(payload) {
|
|
52
|
+
return payload.transcript_path || payload.transcriptPath || '';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function redactTranscriptPath(text, path) {
|
|
56
|
+
let output = String(text || '');
|
|
57
|
+
if (path) output = output.split(path).join('[REDACTED:transcript-path]');
|
|
58
|
+
output = output.replace(/("transcript_path"\s*:\s*)"[^"]*"/gi, '$1"[REDACTED:transcript-path]"');
|
|
59
|
+
output = output.replace(/('transcript_path'\s*:\s*)'[^']*'/gi, "$1'[REDACTED:transcript-path]'");
|
|
60
|
+
output = output.replace(/\btranscript_path\s*=\s*[^\s"'`]+/gi, 'transcript_path=[REDACTED:transcript-path]');
|
|
61
|
+
return output;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function readTranscriptTail(path, maxBytes) {
|
|
65
|
+
if (!path) return { ok: false, reason: 'missing-transcript-path', text: '' };
|
|
66
|
+
if (maxBytes <= 0) return { ok: false, reason: 'transcript-tail-disabled', text: '' };
|
|
67
|
+
|
|
68
|
+
let handle = null;
|
|
69
|
+
try {
|
|
70
|
+
const info = await stat(path);
|
|
71
|
+
if (!info.isFile()) return { ok: false, reason: 'transcript-not-file', text: '' };
|
|
72
|
+
const length = Math.min(info.size, maxBytes);
|
|
73
|
+
const start = Math.max(0, info.size - length);
|
|
74
|
+
const buffer = Buffer.alloc(length);
|
|
75
|
+
handle = await open(path, 'r');
|
|
76
|
+
await handle.read(buffer, 0, length, start);
|
|
77
|
+
const text = summarizeForStorage(redactTranscriptPath(buffer.toString('utf8'), path), 4000);
|
|
78
|
+
return {
|
|
79
|
+
ok: true,
|
|
80
|
+
text,
|
|
81
|
+
bytesRead: length,
|
|
82
|
+
truncated: info.size > maxBytes,
|
|
83
|
+
};
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return {
|
|
86
|
+
ok: false,
|
|
87
|
+
reason: summarizeForStorage(error?.code || error?.message || 'read-failed', 200),
|
|
88
|
+
text: '',
|
|
89
|
+
};
|
|
90
|
+
} finally {
|
|
91
|
+
await handle?.close().catch(() => {});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function appendToField(current, addition) {
|
|
96
|
+
const existing = String(current || '').trim();
|
|
97
|
+
if (!existing || existing === '(not captured)') return addition;
|
|
98
|
+
return `${existing}\n${addition}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function entryWithTranscriptTail(entry, tail) {
|
|
102
|
+
const next = { ...entry };
|
|
103
|
+
if (tail.ok && tail.text) {
|
|
104
|
+
const marker = tail.truncated
|
|
105
|
+
? 'PreCompact transcript tail (redacted bounded excerpt; earlier transcript omitted):'
|
|
106
|
+
: 'PreCompact transcript tail (redacted bounded excerpt):';
|
|
107
|
+
next.result = appendToField(next.result, `${marker}\n${tail.text}`);
|
|
108
|
+
next.followUp = appendToField(
|
|
109
|
+
next.followUp,
|
|
110
|
+
'Compact checkpoint includes only a bounded redacted transcript tail, not the raw transcript path or full transcript.'
|
|
111
|
+
);
|
|
112
|
+
} else if (tail.reason && tail.reason !== 'missing-transcript-path') {
|
|
113
|
+
next.followUp = appendToField(
|
|
114
|
+
next.followUp,
|
|
115
|
+
`Transcript tail unavailable during PreCompact (${tail.reason}); checkpoint used turn state only.`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return next;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function failureOutput(provider, eventName, message, mode) {
|
|
122
|
+
if (mode === 'off') return {};
|
|
123
|
+
const reason = summarizeForStorage(message, 1200);
|
|
124
|
+
if (mode === 'limited' && providerSupportsBlock(provider)) {
|
|
125
|
+
return {
|
|
126
|
+
decision: 'block',
|
|
127
|
+
reason,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return contextOutput(eventName, `LLM Wiki PreCompact preservation warning:\n${reason}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function handlePreCompactCapture(projectRoot, provider, eventName, payload) {
|
|
134
|
+
const mode = preCompactEnforcementMode();
|
|
135
|
+
const failures = [];
|
|
136
|
+
const tail = await readTranscriptTail(transcriptPath(payload), preCompactTranscriptTailBytes());
|
|
137
|
+
const stateEntry = await buildEntryFromState(projectRoot, payload, payload.last_assistant_message || payload.response || '');
|
|
138
|
+
const entry = entryWithTranscriptTail(stateEntry, tail);
|
|
139
|
+
const classification = classifyTurn(entry, eventName);
|
|
140
|
+
|
|
141
|
+
await appendContextNote(
|
|
142
|
+
projectRoot,
|
|
143
|
+
eventName,
|
|
144
|
+
[
|
|
145
|
+
`PreCompact classification: ${classification.kind}.`,
|
|
146
|
+
tail.ok ? `Transcript tail bytes read: ${tail.bytesRead}.` : `Transcript tail: ${tail.reason || 'not available'}.`,
|
|
147
|
+
'Turn state is intentionally kept for the later Stop/SessionEnd hook.',
|
|
148
|
+
].join('\n')
|
|
149
|
+
).catch((error) => {
|
|
150
|
+
failures.push(`context note failed: ${error?.message || error}`);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
let liveQaPath = null;
|
|
154
|
+
if (classification.archive) {
|
|
155
|
+
const checkpointEntry = {
|
|
156
|
+
...entry,
|
|
157
|
+
followUp: appendToField(
|
|
158
|
+
entry.followUp,
|
|
159
|
+
classification.suggestDurable
|
|
160
|
+
? 'PreCompact saved this checkpoint and queued durable wiki review; merge reusable facts into existing wiki pages after the current answer.'
|
|
161
|
+
: 'PreCompact saved this checkpoint before context compaction; reusable facts should be merged only when appropriate.'
|
|
162
|
+
),
|
|
163
|
+
};
|
|
164
|
+
try {
|
|
165
|
+
liveQaPath = await appendLiveQa(projectRoot, checkpointEntry);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
failures.push(`live Q&A checkpoint failed: ${error?.message || error}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (liveQaPath && ['suggest-durable', 'explicit-durable'].includes(classification.kind)) {
|
|
172
|
+
try {
|
|
173
|
+
const result = await recordMaintenanceForEntry(projectRoot, entry, {
|
|
174
|
+
source: liveQaPath,
|
|
175
|
+
eventName,
|
|
176
|
+
reason: 'PreCompact durable candidate checkpoint needs review after context compaction.',
|
|
177
|
+
});
|
|
178
|
+
if (result.created === false && result.reason) {
|
|
179
|
+
failures.push(`maintenance queue failed: ${result.reason}`);
|
|
180
|
+
}
|
|
181
|
+
} catch (error) {
|
|
182
|
+
failures.push(`maintenance queue failed: ${error?.message || error}`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (liveQaPath) {
|
|
187
|
+
await appendWikiLog(
|
|
188
|
+
projectRoot,
|
|
189
|
+
`precompact checkpoint; archive=${relative(projectRoot, liveQaPath)}; classification=${classification.kind}`
|
|
190
|
+
).catch((error) => {
|
|
191
|
+
failures.push(`wiki log failed: ${error?.message || error}`);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (failures.length > 0) {
|
|
196
|
+
return failureOutput(
|
|
197
|
+
provider,
|
|
198
|
+
eventName,
|
|
199
|
+
`LLM Wiki PreCompact preservation did not complete. ${failures.join('; ')}`,
|
|
200
|
+
mode
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function recordPostCompactSummary(projectRoot, eventName, payload) {
|
|
208
|
+
const summary = compactSummaryText(payload);
|
|
209
|
+
await appendContextNote(
|
|
210
|
+
projectRoot,
|
|
211
|
+
eventName,
|
|
212
|
+
summary ? `Compact summary:\n${redactText(summary, 4000)}` : 'Compact summary was not provided.'
|
|
213
|
+
);
|
|
214
|
+
}
|
package/src/hook.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { findProjectRoot } from './fs-utils.js';
|
|
2
2
|
import { classifyTurn, formatDurableCaptureGuidance, hasDetectedDurableWikiChange, isLegacyEagerCaptureMode } from './capture-policy.js';
|
|
3
|
-
import { bootstrapProject,
|
|
3
|
+
import { bootstrapProject, appendLiveQa, appendSessionEnvelope, appendWikiLog, buildContextBrief, writeDecisionPage, writeQueryPage } from './project.js';
|
|
4
|
+
import { compactSummaryText, handlePreCompactCapture, recordPostCompactSummary } from './compact-capture.js';
|
|
4
5
|
import { recoverStaleTurnStates, recordMaintenanceForEntry } from './maintenance.js';
|
|
5
6
|
import { applyProjectTemplateUpdate, inspectProjectState } from './project-state.js';
|
|
6
7
|
import { recordProject } from './projects.js';
|
|
@@ -156,11 +157,13 @@ export async function handleHook(provider, explicitEvent) {
|
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
if (eventName === 'PreCompact' || eventName === 'PostCompact') {
|
|
159
|
-
await appendContextNote(projectRoot, eventName, 'Compaction lifecycle event captured by llm-wiki-kit.');
|
|
160
160
|
if (eventName === 'PostCompact') {
|
|
161
|
-
|
|
161
|
+
const summary = compactSummaryText(payload);
|
|
162
|
+
await recordPostCompactSummary(projectRoot, eventName, payload);
|
|
163
|
+
const context = await hookContext(projectRoot, eventName, await buildContextBrief(projectRoot, eventName, summary), payload);
|
|
164
|
+
return contextOutput(eventName, context);
|
|
162
165
|
}
|
|
163
|
-
return
|
|
166
|
+
return handlePreCompactCapture(projectRoot, provider, eventName, payload);
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
if (eventName === 'Stop' || eventName === 'SubagentStop' || eventName === 'SessionEnd') {
|
package/src/install.js
CHANGED
|
@@ -19,6 +19,8 @@ import { bootstrapProject } from './project.js';
|
|
|
19
19
|
import { recordProject } from './projects.js';
|
|
20
20
|
import { binPath, detectInstallSource, packageRoot, runtimeVersion } from './version.js';
|
|
21
21
|
|
|
22
|
+
const SESSION_START_MATCHER = 'startup|resume|clear|compact';
|
|
23
|
+
|
|
22
24
|
export function hookCommand(provider, eventName, options = {}) {
|
|
23
25
|
return commandForNodeScript(binPath, ['hook', provider, eventName], options);
|
|
24
26
|
}
|
|
@@ -125,11 +127,23 @@ async function reconcileWindowsCommand() {
|
|
|
125
127
|
|
|
126
128
|
function addHook(hooks, eventName, command, options = {}) {
|
|
127
129
|
hooks[eventName] = Array.isArray(hooks[eventName]) ? hooks[eventName] : [];
|
|
128
|
-
const
|
|
130
|
+
const existing = hooks[eventName].find((entry) => (
|
|
129
131
|
Array.isArray(entry?.hooks) &&
|
|
130
132
|
entry.hooks.some((hook) => hook?.type === 'command' && hook?.command === command)
|
|
131
133
|
));
|
|
132
|
-
if (
|
|
134
|
+
if (existing) {
|
|
135
|
+
let changed = false;
|
|
136
|
+
if (options.matcher && existing.matcher !== options.matcher) {
|
|
137
|
+
existing.matcher = options.matcher;
|
|
138
|
+
changed = true;
|
|
139
|
+
}
|
|
140
|
+
const hook = existing.hooks.find((item) => item?.type === 'command' && item?.command === command);
|
|
141
|
+
if (hook && options.commandWindows && hook.commandWindows !== options.commandWindows) {
|
|
142
|
+
hook.commandWindows = options.commandWindows;
|
|
143
|
+
changed = true;
|
|
144
|
+
}
|
|
145
|
+
return changed;
|
|
146
|
+
}
|
|
133
147
|
const entry = {
|
|
134
148
|
hooks: [
|
|
135
149
|
{
|
|
@@ -186,12 +200,21 @@ function unsupportedKitClaudeEvents(hooks, supportedEvents) {
|
|
|
186
200
|
.sort();
|
|
187
201
|
}
|
|
188
202
|
|
|
189
|
-
function
|
|
190
|
-
return
|
|
203
|
+
function expectedMatcher(eventName) {
|
|
204
|
+
return eventName === 'SessionStart' ? SESSION_START_MATCHER : undefined;
|
|
191
205
|
}
|
|
192
206
|
|
|
193
|
-
function
|
|
194
|
-
return
|
|
207
|
+
function hookCommandMatches(hook, command) {
|
|
208
|
+
return hook?.command === command || hook?.commandWindows === command;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function hookEventIsCurrent(entries, eventName, command) {
|
|
212
|
+
const matcher = expectedMatcher(eventName);
|
|
213
|
+
return Array.isArray(entries) && entries.some((entry) => (
|
|
214
|
+
(!matcher || entry.matcher === matcher) &&
|
|
215
|
+
Array.isArray(entry?.hooks) &&
|
|
216
|
+
entry.hooks.some((hook) => hook?.type === 'command' && hookCommandMatches(hook, command))
|
|
217
|
+
));
|
|
195
218
|
}
|
|
196
219
|
|
|
197
220
|
export async function install(options = {}) {
|
|
@@ -221,7 +244,7 @@ export async function install(options = {}) {
|
|
|
221
244
|
changed.push('codex:replaced');
|
|
222
245
|
}
|
|
223
246
|
for (const eventName of CODEX_EVENTS) {
|
|
224
|
-
const matcher = eventName
|
|
247
|
+
const matcher = expectedMatcher(eventName);
|
|
225
248
|
const command = hookCommand('codex', eventName, { platform });
|
|
226
249
|
const commandWindows = isWindows({ platform }) ? hookCommand('codex', eventName, { platform: 'win32' }) : undefined;
|
|
227
250
|
if (addHook(codex.hooks, eventName, command, { matcher, commandWindows })) {
|
|
@@ -251,7 +274,7 @@ export async function install(options = {}) {
|
|
|
251
274
|
changed.push(...removedUnsupported.map((eventName) => `claude:removed-unsupported:${eventName}`));
|
|
252
275
|
}
|
|
253
276
|
for (const eventName of claudeEvents) {
|
|
254
|
-
const matcher = eventName
|
|
277
|
+
const matcher = expectedMatcher(eventName);
|
|
255
278
|
if (addHook(claude.hooks, eventName, hookCommand('claude', eventName, { platform }), { matcher })) {
|
|
256
279
|
claudeChanged = true;
|
|
257
280
|
changed.push(`claude:${eventName}`);
|
|
@@ -312,9 +335,18 @@ export async function status(options = {}) {
|
|
|
312
335
|
const claude = await readJson(claudeSettingsPath, {});
|
|
313
336
|
const claudeDetection = detectClaudeVersion();
|
|
314
337
|
const claudeEvents = supportedClaudeEvents(claudeDetection);
|
|
315
|
-
const
|
|
338
|
+
const codexMissingEvents = CODEX_EVENTS.filter((eventName) => !hookEventIsCurrent(
|
|
339
|
+
codex.hooks?.[eventName] || [],
|
|
340
|
+
eventName,
|
|
341
|
+
hookCommand('codex', eventName, { platform })
|
|
342
|
+
));
|
|
343
|
+
const claudeMissingEvents = claudeEvents.filter((eventName) => !hookEventIsCurrent(
|
|
344
|
+
claude.hooks?.[eventName] || [],
|
|
345
|
+
eventName,
|
|
346
|
+
hookCommand('claude', eventName, { platform })
|
|
347
|
+
));
|
|
316
348
|
const claudeUnsupportedKitEvents = unsupportedKitClaudeEvents(claude.hooks || {}, claudeEvents);
|
|
317
|
-
const codexInstalled =
|
|
349
|
+
const codexInstalled = codexMissingEvents.length === 0;
|
|
318
350
|
const claudeInstalled = claudeMissingEvents.length === 0 && claudeUnsupportedKitEvents.length === 0;
|
|
319
351
|
const discoveredCommandPaths = await findCommandPaths('llm-wiki', { platform });
|
|
320
352
|
const commandPath = discoveredCommandPaths[0] || null;
|
|
@@ -344,6 +376,7 @@ export async function status(options = {}) {
|
|
|
344
376
|
claudeVersion: claudeDetection.version || 'unknown',
|
|
345
377
|
claudeModernHooks: claudeDetection.modern,
|
|
346
378
|
claudeSupportedEvents: claudeEvents,
|
|
379
|
+
codexMissingEvents,
|
|
347
380
|
claudeMissingEvents,
|
|
348
381
|
claudeUnsupportedKitEvents,
|
|
349
382
|
hooksCurrent: codexInstalled && claudeInstalled,
|
package/src/maintenance.js
CHANGED
|
@@ -240,15 +240,15 @@ export function formatMaintenanceContext(summary, options = {}) {
|
|
|
240
240
|
|
|
241
241
|
const lines = [
|
|
242
242
|
eventName === 'UserPromptSubmit'
|
|
243
|
-
? 'LLM Wiki maintenance
|
|
244
|
-
: 'LLM Wiki maintenance
|
|
245
|
-
`- pending: ${summary.pendingCount}. ํ์ฌ
|
|
243
|
+
? 'LLM Wiki maintenance status:'
|
|
244
|
+
: 'LLM Wiki maintenance status:',
|
|
245
|
+
`- pending review items: ${summary.pendingCount}. ํ์ฌ ์์ฒญ์ด ์ฐ์ ์ด๋ฉฐ, ๊ด๋ จ ์์ ๋๋ง durable wiki ์ ๋ฆฌ์ ์ฌ์ฉํ๋ค.`,
|
|
246
246
|
];
|
|
247
247
|
for (const item of pending) {
|
|
248
|
-
lines.push(`- ${item.topic || item.id}:
|
|
248
|
+
lines.push(`- ${item.topic || item.id}: ${item.suggested_target}; source=${item.source}${item.result_missing ? '; result missing' : ''}`);
|
|
249
249
|
}
|
|
250
250
|
if (summary.pending.length > pending.length) {
|
|
251
|
-
lines.push(`- ${summary.pending.length - pending.length} more pending item(s)
|
|
251
|
+
lines.push(`- ${summary.pending.length - pending.length} more pending item(s) available in llm-wiki/outputs/maintenance/queue.md.`);
|
|
252
252
|
}
|
|
253
253
|
return lines.join('\n');
|
|
254
254
|
}
|
package/src/project.js
CHANGED
|
@@ -13,7 +13,7 @@ import { formatMaintenanceContext, maintenanceSummary } from './maintenance.js';
|
|
|
13
13
|
import { normalizeForStorage, redactText, summarizeForStorage } from './redaction.js';
|
|
14
14
|
import { gitignore, indexPage, llmWikiAgents, logPage, memoryPage, procedure, rootAgentsPolicy } from './templates.js';
|
|
15
15
|
import { formatProjectMaintenanceContext, inspectProjectState, recordManagedTemplates } from './project-state.js';
|
|
16
|
-
import { buildContextPack,
|
|
16
|
+
import { buildContextPack, formatHookContextPack, searchWiki as searchWikiWithIndex } from './wiki-search.js';
|
|
17
17
|
|
|
18
18
|
export async function bootstrapProject(projectRoot, options = {}) {
|
|
19
19
|
if (process.env.LLM_WIKI_KIT_DISABLE_BOOTSTRAP === '1') return { created: false };
|
|
@@ -171,5 +171,9 @@ export async function buildContextBrief(projectRoot, eventName, query = '') {
|
|
|
171
171
|
const wikiMaintenance = await maintenanceSummary(projectRoot)
|
|
172
172
|
.then((summary) => formatMaintenanceContext(summary, { eventName, query }))
|
|
173
173
|
.catch(() => '');
|
|
174
|
-
return [
|
|
174
|
+
return [formatHookContextPack(pack, {
|
|
175
|
+
eventName,
|
|
176
|
+
hitLimit: eventName === 'SessionStart' ? 2 : 3,
|
|
177
|
+
logLineLimit: eventName === 'SessionStart' ? 2 : 0,
|
|
178
|
+
}), maintenance, wikiMaintenance].filter(Boolean).join('\n\n');
|
|
175
179
|
}
|
package/src/update-notice.js
CHANGED
|
@@ -114,12 +114,11 @@ export async function updateNoticeContext(projectRoot, eventName, options = {})
|
|
|
114
114
|
const workspace = resolve(projectRoot || process.cwd());
|
|
115
115
|
const updateCommand = commandForProject('update', workspace);
|
|
116
116
|
return [
|
|
117
|
-
'LLM Wiki
|
|
118
|
-
`-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
'-
|
|
123
|
-
'- ์ฌ์ฉ์๊ฐ ์์ฒญํ๊ธฐ ์ ์๋ ์๋ ์ค์นํ์ง ๋ง์ธ์.',
|
|
117
|
+
'LLM Wiki runtime update status:',
|
|
118
|
+
`- current hook runtime: ${notice.installedVersion}; npm registry: ${notice.latestVersion}`,
|
|
119
|
+
'- ํ์ฌ ์์ฒญ ์ฒ๋ฆฌ๊ฐ ์ฐ์ ์ด๋ฉฐ, ํ์ฌ runtime ๊ธฐ๋ฅ์ ๊ณ์ ๋์ํ๋ค.',
|
|
120
|
+
`- ์
๋ฐ์ดํธ/์ ๋น๋ฅผ ์ํํ ๋ ์ฌ์ฉํ ๋ช
๋ น: \`${updateCommand}\``,
|
|
121
|
+
'- npm ์ ์ญ ์ค์น ๊ถํ ์ค๋ฅ๊ฐ ๋๋ฉด ํ๊ฒฝ์ ๋ง๊ฒ `npm install -g llm-wiki-kit@latest` ๋๋ `sudo npm install -g llm-wiki-kit@latest` ํ ๊ฐ์ update ๋ช
๋ น์ ๋ค์ ์คํํ๋ค.',
|
|
122
|
+
'- ์ด status๋ง์ผ๋ก ์ค์น๋ ์
๋ฐ์ดํธ๋ฅผ ์คํํ์ง ์๋๋ค.',
|
|
124
123
|
].join('\n');
|
|
125
124
|
}
|
package/src/wiki-search.js
CHANGED
|
@@ -5,11 +5,13 @@ import {
|
|
|
5
5
|
buildWikiGraph,
|
|
6
6
|
collectWikiPages,
|
|
7
7
|
pageLookup,
|
|
8
|
+
parseFrontmatter,
|
|
8
9
|
readMemoryExcerpt,
|
|
9
10
|
} from './wiki-model.js';
|
|
10
11
|
|
|
11
12
|
const DEFAULT_LIMIT = 5;
|
|
12
13
|
const SNIPPET_CHARS = 350;
|
|
14
|
+
const HOOK_SNIPPET_CHARS = 180;
|
|
13
15
|
let miniSearchLoader = null;
|
|
14
16
|
|
|
15
17
|
async function loadMiniSearch() {
|
|
@@ -202,6 +204,80 @@ function redactHit(hit) {
|
|
|
202
204
|
};
|
|
203
205
|
}
|
|
204
206
|
|
|
207
|
+
function compactText(value, maxLength = 220) {
|
|
208
|
+
const text = String(value || '').replace(/\s+/g, ' ').trim();
|
|
209
|
+
if (text.length <= maxLength) return text;
|
|
210
|
+
return `${text.slice(0, Math.max(0, maxLength - 3)).trimEnd()}...`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function stripGeneratedMemoryBlock(value) {
|
|
214
|
+
return String(value || '')
|
|
215
|
+
.replace(/<!-- llm-wiki-kit:memory-start -->[\s\S]*?<!-- llm-wiki-kit:memory-end -->/g, '')
|
|
216
|
+
.trim();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function sectionBody(body, heading) {
|
|
220
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
221
|
+
const match = String(body || '').match(new RegExp(`(?:^|\\n)##\\s+${escaped}\\s*\\n([\\s\\S]*?)(?=\\n##\\s+|$)`, 'i'));
|
|
222
|
+
return match?.[1] || '';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function bulletLines(value) {
|
|
226
|
+
return String(value || '')
|
|
227
|
+
.split('\n')
|
|
228
|
+
.map((line) => line.trim())
|
|
229
|
+
.filter((line) => /^-\s+/.test(line))
|
|
230
|
+
.map((line) => compactText(line, 240));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function memoryFocusLines(memoryExcerpt, limit = 6) {
|
|
234
|
+
const { body } = parseFrontmatter(memoryExcerpt || '');
|
|
235
|
+
const bodyWithoutGenerated = stripGeneratedMemoryBlock(body);
|
|
236
|
+
const current = bulletLines(sectionBody(bodyWithoutGenerated, 'Current Focus'));
|
|
237
|
+
const entries = bulletLines(sectionBody(bodyWithoutGenerated, 'Durable Entry Points')).slice(0, 2);
|
|
238
|
+
if (current.length > 0) {
|
|
239
|
+
const currentLimit = Math.max(1, limit - entries.length);
|
|
240
|
+
return [...current.slice(-currentLimit), ...entries].slice(0, limit);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const bullets = bulletLines(bodyWithoutGenerated);
|
|
244
|
+
if (bullets.length > 0) return bullets.slice(-limit);
|
|
245
|
+
|
|
246
|
+
const lines = bodyWithoutGenerated
|
|
247
|
+
.split('\n')
|
|
248
|
+
.map((line) => line.trim())
|
|
249
|
+
.filter(Boolean);
|
|
250
|
+
|
|
251
|
+
return lines
|
|
252
|
+
.filter((line) => !/^#+\s+/.test(line))
|
|
253
|
+
.map((line) => compactText(line, 240))
|
|
254
|
+
.slice(0, Math.max(1, Math.min(limit, 3)));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function logFocusLines(logExcerpt, limit = 2) {
|
|
258
|
+
return String(logExcerpt || '')
|
|
259
|
+
.split('\n')
|
|
260
|
+
.map((line) => line.trim())
|
|
261
|
+
.filter((line) => /^-\s+/.test(line))
|
|
262
|
+
.slice(-limit)
|
|
263
|
+
.map((line) => compactText(line, 220));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function metaWikiQuery(query) {
|
|
267
|
+
return /(?:llm-wiki|wiki|์ํค|memory|๋ฉ๋ชจ๋ฆฌ|๊ธฐ์ต|index|์ธ๋ฑ์ค|log|๋ก๊ทธ|maintenance|queue|๋ฌธ์|๋ฌธ์ํ|๊ธฐ๋ก|์ ๋ฆฌ|context|์ปจํ
์คํธ)/i.test(String(query || ''));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function internalContextPath(path) {
|
|
271
|
+
return /^(?:wiki\/)?(?:memory|index|log)\.md$/i.test(String(path || ''));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function hookHits(pack, options = {}) {
|
|
275
|
+
const allowInternal = options.includeInternalHits || metaWikiQuery(pack.query);
|
|
276
|
+
return (pack.hits || [])
|
|
277
|
+
.filter((hit) => allowInternal || !internalContextPath(hit.path))
|
|
278
|
+
.slice(0, options.hitLimit || 3);
|
|
279
|
+
}
|
|
280
|
+
|
|
205
281
|
export async function buildContextPack(projectRoot, query, options = {}) {
|
|
206
282
|
const limit = Number(options.limit || DEFAULT_LIMIT);
|
|
207
283
|
const expand = options.expand !== false;
|
|
@@ -258,3 +334,45 @@ export function formatContextPack(pack) {
|
|
|
258
334
|
}
|
|
259
335
|
return lines.join('\n').trim();
|
|
260
336
|
}
|
|
337
|
+
|
|
338
|
+
export function formatHookContextPack(pack, options = {}) {
|
|
339
|
+
const lines = [
|
|
340
|
+
'LLM Wiki context (functional compact):',
|
|
341
|
+
'- Priority: answer the current user request first; use wiki context when it helps.',
|
|
342
|
+
'- ๊ธฐ๋ฅ ์ ์ง: hook memory/search/capture/maintenance/update signals are active.',
|
|
343
|
+
'- Durable project knowledge belongs in llm-wiki/wiki; never store credentials, tokens, private keys, or .env contents.',
|
|
344
|
+
];
|
|
345
|
+
if (pack.query) {
|
|
346
|
+
lines.push(`- query: "${compactText(pack.query, 180)}"`);
|
|
347
|
+
lines.push(`- search: ${pack.search}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const memoryLines = memoryFocusLines(pack.memoryExcerpt, options.memoryLineLimit ?? 6);
|
|
351
|
+
if (memoryLines.length > 0) {
|
|
352
|
+
lines.push('', 'Memory focus:');
|
|
353
|
+
lines.push(...memoryLines.map((line) => `- ${line.replace(/^-\s+/, '')}`));
|
|
354
|
+
} else {
|
|
355
|
+
lines.push('', 'Memory focus:', '- See llm-wiki/wiki/memory.md and llm-wiki/wiki/index.md when project context is needed.');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const hits = hookHits(pack, options);
|
|
359
|
+
if (hits.length > 0) {
|
|
360
|
+
lines.push('', 'Relevant wiki pages:');
|
|
361
|
+
for (const hit of hits) {
|
|
362
|
+
const suffix = hit.source === 'linked' && hit.via.length > 0
|
|
363
|
+
? `, via ${hit.via.join(', ')}`
|
|
364
|
+
: '';
|
|
365
|
+
lines.push(`- ${hit.path} (${hit.source}${suffix}): ${compactText(hit.snippet, HOOK_SNIPPET_CHARS)}`);
|
|
366
|
+
}
|
|
367
|
+
} else if (pack.query) {
|
|
368
|
+
lines.push('', 'Relevant wiki pages:', '- No directly relevant durable wiki page found; answer normally and inspect files/wiki only if needed.');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const logLines = logFocusLines(pack.logExcerpt, options.logLineLimit ?? 2);
|
|
372
|
+
if (logLines.length > 0) {
|
|
373
|
+
lines.push('', 'Recent log:');
|
|
374
|
+
lines.push(...logLines.map((line) => `- ${line.replace(/^-\s+/, '')}`));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
return lines.join('\n').trim();
|
|
378
|
+
}
|