Send back a final answer whose prose is English instead of Japanese

Dotfiles repository for managing shell and tool configurations across macOS and Linux systems using Nix + Home Manager with Flakes.
~/.config/# Install Nix (using Determinate Systems installer)
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
# Clone the repository
git clone https://github.com/kokatsu/dotfiles.git ~/dotfiles
cd ~/dotfiles
# Build and activate Home Manager configuration
DOTFILES_DIR="$PWD" nix run home-manager -- switch --flake . --impure
# User environment (packages / dotfiles) on both Linux and macOS.
# Set this to the actual repository path on each PC.
DOTFILES_DIR="$HOME/dotfiles" home-manager switch --flake "$HOME/dotfiles" --impure
# macOS system settings + Homebrew (quit Chrome first: casks get upgraded in place)
sudo HOSTNAME=$(hostname -s) DOTFILES_DIR="$HOME/dotfiles" darwin-rebuild switch --flake "$HOME/dotfiles" --impure
nix flake update
DOTFILES_DIR="$HOME/dotfiles" home-manager switch --flake "$HOME/dotfiles" --impure
nix develop # Enter development shell with every linter / formatter
just check # Format check, lint, typos, script tests, and flake evaluation
just fmt # Format everything (Nix, Lua, shell, TOML, YAML, JSON, TypeScript)
just # List all recipes
Lefthook runs a subset of these on commit and push (formatters, linters, gitleaks, commitlint); the script tests and flake evaluation only run through just check and CI.
.
โโโ flake.nix # Flake outputs and Home Manager/nix-darwin builders
โโโ flake.lock # Locked dependencies for reproducibility
โโโ justfile # Task runner: checks, formatters, tests
โโโ nix/
โ โโโ home/ # Home Manager modules (packages, files, programs, services)
โ โโโ darwin/ # macOS system and Homebrew configuration
โ โโโ overlays/ # Custom packages and upstream workarounds
โโโ bin/ # User scripts, linked to ~/.local/bin/scripts and on PATH
โโโ scripts/ # Test and helper scripts used by just and CI
โโโ karabiner-config/ # Karabiner-Elements rules written with karabiner.ts
โโโ .config/ # Dotfile sources linked by Home Manager
โโโ zsh/ # Zsh shell configuration
โโโ nvim/ # Neovim configuration
โโโ wezterm/ # WezTerm terminal configuration
โโโ claude/ # Claude Code settings, hooks, and skills
โโโ ... # Other tool configurations
Static application settings live in nix/home/programs/. Home Manager generates Git, Ghostty, ripgrep, Starship, Yazi, Biome, markdown-oxide, ov, Taplo, psql, Vim, Claude Code keybindings, and the Codex base configuration from Nix. Edit these modules, then run home-manager switch --flake . --impure to apply them.
Yazi plugins come from nixpkgs yaziPlugins where available; the remaining plugins and flavors are vendored under .config/yazi/. All are installed through programs.yazi; update them via nix flake update or in Git instead of using ya pkg. Yazi's init.lua and git-changes.sh also need home-manager switch --flake . --impure after editing to apply the changes. Lua, shell scripts, and repository tooling configurations remain under .config/. Claude Code's settings.json keeps its writable repository link. Codex receives a writable copy of its generated configuration and preserves the existing local state sections during activation.
MIT. Third-party notices are listed in THIRD_PARTY_LICENSES.md.
hooks/register.ts 38 lines1import type { Register, SessionMessage } from "claude-code";
2
3const MIN_LATIN = 40;
4
5const ENGLISH_REQUEST =
6 /่ฑ่ช(?:ใ ใ|ใฎใฟ)?ใง|่ฑ่จณ|่ฑๆ|in English|English only/i;
7
8const REASON =
9 "ๆฌๆใ่ฑ่ชใงใใๅใๅ
ๅฎนใๆฅๆฌ่ชใงๆธใ็ดใใฆใใ ใใ (ใณใผใใป่ญๅฅๅญใปใณใใใใกใใปใผใธใฏๅๆใฎใพใพ)ใ";
10
11export function isUntranslated(text: string): boolean {
12 const prose = text
13 .replace(/(```|~~~)[\s\S]*?\1/g, "")
14 .replace(/`[^`\n]*`/g, "")
15 .replace(/https?:\/\/\S+/g, "");
16 if (/[\p{Script=Hiragana}\p{Script=Katakana}]/u.test(prose)) return false;
17 return (prose.match(/[A-Za-z]/g)?.length ?? 0) >= MIN_LATIN;
18}
19
20export function requestsEnglish(messages: readonly SessionMessage[]): boolean {
21 const prompt = messages.findLast((m) =>
22 m.role === "user" && !m.toolResults?.length && m.text !== ""
23 );
24 return ENGLISH_REQUEST.test(prompt?.text ?? "");
25}
26
27export const register: Register = (on) => {
28 on("classic.Stop", async ($, e, next) => {
29 if (
30 isUntranslated(e.last_assistant_message ?? "") &&
31 !requestsEnglish(await $.session.messages())
32 ) {
33 return { block: REASON };
34 }
35 return next(e);
36 }).catch((_$, e, next) => next(e));
37};
38