Skip to content

fix(observability): preserve exporter capability and map phase logs #77

fix(observability): preserve exporter capability and map phase logs

fix(observability): preserve exporter capability and map phase logs #77

name: PR Comment Commands
# Issue comment 経由で発火するコマンド群を 1 ワークフローに集約。
# 1 コメントごとに作られる workflow run の数を 4 -> 1 に減らし、
# Actions ダッシュボードに大量の Skipped run が並ぶ現象を抑える。
#
# 各 job は独自の `if` で対象コマンドと著者条件を判定し、
# 該当しないコメントでは即座に Skipped になる。
on:
issue_comment:
types: [created]
jobs:
# =====================================================================
# /review — TAKT PR Review (formerly takt-review.yml)
# =====================================================================
review:
if: |
github.event.issue.pull_request != null &&
(
github.event.comment.body == '/review' ||
startsWith(github.event.comment.body, '/review ') ||
startsWith(github.event.comment.body, '/review
')

Check failure on line 24 in .github/workflows/pr-comment-commands.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/pr-comment-commands.yml

Invalid workflow file

You have an error in your yaml syntax on line 24
) &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association)
concurrency:
group: comment-cmd-review-${{ github.event.issue.number }}
cancel-in-progress: true
runs-on: ubuntu-latest
environment: takt-review
permissions:
contents: read
pull-requests: write
steps:
- name: 受付リアクション
uses: actions/github-script@v7
with:
script: |
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: 'eyes',
});
- name: PR の head SHA を取得
id: pr
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
});
return pr.head.sha;
- uses: actions/checkout@v4
with:
ref: ${{ steps.pr.outputs.result }}
fetch-depth: 0
- name: API キー確認
run: |
if [ -z "$ANTHROPIC_API_KEY" ]; then
echo "::error::ANTHROPIC_API_KEY is not set"
exit 1
fi
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Claude Code & TAKT インストール
run: |
npm install -g @anthropic-ai/claude-code
npm install -g takt
- name: TAKT Review 実行
run: takt --pipeline --skip-git --pr ${{ github.event.issue.number }} -w review-takt-default
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
- name: レビュー結果をPRコメントに投稿
if: always()
run: |
REPORT_DIR=$(ls -td .takt/runs/*/reports 2>/dev/null | head -1)
if [ -n "$REPORT_DIR" ]; then
SUMMARY=$(find "$REPORT_DIR" -name "*review-summary*" -type f | head -1)
if [ -n "$SUMMARY" ]; then
gh pr comment ${{ github.event.issue.number }} --body-file "$SUMMARY"
else
echo "レビューサマリーが見つかりません"
fi
else
echo "レポートディレクトリが見つかりません"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
- name: レビューレポートをアーティファクトに保存
if: always()
uses: actions/upload-artifact@v4
with:
name: takt-review-reports
path: .takt/runs/*/reports/
if-no-files-found: ignore
# =====================================================================
# /resolve — CC Resolve (formerly cc-resolve.yml)
# =====================================================================
resolve:
if: |
github.event.issue.pull_request &&
contains(github.event.comment.body, '/resolve') &&
github.event.comment.author_association == 'OWNER'
runs-on: ubuntu-latest
permissions:
actions: write
contents: write
pull-requests: write
steps:
- name: Acknowledge
run: |
gh api repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
-f content=rocket
gh pr comment ${{ github.event.issue.number }} --repo ${{ github.repository }} \
--body "🚀 cc-resolve started: [View logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check if fork PR
id: pr
run: |
PR_REPO=$(gh pr view ${{ github.event.issue.number }} --repo ${{ github.repository }} \
--json headRepositoryOwner,headRepository \
--jq '"\(.headRepositoryOwner.login)/\(.headRepository.name)"')
BRANCH=$(gh pr view ${{ github.event.issue.number }} --repo ${{ github.repository }} \
--json headRefName -q .headRefName)
echo "branch=${BRANCH}" >> "$GITHUB_OUTPUT"
if [ "$PR_REPO" != "${{ github.repository }}" ]; then
echo "::error::Fork PR はサポートしていません。contributor 側で解決してください。"
exit 1
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v4
with:
ref: ${{ steps.pr.outputs.branch }}
fetch-depth: 0
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Merge main (detect conflicts)
id: merge
run: |
git fetch origin main
# --no-commit --no-ff: コンフリクトの有無にかかわらず常にマージ状態を保持する
# これにより最後の git commit が必ずマージコミット(親2つ)を作る
if git merge --no-commit --no-ff origin/main 2>/dev/null; then
echo "conflicts=false" >> "$GITHUB_OUTPUT"
else
echo "conflicts=true" >> "$GITHUB_OUTPUT"
fi
# コミット済みのコンフリクトマーカーを検出
STALE_MARKERS=$(grep -rl '<<<<<<<' --include='*.ts' --include='*.js' --include='*.json' --include='*.yaml' --include='*.yml' --include='*.md' . 2>/dev/null | grep -v node_modules | grep -v .git || echo "")
if [ -n "$STALE_MARKERS" ]; then
echo "stale_markers=true" >> "$GITHUB_OUTPUT"
{
echo "stale_marker_files<<MARKER_EOF"
echo "$STALE_MARKERS"
echo "MARKER_EOF"
} >> "$GITHUB_OUTPUT"
else
echo "stale_markers=false" >> "$GITHUB_OUTPUT"
fi
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Claude Code
run: npm install -g @anthropic-ai/claude-code
- name: Resolve
run: |
claude -p --dangerously-skip-permissions "$(cat <<'PROMPT'
このPRのコンフリクトを解決してください。
## 状況判定
まず現在の状態を確認してください。以下の2つをすべてチェックする。
1. `git status` でマージコンフリクト(Unmerged paths)の有無を確認
2. ファイル中にコミット済みのコンフリクトマーカー(`<<<<<<<`)が残っていないか `grep -r '<<<<<<<' --include='*.ts' --include='*.js' --include='*.json' .` で確認
**重要**: git status がクリーンでも、ファイル内にコンフリクトマーカーがテキストとしてコミットされている場合がある。必ず grep で確認すること。
どちらも該当しなければ「コンフリクトなし」と報告して終了。
---
## コンフリクト解決
Git merge/rebase/cherry-pick のコンフリクト、およびファイル内に残存するコンフリクトマーカーを、差分分析に基づいて解決する。
**原則: 差分を読み、疑い、判断根拠を書いてから解決する。妄信的に片方を採用しない。**
### 1. コンフリクト状態を確認する
```bash
git status
```
- merge / rebase / cherry-pick のどれが進行中か特定する
- `.git/MERGE_HEAD` があれば merge
- `.git/rebase-merge/` があれば rebase
- `.git/CHERRY_PICK_HEAD` があれば cherry-pick
### 2. コンテキストを把握する
以下を**並列で**実行:
- `git log --oneline HEAD -5` で HEAD 側(現在のブランチ)の最近の変更を確認
- `git log --oneline MERGE_HEAD -5` で取り込み側の最近の変更を確認(merge の場合)
- 両ブランチの関係性(どちらがベースでどちらが新しいか)を理解する
### 3. コンフリクトファイルを列挙する
```bash
git diff --name-only --diff-filter=U
```
加えて、コミット済みマーカーがあるファイルも対象に含める:
```bash
grep -rl '<<<<<<<' --include='*.ts' --include='*.js' --include='*.json' . | grep -v node_modules
```
ファイル数と種類(ソースコード / 設定ファイル / ロックファイル等)を報告する。
### 4. 各ファイルを分析する
**ここが核心。ファイルごとに以下を必ず実行する。省略しない。**
1. ファイル全体を読む(コンフリクトマーカー付きの状態)
2. 各コンフリクトブロック(`<<<<<<<` 〜 `>>>>>>>`)について:
- HEAD 側の内容を具体的に読む
- theirs 側の内容を具体的に読む
- 差分が何を意味するか分析する(バージョン番号?リファクタ?機能追加?型変更?)
- 判断に迷う場合は `git log --oneline -- {file}` で変更履歴を確認する
3. **判断を書く**(以下の形式で必ず出力すること):
```markdown
### ファイル: path/to/file.ts
#### コンフリクト 1 (L30-45)
- HEAD 側: {具体的な内容を書く}
- theirs 側: {具体的な内容を書く}
- 分析: {差分が何を意味するか}
- 判断: {HEAD / theirs / 両方統合} を採用({理由})
```
**疑うべきポイント:**
- 「〇〇側が新しいから」だけで判断していないか? HEAD 側に独自の意図ある変更はないか?
- theirs を採用すると、HEAD 側でしか行っていない作業が消えないか?
- 両方の変更を統合すべきケースではないか?
- package-lock.json のような機械生成ファイルでも、バージョンの意味を確認したか?
### 5. 解決を実施する
ステップ4の分析結果に基づいて解決する:
- 片方採用が明確な場合: `git checkout --ours {file}` / `git checkout --theirs {file}` を使ってよい(**分析済みファイルのみ**)
- 両方の変更を統合する場合: コンフリクトマーカーを除去し、両方の内容を適切に結合する
- 解決したファイルを `git add {file}` でマークする
解決後、`<<<<<<<` を検索し、マーカーの取り残しがないか確認する。
---
## 波及影響確認
**コンフリクトを解決しただけでは終わらない。** 対象外ファイルにも影響が出ていないか検証する。
- ビルド確認(`npm run build`、`./gradlew build` 等、プロジェクトに応じて)
- テスト確認(`npm test`、`./gradlew test` 等)
- 対象外ファイルが、変更と矛盾していないか確認する
- 例: 関数シグネチャを変更したのに、テストが旧シグネチャを期待している
- 例: import パスを変更したのに、別ファイルが旧パスを参照している
問題が見つかった場合はここで修正する。
---
## 結果を報告する
全ファイルの解決結果をサマリーテーブルで報告する:
```markdown
## コンフリクト解決サマリー
| ファイル | コンフリクト数 | 採用 | 理由 |
|---------|-------------|------|------|
| path/to/file.ts | 2 | theirs | リファクタリング済み |
波及修正: {対象外ファイルの修正内容。なければ「なし」}
ビルド: OK / NG
テスト: OK / NG ({passed}/{total})
```
---
## 絶対原則
- **差分を読まずに解決しない。** ファイルの中身を確認せずに `--ours` / `--theirs` を適用しない
- **盲従しない。** HEAD 側に独自の意図がないか必ず疑う
- **判断根拠を省略しない。** 各コンフリクトに「何が・なぜ・どちらを」の3点を書く
- **波及を確認する。** 対象外ファイルもビルド・テストで検証する
## 禁止事項
- 分析なしで `git checkout --ours .` / `git checkout --theirs .` を実行しない
- 「とりあえず片方」で全ファイルを一括解決しない
- コンフリクトマーカー (`<<<<<<<`) が残ったままにしない
- `git merge --abort` を実行しない
- `git reset` を実行しない(MERGE_HEAD が消えてマージコミットが作れなくなる)
- `.git/MERGE_HEAD` を保持したまま作業すること
PROMPT
)" --verbose
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Commit and push
id: push
run: |
git add -A
# MERGE_HEAD があればマージコミット、なければ通常コミット
if [ -f .git/MERGE_HEAD ]; then
git commit -m "merge: integrate main into PR branch"
elif ! git diff --cached --quiet; then
git commit -m "fix: resolve merge conflicts"
fi
AHEAD=$(git rev-list --count origin/${{ steps.pr.outputs.branch }}..HEAD 2>/dev/null || echo "0")
if [ "$AHEAD" -gt 0 ]; then
echo "Pushing $AHEAD commit(s)"
git push
echo "pushed=true" >> "$GITHUB_OUTPUT"
else
echo "Nothing to push"
echo "pushed=false" >> "$GITHUB_OUTPUT"
fi
- name: Trigger CI
if: steps.push.outputs.pushed == 'true'
run: |
gh workflow run ci.yml --ref "${{ steps.pr.outputs.branch }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Report result
if: always()
run: |
PR_NUMBER=${{ github.event.issue.number }}
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
if [ "${{ job.status }}" = "success" ]; then
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "✅ cc-resolve completed. [View logs](${RUN_URL})"
else
gh pr comment "$PR_NUMBER" --repo ${{ github.repository }} --body "❌ cc-resolve failed. [View logs](${RUN_URL})"
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# =====================================================================
# /ci — CI Trigger (formerly ci-trigger.yml)
# =====================================================================
ci:
if: |
github.event.issue.pull_request &&
contains(github.event.comment.body, '/ci') &&
github.event.comment.author_association == 'OWNER'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
statuses: write
steps:
- name: Get PR ref
id: pr
run: |
PR=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} \
--jq '{head_ref: .head.ref, head_sha: .head.sha}')
echo "ref=$(echo "$PR" | jq -r .head_ref)" >> "$GITHUB_OUTPUT"
echo "sha=$(echo "$PR" | jq -r .head_sha)" >> "$GITHUB_OUTPUT"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: actions/checkout@v4
with:
ref: ${{ steps.pr.outputs.ref }}
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Set pending status
run: |
for context in "CI / lint" "CI / test" "CI / e2e-mock"; do
gh api repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.sha }} \
-f state=pending -f context="$context" \
-f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
done
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- run: npm ci
- run: npm run build
- name: Lint
id: lint
run: npm run lint
continue-on-error: true
- name: Test
id: test
run: npm run test
continue-on-error: true
- name: E2E Mock
id: e2e
run: npm run test:e2e:mock
continue-on-error: true
- name: Report statuses
if: always()
run: |
report_status() {
local context="$1" result="$2"
local state="success"
if [ "$result" != "success" ]; then state="failure"; fi
gh api repos/${{ github.repository }}/statuses/${{ steps.pr.outputs.sha }} \
-f state="$state" -f context="$context" \
-f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
report_status "CI / lint" "${{ steps.lint.outcome }}"
report_status "CI / test" "${{ steps.test.outcome }}"
report_status "CI / e2e-mock" "${{ steps.e2e.outcome }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Fail if any step failed
if: always()
run: |
if [ "${{ steps.lint.outcome }}" != "success" ] || \
[ "${{ steps.test.outcome }}" != "success" ] || \
[ "${{ steps.e2e.outcome }}" != "success" ]; then
exit 1
fi
# =====================================================================
# @takt — TAKT Action (formerly takt-action.yml)
# =====================================================================
takt:
if: |
contains(github.event.comment.body, '@takt') &&
github.event.comment.author_association == 'OWNER'
runs-on: ubuntu-latest
permissions:
issues: write
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
- uses: nrslib/takt-action@main
with:
anthropic_api_key: ${{ secrets.TAKT_ANTHROPIC_API_KEY }}
model: ${{ vars.TAKT_MODEL }}
github_token: ${{ secrets.GITHUB_TOKEN }}
log_output: ${{ vars.TAKT_LOG_OUTPUT || 'false' }}
- name: Notify Slack
if: always()
uses: slackapi/slack-github-action@v2.0.0
with:
webhook-type: incoming-webhook
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
payload: |
{
"text": "${{ job.status == 'success' && '✅' || '⚠️' }} TAKT Action ${{ job.status }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*${{ job.status == 'success' && '✅' || '⚠️' }} TAKT Action ${{ job.status }}*\n<${{ github.event.issue.html_url }}|Issue #${{ github.event.issue.number }}>: ${{ github.event.issue.title }}\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View logs>"
}
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Triggered by @${{ github.event.comment.user.login }}"
}
]
}
]
}