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.
Files changed (65) hide show
  1. align_agent-0.4.3/LICENSE +21 -0
  2. align_agent-0.4.3/PKG-INFO +224 -0
  3. align_agent-0.4.3/README.md +187 -0
  4. align_agent-0.4.3/align_agent/__init__.py +11 -0
  5. align_agent-0.4.3/align_agent/_audit/common.py +58 -0
  6. align_agent-0.4.3/align_agent/_audit/deepswe.py +260 -0
  7. align_agent-0.4.3/align_agent/_audit/excerpts.py +203 -0
  8. align_agent-0.4.3/align_agent/_audit/extract.py +798 -0
  9. align_agent-0.4.3/align_agent/_audit/materialize.py +624 -0
  10. align_agent-0.4.3/align_agent/_audit/score.py +325 -0
  11. align_agent-0.4.3/align_agent/_audit/shellparse.py +1141 -0
  12. align_agent-0.4.3/align_agent/_audit/tarsur.py +153 -0
  13. align_agent-0.4.3/align_agent/_vendor/src/__init__.py +3 -0
  14. align_agent-0.4.3/align_agent/_vendor/src/harness/__init__.py +13 -0
  15. align_agent-0.4.3/align_agent/_vendor/src/harness/arms.py +524 -0
  16. align_agent-0.4.3/align_agent/_vendor/src/harness/completion.py +104 -0
  17. align_agent-0.4.3/align_agent/_vendor/src/harness/dependency_notice.py +616 -0
  18. align_agent-0.4.3/align_agent/_vendor/src/harness/freshness_hook.py +131 -0
  19. align_agent-0.4.3/align_agent/_vendor/src/harness/lean.py +41 -0
  20. align_agent-0.4.3/align_agent/_vendor/src/harness/mini_real.py +1040 -0
  21. align_agent-0.4.3/align_agent/_vendor/src/harness/miniswe_scaffold.py +661 -0
  22. align_agent-0.4.3/align_agent/_vendor/src/harness/tail_diff.py +292 -0
  23. align_agent-0.4.3/align_agent/_vendor/src/harness/viewport.py +220 -0
  24. align_agent-0.4.3/align_agent/_vendor/src/metrics/__init__.py +38 -0
  25. align_agent-0.4.3/align_agent/_vendor/src/metrics/coherence.py +354 -0
  26. align_agent-0.4.3/align_agent/_vendor/src/metrics/corruption_oracles.py +139 -0
  27. align_agent-0.4.3/align_agent/_vendor/src/metrics/replay.py +399 -0
  28. align_agent-0.4.3/align_agent/_vendor/src/metrics/statistics.py +185 -0
  29. align_agent-0.4.3/align_agent/_vendor/src/metrics/verification.py +104 -0
  30. align_agent-0.4.3/align_agent/_vendor/src/parsers/__init__.py +33 -0
  31. align_agent-0.4.3/align_agent/_vendor/src/parsers/base_parser.py +349 -0
  32. align_agent-0.4.3/align_agent/_vendor/src/parsers/file_events.py +806 -0
  33. align_agent-0.4.3/align_agent/_vendor/src/parsers/miniswe_parser.py +96 -0
  34. align_agent-0.4.3/align_agent/_vendor/src/parsers/openhands_parser.py +414 -0
  35. align_agent-0.4.3/align_agent/_vendor/src/parsers/sweagent_parser.py +180 -0
  36. align_agent-0.4.3/align_agent/_vendor/src/parsers/terminus_parser.py +106 -0
  37. align_agent-0.4.3/align_agent/cli.py +101 -0
  38. align_agent-0.4.3/align_agent/core.py +161 -0
  39. align_agent-0.4.3/align_agent/run.py +84 -0
  40. align_agent-0.4.3/align_agent/sessions.py +985 -0
  41. align_agent-0.4.3/align_agent/writethrough/__init__.py +4 -0
  42. align_agent-0.4.3/align_agent/writethrough/_lib/__init__.py +1 -0
  43. align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/__init__.py +3 -0
  44. align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/harness/__init__.py +1 -0
  45. align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/harness/dependency_notice.py +616 -0
  46. align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/harness/lean.py +41 -0
  47. align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/harness/tail_diff.py +292 -0
  48. align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/harness/viewport.py +220 -0
  49. align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/metrics/__init__.py +38 -0
  50. align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/metrics/replay.py +399 -0
  51. align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/parsers/__init__.py +1 -0
  52. align_agent-0.4.3/align_agent/writethrough/_lib/wt_src/parsers/file_events.py +806 -0
  53. align_agent-0.4.3/align_agent/writethrough/claude_code.py +81 -0
  54. align_agent-0.4.3/align_agent/writethrough/engine.py +647 -0
  55. align_agent-0.4.3/align_agent/writethrough/hooks.py +227 -0
  56. align_agent-0.4.3/align_agent/writethrough/mcp_server.py +125 -0
  57. align_agent-0.4.3/align_agent/writethrough/protocol.py +68 -0
  58. align_agent-0.4.3/align_agent.egg-info/PKG-INFO +224 -0
  59. align_agent-0.4.3/align_agent.egg-info/SOURCES.txt +63 -0
  60. align_agent-0.4.3/align_agent.egg-info/dependency_links.txt +1 -0
  61. align_agent-0.4.3/align_agent.egg-info/entry_points.txt +6 -0
  62. align_agent-0.4.3/align_agent.egg-info/requires.txt +7 -0
  63. align_agent-0.4.3/align_agent.egg-info/top_level.txt +1 -0
  64. align_agent-0.4.3/pyproject.toml +35 -0
  65. 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