summaryrefslogtreecommitdiff
path: root/README.md
blob: 3fb2b478f431d94c5883759ad6c8cce23ef36fe9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
![bem-te-vi](web/assets/bemtevi.svg)

# notevi

A code reader that remembers. Read files and grep through `notevi`, pin notes to
line ranges, and every one of those calls is appended as a single JSON line to
an **append-only JSONL log** kept in a **private jj commit**. Not your working
copy. Not your history.

Two front ends over that one log — a CLI and a local web app. Both read it, both
write to it. Use either. Use only one.

The bem-te-vi calls all day and keeps a bright crown patch hidden under a plain
black cap. Same idea: the notes are always there, never in the way.

## Install

```sh
go build -o ~/.local/bin/notevi .
```

Needs `jj` (the repo must be a jj workspace) and `rg` for working-copy greps.

## From the shell

```sh
notevi read  src/lib.rs                # whole file, line-numbered
notevi read  src/lib.rs:120-180        # just the range
notevi grep  'fn resolve' src/         # regex over the working copy

notevi note  -f src/lib.rs:143 -t invariant "holds only under the bank lock"
notevi note  -reply 12 "no — see the early return above"

notevi query -op note                  # replay the log, oldest first
notevi query -file accounts_db         # everything touching a path
```

Add `-r REV` to any of them to work at another revision — any jj revset, default
`@`. Every entry records the full change **and** commit id, so a note stays
pinned to the exact bytes it was about even after the change is rewritten.
Notes take an optional `-t KIND` (`fn`, `struct`, `invariant`, whatever you
want) and can reply to each other by number.

## From the browser

```sh
notevi web                             # dashboard: every sidecar under ~
notevi web -repo .                     # one repo, http://127.0.0.1:4200
```

**This is not a viewer.** Click a line, type, and the note is appended to the
same JSONL log `notevi note` writes to — same numbering, same permalinks, at the
exact change and commit you are looking at, attributed to your username. Reply
to notes, thread them, filter by file, text, kind, author, model, session,
change or date range. Never open the CLI and you lose nothing.

What it shows:

- the repo tree with per-reader coverage, and sources highlighted by tree-sitter
  with coverage-tinted line numbers — the colors mix where readers overlap
- every note pinned to its lines, plus a notes-only mode across the whole repo
- who is reading right now, refreshed every few seconds
- ad-hoc jj queries over the repo
- every Zed and Helix theme installed on the machine, previewed live as you
  arrow through the picker

The dashboard scans `~` for repositories, and can start a sidecar in a jj repo
or add colocated jj to a Git one — so a project can begin in the browser too.
It writes no registry and no scan cache.

```sh
notevi web -repo . -out site           # static export: hand it to someone
```

The export is a plain directory of HTML. Tree, coverage, highlighting, notes,
filters and the file finder all still work with no server; writing notes, live
activity and jj queries do not, because there is nothing running.

## The log

One file, `notevi-log.jsonl`, only ever appended to. Lines are never edited and
never deleted: a note you got wrong is superseded by a later note, not rewritten,
so the trace stays a faithful record of what was believed when. Which also means
you can skip the tooling entirely:

```sh
jj file show -r 'exactly(notevi_log, 1)' -- notevi-log.jsonl |
  jq -c 'select(.op == "note") | {file, start, text}'
```

That file lives in one anonymous commit whose parent is `root()`, described
`private: notevi log`. The repo-local revset alias `notevi_log` names it and
`git.private-commits` keeps it from being pushed. Each append is a new revision
of that commit, so the evolog is the log's own history:

```sh
jj evolog -r 'exactly(notevi_log, 1)' --reversed -p --git
```

Appends are serialized and go through an isolated `jj run`; your `@` is never
snapshotted or moved. There is no bookmark and no push — **a fresh clone will
not have your notes.** Back up the jj repo, or export the site.

## If a coding agent is the one running it

`notevi` notices. It reads the harness environment (Claude Code, Codex, Hermes),
falling back to walking the process tree, and adds session id, model and
reasoning effort to every entry it writes. Nothing to configure; a human at a
shell just gets entries without those fields.

That turns the trace into a record of what an agent actually looked at before it
told you something. The web view colors coverage per reader, so overlapping
work is visible, and each note links to the conversation turn it came from.

`constrain/` goes further and makes `notevi` the *only* way to read: a Claude
Code `PreToolUse` hook and a codex execpolicy ruleset that deny
`cat`/`rg`/`sed`/`jj file show`/`git show`/… and point the agent at
`notevi -doc`. Agents converge on it with zero instructions. See
`constrain/README.txt`.

## Coming from `vr` / `vrsite`

Same tool, one binary. Old sidecars (`vr_log`, `vr-log.jsonl`) are still
recognized and renamed in place on first write; `notevi migrate DIR...` does it
up front.