From 5ec72f8723d795d0b02d7e9ebb9f555f0c7e6a2e Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Sun, 2 Aug 2026 23:43:08 -0300 Subject: rebrand to notevi: one CLI over the jj sidecar vr and vrsite become a single binary. vrsite/ folds into a web package in one module (0x4200.cafe/notevi); "notevi web" serves and exports exactly what vrsite did, and "notevi read/grep/note/query" is unchanged. The sidecar is renamed with it: notevi_log, notevi-log.jsonl, and the description "private: notevi log". The pre-rebrand names are still recognized, so an old repository opens and reads; it is renamed in place on the first write, or up front with "notevi migrate DIR...". That rename cannot be a single mv inside jj run. jj only auto-tracks a *new* file in the run working copy below a size limit it does not take from the command line, so writing a whole log under a name the change has never held is silently dropped while jj reports success. ensureLogFile creates the file empty first and lets every later byte be a modification of a tracked file, which snapshots at any size; that also fixes the same latent bug when importing a large legacy vr-log.jsonl. Adds a bem-te-vi mark (favicon and nav brand) and a README. Co-Authored-By: Claude Opus 5 (1M context) --- constrain/AGENTS.md | 14 ++++---- constrain/README.txt | 57 ++++++++++++++++++--------------- constrain/claude-headless-settings.json | 4 +-- constrain/claude-settings.json | 4 +-- constrain/notevi-only-guard.sh | 19 +++++++++++ constrain/notevi-only.rules | 44 +++++++++++++++++++++++++ constrain/vr-only-guard.sh | 19 ----------- constrain/vr-only.rules | 44 ------------------------- 8 files changed, 105 insertions(+), 100 deletions(-) create mode 100755 constrain/notevi-only-guard.sh create mode 100644 constrain/notevi-only.rules delete mode 100755 constrain/vr-only-guard.sh delete mode 100644 constrain/vr-only.rules (limited to 'constrain') diff --git a/constrain/AGENTS.md b/constrain/AGENTS.md index 871fc6c..93df43b 100644 --- a/constrain/AGENTS.md +++ b/constrain/AGENTS.md @@ -1,14 +1,14 @@ # Reading code here -Files are read with the `vr` tool — run `vr -doc` once for full usage. +Files are read with the `notevi` tool — run `notevi -doc` once for full usage. -`vr` stores the trace in the repo's private local jj sidecar named `vr_log`. -Do not set `VR_LOG`, pass `-log`, or create a working-tree `vr-log.jsonl`. +`notevi` stores the trace in the repo's private local jj sidecar named +`notevi_log`. Do not create a working-tree log file; there is no path to set. -- `vr read FILE:START-END` — line-numbered read; prefer ranges over whole files -- `vr grep PATTERN [PATH]` — regex search; scope with a path -- `vr read -r REV FILE`, `vr grep -r REV PATTERN PATH` — at another jj change -- `vr note -f FILE:10-42 -t struct TEXT...` — record observations as you read +- `notevi read FILE:START-END` — line-numbered read; prefer ranges over whole files +- `notevi grep PATTERN [PATH]` — regex search; scope with a path +- `notevi read -r REV FILE`, `notevi grep -r REV PATTERN PATH` — at another jj change +- `notevi note -f FILE:10-42 -t struct TEXT...` — record observations as you read Navigate top-down: grep for the symbol, read the enclosing range, note what you learn. Every entry records the full current jj change and commit IDs; the diff --git a/constrain/README.txt b/constrain/README.txt index 4da2733..6d4b882 100644 --- a/constrain/README.txt +++ b/constrain/README.txt @@ -1,4 +1,4 @@ -Constrain agents to read files only through vr — enforcement comes from +Constrain agents to read files only through notevi — enforcement comes from harness config; AGENTS.md is navigation guidance only, never the constraint. ── files here ────────────────────────────────────────────────────────────── @@ -6,26 +6,28 @@ claude-settings.json project install: /.claude/settings.json (hook path uses $CLAUDE_PROJECT_DIR) claude-headless-settings.json no-install variant for `claude -p --settings` (hook path is absolute into this dir) -vr-only-guard.sh PreToolUse hook: blocks cat/rg/sed/... and +notevi-only-guard.sh PreToolUse hook: blocks cat/rg/sed/... and jj/git content reads; its error message points - the agent at `vr -doc`, so agents converge + the agent at `notevi -doc`, so agents converge even with zero instructions -vr-only.rules codex execpolicy rules (allow vr, forbid readers) -AGENTS.md how-to-navigate-with-vr guidance for the repo +notevi-only.rules codex execpolicy rules (allow notevi, forbid + readers) +AGENTS.md how-to-navigate-with-notevi guidance for the repo ── one-time setup ────────────────────────────────────────────────────────── -put both on PATH: go build -o ~/.local/bin/vr . (repo root) - cd vrsite && go build -o ~/.local/bin/vrsite . - (vr is what agents call; vrsite is how you read the trace) +put it on PATH: go build -o ~/.local/bin/notevi . (repo root) + + one binary now: agents call `notevi read/grep/note`, and you + read the trace back with `notevi web` ── running a constrained CLAUDE investigation ────────────────────────────── From the target jj repo dir: - claude -p --settings /constrain/claude-headless-settings.json \ - --allowedTools 'Bash(vr)' 'Bash(vr:*)' \ + claude -p --settings /constrain/claude-headless-settings.json \ + --allowedTools 'Bash(notevi)' 'Bash(notevi:*)' \ < prompt.txt > report.md - ( is wherever this repo lives; the settings file's hook path is + ( is wherever this repo lives; the settings file's hook path is absolute into this directory, so it must be spelled out in full) - --allowedTools on the CLI is required headless: allow rules inside @@ -36,42 +38,45 @@ From the target jj repo dir: eaten by the flag's list parsing. ── running a constrained CODEX investigation ─────────────────────────────── - cp vr-only.rules ~/.codex/rules/ # activate (GLOBAL: constrains - # every codex session while there) + cp notevi-only.rules ~/.codex/rules/ # activate (GLOBAL: constrains + # every codex session while there) codex exec -s danger-full-access "$(cat prompt.txt)" > report.md - rm ~/.codex/rules/vr-only.rules # deactivate when done + rm ~/.codex/rules/notevi-only.rules # deactivate when done - danger-full-access is required: workspace-write blocks jj metadata writes, - including the sidecar rewrite and `vr read`'s working-copy snapshot. + including the sidecar rewrite and `notevi read`'s working-copy snapshot. - enforcement is pre-exec by codex's execpolicy engine, even through `zsh -lc` wrappers; validate rules with: - codex execpolicy check --rules vr-only.rules -- cat foo.txt + codex execpolicy check --rules notevi-only.rules -- cat foo.txt ── shared trace + rendering ──────────────────────────────────────────────── - - vr creates one anonymous sidecar change directly under jj's root() on the - first append. The repo-local alias vr_log names it; git.private-commits - protects it from accidental pushes. No path or environment setup exists. + - notevi creates one anonymous sidecar change directly under jj's root() on + the first append. The repo-local alias notevi_log names it; + git.private-commits protects it from accidental pushes. No path or + environment setup exists. + - A repository last written by the older `vr` tool is renamed in place the + first time notevi touches it; `notevi migrate ...` does it up front. - Multiple agents in the repo share that sidecar. Rewrites are serialized, note ids are assigned under the same lock, and the project @ is untouched. - The sidecar has no bookmark and is local-only. It does not survive a fresh clone; include the jj repository in backups when the trace matters. - In the prompt, tell the agent to leave pinned notes - (vr note -f FILE:START-END -t kind "...") — that is the payload. - - Read traces afterwards from one process. Bare `vrsite` scans below ~ at + (notevi note -f FILE:START-END -t kind "...") — that is the payload. + - Read traces afterwards from one process. Bare `notevi web` scans below ~ at startup and when Refresh is pressed, then offers every repo with a sidecar: - vrsite + notevi web Its explicit buttons can start an empty private sidecar in a discovered jj repo or initialize colocated jj in a discovered Git repo. The project index is only in memory; those two requested metadata changes are the only writes. - To bypass the dashboard and serve one repository directly: - vrsite -repo -title "..." + notevi web -repo -title "..." or take a static copy to hand around (one change, no server features): - vrsite -repo -out site -title "..." - `vrsite -h` explains both, and what a log needs to be worth reading. + notevi web -repo -out site -title "..." + `notevi web -h` explains both, and what a log needs to be worth reading. ── known holes (accepted) ────────────────────────────────────────────────── Scripting runtimes (python/node/perl) can still open files — uncomment their -rules in vr-only.rules / extend the hook to close, at the cost of breaking +rules in notevi-only.rules / extend the hook to close, at the cost of breaking legitimate scripts. Neither harness constrains its own non-shell internals beyond what the deny rules cover. Codex's rules file is global-only; there is no per-project rules mechanism (probed, none exists as of codex 0.145). diff --git a/constrain/claude-headless-settings.json b/constrain/claude-headless-settings.json index 30aff9b..b22704c 100644 --- a/constrain/claude-headless-settings.json +++ b/constrain/claude-headless-settings.json @@ -1,7 +1,7 @@ { "permissions": { "deny": ["Read", "Grep", "Glob"], - "allow": ["Bash(vr)", "Bash(vr:*)"] + "allow": ["Bash(notevi)", "Bash(notevi:*)"] }, "hooks": { "PreToolUse": [ @@ -10,7 +10,7 @@ "hooks": [ { "type": "command", - "command": "/home/goblin/00-projects/01-tools/vr-agent-logger/constrain/vr-only-guard.sh" + "command": "/home/goblin/00-projects/01-tools/notevi/constrain/notevi-only-guard.sh" } ] } diff --git a/constrain/claude-settings.json b/constrain/claude-settings.json index 524ccbd..639befb 100644 --- a/constrain/claude-settings.json +++ b/constrain/claude-settings.json @@ -1,7 +1,7 @@ { "permissions": { "deny": ["Read", "Grep", "Glob"], - "allow": ["Bash(vr)", "Bash(vr:*)"] + "allow": ["Bash(notevi)", "Bash(notevi:*)"] }, "hooks": { "PreToolUse": [ @@ -10,7 +10,7 @@ "hooks": [ { "type": "command", - "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/vr-only-guard.sh" + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/notevi-only-guard.sh" } ] } diff --git a/constrain/notevi-only-guard.sh b/constrain/notevi-only-guard.sh new file mode 100755 index 0000000..729f4d4 --- /dev/null +++ b/constrain/notevi-only-guard.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# Claude Code PreToolUse hook (matcher: Bash): deny shell commands that read +# files without going through notevi. A guardrail, not a jail — it catches the +# common readers at command position, not every conceivable bypass. +cmd=$(jq -r '.tool_input.command // empty') + +readers='cat|head|tail|less|more|sed|awk|cut|rg|grep|egrep|fgrep|find|fd|strings|xxd|hexdump|od|tac|nl' +pattern='(^|[;&|(`]|\$\()[[:space:]]*('$readers')([[:space:]]|$)' + +if printf '%s' "$cmd" | grep -qE "$pattern"; then + echo "blocked: read/search files only through notevi (run 'notevi -doc' for usage)" >&2 + exit 2 +fi +vcs='jj[[:space:]]+(file[[:space:]]+show|diff)|git[[:space:]]+(show|diff|grep|cat-file|blame|log)' +if printf '%s' "$cmd" | grep -qE "(^|[;&|(\`])[[:space:]]*($vcs)"; then + echo "blocked: use 'notevi read -r REV FILE' / 'notevi grep -r REV' instead of raw jj/git reads" >&2 + exit 2 +fi +exit 0 diff --git a/constrain/notevi-only.rules b/constrain/notevi-only.rules new file mode 100644 index 0000000..f618f18 --- /dev/null +++ b/constrain/notevi-only.rules @@ -0,0 +1,44 @@ +# codex execpolicy: force file reading through notevi (which logs every read). +# Activate: cp notevi-only.rules ~/.codex/rules/ +# Deactivate: rm ~/.codex/rules/notevi-only.rules +# The engine parses through `/bin/zsh -lc '...'` wrappers, so these match the +# inner command. Editing tools (apply_patch) are unaffected — this only +# constrains reading. Known hole: scripting runtimes can still open files; +# uncomment the last block to close it at the cost of breaking legit scripts. + +prefix_rule(pattern=["notevi"], decision="allow") + +prefix_rule(pattern=["cat"], decision="forbidden") +prefix_rule(pattern=["head"], decision="forbidden") +prefix_rule(pattern=["tail"], decision="forbidden") +prefix_rule(pattern=["less"], decision="forbidden") +prefix_rule(pattern=["more"], decision="forbidden") +prefix_rule(pattern=["sed"], decision="forbidden") +prefix_rule(pattern=["awk"], decision="forbidden") +prefix_rule(pattern=["cut"], decision="forbidden") +prefix_rule(pattern=["rg"], decision="forbidden") +prefix_rule(pattern=["grep"], decision="forbidden") +prefix_rule(pattern=["egrep"], decision="forbidden") +prefix_rule(pattern=["fgrep"], decision="forbidden") +prefix_rule(pattern=["find"], decision="forbidden") +prefix_rule(pattern=["fd"], decision="forbidden") +prefix_rule(pattern=["strings"], decision="forbidden") +prefix_rule(pattern=["xxd"], decision="forbidden") +prefix_rule(pattern=["hexdump"], decision="forbidden") +prefix_rule(pattern=["od"], decision="forbidden") +prefix_rule(pattern=["tac"], decision="forbidden") +prefix_rule(pattern=["nl"], decision="forbidden") +prefix_rule(pattern=["jj", "file", "show"], decision="forbidden") +prefix_rule(pattern=["jj", "diff"], decision="forbidden") +prefix_rule(pattern=["git", "show"], decision="forbidden") +prefix_rule(pattern=["git", "diff"], decision="forbidden") +prefix_rule(pattern=["git", "grep"], decision="forbidden") +prefix_rule(pattern=["git", "cat-file"], decision="forbidden") +prefix_rule(pattern=["git", "blame"], decision="forbidden") +prefix_rule(pattern=["git", "log"], decision="forbidden") + +# prefix_rule(pattern=["python3"], decision="forbidden") +# prefix_rule(pattern=["python"], decision="forbidden") +# prefix_rule(pattern=["node"], decision="forbidden") +# prefix_rule(pattern=["perl"], decision="forbidden") +# prefix_rule(pattern=["ruby"], decision="forbidden") diff --git a/constrain/vr-only-guard.sh b/constrain/vr-only-guard.sh deleted file mode 100755 index 0de698f..0000000 --- a/constrain/vr-only-guard.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh -# Claude Code PreToolUse hook (matcher: Bash): deny shell commands that read -# files without going through vr. A guardrail, not a jail — it catches the -# common readers at command position, not every conceivable bypass. -cmd=$(jq -r '.tool_input.command // empty') - -readers='cat|head|tail|less|more|sed|awk|cut|rg|grep|egrep|fgrep|find|fd|strings|xxd|hexdump|od|tac|nl' -pattern='(^|[;&|(`]|\$\()[[:space:]]*('$readers')([[:space:]]|$)' - -if printf '%s' "$cmd" | grep -qE "$pattern"; then - echo "blocked: read/search files only through vr (run 'vr -doc' for usage)" >&2 - exit 2 -fi -vcs='jj[[:space:]]+(file[[:space:]]+show|diff)|git[[:space:]]+(show|diff|grep|cat-file|blame|log)' -if printf '%s' "$cmd" | grep -qE "(^|[;&|(\`])[[:space:]]*($vcs)"; then - echo "blocked: use 'vr read -r REV FILE' / 'vr grep -r REV' instead of raw jj/git reads" >&2 - exit 2 -fi -exit 0 diff --git a/constrain/vr-only.rules b/constrain/vr-only.rules deleted file mode 100644 index 54d3392..0000000 --- a/constrain/vr-only.rules +++ /dev/null @@ -1,44 +0,0 @@ -# codex execpolicy: force file reading through vr (which logs every read). -# Activate: cp vr-only.rules ~/.codex/rules/ -# Deactivate: rm ~/.codex/rules/vr-only.rules -# The engine parses through `/bin/zsh -lc '...'` wrappers, so these match the -# inner command. Editing tools (apply_patch) are unaffected — this only -# constrains reading. Known hole: scripting runtimes can still open files; -# uncomment the last block to close it at the cost of breaking legit scripts. - -prefix_rule(pattern=["vr"], decision="allow") - -prefix_rule(pattern=["cat"], decision="forbidden") -prefix_rule(pattern=["head"], decision="forbidden") -prefix_rule(pattern=["tail"], decision="forbidden") -prefix_rule(pattern=["less"], decision="forbidden") -prefix_rule(pattern=["more"], decision="forbidden") -prefix_rule(pattern=["sed"], decision="forbidden") -prefix_rule(pattern=["awk"], decision="forbidden") -prefix_rule(pattern=["cut"], decision="forbidden") -prefix_rule(pattern=["rg"], decision="forbidden") -prefix_rule(pattern=["grep"], decision="forbidden") -prefix_rule(pattern=["egrep"], decision="forbidden") -prefix_rule(pattern=["fgrep"], decision="forbidden") -prefix_rule(pattern=["find"], decision="forbidden") -prefix_rule(pattern=["fd"], decision="forbidden") -prefix_rule(pattern=["strings"], decision="forbidden") -prefix_rule(pattern=["xxd"], decision="forbidden") -prefix_rule(pattern=["hexdump"], decision="forbidden") -prefix_rule(pattern=["od"], decision="forbidden") -prefix_rule(pattern=["tac"], decision="forbidden") -prefix_rule(pattern=["nl"], decision="forbidden") -prefix_rule(pattern=["jj", "file", "show"], decision="forbidden") -prefix_rule(pattern=["jj", "diff"], decision="forbidden") -prefix_rule(pattern=["git", "show"], decision="forbidden") -prefix_rule(pattern=["git", "diff"], decision="forbidden") -prefix_rule(pattern=["git", "grep"], decision="forbidden") -prefix_rule(pattern=["git", "cat-file"], decision="forbidden") -prefix_rule(pattern=["git", "blame"], decision="forbidden") -prefix_rule(pattern=["git", "log"], decision="forbidden") - -# prefix_rule(pattern=["python3"], decision="forbidden") -# prefix_rule(pattern=["python"], decision="forbidden") -# prefix_rule(pattern=["node"], decision="forbidden") -# prefix_rule(pattern=["perl"], decision="forbidden") -# prefix_rule(pattern=["ruby"], decision="forbidden") -- cgit v1.3