はじめに
大規模なコードベースのリファクタリングに取り組む際、こんな課題に直面したことはありませんか?
- LLMのコンテキストがすぐに埋まる: 関連ファイルを読み込んだだけで上限に到達
- 精度が劣化する: コンテキストが雑然として、的確な提案が得られない
- 全体像が把握できない: どこから手をつければいいのか分からない
本記事では、これらの課題を解決するために設計した2フェーズパイプラインのアーキテクチャを詳しく解説します。
GitHub リポジトリ: https://github.com/nogataka/refactor-doc-pipeline
この記事で分かること
- LLMのコンテキストを効率的に使う2フェーズ設計の考え方
- Language Server Protocol (LSP) を活用した高度なコード解析手法
- 疎検索と密検索を組み合わせたハイブリッド検索の実装
- チャンク→ファイル→モジュール→システムの階層要約生成
- 実装の詳細とスケーラビリティへの配慮
システムの全体像
このシステムは、データストア作成フェーズとデータストア利用フェーズの2つに分かれています。
なぜ2フェーズなのか?
LLMのコンテキストウィンドウは限られています。数万行のコードベースを一度に投げても、LLMは適切に処理できません。
2フェーズ設計の利点:
- 事前処理で知識ベース化: ソースコードを検索可能な形式に変換
- 必要最小限の情報だけ抽出: クエリに関連する部分だけをLLMに渡す
- コンテキストの質を向上: 雑然とした全文ではなく、構造化された要約を活用
フェーズ1: データストア作成
ソースコードを事前処理し、検索可能な知識ベースを構築するフェーズです。
処理の流れ
1. チャンク化 (Chunker)
ソースコードを適切なサイズに分割します。
チャンク化の戦略:
実装の工夫:
- TypeScript/JavaScript: ts-morphでAST解析し、関数・クラス単位で分割
- その他の言語: 構造が取れない場合は6KBの固定長で分割
- シグネチャ抽出: 型情報を含む関数シグネチャを保存
// 抽出されるシグネチャの例
"async functionName(param1: string, param2: number): Promise<void>"
"class ClassName extends Base implements Interface"
なぜ関数・クラス単位なのか?
- 責務が明確で理解しやすい
- 変更の影響範囲が特定しやすい
- 検索時に関連コードをピンポイントで取得可能
2. 要約生成 (Summarizer)
各チャンクの責務を100-200字で要約します。これがシステムの核心部分です。
要約の構造:
interface ChunkSummary {
responsibility: string; // 責務の説明
inputOutput: string; // 入出力の説明
sideEffects: string; // 副作用の説明
mainDependencies: string[]; // 主要な依存関係
notes: string; // 特記事項(例外、I/O、非同期など)
}
プロンプト設計のポイント:
- 簡潔さ: 100-200字に制限して冗長さを排除
- 構造化: JSON形式で出力し、機械処理可能に
- 安定性: temperature=0.3 で再現性を確保
要約例:
{
"responsibility": "ユーザー認証を行い、JWTトークンを生成する",
"inputOutput": "入力: email, password / 出力: JWT文字列",
"sideEffects": "データベースのlast_login_atを更新",
"mainDependencies": ["bcrypt", "jsonwebtoken", "UserModel"],
"notes": "非同期処理、認証失敗時は例外をスロー"
}
なぜ全文ではなく要約なのか?
| 観点 | 全文埋め込み | 要約埋め込み |
|---|---|---|
| コスト | $$$$$ | $ |
| トークン数 | 数千〜数万 | 数百 |
| ノイズ | 多い | 少ない |
| 検索精度 | △ | ⚡ |
要約により、コストを1/10以下に抑えながら、検索精度を向上させています。
3. メタデータ抽出 (MetadataExtractor)
ここがこのシステムの最大の特徴です。Language Server Protocol (LSP) を活用した高度な解析を行います。
LSPとは?
LSPは、IDEがコードを理解するために使う標準プロトコルです。VSCodeやIntelliJなどのIDEは、LSPを通じて言語サーバーと通信し、以下の機能を実現しています:
- シンボル情報の取得: 関数、クラス、変数などの定義情報
- 参照検索: どこでこの関数が使われているか
- 定義ジャンプ: シンボルの定義箇所への移動
- 型情報の取得: 型推論や型チェック
このシステムでは、同じLSPを使ってプログラマティックにコード解析を行います。
LSP統合のアーキテクチャ
3段階のフォールバック戦略:
- LSP解析: 言語サーバーからシンボル情報を取得(最も正確)
- ts-morph解析: TypeScript/JavaScript向けのAST解析
- 正規表現解析: テキストベースのパターンマッチング(最終手段)
この多段階フォールバックにより、言語サーバーが利用できない環境でも動作し、あらゆる言語に対応できます。
対応言語:
| 言語 | 言語サーバー | インストール方法 |
|---|---|---|
| TypeScript/JavaScript | typescript-language-server | npm i -g typescript-language-server |
| Python | pyright-langserver | pip install pyright |
| Go | gopls | go install golang.org/x/tools/gopls@latest |
| Rust | rust-analyzer | rustup component add rust-analyzer |
| Java | eclipse.jdt.ls | 公式サイト |
| C# | csharp-ls | dotnet tool install --global csharp-ls |
| C/C++ | clangd | brew install llvm |
抽出されるメタデータ:
interface ChunkMetadata {
chunkId: string;
imports: string[]; // import文の一覧
exports: string[]; // export情報
callers: string[]; // このコードを呼び出している箇所
callees: string[]; // このコードが呼び出している関数
isAsync: boolean; // 非同期処理を含むか
throwsException: boolean; // 例外をスローする可能性
securityBoundary: boolean; // セキュリティ境界に関わるか
}
LSP解析の利点:
✅ 正確性: IDEと同じセマンティック理解
✅ 言語非依存: 統一インターフェースで多言語対応
✅ 参照追跡: 呼び出し関係の完全な把握
✅ 型情報: 静的型付け言語の型情報を活用
LSP統合の実装詳細:
システムは以下の3つのコンポーネントで構成されています:
- LspServerRegistry: ファイル拡張子から適切な言語サーバーを特定
- LspProcessManager: 言語サーバープロセスのライフサイクル管理
- LspClientPool: 開かれたドキュメントのプール管理
マージ戦略の詳細:
LSP解析とフォールバック解析の結果をインテリジェントにマージします:
マージルール:
- imports/exports/callees: 両方の結果を和集合(補完し合う)
- isAsync/throwsException: LSP優先、なければフォールバック
-
callers:
buildCallGraphフェーズで全体から構築
この戦略により、LSPだけでは取れない情報も補完しながら、LSPの高精度な情報を最大限活用できます。
4. 埋め込み生成 (Embedder)
要約+シグネチャからベクトル埋め込みを生成します。
埋め込み対象テキスト:
const embeddingText = `${signature}\n\n${summary.responsibility}\n${summary.inputOutput}`;
実装の工夫:
- バッチ処理: 100件ずつバッチ化してAPI呼び出し回数を削減
- 進捗表示: 長時間処理の状況を可視化
- エラーハンドリング: 失敗したチャンクをスキップして続行
使用モデル: OpenAI text-embedding-3-small (1536次元)
コサイン類似度の計算:
static cosineSimilarity(a: number[], b: number[]): number {
const dotProduct = a.reduce((sum, val, i) => sum + val * b[i], 0);
const normA = Math.sqrt(a.reduce((sum, val) => sum + val * val, 0));
const normB = Math.sqrt(b.reduce((sum, val) => sum + val * val, 0));
return dotProduct / (normA * normB);
}
5. 保存 (Storage)
インデックス化されたドキュメントをJSONL形式で保存します。
JSONL形式を選んだ理由:
| 特徴 | 利点 |
|---|---|
| 1行1ドキュメント | ストリーム処理が可能 |
| 追記が容易 | ファイル末尾に追加するだけ |
| 差分更新 | 変更ファイルだけ再処理して更新可能 |
| 並列処理 | 行単位で分割して処理可能 |
保存形式:
{"chunk":{...},"summary":{...},"metadata":{...},"embedding":{...},"indexedAt":"2024-01-01T00:00:00.000Z"}
{"chunk":{...},"summary":{...},"metadata":{...},"embedding":{...},"indexedAt":"2024-01-01T00:00:00.000Z"}
フェーズ2: データストア利用
構築した知識ベースから必要な情報だけを検索するフェーズです。
ハイブリッド検索の仕組み
疎検索(Sparse Search)と密検索(Dense Search)を組み合わせます。
疎検索 (Sparse Search)
BM25アルゴリズムベースのキーワード検索で候補を200件に絞り込みます。
BM25とは?
BM25(Best Matching 25)は、情報検索における古典的なランキング関数です。以下の要素を考慮してスコアを計算します:
- TF (Term Frequency): クエリのキーワードが文書内に何回出現するか
- IDF (Inverse Document Frequency): キーワードの希少性(全文書で何回出現するか)
- 文書の長さ: 長い文書はペナルティ
使用ライブラリ: MiniSearch(軽量なBM25実装)
検索対象フィールドとブースト設定:
{
fields: ['name', 'signature', 'responsibility', 'inputOutput', 'notes'],
searchOptions: {
boost: {
name: 3, // 関数名/クラス名を最重視
signature: 2, // シグネチャを次に重視
responsibility: 1.5,
inputOutput: 1,
notes: 1
},
fuzzy: 0.2, // タイポ許容(編集距離)
prefix: true // 前方一致検索
}
}
疎検索の特徴:
| 特徴 | 説明 |
|---|---|
| ✅ 高速 | インデックスベースの検索で数ミリ秒 |
| ✅ 完全一致に強い | 関数名やキーワードの完全マッチ |
| ✅ 軽量 | メモリ使用量が少ない |
| ❌ 意味は捉えられない | "ログイン"と"認証"は別物として扱われる |
| ❌ 多言語対応が弱い | 日本語と英語の対応が困難 |
密検索 (Dense Search)
ベクトル類似度による意味的検索で、疎検索の結果を20件に再ランキングします。
検索フロー:
密検索の特徴:
| 特徴 | 説明 |
|---|---|
| ✅ 意味的類似性 | "ログイン"と"認証"を同じ文脈として認識 |
| ✅ 多言語対応 | 日本語クエリで英語コードも検索可能 |
| ✅ 同義語対応 | "削除"と"delete"を同じ意味として扱う |
| ❌ 計算コスト | 全ベクトルとの比較が必要(O(n)) |
| ❌ メモリ使用量 | ベクトルデータの保持が必要 |
なぜハイブリッドなのか?
疎検索と密検索は相補的です。
| 検索方法 | 速度 | 精度 | 適用範囲 | コスト |
|---|---|---|---|---|
| 疎検索のみ | ⚡⚡⚡ | △ | キーワードマッチ | 無料 |
| 密検索のみ | △ | ⚡⚡ | 意味的マッチ | $ |
| ハイブリッド | ⚡⚡ | ⚡⚡⚡ | 両方の良いとこ取り | $ |
実装上の工夫:
-
疎検索で候補を200件に絞り込む(高速、無料)
- 全10,000チャンクから200件に削減
- BM25スコアでランキング
-
その200件だけ密検索で再ランキング(精度向上、低コスト)
- 200件のベクトル比較のみ(10,000件の1/50)
- コサイン類似度で意味的に近いものを抽出
-
最終的に上位20件をLLMに渡す(コンテキスト最小化)
- 必要十分な情報だけを提供
- コンテキストウィンドウを効率的に使用
クエリ拡張機能:
さらに精度を上げるため、LLMでクエリを拡張する機能も実装しています:
// クエリ拡張の例
expandQuery("認証")
// → ["認証", "ログイン", "セッション", "トークン", "JWT"]
// これらのキーワードで検索してマージ
階層要約生成
チャンク→ファイル→モジュール→システムの階層で要約を生成します。
階層構造
各レベルの責務:
| レベル | 粒度 | 内容 | 文字数 |
|---|---|---|---|
| チャンク | 関数/クラス | 責務・入出力・副作用 | 100-200字 |
| ファイル | 1ファイル | 役割・公開API・依存 | 200字 |
| モジュール | ディレクトリ | 境界・責務分割・他モジュール依存 | 200字 |
| システム | プロジェクト全体 | レイヤ構成・ドメイン境界 | 300字 |
生成プロセス
出力ドキュメント構造
docs/refactor/
├── overview.md # システム全体の統計と概要
├── decisions.md # ADRスタイルの設計判断ログ
├── summaries/
│ ├── files/ # ファイルごとの要約
│ ├── modules/ # モジュールごとの要約
│ ├── system.md # システム全体の要約
│ └── hierarchy.json # 構造化データ
├── modules/
│ └── {module}.md # モジュールの詳細と公開API
├── files/
│ └── {path}.md # ファイル役割・依存・チャンク一覧
└── plans/
├── milestones.md # フェーズ別リファクタリング計画
└── {query}.md # 改善提案(クエリ指定時)
階層要約の利点
粒度に応じた情報取得:
- システムレベル: 全体のアーキテクチャを把握したい
- モジュールレベル: 特定の機能領域を理解したい
- ファイルレベル: 実装の詳細を確認したい
- チャンクレベル: 特定の関数を変更したい
コンテキスト最小化の実現:
必要な粒度の情報だけをLLMに渡すことで、コンテキストウィンドウを効率的に使えます。
例えば、「認証モジュールのリファクタリング計画を立てたい」という場合:
- システム要約で全体像を把握(300字)
- 認証モジュール要約で詳細を確認(200字)
- 関連ファイル要約で実装を理解(200字×3ファイル)
→ 合計900字程度で必要な情報を取得(全文なら数万字)
使い方
インストール
git clone https://github.com/nogataka/refactor-doc-pipeline
cd refactor-doc-pipeline
npm install
npm run build
環境変数の設定
cp env.example .env
.envファイルを編集:
# OpenAI設定
OPENAI_PROVIDER=openai
OPENAI_API_KEY=sk-your-api-key
# モデル設定
OPENAI_SUMMARIZER_MODEL=gpt-4o-mini
OPENAI_DOC_MODEL=gpt-4o
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
# LSP設定(対応言語をカンマ区切りで指定)
LSP_ENABLE_LANGS=ts,js,py,go,rust,java,cs,cpp
Azure OpenAIを使う場合:
OPENAI_PROVIDER=azure
AZURE_OPENAI_API_KEY=your-azure-key
AZURE_OPENAI_ENDPOINT=https://<resource>.openai.azure.com
OPENAI_API_VERSION=2024-05-01-preview
# デプロイ名をモデルとして指定
OPENAI_SUMMARIZER_MODEL=<chat-deployment-name>
OPENAI_EMBEDDING_MODEL=<embedding-deployment-name>
基本的な使い方
1. インデックス化
npm run index -- \
--source ./your-project/src \
--output ./output
処理内容:
- ソースコードをチャンク化
- 要約とメタデータを生成
- ベクトル埋め込みを作成
- JSONL形式で保存
オプション:
-
--no-lsp: LSP解析を無効化 -
--lsp-only: フォールバックを禁止(LSP解析のみ) -
--chunk-size 6144: 固定長チャンクのサイズ指定
実行例:
$ npm run index -- --source ./src --output ./output
=== Indexing Pipeline Started ===
Source directory: /Users/you/project/src
[1/5] Finding files...
Found 150 files
[2/5] Chunking files...
src/auth/login.ts: 5 chunks
src/auth/register.ts: 3 chunks
...
Created 1,234 chunks
[3/5] Generating summaries...
Progress: 100/1234
Progress: 200/1234
...
Generated 1,234 summaries
[4/5] Extracting metadata...
Extracted metadata for 1,234 chunks
[5/5] Generating embeddings...
Generated 1,234 embeddings
=== Indexing Complete ===
Total documents: 1,234
Total files: 150
Average chunk size: 487 bytes
Index file size: 12 MB
2. 検索
npm run query -- \
--query "認証処理" \
--output ./output
処理内容:
- 疎検索で200件に絞り込み
- 密検索で上位20件を抽出
- 結果をコンソールに表示
実行例:
$ npm run query -- --query "ユーザー認証" --output ./output
Starting query...
Query: "ユーザー認証"
Index: /Users/you/project/output
[1/2] Sparse search...
Indexed 1,234 documents for sparse search
Sparse search: query="ユーザー認証", found 187 results
[2/2] Dense search...
Dense search: query="ユーザー認証", re-ranked 187 → 20 results
=== Query Results ===
1. authenticate
File: src/auth/login.ts:45
Score: 0.9234
Responsibility: ユーザー認証を行い、JWTトークンを生成する
2. validateCredentials
File: src/auth/validator.ts:12
Score: 0.8976
Responsibility: 入力されたメールアドレスとパスワードの妥当性をチェック
...
✅ Found 20 results
3. 階層要約生成
npm run summarize -- \
--output ./output
処理内容:
- チャンク→ファイル→モジュール→システムの要約を生成
-
docs/refactor/summaries/に保存
4. ドキュメント生成
npm run gendocs -- \
--output ./output \
--query "認証処理の改善"
処理内容:
- 階層要約を活用してドキュメント一式を生成
- クエリ指定時は改善提案も生成
-
docs/refactor/に保存
5. 統計情報
npm run stats -- \
--output ./output
スケーラビリティとパフォーマンス
コンテキスト最小化戦略
全文を埋め込まない:
- 要約+シグネチャ+コード抜粋のみをベクトル化
- 生成コードや型定義全量は対象外
階層要約:
- チャンク→ファイル→モジュールと段階的に集約
- 必要な粒度の情報だけを取り出せる
疎→密検索:
- 大量の候補から効率的に絞り込み
- 最終的に20件程度をLLMに渡す
バッチ処理によるスループット向上
// 埋め込み生成は100件ずつバッチ化
async embedBatch(chunks: CodeChunk[], summaries: ChunkSummary[]): Promise<Embedding[]> {
const batchSize = 100;
const embeddings: Embedding[] = [];
for (let i = 0; i < chunks.length; i += batchSize) {
const batch = chunks.slice(i, i + batchSize);
const texts = batch.map((chunk, idx) => {
const summary = summaries[i + idx];
return `${chunk.signature}\n\n${summary.responsibility}\n${summary.inputOutput}`;
});
// バッチでAPI呼び出し
const response = await this.openai.embeddings.create({
model: this.embeddingModel,
input: texts
});
// 結果を格納
response.data.forEach((item, idx) => {
embeddings.push({
chunkId: batch[idx].id,
text: texts[idx],
vector: item.embedding
});
});
console.log(` Embedded ${i + batch.length}/${chunks.length}`);
}
return embeddings;
}
バッチ処理の効果:
- API呼び出し回数: 10,000回 → 100回(1/100)
- 処理時間: 約50分 → 約5分(1/10)
- レート制限回避: 安定した処理
差分更新による継続的メンテナンス
// Git差分から変更ファイルのみ再インデックス
async updateDiff(sourceDir: string, changedFiles: string[]): Promise<void> {
console.log(`\n=== Updating ${changedFiles.length} changed files ===`);
// 変更ファイルのみ処理
const chunks = await this.chunkFiles(changedFiles);
const summaries = await this.generateSummaries(chunks);
const metadataList = await this.extractMetadata(chunks);
const embeddings = await this.embedder.embedBatch(chunks, summaries);
const newDocuments = this.buildDocuments(
chunks,
summaries,
metadataList,
embeddings
);
// 既存インデックスから該当ファイルを削除して新データを追加
await this.storage.updateDiff(changedFiles, newDocuments);
console.log('=== Update Complete ===');
}
差分更新の利点:
| 項目 | 全体再インデックス | 差分更新 |
|---|---|---|
| 処理時間 | 10分 | 10秒(変更1%の場合) |
| APIコスト | $2 | $0.02 |
| 更新頻度 | 週1回 | 毎日可能 |
コスト見積もり
中規模プロジェクト(500ファイル、10,000チャンク)
初回インデックス化:
- 要約生成: 10,000回 × $0.15/1M tokens ≈ $1.5
- 埋め込み: 10,000回 × $0.02/1M tokens ≈ $0.2
- 合計: 約$2
検索:
- クエリ埋め込み: 1回 × $0.02/1M tokens ≈ $0.0001
- クエリごとに約 $0.001(クエリ拡張含む)
ドキュメント生成:
- 階層要約: 約 $0.5
- 詳細ドキュメント: 約 $1-2
差分更新(変更率1%の場合):
- 要約生成: 100回 × $0.15/1M tokens ≈ $0.015
- 埋め込み: 100回 × $0.02/1M tokens ≈ $0.002
- 合計: 約$0.02
月間運用コスト例:
| 項目 | 頻度 | コスト |
|---|---|---|
| 初回インデックス | 1回 | $2 |
| 差分更新(平均変更率3%) | 20回/月 | $1.2 |
| 検索 | 100回/月 | $0.1 |
| ドキュメント生成 | 4回/月 | $6 |
| 月間合計 | - | 約$9 |
拡張性
ベクトルDB対応
現在はインメモリですが、ベクトルDBへの移行が容易です:
// Qdrant統合例
class QdrantDenseSearch extends DenseSearch {
private qdrantClient: QdrantClient;
constructor(client: OpenAI, qdrantUrl: string) {
super(client, models);
this.qdrantClient = new QdrantClient({ url: qdrantUrl });
}
async search(query: string, topK: number): Promise<SearchResult[]> {
// クエリをベクトル化
const queryVector = await this.embedQuery(query);
// Qdrantで検索
const results = await this.qdrantClient.search('code_chunks', {
vector: queryVector,
limit: topK,
with_payload: true
});
// SearchResult形式に変換
return results.map(result => ({
document: result.payload as IndexedDocument,
score: result.score,
method: 'dense'
}));
}
}
対応可能なベクトルDB:
- Qdrant(推奨)
- Weaviate
- Chroma
- Pinecone
- Milvus
新言語の追加
言語サーバーを追加するだけで対応言語を拡張できます:
// config/lsp.ts
export const LANGUAGE_REGISTRATIONS: LanguageRegistration[] = [
// 既存の言語...
{
language: 'ruby',
extensions: ['rb'],
defaultLanguageId: 'ruby',
defaultCommand: 'solargraph',
defaultArgs: ['stdio']
},
{
language: 'php',
extensions: ['php'],
defaultLanguageId: 'php',
defaultCommand: 'intelephense',
defaultArgs: ['--stdio']
}
];
カスタム検索戦略
// カスタム検索戦略の実装
interface SearchStrategy {
search(query: string, documents: IndexedDocument[], topK: number): Promise<SearchResult[]>;
}
class SemanticCodeSearch implements SearchStrategy {
async search(query: string, documents: IndexedDocument[], topK: number): Promise<SearchResult[]> {
// 1. クエリを構造化
const structuredQuery = await this.structureQuery(query);
// 2. 複数の観点で検索
const byName = await this.searchByName(structuredQuery.name, documents);
const byPurpose = await this.searchByPurpose(structuredQuery.purpose, documents);
const bySignature = await this.searchBySignature(structuredQuery.signature, documents);
// 3. スコアを統合
const merged = this.mergeResults([byName, byPurpose, bySignature]);
return merged.slice(0, topK);
}
}
まとめ
このシステムの主な強みは以下の通りです:
1. LSP統合による高度な解析 🔍
IDEと同等のセマンティック理解により、正確なメタデータ抽出を実現。
- シンボル情報の正確な取得
- 参照関係の完全な追跡
- 型情報の活用
- 8言語以上に対応
2. 3段階フォールバック戦略 🛡️
LSP → ts-morph → 正規表現で、あらゆる言語・コードに対応。
- 言語サーバーが利用できない環境でも動作
- 段階的に精度を保ちながらフォールバック
- インテリジェントなマージで情報を補完
3. 2フェーズ設計 📊
事前処理と検索を分離し、LLMコンテキストを効率化。
- データストア作成は一度だけ
- 検索は高速かつ低コスト
- 差分更新で継続的メンテナンス
4. ハイブリッド検索 🔎
疎検索と密検索を組み合わせて、精度と速度を両立。
- BM25で高速に候補を絞り込み
- ベクトル類似度で意味的に再ランキング
- クエリ拡張で検索精度を向上
5. 階層要約 📚
チャンク→ファイル→モジュール→システムと段階的に集約。
- 粒度に応じた情報取得
- コンテキスト最小化
- 必要十分な情報のみをLLMに提供
6. JSONL永続化 💾
差分更新が容易で、大規模データに対応。
- ストリーム処理可能
- 追記が容易
- 並列処理対応
7. 高い拡張性 🚀
ベクトルDB、カスタム検索、新言語の追加が容易。
- プラグイン可能なアーキテクチャ
- 標準プロトコル(LSP)の活用
- モジュール化された設計
おわりに
大規模コードベースのリファクタリングは、適切なアーキテクチャがあれば効率的に行えます。
このシステムでは、LSP統合、ハイブリッド検索、階層要約という3つの柱により、LLMのコンテキストを効率的に使いながら、高精度な解析とドキュメント生成を実現しました。
実装のポイント:
- 全文を埋め込まず、要約+シグネチャのみをベクトル化
- LSPでIDEと同等のセマンティック理解
- 疎→密のハイブリッド検索で精度と速度を両立
- 階層要約で必要な粒度の情報を提供
- JSONL形式で差分更新を容易に
ぜひ、あなたのプロジェクトでも試してみてください!
GitHub リポジトリ: https://github.com/nogataka/refactor-doc-pipeline