Skip to content

feat(hooks): カスタムリンターエンジンの導入 - #6

Merged
aloekun merged 1 commit into
masterfrom
feat/custom-linter-engine
Mar 30, 2026
Merged

feat(hooks): カスタムリンターエンジンの導入#6
aloekun merged 1 commit into
masterfrom
feat/custom-linter-engine

Conversation

@aloekun

@aloekun aloekun commented Mar 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • hooks-post-tool-linter に設定駆動型カスタムリントルールエンジンを追加
  • custom-lint-rules.toml でプロジェクト固有ルールを定義可能に(Rustリビルド不要)
  • 初期ルールとして no-console-log を追加(severity: error)
  • 最小限の Logger クラスを実装(console.log の置換先、内部は console.info 等を使用)
  • ADR-007: カスタムリンターの正規表現層/AST層の線引き基準を文書化

Architecture

PostToolUse hook 実行フロー:
  第1層: カスタムルール (正規表現, ~1ms) ← 今回追加
    └─ custom-lint-rules.toml のルールでリテラルマッチ
    └─ 構造化 JSON で additionalContext にフィードバック
  第2層: 外部ツール (biome/oxlint, ~10ms) ← 既存

Files changed

ファイル 変更内容
.claude/custom-lint-rules.toml カスタムルール定義(no-console-log)
.claude/hooks-config.toml カスタムリンターセクションのコメント追加
.claude/hooks-post-tool-linter/Cargo.toml regex クレート追加
.claude/hooks-post-tool-linter/src/main.rs カスタムルールエンジン + ユニットテスト
CLAUDE.md ADR-007 へのリンク追加
docs/adr/adr-007-custom-linter-layer-boundary.md 正規表現/AST層の判断基準
src/logger.ts 最小限の Logger クラス

Test plan

  • cargo test — カスタムルールエンジンのユニットテスト全通過
  • pnpm build:hooks — exe ビルド成功
  • 手動テスト: console.log を含む .ts ファイルで構造化 JSON フィードバックを確認
  • logger.ts がカスタムリンターに引っかからないことを確認(console.info 使用)
  • レビュー: custom-lint-rules.toml のルール定義が適切か

🤖 Generated with Claude Code

Summary by CodeRabbit

リリースノート

  • New Features

    • レベル別ロギング(debug/info/warn/error)を追加しました。
    • プロジェクト固有のカスタムリンターを導入し、console.log 等の検出と削除/logger への置換案を表示します。
  • Documentation

    • カスタムリンターの設計判断をまとめた ADR(ADR-007)を追加しました。
  • Tests

    • カスタムリンターの包括的なテストを追加しました。

@coderabbitai

coderabbitai Bot commented Mar 29, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

TOMLで定義した正規表現ベースのカスタムリンタールールを読み込み、事前リンターステージ(Rust実行バイナリ)でファイルを走査してJSON形式の違反を出力する機能を追加。併せてTypeScriptの簡易Loggerモジュールを追加。

Changes

Cohort / File(s) Summary
Custom lint config & hooks
/.claude/custom-lint-rules.toml, /.claude/hooks-config.toml
プロジェクト固有の正規表現ルール定義ファイルを追加。hooks設定に注釈を挿入してrulesファイルの位置と制約を明記。
Lint runner deps
/.claude/hooks-post-tool-linter/Cargo.toml
regex = "1.10" を依存に追加、tempfile = "3" をdev-dependenciesに追加。
Custom lint implementation
/.claude/hooks-post-tool-linter/src/main.rs
事前カスタムリンターステージを導入:実行ファイル位置からTOMLを読み込み、拡張子フィルタ(大文字小文字を無視)、正規表現マッチ、構造化JSON違反生成、フィードバック送信を実装。多数のユニットテストを追加。
Logger module
src/logger.ts
LogLevel型、Loggerクラス(閾値判定)およびデフォルトsingleton logger を追加(debug/info/warn/error)。
Docs & ADR
CLAUDE.md, docs/adr/adr-007-custom-linter-layer-boundary.md
ADR-007を追加し、正規表現層とAST層の切り分け基準と運用指針を記載。

Sequence Diagram

sequenceDiagram
    autonumber
    actor User
    participant Linter as "hooks-post-tool-linter\n(Rust exe)"
    participant Config as "custom-lint-rules.toml"
    participant File as "Target File"
    participant Feedback as "Feedback Output"

    User->>Linter: スキャン起動
    Linter->>Config: TOML読み込み/パース
    Config-->>Linter: ルール集合(regex/拡張子/メッセージ/修正)
    loop 各対象ファイル
        Linter->>File: 行ごと読み取り
        File-->>Linter: 行内容
        Linter->>Linter: 正規表現マッチ判定(拡張子チェック)
        alt マッチ
            Linter->>Linter: 違反オブジェクト生成(JSON)
        end
    end
    Linter->>Feedback: `[custom-lint]` プレフィックス付きフィードバック送信
    Feedback-->>User: 構造化違反情報
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR のタイトルは「feat(hooks): カスタムリンターエンジンの導入」で、実装の中核である hooks-post-tool-linter へのカスタムリントルールエンジンの追加という主な変更を正確に反映しており、簡潔で具体的です。
Docstring Coverage ✅ Passed Docstring coverage is 92.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
docs/adr/adr-007-custom-linter-layer-boundary.md (1)

24-35: フェンスドコードブロックに言語指定を追加することを推奨

静的解析ツールが指摘しているように、フェンスドコードブロックには言語を指定することがベストプラクティスです。フローチャートなので text または plaintext を指定できます。

📝 修正案
-```
+```text
 Q1. 違反は 1 行だけ見て判定できるか?
     └─ No → AST 層
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/adr/adr-007-custom-linter-layer-boundary.md` around lines 24 - 35, Add a
language identifier to the fenced code block that contains the flowchart (the
block starting with ``` and lines like "Q1. 違反は 1 行だけ見て判定できるか?" / "Q2.
コメント・文字列リテラル内の誤検出が問題になるか?" / "Q3. パターンはリテラル文字列のマッチのみで表現できるか?") — change the
opening fence from ``` to ```text (or ```plaintext) so the code block is
explicitly annotated for the static analysis tool and rendering.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@docs/adr/adr-007-custom-linter-layer-boundary.md`:
- Around line 24-35: Add a language identifier to the fenced code block that
contains the flowchart (the block starting with ``` and lines like "Q1. 違反は 1
行だけ見て判定できるか?" / "Q2. コメント・文字列リテラル内の誤検出が問題になるか?" / "Q3.
パターンはリテラル文字列のマッチのみで表現できるか?") — change the opening fence from ``` to ```text (or
```plaintext) so the code block is explicitly annotated for the static analysis
tool and rendering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ae8c74fb-6a89-4909-bd5e-4db83c7cc76f

📥 Commits

Reviewing files that changed from the base of the PR and between 1051b7b and f3b9f29.

⛔ Files ignored due to path filters (1)
  • .claude/hooks-post-tool-linter/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • .claude/custom-lint-rules.toml
  • .claude/hooks-config.toml
  • .claude/hooks-post-tool-linter/Cargo.toml
  • .claude/hooks-post-tool-linter/src/main.rs
  • CLAUDE.md
  • docs/adr/adr-007-custom-linter-layer-boundary.md
  • src/logger.ts

@aloekun
aloekun force-pushed the feat/custom-linter-engine branch from f3b9f29 to b5b785d Compare March 29, 2026 15:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
.claude/hooks-post-tool-linter/src/main.rs (3)

308-314: パフォーマンス: 正規表現の毎回コンパイル

run_custom_rulesが呼ばれるたびに正規表現をコンパイルしています。ADR-007で目標とされている ~1ms の処理時間を維持するため、ルール数が増えた場合はonce_celllazy_staticを使用してコンパイル済み正規表現をキャッシュすることを検討してください。

現時点ではルール数が少ないため即座の対応は不要ですが、将来の拡張時に考慮すべき点です。

♻️ キャッシュを使用した最適化案(参考)
use once_cell::sync::Lazy;
use std::collections::HashMap;
use std::sync::RwLock;

static COMPILED_REGEXES: Lazy<RwLock<HashMap<String, Regex>>> = 
    Lazy::new(|| RwLock::new(HashMap::new()));

fn get_or_compile_regex(pattern: &str) -> Option<Regex> {
    // Check cache first
    if let Ok(cache) = COMPILED_REGEXES.read() {
        if let Some(re) = cache.get(pattern) {
            return Some(re.clone());
        }
    }
    // Compile and cache
    match Regex::new(pattern) {
        Ok(re) => {
            if let Ok(mut cache) = COMPILED_REGEXES.write() {
                cache.insert(pattern.to_string(), re.clone());
            }
            Some(re)
        }
        Err(_) => None,
    }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/hooks-post-tool-linter/src/main.rs around lines 308 - 314,
run_custom_rules currently calls Regex::new(&rule.pattern) for every rule
invocation which recompiles regexes on each run; replace that with a cached
lookup so compiled Regex instances are reused (e.g. add a static cache using
once_cell::sync::Lazy or lazy_static and a RwLock/Mutex-protected HashMap keyed
by rule.pattern). Implement a helper like get_or_compile_regex(pattern: &str)
that returns a cloned compiled Regex from the cache or compiles and inserts it
on miss, and use that helper in place of direct Regex::new(&rule.pattern) inside
run_custom_rules to avoid repeated compilation and improve performance.

639-663: テストの堅牢性: 一時ディレクトリ名の衝突可能性

テストで固定名の一時ディレクトリ(例: custom_lint_test_console_log)を使用しています。cargo testはデフォルトでスレッドプールを使用するため、並列実行時に競合する可能性があります。

tempfileクレートを使用するか、ユニークなサフィックスを追加することで、より堅牢になります。

♻️ tempfileクレートを使用した改善案

Cargo.toml[dev-dependencies]に追加:

tempfile = "3"

テストコードの例:

 #[test]
 fn run_custom_rules_detects_console_log() {
     use std::io::Write;
-    let dir = std::env::temp_dir().join("custom_lint_test_console_log");
-    let _ = std::fs::create_dir_all(&dir);
+    let dir = tempfile::tempdir().unwrap();
-    let file = dir.join("test.ts");
+    let file = dir.path().join("test.ts");
     {
         let mut f = std::fs::File::create(&file).unwrap();
         writeln!(f, "const x = 1;").unwrap();
         writeln!(f, "console.log('debug');").unwrap();
         writeln!(f, "const y = 2;").unwrap();
     }
     // ... test logic ...
-    let _ = std::fs::remove_dir_all(&dir);
+    // tempdir automatically cleaned up on drop
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/hooks-post-tool-linter/src/main.rs around lines 639 - 663, Replace
the fixed temp directory used in the test run_custom_rules_detects_console_log
with a unique temporary directory from the tempfile crate: add tempfile = "3"
under [dev-dependencies] in Cargo.toml, create a TempDir via tempfile::tempdir()
inside the test and write the test.ts file into tempdir.path(), and pass that
file path to run_custom_rules; remove manual cleanup (remove_dir_all) since
TempDir will auto-delete. Ensure you update references to dir, file, and any
uses of std::env::temp_dir() so the test uses the TempDir API.

294-346: 違反数の上限がない点について

外部ツールの診断出力は20行に制限されていますが(251行目)、カスタムルールの違反には上限がありません。大量の違反がある場合、フィードバックが非常に大きくなる可能性があります。

現段階では問題にならないと思いますが、将来的に違反数の上限を設けることを検討してもよいかもしれません。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/hooks-post-tool-linter/src/main.rs around lines 294 - 346,
run_custom_rules currently returns an unbounded Vec<String> of violations; limit
its output to the same cap used for external diagnostics (use the existing
MAX_DIAGNOSTIC_LINES constant if present, or introduce a constant like
MAX_CUSTOM_VIOLATIONS) by truncating the violations before returning. Locate the
function run_custom_rules and after collecting violations (or during push)
ensure you only keep up to the max (e.g., violations.truncate(MAX); or return
violations.into_iter().take(MAX).collect()), and prefer reusing the existing
MAX_DIAGNOSTIC_LINES symbol to keep behavior consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.claude/hooks-post-tool-linter/src/main.rs:
- Around line 308-314: run_custom_rules currently calls
Regex::new(&rule.pattern) for every rule invocation which recompiles regexes on
each run; replace that with a cached lookup so compiled Regex instances are
reused (e.g. add a static cache using once_cell::sync::Lazy or lazy_static and a
RwLock/Mutex-protected HashMap keyed by rule.pattern). Implement a helper like
get_or_compile_regex(pattern: &str) that returns a cloned compiled Regex from
the cache or compiles and inserts it on miss, and use that helper in place of
direct Regex::new(&rule.pattern) inside run_custom_rules to avoid repeated
compilation and improve performance.
- Around line 639-663: Replace the fixed temp directory used in the test
run_custom_rules_detects_console_log with a unique temporary directory from the
tempfile crate: add tempfile = "3" under [dev-dependencies] in Cargo.toml,
create a TempDir via tempfile::tempdir() inside the test and write the test.ts
file into tempdir.path(), and pass that file path to run_custom_rules; remove
manual cleanup (remove_dir_all) since TempDir will auto-delete. Ensure you
update references to dir, file, and any uses of std::env::temp_dir() so the test
uses the TempDir API.
- Around line 294-346: run_custom_rules currently returns an unbounded
Vec<String> of violations; limit its output to the same cap used for external
diagnostics (use the existing MAX_DIAGNOSTIC_LINES constant if present, or
introduce a constant like MAX_CUSTOM_VIOLATIONS) by truncating the violations
before returning. Locate the function run_custom_rules and after collecting
violations (or during push) ensure you only keep up to the max (e.g.,
violations.truncate(MAX); or return violations.into_iter().take(MAX).collect()),
and prefer reusing the existing MAX_DIAGNOSTIC_LINES symbol to keep behavior
consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4858db1e-f028-4b49-8237-7a813a49570a

📥 Commits

Reviewing files that changed from the base of the PR and between f3b9f29 and b5b785d.

⛔ Files ignored due to path filters (1)
  • .claude/hooks-post-tool-linter/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • .claude/custom-lint-rules.toml
  • .claude/hooks-config.toml
  • .claude/hooks-post-tool-linter/Cargo.toml
  • .claude/hooks-post-tool-linter/src/main.rs
  • CLAUDE.md
  • docs/adr/adr-007-custom-linter-layer-boundary.md
  • src/logger.ts
✅ Files skipped from review due to trivial changes (5)
  • CLAUDE.md
  • .claude/hooks-config.toml
  • .claude/custom-lint-rules.toml
  • .claude/hooks-post-tool-linter/Cargo.toml
  • docs/adr/adr-007-custom-linter-layer-boundary.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/logger.ts

設定駆動型のカスタムリントルールエンジンを hooks-post-tool-linter に追加。
正規表現ベースのリテラルマッチで違反を検出し、構造化JSONでClaudeに
フィードバックする仕組みを実現。

- custom-lint-rules.toml でプロジェクト固有ルールを定義可能に
- 初期ルールとして no-console-log を追加
- 最小限の Logger クラスを実装(console.log 置換先)
- ADR-007: 正規表現層/AST層の線引き基準を文書化
- CodeRabbitレビュー反映:
  - 正規表現をルール読込時にプリコンパイル (CompiledRule)
  - テスト一時ディレクトリを tempfile クレートに移行
  - カスタムルール違反数の上限 (MAX_CUSTOM_VIOLATIONS=20) を追加

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@aloekun
aloekun force-pushed the feat/custom-linter-engine branch from b5b785d to ef7bda8 Compare March 30, 2026 05:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/logger.ts (1)

57-57: 型専用エクスポートは export type に分離してください。

LogLevel は型専用(行10で定義)のため、export { Logger, LogLevel } の形式は TypeScript の isolatedModulesverbatimModuleSyntax 設定を有効化した場合に問題となる可能性があります。値と型を分離することは TypeScript の推奨ベストプラクティスです。

🔧 提案差分
 export const logger = new Logger();
-export { Logger, LogLevel };
+export { Logger };
+export type { LogLevel };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/logger.ts` at line 57, 現在の export { Logger, LogLevel } は LogLevel
が型専用のため問題になる可能性があるので、値エクスポートと型エクスポートを分離してください:残すべき値のシンボル(Logger)を通常の export
として、型専用のシンボル(LogLevel)を export type を使ってエクスポートするように修正し、該当するエクスポート文(Logger と
LogLevel を扱っている箇所)を置き換えてください。
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/logger.ts`:
- Line 57: 現在の export { Logger, LogLevel } は LogLevel
が型専用のため問題になる可能性があるので、値エクスポートと型エクスポートを分離してください:残すべき値のシンボル(Logger)を通常の export
として、型専用のシンボル(LogLevel)を export type を使ってエクスポートするように修正し、該当するエクスポート文(Logger と
LogLevel を扱っている箇所)を置き換えてください。

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb7a6945-eeda-4ded-b9fa-3537cf6972d6

📥 Commits

Reviewing files that changed from the base of the PR and between b5b785d and ef7bda8.

⛔ Files ignored due to path filters (1)
  • .claude/hooks-post-tool-linter/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • .claude/custom-lint-rules.toml
  • .claude/hooks-config.toml
  • .claude/hooks-post-tool-linter/Cargo.toml
  • .claude/hooks-post-tool-linter/src/main.rs
  • CLAUDE.md
  • docs/adr/adr-007-custom-linter-layer-boundary.md
  • src/logger.ts
✅ Files skipped from review due to trivial changes (5)
  • CLAUDE.md
  • .claude/hooks-post-tool-linter/Cargo.toml
  • .claude/hooks-config.toml
  • .claude/custom-lint-rules.toml
  • docs/adr/adr-007-custom-linter-layer-boundary.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • .claude/hooks-post-tool-linter/src/main.rs

@aloekun
aloekun merged commit fa2f186 into master Mar 30, 2026
1 check passed
@aloekun
aloekun deleted the feat/custom-linter-engine branch March 30, 2026 05:10
aloekun added a commit that referenced this pull request Apr 28, 2026
* docs(todo): PR #88 post-merge-feedback の Tier 1/2 finding を採用

PR #88 post-merge-feedback (.claude/feedback-reports/88.md) で生成された 7 件の
finding のうち、ユーザー判断により #1-5 を採用、#6-7 を見送り。

採用 finding (5 件):
- T1 #1 (順位 5): Stop hook の `pnpm lint:md` 統合 — XS、順位 1 完了済の gap closure
- T1 #2 (順位 6): AI 生成一時スクリプト pattern の pre-push 検出 — Small、順位 1 と関連
- T2 #3 (順位 13): `vitest` を devDependencies に固定 — Small
- T2 #4 (順位 12): `cli-pr-monitor` ポーリング延長 + 重複起動ロック — Medium、★ rate-limit critical
- T2 #5 (順位 14): `pnpm create-pr` 必須引数ヘルプ改善 — Small

見送り finding:
- T3 #6: hook 統合時の commit 分割基準 → グローバルルール (~/.claude/) 編集は permission denied、要別経路
- T3 #7: jj rebase conflict 解消手順 → 同上

変更:
- docs/todo3.md 新設 (todo2.md が 50KB に到達したため、PR #88 以降の新規エントリは todo3.md へ)
- docs/todo.md 推奨実行順序サマリーに 5 件を Tier 別に挿入し、20 → 24 タスクへ全 renumber
- 戦略テキストと cross-reference を全面更新
- todo2.md / todo3.md 内の 順位 N 参照を新採番へ追従
- 順位 1 (markdownlint hook 統合) は merged 済として削除参照を merged context に書き換え

* fix(review): apply CodeRabbit fixes for #89

Resolved findings:
- [Minor] docs/todo.md:49 順位参照の文言が現行テーブルと不整合です
- [Major] docs/todo3.md:7 見出しリンクのアンカーが壊れる可能性があります
aloekun added a commit that referenced this pull request May 13, 2026
…an reaper + ADR-030 spec (#154)

PR #109 で実証された ADR-030 仕様違反 (SIGPIPE で feedback workflow が silent 中断 +
.failed marker 未生成) への構造的解消。3 層 (pre-emptive marker / RAII Drop guard /
out-of-process reaper) で abrupt termination 経路を多層防御する。

## 順位 63 — cli-merge-pipeline pre-emptive marker + Drop guard

- write_pending_marker を feedback::run の concurrent_run_guard 直後に追加
- RAII FailedMarkerGuard を armed 状態で生成、Ok path で disarm
- Drop guard は idempotent (marker.exists() check で caller の detailed marker を保護)
- Rust default SIGPIPE は SIG_DFL = unwind せず即時終了するため Drop は呼ばれない。
  pre-emptive marker のディスク先置きで救済する設計 (signal trap A は skip)
- run() を 50 行ガイドライン遵守のため prepare_transcript / reconcile_takt_output に分割

## 順位 64 — hooks-session-start orphan run reaper

- .takt/runs/*/meta.json scan: status=running + post-merge-feedback task prefix +
  ORPHAN_THRESHOLD_SECS (1500s = TAKT_TIMEOUT_SECS + 余裕 5 分) 経過の run を検出
- reap: .failed marker 生成 + meta.json status=failed 更新 (reaped_by field 追加)
- 冪等性: 既存 .failed marker または .claude/feedback-reports/<pr>.md 成功レポート
  存在時は skip (advisor 指摘の ADR-030 §Reconciliation false-positive 対策)
- ISO 8601 parser は check-ci-coderabbit::parse_iso8601_to_unix と同型 (no chrono dep)
- TAKT_TASK_PREFIX_PMF / ORPHAN_THRESHOLD_SECS は cli-merge-pipeline 側と inline duplicate +
  両 crate の test で literal pin (drift 検出)

## 順位 67 — ADR-030 spec amendment

- "Abrupt 終了の多層 recovery (Bundle c-1 で追加)" subsection を追加
- L1 in-process / L2 out-of-process の責務分離マトリクス
- Reconciliation-aware reaper skip ルールを spec として明記
- SLA は ORPHAN_THRESHOLD_SECS / TAKT_TIMEOUT_SECS を name で参照 (数値 drift 防止)

## 検証

- workspace tests: 901+ pass / 0 failures (新規 test: feedback.rs 7 件 + session-start 16 件)
- clippy clean (Error::other 移行 + dead_code on canonical const)
- release exe ビルド + .claude/ にコピー済

## Bundle c 残り (本 PR scope 外)

- 順位 65 (exe + --help PreToolUse block) / 順位 66 (pipe truncate global rule) は
  Bundle c-2 として todo7.md に残置

Refs: ADR-030, PR #109 (root cause), feedback-reports/109.md (Tier 1 #1/#2 + Tier 3 #6)
aloekun added a commit that referenced this pull request Jun 3, 2026
…nup (#193)

* docs(adr-031): § Adoption Criteria threshold 追加 + ADR-039 cross-ref (PR #192 T3-#5)

PR #192 post-merge-feedback Tier 3 #5 採用。Phase E land 時 § 採用判定の根拠 は
観測値の記録のみで「閾値」が暗黙だった。5 閾値 (採用率 ≥ 40% / wall-clock ≤ 10 分 /
FP ≤ 5% / context 圧迫なし / systemic 検出力) を ADR-031 inline で永続記録、
将来 trial ADR の採用判定で参照可能化。

ADR-039 § 関連 にも back-link を追加し双方向 link 形成、§ Bounded lifetime の
3 値判定 (採用 / 却下 / 継続) の具体化例として参照可能。Tier 3 #6 (ADR-039 audit)
の価値も部分吸収。

* docs(todo): Bundle CR-RL stale entry cleanup (順位 167/168/169 — PR #185 で land 済)

PR #185 (commit 7f8b613) で Bundle CR-RL の実装 3 件は全て land 済:
- 順位 167: RATE_LIMIT_MARKERS multi-variant 配列化 (main.rs:261)
- 順位 168: 新 format fixture 3 variant (full / minutes-only / mixed)
- 順位 169: ADR-018 lines 185-186 multi-variant 表記 + ADR-034 § 既知 format 一覧 + § 検出 logic 更新手順

todo9.md / todo-summary.md の stale entry を削除して in-progress を反映。
memory feedback_verify_task_not_already_done の本来用途 (= 既 land 済タスクを
stale entry 削除に再目的化) を実適用。
aloekun added a commit that referenced this pull request Jun 24, 2026
…順位 222 採用 (#219)

* docs(todo): 順位 222 採用 (PR #218 post-merge-feedback #5)

PR #218 (docs PR、ファイルサイズチェックフロー改善計画 + 順位 220/221 採用)
の post-merge-feedback で承認された #5 を採用:

順位 222 (💎 Tier 3、Effort XS):
`~/.claude/CLAUDE.md` に「複数セッション跨ぎの計画文書作成時は AI が
先走らずユーザー確認後に方針報告し GO/NO-GO を得る」ルール追加

由来: PR #218 session 内で Plan file 作成完了報告後、AI がユーザー承認
なしに PR-W0 着手しようとして `[Request interrupted by user]` で停止
された実観測 (Severity Medium、Frequency Low 初観測、Effort XS、
Adoption Risk None)。memory `feedback_no_unauthorized_reorder` の補強
として「planning doc 作成のような大きな task 完了時は GO/NO-GO 確認待ち」
を明文化、派生プロジェクトへ `~/.claude/CLAUDE.md` 経由で自動波及。

採用しなかった項目:
- #1 (weekly audit を feedback entry にも明示): 計画書 PR-W0 で既に管理
- #3 (lib-subprocess stress test): 順位 220 と完全重複
- #4 (Agent template PMF entry): 計画書 Appendix A で既に capture、却下
- #2/#6/#7: 様子見継続

* feat(weekly-review): file_length scan を pre-LLM step として追加 (PR-W0)

ADR-031 weekly-review pipeline に deterministic Rust pre-step として
800 行超 file の scan を追加。LLM facet 不要、純機械測定。

順位 147 (file_length lint) は touch-trigger ratchet で「触られた file の
編集時のみ警告」設計のため、未触り state の violation を可視化できない。
本 step は毎週 1 回 master HEAD に対して 800 行超 file を全件列挙し、
aggregate-weekly facet の input に注入して watchlist として report 化する。

PR-3a (PR #217) で 7 件の 800 行超 file が判明した経緯から、Phase 1
(file split work、PR-W1 〜 W4) の進捗 dashboard としても機能する。
全 file ≤ 800 行に到達後も恒久的に監視継続。

由来: docs/file-length-enforcement-plan.md PR-W0 (PR #218 で land)、
severity = warning (block しない、健康診断目的)。
aloekun added a commit that referenced this pull request Aug 6, 2026
PR #361 への CodeRabbit レビュー (Minor 2 件) の反映。どちらも「書いた内容が実態より
広い/古い」型の指摘で、妥当。

## 1. 「draft-pr は deny のまま」の適用範囲を限定 (2 箇所)

指摘は「**両文書**が PR 3 まで draft-pr に許可経路が無いように読める」というもの。実際には
ADR-071 § 検証記録の drill #6 が示すとおり、--open-draft-prs と有効な config を渡せば exe は
allow になる。

正確には**リポジトリ内の自動化経路に --open-draft-prs を渡す呼び手が 1 つも無いため** deny に
なる、が正しい。「運用挙動は変わらない」の根拠を exe の挙動ではなく呼び手の不在へ置き直した。
根拠を取り違えたまま PR 3 で呼び手が入ると、主張だけが stale に残る。

修正箇所は 2 つ:

- docs/adr/adr-071-*.md § 残課題
- docs/harness-improvement-plan.md § WP-18 の PR chain 宣言

計画書側から ADR-071 § 残課題への参照も付け、同じ限定が 1 箇所に集約されるようにした。

## 2. 計画書 § 2 preamble: 移管済みの事実と矛盾する旧方針を更新

旧文は「現時点では ADR 化せず本ファイルに保持する」と書いていたが、同じ blockquote の
次の行で「ADR-071 へ移管済み」と述べており矛盾していた。

方針文を「担当 WP の ADR 起票時に移管し本節から削除する」という恒常ルールへ書き換え、
移管済み / 未移管を別項目に分けた。未移管の内訳 (cloud routines の daily cap / webhook
上限 / GitHub App 必須 / 緑ステータスの意味) も具体名で列挙し、次の担当 WP が何を移管
すべきか読み取れるようにした。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 6, 2026
…DR-071) (#361)

* feat(autonomy-policy): 未マージ draft 数の背圧を判定コアの入力にする

WP-18 PR 1 (1/5)。ADR-052 原則 5 が自動実行可クラスの前提条件とする背圧を、
判定コア `lib-autonomy-policy` が入力として受け取れるようにする。

## 変更の要点

`Operation::backpressure_connected()` の `DraftPr => false` 固定を廃し、背圧の
**指標の要求**と**指標の実測値**を別の層へ分けた:

- `Operation::requires_draft_backpressure()` — 操作クラスが「未マージ draft 数」を
  要求するかだけを持つ静的分類。状態は持たない
- `GateInputs::open_draft_prs` / `max_open_draft_prs` — 実測値と閾値。どちらか一方でも
  `None` なら `backpressure-unavailable` で deny (fail-closed)

背圧の状態を enum 側にも持たせると `GateInputs` と二重管理になり判定経路が分岐するため、
状態の保持先は `GateInputs` 1 箇所に限定した (計画書 WP-18 PR 1 の明示要件)。

`FixPush` が draft 数を要求しないのは背圧が無いからではなく、その背圧が cli-pr-monitor の
有界 retry (`max_retries`) で、呼び出しごとに gate へ渡す状態を持たないため。この非対称を
doc コメントとテスト (`fix_push_is_unaffected_by_draft_backpressure_inputs`) の両方で固定した。

## 追加した deny 理由

`DenyReason::BackpressureSaturated { open, limit }` (code = `backpressure-saturated`)。
飽和判定は `>=`。閾値は「これ以上は積まない」上限であり、`limit = 0` は「draft を 1 件も
作らない」= 実質停止を意味する。

`describe_sources` の背圧表記は `structural` / `ok(N/M)` / `saturated(N/M)` / `unavailable`
の 4 状態で、実数を必ず併記する。run log 1 行で「数え損ねて止まった」と「積み過ぎて
止まった」を切り分けられるようにするため。

## テスト

判定コアのテストを 16 → 22 件へ。網羅走査テストは背圧 4 パターンを軸に加えて 54 → 216
組合せになり、許可される組合せ数も式で固定した (truthy 表記数 × (fix-push 4 + draft-pr 1))。
境界 (`open == limit` で止まる)、`limit = 0`、kill-switch が背圧より先に効くこと、
背圧入力の欠落 3 パターンをそれぞれ独立に pin している。

## 本コミットの範囲

呼び手 2 件 (`cli-autonomy-gate` / `cli-fix-push-gate`) は本コミットでは `None` を渡す。
`draft-pr` は従来どおり deny のままで、運用挙動は変わらない。config からの閾値読み取りは
2/5、実測 draft 数の受け口は 3/5 で接続する。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(autonomy-policy): 背圧閾値 max_open_draft_prs を config から 1 回の read で取り込む

WP-18 PR 1 (2/5)。背圧の閾値を `autonomy-config.toml` の `[autonomy]
max_open_draft_prs` に置き、kill-switch フラグと同じ信頼境界 (master ref の写し) と
同じ「欠損 → 停止」極性に乗せる。

## read を 1 回に集約した理由

`read_repo_config_enabled(path) -> Option<bool>` を `read_repo_config(path) -> RepoConfig`
へ置き換えた。キーごとに読み取り関数を生やすとファイルを 2 回読むことになり、2 回の read の
間に config が差し替わると「kill-switch は旧世代・閾値は新世代」という混成状態で判定しうる。
1 回の read から派生した値だけを使う。

## 半壊 config は全フィールドが停止側へ倒れる

toml の parse はファイル単位なので、`max_open_draft_prs` が型違い (文字列 / 負値 / 小数) だと
`enabled` も含めて全フィールドが `None` になる。これは意図した挙動で、config の一部が壊れた
状態を「kill-switch だけ有効」で運転させない (ADR-043 fail-closed)。テストで明示的に固定した。

閾値キー欠落時に既定値 (3 など) へ倒さないのも同じ理由。書き忘れが「勝手に 3 件まで作る」
という fail-open にならないよう、欠落は背圧未接続 = draft PR deny とする。

一方で未知キーは無視する (`unknown_keys_are_ignored`)。config に新しい設定を足したときに、
未更新のバイナリが parse 失敗で全停止するのは安全側に振りすぎるため。

## テスト

sources のテストを 6 → 11 件へ。閾値の正常読み取り (0 を含む)、キー欠落が既定値でないこと、
型違い 3 種が kill-switch フラグごと停止側へ倒すこと、未知キー無視を追加した。

## 本コミットの範囲

`cli-autonomy-gate` は閾値を `GateInputs` へ渡すようになったが、実測 draft 数の受け口
(`--open-draft-prs`) はまだ無いため `draft-pr` は `backpressure-unavailable` で deny のまま。
接続は 3/5 で行う。`cli-fix-push-gate` は `enabled` しか使わないため呼び出し形の追従のみ。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(autonomy-gate): --open-draft-prs で実測 draft 数を受け取り背圧を接続する

WP-18 PR 1 (3/5)。判定コア (1/5) と閾値 (2/5) に続き、背圧の残り 1 入力である
**実測件数**の受け口を CLI に開ける。これで `draft-pr` が構造的 deny を脱し、
ADR-052 原則 5 の契約を満たした状態でのみ許可されるようになる。

## 引数の扱い

`--open-draft-prs <count>` は省略可能。`fix-push` では判定に使わないためで、`draft-pr` で
省略した場合は `None` = 背圧未接続として deny に倒れる。**省略が許可へ倒れる経路は無い**。

値のパースは `u32` で、空文字 / 負値 / 小数 / 非数値 / 末尾空白は引数不正 (exit 2) として
弾く。呼び手の `gh api` が失敗したときの出力 (空文字など) を 0 件と読み違えて「draft が
1 件も無いので作ってよい」に倒れるのが最悪の failure mode なので、ここは黙って `None` へ
潰さず loud に落とす。

`--config` / `--operation` の必須性は従来どおり。引数ループは値取得を各 arm へ寄せ、
未知フラグの判定を値の有無より先に行う既存の順序を保った。

## テスト

引数解析のテストを 4 → 7 件へ。省略時が `None` であること、`0` が正しく `Some(0)` として
読まれること (「0 件」と「数えられなかった」を型で区別する契約)、不正値 5 種が引数不正で
あることを固定した。

## 本コミットの範囲

exe 単体では背圧が接続された。実測値を渡す呼び手 (夜間 workflow の `gh api` step) は
**本 PR には含まれず PR 3 で追加する** (ADR-069 chain 宣言 → 計画書 § WP-18 と ADR-071 を
5/5 で更新)。実 exe による drill は 4/5。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(adr-071): 未マージ draft PR 数による背圧を起票し drill 12 シナリオを記録

WP-18 PR 1 (4/5)。1/5〜3/5 で実装した背圧の設計判断・試験運用条件・実測を永続化する。

## 記録した決定 5 件

1. 指標は「未マージ draft PR 数 (claude/ prefix)」。run 回数や経過時間のような代理指標
   ではなく、止めたい事象 (人間が捌けない量の未処理成果物) を直接数える
2. 背圧の状態は GateInputs だけが持つ。enum 側にも状態を置くと判定経路が二股に分かれ、
   「テストは通るが実運用では別の枝を通る」drift を生む
3. 閾値は autonomy-config.toml の max_open_draft_prs、判定は >=。0 は draft-pr クラス
   だけの停止、キー欠落は既定値ではなく deny
4. 数えるのは呼び手 (workflow step の gh api)、判断するのは gate。exe が gh に依存すると
   ローカル drill が GitHub 到達性に依存し、安全装置の再現可能な検証ができなくなる
5. 数えられなかったことは 0 件ではない。不正値は exit 2 で loud に落とす

決定 4 の「呼び手が数を偽れる」問題は、schedule イベントが default branch の workflow 定義を
使うという GitHub の仕様で担保される (ADR-066 の config master ref 契約と同じ信頼境界)。

## 外部 SaaS の課金・上限事実を移管 (計画書 § 2 の退役条件 2)

2026-08-06 に最新値を再確認して永続化した。

- public リポジトリ + standard runner の Actions 実行は無料・分数無制限 (GitHub 公式 docs で
  原文確認)。GitHub Free の 2,000 分/月は private のみ
- claude-code-action@v1 は claude_code_oauth_token で Max 枠を消費。OAuth token は個人
  サブスクに紐づき、自動化の消費が対話作業のレート枠を圧迫しうる

要約: Actions の実行時間は無料だが Max 枠は有限。背圧の経済的根拠はここにある。

## 検証記録

release build の実 exe で drill 12 シナリオを実施し全て設計どおりを確認した。#6/#7 の対で
>= 境界、#8 で limit=0 の停止、#9 で fix push が draft 数から独立していること、#10 で
半壊 config が enabled ごと停止することを実バイナリ上で確認している。

unit test は判定コア 22 / sources 11 / 引数解析 7 件。網羅走査 1 件が 216 組合せを走査する。

## bounded lifetime

decision trigger (b)「閾値到達で実際に次の run が止まること」は本 ADR 固有の観測点で、
exe 単体 drill では作れない状態 (夜間ループが実際に 3 件積む必要がある)。期限は 2026-11-06。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(harness-plan): WP-18 PR 1 の完了を記帳し PR 3 への chain 宣言と SaaS 事実の移管を行う

WP-18 PR 1 (5/5)。計画書側の 3 種類の更新。

## 1. PR 1 完了の記帳

- WP-18 節の見出しと全体像表を「着手可」→「着手中(PR 1 実装済み)」へ
- PR 構成 1 に実装時の設計判断を確定記録: 閾値判定の層は (a) gate 内を採用。
  backpressure_connected() は廃し requires_draft_backpressure()(指標の要求のみ)と
  GateInputs の 2 フィールド(状態)へ分離した
- 着手前決定 2 の記述を過去形へ(backpressure_connected() は既に存在しない名前のため、
  現在形のまま残すと ADR-069 決定 1 の「名前一致」要件に反する)
- WP-19 ステップ 2 を「前倒し済み」→「land 済み」へ

## 2. PR chain 宣言(ADR-069 決定 1)

PR 1 が導入した 3 点(--open-draft-prs フラグ / max_open_draft_prs キー / DraftPr の許可
経路)は PR 3 まで呼び手を持たない。抽出↔呼び手のペアリングを表で具体名指定した。

順序を逆にできない根拠も明記した — ADR-052 原則 5 が背圧の接続を draft-pr クラス有効化の
前提条件としているため、背圧が先に land する必要がある(WP-17 の kill-switch 先行と同構造)。

なお PR 1 単体では draft-pr は deny のままで運用挙動は変わらない。

## 3. SaaS 課金・上限事実の移管(退役条件 2 / 順位 117 の 3 ステップ原則)

permanent 側(ADR-071 § 外部 SaaS の課金・上限事実)を先に作成 → 本ファイルの § 2 から
GitHub Actions 課金 2 点と claude-code-action の OAuth 認証を削除し ADR-071 への参照へ
置き換えた。cloud routines の事実群は WP-19 / ADR-070 の担当範囲のため本節に残している。
runner 単価の相対比も private 化時のみ関係するため移管対象外とした。

pnpm lint:docs / markdownlint ともに green。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: CodeRabbit 指摘 2 件に対応 (主張の適用範囲を限定 / 移管済み方針の更新)

PR #361 への CodeRabbit レビュー (Minor 2 件) の反映。どちらも「書いた内容が実態より
広い/古い」型の指摘で、妥当。

## 1. 「draft-pr は deny のまま」の適用範囲を限定 (2 箇所)

指摘は「**両文書**が PR 3 まで draft-pr に許可経路が無いように読める」というもの。実際には
ADR-071 § 検証記録の drill #6 が示すとおり、--open-draft-prs と有効な config を渡せば exe は
allow になる。

正確には**リポジトリ内の自動化経路に --open-draft-prs を渡す呼び手が 1 つも無いため** deny に
なる、が正しい。「運用挙動は変わらない」の根拠を exe の挙動ではなく呼び手の不在へ置き直した。
根拠を取り違えたまま PR 3 で呼び手が入ると、主張だけが stale に残る。

修正箇所は 2 つ:

- docs/adr/adr-071-*.md § 残課題
- docs/harness-improvement-plan.md § WP-18 の PR chain 宣言

計画書側から ADR-071 § 残課題への参照も付け、同じ限定が 1 箇所に集約されるようにした。

## 2. 計画書 § 2 preamble: 移管済みの事実と矛盾する旧方針を更新

旧文は「現時点では ADR 化せず本ファイルに保持する」と書いていたが、同じ blockquote の
次の行で「ADR-071 へ移管済み」と述べており矛盾していた。

方針文を「担当 WP の ADR 起票時に移管し本節から削除する」という恒常ルールへ書き換え、
移管済み / 未移管を別項目に分けた。未移管の内訳 (cloud routines の daily cap / webhook
上限 / GitHub App 必須 / 緑ステータスの意味) も具体名で列挙し、次の担当 WP が何を移管
すべきか読み取れるようにした。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 7, 2026
…実装と一致させる

WP-18 PR 3。pre-push review が計 8 サイクルで返した REJECT と warning、および CodeRabbit の
指摘 2 件を ADR へ反映する。fix step は毎回 workflow を直すが ADR を一度も更新しないため、
設計根拠を追いつかせる。

## 決定 8 — Windows CI が走らない問題への対処

GITHUB_TOKEN で作成した PR の pull_request イベントは **承認待ちの run** になり、人間が
Approve を押すまで ci.yml が動かない。Windows を主開発環境とする本プロジェクトで 2 OS 検証を
人間の操作待ちにする設計は採れない (2026-08-07 ユーザー判断)。

公式は App installation token または PAT を回避策として挙げるが **PAT は採れない** —
ADR-067 段 0 の ruleset は bypass を Repository admin ロールに与えており、オーナーの PAT は
その admin として動くため 5 層目の防波堤を素通りする。

副次効果として job の GITHUB_TOKEN から write を落とせた。GITHUB_TOKEN は claude-code-action の
github_token 入力として agent (未信頼) が触れる唯一の GitHub 資格情報であり、write のままだと
決定 6/7 の前提を token 側から崩しうる。App token の導入で Phase B より弱い権限で同じことが
できる構成になった。

## 決定 9 — git 操作は agent が触れていない作業ツリーで行う

決定 6 の禁止リストは staged path の列挙で .git 内部を構造的に見ない。一方 add/commit/push は
core.hooksPath / filter.* / core.fsmonitor / credential.helper / core.sshCommand / *.textconv 等
多数の設定経路から外部プログラムを起動する。刺さる先は決定 8 の App token を持つ publish step。

最初は deny-list で対処したが **2 回連続でレビュアーが漏れを見つけた**。列挙で追随する限り
往復は終わらない。publish/ を Implement 終了後に新規 clone し、作業ツリーのファイルだけを
rsync --delete --exclude '.git/' で運ぶ。agent はターン終了後に何も書けないので、その後に
作られた .git は定義上手が届かず、クラスごと消える。

決定 7 とは非対称。master-ref は作り直せないので検知にとどまるが、publish は作り直せるので
排除する。

## 決定 3 — 除外判定を PR 状態からブランチ存在へ

クローズされた draft の順位が再選択され push が non-fast-forward で失敗する無駄ループを塞ぐ。
ls-remote は一致なしでも exit 0 + 空出力なので「0 件」と「取得失敗」を取り違えない。

## CodeRabbit 指摘 2 件への対応

1. **unit test 件数が実装と不一致** — 23 件 (内訳 17/6) と書いていたが実測は 25 件 (17/8)。
   fix step が追加した 2 件を反映していなかった。実測して修正
2. **見出しと本文の件数が不一致** — 見出しと表は 6 件へ更新済みだったが、**本文が「2 件」の
   まま**残っていた。指摘の要約 (「見出しと表の件数」) だけを見ると解消済みに見えるが、
   コメント本文が本文側も名指ししていた。本文を 6 件へ更新し、うち 2 件は指摘の具体例自体が
   誤っていたこと (#3 は誤りに気づかず設計を動かし、#6 は誤りの中の正しい構造を拾った) も
   併記した

## その他

内部 Verify はコストフィルタであって品質保証ではない (ubuntu 単独・--ignored 無し・
hooks smoke 無しで ci.yml の真部分集合)。品質の保証は draft PR に紐づく ci.yml が担う。

スモーク観測 8 項目 / 静的レビューの捕捉実績 6 件 / js-yaml 16 step / 決定 9 件 — 数値の
記載はすべて実測と突き合わせて一致を確認した。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 7, 2026
…実装と一致させる

WP-18 PR 3。pre-push review が計 8 サイクルで返した REJECT と warning、および CodeRabbit の
指摘 2 件を ADR へ反映する。fix step は毎回 workflow を直すが ADR を一度も更新しないため、
設計根拠を追いつかせる。

## 決定 8 — Windows CI が走らない問題への対処

GITHUB_TOKEN で作成した PR の pull_request イベントは **承認待ちの run** になり、人間が
Approve を押すまで ci.yml が動かない。Windows を主開発環境とする本プロジェクトで 2 OS 検証を
人間の操作待ちにする設計は採れない (2026-08-07 ユーザー判断)。

公式は App installation token または PAT を回避策として挙げるが **PAT は採れない** —
ADR-067 段 0 の ruleset は bypass を Repository admin ロールに与えており、オーナーの PAT は
その admin として動くため 5 層目の防波堤を素通りする。

副次効果として job の GITHUB_TOKEN から write を落とせた。GITHUB_TOKEN は claude-code-action の
github_token 入力として agent (未信頼) が触れる唯一の GitHub 資格情報であり、write のままだと
決定 6/7 の前提を token 側から崩しうる。App token の導入で Phase B より弱い権限で同じことが
できる構成になった。

## 決定 9 — git 操作は agent が触れていない作業ツリーで行う

決定 6 の禁止リストは staged path の列挙で .git 内部を構造的に見ない。一方 add/commit/push は
core.hooksPath / filter.* / core.fsmonitor / credential.helper / core.sshCommand / *.textconv 等
多数の設定経路から外部プログラムを起動する。刺さる先は決定 8 の App token を持つ publish step。

最初は deny-list で対処したが **2 回連続でレビュアーが漏れを見つけた**。列挙で追随する限り
往復は終わらない。publish/ を Implement 終了後に新規 clone し、作業ツリーのファイルだけを
rsync --delete --exclude '.git/' で運ぶ。agent はターン終了後に何も書けないので、その後に
作られた .git は定義上手が届かず、クラスごと消える。

決定 7 とは非対称。master-ref は作り直せないので検知にとどまるが、publish は作り直せるので
排除する。

## 決定 3 — 除外判定を PR 状態からブランチ存在へ

クローズされた draft の順位が再選択され push が non-fast-forward で失敗する無駄ループを塞ぐ。
ls-remote は一致なしでも exit 0 + 空出力なので「0 件」と「取得失敗」を取り違えない。

## CodeRabbit 指摘 2 件への対応

1. **unit test 件数が実装と不一致** — 23 件 (内訳 17/6) と書いていたが実測は 25 件 (17/8)。
   fix step が追加した 2 件を反映していなかった。実測して修正
2. **見出しと本文の件数が不一致** — 見出しと表は 6 件へ更新済みだったが、**本文が「2 件」の
   まま**残っていた。指摘の要約 (「見出しと表の件数」) だけを見ると解消済みに見えるが、
   コメント本文が本文側も名指ししていた。本文を 6 件へ更新し、うち 2 件は指摘の具体例自体が
   誤っていたこと (#3 は誤りに気づかず設計を動かし、#6 は誤りの中の正しい構造を拾った) も
   併記した

## その他

内部 Verify はコストフィルタであって品質保証ではない (ubuntu 単独・--ignored 無し・
hooks smoke 無しで ci.yml の真部分集合)。品質の保証は draft PR に紐づく ci.yml が担う。

スモーク観測 8 項目 / 静的レビューの捕捉実績 6 件 / js-yaml 16 step / 決定 9 件 — 数値の
記載はすべて実測と突き合わせて一致を確認した。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 7, 2026
…実装と一致させる

WP-18 PR 3。pre-push review が計 8 サイクルで返した REJECT と warning、および CodeRabbit の
指摘 2 件を ADR へ反映する。fix step は毎回 workflow を直すが ADR を一度も更新しないため、
設計根拠を追いつかせる。

## 決定 8 — Windows CI が走らない問題への対処

GITHUB_TOKEN で作成した PR の pull_request イベントは **承認待ちの run** になり、人間が
Approve を押すまで ci.yml が動かない。Windows を主開発環境とする本プロジェクトで 2 OS 検証を
人間の操作待ちにする設計は採れない (2026-08-07 ユーザー判断)。

公式は App installation token または PAT を回避策として挙げるが **PAT は採れない** —
ADR-067 段 0 の ruleset は bypass を Repository admin ロールに与えており、オーナーの PAT は
その admin として動くため 5 層目の防波堤を素通りする。

副次効果として job の GITHUB_TOKEN から write を落とせた。GITHUB_TOKEN は claude-code-action の
github_token 入力として agent (未信頼) が触れる唯一の GitHub 資格情報であり、write のままだと
決定 6/7 の前提を token 側から崩しうる。App token の導入で Phase B より弱い権限で同じことが
できる構成になった。

## 決定 9 — git 操作は agent が触れていない作業ツリーで行う

決定 6 の禁止リストは staged path の列挙で .git 内部を構造的に見ない。一方 add/commit/push は
core.hooksPath / filter.* / core.fsmonitor / credential.helper / core.sshCommand / *.textconv 等
多数の設定経路から外部プログラムを起動する。刺さる先は決定 8 の App token を持つ publish step。

最初は deny-list で対処したが **2 回連続でレビュアーが漏れを見つけた**。列挙で追随する限り
往復は終わらない。publish/ を Implement 終了後に新規 clone し、作業ツリーのファイルだけを
rsync --delete --exclude '.git/' で運ぶ。agent はターン終了後に何も書けないので、その後に
作られた .git は定義上手が届かず、クラスごと消える。

決定 7 とは非対称。master-ref は作り直せないので検知にとどまるが、publish は作り直せるので
排除する。

## 決定 3 — 除外判定を PR 状態からブランチ存在へ

クローズされた draft の順位が再選択され push が non-fast-forward で失敗する無駄ループを塞ぐ。
ls-remote は一致なしでも exit 0 + 空出力なので「0 件」と「取得失敗」を取り違えない。

## CodeRabbit 指摘 2 件への対応

1. **unit test 件数が実装と不一致** — 23 件 (内訳 17/6) と書いていたが実測は 25 件 (17/8)。
   fix step が追加した 2 件を反映していなかった。実測して修正
2. **見出しと本文の件数が不一致** — 見出しと表は 6 件へ更新済みだったが、**本文が「2 件」の
   まま**残っていた。指摘の要約 (「見出しと表の件数」) だけを見ると解消済みに見えるが、
   コメント本文が本文側も名指ししていた。本文を 6 件へ更新し、うち 2 件は指摘の具体例自体が
   誤っていたこと (#3 は誤りに気づかず設計を動かし、#6 は誤りの中の正しい構造を拾った) も
   併記した

## その他

内部 Verify はコストフィルタであって品質保証ではない (ubuntu 単独・--ignored 無し・
hooks smoke 無しで ci.yml の真部分集合)。品質の保証は draft PR に紐づく ci.yml が担う。

## 決定 6 — 禁止リストに台帳自身を追加

「自分を縛る仕組み」として config と gate exe は列挙したのに、**選択元である台帳**
(docs/claude-code-web-tasks.md) を同じクラスだと認識していなかった。決定 1 で「台帳は
master ref から読む」と信頼境界を引きながら、台帳への書き込み経路を塞いでいない。

agent が台帳を書き換えた diff が draft PR に紛れ、人間が見落としてマージすると、以後の
タスク選択が静かに汚染される。禁止リストへ 1 エントリ追加して塞いだ。

## 決定 10 — 停止の色分けを決定として明文化する

pre-push simplicity review が「Count open drafts に continue-on-error が無く、他の停止点の
ように graceful degradation しない」と指摘した。**現状を維持し、理由を決定として残す。**

設計上の正常な結末 (背圧 deny / タスク無し / guard deny / 空 diff) は green + NIGHTLY_SKIP、
インフラ障害 (gh / network / clone の失敗) は red。両者を同じ扱いにすると、run 一覧から
「本当に壊れた夜」と「何もすることが無かった夜」の区別が消える。毎晩回る無人ループでは、
この 2 つが混ざった時点で run 一覧が読まれなくなる。

Report outcome は if: '!cancelled()' なので red でも 1 行サマリは出る。診断は失われない。

## 件数

スモーク観測 8 項目 / 静的レビューの捕捉実績 8 件 / js-yaml 17 step / 決定 10 件 — 数値の
記載はすべて実測と突き合わせて一致を確認した。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 7, 2026
…実装と一致させる

WP-18 PR 3。pre-push review が計 8 サイクルで返した REJECT と warning、および CodeRabbit の
指摘 2 件を ADR へ反映する。fix step は毎回 workflow を直すが ADR を一度も更新しないため、
設計根拠を追いつかせる。

## 決定 8 — Windows CI が走らない問題への対処

GITHUB_TOKEN で作成した PR の pull_request イベントは **承認待ちの run** になり、人間が
Approve を押すまで ci.yml が動かない。Windows を主開発環境とする本プロジェクトで 2 OS 検証を
人間の操作待ちにする設計は採れない (2026-08-07 ユーザー判断)。

公式は App installation token または PAT を回避策として挙げるが **PAT は採れない** —
ADR-067 段 0 の ruleset は bypass を Repository admin ロールに与えており、オーナーの PAT は
その admin として動くため 5 層目の防波堤を素通りする。

副次効果として job の GITHUB_TOKEN から write を落とせた。GITHUB_TOKEN は claude-code-action の
github_token 入力として agent (未信頼) が触れる唯一の GitHub 資格情報であり、write のままだと
決定 6/7 の前提を token 側から崩しうる。App token の導入で Phase B より弱い権限で同じことが
できる構成になった。

## 決定 9 — git 操作は agent が触れていない作業ツリーで行う

決定 6 の禁止リストは staged path の列挙で .git 内部を構造的に見ない。一方 add/commit/push は
core.hooksPath / filter.* / core.fsmonitor / credential.helper / core.sshCommand / *.textconv 等
多数の設定経路から外部プログラムを起動する。刺さる先は決定 8 の App token を持つ publish step。

最初は deny-list で対処したが **2 回連続でレビュアーが漏れを見つけた**。列挙で追随する限り
往復は終わらない。publish/ を Implement 終了後に新規 clone し、作業ツリーのファイルだけを
rsync --delete --exclude '.git/' で運ぶ。agent はターン終了後に何も書けないので、その後に
作られた .git は定義上手が届かず、クラスごと消える。

決定 7 とは非対称。master-ref は作り直せないので検知にとどまるが、publish は作り直せるので
排除する。

## 決定 3 — 除外判定を PR 状態からブランチ存在へ

クローズされた draft の順位が再選択され push が non-fast-forward で失敗する無駄ループを塞ぐ。
ls-remote は一致なしでも exit 0 + 空出力なので「0 件」と「取得失敗」を取り違えない。

## CodeRabbit 指摘 2 件への対応

1. **unit test 件数が実装と不一致** — 23 件 (内訳 17/6) と書いていたが実測は 25 件 (17/8)。
   fix step が追加した 2 件を反映していなかった。実測して修正
2. **見出しと本文の件数が不一致** — 見出しと表は 6 件へ更新済みだったが、**本文が「2 件」の
   まま**残っていた。指摘の要約 (「見出しと表の件数」) だけを見ると解消済みに見えるが、
   コメント本文が本文側も名指ししていた。本文を 6 件へ更新し、うち 2 件は指摘の具体例自体が
   誤っていたこと (#3 は誤りに気づかず設計を動かし、#6 は誤りの中の正しい構造を拾った) も
   併記した

## その他

内部 Verify はコストフィルタであって品質保証ではない (ubuntu 単独・--ignored 無し・
hooks smoke 無しで ci.yml の真部分集合)。品質の保証は draft PR に紐づく ci.yml が担う。

## 決定 6 — 禁止リストに台帳自身を追加

「自分を縛る仕組み」として config と gate exe は列挙したのに、**選択元である台帳**
(docs/claude-code-web-tasks.md) を同じクラスだと認識していなかった。決定 1 で「台帳は
master ref から読む」と信頼境界を引きながら、台帳への書き込み経路を塞いでいない。

agent が台帳を書き換えた diff が draft PR に紛れ、人間が見落としてマージすると、以後の
タスク選択が静かに汚染される。禁止リストへ 1 エントリ追加して塞いだ。

## 決定 10 — 停止の色分けを決定として明文化する

pre-push simplicity review が「Count open drafts に continue-on-error が無く、他の停止点の
ように graceful degradation しない」と指摘した。**現状を維持し、理由を決定として残す。**

設計上の正常な結末 (背圧 deny / タスク無し / guard deny / 空 diff) は green + NIGHTLY_SKIP、
インフラ障害 (gh / network / clone の失敗) は red。両者を同じ扱いにすると、run 一覧から
「本当に壊れた夜」と「何もすることが無かった夜」の区別が消える。毎晩回る無人ループでは、
この 2 つが混ざった時点で run 一覧が読まれなくなる。

Report outcome は if: '!cancelled()' なので red でも 1 行サマリは出る。診断は失われない。

## 件数

## 捕捉 9 — job の実行時間に上限が無かった

同リポジトリの他 workflow は例外なく timeout-minutes を明示している (ci.yml=60 /
pr-monitor.yml=15, 20 / release-binaries.yml=30) のに、本 job だけ無指定 = 既定 360 分。
同じ claude-code-action を使う pr-monitor.yml の fix job が 30 turns に対し 20 分を課す
のに対し、本 job は turns 2 倍 (60) で上限なしだった。

決定 4 で Max 枠の節約のためにゲートを二重に呼ぶ設計にしておきながら、**ハングした run が
枠を焼き続ける経路**を空けていた。timeout-minutes: 60 を明示。

スモーク観測 8 項目 / 静的レビューの捕捉実績 9 件 / js-yaml 17 step / 決定 10 件 — 数値の
記載はすべて実測と突き合わせて一致を確認した。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 7, 2026
…R-072) (#363)

* feat(nightly-task-select): 台帳から無人可タスクを決定論的に選ぶ exe を新設する

WP-18 PR 3 (1/5)。夜間ループの「何を実装するか」を決める層。ユーザー確認済みの設計
(2026-08-06): 選択ロジックは workflow の shell ではなく Rust exe に置く。

## なぜ LLM でも shell でもないのか

ADR-052 は「分類ロジックを Rust 分類関数を用意せず自律 actor の実行時 LLM 判断に委ねる」
ことをアンチパターンとして挙げている。夜間ループで何を実装するかは自律動作の起点であり、
ここが揺れると下流のゲートがいくら堅くても「意図しないタスクを正しく実装した draft PR」が
出てくる。

shell (awk/grep) 案を採らなかったのは、markdown table の境界 (列ずれ・全角・エスケープ
されたパイプ・無関係な表の混在) に回帰テストを書く場がないため。本 crate は 25 件の
unit test でその境界を固定している。

## 毎晩同じタスクを実装し直す問題

台帳の行はタスクが**マージされるまで**残る。素朴に「無人可の先頭行」を選ぶと毎晩同じ
タスクを実装する。ブランチ名に順位を埋め (claude/nightly-<順位>)、open な同名ブランチの
順位を --exclude-ranks で除外する形で決定論のまま解いた。

--exclude-ranks は空でも省略できない。空文字は「数えた結果 0 件」、フラグ欠落は
「数えられなかった」で意味が違う。省略可能にすると gh api が失敗した run が「開いている
draft は無い」と解釈して同じタスクを二重実装する (PR 1 の --open-draft-prs と同じ設計)。

## 曖昧さはすべて停止側へ

台帳は人間が手で編集する markdown なので、列ずれ・順位の重複・未知のマーク表記が起こる。
これらは読み飛ばさず **エラー (exit 2)** にする。読み飛ばした行が本来の選択対象だった場合、
ループは黙って別のタスクを実装するため。

「対象ファイル」列の解決も同じ方針を採る。実表記が「対象ファイル」と「対象ファイル (実パス)」
の 2 種あるため前方一致で探すが、**複数ヒットしたら先頭を黙って採らずエラーにする**。将来
「対象ファイル案」のような列が先に追加されると、誤った列を agent への指示に使ってしまう
(pre-push simplicity review の指摘 SIM-NEW-ledger-rs-L865 を反映)。

「✅ (条件付き)」のような書き足しもエラーにする。無人可でない側へ倒すと人間の意図と判定が
ずれたまま静かに進む。

exit コードは 0 = 選択 / 2 = 入力不正・台帳破損 / 3 = 該当なし (正常な no-op)。3 と 2 を
分けるのは run log で「何もすることが無かった」と「台帳が壊れている」を切り分けるためで、
後続を動かさない点は同じ。

## 実データ検証

PR 2 (#362) マージ後の master 台帳で実走した:

- 除外なし → rank=203 branch=claude/nightly-203 (Batch 1 の先頭 ✅ 行)
- 203 除外 → rank=240
- 無人可 7 件すべて除外 → exit 3 (no-op)
- 棚卸し履歴 / 無人可としなかった理由 の 2 表 (順位 列を持つが 無人可 列を持たない) は
  正しく無視された
- **PR 2 未マージの旧台帳 → exit 2** で loud に停止。台帳が旧構成のままなら夜間ループは
  黙って no-op せず、理由を出して止まる

依存 crate ゼロ。本 exe は夜間ループで唯一「何を実装するか」を決める存在なので、供給元を
増やさないこと自体を設計制約とした。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* chore(build): cli-nightly-task-select を build script と build:all へ配線する

WP-18 PR 3 (2/5)。他の cli-* と同じ形 (cargo build --release + deploy-artifacts) を踏襲する。

CI 経路 (夜間 workflow) は master ref から cargo build するため deploy 先の .claude/*.exe を
使わないが、cli-fix-push-gate も同じく CI 専用でありながら build script + deploy を持つ。
形を揃えることで、ローカル drill が他の exe と同じ `node scripts/run-artifact.mjs` 経由で
実行できる。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* feat(nightly-todo): 夜間 todo 消化 workflow を新設する (schedule 03:00 JST)

WP-18 PR 3 (3/5)。台帳の無人可タスクを 1 件無人実装し **draft PR 作成で停止**する
15 step の workflow。マージ判断は人間 (ADR-052 の commitment 点の手前で止まる操作)。

schedule は毎日 1 回 18:00 UTC = 03:00 JST。反復検証のため workflow_dispatch も持たせた
(ADR-067 段 2 の知見 2)。dry_run 入力でゲート通過まで走らせて push を止められる。

## 信頼境界 (ADR-066 決定 3 / ADR-067 と同型)

台帳・ゲート exe・autonomy-config.toml はすべて master ref の写しから調達する。schedule
イベントは GitHub の仕様上 default branch の workflow 定義で実行されるため、本ファイル自体も
PR ブランチからは差し替えられない。push / PR 作成は workflow step が行い agent には gh / git を
与えない。

## 背圧ゲートを 2 回呼ぶ

pre-flight (agent 起動前) は Max 枠の節約、authority (push 直前) は push の権威。pre-flight は
continue-on-error で受け Select task に if を付けて deny 後は後続を走らせない。背圧 deny は
設計上の正常動作なので job failure にはしない。

## agent には Bash を与えない

--allowedTools は Read/Edit/Write/Glob/Grep のみ。cargo を許さないのは、cargo test が build.rs と
テストバイナリ = agent 自身が直前に書いたコードを実行するため。agent のターン中はプロセス env に
action の資格情報が載るので、cargo を与えることは「自分で書いたコードを資格情報のある環境で、
いかなるゲートより前に実行させる」ことに等しい。詳細は ADR-072 決定 5。

## CI を draft PR へ紐づける App token (ADR-072 決定 8)

GITHUB_TOKEN で作成した PR の pull_request イベントは**承認待ちの run** になり、人間が
Approve を押すまで ci.yml が動かない。Windows を主開発環境とする本プロジェクトで 2 OS 検証を
人間の操作待ちにする設計は採れないため、push と PR 作成に App installation token を使う。

PAT ではなく App を選ぶのは、オーナーの PAT が Repository admin として動き ADR-067 の
ruleset backstop を bypass するため。App installation は独立 actor で admin ではない。

App 権限は Contents / Pull requests の write のみで **Workflows は付けない** — .github/workflows/**
を含む push が権限側でも通らず Guard step の禁止リストと二重になる。token 発行は publish 直前
(寿命 1 時間に対し実装 agent が最大 60 ターン走るため)。背圧の計数は GITHUB_TOKEN のままで、
読み取りに triggering の含意が無い以上 App token の露出面を広げない。

## 内部 Verify はコストフィルタであって品質保証ではない

ubuntu 単独・--ignored 無し・hooks smoke 無しで ci.yml の真部分集合。目的は「コンパイルも
通らない変更で draft PR を作り背圧の枠と人間のレビュー時間を浪費しない」ことに限る。品質の
保証は PR に紐づく ci.yml (2 OS matrix) が担う。2 OS matrix をここへ複製しないのは検証の定義が
2 箇所へ分かれて drift するため。この区別は step 名・コメント・PR 本文の 3 箇所に明記した。

## ガードレールの保護 (2 層)

- **禁止リスト**: work/ の diff が autonomy-config.toml / .github/workflows/ / 各ゲート crate に
  触れていたら push しない。空 diff も deny
- **改ざん検知**: agent の file tools は $GITHUB_WORKSPACE 全体に届き master-ref/ を書き換え
  られる。Build 段で採った sha256 と authority gate の直前で照合する。基準値は $RUNNER_TEMP =
  ワークスペース外に置く

## 停止段の切り分けを 1 行で完結させる

Report outcome が全 step の outcome を 1 行に出す (implement / app_token 含む)。
「Stop when there is nothing to implement」は 2 経路 (select 非ゼロ / pre-flight deny で select
自体が skip) で発火するため、経路ごとに参照すべきログ行を出し分ける。

## その他の fail-closed

- open PR の取得が上限 100 件に張り付いたら停止する (背圧の過小評価を防ぐ)
- 着手済み順位の除外は draft かどうかに依らない (ready 化された claude/nightly-N も再実装しない)

## 禁止リストに台帳自身を追加 (ADR-072 決定 6)

docs/claude-code-web-tasks.md は「次に何を実装するか」を決める仕組みであり、config や
gate exe と同じく **agent を縛る側**にある。ここが draft PR 経由で書き換わると、以後の
タスク選択が静かに汚染される。初版はここを落としていた。

## Count open drafts に continue-on-error を付けない理由をコメント化

設計上の停止点 (背圧 deny / タスク無し / guard deny) は green + NIGHTLY_SKIP、インフラ
障害は red、という色分けは意図的。ADR に書いていなかったため pre-push review に再指摘
された。決定 10 として明文化し、workflow 側にも根拠を残す。

## timeout-minutes: 60 を明示

他 workflow は例外なく明示済み (ci.yml=60 / pr-monitor.yml=15, 20 /
release-binaries.yml=30) で、本 job だけ無指定 = 既定 360 分だった。無人・毎日回る job で
ハングを 6 時間放置すると Max 枠を焼き続ける (ADR-072 決定 4 が節約のためにゲートを
二重に呼ぶ設計と矛盾する)。Implement 60 turns + 後続 step を見込んだ上限。

js-yaml で構文検証済み (17 step)。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(adr-072): 決定 8 (App token) / 決定 9 (clean publish tree) を新設し件数記載を実装と一致させる

WP-18 PR 3。pre-push review が計 8 サイクルで返した REJECT と warning、および CodeRabbit の
指摘 2 件を ADR へ反映する。fix step は毎回 workflow を直すが ADR を一度も更新しないため、
設計根拠を追いつかせる。

## 決定 8 — Windows CI が走らない問題への対処

GITHUB_TOKEN で作成した PR の pull_request イベントは **承認待ちの run** になり、人間が
Approve を押すまで ci.yml が動かない。Windows を主開発環境とする本プロジェクトで 2 OS 検証を
人間の操作待ちにする設計は採れない (2026-08-07 ユーザー判断)。

公式は App installation token または PAT を回避策として挙げるが **PAT は採れない** —
ADR-067 段 0 の ruleset は bypass を Repository admin ロールに与えており、オーナーの PAT は
その admin として動くため 5 層目の防波堤を素通りする。

副次効果として job の GITHUB_TOKEN から write を落とせた。GITHUB_TOKEN は claude-code-action の
github_token 入力として agent (未信頼) が触れる唯一の GitHub 資格情報であり、write のままだと
決定 6/7 の前提を token 側から崩しうる。App token の導入で Phase B より弱い権限で同じことが
できる構成になった。

## 決定 9 — git 操作は agent が触れていない作業ツリーで行う

決定 6 の禁止リストは staged path の列挙で .git 内部を構造的に見ない。一方 add/commit/push は
core.hooksPath / filter.* / core.fsmonitor / credential.helper / core.sshCommand / *.textconv 等
多数の設定経路から外部プログラムを起動する。刺さる先は決定 8 の App token を持つ publish step。

最初は deny-list で対処したが **2 回連続でレビュアーが漏れを見つけた**。列挙で追随する限り
往復は終わらない。publish/ を Implement 終了後に新規 clone し、作業ツリーのファイルだけを
rsync --delete --exclude '.git/' で運ぶ。agent はターン終了後に何も書けないので、その後に
作られた .git は定義上手が届かず、クラスごと消える。

決定 7 とは非対称。master-ref は作り直せないので検知にとどまるが、publish は作り直せるので
排除する。

## 決定 3 — 除外判定を PR 状態からブランチ存在へ

クローズされた draft の順位が再選択され push が non-fast-forward で失敗する無駄ループを塞ぐ。
ls-remote は一致なしでも exit 0 + 空出力なので「0 件」と「取得失敗」を取り違えない。

## CodeRabbit 指摘 2 件への対応

1. **unit test 件数が実装と不一致** — 23 件 (内訳 17/6) と書いていたが実測は 25 件 (17/8)。
   fix step が追加した 2 件を反映していなかった。実測して修正
2. **見出しと本文の件数が不一致** — 見出しと表は 6 件へ更新済みだったが、**本文が「2 件」の
   まま**残っていた。指摘の要約 (「見出しと表の件数」) だけを見ると解消済みに見えるが、
   コメント本文が本文側も名指ししていた。本文を 6 件へ更新し、うち 2 件は指摘の具体例自体が
   誤っていたこと (#3 は誤りに気づかず設計を動かし、#6 は誤りの中の正しい構造を拾った) も
   併記した

## その他

内部 Verify はコストフィルタであって品質保証ではない (ubuntu 単独・--ignored 無し・
hooks smoke 無しで ci.yml の真部分集合)。品質の保証は draft PR に紐づく ci.yml が担う。

## 決定 6 — 禁止リストに台帳自身を追加

「自分を縛る仕組み」として config と gate exe は列挙したのに、**選択元である台帳**
(docs/claude-code-web-tasks.md) を同じクラスだと認識していなかった。決定 1 で「台帳は
master ref から読む」と信頼境界を引きながら、台帳への書き込み経路を塞いでいない。

agent が台帳を書き換えた diff が draft PR に紛れ、人間が見落としてマージすると、以後の
タスク選択が静かに汚染される。禁止リストへ 1 エントリ追加して塞いだ。

## 決定 10 — 停止の色分けを決定として明文化する

pre-push simplicity review が「Count open drafts に continue-on-error が無く、他の停止点の
ように graceful degradation しない」と指摘した。**現状を維持し、理由を決定として残す。**

設計上の正常な結末 (背圧 deny / タスク無し / guard deny / 空 diff) は green + NIGHTLY_SKIP、
インフラ障害 (gh / network / clone の失敗) は red。両者を同じ扱いにすると、run 一覧から
「本当に壊れた夜」と「何もすることが無かった夜」の区別が消える。毎晩回る無人ループでは、
この 2 つが混ざった時点で run 一覧が読まれなくなる。

Report outcome は if: '!cancelled()' なので red でも 1 行サマリは出る。診断は失われない。

## 件数

## 捕捉 9 — job の実行時間に上限が無かった

同リポジトリの他 workflow は例外なく timeout-minutes を明示している (ci.yml=60 /
pr-monitor.yml=15, 20 / release-binaries.yml=30) のに、本 job だけ無指定 = 既定 360 分。
同じ claude-code-action を使う pr-monitor.yml の fix job が 30 turns に対し 20 分を課す
のに対し、本 job は turns 2 倍 (60) で上限なしだった。

決定 4 で Max 枠の節約のためにゲートを二重に呼ぶ設計にしておきながら、**ハングした run が
枠を焼き続ける経路**を空けていた。timeout-minutes: 60 を明示。

スモーク観測 8 項目 / 静的レビューの捕捉実績 9 件 / js-yaml 17 step / 決定 10 件 — 数値の
記載はすべて実測と突き合わせて一致を確認した。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(harness-plan): WP-18 の記帳を最終構成へ更新し件数記載を実測と一致させる

WP-18 PR 3 (5/5)。

## 記帳

- WP-18 節の見出しと全体像表を「着手中」→「実装済(実走スモーク待ち)」へ
- PR 2 / PR 3 の項目を実装内容付きで確定記録。PR 3 は 16 step 構成 (選択 → 実装 →
  コストフィルタ → clean publish tree → 禁止リスト → 改ざん検知 → gate → App token →
  draft PR 作成) と、決定 7 / 8 / 9 の由来を残した
- 受け入れ基準を表へ変え、充足 3 件と**未実施 2 件**を分けた

## 件数記載を実測と一致させた

CodeRabbit が ADR 側で指摘した「件数が実装と一致しない」型の不整合が、計画書側にも
同型で存在していた:

- unit test 23 件 → **25 件** (fix step が追加した 2 件を反映していなかった)
- workflow 15 step → **16 step** (clean publish tree 段の追加を反映していなかった)
- スモークの同梱観測 7 項目 → **8 項目**

観測項目については件数だけ直さず、**内訳の列挙をやめて ADR-072 の表を指す形**に変えた。
同じ一覧を 2 箇所に持つ限り再び drift するため (#362 の post-merge feedback が指摘した
single source-of-truth 問題と同型)。

## 受け入れ基準の状態を正直に書く

実走スモークは本 WP の受け入れ基準の中核だが**未実施**である。「実装が全部 land した =
WP 完了」と書ける状態ではないため表で未実施を明示した。

採用率 50% が統計的な意味を持たない点 (2 週間・最大 14 件) も併記した。

## chain 宣言

PR 1 が導入した 3 点の消費側を実装済みの step 名で具体化した。PR 2 → PR 3 の**実行時**
依存も明記した (PR 2 未マージだと選択 exe が exit 2 で止まるが、これは設計どおりの
fail-closed で静かな no-op にはならない)。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
aloekun added a commit that referenced this pull request Aug 8, 2026
* docs(todo): WP-18 で検出した問題を 4 エントリへ登録 (順位 374-377)

WP-18 (夜間 todo 消化ループ、#361 / #362 / #363) の実装中に pre-push review・
CodeRabbit・ユーザー指摘で検出した問題 9 件のうち、todo 登録が要る 8 件を **実装時の
PR 粒度**で 4 エントリへまとめる。切り分けは 2026-08-06 にユーザー確認済み。

## 評価時の #1 を検証し、todo 登録が不要になった

#1 は「Bash prefix 許可の悪用可能性検証と全経路への横展開」として登録予定だった。
ユーザー指示により登録前に検証したところ、**前提が誤りだった**。

#363 の security review は「`Bash(cargo test:*)` は前方一致でシェルを解釈しないため
`cargo test` に任意コマンドを連結すると通過する」と主張していた。公式ドキュメントは
これを明確に否定している:

  Claude Code is aware of shell operators, so a rule like `Bash(safe-cmd *)` won't give
  it permission to run the command `safe-cmd && other-cmd`. The recognized command
  separators are `&&`, `||`, `;`, `|`, `|&`, `&`, and newlines. A rule must match each
  subcommand independently.

`--allowedTools` も同じルール体系に属する (managed settings の deny を --allowedTools で
上書きできない、と明記されている)。

結果:

- `pr-monitor.yml` の Phase A 分析 agent に**当該の穴は無く、対処不要**。production の
  live な穴という当初の見立ては誤りだった
- 残作業は `ADR-072` 決定 5 の根拠記述の訂正のみで、#363 が open のうちに同 PR へ直接
  反映する。よって todo エントリを立てない

検証結果と経緯はセクション冒頭の対応表に残した。

## この一件自体を教訓として取り込んだ

「レビュー指摘への対応時チェックリスト」エントリに 4 項目目を追加した — **指摘が技術的
前提 (ツールの挙動・仕様) に依拠しているなら、対処より先にその前提を検証する**。とくに
設計変更や他経路への横展開を伴う場合。

今回は未検証の前提のまま (a) agent から Bash を落とす設計変更を行い、(b) それを ADR の
決定として記録し、(c) さらに「同じ形が production にもある」と横展開の警告まで出していた。
一次情報に当たれば 1 回の WebFetch で否定できた。

## 内訳

- 順位 374: WP-18 夜間ループの実走スモーク実施 (Tier 1、評価時の #2/#3)
- 順位 375: レビュー指摘への対応時チェックリスト (Tier 2、評価時の #4/#5/#6 + 今回の #1)
- 順位 376: push-runner の bookmark 自動前進がスタック境界を壊す (Tier 2、評価時の #7)
- 順位 377: 夜間ループの防御を検知から防止へ格上げする判断 (Tier 3、評価時の #8/#9)

## まとめ方の方針

リポジトリの既存バッチ登録 (#350#357 の 24 件を 8 エントリへ) と同じく実装時の PR 粒度で
まとめた。評価時の番号との対応はセクション冒頭に表で残してある。

順位 374 (スモーク) の観測項目は `ADR-072` の実走スモーク節に表があるため、todo 側は
スケジューリングの掛かりだけを持ちチェックリストを複製しない。同じ表を 2 箇所で管理すると
必ず drift する (#362 の post-merge feedback が指摘した single source-of-truth 問題と同型)。

ADR-033 (絶対番号は table のみに保持) に従い、エントリ本文には順位番号を書いていない。
todo20.md は 50KB 閾値内。pnpm lint:docs / markdownlint ともに green。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(todo): #363 post-merge feedback の採用 6 件を登録 (順位 378-383)

WP-18 最終 PR (#363、ADR-072) のマージ後 feedback が Tier 1 に 4 件・Tier 2 に 2 件を
採用候補として挙げた。ユーザー承認 (2026-08-07) を得て登録する。

## 6 件は 1 本の根から出ている

台帳 (docs/claude-code-web-tasks.md) の 内容 / 対象ファイル / 注意 は自由記述のまま
無人 agent のプロンプトへ流入する。agent は $GITHUB_WORKSPACE 全体に書き込め、その
出力は draft PR 本文という公開面に出る。ADR-054 の信頼境界そのもの。

- 378 (XS) 台帳を ADR-035 の docs-only 除外パス表へ追加 — 他 3 件の前提。台帳だけを
  変える PR が緩い評価経路に乗ると、対策そのものを迂回する台帳 PR が通りうる
- 379 (S)  tool scope を work/** へ限定 — ADR-072 決定 7 の改ざん検知が必要になって
  いる根本原因。実装後も検知層は残す (防御を 1 枚に減らす変更ではない)
- 380 (M)  台帳フィールドを untrusted data として明示 framing
- 381 (S)  台帳由来 SUMMARY の draft PR 本文出力に screening
- 382 (M)  injection payload の regression test (380 に依存)
- 383 (S)  is_separator_row のパイプ検証欠落

## 期限を「定常運用開始前」に固定する

実効リスクは現時点では低い — 悪意ある台帳行を master へマージするのはユーザー自身で、
単独運用では外部からの注入経路が無い。ただし夜間ループが定常運用に入り draft PR の
流量が増えると前提が変わるため、無期限の Tier 積みにしない。

順位 374 (実走スモーク) は dry_run で PR を作らないため本件の実害が無く、待たせない。

## 383 は実コードで確認済み

is_table_row は行頭 | を要求するが、is_separator_row は split_cells の結果しか見ない。
split_cells("---") は ["---"] を返し全セルが '-' のみなので真になる。markdown の
水平線がセパレータ行として通る (todo ファイル自身が --- を使っている)。
ADR-072 決定 2 の fail-closed 設計の coverage hole。

## 併せて

順位 374 のスモーク観測項目数を 4 → 8 へ修正した (ADR-072 側の実測と不一致だった)。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(harness-plan): WP-18 を 3 PR マージ済へ更新し残作業 2 系統を明示する

#363 のマージ (2026-08-07) で WP-18 の実装 3 本がすべて land した。

## 「実装完了 = WP 完了」ではないことを表で残す

コードは全部 master にあるが、**夜間ループはまだ 1 度も走っていない**。この状態を
「実装済」の一語で片付けると、次のセッションが受け入れ基準を満たしたものと誤読する。
残作業を 2 系統に分けて表にした:

1. 実走スモーク (順位 374) — 受け入れ基準の中核
2. prompt injection 対策 4 件 (順位 378-381) — 定常運用開始前に必須

## スモークの前提が充足したことを記録

受け入れ基準の表は「(a) workflow が master にある (b) 台帳に無人可マークがある」を
未充足として書いていたが、**両方ともマージで解消した**。残る操作は GitHub UI 側の
AUTONOMY_ENABLED 設定のみなので、その 1 点へ書き換えた。

## 依存関係を明示する

378-381 は 1 本の根 (台帳の自由記述が無検証で agent プロンプトへ流入) から出ている。
一方スモークは dry_run で PR を作らないため本件の実害が無い。したがって
**スモークは 378-381 を待たずに着手してよい**と明記した。次セッションが順序で
迷わないようにするため。

## 未 push の改善 3 点の所在を残す

#363 の最終 push が security REJECT で止まったため、改ざん検知の red 化 / 決定 10 の
色分け表 / 決定 6 の列挙基準が master に載っていない。ローカル bookmark
wp18/unpushed-improvements (fc22403c) に保持していることを記録した。いずれも
可観測性と文書の改善で、fail-closed 自体は master 版でも成立している。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs(todo): 外部設定 (GitHub App / variables / secrets) の実体記録を登録 (順位 384)

新セッションでの指摘 (2026-08-07): workflow は vars.NIGHTLY_APP_ID /
secrets.NIGHTLY_APP_PRIVATE_KEY を参照するが、App の作成・インストール・登録を
記録した文書がリポジトリ内に無い。

## 欠けているのは設計根拠ではなく運用実体

ADR-072 決定 8 は「なぜ App token か」「なぜ PAT ではないか (オーナー PAT は
Repository admin として ADR-067 の ruleset backstop を素通りする)」「どの権限を
付けるか (Workflows は付けない)」「なぜ publish 直前に発行するか (token 寿命
1 時間)」を厚く残している。

記録が無いのは以下:

- App を実際に作成した事実・日付・名称・インストール範囲
- NIGHTLY_APP_ID = variable / NIGHTLY_APP_PRIVATE_KEY = secret という登録先の別
- 既存の Claude GitHub App との区別 (あちらは Workflows を含む広い権限を持つ別物)
- 再構築手順 (鍵ローテーション・派生プロジェクト展開)

NIGHTLY_APP の文字列はリポジトリ全体で workflow の 2 行と ADR 残課題の 1 行にしか
現れない。

## これは ADR-051 違反

ADR-051 (クロスシステム設定 coupling) は内部設定と外部 SaaS 設定が論理結合する場合に
(1) 両設定ファイルへの相互参照コメント (2) 期待値の組み合わせ表の ADR 必須記載
(3) 変更は両側を同一 PR、の 3 点を規律として定めている。workflow ↔ GitHub App +
repository variables/secrets はこの型で、3 点とも未実施。

前例として ADR-067 段 0 は repository ruleset を ruleset 名つきで「設定済み」と
記録している。ADR-072 は同じ扱いをしていない。同型の欠落が AUTONOMY_ENABLED にも
あり (ADR-066 は「Actions variable を使う」とは書くが現状値を記録していない)、
本エントリで一緒に扱う。

## 順位 374 と同時実施にする理由

スモークでは AUTONOMY_ENABLED の設定と App token の実動確認のため GitHub UI を
触るので、その過程で実値がすべて揃う。先行して記録しようとすると値が確定せず
二度手間になる。

## 教訓を残す

App の作成手順・Expire user authorization tokens の扱い・既存 App との違いは
2026-08-07 のセッションでユーザーへ提示したが、リポジトリへ残さなかった。会話は
次のセッションに残らないが workflow は残る。参照だけが残って由来が消える状態を
作った。順位 375 と同じクラスの失敗としてエントリ本文に記録した。

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(review): CodeRabbit Major 4 件を妥当性判定のうえ 3 件へ対応する (#364)

自動 fix 経路 (f698d19) が 1 件 (#4 台帳への非記録ルール追加) を対応済みで、
本コミットはその内容を含んだうえで残りを手で対応した結果である。同一ファイルの
近接行のため path 単位で分離できず 1 コミットに畳み込まれている。

## 妥当性の判定

severity ラベルではなく、プロジェクトの設計方針に照らして 1 件ずつ判定した。

- #1 adr-072:335 (秘密値を ADR に記録しない) — **妥当**。「実走スモークで実値を
  確認し ADR へ追記する」は秘密鍵本文まで書くと読める。ADR-051 が記録を課すのは
  結合の存在と期待値の組み合わせであって秘密の実値ではない。設定メタデータに
  限定し、鍵本文と token は ADR にも git 履歴にも残さないことを明記した
- #2 harness-improvement-plan:223 (受け入れ基準が成功経路だけ) — **妥当**。本
  プロジェクトは背圧 12 シナリオ・kill-switch 8 シナリオと停止側を drill で
  固めてきたが、夜間ループの停止側は実走未観測。WP-17 の残課題 (明示的 false と
  config 側 deny が実走未観測) と同じ穴。AUTONOMY_ENABLED の 3 状態を受け入れ
  基準へ追加した。指摘本文が名指しした 2 箇所 (計画書 L223 / todo20 L293-304) の
  両方に反映している
- #3 todo20:480 (prompt injection の回帰 fixture) — **妥当**。順位 382 の payload
  例 "; echo PWNED; #" は shell injection であって prompt injection ではない。
  台帳テキストが流れ込む先は shell ではなく LLM プロンプトなので、テストが目的と
  噛み合っていなかった。自然言語 adversarial payload (本命) と shell/パース形式
  payload (堅牢性) の 2 系統へ分離した

## 自動 fix の実測確認

f698d19 は 1 ファイル 2 行追加のみで範囲外の編集ゼロ。内容も妥当だったため
そのまま採用した (ADR-068 の後退検知の趣旨に沿って diff を実測で確認済み)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant