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
8 changes: 8 additions & 0 deletions .github/workflows/setup-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ on:
- 'herdr/**'
- 'claude/hooks/**'
- 'claude/settings.json'
# zshenv/zshrc は setup/tests/{zshenv,zshrc}.bats の検証対象(PATH 宣言の
# 単一性など)。setup/** を伴わない単独編集でもテストを走らせる。
- 'zshenv'
- 'zshrc'
- '.github/workflows/setup-check.yml'
push:
# feature branch への push は pull_request イベントだけで検証する
Expand All @@ -36,6 +40,10 @@ on:
- 'herdr/**'
- 'claude/hooks/**'
- 'claude/settings.json'
# zshenv/zshrc は setup/tests/{zshenv,zshrc}.bats の検証対象(PATH 宣言の
# 単一性など)。setup/** を伴わない単独編集でもテストを走らせる。
- 'zshenv'
- 'zshrc'
- '.github/workflows/setup-check.yml'

concurrency:
Expand Down
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- `CONTEXT.md` — AI エージェント指示の層(規範フラグメント・グローバル / プロジェクト AGENTS.md・SOUL.md・CLAUDE.local.md・channel prompt)を区別する用語集
- `aliases` — alias 定義の SSOT(root 直下、`~/.aliases` にシンボリックリンク)。旧 `nix/modules/home/zsh.nix` の `shellAliases` から移行(`setup/link.zsh` が配置、Tier 1)
- `scripts/` — 外部シェルスクリプト(`aliases` の alias から呼び出される。旧 `aliase/` から改名)
- `setup/` — Tier 1(リアルタイム symlink)・Tier 2(明示的スクリプト実行)・Tier 3(カットオーバー・ロールバック)の実装。`darwin-rebuild switch` を使わず、`zsh setup/link.zsh` で dotfiles を配置し(Tier 1)、`setup/languages.zsh`(mise による言語ランタイム + corepack + バージョン固定 CLI)/`setup/defaults.zsh`(macOS defaults・IME)/`setup/pam.zsh`(Touch ID for sudo)/`setup/claude-sync.zsh`(skills clone・plugin sync・MCP merge)/`setup/codex-sync.zsh`(config.toml seed-if-absent)/`setup/herdr-sync.zsh`(Herdr ローカルプラグインの link・allowlist 配置)を個別実行する(Tier 2)。既存 PC を home-manager 込みの旧構成から移行する場合は `setup/cutover.zsh`(pre-flight build 確認 + `darwin-rebuild switch`)/`setup/rollback.zsh`(`.before-nix` 衝突検出付きロールバック)を使う(Tier 3)。**実機での実行は `setup/migrate.zsh` が唯一のエントリポイント**(`--dry-run`/`--apply`。Tier 1/2/3 を依存順(Phase 1: link → Phase 2: cutover/pam [root] → Phase 3: languages/defaults/claude-sync/codex-sync/herdr-sync)で実行し、`~/.dotfiles-migrate/manifest.log` で部分適用を検出・再開する。fail-closed、rollback.zsh は自動では呼ばない)。個別スクリプトの直接実行はメンテナンス目的のみ。詳細は `setup/README.md` と `docs/superpowers/specs/2026-08-21-restore-script-management-inventory.md`・`docs/superpowers/specs/2026-08-22-restore-script-management-tier3-cutover-design.md`・`docs/superpowers/specs/2026-08-22-migrate-orchestrator-recovery-plan.md` を参照
- `setup/` — Tier 1(リアルタイム symlink)・Tier 2(明示的スクリプト実行)・Tier 3(カットオーバー・ロールバック)の実装。`darwin-rebuild switch` を使わず、`zsh setup/link.zsh` で dotfiles を配置し(Tier 1)、`setup/languages.zsh`(mise による言語ランタイム + corepack + バージョン固定 CLI)/`setup/defaults.zsh`(macOS defaults・IME)/`setup/pam.zsh`(Touch ID for sudo)/`setup/claude-sync.zsh`(skills clone・plugin sync・MCP merge)/`setup/codex-sync.zsh`(config.toml seed-if-absent)/`setup/herdr-sync.zsh`(Herdr ローカルプラグインの link・allowlist 配置)/`setup/notion.zsh`(Notion CLI `ntn` を公式インストーラで `${HOME}/.local/bin` へ install-if-absent。宣言する版とパスは `setup/lib/notion.zsh` に集約し、`setup/migrate.zsh` の health check も同じ定義から引く)を個別実行する(Tier 2)。既存 PC を home-manager 込みの旧構成から移行する場合は `setup/cutover.zsh`(pre-flight build 確認 + `darwin-rebuild switch`)/`setup/rollback.zsh`(`.before-nix` 衝突検出付きロールバック)を使う(Tier 3)。**実機での実行は `setup/migrate.zsh` が唯一のエントリポイント**(`--dry-run`/`--apply`。Tier 1/2/3 を依存順(Phase 1: link → Phase 2: cutover/pam [root] → Phase 3: languages/defaults/claude-sync/codex-sync/herdr-sync/notion)で実行し、`~/.dotfiles-migrate/manifest.log` で部分適用を検出・再開する。fail-closed、rollback.zsh は自動では呼ばない)。個別スクリプトの直接実行はメンテナンス目的のみ。詳細は `setup/README.md` と `docs/superpowers/specs/2026-08-21-restore-script-management-inventory.md`・`docs/superpowers/specs/2026-08-22-restore-script-management-tier3-cutover-design.md`・`docs/superpowers/specs/2026-08-22-migrate-orchestrator-recovery-plan.md` を参照
- `claude/` — Claude Code 設定(`~/.claude/` にシンボリックリンク)
- `claude/rules/` — 全 AI エージェント向けグローバル指示のフラグメント(SSOT)。`core` / `worker` / `orchestrator` / `hermes-identity` の 4 ファイルを `scripts/build-agent-rules.zsh`(旧 `aliase/build-agent-rules.zsh`)が結合して生成物を作る
- `claude/hermes/SOUL.md` — Hermes Agent 用グローバル指示の生成物(`~/.hermes/SOUL.md` にシンボリックリンク)。直接編集しない
Expand All @@ -26,7 +26,7 @@
- `ssh/` — SSH 設定(`~/.ssh/` にシンボリックリンク)
- `zsh/` — zsh 補完ファイル(`~/.zsh/` にシンボリックリンク)
- `zshrc` — zsh 設定(`~/.zshrc` にシンボリックリンク)
- `zshenv` — zsh 環境変数(`~/.zshenv` にシンボリックリンク)
- `zshenv` — zsh 環境変数(`~/.zshenv` にシンボリックリンク)。`${HOME}/.local/bin` を PATH へ載せる唯一の場所(pipx の出力先であり、Homebrew formula が無い CLI の導入先。例: `setup/notion.zsh` が入れる `ntn`)。追加は **append で 1 回だけ** — prepend にすると Homebrew/mise が供給する同名コマンドを横取りして既存の解決順が変わる。`zshrc` 側に同じ追加を書かない(`setup/tests/zshrc.bats` が重複を落とす)

# シンボリックリンク管理

Expand All @@ -44,6 +44,7 @@
- `taps` / `brews` / `casks` / `masApps` の区分を守る
- CLI tool は Homebrew (`homebrew.nix` の `brews`) で管理する(Tier 3 で home-manager の `packages.nix` から完全移行済み)
- 例外として、**バージョンを固定する必要がある CLI tool だけは mise で供給する**(`setup/languages.zsh`、Tier 2)。Homebrew formula は任意バージョンの pin を表現できず、nix-darwin の `homebrew.brews` にも version フィールドが無いため、宣言的に版を固定できる経路が mise しかないことによる。現状の対象は standalone `dolt`(beads が要求する 2.2.0)。Homebrew が同名 formula を他パッケージの依存として引き込んでいてもそれは削除せず、PATH 先頭を mise が取ることで実行体だけを mise 側に寄せる(non-interactive shell は `zshenv` の `mise activate --shims`、interactive shell は `zshrc` の `mise activate zsh` が `brew shellenv` の後に走ることによる)
- もう 1 つの例外は **Homebrew formula が無く、かつ宣言的に供給できる経路がランタイム依存しか無い CLI tool** で、その場合だけ公式インストーラを呼ぶ専用の Tier 2 スクリプトを立てる。現状の対象は Notion CLI の `ntn`(`setup/notion.zsh`)。`ntn` は mise の npm backend でも導入できるが、グローバル CLI を Node ランタイムに依存させないため公式配布バイナリを選んでいる。版は宣言側(`setup/lib/notion.zsh` の `NTN_PINNED_VERSION`)で固定し、インストーラ既定の `latest`(導入した日で版が決まる)には倒さない。更新は dotfiles 側の明示変更で行い、実機の版ずれは `setup/migrate.zsh` の health check が fail-closed で検出する(自動差し替えはしない)
- PC ローカル専用の cask は `~/.config/dotfiles/homebrew.local.nix`(リポジトリ外配置)で declarative に宣言する。`homebrew.nix` が絶対パスで `builtins.pathExists` + `import` する。用途は「git に追跡させたくないが `default` role の zap から守りたい cask」(例: 特定アカウントの個人用ツール、業務用アプリ)。別 PC では復元されないため、再現性が必要なものは `homebrew.nix` 本体に書くこと。現状 casks のみ対応(brews / taps / masApps の overlay が必要になったら `homebrew.nix` の `local` 解決を拡張する)。nix flake は git tree のみコピーするため、`.gitignore` で除外したリポジトリ内ファイルは flake から不可視になる点に注意(リポジトリ外配置を選んでいる理由)

# zsh スクリプト規約
Expand Down
55 changes: 51 additions & 4 deletions setup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Tier を跨いだ実行順序・部分適用の検出/復旧を担う `setup/mig
## 使い方(実機での唯一のエントリポイントは `setup/migrate.zsh`)

Tier 1/2/3 の各スクリプト(`link.zsh`/`languages.zsh`/`defaults.zsh`/`pam.zsh`/
`claude-sync.zsh`/`codex-sync.zsh`/`herdr-sync.zsh`/`cutover.zsh`)を実機で直接実行することは非推奨。
`claude-sync.zsh`/`codex-sync.zsh`/`herdr-sync.zsh`/`notion.zsh`/`cutover.zsh`)を実機で直接実行することは非推奨。
順序管理なしに個別実行すると部分適用インシデントを再現する(過去に実際に発生した)。
実機での実行は必ず `setup/migrate.zsh` からのみ行う:

Expand All @@ -33,7 +33,7 @@ alias・関数に依存しないため、これが標準の入口。
zsh ${HOME}/.dotfiles/setup/migrate.zsh --dry-run

# 計画を実行する。単一の root 起動で全 Phase (link -> cutover/pam -> languages/defaults/
# claude-sync/codex-sync/herdr-sync) が完結する。sudo が自動設定する SUDO_USER から元ユーザーを
# claude-sync/codex-sync/herdr-sync/notion) が完結する。sudo が自動設定する SUDO_USER から元ユーザーを
# 特定し、非 root ステップは元ユーザーへ委譲実行する(詳細は
# docs/superpowers/specs/2026-08-22-migrate-orchestrator-recovery-plan.md 参照)
sudo zsh ${HOME}/.dotfiles/setup/migrate.zsh --apply
Expand Down Expand Up @@ -67,6 +67,7 @@ zsh ${HOME}/.dotfiles/setup/pam.zsh
zsh ${HOME}/.dotfiles/setup/claude-sync.zsh
zsh ${HOME}/.dotfiles/setup/codex-sync.zsh
zsh ${HOME}/.dotfiles/setup/herdr-sync.zsh
zsh ${HOME}/.dotfiles/setup/notion.zsh
sudo USER=${USER} zsh ${HOME}/.dotfiles/setup/cutover.zsh
```

Expand Down Expand Up @@ -105,6 +106,42 @@ symlink 越しに即座に反映される。再実行が必要なのは「`setup
primary 以外から実行された場合は両方まとめてスキップする。登録先パスが既に一致して
いれば何もせず、`repos.local.json`(マシンローカルの allowlist)は seed-if-absent で
既存の中身に触れない。
- `notion.zsh`: Notion CLI (`ntn`) に Homebrew formula が無いため、公式インストーラ
(`https://ntn.dev/install.sh`) を使う唯一の Tier 2 ステップ。mise の npm backend でも
導入できるが、グローバル CLI を Node ランタイムに依存させないため公式配布バイナリを
使う。インストーラには
2 つの環境変数を渡して導入結果を宣言側で決めきる:
- `NTN_INSTALL_DIR` = `${HOME}/.local/bin`。インストーラ既定の導入先選択は実行時の PATH の
形に依存して揺れるため、宣言側で固定して health check と一致させる
- `NTN_VERSION` = `setup/lib/notion.zsh` の `NTN_PINNED_VERSION`(現在 `0.23.4`)。既定の
`latest` だと「導入した日」で版が決まり PC ごとに別物が入る。版を上げるのは dotfiles
側の明示変更で行う

`${HOME}/.local/bin/ntn` が既に実行可能なファイルなら **インストーラを一切呼ばない**
(既存バイナリを上書きしない)。判定は `-x` 単独ではなく `-f && -x`(実行ビットの立った
ディレクトリを「導入済み」と誤判定しないため。health check も同じ条件)。

この install-if-absent だけだと、宣言の版を上げても既に実体のある PC は古いまま success に
なり続ける(版が効くのは新規導入時だけ)。そこで `migrate.zsh` の health check が
`ntn --version` を実行し、`NTN_PINNED_VERSION` と一致しなければ **fail-closed** で落とす。
自動では差し替えない — 実行中かもしれないバイナリを migrate が黙って置き換えないため、
人が実体を削除してから `--apply` を再実行する。宣言値とパスは `setup/lib/notion.zsh` に
寄せてあり、導入する側と確認する側が同じ定義を引く(`setup/lib/herdr.zsh` と同じ理由)。

この probe は root 起動時に `sudo -u <元ユーザー> -H --` を前置して元ユーザーとして実行する
(`migrate::ntn_version`)。実体は元ユーザーの `$HOME` 配下にあって本人が書き換えられる
ファイルなので、検証のために root の権限で走らせる理由が無い。非 root ステップの委譲実行と
同じ規則で、元ユーザーを特定できないときは probe せず fail-closed に落ちる。

`curl` は `bash` に直結せず一旦ファイルへ落とす(取得失敗時に空スクリプトを実行して
「成功」に見えるのを防ぐ)。**初回ダウンロードの失敗は fail-closed** で migrate 全体を
止める。`ntn` は恒久的に宣言したグローバル必須ツールなので「入らなかったが成功」を健全な
状態として扱わない。既にバイナリがある PC では一切ネットワークに出ないため、オフラインでも
`--apply` は通る(ネットワークが要るのは初回導入のときだけ)。

トークン(`NOTION_API_KEY` 等)は読まない・要求しない・保存しない。導入後の認証は人間が
`ntn` 側の手順で行う。PATH への `${HOME}/.local/bin` 追加は Tier 1 の `zshenv` が
append で 1 箇所だけ行う(`zshrc` 側には書かない)。
- `cutover.zsh`: 実行前に `darwin-rebuild --list-generations` の出力を
`~/.dotfiles-cutover-backup/pre-cutover-generations-<timestamp>.txt` へ記録してから
`nix build`(副作用なし)で pre-flight 確認し、成功したときだけ `darwin-rebuild switch`
Expand All @@ -115,7 +152,7 @@ symlink 越しに即座に反映される。再実行が必要なのは「`setup
`fs::ensure_realfile` と同じ no-data-loss 方針で、自動退避はしない)。
- `migrate.zsh`: Tier 1/2/3 を跨いだ唯一のオーケストレーター。実行順序は Phase 1
(`link`) → Phase 2 (`cutover`/`pam`、root 必須) → Phase 3 (`languages`/`defaults`/
`claude-sync`/`codex-sync`/`herdr-sync`)。`languages.zsh` 自身が「mise は darwin-switch で事前導入
`claude-sync`/`codex-sync`/`herdr-sync`/`notion`)。`languages.zsh` 自身が「mise は darwin-switch で事前導入
済みが前提」と明記しているため、cutover を languages より先に置く。各ステップの結果は
`~/.dotfiles-migrate/manifest.log` に永続化し、success 済みステップは再実行しない
(idempotent な部分適用検出・再開)。ただし `cutover` だけは、必須バイナリの実在と
Expand All @@ -134,6 +171,10 @@ bats setup/tests/*.bats
bats herdr/plugins/*/tests/*.bats
```

`setup/tests/` は `setup/**` だけでなく `zshenv`/`zshrc` も検証対象にしている(PATH 宣言を
1 箇所に保つ `zshenv.bats`/`zshrc.bats`)。`.github/workflows/setup-check.yml` の `paths` にも
この 2 ファイルを含めてあるので、`setup/**` を伴わない単独編集でも CI が走る。

`setup/lib/herdr.zsh` は Herdr プラグインの識別子とパス解決だけを持つ共有ライブラリ。
配置する側(`herdr-sync.zsh`)と確認する側(`migrate.zsh` の health check)が別々に
パスを組み立てると、Herdr が設定ディレクトリの位置を変えたときに health check だけが
Expand All @@ -157,11 +198,17 @@ bats herdr/plugins/*/tests/*.bats
委譲実行を health check にまで広げるほどの利得が無いため)。

`fs::link_file`/`fs::ensure_realfile` は関数単位、`link.zsh`/`languages.zsh`/`defaults.zsh`/
`pam.zsh`/`claude-sync.zsh`/`codex-sync.zsh`/`herdr-sync.zsh`/`cutover.zsh`/`rollback.zsh`/
`pam.zsh`/`claude-sync.zsh`/`codex-sync.zsh`/`herdr-sync.zsh`/`notion.zsh`/`cutover.zsh`/`rollback.zsh`/
`migrate.zsh` は、
実コマンド(`defaults`/`mise`/`corepack`/`claude`/`herdr`/`git`/`darwin-rebuild`/`nix`)を PATH 上の
stub 実行ファイルに差し替え、`$HOME` を一時ディレクトリに差し替えたサンドボックスでの統合テスト
(実機・実ネットワーク・実パッケージマネージャ・実 `darwin-rebuild switch` には一切触れない)。

`notion.zsh` だけは `curl` の扱いが 2 通りある。単体テスト(`setup/tests/notion.bats`)は
`curl` を stub に差し替えて取得失敗の経路まで見る。`migrate.zsh` 経由の統合テストでは stub を
使わず、`NTN_INSTALLER_URL` に `file://` の偽インストーラを渡して **実 `curl` をオフラインで**
走らせる。`migrate.zsh` の委譲実行は Homebrew prefix を PATH 先頭に固定で差し込むため、
そこでの `curl` stub は実機に Homebrew 版 `curl` があると負けて実ネットワークに出てしまう。
`migrate.zsh` のテストは Tier 1 が作る `~/.zshenv` symlink を経由して後続の子 `zsh` プロセスが
実際の zshenv を re-source する(Phase を跨いだ実行を初めて連結するテストのため、単独スクリプトの
テストでは踏まなかった経路)。stub 実行ファイルを `#!/bin/bash` にしているのはこのため
Expand Down
29 changes: 29 additions & 0 deletions setup/lib/notion.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/bin/zsh
# setup/lib/notion.zsh
#
# Notion CLI (ntn) の宣言値とパス解決だけを持つ共有ライブラリ。導入する側
# (setup/notion.zsh) と確認する側 (setup/migrate.zsh の health check) が同じ定義を
# 引くためにここへ寄せている。別々に書くと、版を上げたときに片方だけ古い値を見に行く
# (setup/lib/herdr.zsh を分けているのと同じ理由)。

# 宣言する版。上げるときはここを書き換える。
#
# 既存バイナリの自動差し替えはしない。版を上げると health check が fail-closed で
# 落ちるので、人が実体(notion::bin のパス)を削除してから --apply を再実行する。
# 実行中かもしれないバイナリを migrate が黙って置き換えないための取り決め。
NTN_PINNED_VERSION="0.23.4"

# notion::bin <home> <home> を持つユーザーの ntn 実体パス
notion::bin() {
echo "${1}/.local/bin/ntn"
}

# notion::installed_version <cmd> [<args>...] 実体が報告する版を返す。取得できなければ
# 空文字列。`ntn --version` は `ntn 0.23.4` の形で出すので最終フィールドを取る。
#
# 引数は「ntn を起動する argv 全体」であって実体パス 1 個とは限らない。呼び出し側が
# `sudo -u <user> -H --` を前置して別ユーザーとして probe できるようにするため
# (migrate.zsh の health check は root で走りつつ元ユーザーの実体を見る)。
notion::installed_version() {
"$@" --version 2>/dev/null | awk 'NR == 1 { print $NF }'
}
Loading
Loading