krodo 0.1.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.
- krodo-0.1.0/.github/workflows/ci.yml +64 -0
- krodo-0.1.0/.gitignore +43 -0
- krodo-0.1.0/.krodo/config.yaml +8 -0
- krodo-0.1.0/.krodoignore +12 -0
- krodo-0.1.0/.python-version +1 -0
- krodo-0.1.0/AGENTS.md +407 -0
- krodo-0.1.0/CHANGELOG.md +116 -0
- krodo-0.1.0/CONTRIBUTING.md +169 -0
- krodo-0.1.0/LICENSE +201 -0
- krodo-0.1.0/PKG-INFO +421 -0
- krodo-0.1.0/README.md +386 -0
- krodo-0.1.0/README.zh-CN.md +393 -0
- krodo-0.1.0/SECURITY.md +178 -0
- krodo-0.1.0/docs/ARCHITECTURE_DESIGN-GLM.md.archive +698 -0
- krodo-0.1.0/docs/MODELS.md +279 -0
- krodo-0.1.0/docs/QUICKSTART.md +187 -0
- krodo-0.1.0/docs/architecture.md +1228 -0
- krodo-0.1.0/docs/fixes/001-approval-prompt-missing-tool-args.md +117 -0
- krodo-0.1.0/docs/reviews/ARCHITECTURE_REVIEW_v0.2.md +304 -0
- krodo-0.1.0/docs/reviews/ARCHITECTURE_REVIEW_v0.3.md +156 -0
- krodo-0.1.0/docs/reviews/ARCHITECTURE_REVIEW_v0.4.md +75 -0
- krodo-0.1.0/docs/reviews/PLAN_REVIEW_fix-subcommand-routing.md +229 -0
- krodo-0.1.0/pyproject.toml +136 -0
- krodo-0.1.0/scripts/prototype.py +528 -0
- krodo-0.1.0/src/krodo/__init__.py +0 -0
- krodo-0.1.0/src/krodo/cli/__init__.py +0 -0
- krodo-0.1.0/src/krodo/cli/banner.py +71 -0
- krodo-0.1.0/src/krodo/cli/diff_preview.py +87 -0
- krodo-0.1.0/src/krodo/cli/doctor.py +276 -0
- krodo-0.1.0/src/krodo/cli/group.py +202 -0
- krodo-0.1.0/src/krodo/cli/main.py +692 -0
- krodo-0.1.0/src/krodo/cli/repl.py +306 -0
- krodo-0.1.0/src/krodo/cli/resume.py +490 -0
- krodo-0.1.0/src/krodo/cli/undo.py +245 -0
- krodo-0.1.0/src/krodo/core/__init__.py +25 -0
- krodo-0.1.0/src/krodo/core/budget.py +248 -0
- krodo-0.1.0/src/krodo/core/compression.py +343 -0
- krodo-0.1.0/src/krodo/core/config.py +141 -0
- krodo-0.1.0/src/krodo/core/context.py +160 -0
- krodo-0.1.0/src/krodo/core/events.py +195 -0
- krodo-0.1.0/src/krodo/core/loop.py +671 -0
- krodo-0.1.0/src/krodo/core/recovery.py +317 -0
- krodo-0.1.0/src/krodo/core/types.py +100 -0
- krodo-0.1.0/src/krodo/core/workspace.py +153 -0
- krodo-0.1.0/src/krodo/llm/__init__.py +5 -0
- krodo-0.1.0/src/krodo/llm/litellm_provider.py +338 -0
- krodo-0.1.0/src/krodo/llm/protocols.py +68 -0
- krodo-0.1.0/src/krodo/llm/streaming.py +132 -0
- krodo-0.1.0/src/krodo/memory/__init__.py +12 -0
- krodo-0.1.0/src/krodo/memory/agents_md.py +276 -0
- krodo-0.1.0/src/krodo/memory/replay.py +196 -0
- krodo-0.1.0/src/krodo/memory/store.py +300 -0
- krodo-0.1.0/src/krodo/obs/__init__.py +5 -0
- krodo-0.1.0/src/krodo/obs/cost.py +75 -0
- krodo-0.1.0/src/krodo/obs/logger.py +191 -0
- krodo-0.1.0/src/krodo/sandbox/__init__.py +5 -0
- krodo-0.1.0/src/krodo/sandbox/approval.py +293 -0
- krodo-0.1.0/src/krodo/sandbox/checkpoint.py +207 -0
- krodo-0.1.0/src/krodo/sandbox/firewall.py +131 -0
- krodo-0.1.0/src/krodo/sandbox/ignore.py +260 -0
- krodo-0.1.0/src/krodo/sandbox/path_filter.py +81 -0
- krodo-0.1.0/src/krodo/sandbox/protocols.py +55 -0
- krodo-0.1.0/src/krodo/tools/__init__.py +5 -0
- krodo-0.1.0/src/krodo/tools/builtin/__init__.py +0 -0
- krodo-0.1.0/src/krodo/tools/builtin/fs.py +322 -0
- krodo-0.1.0/src/krodo/tools/builtin/git.py +241 -0
- krodo-0.1.0/src/krodo/tools/builtin/patch.py +315 -0
- krodo-0.1.0/src/krodo/tools/builtin/search.py +385 -0
- krodo-0.1.0/src/krodo/tools/builtin/shell.py +121 -0
- krodo-0.1.0/src/krodo/tools/protocols.py +79 -0
- krodo-0.1.0/src/krodo/tools/registry.py +63 -0
- krodo-0.1.0/tests/e2e/__init__.py +0 -0
- krodo-0.1.0/tests/integration/__init__.py +0 -0
- krodo-0.1.0/tests/integration/test_cli_e2e.py +394 -0
- krodo-0.1.0/tests/integration/test_cli_repl.py +274 -0
- krodo-0.1.0/tests/integration/test_cli_stdin.py +103 -0
- krodo-0.1.0/tests/integration/test_cli_subcommand_routing.py +412 -0
- krodo-0.1.0/tests/integration/test_long_session.py +209 -0
- krodo-0.1.0/tests/integration/test_repl_continue.py +136 -0
- krodo-0.1.0/tests/integration/test_repl_slash.py +226 -0
- krodo-0.1.0/tests/integration/test_resume_e2e.py +225 -0
- krodo-0.1.0/tests/integration/test_undo_e2e.py +184 -0
- krodo-0.1.0/tests/unit/__init__.py +0 -0
- krodo-0.1.0/tests/unit/test_agents_md.py +221 -0
- krodo-0.1.0/tests/unit/test_apply_patch.py +242 -0
- krodo-0.1.0/tests/unit/test_approval.py +241 -0
- krodo-0.1.0/tests/unit/test_approval_persistence.py +292 -0
- krodo-0.1.0/tests/unit/test_budget.py +282 -0
- krodo-0.1.0/tests/unit/test_checkpoint.py +270 -0
- krodo-0.1.0/tests/unit/test_cli_group.py +351 -0
- krodo-0.1.0/tests/unit/test_compression.py +379 -0
- krodo-0.1.0/tests/unit/test_config.py +173 -0
- krodo-0.1.0/tests/unit/test_cost.py +260 -0
- krodo-0.1.0/tests/unit/test_diff_preview.py +69 -0
- krodo-0.1.0/tests/unit/test_edit_file.py +150 -0
- krodo-0.1.0/tests/unit/test_events.py +247 -0
- krodo-0.1.0/tests/unit/test_firewall.py +157 -0
- krodo-0.1.0/tests/unit/test_fs_tools.py +227 -0
- krodo-0.1.0/tests/unit/test_git_tools.py +258 -0
- krodo-0.1.0/tests/unit/test_ignore.py +257 -0
- krodo-0.1.0/tests/unit/test_imports.py +31 -0
- krodo-0.1.0/tests/unit/test_litellm_provider.py +399 -0
- krodo-0.1.0/tests/unit/test_logger.py +119 -0
- krodo-0.1.0/tests/unit/test_loop.py +1096 -0
- krodo-0.1.0/tests/unit/test_obs.py +148 -0
- krodo-0.1.0/tests/unit/test_path_filter.py +107 -0
- krodo-0.1.0/tests/unit/test_recovery.py +349 -0
- krodo-0.1.0/tests/unit/test_replay.py +375 -0
- krodo-0.1.0/tests/unit/test_resume_history_display.py +272 -0
- krodo-0.1.0/tests/unit/test_search_tools.py +402 -0
- krodo-0.1.0/tests/unit/test_session_store.py +295 -0
- krodo-0.1.0/tests/unit/test_shell_tool.py +131 -0
- krodo-0.1.0/tests/unit/test_streaming.py +398 -0
- krodo-0.1.0/tests/unit/test_tool_registry.py +102 -0
- krodo-0.1.0/tests/unit/test_undo_command.py +294 -0
- krodo-0.1.0/tests/unit/test_workspace.py +282 -0
- krodo-0.1.0/uv.lock +2288 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
# Only one CI run per branch — newer pushes cancel older in-flight runs.
|
|
10
|
+
concurrency:
|
|
11
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
12
|
+
cancel-in-progress: true
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
lint-type-test:
|
|
16
|
+
name: Lint, type-check, test (Python ${{ matrix.python-version }} on ${{ matrix.os }})
|
|
17
|
+
runs-on: ${{ matrix.os }}
|
|
18
|
+
strategy:
|
|
19
|
+
fail-fast: false
|
|
20
|
+
matrix:
|
|
21
|
+
os: [ubuntu-latest, macos-latest]
|
|
22
|
+
python-version: ["3.12"]
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- name: Checkout
|
|
26
|
+
uses: actions/checkout@v4
|
|
27
|
+
|
|
28
|
+
- name: Install uv
|
|
29
|
+
uses: astral-sh/setup-uv@v3
|
|
30
|
+
with:
|
|
31
|
+
enable-cache: true
|
|
32
|
+
cache-dependency-glob: "uv.lock"
|
|
33
|
+
|
|
34
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
35
|
+
run: uv python install ${{ matrix.python-version }}
|
|
36
|
+
|
|
37
|
+
- name: Install dependencies
|
|
38
|
+
run: uv sync --all-groups --frozen
|
|
39
|
+
|
|
40
|
+
- name: Lint (ruff)
|
|
41
|
+
run: uv run ruff check .
|
|
42
|
+
|
|
43
|
+
- name: Format check (ruff)
|
|
44
|
+
run: uv run ruff format --check .
|
|
45
|
+
|
|
46
|
+
- name: Type check (mypy --strict)
|
|
47
|
+
run: uv run mypy src
|
|
48
|
+
|
|
49
|
+
- name: Run tests with coverage
|
|
50
|
+
run: uv run pytest --cov-report=xml
|
|
51
|
+
|
|
52
|
+
- name: Upload coverage artifact
|
|
53
|
+
if: matrix.os == 'ubuntu-latest'
|
|
54
|
+
uses: actions/upload-artifact@v4
|
|
55
|
+
with:
|
|
56
|
+
name: coverage-report
|
|
57
|
+
path: |
|
|
58
|
+
coverage.xml
|
|
59
|
+
htmlcov/
|
|
60
|
+
if-no-files-found: ignore
|
|
61
|
+
retention-days: 14
|
|
62
|
+
|
|
63
|
+
# Phase 2.6 will add a provider matrix job (Claude / GPT / Gemini / Ollama)
|
|
64
|
+
# that runs against recorded VCR cassettes. Stub left intentionally for now.
|
krodo-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
wheels/
|
|
8
|
+
*.egg-info
|
|
9
|
+
|
|
10
|
+
# Virtual environments
|
|
11
|
+
.venv/
|
|
12
|
+
|
|
13
|
+
# IDE
|
|
14
|
+
.idea/
|
|
15
|
+
.vscode/
|
|
16
|
+
*.swp
|
|
17
|
+
*.swo
|
|
18
|
+
|
|
19
|
+
# OS
|
|
20
|
+
.DS_Store
|
|
21
|
+
Thumbs.db
|
|
22
|
+
|
|
23
|
+
# Environment
|
|
24
|
+
.env
|
|
25
|
+
.env.*
|
|
26
|
+
!.env.example
|
|
27
|
+
|
|
28
|
+
# Tool caches
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
.mypy_cache/
|
|
31
|
+
.ruff_cache/
|
|
32
|
+
.coverage
|
|
33
|
+
.coverage.*
|
|
34
|
+
htmlcov/
|
|
35
|
+
coverage.xml
|
|
36
|
+
|
|
37
|
+
# Krodo state
|
|
38
|
+
.krodo/cache/
|
|
39
|
+
.krodo/sessions/
|
|
40
|
+
.krodo/logs/
|
|
41
|
+
|
|
42
|
+
# Cursor state
|
|
43
|
+
.cursor/
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# 模型名遵循 LiteLLM 命名约定:<provider>/<model-id>
|
|
2
|
+
# 完整列表 https://docs.litellm.ai/docs/providers
|
|
3
|
+
# 例:zai/glm-4.6 / anthropic/claude-sonnet-4-5-20250929 / openai/gpt-5 / gemini/gemini-2.5-pro
|
|
4
|
+
# 也可直接用 OpenAI Chat Completions 兼容名(如 ollama/llama3.1)
|
|
5
|
+
model: zai/glm-4.6
|
|
6
|
+
approval: auto_edit # read_only | auto_edit | full_auto
|
|
7
|
+
max_tool_calls: 15 # 达到 10 给 soft warning,15 强制摘要
|
|
8
|
+
# soft_warning_at 不是 KrodoConfig 字段,krodo 内部硬编码为 max_tool_calls 的 2/3
|
krodo-0.1.0/.krodoignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
krodo-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# Project memory: Krodo
|
|
2
|
+
|
|
3
|
+
> This file is auto-loaded into the system prompt of every Krodo session running in this repository (see `docs/architecture.md` §5.6). Keep it concise — total budget is 8K tokens.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
Krodo is a local-first, multi-provider coding agent CLI. We are currently in **Phase 1 (MVP)**: building a usable REPL + headless `krodo exec` with 11 tools, three approval modes, automatic git checkpoints, JSONL-backed sessions, and AGENTS.md project memory. We are explicitly **not** building TUI / RAG / Docker sandbox / IDE plugins / Web UI in this phase.
|
|
8
|
+
|
|
9
|
+
The single source of truth for design decisions is [`docs/architecture.md`](docs/architecture.md). When in doubt, read it first; if the design is unclear or wrong, update the doc in the same PR as the code.
|
|
10
|
+
|
|
11
|
+
## Engineering rules (the seven principles, see architecture.md §11)
|
|
12
|
+
|
|
13
|
+
1. Make it work first; optimize later.
|
|
14
|
+
2. **Protocol first** — every core module begins as a `typing.Protocol` in `src/krodo/<module>/base.py`.
|
|
15
|
+
3. **Safe by default** — deny unless explicitly allowed; dangerous ops always prompt; sensitive files always ignored.
|
|
16
|
+
4. **Day 1 observability** — every tool call gets a trace span; every LLM call records tokens + cost. PRs without trace/log are not merged.
|
|
17
|
+
5. **Git is the safety net** — checkpoint with `git stash create` before every write; `krodo undo` restores.
|
|
18
|
+
6. Extract shared abstractions only after a pattern repeats across **three** modules.
|
|
19
|
+
7. **Pin major versions** of LLM/agent core dependencies (`litellm>=1.40,<2`, `anthropic` SDK, etc.). Minor upgrades require a PR review and a passing regression run.
|
|
20
|
+
|
|
21
|
+
## Code conventions
|
|
22
|
+
|
|
23
|
+
- Python **3.12+ only**. Use modern syntax: `list[int]`, `int | None`, `match` statements, `@override`.
|
|
24
|
+
- Strict typing: `mypy --strict` must pass with zero errors. No `# type: ignore` without an inline reason.
|
|
25
|
+
- Lint: `ruff check` with rules `E F I N W UP B S ASYNC T20`. **Never use `print()` in `src/`** — use `structlog`.
|
|
26
|
+
- `subprocess.run(shell=True)` is forbidden in production code (bandit S602). If you need shell semantics, go through `krodo.sandbox.shell.run()` which validates against the dangerous-command policy.
|
|
27
|
+
- Test layout: `tests/unit/` mirrors `src/krodo/`; `tests/integration/` for cross-module tests; `tests/e2e/` for full-loop tests with VCR-recorded LLM responses.
|
|
28
|
+
- Coverage target: ≥ 90% for `krodo.core` / `krodo.llm`; 100% for `krodo.tools` / `krodo.sandbox`.
|
|
29
|
+
- All new tools must:
|
|
30
|
+
1. Define a `pydantic.BaseModel` for arguments.
|
|
31
|
+
2. Return a `ToolResult` (never raise to the loop).
|
|
32
|
+
3. Mark `requires_approval` correctly (default = read-only, write/exec = True).
|
|
33
|
+
4. Have a corresponding entry in `tests/unit/tools/test_<tool>.py` and a recorded VCR cassette in `tests/e2e/`.
|
|
34
|
+
|
|
35
|
+
## Module dependency rules (enforced via `import-linter` in CI)
|
|
36
|
+
|
|
37
|
+
- `core` may depend on `llm` / `tools` / `sandbox` / `memory` / `obs`. Never the reverse.
|
|
38
|
+
- `tools` may depend on `sandbox` / `memory` / `obs`. Never on `core`.
|
|
39
|
+
- `cli` and `tui` are thin layers over `core`'s facade — they never construct messages or call LLMs directly.
|
|
40
|
+
|
|
41
|
+
## Common commands
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
uv sync # install / sync deps
|
|
45
|
+
uv run pytest # all tests
|
|
46
|
+
uv run pytest tests/unit # unit only (fast)
|
|
47
|
+
uv run pytest --cov-report=html # coverage report → htmlcov/
|
|
48
|
+
uv run ruff check . --fix # lint + autofix
|
|
49
|
+
uv run mypy src # type-check
|
|
50
|
+
uv run python scripts/prototype.py # Phase 0 prototype (when written)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## M2 tools: the 8 new tools added in Phase 1 M2
|
|
55
|
+
|
|
56
|
+
M2 extended the 3 M1 tools to a full set of 11:
|
|
57
|
+
|
|
58
|
+
### Search / read tools (no approval needed)
|
|
59
|
+
- **`list_dir`** — list directory with depth control (1–10). Skips noise dirs (node_modules, __pycache__, .git, .venv, etc.).
|
|
60
|
+
- **`glob`** — find files by pathlib glob pattern. Filters via `filter_allowed_paths()` (symlink-safe).
|
|
61
|
+
- **`grep`** — regex search in files. Prefers ripgrep (`rg`) for speed; Python `re` fallback. Install ripgrep for large codebases.
|
|
62
|
+
|
|
63
|
+
### Edit / patch tools (require approval in auto_edit mode)
|
|
64
|
+
- **`edit_file`** — precise string replacement. `old_string` must be unique unless `replace_all=true`. On ambiguity, reports line numbers to help the model narrow context.
|
|
65
|
+
- **`apply_patch`** — apply a unified diff (udiff). Atomic transaction: snapshots all targets before writing; rolls back all on any failure. Handles LF/CRLF transparently.
|
|
66
|
+
|
|
67
|
+
### Git tools (status/diff = no approval; commit = approval)
|
|
68
|
+
- **`git_status`** — `git status --porcelain` via GitPython. Error if not in a git repo.
|
|
69
|
+
- **`git_diff`** — unified diff; supports `staged=true` and path filter.
|
|
70
|
+
- **`git_commit`** — commit staged files. `add_all=true` runs `git add -u` first. Auto-redacts API key literals from commit messages.
|
|
71
|
+
|
|
72
|
+
### Path safety (all tools)
|
|
73
|
+
`sandbox/path_filter.py` provides `filter_allowed_paths()` and `is_noise_dir()`, used by bulk-result tools to silently drop out-of-workspace paths and noise directories.
|
|
74
|
+
|
|
75
|
+
### Approval modes (M2 complete)
|
|
76
|
+
- `read_only` — only read/search/status tools; write/shell always denied.
|
|
77
|
+
- `auto_edit` (default) — reads auto-approved; writes prompt `[y/n/a/p/?]`.
|
|
78
|
+
- `p` enters a pattern rule: `<tool_name> <glob>` (e.g. `run_shell pytest*`).
|
|
79
|
+
- Pattern rules stored in memory; persisted via `JsonlSessionStore` in M5.
|
|
80
|
+
- `full_auto` — all tools auto-approved; red warning banner printed at startup.
|
|
81
|
+
|
|
82
|
+
## M3: Token budget, dual compression, and error recovery (Phase 1 M3)
|
|
83
|
+
|
|
84
|
+
M3 brings context-window safety and centralised error recovery to the agent loop.
|
|
85
|
+
|
|
86
|
+
### Token budget (§3.4.1)
|
|
87
|
+
|
|
88
|
+
`src/krodo/core/budget.py` — `BudgetCalculator`:
|
|
89
|
+
- Total budget = model context window × 0.80
|
|
90
|
+
- Output reserve = total_budget × 0.15
|
|
91
|
+
- `check(messages)` → `BudgetStatus(action: BudgetAction)`: OK / COMPRESS / TRUNCATE / REFUSE
|
|
92
|
+
- `MODEL_CONTEXT_WINDOW` table covers ~20 common models (conservative 95% values)
|
|
93
|
+
- `KRODO_TOKEN_RATIO` env var overrides the per-model ratio (Claude = 1.1× default)
|
|
94
|
+
|
|
95
|
+
### Dual compression (`KRODO_COMPRESS`)
|
|
96
|
+
|
|
97
|
+
`src/krodo/core/compression.py` — `make_compressor(strategy, provider)`:
|
|
98
|
+
- `llm` (default): calls the same LLMProvider to summarise oldest N rounds into a `<SUMMARY>` block; emits `SessionEvent(COMPRESSION)` with cost.
|
|
99
|
+
- `algorithmic`: drops `tool_result` content; keeps tool-call metadata + file paths; zero extra LLM cost.
|
|
100
|
+
- **Pinned context**: most-recent 5 file paths from tool_call args + last user message are **never** compressed.
|
|
101
|
+
- Compression is triggered before each `provider.chat()` call (§4.9 of M3 plan).
|
|
102
|
+
|
|
103
|
+
### Error recovery (`src/krodo/core/recovery.py`)
|
|
104
|
+
|
|
105
|
+
`handle(RecoveryContext) -> (RecoveryAction, str)` dispatches 7 scenarios:
|
|
106
|
+
|
|
107
|
+
| # | `error_kind` | `RecoveryAction` | Behaviour |
|
|
108
|
+
|---|-------------|-----------------|-----------|
|
|
109
|
+
| 1 | `bad_json` | RETRY / ABORT | Re-inject schema+error; retry ×2 |
|
|
110
|
+
| 2 | `tool_timeout` | SKIP | Kill; inject partial-result stub |
|
|
111
|
+
| 3 | `stall` | ABORT | Print last 3 calls; abort turn |
|
|
112
|
+
| 4 | `context_loss` | RETRY | Re-inject pinned file paths |
|
|
113
|
+
| 5 | `sha256_conflict` | SKIP | Block write; ask for re-read |
|
|
114
|
+
| 6 | `provider_error` | RETRY / ABORT | Exp backoff 1s/2s/4s ×3 |
|
|
115
|
+
| 7 | `eacces` | SKIP | Report path + permission bits |
|
|
116
|
+
|
|
117
|
+
`StallDetector`: tracks write-tool-call signatures; raises `StallError` at 3 consecutive identical calls (read-only tools excluded).
|
|
118
|
+
|
|
119
|
+
### SHA-256 conflict detection (scenario 5)
|
|
120
|
+
|
|
121
|
+
`read_file` caches the SHA-256 of the file at read-time in a module-level `_sha256_cache`. `edit_file` and `apply_patch` validate the cache before writing; if the on-disk hash differs, they return an error message asking the agent to re-read the file.
|
|
122
|
+
|
|
123
|
+
### SessionEvent stream (`src/krodo/core/events.py`)
|
|
124
|
+
|
|
125
|
+
`SessionEventLogger` wraps the existing JSONL logger with:
|
|
126
|
+
- Typed `emit(SessionEventType, data)` → `SessionEvent`
|
|
127
|
+
- `emit_from(event)` — for compressor-generated events (overwrites session_id + seq)
|
|
128
|
+
- Monotonically-increasing `seq` counter
|
|
129
|
+
- Session events: `<workspace>/.krodo/sessions/<session_id>.jsonl`
|
|
130
|
+
- App logs: `<workspace>/.krodo/logs/<session_id>.log`
|
|
131
|
+
- Factory: `SessionEventLogger.from_store(session_id, store)` (preferred) or `from_workspace_path` (legacy)
|
|
132
|
+
|
|
133
|
+
Events emitted by AgentLoop: `USER_MESSAGE`, `ASSISTANT_MESSAGE`, `TOOL_CALL`, `APPROVAL_DECISION`, `TOOL_RESULT`, `COMPRESSION`, `ERROR`.
|
|
134
|
+
|
|
135
|
+
### New CLI flags (M3)
|
|
136
|
+
|
|
137
|
+
- `--max-tool-calls N` — tool calls per turn limit (default 25; REPL offers to continue when hit)
|
|
138
|
+
- `--summary-window N` — dialogue rounds to compress in one pass (default 2)
|
|
139
|
+
- Startup banner now shows: model context window / compression strategy / max tool calls
|
|
140
|
+
|
|
141
|
+
## M4: `.krodoignore`, Git checkpoint, `krodo undo`, diff preview
|
|
142
|
+
|
|
143
|
+
### KrodoIgnore (`src/krodo/sandbox/ignore.py`)
|
|
144
|
+
|
|
145
|
+
`KrodoIgnore` merges 4 tiers of ignore rules (backed by `pathspec` gitignore semantics):
|
|
146
|
+
|
|
147
|
+
| Tier | Source | Always active? |
|
|
148
|
+
|------|--------|----------------|
|
|
149
|
+
| 1 | Hard-coded defaults: `.env`, `*.pem`, `id_rsa`, `node_modules/`, `__pycache__/`, etc. | ✅ yes |
|
|
150
|
+
| 2 | Project `.gitignore` | — |
|
|
151
|
+
| 3 | `<workspace_root>/.krodoignore` | — |
|
|
152
|
+
| 4 | `~/.config/krodo/krodoignore` | — |
|
|
153
|
+
|
|
154
|
+
**API**: `ignore.match(path) -> MatchResult`; `ignore.is_ignored(path) -> bool`.
|
|
155
|
+
|
|
156
|
+
All read tools (`read_file`, `list_dir`, `glob`, `grep`) check `ctx.ignore.match()` before accessing the path. On match, they return `PathIgnoredError: '<path>' is ignored (rule: '<pattern>' from <source>)` as `ToolResult(is_error=True)`.
|
|
157
|
+
|
|
158
|
+
One `KrodoIgnore` instance is constructed at session start and injected via `ToolContext.ignore`.
|
|
159
|
+
|
|
160
|
+
### GitCheckpointManager (`src/krodo/sandbox/checkpoint.py`)
|
|
161
|
+
|
|
162
|
+
`GitCheckpointManager.create(affected_paths) -> str | None`:
|
|
163
|
+
- Runs `git stash create` (does **not** push to stash stack; working tree untouched).
|
|
164
|
+
- Returns the stash SHA on success, `None` on clean tree or non-git workspace.
|
|
165
|
+
- Logs a warning and returns `None` on non-git workspaces (no crash).
|
|
166
|
+
|
|
167
|
+
`GitCheckpointManager.restore(sha, paths)`:
|
|
168
|
+
- Runs `git checkout <sha> -- <paths>` (only touches the listed paths).
|
|
169
|
+
- Raises `CheckpointError` on non-git workspace or bad SHA.
|
|
170
|
+
|
|
171
|
+
`shell_command_writes(cmd) -> bool`: heuristic that returns `True` if *cmd* likely modifies the filesystem (redirect `>`, `rm`, `mv`, `sed -i`, `tee`, `wget`, etc.).
|
|
172
|
+
|
|
173
|
+
Write tools wire-up:
|
|
174
|
+
- `write_file`, `edit_file` → `create([target_path])` before writing.
|
|
175
|
+
- `apply_patch` → `create(all_affected_paths)` before applying.
|
|
176
|
+
- `run_shell` → `create([workspace_root])` when `shell_command_writes()` returns `True`.
|
|
177
|
+
|
|
178
|
+
Each checkpoint emits a `CHECKPOINT` `SessionEvent` via `ctx.event_logger`.
|
|
179
|
+
|
|
180
|
+
### `krodo undo` (`src/krodo/cli/undo.py`)
|
|
181
|
+
|
|
182
|
+
Typer subcommand registered in `main.py`:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
krodo undo [--root <workspace>] [--session <session_id>]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Behaviour:
|
|
189
|
+
1. Find the session JSONL (`<workspace>/.krodo/logs/<session_id>.jsonl`; defaults to most-recent).
|
|
190
|
+
2. Find the latest `CHECKPOINT` event (by `seq`).
|
|
191
|
+
3. Call `GitCheckpointManager.restore(sha, affected_paths)`.
|
|
192
|
+
4. Emit `UNDO` `SessionEvent`.
|
|
193
|
+
5. Non-git workspace or no CHECKPOINT → exit 1 with friendly error.
|
|
194
|
+
6. If `affected_paths == [workspace_root]` (shell command scope) → prompt for confirmation.
|
|
195
|
+
|
|
196
|
+
### Diff preview (`src/krodo/cli/diff_preview.py`)
|
|
197
|
+
|
|
198
|
+
`render_diff(old, new, path) -> rich.syntax.Syntax`: returns a Rich `Syntax` object showing a unified diff, truncated to 200 lines.
|
|
199
|
+
|
|
200
|
+
`render_new_file(content, path) -> Syntax`: convenience wrapper for new files (`old=None`).
|
|
201
|
+
|
|
202
|
+
`TerminalApprovalManager._maybe_render_diff()` renders diffs for `write_file`, `edit_file`, `apply_patch` before the `y/n` approval prompt in `auto_edit` mode.
|
|
203
|
+
|
|
204
|
+
### Banner update (`src/krodo/cli/banner.py`)
|
|
205
|
+
|
|
206
|
+
Session banner now shows a `git: <root> | none` line reflecting `workspace.git_root`.
|
|
207
|
+
|
|
208
|
+
### New dependency
|
|
209
|
+
|
|
210
|
+
`pathspec>=0.12,<1` added to `pyproject.toml` (major version pinned per engineering rule 7).
|
|
211
|
+
|
|
212
|
+
## M4.9: Interactive REPL (multi-turn dialogue)
|
|
213
|
+
|
|
214
|
+
The CLI now has two entry shapes — both share the *same* `AgentLoop`
|
|
215
|
+
instance, so history persists naturally:
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
krodo "one-shot task" # headless: run once and exit (unchanged)
|
|
219
|
+
krodo # REPL: read → run → repeat
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Implementation map
|
|
223
|
+
|
|
224
|
+
- `src/krodo/cli/main.py`
|
|
225
|
+
- `_build_session_components(...)` → returns a `SessionComponents`
|
|
226
|
+
bundle (workspace + AgentLoop + logger + session_id + event_logger
|
|
227
|
+
+ log_path + max_tokens).
|
|
228
|
+
- `_run_headless(prompt, components)` → one `loop.run` + headless
|
|
229
|
+
summary (preserves pre-M4.9 behaviour byte-for-byte).
|
|
230
|
+
- `ABORT_REASONS` / `_echo_turn_result` / `_collect_written_paths`
|
|
231
|
+
are module-level so the REPL can reuse them.
|
|
232
|
+
- `src/krodo/cli/repl.py`
|
|
233
|
+
- `run_repl(components)` — the multi-turn loop.
|
|
234
|
+
- `input()` runs in `asyncio.to_thread` so it doesn't block the event loop.
|
|
235
|
+
- Exit tokens: `exit` / `quit` / `:q` / `\q`; Ctrl-D (EOFError) exits
|
|
236
|
+
immediately; Ctrl-C at an empty prompt requires a second press
|
|
237
|
+
(`last_ctrl_c` sentinel, mirrors Python/IPython UX).
|
|
238
|
+
- Ctrl-C *during* a turn cancels just that turn (does **not** exit REPL).
|
|
239
|
+
- Empty / whitespace-only input is silently skipped.
|
|
240
|
+
- Session summary is printed exactly once at exit (`print_session_summary`
|
|
241
|
+
in `main.py`); per-turn output is just the model answer or abort message.
|
|
242
|
+
|
|
243
|
+
### What carries across REPL turns
|
|
244
|
+
|
|
245
|
+
| State | Lifetime |
|
|
246
|
+
|---|---|
|
|
247
|
+
| `session_id` / `SessionEventLogger` / JSONL log path | whole REPL session |
|
|
248
|
+
| `GitCheckpointManager` | whole REPL session (checkpoints accumulate) |
|
|
249
|
+
| `AgentLoop.context_manager` (conversation history) | whole REPL session |
|
|
250
|
+
| `StallDetector` / `tool_calls_made` / `invalid_args_retry` | per turn (reset by `loop.run`) |
|
|
251
|
+
| Banner / `full_auto` warning | printed once at session start |
|
|
252
|
+
|
|
253
|
+
### What's deliberately deferred to M6
|
|
254
|
+
|
|
255
|
+
- `prompt_toolkit` upgrade (arrow-key history, multi-line, syntax highlighting)
|
|
256
|
+
- Slash commands (`:undo`, `:tokens`, `:clear`, `/compact`)
|
|
257
|
+
- Cancelling in-flight LLM streaming on single Ctrl-C (requires provider-level
|
|
258
|
+
cancellation)
|
|
259
|
+
|
|
260
|
+
## M5: Persistence + memory (JSONL sessions, `krodo resume`, AGENTS.md, config)
|
|
261
|
+
|
|
262
|
+
### JsonlSessionStore (`src/krodo/memory/store.py`)
|
|
263
|
+
|
|
264
|
+
`JsonlSessionStore` implements the `SessionStore` Protocol backed by one `.jsonl` file per session in `<workspace>/.krodo/sessions/`.
|
|
265
|
+
|
|
266
|
+
- `create_session(session_id, workspace, model, agents_md_hash)` → writes `SESSION_INIT` as `seq=0`.
|
|
267
|
+
- `append_event(session_id, event)` → appends a single JSONL line.
|
|
268
|
+
- `load_events(session_id)` → reads all events in order.
|
|
269
|
+
- `max_seq(session_id)` → reads only the **last line** for O(1) seq lookup.
|
|
270
|
+
- `list_recent(n)` → reads the `SESSION_INIT` header from each file to build `SessionRow` summaries.
|
|
271
|
+
|
|
272
|
+
`SessionRow` is a lightweight dataclass (`session_id`, `workspace`, `model`, `started_at`, `event_count`) returned by `list_recent`.
|
|
273
|
+
|
|
274
|
+
### `krodo resume` (`src/krodo/cli/resume.py`)
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
krodo resume --list # show recent sessions
|
|
278
|
+
krodo resume <id-or-prefix> # replay + continue in REPL
|
|
279
|
+
krodo resume --root /path <id> # explicit workspace
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Internals:
|
|
283
|
+
1. `_resolve_session_id(store, token)` — exact match or unique prefix match; error on ambiguity.
|
|
284
|
+
2. `store.load_events(session_id)` — load all stored events.
|
|
285
|
+
3. `replay_events(events, context_manager)` — reconstruct `InMemoryContextManager._history` from `USER_MESSAGE`, `ASSISTANT_MESSAGE`, `TOOL_RESULT`, and `COMPRESSION` events.
|
|
286
|
+
4. Drop into the REPL with the restored history.
|
|
287
|
+
|
|
288
|
+
`ReplayStats` (`src/krodo/memory/replay.py`) tracks `messages_restored`, `tool_results_restored`, and `compressed` (bool).
|
|
289
|
+
|
|
290
|
+
### AGENTS.md 3-tier merge (`src/krodo/memory/agents_md.py`)
|
|
291
|
+
|
|
292
|
+
`load_agents_md(workspace, cwd) -> AgentsMdBundle` collects and merges:
|
|
293
|
+
|
|
294
|
+
| Tier | Path | Droppable on overflow? |
|
|
295
|
+
|------|------|----------------------|
|
|
296
|
+
| 1 (system) | `~/.config/krodo/AGENTS.md` | Yes |
|
|
297
|
+
| 2 (project) | `<workspace>/AGENTS.md` | **No** — always kept |
|
|
298
|
+
| 3 (subdir) | `<cwd>/AGENTS.md` … up to workspace root | Yes (outermost first) |
|
|
299
|
+
|
|
300
|
+
Rules:
|
|
301
|
+
- Per-file limit: **8K tokens** (truncated with `…[truncated]` suffix).
|
|
302
|
+
- Total limit: **12K tokens** (tiers 1 and 3 are dropped first).
|
|
303
|
+
- The bundle's SHA-256 hash is stored in the `SESSION_INIT` event.
|
|
304
|
+
- Content is injected as a `<project_memory>…</project_memory>` user message at index 0 of `_history`.
|
|
305
|
+
|
|
306
|
+
### Config loading (`src/krodo/core/config.py`)
|
|
307
|
+
|
|
308
|
+
`KrodoConfig` (Pydantic model) covers `model`, `approval`, `max_tool_calls`, `summary_window`, `compress`, `token_ratio`.
|
|
309
|
+
|
|
310
|
+
`load_config(workspace_root) -> tuple[KrodoConfig, list[tuple[Path, str]]]` merges:
|
|
311
|
+
|
|
312
|
+
1. `~/.config/krodo/config.toml` (user, TOML)
|
|
313
|
+
2. `<workspace>/.krodo/config.yaml` (workspace, YAML — takes precedence over user)
|
|
314
|
+
|
|
315
|
+
Precedence chain: **CLI flag > env var > workspace YAML > user TOML > built-in default**.
|
|
316
|
+
|
|
317
|
+
`main.py` applies config values to Typer options only when `ParameterSource` (from `click.core`) shows the option is still at its default — CLI flags and env vars always win.
|
|
318
|
+
|
|
319
|
+
`krodo doctor` now shows a **Config sources** section listing which files were found and which keys they contribute.
|
|
320
|
+
|
|
321
|
+
## M6: streaming, cost, stdin, slash commands, approval persistence
|
|
322
|
+
|
|
323
|
+
### Streaming output (`src/krodo/llm/streaming.py`, M6.1)
|
|
324
|
+
|
|
325
|
+
`ChunkAccumulator` reassembles `LLMChunk`s into a complete assistant `Message`: text deltas are concatenated, tool-call fragments are merged by index (the `arguments` JSON string arrives split across chunks and is `json.loads`-ed at the end, falling back to `{"_raw": ...}` on malformed JSON so the BAD_JSON recovery path still triggers), and the final `usage` / `finish_reason` are captured.
|
|
326
|
+
|
|
327
|
+
`AgentLoop._call_llm` streams when **both** `LoopConfig.stream` (default True) and the provider's `supports_streaming` attribute are truthy; otherwise it falls back to non-streaming `chat()`. Test mocks don't set the attribute, so they transparently use `chat()` — zero test churn. Text deltas go through the constructor-injected `on_delta` callback (default: Rich raw print, no markup). `TurnResult.streamed` tells `_echo_turn_result` not to print `final_text` a second time.
|
|
328
|
+
|
|
329
|
+
### Cost tracking (`src/krodo/obs/cost.py`, M6.2 — engineering rule #4)
|
|
330
|
+
|
|
331
|
+
- `Message.usage` (`{prompt_tokens, completion_tokens, total_tokens}`) and `Message.cost_usd` are filled by `LiteLLMProvider.chat()` via `response.usage` + `litellm.completion_cost`; the streaming path gets usage from the final chunk and the loop estimates cost with `litellm.cost_per_token`. Unknown models: tokens tracked, cost `None`.
|
|
332
|
+
- `CostTracker` accumulates per-session totals; `AgentLoop` emits one `COST_SNAPSHOT` event per turn with `{turn_*, total_*}` token/cost fields.
|
|
333
|
+
- Both session summaries print `tokens : 12.3k in / 4.1k out | cost $0.0231` (cost omitted when unknown). Replay skips `COST_SNAPSHOT`.
|
|
334
|
+
|
|
335
|
+
### Pipe stdin entry (M6.3)
|
|
336
|
+
|
|
337
|
+
`echo "fix the bug" | krodo` runs headless with the piped text as the prompt. `git diff | krodo "review this"` appends stdin as a `<stdin>...</stdin>` context block after the prompt. Empty piped stdin (CliRunner test streams) keeps the REPL behaviour — this completes task 1.10's three-entry acceptance (REPL / exec / pipe).
|
|
338
|
+
|
|
339
|
+
### REPL slash commands (M6.4)
|
|
340
|
+
|
|
341
|
+
Handled locally in `repl.py:_dispatch_slash` — the LLM never sees them. Checked after the exit-token test, so `:q` still exits.
|
|
342
|
+
|
|
343
|
+
| Command | Action |
|
|
344
|
+
|---------|--------|
|
|
345
|
+
| `:help` | list commands |
|
|
346
|
+
| `:sessions` | recent-sessions table (shared `render_sessions_table` with `resume --list`) |
|
|
347
|
+
| `:undo` | `undo_command(...)` with `typer.Exit` caught so the REPL survives |
|
|
348
|
+
| `:cost` | CostTracker totals |
|
|
349
|
+
| `:resume <id>` | switch session: `run_repl` returns the target id; `repl_session_cycle` (resume.py) rebuilds components, replays history, re-enters |
|
|
350
|
+
|
|
351
|
+
### Approval trust persistence (M6.5)
|
|
352
|
+
|
|
353
|
+
`TerminalApprovalManager.export_state()/restore_state()` snapshot `_session_trusted` + `_pattern_trust`. When a decision is `approve_session`/`approve_pattern`, the `APPROVAL_DECISION` event carries the full `state` snapshot (last one wins). `replay_events(events, ctx, approval=...)` re-applies the latest snapshot on resume, so `a`/`p` answers survive `krodo resume`. Cross-session global policy stays Phase 3 (`policy.toml`).
|
|
354
|
+
|
|
355
|
+
## M7: Brand rename + v0.1.0 release (Phase 1 closeout)
|
|
356
|
+
|
|
357
|
+
M7 closed out Phase 1. The work happened in five independent commits, each independently revertable:
|
|
358
|
+
|
|
359
|
+
| Commit | Type | What |
|
|
360
|
+
|--------|------|------|
|
|
361
|
+
| `ce24ef6` | `fix(mypy)` | cleared 10 mypy `--strict` errors (`types-PyYAML` stubs + streaming `int(object)` overloads + bare `dict`/`list` generics in CLI helpers + 2 stale `# type: ignore`) |
|
|
362
|
+
| `295ce49` | `feat(rename)` | full rebrand `Coda → krodo`: `src/coda/` → `src/krodo/`, ~826 coda/Coda/CODA references across 84+ files (perl 3-case replace), pyproject 5 fields, `.codaignore` → `.krodoignore`, all docs/AGENTS/README/architecture |
|
|
363
|
+
| `0c5c607` | `docs` | QUICKSTART / CONTRIBUTING / SECURITY / CHANGELOG + README polish (4 badges, doc table, PyPI-deferred notice) |
|
|
364
|
+
| `634d051` | `fix(rename)` | cleanup residuals missed by initial perl pass: `.gitignore` patterns + LICENSE copyright holder + physical `.coda/` → `.krodo/` directory on disk |
|
|
365
|
+
| `05a82bd` | `fix(ci)` | ruff format backfill (10 pre-existing files) + correct repo owner `liangck` → `wadekun` across all docs |
|
|
366
|
+
| `90aa414` (PR #1 merge) | merge | M7 dogfood: krodo fixed its own `checkpoint_skipped` warning noise (non-git workspaces now warn once per session, then debug) — `836d829` |
|
|
367
|
+
|
|
368
|
+
### Why the rename
|
|
369
|
+
|
|
370
|
+
- PyPI `coda` was occupied by an unrelated 2017 file-tagging package (claiming both `import coda` and the `coda` console script).
|
|
371
|
+
- GitHub `agno-agi/coda` is an active same-category coding-agent project (Slack surface, well-funded); we would have lost brand discovery.
|
|
372
|
+
- `krodo` verified clean across PyPI / npm / `.ai` / `.dev` / `.com`, no negative etymology.
|
|
373
|
+
|
|
374
|
+
### Release flow (this repo's pattern)
|
|
375
|
+
|
|
376
|
+
1. Bump `version` in `pyproject.toml`.
|
|
377
|
+
2. Move `[Unreleased]` → `[x.y.z] — YYYY-MM-DD` in `CHANGELOG.md`.
|
|
378
|
+
3. `git commit -m "chore(release): vx.y.z"`.
|
|
379
|
+
4. `git tag vx.y.z && git push --tags`.
|
|
380
|
+
5. `gh release create vx.y.z --notes-from-tag` (or paste CHANGELOG section).
|
|
381
|
+
6. PyPI upload is **deferred past v0.1.0** (distribution name `krodo` locked; first release via GitHub only; PyPI in a minor follow-up).
|
|
382
|
+
|
|
383
|
+
### Documentation index (where things live)
|
|
384
|
+
|
|
385
|
+
| Doc | Path | Audience |
|
|
386
|
+
|-----|------|---------|
|
|
387
|
+
| Project overview + roadmap | `README.md` | everyone |
|
|
388
|
+
| 5-minute install | `docs/QUICKSTART.md` | new users |
|
|
389
|
+
| Design baseline (source of truth) | `docs/architecture.md` | contributors |
|
|
390
|
+
| Auto-loaded project memory | `AGENTS.md` (this file) | krodo + contributors |
|
|
391
|
+
| Dev setup + CI gate + PR flow | `CONTRIBUTING.md` | contributors |
|
|
392
|
+
| Threat model + reporting | `SECURITY.md` | users + researchers |
|
|
393
|
+
| Milestone-by-milestone changes | `CHANGELOG.md` | everyone |
|
|
394
|
+
|
|
395
|
+
### M7 lesson learned: full-repo rename coverage
|
|
396
|
+
|
|
397
|
+
The initial rename commit (`295ce49`) ran perl 3-case replacement on a hardcoded list of files (`pyproject` / `README` / `AGENTS` / `architecture` / `ci.yml` / `.krodoignore`). That missed `.gitignore`, `LICENSE`, and the on-disk `.coda/` directory — caught only after CI failed on the first GitHub push. For future full-repo renames, use `rg -l 'pattern'` to enumerate every hit first, then exclude historical archives and intentional references.
|
|
398
|
+
|
|
399
|
+
## When you (the agent) modify this codebase
|
|
400
|
+
|
|
401
|
+
- Always `read_file` before `edit_file` — never guess file contents.
|
|
402
|
+
- Before any write, describe what you intend to do in plain language.
|
|
403
|
+
- After changes, run the relevant test suite (not the full suite — pick what's affected).
|
|
404
|
+
- If `mypy --strict` fails, fix it in the same edit. Do not push type errors.
|
|
405
|
+
- Use `git_diff` to verify your change before declaring success; do not assume the file was written correctly.
|
|
406
|
+
- Never modify `pyproject.toml` dependency versions without explicit user approval (rule 7).
|
|
407
|
+
- Never modify `LICENSE`, `.gitignore`, or `.github/` without explicit user approval.
|
krodo-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Krodo are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
|
|
7
|
+
once v0.1.0 is tagged.
|
|
8
|
+
|
|
9
|
+
## [Unreleased]
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- M7 release scaffold: QUICKSTART, CONTRIBUTING, SECURITY (this changelog).
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- **Brand rename: Coda → Krodo.** Distribution name, Python import package,
|
|
16
|
+
CLI command, workspace state directory (`.coda/` → `.krodo/`), config
|
|
17
|
+
directory (`~/.config/coda/` → `~/.config/krodo/`), and environment
|
|
18
|
+
variables (`CODA_*` → `KRODO_*`) all renamed. Reason: PyPI `coda` occupied
|
|
19
|
+
by an unrelated 2017 file-tagging package; GitHub `agno-agi/coda` is an
|
|
20
|
+
active same-category coding-agent project. Krodo is verified clean across
|
|
21
|
+
PyPI / npm / `.ai` / `.dev` / `.com`.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
- mypy `--strict` now passes with zero errors (was 10 errors before M7).
|
|
25
|
+
Root causes: missing `types-PyYAML` stubs, `int(object)` overload failures
|
|
26
|
+
in `LLMChunk.usage` parsing, bare `dict`/`list` generics in two CLI helpers.
|
|
27
|
+
|
|
28
|
+
## [0.1.0] — Phase 1 milestones M1–M6 (collected changelog)
|
|
29
|
+
|
|
30
|
+
Krodo v0.1.0 is the Phase 1 feature-complete release. It ships a usable
|
|
31
|
+
REPL + headless CLI with 11 tools, three approval modes, automatic git
|
|
32
|
+
checkpoints, JSONL-backed sessions, and AGENTS.md project memory. The
|
|
33
|
+
sections below summarise each milestone; full design notes live in
|
|
34
|
+
[`docs/architecture.md`](docs/architecture.md) §10 changelog.
|
|
35
|
+
|
|
36
|
+
### M1 — Walking skeleton
|
|
37
|
+
- Single-turn ReAct loop (`krodo/core/loop.py`), 3 tools (`read_file`,
|
|
38
|
+
`write_file`, `run_shell`), Typer CLI entry, `ToolRegistry` with
|
|
39
|
+
auto-generated LiteLLM JSON schemas.
|
|
40
|
+
|
|
41
|
+
### M2 — Full tools + three approval modes
|
|
42
|
+
- 8 more tools: `list_dir`, `glob`, `grep`, `edit_file`, `apply_patch`,
|
|
43
|
+
`git_status`, `git_diff`, `git_commit`. Total 11.
|
|
44
|
+
- `TerminalApprovalManager` with `read_only` / `auto_edit` (default) /
|
|
45
|
+
`full_auto` modes. `auto_edit` prompts `y/n/a/p/?` and supports
|
|
46
|
+
pattern trust (e.g. `run_shell pytest*`).
|
|
47
|
+
- Path firewall + dangerous-command blocklist + symlink-safe
|
|
48
|
+
`filter_allowed_paths()`.
|
|
49
|
+
|
|
50
|
+
### M3 — Token budget, dual compression, error recovery
|
|
51
|
+
- `BudgetCalculator` triggers compression at 80% context window.
|
|
52
|
+
- `make_compressor("llm"|"algorithmic")`: LLM summarises oldest N rounds
|
|
53
|
+
into `<SUMMARY>` block; algorithmic drops `tool_result` content but
|
|
54
|
+
keeps metadata. Pinned context (recent 5 file paths + last user
|
|
55
|
+
message) is never compressed.
|
|
56
|
+
- `recovery.py` covers 7 scenarios: `bad_json`, `tool_timeout`, `stall`,
|
|
57
|
+
`context_loss`, `sha256_conflict`, `provider_error`, `eacces`.
|
|
58
|
+
- `StallDetector` aborts the turn after 3 consecutive identical write calls.
|
|
59
|
+
- `SessionEventLogger` emits typed JSONL events for replay and observability.
|
|
60
|
+
- New CLI flags: `--max-tool-calls`, `--summary-window`.
|
|
61
|
+
|
|
62
|
+
### M4 — `.krodoignore`, git checkpoint, `krodo undo`, diff preview
|
|
63
|
+
- `.krodoignore` (4-tier merge): hard-coded defaults + project `.gitignore`
|
|
64
|
+
+ `<workspace>/.krodoignore` + `~/.config/krodo/krodoignore`. Backed by
|
|
65
|
+
`pathspec` gitignore semantics.
|
|
66
|
+
- `GitCheckpointManager.create()` runs `git stash create` before every
|
|
67
|
+
write (does NOT touch the stash stack).
|
|
68
|
+
- `krodo undo` restores files via `git checkout <sha> -- <paths>`.
|
|
69
|
+
- Diff preview rendered before `y/n` approval in `auto_edit` mode.
|
|
70
|
+
|
|
71
|
+
### M4.5–M4.9 — UX polish and interactive REPL
|
|
72
|
+
- LiteLLM noise silenced; abort reason surfaced to user; Windows `glm-*`
|
|
73
|
+
compatibility; `krodo doctor` diagnostics command.
|
|
74
|
+
- Dynamic tool list injected into system prompt; configurable `--max-tokens`.
|
|
75
|
+
- `stop_reason` diagnostic logging; invalid-args retry budget.
|
|
76
|
+
- **Interactive REPL** (`krodo` with no prompt): multi-turn dialogue with
|
|
77
|
+
persistent conversation history. Exit via `exit`/`quit`/`:q`/Ctrl-D/two
|
|
78
|
+
Ctrl-C. Ctrl-C during a turn cancels just that turn.
|
|
79
|
+
|
|
80
|
+
### M5 — Persistence + memory
|
|
81
|
+
- `JsonlSessionStore` writes `<workspace>/.krodo/sessions/<id>.jsonl`
|
|
82
|
+
(one event per line, monotonic `seq`).
|
|
83
|
+
- `krodo resume [--list] [<id-prefix>]` replays stored events into
|
|
84
|
+
`InMemoryContextManager`, restoring full conversation context.
|
|
85
|
+
- AGENTS.md 3-tier merge: system (`~/.config/krodo/AGENTS.md`) + project
|
|
86
|
+
(`<workspace>/AGENTS.md`, always kept) + subdir walk. 8K per-file,
|
|
87
|
+
12K total budget, SHA-256 stored in `SESSION_INIT`.
|
|
88
|
+
- Config loading: `~/.config/krodo/config.toml` + `<workspace>/.krodo/config.yaml`.
|
|
89
|
+
Precedence: CLI flag > env > workspace YAML > user TOML > default.
|
|
90
|
+
|
|
91
|
+
### M6 — Streaming, cost, pipe stdin, slash commands, approval persistence
|
|
92
|
+
- **M6.1 streaming**: `ChunkAccumulator` reassembles LiteLLM chunks;
|
|
93
|
+
`AgentLoop._call_llm` streams when provider declares `supports_streaming`;
|
|
94
|
+
falls back to `chat()` for mocks.
|
|
95
|
+
- **M6.2 cost tracking**: `Message.usage` / `cost_usd` filled by provider;
|
|
96
|
+
`CostTracker` accumulates session totals; one `COST_SNAPSHOT` event per
|
|
97
|
+
turn; session summary prints `tokens: Xk in / Yk out | cost $Z`.
|
|
98
|
+
- **M6.3 pipe stdin**: `echo task | krodo` runs headless with stdin as
|
|
99
|
+
prompt; `git diff | krodo "review this"` adds stdin as `<stdin>` context.
|
|
100
|
+
- **M6.4 REPL slash commands**: `:help` / `:sessions` / `:undo` / `:cost` /
|
|
101
|
+
`:resume <id>`. Handled locally, never sent to the LLM.
|
|
102
|
+
- **M6.5 approval trust persistence**: `approve_session` / `approve_pattern`
|
|
103
|
+
decisions survive `krodo resume` via state snapshots in `APPROVAL_DECISION`
|
|
104
|
+
events.
|
|
105
|
+
- Tool-call-limit interrupt now synthesises a `[skipped: tool call limit
|
|
106
|
+
reached]` tool_result so `krodo` can `continue_turn()` mid-batch without
|
|
107
|
+
losing the LLM's pending work.
|
|
108
|
+
|
|
109
|
+
### M7 — Release preparation
|
|
110
|
+
- mypy strict clean (see Fixed above).
|
|
111
|
+
- Brand rename to Krodo (see Changed above).
|
|
112
|
+
- Documentation quartet (QUICKSTART / CONTRIBUTING / SECURITY / this file).
|
|
113
|
+
- GitHub repo + v0.1.0 tag + Release (deferred until owner confirms).
|
|
114
|
+
|
|
115
|
+
[Unreleased]: https://github.com/wadekun/krodo/compare/v0.1.0...HEAD
|
|
116
|
+
[0.1.0]: https://github.com/wadekun/krodo/releases/tag/v0.1.0
|