@litfamily/litclaude 1.0.25 → 1.0.27

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,22 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.27 - 2026-10-13
4
+
5
+ - The lit-loop map now moves when the work happens in the shell. Writing a test with a shell command (`cat > test/…`, `>>`, `tee` or `sed -i`) turns the map to RED, changing a source file the same way turns it to GREEN, and the next test run turns it to VERIFY. A failing test run while the criterion is still pinned also counts as RED, including a bare `npm test` that exits with an error. The card keeps only the stage, never the command or the file names.
6
+ - `litclaude-ai install` no longer stops with `INSTALL_OWNERSHIP_CONFLICT` when the Claude desktop app has marked the installed plugin as in use. The app's `.in_use` folder, with its process-numbered entries, is left in place; anything else inside it is still reported.
7
+ - The start-work gate lets read-only git through during active work: `git status`, `git diff`, `git log` and `git show`, with any paths they name still checked against the work's grants. Its own lifecycle commands, `litclaude-ai start-work progress`, `complete` and `cancel`, pass too. `node -e` stays blocked, because its script cannot be read as read-only from the command alone.
8
+ - The README demo film is retaken: the card's workflow map turns RED on the failing test, then GREEN and VERIFY when the tests pass, and the live step names the running tool and its time.
9
+
10
+ ## 1.0.26 - 2026-10-13
11
+
12
+ - The LIT card now shows where you are in the LIT workflow. A map row lights the current stage of the running family (interview, plan, work, review, loop, research), and a detail line under it says what that stage is doing: the interview round and ambiguity, whether the plan awaits approval, how many tasks are checked and which comes next, each review lane's verdict. On the right of that line the live step names the tool the main loop is running and for how long, or `thinking` between tool calls, and never shows its arguments.
13
+ - The map moves on its own. LitClaude's hooks advance it as lit-plan saves the plan, start-work's tasks are checked, review lanes report their verdicts and a lit-loop criterion goes from RED to GREEN to VERIFY, so the model does not have to report each step. For what the hooks cannot see, such as a loop step or a crucible verdict, the new `litclaude-ai workflow-stage` command records the stage.
14
+ - In the Claude desktop app the card is drawn as one picture. The app sets text in a proportional font, which pulled the terminal card apart there, so the desktop and editor surfaces now get the same rows as a single drawing, moving under the same `motion` setting. The terminal keeps the card it had.
15
+ - A new `notify` option raises a notification through your own notification channel when LIT work completes or a LIT turn longer than three minutes ends. It is `off` unless you turn it on with `/config` or `litclaude-ai config notify finish`.
16
+ - The start-work gate no longer blocks read-only searches. It used to read a grep or rg pattern, a quoted phrase passed to a node script, or `litclaude-ai start-work status` as an unverified path and deny it. Patterns and plain words now count as data, while the searched paths are still checked against the work's grants.
17
+ - `litclaude-ai doctor` reports the card options before the Claude validation step.
18
+ - The README card pictures are retaken to show the workflow map and the detail line.
19
+
3
20
  ## 1.0.25 - 2026-10-12
4
21
 
5
22
  - The LIT card now moves while LitClaude is at work, so a glance tells you it is busy. The LIT pill and the border slowly glow from pink to flame orange and back, a light passes over the current phase and the skill chips of this request, and a spark runs along the top edge and back along the bottom one. When the turn ends the card goes still again. With the `motion` setting at `subtle` only the glow stays, and `off` keeps the card still.
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.25/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/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.27/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/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.25/docs/assets/readme/badge-version.svg" alt="1.0.25" />
8
- <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
7
+ <img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/docs/assets/readme/badge-version.svg" alt="1.0.27" />
8
+ <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/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.25/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.27/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.25/docs/privacy.md)
77
- - [Migration and ownership conflicts](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/migration.md)
78
- - [Release history](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/CHANGELOG.md)
79
- - [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/LICENSE)
76
+ - [Privacy and network behavior](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/docs/privacy.md)
77
+ - [Migration and ownership conflicts](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/docs/migration.md)
78
+ - [Release history](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/CHANGELOG.md)
79
+ - [MIT license](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/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.25/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/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.27/docs/assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/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.25/docs/assets/readme/badge-version.svg" alt="1.0.25" />
8
- <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/assets/readme/badge-license.svg" alt="MIT license" /></a>
7
+ <img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/docs/assets/readme/badge-version.svg" alt="1.0.27" />
8
+ <a href="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/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.25/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.27/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.25/docs/privacy.md)
75
- - [이전과 소유권 충돌](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/docs/migration.md)
76
- - [변경 이력](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/CHANGELOG.md)
77
- - [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.25/LICENSE)
74
+ - [개인정보와 네트워크 동작](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/docs/privacy.md)
75
+ - [이전과 소유권 충돌](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/docs/migration.md)
76
+ - [변경 이력](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/CHANGELOG.md)
77
+ - [MIT 라이선스](https://cdn.jsdelivr.net/npm/@litfamily/litclaude@1.0.27/LICENSE)
@@ -23,6 +23,11 @@ const legacyVendorPathMap = [
23
23
  // Python writes bytecode next to the scripts it runs. That cache is generated, never user data, so ownership
24
24
  // checks skip it. A symlink or a plain file named __pycache__ is not a cache and is still reported.
25
25
  const isBytecodeCache = (name, value) => (value.isDirectory() && name === "__pycache__") || (value.isFile() && name.endsWith(".pyc"));
26
+ // Claude Code and the desktop app mark a cache version they have loaded with `.in_use/`, empty or
27
+ // holding one pid-named entry per process. That marker is the host's: ownership checks skip it and
28
+ // never remove it. Any other name inside it, or a symlink, is still reported.
29
+ const isHostUseMarker = (path, name, value) => name === hostUseMarker && value.isDirectory()
30
+ && readdirSync(path, { withFileTypes: true }).every((entry) => /^\d+$/u.test(entry.name) && (entry.isDirectory() || entry.isFile()));
26
31
  const conflict = (path, detail = "") => { throw new Error(`INSTALL_OWNERSHIP_CONFLICT: ${path}; use a compatible CLI or preserve this modified, foreign, or unsupported state before retrying${detail ? `. ${detail}` : ""}`); };
27
32
  const stat = (path) => {
28
33
  try { return lstatSync(path); } catch (error) { if (error.code === "ENOENT") return null; throw error; }
@@ -49,7 +54,7 @@ function treeHash(root) {
49
54
  if (!prefix && name === receiptName) continue;
50
55
  const next = join(path, name), rel = prefix ? `${prefix}/${name}` : name, value = lstatSync(next);
51
56
  if (isBytecodeCache(name, value)) continue;
52
- if (!prefix && name === hostUseMarker && value.isDirectory() && readdirSync(next).length === 0) continue;
57
+ if (!prefix && isHostUseMarker(next, name, value)) continue;
53
58
  if (value.isDirectory()) { entries.push([rel, "directory"]); walk(next, rel); }
54
59
  else if (value.isFile() && value.nlink === 1) entries.push([rel, "file", createHash("sha256").update(readFileSync(next)).digest("hex")]);
55
60
  else conflict(next);
@@ -69,6 +74,7 @@ function detailedTree(root, { receipt = false } = {}) {
69
74
  rows.push([prefix, "directory", value.mode & 0o777]);
70
75
  for (const name of readdirSync(path).sort()) {
71
76
  if (!prefix && !receipt && name === receiptName) continue;
77
+ if (!prefix && isHostUseMarker(join(path, name), name, lstatSync(join(path, name)))) continue;
72
78
  walk(join(path, name), prefix ? `${prefix}/${name}` : name);
73
79
  }
74
80
  return;
@@ -270,6 +276,18 @@ export function assertCurrentPointer(home, current, target, version, { expectedV
270
276
  return currentVersion;
271
277
  }
272
278
 
279
+ // Empties an owned tree before a fresh copy, leaving the host's `.in_use` marker in place.
280
+ export function clearOwnedTree(path) {
281
+ const value = stat(path);
282
+ if (!value) return;
283
+ if (!value.isDirectory() || value.isSymbolicLink()) { rmSync(path, { recursive: true, force: true }); return; }
284
+ for (const name of readdirSync(path)) {
285
+ const next = join(path, name);
286
+ if (isHostUseMarker(next, name, lstatSync(next))) continue;
287
+ rmSync(next, { recursive: true, force: true });
288
+ }
289
+ }
290
+
273
291
  export function removeEmptyDirectory(path) {
274
292
  try { rmdirSync(path); } catch (error) { if (!["ENOENT", "ENOTEMPTY", "EEXIST"].includes(error.code)) throw error; }
275
293
  }
@@ -39,13 +39,14 @@ import { runStartWorkContinuationCli } from "../plugins/litclaude/lib/start-work
39
39
  import { runStartWorkCli } from "../plugins/litclaude/lib/start-work-cli.mjs";
40
40
  import { runLitgoalCli } from "../plugins/litclaude/lib/litgoal/cli.mjs";
41
41
  import { runWorkflowCheckCli } from "../plugins/litclaude/lib/workflow-check.mjs";
42
+ import { runWorkflowStageCli } from "../plugins/litclaude/lib/workflow-stage.mjs";
42
43
  import { runWikifyKnowledgeCli } from "../plugins/litclaude/lib/wikify-knowledge-cli.mjs";
43
44
  import { automaticUpdateRoot, runAutomaticUpdate } from "../plugins/litclaude/lib/automatic-update.mjs";
44
45
  import { jevStatusLine } from "../plugins/litclaude/lib/jev-skill-hint.mjs";
45
46
  import { autoHandoffDoctorLines } from "../plugins/litclaude/lib/auto-handoff.mjs";
46
47
  import { resolveProjectStateRoot } from "../plugins/litclaude/lib/project-state-root.mjs";
47
48
  import { runUpdateNotifier } from "./update-notifier.mjs";
48
- import { assertInstallPath, assertOwnedTree, assertCurrentPointer, assertOwnedRegistrations, migrateLegacyVendorPaths, planModifiedLegacySkill, preserveModifiedLegacySkill, registeredPluginInstallation, writeOwnershipReceipt, removeEmptyDirectory } from "./install-ownership.mjs";
49
+ import { assertInstallPath, assertOwnedTree, assertCurrentPointer, assertOwnedRegistrations, migrateLegacyVendorPaths, planModifiedLegacySkill, preserveModifiedLegacySkill, registeredPluginInstallation, writeOwnershipReceipt, removeEmptyDirectory, clearOwnedTree } from "./install-ownership.mjs";
49
50
  import { banner, lockup, colorMode, supportsBlocks, terminalRows } from "./litfamily-banner.mjs";
50
51
  import {
51
52
  ICON_FONT,
@@ -81,14 +82,14 @@ const prewarmMotionRuntime = () => {
81
82
  };
82
83
  const version = packageJson.version;
83
84
 
84
- const usage = `Usage: litclaude-ai [--dry-run] <install|doctor|config|path|run|update|uninstall|litgoal|wikify|workflow-check|start-work|start-work-next|public-read|motion-runtime> [...args]
85
+ const usage = `Usage: litclaude-ai [--dry-run] <install|doctor|config|path|run|update|uninstall|litgoal|wikify|workflow-check|workflow-stage|start-work|start-work-next|public-read|motion-runtime> [...args]
85
86
  litclaude-ai --version
86
87
 
87
88
  Commands:
88
89
  install Register the packaged LitClaude plugin in Claude Code.
89
90
  doctor Validate the installed LitClaude plugin path.
90
91
  config [<option> <value>]
91
- Show or change the LIT card options (motion, glyphs, band) in
92
+ Show or change the LIT card options (motion, glyphs, band, notify) in
92
93
  pluginConfigs, the same values /config edits. Works for the
93
94
  desktop app too, which shows no editor for them.
94
95
  path Print the installed Claude plugin path.
@@ -98,6 +99,9 @@ Commands:
98
99
  workflow-check Verify Dynamic workflow, /goal, and subagent delegation readiness.
99
100
  start-work Manage schema-3 bounded-authority lifecycle state.
100
101
  start-work-next Print the next active start-work continuation directive.
102
+ workflow-stage <family> <stage> [--n N --total M --unit U --verdict name=pass]
103
+ Record where a review, loop, iteration or crucible run stands, for
104
+ the LIT card's workflow map (.litclaude/workflow-stage.json).
101
105
  public-read Read a public http(s) source with FetchAttempt/FetchVerdict JSON.
102
106
  motion-runtime Pre-warm (install [--audio] [--word-timing]) or report (status) the
103
107
  lit-typographic-motion runtime cache. Run it outside a sandboxed session.
@@ -383,7 +387,7 @@ const familyBanner = ({ color = shouldColorInstall() } = {}) => [
383
387
  ].join("\n");
384
388
 
385
389
  const printCommandBanner = (parsed) => {
386
- if (["install", "update"].includes(parsed.command)) return;
390
+ if (["install", "update", "workflow-stage"].includes(parsed.command)) return;
387
391
  if (parsed.rest.some((arg) => arg === "--json" || arg.startsWith("--json="))) return;
388
392
  const color = shouldColorInstall();
389
393
  process.stdout.write(`${renderLitClaudeWordmark({ color }).join("\n")}\n`);
@@ -1389,7 +1393,7 @@ const install = async ({ dryRun, rest }) => {
1389
1393
  target: targetPlugin,
1390
1394
  },
1391
1395
  async () => {
1392
- await rm(targetPlugin, { recursive: true, force: true });
1396
+ clearOwnedTree(targetPlugin);
1393
1397
  await mkdir(dirname(targetPlugin), { recursive: true });
1394
1398
  await cp(sourcePlugin, targetPlugin, { recursive: true });
1395
1399
  writeOwnershipReceipt(targetPlugin, "plugin", version);
@@ -1837,6 +1841,9 @@ const main = async () => {
1837
1841
  case "workflow-check":
1838
1842
  exitAfterFlush(runWorkflowCheckCli(root, version, parsed.rest));
1839
1843
  break;
1844
+ case "workflow-stage":
1845
+ exitAfterFlush(runWorkflowStageCli(parsed.rest, process.cwd()));
1846
+ break;
1840
1847
  case "public-read":
1841
1848
  exitAfterFlush(await runPublicRead(parsed));
1842
1849
  break;
@@ -1 +1 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.25"><title>release: 1.0.25</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.25</text></g></svg>
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="146" height="20" role="img" aria-label="release: 1.0.27"><title>release: 1.0.27</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.27</text></g></svg>
package/docs/hooks.md CHANGED
@@ -12,7 +12,7 @@ surface: Claude Code plugin hooks
12
12
  runtime: plugins/litclaude/bin/litclaude-hook.js
13
13
  registration: plugins/litclaude/hooks/hooks.json
14
14
  settings_activation: global Claude settings.json enabledPlugins entry for litclaude@litclaude-ai
15
- events: [SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SubagentStart, SubagentStop, SessionEnd]
15
+ events: [SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, Stop, SubagentStart, SubagentStop, SessionEnd]
16
16
  verdicts: [PASS, FAIL, BLOCKED]
17
17
  ```
18
18
 
@@ -105,10 +105,11 @@ silent no-op for callers that still invoke it directly.
105
105
  | `SessionStart` | `plugins/litclaude/bin/litclaude-hook.js session-start` | Runs the bounded foreground automatic-update barrier on a fresh session (or starts the detached version-cache refresh when the cache is stale), discovers repo-local rule files, names the newest valid `plans/<slug>.md`, and on `source: compact` spends one of two durable re-injection reservations at reduced caps and, with automatic handoff on, reloads the fresh handoff once. |
106
106
  | `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; with `LITCLAUDE_JEV=1` and `TYPESAFE_API_KEY` set, adds at most one Jev skill hint line to an unrouted turn; answers the exact prompts `lit-handoff auto on <percent>`, `lit-handoff auto off` and `lit-handoff auto status` by changing or reporting the automatic handoff setting. |
107
107
  | `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; denies supported reader-facing text writes with a humanizer block-tier finding. |
108
- | `PostToolUse` | `plugins/litclaude/bin/litclaude-hook.js post-tool-use` | Names the post-edit checks the edit actually earned, injects any glob-scoped rule matching the edited paths, and checks successfully created DOCX/PPTX/PDF files for humanizer findings, including paths reported by Bash output. |
108
+ | `PostToolUse` | `plugins/litclaude/bin/litclaude-hook.js post-tool-use` | Moves the LIT card's stage record on from what the tool did (plan saved, task checked, lit-loop step, a reviewer's verdict from an `Agent` result). Names the post-edit checks the edit actually earned, injects any glob-scoped rule matching the edited paths, and checks successfully created DOCX/PPTX/PDF files for humanizer findings, including paths reported by Bash output. |
109
+ | `PostToolUseFailure` | `plugins/litclaude/bin/litclaude-hook.js post-tool-use-failure` | For a Bash command that exits non-zero, such as a bare failing `npm test`, moves the LIT card's lit-loop map from what the command wrote and from its failing test run, which never counts as VERIFY. Nothing else runs on a failed tool. |
109
110
  | `Stop` | `plugins/litclaude/bin/litclaude-hook.js stop` | Holds a lit-plan turn with no persisted plan and a lit workflow turn that edited interface files without a clean interface-probe run (each at most twice per turn), emits bounded start-work continuation on new progress, otherwise applies the opt-in litgoal autoloop gate, and last of all, with automatic handoff on, blocks once per crossing of the chosen percent to have the handoff written. |
110
111
  | `SubagentStart` | `plugins/litclaude/bin/litclaude-hook.js subagent-start` | Registers child-lane and Claude-owned worktree identity. |
111
- | `SubagentStop` | `plugins/litclaude/bin/litclaude-hook.js subagent-stop` | Finalizes the lane without child continuation. |
112
+ | `SubagentStop` | `plugins/litclaude/bin/litclaude-hook.js subagent-stop` | Finalizes the lane without child continuation, and gives a background review-work reviewer's verdict to the LIT card's stage record. |
112
113
  | `SessionEnd` | `plugins/litclaude/bin/litclaude-hook.js session-end` | Records root-session end without blocking Claude. |
113
114
 
114
115
  ### Changed-text humanizer guard
@@ -347,38 +348,74 @@ itself. LitClaude's is `plugins/litclaude/hooks/lit-mod.mjs`, named under `modul
347
348
  setup. The status line stays as it is; the band adds a live layer above the prompt.
348
349
 
349
350
  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
+ same five rows whether LitClaude is working or idle, and takes the status line's accent
351
352
  (`LITCLAUDE_HUD_ACCENT`, read from the installed status line command), so the card and the
352
353
  status line read as one family:
353
354
 
354
355
  ```
355
- ╭─ [🔥 LIT] · lit-loop ─────────────────────────── WORK ● ─╮
356
- │ ● PLAN ━━ [WORK] ━━ ○ REVIEW goal ██░░░ 2/5 │
357
- │ skills [⚡ lit-plan] [⚡ litresearch] [↯ lit-docx] │
358
- ╰─ ctx 42% ▰▰▱▱▱ · 5h 31% · 1w 12% · git main ─────────────╯
356
+ ╭─ [🔥 LIT] · start-work ───────────────────────────────── WORK ● ─╮
357
+ │ interview ✓ ━━ plan ✓ ━━ [work 3/7] ━━ review · ── done · 1 lane │
358
+ │ └ task 3/7 ███░░░░░ · next ▸ 4. README demo - Bash · 0:12 │
359
+ │ skills [⚡ start-work] [↯ lit-plan] │
360
+ ╰─ ctx 42% ▰▰▱▱▱ · 5h 31% · 1w 12% · git main ─────────────────────╯
359
361
  ```
360
362
 
361
363
  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
364
+ glyphs, one cell of the pill's colour with `basic`. The brand is a pink pill, the current stage
363
365
  and the skills of this request are pills on the accent with dark text, and skills from earlier
364
366
  requests are muted grey pills.
365
367
 
366
368
  - 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
369
+ session is idle; `ready` before any) and, on the right, the current stage: `WORK ●`, `RED ●`,
370
+ `idle`, or `DONE ✓` during the finish. While a subagent is still running the card stays at
371
+ work (`LIVE ●` when no stage is current) and never reads `DONE`, even after the main turn ended.
372
+ - The second row is the workflow map: where the session stands in its LIT family
373
+ (`lib/mod/workflow.mjs`). The main chain is `interview › plan › work n/m › review › done`
374
+ (litgoal criteria and autoresearch iterations ride the work stage); litresearch is
375
+ `decompose › wave n › expand › verify › synthesis`; a lit-loop criterion cycles
376
+ `PIN › RED › GREEN › VERIFY › REVIEW › RECORD` (SURFACE shows on VERIFY, CLEAN on RECORD).
377
+ Done stages carry `✓` in a colour graded along the rail from flame orange through pink to the
378
+ accent, the current stage is a pill, the stages ahead are dim `name ·`. The rail into the
379
+ current stage is solid `━`; the rail leaving it fills with the stage's count and, while
380
+ LitClaude works, carries a bright head; the rails ahead are dim `─`. On its right the row keeps
381
+ the live subagents (`● 2 lanes`), the autoloop count and its time left, and `ctx 76% → 80%` when
382
+ the context nears the auto-handoff percent. A session no family places (a lit-docx request, a
383
+ fresh session) keeps the old `PLAN ━━ WORK ━━ REVIEW` track there.
384
+ - The stage comes from the newest of the ignited discipline, a LitClaude skill this request
385
+ fired, and the stage record; with none of those, from the state that says work is under way.
386
+ The detail comes from files LitClaude already writes: the deep-interview state
387
+ (`round 3/12 · ambiguity 42% → 20%`), the lit-plan draft's approval gate (`drafting`,
388
+ `awaiting approval`), the start-work plan's tasks and next task, the litgoal criteria and the
389
+ litresearch session folder (`wave-*.md`, `verify-*.md`, `SYNTHESIS.md`). What no file
390
+ records goes into `.litclaude/workflow-stage.json`, a small record LitClaude's hooks keep
391
+ moving without the model's help. The prompt and PreToolUse hooks start it when lit-plan,
392
+ review-work, lit-loop, autoresearch or lit-crucible starts, and clear it for any other skill. PostToolUse checks the plan stage when lit-plan saves
393
+ `plans/<slug>.md` (`plan ✓`, detail `plan saved`), counts the tasks each time the active
394
+ start-work plan is edited (`work 1/2`), and steps a lit-loop criterion to RED when a test
395
+ file is written, GREEN when the code changes after it, VERIFY when the tests run and RECORD
396
+ when `litgoal record-evidence` runs. For review-work, PreToolUse marks a lane running when a
397
+ prompt titled `Review lane: <lane>.` is spawned, and the lane's `verdict:` line, read by
398
+ PostToolUse for a foreground reviewer and by SubagentStop for a background one, marks it
399
+ passed or failed. The card names the lanes `goal`, `quality`, `security`, `qa` and `context`
400
+ (review-work's scope/diff, package/payload, security/provenance, tests/evidence and
401
+ real-surface/docs lanes); five passes finish the review. The hooks keep only the stage, the
402
+ lane and the counts, never a path, command or prompt. The model can still refine the record
403
+ with `litclaude-ai workflow-stage <family> <stage> [--n --total --unit --verdict
404
+ name=pass|fail|pending|running]` for what the hooks cannot see: a lane run without a child
405
+ agent, a loop step such as SURFACE, an autoresearch iteration, the crucible verdict. Another
406
+ session's record is ignored and a source older than six hours no longer counts.
407
+ - The third row is the detail line: `└` and the current stage's detail (a pink-to-cyan bar for a
408
+ count, the lane verdicts as `✓`, `✗`, `●` while a reviewer runs, or `·`, the next task), and on the right the live step,
409
+ the tool the main loop is running and for how long (`- Bash · 0:12`, `thinking` between tool
410
+ calls). The step shows the tool's name and its clock only, never its arguments, command,
411
+ prompt or paths.
412
+ - The fourth row shows each LitFamily skill that fired as a pill: a Skill tool call naming a LitClaude
376
413
  skill, a typed `/litclaude:<name>`, or bare `lit` routing seen through the ignition record.
377
414
  A skill fired in the current request is bright, with a short light passing over its name as
378
415
  it arrives; when the turn completes it dims into the `recent` list, and a new firing brightens
379
416
  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.
417
+ With no chip yet, the row shows the next task while work runs (when the detail line does not),
418
+ or how to start (`lit <task>`) while idle.
382
419
  - The bottom edge carries the live figures from the status line: context fill, the five-hour
383
420
  and weekly rate windows and the Git branch (read from `HEAD`, across a linked worktree).
384
421
  - A notice takes the chips row for a few seconds, with a toast, when a gate fires: lit-plan not
@@ -399,7 +436,9 @@ stops counting too, so a crashed or lost agent cannot hold the card at work.
399
436
 
400
437
  The card is the terminal's width up to 120 columns and every row is fitted to it, with Hangul and
401
438
  emoji counted as two cells. Nothing wraps: on a narrow terminal the Git branch goes first, then
402
- the weekly and five-hour windows, the lanes, the autoloop count and the long phase labels (`P ━ W ━ R`).
439
+ the weekly and five-hour windows, the lanes and the autoloop count. The map drops its rails, then
440
+ shortens the names (`int ✓ pla ✓ [wor 3/7] rev ·`), then shows only the current stage and its
441
+ neighbours; the review verdicts shorten to three letters and then to their marks.
403
442
 
404
443
  The spinner gains ` · <discipline> · n/m` after its ellipsis. `/lit-evidence` opens a pane with
405
444
  three tabs (keys `1`, `2`, `3`): the goal's criteria with their last evidence, blockers and the
@@ -410,18 +449,31 @@ changes ease in and out (300 ms and 140 ms), notices slide in over 220 ms and le
410
449
  a chip's arrival light runs 400 ms (a plain fade under `subtle`, nothing under `off`), and the
411
450
  finish lasts at most 1.2 s. While LitClaude is at work the card keeps moving so you can tell it
412
451
  is active at a glance: the LIT pill breathes from pink to flame orange every 2.4 s, a light sweeps
413
- across the active phase and this request's skill chips every 1.8 s, and a comet runs along the top rule and back along the
414
- bottom one every 2.6 s. Under `subtle` only the breathing stays; under `off` nothing moves. An
452
+ across the current stage and this request's skill chips every 1.8 s, a bright head runs the rail
453
+ leaving the current stage every 1.4 s, the count bar's head shimmers, and a comet runs along the
454
+ top rule and back along the bottom one every 2.6 s. Under `subtle` only the breathing stays; under `off` nothing moves. An
415
455
  idle card holds still. The band's `Client` element animates on Claude Code's frame clock, every
416
456
  33 ms while a transition plays and every 66 ms during work, and stops its timer once the card is
417
457
  idle and settled. In Claude
418
458
  Code 2.1.292 a terminal row whose only change is a non-ASCII glyph is not repainted, so every
419
459
  moving part changes ASCII text or colour: bar cells are coloured spaces, embers are ASCII sparks,
420
- a chip's light moves over its ASCII name, and a glyph swaps only together with its words.
460
+ a chip's light moves over its ASCII name, the map's `━` and `─` cells move by colour alone, the
461
+ step's spinner (`- \ | /`) and clock are ASCII, and a glyph swaps only together with its words.
462
+
463
+ The desktop app's Code tab, and the other surfaces that are not a terminal, draw a mod's `Text` in
464
+ a proportional font and cut each span to the cells it was laid out for, so the card above comes
465
+ apart there. On those surfaces the mod draws the same five rows as one `Svg` element instead
466
+ (`lib/mod/svg-card.mjs`): monospace text placed by counted cells, real pills and a rounded border.
467
+ It shows the settled state, with no entrances or crossfades. While LitClaude works it moves with
468
+ SMIL animation inside the drawing, which the surface runs in a script-less sandboxed frame: the
469
+ pill and the border breathe and the step's spinner turns, and on `full` a light crosses the
470
+ current stage and the current chips, a head runs the rail leaving it, the count bar's head
471
+ shimmers and a comet runs around the border. An idle card is a still image. The step's clock
472
+ moves with each new drawing, about every two seconds.
421
473
 
422
474
  Options, in `/config` under LitClaude (stored in `pluginConfigs` in `~/.claude/settings.json`,
423
475
  which the terminal and the desktop app's Code tab both read). The installer writes each default
424
- you have not set, so the desktop app, which has no editor for them, starts with all three;
476
+ you have not set, so the desktop app, which has no editor for them, starts with all four;
425
477
  `litclaude-ai config` prints them and `litclaude-ai config <option> <value>` changes one.
426
478
  Uninstall removes only the defaults it wrote that are still unchanged.
427
479
 
@@ -430,6 +482,7 @@ Uninstall removes only the defaults it wrote that are still unchanged.
430
482
  | `motion` | `full`, `subtle` (no overshoot, shimmer, embers, sweep or comet, shorter; the LIT pill still breathes during work), `off` (final frames only) | `full` |
431
483
  | `glyphs` | `auto` (Nerd glyphs when the icon font is installed), `nerd`, or `basic` | `auto` |
432
484
  | `band` | `always` (the whole session, idle included), `auto` (only while work is active or a skill of this request is lit), `off` | `always` |
485
+ | `notify` | `off`, `finish` (a notification through your own notification channel, `$.ui.notify`, when LIT work completes or a LIT turn longer than three minutes ends), `finish+sound` (the same; a sound is added once LitClaude ships one, and none ships yet) | `off` |
433
486
 
434
487
  Unknown values fall back to the default. The fields are plain strings with no `options` list on
435
488
  purpose: Claude Code 2.1.270 and older refuse to load a plugin whose `userConfig` has one, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litfamily/litclaude",
3
- "version": "1.0.25",
3
+ "version": "1.0.27",
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.25",
4
+ "version": "1.0.27",
5
5
  "author": {
6
6
  "name": "LitClaude contributors"
7
7
  },
@@ -32,6 +32,12 @@
32
32
  "title": "LIT band",
33
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
34
  "default": "always"
35
+ },
36
+ "notify": {
37
+ "type": "string",
38
+ "title": "LIT finish notification",
39
+ "description": "off, or finish: a notification through your own notification channel when LIT work completes or a LIT turn longer than three minutes ends. finish+sound also plays a sound once LitClaude ships one; until then it notifies like finish.",
40
+ "default": "off"
35
41
  }
36
42
  }
37
43
  }
@@ -6,6 +6,8 @@ import { fileURLToPath } from "node:url";
6
6
  import { standard, micro, colorMode, supportsBlocks } from "../lib/lit-mark.mjs";
7
7
  import { NEON_END, NEON_START, rainbowText } from "../lib/hud-accent-palette.mjs";
8
8
  import { ignitionMark, writeIgnitionState } from "../lib/hud-ignition.mjs";
9
+ import { DISCIPLINE_ENTRY } from "../lib/mod/workflow.mjs";
10
+ import { observeWorkflowPostTool, observeWorkflowPreTool, observeWorkflowSubagentStop, startWorkflowStage } from "../lib/workflow-stage.mjs";
9
11
  import { renameAliases, renameNote } from "../lib/rename-aliases.mjs";
10
12
  import { hasInterfaceModeWord, interfaceMode } from "../lib/interface-mode.mjs";
11
13
  import { transcriptHasContextPressure } from "../lib/context-pressure.mjs";
@@ -122,6 +124,7 @@ const hookEventNames = {
122
124
  "user-prompt-submit": "UserPromptSubmit",
123
125
  "pre-tool-use": "PreToolUse",
124
126
  "post-tool-use": "PostToolUse",
127
+ "post-tool-use-failure": "PostToolUseFailure",
125
128
  "stop": "Stop",
126
129
  "subagent-start": "SubagentStart",
127
130
  "subagent-stop": "SubagentStop",
@@ -1322,6 +1325,13 @@ switch (eventName) {
1322
1325
  else clearLitPlanTurn(stateRoot);
1323
1326
  recordInterfaceProbeTurn(stateRoot, { sessionId: input.session_id, discipline: trigger?.safetyBlock ? null : trigger?.discipline });
1324
1327
  recordMotionTurn(stateRoot, { sessionId: input.session_id, discipline: trigger?.discipline });
1328
+ // The LIT card's stage record follows the discipline this prompt ignited: it starts over for a
1329
+ // skill only it can follow and is cleared for any other.
1330
+ // A typed `/litclaude:<skill>` is expanded by the host and never routes here, so its skill
1331
+ // is read off the prompt's first word.
1332
+ const typed = /^\/(?:litclaude:)?([a-z][a-z-]{0,40})(?=\s|$)/u.exec(prompt.trim())?.[1];
1333
+ const started = trigger?.discipline && !trigger.safetyBlock ? trigger.discipline : typed && Object.hasOwn(DISCIPLINE_ENTRY, typed) ? typed : null;
1334
+ if (started) startWorkflowStage(cwd, { sessionId: input.session_id, skill: started });
1325
1335
  } catch {
1326
1336
  // Persistence-gate state is advisory to routing; an unwritable record never alters the prompt hook.
1327
1337
  }
@@ -1407,6 +1417,13 @@ switch (eventName) {
1407
1417
  },
1408
1418
  }));
1409
1419
  } else {
1420
+ // The LIT card's stage record: a workflow skill starts it, a review lane's reviewer marks
1421
+ // its lane running.
1422
+ try {
1423
+ observeWorkflowPreTool(cwd, guardInput);
1424
+ } catch {
1425
+ // The stage record only feeds the card; a failed write never changes the tool decision.
1426
+ }
1410
1427
  let humanizerContext = "";
1411
1428
  try {
1412
1429
  const result = evaluateDeliverableHedgeGuard({ input: guardInput, pluginRoot });
@@ -1455,6 +1472,16 @@ switch (eventName) {
1455
1472
  case "post-tool-use": {
1456
1473
  const toolName = typeof input.tool_name === "string" ? input.tool_name : "unknown";
1457
1474
  const filePaths = extractMutatedFilePaths(input);
1475
+ // The LIT card's stage record moves on from what this tool did: the plan saved, a task checked
1476
+ // in the active plan, a lit-loop step, a reviewer's verdict.
1477
+ try {
1478
+ const cwd = typeof input.cwd === "string" ? input.cwd : process.cwd();
1479
+ observeWorkflowPostTool(cwd, input, { filePaths, lifecycleRoot: lifecycleRootFor(cwd) });
1480
+ } catch {
1481
+ // The stage record only feeds the card; a failed write never changes the tool's result.
1482
+ }
1483
+ // A reviewer's report only moves the card; the post-edit checks below are for writes.
1484
+ if (toolName === "Agent") break;
1458
1485
  try {
1459
1486
  const cwd = typeof input.cwd === "string" ? input.cwd : process.cwd();
1460
1487
  observeInterfaceProbeTool(resolveProjectStateRoot(cwd), { input, filePaths });
@@ -1522,6 +1549,17 @@ switch (eventName) {
1522
1549
  // dispatch silent; compact re-injection is delivered by SessionStart source=compact.
1523
1550
  break;
1524
1551
  }
1552
+ case "post-tool-use-failure": {
1553
+ // A Bash command that exits non-zero (a bare failing `npm test`) never reaches PostToolUse. Its
1554
+ // writes and its failing run still move the LIT card's lit-loop map; nothing else runs here.
1555
+ try {
1556
+ const cwd = typeof input.cwd === "string" ? input.cwd : process.cwd();
1557
+ observeWorkflowPostTool(cwd, input, { lifecycleRoot: lifecycleRootFor(cwd), failed: true });
1558
+ } catch {
1559
+ // The stage record only feeds the card.
1560
+ }
1561
+ break;
1562
+ }
1525
1563
  case "subagent-start": {
1526
1564
  try {
1527
1565
  const cwd = typeof input.cwd === "string" ? input.cwd : process.cwd();
@@ -1545,6 +1583,11 @@ switch (eventName) {
1545
1583
  } catch {
1546
1584
  // SubagentStop remains continuation-inert; completion will fail closed on an active lane.
1547
1585
  }
1586
+ try {
1587
+ observeWorkflowSubagentStop(typeof input.cwd === "string" ? input.cwd : process.cwd(), input);
1588
+ } catch {
1589
+ // A background reviewer's verdict only feeds the card.
1590
+ }
1548
1591
  break;
1549
1592
  }
1550
1593
  case "session-end": {
@@ -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.25";
12
+ const serverVersion = "1.0.27";
13
13
 
14
14
  const publicSourceReadTool = {
15
15
  name: "public_source_read",
@@ -40,7 +40,7 @@
40
40
  ],
41
41
  "PostToolUse": [
42
42
  {
43
- "matcher": "^(Write|Edit|MultiEdit|NotebookEdit|Bash)$",
43
+ "matcher": "^(Write|Edit|MultiEdit|NotebookEdit|Bash|Agent)$",
44
44
  "hooks": [
45
45
  {
46
46
  "type": "command",
@@ -51,6 +51,19 @@
51
51
  ]
52
52
  }
53
53
  ],
54
+ "PostToolUseFailure": [
55
+ {
56
+ "matcher": "^Bash$",
57
+ "hooks": [
58
+ {
59
+ "type": "command",
60
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/bin/litclaude-hook.js\" post-tool-use-failure",
61
+ "timeout": 10,
62
+ "statusMessage": "moving the LIT card's workflow map"
63
+ }
64
+ ]
65
+ }
66
+ ],
54
67
  "Stop": [
55
68
  {
56
69
  "hooks": [