これはなに?
GitLab Duo ChatのAPIを活用して、C言語やCOBOLのプログラムファイルを自動的に診断するPythonスクリプト
せつめい
このツールは、大規模なソースコードを論理的な単位や行数ごとに分割し、生成AIへ段階的に送信することで、長大なファイルでも処理できるように設計しています。
つまりどういうこと
主な機能は、コードのサイクロマティック複雑度を測定し、リファクタリングが必要な箇所や潜在的なバグを特定することです。解析結果はGitLabのコード品質レポートと互換性のあるJSON形式で出力され、開発者が修正すべき問題を一覧で確認できます。このように、このスクリプトはAIによる静的解析を自動化し、レガシーシステムの品質管理を効率化することを目的としています。
import os
import json
import re
import requests
# === 🛠️ 設定(環境に合わせて変更してください) ===
GITLAB_URL = "https://your-gitlab-instance.com" # セルフホスト版のURL
PROJECT_ID = "123" # 対象プロジェクトのID
# GitLabの「設定 > アクセストークン」から api 権限の Personal Access Token を取得して設定
PRIVATE_TOKEN = os.getenv("GITLAB_PAT", "YOUR_PERSONAL_ACCESS_TOKEN")
TARGET_EXTENSIONS = ('.c', '.h', '.cbl', '.cob')
MAX_LINES_PER_CHUNK = 500 # LLMが1回で処理しやすいよう500行単位に分割
def split_cobol_code(code_content):
"""
COBOLコードを論理セクション(DIVISIONやSECTIONなど)を意識しつつ
指定行数ごとにチャンク(分割塊)に分ける
"""
lines = code_content.splitlines()
chunks = []
current_chunk = []
for i, line in enumerate(lines):
current_chunk.append((i + 1, line)) # (元の行番号, 行のテキスト)
# 500行に達するか、COBOLの重要な区切り(DIVISIONなど)が来たらチャンクを区切る
if len(current_chunk) >= MAX_LINES_PER_CHUNK or any(keyword in line.upper() for keyword in ["DIVISION.", "SECTION."]):
if current_chunk:
chunks.append(current_chunk)
current_chunk = []
if current_chunk:
chunks.append(current_chunk)
return chunks
def call_gitlab_duo_chat(prompt):
"""
GitLab の GraphQL API を利用して Duo Chat 機能を呼び出す
"""
url = f"{GITLAB_URL}/api/graphql"
headers = {
"Authorization": f"Bearer {PRIVATE_TOKEN}",
"Content-Type": "application/json"
}
# GraphQLのaiActionミューテーションを利用(Duo Chatと対話する標準的な形式)
query = """
mutation (projectId: ProjectID!, content: String!) {
aiAction(input: { chat: { resourceId: projectId, content: content } }) {
errors
}
}
"""
# 注意: セルフホスト版の設定やバージョンによっては REST API (`/api/v4/projects/:id/chat/completions`) が
# feature flag `access_rest_chat` で有効化されている場合、そちらを叩く方がシンプルなケースもあります。
payload = {
"query": query,
"variables": {
"projectId": f"gid://gitlab/Project/{PROJECT_ID}",
"content": prompt
}
}
try:
response = requests.post(url, json=payload, headers=headers)
response.raise_for_status()
# 応答結果からテキストをパース(セルフホストの構成によって応答から最終テキストを取得するパスを調整してください)
# ※単発実行の自動検証用に、LLMの返答を待ち受ける処理か、
# 簡易的にはDuoに「JSONとして結果のみを返すように厳格に指示」します。
# 💡ここでは解説のために簡略化していますが、Duo Chatは非同期処理の場合があるため、
# プロンプト側で「確定的なJSONを返すよう」に徹底指示します。
res_json = response.json()
return res_json
except Exception as e:
print(f"APIエラー: {e}")
return None
def analyze_chunk(file_path, chunk, lang):
"""
分割されたコード片(チャンク)をDuoに送り、複雑度を算出させる
"""
start_line = chunk[0][0]
code_text = "\n".join([line[1] for line in chunk])
prompt = f"""
あなたは高度な静的解析エージェントです。以下の{lang}ソースコードの一部(ファイル: {file_path} の {start_line}行目付近から)のサイクロマティック複雑度(循環的複雑度)を測定し、10を超えている関数や重大なバグ・可観測性の欠如を指摘してください。
出力は必ず、以下のGitLab Code Quality互換のJSON配列のみを返してください。解説テキストやマークダウン(```json など)の囲みは一切不要です。問題がない場合は空配列 `[]` を返してください。
[
{{
"description": "(例)関数 'XXXX' のサイクロマティック複雑度が 15 です。10を超えているためリファクタリングを推奨します。",
"fingerprint": "{file_path}_{start_line}_complexity",
"severity": "major",
"location": {{
"path": "{file_path}",
"lines": {{
"begin": {start_line}
}}
}}
}}
]
【ソースコード】
{code_text}
"""
# 実際にはここにDuo Chat APIのコールと結果パースが入ります
print(f" -> {file_path} の {start_line} 行目からを解析中...")
result = call_gitlab_duo_chat(prompt)
# 擬似パース処理(実運用の際はDuoの返答テキストからJSON部分を抽出してください)
# ここでは空のリストを返していますが、APIから得たJSONテキストを `json.loads()` します
return []
def main():
all_issues = []
print("🔍 単発コード複雑度解析エージェントを開始します。")
for root, _, files in os.walk("."):
for file in files:
if file.endswith(TARGET_EXTENSIONS):
file_path = os.path.join(root, file)
lang = "COBOL" if file.endswith(('.cbl', '.cob')) else "C言語"
print(f"\n📄 ファイル発見: {file_path} ({lang})")
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
content = f.read()
# 5万行対策:チャンクに分割
chunks = split_cobol_code(content)
print(f" 📂 ファイルを {len(chunks)} 個のチャンクに分割しました。順次解析します。")
for chunk in chunks:
issues = analyze_chunk(file_path, chunk, lang)
if issues:
all_issues.extend(issues)
# 最終レポートを保存
output_report = "standalone-code-quality-report.json"
with open(output_report, "w", encoding="utf-8") as f:
json.dump(all_issues, f, indent=2, ensure_ascii=False)
print(f"\n🎉 解析完了。レポートを '{output_report}' に出力しました。")
if __name__ == "__main__":
main()