From a20a1ff497ed8653dfb9fc94214e4fa3884c01ac Mon Sep 17 00:00:00 2001 From: Nikolay Petrov Date: Thu, 22 Oct 2026 15:18:26 -0700 Subject: [PATCH 1/2] fix(repo): a bare root-level name is not a run of the arm's file A bare `lint` resolves through PATH, not to the workspace file, so the first word of a command counts only as ./lint, under the workspace path or after an interpreter. Answers the review question on uptake.py:109. Signed-off-by: Nikolay Petrov --- .../20261022_230000_napetrov_uptake-bare-root-name.md | 6 ++++++ dokimi/docs/repo-evaluation-contract.md | 2 +- dokimi/dokimi/repos/uptake.py | 10 +++++----- dokimi/tests/test_repo_uptake.py | 7 ++++--- 4 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 dokimi/changelog.d/20261022_230000_napetrov_uptake-bare-root-name.md diff --git a/dokimi/changelog.d/20261022_230000_napetrov_uptake-bare-root-name.md b/dokimi/changelog.d/20261022_230000_napetrov_uptake-bare-root-name.md new file mode 100644 index 00000000..b28e3cca --- /dev/null +++ b/dokimi/changelog.d/20261022_230000_napetrov_uptake-bare-root-name.md @@ -0,0 +1,6 @@ +### Fixed + +- **repo run: uptake of a root-level file** — a bare `lint` at the start of a + command no longer counts as a run of the arm's `lint` file, since a bare name + resolves through `PATH`; it counts as `./lint`, under the workspace path, or + after an interpreter. diff --git a/dokimi/docs/repo-evaluation-contract.md b/dokimi/docs/repo-evaluation-contract.md index f3fbc398..0d33778d 100644 --- a/dokimi/docs/repo-evaluation-contract.md +++ b/dokimi/docs/repo-evaluation-contract.md @@ -389,7 +389,7 @@ For a `patch:` arm with `added` files or `mcp_servers`, `result.json` has an `mcp_servers` list what it added (sorted); `commands` counts command events that name an added file as a workspace path (`tools/fmt`, `./tools/fmt`, `/eval/run/workspace/tools/fmt`, not `cd tools && ./fmt`; a file -with no `/` only as `./name`, under the workspace path, first in a command or after an interpreter, so +with no `/` only as `./name`, under the workspace path or after an interpreter (a bare `name` resolves through `PATH`), so `npm run lint` does not name a `lint` file), `reads` the read events of one (a read of an added script counts as use), `mcp_calls` the calls of the servers' tools (`mcp____`), none of them a call the harness denied; `used` diff --git a/dokimi/dokimi/repos/uptake.py b/dokimi/dokimi/repos/uptake.py index 9be565d0..674ca665 100644 --- a/dokimi/dokimi/repos/uptake.py +++ b/dokimi/dokimi/repos/uptake.py @@ -13,8 +13,8 @@ - **commands**: command events whose text names one of those files, as a path relative to the workspace (``tools/fmt``, ``./tools/fmt``, ``bash tools/fmt``) or under the workspace's absolute path. A file at the workspace root is named only as ``./name``, under the - absolute path, as the first word of a command (or of a ``;``/``&&``/``|`` part) or after a shell or - interpreter (``sh run.sh``): ``npm run lint`` does not name a ``lint`` file. A command run as ``cd tools && ./fmt`` + absolute path or after a shell or interpreter (``sh run.sh``): a bare ``lint`` resolves through ``PATH``, not to + the workspace file, and ``npm run lint`` does not name a ``lint`` file. A command run as ``cd tools && ./fmt`` is not counted; - **reads**: read events of one of those files (reading a script's text counts: it is the agent engaging with what the arm added, not only running it); @@ -92,8 +92,8 @@ def arm_uptake(events: list[dict[str, Any]] | None, files: list[str], servers: l def _path_pattern(files: list[str], workdir: str) -> re.Pattern[str] | None: """One of ``files`` as a whole word of a command: relative (optionally ``./``) or under ``workdir``. - A file with no ``/`` is a common word, so it must be ``./``-prefixed, under ``workdir``, first in a command - or run by an interpreter.""" + A file with no ``/`` is a common word, and bare it resolves through ``PATH``, so it must be ``./``-prefixed, + under ``workdir`` or run by an interpreter.""" if not files: return None root = re.escape(workdir.rstrip("/") + "/") @@ -106,7 +106,7 @@ def _path_pattern(files: list[str], workdir: str) -> re.Pattern[str] | None: parts.append(rf"(?:^|(?<=[\s'\"`=;&|(<>])|(?<=\./)|(?<={root}))(?:{paths}){end}") if flat: names = "|".join(re.escape(f) for f in flat) - parts.append(rf"(?:^\s*(?:\./)?|[;&|(]\s*(?:\./)?|(?:^|(?<=\s)){_INTERPRETERS}\s+|(?<=[\s'\"`=;&|(<>])\./|(?<={root}))" + parts.append(rf"(?:^\s*\./|[;&|(]\s*\./|(?:^|(?<=\s)){_INTERPRETERS}\s+|(?<=[\s'\"`=;&|(<>])\./|(?<={root}))" rf"(?:{names}){end}") return re.compile("|".join(parts)) diff --git a/dokimi/tests/test_repo_uptake.py b/dokimi/tests/test_repo_uptake.py index d9ab8c63..ccedc4f4 100644 --- a/dokimi/tests/test_repo_uptake.py +++ b/dokimi/tests/test_repo_uptake.py @@ -235,13 +235,14 @@ def test_compare_counts_the_uptake_of_the_models_it_compares(setup, monkeypatch) assert m2["uptake"] == {arm: {"cells": 2, "used": 0, "unmeasured": 0}} and _used_none(m2) -@pytest.mark.parametrize("text", ["npm run lint", "docker run img", "git log -- build", "cat lint", "make build"]) +@pytest.mark.parametrize("text", ["npm run lint", "docker run img", "git log -- build", "cat lint", "make build", + "lint x", "cd d && lint", "run"]) def test_a_root_level_added_file_is_not_named_by_a_common_word_of_a_command(text): for name in ("lint", "run", "build"): - assert arm_uptake([_cmd(text)], [name], [], WS)["commands"] == (1 if text.split()[0] == name else 0) + assert arm_uptake([_cmd(text)], [name], [], WS)["commands"] == 0 -@pytest.mark.parametrize("text", ["./lint x", "lint x", f"{WS}/lint", "cd d && lint", "x; ./lint", "sh lint", "bash ./lint a", +@pytest.mark.parametrize("text", ["./lint x", f"{WS}/lint", "x; ./lint", "sh lint", "bash ./lint a", "python3 lint"]) def test_a_root_level_added_file_is_named_by_a_path_or_as_a_command(text): assert arm_uptake([_cmd(text)], ["lint"], [], WS)["commands"] == 1 From def5208adac76a15f38d38b8398061f60ee25a01 Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Fri, 23 Oct 2026 12:32:44 +0000 Subject: [PATCH 2/2] Drop dead separator alternative from the uptake pattern; pin its cases Signed-off-by: Anton Volkov --- dokimi/dokimi/repos/uptake.py | 2 +- dokimi/tests/test_repo_uptake.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dokimi/dokimi/repos/uptake.py b/dokimi/dokimi/repos/uptake.py index 674ca665..9e00b20d 100644 --- a/dokimi/dokimi/repos/uptake.py +++ b/dokimi/dokimi/repos/uptake.py @@ -106,7 +106,7 @@ def _path_pattern(files: list[str], workdir: str) -> re.Pattern[str] | None: parts.append(rf"(?:^|(?<=[\s'\"`=;&|(<>])|(?<=\./)|(?<={root}))(?:{paths}){end}") if flat: names = "|".join(re.escape(f) for f in flat) - parts.append(rf"(?:^\s*\./|[;&|(]\s*\./|(?:^|(?<=\s)){_INTERPRETERS}\s+|(?<=[\s'\"`=;&|(<>])\./|(?<={root}))" + parts.append(rf"(?:^\s*\./|(?:^|(?<=\s)){_INTERPRETERS}\s+|(?<=[\s'\"`=;&|(<>])\./|(?<={root}))" rf"(?:{names}){end}") return re.compile("|".join(parts)) diff --git a/dokimi/tests/test_repo_uptake.py b/dokimi/tests/test_repo_uptake.py index ccedc4f4..2f6be8fa 100644 --- a/dokimi/tests/test_repo_uptake.py +++ b/dokimi/tests/test_repo_uptake.py @@ -236,7 +236,7 @@ def test_compare_counts_the_uptake_of_the_models_it_compares(setup, monkeypatch) @pytest.mark.parametrize("text", ["npm run lint", "docker run img", "git log -- build", "cat lint", "make build", - "lint x", "cd d && lint", "run"]) + "lint x", "cd d && lint", "run", "x | lint", "(lint)", ";lint"]) def test_a_root_level_added_file_is_not_named_by_a_common_word_of_a_command(text): for name in ("lint", "run", "build"): assert arm_uptake([_cmd(text)], [name], [], WS)["commands"] == 0