hexcli 2.13.0__tar.gz → 2.14.0__tar.gz
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.
- {hexcli-2.13.0 → hexcli-2.14.0}/CHANGELOG.md +44 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/PKG-INFO +1 -1
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/__init__.py +1 -1
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/agent.py +9 -5
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/editing.py +19 -6
- {hexcli-2.13.0 → hexcli-2.14.0}/.gitignore +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/Hex CLI.cmd +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/LICENSE +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/README.md +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/assets/hexcli.ico +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/assets/hexcli.png +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/cancel.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/chatlog.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/commands.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/compaction.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/config.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/diffview.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/distribution.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/doctor.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/http_client.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/launcher.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/lineedit.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/llm.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/lockfile.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/markdown_stream.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/memory.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/network.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/parsing.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/paths.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/prompts.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/psparse.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/repl.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/safety.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/sessions.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/setup_wizard.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/statusbar.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/stream_render.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/telemetry.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/tools.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/hexcli/ui.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/install.ps1 +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/launcher.py +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/pyproject.toml +0 -0
- {hexcli-2.13.0 → hexcli-2.14.0}/shellai.example.json +0 -0
|
@@ -4,6 +4,50 @@ Full evidence for every claim below — including the experiments that failed
|
|
|
4
4
|
lives in `docs/V2_PLAN.md` §14. Numbers are pass^k over repeated live runs on
|
|
5
5
|
the Hexagon NPU, not single-run anecdotes.
|
|
6
6
|
|
|
7
|
+
## 2.14.0 — 2026-09-17
|
|
8
|
+
|
|
9
|
+
A minor release: one tool behaves differently for the model (a `write_file`
|
|
10
|
+
body that was JSON-escaped twice is decoded), plus a test-hygiene fix.
|
|
11
|
+
|
|
12
|
+
> **Gate: PASS** on the pinned 24-case set, all 24 held at 5 runs with no
|
|
13
|
+
> recheck. Own arm, fresh server, seed 20260917, 0 invalid of 245 runs.
|
|
14
|
+
> Against the v2.11.1 12-run baseline: run-level 402/514 vs 179/230, −0.4 %,
|
|
15
|
+
> Fisher p = 0.92; pass^k 26 → 30 of 46 shared cases, McNemar p = 0.22. The
|
|
16
|
+
> rule fired in the arm on 3 of 5 `runit-1` writes and on nothing else
|
|
17
|
+
> (one `trap-1` poem hit the older newline shape, as before). `runit-1`:
|
|
18
|
+
> 5/5 in the arm and 15/15 in a follow-up on the same server, **20/20
|
|
19
|
+
> against 5/10 on 2.12.0, p = 0.002**. Ceiling cases that moved: `agentic-3`
|
|
20
|
+
> 3/5 (p = 0.17 against 18/20; both misses are the model writing invalid
|
|
21
|
+
> JSON through `edit_file`, where this rule does not run), `ambiguous-1`
|
|
22
|
+
> 4/5 (p = 0.50). Smoke 10/10 on a fresh server before the arm.
|
|
23
|
+
|
|
24
|
+
- `write_file` decodes a body whose every quote is escaped. `runit-1`
|
|
25
|
+
("Write hello.py that prints Hello, world and then run it") sat at 5/10
|
|
26
|
+
in the 2.12.0 arm, and not one of the misses was the "and run it" the
|
|
27
|
+
case was written for: every run wrote the file and ran it. The model
|
|
28
|
+
escaped its JSON twice, `{"content":"print(\\\"Hello, world\\\")"}`, so
|
|
29
|
+
the file held `print(\"Hello, world\")`, a SyntaxError; it then "fixed"
|
|
30
|
+
the line by editing it to itself, twice, until the step limit. The
|
|
31
|
+
double-escape rule from 2.9.0 only knew the newline shape (literal `\n`
|
|
32
|
+
and no real line break). It now also knows the quote shape — at least one
|
|
33
|
+
`\"` and not a single bare `"` — and decodes the body once, as before.
|
|
34
|
+
Measured over the 511 `write_file` bodies on record: 11 have the shape,
|
|
35
|
+
all of them this defect in this case, and none has both escaped and bare
|
|
36
|
+
quotes, so a body with even one bare quote is left exactly as sent.
|
|
37
|
+
`append_file` is untouched: 0 of its 17 recorded bodies have either shape.
|
|
38
|
+
|
|
39
|
+
- A test suite was writing into the owner's real chat log. The shell wiring
|
|
40
|
+
test in `evals/test_product_shell.py` drove `hexcli.agent` with the mock
|
|
41
|
+
backend and a temp config that left `chat_log_enabled` on, so every run
|
|
42
|
+
of the suite added a "say hello" session under `~/.shellai/chatlog/` — 93
|
|
43
|
+
of them by 2026-09-17, beside the 127 real sessions. The 2.12.0 exposure
|
|
44
|
+
counts were taken over that mix: "306 real turns" was 196 real turns and
|
|
45
|
+
93 scripted ones (the 11 firings were all on real turns, so the true
|
|
46
|
+
positives stand; the denominator did not). The test now turns the log
|
|
47
|
+
off, the corpus loader skips mock-backend sessions, the mock files were
|
|
48
|
+
moved aside, and the intent nudge's recorded rate is corrected to 9 of
|
|
49
|
+
196 real turns.
|
|
50
|
+
|
|
7
51
|
## 2.13.0 — 2026-09-17
|
|
8
52
|
|
|
9
53
|
- Commands are classified by the name the shell will really run, not by the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: hexcli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.14.0
|
|
4
4
|
Summary: Local Hexagon NPU terminal agent for Snapdragon X Elite Windows ARM64
|
|
5
5
|
Project-URL: Homepage, https://github.com/NathanL15/Hex-CLI
|
|
6
6
|
Project-URL: Repository, https://github.com/NathanL15/Hex-CLI
|
|
@@ -1741,8 +1741,11 @@ def _retry_echo(raw: str) -> str:
|
|
|
1741
1741
|
# none of the four can fire on it. evals/test_agent_loop.py pins that.
|
|
1742
1742
|
#
|
|
1743
1743
|
# Every guard below is here because an earlier draft fired on something
|
|
1744
|
-
# that was already RIGHT. They were found by replaying
|
|
1745
|
-
# turns and 1,366 recorded eval runs against the detectors
|
|
1744
|
+
# that was already RIGHT. They were found by replaying the real chat-log
|
|
1745
|
+
# turns and 1,366 recorded eval runs against the detectors (the count
|
|
1746
|
+
# first written here, 306 turns, included 93 that a test suite had
|
|
1747
|
+
# written into the real log with the mock backend; the owner's own
|
|
1748
|
+
# sessions are 196 turns, re-counted 2026-09-17):
|
|
1746
1749
|
#
|
|
1747
1750
|
# * "the condition is checked before the loop" — prose about code the
|
|
1748
1751
|
# user pasted, in an answer that never touches the filesystem (two real
|
|
@@ -1763,9 +1766,10 @@ def _retry_echo(raw: str) -> str:
|
|
|
1763
1766
|
# because a denied call is not in tools_used. Telling them apart needs
|
|
1764
1767
|
# the denial recorded, which is a separate change.
|
|
1765
1768
|
#
|
|
1766
|
-
# After the guards,
|
|
1767
|
-
# genuine miss
|
|
1768
|
-
#
|
|
1769
|
+
# After the guards, 9 of the 196 real turns fire and every one is a
|
|
1770
|
+
# genuine miss (the calculator, web-app, HiLo and resume sessions); 1 of
|
|
1771
|
+
# the 1,366 eval runs fires, a self-correct-1 run that claimed to have
|
|
1772
|
+
# checked and fixed with no tool call at all.
|
|
1769
1773
|
|
|
1770
1774
|
_WANTS_RUN_RE = re.compile(
|
|
1771
1775
|
r"\b(and|then|,)\s+(run|execute|start|launch)\s+(it|them|the\s+\w+)\b"
|
|
@@ -142,6 +142,7 @@ def unescape_json(text: str) -> str:
|
|
|
142
142
|
|
|
143
143
|
_DELTA_TOKEN_RE = re.compile(r"\w+|\s+|[^\w\s]")
|
|
144
144
|
_ESCAPED_RE = re.compile(r'\\["nt\\]')
|
|
145
|
+
_BARE_QUOTE_RE = re.compile(r'(?<!\\)"')
|
|
145
146
|
_DELTA_MAX_HUNKS = 4
|
|
146
147
|
_DELTA_MAX_REPLACED_CHARS = 40
|
|
147
148
|
_DELTA_MAX_INSERTED_CHARS = 400
|
|
@@ -149,12 +150,24 @@ _DELTA_MIN_MATCHED = 0.6 # fraction of old_string's tokens found in the window
|
|
|
149
150
|
|
|
150
151
|
|
|
151
152
|
def looks_double_escaped(text: str) -> bool:
|
|
152
|
-
"""A file body
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
"""A file body JSON-escaped a second time, in either of the two shapes
|
|
154
|
+
the model actually produces.
|
|
155
|
+
|
|
156
|
+
Newlines: two or more literal backslash-n sequences and not a single
|
|
157
|
+
real line break (write_file, persona_guard 2026-09-12: 'import
|
|
158
|
+
re\\n\\n\\n# Regex …' written as one line). A real one-line file that
|
|
159
|
+
spells "\\n" on purpose is rarer than the model's habit; anything with a
|
|
160
|
+
genuine newline is left alone.
|
|
161
|
+
|
|
162
|
+
Quotes: at least one backslash-quote and not a single bare quote
|
|
163
|
+
(runit-1, 2026-09-17: `print(\\"Hello, world\\")` as the whole of
|
|
164
|
+
hello.py, a SyntaxError the model then "fixed" by editing the line to
|
|
165
|
+
itself until the step limit). Over the 511 write_file bodies on record
|
|
166
|
+
11 have this shape and every one is that defect; none has both escaped
|
|
167
|
+
and bare quotes, so a body with even one bare quote is left alone."""
|
|
168
|
+
if "\n" not in text and text.count("\\n") >= 2:
|
|
169
|
+
return True
|
|
170
|
+
return '\\"' in text and _BARE_QUOTE_RE.search(text) is None
|
|
158
171
|
|
|
159
172
|
|
|
160
173
|
def _unescape_json(text: str) -> str:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|