@litfamily/lithermes 1.0.23 → 1.0.25

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.
@@ -1,7 +1,7 @@
1
1
  {
2
- "syncedAt": "2026-10-15T09:46:03.900Z",
2
+ "syncedAt": "2026-10-18T09:42:50.174Z",
3
3
  "source": "bundled-payload",
4
- "sourceHash": "164f684b1b783534bf9b71f657f9e9d86b321e018c4a1ac02e7abe3cf604edd6",
4
+ "sourceHash": "91ef78ac52df745352ac069cd0872ee6a17253c4fa55b5577e52482b9b329971",
5
5
  "files": [
6
6
  {
7
7
  "path": "NOTICE.md",
@@ -9,11 +9,11 @@
9
9
  },
10
10
  {
11
11
  "path": "README.md",
12
- "sha256": "03256264acbaba328e51b82a527fe511db7f4ff907d0a74762bdaf0baadc8e20"
12
+ "sha256": "882eb72644fe3bab11a110196dd6692d74ee51525f75e64a7cfa04e3c658de60"
13
13
  },
14
14
  {
15
15
  "path": "__init__.py",
16
- "sha256": "76cf72655600b82544e9047fab1f5f4030927803c0cfbc08fca311204f37d32c"
16
+ "sha256": "057f8d630323ac427a48749c05cbd23076810fb66fa78821c6ab16076574127c"
17
17
  },
18
18
  {
19
19
  "path": "auto_handoff.py",
@@ -21,7 +21,7 @@
21
21
  },
22
22
  {
23
23
  "path": "auto_update.py",
24
- "sha256": "ff05a5c11ecdfb397e57c49b71aff24d6e3455e5be298c4c9c2de301dcc64333"
24
+ "sha256": "ba2beba07210ccd934ddc5902a5054a8fbe0f5bb9dcd9e9019196551bf4ea92b"
25
25
  },
26
26
  {
27
27
  "path": "bounded_work.py",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  {
35
35
  "path": "core.py",
36
- "sha256": "b431f7341d6e82badd752fe1ac56fd39d309f06159f13cbbfc5124728d5fa8f5"
36
+ "sha256": "44ea3860657ebe8dc335ccefb613ad21700b725ba0f4cb60a1cf81da18f61ff5"
37
37
  },
38
38
  {
39
39
  "path": "core_commands.py",
@@ -41,11 +41,11 @@
41
41
  },
42
42
  {
43
43
  "path": "core_contexts.py",
44
- "sha256": "5c31f1c73cd4c3757883f5fc9945ccac26856a98cc1f989853db1f29441711fc"
44
+ "sha256": "4923e1e9f72e52c6c83e19adaab835b6bccd1de6a3943f3275483c5b42eea7d8"
45
45
  },
46
46
  {
47
47
  "path": "core_contract.py",
48
- "sha256": "f4ad205e6da5788520ed46d3aacf805bda4a95d930fad2a6b0633a749e777732"
48
+ "sha256": "532d73146aaff3e5e2553195b0d08a941bd1c90b4ec29539dd00f5904075cb02"
49
49
  },
50
50
  {
51
51
  "path": "core_plans.py",
@@ -73,7 +73,7 @@
73
73
  },
74
74
  {
75
75
  "path": "core_runs.py",
76
- "sha256": "24e484ceebad1726834535949e89667f4dee1ec78bc0a977d03ec438cc9e0037"
76
+ "sha256": "066d605fef75c8f2959f7ab7586f144bbd5e43916075ccba6a9608e5ece7d986"
77
77
  },
78
78
  {
79
79
  "path": "core_runtime.py",
@@ -85,11 +85,11 @@
85
85
  },
86
86
  {
87
87
  "path": "deliverable_hedge_guard.py",
88
- "sha256": "804cf1253064e94ac19db45df03a5eb2ed68ef968fe1658c720d56159bf29048"
88
+ "sha256": "084eb4381d8be3055eada034bf0c3c31339b9c766cbf3b19fd85042e2e254039"
89
89
  },
90
90
  {
91
91
  "path": "diagnostics.py",
92
- "sha256": "88d7b76438c82755fb9456882994ab4efd70c889705c04095e5a12d1759425fb"
92
+ "sha256": "1fd173995dd1abc1d58cc719ecd351f91bd1c65c9056fdffa064fda684f18318"
93
93
  },
94
94
  {
95
95
  "path": "handoff.py",
@@ -97,7 +97,7 @@
97
97
  },
98
98
  {
99
99
  "path": "humanizer_detector.py",
100
- "sha256": "bca68ebaad84ab2fec5a41a91f293abe9e194011669ef03386611c8412561e97"
100
+ "sha256": "48e4f3a02326521769ac193b858eecbb90773cb765aa8d5794ffaa3c1dee2d62"
101
101
  },
102
102
  {
103
103
  "path": "humanizer_ko_metrics.py",
@@ -107,10 +107,6 @@
107
107
  "path": "humanizer_office.py",
108
108
  "sha256": "97ea96c5eb688e298d57ba7991bbd4bd8d92f969a2ad608add877aefa5de7128"
109
109
  },
110
- {
111
- "path": "jev_hint.py",
112
- "sha256": "77238cf88d875f3c1945ad26bf9699c81933711ff675dc19df48215d58884c33"
113
- },
114
110
  {
115
111
  "path": "knowledge.py",
116
112
  "sha256": "ef11fb0f568ae191809a81eb19015f8a3c8d2141d141e3453bd7f8b515a9a8b0"
@@ -181,7 +177,7 @@
181
177
  },
182
178
  {
183
179
  "path": "plugin.yaml",
184
- "sha256": "92d263e3abf96c4533db6814cfb6a104f3a8822e3de205106eaa4edff1c76338"
180
+ "sha256": "d9eae351c038cfaff91980666d4d3ebe8f2ad82f31bf8ef9539fb4c4ad088e84"
185
181
  },
186
182
  {
187
183
  "path": "provider_metrics.py",
@@ -1285,7 +1281,7 @@
1285
1281
  },
1286
1282
  {
1287
1283
  "path": "skills/frontend-ui-ux/references/complete-contract.md",
1288
- "sha256": "f872ab0d5e2c5a99c40fec7630df67c45c23a927b562fd35943e404204e756aa"
1284
+ "sha256": "df99f8f532c06232220537041128d8a5480bb60aedbbe3af8909444aaa8944ac"
1289
1285
  },
1290
1286
  {
1291
1287
  "path": "skills/frontend-ui-ux/references/composition.md",
@@ -3277,7 +3273,7 @@
3277
3273
  },
3278
3274
  {
3279
3275
  "path": "skills/lit-humanizer/SKILL.md",
3280
- "sha256": "1df63514da5f9295578c3fc7ff475ae911da2310ed43ca3e794759dc4414b66a"
3276
+ "sha256": "47c562a344c89d16be5271f86874af556991dd5c9a26f45e0800eda5a08f5381"
3281
3277
  },
3282
3278
  {
3283
3279
  "path": "skills/lit-humanizer/assets/report-skeleton.md",
@@ -3513,11 +3509,11 @@
3513
3509
  },
3514
3510
  {
3515
3511
  "path": "skills/lit-humanizer/references/taxonomy.md",
3516
- "sha256": "d0958c702eb531f82612ff48d16134afbfd6c65b40bc80ebc4bce778a02b4238"
3512
+ "sha256": "7bdda99ad8f2e1358b70f96e017d5a70b879251b4681f8752babaf7598030239"
3517
3513
  },
3518
3514
  {
3519
3515
  "path": "skills/lit-humanizer/rules.json",
3520
- "sha256": "806e8caaeea25b34fa7a2caf8957a4af6fe95e557d5db71fa6d6e90fcea7091c"
3516
+ "sha256": "61f494bbbd9dee707b96b50308bf9c045d6b268218a442b3022b1c29194bb95c"
3521
3517
  },
3522
3518
  {
3523
3519
  "path": "skills/lit-humanizer/scripts/detect.py",
@@ -4405,7 +4401,7 @@
4405
4401
  },
4406
4402
  {
4407
4403
  "path": "skills/visual-qa/references/complete-contract.md",
4408
- "sha256": "daa71196ff16fc545fdaad07367cdcd425c9e5cd3cbcb10e20f28ba10f1c3be3"
4404
+ "sha256": "16ccc6a685865e00a226fbdbbdf1d4e170ca9c37731a94974ea135f281032e51"
4409
4405
  },
4410
4406
  {
4411
4407
  "path": "skills/visual-qa/schemas/evidence-manifest-v1alpha1.schema.json",
@@ -1,5 +1,5 @@
1
1
  name: lithermes
2
- version: 1.0.23
2
+ version: 1.0.25
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
@@ -10,6 +10,7 @@ hooks:
10
10
  - pre_tool_call
11
11
  - pre_command
12
12
  - post_tool_call
13
+ - transform_tool_result
13
14
  - post_api_request
14
15
  - subagent_stop
15
16
  - transform_llm_output
@@ -33,6 +34,7 @@ provides_hooks:
33
34
  - pre_tool_call
34
35
  - pre_command
35
36
  - post_tool_call
37
+ - transform_tool_result
36
38
  - post_api_request
37
39
  - subagent_stop
38
40
  - transform_llm_output
@@ -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.23
448
+ Installed LitHermes 1.0.25
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.23)
465
+ [OK] plugin.yaml readable (version 1.0.25)
466
466
  [OK] skills bundled: 36
467
467
  [OK] litgoal durable runtime importable
468
468
  ```
@@ -21,7 +21,7 @@ identity:
21
21
  invocation: "lithermes:<frontmatter.name>"
22
22
  surfaces:
23
23
  manifest: plugin.yaml
24
- python_entrypoints: ["__init__.py:register", "core.py:pre_llm_call", "deliverable_hedge_guard.py:pre_tool_call"]
24
+ python_entrypoints: ["__init__.py:register", "core.py:pre_llm_call", "deliverable_hedge_guard.py:pre_tool_call", "deliverable_hedge_guard.py:transform_tool_result"]
25
25
  bundled_rule: rules/bundled-rules/lit-humanizer.md
26
26
  detector: skills/lit-humanizer/scripts/detect.py
27
27
  state:
@@ -60,7 +60,7 @@ after_payload_edit: "npm --prefix packages/lithermes-installer run sync-plugin -
60
60
 
61
61
  For a file edit, the Hermes `pre_tool_call` guard blocks block-tier findings in added text passed to `write_file` or `patch` for Markdown, HTML, text, TeX, CSV, SVG, reStructuredText, and AsciiDoc files. Warn-tier findings arrive as advisory context. Fenced code, inline code, exact quoted user spans, and internal `plans/`, `evidence/`, `HANDOFF*`, ledger, `.lit*/`, and `.hermes/` paths are excluded. The guard never scans unchanged file text. Use `write_file` or `patch` for prose edits when pre-save checking is required.
62
62
 
63
- DOCX/PPTX/PDF checks happen after a supported file-write event or when a script-like tool reports an explicit output path in its result. Hermes' `post_tool_call` is observer-only, so a finding cannot undo a completed export: revise the source and rebuild the file. DOCX/PPTX extraction uses Python's standard-library ZIP/XML support. PDF extraction uses host `pdftotext` when present; if it is unavailable or fails, the scan fails open and reports that boundary. Script-created files without a reported output path are outside automatic post-write inspection; run the detector explicitly after such a write.
63
+ DOCX/PPTX/PDF checks happen after a supported file-write event or when a script-like tool reports an explicit output path in its result. Hermes' `post_tool_call` is observer-only, so a finding cannot undo a completed export: revise the source and rebuild the file. DOCX/PPTX extraction uses Python's standard-library ZIP/XML support. PDF extraction uses host `pdftotext` when present; if it is unavailable or fails, the scan fails open and reports that boundary. A DOCX, PPTX or Markdown file that a `terminal` or `execute_code` call builds is checked when that call returns: Hermes' `transform_tool_result` hook finds files modified while the call ran (the reported or session working directory, `workdir`, and folders of paths named in the command, code or output; bounded depth, file count and size, internal paths skipped) and, on a block-tier finding, puts `LIT_HUMANIZER_SCRIPT_DELIVERABLE_BLOCK` with the rule ids in that tool result's `error` field. The command's exit code stays as it ran, and the file stays on disk: edit the build source and rebuild before presenting it. A file written outside those folders, deeper than the scan, or by a background process after the call returned is not seen; run the detector on it explicitly.
64
64
 
65
65
  ## #contract.language_and_scope
66
66
 
@@ -17,7 +17,7 @@ This original category comes from the 26 real lines in `examples/detector-valida
17
17
  - ko-bracket-status-tag and en-bracket-status-tag — ko/en, block. Replace bracket keys with a sentence describing the status.
18
18
  - ko-standalone-scope-disclaimer and en-standalone-scope-disclaimer — ko/en, block for stock disclaimers. Preserve a user-requested legal, medical or safety notice and place it once where it applies.
19
19
  - ko-status-negation (also catches 쓰지, 않습니다/않았습니다 endings and bold-wrapped endings), ko-mi-status, en-epistemic-negation and en-not-performed — warnings. A negative result or unperformed action may be the finding. Write the actor, scope and verb plainly; never turn unknown into false or an estimate into a fact.
20
- - ko-negative-pledge and en-negative-pledge — ko/en, block. A line that tells the reader what the document, a table or a number is not for (`동일 조건의 독립 성능 비교로 쓰지 않음`, `This is not intended as a head-to-head benchmark`) is a pledge to the reader. Move it to the chat reply, or restate the real limit as a data fact beside the number ("샘플은 한 공정 조건만 포함한다"). Operating rules, method lines and legal or financial notices ("원금을 보장하지 않습니다", "not a substitute for professional advice") are not pledges and pass; so does a negative finding such as 수율을 개선하지 않았다.
20
+ - ko-negative-pledge and en-negative-pledge — ko/en, block. A line that tells the reader what the document, a table or a number is not for (`동일 조건의 독립 성능 비교로 쓰지 않음`, `This is not intended as a head-to-head benchmark`) is a pledge to the reader. Move it to the chat reply, or restate the real limit as a data fact beside the number ("샘플은 한 공정 조건만 포함한다"). Operating rules, method lines and legal or financial notices ("원금을 보장하지 않습니다", "not a substitute for professional advice") are not pledges and pass; so does a negative finding such as 수율을 개선하지 않았다. The same refusal covers the phrasings a model reaches for next (`이 표는 순위가 아닙니다`, `숫자끼리 서로 비교할 수 없습니다`, `This table is not a ranking`), while a plain instruction such as `Do not compare these values directly; convert to SI first` or a unit-mismatch rule is an operating rule and passes. A quote line is skipped unless it opens with a bold label (`> **주의:** …`); that is the author's own callout and is read like any other line.
21
21
  - ko-negative-pledge-repeat — ko, block, whole-text scope. Two or more sentences ending in 해석하지/주장하지/대표하지 + 않음, or in a claim noun + 이/가 + 아님, make a disclaimer list. One such sentence stays with the warning above.
22
22
  - ko-plain-meta-label-warn and en-plain-caveat-label — warnings for boundary headings. They may be useful in internal or technical packets.
23
23
  - en-knowledge-cutoff, en-ai-self-disclaimer, en-hope-helps and ko-chatbot-signoff — block. Remove the model wrapper or generic sign-off, then retain the answer.
@@ -884,14 +884,15 @@
884
884
  "id": "ko-negative-pledge",
885
885
  "lang": "ko",
886
886
  "severity": "block",
887
- "pattern": "(?<![가-힣])(?:이|본|해당|위|아래)\\s+(?:자료|문서|보고서|슬라이드|덱|표|그림|그래프|차트|수치|결과|비교|분석|내용|예시)(?:은|는)\\s[^.!?\\n]{0,40}?(?:(?:해석하|의미하|주장하|대표하)(?:지\\s*(?:[*_]{1,2}\\s*)?(?:않(?:음|는다|습니다)|말\\s*것|마세요|마십시오)|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다))|(?:(?:비교|결론|벤치마크|순위|대표값|일반화)(?:이|가)|목적이|위한\\s*것이)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아니다|아닙니다)(?![가-힣]))|(?:비교|결론|벤치마크|순위|대표값|일반화)(?:으로|로)\\s*(?:[*_]{1,2}\\s*)?(?:쓰|사용하|해석하|읽|간주하|인용하)(?:지\\s*(?:[*_]{1,2}\\s*)?(?:않(?:음|는다|습니다)|말\\s*것|마세요|마십시오)|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다))|(?:공개|배포|대외|외부|홍보|발표|보고|비교|인용|공식)용\\s*(?:자료|문서|수치|표|그래프)(?:이|가)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아니다|아닙니다)(?![가-힣])",
887
+ "pattern": "(?<![가-힣])(?:이|본|해당|위|아래)\\s+(?:자료|문서|보고서|슬라이드|덱|표|그림|그래프|차트|수치|결과|비교|분석|내용|예시)(?:은|는)\\s[^.!?\\n]{0,40}?(?:(?:해석하|의미하|주장하|대표하)(?:지\\s*(?:[*_]{1,2}\\s*)?(?:않(?:음|는다|습니다)|말\\s*것|마세요|마십시오|말아\\s*주(?:세요|십시오))|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다)(?![가-힣]))|(?:(?:비교|결론|벤치마크|순위|대표값|일반화)(?:이|가)|목적이|(?:위한|려는)\\s*것이)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아니다|아닙니다)(?![가-힣])|순위표(?:이|가)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아니다|아닙니다|아니고)(?![가-힣]))|(?:비교|결론|벤치마크|순위|대표값|일반화)(?:처럼|으로|로)\\s*(?:[*_]{1,2}\\s*)?(?:쓰|사용하|해석하|읽|간주하|인용하)(?:지\\s*(?:[*_]{1,2}\\s*)?(?:않(?:음|는다|습니다)|말\\s*것|마세요|마십시오|말아\\s*주(?:세요|십시오))|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다)(?![가-힣]))|(?:공개|배포|대외|외부|홍보|발표|보고|비교|인용|공식)용\\s*(?:자료|문서|수치|표|그래프)(?:이|가)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아니다|아닙니다)(?![가-힣])|(?<![가-힣])(?:숫자|수치)(?:들)?(?:은|는|이|가|을|를|끼리|도)(?![가-힣])(?:(?!면\\s)[^.!?\\n]){0,40}?(?<![와과]\\s)(?<![와과])비교할\\s*수\\s*(?:는\\s*)?없(?:다|습니다|음|나)(?![가-힣])|(?<![가-힣])(?:숫자|수치)(?:들)?끼리\\s*(?:서로\\s*|직접\\s*)?비교(?:해서|하여|해)\\s[^.!?\\n]{0,30}?[\"”’']\\s*(?:는|라는)\\s*결론[^.!?\\n]{0,8}?(?:수\\s*없(?:다|습니다)(?![가-힣])|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다)(?![가-힣]))|(?:(?<![가-힣])(?:숫자|수치)(?:들)?|(?<![가-힣])(?:이|위|아래|본)\\s+(?:표|값|자료|그래프|차트))[^.!?\\n]{0,40}?(?:빠르|느리)다고\\s*판단하(?:지\\s*(?:[*_]{1,2}\\s*)?(?:말\\s*것|마세요|마십시오|말아\\s*주(?:세요|십시오))|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다)(?![가-힣]))|(?:빠르|느리)다[\"”’']?\\s*(?:는|라는)\\s*식으로\\s*읽(?:지\\s*(?:[*_]{1,2}\\s*)?(?:말\\s*것|마세요|마십시오|말아\\s*주(?:세요|십시오))|(?:으)?면\\s*(?:[*_]{1,2}\\s*)?안\\s*(?:됨|된다|됩니다)(?![가-힣]))|만으로\\s[^.!?\\n]{0,40}?(?<![가-힣])순위를\\s*정하[^.!?\\n]{0,25}?(?:수\\s*없(?:다|습니다|음)(?![가-힣])|(?:해|하)서는\\s*안\\s*(?:됨|된다|됩니다)(?![가-힣]))|(?<![가-힣])순위[^.!?\\n]{0,20}?근거로\\s*(?:[*_]{1,2}\\s*)?사용할\\s*수\\s*(?:는\\s*)?없(?:다|습니다|음)(?![가-힣])|할\\s*수\\s*없는\\s*것\\s{0,3}:\\s{0,3}(?:[*_]{1,2}\\s{0,3})?[^.!?\\n]{0,30}?(?<![가-힣])순위를?\\s*매기거나|(?<![가-힣])표의\\s+(?:나열\\s+)?순서(?:는|가)\\s[^.!?\\n]{0,20}?(?<![가-힣])순위(?:(?:를|을)\\s*뜻하지\\s*(?:[*_]{1,2}\\s*)?않(?:음|는다|습니다)|(?:가|이)\\s*(?:[*_]{1,2}\\s*)?(?:아님|아니다|아닙니다)(?![가-힣]))",
888
888
  "flags": "m",
889
889
  "scope": "line",
890
890
  "context": [
891
891
  "fenced_code",
892
- "quoted_user_text"
892
+ "quoted_user_text",
893
+ "blockquote_callout"
893
894
  ],
894
- "rationale": "A line that tells the reader what the document, a table or a number is not for is a pledge to the reader, not a finding, so the write is refused and the pledge goes to the chat reply or turns into a data fact. The pattern recognises three shapes: a self-reference such as 이 표 or 본 자료 followed by a negated claim verb or a claim noun plus 아님; a claim noun such as 비교 or 순위 followed by (으)로 and a negated use verb, which has no subject at all; and a purpose-용 noun such as 공개용 수치 followed by 아님. Use verbs after a self-reference, 근거, 평가, 증거 and 추천 are deliberately absent, as are the legal and financial notice verbs 대체하, 보장하 and 권유, because operating rules, method lines and standard notices end that way.",
895
+ "rationale": "A line that tells the reader what the document, a table or a number is not for is a pledge to the reader, not a finding, so the write is refused and the pledge goes to the chat reply or turns into a data fact. The pattern recognises these shapes: a self-reference such as 이 표 or 본 자료 followed by a negated claim verb, a claim noun plus 아님 (순위표 also takes 아니고), or 위한/-려는 것이 아님; a claim noun such as 비교 or 순위 followed by 처럼/(으)로 and a negated use or reading verb, including 말아 주세요; a purpose-용 noun such as 공개용 수치 followed by 아님; 숫자 or 수치 said to be incomparable (서로 비교할 수 없다) or compared among themselves toward a quoted verdict; a speed judgment or a quoted verdict forbidden for these numbers; ranking refused for this data (…만으로 순위를 정할 수 없다, 할 수 없는 것: 순위를 매기거나); and a table's row order said not to be a ranking. Use verbs after a self-reference, 근거, 평가, 증거 and 추천 stay out, and so do inference limits (결론 내릴 수 없다), operating rules (…근거로 사용하지 마십시오), conditional method facts (…면 비교할 수 없다), type-system rules (숫자 문자열을 그대로 비교하지 마십시오), reported speech and explainers (…면 안 된다는 반론), order notices with no table anchor, and the legal and financial notice verbs 대체하, 보장하 and 권유, because operating rules, method lines, manuals and standard notices end that way. A quote line that opens with a bold label (> **주의:** …) is read as the author's own callout; any other quote line is skipped.",
895
896
  "fixture_ids": [
896
897
  "ko-negative-pledge:positive",
897
898
  "ko-negative-pledge:negative"
@@ -906,7 +907,8 @@
906
907
  "scope": "document",
907
908
  "context": [
908
909
  "fenced_code",
909
- "quoted_user_text"
910
+ "quoted_user_text",
911
+ "blockquote_callout"
910
912
  ],
911
913
  "rationale": "Two sentences in one text that each end in 해석하지, 주장하지 or 대표하지 plus 않음, or in a claim noun plus 이/가 아님, turn a document into a list of disclaimers. The verbs 의미하, 간주하 and 인용하 and the use verbs stay out because repeated definitions, data-processing rules and operating rules end that way. A single such sentence is left to the warning-tier ko-status-negation.",
912
914
  "fixture_ids": [
@@ -918,14 +920,15 @@
918
920
  "id": "en-negative-pledge",
919
921
  "lang": "en",
920
922
  "severity": "block",
921
- "pattern": "\\b(?:this|these)\\s+(?:deck|report|document|analysis|table|chart|figure|figures|numbers|results|slide|comparison)\\s+(?:(?:is|are)\\s+not\\s+(?:intended|meant|to\\s+be\\s+(?:read|used|taken|interpreted))(?:\\s+to\\s+be\\s+(?:read|used|taken|interpreted))?\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|(?:should|must)\\s+not\\s+be\\s+(?:read|used|taken|interpreted)\\s+as\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|makes?\\s+no\\s+claims?\\s+(?:about|that|regarding|as\\s+to)\\b)|\\b(?:this|these)\\s+(?:is|are)\\s+not\\s+(?:intended|meant)\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|\\bthe\\s+(?:deck|report|document|analysis)\\s+makes?\\s+no\\s+claims?\\s+(?:about|that|regarding|as\\s+to)\\b|(?:^|\\s[-*]\\s)\\s*(?:[-*]\\s+|\\d+\\.\\s+)?(?:[*_]{1,2})?not\\s+(?:intended|meant|to\\s+be\\s+read|to\\s+be\\s+used)\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|\\b(?:this|these)\\s+(?:table|chart|figure|figures|numbers|results|comparison|analysis)\\s+(?:does|do)\\s+not\\s+constitute\\s+(?:an?\\s+)?[^.!?]{0,60}?\\b(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)",
923
+ "pattern": "\\b(?:this|these)\\s+(?:deck|report|document|analysis|table|chart|figure|figures|numbers|results|slide|comparison)\\s+(?:(?:is|are)\\s+not\\s+(?:intended|meant|to\\s+be\\s+(?:read|used|taken|interpreted))(?:\\s+to\\s+be\\s+(?:read|used|taken|interpreted))?\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|(?:should|must)\\s+not\\s+be\\s+(?:read|used|taken|interpreted)\\s+as\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|makes?\\s+no\\s+claims?\\s+(?:about|that|regarding|as\\s+to)\\b)|\\b(?:this|these)\\s+(?:is|are)\\s+not\\s+(?:intended|meant)\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|\\bthe\\s+(?:deck|report|document|analysis)\\s+makes?\\s+no\\s+claims?\\s+(?:about|that|regarding|as\\s+to)\\b|(?:^|\\s[-*]\\s)\\s*(?:[-*]\\s+|\\d+\\.\\s+)?(?:[*_]{1,2})?not\\s+(?:intended|meant|to\\s+be\\s+read|to\\s+be\\s+used)\\s+as\\b(?!\\s*(?:a\\s+substitute|a\\s+replacement|(?:legal|investment|medical|professional)\\s+advice))\\s+(?:an?\\s+|the\\s+)?(?:[A-Za-z0-9-]+\\s+){0,4}?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|\\b(?:this|these)\\s+(?:table|chart|figure|figures|numbers|results|comparison|analysis)\\s+(?:does|do)\\s+not\\s+constitute\\s+(?:an?\\s+)?[^.!?]{0,60}?\\b(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)|\\bthey\\s+are\\s+not\\s+(?:an?\\s+)?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)(?=\\s*(?:[.!?*_]|$))|\\b(?:do\\s+not|don[’']t)\\s+compare\\s+(?:these|those)\\s+(?:numbers|figures|results|values|scores|latencies)(?:\\s+(?:with|to)\\s+(?:each\\s+other|one\\s+another)\\b|(?=\\s*(?:[.!?*_]|$)))|\\b(?:these|those|the)\\s+(?:numbers|figures)\\s+are\\s+not\\s+comparable(?:\\s+(?:with|to)\\s+(?:each\\s+other|one\\s+another)\\b|(?=\\s*(?:[.!?*_]|$)))|\\b(?:this|the|these|our)\\s+(?:table|chart|figure|graph|list|report|document|deck|slide|page|summary|overview|analysis)s?\\s+(?:is|are)\\s+not\\s+(?:(?:meant|intended)\\s+)?(?:(?:to\\s+be\\s+)?(?:an?\\s+)?(?:ranking|benchmark|comparison|leaderboard|endorsement)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)(?=\\s*(?:[.!?*_]|$))|to\\s+(?:rank|compare|benchmark)\\b)|\\b(?:do\\s+not|don[’']t)\\s+read\\s+(?:this|the|these|our)\\s+(?:table|chart|figure|graph|list|report|document|deck|slide|page|summary|overview|analysis)s?\\s+as\\s+(?:an?\\s+)?(?:comparison|benchmark|ranking)s?\\b(?!-)(?!\\s+(?:arm|arms|group|groups|standard|standards|class|baseline)\\b)",
922
924
  "flags": "i",
923
925
  "scope": "sentence",
924
926
  "context": [
925
927
  "fenced_code",
926
- "quoted_user_text"
928
+ "quoted_user_text",
929
+ "blockquote_callout"
927
930
  ],
928
- "rationale": "A sentence that says the deck, report, table or figures are not meant to be read as a comparison, benchmark, ranking or evaluation is a pledge to the reader, not a finding. The pattern needs a comparison noun, so the data-fact sentence about a sample that does not represent a population and investment-advice notices pass. A match followed by a substitute, replacement or professional advice is dropped, which keeps the standard legal notice. A sentence-initial 'Not to be read as' list item is covered too.",
931
+ "rationale": "A sentence that says the deck, report, table or figures are not meant to be read as a comparison, benchmark, ranking or evaluation is a pledge to the reader, not a finding. The pattern needs a comparison noun, so the data-fact sentence about a sample that does not represent a population and investment-advice notices pass. A match followed by a substitute, replacement or professional advice is dropped, which keeps the standard legal notice. A sentence-initial 'Not to be read as' list item is covered, and so are the short closers 'They are not a ranking.', 'Do not compare these numbers (with each other).' and 'The numbers are not comparable.', and a deliverable subject (this table, the report, our deck) that is not a ranking, is not meant to rank or compare, or is not to be read as a ranking. A refusal that continues into a description or an instruction (compare these values directly; …) is left alone, and a bare 'this' is not a subject. A bold-label quote callout is read as the author's own line.",
929
932
  "fixture_ids": [
930
933
  "en-negative-pledge:positive",
931
934
  "en-negative-pledge:negative"
@@ -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.23
488
+ Installed LitHermes 1.0.25
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.23)
505
+ [OK] plugin.yaml readable (version 1.0.25)
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.23",
3
+ "version": "1.0.25",
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.23"><title>release: 1.0.23</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.23</text></g></svg>
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>
package/src/lib/check.js CHANGED
@@ -6,6 +6,7 @@ const { detectHermesRuntimeRepo, detectHermesVersion, ensureHermesHome, expandHo
6
6
  const { formatManagedRoute } = require("./modelRoutePolicy");
7
7
  const { inspectPythonCache } = require("./files");
8
8
  const { assetRoot, pluginDest } = require("./install");
9
+ const { autoUpdateDoctorLines } = require("./updateDoctor");
9
10
  const {
10
11
  formatSkillPayload,
11
12
  inspectSkillPayload,
@@ -311,6 +312,7 @@ function doctorLitHermes(flags = {}) {
311
312
  `cli payload dispatch: ${cli.ok ? formatDispatchStatus(cli) : "PATCH_AVAILABLE"}`,
312
313
  `tui payload dispatch: ${tui.ok ? formatDispatchStatus(tui) : "PATCH_AVAILABLE"}`,
313
314
  `gateway underscore dispatch: ${gateway.ok ? formatDispatchStatus(gateway) : "PATCH_AVAILABLE"}`,
315
+ ...autoUpdateDoctorLines(hermesHome),
314
316
  ...motionProbeLines(pluginPath, hermesHome),
315
317
  ];
316
318
  return {
@@ -30,6 +30,9 @@ const MODEL_ONLY_EFFORTS = {
30
30
  lead: new Map([[LEGACY_CHILD_MODEL, CHILD_EFFORT]]),
31
31
  child: new Map([[LEGACY_SOL_MODEL, "high"]]),
32
32
  };
33
+ // Hermes's effort menu also offers `minimal`, which no OpenAI Responses model accepts;
34
+ // Hermes sends it as `low` on every managed OpenAI route, so a saved `minimal` is judged as `low`.
35
+ const HOST_SENT_EFFORTS = new Map([["minimal", "low"]]);
33
36
  const CHILD_TRANSPORT_KEYS = ["provider", "base_url", "api_mode"];
34
37
  const { OPENAI_PROVIDER: OPENAI_PROVIDER_ID } = require("./modelConfigBoundary");
35
38
  const XAI_PROVIDER_ID = "xai";
@@ -151,7 +154,7 @@ function inspectEffectiveRouteSafety(document, expectedProvider) {
151
154
  for (const route of managedRoutes) {
152
155
  if (transportReason && route.route === "global_child") continue;
153
156
  const modelEfforts = MODEL_EFFORTS.get(route.model);
154
- if (modelEfforts && !modelEfforts.has(route.effort)) {
157
+ if (modelEfforts && !modelEfforts.has(HOST_SENT_EFFORTS.get(route.effort) ?? route.effort)) {
155
158
  const isAstra = route.model === ASTRA_MODEL;
156
159
  const isLuna = LUNA_MODELS.has(route.model);
157
160
  return {
@@ -0,0 +1,122 @@
1
+ const fs = require("node:fs");
2
+ const path = require("node:path");
3
+ const { spawnSync } = require("node:child_process");
4
+
5
+ const PACKAGE_NAME = "@litfamily/lithermes";
6
+ const BACKOFF_MS = 24 * 60 * 60 * 1000;
7
+ const OPT_OUT_NAMES = ["LITHERMES_NO_AUTO_UPDATE", "NO_UPDATE_NOTIFIER", "LITHERMES_NO_UPDATE_CHECK", "CI"];
8
+
9
+ // Doctor only reads these records; a link or a non-file is treated as absent.
10
+ function readRecord(file) {
11
+ try {
12
+ if (!fs.lstatSync(file).isFile()) return null;
13
+ const value = JSON.parse(fs.readFileSync(file, "utf8"));
14
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ function when(value) {
21
+ const ms = typeof value === "number" ? value : Date.parse(value);
22
+ return Number.isFinite(ms) ? `${new Date(ms).toISOString().slice(0, 16).replace("T", " ")} UTC` : "an unknown time";
23
+ }
24
+
25
+ function isAlive(pid) {
26
+ try {
27
+ process.kill(pid, 0);
28
+ return true;
29
+ } catch (error) {
30
+ return error.code === "EPERM";
31
+ }
32
+ }
33
+
34
+ function processStartedAt(pid) {
35
+ const result = spawnSync("ps", ["-o", "lstart=", "-p", String(pid)], { encoding: "utf8", env: { ...process.env, LC_ALL: "C" }, timeout: 5000 });
36
+ const ms = result.status === 0 ? Date.parse(result.stdout.trim()) : NaN;
37
+ return Number.isFinite(ms) ? ms : null;
38
+ }
39
+
40
+ function byHand(version) {
41
+ return `install it by hand: npx --yes --package ${PACKAGE_NAME}@${version} -- lithermes install --yes --no-hud`;
42
+ }
43
+
44
+ function attemptLines(attempt) {
45
+ if (!attempt) return [];
46
+ const target = attempt.latestVersion || "the latest release";
47
+ const at = when(attempt.at);
48
+ switch (attempt.reason) {
49
+ case "current":
50
+ return [`update: last check ${at}: up to date`];
51
+ case "updated":
52
+ return [`update: last check ${at}: updated ${attempt.currentVersion} to ${target}`];
53
+ case "update-check-failed":
54
+ return [`update: last check ${at}: could not reach the npm registry`, "update: remedy: check the network or proxy; the next Hermes CLI session checks again"];
55
+ case "automatic-install-failed":
56
+ return [`update: last check ${at}: npm could not install ${target}`, `update: remedy: check npm's registry and proxy settings, or ${byHand(target)}`];
57
+ case "automatic-install-timeout":
58
+ return [`update: last check ${at}: installing ${target} ran past 90 seconds and was stopped`, `update: remedy: ${byHand(target)}`];
59
+ case "backoff-after-failure":
60
+ return [
61
+ `update: last check ${at}: ${target} failed to install (${attempt.failedReason}), so the next try waits until ${when(Date.parse(attempt.failedAt) + BACKOFF_MS)}`,
62
+ `update: remedy: fix the cause above, or ${byHand(target)}`,
63
+ ];
64
+ default:
65
+ return attempt.status === "failed"
66
+ ? [`update: last check ${at}: installing ${target} did not finish cleanly (${attempt.reason}); the old version stayed`, `update: remedy: ${byHand(target)}, then run lithermes doctor`]
67
+ : [];
68
+ }
69
+ }
70
+
71
+ function receiptLines(receipt, installed) {
72
+ if (!receipt || receipt.status !== "failed") return [];
73
+ const target = receipt.targetVersion || "the new version";
74
+ if (receipt.state === "unknown" || receipt.rollback?.status === "failed") {
75
+ return [`update: the automatic update to ${target} could not restore the old files`, "update: remedy: keep the backup under lithermes/auto-update/ and get doctor --offline to pass before you start Hermes"];
76
+ }
77
+ const cause = receipt.doctor === "failed"
78
+ ? "failed its check after install"
79
+ : /timed out|interrupted/.test(String(receipt.reason)) ? "took longer than 30 seconds to install" : "did not install";
80
+ return [`update: ${target} ${cause} and was rolled back; you are still on ${installed} (${when(receipt.completedAt)})`, `update: remedy: run npx --yes --package ${PACKAGE_NAME}@${target} -- lithermes install --yes --no-hud yourself to see the error`];
81
+ }
82
+
83
+ function gatewayLines(hermesHome, installed, installedAtMs, probes) {
84
+ const record = readRecord(path.join(hermesHome, "gateway.pid"));
85
+ const pid = Number(record?.pid);
86
+ if (!record || record.kind !== "hermes-gateway" || !Number.isInteger(pid) || pid <= 0 || !probes.isAlive(pid)) return [];
87
+ const started = probes.processStartedAt(pid);
88
+ if (started === null || !Number.isFinite(installedAtMs)) return [`update: gateway pid ${pid} is running; doctor could not tell whether it started before ${installed} was installed`];
89
+ return started < installedAtMs
90
+ ? [`update: gateway pid ${pid} started before ${installed} was installed and still runs the older plugin; restart it: hermes gateway restart`]
91
+ : [`update: gateway pid ${pid} started after the install, so it loaded ${installed}`];
92
+ }
93
+
94
+ function autoUpdateDoctorLines(hermesHome, options = {}) {
95
+ const env = options.env || process.env;
96
+ const probes = { isAlive: options.isAlive || isAlive, processStartedAt: options.processStartedAt || processStartedAt };
97
+ const state = path.join(hermesHome, "lithermes");
98
+ const manifest = readRecord(path.join(state, "install-manifest.json"));
99
+ if (!manifest?.version) return ["update: no install record; run lithermes install"];
100
+ const installed = manifest.version;
101
+ const receipt = readRecord(path.join(state, "auto-update-receipt.json"));
102
+ const viaUpdate = receipt?.status === "updated" && receipt.targetVersion === installed;
103
+ const lines = [`update: installed ${installed} at ${when(manifest.installedAt)} by ${viaUpdate ? `an automatic update from ${receipt.currentVersion}` : "the installer"}`];
104
+ lines.push(...gatewayLines(hermesHome, installed, Date.parse(manifest.installedAt), probes));
105
+ lines.push(`update: a Hermes CLI, TUI or gateway started before then keeps the older plugin until restarted; doctor cannot see which version a running Hermes loaded`);
106
+ const optOut = OPT_OUT_NAMES.find((name) => Object.prototype.hasOwnProperty.call(env, name));
107
+ if (manifest.distribution !== "npm") {
108
+ lines.push("update: this copy was not installed by the npm installer, so it never updates itself; run lithermes install once to turn updates on");
109
+ } else if (optOut) {
110
+ lines.push(`update: automatic updates are off (${optOut} is set); install new versions yourself`);
111
+ } else {
112
+ lines.push("update: only the first message of a classic hermes CLI session, or lithermes install/check/doctor in a terminal, installs updates; the TUI and gateways never do");
113
+ }
114
+ const attempt = readRecord(path.join(state, "auto-update-attempt.json"));
115
+ const rolledBack = receiptLines(receipt, installed);
116
+ // A rolled-back transaction also makes the bridge exit non-zero; the receipt already names that cause.
117
+ const sameFailure = rolledBack.length && attempt?.reason === "automatic-install-failed" && attempt.latestVersion === receipt.targetVersion;
118
+ lines.push(...rolledBack, ...(sameFailure ? [] : attemptLines(attempt)));
119
+ return lines;
120
+ }
121
+
122
+ module.exports = { autoUpdateDoctorLines };