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
11 changes: 8 additions & 3 deletions .claude/hooks-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -228,13 +228,18 @@ cmd = "pnpm build"

# 順位 147 / PR-W5 (file-length-enforcement-plan.md): file-length Stop gate。
# PR 範囲 (base branch..@) の .rs file が 800 行超なら Stop を block する強制層。
# cmd は cmd.exe (`cmd /c`) 経由で実行されるため forward-slash (`./`) 始まりは不可。
# backslash 相対パスを TOML literal string (single quote、escape 不要) で指定する。
#
# cmd は OS のシェル経由で実行される (Windows: cmd.exe / Linux: sh、WP-15)。
# 両シェルで共通に解決できるのは **forward-slash の絶対パス**だけなので
# (cmd.exe は forward-slash 相対を、sh は backslash を解決できない = 実測)、
# hooks-stop-quality が展開する 2 つのプレースホルダーで書く:
# {{CLAUDE_DIR}} → .claude/ の絶対パス (forward-slash 正規化)
# {{EXE_SUFFIX}} → .exe (Windows) / 空文字 (Linux)
# gate の有効化は下記 [file_length_gate] enabled で制御 (この step があっても
# enabled = false なら exe は即 exit 0 で no-op)。
[[stop_quality.steps]]
name = "file-length"
cmd = '.\.claude\hooks-post-tool-comment-lint-rust.exe --check-modified-files'
cmd = '{{CLAUDE_DIR}}/hooks-post-tool-comment-lint-rust{{EXE_SUFFIX}} --check-modified-files'

# ─── PR-W5: file-length Stop gate 設定 (ADR-039 experimental feature 標準パターン) ───
#
Expand Down
156 changes: 156 additions & 0 deletions .github/workflows/release-binaries.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# release-binaries — Linux バイナリのビルドと rolling release への公開 (WP-15)
#
# 役割: master への push で全 Rust 成果物を x86_64-unknown-linux-gnu 向けにビルドし、
# 固定タグ `nightly` の prerelease へ単一 tarball として公開する。使い捨ての
# クラウドセッション (claude.ai/code) が 19 crate をビルドせずにハーネスを即時
# 有効化できるようにするのが目的。取得側は scripts/cloud-setup.sh。
#
# 設計メモ:
# - **単一 tarball で公開する**: バイナリを個別 asset にすると、run が途中で失敗/
# キャンセルされたとき release に新旧が混在した不整合なセットが残る。1 asset なら
# 差し替えが実質アトミックになり、取得側も 1 回のダウンロードで済む。
# - **rolling tag (`nightly`) を上書き更新する**: 使い捨て環境向けなので「常に master
# 最新」であればよく、バージョン解決を setup script に持ち込みたくない。取得側は
# 常に同一 URL を叩けばよい。再現性が要る場合は tarball 内の BUILD_INFO に
# commit SHA が入っているので、そこから逆引きできる。
# - **prerelease にする**: `gh release list` の latest に載せず、人間向けの正式
# リリースと混同されないようにする。
# - **バイナリ一覧は cargo metadata から導出する**: package.json の build:* や
# deploy-hooks.ts の allowlist に列挙をコピーすると、crate 追加時に片方だけ
# 更新されて無言で欠落する (実際 deploy-hooks.ts の allowlist は 11 個で、
# settings.local.json.template が参照する 3 つの hook exe を既に取りこぼしている)。
# workspace の bin target を機械的に集めることで、この drift を構造的に断つ。
# - **公開前に cargo test を通す**: 壊れたバイナリを rolling release に載せると、
# クラウドセッション側が原因不明の挙動不良を起こす (しかも setup は成功する) ため。
# Windows leg と hooks smoke test を含む本格的な CI matrix は WP-16 で扱う。
# - **paths フィルタで docs-only push を除外する**: 本リポジトリは docs / todo 更新の
# push が多く、そのたびに全 crate を再ビルドするのは無駄。バイナリの内容に影響する
# パスに限定する。
# - checkout は persist-credentials: false (pr-monitor.yml と同じ token 漏洩対策)。
# release の publish に使う token は該当 step の env にのみ置く。

name: release-binaries

on:
push:
branches: [master]
paths:
- "src/**"
- "Cargo.toml"
- "Cargo.lock"
- ".github/workflows/release-binaries.yml"
workflow_dispatch:

# 同時実行を許すと 2 つの run が同じ tag の asset を奪い合う。古い run を止めるのでは
# なく直列化する: cancel すると tarball の差し替えが中途半端な状態で終わりうるため。
concurrency:
group: release-binaries
cancel-in-progress: false

permissions:
contents: write # rolling release (tag `nightly`) の作成と asset 差し替えに必要

env:
RELEASE_TAG: nightly
TARGET_TRIPLE: x86_64-unknown-linux-gnu

jobs:
build:
# glibc は前方互換 (新しい glibc 上で動く) だが後方互換ではないため、
# ビルド側は実行環境より古い glibc に合わせる。22.04 = glibc 2.35。
runs-on: ubuntu-22.04
timeout-minutes: 30

steps:
- name: Check out the repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Cache cargo registry and build artifacts
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-release-${{ hashFiles('Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-release-

- name: Show toolchain versions
run: |
rustc --version
cargo --version
ldd --version | head -1

# 壊れたバイナリを rolling release に載せないためのゲート。
# Linux 側で cmd.exe 依存が復活していないかの検知も兼ねる (WP-15)。
- name: Run tests
run: cargo test --workspace

- name: Build all workspace binaries
run: cargo build --release --workspace --bins

# bin target 名を cargo metadata から取り出す (列挙のハードコードを避ける)。
# workspace_members に属する package だけを見て、依存 crate の bin を拾わない。
- name: Collect binaries into a single tarball
id: package
run: |
set -euo pipefail
mkdir -p dist/bin

cargo metadata --format-version 1 --no-deps \
| jq -r '.packages[].targets[] | select(.kind[] == "bin") | .name' \
| sort -u > dist/bin-names.txt

if [ ! -s dist/bin-names.txt ]; then
echo "error: no bin targets found via cargo metadata" >&2
exit 1
fi

echo "packaging $(wc -l < dist/bin-names.txt) binaries:"
while read -r name; do
src="target/release/${name}"
if [ ! -x "$src" ]; then
echo "error: expected binary not found: ${src}" >&2
exit 1
fi
cp "$src" "dist/bin/${name}"
echo " - ${name}"
done < dist/bin-names.txt

# 取得側が「いつ・どの commit のバイナリか」を確認できるようにする。
# rolling tag は履歴を持たないため、この情報が唯一の provenance になる。
cat > dist/bin/BUILD_INFO <<EOF
commit=${GITHUB_SHA}
ref=${GITHUB_REF}
run_id=${GITHUB_RUN_ID}
target=${TARGET_TRIPLE}
built_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)
EOF

archive="claude-code-hooks-${TARGET_TRIPLE}.tar.gz"
tar -czf "${archive}" -C dist/bin .
sha256sum "${archive}" > "${archive}.sha256"
echo "archive=${archive}" >> "$GITHUB_OUTPUT"

# tag が無ければ作り、あれば asset を --clobber で差し替える。
# notes に commit を書いておくと Release ページだけで世代が判る。
- name: Publish to the rolling prerelease
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ARCHIVE: ${{ steps.package.outputs.archive }}
run: |
set -euo pipefail
if ! gh release view "${RELEASE_TAG}" >/dev/null 2>&1; then
gh release create "${RELEASE_TAG}" \
--prerelease \
--title "Linux binaries (rolling)" \
--notes "master の最新ビルド。scripts/cloud-setup.sh が取得する。"
fi

gh release upload "${RELEASE_TAG}" "${ARCHIVE}" "${ARCHIVE}.sha256" --clobber

gh release edit "${RELEASE_TAG}" \
--notes "master の最新ビルド (commit ${GITHUB_SHA})。scripts/cloud-setup.sh が取得する。"
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@

# Built executables (can be rebuilt with pnpm build:all)
.claude/*.exe
# Linux / macOS のバイナリには拡張子が無く *.exe では捕まらないため、名前で無視する
# (WP-15: クラウドセッションで cloud-setup.sh が配置したバイナリを jj が snapshot して
# しまうのを防ぐ)。設定ファイルを巻き込むのは hooks-config.toml だけなので negate で戻す。
.claude/check-ci-coderabbit
.claude/cli-*
.claude/hooks-*
!.claude/hooks-config.toml
# cloud-setup.sh が展開する provenance ファイル
.claude/BUILD_INFO

# Deploy targets (contains local paths; create from deploy-targets.template.json)
scripts/deploy-targets.json
Expand Down
20 changes: 19 additions & 1 deletion docs/harness-improvement-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ Anthropic 公式のハーネスエンジニアリング指針(決定論的基
| WP-12 | 2 | 発火テレメトリ + ハーネス ROI 棚卸し | M | なし | 実装済(step1 収集層のみ: ADR-055 + lib-telemetry + 6 hook 計装。step2-3〔集計 pre-step / 卒業判定機械化〕は 28 日 warm-up 後着手のため todo 順位 307/308 へ移管) |
| WP-13 | 3 | EXE_SUFFIX 抽象化 | M | なし | 実装済(build/実行 scripts を deploy-artifacts.mjs / run-artifact.mjs 経由に、settings を `/` 区切り + `{{EXE_SUFFIX}}` 化、Rust の機能的 exe 解決を EXE_SUFFIX 化。ADR-005 amendment。cargo test 全 pass・build:all/deploy:hooks/lint:docs 退行なし実測。config TOML の cmd.exe 依存は WP-15 へ。`完了` は初回 push/PR で launcher 経路の実走確認後) |
| WP-14 | 3 | PowerShell 3 本の Rust 化 | S-M ×2 | なし | 実装済(3 本すべて Rust 化: fix-metrics-check→comment-lint `--fix-metrics-check` / prepare-pr-body→cli-pr-monitor サブコマンド / analyze-takt-timings→新規 cli-takt-timings crate。cargo test カバレッジ下・実データで旧 ps1 と出力一致確認。`完了` は初回 push/PR で fix step metrics-check と prepare-pr-body 経路の実走確認後) |
| WP-15 | 3 | Linux バイナリビルド + クラウド setup script | M | WP-13, 14 | 未着手 |
| WP-15 | 3 | Linux バイナリビルド + クラウド setup script | M | WP-13, 14 | 実装済(release-binaries.yml〔master push → rolling `nightly` prerelease に単一 tarball〕+ scripts/cloud-setup.sh 新設。前提として Linux 実行時に壊れる可搬性欠陥を修正: `cmd /c` 決め打ちの唯一の shell spawn 点を `shell_command`〔Windows=cmd /c / 他=sh -c〕へ集約、taskkill のみだった timeout kill に unix 分岐、cmd.exe 構文テストの OS 中立化、config の `.exe`/backslash 依存を `{{CLAUDE_DIR}}`/`{{EXE_SUFFIX}}` 展開へ。**WSL Ubuntu 24.04 で実測**: cargo test --workspace 全 pass・ignored 含め全 pass・clippy clean・hooks 実発火・push pipeline が sh -c 経路で完走。Linux 実測により lock の同時取得レース〔8 中 6 取得〕も発見・修正。`完了` は release 実生成 + 実クラウドセッションでの cloud-setup.sh 実走確認後) |
| WP-16 | 3 | CI matrix(移植退行防止) | S | WP-13, 14 | 未着手 |
| WP-17 | 4 | イベント駆動バックボーン完成(Phase B + routines 移行) | M | WP-09, 10, 11 | 未着手 |
| WP-18 | 4 | 夜間 todo 消化ループ | M-L | WP-15, 17 | 未着手 |
Expand Down Expand Up @@ -251,6 +251,24 @@ Anthropic 公式のハーネスエンジニアリング指針(決定論的基

### WP-15: Linux バイナリビルド + クラウド setup script

> **実装済 (2026-07-20)**: 8 コミットで実装。**スコープ補正**: 当初ステップ (release workflow + setup script) の手前に、**Linux では実行時に壊れる可搬性欠陥**が残っていることが着手時調査で判明したため、受け入れ基準「`cargo test` と push pipeline の dry-run が通る」を満たす前提としてこれを先に修正した。
>
> **① 可搬性修正 (Linux 実行を成立させる前提)**: (a) `lib-subprocess` の `run_cmd_shell_*` が `Command::new("cmd").args(["/c", …])` に固定されており、これが**リポジトリ唯一の shell spawn 点**だったため Linux では quality_gate / push / merge の全 step が spawn 失敗で無言に失敗扱いになる状態だった。OS 判定で `cmd /c` / `sh -c` を返す `shell_command` へ集約 (bash 固有構文を使わない前提で POSIX `sh` を選択 = bash 不在の最小コンテナでも通る)。`cli-push-runner` の `diff.rs` も同じ経路へ統合。(b) `check-ci-coderabbit` の timeout kill が `taskkill` のみで非 Windows 分岐が無く、Linux では `wait_with_output` が**無限ハング**していた (gh がハングすると CI 監視が永久停止) → `kill_process_by_id` で Windows=taskkill / Unix=kill -9 に分岐。(c) cmd.exe 構文 (`for /L` / `type nul` / `exit /b` / `A & B` / `ping -n`) を直書きしたテストが cfg 未ガードで残っており Linux で panic / assert 失敗 → OS 別 const 化 (行数・所要時間を両 OS で揃え、片側だけ主題を検証しない穴を防ぐ)。
>
> **② デプロイ時 config の cmd.exe 依存解消 (WP-13 からの引き継ぎ)**: file-length step の `.\.claude\….exe` は backslash + `.exe` 決め打ちで sh では解決不能、一方 cmd.exe は forward-slash **相対**パスを解決できない。**実測の結果、両シェルが共通で通るのは forward-slash の絶対パスだけ**だったため (ADR-005 が settings.local.json で確認済みの性質と同型)、`hooks-stop-quality` に `{{CLAUDE_DIR}}` / `{{EXE_SUFFIX}}` の展開を追加。`push-runner-config.toml` の `[lint_screen] exe_path` は code 側が既に OS 分岐 default を持つため明示指定をやめた。
>
> **③ ステップ 1 (release workflow)**: `.github/workflows/release-binaries.yml`。master push (paths フィルタで docs-only を除外) で `x86_64-unknown-linux-gnu` をビルドし、固定タグ `nightly` の prerelease へ**単一 tarball**で公開。単一 asset にしたのは run 失敗時に新旧混在の不整合セットが残らないようにするため。**バイナリ一覧は `cargo metadata` から導出**し、package.json / deploy-hooks.ts への列挙コピーによる drift を構造的に断つ (実際 deploy-hooks.ts の allowlist は 11 個で、settings template が参照する hook exe を 3 つ取りこぼしていた)。ubuntu-22.04 固定は glibc 後方互換が無いため。musl は tree-sitter の C コンパイルに musl-tools が必要でビルドが一段複雑になるのに対し、実行先が Ubuntu 系で glibc 2.35 なら十分と判断して不採用。
>
> **④ ステップ 2 (setup script)**: `scripts/cloud-setup.sh`。**要確認事項への回答**: public リポジトリの Release asset は素の HTTPS で取得できるため **gh CLI 認証は不要** (トークン受け渡し構成を持ち込まない = 失敗点を増やさない)。必須バイナリ一覧は `settings.local.json.template` から導出 (「どの exe が無いと hooks が発火しないか」の正解はテンプレート自身が持つ)。バイナリ欠落・settings 生成失敗は fail-closed (setup 成功と報告してハーネス無しで進む事故を防ぐ、ADR-005 の背景と同型)。jj は 0.42.0 固定 (ADR-011/015/045 が 0.42 系挙動に依存)、takt は `pnpm install --frozen-lockfile` で ADR-017 の固定を機械的に担保。あわせて `.gitignore` が `.claude/*.exe` のみで**拡張子なし Linux バイナリを無視しない**問題も修正 (クラウドで jj が成果物を snapshot してしまう)。
>
> **⑤ ステップ 3 (Ollama graceful skip)**: コード監査で**無条件に skip される**ことを確認。lint_screen は `enabled = false` かつ戻り値が `()` で構造的に block 不可能。classifier は exe 側が fallback JSON + exit 0 を返し、runner 側が全失敗経路を空 Vec に潰す二重の fail-open。実 Ollama を叩く eval は `#[ignore]` + env opt-in の二重ガードで `cargo test -- --ignored` でも skip される。fail-closed であるべきゲート (`[fix.gate]` / `docs_only_routing` / `post_takt_regate`) は Ollama 非依存で、ADR-043 の線引きは正しく引かれている。
>
> **受け入れ基準の実測 (WSL Ubuntu 24.04 = 実 Linux)**: `cargo test --workspace` 全 pass / `cargo test -- --ignored --test-threads=1` 全 pass (jj 導入後) / `clippy --workspace --all-targets --all-features -- -D warnings` clean / **hooks 実発火**(SessionStart が additionalContext JSON を出力、PreToolUse が `rm -rf /` を exit 2 でブロックし `echo hello` を通す、tree-sitter の comment-lint が違反検出) / **push pipeline が `sh -c` 経路で完走**(quality_gate の rust-lint-test が clippy・cargo test とも PASS)。cloud-setup.sh の jj 取得は実 URL・実展開ロジックで実走確認。
>
> **Linux 実測で発見した副次不具合**: `cli-pr-monitor` の lock が**同時取得**を許していた (8 スレッド中 6 つが取得)。`create_new` は atomic だが直後のファイルは空で、その窓を読んだ側が TOML parse 失敗を一律「stale」と扱って全員 takeover していた。Windows ではスケジューリング差で顕在化していなかっただけで欠陥は同じ。parse 失敗を内容で 2 分 (空 = 書き込み中 → busy / 非空の不正 = 破損 → takeover) して修正。**「Windows だけで回していると気付けない設計欠陥が実在した」= Linux 実測と WP-16 (CI matrix) の価値を裏づける実例。**
>
> **`完了` 条件**: (1) 本変更が master に入り release-binaries.yml が実走して `nightly` release が生成されること、(2) 実際の claude.ai/code セッションで `cloud-setup.sh` を走らせ hooks 発火と `cargo test` 通過を確認すること。いずれも本セッションでは実施不能 (release 未生成 / クラウド環境未使用) のため `実装済` に留める。**Linux 側の未検証領域**: `#[cfg(windows)]` ガードのテスト (pump_child_io の deadlock 保護、run_cmd_capture の stdout/stderr 分離) は Linux で skip されるため、WP-16 の CI matrix で扱う。以下は当初ステップ (記録用)。

- **目的**: 使い捨てのクラウドセッションで 19 crate をビルドせずにハーネスを即時有効化する。
- **ステップ**:
1. `.github/workflows/release-binaries.yml`: master push で `x86_64-unknown-linux-gnu` をビルドし artifact/Release へ。`lib-ollama-client` が ureq + rustls 構成なら musl 静的リンクも検討(openssl 依存を避ける)。public リポジトリのためビルド時間は無料。
Expand Down
7 changes: 5 additions & 2 deletions push-runner-config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,11 @@ output_path = ".takt/review-diff.txt"
# ---------------------------------------------------------------------------
[lint_screen]
enabled = false
# 以下は default 値、明示しなくてもよいが意図を残すなら明示推奨
exe_path = ".claude/cli-finding-classifier.exe"
# 以下は default 値、明示しなくてもよいが意図を残すなら明示推奨。
# exe_path は **あえて明示しない** (WP-15): code 側の default が
# `.claude/cli-finding-classifier` + OS の実行ファイル拡張子で cfg 分岐しており、
# ここに `.exe` 付きの値を書くと Linux で解決に失敗する。OS ごとに値を変えたい
# 場合のみ明示すること。
model = "mistral:7b"
endpoint = "http://localhost:11434"
timeout_secs = 60
Expand Down
Loading