@litfamily/litclaude 1.0.22 โ†’ 1.0.24

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,19 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.24 - 2026-10-12
4
+
5
+ - The LIT card no longer drops to idle while background work is still running. When the main turn ends after starting background subagents or a workflow, the card stays at work and counts the running agents until the last one finishes, and it never reads `DONE` while an agent is still busy. A workflow's agents now count too, and a subagent that finishes in the middle of a turn no longer dims the chips of the request that is still running. An agent that never reports back stops counting after 30 quiet minutes, so it cannot hold the card at work.
6
+ - The README shows the LIT card as it really appears: resting before anything runs, at work with the skill's chip lit, and after the turn, when the chip dims into the recent list.
7
+ - The README walks through automatic handoff with terminal pictures: turning it on with `lit-handoff auto on 65`, checking and turning it off, the reminder to run `/compact`, and the two shell variables that switch it on before Claude Code starts.
8
+ - The install section has a prompt you can paste into a coding agent so it installs LitClaude for you. The agent shows what would change and waits for your yes, runs the doctor, and reminds you to restart Claude Code.
9
+ - The office runtime now pins sharp 0.35.5, and the README Studio cover templates use sharp 0.35.5 and source-map-js 1.2.2.
10
+
11
+ ## 1.0.23 - 2026-10-12
12
+
13
+ - The LIT card above the prompt now stays on screen for the whole session, so you can still see the status after the work ends. It is a small four-row card in the colours of your status line. The top edge names the workflow and the current phase. Below it are the plan, work and review steps with the progress of the plan or goal. Every LitClaude skill that runs gets its own chip, several at once. A chip is bright while its request is running and dims into a recent list when the turn ends. At the bottom are the context use, the five-hour usage window and the Git branch. The card fits narrow terminals by leaving out the least important parts and never wraps. The `band` setting now defaults to `always`; `auto` brings back the old behaviour, and `off` hides the card.
14
+ - The status line no longer shows the `๐Ÿ”ฅ LIT IGNITED ยท <workflow> ๐Ÿ”ฅ` mark. It took up so much room that the model, context and usage figures were cut off, and the card now shows which workflow is active.
15
+ - Project rules now fit in the skill context when LitClaude is installed under a long folder path. Before, a long install path could cut off the first line of a rule or leave the rules out entirely.
16
+
3
17
  ## 1.0.22 - 2026-10-11
4
18
 
5
19
  - The README is shorter for first-time readers. It says what LitClaude does, how to install it and what to type first, and the detailed material now lives in docs pages (guide, skills, Jev, development) in English and Korean. The GitHub page also plays a short film of a real session.
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
- <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/assets/cover-motion.webp" width="100%" alt="LitFamily motion cover: five armored robots power on one by one, the LitClaude robot wakes with glowing eyes and a lit frame, then LITFAMILY and KEEP THE WORK LIT. light up." /></picture></p>
1
+ <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/assets/cover-motion.webp" width="100%" alt="LitFamily motion cover: five armored robots power on one by one, the LitClaude robot wakes with glowing eyes and a lit frame, then LITFAMILY and KEEP THE WORK LIT. light up." /></picture></p>
2
2
 
3
3
  <h1 align="center">LitClaude</h1>
4
4
  <p align="center"><strong>Keep the work lit.</strong></p>
5
5
  <p align="center">A Claude Code plugin that helps every session plan the work, build it, check it, and leave a note the next session can pick up.</p>
6
6
  <p align="center">
7
- <img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/assets/readme/badge-version.svg" alt="1.0.22" />
8
- <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
7
+ <img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/assets/readme/badge-version.svg" alt="1.0.24" />
8
+ <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
9
9
  </p>
10
10
  <p align="center">
11
- <a href="https://github.com/wjgoarxiv/litclaude#readme">Full guide and skills gallery on GitHub</a> ยท <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/README_ko-KR.md">ํ•œ๊ตญ์–ด</a>
11
+ <a href="https://github.com/wjgoarxiv/litclaude#readme">Full guide and skills gallery on GitHub</a> ยท <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/README_ko-KR.md">ํ•œ๊ตญ์–ด</a>
12
12
  </p>
13
13
 
14
14
  Add `lit` to a Claude Code prompt and Claude pins the goal, writes a failing test first,
@@ -73,7 +73,7 @@ the `/plugin` menu.
73
73
  ## More
74
74
 
75
75
  - [Full guide and skills gallery on GitHub](https://github.com/wjgoarxiv/litclaude#readme)
76
- - [Privacy and network behavior](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/privacy.md)
77
- - [Migration and ownership conflicts](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/migration.md)
78
- - [Release history](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/CHANGELOG.md)
79
- - [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/LICENSE)
76
+ - [Privacy and network behavior](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/privacy.md)
77
+ - [Migration and ownership conflicts](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/migration.md)
78
+ - [Release history](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/CHANGELOG.md)
79
+ - [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/LICENSE)
package/README_ko-KR.md CHANGED
@@ -1,14 +1,14 @@
1
- <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/assets/cover-motion.webp" width="100%" alt="LitFamily ๋ชจ์…˜ ์ปค๋ฒ„: ๋‹ค์„ฏ ๋กœ๋ด‡ ํŒจ๋„์ด ์ฐจ๋ก€๋กœ ์ผœ์ง€๊ณ , LitClaude ๋กœ๋ด‡์˜ ๋ˆˆ๊ณผ ํ…Œ๋‘๋ฆฌ๊ฐ€ ๋น›๋‚œ ๋’ค LITFAMILY์™€ KEEP THE WORK LIT. ๋ฌธ๊ตฌ๊ฐ€ ๋ฐ์•„์ง€๋Š” ์˜์ƒ" /></picture></p>
1
+ <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/assets/cover-motion.webp" width="100%" alt="LitFamily ๋ชจ์…˜ ์ปค๋ฒ„: ๋‹ค์„ฏ ๋กœ๋ด‡ ํŒจ๋„์ด ์ฐจ๋ก€๋กœ ์ผœ์ง€๊ณ , LitClaude ๋กœ๋ด‡์˜ ๋ˆˆ๊ณผ ํ…Œ๋‘๋ฆฌ๊ฐ€ ๋น›๋‚œ ๋’ค LITFAMILY์™€ KEEP THE WORK LIT. ๋ฌธ๊ตฌ๊ฐ€ ๋ฐ์•„์ง€๋Š” ์˜์ƒ" /></picture></p>
2
2
 
3
3
  <h1 align="center">LitClaude</h1>
4
4
  <p align="center"><strong>Keep the work lit.</strong></p>
5
5
  <p align="center">Claude Code์—์„œ ์„ธ์…˜๋งˆ๋‹ค ๊ณ„ํšํ•˜๊ณ , ๋งŒ๋“ค๊ณ , ํ™•์ธํ•œ ๋’ค ๋‹ค์Œ ์„ธ์…˜์ด ์ด์–ด๋ฐ›์„ ๊ธฐ๋ก์„ ๋‚จ๊ธฐ๊ฒŒ ๋•๋Š” ํ”Œ๋Ÿฌ๊ทธ์ธ์ž…๋‹ˆ๋‹ค.</p>
6
6
  <p align="center">
7
- <img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/assets/readme/badge-version.svg" alt="1.0.22" />
8
- <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
7
+ <img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/assets/readme/badge-version.svg" alt="1.0.24" />
8
+ <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
9
9
  </p>
10
10
  <p align="center">
11
- <a href="https://github.com/wjgoarxiv/litclaude/blob/main/README_ko-KR.md">์ „์ฒด ์•ˆ๋‚ด์™€ ์Šคํ‚ฌ ๊ฐค๋Ÿฌ๋ฆฌ๋Š” GitHub์—์„œ</a> ยท <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/README.md">English</a>
11
+ <a href="https://github.com/wjgoarxiv/litclaude/blob/main/README_ko-KR.md">์ „์ฒด ์•ˆ๋‚ด์™€ ์Šคํ‚ฌ ๊ฐค๋Ÿฌ๋ฆฌ๋Š” GitHub์—์„œ</a> ยท <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/README.md">English</a>
12
12
  </p>
13
13
 
14
14
  Claude Code ํ”„๋กฌํ”„ํŠธ์— `lit`์„ ๋ถ™์ด๋ฉด Claude๊ฐ€ ๋ชฉํ‘œ๋ฅผ ๊ณ ์ •ํ•˜๊ณ , ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ์“ฐ๊ณ ,
@@ -71,7 +71,7 @@ LitClaude๋Š” ํ”„๋กœ์ ํŠธ ์ƒํƒœ๋ฅผ `.litclaude/`์— ๋‘๊ณ , ๊ทธ ์•ˆ์˜ `.gitign
71
71
  ## ๋” ๋ณด๊ธฐ
72
72
 
73
73
  - [์ „์ฒด ์•ˆ๋‚ด์™€ ์Šคํ‚ฌ ๊ฐค๋Ÿฌ๋ฆฌ (GitHub)](https://github.com/wjgoarxiv/litclaude/blob/main/README_ko-KR.md)
74
- - [๊ฐœ์ธ์ •๋ณด์™€ ๋„คํŠธ์›Œํฌ ๋™์ž‘](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/privacy.md)
75
- - [์ด์ „๊ณผ ์†Œ์œ ๊ถŒ ์ถฉ๋Œ](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/docs/migration.md)
76
- - [๋ณ€๊ฒฝ ์ด๋ ฅ](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/CHANGELOG.md)
77
- - [MIT ๋ผ์ด์„ ์Šค](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.22/LICENSE)
74
+ - [๊ฐœ์ธ์ •๋ณด์™€ ๋„คํŠธ์›Œํฌ ๋™์ž‘](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/privacy.md)
75
+ - [์ด์ „๊ณผ ์†Œ์œ ๊ถŒ ์ถฉ๋Œ](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/docs/migration.md)
76
+ - [๋ณ€๊ฒฝ ์ด๋ ฅ](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/CHANGELOG.md)
77
+ - [MIT ๋ผ์ด์„ ์Šค](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.24/LICENSE)
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.22"><title>release: 1.0.22</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.22</text></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.24"><title>release: 1.0.24</title><g shape-rendering="crispEdges"><rect width="49" height="20" fill="#080d14"/><rect x="49" width="97" height="20" fill="#ff6337"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text x="255" y="140" textLength="390" transform="scale(.1)">release</text><text x="965" y="140" textLength="870" transform="scale(.1)" fill="#080D14">1.0.24</text></g></svg>
package/docs/hooks.md CHANGED
@@ -346,36 +346,73 @@ itself. LitClaude's is `plugins/litclaude/hooks/lit-mod.mjs`, named under `modul
346
346
  `hooks/hooks.json` next to the settings hooks above. It installs with the plugin and needs no
347
347
  setup. The status line stays as it is; the band adds a live layer above the prompt.
348
348
 
349
- While LitClaude work is active in the session (a start-work plan, an open litgoal goal, a lit
350
- discipline on a running turn, or live subagents) a band sits above the prompt:
351
-
352
- - A ribbon of rounded pills, `LIT ยท PLAN ยท WORK n/m ยท REVIEW`, then a pink-to-cyan progress bar
353
- whose leading cell fades in as it fills, the next task, the number of lanes, and the autoloop
354
- count with the time left in its 30-minute budget. It is fitted to the terminal width, with Hangul
355
- and emoji counted as two cells; parts drop in a fixed order when the row is narrow.
356
- - A card with a round border when two or more lanes run or a warning needs a row: the phase
357
- line, the next task, each lane with its state and elapsed time, and the gates (plan saved,
358
- interface probe, motion gate, auto-handoff).
359
- - Amber when something needs attention: a lane quiet for more than 10 minutes, or context within
360
- five points of the auto-handoff percent (the WORK pill crossfades to amber and the ribbon shows
361
- `ctx 76% โ†’ 80%`). A failed interface probe adds a red row and turns the border red.
362
- - A notice row and a toast when a gate fires: lit-plan not saved, interface probe pending or
363
- failed, motion gate, context near the auto-handoff percent, auto-handoff fired.
364
- - A completion moment when the last task is checked, the work completes or the goal is met: a
365
- light passes over the brand, embers scatter, and `COMPLETE` shows for about a second.
349
+ From the first prompt to the end of the session a LIT card sits above the prompt. It keeps the
350
+ same four rows whether LitClaude is working or idle, and takes the status line's accent
351
+ (`LITCLAUDE_HUD_ACCENT`, read from the installed status line command), so the card and the
352
+ status line read as one family:
353
+
354
+ ```
355
+ โ•ญโ”€ [๐Ÿ”ฅ LIT] ยท lit-loop โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ WORK โ— โ”€โ•ฎ
356
+ โ”‚ โ— PLAN โ”โ” [WORK] โ”โ” โ—‹ REVIEW goal โ–ˆโ–ˆโ–‘โ–‘โ–‘ 2/5 โ”‚
357
+ โ”‚ skills [โšก lit-plan] [โšก litresearch] [โ†ฏ lit-docx] โ”‚
358
+ โ•ฐโ”€ ctx 42% โ–ฐโ–ฐโ–ฑโ–ฑโ–ฑ ยท 5h 31% ยท git main โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ
359
+ ```
360
+
361
+ The brackets stand for the ends of filled pills: rounded caps from the icon font with `nerd`
362
+ glyphs, one cell of the pill's colour with `basic`. The brand is a pink pill, the active phase
363
+ and the skills of this request are pills on the accent with dark text, and skills from earlier
364
+ requests are muted grey pills.
365
+
366
+ - The top edge names the discipline the prompt hook ignited (the last one, dimmed, while the
367
+ session is idle; `ready` before any) and, on the right, the current phase: `WORK โ—`, `idle`,
368
+ or `DONE โœ“` during the finish. While a subagent is still running the card stays at work
369
+ (`LIVE โ—` when no phase is active) and never reads `DONE`, even after the main turn ended.
370
+ - The phase row lights `PLAN โ”โ” WORK โ”โ” REVIEW` from the start-work plan, the litgoal goal and
371
+ the discipline (the active phase as a pill, done phases `โ—`, the rest `โ—‹`), and on its right shows a pink-to-cyan progress bar with the checked tasks or
372
+ passed criteria (`goal โœ“ 5/5` once a goal is met), the number of live subagents (`โ— 2 lanes`), the autoloop
373
+ count with the time left in its 30-minute budget, and `ctx 76% โ†’ 80%` when the context is
374
+ within five points of the auto-handoff percent.
375
+ - The chips row shows each LitFamily skill that fired as a pill: a Skill tool call naming a LitClaude
376
+ skill, a typed `/litclaude:<name>`, or bare `lit` routing seen through the ignition record.
377
+ A skill fired in the current request is bright, with a short light passing over its name as
378
+ it arrives; when the turn completes it dims into the `recent` list, and a new firing brightens
379
+ it again. Chips follow a dim `skills` label; those that do not fit collapse into a muted `+N`. Skills of other plugins never get a chip.
380
+ With no chip yet, the row shows the next task while work runs, or how to start (`lit <task>`)
381
+ while idle.
382
+ - The bottom edge carries the live figures from the status line: context fill, the five-hour
383
+ rate window and the Git branch (read from `HEAD`, across a linked worktree).
384
+ - A notice takes the chips row for a few seconds, with a toast, when a gate fires: lit-plan not
385
+ saved, interface probe pending or failed, motion gate, context near the auto-handoff percent,
386
+ auto-handoff fired. The completion moment takes it when the last task is checked, the work
387
+ completes or the goal is met: a light passes over the brand, embers scatter, and `COMPLETE`
388
+ shows for about a second.
389
+ - Amber or red when something needs attention: a lane quiet for more than 10 minutes turns the
390
+ border amber and puts `โš  <lane> quiet for 12m` on the bottom edge; a failed interface probe
391
+ turns it red.
392
+
393
+ Subagents and teammates come from Claude Code's agent list. A workflow script's agents never
394
+ appear there, so the mod also records every agent at `agent.spawn` and counts it until that
395
+ agent's own `turn.complete` arrives. A subagent's turn ending does not end the main turn: the
396
+ chips of the current request stay bright until the main turn completes. An agent the list shows
397
+ as ended stops counting at once, and one quiet for 30 minutes (no tool call, no status change)
398
+ stops counting too, so a crashed or lost agent cannot hold the card at work.
399
+
400
+ The card is the terminal's width up to 80 columns and every row is fitted to it, with Hangul and
401
+ emoji counted as two cells. Nothing wraps: on a narrow terminal the Git branch goes first, then
402
+ the rate window, the lanes, the autoloop count and the long phase labels (`P โ” W โ” R`).
366
403
 
367
404
  The spinner gains ` ยท <discipline> ยท n/m` after its ellipsis. `/lit-evidence` opens a pane with
368
405
  three tabs (keys `1`, `2`, `3`): the goal's criteria with their last evidence, blockers and the
369
- newest litgoal ledger entries; the lanes; and the gates. When the session is idle the band is not
370
- drawn at all.
406
+ newest litgoal ledger entries; the lanes; and the gates.
371
407
 
372
408
  Motion: entrances pop in 60 ms apart with a slight overshoot (160 ms each), fills and colour
373
409
  changes ease in and out (300 ms and 140 ms), notices slide in over 220 ms and leave over 150 ms,
374
- and the finish lasts at most 1.2 s. The band's `Client` element animates on Claude Code's frame
410
+ a chip's arrival light runs 400 ms (a plain fade under `subtle`, nothing under `off`), and the
411
+ finish lasts at most 1.2 s. The band's `Client` element animates on Claude Code's frame
375
412
  clock only while something moves and stops its timer as soon as everything settles. In Claude
376
413
  Code 2.1.292 a terminal row whose only change is a non-ASCII glyph is not repainted, so every
377
414
  moving part changes ASCII text or colour: bar cells are coloured spaces, embers are ASCII sparks,
378
- and a glyph swaps only together with its words.
415
+ a chip's light moves over its ASCII name, and a glyph swaps only together with its words.
379
416
 
380
417
  Options, in `/config` under LitClaude (stored in `pluginConfigs`):
381
418
 
@@ -383,14 +420,15 @@ Options, in `/config` under LitClaude (stored in `pluginConfigs`):
383
420
  | --- | --- | --- |
384
421
  | `motion` | `full`, `subtle` (no overshoot, shimmer or embers, shorter), `off` (final frames only) | `full` |
385
422
  | `glyphs` | `auto` (Nerd glyphs when the icon font is installed), `nerd`, or `basic` | `auto` |
386
- | `band` | `auto` (only while work is active), `always`, `off` | `auto` |
423
+ | `band` | `always` (the whole session, idle included), `auto` (only while work is active or a skill of this request is lit), `off` | `always` |
387
424
 
388
425
  Unknown values fall back to the default. The fields are plain strings with no `options` list on
389
426
  purpose: Claude Code 2.1.270 and older refuse to load a plugin whose `userConfig` has one, and
390
427
  that would take every LitClaude hook down with it. Nerd glyphs reach only the terminal; the
391
428
  desktop app and the other surfaces always get the basic set.
392
429
 
393
- The rounded pill caps (U+E0B6, U+E0B4) and icons come from a Nerd Font. `litclaude-ai install
430
+ The icons on `nerd` (the rounded pill caps U+E0B6 and U+E0B4, the chip bolt and the branch mark)
431
+ come from a Nerd Font. `litclaude-ai install
394
432
  --icon-font`, or a Yes to the install question (default No), adds the symbols-only Nerd Font,
395
433
  Symbols Nerd Font Mono (MIT), to the per-user font folder (`~/Library/Fonts` on macOS,
396
434
  `~/.local/share/fonts` on Linux, then `fc-cache -f` if present; on Windows it prints how to
@@ -403,8 +441,9 @@ records the two files, `uninstall` removes only those (and only while unchanged)
403
441
  prints `ICON_FONT:`. With `glyphs` on `auto`, the band checks those font folders by stat at session
404
442
  start and says what it decided on the `/lit-evidence` gates tab.
405
443
 
406
- The mod is read-only. It reads LitClaude's own state and nothing else: the ignition record in
407
- the per-user temp folder, `.litclaude/boulder.json` (found the way the hooks find it, across a
444
+ The mod is read-only. It reads LitClaude's own state, the Git `HEAD` of the working directory and
445
+ the merged settings (for the status line's accent) and nothing else: the ignition record in the
446
+ per-user temp folder, `.litclaude/boulder.json` (found the way the hooks find it, across a
408
447
  linked worktree), and under the outermost Git root's `.litclaude/` the litgoal goal, autoloop
409
448
  counter and ledger, the lit-plan, interface-probe and motion turn records, and the auto-handoff
410
449
  settings and session record. Each file must be a regular file under its size cap that resolves
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litfamily/litclaude",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "Claude Code plugin: add lit to a prompt and Claude plans, tests, checks and reviews the work, then leaves a handoff for the next session.",
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": "1.0.22",
4
+ "version": "1.0.24",
5
5
  "author": {
6
6
  "name": "LitClaude contributors"
7
7
  },
@@ -30,8 +30,8 @@
30
30
  "band": {
31
31
  "type": "string",
32
32
  "title": "LIT band",
33
- "description": "When the band shows above the prompt: auto (only while LitClaude work is active), always or off.",
34
- "default": "auto"
33
+ "description": "When the LIT card shows above the prompt: always (the whole session, idle included), auto (only while LitClaude work is active or a skill of this request is lit) or off.",
34
+ "default": "always"
35
35
  }
36
36
  }
37
37
  }
@@ -14,7 +14,7 @@ import {
14
14
  toneText,
15
15
  } from "../lib/hud-accent-palette.mjs";
16
16
  import { latestUsageTokensCached } from "../lib/cache-measurement.mjs";
17
- import { ignitionStatePath, readIgnitionState, renderIgnitionSegment } from "../lib/hud-ignition.mjs";
17
+ import { ignitionStatePath } from "../lib/hud-ignition.mjs";
18
18
  import { jevHudState } from "../lib/jev-skill-hint.mjs";
19
19
  import { resolveProjectStateRoot } from "../lib/project-state-root.mjs";
20
20
  import { recordContextUsage, resolveAutoHandoff } from "../lib/auto-handoff.mjs";
@@ -344,13 +344,10 @@ const main = async () => {
344
344
  const contextText = contextPct === null
345
345
  ? `${text("ctx")} ${text(`?/${windowText}`)}`
346
346
  : `${text("ctx")} ${accent("[")}${makeContextBar(contextPct)}${accent("]")} ${text(`${contextPct}%/${windowText}`)}`;
347
- const ignition = renderIgnitionSegment(readIgnitionState(status.session_id), {
348
- depth: plainOutput ? "plain" : colorDepth,
349
- });
350
347
  const effort = effortLabel(status);
351
348
  const jev = jevBadge(status);
352
349
  const autoHandoff = autoHandoffSegment(status, knownContextPct);
353
- const line = `${prefix}${ignition ? ` ${ignition}` : ""} ${accent("|")} ${text(model)}${effort ? ` ${text(effort)}` : ""}${jev ? ` ${jev}` : ""} ${accent(sep)} ${contextText}${autoHandoff ? ` ${accent(sep)} ${autoHandoff}` : ""}${usageText}${git ? ` ${accent(sep)} ${text("git")} ${text(git)}` : ""}`;
350
+ const line = `${prefix} ${accent("|")} ${text(model)}${effort ? ` ${text(effort)}` : ""}${jev ? ` ${jev}` : ""} ${accent(sep)} ${contextText}${autoHandoff ? ` ${accent(sep)} ${autoHandoff}` : ""}${usageText}${git ? ` ${accent(sep)} ${text("git")} ${text(git)}` : ""}`;
354
351
  process.stdout.write(`${line}\n`);
355
352
 
356
353
  const lastMessage = latestUserMessage(status.transcript_path);
@@ -9,7 +9,7 @@ import {
9
9
  } from "../lib/wikify-knowledge.mjs";
10
10
 
11
11
  const protocolVersion = "2024-11-05";
12
- const serverVersion = "1.0.22";
12
+ const serverVersion = "1.0.24";
13
13
 
14
14
  const publicSourceReadTool = {
15
15
  name: "public_source_read",
@@ -39,6 +39,6 @@ export default function LitBand(props, surface) {
39
39
  if (surface.state?.live !== holder) surface.setState({ live: holder, now: holder.now });
40
40
 
41
41
  if (!model.visible && !timelineBusy(holder.timeline, holder.now, profile)) return empty();
42
- const frame = composeBand(model, sampleTimeline(holder.timeline, holder.now, profile), { columns: props.columns, glyphs: props.glyphs });
42
+ const frame = composeBand(model, sampleTimeline(holder.timeline, holder.now, profile), { columns: props.columns, glyphs: props.glyphs, accent: props.accent });
43
43
  return renderBand(surface.elements, frame);
44
44
  }
@@ -1,7 +1,9 @@
1
- // LitClaude's Claude Code mod (hooks/hooks.json `modules`). It draws the LIT band above the prompt
2
- // while LitClaude work is active, adds the discipline and progress to the spinner, raises gate
3
- // notices, and serves /lit-evidence. It is read-only: it reads LitClaude's own state files through
4
- // lib/mod/state.mjs and the host's live figures, and never writes, spawns, fetches or calls a model.
1
+ // LitClaude's Claude Code mod (hooks/hooks.json `modules`). It draws the LIT card above the prompt
2
+ // for the whole session (the `band` setting can limit it to active work or turn it off), lights a
3
+ // chip for each LitFamily skill that fires, adds the discipline and progress to the spinner, raises
4
+ // gate notices, and serves /lit-evidence. It is read-only: it reads LitClaude's own state files and
5
+ // the Git HEAD through lib/mod/state.mjs, the settings for the status line's accent, and the host's
6
+ // live figures, and never writes, spawns, fetches or calls a model.
5
7
  // Claude Code releases before mods ignore the `modules` key and keep running the settings hooks.
6
8
  //
7
9
  // The mods loader only follows `$` into functions declared at the top of this file, so each helper
@@ -10,8 +12,9 @@
10
12
  import { composeBand, composeEvidence } from "../lib/mod/frame.mjs";
11
13
  import { TIMINGS } from "../lib/mod/motion.mjs";
12
14
  import { renderBand, renderRows } from "../lib/mod/render.mjs";
13
- import { normalizeModSettings, resolveGlyphs } from "../lib/mod/settings.mjs";
14
- import { detectIconFont, readLedgerTail, readLitSnapshot } from "../lib/mod/state.mjs";
15
+ import { accentFromSettings, normalizeModSettings, resolveGlyphs } from "../lib/mod/settings.mjs";
16
+ import { chipStates, litSkillId, noteSkill } from "../lib/mod/skills.mjs";
17
+ import { detectIconFont, readGitBranch, readLedgerTail, readLitSnapshot } from "../lib/mod/state.mjs";
15
18
  import { settledSample } from "../lib/mod/timeline.mjs";
16
19
  import {
17
20
  EMPTY_ACTIVITY,
@@ -19,7 +22,11 @@ import {
19
22
  buildEvidenceModel,
20
23
  detectCompletion,
21
24
  diffNotices,
25
+ mergeSpawned,
22
26
  noteAgentActivity,
27
+ noteSpawn,
28
+ noteSpawnActivity,
29
+ noteSpawnEnded,
23
30
  spinnerSuffix,
24
31
  trackAgents,
25
32
  } from "../lib/mod/view.mjs";
@@ -41,6 +48,7 @@ const createLive = (options) => ({
41
48
  snapshot: null,
42
49
  model: null,
43
50
  agents: [],
51
+ spawned: {},
44
52
  usage: null,
45
53
  activity: EMPTY_ACTIVITY,
46
54
  statusById: {},
@@ -55,6 +63,12 @@ const createLive = (options) => ({
55
63
  evidenceTab: "goal",
56
64
  ledger: [],
57
65
  celebrated: new Set(),
66
+ accent: undefined,
67
+ git: null,
68
+ fired: [],
69
+ settledAt: 0,
70
+ ignitionAt: undefined,
71
+ lastDiscipline: null,
58
72
  });
59
73
 
60
74
  const stateIo = ($) => ({
@@ -77,6 +91,9 @@ async function rebuild($, live) {
77
91
  isWorking: live.isWorking,
78
92
  notice: live.notice,
79
93
  completion: live.completion,
94
+ skills: chipStates(live.fired, live.settledAt),
95
+ lastDiscipline: live.lastDiscipline,
96
+ git: live.git,
80
97
  });
81
98
  live.model = next;
82
99
  if (previous?.visible && !next.visible && !live.exiting) {
@@ -112,20 +129,42 @@ function celebrate($, live, finish) {
112
129
  });
113
130
  }
114
131
 
132
+ // A firing of a skill or command by name; anything that is not LitClaude's is ignored.
133
+ async function noteFiring($, live, name) {
134
+ const id = litSkillId(name);
135
+ if (!id) return;
136
+ live.fired = noteSkill(live.fired, id, await $.clock.now());
137
+ live.lastDiscipline = id;
138
+ await rebuild($, live);
139
+ }
140
+
141
+ // The prompt hook rewrites the ignition record on every prompt; a new record naming a discipline is
142
+ // a bare `lit` (or slash) routing, and lights that skill's chip at the time the hook wrote it.
143
+ function noteIgnition(live, ignition, now) {
144
+ if (!ignition || ignition.at === live.ignitionAt) return;
145
+ live.ignitionAt = ignition.at;
146
+ live.lastDiscipline = ignition.discipline;
147
+ const id = litSkillId(ignition.discipline);
148
+ if (id) live.fired = noteSkill(live.fired, id, ignition.at ?? now);
149
+ }
150
+
115
151
  async function refresh($, live) {
116
152
  if (live.refreshing) return;
117
153
  live.refreshing = true;
118
154
  try {
119
155
  const [cwd, sessionId] = await Promise.all([$.session.cwd(), $.session.id()]);
120
156
  const fresh = await readLitSnapshot(stateIo($), { cwd, sessionId, env: live.env });
121
- live.agents = await $.agent.list().catch(() => []);
157
+ live.git = await readGitBranch(stateIo($), cwd).catch(() => live.git);
158
+ const listed = await $.agent.list().catch(() => []);
122
159
  live.usage = await $.session.usage().catch(() => live.usage);
123
160
  const now = await $.clock.now();
161
+ ({ agents: live.agents, spawned: live.spawned } = mergeSpawned(listed, live.spawned, now));
124
162
  live.activity = trackAgents(live.activity, live.agents, live.statusById, now);
125
163
  live.statusById = Object.fromEntries(live.agents.map((agent) => [agent.id, agent.status]));
126
164
  const before = live.snapshot;
127
165
  const previousModel = live.model;
128
166
  live.snapshot = fresh;
167
+ noteIgnition(live, fresh.ignition, now);
129
168
  const finish = detectCompletion(before, fresh);
130
169
  if (finish) celebrate($, live, finish);
131
170
  const next = await rebuild($, live);
@@ -167,6 +206,8 @@ export const register = (on, options) => {
167
206
  XDG_DATA_HOME: await $.env.get("XDG_DATA_HOME"),
168
207
  LOCALAPPDATA: await $.env.get("LOCALAPPDATA"),
169
208
  });
209
+ live.settledAt = await $.clock.now();
210
+ live.accent = accentFromSettings(await $.settings.read().catch(() => null), await $.env.get("LITCLAUDE_HUD_ACCENT"));
170
211
  await $.command.register({ name: PANE, description: "Show LitClaude goal criteria, evidence, lanes and gates in a pane" });
171
212
  await refresh($, live);
172
213
  $.clock.every(POLL_MS, () => {
@@ -182,7 +223,15 @@ export const register = (on, options) => {
182
223
  });
183
224
 
184
225
  on("turn.complete", async ($, e, next) => {
226
+ // A subagent's or teammate's turn ends its run, not the main turn the card follows.
227
+ if (typeof e.agentId === "string") {
228
+ live.spawned = noteSpawnEnded(live.spawned, e.agentId);
229
+ refreshSoon($, live);
230
+ return next(e);
231
+ }
185
232
  live.isWorking = false;
233
+ // The request is over: its chips dim to the recent list until a skill fires again.
234
+ live.settledAt = await $.clock.now();
186
235
  refreshSoon($, live);
187
236
  return next(e);
188
237
  });
@@ -194,12 +243,36 @@ export const register = (on, options) => {
194
243
  });
195
244
 
196
245
  on("tool.call", async ($, e, next) => {
246
+ if (e.tool === "Skill") await noteFiring($, live, e.skill);
247
+ if (typeof e.agentId === "string") live.spawned = noteSpawnActivity(live.spawned, e.agentId, await $.clock.now());
197
248
  const result = await next(e);
198
- if (typeof e.agentId === "string") live.activity = noteAgentActivity(live.activity, e.agentId, await $.clock.now());
249
+ if (typeof e.agentId === "string") {
250
+ const now = await $.clock.now();
251
+ live.activity = noteAgentActivity(live.activity, e.agentId, now);
252
+ live.spawned = noteSpawnActivity(live.spawned, e.agentId, now);
253
+ }
199
254
  refreshSoon($, live);
200
255
  return result;
201
256
  }).catch(($, e, next) => next(e));
202
257
 
258
+ // Every subagent, teammate and workflow agent starts here. A workflow's never shows in
259
+ // `$.agent.list()`, so the card keeps its own record until the agent's turn.complete.
260
+ on("agent.spawn", async ($, e, next) => {
261
+ const result = await next(e);
262
+ if (typeof result?.agentId === "string") {
263
+ live.spawned = noteSpawn(live.spawned, { id: result.agentId, description: e.description, type: e.subagentType, at: await $.clock.now() });
264
+ refreshSoon($, live);
265
+ }
266
+ return result;
267
+ }).catch(($, e, next) => next(e));
268
+
269
+ // A typed `/litclaude:<name>` and the Skill tool both expand the skill's prompt here; the text
270
+ // passes through untouched.
271
+ on("skill.prompt", async ($, e, next) => {
272
+ await noteFiring($, live, e.skill);
273
+ return next(e);
274
+ }).catch(($, e, next) => next(e));
275
+
203
276
  on("ui.fault", async ($, e, next) => {
204
277
  if (e.component === "AbovePrompt") live.clientFailed = true;
205
278
  return next(e);
@@ -214,9 +287,9 @@ export const register = (on, options) => {
214
287
  const columns = Math.max(20, e.props.bodyColumns ?? e.viewport?.columns ?? 80);
215
288
  let drawn = null;
216
289
  if (elements.Client && !live.clientFailed) {
217
- drawn = elements.Client({ key: "lit-band", module: "./lit-band-client.mjs", props: { model, columns, glyphs, motion: settings.motion } });
290
+ drawn = elements.Client({ key: "lit-band", module: "./lit-band-client.mjs", props: { model, columns, glyphs, motion: settings.motion, accent: live.accent } });
218
291
  } else if (model.visible) {
219
- drawn = renderBand(elements, composeBand(model, settledSample(model), { columns, glyphs }));
292
+ drawn = renderBand(elements, composeBand(model, settledSample(model), { columns, glyphs, accent: live.accent }));
220
293
  }
221
294
  const below = await next(e);
222
295
  if (!drawn) return below;
@@ -11,8 +11,8 @@ export const canonicalSkillResourceManifest = Object.freeze(
11
11
  ["lib/durable-plan.mjs", "33fc7c2ba6305602c73650c8f29e6141e93b8ac32a444f772807e11bf8c22e44"],
12
12
  ["lib/immutable-expected-file-map.mjs", "4061486bc8eda8a53e56b7b346a1e400e7c858c0a682c83362fea460b46dfd4b"],
13
13
  ["lib/lit-mark.mjs", "cb57921a3da0ff983c74e9426dcd3b2379db090198743139e379a059c128021e"],
14
- ["lib/office-runtime-lock/package-lock.json", "4b9552dba69bf2803cecdf71996d26de0497004c00d1e1f5753a5b299f85d8ba"],
15
- ["lib/office-runtime-lock/package.json", "edf7fee2ba78d6ad42bf5761678b2a2210adb7282108864360f83c46adb59708"],
14
+ ["lib/office-runtime-lock/package-lock.json", "16d19b420b410035e9ca6886133368444a2b438121919857b3e8915be280c6a6"],
15
+ ["lib/office-runtime-lock/package.json", "5e4b0c7ee7bd5ff88c888f7f46152c605caf128cc4e7bfd16423571210925394"],
16
16
  ["lib/office-runtime-lock/requirements.lock", "7c63ce0de1ae9a912d1e546e9c01dd99a705d4a50435f36040d351cc3e6e6014"],
17
17
  ["lib/office-runtime.mjs", "24d108a4ae7aa98dd0d6f2fa66686d7c9f020ecf42fe641241be2869075bbea3"],
18
18
  ["lib/office_data.py", "0f4c2449622b6d59630e60508594d181279052c41918df4370e11a68df1ee239"],
@@ -848,11 +848,11 @@ export const canonicalSkillResourceManifest = Object.freeze(
848
848
  ["skills/readme-studio/templates/cover-source.json", "182051737e4cda9002360316bb2c01b500ecc20c27953a2d332d908f044f5cd7"],
849
849
  ["skills/readme-studio/templates/hyperframes-cover/index.html", "edaee52e01cc7d90524b43e3da80b4bc6514fef1d17b4317cbf15ed5e36a5c67"],
850
850
  ["skills/readme-studio/templates/hyperframes-cover/index.motion.json", "bef879ce5abe7810d73cc733ab635884ec807eccd7bc5e1c9bc9900049187249"],
851
- ["skills/readme-studio/templates/hyperframes-cover/package-lock.json", "facce3fcbba7b9b5cd5b368b78192b035663a7c957f7802f97f037497dc193be"],
851
+ ["skills/readme-studio/templates/hyperframes-cover/package-lock.json", "5adacd09e4608c8c3917ca0245052e4d3abae3a566ebe07a2fdad593c67cb91c"],
852
852
  ["skills/readme-studio/templates/hyperframes-cover/package.json", "0993c6f04892f5a318cc99030de653b7c68d3ea6789c69b30b6a2ae80e4120f0"],
853
853
  ["skills/readme-studio/templates/readme-cover-section.md", "1dc4c536f65d3437f12e09a76170a8dc76557225587c7453631a06604a21573d"],
854
854
  ["skills/readme-studio/templates/readme-facts.json", "fb95b8aece0e8591726d3a36e25cc66712b0c6e57835c848db0f07b745e73205"],
855
- ["skills/readme-studio/templates/remotion-cover/package-lock.json", "45cae4a5acee8bc98ef449a2ae91c4eae775afe7fc94769b86017bedb9301511"],
855
+ ["skills/readme-studio/templates/remotion-cover/package-lock.json", "422af74482c6c36e5bfaa86c2fe6a78e7ff54d27d3119f9c462685333a449857"],
856
856
  ["skills/readme-studio/templates/remotion-cover/package.json", "7921a415cf96343b46c9a2a3bea9160ecc807710fb6a34ba227704eff7d83887"],
857
857
  ["skills/readme-studio/templates/remotion-cover/src/index.tsx", "81cf3edd996a98ddca46465d4bc84ca0203ecd3bdce1736bf1dc3d9f2bc15af9"],
858
858
  ["skills/readme-studio/templates/typography/package-lock.json", "1d6dc1ae0d06d0a3a5e1b2b45bea1d40be7497f1f2fb856eefd33cc3649af5bd"],
@@ -1,18 +1,13 @@
1
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
1
+ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
4
 
5
- // Per-session ignition record: the prompt hook writes it on every UserPromptSubmit and the HUD
6
- // status line reads it back, so the status bar reflects the discipline actually selected for
7
- // the current turn instead of anything the model chooses to draw. Mode is per turn and never
8
- // durable, so the record lives in the per-user temp directory: not in the repository state
9
- // root (activation must stay side-effect-free there) and not in the home directory.
10
-
11
- const IGNITION_PALETTE = Object.freeze({
12
- truecolor: "\x1b[38;2;255;99;55m",
13
- "256": "\x1b[38;5;203m",
14
- "16": "\x1b[91m",
15
- });
5
+ // Per-session ignition record: the prompt hook writes it on every UserPromptSubmit and the LIT
6
+ // card (hooks/lit-mod.mjs, through lib/mod/state.mjs) reads it back, so the card reflects the
7
+ // discipline actually selected for the current turn instead of anything the model chooses to
8
+ // draw. Mode is per turn and never durable, so the record lives in the per-user temp directory:
9
+ // not in the repository state root (activation must stay side-effect-free there) and not in the
10
+ // home directory.
16
11
 
17
12
  export const ignitionStateRoot = (env = process.env) => {
18
13
  const override = env.LITCLAUDE_HUD_STATE_ROOT?.trim();
@@ -47,28 +42,3 @@ export function writeIgnitionState({ sessionId, discipline }, env = process.env,
47
42
  renameSync(staging, target);
48
43
  return record;
49
44
  }
50
-
51
- export function readIgnitionState(sessionId, env = process.env) {
52
- const target = ignitionStatePath(sessionId, env);
53
- if (!existsSync(target)) return null;
54
- try {
55
- const parsed = JSON.parse(readFileSync(target, "utf8"));
56
- if (!parsed || typeof parsed !== "object") return null;
57
- return {
58
- sessionId: typeof parsed.sessionId === "string" ? parsed.sessionId : null,
59
- discipline: typeof parsed.discipline === "string" && parsed.discipline ? parsed.discipline : null,
60
- at: typeof parsed.at === "string" ? parsed.at : null,
61
- };
62
- } catch {
63
- return null;
64
- }
65
- }
66
-
67
- export function renderIgnitionSegment(state, { depth = "plain" } = {}) {
68
- const discipline = state?.discipline ?? null;
69
- if (!discipline) return "";
70
- const label = ignitionMark(discipline);
71
- if (depth === "plain") return label;
72
- const paint = IGNITION_PALETTE[depth] ?? IGNITION_PALETTE["256"];
73
- return `\x1b[1m${paint}${label}\x1b[0m`;
74
- }