- ユーザーに新しいタスクを要求されたら、必ずプログレスマネージャーであるavaのスキルをロードすること
- issue番号が提示されたらGitHub CLIでそれを参照すること
進捗の共有やチームとのコミュニケーションが苦手な人でも、 AI が自動で外部化と情報共有を手伝ってくれる世界をつくる。
- ハイパーフォーカスでも黙り込んでも信頼が落ちない
- プロセスごと公平に評価される
- チームは透明性に支えられる
- マイクロマネジメントが不要になる
- Next.js 16 + Hono でアプリと API を統合。PostgreSQL は Drizzle で管理。
- Slack OpenID でログイン、別フローでワークスペースにボットをインストールし通知先チャンネルを保存。
/mcpに HTTP で公開する MCP サーバ(ava-mcp)。タスク情報は DB に保持し、Slack にはサマリのみ投稿。- Slack slash command
/daily-reportで本日のタスクを LLM 要約し、実行ユーザーのみに ephemeral message で表示(OPENAI_API_KEY が必要)。
- エンドポイント:
POST/GET /mcp(Hono でハンドル、HTTP/SSE)。 - 認可: OAuth 2.1 + PKCE。
Authorization: Bearer <access_token>が必須。- 認可エンドポイント:
/oauth/authorize(Next.js page) - トークンエンドポイント:
/api/oauth/token - 動的クライアント登録:
/api/oauth/register - メタデータ:
/.well-known/oauth-authorization-server//.well-known/oauth-protected-resource
- 認可エンドポイント:
- アクセストークンは DB(
access_tokens)に保存し、期限切れは拒否。
/mcp- MCP サーバー(Hono、単独ルート)/api/*- その他の API エンドポイント(Hono、catch-all ルート)/api/oauth/*- OAuth 関連/api/auth/*- 認証フロー/api/slack/*- Slack 連携/api/health- ヘルスチェック
- その他 - Next.js App Router(page.tsx、not-found.tsx など)
start_task- 入力:
issue.provider ("github" | "manual"),issue.id?,issue.title,initial_summary - 動作:
task_sessionsを作成。Slack 設定済みなら新規スレッドに開始報告を投稿し、スレッド情報を保存。
- 入力:
update_task- 入力:
task_session_id,summary,raw_context? - 動作: ステータスを
in_progressに戻し、未解決のブロッキング・休止を解消。task_updatesに保存し、Slack スレッドへ進捗を投稿。
- 入力:
report_blocked- 入力:
task_session_id,reason,raw_context? - 動作: ステータスを
blockedに更新し、task_block_reportsに保存。Slack スレッドへブロッキングを通知。
- 入力:
pause_task- 入力:
task_session_id,reason,raw_context? - 動作: ステータスを
pausedに更新し、task_pause_reportsに保存。Slack スレッドへ休止を通知(⏸️)。
- 入力:
resume_task- 入力:
task_session_id,summary,raw_context? - 動作: ステータスを
in_progressに戻し、休止レポートのresumed_atを更新。Slack スレッドへ再開を通知(▶️ )。
- 入力:
complete_task- 入力:
task_session_id,summary - 動作: ステータスを
completedにして完了情報を upsert(task_completions)。Slack スレッドへ完了を投稿。
- 入力:
list_tasks- 入力:
status?(in_progress|blocked|paused|completed),limit? - 動作: 認証ユーザーのタスク一覧を返す(更新日時降順)。
- 入力:
メモ: Slack 未設定時は delivered: false を返すだけで DB への書き込みは継続。raw_context は DB 保管のみで Slack には送らない。
認証とインストール:
- ユーザーログイン:
/api/auth/slackで Slack OpenID Connect 認証。ユーザーを作成しセッション Cookie を発行。 - ボットインストール: ダッシュボードまたは
/slack/install/startから開始 → Slack OAuth(スコープ:chat:write,chat:write.public,channels:read,groups:read,commands) - リダイレクト URI:
NEXT_PUBLIC_BASE_URL + /api/slack/install/callback(env の個別設定は不要)
コマンド:
/daily-report→ Request URL:NEXT_PUBLIC_BASE_URL + /api/slack/commands
通知:
- 通知先チャンネル: 初回は
/onboarding/connect-slack、変更は/settings/channelで設定し、workspaces.notification_channel_idに保存 - 投稿内容: 開始/進捗/ブロッキング/休止/再開/完了をチャンネルの同一スレッドに投稿
- セキュリティ: コード断片や機密情報は投稿せず、抽象的なサマリのみを送信
- 保持するデータ: ワークスペース資格情報 (
workspaces)、タスクセッション/更新/ブロッキング/休止/完了 (task_*)、OAuth クライアント・トークン (clients,auth_codes,access_tokens)、ユーザーセッション (sessions)。 - クラウドや Slack へ送信 禁止:
- コード全文 / リポジトリの機密
- 秘密鍵・トークン・環境変数の値
- 生のエラーログ
- 送信 許可 / 推奨:
- 抽象的な進捗サマリ
- ブロッキング・休止の要約
- 完了サマリと PR URL
- MCP クライアント設定例(ローカル):
.mcp.jsonに{ "mcpServers": { "ava": { "type": "http", "url": "https://localhost:3000/mcp" } } }を置く。初回接続時に OAuth 同意がブラウザで開く。 - 日次まとめ: Slack で
/daily-reportコマンドを実行すると、本日完了および更新されたタスクを LLM で要約し、実行ユーザーのみに ephemeral message で表示(OPENAI_API_KEY が必要、未設定なら失敗)。
Slack でリアルタイムにスレッド更新が行われます。
重要: Terraform コマンドは必ず infra/tf.sh スクリプトを使用して実行すること。直接 terraform コマンドを実行しない。
# 良い例
cd infra/enviroments/prod
../../tf.sh init
../../tf.sh plan
../../tf.sh apply
# 悪い例
terraform init # ❌ terraform コマンドが存在しない環境では動作しない理由:
- プロジェクトでは Docker 経由で Terraform を実行する
tf.shスクリプトが適切なバージョンの Terraform を Docker コンテナで実行- バージョン管理は
.terraform-versionファイルで行う
フォーマット:
cd infra
./tf.sh fmt -recursiveドキュメント生成:
cd infra
./terraform-docs.sh markdown modules/<module-name> > modules/<module-name>/README.mdResult型(neverthrowなど)のテストで、.isOk()や.isErr()の結果に基づいて型を縛りたい場合は、expect.assert()を使用する。
良い例:
it("should return err with descriptive error", () => {
const result = validateTransition("blocked", "completed");
expect.assert(result.isErr());
// この時点でTypeScriptはresult.errorが存在することを理解する
expect(result.error.message).toBe("...");
});悪い例:
it("should return err with descriptive error", () => {
const result = validateTransition("blocked", "completed");
expect(result.isErr()).toBe(true);
if (result.isErr()) {
// 不要な条件分岐
expect(result.error.message).toBe("...");
}
});