ビルトインワークフロー: review-only(レビュー専用) #60 resolved

This commit is contained in:
nrslib 2026-01-31 20:03:38 +09:00
parent 1e8909d512
commit 7d856287f0
6 changed files with 1077 additions and 0 deletions

View File

@ -284,6 +284,20 @@ Files: `.takt/logs/{sessionId}.jsonl`, with `latest.json` pointer. Legacy `.json
**Instruction auto-injection over explicit placeholders.** The instruction builder auto-injects `{task}`, `{previous_response}`, `{user_inputs}`, and status rules. Templates should contain only step-specific instructions, not boilerplate. **Instruction auto-injection over explicit placeholders.** The instruction builder auto-injects `{task}`, `{previous_response}`, `{user_inputs}`, and status rules. Templates should contain only step-specific instructions, not boilerplate.
**Agent prompts contain only domain knowledge.** Agent prompt files (`resources/global/{lang}/agents/**/*.md`) must contain only domain expertise and behavioral principles — never workflow-specific procedures. Workflow-specific details (which reports to read, step routing, specific templates with hardcoded step names) belong in the workflow YAML's `instruction_template`. This keeps agents reusable across different workflows.
What belongs in agent prompts:
- Role definition ("You are a ... specialist")
- Domain expertise, review criteria, judgment standards
- Do / Don't behavioral rules
- Tool usage knowledge (general, not workflow-specific)
What belongs in workflow `instruction_template`:
- Step-specific procedures ("Read these specific reports")
- References to other steps or their outputs
- Specific report file names or formats
- Comment/output templates with hardcoded review type names
## Isolated Execution (Shared Clone) ## Isolated Execution (Shared Clone)
When tasks specify `worktree: true` or `worktree: "path"`, code runs in a `git clone --shared` (lightweight clone with independent `.git` directory). Clones are ephemeral: created before task execution, auto-committed + pushed after success, then deleted. When tasks specify `worktree: true` or `worktree: "path"`, code runs in a `git clone --shared` (lightweight clone with independent `.git` directory). Clones are ephemeral: created before task execution, auto-committed + pushed after success, then deleted.

View File

@ -0,0 +1,75 @@
# PR Commenter Agent
You are a **PR comment posting specialist**. You post review findings to GitHub Pull Requests using the `gh` CLI.
## Role
- Post review findings as PR comments
- Format findings clearly and concisely for developers
- Filter findings by severity to reduce noise
**Don't:**
- Review code yourself (reviewers already did that)
- Make any file edits
- Run tests or builds
- Make judgments about code quality (post what reviewers found)
## Core Knowledge
### GitHub PR Comment API
**Inline review comments** (file/line-specific findings):
```bash
gh api repos/{owner}/{repo}/pulls/{pr_number}/comments \
-f body="**[{category}]** {description}" \
-f path="{file_path}" \
-F line={line_number} \
-f commit_id="$(gh pr view {pr_number} --json headRefOid -q .headRefOid)"
```
- Use the HEAD commit of the PR for `commit_id`
- Group multiple findings on the same line into a single comment
**Summary comments** (overall review):
```bash
gh pr comment {pr_number} --body "{markdown_body}"
```
- Use HEREDOC for multi-line bodies to avoid escaping issues
### PR Number Extraction
Extract PR number from task context using common patterns:
- "PR #42", "#42", "pull/42", "pulls/42"
- If no PR number is found, report this and finish without posting
## Comment Quality Principles
### Severity-Based Filtering
| Severity | Action |
|----------|--------|
| Critical / High | Always post as inline comment |
| Medium | Post as inline comment |
| Low | Include in summary only |
| Informational | Include in summary only |
### Formatting
- **Be concise.** PR comments should be actionable and to the point
- **Include location.** Always reference specific files and lines when available
- **Categorize findings.** Use labels like `[Security]`, `[Architecture]`, `[AI Pattern]`
## Error Handling
- If `gh` command fails, report the error but don't retry excessively
- If PR number cannot be determined, output an informational message and complete
- If no findings to post, post only the summary comment
## Important
- **Never modify files.** You only post comments.
- **Respect rate limits.** Don't post too many individual comments; batch when possible.
- **Use the review reports** as the source of truth for findings, not your own analysis.

View File

@ -0,0 +1,331 @@
# Review-Only Workflow
# Reviews code or PRs without making any edits
# Local: console output only. PR specified: posts inline comments + summary to PR
#
# Flow:
# plan -> reviewers (parallel: arch-review + security-review + ai-review) -> supervise
# -> pr-comment -> COMPLETE (PR comment requested)
# -> COMPLETE (local: console output only)
# -> ABORT (rejected)
#
# All steps have edit: false (no file modifications)
#
# Template Variables:
# {iteration} - Workflow-wide turn count
# {max_iterations} - Maximum iterations allowed
# {step_iteration} - Per-step iteration count
# {task} - Original user request
# {previous_response} - Output from the previous step
# {user_inputs} - Accumulated user inputs
# {report_dir} - Report directory name
name: review-only
description: Review-only workflow - reviews code without making edits
max_iterations: 10
initial_step: plan
steps:
- name: plan
edit: false
agent: ../agents/default/planner.md
allowed_tools:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: Review scope is clear
next: reviewers
- condition: User is asking a question (not a review task)
next: COMPLETE
- condition: Requirements unclear, insufficient info
next: ABORT
appendix: |
Clarifications needed:
- {Question 1}
- {Question 2}
pass_previous_response: true
instruction_template: |
## Previous Response (when returned from supervise)
{previous_response}
Analyze the review request and create a review plan.
**This is a review-only workflow.** No code edits will be made.
Focus on:
1. Identify which files/modules to review
2. Determine review focus areas (architecture, security, AI patterns, etc.)
3. Note any specific concerns mentioned in the request
**If a PR number is mentioned** (e.g., "PR #42"), include it in your plan
so reviewers can focus on the PR's changed files.
- name: reviewers
parallel:
- name: arch-review
edit: false
agent: ../agents/default/architecture-reviewer.md
report:
name: 01-architect-review.md
format: |
```markdown
# Architecture Review
## Result: APPROVE / IMPROVE / REJECT
## Summary
{1-2 sentences summarizing result}
## Reviewed Perspectives
- [x] Structure & Design
- [x] Code Quality
- [x] Change Scope
## Issues (if REJECT)
| # | Location | Issue | Fix |
|---|----------|-------|-----|
| 1 | `src/file.ts:42` | Issue description | Fix method |
## Improvement Suggestions (optional, non-blocking)
- {Future improvement suggestions}
```
**Cognitive load reduction rules:**
- APPROVE + no issues -> Summary only (5 lines or less)
- APPROVE + minor suggestions -> Summary + suggestions (15 lines or less)
- REJECT -> Issues in table format (30 lines or less)
allowed_tools:
- Read
- Glob
- Grep
- Write
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction_template: |
Focus on **architecture and design** review. Do NOT review AI-specific issues (that's the ai_review step).
Review the code and provide feedback.
- name: security-review
edit: false
agent: ../agents/default/security-reviewer.md
report:
name: 02-security-review.md
format: |
```markdown
# Security Review
## Result: APPROVE / REJECT
## Severity: None / Low / Medium / High / Critical
## Check Results
| Category | Result | Notes |
|----------|--------|-------|
| Injection | - | - |
| Auth/Authz | - | - |
| Data Protection | - | - |
| Dependencies | - | - |
## Vulnerabilities (if REJECT)
| # | Severity | Type | Location | Fix |
|---|----------|------|----------|-----|
| 1 | High | SQLi | `src/db.ts:42` | Use parameterized query |
## Warnings (non-blocking)
- {Security recommendations}
```
**Cognitive load reduction rules:**
- No issues -> Check table only (10 lines or less)
- Warnings -> + Warnings 1-2 lines (15 lines or less)
- Vulnerabilities -> + Table format (30 lines or less)
allowed_tools:
- Read
- Glob
- Grep
- Write
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction_template: |
Perform security review on the code. Check for vulnerabilities including:
- Injection attacks (SQL, Command, XSS)
- Authentication/Authorization issues
- Data exposure risks
- Cryptographic weaknesses
- name: ai-review
edit: false
agent: ../agents/default/ai-antipattern-reviewer.md
report:
name: 03-ai-review.md
format: |
```markdown
# AI-Generated Code Review
## Result: APPROVE / REJECT
## Summary
{One sentence summarizing result}
## Verified Items
| Aspect | Result | Notes |
|--------|--------|-------|
| Assumption validity | - | - |
| API/Library existence | - | - |
| Context fit | - | - |
| Scope | - | - |
## Issues (if REJECT)
| # | Category | Location | Issue |
|---|----------|----------|-------|
| 1 | Hallucinated API | `src/file.ts:23` | Non-existent method |
```
**Cognitive load reduction rules:**
- No issues -> Summary 1 line + check table only (10 lines or less)
- Issues found -> + Issues in table format (25 lines or less)
allowed_tools:
- Read
- Glob
- Grep
- Write
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction_template: |
Review the code for AI-specific issues:
- Assumption validation
- Plausible but wrong patterns
- Context fit with existing codebase
- Scope creep detection
rules:
- condition: all("approved")
next: supervise
- condition: any("needs_fix")
next: supervise
- name: supervise
edit: false
agent: ../agents/default/supervisor.md
report:
- Review Summary: 04-review-summary.md
allowed_tools:
- Read
- Glob
- Grep
- Write
- WebSearch
- WebFetch
rules:
- condition: approved, PR comment requested
next: pr-comment
- condition: approved
next: COMPLETE
- condition: rejected
next: ABORT
pass_previous_response: true
instruction_template: |
## Review Results
{previous_response}
**This is a review-only workflow.** Do NOT run tests or builds.
Your role is to synthesize the review results and produce a final summary.
**Tasks:**
1. Read all review reports in the Report Directory
2. Synthesize findings from architecture, security, and AI reviews
3. Produce a consolidated review summary with overall verdict
4. Determine routing:
- If the task mentions posting to a PR (e.g., "post comments to PR", "comment on PR"),
route to `pr-comment` step (condition: "approved, PR comment requested")
- If local review only, route to COMPLETE (condition: "approved")
- If critical issues found, route to ABORT (condition: "rejected")
**Review Summary report format:**
```markdown
# Review Summary
## Overall Verdict: APPROVE / REJECT
## Summary
{2-3 sentences consolidating all review results}
## Review Results
| Review | Result | Key Findings |
|--------|--------|--------------|
| Architecture | APPROVE/REJECT | {Brief finding} |
| Security | APPROVE/REJECT | {Brief finding} |
| AI Antipattern | APPROVE/REJECT | {Brief finding} |
## Issues Requiring Attention
| # | Severity | Source | Location | Issue |
|---|----------|--------|----------|-------|
| 1 | High | Security | `file:line` | Description |
## Improvement Suggestions
- {Consolidated suggestions from all reviews}
```
- name: pr-comment
edit: false
agent: ../agents/review/pr-commenter.md
allowed_tools:
- Read
- Glob
- Grep
- Bash
rules:
- condition: Comments posted
next: COMPLETE
- condition: Failed to post comments
next: COMPLETE
pass_previous_response: true
instruction_template: |
## Review Summary
{previous_response}
Post the review results to the PR as comments.
**Procedure:**
1. Extract the PR number from the task description
2. Read all review reports in the Report Directory:
- `01-architect-review.md` (Architecture review)
- `02-security-review.md` (Security review)
- `03-ai-review.md` (AI antipattern review)
- `04-review-summary.md` (Consolidated summary)
3. Filter findings by severity and post inline comments for Critical/High/Medium
4. Post a summary comment with the following format:
```
## Automated Review Summary
{Overall verdict and summary from 04-review-summary.md}
### Review Results
| Review | Result |
|--------|--------|
| Architecture | {result} |
| Security | {result} |
| AI Antipattern | {result} |
### Key Findings
{Bulleted list of important findings}
### Improvement Suggestions
{Consolidated suggestions}
---
*Generated by [takt](https://github.com/toruticas/takt) review-only workflow*
```

View File

@ -0,0 +1,75 @@
# PR Commenter Agent
あなたは**PRコメント投稿の専門家**です。`gh` CLIを使用してレビューの指摘をGitHub Pull Requestに投稿します。
## 役割
- レビューの指摘をPRコメントとして投稿
- 開発者向けに指摘を明確かつ簡潔にフォーマット
- 重要度によるフィルタリングでノイズを削減
**やらないこと:**
- 自分でコードをレビューする(レビュアーが既に実施済み)
- ファイルの編集
- テストやビルドの実行
- コード品質の判断(レビュアーの結果をそのまま投稿する)
## コア知識
### GitHub PR Comment API
**インラインレビューコメント**(ファイル・行番号ごとの指摘):
```bash
gh api repos/{owner}/{repo}/pulls/{pr_number}/comments \
-f body="**[{category}]** {description}" \
-f path="{file_path}" \
-F line={line_number} \
-f commit_id="$(gh pr view {pr_number} --json headRefOid -q .headRefOid)"
```
- `commit_id` にはPRのHEADコミットを使用
- 同一行に複数の指摘がある場合は1つのコメントにまとめる
**サマリーコメント**(全体レビュー):
```bash
gh pr comment {pr_number} --body "{markdown_body}"
```
- 複数行のbodyにはHEREDOCを使用してエスケープ問題を回避
### PR番号の抽出
タスクコンテキストから一般的なパターンでPR番号を抽出:
- "PR #42"、"#42"、"pull/42"、"pulls/42"
- PR番号が見つからない場合は報告して投稿せずに終了
## コメント品質の原則
### 重要度ベースのフィルタリング
| 重大度 | アクション |
|--------|-----------|
| Critical / High | 必ずインラインコメントとして投稿 |
| Medium | インラインコメントとして投稿 |
| Low | サマリーにのみ含める |
| Informational | サマリーにのみ含める |
### フォーマット
- **簡潔に。** PRコメントはアクション可能で要点を押さえたものにする
- **場所を含める。** 可能な限り具体的なファイルと行番号を参照
- **指摘を分類する。** `[Security]``[Architecture]``[AI Pattern]` のようなラベルを使用
## エラーハンドリング
- `gh` コマンドが失敗した場合はエラーを報告するが、過度にリトライしない
- PR番号が特定できない場合は情報メッセージを出力して完了
- 投稿する指摘がない場合はサマリーコメントのみ投稿
## 重要
- **ファイルを変更しない。** コメント投稿のみ行う。
- **レート制限を尊重。** 個別コメントを大量投稿しない。可能な限りまとめる。
- **レビューレポートを情報源とする。** 自分の分析ではなく、レビュアーの結果を投稿する。

View File

@ -0,0 +1,332 @@
# レビュー専用ワークフロー
# コードやPRをレビューするだけで編集は行わない
# ローカル: コンソール出力のみ。PR指定時: PRにインラインコメントサマリを投稿
#
# フロー:
# plan -> reviewers (parallel: arch-review + security-review + ai-review) -> supervise
# -> pr-comment -> COMPLETE (PRコメント要求時)
# -> COMPLETE (ローカル: コンソール出力のみ)
# -> ABORT (rejected)
#
# 全ステップ edit: falseファイル変更なし
#
# テンプレート変数:
# {iteration} - ワークフロー全体のターン数
# {max_iterations} - 最大イテレーション数
# {step_iteration} - ステップごとのイテレーション数
# {task} - 元のユーザー要求
# {previous_response} - 前のステップの出力
# {user_inputs} - 蓄積されたユーザー入力
# {report_dir} - レポートディレクトリ名
name: review-only
description: レビュー専用ワークフロー - コードをレビューするだけで編集は行わない
max_iterations: 10
initial_step: plan
steps:
- name: plan
edit: false
agent: ../agents/default/planner.md
allowed_tools:
- Read
- Glob
- Grep
- WebSearch
- WebFetch
rules:
- condition: レビュー対象が明確
next: reviewers
- condition: ユーザーが質問をしている(レビュータスクではない)
next: COMPLETE
- condition: 要件が不明確、情報不足
next: ABORT
appendix: |
確認事項:
- {質問1}
- {質問2}
pass_previous_response: true
instruction_template: |
## Previous Response (superviseからの差し戻し時)
{previous_response}
レビュー依頼を分析し、レビュー方針を立ててください。
**これはレビュー専用ワークフローです。** コード編集は行いません。
以下に集中してください:
1. レビュー対象のファイル/モジュールを特定
2. レビューの重点領域を決定アーキテクチャ、セキュリティ、AIパターン等
3. 依頼に記載された特定の懸念事項を整理
**PR番号が記載されている場合**(例: "PR #42")、レビュアーが
PRの変更ファイルに集中できるよう計画に含めてください。
- name: reviewers
parallel:
- name: arch-review
edit: false
agent: ../agents/default/architecture-reviewer.md
report:
name: 01-architect-review.md
format: |
```markdown
# アーキテクチャレビュー
## 結果: APPROVE / IMPROVE / REJECT
## サマリー
{1-2文で結果を要約}
## 確認した観点
- [x] 構造・設計
- [x] コード品質
- [x] 変更スコープ
## 問題点REJECTの場合
| # | 場所 | 問題 | 修正案 |
|---|------|------|--------|
| 1 | `src/file.ts:42` | 問題の説明 | 修正方法 |
## 改善提案(任意・ブロッキングではない)
- {将来的な改善提案}
```
**認知負荷軽減ルール:**
- APPROVE + 問題なし → サマリーのみ5行以内
- APPROVE + 軽微な提案 → サマリー + 改善提案15行以内
- REJECT → 問題点を表形式で30行以内
allowed_tools:
- Read
- Glob
- Grep
- Write
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction_template: |
**アーキテクチャと設計**のレビューに集中してください。AI特有の問題はレビューしないでくださいai_reviewステップで行います
コードをレビューしてフィードバックを提供してください。
- name: security-review
edit: false
agent: ../agents/default/security-reviewer.md
report:
name: 02-security-review.md
format: |
```markdown
# セキュリティレビュー
## 結果: APPROVE / REJECT
## 重大度: None / Low / Medium / High / Critical
## チェック結果
| カテゴリ | 結果 | 備考 |
|---------|------|------|
| インジェクション | - | - |
| 認証・認可 | - | - |
| データ保護 | - | - |
| 依存関係 | - | - |
## 脆弱性REJECTの場合
| # | 重大度 | 種類 | 場所 | 修正案 |
|---|--------|------|------|--------|
| 1 | High | SQLi | `src/db.ts:42` | パラメータ化クエリを使用 |
## 警告(ブロッキングではない)
- {セキュリティに関する推奨事項}
```
**認知負荷軽減ルール:**
- 問題なし → チェック表のみ10行以内
- 警告あり → + 警告を1-2行15行以内
- 脆弱性あり → + 表形式で30行以内
allowed_tools:
- Read
- Glob
- Grep
- Write
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction_template: |
コードに対してセキュリティレビューを行ってください。以下の脆弱性を確認してください:
- インジェクション攻撃SQL, コマンド, XSS
- 認証・認可の問題
- データ露出リスク
- 暗号化の弱点
- name: ai-review
edit: false
agent: ../agents/default/ai-antipattern-reviewer.md
report:
name: 03-ai-review.md
format: |
```markdown
# AI生成コードレビュー
## 結果: APPROVE / REJECT
## サマリー
{1文で結果を要約}
## 検証した項目
| 観点 | 結果 | 備考 |
|------|------|------|
| 仮定の妥当性 | - | - |
| API/ライブラリの実在 | - | - |
| コンテキスト適合 | - | - |
| スコープ | - | - |
## 問題点REJECTの場合
| # | カテゴリ | 場所 | 問題 |
|---|---------|------|------|
| 1 | 幻覚API | `src/file.ts:23` | 存在しないメソッド |
```
**認知負荷軽減ルール:**
- 問題なし → サマリー1文 + チェック表のみ10行以内
- 問題あり → + 問題を表形式で25行以内
allowed_tools:
- Read
- Glob
- Grep
- Write
- WebSearch
- WebFetch
rules:
- condition: approved
- condition: needs_fix
instruction_template: |
AI特有の問題についてコードをレビューしてください:
- 仮定の検証
- もっともらしいが間違っているパターン
- 既存コードベースとの適合性
- スコープクリープの検出
rules:
- condition: all("approved")
next: supervise
- condition: any("needs_fix")
next: supervise
- name: supervise
edit: false
agent: ../agents/default/supervisor.md
report:
- Review Summary: 04-review-summary.md
allowed_tools:
- Read
- Glob
- Grep
- Write
- WebSearch
- WebFetch
rules:
- condition: approved, PR comment requested
next: pr-comment
- condition: approved
next: COMPLETE
- condition: rejected
next: ABORT
pass_previous_response: true
instruction_template: |
## レビュー結果
{previous_response}
**これはレビュー専用ワークフローです。** テスト実行やビルドは行わないでください。
レビュー結果を統合し、最終サマリーを作成する役割です。
**やること:**
1. Report Directory内の全レビューレポートを読む
2. アーキテクチャ・セキュリティ・AIレビューの結果を統合
3. 統合レビューサマリーと総合判定を作成
4. ルーティング判断:
- タスクにPRへのコメント投稿が含まれている場合
(例: "PRにコメントして"、"PRにレビュー結果を投稿"
→ `pr-comment` ステップへcondition: "approved, PR comment requested"
- ローカルレビューのみ → COMPLETEcondition: "approved"
- 重大な問題が見つかった場合 → ABORTcondition: "rejected"
**Review Summaryレポートフォーマット:**
```markdown
# レビューサマリー
## 総合判定: APPROVE / REJECT
## サマリー
{2-3文で全レビュー結果を統合}
## レビュー結果
| レビュー | 結果 | 主要な発見 |
|---------|------|-----------|
| アーキテクチャ | APPROVE/REJECT | {概要} |
| セキュリティ | APPROVE/REJECT | {概要} |
| AIアンチパターン | APPROVE/REJECT | {概要} |
## 要注意の問題
| # | 重大度 | ソース | 場所 | 問題 |
|---|--------|--------|------|------|
| 1 | High | セキュリティ | `file:line` | 説明 |
## 改善提案
- {全レビューからの統合提案}
```
- name: pr-comment
edit: false
agent: ../agents/review/pr-commenter.md
allowed_tools:
- Read
- Glob
- Grep
- Bash
rules:
- condition: コメント投稿完了
next: COMPLETE
- condition: コメント投稿失敗
next: COMPLETE
pass_previous_response: true
instruction_template: |
## レビューサマリー
{previous_response}
レビュー結果をPRにコメントとして投稿してください。
**手順:**
1. タスク説明からPR番号を抽出
2. Report Directory内の全レビューレポートを読む:
- `01-architect-review.md`(アーキテクチャレビュー)
- `02-security-review.md`(セキュリティレビュー)
- `03-ai-review.md`AIアンチパターンレビュー
- `04-review-summary.md`(統合サマリー)
3. 重要度でフィルタリングし、Critical/High/Mediumの指摘をインラインコメントとして投稿
4. 以下のフォーマットでサマリーコメントを投稿:
```
## 自動レビューサマリー
{04-review-summary.mdからの総合判定とサマリー}
### レビュー結果
| レビュー | 結果 |
|---------|------|
| アーキテクチャ | {結果} |
| セキュリティ | {結果} |
| AIアンチパターン | {結果} |
### 主要な発見
{重要な指摘のリスト}
### 改善提案
{統合された提案}
---
*[takt](https://github.com/toruticas/takt) review-only ワークフローで生成*
```

View File

@ -0,0 +1,250 @@
/**
* Tests for review-only workflow
*
* Covers:
* - Workflow YAML files (EN/JA) load and pass schema validation
* - Workflow structure: plan -> reviewers (parallel) -> supervise -> pr-comment
* - All steps have edit: false
* - pr-commenter agent has Bash in allowed_tools
* - Routing rules for local vs PR comment flows
*/
import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { parse as parseYaml } from 'yaml';
import { WorkflowConfigRawSchema } from '../models/schemas.js';
const RESOURCES_DIR = join(import.meta.dirname, '../../resources/global');
function loadReviewOnlyYaml(lang: 'en' | 'ja') {
const filePath = join(RESOURCES_DIR, lang, 'workflows', 'review-only.yaml');
const content = readFileSync(filePath, 'utf-8');
return parseYaml(content);
}
describe('review-only workflow (EN)', () => {
const raw = loadReviewOnlyYaml('en');
it('should pass schema validation', () => {
const result = WorkflowConfigRawSchema.safeParse(raw);
expect(result.success).toBe(true);
});
it('should have correct name and initial_step', () => {
expect(raw.name).toBe('review-only');
expect(raw.initial_step).toBe('plan');
});
it('should have max_iterations of 10', () => {
expect(raw.max_iterations).toBe(10);
});
it('should have 4 steps: plan, reviewers, supervise, pr-comment', () => {
const stepNames = raw.steps.map((s: { name: string }) => s.name);
expect(stepNames).toEqual(['plan', 'reviewers', 'supervise', 'pr-comment']);
});
it('should have all steps with edit: false', () => {
for (const step of raw.steps) {
if (step.edit !== undefined) {
expect(step.edit).toBe(false);
}
if (step.parallel) {
for (const sub of step.parallel) {
if (sub.edit !== undefined) {
expect(sub.edit).toBe(false);
}
}
}
}
});
it('should have reviewers step with 3 parallel sub-steps', () => {
const reviewers = raw.steps.find((s: { name: string }) => s.name === 'reviewers');
expect(reviewers).toBeDefined();
expect(reviewers.parallel).toHaveLength(3);
const subNames = reviewers.parallel.map((s: { name: string }) => s.name);
expect(subNames).toEqual(['arch-review', 'security-review', 'ai-review']);
});
it('should have reviewers step with aggregate rules', () => {
const reviewers = raw.steps.find((s: { name: string }) => s.name === 'reviewers');
expect(reviewers.rules).toHaveLength(2);
expect(reviewers.rules[0].condition).toBe('all("approved")');
expect(reviewers.rules[0].next).toBe('supervise');
expect(reviewers.rules[1].condition).toBe('any("needs_fix")');
expect(reviewers.rules[1].next).toBe('supervise');
});
it('should have supervise step with routing rules for local and PR flows', () => {
const supervise = raw.steps.find((s: { name: string }) => s.name === 'supervise');
expect(supervise.rules).toHaveLength(3);
const conditions = supervise.rules.map((r: { condition: string }) => r.condition);
expect(conditions).toContain('approved, PR comment requested');
expect(conditions).toContain('approved');
expect(conditions).toContain('rejected');
const prRule = supervise.rules.find((r: { condition: string }) => r.condition === 'approved, PR comment requested');
expect(prRule.next).toBe('pr-comment');
const localRule = supervise.rules.find((r: { condition: string }) => r.condition === 'approved');
expect(localRule.next).toBe('COMPLETE');
const rejectRule = supervise.rules.find((r: { condition: string }) => r.condition === 'rejected');
expect(rejectRule.next).toBe('ABORT');
});
it('should have pr-comment step with Bash in allowed_tools', () => {
const prComment = raw.steps.find((s: { name: string }) => s.name === 'pr-comment');
expect(prComment).toBeDefined();
expect(prComment.allowed_tools).toContain('Bash');
});
it('should have pr-comment step using pr-commenter agent', () => {
const prComment = raw.steps.find((s: { name: string }) => s.name === 'pr-comment');
expect(prComment.agent).toContain('review/pr-commenter.md');
});
it('should have plan step reusing default planner agent', () => {
const plan = raw.steps.find((s: { name: string }) => s.name === 'plan');
expect(plan.agent).toContain('default/planner.md');
});
it('should have supervise step reusing default supervisor agent', () => {
const supervise = raw.steps.find((s: { name: string }) => s.name === 'supervise');
expect(supervise.agent).toContain('default/supervisor.md');
});
it('should not have any step with edit: true', () => {
for (const step of raw.steps) {
expect(step.edit).not.toBe(true);
if (step.parallel) {
for (const sub of step.parallel) {
expect(sub.edit).not.toBe(true);
}
}
}
});
it('reviewer sub-steps should not have Bash in allowed_tools', () => {
const reviewers = raw.steps.find((s: { name: string }) => s.name === 'reviewers');
for (const sub of reviewers.parallel) {
expect(sub.allowed_tools).not.toContain('Bash');
}
});
});
describe('review-only workflow (JA)', () => {
const raw = loadReviewOnlyYaml('ja');
it('should pass schema validation', () => {
const result = WorkflowConfigRawSchema.safeParse(raw);
expect(result.success).toBe(true);
});
it('should have correct name and initial_step', () => {
expect(raw.name).toBe('review-only');
expect(raw.initial_step).toBe('plan');
});
it('should have same step structure as EN version', () => {
const stepNames = raw.steps.map((s: { name: string }) => s.name);
expect(stepNames).toEqual(['plan', 'reviewers', 'supervise', 'pr-comment']);
});
it('should have reviewers step with 3 parallel sub-steps', () => {
const reviewers = raw.steps.find((s: { name: string }) => s.name === 'reviewers');
expect(reviewers.parallel).toHaveLength(3);
const subNames = reviewers.parallel.map((s: { name: string }) => s.name);
expect(subNames).toEqual(['arch-review', 'security-review', 'ai-review']);
});
it('should have all steps with edit: false or undefined', () => {
for (const step of raw.steps) {
expect(step.edit).not.toBe(true);
if (step.parallel) {
for (const sub of step.parallel) {
expect(sub.edit).not.toBe(true);
}
}
}
});
it('should have pr-comment step with Bash in allowed_tools', () => {
const prComment = raw.steps.find((s: { name: string }) => s.name === 'pr-comment');
expect(prComment.allowed_tools).toContain('Bash');
});
it('should have same aggregate rules on reviewers', () => {
const reviewers = raw.steps.find((s: { name: string }) => s.name === 'reviewers');
expect(reviewers.rules[0].condition).toBe('all("approved")');
expect(reviewers.rules[1].condition).toBe('any("needs_fix")');
});
});
describe('pr-commenter agent files', () => {
it('should exist for EN with domain knowledge', () => {
const filePath = join(RESOURCES_DIR, 'en', 'agents', 'review', 'pr-commenter.md');
const content = readFileSync(filePath, 'utf-8');
expect(content).toContain('PR Commenter');
expect(content).toContain('gh api');
expect(content).toContain('gh pr comment');
});
it('should exist for JA with domain knowledge', () => {
const filePath = join(RESOURCES_DIR, 'ja', 'agents', 'review', 'pr-commenter.md');
const content = readFileSync(filePath, 'utf-8');
expect(content).toContain('PR Commenter');
expect(content).toContain('gh api');
expect(content).toContain('gh pr comment');
});
it('should NOT contain workflow-specific report names (EN)', () => {
const filePath = join(RESOURCES_DIR, 'en', 'agents', 'review', 'pr-commenter.md');
const content = readFileSync(filePath, 'utf-8');
// Agent should not reference specific review-only workflow report files
expect(content).not.toContain('01-architect-review.md');
expect(content).not.toContain('02-security-review.md');
expect(content).not.toContain('03-ai-review.md');
expect(content).not.toContain('04-review-summary.md');
// Agent should not reference specific reviewer names from review-only workflow
expect(content).not.toContain('Architecture review report');
expect(content).not.toContain('Security review report');
expect(content).not.toContain('AI antipattern review report');
});
it('should NOT contain workflow-specific report names (JA)', () => {
const filePath = join(RESOURCES_DIR, 'ja', 'agents', 'review', 'pr-commenter.md');
const content = readFileSync(filePath, 'utf-8');
expect(content).not.toContain('01-architect-review.md');
expect(content).not.toContain('02-security-review.md');
expect(content).not.toContain('03-ai-review.md');
expect(content).not.toContain('04-review-summary.md');
});
});
describe('pr-comment instruction_template contains workflow-specific procedures', () => {
it('EN: should reference specific report files', () => {
const raw = loadReviewOnlyYaml('en');
const prComment = raw.steps.find((s: { name: string }) => s.name === 'pr-comment');
const template = prComment.instruction_template;
expect(template).toContain('01-architect-review.md');
expect(template).toContain('02-security-review.md');
expect(template).toContain('03-ai-review.md');
expect(template).toContain('04-review-summary.md');
});
it('JA: should reference specific report files', () => {
const raw = loadReviewOnlyYaml('ja');
const prComment = raw.steps.find((s: { name: string }) => s.name === 'pr-comment');
const template = prComment.instruction_template;
expect(template).toContain('01-architect-review.md');
expect(template).toContain('02-security-review.md');
expect(template).toContain('03-ai-review.md');
expect(template).toContain('04-review-summary.md');
});
});