diff --git a/.SourceSageignore b/.SourceSageignore
index e29d512..8d8735b 100644
--- a/.SourceSageignore
+++ b/.SourceSageignore
@@ -1,38 +1,56 @@
-.git
-__pycache__
-LICENSE
-output.md
-assets
-Style-Bert-VITS2
-output
-streamlit
-SourceSage.md
-data
+# バージョン管理システム関連
+.git/
.gitignore
-.SourceSageignore
-*.png
-Changelog
-SourceSageAssets
-SourceSageAssetsDemo
-__pycache__
-.pyc
+
+# キャッシュファイル
+__pycache__/
+.pytest_cache/
**/__pycache__/**
-modules\__pycache__
-.svg
-sourcesage.egg-info
-.pytest_cache
-dist
-build
-.env
-example
-
-.gaiah.md
-.Gaiah.md
-tmp.md
-tmp2.md
-.SourceSageAssets
-tests
-template
-aira.egg-info
-aira.Gaiah.md
-README_template.md
\ No newline at end of file
+*.pyc
+
+# ビルド・配布関連
+build/
+dist/
+*.egg-info/
+
+# 一時ファイル・出力
+output/
+output.md
+test_output/
+.SourceSageAssets/
+.SourceSageAssetsDemo/
+
+# アセット
+*.png
+*.svg
+*.jpg
+*.jepg
+assets/
+
+# その他
+LICENSE
+example/
+package-lock.json
+.DS_Store
+
+# 特定のディレクトリを除外
+tests/temp/
+docs/drafts/
+
+# パターンの例外(除外対象から除外)
+!docs/important.md
+!.github/workflows/
+repository_summary.md
+
+# Terraform関連
+.terraform
+*.terraform.lock.hcl
+*.backup
+*.tfstate
+
+# Python仮想環境
+venv
+.venv
+
+.harmon_ai
+.github
diff --git a/.aira/config.dev.yml b/.aira/config.dev.yml
deleted file mode 100644
index 4385932..0000000
--- a/.aira/config.dev.yml
+++ /dev/null
@@ -1,9 +0,0 @@
-aira:
- gaiah:
- run: true
- develop:
- config_path: .gaiah/config.dev.yml
- harmon_ai:
- run: true
- config_path: .harmon_ai/config.yml
- instructions_prompt: .aira/instructions.md
\ No newline at end of file
diff --git a/.aira/config.yml b/.aira/config.yml
deleted file mode 100644
index a7a4e48..0000000
--- a/.aira/config.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-aira:
- gaiah:
- run: true
- init:
- config_path: .gaiah/config.init.yml
- develop:
- config_path: .gaiah/config.yml
- harmon_ai:
- run: true
- config_path: .harmon_ai/config.yml
- instructions_prompt: .aira/instructions.md
\ No newline at end of file
diff --git a/.aira/instructions.md b/.aira/instructions.md
deleted file mode 100644
index 57b6c36..0000000
--- a/.aira/instructions.md
+++ /dev/null
@@ -1,2 +0,0 @@
-サンプルリポジトリを作成したいです
-シンプルな計算や文字の表示を行うリポジトリを提案して
\ No newline at end of file
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..1198d0c
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,10 @@
+# AIRAの基本設定
+GAIAH_RUN=true
+COMMIT_MSG_PATH=.Gaiah.md
+
+# LLMの設定
+LLM_MODEL=gemini/gemini-1.5-pro-latest
+GEMINI_API_KEY=your-api-key-here
+
+# GitHubの設定(必要な場合のみ)
+GITHUB_ACCESS_TOKEN=your-github-token-here
diff --git a/.gaiah/config.dev.yml b/.gaiah/config.dev.yml
deleted file mode 100644
index f59f4b0..0000000
--- a/.gaiah/config.dev.yml
+++ /dev/null
@@ -1,12 +0,0 @@
-gaiah:
- repo:
- create_repo: false
-
- local:
- init_repo: false
- repo_dir: "C:/Prj/AIRA"
-
- commit:
- process_commits: true
- commit_msg_path: ".Gaiah.md"
- branch_name: null
\ No newline at end of file
diff --git a/.gaiah/config.init.yml b/.gaiah/config.init.yml
deleted file mode 100644
index da8ddc2..0000000
--- a/.gaiah/config.init.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-gaiah:
- repo:
- create_repo: true
- repo_name: "AIRA-Sample00"
- description: ""
- private: false
-
- local:
- init_repo: true
- repo_dir: "C:/Prj/AIRA-Sample/AIRA-Sample00"
- no_initial_commit: false
-
- commit:
- process_commits: true
- commit_msg_path: "C:/Prj/AIRA-Sample/.Gaiah.md"
- branch_name: null
\ No newline at end of file
diff --git a/.gaiah/config.yml b/.gaiah/config.yml
deleted file mode 100644
index 0cc4c65..0000000
--- a/.gaiah/config.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-gaiah:
- repo:
- create_repo: false
- repo_name: "AIRA-Sample00"
- description: ""
- private: false
-
- local:
- init_repo: false
- repo_dir: "C:/Prj/AIRA-Sample/AIRA-Sample00"
- no_initial_commit: false
-
- commit:
- process_commits: true
- commit_msg_path: ".aira/aira.Gaiah.md"
- branch_name: null
\ No newline at end of file
diff --git a/.github/config.py b/.github/config.py
new file mode 100644
index 0000000..dcccd54
--- /dev/null
+++ b/.github/config.py
@@ -0,0 +1,18 @@
+from pydantic_settings import BaseSettings, SettingsConfigDict
+from pydantic import Field
+from functools import lru_cache
+
+class Settings(BaseSettings):
+ LITELLM_MODEL: str = "gemini/gemini-1.5-pro-latest"
+ GITHUB_TOKEN: str = Field(..., env="GITHUB_TOKEN")
+ GITHUB_REPOSITORY: str = Field(..., env="GITHUB_REPOSITORY")
+ ISSUE_NUMBER: int = Field(0, env="ISSUE_NUMBER")
+ REPOSITORY_SUMMARY_PATH: str = Field("", env="REPOSITORY_SUMMARY_PATH")
+ model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8', extra='ignore')
+ YOUR_PERSONAL_ACCESS_TOKEN: str = Field("", env="YOUR_PERSONAL_ACCESS_TOKEN")
+ YOUR_PERSONAL_ACCESS_TOKEN_IRIS: str = Field("", env="YOUR_PERSONAL_ACCESS_TOKEN_IRIS")
+ # ALLOWED_USERS = ["github-actions[bot]", "Sunwood-ai-labs", "user2"]
+
+@lru_cache()
+def get_settings():
+ return Settings()
diff --git a/.github/labels.csv b/.github/labels.csv
new file mode 100644
index 0000000..c59c419
--- /dev/null
+++ b/.github/labels.csv
@@ -0,0 +1,11 @@
+label,description
+bug,何かが正常に動作していません
+documentation,ドキュメントの改善または追加
+duplicate,このイシューまたはプルリクエストは既に存在します
+enhancement,新機能または要望
+feature,新機能または要望
+good first issue,初心者に適しています
+help wanted,追加の注意が必要です
+invalid,これは適切ではないようです
+question,さらなる情報が必要です
+wontfix,これは対応されません
\ No newline at end of file
diff --git a/.github/scripts/apply_suggestion.py b/.github/scripts/apply_suggestion.py
new file mode 100644
index 0000000..3c9f133
--- /dev/null
+++ b/.github/scripts/apply_suggestion.py
@@ -0,0 +1,127 @@
+import sys
+import os
+
+# Add the parent directory of 'scripts' to the Python path
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from loguru import logger
+from config import get_settings
+from services.llm_service import LLMService
+from services.github_service import GitHubService
+from services.git_service import GitService
+from utils.diff_utils import DiffUtils, create_comment
+from utils.patch_utils import apply_patch, get_patch_file_path
+
+class SuggestionApplier:
+ def __init__(self):
+ self.settings = get_settings()
+ self.llm_service = LLMService()
+ self.github_service = GitHubService()
+ self.git_service = GitService()
+ # self.allowed_users = self.settings.ALLOWED_USERS # 設定から許可されたユーザーのリストを取得
+
+ def run(self):
+ logger.info("変更提案の適用処理を開始します。")
+
+ issue = self.github_service.get_issue()
+ all_comments = self.github_service.get_comments(issue)
+
+ # コメントユーザーの一覧を表示
+ self._display_comment_users(all_comments)
+
+ # 許可されたユーザーのコメントのみをフィルタリング
+ # comments = self._filter_allowed_comments(all_comments)
+ comments = all_comments
+
+ if not self._validate_comments(comments):
+ return
+
+ previous_comment = self._find_previous_bot_comment(comments)
+ if not previous_comment:
+ logger.error("Error: github-actions[bot] のコメントが見つかりませんでした。")
+ raise ValueError("github-actions[bot] のコメントが見つかりません")
+
+ print(previous_comment.body)
+ diffs = DiffUtils.extract_diff(previous_comment.body)
+ if diffs is None:
+ logger.error("有効なdiffを抽出できませんでした。処理を終了します。")
+ return
+
+ try:
+ modified_files = self._process_diffs(diffs)
+ self._create_pull_request(issue, modified_files)
+ except Exception as e:
+ logger.error(f"エラーが発生しました: {str(e)}")
+ raise
+
+ # def _filter_allowed_comments(self, comments):
+ # """許可されたユーザーのコメントのみをフィルタリングする"""
+ # return [comment for comment in comments if comment.user.login in self.allowed_users]
+
+ def _display_comment_users(self, comments):
+ """コメントをしているユーザーの一覧を表示する"""
+ user_list = list(set(comment.user.login for comment in comments))
+ logger.info(f"コメントをしているユーザーの一覧:")
+ for user in user_list:
+ logger.info(f"- {user}")
+
+ def _validate_comments(self, comments):
+ comment_count = len(comments)
+ logger.info(f"イシュー #{self.settings.ISSUE_NUMBER} のコメントを {comment_count}件取得しました。")
+
+ if comment_count == 0 or comments[-1].body.strip().lower() != "ok":
+ logger.warning("最後のコメントが 'ok' ではありません。処理を終了します。")
+ # return False
+ return True
+
+ logger.info("'ok' コメントを確認しました。")
+ return True
+
+ def _find_previous_bot_comment(self, comments, bot_name="github-actions[bot]"):
+ logger.info("直前の github-actions[bot] のコメントを探しています。")
+ return next((comment for comment in reversed(comments[:-1]) if comment.user.login == "Sunwood-ai-labs"), None)
+
+ def _process_diffs(self, diffs):
+ modified_files = []
+ for file_path, diff_content in diffs.items():
+ logger.info(f"{file_path} のパッチ適用を試みます。")
+ patch_file_path = get_patch_file_path(file_path)
+ with open(patch_file_path, 'w', encoding='utf-8') as f:
+ f.write(diff_content)
+ print(diff_content)
+ if apply_patch(patch_file_path, file_path):
+ modified_files.append(file_path)
+ else:
+ logger.info(f"{file_path} のパッチ適用に失敗しました。LLMを使用して変更を生成します。")
+ self._apply_llm_changes(file_path, diff_content)
+ modified_files.append(file_path)
+ return modified_files
+
+ def _apply_llm_changes(self, file_path, diff_content):
+ with open(file_path, "r", encoding="utf-8") as f:
+ original_content = f.read()
+ modified_content = self.llm_service.apply_diff(original_content, diff_content)
+ with open(file_path, "w", encoding="utf-8") as f:
+ f.write(modified_content)
+
+ def _create_pull_request(self, issue, modified_files):
+ branch_name = f"suggestion-issue-{self.settings.ISSUE_NUMBER}"
+ self.git_service.setup_credentials()
+ self.git_service.create_branch(branch_name)
+ self.git_service.commit_changes(modified_files, f"🤖 #{self.settings.ISSUE_NUMBER} の提案を適用")
+ self.git_service.push_changes(branch_name)
+
+ comment = create_comment(modified_files, {file: open(file, "r").read() for file in modified_files})
+ self.github_service.add_comment(issue, comment)
+ logger.info(f"イシュー #{issue.number} にコメントを追加しました。")
+
+ pr_title = f"イシュー #{self.settings.ISSUE_NUMBER} の提案"
+ pr_body = f"このPRはイシュー #{self.settings.ISSUE_NUMBER} の提案を適用しています。"
+ self.github_service.create_pull_request(pr_title, pr_body, branch_name)
+
+def main():
+ applier = SuggestionApplier()
+ applier.run()
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/scripts/deep_comment.py b/.github/scripts/deep_comment.py
new file mode 100644
index 0000000..7724f11
--- /dev/null
+++ b/.github/scripts/deep_comment.py
@@ -0,0 +1,51 @@
+from loguru import logger
+import sys
+import os
+
+# Add the parent directory of 'scripts' to the Python path
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from config import get_settings
+from services.llm_service import LLMService
+from services.github_service import GitHubService
+
+def main():
+ logger.info("イシューの深堀処理を開始します。")
+
+ settings = get_settings()
+ llm_service = LLMService()
+ github_service = GitHubService()
+
+ logger.info("GitHubからイシューを取得中...")
+ issue = github_service.get_issue()
+ logger.info(f"イシュー「{issue.title}」を取得しました。")
+
+ logger.info("リポジトリの概要を読み込み中...")
+ with open(settings.REPOSITORY_SUMMARY_PATH, "r", encoding="utf-8") as f:
+ repository_summary = f.read()
+ logger.info("リポジトリの概要を読み込みました。")
+
+ prompt = f"""
+以下のGitHubイシューに対して、リポジトリの情報を踏まえた詳細なコメントを生成してください:
+
+イシュータイトル: {issue.title}
+イシュー本文: {issue.body}
+
+リポジトリの概要:
+{repository_summary}
+
+詳細なコメント:
+ """
+
+ logger.info("LLMを使用して深いコメントを生成中...")
+ deep_comment = llm_service.get_response(prompt)
+ logger.info("深いコメントの生成が完了しました。")
+
+ logger.info("生成したコメントをGitHubイシューに追加中...")
+ github_service.add_comment(issue, deep_comment)
+ logger.info("コメントをイシューに追加しました。")
+
+ logger.info("イシューの深堀処理が正常に完了しました。")
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/scripts/label_adder.py b/.github/scripts/label_adder.py
new file mode 100644
index 0000000..a0f3542
--- /dev/null
+++ b/.github/scripts/label_adder.py
@@ -0,0 +1,68 @@
+import sys
+import os
+import csv
+
+# Add the parent directory of 'scripts' to the Python path
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from loguru import logger
+from config import get_settings
+from services.llm_service import LLMService
+from services.github_service import GitHubService
+
+def load_labels_from_csv(csv_path):
+ labels = []
+ with open(csv_path, 'r', encoding='utf-8') as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ labels.append(row['label'])
+ return labels
+
+def main():
+ logger.info("イシューの処理を開始します。")
+
+ settings = get_settings()
+ llm_service = LLMService()
+ github_service = GitHubService()
+
+ logger.info("GitHubからイシューを取得しています...")
+ issue = github_service.get_issue()
+ logger.info(f"イシュー #{issue.number} を取得しました: {issue.title}")
+
+ logger.info("labels.csvからラベルのリストを読み込んでいます...")
+ csv_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'labels.csv')
+ existing_labels = load_labels_from_csv(csv_path)
+ logger.info(f"読み込まれたラベル: {', '.join(existing_labels)}")
+
+ logger.info("LLMを使用してイシューを分析し、ラベルを提案しています...")
+ suggested_labels = llm_service.analyze_issue(issue.title, issue.body, existing_labels)
+
+ label_list = [label.strip().replace("*", "") for label in suggested_labels.split(',')]
+ logger.info(f"提案されたラベル: {', '.join(label_list)}")
+
+ # 提案されたラベルを検証し、未登録のラベルをスキップ
+ validated_labels = []
+ skipped_labels = []
+ for label in label_list:
+ if label in existing_labels:
+ validated_labels.append(label)
+ else:
+ skipped_labels.append(label)
+
+ logger.info(f"検証済みのラベル: {', '.join(validated_labels)}")
+ if skipped_labels:
+ logger.warning(f"未登録のためスキップされたラベル: {', '.join(skipped_labels)}")
+
+ logger.info("検証済みのラベルをイシューに適用しています...")
+ github_service.add_labels(issue, validated_labels)
+
+ logger.info("イシューにコメントを追加しています...")
+ comment = f"@iris-s-coon が以下のラベルを提案し、適用しました:\n\n" + "\n".join([f"- {label}" for label in validated_labels])
+ if skipped_labels:
+ comment += f"\n\n以下のラベルは未登録のためスキップされました:\n\n" + "\n".join([f"- {label}" for label in skipped_labels])
+ github_service.add_comment(issue, comment)
+
+ logger.info("イシューの処理が完了しました。")
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/scripts/suggest_changes.py b/.github/scripts/suggest_changes.py
new file mode 100644
index 0000000..00f97f7
--- /dev/null
+++ b/.github/scripts/suggest_changes.py
@@ -0,0 +1,64 @@
+import sys
+import os
+
+# Add the parent directory of 'scripts' to the Python path
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from loguru import logger
+from config import get_settings
+from services.llm_service import LLMService
+from services.github_service import GitHubService
+import os
+
+def save_prompt(prompt, issue_number):
+ prompt_dir = "generated_prompts"
+ os.makedirs(prompt_dir, exist_ok=True)
+ file_path = os.path.join(prompt_dir, f"prompt_issue_{issue_number}.md")
+ with open(file_path, "w", encoding="utf-8") as f:
+ f.write(prompt)
+ logger.info(f"プロンプトを {file_path} に保存しました。")
+
+def main():
+ logger.info("変更提案の生成を開始します。")
+
+ settings = get_settings()
+ llm_service = LLMService()
+ github_service = GitHubService()
+
+ issue = github_service.get_issue()
+ logger.info(f"イシュー '{issue.title}' を取得しました。")
+
+ # リポジトリの概要を取得する
+ with open(settings.REPOSITORY_SUMMARY_PATH, "r", encoding="utf-8") as f:
+ repository_summary = f.read()
+ logger.info("リポジトリの概要を読み込みました。")
+
+ prompt = f"""
+以下のGitHubイシューに対して、リポジトリの情報を踏まえた具体的なコード変更提案を生成してください:
+
+イシュータイトル: {issue.title}
+イシュー本文: {issue.body}
+
+リポジトリの概要:
+{repository_summary}
+
+変更提案(diff形式で記述してください):
+```diff
+# ここにdiff形式の変更提案を記述
+```
+ """
+
+ # プロンプトを保存
+ save_prompt(prompt, issue.number)
+
+ logger.info("LLMにプロンプトを送信します。")
+ suggestion = llm_service.get_response(prompt)
+ logger.info("LLMから変更提案を受け取りました。")
+
+ github_service.add_comment(issue, f"## 変更提案\n\n{suggestion}")
+ logger.info("変更提案をイシューにコメントとして追加しました。")
+
+ logger.info("処理が正常に完了しました。")
+
+if __name__ == "__main__":
+ main()
diff --git a/.github/services/__init__.py b/.github/services/__init__.py
new file mode 100644
index 0000000..e362394
--- /dev/null
+++ b/.github/services/__init__.py
@@ -0,0 +1,3 @@
+from .llm_service import LLMService
+from .github_service import GitHubService
+from .git_service import GitService
diff --git a/.github/services/git_service.py b/.github/services/git_service.py
new file mode 100644
index 0000000..fc909a7
--- /dev/null
+++ b/.github/services/git_service.py
@@ -0,0 +1,35 @@
+import subprocess
+from typing import List
+from loguru import logger
+from config import get_settings
+
+class GitService:
+ def __init__(self):
+ self.settings = get_settings()
+
+ def setup_credentials(self):
+ repo_url = f"https://x-access-token:{self.settings.GITHUB_TOKEN}@github.com/{self.settings.GITHUB_REPOSITORY}.git"
+ subprocess.run(["git", "remote", "set-url", "origin", repo_url])
+ logger.info("Git credentials setup completed.")
+
+ def create_branch(self, branch_name: str):
+ subprocess.run(["git", "checkout", "-b", branch_name])
+ logger.info(f"ブランチ '{branch_name}' を作成しました。")
+
+ def commit_changes(self, file_paths: List[str], commit_message: str):
+ for file_path in file_paths:
+ subprocess.run(["git", "add", file_path])
+
+ cmd = ["git", "commit", "-m", commit_message]
+ result = subprocess.run(cmd, capture_output=True, text=True)
+ if result.returncode != 0:
+ logger.error(f"Commit failed: {result.stderr}")
+ raise RuntimeError("Git commit failed")
+ logger.info(f"変更をコミットしました: {commit_message}")
+
+ def push_changes(self, branch_name: str):
+ result = subprocess.run(["git", "push", "-u", "origin", branch_name], capture_output=True, text=True)
+ if result.returncode != 0:
+ logger.error(f"Push failed: {result.stderr}")
+ raise RuntimeError("Git push failed")
+ logger.info(f"変更を {branch_name} ブランチにプッシュしました。")
diff --git a/.github/services/github_service.py b/.github/services/github_service.py
new file mode 100644
index 0000000..c3baa26
--- /dev/null
+++ b/.github/services/github_service.py
@@ -0,0 +1,33 @@
+from github import Github
+from loguru import logger
+from config import get_settings
+
+class GitHubService:
+ def __init__(self):
+ self.settings = get_settings()
+ # self.g = Github(self.settings.GITHUB_TOKEN)
+ # self.g = Github(self.settings.YOUR_PERSONAL_ACCESS_TOKEN)
+ self.g = Github(self.settings.YOUR_PERSONAL_ACCESS_TOKEN_IRIS)
+ self.repo = self.g.get_repo(self.settings.GITHUB_REPOSITORY)
+ logger.debug(f"Using token: {self.settings.YOUR_PERSONAL_ACCESS_TOKEN[:5]}...")
+
+ def get_issue(self, issue_number: int = None):
+ issue_number = issue_number or self.settings.ISSUE_NUMBER
+ logger.debug(f"issue_number : {issue_number}")
+ return self.repo.get_issue(number=issue_number)
+
+ def get_comments(self, issue):
+ return list(issue.get_comments())
+
+ def add_comment(self, issue, comment):
+ issue.create_comment(comment)
+ logger.info(f"コメントを追加しました: \n{comment[:200]}...")
+
+ def add_labels(self, issue, labels):
+ issue.add_to_labels(*labels)
+ logger.info(f"ラベルを追加しました: {', '.join(labels)}")
+
+ def create_pull_request(self, title, body, head, base="main"):
+ pr = self.repo.create_pull(title=title, body=body, head=head, base=base)
+ logger.info(f"Pull Requestを作成しました: {pr.html_url}")
+ return pr
diff --git a/.github/services/llm_service.py b/.github/services/llm_service.py
new file mode 100644
index 0000000..c583ca8
--- /dev/null
+++ b/.github/services/llm_service.py
@@ -0,0 +1,52 @@
+from loguru import logger
+from litellm import completion
+from config import get_settings
+
+class LLMService:
+ def __init__(self):
+ self.settings = get_settings()
+ self.model = self.settings.LITELLM_MODEL
+
+ def get_response(self, prompt: str) -> str:
+ try:
+ response = completion(
+ model=self.model,
+ messages=[{"role": "user", "content": prompt}]
+ )
+ return response.choices[0].message.content.strip()
+ except Exception as e:
+ logger.error(f"LLMからのレスポンス取得中にエラーが発生しました: {str(e)}")
+ raise
+
+ def apply_diff(self, original_content: str, diff: str) -> str:
+ prompt = f"""```diff
+ {diff}
+ ```
+
+ 上記のdiffを適用した結果を、ファイル全体の内容として出力してください。
+ ファイルの内容:
+ ```
+ {original_content}
+ ```"""
+
+ return self.get_response(prompt)
+
+ def analyze_issue(self, issue_title: str, issue_body: str, existing_labels: list) -> str:
+ prompt = f"""
+ 以下のGitHubイシューを分析し、適切なラベルを提案してください:
+
+ タイトル: {issue_title}
+
+ 本文:
+ {issue_body}
+
+ 既存のラベルのリスト:
+ {', '.join(existing_labels)}
+
+ 上記の既存のラベルのリストから、このイシューに最も適切なラベルを最大3つ選んでください。
+ 選んだラベルをカンマ区切りで提案してください。既存のラベルにない新しいラベルは提案しないでください。
+
+ 回答は以下の形式でラベルのみを提供してください:
+ label1, label2, label3
+ """
+ return self.get_response(prompt)
diff --git a/.github/utils/__init__.py b/.github/utils/__init__.py
new file mode 100644
index 0000000..3072880
--- /dev/null
+++ b/.github/utils/__init__.py
@@ -0,0 +1 @@
+from .diff_utils import DiffUtils, process_diffs, save_files, create_comment
diff --git a/.github/utils/diff_utils.py b/.github/utils/diff_utils.py
new file mode 100644
index 0000000..219221f
--- /dev/null
+++ b/.github/utils/diff_utils.py
@@ -0,0 +1,83 @@
+import re
+from typing import Optional, Dict, List
+from bs4 import BeautifulSoup
+import markdown
+from loguru import logger
+
+class DiffUtils:
+ @staticmethod
+ def convert_md_to_html(md_content: str) -> str:
+ return markdown.markdown(md_content, extensions=['fenced_code', 'codehilite'])
+
+ @staticmethod
+ def extract_diff(comment_body: str) -> Optional[Dict[str, str]]:
+ logger.info("コメント本文から diff を抽出しています...")
+ html_content = DiffUtils.convert_md_to_html(comment_body)
+
+ soup = BeautifulSoup(html_content, 'html.parser')
+ diff_blocks = soup.find_all('pre', class_='codehilite')
+
+ if not diff_blocks:
+ logger.error("diff が見つかりません。")
+ return None
+
+ diffs = {}
+ for diff_block in diff_blocks:
+ diff_code = diff_block.find('code', class_='language-diff')
+ if diff_code:
+ diff_content = diff_code.get_text() + "\n"
+ file_name = DiffUtils.extract_file_name(diff_content)
+ if file_name:
+ diffs[file_name] = diff_content
+
+ if not diffs:
+ logger.error("有効な diff が見つかりません。")
+ return None
+
+ logger.info(f"抽出された diff: {len(diffs)} ファイル")
+ return diffs
+
+ @staticmethod
+ def extract_file_name(diff_content: str) -> Optional[str]:
+ file_name_match = re.search(r'^\+\+\+ b/(.+)$', diff_content, re.MULTILINE)
+ if file_name_match:
+ return file_name_match.group(1)
+ return None
+
+ @staticmethod
+ def extract_code_block_content(html_content: str) -> str:
+ """HTMLコンテンツからコードブロック内の内容を抽出する"""
+ soup = BeautifulSoup(html_content, 'html.parser')
+ code_block = soup.find('pre', class_='codehilite')
+ if code_block:
+ code = code_block.find('code')
+ if code:
+ return code.get_text().strip()
+ return ""
+
+def process_diffs(diffs: Dict[str, str], llm_service) -> Dict[str, str]:
+ modified_contents = {}
+ for file_name, diff in diffs.items():
+ with open(file_name, "r", encoding="utf-8") as f:
+ original_content = f.read()
+ modified_content = llm_service.apply_diff(original_content, diff)
+ html_content = DiffUtils.convert_md_to_html(modified_content)
+ extracted_content = DiffUtils.extract_code_block_content(html_content)
+ modified_contents[file_name] = extracted_content if extracted_content else modified_content
+ return modified_contents
+
+def save_files(modified_contents: Dict[str, str]) -> List[str]:
+ saved_files = []
+ for file_name, content in modified_contents.items():
+ with open(file_name, "w", encoding="utf-8") as f:
+ f.write(content)
+ logger.info(f"{file_name} に変更を保存しました。")
+ saved_files.append(file_name)
+ return saved_files
+
+def create_comment(modified_files: List[str], modified_contents: Dict[str, str]) -> str:
+ comment = "以下のファイルが変更されました:\n\n"
+ for file in modified_files:
+ comment += f"- {file}\n\n"
+ comment += f"```python\n{modified_contents[file]}\n```\n\n"
+ return comment
diff --git a/.github/utils/patch_utils.py b/.github/utils/patch_utils.py
new file mode 100644
index 0000000..7b74110
--- /dev/null
+++ b/.github/utils/patch_utils.py
@@ -0,0 +1,51 @@
+import tempfile
+import subprocess
+import os
+from loguru import logger
+import datetime
+
+def create_patch_directory(file_path):
+ # ベースとなるパッチディレクトリ
+ base_patches_dir = os.path.join(os.getcwd(), 'applied_patches')
+
+ # ファイルパスに基づいてサブディレクトリを作成
+ relative_dir = os.path.dirname(file_path)
+ patches_dir = os.path.join(base_patches_dir, relative_dir)
+
+ # ディレクトリが存在しない場合は作成
+ os.makedirs(patches_dir, exist_ok=True)
+
+ return patches_dir
+
+def apply_patch(patch_file_path, file_path):
+ # パッチを保存するディレクトリを作成
+ # patches_dir = create_patch_directory(file_path)
+
+ # 現在の日時をファイル名に使用
+ # now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
+ # patch_file_name = f"patch_{now}_{os.path.basename(file_path)}.diff"
+ # patch_file_path = os.path.join(patches_dir, patch_file_name)
+
+ # print(patch_content)
+
+ # パッチをローカルに保存
+ # with open(patch_file_path, 'w', encoding='utf-8') as f:
+ # f.write(patch_content)
+ logger.info(f"パッチを {patch_file_path} に保存しました。")
+
+ try:
+ # パッチを適用
+ cmd = ['git', 'apply', patch_file_path, '--ignore-whitespace']
+ logger.info("cmd : {}".format(" ".join(cmd)))
+ subprocess.run(cmd, check=True)
+ logger.info(f"{file_path} にパッチを適用しました。")
+ return True
+ except subprocess.CalledProcessError:
+ logger.error(f"{file_path} へのパッチ適用に失敗しました。")
+ return False
+
+def get_patch_file_path(file_path):
+ patches_dir = create_patch_directory(file_path)
+ now = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
+ patch_file_name = f"patch_{now}_{os.path.basename(file_path)}.diff"
+ return os.path.join(patches_dir, patch_file_name)
diff --git a/.github/utils/vis.py b/.github/utils/vis.py
new file mode 100644
index 0000000..98b2450
--- /dev/null
+++ b/.github/utils/vis.py
@@ -0,0 +1,24 @@
+import sys
+
+def visualize_invisible_chars(filename):
+ with open(filename, 'r', encoding='utf-8') as file:
+ content = file.read()
+
+ # 空白文字を可視化
+ content = content.replace(' ', '·')
+ # タブを可視化
+ content = content.replace('\t', '→ ')
+ # 改行を可視化
+ content = content.replace('\n', '↵\n')
+ # キャリッジリターンを可視化
+ content = content.replace('\r', '←')
+
+ print(f"Visualized content of {filename}:")
+ print(content)
+
+if __name__ == "__main__":
+ if len(sys.argv) != 2:
+ print("Usage: python script.py ")
+ sys.exit(1)
+
+ visualize_invisible_chars(sys.argv[1])
diff --git a/.github/workflows/apply-suggestion.yml b/.github/workflows/apply-suggestion.yml
new file mode 100644
index 0000000..3310f4a
--- /dev/null
+++ b/.github/workflows/apply-suggestion.yml
@@ -0,0 +1,42 @@
+name: 提案された変更の適用
+
+on:
+ issue_comment:
+ types: [created]
+
+jobs:
+ apply-changes:
+ runs-on: ubuntu-latest
+ if: ${{ github.event.issue.pull_request == null && github.event.comment.body == 'ok' }}
+ steps:
+ - name: リポジトリのチェックアウト
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+ token: ${{ secrets.YOUR_PERSONAL_ACCESS_TOKEN }}
+
+ - name: Git の設定
+ run: |
+ git config --global user.name 'github-actionsA[bot]'
+ git config --global user.email 'github-actionsA[bot]@users.noreply.github.com'
+
+ - name: Python のセットアップ
+ uses: actions/setup-python@v3
+ with:
+ python-version: '3.9'
+
+ - name: 依存関係のインストール
+ run: |
+ pip install -r requirements.txt
+
+ - name: スクリプトの実行
+ run: python .github/scripts/apply_suggestion.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ YOUR_PERSONAL_ACCESS_TOKEN: ${{ secrets.YOUR_PERSONAL_ACCESS_TOKEN }}
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ # OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
+ # ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ YOUR_PERSONAL_ACCESS_TOKEN_IRIS: ${{ secrets.YOUR_PERSONAL_ACCESS_TOKEN_IRIS }}
diff --git a/.github/workflows/issue-deep-comment.yml b/.github/workflows/issue-deep-comment.yml
new file mode 100644
index 0000000..53ff238
--- /dev/null
+++ b/.github/workflows/issue-deep-comment.yml
@@ -0,0 +1,88 @@
+name: イシューへの詳細コメントと変更提案
+
+on:
+ issues:
+ types: [opened]
+
+permissions:
+ issues: write
+ contents: read
+
+jobs:
+ comment-on-issue:
+ runs-on: ubuntu-latest
+ steps:
+ - name: リポジトリのチェックアウト
+ uses: actions/checkout@v2
+
+ - name: Python のセットアップ
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.x'
+
+ - name: SourceSage のインストール
+ run: |
+ python -m pip install --upgrade pip
+ pip install sourcesage
+
+ - name: SourceSage の実行
+ run: |
+ mkdir -p .SourceSageAssets/DOCUMIND/
+ sourcesage --ignore-file=".iris.SourceSageignore"
+
+ - name: 依存関係のインストール
+ run: |
+ pip install -r requirements.txt
+
+ - name: 詳細コメントの生成と追加
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
+ # ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ REPOSITORY_SUMMARY_PATH: .SourceSageAssets/DOCUMIND/Repository_summary.md
+ YOUR_PERSONAL_ACCESS_TOKEN: ${{ secrets.YOUR_PERSONAL_ACCESS_TOKEN }}
+ YOUR_PERSONAL_ACCESS_TOKEN_IRIS: ${{ secrets.YOUR_PERSONAL_ACCESS_TOKEN_IRIS }}
+ run: python .github/scripts/deep_comment.py
+
+ suggest-changes:
+ runs-on: ubuntu-latest
+ needs: comment-on-issue # comment-on-issue の後に実行
+ if: ${{ github.event.issue.pull_request == null }} # PR 以外にのみ適用
+ steps:
+ - name: リポジトリのチェックアウト
+ uses: actions/checkout@v2
+
+ - name: Python のセットアップ
+ uses: actions/setup-python@v2
+ with:
+ python-version: '3.x'
+
+ - name: SourceSage のインストール
+ run: |
+ python -m pip install --upgrade pip
+ pip install sourcesage
+
+ - name: SourceSage の実行
+ run: |
+ mkdir -p .SourceSageAssets/DOCUMIND/
+ sourcesage --ignore-file=".iris.SourceSageignore"
+
+ - name: 依存関係のインストール
+ run: |
+ pip install -r requirements.txt
+
+ - name: 変更提案の生成と追加
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ # OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
+ # ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ REPOSITORY_SUMMARY_PATH: .SourceSageAssets/DOCUMIND/Repository_summary.md
+ YOUR_PERSONAL_ACCESS_TOKEN: ${{ secrets.YOUR_PERSONAL_ACCESS_TOKEN }}
+ YOUR_PERSONAL_ACCESS_TOKEN_IRIS: ${{ secrets.YOUR_PERSONAL_ACCESS_TOKEN_IRIS }}
+ run: python .github/scripts/suggest_changes.py
diff --git a/.github/workflows/issue-review.yml b/.github/workflows/issue-review.yml
new file mode 100644
index 0000000..dfb7474
--- /dev/null
+++ b/.github/workflows/issue-review.yml
@@ -0,0 +1,39 @@
+name: イシュー自動レビュー
+
+on:
+ issues:
+ types: [opened]
+
+permissions:
+ issues: write
+ contents: read
+ pull-requests: write
+
+jobs:
+ review-issue:
+ runs-on: ubuntu-latest
+ steps:
+ - name: リポジトリのチェックアウト
+ uses: actions/checkout@v3
+
+ - name: Python のセットアップ
+ uses: actions/setup-python@v3
+ with:
+ python-version: '3.9'
+
+ - name: 依存関係のインストール
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+
+ - name: スクリプトの実行
+ run: python .github/scripts/label_adder.py
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_REPOSITORY: ${{ github.repository }}
+ ISSUE_NUMBER: ${{ github.event.issue.number }}
+ # OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+ GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
+ # ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+ YOUR_PERSONAL_ACCESS_TOKEN: ${{ secrets.YOUR_PERSONAL_ACCESS_TOKEN }}
+ YOUR_PERSONAL_ACCESS_TOKEN_IRIS: ${{ secrets.YOUR_PERSONAL_ACCESS_TOKEN_IRIS }}
diff --git a/.gitignore b/.gitignore
index b6b25b1..a454261 100644
--- a/.gitignore
+++ b/.gitignore
@@ -166,4 +166,5 @@ tmp2.md
.Gaiah.md
.SourceSageAssets
.aira/aira.Gaiah.md
-.harmon_ai/README_template.md
+.harmon_ai
+generated_prompts/
diff --git a/.harmon_ai/README_template.md b/.harmon_ai/README_template.md
deleted file mode 100644
index 643eb96..0000000
--- a/.harmon_ai/README_template.md
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
AIRA
-
- ~AI-Integrated Repository for Accelerated Development~
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [🌐 Website] •
- [🐱 GitHub]
- [🐦 Twitter] •
- [🍀 Official Blog]
-
-
-
-
-
-
->[!IMPORTANT]
->このリポジトリのリリースノートやREADME、コミットメッセージの9割近くは[claude.ai](https://claude.ai/)や[ChatGPT4](https://chatgpt.com/)を活用した[AIRA](https://github.com/Sunwood-ai-labs/AIRA), [SourceSage](https://github.com/Sunwood-ai-labs/SourceSage), [Gaiah](https://github.com/Sunwood-ai-labs/Gaiah), [HarmonAI_II](https://github.com/Sunwood-ai-labs/HarmonAI_II)で生成しています。
-
-## 🌟 Introduction
-
-## 🎥 Demo
-
-## 🚀 Getting Started
-
-## 📝 Updates
-
-## 🤝 Contributing
-
-## 📄 License
-
-## 🙏 Acknowledgements
diff --git a/.harmon_ai/config.yml b/.harmon_ai/config.yml
deleted file mode 100644
index 34de9e7..0000000
--- a/.harmon_ai/config.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-harmon_ai:
- environment:
- repo_name: "AIRA"
- owner_name: "Sunwood-ai-labs"
- package_name: "AIRA"
- icon_url: "https://huggingface.co/datasets/MakiAi/IconAssets/resolve/main/AIRA.png"
- title: "AIRA"
- subtitle: "~AI-Integrated Repository for Accelerated Development~"
- website_url: "https://hamaruki.com/"
- github_url: "https://github.com/Sunwood-ai-labs"
- twitter_url: "https://x.com/hAru_mAki_ch"
- blog_url: "https://hamaruki.com/"
-
- product:
- important_message_file: "important_template.md"
- sections_content_file: "sections_template.md"
- output_file: "README_template.md"
- cicd_file_path: "publish-to-pypi.yml"
- cicd_main_path: "publish-to-pypi.yml"
- github_cicd_dir: ".github/workflows"
-
- development:
- output_dir: "C:/Prj/AIRA-Sample/AIRA-Sample00/.harmon_ai"
-
- main:
- main_dir: "C:/Prj/AIRA-Sample/AIRA-Sample00/"
- replace_readme: true
diff --git a/.harmon_ai/important_template.md b/.harmon_ai/important_template.md
deleted file mode 100644
index 57c3801..0000000
--- a/.harmon_ai/important_template.md
+++ /dev/null
@@ -1 +0,0 @@
-このリポジトリのリリースノートやREADME、コミットメッセージの9割近くは[claude.ai](https://claude.ai/)や[ChatGPT4](https://chatgpt.com/)を活用した[AIRA](https://github.com/Sunwood-ai-labs/AIRA), [SourceSage](https://github.com/Sunwood-ai-labs/SourceSage), [Gaiah](https://github.com/Sunwood-ai-labs/Gaiah), [HarmonAI_II](https://github.com/Sunwood-ai-labs/HarmonAI_II)で生成しています。
\ No newline at end of file
diff --git a/.harmon_ai/publish-to-pypi.yml b/.harmon_ai/publish-to-pypi.yml
deleted file mode 100644
index 42b2a45..0000000
--- a/.harmon_ai/publish-to-pypi.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-name: Publish Python Package
-
-on:
- push:
- tags:
- - '*'
-jobs:
- publish:
- runs-on: ubuntu-latest
- environment:
- name: pypi
- url: https://pypi.org/p/AIRA
- permissions:
- id-token: write
- steps:
- - uses: actions/checkout@v3
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: '3.x'
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install build
- - name: Build package
- run: python -m build
- - name: Publish package
- uses: pypa/gh-action-pypi-publish@release/v1
- with:
- skip-existing: true
\ No newline at end of file
diff --git a/.harmon_ai/sections_template.md b/.harmon_ai/sections_template.md
deleted file mode 100644
index f7f05ff..0000000
--- a/.harmon_ai/sections_template.md
+++ /dev/null
@@ -1,13 +0,0 @@
-## 🌟 Introduction
-
-## 🎥 Demo
-
-## 🚀 Getting Started
-
-## 📝 Updates
-
-## 🤝 Contributing
-
-## 📄 License
-
-## 🙏 Acknowledgements
\ No newline at end of file
diff --git a/.iris.SourceSageignore b/.iris.SourceSageignore
new file mode 100644
index 0000000..9ddddf0
--- /dev/null
+++ b/.iris.SourceSageignore
@@ -0,0 +1,57 @@
+.git
+__pycache__
+LICENSE
+output.md
+assets
+Style-Bert-VITS2
+output
+streamlit
+SourceSage.md
+data
+.gitignore
+.SourceSageignore
+*.png
+Changelog
+SourceSageAssets
+SourceSageAssetsDemo
+__pycache__
+.pyc
+**/__pycache__/**
+modules\__pycache__
+.svg
+sourcesage.egg-info
+.pytest_cache
+dist
+build
+.env
+example
+
+.gaiah.md
+.Gaiah.md
+tmp.md
+tmp2.md
+.SourceSageAssets
+tests
+template
+aira.egg-info
+aira.Gaiah.md
+README_template.md
+
+.SourceSageAssets
+issue_creator.log
+.aira
+
+app.py
+style.css
+utils.py
+.cache
+converted_comment.html
+modified_README.html
+modified_README.md
+
+generated_prompts
+temp.patch
+applied_patches
+.github
+.harmon_ai
+.gaiah
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..d70b1e8
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,64 @@
+## CONTRIBUTING.md
+
+# コントリビューションガイドライン
+
+まず、AIRAに興味を持っていただきありがとうございます! 🙏
+
+AIRAは、オープンソースプロジェクトであり、皆さんのコントリビューションを歓迎します! 😄
+
+バグの報告、機能のリクエスト、ドキュメントの改善など、どのような形でも構いません。
+
+## コントリビューションの方法
+
+### Issueの作成
+
+バグの報告や機能のリクエストを行う場合は、[Issueページ](https://github.com/Sunwood-ai-labs/AIRA/issues)に新しいIssueを作成してください。
+
+Issueを作成する際には、以下の内容を記載するようにしてください。
+
+* Issueのタイトル
+* Issueの内容
+* 再現手順(バグの場合)
+* 期待される動作
+* 実際の動作
+* スクリーンショット(必要であれば)
+
+### Pull Requestの作成
+
+AIRAのコードを修正したり、新しい機能を追加する場合は、Pull Requestを作成してください。
+
+Pull Requestを作成する際には、以下の手順に従ってください。
+
+1. リポジトリをフォークする
+2. フォークしたリポジトリをクローンする
+3. 新しいブランチを作成する
+4. コードを修正または機能を追加する
+5. コミットしてプッシュする
+6. Pull Requestを作成する
+
+Pull Requestを作成する際には、以下の内容を記載するようにしてください。
+
+* Pull Requestのタイトル
+* Pull Requestの内容
+* 関連するIssue
+* スクリーンショット(必要であれば)
+
+### IRISを使った開発支援
+
+本リポジトリでは、[IRIS](https://github.com/Sunwood-ai-labs/IRIS) を利用した開発支援を行っています。
+
+具体的には、以下の機能が自動化されています。
+
+* **ラベルの自動付与:** イシューの内容に基づいて、適切なラベルが自動的に付与されます。
+* **変更提案の自動生成:** イシューの内容に基づいて、コードの変更提案が自動生成されます。
+* **イシューの詳細化:** イシューの内容に基づいて、詳細なコメントが自動生成されます。
+
+これらの機能により、開発者はより効率的に開発を進めることができます。
+例えば、イシューの詳細化機能によって、開発者はイシューの内容を理解するために必要な情報を迅速に得ることができ、より早く解決策を見つけることができます。
+また、変更提案機能によって、開発者は修正案を検討する時間を短縮し、より多くの時間を他のタスクに充てることができます。
+
+これらの機能は、開発者の作業効率向上に大きく貢献します。
+
+## ライセンス
+
+AIRAは、[MITライセンス](https://opensource.org/licenses/MIT)の下で公開されています。
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..c5891c2
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,13 @@
+# Dockerfile
+FROM python:3.9
+
+WORKDIR /app
+
+RUN apt-get update && apt-get install -y git
+
+# Git設定をDockerfile内で行う
+RUN git config --global user.email 'yukihiko.fuyuki@gmail.com' && \
+ git config --global user.name 'Yukihiko'
+
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..7df1042
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Maki
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 643eb96..bdc32c6 100644
--- a/README.md
+++ b/README.md
@@ -34,16 +34,131 @@
>[!IMPORTANT]
>このリポジトリのリリースノートやREADME、コミットメッセージの9割近くは[claude.ai](https://claude.ai/)や[ChatGPT4](https://chatgpt.com/)を活用した[AIRA](https://github.com/Sunwood-ai-labs/AIRA), [SourceSage](https://github.com/Sunwood-ai-labs/SourceSage), [Gaiah](https://github.com/Sunwood-ai-labs/Gaiah), [HarmonAI_II](https://github.com/Sunwood-ai-labs/HarmonAI_II)で生成しています。
-## 🌟 Introduction
+## 🌟 はじめに
-## 🎥 Demo
+AIRAは、リポジトリの管理や開発を加速するためのAIインテグレーション開発ツールです。
+Githubリポジトリの作成、ローカルリポジトリの初期化、コミットメッセージの自動生成、READMEの自動生成などを行うことができます。
-## 🚀 Getting Started
+開発者の皆さんは、AIRAを使うことで以下のようなメリットを得ることができます。
-## 📝 Updates
+- リポジトリ管理の自動化による開発の加速
+- コミットメッセージやREADMEの自動生成による手間の削減
+- 開発者同士のコミュニケーションの円滑化
-## 🤝 Contributing
+AIRAは、開発者の皆さんの開発効率を高め、よりクリエイティブな活動に集中できるようサポートします。
-## 📄 License
+## 🚀 インストール方法
-## 🙏 Acknowledgements
+AIRAは、以下の手順でインストールすることができます。
+
+1. Python 3.7以上がインストールされていることを確認してください。
+2. ターミナルまたはコマンドプロンプトを開きます。
+3. 以下のコマンドを実行して、AIRAをインストールします。
+
+ ```bash
+ pip install aira
+ ```
+
+これで、AIRAのインストールは完了です。
+`aira --help`コマンドを実行して、使い方を確認してみましょう。
+
+## 📝 使い方
+
+### 環境設定
+
+`.env`ファイルを作成し、必要な設定を記述します。
+`.env.example`をコピーして使用することができます。
+
+```bash
+cp .env.example .env
+```
+
+主な設定項目:
+```plaintext
+# AIRAの基本設定
+GAIAH_RUN=true
+COMMIT_MSG_PATH=.Gaiah.md
+
+# LLMの設定
+LLM_MODEL=gemini/gemini-1.5-pro-latest
+GEMINI_API_KEY=your-api-key-here
+
+# GitHubの設定(必要な場合のみ)
+GITHUB_ACCESS_TOKEN=your-github-token-here
+```
+
+### コミットメッセージの自動生成
+
+AIRAには2つのコミット生成モードがあります:
+
+1. 基本的なコミットモード:
+```bash
+aira --mode commit
+```
+
+2. SourceSageを使用した高度なコミットモード:
+```bash
+aira --mode sourcesage commit --ss-model-name="gemini/gemini-1.5-flash-002"
+```
+
+このコマンドを実行すると、以下の処理が行われます:
+
+1. 変更内容の取得と解析
+2. AIによるコミットメッセージの自動生成
+3. ファイルのステージング
+4. コミットの実行
+
+#### コミットモードの違い
+
+- **基本モード(--mode commit)**
+ - シンプルな変更に適しています
+ - 高速な処理が可能
+ - 基本的なコミットメッセージを生成
+
+- **SourceSageモード(--mode sourcesage commit)**
+ - 複雑な変更に適しています
+ - より詳細なコード解析を実行
+ - 高品質なコミットメッセージを生成
+ - カスタムモデルの指定が可能(--ss-model-name)
+
+## 🤝 コントリビューション
+
+AIRAは、オープンソースプロジェクトです。
+皆さんのコントリビューションを歓迎します!
+
+バグ報告や機能リクエストがある場合は、[Issueページ](https://github.com/Sunwood-ai-labs/AIRA/issues)からお願いします。
+また、プルリクエストも大歓迎です。
+
+コントリビューションガイドラインについては、[CONTRIBUTING.md](CONTRIBUTING.md)を参照してください。
+
+## 開発者用
+
+### SourceSageリリースノートを作成コマンド
+
+```shell
+sourcesage --mode DocuMind --docuMind-model "gemini/gemini-1.5-pro-latest" --docuMind-db ".SourceSageAssets\DOCUMIND\Repository_summary.md" --docuMind-release-report ".SourceSageAssets\RELEASE_REPORT\Report_v0.2.2.md" --docuMind-changelog ".SourceSageAssets\Changelog\CHANGELOG_release_0.2.2.md" --docuMind-output ".SourceSageAssets/DOCUMIND/RELEASE_NOTES_v0.2.2.md" --docuMind-prompt-output ".SourceSageAssets/DOCUMIND/_PROMPT_v0.2.2.md" --repo-name "SourceSage" --repo-version "v0.2.2"
+```
+
+## 📄 ライセンス
+
+AIRAは、[MITライセンス](https://opensource.org/licenses/MIT)の下で公開されています。
+詳細は、[LICENSE](LICENSE)ファイルを参照してください。
+
+## 🙏 謝辞
+
+AIRAの開発にあたり、以下のオープンソースプロジェクトを活用させていただきました。
+この場を借りて、お礼申し上げます。
+
+- [SourceSage](https://github.com/Sunwood-ai-labs/SourceSage)
+- [Gaiah](https://github.com/Sunwood-ai-labs/Gaiah)
+- [HarmonAI_II](https://github.com/Sunwood-ai-labs/HarmonAI_II)
+
+また、AIRAの開発には、以下のAIモデルを活用させていただきました。
+
+- [claude.ai](https://claude.ai/)
+- [ChatGPT4](https://chat.openai.com/)
+
+最後に、AIRAを使ってくださる開発者の皆さんに感謝いたします。
+皆さんのフィードバックを元に、より良いツールを目指して開発を続けていきます。
+
+これからもAIRAをよろしくお願いします!
diff --git a/aira/__init__.py b/aira/__init__.py
index 5717afa..40d89bf 100644
--- a/aira/__init__.py
+++ b/aira/__init__.py
@@ -1,5 +1,5 @@
from .cli import main
-__version__ = '0.1.0'
+__version__ = '0.6.0'
-__all__ = ['aira', 'main', '__version__']
\ No newline at end of file
+__all__ = ['aira', 'main', '__version__']
diff --git a/aira/aira.py b/aira/aira.py
index 2e365a6..d817dcf 100644
--- a/aira/aira.py
+++ b/aira/aira.py
@@ -1,10 +1,84 @@
from loguru import logger
-from .gaiah_repo import GaiahRepo
-from .gaiah_commit import GaiahCommit
+from .repository_manager import RepositoryManager
+from .commit_manager import CommitManager
+
+from litellm import completion
+import os
+
+# SourceSageのモジュールをインポート
+from sourcesage.core import SourceSage
+from sourcesage.modules.ReleaseDiffReportGenerator import GitDiffGenerator, MarkdownReportGenerator
+from sourcesage.modules.CommitCraft import CommitCraft
+from sourcesage.modules.DocuMind import DocuMind
+from sourcesage.modules.IssueWize import IssueWize
+
+from dotenv import load_dotenv
+dotenv_path=os.path.join(os.getcwd(), '.env')
+logger.debug(f"dotenv_path : {dotenv_path}")
+load_dotenv(dotenv_path=dotenv_path, verbose=True, override=True)
class Aira:
+ """AIRAメインクラス"""
def __init__(self, args):
- pass
+ """初期化"""
+ self.args = args
+ self.model = args.model
+
+ # リポジトリとコミットマネージャーを初期化
+ self.repo_manager = RepositoryManager()
+ self.commit_manager = CommitManager({"commit_msg_path": os.getenv('COMMIT_MSG_PATH', '.SourceSageAssets/COMMIT_CRAFT/llm_output.md')})
+
+ def run_sourcesage(self):
+ """SourceSageの各モジュールを実行する"""
+ args = self.args
+
+ # -----------------------------------------------
+ # SourceSageの実行
+ if 'all' in args.ss_mode or 'Sage' in args.ss_mode:
+ logger.info("SourceSageを起動します...")
+ sourcesage = SourceSage(args.ss_output, args.repo, args.owner, args.repository,
+ args.ignore_file, args.language_map, args.changelog_start_tag,
+ args.changelog_end_tag)
+ sourcesage.run()
+
+ # -----------------------------------------------
+ # IssueWizeを使用してIssueを作成
+ if 'all' in args.ss_mode or 'IssueWize' in args.ss_mode:
+ issuewize = IssueWize(model=args.issuewize_model)
+ if args.issue_summary and args.project_name and args.repo_overview_file:
+ logger.info("IssueWizeを使用してIssueを作成します...")
+ issuewize.create_optimized_issue(args.issue_summary, args.project_name,
+ args.milestone_name, args.repo_overview_file)
+ else:
+ logger.warning("IssueWizeの実行に必要なパラメータが不足しています。")
+
+ # -----------------------------------------------
+ # レポートの生成
+ if 'all' in args.ss_mode or 'GenerateReport' in args.ss_mode:
+ logger.info("git diff レポートの生成を開始します...")
+ git_diff_generator = GitDiffGenerator(args.repo_path, args.git_fetch_tags, args.git_tag_sort, args.git_diff_command)
+ diff, latest_tag, previous_tag = git_diff_generator.get_git_diff()
+
+ if diff is not None:
+ report_file_name = args.report_file_name.format(latest_tag=latest_tag)
+ os.makedirs(args.ss_output_path, exist_ok=True)
+ output_path = os.path.join(args.ss_output_path, report_file_name)
+
+ markdown_report_generator = MarkdownReportGenerator(diff, latest_tag, previous_tag,
+ args.report_title, args.report_sections,
+ output_path)
+ markdown_report_generator.generate_markdown_report()
+
+ # -----------------------------------------------
+ # CommitCraftを使用してLLMにステージ情報を送信し、コミットメッセージを生成
+ if 'all' in args.ss_mode or 'CommitCraft' in args.ss_mode:
+ stage_info_file = args.stage_info_file
+ llm_output_file = os.path.join(args.commit_craft_output, args.llm_output)
+ os.makedirs(args.commit_craft_output, exist_ok=True)
+ commit_craft = CommitCraft(args.ss_model_name, stage_info_file, llm_output_file)
+ commit_craft.generate_commit_messages()
+
+ # コミットメッセージが生成されたら自動コミットを実行
+ logger.info("自動コミットを実行します...")
+ self.commit_manager.process_commits()
- def run(self):
- pass
\ No newline at end of file
diff --git a/aira/cli.py b/aira/cli.py
index 29cd474..ea32190 100644
--- a/aira/cli.py
+++ b/aira/cli.py
@@ -1,96 +1,48 @@
import argparse
from art import tprint
from loguru import logger
-from gaiah.gaiah import Gaiah
-from gaiah.cli import load_config
-
-from harmon_ai.harmon_ai import HarmonAI
-
import sys
import os
import shutil
-import yaml
+from dotenv import load_dotenv
+from .aira import Aira
+from sourcesage.cli import add_arguments as sourcesage_add_arguments
logger.configure(
handlers=[
{
"sink": sys.stderr,
- "format": "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level:<8} | {name:<45}:{line:<5} | {message}",
+ "format": "{time:YYYY-MM-DD HH:mm:ss.SSS} | {level:<8} | {name:<35}:{function:<35}:{line:<5} | {message}",
"colorize": True,
}
]
)
def parse_arguments():
- """
- コマンドライン引数を解析する
- """
- parser = argparse.ArgumentParser(description='Gaiah - シンプルなGitリポジトリ管理ツール')
-
- parser.add_argument('--config', default='.aira/config.yml', help='設定ファイルのパス')
-
+ """コマンドライン引数を解析する"""
+ load_dotenv()
+ default_model = os.getenv('LLM_MODEL', 'gemini/gemini-1.5-pro-latest')
+ parser = argparse.ArgumentParser(description='AIRA - Automated Source Code Analysis Tool')
+ parser.add_argument('--mode', nargs='+', default=['commit'],
+ help='処理モード(commit: 自動コミット, sourcesage: SourceSage実行)')
+ parser.add_argument('--model', default=default_model, help='使用するLLMモデル名')
+ sourcesage_add_arguments(parser)
return parser.parse_args()
def main():
+ """メイン処理"""
tprint("! Welcome to AIRA !")
-
- # .aira/config.ymlが存在するかチェック
args = parse_arguments()
- aira_config_path = args.config
- if not os.path.exists(aira_config_path):
- # aira\template\config.ymlをコピー
- template_config_path = "aira/template/config.yml"
- os.makedirs(os.path.dirname(aira_config_path), exist_ok=True)
- shutil.copy(template_config_path, aira_config_path)
- logger.info(f"{aira_config_path}が見つかりませんでした。{template_config_path}からコピーしました。")
- else:
- logger.info(f"{aira_config_path}が見つかりました。")
-
- # .aira/config.ymlからconfig_pathを取得
- with open(aira_config_path, "r") as f:
- aira_config = yaml.safe_load(f)
-
-
- # ------------------
- # make abst
- #
-
- # ------------------
- # gaiah init
- #
- if(aira_config["aira"]["gaiah"]["run"]):
- logger.info("gaiah init section ....")
- gaiah_config_path = aira_config["aira"]["gaiah"]["develop"]["config_path"]
- gaiah_config = load_config(gaiah_config_path)
- logger.info(f"Gaiah config path : {gaiah_config_path}")
- if not os.path.exists(os.path.join(gaiah_config["gaiah"]["local"]["repo_dir"], ".git")):
- logger.info("初期化を行います...")
- gaiah_config_path = aira_config["aira"]["gaiah"]["init"]["config_path"]
- gaiah_config = load_config(gaiah_config_path)
+ aira = Aira(args=args)
+
+ for mode in args.mode:
+ if mode == "commit":
+ logger.info("mode is << commit >>")
+ aira.commit_manager.process_commits()
+ elif mode == "sourcesage":
+ logger.info("mode is << sourcesage >>")
+ aira.run_sourcesage()
else:
- logger.info(".gitが発見されました...")
-
- tprint("-- Gaiah --")
- logger.info("Gaiahの処理を開始します...")
- gaiah = Gaiah(gaiah_config)
- gaiah.run()
-
- # ------------------
- # Harmon AI
- #
- if(aira_config["aira"]["harmon_ai"]["run"]):
- logger.info("Harmon AIの処理を開始します...")
- harmon_ai = HarmonAI()
- harmon_ai.run()
-
- # ------------------
- # gaiah run
- #
- logger.info("gaiah run section ....")
- gaiah_config_path = aira_config["aira"]["gaiah"]["develop"]["config_path"]
- gaiah_config = load_config(gaiah_config_path)
- gaiah = Gaiah(gaiah_config)
- gaiah.run()
-
+ logger.warning(f"Unknown mode: {mode}")
if __name__ == "__main__":
- main()
\ No newline at end of file
+ main()
diff --git a/aira/commit_manager.py b/aira/commit_manager.py
new file mode 100644
index 0000000..e567404
--- /dev/null
+++ b/aira/commit_manager.py
@@ -0,0 +1,186 @@
+import os
+import re
+from loguru import logger
+from .utils import run_command, tqdm_sleep
+from .managers.staging_manager import StagingManager
+from .managers.branch_manager import BranchManager
+
+class CommitManager:
+ FILENAME_REGEX = r'(?m)^###\s(.+)'
+ COMMIT_MESSAGE_REGEX = r'```commit-msg\n(.*?)\n```'
+
+ def __init__(self, config):
+ self.config = config
+ self.repo_dir = "./"
+ self.commit_msg_path = config.get('commit_msg_path', '.Gaiah.md')
+ self.staging_manager = StagingManager(self.repo_dir)
+ self.branch_manager = BranchManager(self.repo_dir)
+
+ def commit_changes(self, commit_message, branch_name=None):
+ """変更をコミットする"""
+ try:
+ if branch_name:
+ self.branch_manager.checkout_branch(branch_name, create=True)
+
+ # Gitの設定を確認
+ try:
+ user_name = run_command(["git", "config", "user.name"], cwd=self.repo_dir)
+ user_email = run_command(["git", "config", "user.email"], cwd=self.repo_dir)
+
+ if not user_name or not user_email:
+ logger.warning("Git user configuration is missing")
+ run_command(["git", "config", "user.name", "AIRA Bot"], cwd=self.repo_dir)
+ run_command(["git", "config", "user.email", "aira@example.com"], cwd=self.repo_dir)
+ logger.info("Set default Git configuration")
+ except Exception as e:
+ logger.warning(f"Error checking Git configuration: {e}")
+ run_command(["git", "config", "user.name", "AIRA Bot"], cwd=self.repo_dir)
+ run_command(["git", "config", "user.email", "aira@example.com"], cwd=self.repo_dir)
+
+ # ステージングされたファイルがあるか確認
+ staged_files = run_command(["git", "diff", "--staged", "--name-only"], cwd=self.repo_dir)
+ if not staged_files.strip():
+ logger.warning("No staged changes to commit")
+ return False
+
+ # コミットメッセージファイルを作成
+ commit_message_file = os.path.join(self.repo_dir, ".aira_commit_message.txt")
+ try:
+ with open(commit_message_file, "w", encoding="utf-8") as f:
+ f.write(commit_message)
+ except Exception as e:
+ logger.error(f"Error writing commit message file: {e}")
+ return False
+
+ try:
+ # コミットを実行
+ output = run_command(["git", "commit", "-F", commit_message_file], cwd=self.repo_dir, check=False)
+ if "nothing to commit" in output.lower():
+ logger.warning("Nothing to commit")
+ return False
+
+ logger.success("Committed changes.")
+ return True
+ except Exception as commit_error:
+ logger.error(f"Error during commit operation: {commit_error}")
+ return False
+ finally:
+ # コミットメッセージファイルを削除
+ try:
+ if os.path.exists(commit_message_file):
+ os.remove(commit_message_file)
+ except Exception as e:
+ logger.warning(f"Error removing commit message file: {e}")
+
+ except Exception as e:
+ logger.error(f"Error while committing changes: {e}")
+ return False
+
+ def process_commits(self, branch_name=None):
+ """コミットメッセージファイルからコミットを処理する"""
+ content = self._read_commit_messages()
+ if not content:
+ return
+
+ # ブランチごとのコミット内容を整理
+ branch_commits = {}
+ branch_sections = re.split(r'(?m)^##\s(.+)', content)[1:]
+ self.staging_manager.unstage_files()
+ tqdm_sleep(5)
+
+ # 各セクションをブランチごとにグループ化
+ for i in range(0, len(branch_sections), 2):
+ branch = branch_sections[i].strip()
+ content = branch_sections[i + 1]
+ if branch not in branch_commits:
+ branch_commits[branch] = []
+ branch_commits[branch].append(content)
+
+ # ブランチごとにまとめて処理
+ for branch, contents in branch_commits.items():
+ try:
+ # ブランチ名とコンテンツを直接渡す
+ current_branch = branch_name or branch
+ branch_content = "\n".join(contents)
+ if current_branch != "develop":
+ self.branch_manager.checkout_branch(current_branch, create=True)
+ self._process_commits_for_branch(branch_content, current_branch)
+ except Exception as e:
+ logger.error(f"Error processing branch {branch}: {e}")
+
+ def _read_commit_messages(self):
+ try:
+ with open(self.commit_msg_path, "r", encoding="utf-8") as file:
+ return file.read()
+ except FileNotFoundError:
+ logger.warning(f"Commit messages file not found: {self.commit_msg_path}")
+ logger.info(f"Creating an empty commit messages file: {self.commit_msg_path}")
+ with open(self.commit_msg_path, "w", encoding="utf-8") as file:
+ file.write("")
+ return None
+
+ def _process_commits_for_branch(self, branch_content, branch_name):
+ """ブランチ内の全コミットを処理する"""
+ try:
+ commits = re.split(self.FILENAME_REGEX, branch_content)
+ if commits and not commits[0].strip():
+ commits = commits[1:]
+
+ for j in range(0, len(commits), 2):
+ filename = commits[j].strip()
+ commit_message_section = commits[j + 1]
+ self.process_commit_section(filename, commit_message_section, branch_name)
+
+ # developブランチ以外の場合、コミットが成功したらマージを試みる
+ if branch_name != "develop":
+ try:
+ self.branch_manager.merge_to_develop(branch_name)
+ except Exception as merge_error:
+ logger.error(f"Failed to merge branch {branch_name} to develop: {merge_error}")
+ except Exception as e:
+ logger.error(f"Error processing commits for branch {branch_name}: {e}")
+ raise
+ def process_commit_section(self, filename, commit_message_section, branch_name):
+ """
+ コミットセクションを処理する
+ """
+ commit_message_match = re.search(self.COMMIT_MESSAGE_REGEX, commit_message_section, re.DOTALL)
+ if commit_message_match:
+ commit_message = commit_message_match.group(1)
+ msg = f"{'-'*10} Commit message: [{branch_name}][{filename}]{'-'*10} "
+ logger.info(f"{msg}")
+ for commit_msg in commit_message.split("\n"):
+ logger.info(f"{commit_msg}")
+ commit_message = commit_message.strip()
+ logger.info(f"{'-'*len(msg)}")
+ else:
+ logger.warning("No commit message found in the commit section. Skipping...")
+ return
+
+ self.process_file(filename, commit_message, branch_name)
+
+ def process_file(self, filename, commit_message, branch_name=None):
+ """
+ ファイルを処理する
+ """
+ try:
+ # ファイルの存在チェックとステージング
+ if os.path.exists(os.path.join(self.repo_dir, filename)):
+ logger.info("file is modified")
+ self.staging_manager.stage_file(filename, "modified")
+ else:
+ logger.info("file is deleted")
+ self.staging_manager.stage_file(filename, "deleted")
+
+ # ステージされたファイルの確認
+ changed_files = run_command(["git", "diff", "--staged", "--name-only"], cwd=self.repo_dir).splitlines()
+
+ logger.info(f"changed_files is {changed_files}")
+ if filename in changed_files:
+ self.commit_changes(commit_message, branch_name)
+ else:
+ logger.info(f"No changes detected in file: {filename}")
+
+ except Exception as e:
+ logger.error(f"Error while processing file: {filename} - {e}")
+ raise
diff --git a/aira/managers/__init__.py b/aira/managers/__init__.py
new file mode 100644
index 0000000..47c1628
--- /dev/null
+++ b/aira/managers/__init__.py
@@ -0,0 +1,7 @@
+from .branch_manager import BranchManager
+from .staging_manager import StagingManager
+
+__all__ = [
+ 'BranchManager',
+ 'StagingManager'
+]
diff --git a/aira/managers/branch_manager.py b/aira/managers/branch_manager.py
new file mode 100644
index 0000000..2872573
--- /dev/null
+++ b/aira/managers/branch_manager.py
@@ -0,0 +1,112 @@
+from loguru import logger
+from ..utils import run_command
+
+class BranchManager:
+ def __init__(self, repo_dir="./"):
+ self.repo_dir = repo_dir
+
+ def get_current_branch(self):
+ """現在のブランチ名を取得する"""
+ return run_command(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=self.repo_dir).strip()
+
+ def branch_exists(self, branch_name):
+ """指定したブランチが存在するかチェックする"""
+ try:
+ run_command(["git", "rev-parse", "--verify", branch_name], cwd=self.repo_dir)
+ return True
+ except Exception:
+ return False
+
+ def has_uncommitted_changes(self):
+ """未コミットの変更があるかチェックする"""
+ try:
+ status = run_command(["git", "status", "--porcelain"], cwd=self.repo_dir)
+ return bool(status.strip())
+ except Exception:
+ return False
+
+ def stash_changes(self):
+ """現在の変更を一時保存する"""
+ if self.has_uncommitted_changes():
+ logger.info("Stashing uncommitted changes...")
+ run_command(["git", "stash", "save", "AIRA: Temporary stash"], cwd=self.repo_dir)
+
+ def pop_stashed_changes(self):
+ """一時保存した変更を復元する"""
+ try:
+ run_command(["git", "stash", "pop"], cwd=self.repo_dir)
+ logger.info("Restored stashed changes")
+ except Exception as e:
+ logger.warning(f"No stashed changes to restore or conflict occurred: {e}")
+
+ def checkout_branch(self, branch_name, create=False):
+ """ブランチをチェックアウトする"""
+ try:
+ current = self.get_current_branch()
+ if current == branch_name:
+ logger.info(f"Already on branch {branch_name}")
+ return
+
+ # 未コミットの変更がある場合は一時保存
+ had_changes = self.has_uncommitted_changes()
+ if had_changes:
+ self.stash_changes()
+
+ try:
+ if create and not self.branch_exists(branch_name):
+ run_command(["git", "checkout", "-b", branch_name], cwd=self.repo_dir)
+ else:
+ if not self.branch_exists(branch_name):
+ run_command(["git", "checkout", "-b", branch_name], cwd=self.repo_dir)
+ else:
+ run_command(["git", "checkout", branch_name], cwd=self.repo_dir)
+ logger.success(f"Checked out branch: {branch_name}")
+ finally:
+ # 一時保存した変更を復元
+ if had_changes:
+ self.pop_stashed_changes()
+
+ except Exception as e:
+ logger.error(f"Error while checking out branch: {e}")
+ raise
+
+ def merge_to_develop(self, source_branch):
+ """developブランチへのマージと元ブランチの削除を行う"""
+ if not self.branch_exists(source_branch):
+ logger.warning(f"Branch {source_branch} does not exist. Skipping merge.")
+ return
+
+ try:
+ current_branch = self.get_current_branch()
+
+ # 未コミットの変更がある場合は一時保存
+ had_changes = self.has_uncommitted_changes()
+ if had_changes:
+ self.stash_changes()
+
+ try:
+ self.checkout_branch("develop")
+ run_command(["git", "merge", "--no-ff", source_branch], cwd=self.repo_dir)
+ logger.success(f"Successfully merged {source_branch} into develop")
+
+ if source_branch != "main":
+ run_command(["git", "branch", "-d", source_branch], cwd=self.repo_dir)
+ logger.success(f"Deleted branch: {source_branch}")
+
+ except Exception as merge_error:
+ logger.error(f"Merge conflict occurred: {merge_error}")
+ run_command(["git", "merge", "--abort"], cwd=self.repo_dir)
+ raise
+ finally:
+ if current_branch != source_branch or source_branch == "main":
+ self.checkout_branch(current_branch)
+ else:
+ self.checkout_branch("develop")
+
+ # 一時保存した変更を復元
+ if had_changes:
+ self.pop_stashed_changes()
+
+ except Exception as e:
+ logger.error(f"Error during merge to develop: {e}")
+ raise
diff --git a/aira/managers/staging_manager.py b/aira/managers/staging_manager.py
new file mode 100644
index 0000000..1692545
--- /dev/null
+++ b/aira/managers/staging_manager.py
@@ -0,0 +1,34 @@
+import os
+from loguru import logger
+from ..utils import run_command
+
+class StagingManager:
+ def __init__(self, repo_dir="./"):
+ self.repo_dir = repo_dir
+
+ def unstage_files(self):
+ """ステージにある全てのファイルをアンステージする"""
+ logger.info("Unstaging all files...")
+ try:
+ staged_files = run_command(["git", "diff", "--name-only", "--cached"], cwd=self.repo_dir).splitlines()
+ if staged_files:
+ run_command(["git", "reset", "HEAD", "--"] + staged_files, cwd=self.repo_dir)
+ logger.success(f"Unstaged files: {', '.join(staged_files)}")
+ else:
+ logger.info("No staged files found.")
+ except Exception as e:
+ logger.error(f"Error while unstaging files: {e}")
+ raise
+
+ def stage_file(self, filename, action):
+ """ファイルをステージする"""
+ try:
+ if action == "deleted":
+ logger.info(f"Deleted file: {filename}")
+ run_command(["git", "rm", filename], cwd=self.repo_dir)
+ else:
+ logger.info(f"Staged file: {filename}")
+ run_command(["git", "add", filename], cwd=self.repo_dir)
+ except Exception as e:
+ logger.error(f"Error while staging file: {filename} - {e}")
+ raise
diff --git a/aira/repository_manager.py b/aira/repository_manager.py
new file mode 100644
index 0000000..a35d16b
--- /dev/null
+++ b/aira/repository_manager.py
@@ -0,0 +1,24 @@
+from loguru import logger
+from .utils import run_command
+
+class RepositoryManager:
+ def __init__(self):
+ self.repo_dir = "./"
+
+ def get_staged_files(self):
+ """ステージングされているファイルの一覧を取得する"""
+ staged_files = run_command(["git", "diff", "--name-only", "--cached"], cwd=self.repo_dir)
+ return staged_files.splitlines() if staged_files else []
+
+ def get_current_branch(self):
+ """現在のブランチ名を取得する"""
+ return run_command(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=self.repo_dir).strip()
+
+ def checkout_branch(self, branch_name, create=False):
+ """指定したブランチをチェックアウトする"""
+ if create:
+ run_command(["git", "checkout", "-b", branch_name], cwd=self.repo_dir)
+ else:
+ run_command(["git", "checkout", branch_name], cwd=self.repo_dir)
+ logger.success(f"Checked out branch: {branch_name}")
+ return True
diff --git a/aira/template/config.yml b/aira/template/config.yml
deleted file mode 100644
index 2d300a1..0000000
--- a/aira/template/config.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-aira:
- gaiah:
- run: false
- config_path: .gaiah/config.yml
- harmon_ai:
- run: true
- config_path: .harmon_ai/config.yml
- instructions_prompt: .aira/instructions.md
\ No newline at end of file
diff --git a/aira/utils.py b/aira/utils.py
new file mode 100644
index 0000000..7847929
--- /dev/null
+++ b/aira/utils.py
@@ -0,0 +1,63 @@
+import subprocess
+from loguru import logger
+import time
+from tqdm import tqdm
+
+def run_command(command, cwd=None, check=True):
+ """
+ コマンドを実行し、結果を返す
+
+ Args:
+ command (list): 実行するコマンドとその引数のリスト
+ cwd (str, optional): コマンドを実行するディレクトリ
+ check (bool, optional): エラー時に例外を発生させるかどうか
+
+ Returns:
+ str: コマンドの実行結果
+ """
+ try:
+ logger.info(f"実行コマンド: {' '.join(command)}")
+ result = subprocess.run(command, cwd=cwd, check=check, capture_output=True, text=True, encoding='utf-8')
+ time.sleep(1)
+ return result.stdout.strip()
+ except subprocess.CalledProcessError as e:
+ error_message = e.stderr.strip()
+ logger.warning(f"Error while running command: {' '.join(command)}")
+ logger.warning(f"Error message: {error_message}")
+ raise
+
+def tqdm_sleep(n):
+ """
+ 進捗バーを表示しながらスリープする
+
+ Args:
+ n (int): スリープする秒数
+ """
+ for _ in tqdm(range(n)):
+ time.sleep(1)
+
+def create_config_template():
+ """
+ デフォルトの設定テンプレートを生成する
+
+ Returns:
+ dict: デフォルトの設定
+ """
+ return {
+ "gaiah": {
+ "run": True,
+ "repo": {
+ "repo_name": "AIRA",
+ "description": "AI-Integrated Repository for Accelerated Development",
+ "private": False
+ },
+ "local": {
+ "repo_dir": "./",
+ "no_initial_commit": False
+ },
+ "commit": {
+ "process_commits": True,
+ "commit_msg_path": ".Gaiah.md"
+ }
+ }
+ }
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..55eb2ae
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,13 @@
+# docker-compose.yml
+version: '3.8'
+services:
+ app:
+ build: .
+ volumes:
+ - .:/app
+ # - ~/.ssh:/root/.ssh:ro # SSHキーをマウント
+ # - ./.cache:/root/.cache # キャッシュディレクトリをマウント
+ env_file:
+ - .env
+ # command: python /app/apply_suggestion.py
+ tty: true
diff --git a/docs/.sourcesage_releasenotes.yml b/docs/.sourcesage_releasenotes.yml
new file mode 100644
index 0000000..ac71239
--- /dev/null
+++ b/docs/.sourcesage_releasenotes.yml
@@ -0,0 +1,14 @@
+ss-mode:
+ - DocuMind
+docuMind-model: "gemini/gemini-1.5-pro-latest"
+docuMind-db: ".SourceSageAssets/DOCUMIND/Repository_summary.md"
+docuMind-release-report: ".SourceSageAssets/RELEASE_REPORT/Report_v0.4.0.md"
+docuMind-changelog: ".SourceSageAssets/Changelog/CHANGELOG_main.md"
+docuMind-output: ".SourceSageAssets/DOCUMIND/RELEASE_NOTES_v0.4.0.md"
+docuMind-prompt-output: ".SourceSageAssets/DOCUMIND/_PROMPT_v0.4.0.md"
+repo-name: "AIRA"
+repo-version: "v0.4.0"
+
+# changelog-start-tag: "v0.1.0"
+# changelog-end-tag: "v0.5.0"
+# sourcesage --ss-mode=DocuMind --yaml-file=docs\.sourcesage_releasenotes.yml --ignore-file=".iris.SourceSageignore"
diff --git a/docs/usage.md b/docs/usage.md
new file mode 100644
index 0000000..861b700
--- /dev/null
+++ b/docs/usage.md
@@ -0,0 +1,5 @@
+# 準備中
+
+## sample note
+
+## sample note 2
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..0f5aa1e
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,5 @@
+litellm
+PyGithub
+google-generativeai
+loguru
+pydantic-settings
diff --git a/setup.py b/setup.py
index 383e2fb..52696c5 100644
--- a/setup.py
+++ b/setup.py
@@ -36,6 +36,10 @@ def get_version():
'art',
'loguru',
'tqdm',
+ 'harmon_ai',
+ 'gaiah_toolkit',
+ 'litellm',
+ 'google-generativeai',
],
entry_points={
'console_scripts': [