llm-wiki-kit 0.2.12 → 0.2.14

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
@@ -133,7 +133,7 @@ Most users should not need these during daily Claude Code/Codex work. They exist
133
133
 
134
134
  `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.
135
135
 
136
- `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.
136
+ `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. Before installing, it checks whether `npm root -g` points at the active runtime package root; after installing, it verifies the active runtime and `post-update` runtime reached the registry target. If either check fails, it exits nonzero and prints the exact runtime/npm roots plus the manual install/post-update commands to run. If the installed runtime already satisfies the registry target, it prints an already-current result, 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 root/prefix checks, 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.
137
137
 
138
138
  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.
139
139
 
package/docs/manual.md CHANGED
@@ -163,10 +163,13 @@ Most users should not need these during daily coding. They are for install, upda
163
163
 
164
164
  - runs `npm install -g llm-wiki-kit@<target>` only when needed;
165
165
  - skips npm install when the installed runtime already satisfies the target;
166
- - restarts into the updated binary for post-update work;
166
+ - checks that `npm root -g` points at the active runtime package root before installing;
167
+ - verifies that the active runtime and `post-update` runtime reached the registry target after installing;
168
+ - exits nonzero instead of printing `updated` when `npm install -g` succeeds but the active runtime is still old;
167
169
  - reinstalls hooks without duplicating them;
168
170
  - updates only managed project templates and policy blocks;
169
171
  - preserves user-edited files and curated wiki contents;
172
+ - prints before/after runtime, registry target, npm global root, active runtime root, and PATH command diagnostics;
170
173
  - prints progress to stderr so JSON stdout remains parseable;
171
174
  - supports `--timeout-ms` and `--max-dirs` for slow npm, WSL, or large search roots.
172
175
 
@@ -127,7 +127,9 @@ Installed npm runtimes also run a cached hook-side update notice check while the
127
127
 
128
128
  - runs `npm install -g llm-wiki-kit@<target>` only when the registry target is newer than the installed runtime
129
129
  - skips npm installation when the installed runtime already satisfies the target, then still runs post-update maintenance
130
- - restarts into the updated binary for `post-update`
130
+ - checks `npm root -g`/`npm prefix -g` before install and refuses to claim success when npm would update a different global prefix than the active runtime
131
+ - verifies that the active runtime and `post-update` child runtime reached the registry target after install
132
+ - prints before/after runtime, registry target, npm global root, active runtime root, and PATH command diagnostics
131
133
  - reinstalls hook entries without duplicating them
132
134
  - patches only managed project files across known or discovered project roots
133
135
  - backs up changed files under `~/.local/share/llm-wiki-kit/backups/`
@@ -113,6 +113,8 @@ If the proxy performs TLS inspection, install the organization's CA and set `caf
113
113
 
114
114
  If `npm install -g llm-wiki-kit@latest` succeeds but `llm-wiki --help` does not show newer commands such as `projects`, `context`, `lint`, or `consolidate`, the shell is probably executing an older source checkout or stale shim before the npm global binary.
115
115
 
116
+ Newer `llm-wiki update` releases diagnose this before claiming success. If the npm global root does not match the active runtime root, or if `npm install -g` exits 0 but the active runtime is still old, `update` exits nonzero with details such as `active runtime root`, `npm global root`, `runtime before`, `runtime after`, and `command matches runtime`. On root-owned Linux prefixes, run the printed `sudo npm install -g llm-wiki-kit@<target>` command, then run the printed `llm-wiki post-update ...` command as the normal Codex/Claude user.
117
+
116
118
  Confirm what is actually running:
117
119
 
118
120
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "llm-wiki-kit",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "Hook-first living Markdown wiki runtime for Codex and Claude Code with Korean/English prompt-aware guidance.",
5
5
  "type": "module",
6
6
  "files": [
package/src/cli.js CHANGED
@@ -348,13 +348,26 @@ function formatUpdate(value) {
348
348
  `- project template skipped: ${skipped}`,
349
349
  ].join('\n');
350
350
  }
351
+ const title = value.updateApplied
352
+ ? 'llm-wiki-kit updated'
353
+ : 'llm-wiki-kit already current; post-update complete';
351
354
  return [
352
- 'llm-wiki-kit updated',
355
+ title,
353
356
  `- workspace: ${value.workspace}`,
354
- `- before: ${value.before}`,
357
+ `- runtime before: ${value.runtimeBefore || value.before}`,
358
+ `- registry target: ${value.latest || value.target}`,
359
+ `- runtime after: ${value.runtimeAfter || 'unknown'}`,
360
+ ...(value.postUpdateRuntime ? [`- post-update runtime: ${value.postUpdateRuntime}`] : []),
361
+ `- runtime verified: ${value.runtimeVerified ? 'yes' : 'no'}`,
362
+ `- npm install: ${value.npmInstall?.skipped ? 'skipped' : 'ran'}`,
355
363
  `- target: ${value.target}`,
364
+ `- active runtime root: ${value.activePackageRoot || 'unknown'}`,
365
+ `- npm global root: ${value.npmGlobal?.root || 'unknown'}`,
366
+ `- command: ${value.command?.path || 'not found'}`,
367
+ `- command matches runtime: ${value.command?.matchesRuntime ? 'yes' : 'no'}`,
356
368
  `- scope: ${value.scope || 'current'}`,
357
369
  ...(value.projects ? [`- projects updated: ${value.projects.length}`] : []),
370
+ ...((value.warnings || []).map((warning) => `- warning: ${warning}`)),
358
371
  ].join('\n');
359
372
  }
360
373
 
package/src/update.js CHANGED
@@ -3,10 +3,10 @@ import { join, resolve } from 'path';
3
3
  import { exists } from './fs-utils.js';
4
4
  import { appendWikiLog } from './project.js';
5
5
  import { install } from './install.js';
6
- import { isWindows } from './platform.js';
6
+ import { commandMatchesRuntime, commandPaths, isWindows, sameResolvedPath } from './platform.js';
7
7
  import { applyProjectTemplateUpdate, inspectProjectState } from './project-state.js';
8
8
  import { knownProjectRoots, recordProject } from './projects.js';
9
- import { binPath, detectInstallSource, packageName, runtimeVersion } from './version.js';
9
+ import { binPath, detectInstallSource, packageName, packageRoot, runtimeVersion } from './version.js';
10
10
 
11
11
  function commandLine(command, args) {
12
12
  return [command, ...args].join(' ');
@@ -26,6 +26,7 @@ async function runCommand(command, args, options = {}) {
26
26
  const label = options.label || commandLine(command, args);
27
27
  const startedAt = Date.now();
28
28
  const windows = isWindows(options);
29
+ const useShell = options.shell !== undefined ? options.shell : windows;
29
30
  const detached = !windows;
30
31
  let stdout = '';
31
32
  let stderr = '';
@@ -69,7 +70,7 @@ async function runCommand(command, args, options = {}) {
69
70
  child = spawn(command, args, {
70
71
  detached,
71
72
  env: options.env || process.env,
72
- shell: windows,
73
+ shell: useShell,
73
74
  stdio: ['ignore', 'pipe', 'pipe'],
74
75
  });
75
76
  } catch (error) {
@@ -115,6 +116,10 @@ function binCommand() {
115
116
  return process.env.LLM_WIKI_KIT_BIN || binPath;
116
117
  }
117
118
 
119
+ function packageSpec(target) {
120
+ return `${packageName()}@${target}`;
121
+ }
122
+
118
123
  function assertCommandOk(result, label) {
119
124
  if (result.status === 0 && !result.error) return;
120
125
  const detail = result.stderr.trim() ||
@@ -125,6 +130,45 @@ function assertCommandOk(result, label) {
125
130
  throw new Error(`${label} failed: ${detail}`);
126
131
  }
127
132
 
133
+ function postUpdateCommand(workspace, options = {}) {
134
+ const command = ['llm-wiki', 'post-update'];
135
+ if (shouldUpdateAllProjects(options)) command.push('--all');
136
+ command.push('--workspace', workspace);
137
+ return command.join(' ');
138
+ }
139
+
140
+ function formatRemediation(workspace, target, options = {}) {
141
+ const install = isWindows(options)
142
+ ? `npm install -g ${packageSpec(target)}`
143
+ : `sudo npm install -g ${packageSpec(target)}`;
144
+ return [
145
+ 'Run the npm install command that owns the active runtime, then reapply hooks/templates:',
146
+ ` ${install}`,
147
+ ` ${postUpdateCommand(workspace, options)}`,
148
+ 'Then verify:',
149
+ ` llm-wiki status --workspace ${workspace}`,
150
+ ].join('\n');
151
+ }
152
+
153
+ function createUpdateVerificationError(message, diagnostics = {}, workspace, target, options = {}) {
154
+ const lines = [
155
+ 'llm-wiki-kit update did not update the active runtime.',
156
+ message,
157
+ ];
158
+ if (diagnostics.registryTarget) lines.push(`- registry target: ${diagnostics.registryTarget}`);
159
+ if (diagnostics.runtimeBefore) lines.push(`- runtime before: ${diagnostics.runtimeBefore}`);
160
+ if (diagnostics.runtimeAfter) lines.push(`- runtime after: ${diagnostics.runtimeAfter}`);
161
+ if (diagnostics.activePackageRoot) lines.push(`- active runtime root: ${diagnostics.activePackageRoot}`);
162
+ if (diagnostics.npmRoot) lines.push(`- npm global root: ${diagnostics.npmRoot}`);
163
+ if (diagnostics.npmPackageRoot) lines.push(`- npm target package root: ${diagnostics.npmPackageRoot}`);
164
+ if (diagnostics.commandPath) lines.push(`- command on PATH: ${diagnostics.commandPath}`);
165
+ if (diagnostics.commandMatchesRuntime !== undefined) {
166
+ lines.push(`- command matches runtime: ${diagnostics.commandMatchesRuntime ? 'yes' : 'no'}`);
167
+ }
168
+ lines.push(formatRemediation(workspace, target, options));
169
+ return Object.assign(new Error(lines.join('\n')), { diagnostics });
170
+ }
171
+
128
172
  async function hasProjectWiki(projectRoot) {
129
173
  return exists(join(projectRoot, 'llm-wiki', 'wiki'));
130
174
  }
@@ -190,6 +234,74 @@ export function compareVersions(a, b) {
190
234
  return left.prerelease > right.prerelease ? 1 : -1;
191
235
  }
192
236
 
237
+ async function npmGlobalInfo(options = {}) {
238
+ const rootResult = await runCommand(npmCommand(), ['root', '-g'], {
239
+ ...options,
240
+ label: 'npm root -g',
241
+ timeout: options.timeout || 30000,
242
+ });
243
+ assertCommandOk(rootResult, 'npm root -g');
244
+ const prefixResult = await runCommand(npmCommand(), ['prefix', '-g'], {
245
+ ...options,
246
+ label: 'npm prefix -g',
247
+ timeout: options.timeout || 30000,
248
+ });
249
+ assertCommandOk(prefixResult, 'npm prefix -g');
250
+ const root = rootResult.stdout.trim().split(/\r?\n/).at(-1) || '';
251
+ const prefix = prefixResult.stdout.trim().split(/\r?\n/).at(-1) || '';
252
+ return {
253
+ root,
254
+ prefix,
255
+ packageRoot: root ? join(root, packageName()) : '',
256
+ };
257
+ }
258
+
259
+ async function commandInfo(options = {}) {
260
+ const paths = await commandPaths('llm-wiki', options);
261
+ const commandPath = paths[0] || null;
262
+ const matchesRuntime = await commandMatchesRuntime(commandPath, binPath, {
263
+ ...options,
264
+ installSource: detectInstallSource(),
265
+ });
266
+ return {
267
+ path: commandPath,
268
+ paths,
269
+ matchesRuntime: Boolean(matchesRuntime),
270
+ };
271
+ }
272
+
273
+ function runtimeSatisfiesTarget(runtime, targetVersion) {
274
+ return compareVersions(runtime, targetVersion) >= 0;
275
+ }
276
+
277
+ function rootMatchesActiveRuntime(npmPackageRoot) {
278
+ return sameResolvedPath(npmPackageRoot, packageRoot) ||
279
+ resolve(npmPackageRoot || '') === resolve(packageRoot);
280
+ }
281
+
282
+ async function preflightRuntimeUpdate(check, workspace, target, options = {}) {
283
+ const npmGlobal = await npmGlobalInfo(options);
284
+ const command = await commandInfo(options);
285
+ if (check.updateAvailable && detectInstallSource() === 'npm' && !rootMatchesActiveRuntime(npmGlobal.packageRoot)) {
286
+ throw createUpdateVerificationError(
287
+ 'The active llm-wiki runtime is not under the npm global root that this update command would modify.',
288
+ {
289
+ registryTarget: check.latestVersion,
290
+ runtimeBefore: check.installedVersion,
291
+ activePackageRoot: packageRoot,
292
+ npmRoot: npmGlobal.root,
293
+ npmPackageRoot: npmGlobal.packageRoot,
294
+ commandPath: command.path,
295
+ commandMatchesRuntime: command.matchesRuntime,
296
+ },
297
+ workspace,
298
+ target,
299
+ options
300
+ );
301
+ }
302
+ return { npmGlobal, command };
303
+ }
304
+
193
305
  export async function checkForUpdate(options = {}) {
194
306
  const target = options.to || 'latest';
195
307
  const installedVersion = runtimeVersion();
@@ -297,9 +409,10 @@ export async function update(options = {}) {
297
409
  }
298
410
 
299
411
  const target = options.to || 'latest';
412
+ const beforeDiagnostics = await preflightRuntimeUpdate(check, workspace, target, options);
300
413
  const shouldRunNpmInstall = check.updateAvailable;
301
414
  const installResult = shouldRunNpmInstall
302
- ? await runCommand(npmCommand(), ['install', '-g', `${packageName()}@${target}`], {
415
+ ? await runCommand(npmCommand(), ['install', '-g', packageSpec(target)], {
303
416
  ...options,
304
417
  label: 'npm install -g',
305
418
  timeout: options.timeout || 120000,
@@ -310,6 +423,25 @@ export async function update(options = {}) {
310
423
  stderr: '',
311
424
  };
312
425
  assertCommandOk(installResult, 'npm install -g');
426
+ const runtimeAfterInstall = runtimeVersion();
427
+ if (shouldRunNpmInstall && !runtimeSatisfiesTarget(runtimeAfterInstall, check.latestVersion)) {
428
+ throw createUpdateVerificationError(
429
+ 'npm install -g exited successfully, but the active runtime version did not change to the registry target.',
430
+ {
431
+ registryTarget: check.latestVersion,
432
+ runtimeBefore: check.installedVersion,
433
+ runtimeAfter: runtimeAfterInstall,
434
+ activePackageRoot: packageRoot,
435
+ npmRoot: beforeDiagnostics.npmGlobal.root,
436
+ npmPackageRoot: beforeDiagnostics.npmGlobal.packageRoot,
437
+ commandPath: beforeDiagnostics.command.path,
438
+ commandMatchesRuntime: beforeDiagnostics.command.matchesRuntime,
439
+ },
440
+ workspace,
441
+ target,
442
+ options
443
+ );
444
+ }
313
445
 
314
446
  const postArgs = [binCommand(), 'post-update', '--workspace', workspace, '--json'];
315
447
  if (shouldUpdateAllProjects(options)) postArgs.push('--all');
@@ -324,17 +456,52 @@ export async function update(options = {}) {
324
456
  LLM_WIKI_KIT_PROGRESS: process.env.LLM_WIKI_KIT_PROGRESS || '1',
325
457
  },
326
458
  label: 'post-update',
459
+ shell: false,
327
460
  timeout: options.timeout || 120000,
328
461
  });
329
462
  assertCommandOk(postResult, 'post-update');
330
463
  const parsedPostUpdate = parseJsonOutput(postResult.stdout);
464
+ const postRuntimeVersion = parsedPostUpdate?.runtimeVersion || null;
465
+ if (shouldRunNpmInstall && postRuntimeVersion && !runtimeSatisfiesTarget(postRuntimeVersion, check.latestVersion)) {
466
+ throw createUpdateVerificationError(
467
+ 'post-update ran, but it did not run from the registry target runtime.',
468
+ {
469
+ registryTarget: check.latestVersion,
470
+ runtimeBefore: check.installedVersion,
471
+ runtimeAfter: postRuntimeVersion,
472
+ activePackageRoot: packageRoot,
473
+ npmRoot: beforeDiagnostics.npmGlobal.root,
474
+ npmPackageRoot: beforeDiagnostics.npmGlobal.packageRoot,
475
+ commandPath: beforeDiagnostics.command.path,
476
+ commandMatchesRuntime: beforeDiagnostics.command.matchesRuntime,
477
+ },
478
+ workspace,
479
+ target,
480
+ options
481
+ );
482
+ }
483
+ const afterCommand = await commandInfo(options);
484
+ const warnings = [];
485
+ if (!afterCommand.matchesRuntime) {
486
+ warnings.push(`llm-wiki command on PATH does not resolve to the active runtime: ${afterCommand.path || 'not found'}`);
487
+ }
331
488
 
332
489
  return {
333
490
  mode: 'update',
334
491
  workspace,
335
492
  before: check.installedVersion,
336
493
  target,
494
+ latest: check.latestVersion,
495
+ runtimeBefore: check.installedVersion,
496
+ runtimeAfter: runtimeAfterInstall,
497
+ postUpdateRuntime: postRuntimeVersion,
498
+ updateApplied: shouldRunNpmInstall,
499
+ runtimeVerified: runtimeSatisfiesTarget(runtimeAfterInstall, check.latestVersion),
337
500
  scope: shouldUpdateAllProjects(options) ? 'all' : 'current',
501
+ activePackageRoot: packageRoot,
502
+ npmGlobal: beforeDiagnostics.npmGlobal,
503
+ command: afterCommand,
504
+ warnings,
338
505
  npmInstall: {
339
506
  skipped: !shouldRunNpmInstall,
340
507
  stdout: installResult.stdout.trim(),