@litfamily/lithermes 1.0.25 → 1.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,23 @@
1
- <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/cover-motion.webp" width="100%" alt="LitFamily motion cover: five armored robots power on one by one, the LitHermes 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/lithermes@1.0.26/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/cover-motion.webp" width="100%" alt="LitFamily motion cover: five armored robots power on one by one, the LitHermes robot wakes with glowing eyes and a lit frame, then LITFAMILY and KEEP THE WORK LIT. light up." /></picture></p>
2
2
 
3
3
  <p align="center">
4
- <a href="#install"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/badge-version.svg" alt="1.0.25" /></a>
5
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/badge-license.svg" alt="MIT license" /></a>
4
+ <a href="#install"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/badge-version.svg" alt="1.0.26" /></a>
5
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/badge-license.svg" alt="MIT license" /></a>
6
6
  </p>
7
7
 
8
8
  <p align="center">
9
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/lucide-book-open.svg" width="16" alt="" /> Guide</a> &nbsp;
10
- <a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition film</a> &nbsp;
11
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
9
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-book-open.svg" width="16" alt="" /> Guide</a> &nbsp;
10
+ <a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition film</a> &nbsp;
11
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
12
12
  </p>
13
13
 
14
14
  # LitHermes
15
15
 
16
16
  **Keep the work lit.**
17
17
 
18
- A plugin for **Hermes Agent**. Put `lit` in front of a request, and Hermes plans the task, does it, checks what it did and leaves a note that the next session can pick up.
18
+ A plugin for **Hermes Agent**. Put `lit` in front of a request, and Hermes plans the task, does it, checks the result and writes down where things stand, so the next session can pick up where this one stopped.
19
19
 
20
- **[README and docs on GitHub](https://github.com/wjgoarxiv/lithermes#readme)** · [한국어](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/README_Ko-KR.md)
20
+ **[README and docs on GitHub](https://github.com/wjgoarxiv/lithermes#readme)** · [한국어](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/README_Ko-KR.md)
21
21
 
22
22
  ## Install
23
23
 
@@ -27,7 +27,7 @@ You need Hermes Agent, Node.js 18 or later, and write access to your Hermes home
27
27
  npx --yes --package @litfamily/lithermes@latest -- lithermes install --yes --no-style
28
28
  ```
29
29
 
30
- It installs the plugin and updates your Hermes configuration without stopping to ask; `--no-style` skips the reply-style picker. To try it in a separate profile, set `HERMES_HOME` to an empty directory first. [More install options](https://github.com/wjgoarxiv/lithermes/blob/main/docs/install.md).
30
+ The command copies the plugin into your Hermes home and switches it on, without stopping to ask; `--no-style` skips the question about reply style. To try it in a separate profile first, point `HERMES_HOME` at an empty directory before you install. [More install options](https://github.com/wjgoarxiv/lithermes/blob/main/docs/install.md).
31
31
 
32
32
  ## Your first task
33
33
 
@@ -37,7 +37,7 @@ Restart the Hermes CLI or gateway, then type:
37
37
  lit Build a to-do list in one HTML file without external dependencies. Implement add, complete, and delete; record what you checked and what remains.
38
38
  ```
39
39
 
40
- Once the work has started, the reply opens with a `🔥 **LIT IGNITED · <discipline>** 🔥` line. When it finishes, check the file yourself, then run `/lit-handoff` so the next session in this project can continue. [Automatic handoff](https://github.com/wjgoarxiv/lithermes/blob/main/docs/automatic-handoff.md) is off by default; `/lit-handoff auto on 60` (a whole number from 1 to 99) asks for a handoff before you run `/compact`.
40
+ The reply opens with a `🔥 **LIT IGNITED · <discipline>** 🔥` line once the lit workflow has started. When the task is done, check the file yourself, then run `/lit-handoff` so the next session in this project can carry on from the note it writes. [Automatic handoff](https://github.com/wjgoarxiv/lithermes/blob/main/docs/automatic-handoff.md) is off by default; `/lit-handoff auto on 60` (a whole number from 1 to 99) asks for a handoff before you run `/compact`.
41
41
 
42
42
  ## What you get
43
43
 
package/README_Ko-KR.md CHANGED
@@ -1,23 +1,23 @@
1
- <p align="center"><picture><source media="(prefers-reduced-motion: reduce)" srcset="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/cover-motion.webp" width="100%" alt="LitFamily 모션 커버: 다섯 로봇 패널이 차례로 켜지고, LitHermes 로봇의 눈과 테두리가 빛난 뒤 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/lithermes@1.0.26/readme-assets/cover-motion-still.webp" /><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/cover-motion.webp" width="100%" alt="LitFamily 모션 커버: 다섯 로봇 패널이 차례로 켜지고, LitHermes 로봇의 눈과 테두리가 빛난 뒤 LITFAMILY와 KEEP THE WORK LIT. 문구가 밝아지는 영상" /></picture></p>
2
2
 
3
3
  <p align="center">
4
- <a href="#설치"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/badge-version.svg" alt="1.0.25" /></a>
5
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/badge-license.svg" alt="MIT license" /></a>
4
+ <a href="#설치"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/badge-version.svg" alt="1.0.26" /></a>
5
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/badge-license.svg" alt="MIT license" /></a>
6
6
  </p>
7
7
 
8
8
  <p align="center">
9
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.ko.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/lucide-book-open.svg" width="16" alt="" /> 안내</a> &nbsp;
10
- <a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition 영상</a> &nbsp;
11
- <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
9
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/docs/guide.ko.md"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-book-open.svg" width="16" alt="" /> 안내</a> &nbsp;
10
+ <a href="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/ignition-film.mp4"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-play.svg" width="16" alt="" /> Ignition 영상</a> &nbsp;
11
+ <a href="https://github.com/wjgoarxiv/lithermes/blob/main/LICENSE"><img src="https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/readme-assets/lucide-shield-check.svg" width="16" alt="" /> MIT</a>
12
12
  </p>
13
13
 
14
14
  # LitHermes
15
15
 
16
16
  **Keep the work lit.**
17
17
 
18
- **Hermes Agent**용 플러그인입니다. 요청 앞에 `lit`을 붙이면 Hermes가 작업을 계획하고, 해내고, 한 일을 확인한 뒤, 다음 세션이 이어받을 수 있는 기록을 남깁니다.
18
+ **Hermes Agent**용 플러그인입니다. 요청 앞에 `lit`을 붙이면 Hermes가 작업을 계획하고, 해내고, 결과를 확인한 뒤 어디까지 왔는지 적어 둡니다. 그래서 다음 세션이 그 기록을 읽고 멈춘 자리에서 이어 갈 수 있습니다.
19
19
 
20
- **[GitHub의 README와 문서](https://github.com/wjgoarxiv/lithermes/blob/main/README_Ko-KR.md)** · [English](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.25/README.md)
20
+ **[GitHub의 README와 문서](https://github.com/wjgoarxiv/lithermes/blob/main/README_Ko-KR.md)** · [English](https://cdn.jsdelivr.net/npm/@litfamily/lithermes@1.0.26/README.md)
21
21
 
22
22
  ## 설치
23
23
 
@@ -27,7 +27,7 @@ Hermes Agent와 Node.js 18 이상, 그리고 Hermes 홈(기본 `~/.hermes`)에
27
27
  npx --yes --package @litfamily/lithermes@latest -- lithermes install --yes --no-style
28
28
  ```
29
29
 
30
- 플러그인을 설치하고 Hermes 설정을 고치는 동안 승인을 묻느라 멈추지 않습니다. `--no-style`은 답변 문체 고르기를 건너뜁니다. 따로 떼어 둔 프로필에서 써 보려면 먼저 `HERMES_HOME`을 빈 디렉터리로 지정하세요. 다른 옵션은 [설치 문서](https://github.com/wjgoarxiv/lithermes/blob/main/docs/install.ko.md)에 있습니다.
30
+ 이 명령은 플러그인을 Hermes 홈에 복사하고 켜 두며, 중간에 승인을 묻지 않습니다. `--no-style`은 답변 문체를 묻는 단계를 건너뜁니다. 따로 떼어 둔 프로필에서 먼저 써 보려면 설치 전에 `HERMES_HOME`을 빈 디렉터리로 지정하세요. 다른 옵션은 [설치 문서](https://github.com/wjgoarxiv/lithermes/blob/main/docs/install.ko.md)에 있습니다.
31
31
 
32
32
  ## 첫 작업
33
33
 
@@ -37,7 +37,7 @@ Hermes CLI 또는 gateway를 다시 시작한 뒤 이렇게 입력하세요.
37
37
  lit 외부 의존성 없이 HTML 파일 하나로 할 일 목록을 만들어줘. 추가·완료·삭제를 구현하고 확인한 내용과 다음 행동을 남겨줘.
38
38
  ```
39
39
 
40
- 응답 첫 줄에 `🔥 **LIT IGNITED · <discipline>** 🔥`가 나오면 작업이 시작된 것입니다. 끝나면 파일을 직접 확인하고 `/lit-handoff`를 실행하세요. 같은 프로젝트의 다음 세션이 그 기록을 읽고 이어갑니다. [자동 핸드오프](https://github.com/wjgoarxiv/lithermes/blob/main/docs/automatic-handoff.ko.md)는 기본값이 꺼짐이고, `/lit-handoff auto on 60`(1에서 99 사이의 정수)을 켜 두면 `/compact` 전에 인계 기록을 먼저 요청합니다.
40
+ 응답 첫 줄에 `🔥 **LIT IGNITED · <discipline>** 🔥`가 나오면 lit 작업이 시작된 것입니다. 끝나면 파일을 직접 확인하고 `/lit-handoff`를 실행하세요. 같은 프로젝트의 다음 세션이 그 기록을 읽고 이어 갑니다. [자동 핸드오프](https://github.com/wjgoarxiv/lithermes/blob/main/docs/automatic-handoff.ko.md)는 기본값이 꺼짐이고, `/lit-handoff auto on 60`(1에서 99 사이의 정수)을 켜 두면 `/compact` 전에 인계 기록을 먼저 요청합니다.
41
41
 
42
42
  ## 할 수 있는 일
43
43
 
@@ -185,22 +185,6 @@ def _host_config_path() -> Path:
185
185
  return home / "config.yaml"
186
186
 
187
187
 
188
- def _retired_hint_data_line() -> str | None:
189
- # The Jev skill hint is gone. Files it wrote under Hermes home are left for the
190
- # user to delete; doctor only says where they are.
191
- state = _host_config_path().parent / "lithermes"
192
- try:
193
- found = any(
194
- entry.name == "jev-trace.jsonl" or entry.name.startswith("jev-last-")
195
- for entry in state.iterdir()
196
- )
197
- except OSError:
198
- return None
199
- if not found:
200
- return None
201
- return f"old Jev data can be deleted at {state}/jev-*"
202
-
203
-
204
188
  def _is_secret_looking_scalar(value: object) -> bool:
205
189
  if not isinstance(value, str):
206
190
  return False
@@ -583,9 +567,6 @@ def doctor_report(host_version: str | None = None) -> tuple[list[str], int]:
583
567
  )
584
568
  healthy = healthy and knowledge_ok
585
569
  lines.append(f"[NOTE] {_provider_metrics.capability_line()}")
586
- retired_hint = _retired_hint_data_line()
587
- if retired_hint:
588
- lines.append(f"[NOTE] {retired_hint}")
589
570
  handoff_tag, handoff_text = _auto_handoff.doctor_line()
590
571
  lines.append(f"[{handoff_tag}] {handoff_text}")
591
572
  if version != "unknown":
@@ -1,7 +1,7 @@
1
1
  {
2
- "syncedAt": "2026-10-18T09:42:50.174Z",
2
+ "syncedAt": "2026-10-18T12:47:41.644Z",
3
3
  "source": "bundled-payload",
4
- "sourceHash": "91ef78ac52df745352ac069cd0872ee6a17253c4fa55b5577e52482b9b329971",
4
+ "sourceHash": "509d6b1fd0205b3d3e0817896975e01a9dfa709994485c645f67ef5f49d44ded",
5
5
  "files": [
6
6
  {
7
7
  "path": "NOTICE.md",
@@ -89,7 +89,7 @@
89
89
  },
90
90
  {
91
91
  "path": "diagnostics.py",
92
- "sha256": "1fd173995dd1abc1d58cc719ecd351f91bd1c65c9056fdffa064fda684f18318"
92
+ "sha256": "13fdef893d10cecdffdbb61daf64629e5ccbf4777964e75930ca34017472828e"
93
93
  },
94
94
  {
95
95
  "path": "handoff.py",
@@ -177,7 +177,7 @@
177
177
  },
178
178
  {
179
179
  "path": "plugin.yaml",
180
- "sha256": "d9eae351c038cfaff91980666d4d3ebe8f2ad82f31bf8ef9539fb4c4ad088e84"
180
+ "sha256": "51608ca52a559fb7ab2ddfeea89f5c5a3e976ebb347b22cff8cf5906473fbb35"
181
181
  },
182
182
  {
183
183
  "path": "provider_metrics.py",
@@ -1281,7 +1281,7 @@
1281
1281
  },
1282
1282
  {
1283
1283
  "path": "skills/frontend-ui-ux/references/complete-contract.md",
1284
- "sha256": "df99f8f532c06232220537041128d8a5480bb60aedbbe3af8909444aaa8944ac"
1284
+ "sha256": "6bf0d7d03ede7521da24a267a7f71229fe150a35850b97c797a839607cdfb9fe"
1285
1285
  },
1286
1286
  {
1287
1287
  "path": "skills/frontend-ui-ux/references/composition.md",
@@ -4401,7 +4401,7 @@
4401
4401
  },
4402
4402
  {
4403
4403
  "path": "skills/visual-qa/references/complete-contract.md",
4404
- "sha256": "16ccc6a685865e00a226fbdbbdf1d4e170ca9c37731a94974ea135f281032e51"
4404
+ "sha256": "f7a55c0000bb48c576a0998d661d07183ad2b197bace99cf5a4435cc6ebfbac7"
4405
4405
  },
4406
4406
  {
4407
4407
  "path": "skills/visual-qa/schemas/evidence-manifest-v1alpha1.schema.json",
@@ -1,5 +1,5 @@
1
1
  name: lithermes
2
- version: 1.0.25
2
+ version: 1.0.26
3
3
  description: "Hermes-native workflow toolkit: litgoal durable runtime, 5-lane review orchestrator, Litwork commands, skills, and prompt steering."
4
4
  author: "LitHermes contributors"
5
5
  kind: standalone
@@ -445,7 +445,7 @@ isolated Hermes home. Use `--hermes-home`; never `--home`.
445
445
 
446
446
  ```text
447
447
  $ node packages/lithermes-installer/bin/lithermes.js install --yes --offline --no-hud --hermes-home "$ISOLATED"
448
- Installed LitHermes 1.0.25
448
+ Installed LitHermes 1.0.26
449
449
  plugin: $ISOLATED/plugins/lithermes
450
450
  model config: updated
451
451
 
@@ -462,7 +462,7 @@ installed payload: PASS
462
462
  enabled config: PASS
463
463
 
464
464
  $ hermes lithermes doctor
465
- [OK] plugin.yaml readable (version 1.0.25)
465
+ [OK] plugin.yaml readable (version 1.0.26)
466
466
  [OK] skills bundled: 36
467
467
  [OK] litgoal durable runtime importable
468
468
  ```
@@ -485,7 +485,7 @@ isolated Hermes home. Use `--hermes-home`; never `--home`.
485
485
 
486
486
  ```text
487
487
  $ node packages/lithermes-installer/bin/lithermes.js install --yes --offline --no-hud --hermes-home "$ISOLATED"
488
- Installed LitHermes 1.0.25
488
+ Installed LitHermes 1.0.26
489
489
  plugin: $ISOLATED/plugins/lithermes
490
490
  model config: updated
491
491
 
@@ -502,7 +502,7 @@ installed payload: PASS
502
502
  enabled config: PASS
503
503
 
504
504
  $ hermes lithermes doctor
505
- [OK] plugin.yaml readable (version 1.0.25)
505
+ [OK] plugin.yaml readable (version 1.0.26)
506
506
  [OK] skills bundled: 36
507
507
  [OK] litgoal durable runtime importable
508
508
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@litfamily/lithermes",
3
- "version": "1.0.25",
3
+ "version": "1.0.26",
4
4
  "description": "Hermes Agent plugin that plans, builds, checks and hands off your work when you add one word, lit, to a request.",
5
5
  "keywords": [
6
6
  "hermes-agent",
@@ -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.26"><title>release: 1.0.26</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.26</text></g></svg>
package/src/lib/files.js CHANGED
@@ -106,6 +106,29 @@ function writeFileAtomic(filePath, data, encoding) {
106
106
  }
107
107
  }
108
108
 
109
+ // The payload-relative source a host-written bytecode path belongs to, or null.
110
+ // Only CPython's own layout counts: <dir>/__pycache__/<module>.<tag>[.opt-N].pyc.
111
+ function hostBytecodeSource(relative) {
112
+ const segments = relative.split("/");
113
+ if (segments.length < 2 || segments[segments.length - 2] !== "__pycache__") return null;
114
+ const match = /^([^.]+)\.[a-zA-Z][a-zA-Z0-9_-]*(?:\.opt-[0-9]+)?\.pyc$/.exec(segments[segments.length - 1]);
115
+ return match ? [...segments.slice(0, -2), `${match[1]}.py`].join("/") : null;
116
+ }
117
+
118
+ // Removes the host bytecode for one removed source module, then its cache
119
+ // folder once empty. Anything else in that folder stays where it is.
120
+ function removeHostBytecode(sourceFile) {
121
+ const cacheDir = path.join(path.dirname(sourceFile), "__pycache__");
122
+ if (!fs.lstatSync(cacheDir, { throwIfNoEntry: false })?.isDirectory()) return;
123
+ const stem = path.basename(sourceFile, ".py");
124
+ for (const entry of fs.readdirSync(cacheDir, { withFileTypes: true })) {
125
+ if (entry.isFile() && hostBytecodeSource(`__pycache__/${entry.name}`) === `${stem}.py`) {
126
+ fs.unlinkSync(path.join(cacheDir, entry.name));
127
+ }
128
+ }
129
+ if (fs.readdirSync(cacheDir).length === 0) fs.rmdirSync(cacheDir);
130
+ }
131
+
109
132
  function removeEmptyDirs(dir, stopAt) {
110
133
  let current = dir;
111
134
  while (current && current !== stopAt && current.startsWith(stopAt) && fs.existsSync(current)) {
@@ -118,9 +141,11 @@ function removeEmptyDirs(dir, stopAt) {
118
141
  module.exports = {
119
142
  cleanPythonCache,
120
143
  copyTree,
144
+ hostBytecodeSource,
121
145
  inspectPythonCache,
122
146
  listFiles,
123
147
  removeEmptyDirs,
148
+ removeHostBytecode,
124
149
  sha256,
125
150
  writeFileAtomic,
126
151
  };
@@ -15,7 +15,7 @@ const {
15
15
  revalidateModelConfigPlan,
16
16
  writeConfig,
17
17
  } = require("./config");
18
- const { copyTree, removeEmptyDirs, sha256, writeFileAtomic } = require("./files");
18
+ const { copyTree, removeEmptyDirs, removeHostBytecode, sha256, writeFileAtomic } = require("./files");
19
19
  const { assertStateParent, assertPluginParent, defaultHermesHome, defaultHermesRepo, detectHermesRuntimeRepo, detectHermesVersion, ensureHermesHome, inspectHermesHostCapabilities, LitHermesError } = require("./hermesDiscovery");
20
20
  const { NOT_PROBED_HOST_VERSION } = require("./modelConfigBoundary");
21
21
  const { patchInstalledHermes, patchManifestPath, rollbackPatches } = require("./patch");
@@ -802,6 +802,7 @@ function uninstallLitHermes(flags = {}) {
802
802
  const file = path.join(dest, entry.path);
803
803
  if (isInstalledRegularFile(dest, entry.path) && sha256(file) === entry.sha256) {
804
804
  fs.unlinkSync(file);
805
+ if (entry.path.endsWith(".py")) removeHostBytecode(file);
805
806
  removeEmptyDirs(path.dirname(file), dest);
806
807
  }
807
808
  }
@@ -1,7 +1,7 @@
1
1
  const crypto = require("node:crypto");
2
2
  const fs = require("node:fs");
3
3
  const path = require("node:path");
4
- const { sha256 } = require("./files");
4
+ const { hostBytecodeSource, sha256 } = require("./files");
5
5
  const {
6
6
  captureRegularFile,
7
7
  parseJsonRejectingDuplicateKeys,
@@ -229,12 +229,31 @@ function inspectSkillPayload(pluginPath, manifest, verifySourceHash = false, opt
229
229
  allowedDirectories.add(`${segments.slice(0, index).join("/")}/`);
230
230
  }
231
231
  }
232
+ // Hermes imports the plugin with bytecode writing on, so CPython caches for
233
+ // shipped modules are expected host output; any other cache content is not.
234
+ const hostCacheDirectories = new Set();
235
+ for (const entryPath of inspected.entries.keys()) {
236
+ if (!entryPath.endsWith(".py")) continue;
237
+ const directory = path.posix.dirname(entryPath);
238
+ hostCacheDirectories.add(directory === "." ? "__pycache__/" : `${directory}/__pycache__/`);
239
+ }
240
+ const isHostBytecode = (entryPath) => {
241
+ const source = hostBytecodeSource(entryPath);
242
+ if (!source || !inspected.entries.has(source)) return false;
243
+ try {
244
+ return fs.lstatSync(path.join(pluginPath, ...entryPath.split("/"))).isFile();
245
+ } catch {
246
+ return false;
247
+ }
248
+ };
232
249
  const traversal = listPayloadEntries(pluginPath, options.readDirectory);
233
250
  for (const entryPath of traversal.entries) {
234
251
  if (
235
252
  inspected.entries.has(entryPath)
236
253
  || allowedUnlisted.has(entryPath)
237
254
  || allowedDirectories.has(entryPath)
255
+ || hostCacheDirectories.has(entryPath)
256
+ || isHostBytecode(entryPath)
238
257
  ) continue;
239
258
  failures.push({ ...entryLabel(entryPath), reason: "unexpected" });
240
259
  }