chad-code 1.0.7__tar.gz → 1.0.8__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.
Files changed (110) hide show
  1. {chad_code-1.0.7/src/chad_code.egg-info → chad_code-1.0.8}/PKG-INFO +21 -5
  2. {chad_code-1.0.7 → chad_code-1.0.8}/README.md +20 -4
  3. {chad_code-1.0.7 → chad_code-1.0.8}/pyproject.toml +9 -5
  4. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/__init__.py +1 -1
  5. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/speech.py +22 -1
  6. {chad_code-1.0.7 → chad_code-1.0.8/src/chad_code.egg-info}/PKG-INFO +21 -5
  7. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_speech.py +18 -1
  8. {chad_code-1.0.7 → chad_code-1.0.8}/LICENSE +0 -0
  9. {chad_code-1.0.7 → chad_code-1.0.8}/setup.cfg +0 -0
  10. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/agent.py +0 -0
  11. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/atif.py +0 -0
  12. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/base_engine.py +0 -0
  13. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/bench.py +0 -0
  14. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/cli.py +0 -0
  15. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/compaction.py +0 -0
  16. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/completion_engine.py +0 -0
  17. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/config.py +0 -0
  18. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/diag.py +0 -0
  19. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/engine.py +0 -0
  20. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/guardrails.py +0 -0
  21. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/ignore.py +0 -0
  22. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/levers.py +0 -0
  23. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/lsp.py +0 -0
  24. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/lspclient.py +0 -0
  25. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/lspservers.py +0 -0
  26. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/mcp.py +0 -0
  27. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/mcp_oauth.py +0 -0
  28. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/mlx_fastpath.py +0 -0
  29. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/mlx_qsdpa.py +0 -0
  30. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/LICENSE +0 -0
  31. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/__init__.py +0 -0
  32. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/alignment.py +0 -0
  33. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/attention.py +0 -0
  34. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/audio.py +0 -0
  35. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/cache.py +0 -0
  36. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/conformer.py +0 -0
  37. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/ctc.py +0 -0
  38. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/parakeet.py +0 -0
  39. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/rnnt.py +0 -0
  40. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/tokenizer.py +0 -0
  41. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/parakeet/utils.py +0 -0
  42. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/profiles.py +0 -0
  43. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/prompt.py +0 -0
  44. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/prove.py +0 -0
  45. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/render.py +0 -0
  46. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/repomap.py +0 -0
  47. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/serve.py +0 -0
  48. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/session.py +0 -0
  49. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/skills.py +0 -0
  50. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/symbols.py +0 -0
  51. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/syntaxgate.py +0 -0
  52. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/toolcall_parse.py +0 -0
  53. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/tools.py +0 -0
  54. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/tui.py +0 -0
  55. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad/validate.py +0 -0
  56. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad_code.egg-info/SOURCES.txt +0 -0
  57. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad_code.egg-info/dependency_links.txt +0 -0
  58. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad_code.egg-info/entry_points.txt +0 -0
  59. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad_code.egg-info/requires.txt +0 -0
  60. {chad_code-1.0.7 → chad_code-1.0.8}/src/chad_code.egg-info/top_level.txt +0 -0
  61. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_agent.py +0 -0
  62. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_agent_e2e.py +0 -0
  63. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_agent_guards.py +0 -0
  64. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_atif.py +0 -0
  65. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_bench.py +0 -0
  66. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_cli.py +0 -0
  67. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_compact_notice.py +0 -0
  68. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_compaction.py +0 -0
  69. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_completion_engine.py +0 -0
  70. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_config.py +0 -0
  71. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_confirm_preview.py +0 -0
  72. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_done_audit.py +0 -0
  73. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_drift_warn.py +0 -0
  74. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_edit.py +0 -0
  75. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_edit_corruption.py +0 -0
  76. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_engine.py +0 -0
  77. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_engine_kvquant.py +0 -0
  78. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_engine_pld_hybrid.py +0 -0
  79. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_feel_pack.py +0 -0
  80. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_garble_invariant.py +0 -0
  81. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_gate.py +0 -0
  82. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_ignore.py +0 -0
  83. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_intent.py +0 -0
  84. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_lever_bite.py +0 -0
  85. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_levers.py +0 -0
  86. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_log_redaction.py +0 -0
  87. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_lsp.py +0 -0
  88. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_lsp_live.py +0 -0
  89. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_lspclient.py +0 -0
  90. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_mcp.py +0 -0
  91. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_mcp_oauth.py +0 -0
  92. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_mlx_fastpath.py +0 -0
  93. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_mlx_qsdpa.py +0 -0
  94. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_plan_review.py +0 -0
  95. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_prove.py +0 -0
  96. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_render.py +0 -0
  97. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_replace_lines.py +0 -0
  98. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_repomap.py +0 -0
  99. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_repomap_polyglot.py +0 -0
  100. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_serve.py +0 -0
  101. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_session.py +0 -0
  102. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_skills.py +0 -0
  103. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_speech_tui.py +0 -0
  104. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_subagent.py +0 -0
  105. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_symbols.py +0 -0
  106. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_syntaxgate.py +0 -0
  107. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_toolcall_parse.py +0 -0
  108. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_tools.py +0 -0
  109. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_tui.py +0 -0
  110. {chad_code-1.0.7 → chad_code-1.0.8}/tests/test_validate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chad-code
3
- Version: 1.0.7
3
+ Version: 1.0.8
4
4
  Summary: Local MLX-backed, Claude-Code-style coding agent (Apple Silicon, Ornith 35B/9B)
5
5
  License-Expression: MIT
6
6
  Project-URL: Repository, https://github.com/nathansutton/chad
@@ -139,6 +139,21 @@ uv run chad "add a --json flag to main.py and update the tests" # one-shot, he
139
139
  uv run chad -c # resume this directory's last conversation
140
140
  ```
141
141
 
142
+ **Optional extras.** Everything core is in the base install; two features are opt-in
143
+ because they pull deps not every install wants — `speech` (voice mode: a mic library,
144
+ no torch) and `highlight` (syntax colour in diffs/previews). An extra rides on the
145
+ **install spec**, not on a separate command, so how you add it depends on how you
146
+ installed chad:
147
+
148
+ ```bash
149
+ uv tool install --force 'chad-code[speech]' # add to an existing `uv tool` install
150
+ uvx --from 'chad-code[speech]' chad # one-off run, nothing installed
151
+ uv sync --extra speech # from a clone
152
+ ```
153
+
154
+ `/speech` in the TUI prints whichever of those matches your install, so you never have
155
+ to work it out from here.
156
+
142
157
  **The model.** chad picks one for you by RAM and downloads it once into the shared Hugging
143
158
  Face cache (`~/.cache/huggingface`, reused across every project). Override with
144
159
  `--model 9b` / `--model 35b`, or `--model <repo or local dir>` for anything else.
@@ -164,8 +179,8 @@ re-downloads the model.
164
179
  **Development.** `uv sync` once, then `uv run pytest -q` — the fast unit gate loads **no
165
180
  model weights**, runs in seconds, and is what CI runs. Throughput on your own machine:
166
181
  `uv run chad-bench` (see [Throughput & performance](docs/benchmarks.md)). LSP-precise
167
- find-references / rename need the `lsp` extra (`uv tool install 'chad-code[lsp]'`); without
168
- it chad uses the tree-sitter fallback automatically.
182
+ find-references / rename need no extra chad fetches pyright via `uvx` on first use and
183
+ falls back to tree-sitter when a language server can't start.
169
184
 
170
185
  ## Interactive UX
171
186
 
@@ -191,8 +206,9 @@ it chad uses the tree-sitter fallback automatically.
191
206
  isn't clipped, and a personal word table (`~/.chad/speech_words.json`) teaches it your
192
207
  identifiers — `{"pie test": "pytest"}`. Dictation cost is linear in take length, so a
193
208
  long thought is fine; `/speech` off releases both the mic and the weights. Nothing
194
- leaves the machine. Needs the `speech` extra (`uv sync --extra speech` just a mic
195
- library; no torch, no numba).
209
+ leaves the machine. Needs the `speech` extra just a mic library; no torch, no numba
210
+ (see [Installing & upgrading](#installing--upgrading); on a `uv tool` install that's
211
+ `uv tool install --force 'chad-code[speech]'`).
196
212
 
197
213
  **Usage.** `uv run chad --help` is the source of truth:
198
214
 
@@ -103,6 +103,21 @@ uv run chad "add a --json flag to main.py and update the tests" # one-shot, he
103
103
  uv run chad -c # resume this directory's last conversation
104
104
  ```
105
105
 
106
+ **Optional extras.** Everything core is in the base install; two features are opt-in
107
+ because they pull deps not every install wants — `speech` (voice mode: a mic library,
108
+ no torch) and `highlight` (syntax colour in diffs/previews). An extra rides on the
109
+ **install spec**, not on a separate command, so how you add it depends on how you
110
+ installed chad:
111
+
112
+ ```bash
113
+ uv tool install --force 'chad-code[speech]' # add to an existing `uv tool` install
114
+ uvx --from 'chad-code[speech]' chad # one-off run, nothing installed
115
+ uv sync --extra speech # from a clone
116
+ ```
117
+
118
+ `/speech` in the TUI prints whichever of those matches your install, so you never have
119
+ to work it out from here.
120
+
106
121
  **The model.** chad picks one for you by RAM and downloads it once into the shared Hugging
107
122
  Face cache (`~/.cache/huggingface`, reused across every project). Override with
108
123
  `--model 9b` / `--model 35b`, or `--model <repo or local dir>` for anything else.
@@ -128,8 +143,8 @@ re-downloads the model.
128
143
  **Development.** `uv sync` once, then `uv run pytest -q` — the fast unit gate loads **no
129
144
  model weights**, runs in seconds, and is what CI runs. Throughput on your own machine:
130
145
  `uv run chad-bench` (see [Throughput & performance](docs/benchmarks.md)). LSP-precise
131
- find-references / rename need the `lsp` extra (`uv tool install 'chad-code[lsp]'`); without
132
- it chad uses the tree-sitter fallback automatically.
146
+ find-references / rename need no extra chad fetches pyright via `uvx` on first use and
147
+ falls back to tree-sitter when a language server can't start.
133
148
 
134
149
  ## Interactive UX
135
150
 
@@ -155,8 +170,9 @@ it chad uses the tree-sitter fallback automatically.
155
170
  isn't clipped, and a personal word table (`~/.chad/speech_words.json`) teaches it your
156
171
  identifiers — `{"pie test": "pytest"}`. Dictation cost is linear in take length, so a
157
172
  long thought is fine; `/speech` off releases both the mic and the weights. Nothing
158
- leaves the machine. Needs the `speech` extra (`uv sync --extra speech` just a mic
159
- library; no torch, no numba).
173
+ leaves the machine. Needs the `speech` extra just a mic library; no torch, no numba
174
+ (see [Installing & upgrading](#installing--upgrading); on a `uv tool` install that's
175
+ `uv tool install --force 'chad-code[speech]'`).
160
176
 
161
177
  **Usage.** `uv run chad --help` is the source of truth:
162
178
 
@@ -4,7 +4,7 @@
4
4
  # import name, and command name are independent. `uvx chad-code` runs the alias
5
5
  # script added under [project.scripts].
6
6
  name = "chad-code"
7
- version = "1.0.7"
7
+ version = "1.0.8"
8
8
  description = "Local MLX-backed, Claude-Code-style coding agent (Apple Silicon, Ornith 35B/9B)"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -72,11 +72,15 @@ dependencies = [
72
72
  "mcp>=2,<3",
73
73
  ]
74
74
 
75
+ # Extras ride on the INSTALL SPEC, so the command depends on how chad was installed:
76
+ # `uv tool install --force 'chad-code[<extra>]'` (or `uvx --from 'chad-code[<extra>]' chad`)
77
+ # for a PyPI install, `uv sync --extra <extra>` only in a clone. speech.install_hint()
78
+ # derives the right one at runtime — keep it in step with any extra added here.
79
+ # NOTE: the PyPI distribution is `chad-code`; 'pip install chad' installs an unrelated
80
+ # squatted package.
81
+ #
75
82
  # Optional syntax highlighting in diffs / confirm previews. Pure-Python,
76
83
  # import-guarded in render.py: absent, output is byte-identical to the un-highlighted path.
77
- # Install with `uv sync --extra highlight`.
78
- # NOTE: no PyPI release exists; 'pip install chad' installs an unrelated squatted
79
- # package. Document uv/git installs only.
80
84
  [project.optional-dependencies]
81
85
  highlight = ["pygments>=2.17"]
82
86
  # Voice mode (/speech in the TUI): push-to-talk dictation via chad's vendored
@@ -85,7 +89,7 @@ highlight = ["pygments>=2.17"]
85
89
  # `say`. All on-device, nothing leaves the machine. mlx/numpy/huggingface-hub
86
90
  # are already core deps, so the extra adds only the mic (sounddevice/PortAudio).
87
91
  # Optional and import-guarded in speech.py: absent, /speech prints the install
88
- # hint and everything else is untouched. Install with `uv sync --extra speech`.
92
+ # hint and everything else is untouched.
89
93
  speech = [
90
94
  "sounddevice>=0.5",
91
95
  ]
@@ -4,7 +4,7 @@ A flat collection of cooperating modules behind one console script (``chad``):
4
4
  the inference engine, the tool layer, the agent loop, and the terminal UI.
5
5
  """
6
6
 
7
- __version__ = "1.0.7"
7
+ __version__ = "1.0.8"
8
8
 
9
9
  # chad sets no MLX_* runtime vars. MLX_METAL_FAST_SYNCH, MLX_MAX_OPS_PER_BUFFER
10
10
  # and MLX_MAX_MB_PER_BUFFER were each measured end-to-end on the 35B and every
@@ -85,6 +85,27 @@ def model_cached() -> bool:
85
85
  return False
86
86
 
87
87
 
88
+ def install_hint(extra: str = "speech", path: str | None = None) -> str:
89
+ """The command that adds an optional extra to THIS install.
90
+
91
+ There is no single right answer: an extra attaches to the install SPEC for a
92
+ `uv tool`/`uvx` install and to `uv sync` only in a clone, so the one-line
93
+ hint /speech prints has to know which of those it's talking to — a clone-only
94
+ hint sends the (majority) PyPI user to a command that can't work. Keyed off
95
+ where this file physically lives, the one thing we can observe.
96
+ """
97
+ here = os.path.realpath(path or __file__)
98
+ if f"{os.sep}uv{os.sep}tools{os.sep}" in here: # uv tool install
99
+ return f"uv tool install --force 'chad-code[{extra}]'"
100
+ # uvx's ephemeral env: hardlinked out of the uv cache (archive-v*/environments-v*).
101
+ if f"{os.sep}uv{os.sep}archive-" in here or f"{os.sep}uv{os.sep}environments-" in here:
102
+ return f"uvx --from 'chad-code[{extra}]' chad"
103
+ root = os.path.dirname(os.path.dirname(os.path.dirname(here))) # src/chad/x.py -> repo
104
+ if os.path.exists(os.path.join(root, "pyproject.toml")):
105
+ return f"uv sync --extra {extra}"
106
+ return f"uv pip install 'chad-code[{extra}]'" # a plain venv
107
+
108
+
88
109
  def available():
89
110
  """(ok, reason). ok=False carries the one-line reason speech can't start —
90
111
  surfaced verbatim by /speech, so it names the fix, not just the failure."""
@@ -102,7 +123,7 @@ def available():
102
123
  missing.append("sounddevice")
103
124
  if missing:
104
125
  return False, (f"speech needs {' + '.join(missing)} — install with "
105
- f"`uv sync --extra speech` (all local: Parakeet-on-MLX STT, "
126
+ f"`{install_hint()}` (all local: Parakeet-on-MLX STT, "
106
127
  f"macOS `say` TTS)")
107
128
  return True, ""
108
129
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chad-code
3
- Version: 1.0.7
3
+ Version: 1.0.8
4
4
  Summary: Local MLX-backed, Claude-Code-style coding agent (Apple Silicon, Ornith 35B/9B)
5
5
  License-Expression: MIT
6
6
  Project-URL: Repository, https://github.com/nathansutton/chad
@@ -139,6 +139,21 @@ uv run chad "add a --json flag to main.py and update the tests" # one-shot, he
139
139
  uv run chad -c # resume this directory's last conversation
140
140
  ```
141
141
 
142
+ **Optional extras.** Everything core is in the base install; two features are opt-in
143
+ because they pull deps not every install wants — `speech` (voice mode: a mic library,
144
+ no torch) and `highlight` (syntax colour in diffs/previews). An extra rides on the
145
+ **install spec**, not on a separate command, so how you add it depends on how you
146
+ installed chad:
147
+
148
+ ```bash
149
+ uv tool install --force 'chad-code[speech]' # add to an existing `uv tool` install
150
+ uvx --from 'chad-code[speech]' chad # one-off run, nothing installed
151
+ uv sync --extra speech # from a clone
152
+ ```
153
+
154
+ `/speech` in the TUI prints whichever of those matches your install, so you never have
155
+ to work it out from here.
156
+
142
157
  **The model.** chad picks one for you by RAM and downloads it once into the shared Hugging
143
158
  Face cache (`~/.cache/huggingface`, reused across every project). Override with
144
159
  `--model 9b` / `--model 35b`, or `--model <repo or local dir>` for anything else.
@@ -164,8 +179,8 @@ re-downloads the model.
164
179
  **Development.** `uv sync` once, then `uv run pytest -q` — the fast unit gate loads **no
165
180
  model weights**, runs in seconds, and is what CI runs. Throughput on your own machine:
166
181
  `uv run chad-bench` (see [Throughput & performance](docs/benchmarks.md)). LSP-precise
167
- find-references / rename need the `lsp` extra (`uv tool install 'chad-code[lsp]'`); without
168
- it chad uses the tree-sitter fallback automatically.
182
+ find-references / rename need no extra chad fetches pyright via `uvx` on first use and
183
+ falls back to tree-sitter when a language server can't start.
169
184
 
170
185
  ## Interactive UX
171
186
 
@@ -191,8 +206,9 @@ it chad uses the tree-sitter fallback automatically.
191
206
  isn't clipped, and a personal word table (`~/.chad/speech_words.json`) teaches it your
192
207
  identifiers — `{"pie test": "pytest"}`. Dictation cost is linear in take length, so a
193
208
  long thought is fine; `/speech` off releases both the mic and the weights. Nothing
194
- leaves the machine. Needs the `speech` extra (`uv sync --extra speech` just a mic
195
- library; no torch, no numba).
209
+ leaves the machine. Needs the `speech` extra just a mic library; no torch, no numba
210
+ (see [Installing & upgrading](#installing--upgrading); on a `uv tool` install that's
211
+ `uv tool install --force 'chad-code[speech]'`).
196
212
 
197
213
  **Usage.** `uv run chad --help` is the source of truth:
198
214
 
@@ -110,7 +110,24 @@ def test_available_reports_missing_deps_as_hint():
110
110
  ok, reason = speech.available()
111
111
  assert ok in (True, False)
112
112
  if not ok:
113
- assert "uv sync --extra speech" in reason
113
+ assert "sounddevice" in reason and "speech" in reason
114
+
115
+
116
+ def test_install_hint_matches_how_chad_was_installed():
117
+ # The hint is the whole fix, so it has to name the command that works for the
118
+ # reader: extras ride on the install spec for uvx/tool installs, on `uv sync`
119
+ # only in a clone. Paths are the real shapes uv produces.
120
+ h = speech.install_hint
121
+ assert h(path="/Users/x/.local/share/uv/tools/chad-code/lib/python3.13/"
122
+ "site-packages/chad/speech.py") == \
123
+ "uv tool install --force 'chad-code[speech]'"
124
+ assert h(path="/Users/x/.cache/uv/archive-v0/abc123/lib/python3.13/"
125
+ "site-packages/chad/speech.py") == "uvx --from 'chad-code[speech]' chad"
126
+ assert h(path="/Users/x/.local/share/uv/python/venv/lib/python3.13/"
127
+ "site-packages/chad/speech.py") == "uv pip install 'chad-code[speech]'"
128
+ # This test file IS a clone, so the live module resolves to the clone form.
129
+ assert speech.install_hint() == "uv sync --extra speech"
130
+ assert h("highlight") == "uv sync --extra highlight"
114
131
 
115
132
 
116
133
  def test_collapse_repeats_kills_hallucination_loop():
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