Skip to content

docs(hook): decide how a sourced target follows an apply - #79

Merged
torabit merged 2 commits into
mainfrom
torabit/docs/hook
Sep 8, 2026
Merged

torabit merged 2 commits into
mainfrom
torabit/docs/hook

Conversation

@torabit

@torabit torabit commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Closes #77.

docs/hook.md. The document, not the implementation: the issue carries the design label, and
CLAUDE.md resolves a design issue to a document under docs/ before code is written. The
implementation follows in its own PR.

What is decided

vanadis hook <zsh|fish|bash> prints a snippet to eval, the shape direnv, mise, starship
and zoxide already have. hook and not init, because vanadis init is taken by the command
that turns an existing config file into a template.

A per-target shell key, beside reload. A shell name rather than a boolean, so a zsh
target and a fish target sit side by side and each hook sources its own. Not a top-level
table, which would be a second place a target's name is written.

The change signal is the output's own mtime. A missing output sources nothing and is not an
error, because a prompt hook says whatever it says forever. The resolution is one second, and
the gap that leaves is recorded rather than closed.

The paths are baked in. A config parse per prompt is a vanadis process per prompt, which is
what docs/config.md already refused for get. Re-eval is the answer to a stale snippet, and
a baked snippet still works in a shell whose $PATH has lost vanadis.

zsh and fish detect a change with no fork (zstat, path mtime); bash spends one
stat
, written into the snippet at generation time from the system vanadis is on, so there is
no runtime branch. The fork-free bash alternative ([[ output -nt stamp ]]) is rejected in the
document: the stamp has to be per shell process, and removing it needs the one EXIT trap the
user also has.

Two invariants for the snippet: a second eval leaves one hook registered, and the hook
returns the exit status it found, so a prompt that shows the last command's status is not lied
to.

Also in this PR

The zsh row of the reload table in docs/config.md now says what does reach it, and the
document is cross-referenced from the header.

Deliberately not in this PR

The same row in skills/vanadis/references/reload.md, and the CI that runs the snippet under
each shell. The skill ships to users; it should not name vanadis hook before a released binary
has it. Both land with the implementation.

🤖 Generated with Claude Code

https://claude.ai/code/session_011wxMB7uC4uxSJ2dfHi8ucf

torabit and others added 2 commits September 8, 2026 03:51
A target whose output a shell sources cannot be reached by `reload`. The shell
that has the wrong colours is vanadis's parent, and no process replaces its
parent's image, so `exec zsh` is unreachable however it is spelled. fzf is the
entry `docs/config.md` names for this, and `LS_COLORS`, `GREP_COLORS` and a
pager named through a variable are the same shape.

`vanadis hook <shell>` prints a snippet to `eval`, the way direnv, mise,
starship and zoxide do. The snippet sources the outputs of the targets marked
`shell = "zsh"` and registers a prompt hook that sources each again when its
own mtime has moved. The state file is not the signal: `apply --only nvim`
moves it without touching a shell target, and watching it would put
`$XDG_STATE_HOME` and a file format into a snippet that otherwise needs only a
path.

The decisions the document records: a per-target `shell` key rather than a
top-level table, because a table naming targets is a second place a name is
written; paths baked into the snippet rather than a config parse per prompt,
with re-`eval` as the answer to staleness; zsh and fish detecting a change with
no fork, and bash spending one `stat`, with the `stat` chosen when the snippet
is generated rather than branched at runtime; a stamp file compared with `-nt`
rejected for bash because cleaning it up needs the one `EXIT` trap the user
also has; one hook after a second `eval`, and the exit status restored so a
prompt that shows it is not lied to.

The zsh row of the `reload` table now says what does reach it. The same row in
the shipped skill, and the CI that runs the snippet under each shell, land with
the implementation.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wxMB7uC4uxSJ2dfHi8ucf
The state file as the signal, a top-level table, and a config read at prompt
time are each argued where they come up. Restating them under Rejected
alternatives said the same thing twice and made the section long enough to read
as the point of the document. What is left is the five the body never raises.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wxMB7uC4uxSJ2dfHi8ucf
@torabit
torabit merged commit a8271e0 into main Sep 8, 2026
7 checks passed
@torabit
torabit deleted the torabit/docs/hook branch September 8, 2026 02:33
@torabit torabit mentioned this pull request Sep 8, 2026
torabit added a commit that referenced this pull request Sep 8, 2026
#79 was squashed into main, so `docs/hook.md` arrives from both sides as an
add. The branch holds the same document plus the two paragraphs this
implementation added to it: the fish spelling of the eval line, and why the
fish CI run needs a terminal. Resolved to the branch's copy, which is main's
copy with those two additions and nothing else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011wxMB7uC4uxSJ2dfHi8ucf
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ship a shell prompt hook, so a target read from the environment can follow a cycle

1 participant