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 +18 -1
- package/README.md +20 -12
- package/README_ko-KR.md +20 -12
- package/RELEASE_CHECKLIST.md +7 -7
- package/bin/litclaude-ai.js +166 -4
- package/docs/hooks.md +16 -1
- package/docs/native-goal-surface.md +4 -2
- package/package.json +1 -1
- package/plugins/litclaude/.claude-plugin/plugin.json +1 -1
- package/plugins/litclaude/bin/litclaude-hook.js +35 -3
- package/plugins/litclaude/bin/litclaude-mcp.js +1 -1
- package/plugins/litclaude/hooks/hooks.json +2 -2
- package/plugins/litclaude/lib/automatic-update.mjs +684 -0
- package/plugins/litclaude/output-styles/asd-ste100.md +46 -0
- package/plugins/litclaude/output-styles/asd-ste100_ko-KR.md +47 -0
- package/plugins/litclaude/output-styles/eli5.md +17 -0
- package/plugins/litclaude/output-styles/eli5_ko-KR.md +19 -0
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.
|
|
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.
|
|
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.
|
|
181
|
+
npm view litclaude-ai@0.3.41 version
|
|
182
182
|
```
|
|
183
183
|
|
|
184
|
-
If that lookup returns 0.3.
|
|
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.
|
|
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
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
186
|
+
`0.3.41`은 이 checkout에서 준비 중인 version입니다. exact version의 설치 가능
|
|
187
187
|
여부에는 registry 조회 결과를 authoritative한 기준으로 사용합니다.
|
|
188
188
|
|
|
189
189
|
```bash
|
|
190
|
-
npm view litclaude-ai@0.3.
|
|
190
|
+
npm view litclaude-ai@0.3.41 version
|
|
191
191
|
```
|
|
192
192
|
|
|
193
|
-
조회 결과가 0.3.
|
|
193
|
+
조회 결과가 0.3.41이면 exact install을 사용할 수 있습니다.
|
|
194
194
|
|
|
195
195
|
```bash
|
|
196
196
|
cd /tmp
|
|
197
|
-
npx --yes litclaude-ai@0.3.
|
|
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는 기다리지 않습니다.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
`
|
|
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.
|
|
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입니다.
|
package/RELEASE_CHECKLIST.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# LitClaude Release Checklist
|
|
2
2
|
|
|
3
|
-
Status: `litclaude-ai@0.3.
|
|
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.
|
|
49
|
-
`plugins/litclaude/.claude-plugin/plugin.json` is aligned to `0.3.
|
|
50
|
-
plugin-local MCP server reports `0.3.
|
|
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.
|
|
284
|
-
- `plugins/litclaude/.claude-plugin/plugin.json` version is `0.3.
|
|
285
|
-
- `plugins/litclaude/bin/litclaude-mcp.js` reports server version `0.3.
|
|
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.
|
package/bin/litclaude-ai.js
CHANGED
|
@@ -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 = [
|
|
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
|
-
() =>
|
|
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` |
|
|
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
|
-
|
|
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
|
@@ -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:
|
|
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(
|
|
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": {
|
|
@@ -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
|
-
"statusMessage": "
|
|
9
|
+
"timeout": 60,
|
|
10
|
+
"statusMessage": "checking LitClaude update and loading rules"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
}
|