Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 18 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ jobs:
- uses: actions/checkout@v4
- name: Syntax check shell scripts
run: |
# リポジトリ内の追跡対象シェルスクリプトを網羅的に構文チェックする。
# shebang を見てインタプリタを判定する:
# Exhaustively syntax-check every tracked shell script in the repo.
# Pick the interpreter from the shebang:
# - bash shebang -> bash -n
# - #!/bin/sh (= dash) -> sh -n (bash -n は dash で落ちる構文を見逃す)
# - shebang なし -> bash -n (env.sh / env.d/* は bash/zsh が source
# する断片で bash 構文を含むため sh -n だと誤検知)
# - #!/bin/sh (= dash) -> sh -n (bash -n misses syntax that fails under dash)
# - no shebang -> bash -n (env.sh / env.d/* are fragments sourced by
# bash/zsh and contain bash syntax, so sh -n false-flags)
st=0
while IFS= read -r f; do
case "$(head -n1 "$f")" in
Expand All @@ -31,7 +31,7 @@ jobs:
exit $st

ubuntu-setup:
# Codespaces / WSL Ubuntu セットアップ (apt + 並列 *env install) を実走する
# Actually run the Codespaces / WSL Ubuntu setup (apt + parallel *env install)
runs-on: ubuntu-latest
timeout-minutes: 60
env:
Expand All @@ -50,8 +50,8 @@ jobs:
pyenv="$HOME/.pyenv/bin/pyenv"
goenv="$HOME/.goenv/bin/goenv"

# ピン留めバージョンは各 setup スクリプトの VERSION= から抽出し、
# ここでハードコードしないことで定義のドリフトを防ぐ。
# Extract the pinned version from each setup script's VERSION= line
# rather than hardcoding it here, so the check never drifts from the source.
expected_version() {
sed -n 's/^VERSION=\(.*\)$/\1/p' "$1" | head -n1
}
Expand All @@ -72,21 +72,21 @@ jobs:
python_expected="$(expected_version setup.d/ubuntu/004-pyenv.sh)"
go_expected="$(expected_version setup.d/ubuntu/005-goenv.sh)"

# *env exec は PATH/shims に依存せず、各 *env が選択中バージョンの管理下
# バイナリを直接実行する。ただし *env はカレントディレクトリ階層の
# .ruby-version 等のローカル pin を global より優先する。このリポジトリ自身は
# ルートに `.ruby-version = system` を持つため、setup が入れた global を検証
# するには中立なディレクトリ ($HOME) で実行する必要がある。
# node --version → "v24.16.0" の先頭 v を除去
# `*env exec` runs each *env's selected-version binary directly, without
# depending on PATH/shims ordering. However, *env honors a directory-local
# pin (e.g. .ruby-version) over the global one. This repo itself has
# `.ruby-version = system` at its root, so to verify the global that setup
# installed we must run from a neutral directory ($HOME).
# node --version → "v24.16.0": strip the leading v
assert_version node "$node_expected" "$(cd "$HOME" && "$nodenv" exec node --version | sed 's/^v//')"
# ruby --version → "ruby 4.0.5 (...) ..." の 2 フィールド目
# ruby --version → "ruby 4.0.5 (...) ...": 2nd field
assert_version ruby "$ruby_expected" "$(cd "$HOME" && "$rbenv" exec ruby --version | awk '{print $2}')"
# python --version → "Python 3.14.6" の 2 フィールド目
# python --version → "Python 3.14.6": 2nd field
assert_version python "$python_expected" "$(cd "$HOME" && "$pyenv" exec python --version | awk '{print $2}')"
# go version → "go version go1.26.4 linux/amd64"go プレフィックスを除去
# go version → "go version go1.26.4 linux/amd64": strip the go prefix
assert_version go "$go_expected" "$(cd "$HOME" && "$goenv" exec go version | awk '{print $3}' | sed 's/^go//')"

# 不一致時は原因切り分け用に各 *env の選択状況をダンプする
# On mismatch, dump each *env's selection to help diagnose the cause
if [ "$st" -ne 0 ]; then
echo "--- diagnostics ---"
"$rbenv" version || true
Expand Down
99 changes: 54 additions & 45 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,63 @@
# AGENTS.md

このリポジトリで作業する AI エージェント向けのガイド。リポジトリ固有のハマりどころを集約する。

## シェル / 実行環境

- **デフォルトシェルは zsh**(エージェントの Bash ツールも zsh で動く)。スクリプト内で
`status` を変数名に使わないこと — zsh では `status` は読み取り専用で `read-only variable`
エラーになる。`st` などに置き換える。
- `jq` のフィルタで `!=` を使うと zsh が `\!=` にエスケープしてパースエラーになる。
`select(.x | . == null | not)` のように `| not` パターンで書く。

## シェルスクリプトの構文チェック / lint

- `env.sh` と `env.d/**/*.sh` は **shebang を持たない**。シェル起動時に source される断片で、
bash/zsh 構文(関数定義・`[[ ]]` 等)を含む。したがって `sh -n`(dash)では誤検知する。
これらは `bash -n` で検査する。
- CI(`.github/workflows/ci.yml` の lint ジョブ)は追跡対象の全 `*.sh` を `git ls-files` で
列挙し、shebang で判定する: `*bash*` → `bash -n`、`#!...sh` → `sh -n`、**shebang なし →
`bash -n`**(上記の source 断片のため)。

## *env(言語ランタイム)

- バージョンのピン留めは `setup.d/ubuntu/00X-*.sh` の `VERSION=` に一元化されている
(nodenv=002 / rbenv=003 / pyenv=004 / goenv=005)。変更はこのファイルだけを書き換える。
- CI はインストール結果がこのピン留め値と一致するかをアサートする(不一致で fail)。期待値は
各 setup スクリプトの `VERSION=` から抽出するのでハードコードしない。
- **バージョン検証時は shims を PATH 先頭に置くこと。** `rbenv init -` / `goenv init -` は
shims を PATH 先頭に入れない実装があり、ランナー同梱の system ruby/go が優先されてしまう。
`export PATH="$HOME/.rbenv/shims:$HOME/.goenv/shims:...:$PATH"` のように明示する
(nodenv/pyenv は `init -` だけで効く)。
- リポジトリ更新は `git clone` 以外(パッケージ・手動展開・symlink)で配置された場合に備え、
`[ -d ~/.Xenv/.git ]` ガードで囲み `git pull --ff-only`(意図しない merge を作らない)。
Guide for AI agents working in this repository. It collects repo-specific
gotchas.

## Shell / execution environment

- **The default shell is zsh** (the agent's Bash tool runs under zsh too). Do not
use `status` as a variable name in scripts — in zsh `status` is read-only and
assigning to it raises a `read-only variable` error. Use `st` or similar.
- Using `!=` in a `jq` filter gets escaped by zsh to `\!=` and causes a parse
error. Write it with the `| not` pattern, e.g. `select(.x | . == null | not)`.

## Shell script syntax checking / linting

- `env.sh` and `env.d/**/*.sh` have **no shebang**. They are fragments sourced at
shell startup and contain bash/zsh syntax (function definitions, `[[ ]]`, etc.),
so `sh -n` (dash) false-flags them. Check these with `bash -n`.
- CI (the lint job in `.github/workflows/ci.yml`) enumerates every tracked `*.sh`
via `git ls-files` and decides the interpreter from the shebang: `*bash*` →
`bash -n`, `#!...sh` → `sh -n`, **no shebang → `bash -n`** (for the sourced
fragments above).

## *env (language runtimes)

- Version pins are centralized in the `VERSION=` line of each
`setup.d/ubuntu/00X-*.sh` (nodenv=002 / rbenv=003 / pyenv=004 / goenv=005).
Change only that file.
- CI asserts that the installed result matches this pin (and fails on mismatch).
The expected value is extracted from each setup script's `VERSION=` line, so do
not hardcode it.
- **When verifying versions, run from a neutral directory and don't rely on
PATH/shims.** `rbenv init -` / `goenv init -` may not prepend shims to PATH, so
the runner's system ruby/go can win. Also, *env honors a directory-local pin
(e.g. `.ruby-version`) over the global one — and this repo's root has
`.ruby-version = system`. CI therefore runs `*env exec` from `$HOME` to check
the global version that setup installed.
- For repo updates, guard with `[ -d ~/.Xenv/.git ]` and use `git pull --ff-only`
so a non-git install (package / manual extraction / symlink) doesn't break and
no unintended merge commit is created.

## GnuPG

- `~/.gnupg` は `rc.d/gnupg` への symlink。`gpg-agent.conf` は **生成物で gitignore 対象**。
ソースは `rc.d/gnupg/gpg-agent.conf.linux` / `.darwin` で、`setup.d/dotfiles.sh` が
プラットフォーム別にコピー生成する。設定変更は生成物ではなく `.linux` / `.darwin` を編集する。
キャッシュ TTL は 1 年(一度 unlock すれば保持)。
- コミットは GPG 署名される(鍵 `036459B1`)。エージェントの非 tty シェルでは対話的 pinentry が
動かずコミットがハングする。**セッション開始時に一度ユーザーが**
`echo | gpg --clearsign -u 036459B1 -o /dev/null` を実行してパスフレーズを agent に
キャッシュさせてから、コミット系の作業を進める。
- `~/.gnupg` is a symlink to `rc.d/gnupg`. `gpg-agent.conf` is a **generated file
and is gitignored**. Its sources are `rc.d/gnupg/gpg-agent.conf.linux` /
`.darwin`, which `setup.d/dotfiles.sh` copies into place per platform. Edit the
`.linux` / `.darwin` sources, not the generated file. The cache TTL is one year
(unlock once and it stays cached).
- Commits are GPG-signed (key `036459B1`). The agent's non-tty shell can't run an
interactive pinentry, so a commit would hang. **At the start of a session, have
the user run** `echo | gpg --clearsign -u 036459B1 -o /dev/null` **once** to
cache the passphrase in the agent before doing any commit work.

## dotfiles symlink 規約
## dotfiles symlink conventions

- プラットフォーム固有ファイルは `*.darwin` / `*.linux` サフィックスで管理し、
`setup.d/dotfiles.sh` の `resolve_os_name` が OS に応じて実体名へ解決する。
- push は必ず `git push origin <current-branch>`bare push 禁止)。
- Platform-specific files are managed with a `*.darwin` / `*.linux` suffix;
`resolve_os_name` in `setup.d/dotfiles.sh` resolves them to the real name per OS.
- Always push with `git push origin <current-branch>` (no bare push).

## このファイルと CLAUDE.md
## This file and CLAUDE.md

- `CLAUDE.md` はこの `AGENTS.md` への symlink。内容は AGENTS.md 側を編集する。
- `CLAUDE.md` is a symlink to this `AGENTS.md`. Edit the content on the AGENTS.md
side.
11 changes: 6 additions & 5 deletions script/setup-ubuntu.sh
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/bin/bash
#
# setup.d/ubuntu/*.sh を実行する。001-apt.sh で依存パッケージを入れてから、
# 残り (002〜 の *env 系・gcloud.sh) は互いに独立なので並列実行する。
# 出力の interleave を避けるため各スクリプトのログはファイルに分離し、
# 失敗時は末尾を表示する。setup.sh から呼ばれるほか、CI から単体でも実行できる。
# Run setup.d/ubuntu/*.sh. First 001-apt.sh installs dependency packages, then
# the rest (the 002+ *env scripts and gcloud.sh) are independent of each other
# and run in parallel. To avoid interleaved output, each script's log is written
# to a separate file, and its tail is shown on failure. Called from setup.sh, and
# can also be run standalone from CI.
set -eu

DOTFILES=$(cd "$(dirname "$0")/.." && pwd)
Expand All @@ -24,7 +25,7 @@ for f in "$DOTFILES"/setup.d/ubuntu/*.sh ; do
fi
done

# どれか 1 つでも失敗したら非ゼロで exit する (全 PID を wait 済みなのでハングはしない)
# Exit non-zero if any one of them failed (we wait on every PID, so no hang)
status=0
for job in $jobs; do
pid=${job%%:*}
Expand Down
7 changes: 4 additions & 3 deletions setup.d/ubuntu/002-nodenv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ if [ ! -d ~/.nodenv/plugins/node-build ]; then
git clone https://github.com/nodenv/node-build.git ~/.nodenv/plugins/node-build
fi

# merge を作らないよう --ff-only で安全に更新する (git リポジトリのときだけ)
# Update safely with --ff-only so no merge commit is created, and only when
# it is actually a git repository.
if [ -d ~/.nodenv/plugins/node-build/.git ]; then
git -C ~/.nodenv/plugins/node-build pull --ff-only
fi

eval "$(~/.nodenv/bin/nodenv init -)"
# Node.js 24 = Active LTS "Krypton" (サポートは 2028-04 まで)
# Node.js 24 = Active LTS "Krypton" (supported until 2028-04)
VERSION=24.16.0
# ソースビルドになった場合に make を並列化 (node-build は通常プリビルドバイナリを取得)
# Parallelize make in case a source build happens (node-build normally fetches a prebuilt binary)
export MAKE_OPTS="-j$(nproc)"
nodenv install -s $VERSION
nodenv global $VERSION
Expand Down
7 changes: 4 additions & 3 deletions setup.d/ubuntu/003-rbenv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ if [ ! -d ~/.rbenv/plugins/ruby-build ]; then
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
fi

# merge を作らないよう --ff-only で安全に更新する (git リポジトリのときだけ)
# Update safely with --ff-only so no merge commit is created, and only when
# it is actually a git repository.
if [ -d ~/.rbenv/plugins/ruby-build/.git ]; then
git -C ~/.rbenv/plugins/ruby-build pull --ff-only
fi

eval "$(~/.rbenv/bin/rbenv init -)"
# Ruby LTS はないため最新安定版を使用 (4.0 )
# Ruby has no LTS, so use the latest stable release (4.0 series)
VERSION=4.0.5
# make を並列化し、rdoc/ri の生成をスキップしてビルドを高速化
# Parallelize make and skip rdoc/ri generation to speed up the build
export MAKE_OPTS="-j$(nproc)"
export RUBY_CONFIGURE_OPTS="--disable-install-doc"
rbenv install -s $VERSION
Expand Down
13 changes: 7 additions & 6 deletions setup.d/ubuntu/004-pyenv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,22 @@ if [ ! -d ~/.pyenv ]; then
cd ~/.pyenv && src/configure && make -C src
fi

# 新しいバージョン定義を取得するため更新する。git clone 以外 (パッケージ
# 管理・手動展開・シンボリックリンク等) で配置された場合に備え、git リポジトリ
# のときだけ更新する。merge を作らないよう --ff-only で安全に取得する。
# Update to pick up new version definitions. In case it was installed by
# something other than git clone (package manager, manual extraction, symlink,
# etc.), only update when it is a git repository. Use --ff-only so no merge
# commit is created.
if [ -d ~/.pyenv/.git ]; then
git -C ~/.pyenv pull --ff-only
fi

# pyenv init - の出力が pyenv コマンド自体を呼ぶため、先に PATH を通しておく
# `pyenv init -` output invokes the pyenv command itself, so put it on PATH first
export PATH="${HOME}/.pyenv/bin:$PATH"
eval "$(~/.pyenv/bin/pyenv init -)"

# Python LTS はないため最新安定版を使用 (3.14 系、サポートは 2030-10 まで)
# Python has no LTS, so use the latest stable release (3.14 series, supported until 2030-10)
VERSION=3.14.6

# make を並列化してビルドを高速化
# Parallelize make to speed up the build
export MAKE_OPTS="-j$(nproc)"
export PYTHON_CONFIGURE_OPTS="--enable-shared"

Expand Down
11 changes: 6 additions & 5 deletions setup.d/ubuntu/005-goenv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,21 @@ if [ ! -d ~/.goenv ]; then
cd ~/.goenv && src/configure && make -C src
fi

# 新しいバージョン定義を取得するため更新する。git clone 以外 (パッケージ
# 管理・手動展開・シンボリックリンク等) で配置された場合に備え、git リポジトリ
# のときだけ更新する。merge を作らないよう --ff-only で安全に取得する。
# Update to pick up new version definitions. In case it was installed by
# something other than git clone (package manager, manual extraction, symlink,
# etc.), only update when it is a git repository. Use --ff-only so no merge
# commit is created.
if [ -d ~/.goenv/.git ]; then
git -C ~/.goenv pull --ff-only
fi

export PATH="${HOME}/.goenv/bin:$PATH"
eval "$(~/.goenv/bin/goenv init -)"

# Go LTS はなく最新 2 系列がサポート対象のため最新安定版を使用
# Go has no LTS; the latest two release series are supported, so use the latest stable
VERSION=1.26.4

# goenv install は通常バイナリ取得のためビルドは走らないが、ソースビルド時に備えて並列化
# goenv install usually fetches a binary so no build runs, but parallelize in case of a source build
export MAKE_OPTS="-j$(nproc)"

goenv install -s $VERSION
Expand Down
Loading