idea_world_labDEV JOURNAL
2026年6月27日土曜日

ソース入力からASTとRetrieverまでつながるライン追跡フロー

作成日: 2026年 6月 27日

目的

この文書は docs/roadmaps/2026-06-26-source-to-ast-input-flow.md で一貫性が失われていた部分を 27日基準で再整理した文書です。

重要なのは新しい設計単位を再定義することではありません。すでに全体アーキテクチャとDB設計の方向性は別途整理されています。ここでは実際に入力が入ったとき、どのファイルのどの行が AST Parser に渡され、どの断片が Retriever 検索に使用され、どの根拠候補が返ってきて LLM の判断につながるかを最後まで追跡します。

固定すべき原則は単純です。

ファイルパスは位置を追跡するために付けます。  
Retrieverにはファイルパスではなくコード/本文の断片が入ります。  
.gd ファイルは AST パーサーが関数と宣言単位で切り分けます。  
それ以外の必要なテキストファイルは行範囲単位で直接切り分けます。  
各断片は順番に Retriever と LLM の判断を経ます。

入力形式

プロジェクトをクローンまたはアップロードすると、まずファイルを相対パスとともに展開します。

形式は次のように見えます。

# <相対パス>
<ファイル本文>

# <相対パス>
<ファイル本文>

この表記は人がデバッグするためのものです。 # player.gd のようなヘッダーは「この下の本文がどのファイルから来たか」を示す表示にすぎません。

Retriever 検索には # player.gd は含まれません。AST Parser にも # player.gd は含まれません。含まれるのはヘッダー下の実際のファイル本文です。

例示入力

以下は小さな Godot プロジェクトが展開された様子です。 E001E002 のような番号は説明用の行番号であり、実際の入力文字には含まれません。

E001 # project.godot
E002 config_version=5
E003
E004 [application]
E005 config/name="Mini Dodge"
E006 run/main_scene="res://player.tscn"
E007
E008 # player.gd
E009 extends Area2D
E010
E011 signal hit
E012
E013 @export var speed = 400
E014 var screen_size
E015
E016 func _ready():
E017 	screen_size = get_viewport_rect().size
E018 	hide()
E019
E020 func _process(delta):
E021 	var velocity = Vector2.ZERO
E022 	if Input.is_action_pressed(&"move_right"):
E023 		velocity.x += 1
E024 	if Input.is_action_pressed(&"move_left"):
E025 		velocity.x -= 1
E026
E027 	if velocity.length() > 0:
E028 		velocity = velocity.normalized() * speed
E029 		$AnimatedSprite2D.play()
E030 	else:
E031 		$AnimatedSprite2D.stop()
E032
E033 	position += velocity * delta
E034 	position = position.clamp(Vector2.ZERO, screen_size)
E035
E036 func start(pos):
E037 	position = pos
E038 	show()
E039 	$CollisionShape2D.disabled = false
E040
E041 # player.tscn
E042 [gd_scene load_steps=3 format=3 uid="uid://mini"]
E043
E044 [ext_resource type="Script" path="res://player.gd" id="1"]
E045
E046 [node name="Player" type="Area2D"]
E047 script = ExtResource("1")

1段階: ファイル境界の分割

まず # <相対パス> ヘッダーを基準にファイルを分割する。

project.godot は E001 から開始する。実際の本文は E002 から E006 までである。E001 はファイル境界表示なので分析本文ではない。

player.gd は E008 から開始する。実際の本文は E009 から E039 までである。E008 はファイル境界表示なので AST Parser には入らない。

player.tscn は E041 から開始する。実際の本文は E042 から E047 までである。E041 はファイル境界表示なので Retriever 検索本文ではない。

この段階で UI は “現在 player.gd の E009‑E039 を処理する” のように表示できる。ただし検索語はまだ作成しない。

2段階: ファイル種別ごとの処理方針を決める

player.gd は GDScript ファイルである。したがって E009‑E039 全体が AST Parser に入る。

project.godot は設定ファイルである。AST Parser に送らず、必要なセクション単位で直接切り出す。

player.tscn はシーンファイルである。AST Parser に送らず、node/resource ブロック単位で直接切り出す。

README や Markdown 説明文書はソースコード分析モードでは基本的に除外する。除外されたファイルも UI には表示すべきである。理由は “何が抜けているか” を人が確認できるようにするためである。

例えば README.md がある場合、分析から除外され、除外理由が “ソースコード分析モードでは文書ファイルを基本除外” と表示される。ただし、この除外決定は分析モードのデフォルト値に過ぎず、後で文書まで読み込むモードが必要になった場合は別途扱う。

3段階: .gd ファイルを AST Parser に渡す

player.gd で AST Parser に入る入力は E009‑E039 である。

実際に AST Parser が受け取る本文は次のとおりである。

extends Area2D

signal hit

@export var speed = 400
var screen_size

func _ready():
	screen_size = get_viewport_rect().size
	hide()

func _process(delta):
	var velocity = Vector2.ZERO
	if Input.is_action_pressed(&"move_right"):
		velocity.x += 1
	if Input.is_action_pressed(&"move_left"):
		velocity.x -= 1

	if velocity.length() > 0:
		velocity = velocity.normalized() * speed
		$AnimatedSprite2D.play()
	else:
		$AnimatedSprite2D.stop()

	position += velocity * delta
	position = position.clamp(Vector2.ZERO, screen_size)

func start(pos):
	position = pos
	show()
	$CollisionShape2D.disabled = false

ここには # player.gd がありません。

AST Parserはこの本文を上から下へ読み取り、宣言と関数単位で切り分けます。

最初の断片はE009の1行です。

extends Area2D

2番目のピースはE011の1行です。

signal hit

第3のピースはE013‑E014です。

@export var speed = 400
var screen_size

第4ピースはE016‑E018です。

func _ready():
	screen_size = get_viewport_rect().size
	hide()

5番目のピースはE020-E034です。

func _process(delta):
	var velocity = Vector2.ZERO
	if Input.is_action_pressed(&"move_right"):
		velocity.x += 1
	if Input.is_action_pressed(&"move_left"):
		velocity.x -= 1

	if velocity.length() > 0:
		velocity = velocity.normalized() * speed
		$AnimatedSprite2D.play()
	else:
		$AnimatedSprite2D.stop()

	position += velocity * delta
	position = position.clamp(Vector2.ZERO, screen_size)

第6のピースはE036‑E039です。

func start(pos):
	position = pos
	show()
	$CollisionShape2D.disabled = false

重要な点は、スライスが作成された後でも元の行範囲を失わないことです。例えば、_process スライスは引き続き “player.gd の E020‑E034 からのスライス” として追跡されます。

4段階: スライスを直接処理する

AST パーサーの対象でないテキストファイルが必要な場合も、スライスに分割します。

project.godot では E004‑E006 がアプリケーション設定スライスになります。

[application]
config/name="Mini Dodge"
run/main_scene="res://player.tscn"

この断片は run/main_scene="res://player.tscn" のため、プロジェクトの開始シーンを探すのに使用できる。Retriever検索を行う場合、本文中の run/main_sceneres://player.tscnapplication などの内容が検索材料になる。project.godot というファイルパス自体が検索語になるわけではない。

player.tscn では E044 の1行がスクリプト参照の断片になる。

[ext_resource type="Script" path="res://player.gd" id="1"]

E046-E047は node の断片になる。

[node name="Player" type="Area2D"]
script = ExtResource("1")

このような直接的なチャンクは AST チャンクではありませんが、以降のフローは同じです。本文の一部を使って Retriever で検索し、検索された候補を LLM が再検証します。

第5段階: Retriever に実際に入る内容

Retriever には “ファイルパス” ではなく “現在のチャンクの本文と本文から抽出された検索手がかり” が入ります。

例えば _process チャンクを処理する際、検索基準となる本文は E020‑E034 です。

func _process(delta):
	var velocity = Vector2.ZERO
	if Input.is_action_pressed(&"move_right"):
		velocity.x += 1
	if Input.is_action_pressed(&"move_left"):
		velocity.x -= 1

	if velocity.length() > 0:
		velocity = velocity.normalized() * speed
		$AnimatedSprite2D.play()
	else:
		$AnimatedSprite2D.stop()

	position += velocity * delta
	position = position.clamp(Vector2.ZERO, screen_size)

この本文で検索に使用できる手がかりは次のように示されています。

Input.is_action_pressed
Vector2.ZERO
velocity.normalized
position.clamp
AnimatedSprite2D
_process
Godot 3
Godot 4
migration

上記の手がかりの中で Godot 3Godot 4migration は、ユーザープロンプトが「このプロジェクトが Godot 3 か Godot 4 かを確認し、必要に応じて変換根拠を探してほしい」のようなリクエストのときに付与されます。

Retriever の検索はこの断片本文と手がかりを利用します。

ベクトル検索には _process 関数本文全体と主要シンボルが含まれます。

キーワード検索には Input.is_action_pressedVector2.ZEROposition.clamp のようなシンボル名が入ります。

ファイルパス player.gd は検索語として使用しません。ただし、後で「この検索が player.gd の E020‑E034 で発生した」とログや UI に付加する際に使用します。

6段階: Retriever が何を探すか

Retriever は同じ断片で docs_chunksapi_mappinglabel_prototypes を同様の方法で検索します。3 つのテーブルのうちどれも特別扱いはしません。

docs_chunks では Input.is_action_pressedVector2.ZEROpositionArea2D のような公式ドキュメント説明候補が出てくる可能性があります。

api_mapping では Godot 3 から Godot 4 へ変更された関数名、クラス名、プロパティ名の候補が出てくる可能性があります。たとえばコード断片に KinematicBody2DyieldColor8 のような手がかりがあれば、関連する migration 候補が出てくることがあります。

label_prototypes では「関数の使用方法が丸ごと変わるパターン」や「引数構成と呼び出し形態が変わる例」の候補が出てくる可能性があります。単に名前が変わっただけでなく、使用方法自体を説明する必要がある場合に意味があります。

たとえば _process 断片では次のような候補が返ってくることがあります。

docs_chunks 候補:
  Input.is_action_pressed 公式ドキュメントの説明
  Vector2.ZERO 公式ドキュメントの説明
  Node2D.position 公式ドキュメントの説明

api_mapping 候補:
  この断片では特別な Godot 3 -> 4 の名前変更候補がない場合があります

label_prototypes 候補:
  Input.is_action_pressed と Vector2.ZERO を利用した Godot 4 の移動コード例

この候補は正解ではありません。「このコード片に関連している可能性がある根拠」だけです。

7段階: Retriever の結果を LLM に渡す

LLM にはユーザーのプロンプト、現在のコード片、Retriever の候補が一緒に入ります。

例えば _process 片では次のように入ります。

ユーザーリクエスト:
このプロジェクトが Godot 3 か Godot 4 かを確認し、必要な場合は変換の根拠を探してください。

現在のコード片:
player.gdの E020-E034

func _process(delta):
	var velocity = Vector2.ZERO
	if Input.is_action_pressed(&"move_right"):
		velocity.x += 1
	if Input.is_action_pressed(&"move_left"):
		velocity.x -= 1

	if velocity.length() > 0:
		velocity = velocity.normalized() * speed
		$AnimatedSprite2D.play()
	else:
		$AnimatedSprite2D.stop()

	position += velocity * delta
	position = position.clamp(Vector2.ZERO, screen_size)

Retriever 候補:
- docs_chunks での Input.is_action_pressed 公式文書候補
- docs_chunks での Vector2.ZERO 公式文書候補
- docs_chunks での Node2D.position 公式文書候補
- label_prototypes での Godot 4 入力処理例候補

LLMはここで二つを判断する。

1つ目は、検索された候補が実際に現在のコード片と関連があるかを確認すること。

2つ目は、現在のコード片が Godot 3 側のシグナルか、Godot 4 側のシグナルか、移行が必要かを判断すること。

この例では Input.is_action_pressedVector2.ZEROposition.clamp が Godot 4 でも使用できるコード根拠として解釈できる。したがってこの片は「Godot 4 のコードとして見なす、移行は不要」側に蓄積される可能性が高い。

8段階: Godot 3 例示片のフロー

別のファイルに次の片があると仮定する。

G101 # enemy.gd
G102 extends KinematicBody2D
G103
G104 var velocity = Vector2.ZERO
G105
G106 func _physics_process(delta):
G107 	velocity = move_and_slide(velocity)

enemy.gd.gd ファイルなので AST Parser に G102-G107 本文が入ります。

最初のピースは G102です。

extends KinematicBody2D

第2のピースは G106-G107 です。

func _physics_process(delta):
	velocity = move_and_slide(velocity)

extends KinematicBody2D の断片を Retriever に渡すと、検索手がかりは次のように取得されます。

KinematicBody2D
Godot 3
Godot 4
migration

このとき api_mapping では KinematicBody2D が Godot 4 で CharacterBody2D 系にマイグレーションされる候補が出てくる可能性があります。

docs_chunks では CharacterBody2D や physics body に関する公式ドキュメントの候補が出てくる可能性があります。

label_prototypes では KinematicBody2D ベースの移動コードが CharacterBody2Dvelocitymove_and_slide() の方式に変わる例の候補が出てくる可能性があります。

LLM には次のように入ります。

ユーザーのリクエスト:
このプロジェクトが Godot 3 か Godot 4 かを確認し、必要な場合は変換の根拠を探してください。

現在のコード片:
enemy.gdの G102

extends KinematicBody2D

Retriever 候補:
api_mapping で KinematicBody2D -> CharacterBody2D 移行候補
docs_chunks で CharacterBody2D 公式ドキュメント候補
label_prototypes で KinematicBody2D 移行コード変換例候補

LLMはこの断片を「Godot 3 のシグナルが強く、マイグレーションが必要」と判断できる。

その後、G106‑G107 の断片も別途処理する。

func _physics_process(delta):
	velocity = move_and_slide(velocity)

この断片では move_and_slide(velocity) の呼び出し方式が検索手がかりになる。関数名だけを見るのではなく、引数が入る呼び出し形までを見る。

このとき単純に関数名が変わるだけなら api_mapping の候補が重要で、呼び出し方式全体が変わる場合は label_prototypes の候補も同時に重要になる。

9段階: 断片結果の蓄積

各断片は一つずつ判断される。

player.gd の E020‑E034 は Godot 4 側のシグナルとして蓄積できる。

enemy.gd の G102 は Godot 3 側のシグナルとして蓄積できる。

enemy.gd の G106‑G107 も Godot 3 マイグレーションシグナルとして蓄積できる。

プロジェクト全体の判断はファイル名で行わない。ファイルパスは追跡用である。プロジェクト全体の判断は各コード断片の判断が蓄積された結果で行う。

例えば、あるプロジェクト内で Godot 4 のシグナルが大半で、一部に Godot 3 マイグレーションシグナルが出てきた場合、mixed に近い判断になる可能性がある。逆に Godot 3 マイグレーションシグナルが大半であれば、Godot 3 プロジェクトとして分類できる。

中断と再開

RunPod やローカルウェブサーバーが途中で止まることがある。そのため処理はファイル単位ではなく断片単位で継続できる必要がある。

例えば player.gd の E009、E011、E013‑E014 まで処理し、E016‑E018 で止まった場合、再起動後は E016‑E018 から再開しなければならない。

既に完了した E009、E011、E013‑E014 を再度処理してはいけない。

ウェブ UI では以下が表示されるべきである。

player.gd E009 処理完了  
player.gd E011 処理完了  
player.gd E013-E014 処理完了  
player.gd E016-E018 処理中止  
player.gd E020-E034 待機  
player.gd E036-E039 待機

このように表示されれば「既存の処理が引き継がれるか、最初からやり直すか」を確認できる。

デバッグ画面で表示すべきこと

デバッグ画面はトレースのために必要です。

最低限、次のものが表示される必要があります。

現在のファイル: player.gd  
現在の行範囲: E020-E034  
現在のスニペット本文:
func _process(delta):
	...

Retrieverに入った検索手がかり:
- Input.is_action_pressed
- Vector2.ZERO
- position.clamp

検索された候補:
- docs_chunks: Input.is_action_pressed
- docs_chunks: Vector2.ZERO
- label_prototypes: Godot 4 input polling example

LLM判断:
- Godot 4 信号
- migration 必要なし

ここで重要なのは「何が検索に入ったか」と「何が単に追跡用として付けられたか」が分離して見えることです。

player.gdというパスは現在のファイル表示には見えても構いません。しかし Retriever の検索手掛かり一覧には入れてはいけません。

整理

27日基準の文書で固定する流れは次のとおりです。

# <相対パス>
<本文>

-> ヘッダーでファイルの境界だけを分割する。  
-> .gd ファイルの本文は AST パーサーに入る。  
-> AST パーサーが関数と宣言を順番に切り取る。  
-> 各断片の実際の本文とシンボル手がかりが Retriever 検索に入る。  
-> docs_chunks、api_mapping、label_prototypes を同じ方式で検索する。  
-> 検索候補と現在の断片を LLM に一緒に渡す。  
-> LLM の判断を断片単位で保存し、プロジェクト単位で蓄積する。

この文書の目的は新しい構造を決めることではなく、実際の入力がどの行で切れ、どの本文が次の段階に進むかを確認できるようにすることです。実装時もこの基準に従って、Web UIで現在のファイル、現在の行範囲、実際に伝達された本文、Retriever の検索手がかり、検索候補、LLM の判断を一緒に表示する必要があります。