lcode-cli 0.28.1__tar.gz → 0.29.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.
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/CHANGELOG.md +20 -1
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/PKG-INFO +1 -1
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/automation.md +1 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/configuration.md +1 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/quickstart.md +1 -1
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/usage.md +36 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/__init__.py +1 -1
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/agent.py +89 -2
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/api.py +3 -0
- lcode_cli-0.29.0/src/lcode/checks.py +160 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/cli.py +6 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/config.py +1 -0
- lcode_cli-0.29.0/tests/test_checks.py +192 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.editorconfig +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.github/CODEOWNERS +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.github/dependabot.yml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.github/workflows/ci.yml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.github/workflows/docs.yml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.github/workflows/release.yml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.gitignore +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/.pre-commit-config.yaml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/AGENTS.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/CODE_OF_CONDUCT.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/CONTRIBUTING.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/LICENSE +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/README.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/SECURITY.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/action.yml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/agents.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/assets/demo.svg +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/assets/extra.css +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/assets/logo.svg +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/assets/social-preview.png +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/bench.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/changelog.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/commands.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/contributing.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/editors.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/git.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/hooks.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/how-it-works.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/index.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/installation.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/lsp.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/mcp.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/memory.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/models.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/sandbox.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/search.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/servers.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/docs/troubleshooting.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/examples/README.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/examples/api/fix_tests.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/examples/commands/review.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/examples/skills/commit-message/SKILL.md +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/examples/skills/commit-message/scripts/staged.sh +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/install.sh +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/mkdocs.yml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/overrides/main.html +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/pyproject.toml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/scripts/record_demo.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/scripts/social-preview.html +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/__main__.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/acp.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/action.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/backends.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/bench.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/bench_backend.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/bench_runtime.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/catalog.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/checkpoints.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/clipboard.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/codesearch.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/context.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/extensions.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/frontmatter.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/gitflow.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/hardware.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/hooks.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/jobs.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/limits.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/lsp.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/__init__.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/auth.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/catalog.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/catalog.toml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/client.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/commands.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/config.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/http.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/manager.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/protocol.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/server.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/mcp/stdio.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/memory.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/models.toml +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/notify.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/ollama.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/permissions.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/planning.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/render.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/repeats.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/repl.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/repomap.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/residency.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/sandbox.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/secretscan.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/servers/__init__.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/servers/encord.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/servers/valohai.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/servers/xai.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/sessions.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/subagents.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/tools.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/update.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/vision.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/src/lcode/web.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/conftest.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/fake_lsp_server.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/fake_mcp_server.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_acp.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_action.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_agent.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_api.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_backends.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_bench.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_catalog.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_checkpoints.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_cli.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_clipboard.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_codesearch.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_config.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_context.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_context_picker.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_extensions.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_gitflow.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_hooks.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_jobs.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_lsp.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_mcp.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_memory.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_permissions.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_plan_mode.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_repeats.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_repomap.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_residency.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_robustness.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_sandbox.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_secretscan.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_servers.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_sessions.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_subagents.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_tools.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_update.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_vision.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_web.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/tests/test_xai.py +0 -0
- {lcode_cli-0.28.1 → lcode_cli-0.29.0}/uv.lock +0 -0
|
@@ -6,6 +6,24 @@ All notable changes to lcode are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.29.0] - 2026-10-13
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- lcode runs the project's checks before calling work done. After a request that changed files, it
|
|
14
|
+
runs the tests (pytest, unittest, npm/pnpm/yarn/bun test, cargo test, go test or make test, found
|
|
15
|
+
from the project, or the command in `.lcode/settings.toml` or the `checks` setting). When they
|
|
16
|
+
fail, the model gets the output and keeps working, twice at most, and then says what still fails.
|
|
17
|
+
Checks the model already ran after its last change aren't repeated. `lcode -p --output json`
|
|
18
|
+
reports them in `checks`, and `lcode bench --checks off` compares. See
|
|
19
|
+
[Checks before done](https://nasser1941.github.io/lcode/usage/#checks-before-done).
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- A tool call that Ollama couldn't read because its XML was out of order (`expected element type
|
|
24
|
+
<function> but have <parameter>`) ended the request with an error. Like other malformed calls,
|
|
25
|
+
it's now sent back to the model to make again.
|
|
26
|
+
|
|
9
27
|
## [0.28.1] - 2026-10-13
|
|
10
28
|
|
|
11
29
|
### Fixed
|
|
@@ -632,7 +650,8 @@ First public release.
|
|
|
632
650
|
- `AGENTS.md` project instructions and `/init` to generate them.
|
|
633
651
|
- One-line installer for Linux and macOS.
|
|
634
652
|
|
|
635
|
-
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.
|
|
653
|
+
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.29.0...HEAD
|
|
654
|
+
[0.29.0]: https://github.com/nasser1941/lcode/compare/v0.28.1...v0.29.0
|
|
636
655
|
[0.28.1]: https://github.com/nasser1941/lcode/compare/v0.28.0...v0.28.1
|
|
637
656
|
[0.28.0]: https://github.com/nasser1941/lcode/compare/v0.27.0...v0.28.0
|
|
638
657
|
[0.27.0]: https://github.com/nasser1941/lcode/compare/v0.26.0...v0.27.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.29.0
|
|
4
4
|
Summary: A local-first terminal coding agent powered by open-weight models on your own GPU or Mac, via Ollama.
|
|
5
5
|
Project-URL: Homepage, https://nasser1941.github.io/lcode/
|
|
6
6
|
Project-URL: Documentation, https://nasser1941.github.io/lcode/
|
|
@@ -55,6 +55,7 @@ went: `0` success, `1` an error, `3` stopped at `--max-steps`, `4` stopped becau
|
|
|
55
55
|
| `tool_calls` | Every tool call, with its arguments and output (the first 4,000 characters). `error` is true when the call failed or wasn't allowed |
|
|
56
56
|
| `files_changed` | Files the request added (`A`), modified (`M`) or deleted (`D`), shell commands included |
|
|
57
57
|
| `usage` | Model requests and tokens; `prompt_tokens` includes tokens served from the prompt cache |
|
|
58
|
+
| `checks` | The [checks](usage.md#checks-before-done) that ran after the change: `command`, `exit_code` (`null` when they weren't allowed to run) and `by` (`lcode`, or `model` when the model ran them itself) |
|
|
58
59
|
| `error` | What went wrong, when `status` is `error` |
|
|
59
60
|
| `session_id` | Continue the conversation later with `lcode --resume <id>` |
|
|
60
61
|
|
|
@@ -43,6 +43,7 @@ lcode config path # print the file location
|
|
|
43
43
|
| `max_parallel_agents` | `1` | Subagents that run at the same time; more needs `OLLAMA_NUM_PARALLEL` on the Ollama server ([details](agents.md#several-at-once)) |
|
|
44
44
|
| `notify` | `true` | A desktop [notification](usage.md#notifications) when a long request is done or waits for your answer |
|
|
45
45
|
| `notify_after` | `30` | Seconds a request runs before it notifies |
|
|
46
|
+
| `checks` | `auto` | Run the project's [checks](usage.md#checks-before-done) after a request changed files: `auto` (found from the project), `off`, or a command |
|
|
46
47
|
| `repeat_limit` | `3` | Identical tool calls with identical results before lcode [steps in](how-it-works.md#when-the-model-repeats-itself); `0` turns it off |
|
|
47
48
|
| `secret_check` | `true` | Before `git commit` and `git push`, look for passwords, keys and tokens they'd add ([details](usage.md#secrets-in-commits)) |
|
|
48
49
|
| `checkpoints` | `true` | Save a checkpoint before the model changes files, so [`/undo`](usage.md#undo-and-checkpoints) can restore them |
|
|
@@ -113,6 +113,42 @@ reasoning is on.
|
|
|
113
113
|
lcode refuses to edit a file the model hasn't read in the session, or one that changed on disk since
|
|
114
114
|
it was read, so the model always edits the current version.
|
|
115
115
|
|
|
116
|
+
## Checks before done
|
|
117
|
+
|
|
118
|
+
Small models like to call work done after a syntax check. So after a request that changed files, lcode
|
|
119
|
+
runs the project's checks itself before the answer is final:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
◆ Checking the work: python -m unittest (from test files)
|
|
123
|
+
⚠ Checks fail (python -m unittest, exit code 1): the model gets the output to fix it.
|
|
124
|
+
◆ Checking the work: python -m unittest (from test files)
|
|
125
|
+
◆ Checks pass: python -m unittest
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
- **When they fail,** the model gets the output and keeps working, twice at most. After that its answer
|
|
129
|
+
says what still fails instead of claiming success. A failure that the change didn't cause is
|
|
130
|
+
reported, not chased, and the checks run again only if the model changes something more.
|
|
131
|
+
- **When nothing could run** (no tests collected, or the test runner isn't installed), lcode says so
|
|
132
|
+
and doesn't send it to the model.
|
|
133
|
+
- **When the model already ran them** after its last change and they passed, they aren't run again.
|
|
134
|
+
- **Permission:** they run like any other command, so in `ask` mode the first run asks (++a++ allows
|
|
135
|
+
them for the session).
|
|
136
|
+
- **Programs** using `lcode -p --output json` see the runs in the result's `checks`.
|
|
137
|
+
|
|
138
|
+
**Which checks.** lcode looks at the project: pytest when the configuration mentions it (`uv run
|
|
139
|
+
pytest -q` with a `uv.lock`), otherwise `python -m unittest` for `test_*.py` files. Then
|
|
140
|
+
`npm test` (or pnpm, yarn, bun) when `package.json` has a test script, `cargo test`, `go test ./...`
|
|
141
|
+
and `make test`. To choose the command yourself, set it for the repository in
|
|
142
|
+
`.lcode/settings.toml`. That file is used once you trust the repository, like its hooks:
|
|
143
|
+
|
|
144
|
+
```toml
|
|
145
|
+
[checks]
|
|
146
|
+
command = "uv run pytest -q && uv run ruff check ."
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Or set it for yourself with `lcode config set checks "make check"`; `lcode config set checks off`
|
|
150
|
+
turns checks off.
|
|
151
|
+
|
|
116
152
|
## Background commands
|
|
117
153
|
|
|
118
154
|
Some commands keep running: a dev server, a test watcher, a build in watch mode. The model starts
|
|
@@ -27,6 +27,7 @@ from rich.text import Text
|
|
|
27
27
|
|
|
28
28
|
from lcode import (
|
|
29
29
|
catalog,
|
|
30
|
+
checks,
|
|
30
31
|
codesearch,
|
|
31
32
|
extensions,
|
|
32
33
|
limits,
|
|
@@ -81,7 +82,13 @@ EVENT_OUTPUT = 4000 # characters of a tool's output in an event
|
|
|
81
82
|
REFUSED = ("Error:", "A deny rule", "The user denied", "This needs the user's permission")
|
|
82
83
|
MAX_MALFORMED_CALL_RETRIES = 2 # Ollama rejects tool calls whose arguments aren't valid JSON
|
|
83
84
|
# How Ollama reports a tool call it couldn't parse (Qwen3.6's calls are XML)
|
|
84
|
-
MALFORMED_CALL = (
|
|
85
|
+
MALFORMED_CALL = (
|
|
86
|
+
"error parsing tool call",
|
|
87
|
+
# Go's XML decoder, for models that write calls as XML (Qwen3.6): a cut-off or misordered call
|
|
88
|
+
"XML syntax error",
|
|
89
|
+
"expected element type",
|
|
90
|
+
"closed by </",
|
|
91
|
+
)
|
|
85
92
|
RESULT_SHARE = 0.25 # of the context window, at most, for one tool result
|
|
86
93
|
MIN_RESULT_CHARS = 8000
|
|
87
94
|
READING_NOTE_TOKENS = 4000 # new input this large gets "Reading … new tokens" in the status line
|
|
@@ -114,7 +121,7 @@ SYSTEM_PROMPT = """You are lcode, an autonomous software-engineering agent runni
|
|
|
114
121
|
- A syntax check or an import only shows that code loads. Run what you changed the way it will be used (the script, the tests, the app) and fix what fails.
|
|
115
122
|
- To fix a bug, first reproduce each problem the user reports with a test, a command or a short script, then run the same checks to show they're gone.
|
|
116
123
|
- For what you can't see, like a GUI, a web page or a layout, check it without a screen where you can: `QT_QPA_PLATFORM=offscreen` for Qt, a headless browser, or a script that prints sizes and positions or renders to an image{look}. Measure instead of assuming.
|
|
117
|
-
- Never claim something works, mark a todo done, or call it tested unless you ran it. If you couldn't check something, say so and tell the user exactly what to look for. If something fails, say so and show the relevant error.
|
|
124
|
+
- Never claim something works, mark a todo done, or call it tested unless you ran it. If you couldn't check something, say so and tell the user exactly what to look for. If something fails, say so and show the relevant error.{checks}
|
|
118
125
|
|
|
119
126
|
# Communication
|
|
120
127
|
- Be concise and direct; no filler. Use GitHub-flavored markdown.
|
|
@@ -268,6 +275,7 @@ class Settings:
|
|
|
268
275
|
secret_check: bool = True # scan what git commit and git push would add (lcode.secretscan)
|
|
269
276
|
compact_at: int = 0 # tokens: make room at this size even when the window is larger (0: only at 85%)
|
|
270
277
|
repeat_limit: int = 3 # identical calls with identical results before the model is told (lcode.repeats)
|
|
278
|
+
checks: str = "off" # the project's checks after a change (lcode.checks): auto, off or a command
|
|
271
279
|
|
|
272
280
|
|
|
273
281
|
class Agent:
|
|
@@ -324,6 +332,8 @@ class Agent:
|
|
|
324
332
|
self.made_room_for = "" # why the model was unloaded (the vision model, an MCP tool), for the reload line
|
|
325
333
|
self.load_state = "" # "loading" while the model loads in the background at startup (lcode.repl)
|
|
326
334
|
self.autosave = False # save the session after every step (sessions people resume; not the bench's)
|
|
335
|
+
self._check: tuple[Path, checks.Check | None] | None = None
|
|
336
|
+
self.check_results: list[dict] = [] # the checks of the last request: command, exit code, who ran them
|
|
327
337
|
self.sent_chars = 0 # characters of the conversation the model had already read
|
|
328
338
|
self.counted_chars: int | None = None # characters that ctx_used covers (None: all of them)
|
|
329
339
|
self.turn_status = "" # how the last request ended: success, max_steps, loop
|
|
@@ -363,6 +373,7 @@ class Agent:
|
|
|
363
373
|
code=self.code_prompt(),
|
|
364
374
|
map=self.map_prompt(),
|
|
365
375
|
look=" you look at with view_image" if self.vision_model() else "",
|
|
376
|
+
checks=self.checks_prompt(),
|
|
366
377
|
)
|
|
367
378
|
|
|
368
379
|
def repo_map(self) -> repomap.RepoMap | None:
|
|
@@ -812,6 +823,64 @@ class Agent:
|
|
|
812
823
|
self.last_speed = final.get("eval_count", 0) / seconds if seconds else 0.0
|
|
813
824
|
return message
|
|
814
825
|
|
|
826
|
+
def checks_prompt(self) -> str:
|
|
827
|
+
check = self.project_check()
|
|
828
|
+
if check is None:
|
|
829
|
+
return ""
|
|
830
|
+
return (
|
|
831
|
+
f"\n- When you've changed files, lcode runs the project's checks (`{check.command}`) before your answer "
|
|
832
|
+
"is final, and gives you the failures to fix. Run them yourself while you work, too."
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
def project_check(self) -> checks.Check | None:
|
|
836
|
+
"""The project's check command, for the main agent outside plan mode and reviews (None otherwise)."""
|
|
837
|
+
if self.allowed_tools is not None or self.planning() or self.no_changes or self.settings.checks == "off":
|
|
838
|
+
return None
|
|
839
|
+
root = project_root_or_cwd(self.cwd)
|
|
840
|
+
if self._check is None or self._check[0] != root or self._check[1] is None: # tests may appear meanwhile
|
|
841
|
+
self._check = (root, checks.find(root, self.settings.checks, self.settings.trust_project))
|
|
842
|
+
return self._check[1]
|
|
843
|
+
|
|
844
|
+
def model_ran_checks(self, command: str, result: str) -> bool:
|
|
845
|
+
"""Whether a command the model ran was the project's checks, and they passed."""
|
|
846
|
+
check = self.project_check()
|
|
847
|
+
if check is None or not checks.covers(check, command) or checks.exit_code(result) != 0:
|
|
848
|
+
return False
|
|
849
|
+
self.check_results.append({"command": check.command, "exit_code": 0, "by": "model"})
|
|
850
|
+
return True
|
|
851
|
+
|
|
852
|
+
def run_check(self, check: checks.Check, round_: int) -> str | None:
|
|
853
|
+
"""Run the checks after a change. Returns what to tell the model when they fail, else None."""
|
|
854
|
+
self.say(f"Checking the work: {check.command} (from {check.source})")
|
|
855
|
+
result = self.tools.run("bash", {"command": check.command, "timeout": checks.TIMEOUT})
|
|
856
|
+
code = checks.exit_code(result)
|
|
857
|
+
self.check_results.append({"command": check.command, "exit_code": code, "by": "lcode"})
|
|
858
|
+
if code is None:
|
|
859
|
+
self.say(f"The checks didn't run: {result.splitlines()[0][:200]}", warning=True)
|
|
860
|
+
return None
|
|
861
|
+
if code == 0:
|
|
862
|
+
self.say(f"Checks pass: {check.command}")
|
|
863
|
+
return None
|
|
864
|
+
if checks.no_tests(check, result):
|
|
865
|
+
self.say(f"The checks found no tests to run ({check.command}).")
|
|
866
|
+
return None
|
|
867
|
+
if checks.missing(check, result):
|
|
868
|
+
self.say(
|
|
869
|
+
f"The checks couldn't run ({check.command}): {result.splitlines()[0][:160]}. Install what they need, "
|
|
870
|
+
'or set the command: lcode config set checks "…" (or checks off).',
|
|
871
|
+
warning=True,
|
|
872
|
+
)
|
|
873
|
+
return None
|
|
874
|
+
if round_ >= checks.MAX_ROUNDS:
|
|
875
|
+
self.say(
|
|
876
|
+
f"Checks still fail ({check.command}, exit code {code}): the model will say what's left.", warning=True
|
|
877
|
+
)
|
|
878
|
+
else:
|
|
879
|
+
self.say(
|
|
880
|
+
f"Checks fail ({check.command}, exit code {code}): the model gets the output to fix it.", warning=True
|
|
881
|
+
)
|
|
882
|
+
return checks.failure_note(check, result, round_)
|
|
883
|
+
|
|
815
884
|
def before_request(self, progress: Progress) -> residency.LoadWatch | None:
|
|
816
885
|
"""Set the status line for what comes before the first token: loading the model if it isn't in memory
|
|
817
886
|
(watched until it is), then reading the prompt. Returns the watch while the model loads."""
|
|
@@ -948,6 +1017,9 @@ class Agent:
|
|
|
948
1017
|
note = planning.NOTE if self.planning() and self.allowed_tools is None else ""
|
|
949
1018
|
self.messages.append({"role": "user", "content": self.expand_mentions(user_text) + note})
|
|
950
1019
|
self.repeats.reset()
|
|
1020
|
+
self.check_results = []
|
|
1021
|
+
changed = verified = False # files changed in this request; the checks passed since
|
|
1022
|
+
check_rounds = 0
|
|
951
1023
|
malformed = 0
|
|
952
1024
|
looping = "" # the call the model kept repeating, when lcode stopped the request for it
|
|
953
1025
|
for step in range(self.max_steps):
|
|
@@ -983,6 +1055,8 @@ class Agent:
|
|
|
983
1055
|
reason = str(e).rsplit("err=", 1)[-1].strip()
|
|
984
1056
|
elif "XML syntax error" in str(e):
|
|
985
1057
|
reason = str(e).split("XML syntax error", 1)[1].strip(" :") or "invalid XML"
|
|
1058
|
+
elif "expected element type" in str(e) or "closed by </" in str(e):
|
|
1059
|
+
reason = str(e).split("Ollama error:", 1)[-1].strip() or "invalid XML"
|
|
986
1060
|
else:
|
|
987
1061
|
reason = "invalid JSON"
|
|
988
1062
|
self.console.print("[yellow]The model wrote a malformed tool call; asking it to try again.[/]")
|
|
@@ -996,6 +1070,15 @@ class Agent:
|
|
|
996
1070
|
)
|
|
997
1071
|
continue
|
|
998
1072
|
if not calls:
|
|
1073
|
+
check = self.project_check() if changed and not verified else None
|
|
1074
|
+
if check is not None and check_rounds < checks.MAX_ROUNDS:
|
|
1075
|
+
check_rounds += 1
|
|
1076
|
+
feedback = self.run_check(check, check_rounds)
|
|
1077
|
+
if feedback:
|
|
1078
|
+
self.messages.append({"role": "user", "content": feedback})
|
|
1079
|
+
changed = False # run them again only if the model changes something more
|
|
1080
|
+
continue
|
|
1081
|
+
verified = True
|
|
999
1082
|
break
|
|
1000
1083
|
done: dict[int, str] = {} # results of subagents that ran together
|
|
1001
1084
|
together = [
|
|
@@ -1051,6 +1134,10 @@ class Agent:
|
|
|
1051
1134
|
)
|
|
1052
1135
|
if verdict.stop:
|
|
1053
1136
|
looping = self.describe_call(name, args)
|
|
1137
|
+
if name in ("edit_file", "write_file") and not result.startswith(REFUSED):
|
|
1138
|
+
changed, verified = True, False
|
|
1139
|
+
elif name == "bash" and changed and self.model_ran_checks(str(args.get("command", "")), result):
|
|
1140
|
+
verified = True # the model ran the checks itself, after its last change
|
|
1054
1141
|
tool_msg = {"role": "tool", "tool_name": name, "content": result}
|
|
1055
1142
|
if call.get("id"):
|
|
1056
1143
|
tool_msg["tool_call_id"] = call["id"]
|
|
@@ -76,6 +76,7 @@ class Result:
|
|
|
76
76
|
tool_calls: list[dict] = field(default_factory=list) # {"id", "name", "arguments", "error", "output"}
|
|
77
77
|
files_changed: list[dict] = field(default_factory=list) # {"status": "A" | "M" | "D", "path"}
|
|
78
78
|
usage: dict = field(default_factory=dict) # requests, prompt_tokens, output_tokens
|
|
79
|
+
checks: list[dict] = field(default_factory=list) # {"command", "exit_code", "by": "lcode" | "model"}
|
|
79
80
|
seconds: float = 0.0
|
|
80
81
|
error: str = ""
|
|
81
82
|
|
|
@@ -196,6 +197,7 @@ def open_agent(cwd: Path, options: Options, console: Console, hw: Hardware | Non
|
|
|
196
197
|
notify_after=cfg["notify_after"],
|
|
197
198
|
secret_check=cfg["secret_check"],
|
|
198
199
|
repeat_limit=cfg["repeat_limit"],
|
|
200
|
+
checks=cfg["checks"],
|
|
199
201
|
compact_at=cfg["compact_at"],
|
|
200
202
|
)
|
|
201
203
|
agent = Agent(ollama, settings, cwd, console=console)
|
|
@@ -303,6 +305,7 @@ def run_request(agent: Agent, prompt: str) -> Result:
|
|
|
303
305
|
tool_calls=list(calls.values()),
|
|
304
306
|
files_changed=files,
|
|
305
307
|
usage=usage,
|
|
308
|
+
checks=list(agent.check_results),
|
|
306
309
|
seconds=round(time.monotonic() - started, 2),
|
|
307
310
|
error=error,
|
|
308
311
|
)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Run the project's checks before calling work done.
|
|
2
|
+
|
|
3
|
+
Small models call work done after a syntax check, and telling them to verify isn't enough. So after a request
|
|
4
|
+
that changed files, lcode runs the project's check command itself (its tests, or a linter) before the answer
|
|
5
|
+
counts as final. When it fails, the output goes back to the model, which keeps working, at most `MAX_ROUNDS`
|
|
6
|
+
times; then the answer says plainly that the checks still fail.
|
|
7
|
+
|
|
8
|
+
The command is, in order: the repository's `.lcode/settings.toml` (`[checks] command = "…"`, used once the
|
|
9
|
+
repository is trusted, like its hooks), the user's `checks` setting, or what the project looks like: pytest,
|
|
10
|
+
unittest, npm test, cargo test, go test, make test. `checks = "off"` turns it off.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import re
|
|
17
|
+
import shutil
|
|
18
|
+
from dataclasses import dataclass
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
MAX_ROUNDS = 2 # times the model gets failing checks back before lcode stops asking
|
|
22
|
+
TIMEOUT = 600 # seconds a check may run
|
|
23
|
+
OUTPUT_TAIL = 6000 # characters of a failing check's output the model gets back
|
|
24
|
+
EXIT = re.compile(r"\[exit code: (-?\d+)\]\s*$")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class Check:
|
|
29
|
+
command: str
|
|
30
|
+
source: str # where it comes from, for the user: "pyproject.toml", "your configuration"…
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def find(root: Path, setting: str, trusted: bool) -> Check | None:
|
|
34
|
+
"""The project's check command, or None when there's none or checks are off."""
|
|
35
|
+
setting = (setting or "auto").strip()
|
|
36
|
+
if setting == "off":
|
|
37
|
+
return None
|
|
38
|
+
if trusted:
|
|
39
|
+
from lcode.hooks import HookError, read_toml
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
data = read_toml(root / ".lcode" / "settings.toml")
|
|
43
|
+
except HookError:
|
|
44
|
+
data = {}
|
|
45
|
+
command = str((data.get("checks") or {}).get("command") or "").strip()
|
|
46
|
+
if command:
|
|
47
|
+
return Check(command, ".lcode/settings.toml")
|
|
48
|
+
if setting != "auto":
|
|
49
|
+
return Check(setting, "your configuration (checks)")
|
|
50
|
+
return detect(root)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def python() -> str:
|
|
54
|
+
return "python" if shutil.which("python") else "python3"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def text(path: Path) -> str:
|
|
58
|
+
try:
|
|
59
|
+
return path.read_text(errors="replace") if path.is_file() else ""
|
|
60
|
+
except OSError:
|
|
61
|
+
return ""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def python_tests(root: Path) -> bool:
|
|
65
|
+
"""Whether the project has Python test files (a few levels deep, outside environments and caches)."""
|
|
66
|
+
from lcode.tools import IGNORE_DIRS
|
|
67
|
+
|
|
68
|
+
def walk(folder: Path, depth: int) -> bool:
|
|
69
|
+
try:
|
|
70
|
+
entries = list(folder.iterdir())
|
|
71
|
+
except OSError:
|
|
72
|
+
return False
|
|
73
|
+
if any(e.is_file() and re.fullmatch(r"test_.*\.py|.*_test\.py", e.name) for e in entries):
|
|
74
|
+
return True
|
|
75
|
+
return depth > 0 and any(
|
|
76
|
+
walk(e, depth - 1)
|
|
77
|
+
for e in entries
|
|
78
|
+
if e.is_dir() and e.name not in IGNORE_DIRS and not e.name.startswith(".")
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
return walk(root, 3)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def detect(root: Path) -> Check | None:
|
|
85
|
+
"""A check command from what the project looks like. Python checks need test files: pytest configured in a
|
|
86
|
+
project without tests "fails" with nothing to run."""
|
|
87
|
+
if python_tests(root):
|
|
88
|
+
configs = text(root / "pyproject.toml") + text(root / "setup.cfg") + text(root / "tox.ini")
|
|
89
|
+
if "pytest" in configs or any((root / name).is_file() for name in ("pytest.ini", "conftest.py")):
|
|
90
|
+
runner = "uv run pytest -q" if (root / "uv.lock").is_file() else f"{python()} -m pytest -q"
|
|
91
|
+
return Check(runner, "pytest in the project's configuration")
|
|
92
|
+
if list(root.glob("test_*.py")) or list(root.glob("*_test.py")):
|
|
93
|
+
return Check(f"{python()} -m unittest", "test files")
|
|
94
|
+
if (root / "tests").is_dir():
|
|
95
|
+
return Check(f"{python()} -m unittest discover -s tests -t .", "tests/")
|
|
96
|
+
package = text(root / "package.json")
|
|
97
|
+
if package:
|
|
98
|
+
try:
|
|
99
|
+
test = (json.loads(package).get("scripts") or {}).get("test") or ""
|
|
100
|
+
except ValueError:
|
|
101
|
+
test = ""
|
|
102
|
+
if test and "no test specified" not in test:
|
|
103
|
+
for lock, tool in (("pnpm-lock.yaml", "pnpm"), ("yarn.lock", "yarn"), ("bun.lockb", "bun")):
|
|
104
|
+
if (root / lock).is_file():
|
|
105
|
+
return Check(f"{tool} test", "package.json")
|
|
106
|
+
return Check("npm test", "package.json")
|
|
107
|
+
if (root / "Cargo.toml").is_file():
|
|
108
|
+
return Check("cargo test", "Cargo.toml")
|
|
109
|
+
if (root / "go.mod").is_file():
|
|
110
|
+
return Check("go test ./...", "go.mod")
|
|
111
|
+
if re.search(r"^test:", text(root / "Makefile"), re.M):
|
|
112
|
+
return Check("make test", "Makefile")
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
NO_TESTS = 5 # pytest's and unittest's exit code when no test ran: not a failure
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def no_tests(check: Check, result: str) -> bool:
|
|
120
|
+
return exit_code(result) == NO_TESTS and bool(re.search(r"pytest|unittest", check.command))
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def exit_code(result: str) -> int | None:
|
|
124
|
+
"""The exit code at the end of a bash tool result (None: it didn't run, e.g. not allowed)."""
|
|
125
|
+
found = EXIT.search(result)
|
|
126
|
+
return int(found.group(1)) if found else None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def covers(check: Check, command: str) -> bool:
|
|
130
|
+
"""Whether a command the model ran is this check (`cd x && uv run pytest -q 2>&1 | tail` counts)."""
|
|
131
|
+
return " ".join(check.command.split()) in " ".join(command.split())
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def failure_note(check: Check, result: str, round_: int) -> str:
|
|
135
|
+
output = result if len(result) <= OUTPUT_TAIL else "…" + result[-OUTPUT_TAIL:]
|
|
136
|
+
last = round_ >= MAX_ROUNDS
|
|
137
|
+
return (
|
|
138
|
+
f"[lcode] Your changes are done, but the project's checks fail: `{check.command}` "
|
|
139
|
+
f"(exit code {exit_code(result)}).\n\n{output}\n\n"
|
|
140
|
+
+ (
|
|
141
|
+
"This was the last check lcode runs for this request. Don't change anything more: tell the user what "
|
|
142
|
+
"still fails and why."
|
|
143
|
+
if last
|
|
144
|
+
else "Fix what your changes broke, then finish. If a failure isn't caused by your changes (it failed "
|
|
145
|
+
"before), don't chase it: say so in your answer."
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# The check command itself is missing (not the code under test): nothing the model can fix.
|
|
151
|
+
MISSING = re.compile(r"No module named '?pytest'?\s*$|command not found|^\S+: not found$", re.I | re.M)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def missing(check: Check, result: str) -> bool:
|
|
155
|
+
"""Whether the check couldn't run at all: its program isn't installed."""
|
|
156
|
+
code = exit_code(result)
|
|
157
|
+
if code == 127:
|
|
158
|
+
return True
|
|
159
|
+
head = result.splitlines()[:5] # a missing program says so first; test failures look different
|
|
160
|
+
return code not in (0, None) and any(MISSING.search(line) for line in head)
|
|
@@ -580,6 +580,7 @@ def cmd_bench(args) -> None:
|
|
|
580
580
|
checkpoints=False,
|
|
581
581
|
prune=not args.no_prune,
|
|
582
582
|
compact_at=cfg["compact_at"] if args.compact_at is None else args.compact_at,
|
|
583
|
+
checks=cfg["checks"] if args.checks is None else args.checks,
|
|
583
584
|
repo_map=args.repo_map,
|
|
584
585
|
embed_model=cfg["embed_model"] if args.code_search else "off",
|
|
585
586
|
)
|
|
@@ -774,6 +775,11 @@ def build_subparsers() -> dict[str, argparse.ArgumentParser]:
|
|
|
774
775
|
p.add_argument("--repo-map", action="store_true", help="give the model the repository map (off by default)")
|
|
775
776
|
p.add_argument("--code-search", action="store_true", help="index each task's folder and offer semantic code search")
|
|
776
777
|
p.add_argument("--no-prune", action="store_true", help="don't remove old tool output (to compare, with --session)")
|
|
778
|
+
p.add_argument(
|
|
779
|
+
"--checks",
|
|
780
|
+
metavar="auto|off|COMMAND",
|
|
781
|
+
help="run each task's checks before it counts as done (default: the checks setting)",
|
|
782
|
+
)
|
|
777
783
|
p.add_argument(
|
|
778
784
|
"--compact-at",
|
|
779
785
|
type=compact_tokens,
|
|
@@ -59,6 +59,7 @@ SETTINGS: dict[str, tuple[object, type, str]] = {
|
|
|
59
59
|
"subagents": (True, bool, "let the model hand tasks to subagents that have their own context"),
|
|
60
60
|
"notify": (True, bool, "desktop notification when a long request is done or waits for you"),
|
|
61
61
|
"notify_after": (30, int, "seconds a request runs before it notifies (with notify on)"),
|
|
62
|
+
"checks": ("auto", str, "run the project's checks before calling work done: auto | off | a command"),
|
|
62
63
|
"repeat_limit": (3, int, "identical tool calls with identical results before lcode steps in (0: never)"),
|
|
63
64
|
"secret_check": (True, bool, "before git commit and git push, look for passwords, keys and tokens they'd add"),
|
|
64
65
|
"compact_at": (65536, int, "summarize at this many tokens even when the window is larger (0: only at 85%)"),
|