ryo_app/intent.rs
1//! Intent: Public DSL for code transformation
2//!
3//! # Architecture: Intent vs MutationSpec
4//!
5//! `Intent` is the **user-facing DSL** for `ryo run` command.
6//! It provides a high-level, pattern-based interface for specifying transformations.
7//!
8//! ```text
9//! ┌─────────────────────────────────────────────────────────────────┐
10//! │ Intent (this module) │
11//! │ - Public DSL: `ryo run -f goal.json` │
12//! │ - Pattern-based: Pattern::Glob("*Config") │
13//! │ - High-level: one Intent may affect multiple symbols │
14//! └───────────────────────────┬─────────────────────────────────────┘
15//! ↓ Symbol Resolution
16//! ┌─────────────────────────────────────────────────────────────────┐
17//! │ MutationSpec (ryo-executor::executor::spec) │
18//! │ - Execution-level: concrete targets │
19//! │ - One Intent → N MutationSpecs (after pattern expansion) │
20//! │ - Direct SymbolId/name targeting │
21//! └───────────────────────────┬─────────────────────────────────────┘
22//! ↓ Execution
23//! ┌─────────────────────────────────────────────────────────────────┐
24//! │ AST Mutation │
25//! └─────────────────────────────────────────────────────────────────┘
26//! ```
27//!
28//! ## Intent vs MutationSpec: When to Use
29//!
30//! | Layer | Use Case | Example |
31//! |-------|----------|---------|
32//! | Intent | CLI users, DSL files | `AddField { target: "*Config", ... }` |
33//! | MutationSpec | Suggest system, programmatic API | `AddField { struct_name: "AppConfig", ... }` |
34//!
35//! The `Suggest` system bypasses Intent and generates `MutationSpec` directly,
36//! since it already has resolved symbols from analysis.
37//!
38//! # IntentExtractor (NL → Goal)
39//!
40//! ```text
41//! ┌─────────────────────────────────────────────────────────────────┐
42//! │ User Query (Natural Language) │
43//! │ "ConfigをDatabaseConfigに統一" │
44//! └───────────────────────────┬─────────────────────────────────────┘
45//! ↓
46//! ┌─────────────────────────────────────────────────────────────────┐
47//! │ IntentExtractor │
48//! │ ・責務: NLクエリ → Goal (Intent + Scope + Constraints) │
49//! │ ・LLM呼び出し1回のみ │
50//! │ ・検索・変換には一切関与しない │
51//! └───────────────────────────┬─────────────────────────────────────┘
52//! ↓
53//! ┌─────────────────────────────────────────────────────────────────┐
54//! │ Goal │
55//! │ ・intent: RenameIdent { from: "Config", to: "DatabaseConfig" } │
56//! │ ・scope: ScopeHint { file_patterns: ["**/*.rs"], ... } │
57//! │ ・constraints: [MustCompile] │
58//! └─────────────────────────────────────────────────────────────────┘
59//! ```
60
61use ryo_executor::executor::{EnumToTraitStrategy, MatchHandling};
62#[cfg(feature = "schemars")]
63use schemars::JsonSchema;
64use serde::{Deserialize, Serialize};
65
66fn default_true() -> bool {
67 true
68}
69
70fn default_confidence() -> f64 {
71 1.0
72}
73
74fn default_ident_kind_any() -> IdentKind {
75 IdentKind::Any
76}
77
78fn default_variant_type() -> String {
79 "unit".to_string()
80}
81
82fn default_method_body() -> String {
83 "todo!()".to_string()
84}
85
86// Re-export ItemKind from ryo-source (canonical definition)
87pub use ryo_source::ItemKind;
88
89// ============================================================================
90// Goal: IntentExtractorの出力
91// ============================================================================
92
93/// コンフリクト解決戦略
94#[cfg_attr(feature = "schemars", derive(JsonSchema))]
95#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
96pub enum ConflictStrategy {
97 /// Intentsの順序で実行(同じターゲットでも順序付けで解決)
98 #[default]
99 IntentOrder,
100 /// コンフリクトでエラー(厳密モード)
101 Fail,
102 /// 並列実行可能なもののみ実行、それ以外はスキップ
103 ParallelOnly,
104}
105
106/// 抽出されたゴール
107///
108/// IntentExtractorの唯一の出力物。これ以降の処理には関与しない。
109#[cfg_attr(feature = "schemars", derive(JsonSchema))]
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct Goal {
112 /// 元のクエリテキスト(DSL直接実行時は空でOK)
113 #[serde(default)]
114 pub query: String,
115
116 /// 抽出されたIntent(複数可)
117 pub intents: Vec<Intent>,
118
119 /// 検索スコープのヒント(並列Discovery用)
120 #[serde(default)]
121 pub scope: ScopeHint,
122
123 /// 実行時の制約条件
124 #[serde(default)]
125 pub constraints: Vec<Constraint>,
126
127 /// コンフリクト解決戦略
128 #[serde(default)]
129 pub conflict_strategy: ConflictStrategy,
130
131 /// 抽出時の信頼度(0.0-1.0)
132 #[serde(default = "default_confidence")]
133 pub confidence: f64,
134}
135
136// ============================================================================
137// Intent: 変換意図
138// ============================================================================
139
140/// 変換意図
141///
142/// # Future Intents (Not Yet Implemented)
143///
144/// The following Intents are planned for future implementation:
145///
146/// - `Decorator` → AddFunction + Rename (wrap function with logging/timing/retry)
147/// - `ExtractFunction` → AddFunction + ReplaceExpr (extract statements into new function)
148/// - `InlineFunction` → ReplaceExpr + RemoveFunction (inline function calls)
149///
150/// These will be implemented as compositions of existing MutationSpecs.
151#[cfg_attr(feature = "schemars", derive(JsonSchema))]
152#[derive(Debug, Clone, Serialize, Deserialize)]
153#[serde(tag = "type", deny_unknown_fields)]
154pub enum Intent {
155 // === 識別子リネーム系 ===
156 /// 単一識別子のリネーム
157 RenameIdent {
158 // --- Target (3-field) ---
159 /// SymbolId(O(1)確定、"7v2"形式)
160 #[serde(default, skip_serializing_if = "Option::is_none")]
161 symbol_id: Option<String>,
162 /// シンボルパス("tokio::net::TcpStream"形式)
163 #[serde(default, skip_serializing_if = "Option::is_none")]
164 symbol_path: Option<String>,
165 /// リネーム元の識別子名(診断用、フォールバック検索)
166 #[serde(
167 default,
168 skip_serializing_if = "Option::is_none",
169 alias = "target",
170 alias = "target_name"
171 )]
172 target_ident: Option<String>,
173 // --- Rename attributes ---
174 to: String,
175 #[serde(default = "default_ident_kind_any")]
176 kind: IdentKind,
177 },
178
179 // === 構造変更系 ===
180 /// 可視性変更
181 ChangeVisibility {
182 /// SymbolId(O(1)確定、"7v2"形式)
183 #[serde(default, skip_serializing_if = "Option::is_none")]
184 symbol_id: Option<String>,
185 /// シンボルパス("crate::module::Type"形式)
186 #[serde(default, skip_serializing_if = "Option::is_none")]
187 symbol_path: Option<String>,
188 /// ターゲット名(診断用、フォールバック検索)
189 #[serde(
190 default,
191 skip_serializing_if = "Option::is_none",
192 alias = "target",
193 alias = "target_name"
194 )]
195 target_item: Option<String>,
196 /// 変更後のvisibility
197 to: Visibility,
198 },
199
200 /// アイテムを別モジュールに移動
201 MoveItem {
202 // --- Target (3-field) ---
203 /// SymbolId(O(1)確定、"7v2"形式)
204 #[serde(default, skip_serializing_if = "Option::is_none")]
205 symbol_id: Option<String>,
206 /// シンボルパス("tokio::net::TcpStream"形式)
207 #[serde(default, skip_serializing_if = "Option::is_none")]
208 symbol_path: Option<String>,
209 /// 移動対象の名前(診断用、フォールバック検索)
210 #[serde(
211 default,
212 skip_serializing_if = "Option::is_none",
213 alias = "target",
214 alias = "target_name"
215 )]
216 target_item: Option<String>,
217 // --- Move attributes ---
218 to_module: String,
219 },
220
221 /// implからトレイトを抽出
222 ExtractTrait {
223 // --- Target impl (3-field) ---
224 /// SymbolId(O(1)確定、"7v2"形式)
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 symbol_id: Option<String>,
227 /// シンボルパス("tokio::net::TcpStream"形式)
228 #[serde(default, skip_serializing_if = "Option::is_none")]
229 symbol_path: Option<String>,
230 /// impl対象の型名(診断用、フォールバック検索)
231 #[serde(
232 default,
233 skip_serializing_if = "Option::is_none",
234 alias = "target",
235 alias = "target_name"
236 )]
237 target_type: Option<String>,
238 // --- Extract attributes ---
239 trait_name: String,
240 /// 抽出するメソッド(空 = 全メソッド)
241 #[serde(default)]
242 methods: Vec<String>,
243 },
244
245 /// トレイトをインライン化(implに戻す)
246 InlineTrait {
247 // --- Target trait (3-field) ---
248 /// Trait SymbolId
249 #[serde(default, skip_serializing_if = "Option::is_none")]
250 trait_symbol_id: Option<String>,
251 /// Trait シンボルパス
252 #[serde(default, skip_serializing_if = "Option::is_none")]
253 trait_symbol_path: Option<String>,
254 /// Trait名(診断用、フォールバック検索)
255 #[serde(
256 default,
257 skip_serializing_if = "Option::is_none",
258 alias = "target",
259 alias = "target_name"
260 )]
261 target_trait: Option<String>,
262 // --- Target struct (3-field) ---
263 /// Struct SymbolId
264 #[serde(default, skip_serializing_if = "Option::is_none")]
265 struct_symbol_id: Option<String>,
266 /// Struct シンボルパス
267 #[serde(default, skip_serializing_if = "Option::is_none")]
268 struct_symbol_path: Option<String>,
269 /// Struct名(診断用、フォールバック検索)
270 #[serde(default, skip_serializing_if = "Option::is_none")]
271 target_struct: Option<String>,
272 // --- Inline attributes ---
273 /// トレイト定義を削除するか
274 #[serde(default = "default_true")]
275 remove_trait: bool,
276 },
277
278 /// EnumをTraitに変換(Replace Conditional with Polymorphism)
279 ///
280 /// Enum variants become struct implementations of the generated trait.
281 /// Use `strategy` to control type annotation replacement.
282 EnumToTrait {
283 // --- Target enum (3-field) ---
284 /// SymbolId(O(1)確定、"7v2"形式)
285 #[serde(default, skip_serializing_if = "Option::is_none")]
286 symbol_id: Option<String>,
287 /// シンボルパス("tokio::net::TcpStream"形式)
288 #[serde(default, skip_serializing_if = "Option::is_none")]
289 symbol_path: Option<String>,
290 /// 変換対象のEnum名(診断用、フォールバック検索)
291 #[serde(
292 default,
293 skip_serializing_if = "Option::is_none",
294 alias = "target",
295 alias = "target_name"
296 )]
297 target_enum: Option<String>,
298 // --- Conversion attributes ---
299 /// 生成するTrait名(省略時はEnum名を使用)
300 #[serde(default)]
301 new_trait_name: Option<String>,
302 /// 元のEnumを削除するか
303 #[serde(default = "default_true")]
304 remove_enum: bool,
305 /// 変換戦略: Dynamic (Box<dyn>), Static (impl), MarkerOnly
306 #[serde(default)]
307 #[cfg_attr(feature = "schemars", schemars(skip))]
308 strategy: EnumToTraitStrategy,
309 /// match式の処理方法
310 #[serde(default)]
311 #[cfg_attr(feature = "schemars", schemars(skip))]
312 match_handling: MatchHandling,
313 },
314
315 // === モジュール操作系 ===
316 // Note: AddMod was consolidated into CreateMod.
317 /// モジュール宣言を削除
318 RemoveMod {
319 /// 親モジュールパス (空 = crate root)
320 #[serde(default)]
321 parent_mod: Vec<String>,
322 /// 削除するモジュール名
323 mod_name: String,
324 },
325
326 /// モジュールファイルを作成
327 CreateMod {
328 /// 親モジュールパス (空 = crate root)
329 #[serde(default)]
330 parent_mod: Vec<String>,
331 /// 作成するモジュール名
332 mod_name: String,
333 /// 初期コンテンツ
334 #[serde(default)]
335 content: String,
336 /// public かどうか
337 #[serde(default)]
338 is_pub: bool,
339 },
340
341 // === フィールド操作系 ===
342 /// フィールドを追加
343 AddField {
344 // --- Target struct (3-field) ---
345 /// SymbolId(O(1)確定、"7v2"形式)
346 #[serde(default, skip_serializing_if = "Option::is_none")]
347 symbol_id: Option<String>,
348 /// シンボルパス("tokio::net::TcpStream"形式)
349 #[serde(default, skip_serializing_if = "Option::is_none")]
350 symbol_path: Option<String>,
351 /// 対象の構造体名(診断用、フォールバック検索)
352 #[serde(
353 default,
354 skip_serializing_if = "Option::is_none",
355 alias = "target",
356 alias = "target_name"
357 )]
358 target_struct: Option<String>,
359 // --- Field attributes ---
360 field_name: String,
361 field_type: String,
362 #[serde(default)]
363 is_pub: bool,
364 },
365
366 /// フィールドを削除
367 RemoveField {
368 // --- Target struct (3-field) ---
369 /// SymbolId(O(1)確定、"7v2"形式)
370 #[serde(default, skip_serializing_if = "Option::is_none")]
371 symbol_id: Option<String>,
372 /// シンボルパス("tokio::net::TcpStream"形式)
373 #[serde(default, skip_serializing_if = "Option::is_none")]
374 symbol_path: Option<String>,
375 /// 対象の構造体名(診断用、フォールバック検索)
376 #[serde(
377 default,
378 skip_serializing_if = "Option::is_none",
379 alias = "target",
380 alias = "target_name"
381 )]
382 target_struct: Option<String>,
383 // --- Field attributes ---
384 field_name: String,
385 },
386
387 // === Derive操作系 ===
388 /// Deriveマクロを追加
389 AddDerive {
390 // --- Target type (3-field) ---
391 /// SymbolId(O(1)確定、"7v2"形式)
392 #[serde(default, skip_serializing_if = "Option::is_none")]
393 symbol_id: Option<String>,
394 /// シンボルパス("tokio::net::TcpStream"形式)
395 #[serde(default, skip_serializing_if = "Option::is_none")]
396 symbol_path: Option<String>,
397 /// 対象の型名(診断用、フォールバック検索)
398 #[serde(
399 default,
400 skip_serializing_if = "Option::is_none",
401 alias = "target",
402 alias = "target_name"
403 )]
404 target_type: Option<String>,
405 // --- Derive attributes ---
406 derives: Vec<String>,
407 },
408
409 /// Deriveマクロを削除
410 RemoveDerive {
411 // --- Target type (3-field) ---
412 /// SymbolId(O(1)確定、"7v2"形式)
413 #[serde(default, skip_serializing_if = "Option::is_none")]
414 symbol_id: Option<String>,
415 /// シンボルパス("tokio::net::TcpStream"形式)
416 #[serde(default, skip_serializing_if = "Option::is_none")]
417 symbol_path: Option<String>,
418 /// 対象の型名(診断用、フォールバック検索)
419 #[serde(
420 default,
421 skip_serializing_if = "Option::is_none",
422 alias = "target",
423 alias = "target_name"
424 )]
425 target_type: Option<String>,
426 // --- Derive attributes ---
427 derives: Vec<String>,
428 },
429
430 // === Enum操作系 ===
431 /// Enumを追加
432 AddEnum {
433 /// 追加先モジュールパス(必須: "my_crate", "my_crate::domain"等)
434 symbol_path: String,
435 name: String,
436 #[serde(default)]
437 variants: Vec<String>,
438 #[serde(default)]
439 is_pub: bool,
440 #[serde(default)]
441 derives: Vec<String>,
442 },
443
444 /// Enumにバリアントを追加
445 AddVariant {
446 // --- Target enum (3-field) ---
447 /// SymbolId(O(1)確定、"7v2"形式)
448 #[serde(default, skip_serializing_if = "Option::is_none")]
449 symbol_id: Option<String>,
450 /// シンボルパス("tokio::net::TcpStream"形式)
451 #[serde(default, skip_serializing_if = "Option::is_none")]
452 symbol_path: Option<String>,
453 /// 対象のEnum名(診断用、フォールバック検索)
454 #[serde(
455 default,
456 skip_serializing_if = "Option::is_none",
457 alias = "target",
458 alias = "target_name"
459 )]
460 target_enum: Option<String>,
461 // --- Variant attributes ---
462 variant_name: String,
463 /// "unit", "tuple:Type1,Type2", "struct:field1:Type1,field2:Type2"
464 #[serde(default = "default_variant_type")]
465 variant_type: String,
466 },
467
468 /// Enumからバリアントを削除
469 RemoveVariant {
470 // --- Target enum (3-field) ---
471 /// SymbolId(O(1)確定、"7v2"形式)
472 #[serde(default, skip_serializing_if = "Option::is_none")]
473 symbol_id: Option<String>,
474 /// シンボルパス("tokio::net::TcpStream"形式)
475 #[serde(default, skip_serializing_if = "Option::is_none")]
476 symbol_path: Option<String>,
477 /// 対象のEnum名(診断用、フォールバック検索)
478 #[serde(
479 default,
480 skip_serializing_if = "Option::is_none",
481 alias = "target",
482 alias = "target_name"
483 )]
484 target_enum: Option<String>,
485 // --- Variant attributes ---
486 variant_name: String,
487 },
488
489 /// Match式にarmを追加(Cascade用)
490 ///
491 /// AddVariantと組み合わせて使用。CascadeAnalyzerで生成された
492 /// AddMatchArmをIntentとして渡すことで、網羅性エラーを自動修正。
493 AddMatchArm {
494 // --- Target function (3-field pattern) ---
495 /// SymbolId(O(1)確定、"7v2"形式)
496 #[serde(default, skip_serializing_if = "Option::is_none")]
497 symbol_id: Option<String>,
498 /// 関数のシンボルパス (e.g., "crate::handlers::process")
499 #[serde(default, skip_serializing_if = "Option::is_none")]
500 symbol_path: Option<String>,
501 /// 関数名(symbol_pathから自動抽出可能、明示指定も可)
502 #[serde(default, skip_serializing_if = "Option::is_none")]
503 target_fn: Option<String>,
504 // --- Match arm parameters ---
505 /// 対象のenum型名
506 enum_name: String,
507 /// 追加するパターン (e.g., "Status::Cancelled")
508 pattern: String,
509 /// Armのbody (e.g., "todo!()")
510 #[serde(default = "default_method_body")]
511 body: String,
512 },
513
514 /// Match式からarmを削除(Cascade用)
515 ///
516 /// RemoveVariantと組み合わせて使用。バリアント削除後の
517 /// 不要なmatch armを削除する。
518 RemoveMatchArm {
519 // --- Target function (3-field pattern) ---
520 /// SymbolId(O(1)確定、"7v2"形式)
521 #[serde(default, skip_serializing_if = "Option::is_none")]
522 symbol_id: Option<String>,
523 /// 関数のシンボルパス (e.g., "crate::handlers::process")
524 #[serde(default, skip_serializing_if = "Option::is_none")]
525 symbol_path: Option<String>,
526 /// 関数名(symbol_pathから自動抽出可能、明示指定も可)
527 #[serde(default, skip_serializing_if = "Option::is_none")]
528 target_fn: Option<String>,
529 // --- Match arm parameters ---
530 /// 対象のenum型名
531 enum_name: String,
532 /// 削除するパターン (e.g., "Status::Completed")
533 pattern: String,
534 },
535
536 /// Match armを置換(パターン + body をセットで変更)
537 ///
538 /// ReplaceExprはbody(式)のみを置換するが、このIntentはパターンも
539 /// 同時に置換できる。例えば `{ start: _, end: _ }` を `{ start, end }`
540 /// に変更しつつ、bodyも新しい実装に置き換える場合に使用する。
541 ReplaceMatchArm {
542 // --- Target function (3-field pattern) ---
543 /// SymbolId(O(1)確定、"7v2"形式)
544 #[serde(default, skip_serializing_if = "Option::is_none")]
545 symbol_id: Option<String>,
546 /// 関数のシンボルパス (e.g., "crate::handlers::process")
547 #[serde(default, skip_serializing_if = "Option::is_none")]
548 symbol_path: Option<String>,
549 /// 関数名(symbol_pathから自動抽出可能、明示指定も可)
550 #[serde(default, skip_serializing_if = "Option::is_none")]
551 target_fn: Option<String>,
552 // --- Match arm parameters ---
553 /// 対象のenum型名
554 enum_name: String,
555 /// 置換対象のパターン (e.g., "PathSegment::Slice { start: _, end: _ }")
556 old_pattern: String,
557 /// 新しいパターン (e.g., "PathSegment::Slice { start, end }")
558 new_pattern: String,
559 /// 新しいbody (e.g., "{ let s = start.unwrap_or(0); ... }")
560 new_body: String,
561 },
562
563 /// 構造体リテラルにフィールドを追加(Cascade用)
564 ///
565 /// AddFieldと組み合わせて使用。フィールド追加後の構造体リテラルを
566 /// 自動的に更新する。
567 AddStructLiteralField {
568 // --- Target struct (3-field) ---
569 /// SymbolId(O(1)確定、"7v2"形式)
570 #[serde(default, skip_serializing_if = "Option::is_none")]
571 symbol_id: Option<String>,
572 /// シンボルパス("tokio::net::TcpStream"形式)
573 #[serde(default, skip_serializing_if = "Option::is_none")]
574 symbol_path: Option<String>,
575 /// 対象の構造体名(診断用、フォールバック検索)
576 #[serde(
577 default,
578 skip_serializing_if = "Option::is_none",
579 alias = "target",
580 alias = "target_name"
581 )]
582 target_struct: Option<String>,
583 // --- Field attributes ---
584 /// 追加するフィールド名
585 field_name: String,
586 /// フィールドの値 (e.g., "None", "Default::default()")
587 value: String,
588 },
589
590 /// 構造体リテラルからフィールドを削除(Cascade用)
591 ///
592 /// RemoveFieldと組み合わせて使用。フィールド削除後の構造体リテラルを
593 /// 自動的に更新する。
594 RemoveStructLiteralField {
595 // --- Target struct (3-field) ---
596 /// SymbolId(O(1)確定、"7v2"形式)
597 #[serde(default, skip_serializing_if = "Option::is_none")]
598 symbol_id: Option<String>,
599 /// シンボルパス("tokio::net::TcpStream"形式)
600 #[serde(default, skip_serializing_if = "Option::is_none")]
601 symbol_path: Option<String>,
602 /// 対象の構造体名(診断用、フォールバック検索)
603 #[serde(
604 default,
605 skip_serializing_if = "Option::is_none",
606 alias = "target",
607 alias = "target_name"
608 )]
609 target_struct: Option<String>,
610 // --- Field attributes ---
611 /// 削除するフィールド名
612 field_name: String,
613 },
614
615 // === 構造体/Enum削除系 ===
616 /// 構造体を削除
617 RemoveStruct {
618 // --- Target struct (3-field) ---
619 /// SymbolId(O(1)確定、"7v2"形式)
620 #[serde(default, skip_serializing_if = "Option::is_none")]
621 symbol_id: Option<String>,
622 /// シンボルパス("tokio::net::TcpStream"形式)
623 #[serde(default, skip_serializing_if = "Option::is_none")]
624 symbol_path: Option<String>,
625 /// 対象の構造体名(診断用、フォールバック検索)
626 #[serde(
627 default,
628 skip_serializing_if = "Option::is_none",
629 alias = "target",
630 alias = "target_name"
631 )]
632 target_struct: Option<String>,
633 },
634
635 /// Enumを削除
636 RemoveEnum {
637 // --- Target enum (3-field) ---
638 /// SymbolId(O(1)確定、"7v2"形式)
639 #[serde(default, skip_serializing_if = "Option::is_none")]
640 symbol_id: Option<String>,
641 /// シンボルパス("tokio::net::TcpStream"形式)
642 #[serde(default, skip_serializing_if = "Option::is_none")]
643 symbol_path: Option<String>,
644 /// 対象のEnum名(診断用、フォールバック検索)
645 #[serde(
646 default,
647 skip_serializing_if = "Option::is_none",
648 alias = "target",
649 alias = "target_name"
650 )]
651 target_enum: Option<String>,
652 },
653
654 // === 複製系 ===
655 /// 関数を複製
656 DuplicateFunction {
657 // --- Target function (3-field) ---
658 /// SymbolId(O(1)確定、"7v2"形式)
659 #[serde(default, skip_serializing_if = "Option::is_none")]
660 symbol_id: Option<String>,
661 /// シンボルパス("tokio::net::TcpStream"形式)
662 #[serde(default, skip_serializing_if = "Option::is_none")]
663 symbol_path: Option<String>,
664 /// 複製元の関数名(診断用、フォールバック検索)
665 #[serde(
666 default,
667 skip_serializing_if = "Option::is_none",
668 alias = "target",
669 alias = "target_name"
670 )]
671 target_fn: Option<String>,
672 // --- Duplicate attributes ---
673 /// 新しい関数名
674 to: String,
675 },
676
677 /// 構造体を複製(関連impl含む)
678 DuplicateStruct {
679 // --- Target struct (3-field) ---
680 /// SymbolId(O(1)確定、"7v2"形式)
681 #[serde(default, skip_serializing_if = "Option::is_none")]
682 symbol_id: Option<String>,
683 /// シンボルパス("tokio::net::TcpStream"形式)
684 #[serde(default, skip_serializing_if = "Option::is_none")]
685 symbol_path: Option<String>,
686 /// 複製元の構造体名(診断用、フォールバック検索)
687 #[serde(
688 default,
689 skip_serializing_if = "Option::is_none",
690 alias = "target",
691 alias = "target_name"
692 )]
693 target_struct: Option<String>,
694 // --- Duplicate attributes ---
695 /// 新しい構造体名
696 to: String,
697 /// impl blockも複製するか
698 #[serde(default = "default_true")]
699 include_impls: bool,
700 },
701
702 /// Enumを複製(関連impl含む)
703 DuplicateEnum {
704 // --- Target enum (3-field) ---
705 /// SymbolId(O(1)確定、"7v2"形式)
706 #[serde(default, skip_serializing_if = "Option::is_none")]
707 symbol_id: Option<String>,
708 /// シンボルパス("tokio::net::TcpStream"形式)
709 #[serde(default, skip_serializing_if = "Option::is_none")]
710 symbol_path: Option<String>,
711 /// 複製元のEnum名(診断用、フォールバック検索)
712 #[serde(
713 default,
714 skip_serializing_if = "Option::is_none",
715 alias = "target",
716 alias = "target_name"
717 )]
718 target_enum: Option<String>,
719 // --- Duplicate attributes ---
720 /// 新しいEnum名
721 to: String,
722 /// impl blockも複製するか
723 #[serde(default = "default_true")]
724 include_impls: bool,
725 },
726
727 /// インラインモジュールを複製
728 DuplicateModTree {
729 // --- Target module (3-field) ---
730 /// SymbolId(O(1)確定、"7v2"形式)
731 #[serde(default, skip_serializing_if = "Option::is_none")]
732 symbol_id: Option<String>,
733 /// シンボルパス("tokio::net::TcpStream"形式)
734 #[serde(default, skip_serializing_if = "Option::is_none")]
735 symbol_path: Option<String>,
736 /// 複製元のモジュール名(診断用、フォールバック検索)
737 #[serde(
738 default,
739 skip_serializing_if = "Option::is_none",
740 alias = "target",
741 alias = "target_name"
742 )]
743 target_mod: Option<String>,
744 // --- Duplicate attributes ---
745 /// 新しいモジュール名
746 to: String,
747 },
748
749 // === 定数/型エイリアス系 ===
750 /// 定数を追加
751 AddConst {
752 /// 追加先モジュールパス(必須: "my_crate", "my_crate::domain"等)
753 symbol_path: String,
754 name: String,
755 ty: String,
756 value: String,
757 #[serde(default)]
758 is_pub: bool,
759 },
760
761 /// 型エイリアスを追加
762 AddTypeAlias {
763 /// 追加先モジュールパス(必須: "my_crate", "my_crate::domain"等)
764 symbol_path: String,
765 name: String,
766 ty: String,
767 #[serde(default)]
768 is_pub: bool,
769 },
770
771 // === Spec系 ===
772 /// Spec TypeAliasを追加 (ドメイン仕様マーカー)
773 AddSpec {
774 // --- Target type (3-field) ---
775 /// SymbolId(O(1)確定、"7v2"形式)
776 #[serde(default, skip_serializing_if = "Option::is_none")]
777 symbol_id: Option<String>,
778 /// シンボルパス("tokio::net::TcpStream"形式)
779 #[serde(default, skip_serializing_if = "Option::is_none")]
780 symbol_path: Option<String>,
781 /// 対象の型名(診断用、フォールバック検索)
782 #[serde(
783 default,
784 skip_serializing_if = "Option::is_none",
785 alias = "target",
786 alias = "target_name"
787 )]
788 target_type: Option<String>,
789
790 // --- Module (3-field) ---
791 /// モジュールのSymbolId
792 #[serde(default, skip_serializing_if = "Option::is_none")]
793 module_id: Option<String>,
794 /// モジュールのシンボルパス (e.g., "crate::domain")
795 #[serde(default, skip_serializing_if = "Option::is_none")]
796 module_path: Option<String>,
797 /// モジュール名(診断用、フォールバック検索)
798 #[serde(default, skip_serializing_if = "Option::is_none")]
799 target_mod: Option<String>,
800
801 /// グループ名 (e.g., "DomainGroup", "ConfigGroup")
802 group: String,
803 /// エイリアス名 (省略時: "{target_type}Spec")
804 #[serde(default)]
805 alias_name: Option<String>,
806 /// 依存関係 (最大3つ)
807 #[serde(default)]
808 relations: Vec<SpecRelation>,
809 },
810
811 // === メソッド追加・削除 ===
812 /// implにメソッドを追加
813 AddMethod {
814 /// SymbolId(O(1)確定、"7v2"形式)
815 #[serde(default, skip_serializing_if = "Option::is_none")]
816 symbol_id: Option<String>,
817 /// シンボルパス("tokio::net::TcpStream"形式)
818 #[serde(default, skip_serializing_if = "Option::is_none")]
819 symbol_path: Option<String>,
820 /// 対象の型名(診断用、フォールバック検索)
821 #[serde(
822 default,
823 skip_serializing_if = "Option::is_none",
824 alias = "target",
825 alias = "target_name"
826 )]
827 target_type: Option<String>,
828 /// メソッド名
829 method_name: String,
830 /// パラメータ (name, type) のペア
831 #[serde(default)]
832 params: Vec<(String, String)>,
833 /// 戻り値の型 (None = unit)
834 #[serde(default)]
835 return_type: Option<String>,
836 /// メソッド本体
837 #[serde(default = "default_method_body")]
838 body: String,
839 /// public かどうか
840 #[serde(default)]
841 is_pub: bool,
842 /// selfパラメータ
843 #[serde(default)]
844 self_param: Option<SelfParam>,
845 },
846
847 /// implからメソッドを削除
848 RemoveMethod {
849 // --- Target impl type (3-field) ---
850 /// SymbolId(O(1)確定、"7v2"形式)
851 #[serde(default, skip_serializing_if = "Option::is_none")]
852 symbol_id: Option<String>,
853 /// シンボルパス("tokio::net::TcpStream"形式)
854 #[serde(default, skip_serializing_if = "Option::is_none")]
855 symbol_path: Option<String>,
856 /// 対象の型名(診断用、フォールバック検索)
857 #[serde(
858 default,
859 skip_serializing_if = "Option::is_none",
860 alias = "target",
861 alias = "target_name"
862 )]
863 target_type: Option<String>,
864 // --- Method attributes ---
865 method_name: String,
866 },
867
868 // === 削除系 ===
869 /// 定数を削除
870 RemoveConst {
871 // --- Target const (3-field) ---
872 /// SymbolId(O(1)確定、"7v2"形式)
873 #[serde(default, skip_serializing_if = "Option::is_none")]
874 symbol_id: Option<String>,
875 /// シンボルパス("tokio::net::TcpStream"形式)
876 #[serde(default, skip_serializing_if = "Option::is_none")]
877 symbol_path: Option<String>,
878 /// 対象の定数名(診断用、フォールバック検索)
879 #[serde(
880 default,
881 skip_serializing_if = "Option::is_none",
882 alias = "target",
883 alias = "target_name"
884 )]
885 target_const: Option<String>,
886 },
887
888 /// 型エイリアスを削除
889 RemoveTypeAlias {
890 // --- Target type alias (3-field) ---
891 /// SymbolId(O(1)確定、"7v2"形式)
892 #[serde(default, skip_serializing_if = "Option::is_none")]
893 symbol_id: Option<String>,
894 /// シンボルパス("tokio::net::TcpStream"形式)
895 #[serde(default, skip_serializing_if = "Option::is_none")]
896 symbol_path: Option<String>,
897 /// 対象の型エイリアス名(診断用、フォールバック検索)
898 #[serde(
899 default,
900 skip_serializing_if = "Option::is_none",
901 alias = "target",
902 alias = "target_name"
903 )]
904 target_type_alias: Option<String>,
905 },
906
907 /// use文を削除
908 RemoveUse {
909 // --- Target use (3-field) ---
910 /// SymbolId(O(1)確定、"7v2"形式)
911 #[serde(default, skip_serializing_if = "Option::is_none")]
912 symbol_id: Option<String>,
913 /// シンボルパス("tokio::net::TcpStream"形式)
914 #[serde(default, skip_serializing_if = "Option::is_none")]
915 symbol_path: Option<String>,
916 /// 対象のuseパス(診断用、フォールバック検索)
917 #[serde(
918 default,
919 skip_serializing_if = "Option::is_none",
920 alias = "target",
921 alias = "target_name"
922 )]
923 target_use: Option<String>,
924 },
925
926 /// Traitを削除
927 RemoveTrait {
928 // --- Target trait (3-field) ---
929 /// SymbolId(O(1)確定、"7v2"形式)
930 #[serde(default, skip_serializing_if = "Option::is_none")]
931 symbol_id: Option<String>,
932 /// シンボルパス("tokio::net::TcpStream"形式)
933 #[serde(default, skip_serializing_if = "Option::is_none")]
934 symbol_path: Option<String>,
935 /// 対象のTrait名(診断用、フォールバック検索)
936 #[serde(
937 default,
938 skip_serializing_if = "Option::is_none",
939 alias = "target",
940 alias = "target_name"
941 )]
942 target_trait: Option<String>,
943 },
944
945 /// impl blockを削除
946 RemoveImpl {
947 // --- Target impl (3-field) ---
948 /// SymbolId(O(1)確定、"7v2"形式)
949 #[serde(default, skip_serializing_if = "Option::is_none")]
950 symbol_id: Option<String>,
951 /// シンボルパス("tokio::net::TcpStream"形式)
952 #[serde(default, skip_serializing_if = "Option::is_none")]
953 symbol_path: Option<String>,
954 /// 対象の型名(診断用、フォールバック検索)
955 #[serde(
956 default,
957 skip_serializing_if = "Option::is_none",
958 alias = "target",
959 alias = "target_name"
960 )]
961 target_type: Option<String>,
962 // --- Impl attributes ---
963 trait_name: Option<String>,
964 },
965
966 // === 追加・削除系 ===
967 /// アイテム追加
968 AddItem {
969 // --- Target (3-field) ---
970 /// SymbolId(O(1)確定、"7v2"形式)
971 #[serde(default, skip_serializing_if = "Option::is_none")]
972 symbol_id: Option<String>,
973 /// シンボルパス("crate::module"形式)
974 #[serde(default, skip_serializing_if = "Option::is_none")]
975 symbol_path: Option<String>,
976 /// モジュール名(診断用、フォールバック検索)
977 #[serde(
978 default,
979 skip_serializing_if = "Option::is_none",
980 alias = "target",
981 alias = "target_name"
982 )]
983 target_mod: Option<String>,
984 // --- Add attributes ---
985 content: String,
986 item_kind: ItemKind,
987 },
988
989 /// アイテム削除
990 RemoveItem {
991 // --- Target (3-field) ---
992 /// SymbolId(O(1)確定、"7v2"形式)
993 #[serde(default, skip_serializing_if = "Option::is_none")]
994 symbol_id: Option<String>,
995 /// シンボルパス("tokio::net::TcpStream"形式)
996 #[serde(default, skip_serializing_if = "Option::is_none")]
997 symbol_path: Option<String>,
998 /// 削除対象の名前(診断用、フォールバック検索)
999 #[serde(
1000 default,
1001 skip_serializing_if = "Option::is_none",
1002 alias = "target",
1003 alias = "target_name"
1004 )]
1005 target_item: Option<String>,
1006 // --- Remove attributes ---
1007 item_kind: ItemKind,
1008 },
1009
1010 /// コードを親パスに追加(存在しなければモジュール自動作成)
1011 ///
1012 /// AddItemの簡易版。item_kindはsynパースで自動判定し、
1013 /// 親パスが存在しない場合は再帰的にモジュールを作成する。
1014 ///
1015 /// ## 自動モジュール作成
1016 ///
1017 /// - **SymbolRegistryあり**: 既存モジュールをスキップし、存在しないモジュールのみ作成
1018 /// - **SymbolRegistryなし**: 全親セグメントに対してCreateModを生成(CreateModは冪等)
1019 ///
1020 /// 例: `parent: "crate::infrastructure::memory"` の場合
1021 /// 1. CreateMod { parent: "crate", mod_name: "infrastructure" }
1022 /// 2. CreateMod { parent: "crate::infrastructure", mod_name: "memory" }
1023 /// 3. AddItem { target: "crate::infrastructure::memory", ... }
1024 ///
1025 /// ## parent と parent_ref
1026 ///
1027 /// どちらか一方を指定。両方指定時は parent_ref を優先。
1028 ///
1029 /// # Example (シンプル形式 - parent)
1030 /// ```json
1031 /// {
1032 /// "type": "AddCode",
1033 /// "parent": "crate::usecase",
1034 /// "code": "pub struct CreateOrderInput { pub user_id: UserId }"
1035 /// }
1036 /// ```
1037 ///
1038 /// # Example (明示的形式 - parent_ref)
1039 /// ```json
1040 /// {
1041 /// "type": "AddCode",
1042 /// "parent_ref": { "type": "FilePath", "path": "src/usecase.rs" },
1043 /// "code": "pub struct CreateOrderInput { ... }"
1044 /// }
1045 /// ```
1046 AddCode {
1047 /// SymbolId直接指定("7v2"形式、O(1)解決)
1048 #[serde(default)]
1049 symbol_id: Option<String>,
1050 /// SymbolPathまたはファイルパス
1051 /// "::"を含めばSymbolPath、それ以外はファイルパスとして解釈
1052 #[serde(default, alias = "parent")]
1053 symbol_path: Option<String>,
1054 /// モジュール名(診断/フォールバック用)
1055 #[serde(default, alias = "target", alias = "target_name")]
1056 target_mod: Option<String>,
1057 /// 追加するRustコード(複数Item可、synでパース)
1058 code: String,
1059 },
1060
1061 // === IDIOM系 (Agent自動適用向け) ===
1062 /// use文の整理・ソート
1063 OrganizeImports {
1064 /// 対象モジュール(None = 全モジュール)"lib::foo::bar" 形式
1065 #[serde(default)]
1066 target_mod: Option<String>,
1067 /// 重複を削除
1068 #[serde(default = "default_true")]
1069 deduplicate: bool,
1070 /// 同一プレフィックスをグループ化
1071 #[serde(default = "default_true")]
1072 merge_groups: bool,
1073 },
1074
1075 /// 同一型のimplブロックをマージ
1076 MergeImplBlocks {
1077 /// 対象モジュール(None = 全モジュール)
1078 #[serde(default)]
1079 target_mod: Option<String>,
1080 /// 対象の型名 (None = 全て)
1081 #[serde(default)]
1082 target_type: Option<String>,
1083 /// inherent implのみ (trait implを除外)
1084 #[serde(default)]
1085 inherent_only: bool,
1086 },
1087
1088 /// forループをイテレータチェーンに変換
1089 LoopToIterator {
1090 /// 対象モジュール(None = 全モジュール)
1091 #[serde(default)]
1092 target_mod: Option<String>,
1093 /// 特定の変数名のループのみ対象
1094 #[serde(default)]
1095 target_var: Option<String>,
1096 },
1097
1098 /// .unwrap()/.expect() を ? 演算子に変換
1099 UnwrapToQuestion {
1100 /// 対象モジュール(None = 全モジュール)
1101 #[serde(default)]
1102 target_mod: Option<String>,
1103 /// 特定の関数のみ対象
1104 #[serde(default)]
1105 target_fn: Option<String>,
1106 /// .expect()も変換対象にする
1107 #[serde(default = "default_true")]
1108 include_expect: bool,
1109 },
1110
1111 /// 重複式を変数に抽出
1112 IntroduceVariable {
1113 /// 対象モジュール(None = 全モジュール)
1114 #[serde(default)]
1115 target_mod: Option<String>,
1116 /// 対象の関数名(None = 全関数)
1117 #[serde(default)]
1118 target_fn: Option<String>,
1119 /// 抽出対象の式(Rustコード文字列, e.g. "a + b * c")
1120 expr: String,
1121 /// 抽出する変数名
1122 var_name: String,
1123 },
1124
1125 /// Builder patternを生成
1126 ///
1127 /// 指定した struct に対して Builder pattern を生成する。
1128 /// 以下のコードが生成される:
1129 /// - `{StructName}Builder` struct (Option-wrapped fields)
1130 /// - `impl {StructName}Builder` with `new()`, setter methods, `build()`
1131 /// - (オプション) `impl {StructName}` with `builder()` method
1132 ///
1133 /// # Example
1134 /// ```json
1135 /// {
1136 /// "type": "GenerateBuilder",
1137 /// "struct_name": "Config",
1138 /// "fields": [
1139 /// ["host", "String"],
1140 /// ["port", "u16"],
1141 /// ["timeout", "Option<u32>"]
1142 /// ]
1143 /// }
1144 /// ```
1145 GenerateBuilder {
1146 // --- Target struct (3-field) ---
1147 /// SymbolId(O(1)確定、"7v2"形式)
1148 #[serde(default, skip_serializing_if = "Option::is_none")]
1149 symbol_id: Option<String>,
1150 /// シンボルパス("tokio::net::TcpStream"形式)
1151 #[serde(default, skip_serializing_if = "Option::is_none")]
1152 symbol_path: Option<String>,
1153 /// 対象の struct 名(診断用、フォールバック検索)
1154 #[serde(
1155 default,
1156 skip_serializing_if = "Option::is_none",
1157 alias = "target",
1158 alias = "target_name"
1159 )]
1160 target_struct: Option<String>,
1161 // --- Builder attributes ---
1162 /// Builder追加先のモジュールパス(例: "crate::config")
1163 /// symbol_id指定時は省略可(registryから解決)
1164 #[serde(default)]
1165 target_mod: Option<String>,
1166 /// フィールド定義 [(name, type), ...]
1167 fields: Vec<(String, String)>,
1168 /// 元の struct に builder() メソッドを追加するか(デフォルト: true)
1169 #[serde(default = "default_true")]
1170 add_builder_method: bool,
1171 },
1172
1173 // === PureStmt/PureExpr 操作系 ===
1174 /// 式を別の式に置換
1175 ///
1176 /// 対象指定方法(どちらか一方を使用):
1177 /// - `old_expr`: パターンマッチで対象を検索
1178 /// - `symbol_path`: 直接位置指定(例: "crate::fn::$body::0::1")
1179 ReplaceExpr {
1180 /// 対象モジュール(None = 全モジュール)
1181 #[serde(default)]
1182 target_mod: Option<String>,
1183 /// 対象の関数名(None=全関数)
1184 #[serde(default)]
1185 target_fn: Option<String>,
1186 /// 置換元の式(Rustコード文字列)- パターンマッチ方式
1187 old_expr: String,
1188 /// 置換先の式(Rustコード文字列)
1189 new_expr: String,
1190 /// 全ての出現箇所を置換するか(デフォルト: true)
1191 #[serde(default = "default_true")]
1192 replace_all: bool,
1193 /// 直接位置指定(例: "my_crate::my_fn::$body::0::1::2")
1194 /// 指定時は old_expr を無視し、この位置の式を直接置換
1195 #[serde(default)]
1196 symbol_path: Option<String>,
1197 },
1198
1199 /// 文を削除
1200 ///
1201 /// 対象指定方法(どちらか一方を使用):
1202 /// - `pattern`: パターンマッチで対象を検索
1203 /// - `symbol_path`: 直接位置指定(例: "crate::fn::$body::2")
1204 RemoveStatement {
1205 /// 対象モジュール(None = 全モジュール)
1206 #[serde(default)]
1207 target_mod: Option<String>,
1208 /// 対象の関数名(None=全関数)
1209 #[serde(default)]
1210 target_fn: Option<String>,
1211 /// 削除対象の文パターン(Rustコード文字列, e.g. "println!(..)")- パターンマッチ方式
1212 pattern: String,
1213 /// 全ての出現箇所を削除するか(デフォルト: true)
1214 #[serde(default = "default_true")]
1215 remove_all: bool,
1216 /// 直接位置指定(例: "my_crate::my_fn::$body::2")
1217 /// 指定時は pattern を無視し、この位置の文を直接削除
1218 #[serde(default)]
1219 symbol_path: Option<String>,
1220 },
1221
1222 /// 文を挿入
1223 ///
1224 /// 対象指定方法:
1225 /// - `target_fn` + `position`: 従来方式
1226 /// - `symbol_path`: 直接位置指定($body::N の後に挿入)
1227 InsertStatement {
1228 /// 対象モジュール(None = 全モジュール)
1229 #[serde(default)]
1230 target_mod: Option<String>,
1231 /// 対象の関数名
1232 target_fn: String,
1233 /// 挿入する文(Rustコード文字列)
1234 stmt: String,
1235 /// 挿入位置
1236 #[serde(default)]
1237 position: StmtInsertPosition,
1238 /// 参照パターン(BeforePattern/AfterPattern用)
1239 #[serde(default)]
1240 reference_pattern: Option<String>,
1241 /// 直接位置指定(例: "my_crate::my_fn::$body::2")
1242 /// 指定時は position を無視し、この位置の後に挿入
1243 #[serde(default)]
1244 symbol_path: Option<String>,
1245 },
1246
1247 /// 文を別の文に置換
1248 ///
1249 /// 対象指定方法(どちらか一方を使用):
1250 /// - `old_stmt`: パターンマッチで対象を検索
1251 /// - `symbol_path`: 直接位置指定(例: "crate::fn::$body::1")
1252 ReplaceStatement {
1253 /// 対象モジュール(None = 全モジュール)
1254 #[serde(default)]
1255 target_mod: Option<String>,
1256 /// 対象の関数名(None=全関数)
1257 #[serde(default)]
1258 target_fn: Option<String>,
1259 /// 置換元の文パターン(Rustコード文字列)- パターンマッチ方式
1260 old_stmt: String,
1261 /// 置換先の文(Rustコード文字列)
1262 new_stmt: String,
1263 /// 直接位置指定(例: "my_crate::my_fn::$body::1")
1264 /// 指定時は old_stmt を無視し、この位置の文を直接置換
1265 #[serde(default)]
1266 symbol_path: Option<String>,
1267 },
1268
1269 // === 追加 Idiom変換系 ===
1270 /// 代入演算子の簡略化: `x = x + 1` → `x += 1`
1271 AssignOp {
1272 /// 対象モジュール(None = 全モジュール)
1273 #[serde(default)]
1274 target_mod: Option<String>,
1275 /// 特定の関数のみ対象
1276 #[serde(default)]
1277 target_fn: Option<String>,
1278 },
1279
1280 /// bool式の簡略化: `x == true` → `x`, `x == false` → `!x`
1281 BoolSimplify {
1282 /// 対象モジュール(None = 全モジュール)
1283 #[serde(default)]
1284 target_mod: Option<String>,
1285 },
1286
1287 /// Copy型での冗長な.clone()削除
1288 CloneOnCopy {
1289 /// 対象モジュール(None = 全モジュール)
1290 #[serde(default)]
1291 target_mod: Option<String>,
1292 },
1293
1294 /// ネストしたifを&&で統合: `if a { if b { ... } }` → `if a && b { ... }`
1295 CollapsibleIf {
1296 /// 対象モジュール(None = 全モジュール)
1297 #[serde(default)]
1298 target_mod: Option<String>,
1299 },
1300
1301 /// 比較をメソッド呼び出しに変換: `s == ""` → `s.is_empty()`
1302 ComparisonToMethod {
1303 /// 対象モジュール(None = 全モジュール)
1304 #[serde(default)]
1305 target_mod: Option<String>,
1306 },
1307
1308 /// 冗長なクロージャを削除: `|x| f(x)` → `f`
1309 RedundantClosure {
1310 /// 対象モジュール(None = 全モジュール)
1311 #[serde(default)]
1312 target_mod: Option<String>,
1313 },
1314
1315 /// matchをmap()に変換: `match opt { Some(x) => Some(f(x)), None => None }` → `opt.map(f)`
1316 ManualMap {
1317 /// 対象モジュール(None = 全モジュール)
1318 #[serde(default)]
1319 target_mod: Option<String>,
1320 },
1321
1322 /// matchをif letに変換
1323 MatchToIfLet {
1324 /// 対象モジュール(None = 全モジュール)
1325 #[serde(default)]
1326 target_mod: Option<String>,
1327 },
1328
1329 /// .filter().next() を .find() に変換
1330 FilterNext {
1331 /// 対象モジュール(None = 全モジュール)
1332 #[serde(default)]
1333 target_mod: Option<String>,
1334 /// 特定の関数のみ対象
1335 #[serde(default)]
1336 target_fn: Option<String>,
1337 },
1338
1339 /// .map().unwrap_or() を .map_or() に変換
1340 MapUnwrapOr {
1341 /// 対象モジュール(None = 全モジュール)
1342 #[serde(default)]
1343 target_mod: Option<String>,
1344 /// 特定の関数のみ対象
1345 #[serde(default)]
1346 target_fn: Option<String>,
1347 },
1348
1349 // === カスタム ===
1350 /// LLMが理解したが定義済みIntentにマッチしない場合
1351 Custom {
1352 description: String,
1353 /// LLMが推測した変換例(Before → After)
1354 examples: Vec<TransformExample>,
1355 },
1356
1357 // === WASM Plugin ===
1358 /// Execute a WASM plugin by name
1359 #[cfg(feature = "wasm-plugin")]
1360 Plugin {
1361 /// Plugin name (must be registered in MutationRegistry)
1362 name: String,
1363 /// Glob patterns for multi-file targeting (e.g., ["**/*.rs", "src/lib.rs"])
1364 /// If empty, uses all files in project or scope.file_patterns
1365 #[serde(default)]
1366 file_patterns: Vec<String>,
1367 },
1368}
1369
1370// ============================================================================
1371// Supporting Types
1372// ============================================================================
1373
1374/// 文の挿入位置
1375#[cfg_attr(feature = "schemars", derive(JsonSchema))]
1376#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1377#[serde(rename_all = "snake_case")]
1378#[derive(Default)]
1379pub enum StmtInsertPosition {
1380 /// 関数の先頭
1381 Start,
1382 /// 関数の末尾(return文の前)
1383 #[default]
1384 End,
1385 /// 指定した文の前
1386 BeforePattern,
1387 /// 指定した文の後
1388 AfterPattern,
1389}
1390
1391/// 識別子の種類
1392#[cfg_attr(feature = "schemars", derive(JsonSchema))]
1393#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1394pub enum IdentKind {
1395 /// 変数: let x
1396 Var,
1397 /// フィールド: struct.field
1398 Field,
1399 /// 関数: fn foo()
1400 Fn,
1401 /// 型: struct/enum/type alias
1402 Type,
1403 /// モジュール: mod foo
1404 Module,
1405 /// 種類不明
1406 Any,
1407}
1408
1409/// 可視性
1410#[cfg_attr(feature = "schemars", derive(JsonSchema))]
1411#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1412pub enum Visibility {
1413 Private,
1414 Pub,
1415 PubCrate,
1416 PubSuper,
1417}
1418
1419/// メソッドのselfパラメータ
1420#[cfg_attr(feature = "schemars", derive(JsonSchema))]
1421#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
1422#[serde(rename_all = "lowercase")]
1423pub enum SelfParam {
1424 /// &self
1425 Ref,
1426 /// &mut self
1427 Mut,
1428 /// self (owned)
1429 Owned,
1430}
1431
1432/// Spec TypeAliasの依存関係
1433#[cfg_attr(feature = "schemars", derive(JsonSchema))]
1434#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1435pub struct SpecRelation {
1436 /// 関係の種類
1437 pub kind: SpecRelationKind,
1438 /// 対象の型名
1439 pub target: String,
1440}
1441
1442/// Spec依存関係の種類
1443#[cfg_attr(feature = "schemars", derive(JsonSchema))]
1444#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1445#[serde(rename_all = "PascalCase")]
1446pub enum SpecRelationKind {
1447 /// AがBに依存する (AはBがないと機能しない)
1448 DependsOn,
1449 /// AがBと関連する (意味的な関係)
1450 RelatedTo,
1451 /// AがBの一部である (集約メンバーシップ)
1452 PartOf,
1453}
1454
1455/// 変換例(Custom用)
1456#[cfg_attr(feature = "schemars", derive(JsonSchema))]
1457#[derive(Debug, Clone, Serialize, Deserialize)]
1458pub struct TransformExample {
1459 pub before: String,
1460 pub after: String,
1461}
1462
1463// ============================================================================
1464// ScopeHint: 並列Discovery用ヒント
1465// ============================================================================
1466
1467/// 検索スコープのヒント
1468///
1469/// 後段のExecutorが並列Discovery戦略を決定するために使用。
1470/// すべてのフィールドは省略可能で、デフォルト値が使用される。
1471#[cfg_attr(feature = "schemars", derive(JsonSchema))]
1472#[derive(Debug, Clone, Serialize, Deserialize)]
1473#[serde(default)]
1474pub struct ScopeHint {
1475 /// ファイルパターン(例: "src/**/*.rs", "crates/project-name/**")
1476 #[serde(skip_serializing_if = "Vec::is_empty")]
1477 pub file_patterns: Vec<String>,
1478
1479 /// シンボルパターン(例: "*Config", "crate::domain::User")
1480 /// "::"を含む → SymbolPath、それ以外 → Globパターン
1481 #[serde(skip_serializing_if = "Vec::is_empty")]
1482 pub symbol_patterns: Vec<String>,
1483
1484 /// 推定影響範囲
1485 #[serde(skip_serializing_if = "is_estimated_scope_unknown")]
1486 pub estimated_scope: EstimatedScope,
1487}
1488
1489fn is_estimated_scope_unknown(scope: &EstimatedScope) -> bool {
1490 matches!(scope, EstimatedScope::Unknown)
1491}
1492
1493/// 推定影響範囲
1494#[cfg_attr(feature = "schemars", derive(JsonSchema))]
1495#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
1496pub enum EstimatedScope {
1497 /// 1ファイルのみ
1498 SingleFile,
1499 /// 2-5ファイル
1500 FewFiles,
1501 /// 6+ファイル
1502 ManyFiles,
1503 /// プロジェクト全体
1504 ProjectWide,
1505 /// 不明
1506 #[default]
1507 Unknown,
1508}
1509
1510impl EstimatedScope {
1511 /// Check if this is the Unknown variant (for serde skip_serializing_if)
1512 pub fn is_unknown(&self) -> bool {
1513 matches!(self, Self::Unknown)
1514 }
1515}
1516
1517impl Default for ScopeHint {
1518 fn default() -> Self {
1519 Self {
1520 file_patterns: vec!["**/*.rs".to_string()],
1521 symbol_patterns: vec![],
1522 estimated_scope: EstimatedScope::Unknown,
1523 }
1524 }
1525}
1526
1527impl ScopeHint {
1528 pub fn new() -> Self {
1529 Self::default()
1530 }
1531
1532 pub fn with_file_patterns(mut self, patterns: Vec<String>) -> Self {
1533 self.file_patterns = patterns;
1534 self
1535 }
1536
1537 pub fn with_symbol_patterns(mut self, patterns: Vec<String>) -> Self {
1538 self.symbol_patterns = patterns;
1539 self
1540 }
1541
1542 pub fn with_estimated_scope(mut self, scope: EstimatedScope) -> Self {
1543 self.estimated_scope = scope;
1544 self
1545 }
1546}
1547
1548// ============================================================================
1549// Constraint: 実行時制約
1550// ============================================================================
1551
1552/// 実行時制約
1553#[cfg_attr(feature = "schemars", derive(JsonSchema))]
1554#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
1555pub enum Constraint {
1556 /// コンパイルが通ること(syn parse + cargo check)
1557 MustCompile,
1558
1559 /// pub APIを破壊しない
1560 PreservePublicApi,
1561
1562 /// テストが通ること
1563 MustPassTests,
1564
1565 /// 変更しない(プレビューのみ)
1566 DryRun,
1567
1568 /// 対話的確認が必要
1569 RequireConfirmation,
1570
1571 /// 適用後にcargo checkを実行
1572 CargoCheck,
1573
1574 /// cargo check失敗時に変更をロールバック(CargoCheckと併用)
1575 RollbackOnFailure,
1576}
1577
1578// ============================================================================
1579// ExtractError
1580// ============================================================================
1581
1582/// Intent抽出エラー
1583#[derive(Debug, thiserror::Error)]
1584pub enum ExtractError {
1585 #[error("LLM connection failed: {0}")]
1586 ConnectionFailed(String),
1587
1588 #[error("Failed to parse LLM response: {0}")]
1589 ParseFailed(String),
1590
1591 #[error("Query too ambiguous: {0}")]
1592 AmbiguousQuery(String),
1593
1594 #[error("Unsupported query type: {0}")]
1595 UnsupportedQuery(String),
1596}
1597
1598// ============================================================================
1599// IntentExtractor Trait
1600// ============================================================================
1601
1602/// IntentExtractor: NLクエリ → Goal
1603///
1604/// 責務:ゴール設定のみ。検索・変換は一切行わない。
1605pub trait IntentExtractor: Send + Sync {
1606 /// クエリからGoalを抽出
1607 fn extract(&self, query: &str) -> Result<Goal, ExtractError>;
1608
1609 /// バッチ抽出(複数クエリを並列処理)
1610 fn extract_batch(&self, queries: &[String]) -> Vec<Result<Goal, ExtractError>>;
1611
1612 /// 使用モデル名
1613 fn model_name(&self) -> &str;
1614}
1615
1616// ============================================================================
1617// Goal Builder
1618// ============================================================================
1619
1620impl Goal {
1621 /// 新しいGoalを構築(単一Intent)
1622 pub fn new(query: impl Into<String>, intent: Intent) -> Self {
1623 Self {
1624 query: query.into(),
1625 intents: vec![intent],
1626 scope: ScopeHint::default(),
1627 constraints: vec![Constraint::MustCompile],
1628 conflict_strategy: ConflictStrategy::default(),
1629 confidence: 1.0,
1630 }
1631 }
1632
1633 /// 複数Intentsで構築
1634 pub fn with_intents(query: impl Into<String>, intents: Vec<Intent>) -> Self {
1635 Self {
1636 query: query.into(),
1637 intents,
1638 scope: ScopeHint::default(),
1639 constraints: vec![Constraint::MustCompile],
1640 conflict_strategy: ConflictStrategy::default(),
1641 confidence: 1.0,
1642 }
1643 }
1644
1645 /// コンフリクト解決戦略を設定
1646 pub fn with_conflict_strategy(mut self, strategy: ConflictStrategy) -> Self {
1647 self.conflict_strategy = strategy;
1648 self
1649 }
1650
1651 pub fn with_scope(mut self, scope: ScopeHint) -> Self {
1652 self.scope = scope;
1653 self
1654 }
1655
1656 pub fn with_constraints(mut self, constraints: Vec<Constraint>) -> Self {
1657 self.constraints = constraints;
1658 self
1659 }
1660
1661 pub fn with_confidence(mut self, confidence: f64) -> Self {
1662 self.confidence = confidence;
1663 self
1664 }
1665
1666 /// RenameIdentのGoalを簡易構築
1667 pub fn rename(from: impl Into<String>, to: impl Into<String>) -> Self {
1668 let from_str = from.into();
1669 let to_str = to.into();
1670 let query = format!("{} → {}", from_str, to_str);
1671
1672 Self::new(
1673 &query,
1674 Intent::RenameIdent {
1675 symbol_id: None,
1676 symbol_path: None,
1677 target_ident: Some(from_str.clone()),
1678 to: to_str,
1679 kind: IdentKind::Any,
1680 },
1681 )
1682 .with_scope(ScopeHint::new().with_estimated_scope(EstimatedScope::FewFiles))
1683 }
1684
1685 // Note: to_mutations() and to_mutations_with_discovery() methods are
1686 // implemented in ryo-executor crate where Mutation execution logic resides.
1687}
1688
1689// ============================================================================
1690// Fuzzy JSON Parsing (feature-gated)
1691// ============================================================================
1692
1693#[cfg(feature = "fuzzy-parser")]
1694impl Goal {
1695 /// Parse a Goal from JSON with fuzzy correction for typos
1696 ///
1697 /// This method automatically corrects common typos in Intent type names
1698 /// and field names that may occur when JSON is generated by LLMs.
1699 ///
1700 /// # Example
1701 /// ```ignore
1702 /// let json = r#"{
1703 /// "query": "Add derives",
1704 /// "intents": [{"type": "AddDeriv", "taget": "User", "derives": ["Debug"]}]
1705 /// }"#;
1706 /// let (goal, corrections) = Goal::from_json_fuzzy(json)?;
1707 /// // Typos corrected: AddDeriv → AddDerive, taget → target
1708 /// ```
1709 pub fn from_json_fuzzy(
1710 json: &str,
1711 ) -> Result<(Self, Vec<ryo_fuzzy_parser::Correction>), GoalParseError> {
1712 Self::from_json_fuzzy_with_options(json, &ryo_fuzzy_parser::FuzzyOptions::default())
1713 }
1714
1715 /// Parse a Goal from JSON with fuzzy correction using custom options
1716 pub fn from_json_fuzzy_with_options(
1717 json: &str,
1718 options: &ryo_fuzzy_parser::FuzzyOptions,
1719 ) -> Result<(Self, Vec<ryo_fuzzy_parser::Correction>), GoalParseError> {
1720 use crate::intent_schema::{intent_schema, GOAL_FIELDS};
1721 use ryo_fuzzy_parser::{repair_fields_with_list, repair_tagged_enum_array, ObjectSchema};
1722
1723 // Parse JSON first
1724 let mut value: serde_json::Value =
1725 serde_json::from_str(json).map_err(|e| GoalParseError::JsonParse(e.to_string()))?;
1726
1727 let mut all_corrections = Vec::new();
1728
1729 // Repair Goal-level fields
1730 if let Some(obj) = value.as_object_mut() {
1731 let goal_schema = ObjectSchema::new(GOAL_FIELDS);
1732 let corrections = repair_fields_with_list(obj, goal_schema.valid_fields, "$", options);
1733 all_corrections.extend(corrections);
1734
1735 // Repair intents array
1736 if let Some(intents) = obj.get_mut("intents").and_then(|v| v.as_array_mut()) {
1737 let schema = intent_schema();
1738 let corrections = repair_tagged_enum_array(intents, &schema, "$.intents", options);
1739 all_corrections.extend(corrections);
1740 }
1741 }
1742
1743 let goal: Goal =
1744 serde_json::from_value(value).map_err(|e| GoalParseError::JsonParse(e.to_string()))?;
1745
1746 Ok((goal, all_corrections))
1747 }
1748}
1749
1750#[cfg(feature = "fuzzy-parser")]
1751impl Intent {
1752 /// Parse an Intent from JSON with fuzzy correction for typos
1753 ///
1754 /// # Example
1755 /// ```ignore
1756 /// let json = r#"{"type": "AddDeriv", "taget": "User", "derives": ["Debug"]}"#;
1757 /// let (intent, corrections) = Intent::from_json_fuzzy(json)?;
1758 /// ```
1759 pub fn from_json_fuzzy(
1760 json: &str,
1761 ) -> Result<(Self, Vec<ryo_fuzzy_parser::Correction>), GoalParseError> {
1762 Self::from_json_fuzzy_with_options(json, &ryo_fuzzy_parser::FuzzyOptions::default())
1763 }
1764
1765 /// Parse an Intent from JSON with fuzzy correction using custom options
1766 pub fn from_json_fuzzy_with_options(
1767 json: &str,
1768 options: &ryo_fuzzy_parser::FuzzyOptions,
1769 ) -> Result<(Self, Vec<ryo_fuzzy_parser::Correction>), GoalParseError> {
1770 use crate::intent_schema::intent_schema;
1771 use ryo_fuzzy_parser::repair_tagged_enum_json;
1772
1773 let schema = intent_schema();
1774 let result = repair_tagged_enum_json(json, &schema, options)
1775 .map_err(|e| GoalParseError::FuzzyRepair(e.to_string()))?;
1776
1777 let intent: Intent = serde_json::from_value(result.repaired)
1778 .map_err(|e| GoalParseError::JsonParse(e.to_string()))?;
1779
1780 Ok((intent, result.corrections))
1781 }
1782}
1783
1784/// Error type for Goal/Intent parsing
1785#[cfg(feature = "fuzzy-parser")]
1786#[derive(Debug, thiserror::Error)]
1787pub enum GoalParseError {
1788 #[error("JSON parse error: {0}")]
1789 JsonParse(String),
1790
1791 #[error("Fuzzy repair error: {0}")]
1792 FuzzyRepair(String),
1793}
1794// ============================================================================
1795// From<CascadeSpec> for Intent
1796// ============================================================================
1797
1798impl From<ryo_analysis::cascade::CascadeSpec> for Intent {
1799 fn from(spec: ryo_analysis::cascade::CascadeSpec) -> Self {
1800 use ryo_analysis::cascade::CascadeSpec;
1801
1802 match spec {
1803 CascadeSpec::AddMatchArm {
1804 target,
1805 function_name,
1806 enum_name,
1807 pattern,
1808 body,
1809 } => {
1810 // Construct full function path: target (module or module::Type) + function_name
1811 let fn_path = target
1812 .child(&function_name)
1813 .map(|p| p.to_string())
1814 .unwrap_or_else(|_| format!("{}::{}", target, function_name));
1815 Intent::AddMatchArm {
1816 symbol_id: None,
1817 symbol_path: Some(fn_path),
1818 target_fn: None,
1819 enum_name,
1820 pattern,
1821 body,
1822 }
1823 }
1824 CascadeSpec::AddDerive { symbol_id, derives } => Intent::AddDerive {
1825 symbol_id: Some(format!("{:?}", symbol_id)),
1826 symbol_path: None,
1827 target_type: None,
1828 derives,
1829 },
1830 CascadeSpec::ChangeVisibility {
1831 symbol_id,
1832 visibility,
1833 ..
1834 } => {
1835 let vis = match visibility {
1836 ryo_analysis::cascade::Visibility::Private => Visibility::Private,
1837 ryo_analysis::cascade::Visibility::Crate => Visibility::PubCrate,
1838 ryo_analysis::cascade::Visibility::Super => Visibility::PubSuper,
1839 ryo_analysis::cascade::Visibility::Public => Visibility::Pub,
1840 };
1841 Intent::ChangeVisibility {
1842 symbol_id: Some(format!("{:?}", symbol_id)),
1843 symbol_path: None,
1844 target_item: None,
1845 to: vis,
1846 }
1847 }
1848 CascadeSpec::AddUse { path, .. } => Intent::Custom {
1849 description: format!("Add use statement: {}", path),
1850 examples: vec![],
1851 },
1852 CascadeSpec::GenerateImpl {
1853 target, trait_name, ..
1854 } => Intent::Custom {
1855 description: format!("Generate impl {} for {}", trait_name, target),
1856 examples: vec![],
1857 },
1858 CascadeSpec::RemoveMatchArm {
1859 target,
1860 function_name,
1861 enum_name,
1862 pattern,
1863 } => {
1864 // Construct full function path: target (module or module::Type) + function_name
1865 let fn_path = target
1866 .child(&function_name)
1867 .map(|p| p.to_string())
1868 .unwrap_or_else(|_| format!("{}::{}", target, function_name));
1869 Intent::RemoveMatchArm {
1870 symbol_id: None,
1871 symbol_path: Some(fn_path),
1872 target_fn: None,
1873 enum_name,
1874 pattern,
1875 }
1876 }
1877 }
1878 }
1879}
1880
1881// ============================================================================
1882// Tests
1883// ============================================================================
1884
1885#[cfg(test)]
1886mod tests {
1887 use super::*;
1888
1889 #[test]
1890 fn test_goal_builder() {
1891 let goal = Goal::rename("is_debug", "enable_debug");
1892 assert!(goal.query.contains("is_debug"));
1893
1894 if let Intent::RenameIdent {
1895 target_ident,
1896 to,
1897 kind,
1898 ..
1899 } = &goal.intents[0]
1900 {
1901 assert_eq!(target_ident.as_deref(), Some("is_debug"));
1902 assert_eq!(to, "enable_debug");
1903 assert_eq!(kind, &IdentKind::Any);
1904 } else {
1905 panic!("Expected RenameIdent intent");
1906 }
1907 }
1908
1909 #[test]
1910 fn test_scope_hint_builder() {
1911 let scope = ScopeHint::new()
1912 .with_file_patterns(vec!["src/**/*.rs".to_string()])
1913 .with_symbol_patterns(vec!["*Config".to_string()])
1914 .with_estimated_scope(EstimatedScope::ManyFiles);
1915
1916 assert_eq!(scope.file_patterns, vec!["src/**/*.rs"]);
1917 assert_eq!(scope.symbol_patterns, vec!["*Config"]);
1918 assert_eq!(scope.estimated_scope, EstimatedScope::ManyFiles);
1919 }
1920
1921 #[test]
1922 fn add_variant_rejects_unknown_field() {
1923 // NG-1: variant_fields is not a valid field — should error, not silently ignore
1924 let json = serde_json::json!({
1925 "type": "AddVariant",
1926 "target_enum": "Filter",
1927 "variant_name": "Add",
1928 "variant_fields": [["left", "Box<Filter>"], ["right", "Box<Filter>"]]
1929 });
1930 let result = serde_json::from_value::<Intent>(json);
1931 assert!(
1932 result.is_err(),
1933 "Unknown field 'variant_fields' should cause deserialization error, got: {:?}",
1934 result.unwrap()
1935 );
1936 let err_msg = result.unwrap_err().to_string();
1937 assert!(
1938 err_msg.contains("unknown field"),
1939 "Error should mention 'unknown field', got: {}",
1940 err_msg
1941 );
1942 }
1943
1944 #[test]
1945 fn add_variant_valid_fields_accepted() {
1946 let json = serde_json::json!({
1947 "type": "AddVariant",
1948 "target_enum": "Filter",
1949 "variant_name": "Add",
1950 "variant_type": "tuple:Box<Filter>,Box<Filter>"
1951 });
1952 let result = serde_json::from_value::<Intent>(json);
1953 assert!(
1954 result.is_ok(),
1955 "Valid AddVariant should parse: {:?}",
1956 result.err()
1957 );
1958 }
1959}