You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: AGENTS.md
+102Lines changed: 102 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -21,3 +21,105 @@ Use `scripts/dependency-topology/scan_topology.py` to inspect and track architec
21
21
- Pass `--snapshot <git-ref>` for historical snapshots
22
22
- Pass `--json` when feeding outputs into scripts or agents
23
23
- Keep architecture cleanup discussions anchored on scanner output instead of ad-hoc grep chains
24
+
25
+
## Runtime Performance Profiling
26
+
27
+
Use this section when there is a runtime performance problem or a credible performance report: slow render, delayed keypress, streaming lag, startup slowdown, a profiler screenshot, or a benchmark/test showing a regression. Before changing code for that problem, capture evidence and reduce it to a cost model. Pick the profiling method by what is available; do not require a specific plugin.
28
+
29
+
### Capture options
30
+
31
+
Use the first option that fits the machine and the symptom.
32
+
33
+
#### Instrumentation profiler
34
+
35
+
Use this when a profiler plugin is already available. It records function call trees with time and count.
36
+
37
+
Example with `folke/snacks.nvim`, if it is installed:
38
+
39
+
```vim
40
+
:lua Snacks.profiler.start()
41
+
" reproduce the slow action once
42
+
:lua Snacks.profiler.stop({ pick = true })
43
+
```
44
+
45
+
Read it as a call tree. Parent time includes child time. `count` is useful for spotting repeated work.
46
+
47
+
#### LuaJIT sampling profiler
48
+
49
+
Use this when no profiler plugin is available. Neovim normally exposes LuaJIT's profiler as `jit.p`.
Open `/tmp/nvim-jit-profile.log`. Treat it like a sampled CPU profile: it shows where Lua spent CPU time by stack/location, but it does not give exact call counts. If the issue is repeated work, pair it with a counter or scoped timer.
58
+
59
+
#### Scoped wall-time timer
60
+
61
+
Use this when the question is “which lifecycle boundary blocks the user?” or when sampling does not show wall-clock delay. Add temporary instrumentation around suspected boundaries only while investigating.
count: <hotspot calls per trigger, or "sampled" if using jit.p>
104
+
cost: <hotspot total time and per-call time if available>
105
+
repeated unit: <message | part | line | file | buffer | session>
106
+
invariant data: <inputs that are unchanged across repeated calls>
107
+
```
108
+
109
+
Rules for reading evidence:
110
+
111
+
- In instrumentation traces, parent time includes child time. If parent and child times are almost equal, optimize the child or the child's call frequency.
112
+
- In sampling traces, sample share is not exact wall time and does not prove call count. Use it to find the hot stack, then verify count with instrumentation or counters.
113
+
- A 400 ms function called 10 times is a repeated-work problem. A 4 s function called once is a single expensive operation.
114
+
- Do not optimize tiny high-count helpers unless their caller stack explains the user-visible delay.
115
+
116
+
### Fix criteria
117
+
118
+
A valid fix must change one measured fact:
119
+
120
+
- remove expensive work from the blocking path;
121
+
- move invariant work to the smallest valid lifecycle boundary;
122
+
- defer work to an explicit user action;
123
+
- reduce repeated calls and prove the new call count with a test.
124
+
125
+
Do not add a cache until its invalidation boundary is named. Acceptable boundaries are concrete lifecycle points such as one render flush, one full session render, one keypress, one state change subscription, or one buffer change. Add a regression test that fails on the old call count.
This directory owns the rendered conversation UI and the interactive targets drawn on top of assistant text.
4
+
5
+
## Reference target model
6
+
7
+
The stable chain is:
8
+
9
+
```text
10
+
assistant text
11
+
-> reference_parser: positioned mention spans
12
+
-> reference_facts: current-session refs + current executable file list
13
+
-> formatter/render: screen-coordinate file and symbol targets
14
+
-> navigation: execute the current RenderState target only
15
+
```
16
+
17
+
`reference_parser` only identifies text spans. It does not prove that a file exists. It must keep separate non-overlapping mentions even when they point to the same path. Path-level dedupe belongs only to picker-style file lists.
18
+
19
+
`reference_facts` is the maintained projection from current session messages. It owns two facts: current refs from assistant text and tool file-path facts, and the current executable file list derived from those refs. A file is executable when the referenced path currently exists on disk. This file list is the authority for rendering file affordances.
20
+
21
+
`formatter` must not parse assistant text or scan session messages. It consumes `context.current_refs` and `context.current_files`. A mention becomes an icon, highlight, and `RenderState` file target only when its path is present in `current_files`. A missing file mention stays ordinary text.
22
+
23
+
Symbol targets are bounded by the same file list. During a render cycle, `symbol_snapshot.new_cycle()` may reuse per-file Tree-sitter work inside that cycle. Symbol truth must not become long-lived UI state.
24
+
25
+
`navigation` consumes `RenderState` targets. It must not rediscover targets from the output buffer text. Keypress executes the target that render already produced; it is not a target lifecycle or refresh boundary.
26
+
27
+
Assistant message updates maintain `reference_facts` incrementally. New reference mentions extend the current refs and rebuild the executable file list before the affected rendered text parts are formatted.
28
+
29
+
`file.edited`, `file.watcher.updated`, and local buffer file lifecycle events are render invalidation boundaries. Local writes, buffer renames, buffer unloads, shell-change notifications, server file edits, and watcher add/change/unlink events can change executable files and symbol truth without changing assistant text. They refresh the reference file list and dirty currently rendered assistant text parts. The next render recreates or removes affordances through the same path: current refs, current file list, current Tree-sitter snapshot, formatter output.
30
+
31
+
This invalidation is limited to parts already in `RenderState`. Lazy-rendered history that is not in the output buffer waits for its normal render path. In normal edits the reference file list often stays the same; only symbol truth changes, so the next render reuses the same reference files and a fresh per-render Tree-sitter cycle.
32
+
33
+
## Expected failure diagnosis
34
+
35
+
If a visible path does not jump, inspect in this order:
36
+
37
+
```text
38
+
cursor position
39
+
-> renderer.get_target_at_position(line, col)
40
+
-> reference_facts.current_files()
41
+
-> formatter context for that render
42
+
-> navigation result
43
+
```
44
+
45
+
If `reference_facts.current_refs()` contains a mention but `renderer.get_target_at_position()` is nil, the problem is render projection or file-list membership.
46
+
47
+
If `renderer.get_target_at_position()` returns a target but jump fails, the problem is keypress-time execution or a missing edit invalidation event. Keypress must not patch the rendered state; fix the save/edit invalidation path.
48
+
49
+
If a nonexistent file has an icon or highlight, the bug is in render projection. Do not add cwd/root fallback code in `formatter`; fix the file list or the mention source.
50
+
51
+
## Editing rule
52
+
53
+
Prefer removing duplicate derivations over adding recovery paths. The UI should have one path from facts to rendered targets, and one path from rendered targets to execution.
54
+
55
+
Do not add a second resolver layer, compatibility shim, screen-text scanner, or root fallback to hide a broken file list.
0 commit comments