-
Notifications
You must be signed in to change notification settings - Fork 1
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[完了]
Sources: main.go:474-636
リポジトリの取得は 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
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
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
構造決定フェーズでは、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
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
Sources: main.go:225-243
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
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
構造決定フェーズ完了後、writeHomeAndSidebar() 関数(main.go:638)が Home.md と _Sidebar.md を即座に書き込む。これにより、ページ生成が途中で中断された場合でもWiki構造が保持される。
Sources: main.go:545-546, main.go:638-665
ページ生成は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
Sources: main.go:563-615
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
Sources: main.go:580-610
-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
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
Sources: main.go:741-875
-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 返却
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
構造決定フェーズで生成される内部ページ定義(main.go:62)。
| フィールド | 型 | 説明 |
|---|---|---|
| ID | string |
page-1 形式の一意識別子 |
| Title | string | ページタイトル |
| Filename | string | GitHub Wiki互換ファイル名 |
| Description | string | ページの説明(クロスリンク用) |
| Content | string | 生成されたMarkdownコンテンツ |
Sources: main.go:62-68
- System Overview — wikigenの全体目的とアーキテクチャの概要
- Architecture and Design — パイプライン設計とコンポーネント関係の詳細
- Wiki Generation Pipeline and Claude Integration — Claude Codeとの連携、プロンプト設計の詳細
- Error Handling and Recovery — エラーログ、リトライ機構のトラブルシューティング
- Authentication and Security — SSH/HTTPS認証、入力バリデーションのセキュリティ詳細
- Parallelism and Performance — セマフォによる並列処理制御の詳細
-
CLI Reference and Usage —
-retry・-dry-run・-localフラグの使用方法 - Configuration and Environment Variables — 環境変数・設定オプションの完全リファレンス
- System Overview
- Architecture & Design
- CLI Usage & Commands
- Configuration & Environment
- Input Formats & Repository Configuration
- Authentication & Git Integration
- Output Format & Wiki Structure
- Error Handling & Retry Mechanism
- Parallel Processing & Performance
- Input Validation & Security
- Build & Deployment
- Claude Code Integration
- Wiki Generation Processing Flow
- Multi-Repository Wiki Support
- Progress Tracking & Output Modes