align-agent 0.4.3__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.
- align_agent-0.4.3/LICENSE +21 -0
- align_agent-0.4.3/PKG-INFO +224 -0
- align_agent-0.4.3/README.md +187 -0
- align_agent-0.4.3/align_agent/__init__.py +11 -0
- align_agent-0.4.3/align_agent/_audit/common.py +58 -0
- align_agent-0.4.3/align_agent/_audit/deepswe.py +260 -0
- align_agent-0.4.3/align_agent/_audit/excerpts.py +203 -0
- align_agent-0.4.3/align_agent/_audit/extract.py +798 -0
- align_agent-0.4.3/align_agent/_audit/materialize.py +624 -0
- align_agent-0.4.3/align_agent/_audit/score.py +325 -0
- align_agent-0.4.3/align_agent/_audit/shellparse.py +1141 -0
- align_agent-0.4.3/align_agent/_audit/tarsur.py +153 -0
- align_agent-0.4.3/align_agent/_vendor/src/__init__.py +3 -0
- align_agent-0.4.3/align_agent/_vendor/src/harness/__init__.py +13 -0
- align_agent-0.4.3/align_agent/_vendor/src/harness/arms.py +524 -0
- align_agent-0.4.3/align_agent/_vendor/src/harness/completion.py +104 -0
- align_agent-0.4.3/align_agent/_vendor/src/harness/dependency_notice.py +616 -0
- align_agent-0.4.3/align_agent/_vendor/src/harness/freshness_hook.py +131 -0
- align_agent-0.4.3/align_agent/_vendor/src/harness/lean.py +41 -0
- align_agent-0.4.3/align_agent/_vendor/src/harness/mini_real.py +1040 -0
- align_agent-0.4.3/align_agent/_vendor/src/harness/miniswe_scaffold.py +661 -0
- align_agent-0.4.3/align_agent/_vendor/src/harness/tail_diff.py +292 -0
- align_agent-0.4.3/align_agent/_vendor/src/harness/viewport.py +220 -0
- align_agent-0.4.3/align_agent/_vendor/src/metrics/__init__.py +38 -0
- align_agent-0.4.3/align_agent/_vendor/src/metrics/coherence.py +354 -0
- align_agent-0.4.3/align_agent/_vendor/src/metrics/corruption_oracles.py +139 -0
- align_agent-0.4.3/align_agent/_vendor/src/metrics/replay.py +399 -0
- align_agent-0.4.3/align_agent/_vendor/src/metrics/statistics.py +185 -0
- align_agent-0.4.3/align_agent/_vendor/src/metrics/verification.py +104 -0
- align_agent-0.4.3/align_agent/_vendor/src/parsers/__init__.py +33 -0
- align_agent-0.4.3/align_agent/_vendor/src/parsers/base_parser.py +349 -0
- align_agent-0.4.3/align_agent/_vendor/src/parsers/file_events.py +806 -0
- align_agent-0.4.3/align_agent/_vendor/src/parsers/miniswe_parser.py +96 -0
- align_agent-0.4.3/align_agent/_vendor/src/parsers/openhands_parser.py +414 -0
- align_agent-0.4.3/align_agent/_vendor/src/parsers/sweagent_parser.py +180 -0
- align_agent-0.4.3/align_agent/_vendor/src/parsers/terminus_parser.py +106 -0
- align_agent-0.4.3/align_agent/cli.py +101 -0
- align_agent-0.4.3/align_agent/core.py +161 -0
- align_agent-0.4.3/align_agent/run.py +84 -0
- align_agent-0.4.3/align_agent/sessions.py +985 -0
- align_agent-0.4.3/align_agent/writethrough/__init__.py +4 -0
- align_agent-0.4.3/align_agent/writethrough/_lib/__init__.py +1 -0
- align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/__init__.py +3 -0
- align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/harness/__init__.py +1 -0
- align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/harness/dependency_notice.py +616 -0
- align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/harness/lean.py +41 -0
- align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/harness/tail_diff.py +292 -0
- align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/harness/viewport.py +220 -0
- align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/metrics/__init__.py +38 -0
- align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/metrics/replay.py +399 -0
- align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/parsers/__init__.py +1 -0
- align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/parsers/file_events.py +806 -0
- align_agent-0.4.3/align_agent/writethrough/claude_code.py +81 -0
- align_agent-0.4.3/align_agent/writethrough/engine.py +647 -0
- align_agent-0.4.3/align_agent/writethrough/hooks.py +227 -0
- align_agent-0.4.3/align_agent/writethrough/mcp_server.py +125 -0
- align_agent-0.4.3/align_agent/writethrough/protocol.py +68 -0
- align_agent-0.4.3/align_agent.egg-info/PKG-INFO +224 -0
- align_agent-0.4.3/align_agent.egg-info/SOURCES.txt +63 -0
- align_agent-0.4.3/align_agent.egg-info/dependency_links.txt +1 -0
- align_agent-0.4.3/align_agent.egg-info/entry_points.txt +6 -0
- align_agent-0.4.3/align_agent.egg-info/requires.txt +7 -0
- align_agent-0.4.3/align_agent.egg-info/top_level.txt +1 -0
- align_agent-0.4.3/pyproject.toml +35 -0
- align_agent-0.4.3/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The ALIGN authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: align-agent
|
|
3
|
+
Version: 0.4.3
|
|
4
|
+
Summary: Find edits a coding agent made against a stale view of a file, and keep agents' views current with a write-through hook (Claude Code, any hook system, any MCP client, mini-swe-agent)
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 The ALIGN authors
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
License-File: LICENSE
|
|
30
|
+
Requires-Dist: scipy>=1.11
|
|
31
|
+
Provides-Extra: run
|
|
32
|
+
Requires-Dist: mini-swe-agent==2.4.6; extra == "run"
|
|
33
|
+
Requires-Dist: Jinja2==3.1.6; extra == "run"
|
|
34
|
+
Requires-Dist: PyYAML==6.0.3; extra == "run"
|
|
35
|
+
Requires-Dist: pydantic==2.13.4; extra == "run"
|
|
36
|
+
Dynamic: license-file
|
|
37
|
+
|
|
38
|
+
# ALIGN: Agent Line-Index Guard and Notices
|
|
39
|
+
|
|
40
|
+
A coding agent edits files through what it last saw of them, and its own edits make those views
|
|
41
|
+
out of date. A `sed -i`, a script, a formatter or `git checkout` changes a file the agent read, and
|
|
42
|
+
nothing tells it; a later edit by line number or by remembered text then lands on the wrong lines,
|
|
43
|
+
is applied twice, or misses. ALIGN keeps the agent's view current while it works, and finds
|
|
44
|
+
these edits in recorded agent trajectories.
|
|
45
|
+
|
|
46
|
+
## Set up
|
|
47
|
+
|
|
48
|
+
You need Python 3.10 or later as `python3` on your `PATH` (`python3 --version`). Nothing else: the
|
|
49
|
+
engine uses only the standard library.
|
|
50
|
+
|
|
51
|
+
### 1. Install the commands
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pipx install git+https://github.com/VA24d/align-agent@v0.4.3 # recommended: its own environment, commands on PATH
|
|
55
|
+
# or, in a virtual environment:
|
|
56
|
+
python3 -m venv ~/.align && ~/.align/bin/pip install git+https://github.com/VA24d/align-agent@v0.4.3
|
|
57
|
+
export PATH="$HOME/.align/bin:$PATH" # add this line to ~/.zshrc or ~/.bashrc
|
|
58
|
+
align-agent --version # prints: align-agent 0.4.3
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
If the shell says `command not found: align-agent`, the install folder is not on `PATH`: run
|
|
62
|
+
`pipx ensurepath` (pipx) or the `export` line above, then open a new terminal. The Claude Code
|
|
63
|
+
plugin (step 2) does not need this step; the other agents and `analyze` do.
|
|
64
|
+
|
|
65
|
+
### 2. Turn it on in your agent
|
|
66
|
+
|
|
67
|
+
**Claude Code.** Inside Claude Code:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
/plugin marketplace add VA24d/align-agent
|
|
71
|
+
/plugin install align-write-through@align-agent
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Restart Claude Code, then run `/hooks`: `SessionStart`, `PreToolUse` (Bash) and `PostToolUse`
|
|
75
|
+
should list the plugin's `hook.py`.
|
|
76
|
+
|
|
77
|
+
**Codex CLI.** Copy [`integrations/codex/hooks.json`](integrations/codex/hooks.json) to
|
|
78
|
+
`~/.codex/hooks.json` (all projects) or `.codex/hooks.json` (one project), start Codex and trust the
|
|
79
|
+
hooks once with `/hooks`.
|
|
80
|
+
|
|
81
|
+
**Gemini CLI, Cursor, Copilot CLI, OpenCode, Cline, MCP clients.** Copy the file for your agent
|
|
82
|
+
from [`integrations/`](integrations/README.md) to the place its row names.
|
|
83
|
+
|
|
84
|
+
### 3. Check that it works
|
|
85
|
+
|
|
86
|
+
In a scratch folder:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
mkdir -p /tmp/align-check && cd /tmp/align-check && git init -q
|
|
90
|
+
seq 1 40 | sed 's/.*/v& = &/' > demo.py # 40 lines: v1 = 1 ... v40 = 40
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Start the agent there and ask: *"Run `cat -n demo.py`. Then run
|
|
94
|
+
`perl -i -pe 'print "import os\n" if $. == 1' demo.py`. Then, using the line numbers from the first
|
|
95
|
+
command, change line 30 with `perl -i -pe 's/.*/v30 = 300/ if $. == 30' demo.py`."* (`perl -i` works
|
|
96
|
+
the same on Linux and macOS, where `sed -i` differs.) With ALIGN on:
|
|
97
|
+
|
|
98
|
+
- the first `perl` command's output ends with a `[TAIL DIFF: File 'demo.py' mutated on disk vs your held view]`
|
|
99
|
+
block that shows the inserted line, and a `[LINE NUMBERS: ... lines 1-40 are now 2-41 (+1)]` note;
|
|
100
|
+
- if the agent still sends line 30 (now `v29 = 29`), ALIGN refuses that command once with
|
|
101
|
+
`[STALE LINE NUMBERS]` and shows lines 25-35 as they are now, and the agent reissues it.
|
|
102
|
+
|
|
103
|
+
In Claude Code, press `ctrl+o` to see the full tool output. If nothing appears, check `/hooks`
|
|
104
|
+
(Claude Code, Codex) or your agent's hook log, and that `python3 --version` is 3.10 or later.
|
|
105
|
+
|
|
106
|
+
### 4. Audit sessions you already ran
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
align-agent analyze ~/.claude/projects/ # every Claude Code project on this machine
|
|
110
|
+
align-agent analyze ~/.codex/sessions/ # every Codex rollout
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Claude Code deletes session logs after 30 days unless `cleanupPeriodDays` in
|
|
114
|
+
`~/.claude/settings.json` is set higher.
|
|
115
|
+
|
|
116
|
+
### Update or remove
|
|
117
|
+
|
|
118
|
+
`pipx upgrade align-agent` or `pipx uninstall align-agent`; in Claude Code, `/plugin` lists the plugin
|
|
119
|
+
to update or uninstall. ALIGN's state (copies of viewed files, readable only by you) is in
|
|
120
|
+
`~/.cache/align-agent` and can be deleted at any time.
|
|
121
|
+
|
|
122
|
+
## Keep your agent's view current
|
|
123
|
+
|
|
124
|
+
After every tool call, ALIGN appends to what the model sees the diff of each file it has seen
|
|
125
|
+
that changed since, with current line numbers, a note for each stretch of lines that moved without
|
|
126
|
+
being shown, and the remaining uses in other files of any definition a change removed or
|
|
127
|
+
re-signatured. Before an in-place edit by line number (`sed`, `perl`, `awk`, `ed`, `ex`) on lines the model has
|
|
128
|
+
not seen numbered as they are now, it refuses
|
|
129
|
+
the command once and shows those lines. Nothing earlier in the conversation is rewritten, so the
|
|
130
|
+
prompt cache is kept.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
pip install git+https://github.com/VA24d/align-agent@v0.4.3 # the write-through engine itself uses only the standard library
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
| Agent | Set up |
|
|
137
|
+
|---|---|
|
|
138
|
+
| Claude Code | `/plugin marketplace add VA24d/align-agent`, then `/plugin install align-write-through@align-agent` |
|
|
139
|
+
| Codex CLI, Gemini CLI, Cursor, GitHub Copilot CLI, OpenCode, Cline | the config for your agent in [`integrations/`](integrations/README.md) |
|
|
140
|
+
| Any MCP client (Goose, Antigravity CLI, IDEs, ...) | `align-agent-mcp` as an MCP server, with the built-in shell turned off ([`integrations/mcp`](integrations/mcp/README.md)) |
|
|
141
|
+
| Your own harness | `align-agent-wt`: one JSON event on stdin, `{"context": ...}` or `{"deny": ...}` back; or `from align_agent.writethrough import Session` |
|
|
142
|
+
|
|
143
|
+
In a 2,000-file repository a call takes well under 0.5 s; in Claude Code a call took about 0.1 s,
|
|
144
|
+
Python start-up included.
|
|
145
|
+
|
|
146
|
+
To compute diffs, ALIGN keeps a copy of each file the model viewed in its state folder
|
|
147
|
+
(`~/.cache/align-agent`, or the plugin's data folder), readable only by you. Files named like
|
|
148
|
+
keys, `.env` files and credential stores are never copied or diffed; copies no longer needed are
|
|
149
|
+
deleted after each call, and sessions idle for 7 days are deleted (`ALIGN_KEEP_DAYS`).
|
|
150
|
+
|
|
151
|
+
## Find stale edits in trajectories
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
pip install git+https://github.com/VA24d/align-agent@v0.4.3
|
|
155
|
+
align-agent analyze examples/teleport_control.json examples/teleport_diff.json
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Or, with nothing to install or build (image for amd64 and arm64, examples included):
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
docker run --rm ghcr.io/va24d/align-agent:0.4.3 analyze /opt/align-agent/examples/teleport_control.json
|
|
162
|
+
docker run --rm -v "$PWD:/work" ghcr.io/va24d/align-agent:0.4.3 analyze /work/my_trajectories/
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## `align-agent analyze`
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
align-agent analyze PATH [PATH ...] [--format F] [--cwd DIR] [--json OUT] [--quiet]
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Reads trajectory files, or directories of them. The format is detected:
|
|
172
|
+
|
|
173
|
+
| `--format` | Source |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `mini` | mini-swe-agent trajectories (text actions, `<returncode>` observations) |
|
|
176
|
+
| `sweagent` | SWE-agent `.traj` |
|
|
177
|
+
| `openhands` | OpenHands completion logs and event streams |
|
|
178
|
+
| `terminus` | Terminus 2 episodes |
|
|
179
|
+
| `atif` | Harbor ATIF trials, e.g. the DeepSWE leaderboard |
|
|
180
|
+
| `toolcalls` | OpenAI-style messages with a `bash` tool |
|
|
181
|
+
| `claudecode` | Claude Code session logs (`~/.claude/projects/*/*.jsonl`) |
|
|
182
|
+
| `codex` | Codex CLI session rollouts (`~/.codex/sessions`) |
|
|
183
|
+
|
|
184
|
+
A `.jsonl` file is a Claude Code or Codex session, told apart by the shape of its lines, and a directory is searched for `*.json` and `*.jsonl`. To audit your own sessions:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
align-agent analyze ~/.claude/projects/-home-me-myrepo/ # every session of one project, and its subagents
|
|
188
|
+
align-agent analyze ~/.codex/sessions/2026/10/ --json codex.json
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Paths resolve against the working directory the log records. In Claude Code, `Read` is a view of the lines it returned, `Edit` and `MultiEdit` are edits anchored on quoted text, `Write` is a whole-file write, and `Bash` goes through the shell parser. Claude Code's changed-file notices count as views. A subagent shares the workspace but not the context, so in the parent session its writes are writes the parent did not see; its own transcript is analyzed as a separate trajectory. In Codex, `apply_patch` hunks are anchored edits, `*** Add File` is a whole-file write, and shell commands (including those a code-mode `exec` script passes to `exec_command`, and patches it passes to `apply_patch`) go through the shell parser. When one script runs several commands and prints their results under headers (`### 1`, `--- 0 ---`, ...), the output is split back per command. When Codex cuts the middle of a long output (`…N tokens truncated…`), the command before the cut keeps the head of its output, the last command whose header was cut keeps the tail, and commands lost entirely in the cut are left without output. A context compaction starts over: views from before it no longer count. `--json` gives per-session counts of every line and tool the adapters did not map. Not covered yet: a Codex subagent's rollout is analyzed on its own, not as unseen writes in its parent; a script that prints its commands' results without headers keeps one combined output.
|
|
192
|
+
|
|
193
|
+
Each command is parsed into file operations: views (`cat`, `sed -n`, `grep -n`, editor views, file paths quoted in compiler and test output), anchored edits (`sed` by line or pattern, `str_replace`, Python `.replace`, patches) and whole-file writes. Each view is bound to the version of the file it showed. An anchored edit to a file whose last view no longer matches the file is a **stale edit**. For each one, the output gives the step that held the view, whether that view came from the shell or an editor tool, which unseen writes changed the file, and a class:
|
|
194
|
+
|
|
195
|
+
- `MATERIALIZED-M3`: the edit went wrong because of the stale view (applied twice, or a line anchor that the unseen writes moved).
|
|
196
|
+
- `BENIGN-FORWARDED`, `BENIGN-DISJOINT`: the unseen change was accounted for, or did not touch what the edit relied on.
|
|
197
|
+
- `FAILED-OWN-WRITE`, `FAILED-UNRELATED`, `SPURIOUS`: the edit failed for its own reasons, or the unseen write changed nothing.
|
|
198
|
+
- `INDETERMINATE`: the transcript does not show enough to decide.
|
|
199
|
+
|
|
200
|
+
The summary reports the stale-edit rate over anchored edits to viewed files, with a 95% Clopper–Pearson interval. `--json` writes every edit.
|
|
201
|
+
|
|
202
|
+
## `align-agent run`
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
align-agent run --repo DIR --task TEXT_OR_FILE --model NAME --base-url URL \
|
|
206
|
+
[--arm control|viewport|diff|lean|dependency] [--image IMAGE] [--out trajectory.json]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Needs the `[run]` extra: `pip install 'align-agent[run] @ git+https://github.com/VA24d/align-agent@v0.4.3'`. Runs mini-swe-agent 2.4.6 against any OpenAI-compatible endpoint. The API key is read from `$OPENAI_API_KEY`, or from the variable that `--api-key-env` names. After each command, the hook finds the files the command changed by content hash and appends one of these to that command's output:
|
|
210
|
+
|
|
211
|
+
- `viewport`: a numbered window around each change.
|
|
212
|
+
- `diff`: the net diff since the agent last saw the file.
|
|
213
|
+
- `lean`: the same diff, skipped for writes the agent already knows (a whole file it supplied, a file the command printed).
|
|
214
|
+
- `dependency`: the diff, plus the remaining uses in other files of any definition the write removed or re-signatured.
|
|
215
|
+
|
|
216
|
+
Nothing earlier in the conversation is rewritten, so the prompt stays cacheable. With `--image`, commands run in that container with the repository mounted. Without it, use `--no-sandbox` only inside a container. The trajectory is written in mini-swe-agent's format, so `analyze` reads it back.
|
|
217
|
+
|
|
218
|
+
## What the numbers mean
|
|
219
|
+
|
|
220
|
+
The analyzer is the scorer and classifier of the study behind this tool, unchanged. On the published trajectories it was built for, an independent hand audit of every stale edit agreed with a second blind labeller at κ = 0.73 on the classes. The `MATERIALIZED` class comes from rules, not execution, and errs both ways. Treat a `MATERIALIZED` label as a pointer to a step worth reading, not as a verdict.
|
|
221
|
+
|
|
222
|
+
## Licence
|
|
223
|
+
|
|
224
|
+
MIT. The bundled examples are trajectories from our own runs.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# ALIGN: Agent Line-Index Guard and Notices
|
|
2
|
+
|
|
3
|
+
A coding agent edits files through what it last saw of them, and its own edits make those views
|
|
4
|
+
out of date. A `sed -i`, a script, a formatter or `git checkout` changes a file the agent read, and
|
|
5
|
+
nothing tells it; a later edit by line number or by remembered text then lands on the wrong lines,
|
|
6
|
+
is applied twice, or misses. ALIGN keeps the agent's view current while it works, and finds
|
|
7
|
+
these edits in recorded agent trajectories.
|
|
8
|
+
|
|
9
|
+
## Set up
|
|
10
|
+
|
|
11
|
+
You need Python 3.10 or later as `python3` on your `PATH` (`python3 --version`). Nothing else: the
|
|
12
|
+
engine uses only the standard library.
|
|
13
|
+
|
|
14
|
+
### 1. Install the commands
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pipx install git+https://github.com/VA24d/align-agent@v0.4.3 # recommended: its own environment, commands on PATH
|
|
18
|
+
# or, in a virtual environment:
|
|
19
|
+
python3 -m venv ~/.align && ~/.align/bin/pip install git+https://github.com/VA24d/align-agent@v0.4.3
|
|
20
|
+
export PATH="$HOME/.align/bin:$PATH" # add this line to ~/.zshrc or ~/.bashrc
|
|
21
|
+
align-agent --version # prints: align-agent 0.4.3
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
If the shell says `command not found: align-agent`, the install folder is not on `PATH`: run
|
|
25
|
+
`pipx ensurepath` (pipx) or the `export` line above, then open a new terminal. The Claude Code
|
|
26
|
+
plugin (step 2) does not need this step; the other agents and `analyze` do.
|
|
27
|
+
|
|
28
|
+
### 2. Turn it on in your agent
|
|
29
|
+
|
|
30
|
+
**Claude Code.** Inside Claude Code:
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
/plugin marketplace add VA24d/align-agent
|
|
34
|
+
/plugin install align-write-through@align-agent
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Restart Claude Code, then run `/hooks`: `SessionStart`, `PreToolUse` (Bash) and `PostToolUse`
|
|
38
|
+
should list the plugin's `hook.py`.
|
|
39
|
+
|
|
40
|
+
**Codex CLI.** Copy [`integrations/codex/hooks.json`](integrations/codex/hooks.json) to
|
|
41
|
+
`~/.codex/hooks.json` (all projects) or `.codex/hooks.json` (one project), start Codex and trust the
|
|
42
|
+
hooks once with `/hooks`.
|
|
43
|
+
|
|
44
|
+
**Gemini CLI, Cursor, Copilot CLI, OpenCode, Cline, MCP clients.** Copy the file for your agent
|
|
45
|
+
from [`integrations/`](integrations/README.md) to the place its row names.
|
|
46
|
+
|
|
47
|
+
### 3. Check that it works
|
|
48
|
+
|
|
49
|
+
In a scratch folder:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
mkdir -p /tmp/align-check && cd /tmp/align-check && git init -q
|
|
53
|
+
seq 1 40 | sed 's/.*/v& = &/' > demo.py # 40 lines: v1 = 1 ... v40 = 40
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Start the agent there and ask: *"Run `cat -n demo.py`. Then run
|
|
57
|
+
`perl -i -pe 'print "import os\n" if $. == 1' demo.py`. Then, using the line numbers from the first
|
|
58
|
+
command, change line 30 with `perl -i -pe 's/.*/v30 = 300/ if $. == 30' demo.py`."* (`perl -i` works
|
|
59
|
+
the same on Linux and macOS, where `sed -i` differs.) With ALIGN on:
|
|
60
|
+
|
|
61
|
+
- the first `perl` command's output ends with a `[TAIL DIFF: File 'demo.py' mutated on disk vs your held view]`
|
|
62
|
+
block that shows the inserted line, and a `[LINE NUMBERS: ... lines 1-40 are now 2-41 (+1)]` note;
|
|
63
|
+
- if the agent still sends line 30 (now `v29 = 29`), ALIGN refuses that command once with
|
|
64
|
+
`[STALE LINE NUMBERS]` and shows lines 25-35 as they are now, and the agent reissues it.
|
|
65
|
+
|
|
66
|
+
In Claude Code, press `ctrl+o` to see the full tool output. If nothing appears, check `/hooks`
|
|
67
|
+
(Claude Code, Codex) or your agent's hook log, and that `python3 --version` is 3.10 or later.
|
|
68
|
+
|
|
69
|
+
### 4. Audit sessions you already ran
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
align-agent analyze ~/.claude/projects/ # every Claude Code project on this machine
|
|
73
|
+
align-agent analyze ~/.codex/sessions/ # every Codex rollout
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Claude Code deletes session logs after 30 days unless `cleanupPeriodDays` in
|
|
77
|
+
`~/.claude/settings.json` is set higher.
|
|
78
|
+
|
|
79
|
+
### Update or remove
|
|
80
|
+
|
|
81
|
+
`pipx upgrade align-agent` or `pipx uninstall align-agent`; in Claude Code, `/plugin` lists the plugin
|
|
82
|
+
to update or uninstall. ALIGN's state (copies of viewed files, readable only by you) is in
|
|
83
|
+
`~/.cache/align-agent` and can be deleted at any time.
|
|
84
|
+
|
|
85
|
+
## Keep your agent's view current
|
|
86
|
+
|
|
87
|
+
After every tool call, ALIGN appends to what the model sees the diff of each file it has seen
|
|
88
|
+
that changed since, with current line numbers, a note for each stretch of lines that moved without
|
|
89
|
+
being shown, and the remaining uses in other files of any definition a change removed or
|
|
90
|
+
re-signatured. Before an in-place edit by line number (`sed`, `perl`, `awk`, `ed`, `ex`) on lines the model has
|
|
91
|
+
not seen numbered as they are now, it refuses
|
|
92
|
+
the command once and shows those lines. Nothing earlier in the conversation is rewritten, so the
|
|
93
|
+
prompt cache is kept.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install git+https://github.com/VA24d/align-agent@v0.4.3 # the write-through engine itself uses only the standard library
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
| Agent | Set up |
|
|
100
|
+
|---|---|
|
|
101
|
+
| Claude Code | `/plugin marketplace add VA24d/align-agent`, then `/plugin install align-write-through@align-agent` |
|
|
102
|
+
| Codex CLI, Gemini CLI, Cursor, GitHub Copilot CLI, OpenCode, Cline | the config for your agent in [`integrations/`](integrations/README.md) |
|
|
103
|
+
| Any MCP client (Goose, Antigravity CLI, IDEs, ...) | `align-agent-mcp` as an MCP server, with the built-in shell turned off ([`integrations/mcp`](integrations/mcp/README.md)) |
|
|
104
|
+
| Your own harness | `align-agent-wt`: one JSON event on stdin, `{"context": ...}` or `{"deny": ...}` back; or `from align_agent.writethrough import Session` |
|
|
105
|
+
|
|
106
|
+
In a 2,000-file repository a call takes well under 0.5 s; in Claude Code a call took about 0.1 s,
|
|
107
|
+
Python start-up included.
|
|
108
|
+
|
|
109
|
+
To compute diffs, ALIGN keeps a copy of each file the model viewed in its state folder
|
|
110
|
+
(`~/.cache/align-agent`, or the plugin's data folder), readable only by you. Files named like
|
|
111
|
+
keys, `.env` files and credential stores are never copied or diffed; copies no longer needed are
|
|
112
|
+
deleted after each call, and sessions idle for 7 days are deleted (`ALIGN_KEEP_DAYS`).
|
|
113
|
+
|
|
114
|
+
## Find stale edits in trajectories
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
pip install git+https://github.com/VA24d/align-agent@v0.4.3
|
|
118
|
+
align-agent analyze examples/teleport_control.json examples/teleport_diff.json
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Or, with nothing to install or build (image for amd64 and arm64, examples included):
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
docker run --rm ghcr.io/va24d/align-agent:0.4.3 analyze /opt/align-agent/examples/teleport_control.json
|
|
125
|
+
docker run --rm -v "$PWD:/work" ghcr.io/va24d/align-agent:0.4.3 analyze /work/my_trajectories/
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## `align-agent analyze`
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
align-agent analyze PATH [PATH ...] [--format F] [--cwd DIR] [--json OUT] [--quiet]
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Reads trajectory files, or directories of them. The format is detected:
|
|
135
|
+
|
|
136
|
+
| `--format` | Source |
|
|
137
|
+
|---|---|
|
|
138
|
+
| `mini` | mini-swe-agent trajectories (text actions, `<returncode>` observations) |
|
|
139
|
+
| `sweagent` | SWE-agent `.traj` |
|
|
140
|
+
| `openhands` | OpenHands completion logs and event streams |
|
|
141
|
+
| `terminus` | Terminus 2 episodes |
|
|
142
|
+
| `atif` | Harbor ATIF trials, e.g. the DeepSWE leaderboard |
|
|
143
|
+
| `toolcalls` | OpenAI-style messages with a `bash` tool |
|
|
144
|
+
| `claudecode` | Claude Code session logs (`~/.claude/projects/*/*.jsonl`) |
|
|
145
|
+
| `codex` | Codex CLI session rollouts (`~/.codex/sessions`) |
|
|
146
|
+
|
|
147
|
+
A `.jsonl` file is a Claude Code or Codex session, told apart by the shape of its lines, and a directory is searched for `*.json` and `*.jsonl`. To audit your own sessions:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
align-agent analyze ~/.claude/projects/-home-me-myrepo/ # every session of one project, and its subagents
|
|
151
|
+
align-agent analyze ~/.codex/sessions/2026/10/ --json codex.json
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Paths resolve against the working directory the log records. In Claude Code, `Read` is a view of the lines it returned, `Edit` and `MultiEdit` are edits anchored on quoted text, `Write` is a whole-file write, and `Bash` goes through the shell parser. Claude Code's changed-file notices count as views. A subagent shares the workspace but not the context, so in the parent session its writes are writes the parent did not see; its own transcript is analyzed as a separate trajectory. In Codex, `apply_patch` hunks are anchored edits, `*** Add File` is a whole-file write, and shell commands (including those a code-mode `exec` script passes to `exec_command`, and patches it passes to `apply_patch`) go through the shell parser. When one script runs several commands and prints their results under headers (`### 1`, `--- 0 ---`, ...), the output is split back per command. When Codex cuts the middle of a long output (`…N tokens truncated…`), the command before the cut keeps the head of its output, the last command whose header was cut keeps the tail, and commands lost entirely in the cut are left without output. A context compaction starts over: views from before it no longer count. `--json` gives per-session counts of every line and tool the adapters did not map. Not covered yet: a Codex subagent's rollout is analyzed on its own, not as unseen writes in its parent; a script that prints its commands' results without headers keeps one combined output.
|
|
155
|
+
|
|
156
|
+
Each command is parsed into file operations: views (`cat`, `sed -n`, `grep -n`, editor views, file paths quoted in compiler and test output), anchored edits (`sed` by line or pattern, `str_replace`, Python `.replace`, patches) and whole-file writes. Each view is bound to the version of the file it showed. An anchored edit to a file whose last view no longer matches the file is a **stale edit**. For each one, the output gives the step that held the view, whether that view came from the shell or an editor tool, which unseen writes changed the file, and a class:
|
|
157
|
+
|
|
158
|
+
- `MATERIALIZED-M3`: the edit went wrong because of the stale view (applied twice, or a line anchor that the unseen writes moved).
|
|
159
|
+
- `BENIGN-FORWARDED`, `BENIGN-DISJOINT`: the unseen change was accounted for, or did not touch what the edit relied on.
|
|
160
|
+
- `FAILED-OWN-WRITE`, `FAILED-UNRELATED`, `SPURIOUS`: the edit failed for its own reasons, or the unseen write changed nothing.
|
|
161
|
+
- `INDETERMINATE`: the transcript does not show enough to decide.
|
|
162
|
+
|
|
163
|
+
The summary reports the stale-edit rate over anchored edits to viewed files, with a 95% Clopper–Pearson interval. `--json` writes every edit.
|
|
164
|
+
|
|
165
|
+
## `align-agent run`
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
align-agent run --repo DIR --task TEXT_OR_FILE --model NAME --base-url URL \
|
|
169
|
+
[--arm control|viewport|diff|lean|dependency] [--image IMAGE] [--out trajectory.json]
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Needs the `[run]` extra: `pip install 'align-agent[run] @ git+https://github.com/VA24d/align-agent@v0.4.3'`. Runs mini-swe-agent 2.4.6 against any OpenAI-compatible endpoint. The API key is read from `$OPENAI_API_KEY`, or from the variable that `--api-key-env` names. After each command, the hook finds the files the command changed by content hash and appends one of these to that command's output:
|
|
173
|
+
|
|
174
|
+
- `viewport`: a numbered window around each change.
|
|
175
|
+
- `diff`: the net diff since the agent last saw the file.
|
|
176
|
+
- `lean`: the same diff, skipped for writes the agent already knows (a whole file it supplied, a file the command printed).
|
|
177
|
+
- `dependency`: the diff, plus the remaining uses in other files of any definition the write removed or re-signatured.
|
|
178
|
+
|
|
179
|
+
Nothing earlier in the conversation is rewritten, so the prompt stays cacheable. With `--image`, commands run in that container with the repository mounted. Without it, use `--no-sandbox` only inside a container. The trajectory is written in mini-swe-agent's format, so `analyze` reads it back.
|
|
180
|
+
|
|
181
|
+
## What the numbers mean
|
|
182
|
+
|
|
183
|
+
The analyzer is the scorer and classifier of the study behind this tool, unchanged. On the published trajectories it was built for, an independent hand audit of every stale edit agreed with a second blind labeller at κ = 0.73 on the classes. The `MATERIALIZED` class comes from rules, not execution, and errs both ways. Treat a `MATERIALIZED` label as a pointer to a step worth reading, not as a verdict.
|
|
184
|
+
|
|
185
|
+
## Licence
|
|
186
|
+
|
|
187
|
+
MIT. The bundled examples are trajectories from our own runs.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""ALIGN: find edits a coding agent composed against a stale view of a file.
|
|
2
|
+
|
|
3
|
+
`align-agent analyze` replays agent trajectories (mini-SWE-agent, SWE-agent,
|
|
4
|
+
OpenHands, Terminus 2, Harbor/ATIF, OpenAI-style tool calls) and the session
|
|
5
|
+
logs of Claude Code and Codex CLI, and reports every anchored edit made to a
|
|
6
|
+
file whose last view the agent's own writes had outdated, with the step that
|
|
7
|
+
held the view, how the view was delivered, and whether the edit went wrong
|
|
8
|
+
because of it.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
__version__ = "0.4.3"
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Shared read-only helpers for the RQ1 audit. Usage: imported by the other scripts/audit/*.py files.
|
|
2
|
+
|
|
3
|
+
Reads the manifest from the worktree and raw trajectories straight from the
|
|
4
|
+
shared cache. Never downloads, never writes into the cache.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
12
|
+
ROOT = os.path.abspath(os.path.join(HERE, "..", ".."))
|
|
13
|
+
MANIFEST = os.path.join(ROOT, "data", "manifests", "bench_manifest.verified.jsonl")
|
|
14
|
+
# The extracted CodeTraceBench trajectories (one JSON per traj_id) are gitignored;
|
|
15
|
+
# point RQ1_CACHE at them, or place them under data/cache/extracted.
|
|
16
|
+
CACHE = os.environ.get("RQ1_CACHE", os.path.join(ROOT, "data", "cache", "extracted"))
|
|
17
|
+
OUT = os.path.join(ROOT, "docs", "audit", "rq1")
|
|
18
|
+
WORK = os.path.join(OUT, "work") # intermediate machine-readable outputs
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def load_manifest(path: str = MANIFEST) -> List[Dict[str, Any]]:
|
|
22
|
+
with open(path, encoding="utf-8") as f:
|
|
23
|
+
return [json.loads(line) for line in f if line.strip()]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def cache_path(traj_id: str) -> str:
|
|
27
|
+
return os.path.join(CACHE, f"{traj_id}.json")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_raw(traj_id: str) -> Optional[Any]:
|
|
31
|
+
"""Raw trajectory from the cache, or None when the cache has no file."""
|
|
32
|
+
p = cache_path(traj_id)
|
|
33
|
+
if not os.path.exists(p):
|
|
34
|
+
return None
|
|
35
|
+
with open(p, encoding="utf-8") as f:
|
|
36
|
+
return json.load(f)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def annotated_ids(item: Dict[str, Any]):
|
|
40
|
+
inc, unu = set(), set()
|
|
41
|
+
for st in item.get("incorrect_stages") or []:
|
|
42
|
+
inc.update(st.get("incorrect_step_ids") or [])
|
|
43
|
+
unu.update(st.get("unuseful_step_ids") or [])
|
|
44
|
+
return inc, unu
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def short_model(m: str) -> str:
|
|
48
|
+
return {"Anthropic/Claude-Sonnet-4-20250514-Thinking": "Claude-Sonnet-4",
|
|
49
|
+
"DeepSeek/DeepSeek-V3.2": "DeepSeek-V3.2",
|
|
50
|
+
"OpenAI/GPT-5": "GPT-5"}.get(m, m)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def dump(obj: Any, name: str) -> str:
|
|
54
|
+
os.makedirs(WORK, exist_ok=True)
|
|
55
|
+
p = os.path.join(WORK, name)
|
|
56
|
+
with open(p, "w", encoding="utf-8") as f:
|
|
57
|
+
json.dump(obj, f, indent=1, default=str)
|
|
58
|
+
return p
|