コンテンツにスキップ
検索語を入力してください

    ストレージと SQLite

    dagaynの知識グラフはリポジトリ直下の .dagayn/ に保存される。中核は graph.db(SQLite)で、埋め込みは別ファイル embeddings.db に分離する。

    パス内容
    .dagayn/graph.dbノード・エッジ・派生テーブル
    .dagayn/embeddings.dbベクトル埋め込み(任意)
    .dagayn/ 配下その他フローJSON、コミュニティ、Wiki生成物など

    グラフはローカル完結である。チーム共有は通常gitignoreのまま、各開発者がローカルで dagayn build する運用を基本とする。

    • インストール不要で単一ファイル配布できる
    • インメモリ相当の読み取り性能(WAL + mmap)
    • recursive CTEで到達集合を1クエリで取れる
    • Python / Rust双方から安定してバインドできる

    ネットワークグラフDBは過剰である。エージェントが毎タスクで触るのは「このリポジトリの構造」であり、クラスタ横断のリアルタイム同期は要件に入らない。

    各シンボル・文書セクション・Terraform blockは1行になる。

    役割
    kindFunction, Class, DocSection
    qualified_nameグラフ上の安定ID(UNIQUE
    file_pathリポジトリルート相対パス
    line_start / line_endソース位置
    languageパーサ種別
    is_testテストコード判定
    extraJSON。言語固有メタデータ

    エッジは source_id / target_id ではなく qualified_name 文字列 で結ぶ。

    CREATE TABLE edges (
    kind TEXT NOT NULL,
    source_qualified TEXT NOT NULL,
    target_qualified TEXT NOT NULL,
    file_path TEXT NOT NULL,
    confidence REAL DEFAULT 1.0,
    confidence_tier TEXT DEFAULT 'EXTRACTED',
    extra TEXT DEFAULT '{}'
    );

    この設計の利点:

    • パーサがnode IDを知らない段階でエッジを吐ける
    • ファイル単位の差し替え更新が単純
    • Markdown / Terraform / コードを同じkey空間に載せられる
    • MCPレスポンスが人間可読

    整数ID joinの方が速い場面は、派生テーブルやクエリ時のmaterializeで補う。

    探索は「このノードから出るエッジ」「入るエッジ」「kindで絞る」の繰り返しである。

    CREATE INDEX idx_edges_source ON edges(source_qualified);
    CREATE INDEX idx_edges_target ON edges(target_qualified);
    CREATE INDEX idx_edges_kind ON edges(kind);
    CREATE INDEX idx_edges_target_kind ON edges(target_qualified, kind);
    CREATE INDEX idx_edges_source_kind ON edges(source_qualified, kind);

    書き込みベンチマークでも効く地味な設定:

    PRAGMA journal_mode=WAL;
    PRAGMA synchronous=NORMAL;
    PRAGMA cache_size=-64000;
    PRAGMA mmap_size=268435456;
    PRAGMA temp_store=MEMORY;

    WALは読み取りと書き込みの並行に強く、hookによる頻繁な update とMCPクエリの共存に向く。

    import循環、ドキュメント相互参照、Terraform module参照は現実に存在する。保存時に循環を弾くと壊れた構造を観測できない

    • CONTAINS はほぼ木
    • CALLS は循環が普通
    • IMPORTS_FROM は循環してほしくない

    DAGとしての検査(ADP)は後処理で、依存エッジの部分グラフに対して行う。詳細は 構造メトリクス を参照。

    dagayn update はファイル単位のatomic replacementである。

    changed file
    → parse file
    → DELETE old nodes/edges for file_path
    → INSERT new nodes/edges

    パーサは「このファイルから見えたノード / エッジ」だけを吐けばよい。過去との差分計算はDB側で古い行を消して差し替える。

    変更検出はtracked diffに加え、staged / unstaged / untracked も含む。新規ファイルはステージしなくてもパース対象になる。

    複数ファイルは store_file_batch でまとめて書き込み、トランザクション境界を粗く保つ。

    リクエスト時に毎回計算しないもの:

    派生テーブル / 成果物
    全文検索nodes_fts(FTS5 仮想テーブル)
    実行フローflows, flow_memberships
    コミュニティcommunities
    Hub / Bridgehub_scores, bridge_scores
    リスク指標risk_index
    raw graph derived (postprocess)
    ───────── ───────────────────
    nodes → nodes_fts
    edges → flows / communities / hub_scores

    MCPツールはユーザー操作の前段にある。毎回全edgeを読みNetworkXでcentralityを計算する形にはしない。

    BFS / DFSでnodeごとにSQLを投げるとN+1になる。frontierを層ごとにまとめる。

    frontier = [start]
    while frontier:
    nodes = SELECT * FROM nodes WHERE qualified_name IN (frontier)
    edges = SELECT * FROM edges
    WHERE source_qualified IN (frontier)
    OR target_qualified IN (frontier)
    next_frontier = build in memory

    edge kindごとの重み、token budget、双方向探索、応答整形が必要なら、アプリ側traversalを選ぶ。

    届くノード集合だけ欲しいときは、SQLiteのrecursive CTEが相性良い。

    WITH RECURSIVE impacted(qn, depth) AS (
    SELECT ?, 0
    UNION
    SELECT e.target_qualified, impacted.depth + 1
    FROM edges e
    JOIN impacted ON e.source_qualified = impacted.qn
    WHERE impacted.depth < ?
    )
    SELECT qn, depth FROM impacted;

    impact radiusのような到達集合の一括取得に使う。詳細は レビューと影響分析 を参照。

    Python GraphStore がCLI・MCP・テスト向けの安定APIを提供する。Rust dagayn_core はPyO3経由でホットパスを加速する。

    責務
    Python GraphStoreスキーマ互換、トランザクション、パス正規化、キャッシュ
    Rust backendバッチ格納、パース、フロー/コミュニティ永続化、centrality 計算

    新規コードはRust binding直接ではなく GraphStore メソッドに依存する。