Skip to content

Repository Analysis Process

github-actions[bot] edited this page Mar 14, 2026 · 1 revision

Repository-Analysis-Process

wikigenがソースコードリポジトリを分析してWikiを生成するプロセスを詳細に説明するページである。リポジトリのクローン取得から始まり、Wiki構造の決定、個別ページの生成、そして失敗時のリトライ機構まで、パイプライン全体の動作を記述する。このページは Architecture and Design および Wiki Generation Pipeline and Claude Integration と密接に関連しており、それらと合わせて参照することを推奨する。

本ページが対象とするリポジトリは tomohiro-owada/wikigen であり、すべての実装は main.go の単一ファイルに集約されている。

リポジトリ分析パイプラインの概要

wikigenのリポジトリ分析は、generateWiki() 関数(main.go:474)を中心として4つのフェーズで構成される。

flowchart TD
    A[generateWiki 開始] --> B[入力バリデーション]
    B --> C{-local フラグ?}
    C -->|あり| D[ローカルディレクトリ使用]
    C -->|なし| E[gitClone 実行]
    E --> F{PAT トークン?}
    F -->|あり| G[HTTPS + PAT クローン]
    F -->|なし| H[SSH クローン]
    G --> I[出力ディレクトリ作成]
    H --> I
    D --> I
    I --> J[Step 1: 構造決定フェーズ]
    J --> K[Claude API 呼び出し]
    K --> L[XML レスポンス解析]
    L --> M[Home.md / Sidebar.md 生成]
    M --> N{-dry-run?}
    N -->|あり| O[処理終了]
    N -->|なし| P[Step 2: ページ生成フェーズ]
    P --> Q[並列ゴルーチン起動]
    Q --> R[各ページ生成 最大3回リトライ]
    R --> S[最終ステータス確認]
    S --> T[完了]
Loading

Sources: main.go:474-636

Step 0: リポジトリクローン

gitClone 関数

リポジトリの取得は gitClone() 関数(main.go:147)が担当する。この関数はSSHとHTTPS(PAT)の両認証方式に対応している。

func gitClone(repoURL, token, destDir string) error {
    if _, err := os.Stat(filepath.Join(destDir, ".git")); err == nil {
        cmd := exec.Command("git", "-C", destDir, "pull", "--ff-only")
        cmd.Stdout = os.Stderr
        cmd.Stderr = os.Stderr
        return cmd.Run()
    }

    cloneURL := repoURL
    if token != "" {
        // PAT specified — use HTTPS with token
        cloneURL = strings.Replace(repoURL, "https://", fmt.Sprintf("https://%s@", token), 1)
    } else {
        // No PAT — use SSH
        cloneURL = strings.Replace(repoURL, "https://github.com/", "git@github.com:", 1)
        if !strings.HasSuffix(cloneURL, ".git") {
            cloneURL += ".git"
        }
    }

    cmd := exec.Command("git", "clone", "--depth=1", "--single-branch", cloneURL, destDir)
    cmd.Stdout = os.Stderr
    cmd.Stderr = os.Stderr
    return cmd.Run()
}

Sources: main.go:147-171

SSH vs HTTPS 認証フロー

flowchart TD
    A[クローン要求] --> B{既存 .git ディレクトリ?}
    B -->|あり| C[git pull --ff-only]
    B -->|なし| D{token パラメータ?}
    D -->|空文字| E[SSH URL 変換]
    D -->|PAT 指定| F[HTTPS URL にトークン埋め込み]
    E --> G["git@github.com:owner/repo.git"]
    F --> H["https://TOKEN@github.com/owner/repo"]
    G --> I[git clone --depth=1 --single-branch]
    H --> I
    C --> J[完了]
    I --> J
Loading

Sources: main.go:147-171

認証方式の仕様

方式 条件 URL形式 用途
SSH -token フラグなし git@github.com:owner/repo.git デフォルト(SSH鍵設定済み環境)
HTTPS + PAT -token または GITHUB_TOKEN 環境変数あり https://TOKEN@github.com/owner/repo CI/CD環境、GitHub Actions
pull(更新) .git ディレクトリが既存 既存URL 再実行時の差分取得

Sources: main.go:147-171

浅いクローンの仕様

--depth=1 --single-branch オプションを使用することで、リポジトリの最新コミットのみをダウンロードし、ネットワーク転送量とディスク使用量を最小化する(main.go:167)。

ローカルディレクトリの利用

-local フラグが指定された場合、クローン処理をスキップしてローカルディレクトリを直接使用する(main.go:490-494)。

if localDir != "" {
    absLocal, _ := filepath.Abs(localDir)
    repoDirs = append(repoDirs, absLocal)
}

Sources: main.go:488-514

入力バリデーション

クローン前に validateRepo() 関数(main.go:81)が全リポジトリのURIを検証する。

var validRepoPattern = regexp.MustCompile(`^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$`)

func validateRepo(repo string) error {
    if !validRepoPattern.MatchString(repo) {
        return fmt.Errorf("invalid repo format: %q (expected owner/repo)", repo)
    }
    if strings.Contains(repo, "..") {
        return fmt.Errorf("path traversal detected in repo: %q", repo)
    }
    if strings.ContainsAny(repo, ";&|`$(){}[]!~") {
        return fmt.Errorf("invalid characters in repo: %q", repo)
    }
    return nil
}

バリデーションルールの詳細は Authentication and Security を参照のこと。

Sources: main.go:79-92

Step 1: 構造決定フェーズ

概要

構造決定フェーズでは、Claude Code が対象リポジトリのソースコードを分析し、Wiki全体のページ構成をXML形式で返す。このフェーズはパイプラインの核心であり、以降のページ生成で使用される全ページ定義を確立する。

// Step 1: Determine wiki structure
progress.set(projectName, "📋 structure...")

structureContent, err := claudeCall(claudePath, model, repoDirs, xmlSystemPrompt, structurePrompt(projectName, repos, language), wikiDir)
if err != nil {
    return result, fmt.Errorf("structure: %w", err)
}
structureContent = cleanXMLResponse(structureContent)

pages := parsePages(structureContent)
if len(pages) == 0 {
    return result, fmt.Errorf("no pages found in structure")
}

Sources: main.go:524-536

XMLシステムプロンプト

Claude Code がXMLを返す際にマークダウンコードフェンスを付加しないよう、専用のシステムプロンプトを使用する(main.go:175)。

const xmlSystemPrompt = `CRITICAL INSTRUCTIONS FOR XML RESPONSES:
When the user requests XML output (e.g., wiki_structure, or any XML format):
1. Return ONLY the raw XML - no markdown code fences, no backticks, no explanation
2. Do NOT wrap XML in triple backticks or markdown code blocks
3. Do NOT add any text before or after the XML
4. Start directly with the opening XML tag and end with the closing XML tag
5. Ensure the XML is well-formed and valid`

Sources: main.go:175-181

構造プロンプトの仕様

structurePrompt() 関数(main.go:183)は、Claude Code に対してリポジトリを分析し適切なWikiページ構造を決定するよう指示するプロンプトを生成する。

ドキュメントカテゴリ

カテゴリ 種別 説明
System Overview コアドキュメント プロジェクト目的・技術スタック・ディレクトリ構造
Architecture コアドキュメント システム設計・コンポーネント関係・デザインパターン
API Specification コアドキュメント REST/GraphQLエンドポイント・スキーマ
Data Model コアドキュメント DBスキーマ・マイグレーション・ORMモデル
Configuration コアドキュメント 設定ファイル・環境変数・フィーチャーフラグ
Error Handling コアドキュメント エラー型・ログ戦略
Processing Flows 推論ドキュメント 関数呼び出しチェーンから導出した業務フロー
Security Design 推論ドキュメント ミドルウェア・バリデーションに見られるセキュリティ対策
Performance 推論ドキュメント キャッシュ・最適化パターン

Sources: main.go:200-244

ページ生成ルール

flowchart TD
    A[コードベース分析] --> B{コード証拠あり?}
    B -->|あり| C[ページ作成]
    B -->|なし| D[ページ作成しない]
    C --> E{複数リポジトリ?}
    E -->|あり| F[クロスリポジトリ ドキュメント作成]
    E -->|なし| G[単一リポジトリ ページ作成]
    F --> H[XML 出力]
    G --> H
Loading

Sources: main.go:225-243

XML出力形式

Claude Code が返すXML構造は以下の形式に従う。

<wiki_structure>
  <title>[Wiki タイトル]</title>
  <description>[プロジェクト説明]</description>
  <pages>
    <page id="page-1">
      <title>[ページタイトル]</title>
      <filename>[Page-Filename]</filename>
      <description>[ページの内容とその必要性]</description>
      <importance>high|medium|low</importance>
      <relevant_files>
        <file_path>[リポジトリ内の実際のファイルパス]</file_path>
      </relevant_files>
      <related_pages>
        <related>page-2</related>
      </related_pages>
    </page>
  </pages>
</wiki_structure>

Sources: main.go:250-271

XML解析

parsePages() 関数(main.go:399)がXMLレスポンスを解析して WikiPage 構造体のスライスを生成する。

type WikiPage struct {
    ID          string
    Title       string
    Filename    string
    Description string
    Content     string
}

cleanXMLResponse() 関数(main.go:380)は、マークダウンコードフェンスや </wiki_structure> 以降の余分なコンテンツを除去する前処理を行う。

Sources: main.go:62-68, main.go:380-434

ファイル名変換

titleToFilename() 関数(main.go:451)はページタイトルをGitHub Wiki互換のファイル名に変換する。

func titleToFilename(title string) string {
    replacer := strings.NewReplacer(
        " ", "-", "/", "-", "\\", "-", ":", "-", "*", "", "?", "",
        "\"", "", "<", "", ">", "", "|", "", "(", "-", ")", "",
        "・", "-", " ", "-",
    )
    return replacer.Replace(title)
}

Sources: main.go:451-458

Home.md と _Sidebar.md の即時生成

構造決定フェーズ完了後、writeHomeAndSidebar() 関数(main.go:638)が Home.md_Sidebar.md を即座に書き込む。これにより、ページ生成が途中で中断された場合でもWiki構造が保持される。

Sources: main.go:545-546, main.go:638-665

Step 2: ページ生成フェーズ

並列処理アーキテクチャ

ページ生成はGoのゴルーチンとセマフォパターンを使用して並列処理される(main.go:563)。

// Step 2: Generate pages with parallelism
var pageDone int32
pageSem := make(chan struct{}, pageParallel)
var pageWg sync.WaitGroup

for i := range allPages {
    pageWg.Add(1)
    pageSem <- struct{}{}
    go func(idx int) {
        defer pageWg.Done()
        defer func() { <-pageSem }()
        // ... ページ生成処理 ...
    }(i)
}
pageWg.Wait()

pageParallel はデフォルト値3で、-pp フラグで変更可能。セマフォがチャネルバッファとして機能し、同時実行数を制限する。

Sources: main.go:563-615

ページプロンプトの仕様

pagePrompt() 関数(main.go:275)は各ページ生成用のプロンプトを生成する。プロンプトには以下の要求が含まれる。

要求項目 内容
ソースコード読み込み Read/Grep/Glob/Bash ツールを使用してコードを実際に読んでから記述
導入部 1〜2段落、目的・スコープ・関連ページへのリンク
詳細セクション アーキテクチャ・コンポーネント・データフロー
Mermaid図 flowchart TD / sequenceDiagram / classDiagram
テーブル APIエンドポイント・設定オプション
コードスニペット ソースファイルから直接引用
ソース引用 最低5ファイル以上、行番号付きで引用必須
クロスリンク GitHub Wiki形式 [タイトル](ファイル名)

Sources: main.go:275-362

ページ生成フロー(詳細)

sequenceDiagram
    participant Main as メインゴルーチン
    participant Sem as セマフォ
    participant Worker as ページワーカー
    participant Claude as Claude Code CLI
    participant FS as ファイルシステム

    Main->>Sem: チャネル送信(ブロック)
    Sem->>Worker: ゴルーチン起動
    Worker->>Worker: プログレス更新
    Worker->>Claude: claudeCall() 実行
    Claude->>FS: Write ツールでファイル書き込み
    Claude-->>Worker: レスポンス返却
    Worker->>FS: ファイルサイズ確認 > 100 bytes
    alt 成功
        Worker->>Worker: success = true
    else 失敗
        Worker->>Worker: リトライ(最大3回)
    end
    Worker->>Sem: チャネル受信(解放)
    Worker-->>Main: WaitGroup Done
Loading

Sources: main.go:563-615

claudeCall 関数

claudeCall() 関数(main.go:116)は Claude Code CLI を呼び出す中核関数である。

func claudeCall(claudePath, model string, repoDirs []string, systemPrompt, prompt, workDir string) (string, error) {
    args := []string{"-p", "--output-format", "text", "--dangerously-skip-permissions"}
    if model != "" {
        args = append(args, "--model", model)
    }
    for _, dir := range repoDirs {
        args = append(args, "--add-dir", dir)
    }
    if systemPrompt != "" {
        args = append(args, "--system-prompt", systemPrompt)
    }

    cmd := exec.Command(claudePath, args...)
    cmd.Stdin = strings.NewReader(prompt)
    if workDir != "" {
        cmd.Dir = workDir
    }
    // ...
}

--add-dir オプションによって、Claude Code はリポジトリディレクトリへのファイルアクセス権を持つ。作業ディレクトリ(workDir)をWiki出力ディレクトリに設定することで、Claudeが直接 .md ファイルを書き込めるようにする。

Sources: main.go:116-143

リトライ機構

インライン自動リトライ

各ページは最大3回まで自動的にリトライされる(main.go:580)。

maxRetries := 3
var success bool

for attempt := 1; attempt <= maxRetries; attempt++ {
    if attempt > 1 {
        progress.set(projectName, fmt.Sprintf("🔄 %d/%d %s (retry %d)", idx+1, len(allPages), page.Title, attempt))
    }

    // Remove previous failed file
    os.Remove(filename)

    _, err := claudeCall(claudePath, model, repoDirs, "", pagePrompt(*page, allPages, projectName, repos, language), wikiDir)
    if err != nil {
        continue
    }

    // Check if claude wrote the file
    written, readErr := os.ReadFile(filename)
    if readErr == nil && len(written) > 100 {
        page.Content = string(written)
        success = true
        break
    }
}

Sources: main.go:580-605

リトライ成功判定条件

条件 判定
claudeCall() がエラーなく完了 必要条件
ファイルが存在する 必要条件
ファイルサイズ > 100 bytes 必要条件
上記すべて満たす 成功

失敗時のスタブファイル生成

3回すべて失敗した場合、エラーログに記録し、スタブファイルを生成する(main.go:607-609)。

if !success {
    appendError(wikiDir, fmt.Sprintf("Page %d/%d: %s — failed after %d attempts", idx+1, len(allPages), page.Title, maxRetries))
    os.WriteFile(filename, []byte(fmt.Sprintf("# %s\n\n*Content generation failed after %d attempts*\n", page.Title, maxRetries)), 0644)
}

このスタブファイルは -retry フラグによる後日リトライの検出マーカーとして機能する。

Sources: main.go:607-609

エラーログ

appendError() 関数(main.go:462)がタイムスタンプ付きでエラーを _errors.log に記録する。

func appendError(dir, msg string) {
    errFile := filepath.Join(dir, "_errors.log")
    f, err := os.OpenFile(errFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    if err != nil {
        return
    }
    defer f.Close()
    fmt.Fprintf(f, "[%s] %s\n", time.Now().Format("15:04:05"), msg)
}

Sources: main.go:462-470

リトライフロー

flowchart TD
    A[ページ生成開始] --> B[attempt = 1]
    B --> C[既存ファイル削除]
    C --> D[claudeCall 実行]
    D --> E{エラー?}
    E -->|あり| F{attempt < 3?}
    E -->|なし| G[ファイルサイズ確認]
    G --> H{> 100 bytes?}
    H -->|あり| I[success = true]
    H -->|なし| F
    F -->|あり| J[attempt++]
    J --> C
    F -->|なし| K[_errors.log に記録]
    K --> L[スタブファイル生成]
    I --> M[完了]
    L --> M
Loading

Sources: main.go:580-610

-retry フラグによる再実行

概要

-retry フラグを使用すると、過去の生成で失敗したページのみを再生成できる。retryFailedPages() 関数(main.go:741)がこの処理を担当する。

失敗ページの検出

for _, f := range files {
    content, err := os.ReadFile(filepath.Join(projectDir, f.Name()))
    if err != nil {
        continue
    }
    if strings.Contains(string(content), "Content generation failed") || len(content) < 200 {
        // 失敗ページとして記録
    }
}

失敗ページの判定基準:

  • ファイルに "Content generation failed" テキストが含まれる
  • ファイルサイズが200バイト未満

Sources: main.go:763-779

-retry 実行フロー

flowchart TD
    A[-retry フラグ検出] --> B[wiki-output ディレクトリをスキャン]
    B --> C[各プロジェクトディレクトリを調査]
    C --> D[.md ファイルを読み込み]
    D --> E{失敗マーカー検出?}
    E -->|あり| F[失敗ページリストに追加]
    E -->|なし| G[次ファイルへ]
    F --> H[プロジェクト別にグループ化]
    H --> I[Home.md からページ説明を抽出]
    I --> J[クローン済みリポジトリを特定]
    J --> K[pagePrompt 再生成]
    K --> L[claudeCall 実行]
    L --> M[ファイルサイズ確認 > 100 bytes]
    M --> N{成功?}
    N -->|あり| O[完了ログ出力]
    N -->|なし| P[エラーログ出力]
    O --> Q[次ページへ]
    P --> Q
Loading

Sources: main.go:741-875

Home.md からの情報復元

-retry 実行時、元の構造プロンプト結果は保存されていないため、Home.md からページ一覧と説明を復元してクロスリンク用の allPages を再構築する(main.go:832)。

Sources: main.go:807-853

最終ステータス判定

全ページ生成完了後、generateWiki() 関数は各ページの実際のファイルサイズを確認してステータスを確定する(main.go:617)。

var failedCount int
for i, p := range allPages {
    fileInfo, err := os.Stat(filepath.Join(wikiDir, p.Filename+".md"))
    if err != nil || fileInfo.Size() < 200 {
        result.Pages[i].Status = "failed"
        result.Pages[i].Size = 0
        failedCount++
    } else {
        result.Pages[i].Status = "ok"
        result.Pages[i].Size = int(fileInfo.Size())
    }
}
result.Failed = failedCount
result.Status = "completed"

ここでの閾値は200バイト(インライン リトライ時の100バイトより厳しい)。

Sources: main.go:617-631

パイプライン全体のシーケンス図

sequenceDiagram
    participant CLI as コマンドライン
    participant Main as generateWiki
    participant Git as git CLI
    participant Claude as Claude Code CLI
    participant FS as ファイルシステム

    CLI->>Main: generateWiki() 呼び出し
    Main->>Main: validateRepo() 検証
    Main->>Git: git clone / git pull
    Git-->>Main: クローン完了
    Main->>FS: 出力ディレクトリ作成
    Main->>Claude: structurePrompt 送信
    Claude->>Git: コードベース分析
    Claude-->>Main: XML レスポンス
    Main->>Main: parsePages() 解析
    Main->>FS: Home.md / _Sidebar.md 書き込み
    loop 全ページ(並列)
        Main->>Claude: pagePrompt 送信
        Claude->>FS: Read/Grep/Glob ツール使用
        Claude->>FS: Write ツールで .md 書き込み
        Claude-->>Main: 完了通知
        Main->>FS: ファイルサイズ確認
    end
    Main->>FS: _errors.log 確認
    Main-->>CLI: WikiResult 返却
Loading

データ構造

WikiResult

JSON出力時に返される最終結果構造体(main.go:96)。

type WikiResult struct {
    Project    string           `json:"project"`
    Repos      []string         `json:"repos"`
    OutputDir  string           `json:"output_dir"`
    Pages      []WikiPageResult `json:"pages"`
    TotalPages int              `json:"total_pages"`
    Failed     int              `json:"failed"`
    Duration   string           `json:"duration"`
    Status     string           `json:"status"`
}

Sources: main.go:96-112

WikiPage

構造決定フェーズで生成される内部ページ定義(main.go:62)。

フィールド 説明
ID string page-1 形式の一意識別子
Title string ページタイトル
Filename string GitHub Wiki互換ファイル名
Description string ページの説明(クロスリンク用)
Content string 生成されたMarkdownコンテンツ

Sources: main.go:62-68

関連ページ

Clone this wiki locally