oc_chat
Loading...
Searching...
No Matches
oc_chat.tools.filesystem Namespace Reference

Functions

Iterator[None] human_confirmed_write (str target)
bool _plan_envelope_covers_write (str target)
dict _make_protected_write_block (str target, str reason)
dict deny_protected_write (dict result, *, str note="")
"Path | dict" _enforce_write_gates (str path)
Path _safe_path (str path)
Path|None _find_worktree_root (Path|None start=None)
tuple[Path, str] _sandbox_root_info ()
Path _sandbox_root ()
"tuple[Path, Path]" _resolve_unclamped (str path)
"str | None" _outside_sandbox_error (str path)
bool _was_discovery_rescue (str requested, Path resolved)
Path _discover_file_path (str path)
Path _discover_dir_path (str path)
dict list_dir (str path=".", int max_depth=2, bool exclude_hidden=True)
ReadFileResult|ToolError read_file (str path, int max_bytes=50_000, int offset=0)
str _normalize_newlines (str content)
WriteFileResult|ToolError write_file (str path, str content, int chunk_size=0, int max_chunks=0)
list[int] _match_start_lines (str text, str anchor)
str _edit_near_miss_message (str text, str anchor, str path)
str _edit_context_snippet (str edited_text, str replacement, int start_line, int radius=2)
None _atomic_write_bytes (Path target, bytes data)
dict edit_file (str path, str old_string, str new_string, int expected_count=1)
dict file_info (str path)
dict read_chunks (str path, int chunk_size=10_000, int max_chunks=10, int offset=0)
dict search_files (str root_dir=".", str pattern="*")
str read_prompt_file (str filepath, *, bool allow_absolute=False)

Variables

 _HARDCODED_ROOT = Path(__file__).parent.parent.parent.resolve()
 ROOT = _HARDCODED_ROOT
str _PROTECTED_READ_MSG = "Reading this file is not permitted: it is in the protected-read (secret) class."
str _PROTECTED_WRITE_MSG = "Writing this file is not permitted: it is in the protected-write class (integrity / config-as-code / secret)."
contextvars _human_approved_write
dict TOOLS
list SCHEMAS

Detailed Description

Filesystem tools — all operations are clamped to the working directory.

Purpose
-------
Provides :func:`list_dir`, :func:`read_file`, :func:`write_file`,
:func:`edit_file`, :func:`read_chunks`, and :func:`search_files` as both
Python callables and OpenAI tool schemas. Every path is resolved relative
to the project root and validated to prevent escaping the sandbox.

Responsibilities
----------------
- :func:`_safe_path` — resolve and reject paths that escape ``ROOT``.
- :func:`_enforce_write_gates` — the single shared write-side M8 gate
  (sandbox clamp, hard-deny protected-write, human-confirm tier) both
  :func:`write_file` and :func:`edit_file` route through.
- :func:`list_dir` — recursive directory walk with depth limit and
  hidden-file filter.
- :func:`read_file`, :func:`read_chunks` — UTF-8 reads with offset
  pagination and natural-boundary lookahead.
- :func:`write_file` — whole-file overwrite/create with informational
  chunk metadata.
- :func:`edit_file` — fail-closed anchored partial edit (exact
  ``old_string`` → ``new_string``); atomic, newline-preserving; the
  primary modify path (plan 171 / F20).
- :func:`search_files` — recursive grep over text files.
- Module-level ``TOOLS`` (callables) and ``SCHEMAS`` (OpenAI shapes)
  for auto-registration.

Diagnostics
-----------
Domain: OC_CHAT.TOOLS
Levels:
    L2 — lifecycle events
    L3 — semantic details
    L4 — deep tracing

Contracts
---------
- All paths are validated by :func:`_safe_path`; escapes raise
  :class:`PermissionError` rather than silently allowing access.
- Reads/writes use UTF-8 with ``errors="replace"``; binary files are
  rejected.
- ``read_chunks`` clamps ``offset`` to ``[0, total_chars]`` and never
  reads past EOF.
- ``write_file`` chunk slicing is character-based (not byte-based)
  to handle multi-byte UTF-8 correctly.

Dependencies
------------
- oc_diagnostics (structured debug logging via _dbg)
- oc_chat.core.tool_results (ReadFileResult, WriteFileResult and ToolError typed return shapes)
- oc_chat.tools._sandbox (sandbox clamp, protected read and write classes, human-confirm tier and secret redaction)
- oc_chat.core.cancellation (lazy; cooperative Ctrl+C check in list_dir and search_files)
- oc_chat.core.resource_caps (lazy; clamps the model-supplied depth, byte and chunk limits)

See Also
--------
oc_chat/docs/conventions/CONTRACTS.md §3 Tool registry contract; §4 Tool return-
value conventions (read_file → §4.2, write_file → §4.3).

Function Documentation

◆ _atomic_write_bytes()

None oc_chat.tools.filesystem._atomic_write_bytes ( Path target,
bytes data )
protected
Write *data* to *target* atomically: temp file in the SAME dir + ``os.replace``.

A same-directory temp keeps the final rename on one filesystem (so
:func:`os.replace` is atomic); on any failure the temp is unlinked, so a failed
edit never leaves a partial file — the target keeps its original bytes.

Definition at line 737 of file filesystem.py.

Here is the caller graph for this function:

◆ _discover_dir_path()

Path oc_chat.tools.filesystem._discover_dir_path ( str path)
protected
Intelligently discover directory path with fallback mechanisms.

Args:
    path: Original directory path requested
    
Returns:
    Path object for the discovered directory
    
Raises:
    NotADirectoryError: If directory cannot be found in any allowed location

Definition at line 382 of file filesystem.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _discover_file_path()

Path oc_chat.tools.filesystem._discover_file_path ( str path)
protected
Intelligently discover file path with fallback mechanisms.

Args:
    path: Original path requested by user/tool
    
Returns:
    Path object for the discovered file location
    
Raises:
    FileNotFoundError: If file cannot be found in any allowed location

Definition at line 309 of file filesystem.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _edit_context_snippet()

str oc_chat.tools.filesystem._edit_context_snippet ( str edited_text,
str replacement,
int start_line,
int radius = 2 )
protected
A ±*radius*-line, line-numbered snippet of the applied change (ground-truth echo).

Spans the replacement's own line range plus *radius* lines on each side of the
first edited line, so the caller sees WHAT changed and WHERE without a re-read
(F16/F25 direction; 172 generalizes this echo to reads).

Definition at line 723 of file filesystem.py.

Here is the caller graph for this function:

◆ _edit_near_miss_message()

str oc_chat.tools.filesystem._edit_near_miss_message ( str text,
str anchor,
str path )
protected
Name the closest existing line to *anchor*'s first line + the first divergence.

A 0-match re-anchor aid (plan 171): the model gets the nearest line number, a
similarity score, and the column where its ``old_string`` first diverges from
the file, so it can copy an exact anchor from a fresh ``read_file`` instead of
guessing blindly.

Definition at line 687 of file filesystem.py.

Here is the caller graph for this function:

◆ _enforce_write_gates()

"Path | dict" oc_chat.tools.filesystem._enforce_write_gates ( str path)
protected
Resolve *path* through the shared write-side M8 gate; return the target or a block.

The SINGLE enforcement path every write-class tool routes through
(:func:`write_file`, :func:`edit_file`) — one policy, never a parallel path.
In order (M8.0 WU-5 / WU-8; §5.1.2):

1. Hard-deny the protected-write class BEFORE the sandbox clamp (``.git``
   internals, the protected-read secret class, ``*.pth`` / ``sitecustomize`` /
   ``conftest`` import-exec hooks, the clean-exit marker).
2. Clamp to the effective sandbox root (a ``..`` / sibling-prefix / symlink
   escape is refused).
3. Re-check the RESOLVED target (catches a ``..`` / symlink into a ``.git``
   dir the literal string hid).
4. The human-confirm protected-write TIER (config-as-code + the importable
   tree): the ``_human_approved_write`` ContextVar authorizes EXACTLY the
   approved resolved target on a react-loop re-dispatch (a divergence fails
   closed); otherwise a ``requires_confirmation`` block is returned (the
   react-loop relays a y/N; headless fails closed, F70a).

Returns the resolved absolute :class:`~pathlib.Path` on success, or a
``{"error": ...}`` / ``{"requires_confirmation": ...}`` dict on denial / relay.

Definition at line 147 of file filesystem.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _find_worktree_root()

Path | None oc_chat.tools.filesystem._find_worktree_root ( Path | None start = None)
protected
Return nearest ancestor containing a Git ``.git`` dir or file.

Definition at line 215 of file filesystem.py.

Here is the caller graph for this function:

◆ _make_protected_write_block()

dict oc_chat.tools.filesystem._make_protected_write_block ( str target,
str reason )
protected
The human-confirm-tier signal write_file returns (relayed by the react-loop;
failed closed headless by F70a).

Definition at line 121 of file filesystem.py.

Here is the caller graph for this function:

◆ _match_start_lines()

list[int] oc_chat.tools.filesystem._match_start_lines ( str text,
str anchor )
protected
1-based line numbers where each non-overlapping occurrence of *anchor* begins.

Advances by ``len(anchor)`` so the count matches :meth:`str.count` /
:meth:`str.replace` (both non-overlapping), keeping the reported line numbers
consistent with the occurrence count the caller gated on.

Definition at line 671 of file filesystem.py.

Here is the caller graph for this function:

◆ _normalize_newlines()

str oc_chat.tools.filesystem._normalize_newlines ( str content)
protected
Normalize line endings to prevent accumulation of \r\n sequences.

Definition at line 572 of file filesystem.py.

Here is the caller graph for this function:

◆ _outside_sandbox_error()

"str | None" oc_chat.tools.filesystem._outside_sandbox_error ( str path)
protected
A boundary-truthful error for an out-of-sandbox *path* (F25), or ``None``.

When *path* resolves OUTSIDE the effective sandbox root, return a message that
NAMES the root + the resolved absolute path and says whether it exists on disk,
so the model can tell "exists where you cannot reach" from "does not exist"
instead of reading every boundary denial as nonexistence (the F25 confusion that
produced the factually-wrong "the worktree lacks oc_diagnostics" report claim).

M8 basis (design foundation V1.1): the sandbox clamps REACH — every filesystem op
resolves through ``require_inside_sandbox(path, effective_root)`` (§5.1.2) — not
knowledge; the T1 tier has "no determined in-process adversary" and a git-recoverable
local blast radius (§3.2). SECRET existence/paths are withheld by the F46 protected-read
class, which every caller checks FIRST; defensively, this returns ``None`` (disclose
nothing, fall through to not-found) when the resolved out-of-bounds target is itself in
the protected-read class (a symlink-hidden secret). No content is read; the existence
probe is a bare ``os.path.exists`` on the ALREADY-RESOLVED path — no further
out-of-bounds symlink following.

Returns ``None`` for an in-bounds path (not a boundary case).

Definition at line 265 of file filesystem.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _plan_envelope_covers_write()

bool oc_chat.tools.filesystem._plan_envelope_covers_write ( str target)
protected
F70a forward-compat seam: does a signed ``PlanAutonomyEnvelope`` cover this
headless write? M8.0 has no envelope machinery (M8.3), so always ``False`` — a
headless config/importable write is denied, never silently auto-allowed.

Definition at line 114 of file filesystem.py.

◆ _resolve_unclamped()

"tuple[Path, Path]" oc_chat.tools.filesystem._resolve_unclamped ( str path)
protected
Resolve *path* against the effective sandbox root WITHOUT clamping.

Returns ``(abs_root, abs_candidate)`` — the resolved sandbox root and the
resolved absolute path *path* names, whether or not it stays inside the root.
A relative *path* is joined to the root (matching :func:`require_inside_sandbox`);
symlinks / ``..`` are resolved via ``Path.resolve(strict=False)`` so the file
need not exist. The counterpart of the clamped :func:`_safe_path`, used only to
build a truthful boundary error (:func:`_outside_sandbox_error`).

Definition at line 248 of file filesystem.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _safe_path()

Path oc_chat.tools.filesystem._safe_path ( str path)
protected
Resolve *path* against the effective sandbox root; raise if it escapes.

Uses the effective sandbox root (:func:`_sandbox_root` — honouring the
session override / ``OC_CHAT_SANDBOX_ROOT`` / worktree) and the shared
:func:`require_inside_sandbox` ``relative_to`` check, NOT a string
``startswith`` on the hardcoded package ``ROOT``. The old ``startswith``
let a sibling directory sharing the root's path prefix escape the sandbox
(e.g. ``<root>`` vs ``<root>_evil``), and pinned reads to the package root
while ``write_file`` already honoured the effective root — diverging the
read and write sandboxes. M8 F4/F4a/F69/F77; M8.0 WU-1.

Definition at line 195 of file filesystem.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _sandbox_root()

Path oc_chat.tools.filesystem._sandbox_root ( )
protected
Determine the sandbox root for write-side filesystem operations.

Definition at line 242 of file filesystem.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _sandbox_root_info()

tuple[Path, str] oc_chat.tools.filesystem._sandbox_root_info ( )
protected
Return the effective sandbox root and its source label.

Definition at line 226 of file filesystem.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ _was_discovery_rescue()

bool oc_chat.tools.filesystem._was_discovery_rescue ( str requested,
Path resolved )
protected
``True`` when *resolved* differs from the literal in-sandbox resolution of
*requested* — i.e. the discovery fallback rescued a wrong/short path (F13).

Lets a read result SAY resolved-via-discovery with both paths (Change 2) so a
silent rescue stops masking a path error until write time.

Definition at line 298 of file filesystem.py.

Here is the call graph for this function:
Here is the caller graph for this function:

◆ deny_protected_write()

dict oc_chat.tools.filesystem.deny_protected_write ( dict result,
* ,
str note = "" )
Convert a protected-write block into a terminal ``error`` (headless / declined).

Definition at line 139 of file filesystem.py.

◆ edit_file()

dict oc_chat.tools.filesystem.edit_file ( str path,
str old_string,
str new_string,
int expected_count = 1 )
Replace an exact ``old_string`` with ``new_string`` in an EXISTING file.

The anchored-edit primary modify path (plan 171 / F20): the tool-call payload
is the anchor + replacement, not the whole file, so it does not hit the
large-write provider-truncation class. Fail-closed always — ``old_string`` must
occur exactly *expected_count* times (default 1) or nothing is written:

* 0 matches → an error naming the closest near-miss line (first divergence) so
  the model can re-anchor.
* more than *expected_count* → an error listing the occurrence line numbers; no
  write. (Set ``expected_count`` to the reported count to replace them all.)
* ``old_string == new_string`` or an empty ``old_string`` → error (this tool
  never creates files; ``write_file`` owns creation).

Routes through the SAME :func:`_enforce_write_gates` policy as ``write_file``
(sandbox clamp, hard-deny protected-write, human-confirm tier). The write is
atomic (temp + ``os.replace``) and preserves the file's existing newline style
and UTF-8 encoding; binary files are refused. The result echoes the RESOLVED
absolute path + the match line number(s) + a ±2-line context snippet of the
applied change.

Args:
    path: File path (relative to the working directory) to edit in place.
    old_string: The exact text to find (anchor). Must match the file byte-for-byte
        after newline normalization; include enough surrounding context to be unique.
    new_string: The replacement text.
    expected_count: Required number of occurrences of ``old_string`` (default 1).

Definition at line 757 of file filesystem.py.

Here is the call graph for this function:

◆ file_info()

dict oc_chat.tools.filesystem.file_info ( str path)
Return file metadata without reading content.

Definition at line 871 of file filesystem.py.

Here is the call graph for this function:

◆ human_confirmed_write()

Iterator[None] oc_chat.tools.filesystem.human_confirmed_write ( str target)
Authorize exactly *target* (a resolved path string) for one write_file
re-dispatch (F70). For the interactive react-loop only; reset on exit.

Definition at line 104 of file filesystem.py.

◆ list_dir()

dict oc_chat.tools.filesystem.list_dir ( str path = ".",
int max_depth = 2,
bool exclude_hidden = True )
List files and subdirectories within the working directory.

Returns a dict keyed by relative path, each value being
``{"dirs": [...], "files": [...]}``.  ``max_depth=0`` lists only the
target directory itself.

Definition at line 423 of file filesystem.py.

Here is the call graph for this function:

◆ read_chunks()

dict oc_chat.tools.filesystem.read_chunks ( str path,
int chunk_size = 10_000,
int max_chunks = 10,
int offset = 0 )
Read file in optimized chunks with natural boundaries.

Args:
    path: File path relative to working directory
    chunk_size: Target chunk size in characters (default 10000)
    max_chunks: Maximum number of chunks to return (default 10)
    offset: Character offset to start reading from (default 0);
            pass the ``next_offset`` from a previous result to paginate.

Definition at line 937 of file filesystem.py.

Here is the call graph for this function:

◆ read_file()

ReadFileResult | ToolError oc_chat.tools.filesystem.read_file ( str path,
int max_bytes = 50_000,
int offset = 0 )
Read a text file within the working directory with pagination support.

Returns ``{"content": ..., "truncated": bool, "total_size": int, "offset": int,
"next_offset": int|None, "lines_count": int}`` or ``{"error": ...}``.

Args:
    path: File path relative to working directory
    max_bytes: Maximum bytes to read per request (default 50_000 to avoid API truncation)
    offset: Byte offset to start reading from (default 0)

Definition at line 495 of file filesystem.py.

Here is the call graph for this function:

◆ read_prompt_file()

str oc_chat.tools.filesystem.read_prompt_file ( str filepath,
* ,
bool allow_absolute = False )
Read a file destined for the provider-bound prompt, with M8.0 WU-2b (F73)
enforcement.

Prompt-composition reads (``system_prompt_file``, ``include_files``) happen
before the tool gate and their content egresses to the model provider, so
they must honour the same floor as the read tools:

* **No absolute paths** -- there is no out-of-band allowlist yet, so
  ``allow_absolute`` is effectively always ``False`` (the seam for a future
  OOB-allow; F73 "unless OOB-allowed").
* **Clamp to the effective sandbox root** (``_sandbox_root()``); a ``..`` /
  sibling-prefix / symlink escape is refused.
* **Deny the protected-read (secret) class** on the final resolved path.
* **Redact secret patterns** from the returned content (defence in depth).

Returns the file's text, stripped + redacted. Raises :class:`PermissionError`
on a policy denial (absolute, sandbox escape, or protected-read) and
:class:`FileNotFoundError` when the file is missing or empty -- the callers
fall back to the built-in default / skip the include, so a denied secret is
never inserted into the prompt.

Definition at line 1053 of file filesystem.py.

Here is the call graph for this function:

◆ search_files()

dict oc_chat.tools.filesystem.search_files ( str root_dir = ".",
str pattern = "*" )
Search recursively for files matching a glob pattern.

Definition at line 1038 of file filesystem.py.

Here is the call graph for this function:

◆ write_file()

WriteFileResult | ToolError oc_chat.tools.filesystem.write_file ( str path,
str content,
int chunk_size = 0,
int max_chunks = 0 )
Write (or overwrite) a text file within a working directory tree.

Files whose basename starts with ``_tmp`` are automatically redirected
to ``logs/`` so that repository root stays tidy.

Args:
    path: Destination path relative to working directory
    content: Text content to write
    chunk_size: Target chunk size in bytes for large content (default 0 = no chunking)
    max_chunks: Maximum number of chunks to return (default 0 = no chunking)

Definition at line 577 of file filesystem.py.

Here is the call graph for this function:

Variable Documentation

◆ _HARDCODED_ROOT

oc_chat.tools.filesystem._HARDCODED_ROOT = Path(__file__).parent.parent.parent.resolve()
protected

Definition at line 86 of file filesystem.py.

◆ _human_approved_write

contextvars oc_chat.tools.filesystem._human_approved_write
protected
Initial value:
1= contextvars.ContextVar(
2 "oc_chat_human_approved_write", default=None)

Definition at line 99 of file filesystem.py.

◆ _PROTECTED_READ_MSG

str oc_chat.tools.filesystem._PROTECTED_READ_MSG = "Reading this file is not permitted: it is in the protected-read (secret) class."
protected

Definition at line 91 of file filesystem.py.

◆ _PROTECTED_WRITE_MSG

str oc_chat.tools.filesystem._PROTECTED_WRITE_MSG = "Writing this file is not permitted: it is in the protected-write class (integrity / config-as-code / secret)."
protected

Definition at line 92 of file filesystem.py.

◆ ROOT

oc_chat.tools.filesystem.ROOT = _HARDCODED_ROOT

Definition at line 87 of file filesystem.py.

◆ SCHEMAS

list oc_chat.tools.filesystem.SCHEMAS

Definition at line 1111 of file filesystem.py.

◆ TOOLS

dict oc_chat.tools.filesystem.TOOLS
Initial value:
1= {
2 "list_dir": list_dir,
3 "read_file": read_file,
4 "write_file": write_file,
5 "edit_file": edit_file,
6 "search_files": search_files,
7 "file_info": file_info,
8 "read_chunks": read_chunks,
9}

Definition at line 1101 of file filesystem.py.