関連 issue: #80
作成日: 2026-07-06
ステータス: Phase 1 / Phase 2 実装済み、Phase 3 は恒久保留(後続は issue #81 で別方式を検討)
Issue #80 comment #2859 の実行可能性レビューを反映し、初版から次を修正した。
work_observe() の available_next に dict を混ぜる案を撤回する。現行 available_next は list[str] なので、Project API 判定結果は専用 tool の戻り値として返す。project_capability_check() は mcp_facade.py から worker を直接呼ばず、新規 project_backend.py 相当の backend 層を経由する。GiteaProjectWorker 分離は Phase 1 では行わない。分離する場合は config/intent_maid_routes.yml の routing 条件設計を必須作業に含める。read_from_gitea.detect_project_api ではなく、既存規約に合わせて payload.operation = "detect_project_api" とする。unittest.mock.patch のインライン payload で追加する。JSON fixture は Swagger payload が大きくなった場合の選択肢とする。別リポジトリの初期整備で、Butler / Gitea API による Issue 作成と label 作成はできたが、Project ボード作成と Issue の Project 追加を自動化できなかった。
対象 Gitea は 1.25.5 で、repo API は has_projects: true と projects_mode: all を返す。一方、swagger.v1.json には repo project / project board / card 操作用の path が見当たらなかった。
Gitea v1.25.5 の Swagger template でも、repository 定義には has_projects と projects_mode が存在するが、Project 操作用の /projects path は確認できない。したがって、少なくとも現環境では「repo の Project unit が有効であること」と「API から Project を操作できること」を分けて扱う必要がある。
Phase 1.5 確定調査(issue #80 comment #2865): 公式 v1.25.5 の swagger template 全文・routers/api/v1/api.go の route 一覧・実環境 gitea.keinafarm.net への detect_project_api 実行・Gitea 本体の GitHub issue(#14299, #35921, #36824)の4方向から、Gitea v1.25.5 に Project 操作用 REST API は存在しないことを確定した。Project 機能自体(models/project/, services/projects/)は実装されているが、routers/web/...(Web UI 層)にのみ配線されており routers/api/v1/... には配線されていない。つまり「未実装」ではなく「意図的に Web UI 専用」である。5年以上前から追加要望が出ているが未解決・マージ済み PR なし。
採用方針は次の通り。
has_projects だけで Project API 利用可能と判断しない。status=ok + manual_fallback.required=true を返し、UI 手動作成が必要なことを明示する。GiteaIssueWorker に read-only の detect_project_api だけを追加する。v1.25.5 には Project 操作用 REST API が存在しないため、Project 作成・Issue card 追加・column 移動などの操作本体は実装対象にしない。project_capability_check() / project_backend.py を通じて、Brain-facing に API 非対応と UI 手動 fallback を明示する。gitea_* を出さず、必要なら project_* 系の抽象 tool として公開する。Issue #80 の達成形は「Project 操作を API で自動化すること」ではなく、「Gitea 1.25.5 では Project API 非対応であることを確実に判定し、迷わず UI 手動経路へ誘導できること」である。
以下は、Issue #80 で必要性が確認された Project 操作要件である。ただし Gitea v1.25.5 には Project REST API が存在しないため、現時点で Butler が実装する対象は detect_project_api / project_capability_check() / manual fallback までとする。Project 操作本体は、将来 Gitea 側に REST API が追加された場合の参考要件として保持する。
| 操作 | Butler operation 案 | 備考 |
|---|---|---|
| Project 一覧取得 | list_projects |
repo / owner scope の違いを evidence に残す |
| Project 作成 | create_project |
API 非対応なら明示エラー |
| 単一 Project 詳細取得 | get_project |
project id または name |
| Issue を Project に追加 | add_issue_to_project |
card 作成に相当 |
| Issue の Project / column 位置取得 | get_issue_project_position |
運用上の確認に必須 |
| card を別 column へ移動 | move_project_card |
進捗更新に必須 |
| 操作 | Butler operation 案 |
|---|---|
| Project 更新 | update_project |
| Project 削除 | delete_project |
| column 一覧取得 | list_project_columns |
| column 作成 | create_project_column |
| column 更新 | update_project_column |
| column 並び替え | reorder_project_columns |
| column 削除 | delete_project_column |
| card 一覧取得 | list_project_cards |
| card 順変更 | reorder_project_cards |
| card 削除 | delete_project_card |
| Issue と Project の関連一覧取得 | list_issue_project_links |
Butler は次を別々に判定する。
| 項目 | 判定元 | 意味 |
|---|---|---|
repo_projects_enabled |
GET /repos/{owner}/{repo} の has_projects / projects_mode |
UI 上の Project unit が有効か |
project_api_declared |
GET /swagger.v1.json または /api/swagger の paths |
API path が公開されているか |
project_api_usable |
将来 API が追加された場合の軽量 GET / OPTIONS 相当 | token 権限込みで利用可能か。v1.25.5 では常に未判定 / 非対応 |
project_api_scope |
path 形状 | repo project / owner project のどちらか |
repo_projects_enabled=true でも project_api_declared=false なら、Butler は Project 操作を実行しない。
{
"status": "ok",
"summary": "Gitea Project API capability を判定しました",
"data": {
"owner": "akira",
"repo": "butler2",
"gitea_version": "1.25.5",
"repo_projects_enabled": true,
"projects_mode": "all",
"project_api_declared": false,
"project_api_usable": null,
"project_api_scope": null,
"unsupported_reason": "swagger_path_missing",
"manual_fallback": {
"required": true,
"message": "この Gitea では Project 操作用 API path が公開されていないため、Project ボード作成と Issue カード追加は UI で行ってください。"
}
}
}
Gitea の版差分に備え、path 名を固定しすぎず、Swagger の paths から候補を抽出する。
候補条件:
projects または project が含まれるrepos/{owner}/{repo}、orgs/{org}、users/{username} など scope を推定できるGET / POST / PATCH / DELETE のいずれかproject が含まれる場合も候補にする候補が 0 件の場合は project_api_declared=false とする。
候補がある場合でも、未知の API 形状なら即 write 操作を許可しない。Gitea v1.25.5 では候補が無いことが確定しているため、現時点では read-only の判定結果と manual fallback のみを返す。
対象:
butler/maids/gitea_issue_worker.pybutler/issue_backend.py とは分離docs/capabilities/gitea/仕様書.mdtests/test_gitea_non_llm_maid.py または新規 tests/test_gitea_project_*.py追加 operation:
detect_project_api既存規約では read_from_gitea は TaskContract.intent、operation は payload["operation"] のフラットな文字列である。したがって _READ_OPERATIONS に "detect_project_api" を追加する。
処理:
GET /api/v1/repos/{owner}/{repo} で has_projects / projects_mode を取得する。GET /swagger.v1.json を取得する。失敗時は /api/swagger など現環境で取れる候補を試す。paths を走査して Project 候補を抽出する。project_api_declared=false とし、manual fallback を evidence に含める。project_api_declared=true とし、候補 path / method / scope を evidence に含める。Phase 1 では Project 作成や card 操作は実装しない。まず「できないことが分かる」状態を作る。
Phase 1 では新規 worker 分離を行わない。現状 config/intent_maid_routes.yml は read_from_gitea / record_to_gitea を無条件で gitea_issue_worker へ routing しており、同一 intent に GiteaProjectWorker を無条件追加すると競合するためである。
対象:
butler/project_backend.pybutler/mcp_facade.pydocs/Butler利用ガイド.md追加 tool 案:
project_capability_check()呼び出し経路:
MCP project_capability_check()
-> project_backend.check_project_capability()
-> session の owner / repo を解決
-> TaskContract(intent="read_from_gitea", payload.operation="detect_project_api") を生成
-> runtime.execute_task()
-> GiteaIssueWorker.detect_project_api
-> Brain-facing の status / summary / data へ整形
既存の issue_read / issue_comment などと同じく、MCP tool から worker を直接呼ばない。
実装済み: butler/project_backend.py の check_project_capability() と、mcp_facade.py の MCP tool project_capability_check() を追加した。呼び出し経路は上記の通り。
返却方針:
status=ok で manual_fallback.required=true を返すstatus=error または need_inputwork_observe() の available_next にはこの結果を混ぜない。現行 available_next は git/session 状態から生成される list[str] であり、dict を入れると型契約を壊す。
Project API 非対応時の案内は project_capability_check() の戻り値に含める。
{
"status": "ok",
"summary": "Gitea Project API はこの環境では利用できません",
"data": {
"project_api_declared": false,
"manual_fallback": {
"required": true,
"action": "manual_gitea_project_setup",
"message": "Gitea UI で Project ボードを作成してください",
"reason": "この Gitea では Project 操作用 API path が公開されていません",
"automatable": false
}
}
}
Phase 1.5 の確定調査(背景セクション参照)により、Gitea v1.25.5 に Project 操作用 REST API は存在しないことが確定した。したがって Phase 3 は「API が見つかったら実装する条件起動フェーズ」ではなく、現時点では着手先が存在しない保留フェーズとして扱う。
Gitea が Project REST API を追加した時点(#36824 等が実装された場合)で、その path 形状に合わせて実装計画を具体化する。Phase 4 の手動 fallback workflow は暫定策ではなく、それまでの定常運用として扱う。
将来 REST API が追加された場合に検討する操作:
read:
list_projectsget_projectlist_project_columnslist_project_cardsget_issue_project_positionwrite:
create_projectupdate_projectcreate_project_columnadd_issue_to_projectmove_project_cardwrite operation は capability 判定を前置し、project_api_usable=false なら API 呼び出し前に止める。ただし v1.25.5 では write operation を追加しない。
新規 GiteaProjectWorker へ分離する場合の追加対象:
butler/maids/gitea_project_worker.pyconfig/intent_maid_routes.ymlconfig/maid_registry.yml同じ read_from_gitea / record_to_gitea intent を使うなら、payload.operation による route 条件が必要になる。route 条件を増やさない場合は、既存 GiteaIssueWorker 内で Project operation も扱う方が安全である。
別リポジトリ初期整備の自動化では、Project 操作を必須ステップにしない。Gitea v1.25.5 では Project REST API が無いため、Project だけ UI 手動作成へ誘導する workflow を正として扱う。
推奨 workflow:
project_capability_check() を実行する。これにより、Gitea の Project API 非対応が全体の初期整備を失敗扱いにしない。
実装済み: 専用の初期整備自動化スクリプトは無いため、コード側の追加実装は無し。上記 workflow は docs/Butler利用ガイド.md と mcp_facade.py の INSTRUCTIONS / USAGE_GUIDE / SKILLS_GUIDE に Brain 向けガイダンスとして反映した。Brain は issue_create 等で repo 初期整備を進める際、Project ボードが必要になったタイミングで project_capability_check() を呼び、manual_fallback.required=true ならユーザーに UI 手動作成を案内する。
{
"status": "ok",
"summary": "Gitea Project API はこの環境では利用できません",
"data": {
"operation": "detect_project_api",
"unsupported_reason": "swagger_path_missing",
"repo_projects_enabled": true,
"projects_mode": "all",
"manual_fallback": {
"required": true,
"message": "この Gitea では Project 操作用 API path が公開されていないため、Project ボード作成と Issue カード追加は UI で行ってください。"
}
}
}
{
"status": "ok",
"summary": "対象 repo で Projects が無効です",
"data": {
"operation": "detect_project_api",
"unsupported_reason": "repo_projects_disabled",
"repo_projects_enabled": false,
"manual_fallback": {
"required": true,
"message": "対象 repo で Project 機能が無効になっています。Gitea UI で Project unit を有効化するか、Project ボード作成と Issue カード追加は UI で行ってください。"
}
}
}
Swagger 取得失敗は「API 非対応」と断定しない。
{
"status": "failed",
"summary": "Gitea Swagger の取得に失敗したため Project API capability を判定できません",
"data": {
"operation": "detect_project_api",
"failure_reason": "swagger_fetch_failed",
"manual_fallback_required": true
}
}
detect_project_api が has_projects=true, projects_mode=all, Project path なしを project_api_declared=false と判定するhas_projects=false の場合に repo_projects_disabled を返すswagger_fetch_failed を返すproject_capability_check() が project_backend.py 経由で session owner / repo を解決するproject_capability_check() が work_observe().available_next を変更しない既存 tests/test_gitea_non_llm_maid.py は unittest.mock.patch とインライン dict/list で _api_call の返却を差し替えている。Phase 1 のテストもまずはこの慣習に合わせる。
Swagger payload が大きくなり、インライン dict では可読性が落ちる場合に限り、tests/fixtures/gitea/ を新設して次を置く。
swagger_1_25_5_without_projects.jsonrepo_with_projects_enabled.jsonrepo_with_projects_disabled.jsonswagger_with_project_paths.json現 Gitea 1.25.5 で次を確認する。
detect_project_api が project_api_declared=false を返す。project_capability_check() が manual fallback を返す。Phase 1 完了後に追記する文書:
docs/capabilities/gitea/仕様書.md
docs/Butler利用ガイド.md
has_projects=true は API 操作可能を意味しないことを書くdocs/capabilities/README.md
UI 内部 API の利用は現時点では採用しない。CSRF、HTML form、認証 cookie、画面変更への追従が必要になり、Butler の安定した API capability としては壊れやすい。正式 REST / Swagger に載る経路が無い場合は、手動 fallback を正として扱う。
v1.25.5 Swagger template: https://raw.githubusercontent.com/go-gitea/gitea/v1.25.5/templates/swagger/v1_json.tmpldocs/capabilities/gitea/仕様書.mdbutler/maids/gitea_issue_worker.py