litclaude-ai 0.3.39 → 0.3.41

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/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.41 - 2026-08-09 — foreground automatic updates
4
+
5
+ - Add a default-on SessionStart update barrier that installs an exact stable
6
+ package candidate only after a cached check, runs doctor/version verification,
7
+ records a transaction receipt, and fails closed when rollback is unknown.
8
+ - Keep the detached notifier cache-only, with sanitized npm environment,
9
+ bounded locking, recursion/opt-out guards, and the documented
10
+ `--no-auto-update` escape hatch.
11
+
3
12
  ## 0.3.39 - 2026-08-04 — executable plan validation
4
13
 
5
14
  - Require planner handoff to use a generated plan that passes
@@ -17,7 +26,15 @@
17
26
  tools, so the harness read-before-write guard never sees them — copying an
18
27
  example literally could clobber a file in the user's working directory.
19
28
 
20
- ## Unreleased — `lit-comprehend` explainer artifacts
29
+ ## Unreleased — output styles enrollment and `lit-comprehend` explainer artifacts
30
+
31
+ - Add output styles enrollment to the installer: a `chooseOutputStyle` prompt
32
+ (mirroring `chooseHudAccent`) lets users pick `off`, `asd-ste100`,
33
+ `asd-ste100-ko`, `eli5`, or `eli5-ko` on every install run, defaulted from
34
+ the prior choice. The installer writes `settings.outputStyle` clobber-safely —
35
+ skipping the write and printing a note when the field was changed outside
36
+ LitClaude — and tracks `outputStyleChoice`/`outputStyleWrittenName` under
37
+ the `litclaude` settings key for idempotent reinstall defaulting.
21
38
 
22
39
  - Add the `lit-comprehend` skill, the `/litclaude:lit-comprehend` command, and the
23
40
  `lit-comprehend` / `comprehend` / `lit comprehend` / `$lit-comprehend` /
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
  <p align="center">
11
11
  <img src="https://img.shields.io/badge/npm-litclaude--ai-cb3837" />
12
- <img src="https://img.shields.io/badge/version-0.3.39-2ea44f" />
12
+ <img src="https://img.shields.io/badge/version-0.3.41-2ea44f" />
13
13
  <img src="https://img.shields.io/badge/Claude%20Code-plugin-blueviolet" />
14
14
  <img src="https://img.shields.io/badge/license-MIT-blue" />
15
15
  </p>
@@ -174,18 +174,18 @@ cd /tmp
174
174
  npx --yes litclaude-ai@latest install
175
175
  ```
176
176
 
177
- `0.3.39` is the version prepared by this checkout. Registry lookup is authoritative
177
+ `0.3.41` is the version prepared by this checkout. Registry lookup is authoritative
178
178
  for whether that exact version can be installed:
179
179
 
180
180
  ```bash
181
- npm view litclaude-ai@0.3.39 version
181
+ npm view litclaude-ai@0.3.41 version
182
182
  ```
183
183
 
184
- If that lookup returns 0.3.39, the exact install is available:
184
+ If that lookup returns 0.3.41, the exact install is available:
185
185
 
186
186
  ```bash
187
187
  cd /tmp
188
- npx --yes litclaude-ai@0.3.39 install
188
+ npx --yes litclaude-ai@0.3.41 install
189
189
  ```
190
190
 
191
191
  Otherwise, wait for explicit human publication and repeat the lookup before using
@@ -205,18 +205,26 @@ offline, failed, or interrupted refreshes while preserving the last successful
205
205
  version and check time. A transition/recovery mutex and per-attempt ownership
206
206
  generation serialize stale takeover and completion, so an old worker cannot
207
207
  remove a newer reservation or overwrite its result. The refresh is a detached, three-second request to the
208
- official npm registry, so the management command does not wait. Noncanonical or
209
- future cache timestamps are not trusted. The notice is disabled for CI, JSON,
210
- dry-run, or non-TTY use and by either `NO_UPDATE_NOTIFIER` or
211
- `LITCLAUDE_NO_UPDATE_CHECK`. It never reads npm credentials or installs an
212
- update. Follow the version-pinned `npx --yes litclaude-ai@<version> install`
213
- command shown in the notice, then restart Claude Code.
208
+ official npm registry, so the management command does not wait; this advisory
209
+ refresh is cache-only.
210
+
211
+ When a fresh cache entry identifies a newer strict stable version, an interactive
212
+ Claude `SessionStart` or TTY `install`/`update`/`doctor` command now performs a
213
+ foreground automatic update. It invokes the exact `litclaude-ai@<version>` package,
214
+ passes `--no-auto-update` to the child, strips npm credential variables, takes a bounded
215
+ backup, runs the exact-version doctor, and rolls back on timeout or verification
216
+ failure. A receipt and journal are written under `~/.litclaude/update-notifier/automatic/`.
217
+ The automatic lane is disabled by `--no-auto-update`, `LITCLAUDE_NO_AUTO_UPDATE`,
218
+ `NO_UPDATE_NOTIFIER`, or `LITCLAUDE_NO_UPDATE_CHECK`; it is also a no-op for CI,
219
+ JSON, dry-run, non-TTY management commands, imports, and tool surfaces. The
220
+ SessionStart hook is the host-owned interactive lifecycle exception to the TTY
221
+ check. The legacy cache refresh never installs a package by itself.
214
222
 
215
223
  The installer also sets Claude Code's `statusLine` command to the packaged
216
224
  LitClaude HUD. A typical no-color render starts like:
217
225
 
218
226
  ```text
219
- [🔥LITCLAUDE v0.3.39] | O4.8 │ ctx [▎░░] 9%/1000k │ 5h [▏░] 4% ↻2h15m │ 1w [▊░] 35% ↻3d6h │ git main +3 ✓
227
+ [🔥LITCLAUDE v0.3.41] | O4.8 │ ctx [▎░░] 9%/1000k │ 5h [▏░] 4% ↻2h15m │ 1w [▊░] 35% ↻3d6h │ git main +3 ✓
220
228
  ```
221
229
 
222
230
  The `↻` suffix is a compact rate-limit reset countdown. It is separated from
package/README_ko-KR.md CHANGED
@@ -9,7 +9,7 @@
9
9
  </p>
10
10
  <p align="center">
11
11
  <img src="https://img.shields.io/badge/npm-litclaude--ai-cb3837" />
12
- <img src="https://img.shields.io/badge/version-0.3.39-2ea44f" />
12
+ <img src="https://img.shields.io/badge/version-0.3.41-2ea44f" />
13
13
  <img src="https://img.shields.io/badge/Claude%20Code-plugin-blueviolet" />
14
14
  <img src="https://img.shields.io/badge/license-MIT-blue" />
15
15
  </p>
@@ -183,18 +183,18 @@ cd /tmp
183
183
  npx --yes litclaude-ai@latest install
184
184
  ```
185
185
 
186
- `0.3.39`은 이 checkout에서 준비 중인 version입니다. exact version의 설치 가능
186
+ `0.3.41`은 이 checkout에서 준비 중인 version입니다. exact version의 설치 가능
187
187
  여부에는 registry 조회 결과를 authoritative한 기준으로 사용합니다.
188
188
 
189
189
  ```bash
190
- npm view litclaude-ai@0.3.39 version
190
+ npm view litclaude-ai@0.3.41 version
191
191
  ```
192
192
 
193
- 조회 결과가 0.3.39이면 exact install을 사용할 수 있습니다.
193
+ 조회 결과가 0.3.41이면 exact install을 사용할 수 있습니다.
194
194
 
195
195
  ```bash
196
196
  cd /tmp
197
- npx --yes litclaude-ai@0.3.39 install
197
+ npx --yes litclaude-ai@0.3.41 install
198
198
  ```
199
199
 
200
200
  그렇지 않으면 명시적인 human publication을 기다립니다. 게시 후 조회를 다시 실행해
@@ -214,18 +214,26 @@ time을 남겨 24시간 동안 재시도를 제한하되 마지막 성공 versio
214
214
  보존합니다. transition/recovery mutex와 attempt별 ownership generation이 stale
215
215
  takeover와 completion을 직렬화하므로 이전 worker가 새 reservation을 지우거나 새
216
216
  결과를 덮어쓸 수 없습니다. refresh는 official npm registry를 최대 3초 동안 조회하는 detached
217
- process이므로 management command는 기다리지 않습니다. canonical하지 않거나
218
- 미래인 cache timestamp는 신뢰하지 않습니다. CI, JSON, dry-run, non-TTY에서는
219
- 동작하지 않으며 `NO_UPDATE_NOTIFIER` 또는 `LITCLAUDE_NO_UPDATE_CHECK`로 끌 수
220
- 있습니다. npm credential을 읽거나 update를 자동 설치하지 않습니다. notice가 제시한
221
- `npx --yes litclaude-ai@<version> install`을 실행한 Claude Code 다시
222
- 시작하세요.
217
+ process이므로 management command는 기다리지 않습니다. advisory refresh는
218
+ cache 갱신합니다.
219
+
220
+ fresh cache가 높은 strict stable version을 가리키면 interactive Claude
221
+ `SessionStart` 또는 TTY `install`/`update`/`doctor`가 foreground automatic update
222
+ 수행합니다. exact `litclaude-ai@<version>` package만 실행하고 child에
223
+ `--no-auto-update`를 전달하며 npm credential 변수를 제거한 환경, bounded backup,
224
+ exact-version doctor, 실패 시 rollback을 사용합니다. receipt와 journal은
225
+ `~/.litclaude/update-notifier/automatic/`에 남습니다. `--no-auto-update`,
226
+ `LITCLAUDE_NO_AUTO_UPDATE`, `NO_UPDATE_NOTIFIER`, `LITCLAUDE_NO_UPDATE_CHECK`로
227
+ 자동 설치를 끌 수 있고 CI, JSON, dry-run, non-TTY management command, import,
228
+ tool surface에서는 동작하지 않습니다. SessionStart hook은 host가 소유한
229
+ interactive lifecycle이므로 TTY 검사만 예외입니다. 기존 cache refresh가
230
+ package를 자동 설치하지는 않습니다.
223
231
 
224
232
  installer는 Claude Code의 `statusLine` command도 packaged LitClaude HUD로
225
233
  설정합니다. 색상을 제거한 예시는 다음처럼 시작합니다.
226
234
 
227
235
  ```text
228
- [🔥LITCLAUDE v0.3.39] | O4.8 │ ctx [▎░░] 9%/1000k │ 5h [▏░] 4% ↻2h15m │ 1w [▊░] 35% ↻3d6h │ git main +3 ✓
236
+ [🔥LITCLAUDE v0.3.41] | O4.8 │ ctx [▎░░] 9%/1000k │ 5h [▏░] 4% ↻2h15m │ 1w [▊░] 35% ↻3d6h │ git main +3 ✓
229
237
  ```
230
238
 
231
239
  `↻` 표시는 rate-limit reset까지 남은 시간을 짧게 보여주는 countdown입니다.
@@ -1,6 +1,6 @@
1
1
  # LitClaude Release Checklist
2
2
 
3
- Status: `litclaude-ai@0.3.39` is the current release candidate — exact canonical
3
+ Status: `litclaude-ai@0.3.41` is the current release candidate — exact canonical
4
4
  frontend corpus plus Claude-native `autoresearch`, `autoconference`, and `wikify`
5
5
  workflow-family integration. It byte-pins the frontend library, legal companions,
6
6
  family source closures, and adapters through independent commitments and package
@@ -45,9 +45,9 @@ side-effect-free, the launcher starts only a separate Claude Code
45
45
  print/background worker, and the release preserves the Korean polishing
46
46
  command, strict multi-agent review pipeline, fidelity guardrails, package
47
47
  hygiene checks, native route gates, and safe start-work handoff behavior.
48
- `package.json` is aligned to `0.3.39`,
49
- `plugins/litclaude/.claude-plugin/plugin.json` is aligned to `0.3.39`, and the
50
- plugin-local MCP server reports `0.3.39`.
48
+ `package.json` is aligned to `0.3.41`,
49
+ `plugins/litclaude/.claude-plugin/plugin.json` is aligned to `0.3.41`, and the
50
+ plugin-local MCP server reports `0.3.41`.
51
51
 
52
52
  This release carries the v0.2.2 Dynamic workflow hardening surfaces:
53
53
  `/dynamic-workflow`, `workflow-check --json`, native `/goal` fallback guidance,
@@ -280,9 +280,9 @@ checkout and from an isolated install of the packed tarball:
280
280
  Before requesting publication approval, confirm these artifacts from the current
281
281
  checkout:
282
282
 
283
- - `package.json` version is `0.3.39`.
284
- - `plugins/litclaude/.claude-plugin/plugin.json` version is `0.3.39`.
285
- - `plugins/litclaude/bin/litclaude-mcp.js` reports server version `0.3.39`.
283
+ - `package.json` version is `0.3.41`.
284
+ - `plugins/litclaude/.claude-plugin/plugin.json` version is `0.3.41`.
285
+ - `plugins/litclaude/bin/litclaude-mcp.js` reports server version `0.3.41`.
286
286
  - Prompt-hook tests cover bundled `SKILL.md` body injection for bare `hyperplan`, `litresearch`, `lit research`, `init-deep`, and explicit leading `$start-work`; diagnostic/copy mentions stay inert while leading natural-language `lit start work` stays BLOCKED.
287
287
  - `lit search` and `lit query` route to `/litclaude:litresearch` without activating on slash mentions, code spans, or non-lit prompts.
288
288
  - Litresearch web lanes require public API/feed preference, validator-first checks, route traces, prompt-injection quarantine, and honest auth/paywall/private-data stop reasons.
@@ -30,6 +30,7 @@ import { runStartWorkContinuationCli } from "../plugins/litclaude/lib/start-work
30
30
  import { runStartWorkCli } from "../plugins/litclaude/lib/start-work-cli.mjs";
31
31
  import { runLitgoalCli } from "../plugins/litclaude/lib/litgoal/cli.mjs";
32
32
  import { runWorkflowCheckCli } from "../plugins/litclaude/lib/workflow-check.mjs";
33
+ import { automaticUpdateRoot, runAutomaticUpdate } from "../plugins/litclaude/lib/automatic-update.mjs";
33
34
  import { runUpdateNotifier } from "./update-notifier.mjs";
34
35
 
35
36
  const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
@@ -55,6 +56,11 @@ Commands:
55
56
  Public read options:
56
57
  --json Emit a machine-readable JSON result.
57
58
 
59
+ Update options:
60
+ --no-auto-update
61
+ Skip LitClaude's foreground automatic install for this command.
62
+ LITCLAUDE_NO_AUTO_UPDATE provides the same opt-out for a session.
63
+
58
64
  Install options:
59
65
  --permission-mode <safe|balanced|yolo>
60
66
  Mutates global Claude settings permissions.allow/deny.
@@ -67,12 +73,24 @@ Install options:
67
73
  `;
68
74
 
69
75
  const parseArgs = (argv) => {
70
- const args = [...argv];
76
+ const args = [];
77
+ let noAutoUpdate = false;
78
+ let invalidOption;
79
+ for (const arg of argv) {
80
+ if (arg === "--no-auto-update") {
81
+ noAutoUpdate = true;
82
+ continue;
83
+ }
84
+ if (arg.startsWith("--no-auto-update=")) {
85
+ invalidOption = arg;
86
+ }
87
+ args.push(arg);
88
+ }
71
89
  const wantsVersion = args[0] === "--version" || args[0] === "-v";
72
- if (wantsVersion) return { dryRun: false, command: "version", rest: [] };
90
+ if (wantsVersion) return { dryRun: false, command: "version", rest: [], noAutoUpdate, invalidOption };
73
91
  const dryRun = args[0] === "--dry-run";
74
92
  if (dryRun) args.shift();
75
- return { dryRun, command: args[0], rest: args.slice(1) };
93
+ return { dryRun, command: args[0], rest: args.slice(1), noAutoUpdate, invalidOption };
76
94
  };
77
95
 
78
96
  const litHome = () => resolve(process.env.LITCLAUDE_HOME ?? join(homedir(), ".litclaude"));
@@ -100,6 +118,19 @@ const knownExternalLspPlugins = {
100
118
  };
101
119
  const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
102
120
  const hudAccentThemes = HUD_ACCENT_THEMES;
121
+ const OUTPUT_STYLE_OPTIONS = [
122
+ { id: "off", label: "None / keep current" },
123
+ { id: "asd-ste100", label: "ASD-STE100 (English)" },
124
+ { id: "asd-ste100-ko", label: "ASD-STE100 (한국어)" },
125
+ { id: "eli5", label: "ELI5 (English)" },
126
+ { id: "eli5-ko", label: "ELI5 (한국어)" },
127
+ ];
128
+ const OUTPUT_STYLE_NAMES = {
129
+ "asd-ste100": "LitClaude — ASD-STE100",
130
+ "asd-ste100-ko": "LitClaude — ASD-STE100 (한국어)",
131
+ "eli5": "LitClaude — ELI5",
132
+ "eli5-ko": "LitClaude — ELI5 (한국어)",
133
+ };
103
134
  const permissionModes = new Set(["safe", "balanced", "yolo"]);
104
135
  const permissionOwnershipDescription =
105
136
  "preserves pre-existing entries; removes only LitClaude-inserted entries on mode change or uninstall";
@@ -351,6 +382,50 @@ const chooseHudAccent = async (existingAccent = "cyan") => {
351
382
  }
352
383
  };
353
384
 
385
+ const normalizeOutputStyle = (value) => {
386
+ if (!value) return "off";
387
+ const v = String(value).trim().toLowerCase();
388
+ const match = OUTPUT_STYLE_OPTIONS.find((opt) => opt.id === v);
389
+ return match ? match.id : "off";
390
+ };
391
+
392
+ const shouldPromptOutputStyle = () => {
393
+ if (process.env.LITCLAUDE_OUTPUT_STYLE_PROMPT === "1") return true;
394
+ if (process.env.LITCLAUDE_OUTPUT_STYLE_PROMPT === "0") return false;
395
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
396
+ };
397
+
398
+ const chooseOutputStyle = async (existingChoice = "off") => {
399
+ const preserved = normalizeOutputStyle(existingChoice);
400
+ const envStyle = process.env.LITCLAUDE_OUTPUT_STYLE;
401
+ if (envStyle && process.env.LITCLAUDE_OUTPUT_STYLE_PROMPT !== "1") {
402
+ return normalizeOutputStyle(envStyle);
403
+ }
404
+ if (!shouldPromptOutputStyle()) return envStyle ? normalizeOutputStyle(envStyle) : preserved;
405
+
406
+ process.stdout.write("Choose LitClaude output style:\n");
407
+ for (const [index, option] of OUTPUT_STYLE_OPTIONS.entries()) {
408
+ process.stdout.write(` ${index}) ${option.label}\n`);
409
+ }
410
+
411
+ const preservedLabel = OUTPUT_STYLE_OPTIONS.find((opt) => opt.id === preserved)?.label ?? preserved;
412
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
413
+ try {
414
+ const answer = await rl.question(`Style 0-4 [${preservedLabel}]: `);
415
+ const trimmed = answer.trim();
416
+ const byIndex = OUTPUT_STYLE_OPTIONS[Number.parseInt(trimmed, 10)]?.id;
417
+ const selected = byIndex !== undefined ? byIndex : (trimmed ? normalizeOutputStyle(trimmed) : preserved);
418
+ const isExplicitOff = trimmed.toLowerCase() === "off" || trimmed.toLowerCase() === "none";
419
+ if (trimmed && byIndex === undefined && selected === "off" && !isExplicitOff) {
420
+ process.stdout.write("OUTPUT_STYLE_WARNING: unknown style; using 'off'\n");
421
+ }
422
+ process.stdout.write(`OUTPUT_STYLE_SELECTED: ${selected}\n`);
423
+ return selected;
424
+ } finally {
425
+ rl.close();
426
+ }
427
+ };
428
+
354
429
  const hudCommandForPlugin = (pluginPath, accent = "cyan") =>
355
430
  `LITCLAUDE_HUD_ACCENT=${normalizeHudAccent(accent)} node "${join(pluginPath, "bin", "litclaude-hud.js")}"`;
356
431
 
@@ -458,6 +533,30 @@ const installHudStatusLine = (pluginPath, home = claudeHome(), accent = "cyan")
458
533
  writeClaudeSettings(settings, home);
459
534
  };
460
535
 
536
+ const installOutputStyle = (choice, home = claudeHome()) => {
537
+ if (choice === "off") return;
538
+ const writtenName = OUTPUT_STYLE_NAMES[choice] ?? null;
539
+ if (!writtenName) return;
540
+
541
+ const settings = readClaudeSettings(home);
542
+ const existingLitClaude = settings[litClaudeSettingsKey] ?? {};
543
+ const previousWrittenName = existingLitClaude.outputStyleWrittenName ?? null;
544
+
545
+ const currentOutputStyle = settings.outputStyle;
546
+ if (currentOutputStyle === undefined || currentOutputStyle === previousWrittenName) {
547
+ settings.outputStyle = writtenName;
548
+ } else {
549
+ process.stdout.write(`Note: outputStyle was changed outside LitClaude (currently "${currentOutputStyle}"). Switch it via /config if you want LitClaude's style.\n`);
550
+ }
551
+
552
+ settings[litClaudeSettingsKey] = {
553
+ ...existingLitClaude,
554
+ outputStyleChoice: choice,
555
+ outputStyleWrittenName: writtenName,
556
+ };
557
+ writeClaudeSettings(settings, home);
558
+ };
559
+
461
560
  const uninstallHudStatusLine = (home = claudeHome()) => {
462
561
  const settingsPath = claudeSettingsPath(home);
463
562
  if (!existsSync(settingsPath)) return;
@@ -840,6 +939,7 @@ const install = async ({ dryRun, rest }) => {
840
939
  const existingLitClaude = readClaudeSettings(home)[litClaudeSettingsKey] ?? {};
841
940
  const { permissionMode, permissionExplicit } = parseInstallOptions(rest, existingLitClaude.permissionMode);
842
941
  const existingHudAccent = normalizeHudAccent(existingLitClaude.hudAccent);
942
+ const existingOutputStyle = existingLitClaude.outputStyleChoice ?? "off";
843
943
  const requestedHudAccent = process.env.LITCLAUDE_HUD_ACCENT
844
944
  ? normalizeHudAccent(process.env.LITCLAUDE_HUD_ACCENT)
845
945
  : existingHudAccent;
@@ -868,6 +968,7 @@ const install = async ({ dryRun, rest }) => {
868
968
  }
869
969
 
870
970
  const hudAccent = await chooseHudAccent(existingHudAccent);
971
+ const outputStyleChoice = await chooseOutputStyle(existingOutputStyle);
871
972
  progress.prepare({ claudeTarget: home, litTarget: litClaudeHome });
872
973
  await progress.run(
873
974
  {
@@ -901,7 +1002,10 @@ const install = async ({ dryRun, rest }) => {
901
1002
  purpose: "Enable the plugin and HUD, then update global Claude settings permissions.allow/deny",
902
1003
  target: claudeSettingsPath(home),
903
1004
  },
904
- () => registerClaudePlugin(targetPlugin, localMarketplace, home, hudAccent, permissionMode),
1005
+ () => {
1006
+ registerClaudePlugin(targetPlugin, localMarketplace, home, hudAccent, permissionMode);
1007
+ installOutputStyle(outputStyleChoice, home);
1008
+ },
905
1009
  );
906
1010
 
907
1011
  const litRoot = versionRoot(litClaudeHome);
@@ -958,6 +1062,7 @@ const doctor = ({ dryRun }) => {
958
1062
  process.stdout.write(`Would check Claude plugin registry: ${pluginKey}\n`);
959
1063
  process.stdout.write(`Would check Claude statusLine HUD: ${hudCommandForPlugin(pluginPath)}\n`);
960
1064
  process.stdout.write("Would check global Claude settings permissions.allow/deny and LitClaude ownership-safe removal metadata\n");
1065
+ process.stdout.write(`Would inspect automatic-update receipt/journal under: ${join(automaticUpdateRoot(litHome()), "receipt.json")}\n`);
961
1066
  process.stdout.write(`Would run: node ${join(pluginPath, "bin", "litclaude-scientific-visualization-doctor.js")}\n`);
962
1067
  process.stdout.write("Would verify exact canonical frontend corpus bytes, legal companions, no extras, and three canonical runtime closures\n");
963
1068
  return;
@@ -1063,6 +1168,10 @@ const doctor = ({ dryRun }) => {
1063
1168
  }
1064
1169
 
1065
1170
  process.stdout.write(`Plugin path: ${pluginPath}\n`);
1171
+ const automaticReceiptPath = join(automaticUpdateRoot(litHome()), "receipt.json");
1172
+ if (existsSync(automaticReceiptPath)) {
1173
+ process.stdout.write(`AUTOMATIC_UPDATE_RECEIPT: ${automaticReceiptPath}\n`);
1174
+ }
1066
1175
  const settings = readClaudeSettings();
1067
1176
  const permissionFailures = permissionIntegrityFailures(settings);
1068
1177
  if (permissionFailures.length > 0) {
@@ -1152,6 +1261,48 @@ const uninstall = ({ dryRun }) => {
1152
1261
  process.stdout.write("UNINSTALL_PASS\n");
1153
1262
  };
1154
1263
 
1264
+ const emitAutomaticUpdateResult = (result) => {
1265
+ if (!result || result.status === "gated" || result.status === "no-candidate" || result.status === "current" || result.status === "stale-cache" || result.status === "locked") return;
1266
+ if (result.status === "installed") {
1267
+ process.stdout.write(`AUTO_UPDATE_PASS: LitClaude ${result.targetVersion} installed\n`);
1268
+ if (result.receiptPath) process.stdout.write(`AUTO_UPDATE_RECEIPT: ${result.receiptPath}\n`);
1269
+ return;
1270
+ }
1271
+ const label = result.status === "rolled-back"
1272
+ ? "ROLLBACK"
1273
+ : result.status === "rollback-failed" ? "UNKNOWN_STATE" : "STOPPED";
1274
+ process.stderr.write(`AUTO_UPDATE_${label}: automatic update did not replace the current install\n`);
1275
+ if (result.receiptPath) process.stderr.write(`AUTO_UPDATE_RECEIPT: ${result.receiptPath}\n`);
1276
+ };
1277
+
1278
+ const runEligibleAutomaticUpdate = (parsed) => {
1279
+ if (!["install", "update", "doctor"].includes(parsed.command)) return { status: "gated" };
1280
+ // Validate install/update flags before any automatic side effect. This keeps
1281
+ // malformed or ambiguous requests fail-closed rather than forwarding them to npm.
1282
+ if (parsed.command === "install" || parsed.command === "update") {
1283
+ const existing = readClaudeSettings()[litClaudeSettingsKey] ?? {};
1284
+ parseInstallOptions(parsed.rest, existing.permissionMode);
1285
+ }
1286
+ const result = runAutomaticUpdate({
1287
+ surface: "management",
1288
+ command: parsed.command,
1289
+ rest: parsed.rest,
1290
+ dryRun: parsed.dryRun,
1291
+ currentVersion: version,
1292
+ cachePath: updateNotifierCachePath(),
1293
+ litHome: litHome(),
1294
+ claudeHome: claudeHome(),
1295
+ env: process.env,
1296
+ stdin: process.stdin,
1297
+ stdout: process.stdout,
1298
+ stderr: process.stderr,
1299
+ noAutoUpdate: parsed.noAutoUpdate,
1300
+ cwd: process.cwd(),
1301
+ });
1302
+ emitAutomaticUpdateResult(result);
1303
+ return result;
1304
+ };
1305
+
1155
1306
  const main = async () => {
1156
1307
  const parsed = parseArgs(process.argv.slice(2));
1157
1308
  const { command } = parsed;
@@ -1160,6 +1311,17 @@ const main = async () => {
1160
1311
  printUsage();
1161
1312
  process.exit(64);
1162
1313
  }
1314
+ if (parsed.invalidOption) fail(`Unknown update option: ${parsed.invalidOption}`, 64);
1315
+
1316
+ const automaticUpdate = runEligibleAutomaticUpdate(parsed);
1317
+ if (automaticUpdate.status === "rollback-failed") {
1318
+ fail("AUTO_UPDATE_UNKNOWN_STATE: automatic update rollback failed; run `litclaude doctor` before using the install", 1);
1319
+ }
1320
+ if (automaticUpdate.status === "installed") {
1321
+ // The automatic transaction already ran the exact-version install and its
1322
+ // post-install doctor. Do not immediately re-run the old package's command.
1323
+ return;
1324
+ }
1163
1325
 
1164
1326
  switch (command) {
1165
1327
  case "install":
package/docs/hooks.md CHANGED
@@ -99,7 +99,7 @@ incumbents are not silently removed.
99
99
 
100
100
  | Event | Runner | Purpose |
101
101
  | --- | --- | --- |
102
- | `SessionStart` | `plugins/litclaude/bin/litclaude-hook.js session-start` | Discovers repo-local rule files and injects the bodies of always-on rules, plus the rules-loaded context line. |
102
+ | `SessionStart` | `plugins/litclaude/bin/litclaude-hook.js session-start` | Runs the bounded foreground automatic-update barrier when a fresh cache names a newer version, then discovers repo-local rule files and injects the bodies of always-on rules plus the rules-loaded context line. |
103
103
  | `UserPromptSubmit` | `plugins/litclaude/bin/litclaude-hook.js user-prompt-submit` | Detects prompt routes and injects workflow context; independently delivers static rules not already recorded for this session. |
104
104
  | `PreToolUse` | `plugins/litclaude/bin/litclaude-hook.js pre-tool-use` | Enforces semantic action/root grants before Write, Edit, MultiEdit, NotebookEdit, Bash, Agent, and bounded read tools execute. |
105
105
  | `PostToolUse` | `plugins/litclaude/bin/litclaude-hook.js post-tool-use` | Names the post-edit checks the edit actually earned, and injects any glob-scoped rule matching the edited paths. |
@@ -109,6 +109,21 @@ incumbents are not silently removed.
109
109
  | `SubagentStop` | `plugins/litclaude/bin/litclaude-hook.js subagent-stop` | Finalizes the lane without child continuation. |
110
110
  | `SessionEnd` | `plugins/litclaude/bin/litclaude-hook.js session-end` | Records root-session end without blocking Claude. |
111
111
 
112
+ ### Foreground automatic-update barrier
113
+
114
+ The SessionStart hook waits for one bounded automatic-update transaction when a
115
+ fresh product-owned cache entry names a newer strict stable package version. The
116
+ transaction uses a separate install lock, credential-free npm environment, exact
117
+ `npm exec --yes --package litclaude-ai@<version>` arguments, a backup/journal,
118
+ post-install doctor, and rollback before the hook emits its rules context. The
119
+ same lane is available to interactive TTY `install`, `update`, and `doctor`
120
+ commands. `--no-auto-update`, `LITCLAUDE_NO_AUTO_UPDATE`,
121
+ `NO_UPDATE_NOTIFIER`, and `LITCLAUDE_NO_UPDATE_CHECK` disable it. CI, JSON,
122
+ dry-run, non-TTY management commands, import, and tool surfaces remain no-op;
123
+ the host-owned SessionStart hook is the lifecycle exception to the TTY check.
124
+ The detached registry cache refresh described in the update-notifier section is
125
+ advisory and never performs an install.
126
+
112
127
  ## Bounded-authority start-work lifecycle
113
128
 
114
129
  The hook and CLI share `plugins/litclaude/lib/start-work-lifecycle.mjs`.
@@ -37,5 +37,7 @@ path. The condition does not make the ledger self-validating; recorded evidence
37
37
  still needs the normal LitClaude review gates.
38
38
 
39
39
  This documentation does not add a fake command, MCP method, or hook dispatch.
40
- It also does not auto-install anything, mutate a transcript, inject keystrokes,
41
- or claim success based on prompt context alone.
40
+ The native-goal binding itself does not auto-install a goal, mutate a transcript,
41
+ inject keystrokes, or claim success based on prompt context alone. Package
42
+ updates, when eligible, follow the separate bounded transaction documented in
43
+ [`docs/hooks.md`](hooks.md#foreground-automatic-update-barrier).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litclaude-ai",
3
- "version": "0.3.39",
3
+ "version": "0.3.41",
4
4
  "description": "Claude Code-native workflow distribution.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "litclaude",
3
3
  "description": "Claude Code-native workflow plugin with a bounded-authority start-work lifecycle.",
4
- "version": "0.3.39",
4
+ "version": "0.3.41",
5
5
  "author": {
6
6
  "name": "LitClaude contributors"
7
7
  },
@@ -20,6 +20,7 @@ import { litgoalGoalsPath } from "../lib/litgoal/paths.mjs";
20
20
  import { readLitgoalState } from "../lib/litgoal/state.mjs";
21
21
  import { completeAutoloopGoal, evaluateAutoloop, readAutoloopState, writeAutoloopState } from "../lib/litgoal/autoloop.mjs";
22
22
  import { buildNativeGoalBindingGuidance, normalizeGoalObjective } from "../lib/native-goal-binding.mjs";
23
+ import { automaticUpdateCachePath, runAutomaticUpdate } from "../lib/automatic-update.mjs";
23
24
  import {
24
25
  handleStartWorkStop,
25
26
  handleStartWorkPreToolUse,
@@ -84,9 +85,9 @@ const hookEventNames = {
84
85
  "session-end": "SessionEnd",
85
86
  };
86
87
 
87
- const writeContext = (additionalContext, systemMessage) => {
88
+ const writeContext = (additionalContext, systemMessage, shouldContinue = true) => {
88
89
  const payload = {
89
- continue: true,
90
+ continue: shouldContinue,
90
91
  hookSpecificOutput: {
91
92
  hookEventName: hookEventNames[eventName],
92
93
  additionalContext,
@@ -785,6 +786,14 @@ const litworkContext = ({ command, skill, skillId, discipline, softConfirm, safe
785
786
  // working directory — the same discipline staticSkillBodyContext uses.
786
787
  const pluginRoot = fileURLToPath(new URL("../", import.meta.url));
787
788
 
789
+ const pluginVersion = (() => {
790
+ try {
791
+ return JSON.parse(readFileSync(join(pluginRoot, ".claude-plugin", "plugin.json"), "utf8")).version;
792
+ } catch {
793
+ return null;
794
+ }
795
+ })();
796
+
788
797
  // The rules engine is advisory context. Every lane below is wrapped so a rules failure
789
798
  // degrades to "no rules injected" instead of taking the hook — and the hook's own
790
799
  // contract — down with it.
@@ -813,6 +822,25 @@ const input = readInput();
813
822
  switch (eventName) {
814
823
  case "session-start": {
815
824
  const cwd = typeof input.cwd === "string" ? input.cwd : "unknown workspace";
825
+ const automaticUpdate = pluginVersion
826
+ ? runAutomaticUpdate({
827
+ surface: "session-start",
828
+ currentVersion: pluginVersion,
829
+ cachePath: automaticUpdateCachePath(),
830
+ litHome: process.env.LITCLAUDE_HOME,
831
+ claudeHome: process.env.CLAUDE_CONFIG_DIR ?? process.env.CLAUDE_HOME,
832
+ env: process.env,
833
+ input,
834
+ cwd: typeof input.cwd === "string" ? input.cwd : process.cwd(),
835
+ })
836
+ : { status: "gated" };
837
+ const automaticContext = automaticUpdate.status === "installed"
838
+ ? ` Automatic update completed in the foreground to ${automaticUpdate.targetVersion}; receipt: ${inertFilesystemData(automaticUpdate.receiptPath)}.`
839
+ : automaticUpdate.status === "rollback-failed"
840
+ ? " BLOCKED: automatic update rollback failed; install state may be inconsistent. Run `litclaude doctor` before relying on it."
841
+ : ["rolled-back", "failed"].includes(automaticUpdate.status)
842
+ ? " Automatic update was not applied; the existing install was retained and the receipt records the result."
843
+ : "";
816
844
  const pressureContext = transcriptHasContextPressure(input.transcript_path)
817
845
  ? " Context pressure detected: before edits, reread HANDOFF.md, the active plan, .litclaude/start-work/ledger.jsonl, .litclaude/boulder.json, and git status --short."
818
846
  : "";
@@ -820,7 +848,11 @@ switch (eventName) {
820
848
  // AGENTS.md, plans). The engine now actually delivers the rule bodies themselves.
821
849
  const rules = staticRulesContext({ cwd, sessionId: input.session_id });
822
850
  const rulesSuffix = rules ? `\n\n${rules}` : "";
823
- writeContext(`LitClaude rules loaded for ${inertFilesystemData(cwd)}. Read CLAUDE.md, AGENTS.md, .claude/rules/**/*.md, .github/instructions/**/*.md, and named plan or handoff files before edits.${pressureContext}${rulesSuffix}`);
851
+ writeContext(
852
+ `LitClaude rules loaded for ${inertFilesystemData(cwd)}. Read CLAUDE.md, AGENTS.md, .claude/rules/**/*.md, .github/instructions/**/*.md, and named plan or handoff files before edits.${automaticContext}${pressureContext}${rulesSuffix}`,
853
+ undefined,
854
+ automaticUpdate.status !== "rollback-failed",
855
+ );
824
856
  break;
825
857
  }
826
858
  case "user-prompt-submit": {
@@ -3,7 +3,7 @@
3
3
  import { readPublicSource } from "../lib/public-source-reader/reader.mjs";
4
4
 
5
5
  const protocolVersion = "2024-11-05";
6
- const serverVersion = "0.3.39";
6
+ const serverVersion = "0.3.41";
7
7
 
8
8
  const publicSourceReadTool = {
9
9
  name: "public_source_read",
@@ -6,8 +6,8 @@
6
6
  {
7
7
  "type": "command",
8
8
  "command": "node \"${CLAUDE_PLUGIN_ROOT}/bin/litclaude-hook.js\" session-start",
9
- "timeout": 10,
10
- "statusMessage": "loading LitClaude rules"
9
+ "timeout": 60,
10
+ "statusMessage": "checking LitClaude update and loading rules"
11
11
  }
12
12
  ]
13
13
  }