llm-wiki-kit 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,7 +11,7 @@ After the package is published to npm:
11
11
  ```bash
12
12
  npm install -g llm-wiki-kit
13
13
  llm-wiki install --workspace /apps --profile standard
14
- llm-wiki doctor
14
+ llm-wiki doctor --workspace /apps
15
15
  ```
16
16
 
17
17
  Restart Claude Code and Codex sessions after installation.
@@ -69,7 +69,7 @@ Use Claude Code or Codex normally.
69
69
 
70
70
  The installed hooks:
71
71
 
72
- - inject `wiki/memory.md`, `wiki/index.md`, and relevant wiki context at session start and prompt submit time
72
+ - inject `wiki/memory.md`, `wiki/index.md`, and relevant wiki context at session start, instructions loaded, prompt submit, and post-compact time
73
73
  - record redacted turn summaries
74
74
  - capture decision points, debugging findings, changed files, and verification notes
75
75
  - allow tool calls to proceed without secret/PII-based hook blocking
@@ -85,16 +85,18 @@ If you need to think about saving every answer manually, the setup has failed.
85
85
 
86
86
  Most users should not need these during daily Claude Code/Codex work. They exist for install, update, diagnostics, and agent-side maintenance.
87
87
 
88
- - Install/update: `llm-wiki install`, `llm-wiki update`, `llm-wiki projects`
88
+ - Install/update: `llm-wiki install`, `llm-wiki update`, `llm-wiki post-update`, `llm-wiki projects`
89
89
  - Diagnostics: `llm-wiki doctor`, `llm-wiki status`, `llm-wiki version`
90
90
  - Agent maintenance helpers: `llm-wiki context`, `llm-wiki lint`, `llm-wiki consolidate`
91
91
  - Cleanup: `llm-wiki uninstall`
92
92
 
93
- `llm-wiki status` is an offline consistency check. It reports the installed runtime version, hook targets, and whether the current workspace has the current managed templates applied.
93
+ `llm-wiki status` is an offline consistency check. It reports the installed runtime version, hook targets, whether the `llm-wiki` command on `PATH` resolves to the current runtime, whether the current workspace has the current managed templates applied, how many rules are auto-updateable, and how many managed-looking rules need agent cleanup.
94
94
 
95
- `llm-wiki update --check` is the online update check. It compares the installed package version with the npm registry without changing files.
95
+ `llm-wiki update --check [--to <version-or-tag>]` is the online update check. It compares the installed package version with the npm registry target without changing files, and reports an available update only when the target version is newer than the installed version.
96
96
 
97
- `llm-wiki update` upgrades the global npm package, reinstalls the hook entries, and patches only managed project files such as the marked `AGENTS.md` policy block and generated `llm-wiki/AGENTS.md`/procedure files. Existing wiki content is not overwritten.
97
+ `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.
98
+
99
+ `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.
98
100
 
99
101
  `llm-wiki context "<query>"` prints the same layered context used by hooks. It is mainly for debugging what Codex/Claude Code will see; daily use should rely on hook injection.
100
102
 
@@ -102,10 +104,12 @@ Most users should not need these during daily Claude Code/Codex work. They exist
102
104
 
103
105
  `llm-wiki consolidate` refreshes only generated marker blocks in `wiki/memory.md` and `wiki/index.md`. It is an agent maintenance helper, not a command users should run after every turn.
104
106
 
105
- `llm-wiki projects --workspace /apps` lists project roots that already have `llm-wiki-kit` state and shows the update commands to run. `llm-wiki update --all --workspace /apps` updates the global runtime once, then reapplies managed templates across every discovered project root under `/apps`.
107
+ `llm-wiki projects --workspace /apps` lists project roots that already have `llm-wiki-kit` state or an older `llm-wiki/wiki/index.md`, and shows the update commands to run. `llm-wiki update --workspace /apps` updates the global runtime once, then reapplies managed templates across every known or discovered project root under `/apps`.
106
108
 
107
109
  After a plain `npm install -g llm-wiki-kit@latest`, existing hooks keep working when they already point at the global npm package path. The next `SessionStart`/`InstructionsLoaded` hook automatically reapplies safe managed template updates for the active project root. Clearly generated older `llm-wiki/AGENTS.md` and procedure files are refreshed even when old state is missing; user-edited files are not overwritten and are surfaced to the active agent as cleanup work. If hooks point at a source checkout or stale shim, run `llm-wiki post-update --workspace <project>` or `llm-wiki install --workspace <project>` once to reconnect them.
108
110
 
111
+ `llm-wiki install` no longer creates a user-local `~/.local/bin/llm-wiki` shim when an npm/nvm global `llm-wiki` command already resolves to the current runtime. If an older kit-managed local shim is shadowing that npm command, install backs it up and removes it.
112
+
109
113
  On PCs that use nvm or user-local npm, prefer the non-sudo global install and make sure the `llm-wiki` command resolves to that npm package:
110
114
 
111
115
  ```bash
@@ -116,10 +120,10 @@ llm-wiki version
116
120
  llm-wiki status
117
121
  ```
118
122
 
119
- If `which -a llm-wiki` shows a stale `~/.local/bin/llm-wiki` before the nvm/npm global binary, remove only that file, not the whole `~/.local/bin` directory:
123
+ If `which -a llm-wiki` shows a stale `~/.local/bin/llm-wiki` before the nvm/npm global binary, run install once so the kit can remove a managed shadowing shim or report an unmanaged one:
120
124
 
121
125
  ```bash
122
- rm -f "$HOME/.local/bin/llm-wiki"
126
+ llm-wiki install --workspace /path/to/project --profile standard
123
127
  hash -r
124
128
  llm-wiki version
125
129
  ```
package/docs/concepts.md CHANGED
@@ -18,9 +18,9 @@ The important behavior is a loop:
18
18
  2. `memory.md`, `index.md`, and relevant wiki context are injected automatically.
19
19
  3. The user works normally; no extra command loop is required.
20
20
  4. Hooks gather redacted prompt/tool/result summaries.
21
- 5. At stop/session end, useful knowledge is written back to Markdown.
22
- 6. Future sessions start from the improved wiki instead of relying on long chat history.
23
- 7. When reusable knowledge appears, the active Claude Code/Codex agent folds it into existing durable wiki pages instead of leaving everything as one-off Q&A.
21
+ 5. At stop/session end, hooks append redacted live Q&A and create query or decision candidates when the turn has enough captured context.
22
+ 6. When reusable knowledge appears, the active Claude Code/Codex agent folds it into existing durable wiki pages instead of leaving everything as one-off Q&A.
23
+ 7. Future sessions start from the improved wiki instead of relying on long chat history.
24
24
 
25
25
  The kit is a template/runtime repository. It must not centralize project wiki contents.
26
26
 
@@ -38,15 +38,19 @@ 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.
41
+ 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.
42
42
 
43
- At `SessionStart`/`InstructionsLoaded`, the hook injects `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`, recent log context, and operating rules. At `UserPromptSubmit`, it searches wiki pages with MiniSearch or substring fallback, expands one-hop wikilinks, redacts context fields, and injects the smallest useful context set.
43
+ At `SessionStart`/`InstructionsLoaded`, the hook first attempts a safe managed-template refresh, then injects `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`, recent log context, operating rules, and any maintenance note for outdated or customized managed rules. At `UserPromptSubmit`, it searches wiki pages with MiniSearch or substring fallback, expands one-hop wikilinks, redacts context fields, and injects the smallest useful context set.
44
+
45
+ `PostToolUse` and `PostToolBatch` record redacted tool summaries in the same turn buffer. `PreCompact` records a compaction note, and `PostCompact` records the note and returns fresh wiki context. `SubagentStop`, `Stop`, and `SessionEnd` append live Q&A and create `wiki/queries/` candidates when a captured question exists. A `wiki/decisions/` page is created only when the captured turn looks decision-like. `Stop` and `SessionEnd` clear the per-session turn buffer after recording; `SubagentStop` does not.
46
+
47
+ Set `LLM_WIKI_KIT_AUTO_PROJECT_UPDATE=0` only while diagnosing automatic managed-template refresh behavior.
44
48
 
45
49
  After installation or update, run:
46
50
 
47
51
  ```bash
48
52
  llm-wiki status --workspace /path/to/project
49
- llm-wiki doctor
53
+ llm-wiki doctor --workspace /path/to/project
50
54
  ```
51
55
 
52
56
  Restart Claude Code so it reloads hook settings.
@@ -29,17 +29,23 @@ Handled events:
29
29
 
30
30
  Expected behavior:
31
31
 
32
- - `SessionStart` injects `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`, recent log context, and operating rules.
32
+ - `SessionStart` first attempts a safe managed-template refresh, then injects `llm-wiki/wiki/memory.md`, `llm-wiki/wiki/index.md`, recent log context, operating rules, and any maintenance note for outdated or customized managed rules.
33
33
  - `UserPromptSubmit` searches project wiki pages with MiniSearch or substring fallback, expands one-hop wikilinks, redacts context fields, and injects the smallest useful context set.
34
34
  - `PreToolUse` records redacted tool summaries without blocking tool calls.
35
35
  - `PostToolUse` records redacted tool summaries in a turn buffer.
36
- - `Stop` writes live Q&A and durable wiki pages.
36
+ - `PreCompact` records a compaction note; `PostCompact` records the note and returns fresh wiki context.
37
+ - `SubagentStop` and `Stop` append live Q&A and create `wiki/queries/` candidates when a captured question exists. A `wiki/decisions/` page is created only when the captured turn looks decision-like.
38
+ - `Stop` clears the per-session turn buffer after recording. `SubagentStop` leaves the parent turn buffer available for the final stop event.
39
+
40
+ 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.
41
+
42
+ Set `LLM_WIKI_KIT_AUTO_PROJECT_UPDATE=0` only while diagnosing automatic managed-template refresh behavior.
37
43
 
38
44
  Run these after install:
39
45
 
40
46
  ```bash
41
47
  llm-wiki status --workspace /path/to/project
42
- llm-wiki doctor
48
+ llm-wiki doctor --workspace /path/to/project
43
49
  ```
44
50
 
45
51
  If Codex reports untrusted hooks, trust the hook through Codex's own trust flow or run a trusted automation profile that explicitly accepts the hook source. Restart Codex after hook installation or update.
@@ -41,7 +41,9 @@ Avoid mixing root-owned and user-local installs unless you intentionally choose
41
41
 
42
42
  The installer:
43
43
 
44
- - creates `~/.local/bin/llm-wiki`
44
+ - uses an npm/nvm global `llm-wiki` command when it already resolves to the current runtime
45
+ - removes an older kit-managed `~/.local/bin/llm-wiki` shim when it shadows that npm/nvm command
46
+ - creates a user-local shim only when no `PATH` command points at the current runtime, such as source checkout development or user-local fallback installs
45
47
  - backs up existing Codex/Claude settings before editing
46
48
  - merges hook entries without removing existing hooks
47
49
  - bootstraps the workspace `llm-wiki/`
@@ -73,11 +75,14 @@ Daily work should happen through Claude Code/Codex. These commands are for maint
73
75
  ```bash
74
76
  llm-wiki version
75
77
  llm-wiki status --workspace /path/to/project
78
+ llm-wiki doctor --workspace /path/to/project
76
79
  llm-wiki projects --workspace /path/to/search-root
77
- llm-wiki update --check --workspace /path/to/project
78
- llm-wiki update --all --workspace /path/to/search-root
80
+ llm-wiki update --check --workspace /path/to/project [--to <version-or-tag>]
81
+ llm-wiki update --workspace /path/to/search-root
82
+ llm-wiki update --current-only --workspace /path/to/project
79
83
  llm-wiki update --dry-run --workspace /path/to/project
80
- llm-wiki update --workspace /path/to/project
84
+ llm-wiki post-update --workspace /path/to/project
85
+ llm-wiki post-update --all --workspace /path/to/search-root
81
86
  llm-wiki context "search phrase" --workspace /path/to/project
82
87
  llm-wiki lint --workspace /path/to/project
83
88
  llm-wiki consolidate --workspace /path/to/project
@@ -86,25 +91,30 @@ llm-wiki consolidate --workspace /path/to/project
86
91
  `status` is offline and answers whether the local installation is internally consistent:
87
92
 
88
93
  - runtime version and install source
94
+ - whether the `llm-wiki` command on `PATH` resolves to the current runtime
89
95
  - Codex/Claude hook entries pointing at the current runtime
90
96
  - project template state from `llm-wiki/.kit-state.json`
91
97
  - current managed file hashes
98
+ - auto-updateable managed rules and managed-looking rules that need agent cleanup
92
99
 
93
- `update --check` is online and asks npm whether a newer package version exists.
100
+ `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.
94
101
 
95
- `projects --workspace <search-root>` lists discovered project roots that have `llm-wiki/.kit-state.json`, reports whether their managed templates are current, and prints the update commands for the search root.
102
+ `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.
96
103
 
97
104
  `update` applies changes explicitly only when the user runs it:
98
105
 
99
- - runs `npm install -g llm-wiki-kit@<target>`
106
+ - runs `npm install -g llm-wiki-kit@<target>` only when the registry target is newer than the installed runtime
107
+ - skips npm installation when the installed runtime already satisfies the target, then still runs post-update maintenance
100
108
  - restarts into the updated binary for `post-update`
101
109
  - reinstalls hook entries without duplicating them
102
- - patches only managed project files
110
+ - patches only managed project files across known or discovered project roots
103
111
  - backs up changed files under `~/.local/share/llm-wiki-kit/backups/`
104
112
 
105
- `update --all --workspace <search-root>` performs the npm runtime update once, then reapplies managed templates to every discovered project root under the search root.
113
+ By default, `update --workspace <search-root>` performs the npm runtime update once, then reapplies managed templates to every known or discovered project root under the search root. Use `update --current-only --workspace <project>` to limit template reapplication to one project.
106
114
 
107
- After a plain `npm install -g llm-wiki-kit@latest`, existing hooks keep working when they already point at the global npm package path. On the next `SessionStart` or `InstructionsLoaded`, the runtime automatically reapplies safe managed template updates for the active project root. If hooks point at a source checkout or stale shim, run `llm-wiki post-update --workspace <project>` or `llm-wiki install --workspace <project>` once to reconnect them.
115
+ `post-update --workspace <project>` skips npm installation and reapplies the current runtime's hook entries plus safe managed template updates. `post-update --all --workspace <search-root>` does the same template reapplication across discovered project roots.
116
+
117
+ After a plain `npm install -g llm-wiki-kit@latest`, existing hooks keep working when they already point at the global npm package path. On the next `SessionStart` or `InstructionsLoaded`, the runtime automatically reapplies safe managed template updates for the active project root. If hooks point at a source checkout or stale shim, run `llm-wiki post-update --workspace <project>` or `llm-wiki install --workspace <project>` once to reconnect them. `install` will avoid recreating a local shim when an npm/nvm command already points at the current runtime.
108
118
 
109
119
  ## Context And Wiki Maintenance
110
120
 
@@ -153,7 +163,7 @@ Broken links, invalid source IDs, and secret-like content are errors and return
153
163
 
154
164
  Agents may run `consolidate` after meaningful wiki growth. Users should not need to run it after every turn.
155
165
 
156
- When a new runtime sees an older project, `SessionStart`/`InstructionsLoaded` automatically reapplies safe managed template updates. Files that are clearly generated by older kit versions are refreshed. Files that look user-edited are preserved and surfaced to the active agent as cleanup context instead of being overwritten.
166
+ When a new runtime sees an older project, `SessionStart`/`InstructionsLoaded` automatically reapplies safe managed template updates. Files that are clearly generated by older kit versions are refreshed. Files that look user-edited are preserved and surfaced to the active agent as cleanup context instead of being overwritten. Set `LLM_WIKI_KIT_AUTO_PROJECT_UPDATE=0` only while diagnosing automatic template refresh behavior.
157
167
 
158
168
  ## Updating User-Local Or nvm Installs
159
169
 
@@ -174,10 +184,10 @@ npm root -g
174
184
  node "$(npm root -g)/llm-wiki-kit/bin/llm-wiki.js" version
175
185
  ```
176
186
 
177
- When `which -a llm-wiki` shows `~/.local/bin/llm-wiki` before the nvm global binary, remove only that stale command file:
187
+ When `which -a llm-wiki` shows `~/.local/bin/llm-wiki` before the nvm global binary, run install once. The kit removes an older managed shadowing shim when the npm/nvm command already points at the current runtime, and reports unmanaged local commands through `status`/`doctor`.
178
188
 
179
189
  ```bash
180
- rm -f "$HOME/.local/bin/llm-wiki"
190
+ llm-wiki install --workspace /path/to/project --profile standard
181
191
  hash -r
182
192
  llm-wiki version
183
193
  ```
@@ -195,8 +205,10 @@ npm uninstall -g llm-wiki-kit
195
205
  Managed project files are conservative:
196
206
 
197
207
  - root `AGENTS.md` is patched only inside the `llm-wiki-kit` marker block
198
- - `llm-wiki/AGENTS.md` is replaced only when its recorded hash still matches or the whole file exactly matches the current generated template
199
- - generated procedures are replaced only when their recorded hash still matches or the whole file exactly matches the current generated template
208
+ - `llm-wiki/AGENTS.md` is replaced only when its recorded hash still matches, the whole file exactly matches the current generated template, or the whole file exactly matches a known legacy generated template
209
+ - generated procedures are replaced only when their recorded hash still matches, the whole file exactly matches the current generated template, or the whole file exactly matches a known legacy generated template
210
+ - missing generated `llm-wiki/AGENTS.md` and procedure files are restored when the project wiki tree exists
211
+ - customized managed-looking files and malformed root policy markers are not overwritten; `status`, `lint`, and injected maintenance context surface them for agent cleanup
200
212
  - `llm-wiki/wiki/memory.md` is created if missing but never overwritten by template update once it exists
201
213
  - `llm-wiki/wiki/index.md` and existing wiki pages are not overwritten by runtime update; `llm-wiki consolidate` updates only its generated marker block
202
214
 
@@ -220,7 +232,7 @@ npm view llm-wiki-kit version
220
232
  npm install -g llm-wiki-kit
221
233
  llm-wiki install --workspace /apps --profile standard
222
234
  llm-wiki status --workspace /apps
223
- llm-wiki doctor
235
+ llm-wiki doctor --workspace /apps
224
236
  llm-wiki update --check --workspace /apps
225
237
  ```
226
238
 
@@ -5,12 +5,21 @@
5
5
  Run:
6
6
 
7
7
  ```bash
8
- llm-wiki doctor
9
- llm-wiki status
8
+ llm-wiki doctor --workspace /path/to/project
9
+ llm-wiki status --workspace /path/to/project
10
10
  ```
11
11
 
12
12
  Then restart Claude Code or Codex.
13
13
 
14
+ `doctor` and `status` report whether hooks point at the current runtime, whether the `llm-wiki` command on `PATH` resolves to that runtime, and whether project managed templates are current. If hooks or shims point at a source checkout or stale path, run one of these once:
15
+
16
+ ```bash
17
+ llm-wiki post-update --workspace /path/to/project
18
+ llm-wiki install --workspace /path/to/project --profile standard
19
+ ```
20
+
21
+ Use `LLM_WIKI_KIT_AUTO_PROJECT_UPDATE=0` only while diagnosing automatic managed-template refresh behavior.
22
+
14
23
  ## Is An Update Available?
15
24
 
16
25
  Use the offline check first:
@@ -85,30 +94,32 @@ npm root -g
85
94
  node "$(npm root -g)/llm-wiki-kit/bin/llm-wiki.js" version
86
95
  ```
87
96
 
88
- If the direct `node "$(npm root -g)/llm-wiki-kit/bin/llm-wiki.js" version` command prints the latest version but plain `llm-wiki` is old, fix `PATH` or replace the stale shim:
97
+ If the direct `node "$(npm root -g)/llm-wiki-kit/bin/llm-wiki.js" version` command prints the latest version but plain `llm-wiki` is old, reconnect through the installer:
89
98
 
90
99
  ```bash
91
- mkdir -p "$HOME/.local/bin"
92
- ln -sfn "$(npm root -g)/llm-wiki-kit/bin/llm-wiki.js" "$HOME/.local/bin/llm-wiki"
93
- export PATH="$HOME/.local/bin:$PATH"
100
+ llm-wiki install --workspace /path/to/project --profile standard
94
101
  hash -r
95
- llm-wiki status
102
+ llm-wiki status --workspace /path/to/project
96
103
  ```
97
104
 
105
+ After a manual `sudo npm install -g llm-wiki-kit@latest`, a normal-user `llm-wiki update --workspace <search-root>` can be used to reapply hooks and managed templates. When the installed runtime already matches the registry target, `update` skips the npm install step and runs only post-update maintenance.
106
+
98
107
  If `readlink -f "$(command -v llm-wiki)"` points at a repository checkout such as `/mnt/d/dev_proj/llm-wiki-kit/bin/llm-wiki.js`, `npm install -g` is not updating that checkout. Either switch the shim to the npm package as above, or update the checkout itself with `git pull` and run it intentionally as source.
99
108
 
100
- If `which -a llm-wiki` shows both `~/.local/bin/llm-wiki` and an nvm path such as `~/.nvm/versions/node/v20.20.2/bin/llm-wiki`, the `~/.local/bin` entry usually wins because it appears earlier on `PATH`. If the `~/.local/bin/llm-wiki` file is stale, remove only that file and let the nvm npm global command take over:
109
+ Running `llm-wiki post-update --workspace /path/to/project` or `llm-wiki install --workspace /path/to/project --profile standard` also reconnects hook entries to the current runtime. `install` uses an npm/nvm global command when it already resolves to the current runtime, removes an older kit-managed local shim when it shadows that command, and creates a local shim only when no `PATH` command points at the current runtime.
110
+
111
+ If `which -a llm-wiki` shows both `~/.local/bin/llm-wiki` and an nvm path such as `~/.nvm/versions/node/v20.20.2/bin/llm-wiki`, the `~/.local/bin` entry usually wins because it appears earlier on `PATH`. Let install handle managed shims first:
101
112
 
102
113
  ```bash
103
- rm -f "$HOME/.local/bin/llm-wiki"
114
+ llm-wiki install --workspace /path/to/project --profile standard
104
115
  hash -r
105
116
  which -a llm-wiki
106
117
  readlink -f "$(command -v llm-wiki)"
107
118
  llm-wiki version
108
- llm-wiki status
119
+ llm-wiki status --workspace /path/to/project
109
120
  ```
110
121
 
111
- Do not remove the entire `~/.local/bin` directory; it may contain unrelated commands. If the nvm command is missing after removing the stale file, reinstall the package in the active nvm Node version:
122
+ Do not remove the entire `~/.local/bin` directory; it may contain unrelated commands. If `status` or `doctor` reports an unmanaged local command that still shadows npm, inspect that one file before removing it. If the nvm command is missing, reinstall the package in the active nvm Node version:
112
123
 
113
124
  ```bash
114
125
  npm uninstall -g llm-wiki-kit
@@ -170,6 +181,8 @@ Check:
170
181
 
171
182
  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.
172
183
 
184
+ 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.
185
+
173
186
  ## Duplicate Pages Appear
174
187
 
175
188
  Run a manual review:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-wiki-kit",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Hook-first living LLM Wiki runtime for Codex and Claude Code.",
5
5
  "type": "module",
6
6
  "files": [
package/src/cli.js CHANGED
@@ -4,7 +4,7 @@ import { handleHook } from './hook.js';
4
4
  import { install, status, uninstall } from './install.js';
5
5
  import { bootstrapProject } from './project.js';
6
6
  import { inspectProjectState } from './project-state.js';
7
- import { commandForProject, knownProjectRoots } from './projects.js';
7
+ import { commandForProject, knownProjectRoots, recordProject } from './projects.js';
8
8
  import { formatDoctor, runDoctor } from './doctor.js';
9
9
  import { migrate } from './migrate.js';
10
10
  import { postUpdate, update } from './update.js';
@@ -54,6 +54,8 @@ function parseOptions(args) {
54
54
  options.replaceHooks = true;
55
55
  } else if (arg === '--all') {
56
56
  options.all = true;
57
+ } else if (arg === '--current-only') {
58
+ options.currentOnly = true;
57
59
  } else if (arg === '--json') {
58
60
  options.json = true;
59
61
  } else {
@@ -82,8 +84,9 @@ export async function runCli(args) {
82
84
 
83
85
  Usage:
84
86
  llm-wiki install --workspace /apps [--profile standard]
85
- llm-wiki update --workspace <project> [--check|--dry-run|--to <version-or-tag>]
86
- llm-wiki doctor
87
+ llm-wiki update --workspace <project-or-search-root> [--check|--dry-run|--current-only|--to <version-or-tag>]
88
+ llm-wiki post-update --workspace <project> [--all]
89
+ llm-wiki doctor --workspace <project>
87
90
  llm-wiki projects --workspace /apps
88
91
  llm-wiki status
89
92
  llm-wiki version
@@ -104,7 +107,9 @@ Usage:
104
107
  printJsonOrText(result, options, (value) => [
105
108
  'llm-wiki-kit installed',
106
109
  `- workspace: ${value.workspace}`,
107
- `- bin: ${value.localBin}`,
110
+ `- runtime bin: ${value.binPath}`,
111
+ `- command: ${value.commandPath || 'not found on PATH'}`,
112
+ `- local shim: ${value.localBin} (${value.localBinAction})`,
108
113
  `- changed hooks: ${value.changed.length ? value.changed.join(', ') : 'none (already installed)'}`,
109
114
  'Restart Codex/Claude Code sessions so new hooks are loaded.',
110
115
  ].join('\n'));
@@ -149,7 +154,7 @@ Usage:
149
154
  }
150
155
 
151
156
  if (command === 'doctor') {
152
- const result = await runDoctor();
157
+ const result = await runDoctor(options);
153
158
  printJsonOrText(result, options, formatDoctor);
154
159
  if (!result.ok) process.exitCode = 1;
155
160
  return;
@@ -157,7 +162,9 @@ Usage:
157
162
 
158
163
  if (command === 'bootstrap') {
159
164
  const projectRoot = resolve(options.workspace || process.cwd());
160
- printJsonOrText(await bootstrapProject(projectRoot, options), options);
165
+ const result = await bootstrapProject(projectRoot, options);
166
+ await recordProject(projectRoot, 'bootstrap').catch(() => {});
167
+ printJsonOrText(result, options);
161
168
  return;
162
169
  }
163
170
 
@@ -232,6 +239,7 @@ function formatStatus(value) {
232
239
  `- bin: ${value.binPath}`,
233
240
  `- command: ${value.commandPath || 'not found'}`,
234
241
  `- command matches runtime: ${value.commandMatchesRuntime ? 'yes' : 'no'}`,
242
+ `- local shim: ${value.localBin?.path || 'unknown'} (${value.localBin?.exists ? (value.localBin.managed ? 'managed' : 'unmanaged') : 'absent'}${value.localBin?.matchesRuntime ? ', current' : ''})`,
235
243
  `- hooks current: ${value.hooksCurrent ? 'yes' : 'no'}`,
236
244
  `- codex hook: ${value.codexInstalled ? 'current' : 'missing/outdated'}`,
237
245
  `- claude hook: ${value.claudeInstalled ? 'current' : 'missing/outdated'}`,
@@ -253,13 +261,22 @@ function formatUpdate(value) {
253
261
  ].join('\n');
254
262
  }
255
263
  if (value.mode === 'dry-run') {
264
+ const projects = Array.isArray(value.projects) ? value.projects : [];
265
+ const changed = projects.length > 0
266
+ ? projects.reduce((sum, item) => sum + (item.project?.changed?.length || 0), 0)
267
+ : (value.project?.changed?.length || 0);
268
+ const skipped = projects.length > 0
269
+ ? projects.reduce((sum, item) => sum + (item.project?.skipped?.length || 0), 0)
270
+ : (value.project?.skipped?.length || 0);
256
271
  return [
257
272
  'llm-wiki-kit update dry-run',
258
273
  `- installed: ${value.installedVersion}`,
259
274
  `- latest: ${value.latestVersion}`,
260
275
  `- update available: ${value.updateAvailable ? 'yes' : 'no'}`,
261
- `- project template changes: ${value.project?.changed?.length || 0}`,
262
- `- project template skipped: ${value.project?.skipped?.length || 0}`,
276
+ `- scope: ${value.scope || 'current'}`,
277
+ ...(projects.length > 0 ? [`- projects checked: ${projects.length}`] : []),
278
+ `- project template changes: ${changed}`,
279
+ `- project template skipped: ${skipped}`,
263
280
  ].join('\n');
264
281
  }
265
282
  return [
@@ -267,6 +284,7 @@ function formatUpdate(value) {
267
284
  `- workspace: ${value.workspace}`,
268
285
  `- before: ${value.before}`,
269
286
  `- target: ${value.target}`,
287
+ `- scope: ${value.scope || 'current'}`,
270
288
  ...(value.projects ? [`- projects updated: ${value.projects.length}`] : []),
271
289
  ].join('\n');
272
290
  }
@@ -304,7 +322,8 @@ function formatProjects(value) {
304
322
  lines.push(`- ${project.workspace}: ${status}`);
305
323
  }
306
324
  }
307
- lines.push('', 'To update every listed project root:', commandForProject('update --all', value.workspace));
325
+ lines.push('', 'To update every listed project root:', commandForProject('update', value.workspace));
326
+ lines.push('To update only the current project root:', commandForProject('update --current-only', value.workspace));
308
327
  lines.push('To reapply templates without npm install:', commandForProject('post-update --all', value.workspace));
309
328
  return lines.join('\n');
310
329
  }
package/src/doctor.js CHANGED
@@ -9,27 +9,27 @@ function nodeMajor() {
9
9
  return Number.parseInt(process.versions.node.split('.')[0], 10);
10
10
  }
11
11
 
12
- export async function runDoctor() {
12
+ export async function runDoctor(options = {}) {
13
13
  const checks = [];
14
- const add = (name, ok, detail = '') => checks.push({ name, ok, detail });
15
- const stat = await status();
14
+ const add = (id, name, ok, detail = '') => checks.push({ id, name, ok, detail });
15
+ const stat = await status(options);
16
16
 
17
- add('node >= 20', nodeMajor() >= 20, process.version);
18
- add('runtime version detected', Boolean(stat.runtimeVersion), stat.runtimeVersion || 'unknown');
19
- add('llm-wiki bin exists', await exists(stat.binPath), stat.binPath);
20
- add('runtime installed from npm', stat.installSource === 'npm', `${stat.installSource}; npm install -g does not update source checkouts`);
21
- add('llm-wiki command resolves to current runtime', stat.commandMatchesRuntime, stat.commandPath ? `command=${stat.commandPath}; runtime=${stat.binPath}` : 'command not found on PATH');
22
- add('Codex hook installed', stat.codexInstalled, stat.codexHooksPath);
23
- add('Claude hook installed', stat.claudeInstalled, stat.claudeSettingsPath);
24
- add('project templates current', stat.project.managedFilesCurrent, stat.project.statePath);
25
- add('codex command available', spawnSync('codex', ['--version'], { encoding: 'utf8' }).status === 0, 'codex --version');
26
- add('claude command available', spawnSync('claude', ['--version'], { encoding: 'utf8' }).status === 0, 'claude --version');
27
- add('state directory writable', await canWrite(join(kitDataDir(), '.doctor')), kitDataDir());
28
- add('docs present', await docsPresent(), 'README.md and docs/');
29
- add('sample hook roundtrip', await sampleHookRoundtrip(stat.binPath), 'UserPromptSubmit fixture');
17
+ add('node', 'node >= 20', nodeMajor() >= 20, process.version);
18
+ add('runtime-version', 'runtime version detected', Boolean(stat.runtimeVersion), stat.runtimeVersion || 'unknown');
19
+ add('runtime-bin', 'llm-wiki bin exists', await exists(stat.binPath), stat.binPath);
20
+ add('install-source', 'runtime installed from npm', stat.installSource === 'npm', `${stat.installSource}; npm install -g does not update source checkouts`);
21
+ add('command-path', 'llm-wiki command resolves to current runtime', stat.commandMatchesRuntime, stat.commandPath ? `command=${stat.commandPath}; runtime=${stat.binPath}` : 'command not found on PATH');
22
+ add('codex-hook', 'Codex hook installed', stat.codexInstalled, stat.codexHooksPath);
23
+ add('claude-hook', 'Claude hook installed', stat.claudeInstalled, stat.claudeSettingsPath);
24
+ add('project-templates', 'project templates current', stat.project.managedFilesCurrent, stat.project.statePath);
25
+ add('codex-command', 'codex command available', spawnSync('codex', ['--version'], { encoding: 'utf8' }).status === 0, 'codex --version');
26
+ add('claude-command', 'claude command available', spawnSync('claude', ['--version'], { encoding: 'utf8' }).status === 0, 'claude --version');
27
+ add('state-writable', 'state directory writable', await canWrite(join(kitDataDir(), '.doctor')), kitDataDir());
28
+ add('docs', 'docs present', await docsPresent(), 'README.md and docs/');
29
+ add('sample-hook', 'sample hook roundtrip', await sampleHookRoundtrip(stat.binPath), 'UserPromptSubmit fixture');
30
30
 
31
31
  const allOk = checks.every((check) => check.ok);
32
- return { ok: allOk, checks };
32
+ return { ok: allOk, checks, workspace: stat.workspace, status: stat };
33
33
  }
34
34
 
35
35
  async function docsPresent() {
@@ -84,8 +84,38 @@ export function formatDoctor(result) {
84
84
  lines.push(`- ${check.ok ? 'PASS' : 'WARN'} ${check.name}${check.detail ? ` (${check.detail})` : ''}`);
85
85
  }
86
86
  if (!result.ok) {
87
- lines.push('');
88
- lines.push('Run ./install.sh --workspace /apps --profile standard to install hooks, then restart Codex/Claude Code sessions.');
87
+ const remediation = doctorRemediation(result);
88
+ if (remediation.length > 0) {
89
+ lines.push('', 'Suggested fix:');
90
+ for (const item of remediation) lines.push(`- ${item}`);
91
+ }
89
92
  }
90
93
  return lines.join('\n');
91
94
  }
95
+
96
+ function failed(result, id) {
97
+ return (result.checks || []).some((check) => check.id === id && !check.ok);
98
+ }
99
+
100
+ function doctorRemediation(result) {
101
+ const workspace = result.workspace || process.cwd();
102
+ const suggestions = [];
103
+ if (failed(result, 'install-source')) {
104
+ suggestions.push(`normal install: npm install -g llm-wiki-kit@latest && llm-wiki install --workspace ${workspace} --profile standard`);
105
+ suggestions.push(`source checkout development: ./install.sh --workspace ${workspace} --profile standard`);
106
+ }
107
+ if (failed(result, 'command-path')) {
108
+ const local = result.status?.localBin;
109
+ if (result.status?.installSource === 'npm' && local?.exists && local.managed) {
110
+ suggestions.push(`remove stale managed local shim if it still shadows npm: rm -f ${local.path} && hash -r`);
111
+ }
112
+ suggestions.push(`reconnect command and hooks: llm-wiki install --workspace ${workspace} --profile standard`);
113
+ }
114
+ if (failed(result, 'codex-hook') || failed(result, 'claude-hook')) {
115
+ suggestions.push(`install hooks: llm-wiki install --workspace ${workspace} --profile standard, then restart Codex/Claude Code sessions`);
116
+ }
117
+ if (failed(result, 'project-templates')) {
118
+ suggestions.push(`refresh managed project templates: llm-wiki post-update --workspace ${workspace}`);
119
+ }
120
+ return [...new Set(suggestions)];
121
+ }
package/src/install.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { realpathSync } from 'fs';
2
- import { chmod } from 'fs/promises';
2
+ import { chmod, lstat, readlink, unlink } from 'fs/promises';
3
3
  import { spawnSync } from 'child_process';
4
4
  import { join, resolve } from 'path';
5
5
  import { CLAUDE_EVENTS, CODEX_EVENTS, KIT_NAME } from './constants.js';
6
- import { backupFile, ensureDir, exists, homeDir, readJson, safeSymlink, writeJson } from './fs-utils.js';
6
+ import { backupFile, exists, homeDir, readJson, safeSymlink, writeJson } from './fs-utils.js';
7
7
  import { inspectProjectState } from './project-state.js';
8
8
  import { bootstrapProject } from './project.js';
9
9
  import { recordProject } from './projects.js';
@@ -34,6 +34,91 @@ function realpathOrOriginal(path) {
34
34
  }
35
35
  }
36
36
 
37
+ function sameResolvedPath(left, right) {
38
+ const resolvedLeft = realpathOrOriginal(left);
39
+ const resolvedRight = realpathOrOriginal(right);
40
+ return Boolean(resolvedLeft && resolvedRight && resolvedLeft === resolvedRight);
41
+ }
42
+
43
+ function samePath(left, right) {
44
+ if (!left || !right) return false;
45
+ return resolve(left) === resolve(right);
46
+ }
47
+
48
+ function isKitPath(path) {
49
+ return String(path || '').replace(/\\/g, '/').includes('/llm-wiki-kit/');
50
+ }
51
+
52
+ async function inspectLocalBin(localBinPath) {
53
+ try {
54
+ const stat = await lstat(localBinPath);
55
+ const symlink = stat.isSymbolicLink();
56
+ const target = symlink ? await readlink(localBinPath).catch(() => null) : null;
57
+ const resolved = realpathOrOriginal(localBinPath);
58
+ return {
59
+ path: localBinPath,
60
+ exists: true,
61
+ symlink,
62
+ target,
63
+ resolved,
64
+ managed: symlink && (isKitPath(target) || isKitPath(resolved)),
65
+ matchesRuntime: sameResolvedPath(localBinPath, binPath),
66
+ };
67
+ } catch {
68
+ return {
69
+ path: localBinPath,
70
+ exists: false,
71
+ symlink: false,
72
+ target: null,
73
+ resolved: null,
74
+ managed: false,
75
+ matchesRuntime: false,
76
+ };
77
+ }
78
+ }
79
+
80
+ async function reconcileLocalBin(localBinPath) {
81
+ const commandPathsBefore = llmWikiCommandPaths();
82
+ const localBefore = await inspectLocalBin(localBinPath);
83
+ const alternateRuntimeCommand = commandPathsBefore.some((path) => (
84
+ !samePath(path, localBinPath) && sameResolvedPath(path, binPath)
85
+ ));
86
+ let action = 'kept';
87
+
88
+ if (alternateRuntimeCommand) {
89
+ if (localBefore.exists && localBefore.managed) {
90
+ await backupFile(localBinPath, 'local-bin-llm-wiki');
91
+ await unlink(localBinPath).catch(() => {});
92
+ action = 'removed-shadowing-shim';
93
+ } else if (localBefore.exists) {
94
+ action = 'left-unmanaged-local-bin';
95
+ } else {
96
+ action = 'skipped-npm-command-available';
97
+ }
98
+ } else if (localBefore.matchesRuntime) {
99
+ action = 'kept-current-shim';
100
+ } else {
101
+ if (localBefore.exists) {
102
+ await backupFile(localBinPath, 'local-bin-llm-wiki');
103
+ action = 'replaced-local-shim';
104
+ } else {
105
+ action = 'created-local-shim';
106
+ }
107
+ await safeSymlink(binPath, localBinPath);
108
+ }
109
+
110
+ const commandPathsAfter = llmWikiCommandPaths();
111
+ const localAfter = await inspectLocalBin(localBinPath);
112
+ return {
113
+ ...localAfter,
114
+ action,
115
+ alternateRuntimeCommand,
116
+ commandPathsBefore,
117
+ commandPathsAfter,
118
+ commandPath: commandPathsAfter[0] || null,
119
+ };
120
+ }
121
+
37
122
  function addHook(hooks, eventName, command, options = {}) {
38
123
  hooks[eventName] = Array.isArray(hooks[eventName]) ? hooks[eventName] : [];
39
124
  const already = JSON.stringify(hooks[eventName]).includes(command);
@@ -72,12 +157,8 @@ export async function install(options = {}) {
72
157
  const workspace = resolve(options.workspace || process.cwd());
73
158
  await chmod(binPath, 0o755).catch(() => {});
74
159
  const localBin = join(homeDir(), '.local', 'bin');
75
- await ensureDir(localBin);
76
160
  const localBinPath = join(localBin, 'llm-wiki');
77
- if (await exists(localBinPath)) {
78
- await backupFile(localBinPath, 'local-bin-llm-wiki');
79
- }
80
- await safeSymlink(binPath, localBinPath);
161
+ const localBinResult = await reconcileLocalBin(localBinPath);
81
162
  if (!options.noProject) {
82
163
  await bootstrapProject(workspace, { profile: options.profile || 'standard', recordState: true });
83
164
  await recordProject(workspace, 'install');
@@ -133,7 +214,12 @@ export async function install(options = {}) {
133
214
  workspace,
134
215
  runtimeVersion: runtimeVersion(),
135
216
  binPath,
136
- localBin: join(localBin, 'llm-wiki'),
217
+ localBin: localBinPath,
218
+ localBinAction: localBinResult.action,
219
+ localBinManaged: localBinResult.managed,
220
+ localBinMatchesRuntime: localBinResult.matchesRuntime,
221
+ commandPath: localBinResult.commandPath,
222
+ commandPaths: localBinResult.commandPathsAfter,
137
223
  changed,
138
224
  };
139
225
  }
@@ -176,7 +262,10 @@ export async function status(options = {}) {
176
262
  const commandPath = commandPaths[0] || null;
177
263
  const resolvedCommandPath = realpathOrOriginal(commandPath);
178
264
  const resolvedBinPath = realpathOrOriginal(binPath);
265
+ const localBinPath = join(homeDir(), '.local', 'bin', 'llm-wiki');
266
+ const localBin = await inspectLocalBin(localBinPath);
179
267
  return {
268
+ workspace,
180
269
  runtimeVersion: runtimeVersion(),
181
270
  packageRoot,
182
271
  installSource: detectInstallSource(),
@@ -184,6 +273,7 @@ export async function status(options = {}) {
184
273
  commandPath,
185
274
  commandPaths,
186
275
  commandMatchesRuntime: Boolean(resolvedCommandPath && resolvedBinPath && resolvedCommandPath === resolvedBinPath),
276
+ localBin,
187
277
  codexInstalled,
188
278
  claudeInstalled,
189
279
  hooksCurrent: codexInstalled && claudeInstalled,
package/src/projects.js CHANGED
@@ -50,6 +50,11 @@ export async function recordProject(projectRoot, source = 'unknown') {
50
50
  return registry.projects[workspace];
51
51
  }
52
52
 
53
+ async function looksLikeProjectRoot(root) {
54
+ return (await exists(join(root, 'llm-wiki', '.kit-state.json'))) ||
55
+ (await exists(join(root, 'llm-wiki', 'wiki', 'index.md')));
56
+ }
57
+
53
58
  export async function discoverProjectRoots(searchRoot, options = {}) {
54
59
  const root = resolve(searchRoot || process.cwd());
55
60
  const maxDirs = options.maxDirs || 5000;
@@ -60,7 +65,7 @@ export async function discoverProjectRoots(searchRoot, options = {}) {
60
65
  if (seen >= maxDirs) return;
61
66
  seen += 1;
62
67
 
63
- if (await exists(join(dir, 'llm-wiki', '.kit-state.json'))) {
68
+ if (await looksLikeProjectRoot(dir)) {
64
69
  roots.add(dir);
65
70
  }
66
71
 
@@ -73,8 +78,15 @@ export async function discoverProjectRoots(searchRoot, options = {}) {
73
78
 
74
79
  for (const entry of entries) {
75
80
  if (seen >= maxDirs) return;
76
- if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
77
- await walk(join(dir, entry.name));
81
+ if (!entry.isDirectory()) continue;
82
+ const child = join(dir, entry.name);
83
+ if (SKIP_DIRS.has(entry.name)) {
84
+ if (entry.name === 'llm-wiki' && await looksLikeProjectRoot(child)) {
85
+ await walk(child);
86
+ }
87
+ continue;
88
+ }
89
+ await walk(child);
78
90
  }
79
91
  }
80
92
 
@@ -92,7 +104,7 @@ export async function knownProjectRoots(options = {}) {
92
104
 
93
105
  const existing = [];
94
106
  for (const root of [...roots].sort()) {
95
- if (await isDirectory(root) && await exists(join(root, 'llm-wiki', '.kit-state.json'))) {
107
+ if (await isDirectory(root) && await looksLikeProjectRoot(root)) {
96
108
  existing.push(root);
97
109
  }
98
110
  }
package/src/templates.js CHANGED
@@ -13,6 +13,8 @@ This repository uses llm-wiki-kit as a hook-first living Markdown wiki for Codex
13
13
  - \`llm-wiki/raw/\`는 원본 또는 redacted 근거 저장소다. hook envelope append 외에는 원본 capture를 수정하지 않는다.
14
14
  - \`llm-wiki/wiki/\`는 agent가 관리하는 지식층이다. 결정, 구조, 디버깅, 개념, 절차, 맥락을 여기에 정리한다.
15
15
  - \`llm-wiki/wiki/memory.md\`는 짧은 핵심 기억이다. 긴 설명 대신 현재 상태와 중요한 문서 링크만 유지한다.
16
+ - hook이 주입한 context를 우선 사용한다. 수동 확인이나 정리에는 \`llm-wiki context\`, \`llm-wiki lint\`, \`llm-wiki consolidate\`를 agent 보조 도구로 사용한다.
17
+ - hook은 redacted live Q&A와 query/decision 후보를 기록한다. 재사용 가능한 지식은 agent가 기존 정식 wiki 문서에 합친다.
16
18
  - 새 문서를 만들기 전에 기존 wiki 문서를 먼저 찾아 갱신한다. 반복해서 쓸 사실은 \`outputs/questions/\`에만 두지 말고 적절한 wiki 문서에 합친다.
17
19
  - 일회성 작업 기록은 \`llm-wiki/outputs/questions/\`에 보존하고, 재사용 가능한 결론은 \`wiki/architecture/\`, \`wiki/debugging/\`, \`wiki/decisions/\`, \`wiki/concepts/\`, \`procedures/\`에 반영한다.
18
20
  - 검증 명령, 근거 파일, 불확실한 점을 함께 남긴다. 추론은 추론이라고 표시하고, 모순은 지우지 말고 Open Questions 또는 Contradictions에 남긴다.
package/src/update.js CHANGED
@@ -4,7 +4,7 @@ import { exists } from './fs-utils.js';
4
4
  import { appendWikiLog } from './project.js';
5
5
  import { install } from './install.js';
6
6
  import { applyProjectTemplateUpdate, inspectProjectState } from './project-state.js';
7
- import { knownProjectRoots } from './projects.js';
7
+ import { knownProjectRoots, recordProject } from './projects.js';
8
8
  import { binPath, detectInstallSource, packageName, runtimeVersion } from './version.js';
9
9
 
10
10
  function runCommand(command, args, options = {}) {
@@ -39,6 +39,34 @@ async function hasProjectWiki(projectRoot) {
39
39
  return exists(join(projectRoot, 'llm-wiki', 'wiki'));
40
40
  }
41
41
 
42
+ function shouldUpdateAllProjects(options = {}) {
43
+ return options.all || !options.currentOnly;
44
+ }
45
+
46
+ async function projectRootsForUpdate(workspace, options = {}) {
47
+ if (!shouldUpdateAllProjects(options)) return [workspace];
48
+ const roots = await knownProjectRoots({ workspace });
49
+ return roots.length > 0 ? roots : [workspace];
50
+ }
51
+
52
+ async function applyTemplatesToProject(projectRoot, options = {}) {
53
+ const projectResult = options.noProject
54
+ ? null
55
+ : await applyProjectTemplateUpdate(projectRoot, { dryRun: options.dryRun });
56
+ if (!options.noProject && !options.dryRun && await hasProjectWiki(projectRoot)) {
57
+ await recordProject(projectRoot, options.source || 'post-update').catch(() => {});
58
+ }
59
+ return projectResult;
60
+ }
61
+
62
+ function parseJsonOutput(output) {
63
+ try {
64
+ return JSON.parse(output);
65
+ } catch {
66
+ return null;
67
+ }
68
+ }
69
+
42
70
  export function parseRegistryVersion(output) {
43
71
  return String(output || '').trim().split(/\s+/).at(-1) || '';
44
72
  }
@@ -95,9 +123,7 @@ export async function postUpdate(options = {}) {
95
123
  const workspaces = await knownProjectRoots({ workspace });
96
124
  const projects = [];
97
125
  for (const projectRoot of workspaces) {
98
- const projectResult = options.noProject
99
- ? null
100
- : await applyProjectTemplateUpdate(projectRoot);
126
+ const projectResult = await applyTemplatesToProject(projectRoot, options);
101
127
  if (!options.noProject && await hasProjectWiki(projectRoot)) {
102
128
  await appendWikiLog(projectRoot, `llm-wiki-kit post-update applied runtime ${runtimeVersion()}; changed templates: ${projectResult.changed.length}`);
103
129
  }
@@ -114,9 +140,7 @@ export async function postUpdate(options = {}) {
114
140
  };
115
141
  }
116
142
 
117
- const projectResult = options.noProject
118
- ? null
119
- : await applyProjectTemplateUpdate(workspace);
143
+ const projectResult = await applyTemplatesToProject(workspace, options);
120
144
 
121
145
  if (!options.noProject && await hasProjectWiki(workspace)) {
122
146
  await appendWikiLog(workspace, `llm-wiki-kit post-update applied runtime ${runtimeVersion()}; changed templates: ${projectResult.changed.length}`);
@@ -148,23 +172,40 @@ export async function update(options = {}) {
148
172
  }
149
173
 
150
174
  if (options.dryRun) {
175
+ const workspaces = await projectRootsForUpdate(workspace, options);
176
+ const projects = [];
177
+ for (const projectRoot of workspaces) {
178
+ projects.push({
179
+ workspace: projectRoot,
180
+ project: options.noProject ? null : await applyProjectTemplateUpdate(projectRoot, { dryRun: true }),
181
+ });
182
+ }
151
183
  return {
152
184
  mode: 'dry-run',
153
185
  workspace,
154
186
  ...check,
155
187
  installSource: detectInstallSource(),
156
- project: options.noProject ? null : await applyProjectTemplateUpdate(workspace, { dryRun: true }),
188
+ scope: shouldUpdateAllProjects(options) ? 'all' : 'current',
189
+ project: projects.find((item) => item.workspace === workspace)?.project || projects[0]?.project || null,
190
+ projects: shouldUpdateAllProjects(options) ? projects : undefined,
157
191
  };
158
192
  }
159
193
 
160
194
  const target = options.to || 'latest';
161
- const installResult = runCommand(npmCommand(), ['install', '-g', `${packageName()}@${target}`], {
162
- timeout: options.timeout || 120000,
163
- });
195
+ const shouldRunNpmInstall = check.updateAvailable;
196
+ const installResult = shouldRunNpmInstall
197
+ ? runCommand(npmCommand(), ['install', '-g', `${packageName()}@${target}`], {
198
+ timeout: options.timeout || 120000,
199
+ })
200
+ : {
201
+ status: 0,
202
+ stdout: `skipped; installed ${check.installedVersion} is already >= target ${check.latestVersion}`,
203
+ stderr: '',
204
+ };
164
205
  assertCommandOk(installResult, 'npm install -g');
165
206
 
166
- const postArgs = [binCommand(), 'post-update', '--workspace', workspace];
167
- if (options.all) postArgs.push('--all');
207
+ const postArgs = [binCommand(), 'post-update', '--workspace', workspace, '--json'];
208
+ if (shouldUpdateAllProjects(options)) postArgs.push('--all');
168
209
  if (options.profile) postArgs.push('--profile', options.profile);
169
210
  if (options.noProject) postArgs.push('--no-project');
170
211
  if (options.codex === false) postArgs.push('--no-codex');
@@ -173,16 +214,21 @@ export async function update(options = {}) {
173
214
  timeout: options.timeout || 120000,
174
215
  });
175
216
  assertCommandOk(postResult, 'post-update');
217
+ const parsedPostUpdate = parseJsonOutput(postResult.stdout);
176
218
 
177
219
  return {
178
220
  mode: 'update',
179
221
  workspace,
180
222
  before: check.installedVersion,
181
223
  target,
224
+ scope: shouldUpdateAllProjects(options) ? 'all' : 'current',
182
225
  npmInstall: {
226
+ skipped: !shouldRunNpmInstall,
183
227
  stdout: installResult.stdout.trim(),
184
228
  stderr: installResult.stderr.trim(),
185
229
  },
186
- postUpdate: postResult.stdout.trim(),
230
+ postUpdate: parsedPostUpdate || postResult.stdout.trim(),
231
+ project: parsedPostUpdate?.project,
232
+ projects: parsedPostUpdate?.projects,
187
233
  };
188
234
  }