lcode-cli 0.30.0__tar.gz → 0.31.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.30.0 → lcode_cli-0.31.0}/CHANGELOG.md +13 -1
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/PKG-INFO +1 -1
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/how-it-works.md +10 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/quickstart.md +1 -1
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/usage.md +2 -1
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/__init__.py +1 -1
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/agent.py +14 -2
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/context.py +48 -1
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/repl.py +28 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/tools.py +34 -4
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_context.py +54 -1
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_tools.py +8 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.editorconfig +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.github/CODEOWNERS +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.github/ISSUE_TEMPLATE/model_request.yml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.github/dependabot.yml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.github/workflows/ci.yml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.github/workflows/docs.yml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.github/workflows/release.yml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.gitignore +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/.pre-commit-config.yaml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/AGENTS.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/CODE_OF_CONDUCT.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/CONTRIBUTING.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/LICENSE +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/README.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/SECURITY.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/action.yml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/agents.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/assets/demo.svg +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/assets/extra.css +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/assets/logo.svg +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/assets/social-preview.png +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/automation.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/bench.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/changelog.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/commands.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/configuration.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/contributing.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/editors.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/git.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/hooks.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/index.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/installation.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/lsp.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/mcp.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/memory.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/models.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/sandbox.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/search.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/servers.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/docs/troubleshooting.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/examples/README.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/examples/api/fix_tests.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/examples/commands/review.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/examples/skills/commit-message/SKILL.md +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/examples/skills/commit-message/scripts/staged.sh +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/install.sh +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/mkdocs.yml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/overrides/main.html +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/pyproject.toml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/scripts/record_demo.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/scripts/social-preview.html +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/__main__.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/acp.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/action.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/api.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/backends.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/bench.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/bench_backend.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/bench_runtime.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/catalog.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/checkpoints.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/checks.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/cli.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/clipboard.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/codesearch.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/config.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/extensions.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/frontmatter.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/gitflow.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/hardware.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/hooks.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/jobs.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/limits.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/lsp.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/matching.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/__init__.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/auth.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/catalog.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/catalog.toml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/client.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/commands.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/config.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/http.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/manager.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/protocol.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/server.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/mcp/stdio.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/memory.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/models.toml +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/notify.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/ollama.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/permissions.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/planning.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/render.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/repeats.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/repomap.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/residency.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/sandbox.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/secretscan.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/servers/__init__.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/servers/encord.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/servers/valohai.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/servers/xai.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/sessions.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/subagents.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/update.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/vision.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/src/lcode/web.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/conftest.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/fake_lsp_server.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/fake_mcp_server.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_acp.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_action.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_agent.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_api.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_backends.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_bench.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_catalog.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_checkpoints.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_checks.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_cli.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_clipboard.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_codesearch.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_config.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_context_picker.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_extensions.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_gitflow.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_hooks.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_jobs.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_lsp.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_matching.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_mcp.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_memory.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_permissions.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_plan_mode.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_repeats.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_repomap.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_residency.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_robustness.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_sandbox.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_secretscan.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_servers.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_sessions.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_subagents.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_update.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_vision.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_web.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/tests/test_xai.py +0 -0
- {lcode_cli-0.30.0 → lcode_cli-0.31.0}/uv.lock +0 -0
|
@@ -6,6 +6,17 @@ All notable changes to lcode are documented here. The format follows
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.31.0] - 2026-10-14
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- A task sheet that survives long sessions. The plan (the todo list), the model's notes (decisions,
|
|
14
|
+
findings, open questions, kept in `todo_write`'s new `notes`, or inside a todo item, where small
|
|
15
|
+
models tend to put them), the files changed and the last check are pinned next to the summary and
|
|
16
|
+
next to your request when old output is removed. It's saved with the session, so `lcode -c`
|
|
17
|
+
brings it back (the todo list used to be lost), and `/status` shows it. See
|
|
18
|
+
[Context management](https://nasser1941.github.io/lcode/how-it-works/#context-management).
|
|
19
|
+
|
|
9
20
|
## [0.30.0] - 2026-10-13
|
|
10
21
|
|
|
11
22
|
### Added
|
|
@@ -668,7 +679,8 @@ First public release.
|
|
|
668
679
|
- `AGENTS.md` project instructions and `/init` to generate them.
|
|
669
680
|
- One-line installer for Linux and macOS.
|
|
670
681
|
|
|
671
|
-
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.
|
|
682
|
+
[Unreleased]: https://github.com/nasser1941/lcode/compare/v0.31.0...HEAD
|
|
683
|
+
[0.31.0]: https://github.com/nasser1941/lcode/compare/v0.30.0...v0.31.0
|
|
672
684
|
[0.30.0]: https://github.com/nasser1941/lcode/compare/v0.29.0...v0.30.0
|
|
673
685
|
[0.29.0]: https://github.com/nasser1941/lcode/compare/v0.28.1...v0.29.0
|
|
674
686
|
[0.28.1]: https://github.com/nasser1941/lcode/compare/v0.28.0...v0.28.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lcode-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.31.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/
|
|
@@ -97,6 +97,16 @@ context is old tool output, so:
|
|
|
97
97
|
findings, files changed, next steps) and continues from it. Your latest requests (up to eight)
|
|
98
98
|
are kept word for word under the summary, so the goal isn't lost in the retelling.
|
|
99
99
|
|
|
100
|
+
**The task sheet.** Next to the summary, and next to your request when old tool output is removed,
|
|
101
|
+
lcode pins a short sheet, so the state of the work survives:
|
|
102
|
+
|
|
103
|
+
- the plan (the model's todo list);
|
|
104
|
+
- the notes the model keeps with it (decisions and why, findings, open questions);
|
|
105
|
+
- the files changed in the session;
|
|
106
|
+
- the last check.
|
|
107
|
+
|
|
108
|
+
It's saved with the session, so `lcode -c` brings it back, and `/status` shows it to you.
|
|
109
|
+
|
|
100
110
|
`lcode config set compact_at 128k` moves the ceiling, and `0` turns it off, leaving only the 85%
|
|
101
111
|
rule. `/context` shows where the current session makes room.
|
|
102
112
|
|
|
@@ -80,6 +80,7 @@ reasoning is on.
|
|
|
80
80
|
| `/mode [ask\|plan\|auto-edit\|yolo]` | Set the permission mode |
|
|
81
81
|
| `/cd DIR` | Change the working directory |
|
|
82
82
|
| `/todos` | Show the model's task list |
|
|
83
|
+
| `/status` | The task sheet: your latest requests, the plan and the model's notes, files changed, the last check |
|
|
83
84
|
| `/jobs [stop ID]` | The [background commands](#background-commands) the model started; stop one |
|
|
84
85
|
| `/commit [notes]` | [Commit](git.md#commit) the changes with a message in the repository's style, once you approve it |
|
|
85
86
|
| `/review [base]` | [Review](git.md#review) the uncommitted changes, or the branch against a base |
|
|
@@ -99,7 +100,7 @@ reasoning is on.
|
|
|
99
100
|
| `grep` | Regex search with ripgrep (falls back to Python if ripgrep is missing) |
|
|
100
101
|
| `bash` | Run a shell command with live output, a timeout and a persistent working directory; with `background`, keep it running ([details](#background-commands)) |
|
|
101
102
|
| `bash_output`, `bash_stop` | Read what a background command printed since last time; stop it |
|
|
102
|
-
| `todo_write` | Keep a visible task list for multi-step work |
|
|
103
|
+
| `todo_write` | Keep a visible task list for multi-step work, with notes (decisions, findings) that survive when old messages are removed; `/status` shows them |
|
|
103
104
|
| `web_search` | Search the web for current information (needs a [search provider](#web-search)) |
|
|
104
105
|
| `web_fetch` | Read a web page or text file by URL as clean text |
|
|
105
106
|
| `view_image` | Look at an image: a model that can see describes it ([Images](#images)) |
|
|
@@ -112,7 +112,7 @@ SYSTEM_PROMPT = """You are lcode, an autonomous software-engineering agent runni
|
|
|
112
112
|
- To understand a repository: look at the layout, README/docs, entry points and config, then grep and read the relevant files. Cite code as `path:line`.
|
|
113
113
|
- Read a file before editing it. Use edit_file for targeted changes and write_file for new files or full rewrites. old_string must be copied exactly from the file (without the line-number prefixes).
|
|
114
114
|
- Match the existing code style, naming and structure. Don't add unrequested features.
|
|
115
|
-
- For multi-step tasks, plan with todo_write and keep it updated.
|
|
115
|
+
- For multi-step tasks, plan with todo_write and keep it updated; put decisions and findings you must not lose in its notes.
|
|
116
116
|
- Work autonomously until the task is done. Only stop to ask the user when you are genuinely blocked or the decision is theirs.
|
|
117
117
|
- Don't run destructive or irreversible commands (rm -rf, git reset --hard, git push --force, dropping data) unless the user explicitly asked.
|
|
118
118
|
- If the user denies a tool call, don't retry the same thing; adjust or ask.
|
|
@@ -613,6 +613,9 @@ class Agent:
|
|
|
613
613
|
"title": self.session_title,
|
|
614
614
|
"messages": self.messages,
|
|
615
615
|
"checkpoints": self.checkpoints.to_json(),
|
|
616
|
+
"todos": self.tools.todos,
|
|
617
|
+
"notes": self.tools.notes,
|
|
618
|
+
"changed": list(self.tools.changed),
|
|
616
619
|
}
|
|
617
620
|
# Written next to it and then renamed: a crash halfway leaves the previous version, not a broken file.
|
|
618
621
|
partial = f.with_name(f".{f.name}.partial")
|
|
@@ -626,6 +629,8 @@ class Agent:
|
|
|
626
629
|
self.session_name = ""
|
|
627
630
|
self.session_title = ""
|
|
628
631
|
self.checkpoints.reset(self.session_id)
|
|
632
|
+
self.tools.todos, self.tools.notes, self.tools.changed = [], [], {}
|
|
633
|
+
self.check_results = []
|
|
629
634
|
|
|
630
635
|
def rename(self, name: str) -> None:
|
|
631
636
|
self.session_name = " ".join(name.split())
|
|
@@ -652,6 +657,9 @@ class Agent:
|
|
|
652
657
|
self.session_name = data.get("name", "")
|
|
653
658
|
self.session_title = data.get("title") or sessions.title_from(self.messages)
|
|
654
659
|
self.tools.read_mtimes.clear() # files may have changed since; the model must read them again
|
|
660
|
+
self.tools.todos = list(data.get("todos") or [])
|
|
661
|
+
self.tools.notes = [str(n) for n in data.get("notes") or []]
|
|
662
|
+
self.tools.changed = dict.fromkeys(str(f) for f in data.get("changed") or [])
|
|
655
663
|
self.reflected = len(self.messages) # checked for notes to remember when that session ended
|
|
656
664
|
self.ctx_used = sum(len(json.dumps(m)) for m in self.messages) // 3
|
|
657
665
|
self.counted_chars = None
|
|
@@ -1289,6 +1297,8 @@ class Agent:
|
|
|
1289
1297
|
self.messages, keep=1, removed=removed
|
|
1290
1298
|
) # not enough: before the last request
|
|
1291
1299
|
context_tools.note_removed(self.messages, removed)
|
|
1300
|
+
if saved:
|
|
1301
|
+
context_tools.pin(self.messages, context_tools.task_sheet(self))
|
|
1292
1302
|
if saved:
|
|
1293
1303
|
self.sent_chars = 0 # the conversation changed: the model reads it again
|
|
1294
1304
|
self.pruned += 1
|
|
@@ -1333,7 +1343,9 @@ class Agent:
|
|
|
1333
1343
|
{"role": "system", "content": self.system_prompt()},
|
|
1334
1344
|
{
|
|
1335
1345
|
"role": "user",
|
|
1336
|
-
"content": f"{sessions.SUMMARY_PREFIX}\n\n{summary}"
|
|
1346
|
+
"content": f"{sessions.SUMMARY_PREFIX}\n\n{summary}"
|
|
1347
|
+
+ context_tools.requests_block(requests)
|
|
1348
|
+
+ (f"\n\n{sheet}" if (sheet := context_tools.task_sheet(self)) else ""),
|
|
1337
1349
|
},
|
|
1338
1350
|
{"role": "assistant", "content": "Got it — I have the context from the summary and will continue."},
|
|
1339
1351
|
]
|
|
@@ -49,7 +49,9 @@ REQUESTS_HEADER = "[The user's requests in this conversation, word for word, old
|
|
|
49
49
|
MAX_REQUESTS = 8 # the most recent ones are kept through a summary
|
|
50
50
|
MAX_REQUEST_CHARS = 1500
|
|
51
51
|
# What lcode appends to the user's words: attached files, images and folders, and its own notes.
|
|
52
|
-
ATTACHED = re.compile(
|
|
52
|
+
ATTACHED = re.compile(
|
|
53
|
+
r"\n\n(?:<file path=|<image path=|<directory path=|\[lcode[:\]] |\[The user wants the )"
|
|
54
|
+
) # also the sheet
|
|
53
55
|
|
|
54
56
|
|
|
55
57
|
def request_text(content: str) -> str:
|
|
@@ -187,6 +189,51 @@ def prune(messages: list[dict], keep: int = KEEP_RECENT_REQUESTS, removed: list[
|
|
|
187
189
|
return saved
|
|
188
190
|
|
|
189
191
|
|
|
192
|
+
SHEET_HEADER = "[lcode] Task sheet: the plan, notes and changes so far, kept when old messages are removed"
|
|
193
|
+
MAX_SHEET = 3000 # characters
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def task_sheet(agent: Agent) -> str:
|
|
197
|
+
"""The state of the work, from lcode's own records: the todo list and the model's notes (todo_write), the
|
|
198
|
+
files changed in the session and the last check. Pinned where old messages go: in summaries and next to the
|
|
199
|
+
request when tool output is removed."""
|
|
200
|
+
tools = agent.tools
|
|
201
|
+
parts = []
|
|
202
|
+
if tools.todos:
|
|
203
|
+
marks = {"completed": "[x]", "in_progress": "[>]", "pending": "[ ]"}
|
|
204
|
+
plan = "\n".join(f"{marks.get(t.get('status', ''), '[ ]')} {t.get('content', '')}" for t in tools.todos)
|
|
205
|
+
parts.append(f"Plan:\n{plan}")
|
|
206
|
+
if tools.notes:
|
|
207
|
+
parts.append("Notes:\n" + "\n".join(f"- {note}" for note in tools.notes))
|
|
208
|
+
if tools.changed:
|
|
209
|
+
files = list(tools.changed)
|
|
210
|
+
more = f" and {len(files) - 30} more" if len(files) > 30 else ""
|
|
211
|
+
parts.append("Files changed in this session: " + ", ".join(files[-30:]) + more)
|
|
212
|
+
if agent.check_results:
|
|
213
|
+
last = agent.check_results[-1]
|
|
214
|
+
code = last.get("exit_code")
|
|
215
|
+
state = "passed" if code == 0 else "didn't run" if code is None else f"failed (exit code {code})"
|
|
216
|
+
parts.append(f"Last check: `{last.get('command')}` {state}")
|
|
217
|
+
if not parts:
|
|
218
|
+
return ""
|
|
219
|
+
sheet = "\n\n".join(parts)
|
|
220
|
+
if len(sheet) > MAX_SHEET:
|
|
221
|
+
sheet = sheet[: MAX_SHEET - 20] + "\n[…cut]"
|
|
222
|
+
return f"{SHEET_HEADER}\n{sheet}"
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def pin(messages: list[dict], text: str) -> None:
|
|
226
|
+
"""Put a note next to the request the model is working on, replacing an earlier sheet there."""
|
|
227
|
+
if not text:
|
|
228
|
+
return
|
|
229
|
+
for message in reversed(messages):
|
|
230
|
+
content = str(message.get("content", ""))
|
|
231
|
+
if message.get("role") == "user" and not content.startswith(("[lcode]", SUMMARY_PREFIX)):
|
|
232
|
+
content = content.split("\n\n" + SHEET_HEADER, 1)[0]
|
|
233
|
+
message["content"] = f"{content}\n\n{text}"
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
|
|
190
237
|
def note_removed(messages: list[dict], removed: list[str]) -> None:
|
|
191
238
|
"""Tell the model, next to the request it's working on, which files it no longer has: a stub deep in the
|
|
192
239
|
conversation is easy to overlook, and models then answer from memory instead of reading again."""
|
|
@@ -63,6 +63,7 @@ COMMANDS = {
|
|
|
63
63
|
"/mode": "Permission mode: ask | plan | auto-edit | yolo (Shift+Tab cycles)",
|
|
64
64
|
"/cd": "Change the working directory",
|
|
65
65
|
"/todos": "Show the current todo list",
|
|
66
|
+
"/status": "The task sheet: your requests, the plan and notes, files changed, the last check",
|
|
66
67
|
"/jobs": "Background commands the model started: /jobs, /jobs stop <id>",
|
|
67
68
|
"/commit": "Commit the changes with a message in the repository's style, after you approve it",
|
|
68
69
|
"/review": "Review the uncommitted changes, or the branch against a base: /review [base]",
|
|
@@ -109,6 +110,31 @@ def mention(path: Path) -> str:
|
|
|
109
110
|
return f'@"{shown}"' if " " in shown else f"@{shown}"
|
|
110
111
|
|
|
111
112
|
|
|
113
|
+
def status_command(agent: Agent) -> None:
|
|
114
|
+
"""What the session is doing, as lcode keeps it for the model (the task sheet)."""
|
|
115
|
+
c = agent.console
|
|
116
|
+
requests = context_tools.user_requests(agent.messages)
|
|
117
|
+
if not requests and not agent.tools.todos and not agent.tools.changed:
|
|
118
|
+
c.print("Nothing yet: no requests in this session.")
|
|
119
|
+
return
|
|
120
|
+
if requests:
|
|
121
|
+
latest = requests[-3:]
|
|
122
|
+
lines = [f"[dim]{len(requests) - len(latest) + i + 1}.[/] {escape(r[:300])}" for i, r in enumerate(latest)]
|
|
123
|
+
c.print(Panel("\n".join(lines), title="Requests (latest)", title_align="left", border_style="cyan"))
|
|
124
|
+
if agent.tools.todos or agent.tools.notes:
|
|
125
|
+
agent.tools.show_todos()
|
|
126
|
+
if agent.tools.changed:
|
|
127
|
+
c.print(f"[bold]Files changed:[/] {escape(', '.join(agent.tools.changed))}")
|
|
128
|
+
if agent.check_results:
|
|
129
|
+
last = agent.check_results[-1]
|
|
130
|
+
code = last.get("exit_code")
|
|
131
|
+
state = (
|
|
132
|
+
"[green]passed[/]" if code == 0 else "didn't run" if code is None else f"[red]failed[/] (exit code {code})"
|
|
133
|
+
)
|
|
134
|
+
c.print(f"[bold]Last check:[/] {escape(str(last.get('command')))} {state}")
|
|
135
|
+
c.print("[dim]The model keeps this too, even when old messages are removed or summarized.[/]")
|
|
136
|
+
|
|
137
|
+
|
|
112
138
|
def build_session(agent: Agent) -> PromptSession:
|
|
113
139
|
kb = KeyBindings()
|
|
114
140
|
|
|
@@ -978,6 +1004,8 @@ def handle_command(agent: Agent, line: str, hardware: Hardware) -> bool:
|
|
|
978
1004
|
c.print(f"Working directory: {p}")
|
|
979
1005
|
else:
|
|
980
1006
|
c.print(f"[red]Not a directory: {p}[/]")
|
|
1007
|
+
elif cmd == "/status":
|
|
1008
|
+
status_command(agent)
|
|
981
1009
|
elif cmd == "/todos":
|
|
982
1010
|
agent.tools.show_todos()
|
|
983
1011
|
elif cmd == "/jobs":
|
|
@@ -24,6 +24,7 @@ from pathlib import Path
|
|
|
24
24
|
from typing import TYPE_CHECKING
|
|
25
25
|
|
|
26
26
|
from rich.console import Group
|
|
27
|
+
from rich.markup import escape
|
|
27
28
|
from rich.panel import Panel
|
|
28
29
|
from rich.syntax import Syntax
|
|
29
30
|
from rich.text import Text
|
|
@@ -45,6 +46,7 @@ NO_NETWORK = re.compile(
|
|
|
45
46
|
)
|
|
46
47
|
MAX_TOOL_OUTPUT = 30_000 # characters returned to the model per tool call
|
|
47
48
|
PENDING_LINES = 5000 # output lines waiting to be read, at most
|
|
49
|
+
MAX_NOTES, MAX_NOTE = 12, 300 # the model's task notes: how many, how long each
|
|
48
50
|
LEFTOVER_GRACE = 0.5 # seconds to wait for output after a command ends, before stopping what it left running
|
|
49
51
|
IGNORE_DIRS = {
|
|
50
52
|
".git", "node_modules", "__pycache__", ".venv", "venv", "env", ".mypy_cache", ".pytest_cache",
|
|
@@ -161,7 +163,8 @@ SCHEMAS = [
|
|
|
161
163
|
_fn(
|
|
162
164
|
"todo_write",
|
|
163
165
|
"Create or update your task list for multi-step work. Pass the full list every time. Keep exactly one "
|
|
164
|
-
"item in_progress while working."
|
|
166
|
+
"item in_progress while working. In `notes`, keep what you must not forget: decisions and why, findings, "
|
|
167
|
+
"open questions. The list and the notes stay with you even when old messages are removed.",
|
|
165
168
|
{
|
|
166
169
|
"todos": {
|
|
167
170
|
"type": "array",
|
|
@@ -173,7 +176,12 @@ SCHEMAS = [
|
|
|
173
176
|
},
|
|
174
177
|
"required": ["content", "status"],
|
|
175
178
|
},
|
|
176
|
-
}
|
|
179
|
+
},
|
|
180
|
+
"notes": {
|
|
181
|
+
"type": "array",
|
|
182
|
+
"items": {"type": "string"},
|
|
183
|
+
"description": "Short notes, the full list each time (omit to keep the current ones)",
|
|
184
|
+
},
|
|
177
185
|
},
|
|
178
186
|
["todos"],
|
|
179
187
|
),
|
|
@@ -438,6 +446,8 @@ class Toolbox:
|
|
|
438
446
|
self.agent = agent
|
|
439
447
|
self.read_mtimes: dict[str, float] = {}
|
|
440
448
|
self.formats: dict[str, TextFormat] = {} # encoding and line endings of the files read
|
|
449
|
+
self.notes: list[str] = [] # the model's notes on the task (todo_write): decisions, findings, questions
|
|
450
|
+
self.changed: dict[str, None] = {} # files lcode wrote in this session, in order
|
|
441
451
|
self.todos: list[dict] = []
|
|
442
452
|
# An editor's buffers (lcode acp): read unsaved changes and write through the editor.
|
|
443
453
|
self.reader: Callable[[Path], str | None] | None = None
|
|
@@ -865,6 +875,7 @@ class Toolbox:
|
|
|
865
875
|
if not p.exists() or p.read_bytes() != data:
|
|
866
876
|
p.write_bytes(data) # the file on disk must match, for checkpoints and the tools
|
|
867
877
|
self.read_mtimes[str(p)] = p.stat().st_mtime
|
|
878
|
+
self.changed[self.rel(p)] = None
|
|
868
879
|
|
|
869
880
|
def t_write_file(self, path: str, content: str) -> str:
|
|
870
881
|
p = self.path(path)
|
|
@@ -1248,12 +1259,29 @@ class Toolbox:
|
|
|
1248
1259
|
return f"[{describe(job)}]" + (f"\nIts last output:\n{last}" if last else "")
|
|
1249
1260
|
|
|
1250
1261
|
# -- planning
|
|
1251
|
-
def t_todo_write(self, todos: list | str) -> str:
|
|
1262
|
+
def t_todo_write(self, todos: list | str, notes: list | str | None = None) -> str:
|
|
1252
1263
|
if isinstance(todos, str):
|
|
1253
1264
|
todos = json.loads(todos)
|
|
1254
1265
|
self.todos = list(todos)
|
|
1266
|
+
# Small models often put a note inside an item instead of in `notes`: keep it either way.
|
|
1267
|
+
attached = [
|
|
1268
|
+
str(todo.get(key)).strip()
|
|
1269
|
+
for todo in self.todos
|
|
1270
|
+
if isinstance(todo, dict)
|
|
1271
|
+
for key in ("notes", "note", "details")
|
|
1272
|
+
if str(todo.get(key) or "").strip()
|
|
1273
|
+
]
|
|
1274
|
+
if attached:
|
|
1275
|
+
notes = [*([] if notes is None else ([notes] if isinstance(notes, str) else list(notes))), *attached]
|
|
1276
|
+
if notes is not None:
|
|
1277
|
+
if isinstance(notes, str):
|
|
1278
|
+
try:
|
|
1279
|
+
notes = json.loads(notes)
|
|
1280
|
+
except ValueError:
|
|
1281
|
+
notes = [notes]
|
|
1282
|
+
self.notes = [str(n).strip()[:MAX_NOTE] for n in notes if str(n).strip()][:MAX_NOTES]
|
|
1255
1283
|
self.show_todos()
|
|
1256
|
-
return "Todo list updated."
|
|
1284
|
+
return "Todo list updated." + (f" {len(self.notes)} note(s) kept." if notes is not None else "")
|
|
1257
1285
|
|
|
1258
1286
|
def show_todos(self) -> None:
|
|
1259
1287
|
icons = {"completed": "[green]✔[/]", "in_progress": "[yellow]◐[/]", "pending": "[dim]○[/]"}
|
|
@@ -1265,4 +1293,6 @@ class Toolbox:
|
|
|
1265
1293
|
elif status == "in_progress":
|
|
1266
1294
|
text = f"[bold]{text}[/]"
|
|
1267
1295
|
rows.append(f"{icons.get(status, '○')} {text}")
|
|
1296
|
+
if self.notes:
|
|
1297
|
+
rows += ["", *(f"[dim]•[/] {escape(note)}" for note in self.notes)]
|
|
1268
1298
|
self.console.print(Panel("\n".join(rows) or "(empty)", title="Todos", title_align="left", border_style="blue"))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from conftest import output, reply
|
|
3
|
+
from conftest import call, output, reply
|
|
4
4
|
from lcode import context
|
|
5
5
|
from lcode.hardware import Hardware
|
|
6
6
|
from lcode.repl import handle_command
|
|
@@ -245,3 +245,56 @@ def test_the_model_is_told_next_to_its_request_what_it_no_longer_has(make_agent)
|
|
|
245
245
|
"conversation any more: read it again"
|
|
246
246
|
)
|
|
247
247
|
assert context.user_requests(agent.messages)[-1] == "third request"
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
TODOS = [
|
|
251
|
+
{"content": "Port the parser", "status": "completed"},
|
|
252
|
+
{"content": "Keep the old flags", "status": "in_progress"},
|
|
253
|
+
]
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def test_the_task_sheet_survives_a_summary_and_a_resume(make_agent, repo):
|
|
257
|
+
agent = make_agent(
|
|
258
|
+
[
|
|
259
|
+
reply(tool_calls=[call("read_file", path="README.md")]),
|
|
260
|
+
reply(tool_calls=[call("edit_file", path="README.md", old_string="Demo", new_string="Demo app")]),
|
|
261
|
+
reply(tool_calls=[call("todo_write", todos=TODOS, notes=["Decided: keep --legacy as an alias"])]),
|
|
262
|
+
reply("Working on it."),
|
|
263
|
+
reply("Summary."),
|
|
264
|
+
]
|
|
265
|
+
)
|
|
266
|
+
agent.run_turn("port the parser")
|
|
267
|
+
agent.save()
|
|
268
|
+
agent.compact()
|
|
269
|
+
summary = agent.messages[1]["content"]
|
|
270
|
+
assert context.SHEET_HEADER in summary and "[>] Keep the old flags" in summary
|
|
271
|
+
assert "- Decided: keep --legacy as an alias" in summary and "Files changed in this session: README.md" in summary
|
|
272
|
+
other = make_agent()
|
|
273
|
+
other.load_latest()
|
|
274
|
+
assert other.tools.todos == TODOS and other.tools.notes == ["Decided: keep --legacy as an alias"]
|
|
275
|
+
assert list(other.tools.changed) == ["README.md"]
|
|
276
|
+
other.new_session()
|
|
277
|
+
assert other.tools.todos == [] and other.tools.notes == [] and not other.tools.changed
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def test_the_sheet_is_pinned_next_to_the_request_when_output_is_removed(make_agent):
|
|
281
|
+
agent = make_agent([reply("ok")], context=16384)
|
|
282
|
+
agent.messages = conversation(BIG * 10)
|
|
283
|
+
agent.tools.todos = TODOS
|
|
284
|
+
agent.ctx_used = int(0.9 * 16384)
|
|
285
|
+
agent.maybe_compact()
|
|
286
|
+
agent.maybe_compact() # pinned again: replaced, not added
|
|
287
|
+
latest = agent.messages[11]["content"]
|
|
288
|
+
assert latest.startswith("third request") and latest.count(context.SHEET_HEADER) == 1
|
|
289
|
+
assert context.user_requests(agent.messages)[-1] == "third request" # the sheet isn't the user's words
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def test_status_shows_the_sheet(make_agent):
|
|
293
|
+
from lcode.repl import handle_command
|
|
294
|
+
|
|
295
|
+
agent = make_agent([reply(tool_calls=[call("todo_write", todos=TODOS, notes=["Open: --strict?"])]), reply("ok")])
|
|
296
|
+
agent.run_turn("port the parser")
|
|
297
|
+
handle_command(agent, "/status", HW)
|
|
298
|
+
text = output(agent)
|
|
299
|
+
assert "Requests (latest)" in text and "port the parser" in text and "Keep the old flags" in text
|
|
300
|
+
assert "Open: --strict?" in text
|
|
@@ -138,3 +138,11 @@ def test_edits_in_ask_mode(make_agent, repo: Path, monkeypatch):
|
|
|
138
138
|
out = agent.tools.run("edit_file", {"path": "src/pkg/math.py", "old_string": "a - b", "new_string": "b - a"})
|
|
139
139
|
assert out.startswith("Edited src/pkg/math.py")
|
|
140
140
|
assert "return b - a" in (repo / "src/pkg/math.py").read_text()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_notes_inside_a_todo_item_are_kept(agent):
|
|
144
|
+
todos = [{"content": "Add --format", "status": "completed", "notes": "Decision: JSON with sorted keys, indent 2"}]
|
|
145
|
+
assert agent.tools.run("todo_write", {"todos": todos}).endswith("1 note(s) kept.")
|
|
146
|
+
assert agent.tools.notes == ["Decision: JSON with sorted keys, indent 2"]
|
|
147
|
+
agent.tools.run("todo_write", {"todos": todos, "notes": ["Open: does --strict apply?"]})
|
|
148
|
+
assert agent.tools.notes == ["Open: does --strict apply?", "Decision: JSON with sorted keys, indent 2"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|