chad-code 1.0.5__tar.gz → 1.0.7__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 (113) hide show
  1. {chad_code-1.0.5 → chad_code-1.0.7}/PKG-INFO +22 -10
  2. {chad_code-1.0.5 → chad_code-1.0.7}/README.md +17 -4
  3. {chad_code-1.0.5 → chad_code-1.0.7}/pyproject.toml +40 -13
  4. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/__init__.py +1 -1
  5. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/agent.py +43 -20
  6. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/cli.py +6 -6
  7. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/levers.py +11 -0
  8. chad_code-1.0.7/src/chad/lsp.py +340 -0
  9. chad_code-1.0.7/src/chad/lspclient.py +341 -0
  10. chad_code-1.0.7/src/chad/lspservers.py +157 -0
  11. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/mcp.py +81 -31
  12. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/mcp_oauth.py +37 -16
  13. chad_code-1.0.7/src/chad/parakeet/LICENSE +201 -0
  14. chad_code-1.0.7/src/chad/parakeet/__init__.py +27 -0
  15. chad_code-1.0.7/src/chad/parakeet/alignment.py +287 -0
  16. chad_code-1.0.7/src/chad/parakeet/attention.py +640 -0
  17. chad_code-1.0.7/src/chad/parakeet/audio.py +183 -0
  18. chad_code-1.0.7/src/chad/parakeet/cache.py +156 -0
  19. chad_code-1.0.7/src/chad/parakeet/conformer.py +423 -0
  20. chad_code-1.0.7/src/chad/parakeet/ctc.py +34 -0
  21. chad_code-1.0.7/src/chad/parakeet/parakeet.py +1055 -0
  22. chad_code-1.0.7/src/chad/parakeet/rnnt.py +155 -0
  23. chad_code-1.0.7/src/chad/parakeet/tokenizer.py +3 -0
  24. chad_code-1.0.7/src/chad/parakeet/utils.py +112 -0
  25. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/prove.py +1 -1
  26. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/repomap.py +242 -16
  27. chad_code-1.0.7/src/chad/speech.py +565 -0
  28. chad_code-1.0.7/src/chad/symbols.py +162 -0
  29. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/tools.py +30 -5
  30. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/tui.py +296 -13
  31. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad_code.egg-info/PKG-INFO +22 -10
  32. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad_code.egg-info/SOURCES.txt +20 -0
  33. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad_code.egg-info/requires.txt +4 -5
  34. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_agent_e2e.py +14 -14
  35. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_engine.py +1 -1
  36. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_garble_invariant.py +1 -1
  37. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_gate.py +67 -11
  38. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_ignore.py +3 -3
  39. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_lever_bite.py +27 -4
  40. chad_code-1.0.7/tests/test_lsp.py +315 -0
  41. chad_code-1.0.7/tests/test_lsp_live.py +108 -0
  42. chad_code-1.0.7/tests/test_lspclient.py +205 -0
  43. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_mcp.py +77 -7
  44. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_mcp_oauth.py +3 -3
  45. chad_code-1.0.7/tests/test_repomap_polyglot.py +111 -0
  46. chad_code-1.0.7/tests/test_speech.py +284 -0
  47. chad_code-1.0.7/tests/test_speech_tui.py +508 -0
  48. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_subagent.py +7 -5
  49. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_symbols.py +83 -52
  50. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_tui.py +50 -1
  51. chad_code-1.0.5/src/chad/lsp.py +0 -323
  52. chad_code-1.0.5/src/chad/symbols.py +0 -372
  53. chad_code-1.0.5/tests/test_lsp.py +0 -115
  54. {chad_code-1.0.5 → chad_code-1.0.7}/LICENSE +0 -0
  55. {chad_code-1.0.5 → chad_code-1.0.7}/setup.cfg +0 -0
  56. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/atif.py +0 -0
  57. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/base_engine.py +0 -0
  58. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/bench.py +0 -0
  59. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/compaction.py +0 -0
  60. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/completion_engine.py +0 -0
  61. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/config.py +0 -0
  62. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/diag.py +0 -0
  63. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/engine.py +0 -0
  64. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/guardrails.py +0 -0
  65. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/ignore.py +0 -0
  66. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/mlx_fastpath.py +0 -0
  67. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/mlx_qsdpa.py +0 -0
  68. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/profiles.py +0 -0
  69. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/prompt.py +0 -0
  70. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/render.py +0 -0
  71. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/serve.py +0 -0
  72. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/session.py +0 -0
  73. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/skills.py +0 -0
  74. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/syntaxgate.py +0 -0
  75. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/toolcall_parse.py +0 -0
  76. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad/validate.py +0 -0
  77. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad_code.egg-info/dependency_links.txt +0 -0
  78. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad_code.egg-info/entry_points.txt +0 -0
  79. {chad_code-1.0.5 → chad_code-1.0.7}/src/chad_code.egg-info/top_level.txt +0 -0
  80. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_agent.py +0 -0
  81. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_agent_guards.py +0 -0
  82. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_atif.py +0 -0
  83. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_bench.py +0 -0
  84. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_cli.py +0 -0
  85. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_compact_notice.py +0 -0
  86. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_compaction.py +0 -0
  87. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_completion_engine.py +0 -0
  88. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_config.py +0 -0
  89. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_confirm_preview.py +0 -0
  90. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_done_audit.py +0 -0
  91. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_drift_warn.py +0 -0
  92. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_edit.py +0 -0
  93. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_edit_corruption.py +0 -0
  94. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_engine_kvquant.py +0 -0
  95. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_engine_pld_hybrid.py +0 -0
  96. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_feel_pack.py +0 -0
  97. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_intent.py +0 -0
  98. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_levers.py +0 -0
  99. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_log_redaction.py +0 -0
  100. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_mlx_fastpath.py +0 -0
  101. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_mlx_qsdpa.py +0 -0
  102. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_plan_review.py +0 -0
  103. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_prove.py +0 -0
  104. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_render.py +0 -0
  105. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_replace_lines.py +0 -0
  106. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_repomap.py +0 -0
  107. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_serve.py +0 -0
  108. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_session.py +0 -0
  109. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_skills.py +0 -0
  110. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_syntaxgate.py +0 -0
  111. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_toolcall_parse.py +0 -0
  112. {chad_code-1.0.5 → chad_code-1.0.7}/tests/test_tools.py +0 -0
  113. {chad_code-1.0.5 → chad_code-1.0.7}/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.5
3
+ Version: 1.0.7
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
@@ -21,18 +21,17 @@ Requires-Dist: mlx<0.33,>=0.32.0; sys_platform == "darwin"
21
21
  Requires-Dist: mlx-lm<0.32,>=0.31.3; sys_platform == "darwin"
22
22
  Requires-Dist: transformers<5.13,>=5.0
23
23
  Requires-Dist: jinja2>=3.1
24
- Requires-Dist: numpy<2,>=1.26
24
+ Requires-Dist: numpy<3,>=1.26
25
25
  Requires-Dist: huggingface-hub>=1.22.0
26
- Requires-Dist: jedi>=0.19.2
27
26
  Requires-Dist: prompt_toolkit>=3.0.43
28
27
  Requires-Dist: tree-sitter>=0.26.0
29
28
  Requires-Dist: tree-sitter-language-pack>=1.12.2
30
29
  Requires-Dist: rustworkx>=0.15
31
- Requires-Dist: mcp>=1.27
30
+ Requires-Dist: mcp<3,>=2
32
31
  Provides-Extra: highlight
33
32
  Requires-Dist: pygments>=2.17; extra == "highlight"
34
- Provides-Extra: lsp
35
- Requires-Dist: serena-agent>=1.5; extra == "lsp"
33
+ Provides-Extra: speech
34
+ Requires-Dist: sounddevice>=0.5; extra == "speech"
36
35
  Dynamic: license-file
37
36
 
38
37
  # chad — a local, MLX-backed coding agent
@@ -81,10 +80,12 @@ instrument:
81
80
  | **Harness** | open-ended, anything you can imagine | plan. execute. nothing else. |
82
81
  | **When wrong** | reasons a way out | already shipped |
83
82
 
84
- ![chad fixing a failing test end to end — reason, read, edit, rerun pytest, all on a local 35B](docs/demo.gif)
83
+ ![chad fixing a failing test end to end — reason, read, edit, run pytest, confirm green, all on a local 35B](docs/demo.gif)
85
84
 
86
- > Real session, unedited (cold model load cut): a local 35B reasons through the failure,
87
- > edits the file, reruns the tests, confirms green.
85
+ > Real session, unedited (the silent prefill is cut): a local 35B finds the cent that
86
+ > floor division loses, fixes it, and verifies itself — then your own `pytest`, in your
87
+ > own shell. Recorded with `--yolo` so nothing pauses for a keypress; the default mode
88
+ > stops and asks before every edit and every command.
88
89
 
89
90
  ## Frontier scores, laptop cost
90
91
 
@@ -171,7 +172,10 @@ it chad uses the tree-sitter fallback automatically.
171
172
  `uv run chad` launches a full-screen terminal UI (built on prompt_toolkit):
172
173
 
173
174
  - **shift-tab cycles permission modes** — `normal` (confirm each bash/write/edit) →
174
- `auto-accept edits` → `plan mode` (read-only: investigate + propose a numbered plan) → back.
175
+ `auto-accept edits` (edits land silently; **terminal commands still ask**) → `yolo`
176
+ (nothing asks) → `plan mode` (read-only: investigate + propose a numbered plan) → back.
177
+ - **the approval prompt shows what you're approving** — the full command on its own lines
178
+ above the input, not a clipped single line. Approving blind isn't approving.
175
179
  - **type-ahead message queue** — keep typing while the agent works; messages run in order.
176
180
  - **ctrl-c interrupts the running turn** without killing the session.
177
181
  - **live status line** — model, mode, context %, a state glyph + verb, elapsed seconds, and
@@ -181,6 +185,14 @@ it chad uses the tree-sitter fallback automatically.
181
185
  `/model`, `/mode`, `/help`, `/exit`. Same set in the `--repl` line interface.
182
186
  - **`@file` / `@dir` mentions** and **`!command` shell passthrough** — pull a file into
183
187
  context inline, or run a shell command without invoking the model.
188
+ - **voice mode, all local** — `/speech`, then ctrl-t to talk: Parakeet-on-MLX transcribes
189
+ into the input box for you to review before Enter sends it (esc discards a take);
190
+ replies are read aloud via macOS `say`. A pre-roll buffer means your first syllable
191
+ isn't clipped, and a personal word table (`~/.chad/speech_words.json`) teaches it your
192
+ identifiers — `{"pie test": "pytest"}`. Dictation cost is linear in take length, so a
193
+ 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).
184
196
 
185
197
  **Usage.** `uv run chad --help` is the source of truth:
186
198
 
@@ -44,10 +44,12 @@ instrument:
44
44
  | **Harness** | open-ended, anything you can imagine | plan. execute. nothing else. |
45
45
  | **When wrong** | reasons a way out | already shipped |
46
46
 
47
- ![chad fixing a failing test end to end — reason, read, edit, rerun pytest, all on a local 35B](docs/demo.gif)
47
+ ![chad fixing a failing test end to end — reason, read, edit, run pytest, confirm green, all on a local 35B](docs/demo.gif)
48
48
 
49
- > Real session, unedited (cold model load cut): a local 35B reasons through the failure,
50
- > edits the file, reruns the tests, confirms green.
49
+ > Real session, unedited (the silent prefill is cut): a local 35B finds the cent that
50
+ > floor division loses, fixes it, and verifies itself — then your own `pytest`, in your
51
+ > own shell. Recorded with `--yolo` so nothing pauses for a keypress; the default mode
52
+ > stops and asks before every edit and every command.
51
53
 
52
54
  ## Frontier scores, laptop cost
53
55
 
@@ -134,7 +136,10 @@ it chad uses the tree-sitter fallback automatically.
134
136
  `uv run chad` launches a full-screen terminal UI (built on prompt_toolkit):
135
137
 
136
138
  - **shift-tab cycles permission modes** — `normal` (confirm each bash/write/edit) →
137
- `auto-accept edits` → `plan mode` (read-only: investigate + propose a numbered plan) → back.
139
+ `auto-accept edits` (edits land silently; **terminal commands still ask**) → `yolo`
140
+ (nothing asks) → `plan mode` (read-only: investigate + propose a numbered plan) → back.
141
+ - **the approval prompt shows what you're approving** — the full command on its own lines
142
+ above the input, not a clipped single line. Approving blind isn't approving.
138
143
  - **type-ahead message queue** — keep typing while the agent works; messages run in order.
139
144
  - **ctrl-c interrupts the running turn** without killing the session.
140
145
  - **live status line** — model, mode, context %, a state glyph + verb, elapsed seconds, and
@@ -144,6 +149,14 @@ it chad uses the tree-sitter fallback automatically.
144
149
  `/model`, `/mode`, `/help`, `/exit`. Same set in the `--repl` line interface.
145
150
  - **`@file` / `@dir` mentions** and **`!command` shell passthrough** — pull a file into
146
151
  context inline, or run a shell command without invoking the model.
152
+ - **voice mode, all local** — `/speech`, then ctrl-t to talk: Parakeet-on-MLX transcribes
153
+ into the input box for you to review before Enter sends it (esc discards a take);
154
+ replies are read aloud via macOS `say`. A pre-roll buffer means your first syllable
155
+ isn't clipped, and a personal word table (`~/.chad/speech_words.json`) teaches it your
156
+ identifiers — `{"pie test": "pytest"}`. Dictation cost is linear in take length, so a
157
+ 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).
147
160
 
148
161
  **Usage.** `uv run chad --help` is the source of truth:
149
162
 
@@ -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.5"
7
+ version = "1.0.7"
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"
@@ -49,9 +49,11 @@ dependencies = [
49
49
  # absent in a clean install (e.g. chad inside a Linux benchmark container), where its
50
50
  # absence crashes the first turn. Declare it directly.
51
51
  "jinja2>=3.1",
52
- "numpy>=1.26,<2",
52
+ # <3: the old <2 cap was 1.0.0-era caution, not a known break. numpy 2.4.6
53
+ # verified end-to-end (full gate + live Parakeet-on-MLX transcription +
54
+ # `chad prove` 4/4); staying open keeps 2.x-floored ASR deps adoptable.
55
+ "numpy>=1.26,<3",
53
56
  "huggingface-hub>=1.22.0", # first-run model download (cli.py/engine.py); imported directly, not just transitively
54
- "jedi>=0.19.2", # in-process symbolic code intelligence (read/search/edit)
55
57
  "prompt_toolkit>=3.0.43",
56
58
  "tree-sitter>=0.26.0",
57
59
  "tree-sitter-language-pack>=1.12.2",
@@ -60,10 +62,14 @@ dependencies = [
60
62
  # wheel, zero transitive deps. Benchmarked vs scipy/networkx/igraph on a real
61
63
  # 421k-edge graph: identical top-50 ranking, 0.79s -> 0.04s.
62
64
  "rustworkx>=0.15",
63
- # Floor lowered from 1.28.1: serena-agent (the `lsp` extra) hard-pins mcp==1.27.0, and
64
- # chad's mcp surface (ClientSession, stdio/streamable-http clients, OAuthClientProvider)
65
- # is stable across 1.27+. The 1.27->1.28 delta is only deprecations chad doesn't touch.
66
- "mcp>=1.27",
65
+ # Single 2.x code path (the 1.x cap left with serena-agent, which hard-pinned mcp
66
+ # and was the only thing keeping chad below 2). The 1->2 port was done deliberately,
67
+ # not by a resolver: two of the renames (isError->is_error, readOnlyHint->
68
+ # read_only_hint) FAIL SILENTLY through getattr defaults, so both are pinned by
69
+ # tests that would fail on the old attribute names (tests/test_mcp.py). 2.0 also
70
+ # negotiates protocol 2026-07-28; the 1.x line tops out at 2025-11-25. Capped <3:
71
+ # the same class of break will happen again.
72
+ "mcp>=2,<3",
67
73
  ]
68
74
 
69
75
  # Optional syntax highlighting in diffs / confirm previews. Pure-Python,
@@ -73,11 +79,16 @@ dependencies = [
73
79
  # package. Document uv/git installs only.
74
80
  [project.optional-dependencies]
75
81
  highlight = ["pygments>=2.17"]
76
- # Precision code intelligence (docs/design.md "Precision" phase): solidlsp ships inside
77
- # serena-agent and backs lsp.py's find-refs/rename via pyright. Optional on purpose —
78
- # heavyweight tree; absent, every caller falls back to the tree-sitter backend.
79
- # Install with `uv sync --extra lsp`.
80
- lsp = ["serena-agent>=1.5"]
82
+ # Voice mode (/speech in the TUI): push-to-talk dictation via chad's vendored
83
+ # Parakeet-on-MLX (src/chad/parakeet — the PyPI parakeet-mlx drags librosa's
84
+ # numba/scipy tree for one mel-filterbank call) + spoken replies via macOS
85
+ # `say`. All on-device, nothing leaves the machine. mlx/numpy/huggingface-hub
86
+ # are already core deps, so the extra adds only the mic (sounddevice/PortAudio).
87
+ # 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`.
89
+ speech = [
90
+ "sounddevice>=0.5",
91
+ ]
81
92
 
82
93
  # The one entrypoint: `uv run chad`. (`chad-bench` reproduces docs/benchmarks.md.)
83
94
  # `chad-code` is an alias so `uvx chad-code` (which runs the console script matching
@@ -107,6 +118,10 @@ build-backend = "setuptools.build_meta"
107
118
  [tool.setuptools.packages.find]
108
119
  where = ["src"]
109
120
 
121
+ # The vendored Parakeet code is Apache-2.0; its license text must travel with
122
+ # the code, wheel included.
123
+ [tool.setuptools.package-data]
124
+ "chad.parakeet" = ["LICENSE"]
110
125
  [tool.pytest.ini_options]
111
126
  testpaths = ["tests"]
112
127
  # No "-q" here: CI/docs/agents all invoke `pytest -q` explicitly, and stacking the two
@@ -127,10 +142,16 @@ src = ["src", "tests"]
127
142
  select = ["F", "E9", "I"]
128
143
  ignore = ["E741"] # ambiguous var names (l/I) appear in existing math code
129
144
 
145
+ [tool.ruff.lint.per-file-ignores]
146
+ # Vendored upstream (see chad/parakeet/__init__.py), kept byte-close for
147
+ # future re-syncs — not held to chad's own lint standard. (F401: imports left
148
+ # in place for upstream fidelity after the transcribe(path) cut.)
149
+ "src/chad/parakeet/*" = ["F401", "F821", "F841", "E741"]
150
+
130
151
  [tool.mypy]
131
152
  python_version = "3.11"
132
153
  files = ["src/chad"]
133
- ignore_missing_imports = true # mlx/jedi/tree_sitter/mcp ship partial or no stubs
154
+ ignore_missing_imports = true # mlx/tree_sitter/mcp ship partial or no stubs
134
155
  warn_unused_ignores = true
135
156
  implicit_optional = true # `x: T = None` defaults are idiomatic throughout; this
136
157
  # restores the pre-PEP-484 reading rather than rewriting
@@ -142,6 +163,12 @@ disable_error_code = ["var-annotated"]
142
163
  # Tighten (disallow_untyped_defs, re-enable var-annotated, add B/UP/SIM in ruff) in a
143
164
  # follow-up once this baseline holds.
144
165
 
166
+ [[tool.mypy.overrides]]
167
+ # Vendored upstream inference code (see chad/parakeet/__init__.py) is not held
168
+ # to chad's gate so re-syncs stay a plain re-copy.
169
+ module = "chad.parakeet.*"
170
+ ignore_errors = true
171
+
145
172
  [[tool.mypy.overrides]]
146
173
  # mlx_fastpath patches mlx_lm classes that only exist on macOS: with mlx installed
147
174
  # (dev Macs, the macos CI jobs) its `type: ignore[method-assign]` comments are load-
@@ -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.5"
7
+ __version__ = "1.0.7"
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
@@ -57,11 +57,12 @@ SUBAGENT_READ_ONLY = {
57
57
 
58
58
  def subagent_tools_for(parent_mode: str, requested: str) -> str:
59
59
  """The toolset a sub-agent may run with. A sub-agent auto-approves its own tool
60
- calls (mode='auto', no confirm callback), so it must never hold more autonomy
61
- than its parent: only an 'auto' parent (--yolo / headless) may delegate 'all';
62
- a 'normal' parent's human-approval promise and plan mode's read-only promise
63
- both clamp the sub-agent to read-only."""
64
- if parent_mode == "auto" and requested == "all":
60
+ calls (mode='yolo', no confirm callback), so it must never hold more autonomy
61
+ than its parent: only a 'yolo' parent (--yolo / headless) may delegate 'all'.
62
+ A 'normal' parent's human-approval promise, an 'auto' parent's narrower promise
63
+ (edits yes, shell no — and 'all' includes bash), and plan mode's read-only
64
+ promise all clamp the sub-agent to read-only."""
65
+ if parent_mode == "yolo" and requested == "all":
65
66
  return "all"
66
67
  return "read-only"
67
68
 
@@ -209,10 +210,30 @@ def close_unclosed_think(text: str, thinking: bool) -> str:
209
210
 
210
211
  # Permission modes (Claude Code parity, cycled with shift-tab in the TUI):
211
212
  # normal — confirm each mutating tool (bash/write/edit)
212
- # auto — auto-approve mutating tools (yolo)
213
+ # auto — auto-approve FILE EDITS only; bash and MCP tools still ask
214
+ # yolo — auto-approve every mutating tool (--yolo starts here)
213
215
  # plan — read-only: changes are blocked; the model researches and proposes a plan
214
- MODES = ("normal", "auto", "plan")
215
- MODE_LABEL = {"normal": "normal", "auto": "auto-accept edits", "plan": "plan mode"}
216
+ MODES = ("normal", "auto", "yolo", "plan")
217
+ MODE_LABEL = {"normal": "normal", "auto": "auto-accept edits", "yolo": "yolo",
218
+ "plan": "plan mode"}
219
+
220
+ # What `auto` waves through: edits to files in the workspace, whose blast radius is a
221
+ # diff you can read and revert. `bash` is excluded on purpose — a shell command can
222
+ # touch anything on the machine, is the one tool whose preview is genuinely hard to
223
+ # eyeball, and is exactly what a human is still in the loop for. MCP tools are excluded
224
+ # for the same reason (a server-side tool can send mail or hit an API). The list is
225
+ # explicit rather than `MUTATING - {"bash"}` so it fails CLOSED: a mutating tool added
226
+ # later asks for approval until someone deliberately adds it here.
227
+ AUTO_EDIT_TOOLS = {"write", "edit", "replace_lines", "insert_lines",
228
+ "replace_symbol", "insert_symbol", "rename_symbol"}
229
+
230
+
231
+ def auto_approves(mode: str, name: str) -> bool:
232
+ """Does `mode` clear tool `name` without asking a human? Read-only tools never
233
+ reach here (the caller checks is_mutating first)."""
234
+ if mode == "yolo":
235
+ return True
236
+ return mode == "auto" and name in AUTO_EDIT_TOOLS
216
237
  _PLAN_PREFIX = (
217
238
  "[PLAN MODE. Research first (read/grep/glob/repo_map/overview) — do NOT edit "
218
239
  "project files or run commands. Then write ONE self-contained plan to "
@@ -325,7 +346,7 @@ class Agent:
325
346
  skills.reset_session()
326
347
  from . import mcp
327
348
  mcp.reset_session()
328
- self.mode = mode or ("auto" if yolo else "normal")
349
+ self.mode = mode or ("yolo" if yolo else "normal")
329
350
  self.thinking = thinking # Ornith is a reasoning model; toggles <think> blocks
330
351
  # Steps per WINDOW, not a hard kill: a window that landed+verified a change
331
352
  # earns an extension (see guardrails.extend_step_cap; absolute ceiling 4x).
@@ -460,7 +481,7 @@ class Agent:
460
481
 
461
482
  @property
462
483
  def yolo(self) -> bool:
463
- return self.mode == "auto"
484
+ return self.mode == "yolo"
464
485
 
465
486
  @property
466
487
  def tok_per_s(self) -> float:
@@ -586,18 +607,18 @@ class Agent:
586
607
 
587
608
  def _confirm(self, name, args) -> bool:
588
609
  # Destructive-bash seatbelt: a catastrophic shell command (rm -rf ~, mkfs,
589
- # curl|sh, …) is screened even in --yolo/auto mode, because the model acts on
590
- # untrusted repo contents and auto mode has no human in the loop. If a confirm
610
+ # curl|sh, …) is screened even in --yolo mode, because the model acts on
611
+ # untrusted repo contents and yolo mode has no human in the loop. If a confirm
591
612
  # channel exists (TTY or callback) we force the prompt; headless with no channel
592
613
  # we BLOCK rather than execute on injection. CHAD_NO_DESTRUCTIVE_GUARD=1 opts out.
593
614
  dangerous = (name == "bash" and isinstance(args, dict)
594
615
  and not config.flag("CHAD_NO_DESTRUCTIVE_GUARD")
595
616
  and guardrails.is_destructive_bash(str(args.get("command", ""))))
596
- if self.mode == "auto" or not is_mutating(name):
617
+ if not is_mutating(name) or auto_approves(self.mode, name):
597
618
  if not dangerous:
598
619
  return True
599
620
  if self._confirm_cb is None and not sys.stdin.isatty():
600
- self._emit("info", f" [blocked destructive command in auto mode: "
621
+ self._emit("info", f" [blocked destructive command — nobody to approve it: "
601
622
  f"{args.get('command', '')!r}; set CHAD_NO_DESTRUCTIVE_GUARD=1 to allow]")
602
623
  # Tell the MODEL the truth about who blocked it and why: "[denied by
603
624
  # user]" reads as a human refusal and teaches the wrong lesson (the
@@ -642,12 +663,13 @@ class Agent:
642
663
  parent. Its grep/read churn never enters the main transcript; only this return
643
664
  does. Depth 1 only: a sub-agent can't itself call `task` (its schema omits it)."""
644
665
  # Never grant a sub-agent more autonomy than its parent: the sub-agent runs
645
- # mode='auto' with no confirm callback, so an 'all' toolset is honored only when
646
- # the parent itself is auto-approved (--yolo/headless); 'normal' (human confirms
647
- # every mutation) and 'plan' (read-only) parents clamp it to read-only.
666
+ # mode='yolo' with no confirm callback, so an 'all' toolset is honored only when
667
+ # the parent itself is fully auto-approved (--yolo/headless); 'normal' (human
668
+ # confirms every mutation), 'auto' (human still confirms shell) and 'plan'
669
+ # (read-only) parents clamp it to read-only.
648
670
  requested, tools = tools, subagent_tools_for(self.mode, tools)
649
671
  if requested == "all" and tools == "read-only":
650
- self._emit("muted", " ⌊ sub-agent clamped to read-only (parent mode is not auto)")
672
+ self._emit("muted", " ⌊ sub-agent clamped to read-only (parent mode is not yolo)")
651
673
  self._emit("muted", f" ⌊ delegating to sub-agent: {description}")
652
674
  log.info("TASK start | desc=%r | tools=%s | prompt=%r",
653
675
  description, tools, redact(prompt[:300]))
@@ -657,7 +679,7 @@ class Agent:
657
679
  try:
658
680
  sub = Agent(
659
681
  self.engine,
660
- mode="auto", # auto-approve within its restricted toolset
682
+ mode="yolo", # auto-approve within its restricted toolset
661
683
  max_steps=SUBAGENT_MAX_STEPS,
662
684
  ctx_limit=min(self.ctx_limit, SUBAGENT_CTX_LIMIT),
663
685
  thinking=self.thinking,
@@ -1509,7 +1531,8 @@ class Agent:
1509
1531
  # fire, which is how a turn once shipped a 28-token garble as its final
1510
1532
  # answer. read_only/explain asks are exempt. Banks a note so
1511
1533
  # auto-continue relaunches fresh instead of shipping nothing.
1512
- did_nothing = self.mode == "auto" and not read_only_intent and not did_work
1534
+ did_nothing = (self.mode in ("auto", "yolo")
1535
+ and not read_only_intent and not did_work)
1513
1536
  if (action_task and not read_only_intent
1514
1537
  and (not made_edit or unverified_edit)) or did_nothing:
1515
1538
  # Churn→audit handoff: this hard stop used to fire with the
@@ -712,7 +712,7 @@ def _main(argv=None):
712
712
  sys.stderr.write(f"ready in {load_s:.1f}s | context {eng.effective_ctx} tokens "
713
713
  f"(compact at {ctx_limit})\n")
714
714
 
715
- start_mode = "plan" if args.plan else ("auto" if args.yolo else "normal")
715
+ start_mode = "plan" if args.plan else ("yolo" if args.yolo else "normal")
716
716
  thinking = not args.no_think
717
717
 
718
718
  # Live per-turn ctx-limit recheck: the startup number was
@@ -758,9 +758,9 @@ def _main(argv=None):
758
758
  # auto-approve mutating tools unless the user asked for read-only --plan.
759
759
  run_mode = start_mode
760
760
  if run_mode == "normal" and not sys.stdin.isatty():
761
- run_mode = "auto"
761
+ run_mode = "yolo"
762
762
  sys.stderr.write("[headless: auto-approving tools (use --plan for read-only)]\n")
763
- agent = Agent(eng, yolo=(run_mode == "auto"), ctx_limit=ctx_limit,
763
+ agent = Agent(eng, yolo=(run_mode == "yolo"), ctx_limit=ctx_limit,
764
764
  mode=run_mode, thinking=thinking, resume=resume, persist=True,
765
765
  think_budget=args.think_budget,
766
766
  turn_budget_s=turn_budget_s, ctx_limit_fn=ctx_limit_fn)
@@ -777,7 +777,7 @@ def _main(argv=None):
777
777
  # ships as an empty diff — the measured bail signature.
778
778
  auto_continue = config.env_int("CHAD_AUTO_CONTINUE")
779
779
  continues = auto_continue if auto_continue is not None \
780
- else (2 if run_mode == "auto" else 0)
780
+ else (2 if run_mode == "yolo" else 0)
781
781
  used_continues = 0
782
782
  while agent.budget_note:
783
783
  # Base allowance first; past it, keep granting fresh attempts while most
@@ -819,7 +819,7 @@ def _main(argv=None):
819
819
  if getattr(eng, "temp", None) is not None:
820
820
  eng.temp = max(eng.temp, 0.6)
821
821
  eng.reset()
822
- agent = Agent(eng, yolo=(run_mode == "auto"), ctx_limit=ctx_limit,
822
+ agent = Agent(eng, yolo=(run_mode == "yolo"), ctx_limit=ctx_limit,
823
823
  mode=run_mode, thinking=thinking, persist=True,
824
824
  think_budget=args.think_budget,
825
825
  turn_budget_s=relaunch_s, ctx_limit_fn=ctx_limit_fn)
@@ -841,7 +841,7 @@ def _main(argv=None):
841
841
  sys.stderr.write(f"[review] task finished with {turn_budget_s - elapsed:.0f}s "
842
842
  "of budget left; running one fresh-context verification pass\n")
843
843
  eng.reset()
844
- agent = Agent(eng, yolo=(run_mode == "auto"), ctx_limit=ctx_limit,
844
+ agent = Agent(eng, yolo=(run_mode == "yolo"), ctx_limit=ctx_limit,
845
845
  mode=run_mode, thinking=thinking, persist=True,
846
846
  think_budget=args.think_budget,
847
847
  turn_budget_s=review_budget, ctx_limit_fn=ctx_limit_fn)
@@ -446,6 +446,17 @@ LEVERS: dict[str, Lever] = {
446
446
  "a flat file listing, so the model orients without a reflexive step-1 repo_map "
447
447
  "call. Degrades to the flat listing when repomap is unavailable (see prompt.py).",
448
448
  "ai-codex"),
449
+
450
+ # --- group "ctxengine": the uniform-symbols work. -------------------------------
451
+ "post_edit_diagnostics": Lever(
452
+ "Append the language server's post-edit typecheck errors (~50 tokens, errors "
453
+ "only, capped) to edit/symbol-edit results — the semantic tier above "
454
+ "syntaxgate's parse check. A type error surfaced in the edit result replaces "
455
+ "the multi-thousand-token failed test run the model would otherwise need to "
456
+ "discover it. Never spawns a server (an edit must not pay a cold start): "
457
+ "only a server already warmed by find_refs/hover/disambiguation is "
458
+ "consulted; absent one, silence.",
459
+ "ctxengine"),
449
460
  }
450
461
 
451
462