diff --git a/CLAUDE.md b/CLAUDE.md index d1bedae..ee78161 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -83,7 +83,7 @@ Async SQLAlchemy + aiosqlite. `MemoryStore`: schema init (+ lightweight SQLite ` Module-level `@tool` handlers. Call `set_workspace_root()` before use. - **`workspace`**: path resolve under primary root **or** temporary allowlist roots; path substring denylist; command basename denylist (CLI: timed human allow override, default deny on timeout; **not** skipped by YOLO); clearer deny messages. -- **`file`**: `read_file` (`with_lineno`), `write_file`, `listdir`, `tree` (VCS + default noise dirs + optional `skip_dirs` / denylist walk), `glob_paths`, `grep_files` (regex, skip VCS/binary), `edit_replace` (`max_replaces`, reports remaining matches), `edit_lineno` (1-based range), `copy_path` / `move_path` / `delete_path`, `new_temporary_workspace` (system temp allowlist; cleanup on chat exit; no session rebind). +- **`file`**: `read_file` (`with_lineno`), `write_file`, `listdir`, `tree` (VCS + default noise dirs + optional `skip_dirs` / denylist walk), `glob_paths`, `grep_files` (regex, skip VCS/binary), `edit_replace` (`max_replaces`, reports remaining matches), `edit_lineno` (1-based range; only lines read via `with_lineno`; read state resets on any file write/edit/copy/move/delete), `copy_path` / `move_path` / `delete_path`, `new_temporary_workspace` (system temp allowlist; cleanup on chat exit; no session rebind). - **`process`**: `run_command` (argv, no shell, timeout, optional stdin/env); `run_command_batch` (serial steps, `pipe_out` on provider, `mix_stderr`, `stop_on_error`); PTY `open_pty` / `read_pty` / `write_pty` (literal) / `write_pty_keys` (escapes) / `ask_into_pty` (human→PTY only; `secret` no-echo; answer never in tool result) / `close_pty` (POSIX: `pty`+`fork`; Windows: ConPTY via `pywinpty` env marker dep). - PTY: backend in `pty_backend.py`; structured status (`alive`/`exit_code`/`data`); `read_pty(..., until=)` (**CSI sanitized** so host TTY is never reset); keys via `write_pty_keys` only (`\xHH`, `ctrl+x`, `key=esc`); secrets via `ask_into_pty` (not `write_pty` data); session limit/idle TTL/output budget; close terminate→kill. - CLI limit hooks: interactive confirm to raise tool-loop rounds, PTY session cap, or PTY output budget. diff --git a/src/plyngent/agent/directive_checkpoint.py b/src/plyngent/agent/directive_checkpoint.py index 26fb0db..50f0348 100644 --- a/src/plyngent/agent/directive_checkpoint.py +++ b/src/plyngent/agent/directive_checkpoint.py @@ -19,7 +19,8 @@ DEFAULT_DIRECTIVE_REMINDER_TEXT = """\ Tool playbook still applies (see system). Hard constraints: - Prefer file tools over shell; several `run_command` calls may run in parallel; \ use `run_command_batch` for ordered pipelines. -- `edit_replace`: fix match / `max_replaces`; `read_file` with_lineno before `edit_lineno`. +- `edit_replace`: fix match / `max_replaces`; `edit_lineno` edits only lines read via \ +`read_file(with_lineno=true)`, and line numbers reset after any file write. - Prefer `fetch` for HTTP(S); private/LAN hosts need human policy allow (not YOLO). - PTY secrets only via `ask_into_pty`; denylists and confirms still apply. - Todo stack: open items mean unfinished work. diff --git a/src/plyngent/config/models.py b/src/plyngent/config/models.py index 8a181f8..edd1546 100644 --- a/src/plyngent/config/models.py +++ b/src/plyngent/config/models.py @@ -21,7 +21,9 @@ DEFAULT_TOOL_DIRECTIVES = """\ fix `old_string` or `max_replaces`; shell workarounds rarely help. - `edit_replace` defaults to the first match; if remaining matches are reported, \ raise `max_replaces` or narrow `old_string`. -- Before `edit_lineno`, `read_file` with `with_lineno=true`. +- `edit_lineno` edits only lines you read via `read_file(path, with_lineno=true)`; \ +re-read with line numbers after any `edit_*` / `write_file` / copy/move/delete, \ +since line numbers go stale. ### Commands - Prefer `run_command` / `run_command_batch` (argv, no shell) over `bash -c` or similar. diff --git a/src/plyngent/tools/file/read.py b/src/plyngent/tools/file/read.py index 066e9dc..badb33b 100644 --- a/src/plyngent/tools/file/read.py +++ b/src/plyngent/tools/file/read.py @@ -29,7 +29,8 @@ async def read_file( ``offset`` is 0-based line start; ``limit`` is max lines (None = rest of file). When ``with_lineno`` is true, each line is prefixed with its 1-based file line - number (`` N|…``), matching ``edit_lineno`` numbering. + number (`` N|…``), matching ``edit_lineno`` numbering, and those lines are + marked readable for ``edit_lineno`` this turn. """ target = resolve_path(path) if not target.is_file():