From 524fa27da0dae98bb71d207e1282478b01bf2770 Mon Sep 17 00:00:00 2001 From: aloekun Date: Wed, 15 Apr 2026 22:00:57 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat(cli-pr-monitor):=20Phase=202=20?= =?UTF-8?q?=E2=80=94=20fix=20loop=20+=20=E3=83=8F=E3=82=A4=E3=83=96?= =?UTF-8?q?=E3=83=AA=E3=83=83=E3=83=89=20re-push?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit takt ワークフローに fix + supervise ステップを追加: - analyze: プロジェクト適合性フィルタ + 3分岐 (approved/needs_fix/user_decision) - fix: Critical/Major の自動修正 (fix.md を pre-push-review と共有) - supervise: エスカレーション (supervise.md を共有) - loop_monitor: fix -> analyze の最大2回ループ ハイブリッド re-push: - Critical 修正あり → 自動 re-push (jj describe + jj new + push) - Major 以下のみ → ユーザー確認待ち pr-monitor-config.toml に [fix] セクション追加: - auto_push_severity: 自動 push の深刻度閾値 - push_command: push コマンド (jj git push / git push) --- .../facets/instructions/analyze-coderabbit.md | 72 ++++++--- .takt/workflows/post-pr-review.yaml | 129 +++++++++++++++- docs/todo.md | 5 +- pr-monitor-config.toml | 6 + src/cli-pr-monitor/src/config.rs | 50 ++++++ src/cli-pr-monitor/src/stages/mod.rs | 1 + src/cli-pr-monitor/src/stages/monitor.rs | 143 +++++++++++++++--- src/cli-pr-monitor/src/stages/push.rs | 52 +++++++ 8 files changed, 403 insertions(+), 55 deletions(-) create mode 100644 src/cli-pr-monitor/src/stages/push.rs diff --git a/.takt/facets/instructions/analyze-coderabbit.md b/.takt/facets/instructions/analyze-coderabbit.md index 2f32eed5..2b22ec0e 100644 --- a/.takt/facets/instructions/analyze-coderabbit.md +++ b/.takt/facets/instructions/analyze-coderabbit.md @@ -1,4 +1,4 @@ -# CodeRabbit Review Analysis +# CodeRabbit Review Analysis (with Project Fitness Filter) ## Input @@ -11,52 +11,76 @@ Read `.takt/review-comments.json`. This file contains the output from `check-ci- ## Task -1. Read the JSON file with the Read tool +### Step 1: Read and parse +1. Read `.takt/review-comments.json` with the Read tool 2. Parse the `findings` array and `coderabbit` state -3. Classify each finding by severity: Critical > High > Major > Medium > Minor > Low > Info -4. Group findings by file path -5. For each Critical/High/Major finding, provide: - - Root cause analysis (why this is a problem) - - Recommended fix approach - - Impact if not addressed -## Output Format +### Step 2: Project fitness filter +CodeRabbit sometimes raises findings that are not applicable to this project. Before classifying severity, evaluate each finding against the project context: + +1. Read `CLAUDE.md` to understand the project's architecture decisions and constraints +2. For each finding, check: + - **Platform scope**: This project targets Windows only. Findings about cross-platform compatibility (e.g., `.exe` hardcoding) are NOT applicable -- downgrade to `Info` + - **Intentional design**: Check if the finding contradicts an ADR decision. If so, mark as `not_applicable` + - **Scope mismatch**: If the finding targets a read-only zone (`.takt/`, `docs/adr/`, `templates/`), mark as `not_applicable` + - **False positive**: If the finding misunderstands the code logic, mark as `not_applicable` + +Mark each finding as: +- `applicable` -- genuine issue that should be addressed +- `not_applicable` -- does not apply to this project (with reason) + +### Step 3: Severity classification +For `applicable` findings only, classify by severity: +- Critical > High > Major > Medium > Minor > Low > Info + +### Step 4: Produce report and verdict -Produce a structured Markdown report: +## Output Format ```markdown ## CodeRabbit Analysis Report ### Summary - CI: [status] -- CodeRabbit: [N] findings ([X] critical/high, [Y] medium, [Z] low) -- Verdict: PASS / FAIL +- CodeRabbit: [N] findings total, [M] applicable after filter +- Verdict: PASS / NEEDS_FIX / USER_DECISION + +### Filtered Findings (not applicable) +| # | File (Line) | Issue | Filter Reason | +|---|-------------|-------|---------------| +| 1 | path:line | ... | Platform scope: Windows only | -### Findings by Severity +### Applicable Findings by Severity -#### Critical / High +#### Critical / Major | # | File (Line) | Issue | Recommended Action | |---|-------------|-------|--------------------| | 1 | path:line | ... | ... | -#### Medium -... - -#### Low / Info -... +#### Medium / Minor +| # | File (Line) | Issue | Recommended Action | +|---|-------------|-------|--------------------| ### Recommended Actions -1. [Prioritized action items for critical/high findings] +1. [Prioritized action items for critical/major findings] ``` -## Verdict Rules +## Verdict Rules (3-way) -- **FAIL**: Any Critical or High or Major severity finding exists -- **PASS**: Only Medium or lower severity findings (or no findings) +- **approved**: No applicable findings, OR all applicable findings are Info/Low severity + - Output: `approved` condition +- **needs_fix**: Any applicable Critical or Major finding exists + - Output: `needs_fix` condition + - These will be automatically fixed in the next step +- **user_decision**: Only Medium or lower applicable findings exist (no Critical/Major) + - Output: `user_decision` condition + - These are reported but NOT auto-fixed; the user decides ## Important - Do NOT modify any code. This is analysis only. - Do NOT fabricate findings. Report only what is in the JSON. -- If the findings array is empty, report "No actionable findings" with verdict PASS. +- Do NOT skip the fitness filter. Every finding must be evaluated for project applicability. +- If the findings array is empty, report "No actionable findings" with verdict `approved`. - If the JSON file is missing or empty, report the error and exit. +- When this is a re-analysis after a fix iteration, compare with previous reports to check for regression or persistence. diff --git a/.takt/workflows/post-pr-review.yaml b/.takt/workflows/post-pr-review.yaml index 0fdbde1b..75fc733f 100644 --- a/.takt/workflows/post-pr-review.yaml +++ b/.takt/workflows/post-pr-review.yaml @@ -1,8 +1,8 @@ name: post-pr-review description: > - CodeRabbit レビューコメントを分析し、深刻度別レポートを出力する。 + CodeRabbit レビューコメントを分析・フィルタし、Critical/Major を自動修正する。 入力: .takt/review-comments.json (Rust exe が書き出し) - Phase 1: 分析のみ。Phase 2 で fix loop を追加予定。 + フロー: analyze (適合性フィルタ + 深刻度分類) -> fix (Critical/Major) -> supervise workflow_config: provider_options: @@ -11,14 +11,30 @@ workflow_config: opencode: network_access: true -max_steps: 5 +max_steps: 30 initial_step: analyze +loop_monitors: + - cycle: + - analyze + - fix + threshold: 2 + judge: + persona: supervisor + instruction: loop-monitor-reviewers-fix + rules: + - condition: Healthy (progress being made) + next: analyze + - condition: Unproductive (repeated findings, fixes not applied) + next: supervise + steps: # --------------------------------------------------------------------------- # Step 1: analyze - # CodeRabbit の指摘を深刻度別に分類し、構造化レポートを出力する。 - # Phase 2 で fix / re-review ステップを追加予定。 + # CodeRabbit 指摘をプロジェクト適合性でフィルタし、深刻度を再分類する。 + # approved: 指摘なし or 全て不適合 -> COMPLETE + # needs_fix: Critical/Major あり -> fix + # user_decision: Medium 以下のみ -> COMPLETE (レポート出力のみ) # --------------------------------------------------------------------------- - name: analyze edit: false @@ -38,5 +54,106 @@ steps: - name: coderabbit-analysis.md format: coderabbit-analysis rules: - - condition: Analysis complete + - condition: approved + next: COMPLETE + - condition: needs_fix + next: fix + - condition: user_decision next: COMPLETE + + # --------------------------------------------------------------------------- + # Step 2: fix + # Critical/Major の自動修正。fix.md (pre-push-review と共有) をベースに、 + # CodeRabbit 指摘に特化した入力ソース指示を追加。 + # loop_monitor で最大 2 回の fix -> analyze ループを許可。 + # --------------------------------------------------------------------------- + - name: fix + edit: true + persona: coder + model: sonnet + policy: + - coding + - testing + knowledge: + - architecture + - security + provider_options: + claude: + allowed_tools: + - Read + - Glob + - Grep + - Edit + - Write + - Bash + - WebSearch + - WebFetch + required_permission_mode: edit + pass_previous_response: false + instruction: fix + rules: + - condition: Fixes complete + next: analyze + - condition: Unable to proceed with fixes + next: supervise + + # --------------------------------------------------------------------------- + # Step 3: supervise + # エスカレーション。fix.md / supervise.md は pre-push-review と共有。 + # --------------------------------------------------------------------------- + - name: supervise + edit: false + persona: supervisor + policy: review + provider_options: + claude: + allowed_tools: + - Read + - Glob + - Grep + - WebSearch + - WebFetch + instruction: supervise + pass_previous_response: false + output_contracts: + report: + - name: supervisor-validation.md + format: supervisor-validation + - name: summary.md + format: summary + use_judge: false + rules: + - condition: All validations complete, ready to push + next: COMPLETE + - condition: Issues detected + next: fix_supervisor + + - name: fix_supervisor + edit: true + persona: coder + model: sonnet + policy: + - coding + - testing + knowledge: + - architecture + - security + provider_options: + claude: + allowed_tools: + - Read + - Glob + - Grep + - Edit + - Write + - Bash + - WebSearch + - WebFetch + required_permission_mode: edit + instruction: fix-supervisor + pass_previous_response: false + rules: + - condition: Fixes for supervisor findings complete + next: supervise + - condition: Unable to proceed with fixes + next: supervise diff --git a/docs/todo.md b/docs/todo.md index 77159a89..adbe2128 100644 --- a/docs/todo.md +++ b/docs/todo.md @@ -20,9 +20,10 @@ - [x] **templates/ に push-runner-config.toml 追加**: 派生プロジェクトへの deploy:hooks で push-runner-config.toml が配布されない問題。テンプレート追加 + deploy-hooks.ts 更新 - [x] **pre-push-review スキルの役割整理**: takt 導入済みプロジェクトでは不要に。takt 未導入の派生プロジェクト向けにフォールバックとして維持 -## 次ステップ: cli-pr-monitor の takt 化 +## cli-pr-monitor の takt 化 -- [ ] **cli-pr-monitor を takt ベースに段階的移行**: daemon ポーリング完了後に takt ワークフローで CodeRabbit 指摘の自動分析を実行。Phase 2 では fix loop による自動修正 + re-push まで一気通貫で処理する構想。push-runner と同様に「機械的ポーリングは Rust、AI 分析は takt」の分離原則を適用する (ADR-015 次ステップ参照) +- [x] **Phase 1: in-process ポーリング + takt 分析** (PR #38, #39, #40): daemon spawn + CronCreate を廃止し、in-process sequential chain (poll -> collect -> takt analyze -> report) に移行。ADR-018 で決定を記録 +- [x] **Phase 2: fix loop + ハイブリッド re-push** (PR #41): takt ワークフローに fix + supervise ステップを追加。CodeRabbit 指摘のプロジェクト適合性フィルタ、Critical/Major の自動修正、深刻度別の re-push 制御 (Critical=自動, Major以下=ユーザー確認) を実装。fix.md / supervise.md は pre-push-review と共有 ## プロセス改善 diff --git a/pr-monitor-config.toml b/pr-monitor-config.toml index 63e547cb..4c886753 100644 --- a/pr-monitor-config.toml +++ b/pr-monitor-config.toml @@ -15,3 +15,9 @@ check_coderabbit = true workflow = "post-pr-review" task = "analyze PR review comments" extra_args = ["--pipeline", "--skip-git"] + +[fix] +# Critical 修正は自動 re-push。Major 以下はユーザー確認後 push。 +# "critical" = Critical のみ自動, "major" = Critical+Major 自動, "none" = 全て手動 +auto_push_severity = "critical" +push_command = "jj git push" diff --git a/src/cli-pr-monitor/src/config.rs b/src/cli-pr-monitor/src/config.rs index b888eea9..5a510dcb 100644 --- a/src/cli-pr-monitor/src/config.rs +++ b/src/cli-pr-monitor/src/config.rs @@ -13,6 +13,8 @@ pub(crate) struct Config { #[serde(default)] pub(crate) monitor: MonitorConfig, pub(crate) takt: Option, + #[serde(default)] + pub(crate) fix: FixConfig, } #[derive(Deserialize, Clone)] @@ -64,6 +66,32 @@ pub(crate) struct TaktConfig { pub(crate) extra_args: Option>, } +#[derive(Deserialize, Clone)] +pub(crate) struct FixConfig { + /// Critical 修正は自動 re-push する。Major 以下はユーザー確認。 + #[serde(default = "default_auto_push_severity")] + pub(crate) auto_push_severity: String, + /// push コマンド (jj git push / git push) + #[serde(default = "default_push_command")] + pub(crate) push_command: String, +} + +fn default_auto_push_severity() -> String { + "critical".into() +} +fn default_push_command() -> String { + "jj git push".into() +} + +impl Default for FixConfig { + fn default() -> Self { + Self { + auto_push_severity: default_auto_push_severity(), + push_command: default_push_command(), + } + } +} + fn config_path() -> PathBuf { let filename = "pr-monitor-config.toml"; @@ -193,4 +221,26 @@ task = "t" let takt = config.takt.unwrap(); assert!(takt.extra_args.is_none()); } + + #[test] + fn config_fix_defaults() { + let toml_str = "[monitor]\n"; + let config: Config = toml::from_str(toml_str).unwrap(); + assert_eq!(config.fix.auto_push_severity, "critical"); + assert_eq!(config.fix.push_command, "jj git push"); + } + + #[test] + fn config_fix_custom() { + let toml_str = r#" +[monitor] + +[fix] +auto_push_severity = "major" +push_command = "git push" +"#; + let config: Config = toml::from_str(toml_str).unwrap(); + assert_eq!(config.fix.auto_push_severity, "major"); + assert_eq!(config.fix.push_command, "git push"); + } } diff --git a/src/cli-pr-monitor/src/stages/mod.rs b/src/cli-pr-monitor/src/stages/mod.rs index 5de43797..d576e265 100644 --- a/src/cli-pr-monitor/src/stages/mod.rs +++ b/src/cli-pr-monitor/src/stages/mod.rs @@ -3,6 +3,7 @@ mod create_pr; mod mark_notified; mod monitor; pub(crate) mod poll; +pub(crate) mod push; pub(crate) mod takt; pub(crate) use create_pr::run_create_pr; diff --git a/src/cli-pr-monitor/src/stages/monitor.rs b/src/cli-pr-monitor/src/stages/monitor.rs index 47b22002..3847d551 100644 --- a/src/cli-pr-monitor/src/stages/monitor.rs +++ b/src/cli-pr-monitor/src/stages/monitor.rs @@ -2,6 +2,7 @@ use crate::config::load_config; use crate::log::log_info; use crate::stages::collect::collect_findings; use crate::stages::poll::run_poll_loop; +use crate::stages::push::run_push; use crate::stages::takt::run_takt; use crate::util::{get_pr_info, utc_now_iso8601, PrInfo}; @@ -39,25 +40,87 @@ pub(crate) fn start_monitoring(pr_info: &PrInfo) -> i32 { .map(|c| c.new_comments > 0 || c.unresolved_threads.unwrap_or(0) > 0) .unwrap_or(false); + let mut takt_succeeded = false; + if has_coderabbit_findings { if !collect_findings(&poll_result) { log_info("review-comments.json 書き出し失敗 (takt 分析をスキップ)"); } else if let Some(takt_config) = &config.takt { - // Stage 3: takt analysis - if !run_takt(takt_config) { - log_info("takt 分析失敗 (非致命的: ポーリング結果はそのまま報告)"); + // Stage 3: takt analysis + fix loop + takt_succeeded = run_takt(takt_config); + if !takt_succeeded { + log_info("takt ワークフロー失敗 (非致命的: ポーリング結果はそのまま報告)"); } } else { log_info("takt 設定なし: AI 分析をスキップ"); } } - // Stage 4: report to stdout + // Stage 4: re-push (ハイブリッド) + // takt が fix を実行した場合、変更の有無を確認して re-push を判断 + if takt_succeeded && has_coderabbit_findings { + handle_repush(&config.fix, &poll_result, &pr_label); + } + + // Stage 5: report to stdout print_report(&poll_result, &pr_label); 0 } +// ─── re-push ハンドラ ─── + +fn handle_repush( + fix_config: &crate::config::FixConfig, + poll_result: &crate::stages::poll::PollResult, + pr_label: &str, +) { + // jj diff で実際のコード変更があるか確認 + let (ok, diff_output) = crate::runner::run_cmd_direct("jj", &["diff", "--stat"], &[], 30); + if !ok || diff_output.trim().is_empty() { + log_info("takt fix 後の変更なし: re-push スキップ"); + return; + } + + log_info(&format!( + "takt fix による変更を検出:\n{}", + diff_output.trim() + )); + + // 深刻度に基づく自動 push 判定 + let has_critical = poll_result + .findings + .iter() + .any(|f| f.severity.to_lowercase() == "critical"); + let has_major = poll_result + .findings + .iter() + .any(|f| f.severity.to_lowercase() == "major"); + + let auto_push = match fix_config.auto_push_severity.as_str() { + "critical" => has_critical, + "major" => has_critical || has_major, + "none" => false, + _ => has_critical, // fallback + }; + + if auto_push { + let severity_label = if has_critical { "Critical" } else { "Major" }; + log_info(&format!( + "{} の {} 修正を自動 re-push します", + pr_label, severity_label + )); + if run_push(fix_config) { + log_info("自動 re-push 完了"); + } else { + log_info("自動 re-push 失敗 (手動対応が必要です)"); + } + } else { + log_info("修正内容はコミット済みですが、re-push はユーザー確認待ちです"); + log_info("確認後に pnpm push を実行してください"); + } +} + // ─── 監視のみモード ─── pub(crate) fn run_monitor_only() -> i32 { @@ -89,34 +152,68 @@ fn print_report(result: &crate::stages::poll::PollResult, pr_label: &str) { .map(|c| c.overall.as_str()) .unwrap_or("unknown"); - let cr_status = result + let cr_comments = result .coderabbit .as_ref() - .map(|c| { - format!( - "新規コメント{}件, 未解決スレッド{}件", - c.new_comments, - c.unresolved_threads.unwrap_or(0) - ) - }) - .unwrap_or_else(|| "unknown".into()); + .map(|c| c.new_comments) + .unwrap_or(0); + let cr_threads = result + .coderabbit + .as_ref() + .and_then(|c| c.unresolved_threads) + .unwrap_or(0); let findings_count = result.findings.len(); - + let critical_major = result + .findings + .iter() + .filter(|f| { + let s = f.severity.to_lowercase(); + s == "critical" || s == "high" || s == "major" + }) + .count(); + + // 判定 + let verdict = if critical_major > 0 { + "修正が必要な指摘があります" + } else if findings_count > 0 { + "重大な問題は見つかりませんでした。軽微な改善提案があります" + } else { + "問題は見つかりませんでした" + }; + + // 統合レポート形式 (post-pr-create-review-check スキルと同一フォーマット) + println!(); + println!("## Review Report ({})", pr_label); + println!(); + println!( + "CI: {} | CodeRabbit: 新規コメント{}件, 未解決スレッド{}件", + ci_status, cr_comments, cr_threads + ); + println!("action: {} | summary: {}", result.action, result.summary); println!(); - println!("=== {} 監視完了 ===", pr_label); - println!("CI: {}", ci_status); - println!("CodeRabbit: {}", cr_status); - println!("action: {}", result.action); - println!("summary: {}", result.summary); + println!("**判定**: {}", verdict); if findings_count > 0 { println!(); - println!("--- findings ({} 件) ---", findings_count); - for f in &result.findings { + println!("| # | Source | Severity | File (Line) | Issue | Suggestion |"); + println!("|---|--------|----------|-------------|-------|------------|"); + for (i, f) in result.findings.iter().enumerate() { + // suggestion を 80 文字で切り詰め + let suggestion = if f.suggestion.len() > 80 { + format!("{}...", &f.suggestion[..77]) + } else { + f.suggestion.clone() + }; println!( - " [{}] {}:{} - {} ({})", - f.severity, f.file, f.line, f.issue, f.source + "| {} | {} | {} | {} ({}) | {} | {} |", + i + 1, + f.source, + f.severity, + f.file, + f.line, + f.issue, + suggestion ); } } diff --git a/src/cli-pr-monitor/src/stages/push.rs b/src/cli-pr-monitor/src/stages/push.rs new file mode 100644 index 00000000..f99d701a --- /dev/null +++ b/src/cli-pr-monitor/src/stages/push.rs @@ -0,0 +1,52 @@ +use crate::config::FixConfig; +use crate::log::log_info; +use crate::runner::run_cmd_direct; + +const DEFAULT_PUSH_TIMEOUT_SECS: u64 = 300; + +/// fix 後の re-push を実行する +/// +/// 1. jj describe で fix コミットにメッセージを付与 +/// 2. jj new で新しい working copy を作成 +/// 3. push_command (jj git push / git push) を実行 +pub(crate) fn run_push(config: &FixConfig) -> bool { + // Step 1: jj describe (現在の変更にメッセージを付与) + log_info("fix コミットを記録中..."); + let (ok, output) = run_cmd_direct( + "jj", + &[ + "describe", + "-m", + "fix(cli-pr-monitor): CodeRabbit 指摘を自動修正", + ], + &[], + 60, + ); + if !ok { + log_info(&format!("jj describe 失敗: {}", output)); + return false; + } + + // Step 2: jj new (新しい working copy) + let (ok, output) = run_cmd_direct("jj", &["new"], &[], 30); + if !ok { + log_info(&format!("jj new 失敗: {}", output)); + return false; + } + + // Step 3: push + log_info(&format!("re-push 実行: {}", config.push_command)); + let parts: Vec<&str> = config.push_command.split_whitespace().collect(); + if parts.is_empty() { + log_info("push_command が空です"); + return false; + } + + let (ok, output) = run_cmd_direct(parts[0], &parts[1..], &[], DEFAULT_PUSH_TIMEOUT_SECS); + if ok { + log_info("re-push 成功"); + } else { + log_info(&format!("re-push 失敗: {}", output)); + } + ok +} From ccebb2784cafa70ec8f65aba54887358aa9759e8 Mon Sep 17 00:00:00 2001 From: aloekun Date: Thu, 16 Apr 2026 04:02:30 +0900 Subject: [PATCH 2/3] =?UTF-8?q?fix(cli-pr-monitor):=20CodeRabbit=20?= =?UTF-8?q?=E6=8C=87=E6=91=98=E3=82=92=E8=87=AA=E5=8B=95=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../facets/instructions/analyze-coderabbit.md | 2 +- src/cli-pr-monitor/src/config.rs | 2 +- src/cli-pr-monitor/src/stages/monitor.rs | 94 +++++++++++++------ 3 files changed, 66 insertions(+), 32 deletions(-) diff --git a/.takt/facets/instructions/analyze-coderabbit.md b/.takt/facets/instructions/analyze-coderabbit.md index 2b22ec0e..256b3ed6 100644 --- a/.takt/facets/instructions/analyze-coderabbit.md +++ b/.takt/facets/instructions/analyze-coderabbit.md @@ -43,7 +43,7 @@ For `applicable` findings only, classify by severity: ### Summary - CI: [status] - CodeRabbit: [N] findings total, [M] applicable after filter -- Verdict: PASS / NEEDS_FIX / USER_DECISION +- Verdict: approved / needs_fix / user_decision ### Filtered Findings (not applicable) | # | File (Line) | Issue | Filter Reason | diff --git a/src/cli-pr-monitor/src/config.rs b/src/cli-pr-monitor/src/config.rs index 5a510dcb..e157ec8c 100644 --- a/src/cli-pr-monitor/src/config.rs +++ b/src/cli-pr-monitor/src/config.rs @@ -68,7 +68,7 @@ pub(crate) struct TaktConfig { #[derive(Deserialize, Clone)] pub(crate) struct FixConfig { - /// Critical 修正は自動 re-push する。Major 以下はユーザー確認。 + /// "critical" / "major" は自動 re-push。"none" および未知値はユーザー確認。 #[serde(default = "default_auto_push_severity")] pub(crate) auto_push_severity: String, /// push コマンド (jj git push / git push) diff --git a/src/cli-pr-monitor/src/stages/monitor.rs b/src/cli-pr-monitor/src/stages/monitor.rs index 3847d551..d9f8b765 100644 --- a/src/cli-pr-monitor/src/stages/monitor.rs +++ b/src/cli-pr-monitor/src/stages/monitor.rs @@ -1,5 +1,5 @@ use crate::config::load_config; -use crate::log::log_info; +use crate::log::{log_info, truncate_safe}; use crate::stages::collect::collect_findings; use crate::stages::poll::run_poll_loop; use crate::stages::push::run_push; @@ -59,7 +59,7 @@ pub(crate) fn start_monitoring(pr_info: &PrInfo) -> i32 { // Stage 4: re-push (ハイブリッド) // takt が fix を実行した場合、変更の有無を確認して re-push を判断 if takt_succeeded && has_coderabbit_findings { - handle_repush(&config.fix, &poll_result, &pr_label); + handle_repush(&config.fix, &pr_label); } // Stage 5: report to stdout @@ -70,14 +70,33 @@ pub(crate) fn start_monitoring(pr_info: &PrInfo) -> i32 { // ─── re-push ハンドラ ─── -fn handle_repush( - fix_config: &crate::config::FixConfig, - poll_result: &crate::stages::poll::PollResult, - pr_label: &str, -) { +/// auto_push_severity 設定値から自動 push するか否かを返す。 +/// "critical" / "major" => true、"none" => false、未知値 => false (警告ログあり) +pub(crate) fn should_auto_push(setting: &str) -> bool { + match setting { + "none" => false, + "critical" | "major" => true, + other => { + log_info(&format!( + "auto_push_severity に未知の値 '{}' が指定されています。'none' として扱い自動 push をスキップします", + other + )); + false + } + } +} + +fn handle_repush(fix_config: &crate::config::FixConfig, pr_label: &str) { // jj diff で実際のコード変更があるか確認 let (ok, diff_output) = crate::runner::run_cmd_direct("jj", &["diff", "--stat"], &[], 30); - if !ok || diff_output.trim().is_empty() { + if !ok { + log_info(&format!( + "jj diff --stat 実行失敗: re-push を中断します\n{}", + diff_output.trim() + )); + return; + } + if diff_output.trim().is_empty() { log_info("takt fix 後の変更なし: re-push スキップ"); return; } @@ -87,28 +106,15 @@ fn handle_repush( diff_output.trim() )); - // 深刻度に基づく自動 push 判定 - let has_critical = poll_result - .findings - .iter() - .any(|f| f.severity.to_lowercase() == "critical"); - let has_major = poll_result - .findings - .iter() - .any(|f| f.severity.to_lowercase() == "major"); - - let auto_push = match fix_config.auto_push_severity.as_str() { - "critical" => has_critical, - "major" => has_critical || has_major, - "none" => false, - _ => has_critical, // fallback - }; + // 自動 push 判定: takt が fix を実行し jj diff に変更がある場合のみ re-push 対象 + // NOTE: takt の fitness filter が not_applicable な指摘を除外済みのため、 + // ここでは生 findings ではなく auto_push_severity 設定のみで判断する + let auto_push = should_auto_push(fix_config.auto_push_severity.as_str()); if auto_push { - let severity_label = if has_critical { "Critical" } else { "Major" }; log_info(&format!( - "{} の {} 修正を自動 re-push します", - pr_label, severity_label + "{} の takt fix 修正を自動 re-push します", + pr_label )); if run_push(fix_config) { log_info("自動 re-push 完了"); @@ -143,6 +149,34 @@ pub(crate) fn run_monitor_only() -> i32 { start_monitoring(&pr_info) } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_auto_push_none_returns_false() { + assert!(!should_auto_push("none")); + } + + #[test] + fn should_auto_push_critical_returns_true() { + assert!(should_auto_push("critical")); + } + + #[test] + fn should_auto_push_major_returns_true() { + assert!(should_auto_push("major")); + } + + #[test] + fn should_auto_push_unknown_value_returns_false() { + // タイポや未知値は fail-closed: 自動 push しない + assert!(!should_auto_push("non")); + assert!(!should_auto_push("Critical")); + assert!(!should_auto_push("")); + } +} + // ─── レポート出力 ─── fn print_report(result: &crate::stages::poll::PollResult, pr_label: &str) { @@ -199,9 +233,9 @@ fn print_report(result: &crate::stages::poll::PollResult, pr_label: &str) { println!("| # | Source | Severity | File (Line) | Issue | Suggestion |"); println!("|---|--------|----------|-------------|-------|------------|"); for (i, f) in result.findings.iter().enumerate() { - // suggestion を 80 文字で切り詰め - let suggestion = if f.suggestion.len() > 80 { - format!("{}...", &f.suggestion[..77]) + // suggestion を 80 文字 (char 単位) で切り詰め (UTF-8 安全) + let suggestion = if f.suggestion.chars().count() > 80 { + format!("{}...", truncate_safe(&f.suggestion, 77)) } else { f.suggestion.clone() }; From f4bb70bfef19dca723de98d9a991d880369e0723 Mon Sep 17 00:00:00 2001 From: aloekun Date: Thu, 16 Apr 2026 12:18:30 +0900 Subject: [PATCH 3/3] =?UTF-8?q?fix(cli-pr-monitor):=20CodeRabbit=20?= =?UTF-8?q?=E6=8C=87=E6=91=98=E3=82=92=E8=87=AA=E5=8B=95=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .takt/facets/instructions/analyze-coderabbit.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.takt/facets/instructions/analyze-coderabbit.md b/.takt/facets/instructions/analyze-coderabbit.md index 256b3ed6..66ff6bba 100644 --- a/.takt/facets/instructions/analyze-coderabbit.md +++ b/.takt/facets/instructions/analyze-coderabbit.md @@ -52,7 +52,7 @@ For `applicable` findings only, classify by severity: ### Applicable Findings by Severity -#### Critical / Major +#### Critical / High / Major | # | File (Line) | Issue | Recommended Action | |---|-------------|-------|--------------------| | 1 | path:line | ... | ... | @@ -69,10 +69,10 @@ For `applicable` findings only, classify by severity: - **approved**: No applicable findings, OR all applicable findings are Info/Low severity - Output: `approved` condition -- **needs_fix**: Any applicable Critical or Major finding exists +- **needs_fix**: Any applicable Critical, High, or Major finding exists - Output: `needs_fix` condition - These will be automatically fixed in the next step -- **user_decision**: Only Medium or lower applicable findings exist (no Critical/Major) +- **user_decision**: Only Medium or lower applicable findings exist (no Critical/High/Major) - Output: `user_decision` condition - These are reported but NOT auto-fixed; the user decides