Provide entry points
Set one small global in your app so every annotation you capture carries your project context.
How it works
When your app loads, it sets a small window.__HAVI_DEV__ global with your project context. HAVI reads it when you capture an annotation and attaches it to the report.
It is optional and safe. If the global is missing, capture still works — the extra context is simply left out. Nothing runs on every request; the values are read straight from the page.
The fast way
Claude Code and CodexOnce HAVI is connected, run its setup-hooks prompt. Your agent detects your framework (Next.js, Vite, or Phoenix) and writes the setup — nothing to install.
It is one of the HAVI slash-command prompts the connected server provides, alongside /review, /triage, and /resolve — pick it from the / menu.
What gets attached
- project
- Repository name
- branch
- Current git branch
- commit
- Short commit hash
- worktree
- Worktree name, when you work in one
- port
- Dev server port
Add anything else your app knows: environment name, app version, feature flags, or the active A/B variant. Those fields ride along on every report so the agent can act with full context.
Do it by hand
Any framework works — compute your context at build/boot and set window.__HAVI_DEV__ from it. Templates for the common ones:
Next.js
// next.config.js — compute git context once at server boot
const { execSync } = require("child_process");
const git = (cmd) => {
try { return execSync(`git ${cmd}`, { encoding: "utf-8" }).trim(); }
catch { return ""; }
};
const worktree =
git("rev-parse --git-common-dir") !== git("rev-parse --git-dir")
? (process.cwd().split("/").pop() || "")
: "";
const haviDev = {
project: git("remote get-url origin").replace(/.*\//, "").replace(/\.git$/, ""),
worktree,
branch: git("rev-parse --abbrev-ref HEAD"),
commit: git("rev-parse --short HEAD"),
port: process.env.PORT || "3000",
// add your own fields here, e.g.:
env: process.env.NODE_ENV,
};
module.exports = { env: { NEXT_PUBLIC_HAVI_DEV: JSON.stringify(haviDev) } };
// In app/layout.tsx (App Router), inside <head>:
// <script dangerouslySetInnerHTML={{ __html:
// `window.__HAVI_DEV__ = ${process.env.NEXT_PUBLIC_HAVI_DEV || "{}"};` }} />
// (Pages Router: put the same <script> in pages/_document.tsx's <Head>.)
// Restart your dev server after switching branches — values are captured at boot.
Vite
// vite.config.js — inject git context at config load
import { execSync } from "child_process";
import { defineConfig } from "vite";
const git = (cmd) => {
try { return execSync(`git ${cmd}`, { encoding: "utf-8" }).trim(); }
catch { return ""; }
};
const worktree =
git("rev-parse --git-common-dir") !== git("rev-parse --git-dir")
? (process.cwd().split("/").pop() || "")
: "";
const haviDev = {
project: git("remote get-url origin").replace(/.*\//, "").replace(/\.git$/, ""),
worktree,
branch: git("rev-parse --abbrev-ref HEAD"),
commit: git("rev-parse --short HEAD"),
port: Number(process.env.PORT) || 5173,
};
export default defineConfig({
// double-encode: define does raw text substitution, so the value must be a string literal
define: { "import.meta.env.VITE_HAVI_DEV": JSON.stringify(JSON.stringify(haviDev)) },
});
// In your entry (src/main.js):
// try { window.__HAVI_DEV__ = JSON.parse(import.meta.env.VITE_HAVI_DEV || "{}"); } catch {}
// Restart your dev server after switching branches — values are captured at boot.
Phoenix
# lib/my_app_web/havi_dev.ex — capture git context once per boot
defmodule MyAppWeb.HaviDev do
@moduledoc false
def script do
json =
case :persistent_term.get({__MODULE__, :json}, nil) do
nil ->
value = Jason.encode!(context())
:persistent_term.put({__MODULE__, :json}, value)
value
value ->
value
end
Phoenix.HTML.raw("window.__HAVI_DEV__ = #{json};")
end
defp context do
if Code.ensure_loaded?(Mix) and Mix.env() == :dev do
%{
project:
git(~w[remote get-url origin])
|> String.replace(~r|.*/|, "")
|> String.replace(~r|\.git$|, ""),
worktree: worktree(),
branch: git(~w[rev-parse --abbrev-ref HEAD]),
commit: git(~w[rev-parse --short HEAD]),
port: System.get_env("PORT") || "4000"
}
else
%{}
end
end
defp worktree do
if git(~w[rev-parse --git-common-dir]) != git(~w[rev-parse --git-dir]) do
File.cwd!() |> Path.basename()
else
""
end
end
defp git(args) do
case System.cmd("git", args, stderr_to_stdout: true) do
{out, 0} -> String.trim(out)
_ -> ""
end
rescue
_ -> ""
end
end
# In root.html.heex, inside <head>:
# <script><%= MyAppWeb.HaviDev.script() %></script>
# Restart your dev server after switching branches — values are captured at boot.
Verify
Restart your dev server, then open your app and check window.__HAVI_DEV__ in the browser console. You should see your context as an object.
Values are captured at boot, so restart your dev server after switching branches. From here, your next captures arrive with project context already filled in.