@m16khb/llm-wiki 0.1.0 → 0.1.2
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 +200 -47
- package/cmd/llm-wiki/hook_test.go +108 -0
- package/cmd/llm-wiki/main.go +457 -11
- package/go.mod +1 -1
- package/internal/mcpautostart/autostart.go +285 -0
- package/internal/mcpautostart/autostart_test.go +169 -0
- package/internal/mcpautostart/detach_unix.go +9 -0
- package/internal/mcpautostart/detach_windows.go +7 -0
- package/internal/mcpautostart/proxy.go +162 -0
- package/internal/sessionctx/sessionctx.go +649 -0
- package/internal/sessionctx/sessionctx_test.go +415 -0
- package/internal/wiki/cleanup.go +1189 -0
- package/internal/wiki/cleanup_plan.go +569 -0
- package/internal/wiki/cleanup_test.go +450 -0
- package/internal/wiki/init.go +77 -0
- package/internal/wiki/vault_test.go +41 -0
- package/npm/lib/runner.js +50 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,15 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
`llm-wiki`는 Claude Code와 Codex가 같은 Obsidian-backed LLM Wiki vault를 안전하게 공유하도록 하는 Go 기반 MCP 서버/CLI다.
|
|
4
4
|
|
|
5
|
-
현재 기본 구조는 **단일 daemon process + streamable HTTP MCP + SQLite WAL queue + in-memory metadata cache**다.
|
|
5
|
+
현재 기본 구조는 **단일 long-running daemon process + streamable HTTP MCP + SQLite WAL queue + in-memory metadata cache**다. Claude Code/Codex는 기본적으로 `mcp-autostart` stdio shim을 실행하고, shim은 daemon이 없으면 자동으로 띄운 뒤 실제 tool call을 같은 daemon의 HTTP MCP endpoint로 proxy한다. 즉 client마다 짧은 shim process는 생길 수 있지만, vault/DB를 소유하고 write를 직렬화하는 프로세스는 하나의 `llm-wiki serve` daemon이다.
|
|
6
6
|
|
|
7
7
|
## 핵심 원칙
|
|
8
8
|
|
|
9
9
|
- Single writer: `llm-wiki serve` daemon 하나가 write를 직렬화한다.
|
|
10
|
+
- Lazy start: `llm-wiki mcp-autostart`가 MCP client 시작 시 daemon health를 확인하고 없으면 자동 기동한다.
|
|
11
|
+
- Auto init: `llm-wiki init` 또는 `mcp-autostart --init-vault`가 누락된 vault 폴더 구조를 non-destructive하게 만든다.
|
|
10
12
|
- SQLite WAL queue: write job은 durable queue/audit DB에 먼저 기록된다.
|
|
11
13
|
- Local-first: Markdown vault가 canonical source다. SQLite는 queue/index/cache/audit state이며 rebuild 가능해야 한다.
|
|
12
14
|
- Read-safe: `.obsidian/`, hidden/runtime directory, path traversal은 차단한다.
|
|
13
15
|
- Write-minimal: 기본 write는 `wiki_capture`의 additive Markdown 생성뿐이다.
|
|
16
|
+
- Destructive maintenance: `llm-wiki cleanup`은 기본 dry-run이며 `--apply`가 있을 때만 archive/rename/rewrite를 수행한다.
|
|
14
17
|
- Source 보호: `10-sources/` body 수정/삭제 도구는 제공하지 않는다.
|
|
15
18
|
|
|
16
19
|
## 빠른 시작
|
|
@@ -19,18 +22,23 @@
|
|
|
19
22
|
# repo root
|
|
20
23
|
./scripts/install-local.sh
|
|
21
24
|
|
|
22
|
-
# singleton daemon 시작
|
|
23
|
-
./bin/llm-wiki serve --vault ~/workspace/knowledge-base/llm-wiki
|
|
24
|
-
|
|
25
|
-
# 별도 shell에서 health check
|
|
26
|
-
curl http://127.0.0.1:39233/healthz
|
|
27
|
-
|
|
28
25
|
# read-only CLI smoke
|
|
26
|
+
./bin/llm-wiki init --vault ~/workspace/knowledge-base/llm-wiki --json
|
|
29
27
|
./bin/llm-wiki info --vault ~/workspace/knowledge-base/llm-wiki --json
|
|
30
28
|
./bin/llm-wiki search --vault ~/workspace/knowledge-base/llm-wiki --limit 5 karpathy
|
|
31
29
|
./bin/llm-wiki lint --vault ~/workspace/knowledge-base/llm-wiki --json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
MCP client에서는 아래 설정 파일을 복사/병합하면 된다. daemon을 미리 켤 필요는 없다.
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
- Claude Code: `config/claude-code.mcp.json`을 target project의 `.mcp.json`에 복사
|
|
35
|
+
- Codex: `config/codex.config.toml` 내용을 `~/.codex/config.toml`에 병합
|
|
36
|
+
|
|
37
|
+
수동 운영이나 launchd/systemd가 daemon을 관리하는 환경에서는 직접 daemon을 띄우고 HTTP config를 써도 된다.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
./bin/llm-wiki serve --vault ~/workspace/knowledge-base/llm-wiki
|
|
41
|
+
curl http://127.0.0.1:39233/healthz
|
|
34
42
|
./bin/llm-wiki jobs --limit 20
|
|
35
43
|
```
|
|
36
44
|
|
|
@@ -42,15 +50,23 @@ curl http://127.0.0.1:39233/healthz
|
|
|
42
50
|
|
|
43
51
|
| Command | Purpose |
|
|
44
52
|
|---|---|
|
|
53
|
+
| `llm-wiki init` | 누락된 vault root와 표준 폴더 구조 생성 |
|
|
45
54
|
| `llm-wiki serve` | singleton daemon + streamable HTTP MCP endpoint 시작 |
|
|
46
|
-
| `llm-wiki mcp` |
|
|
55
|
+
| `llm-wiki mcp-autostart` | stdio MCP shim; daemon을 자동 기동/재사용하고 HTTP MCP로 proxy |
|
|
56
|
+
| `llm-wiki mcp` | legacy direct stdio MCP server; daemon 공유가 필요 없는 compatibility path |
|
|
47
57
|
| `llm-wiki info` | vault root/count 요약 |
|
|
48
58
|
| `llm-wiki search QUERY` | Markdown lexical search |
|
|
49
59
|
| `llm-wiki read PAGE_OR_PATH` | slug 또는 vault-relative path로 page 읽기 |
|
|
50
60
|
| `llm-wiki lint` | frontmatter/wikilink/slug 구조 점검 |
|
|
51
61
|
| `llm-wiki capture` | SQLite queue를 통해 allowed folder에 curated note 생성 |
|
|
62
|
+
| `llm-wiki cleanup` | dry-run 기본의 apply-gated vault cleanup; `repository-references`, `duplicate-slugs`, LLM-reviewed `analyze`/`apply-plan` 지원 |
|
|
63
|
+
| `llm-wiki session-context` | hook/수동 사용을 위한 project-aware wiki context 검색 |
|
|
64
|
+
| `llm-wiki session-capture` | 세션 중 선별한 내용을 `30-sessions/`에 capture |
|
|
65
|
+
| `llm-wiki hook` | Claude Code/Codex 공용 lifecycle hook entrypoint |
|
|
52
66
|
| `llm-wiki jobs` | SQLite queue 최근 job audit 출력 |
|
|
53
67
|
|
|
68
|
+
표준 vault 구조는 `00-meta/`, `00-meta/reports/`, `10-sources/`, `20-wiki/concepts/`, `20-wiki/entities/`, `20-wiki/summaries/`, `30-sessions/`이다. `init`은 `.obsidian/`을 만들거나 수정하지 않고 기존 파일을 rewrite하지 않는다.
|
|
69
|
+
|
|
54
70
|
## MCP tools
|
|
55
71
|
|
|
56
72
|
| Tool | Type | Purpose |
|
|
@@ -61,35 +77,101 @@ curl http://127.0.0.1:39233/healthz
|
|
|
61
77
|
| `wiki_lint` | read-only | structural lint |
|
|
62
78
|
| `wiki_capture` | queued additive write | `20-wiki/`, `30-sessions/`, `00-meta/reports/` 하위에 note 생성 |
|
|
63
79
|
|
|
64
|
-
##
|
|
80
|
+
## Vault cleanup
|
|
81
|
+
|
|
82
|
+
`llm-wiki cleanup`은 `ai-slop-cleaner`식으로 과도하게 파편화된 wiki 내용을 정리하는 **CLI-only maintenance command**다. MCP tool로 노출하지 않는 이유는 archive/rename/bulk wikilink rewrite가 destructive operation이라서, local shell에서 dry-run 결과를 본 뒤 명시적으로 `--apply`를 붙이는 흐름이 더 안전하기 때문이다.
|
|
65
83
|
|
|
66
|
-
|
|
84
|
+
현재 지원 scope는 `repository-references`와 `duplicate-slugs`다.
|
|
67
85
|
|
|
68
86
|
```bash
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
# 계획만 출력하고 파일은 변경하지 않음
|
|
88
|
+
llm-wiki cleanup --vault ~/workspace/knowledge-base/llm-wiki --json
|
|
89
|
+
|
|
90
|
+
# repo별 dated fragment를 bundle page로 합치고 원본은 _archive/로 rename
|
|
91
|
+
llm-wiki cleanup --vault ~/workspace/knowledge-base/llm-wiki --scope repository-references --apply --json
|
|
92
|
+
|
|
93
|
+
# duplicate slug warning을 없애기 위해 non-canonical page를 unique slug로 rename
|
|
94
|
+
llm-wiki cleanup --vault ~/workspace/knowledge-base/llm-wiki --scope duplicate-slugs --apply --json
|
|
71
95
|
```
|
|
72
96
|
|
|
73
|
-
|
|
97
|
+
LLM이 “진짜 cleanup 대상인지 / 어떤 전략이 안전한지” 먼저 판단하게 하려면 `analyze → plan JSON → apply-plan` 흐름을 사용한다. CLI 자체는 외부 LLM을 호출하지 않고, candidate packet과 deterministic plan executor만 제공한다. Codex/Claude 같은 agent가 analysis를 읽고 allowlisted JSON plan을 작성한 뒤, CLI가 다시 dry-run 검증과 `--apply` gate를 담당한다.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# LLM/agent가 검토할 candidate packet 생성; 파일 변경 없음
|
|
101
|
+
llm-wiki cleanup analyze --vault ~/workspace/knowledge-base/llm-wiki --scope repository-references --json
|
|
102
|
+
llm-wiki cleanup analyze --vault ~/workspace/knowledge-base/llm-wiki --scope duplicate-slugs --json
|
|
103
|
+
|
|
104
|
+
# agent가 작성한 JSON plan을 먼저 검증만 수행
|
|
105
|
+
llm-wiki cleanup apply-plan --vault ~/workspace/knowledge-base/llm-wiki --plan /tmp/cleanup-plan.json --json
|
|
106
|
+
|
|
107
|
+
# 검증 결과가 의도와 맞을 때만 적용
|
|
108
|
+
llm-wiki cleanup apply-plan --vault ~/workspace/knowledge-base/llm-wiki --plan /tmp/cleanup-plan.json --apply --json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Plan schema는 versioned JSON이고 현재 allowlisted action은 `run_scope`와 `rename_page`뿐이다. `run_scope`는 기존 guarded cleanup을 통째로 실행하므로 plan 안에서 단독 action이어야 한다.
|
|
74
112
|
|
|
75
113
|
```json
|
|
76
114
|
{
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
115
|
+
"version": 1,
|
|
116
|
+
"scope": "repository-references",
|
|
117
|
+
"summary": "LLM-reviewed reason for why these candidates are true cleanup targets.",
|
|
118
|
+
"actions": [
|
|
119
|
+
{
|
|
120
|
+
"type": "run_scope",
|
|
121
|
+
"scope": "repository-references",
|
|
122
|
+
"reason": "Consolidate dated fragments through the guarded deterministic cleanup scope."
|
|
81
123
|
}
|
|
82
|
-
|
|
124
|
+
]
|
|
83
125
|
}
|
|
84
126
|
```
|
|
85
127
|
|
|
86
|
-
|
|
128
|
+
Repo-local skill 표면도 제공한다. **MCP 설정만으로 skill이 자동 배포되지는 않는다.** MCP는 tool 호출 표면이고, skill은 Claude Code/Codex client가 별도로 로드하는 prompt package다. 이 repo에는 같은 workflow를 Codex와 Claude Code 양쪽 skill 폴더에 둔다.
|
|
129
|
+
|
|
130
|
+
| Skill | Purpose |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `llm-wiki-query` | `info/search/read/lint/session-context` read-only lookup |
|
|
133
|
+
| `llm-wiki-capture` | `wiki_capture`, `capture`, `session-capture` curated additive write |
|
|
134
|
+
| `llm-wiki-cleanup` | LLM-reviewed `cleanup analyze → apply-plan` workflow |
|
|
135
|
+
| `llm-wiki-mcp-ops` | daemon, `mcp-autostart`, jobs, npx/MCP smoke |
|
|
136
|
+
| `llm-wiki-hooks` | `SessionStart`, `UserPromptSubmit`, `Stop` hook validation |
|
|
137
|
+
|
|
138
|
+
Locations:
|
|
139
|
+
|
|
140
|
+
- Codex/OMX: `.codex/skills/<skill>/SKILL.md` (`$llm-wiki-query`, `$llm-wiki-capture`, ...)
|
|
141
|
+
- Claude Code: `.claude/skills/<skill>/SKILL.md` (client skill picker/skill trigger)
|
|
142
|
+
|
|
143
|
+
`repository-references` 적용 시 동작:
|
|
144
|
+
|
|
145
|
+
- `20-wiki/concepts/repository-references/<repo>/` 아래 dated fragment들을 `<repo>-reference-bundle-YYYY-MM-DD.md`로 통합한다.
|
|
146
|
+
- 원본 fragment는 `_archive/repository-reference-fragments-YYYY-MM-DD/<repo>/archived-*.md`로 이동해 duplicate slug를 피한다.
|
|
147
|
+
- active wiki page의 wikilink는 bundle slug로 rewrite한다.
|
|
148
|
+
- `00-meta/index.md`, `00-meta/log.md`, `00-meta/reports/repository-reference-cleanup-YYYY-MM-DD*.md`를 갱신한다.
|
|
149
|
+
- `.obsidian/`과 `10-sources/` body는 수정하지 않는다.
|
|
87
150
|
|
|
88
|
-
|
|
151
|
+
`duplicate-slugs` 적용 시 동작:
|
|
89
152
|
|
|
90
|
-
|
|
153
|
+
- 같은 filename slug를 가진 page들 중 canonical page를 하나 고른다. `10-sources/`는 보호 대상, `00-meta/` canonical entry는 root compatibility page보다 우선한다.
|
|
154
|
+
- non-canonical page를 같은 폴더의 unique slug로 rename한다. 예: root `index.md` → `root-compatibility-index.md`.
|
|
155
|
+
- renamed page에는 canonical page wikilink와 이전 path notice를 추가한다.
|
|
156
|
+
- `00-meta/log.md`와 `00-meta/reports/duplicate-slug-cleanup-YYYY-MM-DD*.md`를 갱신한다.
|
|
157
|
+
- `.obsidian/`과 `10-sources/` body는 수정하지 않는다.
|
|
91
158
|
|
|
92
|
-
|
|
159
|
+
## Lazy daemon MCP 설정
|
|
160
|
+
|
|
161
|
+
권장 설정은 stdio command로 `llm-wiki mcp-autostart`를 실행하는 방식이다.
|
|
162
|
+
|
|
163
|
+
```text
|
|
164
|
+
MCP client ── stdio ── llm-wiki mcp-autostart shim
|
|
165
|
+
│ health/start/proxy
|
|
166
|
+
▼
|
|
167
|
+
llm-wiki serve daemon ── /mcp ── shared service/queue/vault
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
동시에 여러 client가 시작되어도 같은 `--db` path의 daemon lock이 중복 daemon을 막는다. 이미 다른 vault/DB를 가진 daemon이 같은 address를 점유 중이면 shim은 proxy하지 않고 에러를 낸다.
|
|
171
|
+
|
|
172
|
+
### Claude Code
|
|
173
|
+
|
|
174
|
+
`config/claude-code.mcp.json`을 target project의 `.mcp.json`에 복사한다.
|
|
93
175
|
|
|
94
176
|
```json
|
|
95
177
|
{
|
|
@@ -98,44 +180,111 @@ HTTP daemon을 공유하는 구성이 기본 권장 경로지만, stdio MCP만
|
|
|
98
180
|
"command": "npx",
|
|
99
181
|
"args": [
|
|
100
182
|
"-y",
|
|
183
|
+
"--prefix",
|
|
184
|
+
"/tmp",
|
|
101
185
|
"--package",
|
|
102
186
|
"/Users/m16khb/Workspace/llm-wiki",
|
|
187
|
+
"--",
|
|
103
188
|
"llm-wiki",
|
|
104
|
-
"mcp",
|
|
189
|
+
"mcp-autostart",
|
|
190
|
+
"--init-vault",
|
|
105
191
|
"--vault",
|
|
106
|
-
"/Users/m16khb/Workspace/knowledge-base/llm-wiki"
|
|
192
|
+
"/Users/m16khb/Workspace/knowledge-base/llm-wiki",
|
|
193
|
+
"--addr",
|
|
194
|
+
"127.0.0.1:39233"
|
|
107
195
|
]
|
|
108
196
|
}
|
|
109
197
|
}
|
|
110
198
|
}
|
|
111
199
|
```
|
|
112
200
|
|
|
113
|
-
npm registry
|
|
201
|
+
npm registry package를 쓸 때는 `--package` 값을 `@m16khb/llm-wiki`로 바꾼다. `config/claude-code.npx.mcp.json`은 registry package 예시, `config/claude-code.npx-local.mcp.json`은 local repo 예시다.
|
|
114
202
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
203
|
+
Claude Code는 local stdio뿐 아니라 HTTP MCP server 설정도 지원한다. 공식 문서는 `claude mcp add`와 `.mcp.json` scope를 설명한다: https://code.claude.com/docs/en/mcp
|
|
204
|
+
|
|
205
|
+
### Codex
|
|
206
|
+
|
|
207
|
+
`config/codex.config.toml` 내용을 `~/.codex/config.toml`에 병합한다.
|
|
208
|
+
|
|
209
|
+
```toml
|
|
210
|
+
[mcp_servers.llm-wiki]
|
|
211
|
+
command = "npx"
|
|
212
|
+
args = [
|
|
213
|
+
"-y",
|
|
214
|
+
"--prefix", "/tmp",
|
|
215
|
+
"--package", "/Users/m16khb/Workspace/llm-wiki",
|
|
216
|
+
"--",
|
|
217
|
+
"llm-wiki",
|
|
218
|
+
"mcp-autostart",
|
|
219
|
+
"--init-vault",
|
|
220
|
+
"--vault", "/Users/m16khb/Workspace/knowledge-base/llm-wiki",
|
|
221
|
+
"--addr", "127.0.0.1:39233",
|
|
222
|
+
]
|
|
223
|
+
startup_timeout_sec = 60
|
|
224
|
+
tool_timeout_sec = 60
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Codex config는 MCP stdio server에 `mcp_servers.<id>.command` / `args`를 쓰고, streamable HTTP server에는 `mcp_servers.<id>.url`을 쓴다: https://developers.openai.com/codex/config-reference
|
|
228
|
+
|
|
229
|
+
## Claude Code / Codex 공용 session hooks
|
|
230
|
+
|
|
231
|
+
두 client의 hook 설정 문법은 다르지만, `llm-wiki`는 같은 command를 실행하게 만든다.
|
|
232
|
+
|
|
233
|
+
```text
|
|
234
|
+
SessionStart -> llm-wiki hook -> project/tech terms로 wiki_search 후 additionalContext 주입
|
|
235
|
+
UserPromptSubmit -> llm-wiki hook -> prompt keyword + project/tech terms로 wiki_search 후 relevant context 주입
|
|
236
|
+
Stop -> llm-wiki hook -> assistant가 명시한 llm-wiki-capture block만 30-sessions/에 저장
|
|
132
237
|
```
|
|
133
238
|
|
|
134
|
-
|
|
239
|
+
`SessionStart`/`UserPromptSubmit` hook은 `suppressOutput: true`로 transcript 출력을 숨긴다. 실제 context는
|
|
240
|
+
`hookSpecificOutput.additionalContext`로 주입되지만, Codex/Claude transcript에 긴 `hook context:` 문자열이 빽빽하게
|
|
241
|
+
보이지 않게 하기 위한 UX 안전장치다.
|
|
135
242
|
|
|
136
|
-
|
|
243
|
+
`go.mod`가 있으면 Go/Golang을 감지하고, MCP SDK·SQLite·Gin·GORM·Redis 같은 dependency 신호, `package.json`/`bin` 기반 Node/npm/npx wrapper 신호, `swaggo` dependency나 `swagger.yaml`/`openapi.yaml` 같은 API spec 파일을 tech context로 별도 검색한다. 검색 결과는 현재 project와 맞는 문서를 우선하고, 다른 repository 전용 reference는 hook tech context에서 제외한다.
|
|
137
244
|
|
|
138
|
-
|
|
245
|
+
공용 wrapper는 `scripts/hooks/llm-wiki-hook.sh`다. `LLM_WIKI_BIN`이 있으면 그 binary를 쓰고, 없으면 `llm-wiki` PATH, 마지막으로 이 repo를 `npx --package <repo>`로 실행한다.
|
|
246
|
+
|
|
247
|
+
현재 repo에는 project-local hook 설정도 포함되어 있다: `.claude/settings.json`, `.codex/hooks.json`. 다른 repo에 붙일 때는 아래 예시를 복사하고 command path/vault path를 조정한다.
|
|
248
|
+
|
|
249
|
+
예시 파일:
|
|
250
|
+
|
|
251
|
+
- Claude Code project/user settings 예시: `config/claude-code.hooks.settings.json`
|
|
252
|
+
- Codex project/user hooks 예시: `config/codex.hooks.json`
|
|
253
|
+
|
|
254
|
+
Codex는 `~/.codex/hooks.json` 또는 `<repo>/.codex/hooks.json`, Claude Code는 `.claude/settings.json`/`~/.claude/settings.json`에 같은 wrapper command를 등록하면 된다. Codex hooks는 기본 enabled지만 trust review가 필요할 수 있고, Claude Code는 `UserPromptSubmit`/`SessionStart`에서 `hookSpecificOutput.additionalContext`를 context로 넣는다.
|
|
255
|
+
|
|
256
|
+
세션 중 수동 capture:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
llm-wiki session-capture \
|
|
260
|
+
--vault ~/workspace/knowledge-base/llm-wiki \
|
|
261
|
+
--project "$PWD" \
|
|
262
|
+
--phase progress \
|
|
263
|
+
--title "MCP hook decision" \
|
|
264
|
+
--body "Claude Code와 Codex는 같은 llm-wiki hook command를 호출한다."
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Stop hook에서 자동 저장하려면 assistant 최종 답변에 아래처럼 명시적으로 선별 블록을 포함한다. 블록이 없으면 Stop hook은 아무것도 저장하지 않는다.
|
|
268
|
+
|
|
269
|
+
````markdown
|
|
270
|
+
```llm-wiki-capture
|
|
271
|
+
title: MCP hook decision
|
|
272
|
+
tags: hooks, codex, claude
|
|
273
|
+
---
|
|
274
|
+
Claude Code와 Codex hook은 같은 `llm-wiki hook` command를 호출한다.
|
|
275
|
+
```
|
|
276
|
+
````
|
|
277
|
+
|
|
278
|
+
### 직접 HTTP daemon 설정
|
|
279
|
+
|
|
280
|
+
launchd/systemd/manual command로 daemon을 이미 관리한다면 HTTP URL config를 써도 된다.
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
/Users/m16khb/Workspace/llm-wiki/bin/llm-wiki serve \
|
|
284
|
+
--vault /Users/m16khb/Workspace/knowledge-base/llm-wiki
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Claude Code HTTP 예시는 `config/claude-code.http.mcp.json`, Codex HTTP 예시는 `config/codex.http.config.toml`에 있다.
|
|
139
288
|
|
|
140
289
|
```toml
|
|
141
290
|
[mcp_servers.llm-wiki]
|
|
@@ -144,7 +293,11 @@ startup_timeout_sec = 10
|
|
|
144
293
|
tool_timeout_sec = 60
|
|
145
294
|
```
|
|
146
295
|
|
|
147
|
-
|
|
296
|
+
### Direct stdio compatibility
|
|
297
|
+
|
|
298
|
+
`llm-wiki mcp`는 daemon을 거치지 않고 stdio MCP server가 직접 vault/service를 연다. stdio-only 환경의 비상/호환 경로이며, Claude Code와 Codex가 같은 daemon을 공유해야 하는 기본 운영에는 `mcp-autostart`를 사용한다.
|
|
299
|
+
|
|
300
|
+
npx wrapper는 기존 `bin/llm-wiki`가 있고 Go build input보다 최신이면 그대로 실행한다. bundled binary가 stale이거나 없으면 Go toolchain으로 CLI를 cache에 빌드한 뒤 실행한다. `LLM_WIKI_NPM_REBUILD=1`은 bundled/cache binary를 모두 무시하고 재빌드한다. MCP stdio 안전을 위해 wrapper 자체는 stdout에 진단을 쓰지 않는다. `--prefix /tmp`는 이 repo처럼 현재 프로젝트의 `package.json` 이름이 `@m16khb/llm-wiki`와 같을 때 npx가 local package로 오인하는 것을 피하기 위한 안전장치다.
|
|
148
301
|
|
|
149
302
|
## 개발 문서
|
|
150
303
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
package main
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"io"
|
|
6
|
+
"os"
|
|
7
|
+
"path/filepath"
|
|
8
|
+
"testing"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
func TestContextHookSuppressesTranscriptOutput(t *testing.T) {
|
|
12
|
+
vaultRoot := t.TempDir()
|
|
13
|
+
writeTestFile(t, vaultRoot, "20-wiki/concepts/llm-wiki-hooks.md", `---
|
|
14
|
+
title: LLM Wiki Hooks
|
|
15
|
+
type: concept
|
|
16
|
+
status: active
|
|
17
|
+
created: 2026-05-23
|
|
18
|
+
updated: 2026-05-23
|
|
19
|
+
tags: [llm-wiki, hooks]
|
|
20
|
+
domain: agent-memory
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# LLM Wiki Hooks
|
|
24
|
+
|
|
25
|
+
Hook context for llm-wiki.
|
|
26
|
+
`)
|
|
27
|
+
projectRoot := filepath.Join(t.TempDir(), "llm-wiki")
|
|
28
|
+
writeTestFile(t, projectRoot, "go.mod", "module github.com/m16khb/llm-wiki\n")
|
|
29
|
+
|
|
30
|
+
stdout := captureRunHook(t, []string{"--vault", vaultRoot, "--limit", "5"}, `{
|
|
31
|
+
"hook_event_name": "UserPromptSubmit",
|
|
32
|
+
"cwd": "`+projectRoot+`",
|
|
33
|
+
"prompt": "hook output"
|
|
34
|
+
}`)
|
|
35
|
+
|
|
36
|
+
var payload struct {
|
|
37
|
+
SuppressOutput bool `json:"suppressOutput"`
|
|
38
|
+
HookSpecificOutput struct {
|
|
39
|
+
HookEventName string `json:"hookEventName"`
|
|
40
|
+
AdditionalContext string `json:"additionalContext"`
|
|
41
|
+
} `json:"hookSpecificOutput"`
|
|
42
|
+
}
|
|
43
|
+
if err := json.Unmarshal([]byte(stdout), &payload); err != nil {
|
|
44
|
+
t.Fatalf("hook output is not valid JSON: %v\n%s", err, stdout)
|
|
45
|
+
}
|
|
46
|
+
if !payload.SuppressOutput {
|
|
47
|
+
t.Fatalf("expected context hook to suppress transcript output, got:\n%s", stdout)
|
|
48
|
+
}
|
|
49
|
+
if payload.HookSpecificOutput.HookEventName != "UserPromptSubmit" {
|
|
50
|
+
t.Fatalf("unexpected event name: %q", payload.HookSpecificOutput.HookEventName)
|
|
51
|
+
}
|
|
52
|
+
if payload.HookSpecificOutput.AdditionalContext == "" {
|
|
53
|
+
t.Fatalf("expected additionalContext to remain available for model injection")
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
func captureRunHook(t *testing.T, args []string, stdin string) string {
|
|
58
|
+
t.Helper()
|
|
59
|
+
|
|
60
|
+
oldStdin := os.Stdin
|
|
61
|
+
oldStdout := os.Stdout
|
|
62
|
+
defer func() {
|
|
63
|
+
os.Stdin = oldStdin
|
|
64
|
+
os.Stdout = oldStdout
|
|
65
|
+
}()
|
|
66
|
+
|
|
67
|
+
inRead, inWrite, err := os.Pipe()
|
|
68
|
+
if err != nil {
|
|
69
|
+
t.Fatalf("pipe stdin: %v", err)
|
|
70
|
+
}
|
|
71
|
+
if _, err := inWrite.WriteString(stdin); err != nil {
|
|
72
|
+
t.Fatalf("write stdin: %v", err)
|
|
73
|
+
}
|
|
74
|
+
if err := inWrite.Close(); err != nil {
|
|
75
|
+
t.Fatalf("close stdin writer: %v", err)
|
|
76
|
+
}
|
|
77
|
+
defer inRead.Close()
|
|
78
|
+
|
|
79
|
+
outRead, outWrite, err := os.Pipe()
|
|
80
|
+
if err != nil {
|
|
81
|
+
t.Fatalf("pipe stdout: %v", err)
|
|
82
|
+
}
|
|
83
|
+
defer outRead.Close()
|
|
84
|
+
|
|
85
|
+
os.Stdin = inRead
|
|
86
|
+
os.Stdout = outWrite
|
|
87
|
+
runHook(args)
|
|
88
|
+
if err := outWrite.Close(); err != nil {
|
|
89
|
+
t.Fatalf("close stdout writer: %v", err)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
data, err := io.ReadAll(outRead)
|
|
93
|
+
if err != nil {
|
|
94
|
+
t.Fatalf("read stdout: %v", err)
|
|
95
|
+
}
|
|
96
|
+
return string(data)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func writeTestFile(t *testing.T, root, rel, content string) {
|
|
100
|
+
t.Helper()
|
|
101
|
+
abs := filepath.Join(root, filepath.FromSlash(rel))
|
|
102
|
+
if err := os.MkdirAll(filepath.Dir(abs), 0o755); err != nil {
|
|
103
|
+
t.Fatalf("mkdir %s: %v", rel, err)
|
|
104
|
+
}
|
|
105
|
+
if err := os.WriteFile(abs, []byte(content), 0o644); err != nil {
|
|
106
|
+
t.Fatalf("write %s: %v", rel, err)
|
|
107
|
+
}
|
|
108
|
+
}
|