aihook 0.1.5__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.
@@ -0,0 +1,79 @@
1
+ # This workflow will install Python dependencies, run tests and lint with a single version of Python
2
+ # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3
+
4
+ name: CI
5
+
6
+ on:
7
+ push:
8
+ branches:
9
+ - main
10
+ - develop
11
+ pull_request:
12
+ branches:
13
+ - main
14
+ - develop
15
+
16
+ jobs:
17
+ build:
18
+
19
+ runs-on: ubuntu-latest
20
+
21
+ env:
22
+ # note: 'true' only works if no error occurred.
23
+ # DEBUG_WITH_SSH: 'true'
24
+ DEBUG_WITH_SSH: false
25
+
26
+ steps:
27
+ - uses: actions/checkout@v4
28
+
29
+ - name: Set up Python 3.11
30
+ uses: actions/setup-python@v4
31
+ with:
32
+ python-version: "3.11"
33
+
34
+ # we use uv because it is much faster than normal pip
35
+ - name: Install uv
36
+ run: |
37
+ python -m pip install uv
38
+
39
+ - name: Installing requirements and package
40
+ run: |
41
+ # install this package
42
+ pwd
43
+ uv pip install --system -r requirements.txt
44
+ uv pip install --system -e .
45
+
46
+ - name: configure git
47
+ run: |
48
+ git config --global user.email "ci-user@ci-server.com"
49
+ git config --global user.name "CI User"
50
+ git config --global init.defaultBranch main
51
+
52
+ - name: run all tests
53
+ run: |
54
+ pytest
55
+
56
+
57
+ # debugging (set env.DEBUG_WITH_SSH to true (see above))
58
+ - name: Setup tmate session
59
+ if: env.DEBUG_WITH_SSH == 'true'
60
+ id: tmate
61
+ uses: mxschmitt/action-tmate@v3
62
+ with:
63
+ detached: true
64
+
65
+ # this exports the ad-hoc ssh-connection address to an text file such that it can be accessed via API
66
+ # this allows to connect to the debugging ssh server without manual copy-pasting
67
+ # see https://github.com/cknoll/github-ci-ssh-connect
68
+
69
+ - name: Capture tmate output
70
+ if: env.DEBUG_WITH_SSH == 'true'
71
+ run: |
72
+ echo $(cat /home/runner/work/_temp/_runner_file_commands/* | grep ::notice::SSH | awk '{print $NF}') > tmate-connection.txt
73
+
74
+ - name: Upload tmate logs as artifact
75
+ if: env.DEBUG_WITH_SSH == 'true'
76
+ uses: actions/upload-artifact@v4
77
+ with:
78
+ name: tmate-connection
79
+ path: tmate-connection.txt
@@ -0,0 +1,7 @@
1
+ *.pyc
2
+ */__pycache__/*
3
+ */.directory
4
+ .directory
5
+ */.ipynb_checkpoints/*
6
+ src/*.egg-info/*
7
+ dist
aihook-0.1.5/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 C. Knoll
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,2 @@
1
+ include requirements.txt
2
+ include src/aihook/SKILL.md
aihook-0.1.5/PKG-INFO ADDED
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: aihook
3
+ Version: 0.1.5
4
+ Summary: interactive embedded shell for agents
5
+ Author-email: Carsten Knoll <firstname.lastname@domain.org>
6
+ License-Expression: GPL-3.0-or-later
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Requires-Python: >=3.11
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: ipydex
14
+ Requires-Dist: pytest
15
+ Requires-Dist: PyYAML
16
+ Requires-Dist: platformdirs
17
+ Dynamic: license-file
18
+
19
+ # aihook
20
+
21
+ **Pause a running Python script and let an AI agent inspect and manipulate its live state — without restarting.**
22
+
23
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
24
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/)
25
+
26
+ ---
27
+
28
+ ## The problem
29
+
30
+ Some scripts are expensive to restart. A Selenium browser session takes 60 seconds to reach the
31
+ interesting state. An ML pipeline loads a 4 GB model before the first inference. A scraper
32
+ re-authenticates and paginates through 200 pages before getting to the data you care about.
33
+
34
+ When debugging these scripts, the standard loop — edit, restart, wait, observe — is brutal.
35
+ Each hypothesis costs a full restart.
36
+
37
+ ## What aihook does
38
+
39
+ Drop one call into your script:
40
+
41
+ ```python
42
+ from aihook import agent_hook
43
+ agent_hook() # script pauses here; HTTP REPL starts
44
+ ```
45
+
46
+ The script pauses at that line. An HTTP server starts on a free port. The calling frame's
47
+ variables are all live and accessible. An AI agent (or you) can then probe, manipulate, and
48
+ test fixes against the real running state:
49
+
50
+ ```bash
51
+ aihook 'browser.find_by_css(".comment__replies")'
52
+ aihook -f snippet.py # run a multi-line snippet, edit, repeat
53
+ aihook --exit # resume the script
54
+ ```
55
+
56
+ Each probe takes 2–5 seconds instead of 60.
57
+
58
+ ---
59
+
60
+ ## Real-world example
61
+
62
+ This is what a session looks like in practice. The script is a Selenium browser automation that
63
+ was failing silently on every "show replies" button click:
64
+
65
+ ```bash
66
+ # Start the slow script in the background
67
+ python fetch_comments.py --url "https://example.com/article" > aihook-host.log 2>&1 &
68
+
69
+ # Wait for it to reach agent_hook() — CLI validates pid + port, times out after 180s
70
+ # aihook: session found after 94.3s
71
+
72
+ # Probe the live DOM
73
+ aihook 'browser.find_by_css("[data-comment-id]")'
74
+ # -> [] ← the attribute does not exist
75
+
76
+ aihook 'browser.find_by_css("[id^=cid-]")'
77
+ # -> [<Element id="cid-4821"> ... ] ← found it
78
+
79
+ # Try the JS click that works despite the overlay
80
+ aihook -f snippet.py # snippet tests execute_script("arguments[0].click()", btn)
81
+ # -> reply count went from 8 to 28
82
+
83
+ aihook --exit
84
+ ```
85
+
86
+ Five probes. Zero restarts. A working script at the end.
87
+
88
+ ---
89
+
90
+ ## Install
91
+
92
+ ```bash
93
+ pip install aihook
94
+ ```
95
+
96
+ ### Set up the AI agent skill
97
+
98
+ For Claude Code:
99
+ ```bash
100
+ aihook --bootstrap --agent claude
101
+ ```
102
+
103
+ For aider / aider-desk:
104
+ ```bash
105
+ aihook --bootstrap --agent aider
106
+ ```
107
+
108
+ This installs `SKILL.md` (the agent's instruction file for using aihook) and creates a
109
+ `learnings/` directory where agent-accumulated tips are stored across sessions.
110
+
111
+ ---
112
+
113
+ ## CLI reference
114
+
115
+ | Command | Description |
116
+ |---|---|
117
+ | `aihook '<code>'` | Execute code; single expressions auto-print their `repr()` |
118
+ | `aihook -f FILE` | Send contents of FILE (`-f -` reads from stdin) |
119
+ | `aihook --exit` | Shut down the session and let the script resume |
120
+ | `aihook --status` | Show whether a session is active (exits 0 if healthy) |
121
+ | `aihook --clean` | Remove a stale lock file |
122
+ | `aihook --wait N` | Wait up to N seconds for a healthy session (default: 180s) |
123
+ | `aihook -p PORT` | Target a specific port (skips lock-file discovery) |
124
+ | `aihook --lockfile PATH` | Use a custom lock-file path |
125
+ | `aihook --bootstrap` | Install SKILL.md and create learnings directory |
126
+
127
+ Exit code is non-zero if the remote code raised an exception or wrote to stderr.
128
+
129
+ **Auto-print:** if the submitted code is a single expression, its `repr()` is printed
130
+ automatically. For multi-line snippets, use explicit `print()` — auto-print only applies to
131
+ single expressions.
132
+
133
+ ---
134
+
135
+ ## How it works
136
+
137
+ `agent_hook()` does three things:
138
+
139
+ 1. Captures the calling frame's `globals` and `locals` into a shared namespace.
140
+ 2. Starts a minimal HTTP server on a free port in `5001–5101` (configurable).
141
+ 3. Writes `./aihook-lock.yml` containing `pid`, `port`, `cwd`, and `start_time`.
142
+
143
+ The CLI reads the lock file to find the port, POSTs code to `/execute`, and prints the
144
+ result. The server shuts down on `exit()`, the lock file is removed, and the host script
145
+ resumes.
146
+
147
+ The banner printed on startup includes the source file and line number where `agent_hook()`
148
+ was called — useful when a script has multiple hook points.
149
+
150
+ ---
151
+
152
+ ## One caveat: local variable write-back
153
+
154
+ Rebinding a **local** variable of the calling function from inside the REPL does **not**
155
+ write back to that function's fast-locals. This is the same limitation as `pdb`:
156
+
157
+ ```python
158
+ # Inside my_function():
159
+ x = 1
160
+ agent_hook()
161
+ print(x) # still prints 1 even if you did `x = 2` via aihook
162
+ ```
163
+
164
+ **Mutating mutable objects works fine:**
165
+
166
+ ```python
167
+ my_list.append(99) # visible in the host afterwards
168
+ my_dict["key"] = "new" # same
169
+ obj.attribute = "changed" # same
170
+ ```
171
+
172
+ When testing a fix, mutate containers or attributes rather than rebinding local names.
173
+
174
+ (Of course, this is documented for agents in [SKILL.md](src/aihook/SKILL.md).)
175
+
176
+ ---
177
+
178
+ ## Optional environment variables
179
+
180
+ | Variable | Effect |
181
+ |---|---|
182
+ | `AIHOOK_PORT=NNNN` | Force a specific port |
183
+ | `AIHOOK_PORT_RANGE=LO-HI` | Override the default `5001-5101` range |
184
+
185
+ ---
186
+
187
+ ## Development
188
+
189
+ ```bash
190
+ git clone <repo-url>
191
+ cd aihook
192
+ pip install -e .
193
+ pytest
194
+ ```
195
+
196
+ The learnings directory (created by `--bootstrap`) accumulates session tips in
197
+ `~/.local/share/aihook/learnings/` (Linux) or the platform equivalent. Add
198
+ `topic_<name>.md` files there to share domain-specific knowledge across agent sessions.
aihook-0.1.5/README.md ADDED
@@ -0,0 +1,180 @@
1
+ # aihook
2
+
3
+ **Pause a running Python script and let an AI agent inspect and manipulate its live state — without restarting.**
4
+
5
+ [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
6
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/)
7
+
8
+ ---
9
+
10
+ ## The problem
11
+
12
+ Some scripts are expensive to restart. A Selenium browser session takes 60 seconds to reach the
13
+ interesting state. An ML pipeline loads a 4 GB model before the first inference. A scraper
14
+ re-authenticates and paginates through 200 pages before getting to the data you care about.
15
+
16
+ When debugging these scripts, the standard loop — edit, restart, wait, observe — is brutal.
17
+ Each hypothesis costs a full restart.
18
+
19
+ ## What aihook does
20
+
21
+ Drop one call into your script:
22
+
23
+ ```python
24
+ from aihook import agent_hook
25
+ agent_hook() # script pauses here; HTTP REPL starts
26
+ ```
27
+
28
+ The script pauses at that line. An HTTP server starts on a free port. The calling frame's
29
+ variables are all live and accessible. An AI agent (or you) can then probe, manipulate, and
30
+ test fixes against the real running state:
31
+
32
+ ```bash
33
+ aihook 'browser.find_by_css(".comment__replies")'
34
+ aihook -f snippet.py # run a multi-line snippet, edit, repeat
35
+ aihook --exit # resume the script
36
+ ```
37
+
38
+ Each probe takes 2–5 seconds instead of 60.
39
+
40
+ ---
41
+
42
+ ## Real-world example
43
+
44
+ This is what a session looks like in practice. The script is a Selenium browser automation that
45
+ was failing silently on every "show replies" button click:
46
+
47
+ ```bash
48
+ # Start the slow script in the background
49
+ python fetch_comments.py --url "https://example.com/article" > aihook-host.log 2>&1 &
50
+
51
+ # Wait for it to reach agent_hook() — CLI validates pid + port, times out after 180s
52
+ # aihook: session found after 94.3s
53
+
54
+ # Probe the live DOM
55
+ aihook 'browser.find_by_css("[data-comment-id]")'
56
+ # -> [] ← the attribute does not exist
57
+
58
+ aihook 'browser.find_by_css("[id^=cid-]")'
59
+ # -> [<Element id="cid-4821"> ... ] ← found it
60
+
61
+ # Try the JS click that works despite the overlay
62
+ aihook -f snippet.py # snippet tests execute_script("arguments[0].click()", btn)
63
+ # -> reply count went from 8 to 28
64
+
65
+ aihook --exit
66
+ ```
67
+
68
+ Five probes. Zero restarts. A working script at the end.
69
+
70
+ ---
71
+
72
+ ## Install
73
+
74
+ ```bash
75
+ pip install aihook
76
+ ```
77
+
78
+ ### Set up the AI agent skill
79
+
80
+ For Claude Code:
81
+ ```bash
82
+ aihook --bootstrap --agent claude
83
+ ```
84
+
85
+ For aider / aider-desk:
86
+ ```bash
87
+ aihook --bootstrap --agent aider
88
+ ```
89
+
90
+ This installs `SKILL.md` (the agent's instruction file for using aihook) and creates a
91
+ `learnings/` directory where agent-accumulated tips are stored across sessions.
92
+
93
+ ---
94
+
95
+ ## CLI reference
96
+
97
+ | Command | Description |
98
+ |---|---|
99
+ | `aihook '<code>'` | Execute code; single expressions auto-print their `repr()` |
100
+ | `aihook -f FILE` | Send contents of FILE (`-f -` reads from stdin) |
101
+ | `aihook --exit` | Shut down the session and let the script resume |
102
+ | `aihook --status` | Show whether a session is active (exits 0 if healthy) |
103
+ | `aihook --clean` | Remove a stale lock file |
104
+ | `aihook --wait N` | Wait up to N seconds for a healthy session (default: 180s) |
105
+ | `aihook -p PORT` | Target a specific port (skips lock-file discovery) |
106
+ | `aihook --lockfile PATH` | Use a custom lock-file path |
107
+ | `aihook --bootstrap` | Install SKILL.md and create learnings directory |
108
+
109
+ Exit code is non-zero if the remote code raised an exception or wrote to stderr.
110
+
111
+ **Auto-print:** if the submitted code is a single expression, its `repr()` is printed
112
+ automatically. For multi-line snippets, use explicit `print()` — auto-print only applies to
113
+ single expressions.
114
+
115
+ ---
116
+
117
+ ## How it works
118
+
119
+ `agent_hook()` does three things:
120
+
121
+ 1. Captures the calling frame's `globals` and `locals` into a shared namespace.
122
+ 2. Starts a minimal HTTP server on a free port in `5001–5101` (configurable).
123
+ 3. Writes `./aihook-lock.yml` containing `pid`, `port`, `cwd`, and `start_time`.
124
+
125
+ The CLI reads the lock file to find the port, POSTs code to `/execute`, and prints the
126
+ result. The server shuts down on `exit()`, the lock file is removed, and the host script
127
+ resumes.
128
+
129
+ The banner printed on startup includes the source file and line number where `agent_hook()`
130
+ was called — useful when a script has multiple hook points.
131
+
132
+ ---
133
+
134
+ ## One caveat: local variable write-back
135
+
136
+ Rebinding a **local** variable of the calling function from inside the REPL does **not**
137
+ write back to that function's fast-locals. This is the same limitation as `pdb`:
138
+
139
+ ```python
140
+ # Inside my_function():
141
+ x = 1
142
+ agent_hook()
143
+ print(x) # still prints 1 even if you did `x = 2` via aihook
144
+ ```
145
+
146
+ **Mutating mutable objects works fine:**
147
+
148
+ ```python
149
+ my_list.append(99) # visible in the host afterwards
150
+ my_dict["key"] = "new" # same
151
+ obj.attribute = "changed" # same
152
+ ```
153
+
154
+ When testing a fix, mutate containers or attributes rather than rebinding local names.
155
+
156
+ (Of course, this is documented for agents in [SKILL.md](src/aihook/SKILL.md).)
157
+
158
+ ---
159
+
160
+ ## Optional environment variables
161
+
162
+ | Variable | Effect |
163
+ |---|---|
164
+ | `AIHOOK_PORT=NNNN` | Force a specific port |
165
+ | `AIHOOK_PORT_RANGE=LO-HI` | Override the default `5001-5101` range |
166
+
167
+ ---
168
+
169
+ ## Development
170
+
171
+ ```bash
172
+ git clone <repo-url>
173
+ cd aihook
174
+ pip install -e .
175
+ pytest
176
+ ```
177
+
178
+ The learnings directory (created by `--bootstrap`) accumulates session tips in
179
+ `~/.local/share/aihook/learnings/` (Linux) or the platform equivalent. Add
180
+ `topic_<name>.md` files there to share domain-specific knowledge across agent sessions.
@@ -0,0 +1,199 @@
1
+ # aihook — Improvement Plan
2
+
3
+ Derived from a real first-time agent session and a follow-up Q&A with the testing agent.
4
+
5
+ Items are ordered by value / cost ratio. Each item lists the affected
6
+ file(s), the concrete change, and an acceptance test.
7
+
8
+ ---
9
+
10
+ ## 1. Fix multi-statement execution (HIGH priority, highest value)
11
+
12
+ ### Problem
13
+
14
+ Snippets with more than one top-level statement fail with a confusing
15
+ `SyntaxError`. Reproducer (confirmed by the testing agent):
16
+
17
+ ```python
18
+ # /tmp/aihook_probe.py
19
+ def foo():
20
+ return 42
21
+
22
+ print(foo())
23
+ ```
24
+
25
+ ```
26
+ $ aihook -f /tmp/aihook_probe.py
27
+ File "<console>", line 4
28
+ print(foo())
29
+ ^^^^^
30
+ SyntaxError: invalid syntax
31
+ ```
32
+
33
+ ### Root cause
34
+
35
+ `AgenticREPL.execute_command` in `src/aihook/core.py` first tries
36
+ `compile(command, "<agent>", "eval")`. On `SyntaxError` it falls back to
37
+ `self.console.push(command)`. `InteractiveConsole.push` is **line-oriented**:
38
+ it mimics a human typing at a `>>>` prompt, splitting on newlines and
39
+ feeding lines one at a time through `compile_command`. It cannot execute a
40
+ whole multi-line source string in one call the way `exec(compile(..., "exec"))`
41
+ can — hence the spurious error on the second top-level statement.
42
+
43
+ ### Fix
44
+
45
+ In `src/aihook/core.py`, replace the `else` branch of `execute_command`
46
+ (the `self.console.push(command)` call) with a direct `exec` of the
47
+ compiled module:
48
+
49
+ ```python
50
+ else:
51
+ code_obj = compile(command, "<agent>", "exec")
52
+ exec(code_obj, self.namespace)
53
+ ```
54
+
55
+ Keep the expression-first path unchanged (single expressions still
56
+ auto-print their `repr`). Drop the now-unused `self.console` attribute
57
+ (and the `import code` + `self.console = code.InteractiveConsole(...)` in
58
+ `__init__`) unless we find another use for it — leaner is better.
59
+
60
+ Rationale: the `InteractiveConsole` machinery was only needed for the
61
+ interactive line-by-line prompt semantics, which we do not want. Agents
62
+ send complete, already-edited source fragments; `exec` is the right tool.
63
+
64
+ ### Acceptance tests
65
+
66
+ Add to `tests/test_integration.py`:
67
+
68
+ 1. Multi-statement block executes and produces the expected stdout:
69
+ ```python
70
+ cmd = "def foo():\n return 42\n\nprint(foo())\n"
71
+ # expect stdout == "42\n", stderr == "", exit 0
72
+ ```
73
+ 2. `def` alone (no trailing call) defines the function in the namespace
74
+ (verify by a follow-up `aihook 'foo()'`).
75
+ 3. Assignment + use in one snippet:
76
+ ```python
77
+ cmd = "x = 7\nprint(x * 6)\n"
78
+ # expect stdout == "42\n"
79
+ ```
80
+ 4. Genuine `SyntaxError` in a multi-statement block still reports cleanly
81
+ (non-zero exit, traceback on stderr).
82
+ 5. Single-expression path unchanged: `aihook '1+2'` still auto-prints `3`.
83
+
84
+ ---
85
+
86
+ ## 2. Print a startup hint about the fast-locals caveat (LOW priority, low cost)
87
+
88
+ ### Problem
89
+
90
+ The CPython caveat (rebinding locals of the calling function does not
91
+ propagate back) is documented but remains a trap. Not discoverable at
92
+ runtime.
93
+
94
+ ### Fix
95
+
96
+ In `AgenticREPL.run`, after the existing banner lines (the
97
+ `AIHOOK AgenticREPL:` / `AIHOOK_PORT=` block) in `src/aihook/core.py`,
98
+ print one concise stderr hint:
99
+
100
+ ```python
101
+ sys.stderr.write(
102
+ "AIHOOK: note: rebinding a local variable of the calling function "
103
+ "does not propagate back (CPython fast-locals; same as pdb). "
104
+ "Mutate containers / attributes instead. See SKILL.md.\n"
105
+ )
106
+ sys.stderr.flush()
107
+ ```
108
+
109
+ Keep the banner itself on stdout unchanged (agents grep for `AIHOOK_PORT=`
110
+ there). The hint goes to stderr so it doesn't pollute machine-parseable
111
+ output.
112
+
113
+ ### Acceptance test
114
+
115
+ Integration test asserts the hint substring appears in the host process's
116
+ combined log.
117
+
118
+ ---
119
+
120
+ ## 3. Documentation updates in `src/aihook/SKILL.md` (LOW priority, zero risk)
121
+
122
+ Three small additions:
123
+
124
+ ### 3a. Promote the iterative `-f FILE` loop as the canonical workflow
125
+
126
+ In the "Canonical agent workflow" section, add an explicit bullet framing
127
+ the dominant debugging pattern:
128
+
129
+ > **Iterative probe loop.** For anything beyond a one-liner, keep a
130
+ > `snippet.py` next to the host script and rerun it after each edit:
131
+ > ```
132
+ > # edit snippet.py
133
+ > aihook -f snippet.py
134
+ > # observe, edit snippet.py again, repeat
135
+ > ```
136
+ > This is the primary workflow; the single-expression form is for quick
137
+ > probes only.
138
+
139
+ ### 3b. Document `-f FILE` path resolution
140
+
141
+ Add a note under the CLI reference:
142
+
143
+ > `-f FILE` is opened by the CLI process (agent side), so relative paths
144
+ > resolve against the **agent's** current working directory, not the host
145
+ > script's. Use an absolute path (e.g. `-f "$PWD/snippet.py"`) if you are
146
+ > not certain the cwds match, or if your shell tool chains `cd` commands
147
+ > unreliably.
148
+
149
+ ### 3c. Document lock-file discovery scope and manual cleanup
150
+
151
+ Extend the "Session discovery" section with:
152
+
153
+ > Lock-file discovery does **not** walk up parent directories — `aihook`
154
+ > only looks at `./aihook-lock.yml` in the invoking shell's exact cwd.
155
+ > Run `aihook` from the same directory as the host script.
156
+ >
157
+ > If the host process is killed uncleanly (e.g. `SIGKILL`, OOM), the lock
158
+ > file may remain. A subsequent `agent_hook()` call in the same cwd will
159
+ > detect this automatically (pid not alive) and overwrite it. To clean up
160
+ > manually, just `rm ./aihook-lock.yml`.
161
+
162
+ ---
163
+
164
+ ## 4. Nice-to-haves (deferred, consider after items 1-3 land)
165
+
166
+ These are not worth doing now but are recorded for future consideration:
167
+
168
+ - **`--echo` / `--verbose` flag** that always prints the last value (or
169
+ `None`) regardless of statement vs. expression. Low value once item 1
170
+ lands, because the agent can just wrap in `print(...)` in a multi-line
171
+ block.
172
+ - **Resolve `-f FILE` relative to `lockfile["cwd"]`** when a relative path
173
+ is given and the file is not found locally. Rejected for now: the CLI
174
+ may run on a different machine than the host, and the current behavior
175
+ is consistent with every other Unix tool that reads files.
176
+ - **Walk up parents for lock-file discovery.** Rejected for now: breaks
177
+ the "one aihook per cwd" invariant and invites cross-project confusion.
178
+
179
+ ---
180
+
181
+ ## Out of scope / non-issues confirmed by Q&A
182
+
183
+ - Banner reaches agents via log file reliably (Q4a).
184
+ - `--wait 5` default is a good middle ground (Q4b).
185
+ - Exit-code semantics are correct (Q4c). The `REPL: Exiting` message is
186
+ written to **stdout**, not stderr, so there is no spurious stderr on
187
+ successful `--exit` — the testing agent's suspicion was unfounded.
188
+ - `aihook --exit` shutdown + lock-file cleanup work reliably (Q4d).
189
+
190
+ ---
191
+
192
+ ## Implementation order
193
+
194
+ 1. Item 1 (exec fix + tests) — single commit.
195
+ 2. Item 3 (docs) — single commit, can precede or follow item 1.
196
+ 3. Item 2 (startup hint) — single commit.
197
+
198
+ Each item is independently mergeable. Item 1 is the only one with
199
+ behavioural risk; its test suite additions are mandatory.