From f6d4b2f7e45c0537ff29f1f02fd3b34e4d7e60dc Mon Sep 17 00:00:00 2001 From: akiojin Date: Sun, 25 Oct 2026 03:30:46 +0900 Subject: [PATCH 1/2] fix(lsp): restore create_class writes when lsp is off (#531) --- ...b69e8a00f57e20a633d8b83fb0cdc177be06.jsonl | 1 + ...09ad740793073ab9990c46bdec6a56112946.jsonl | 1 + ...a989644343d63dfe9a7c181891134d30cc94.jsonl | 1 + ...d9526372c56f33d8a02ec5e99ce38c9417b6.jsonl | 1 + ...cbd0f7bf553fc9480f4c3bdbc3588cd39113.jsonl | 1 + ...029b99381b59d7033beffb39ce628d2de75c.jsonl | 1 + ...821a020ee3650aa525564a0c3e3425e24607.jsonl | 1 + CHANGELOG.md | 7 +++ README.md | 2 +- src/tooling/local_tools.rs | 58 ++++++++++++++++--- tests/csharp_post_write.rs | 30 ++++++++-- 11 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 .gwt/work/events/21/215bdfaf9d46e011c7d37404b214b69e8a00f57e20a633d8b83fb0cdc177be06.jsonl create mode 100644 .gwt/work/events/41/4196dd04aaea04d8da8a77d2538309ad740793073ab9990c46bdec6a56112946.jsonl create mode 100644 .gwt/work/events/aa/aa5f23ab6a628246df69325da5a4a989644343d63dfe9a7c181891134d30cc94.jsonl create mode 100644 .gwt/work/events/b3/b3b3f32586fffd2d9a5fb001f68cd9526372c56f33d8a02ec5e99ce38c9417b6.jsonl create mode 100644 .gwt/work/events/e5/e551452969fa7abf345fa088b19ccbd0f7bf553fc9480f4c3bdbc3588cd39113.jsonl create mode 100644 .gwt/work/events/ea/ea0fade0a0d296e167bd46afcbd4029b99381b59d7033beffb39ce628d2de75c.jsonl create mode 100644 .gwt/work/events/fe/fe76bf77160ab704e97786695107821a020ee3650aa525564a0c3e3425e24607.jsonl diff --git a/.gwt/work/events/21/215bdfaf9d46e011c7d37404b214b69e8a00f57e20a633d8b83fb0cdc177be06.jsonl b/.gwt/work/events/21/215bdfaf9d46e011c7d37404b214b69e8a00f57e20a633d8b83fb0cdc177be06.jsonl new file mode 100644 index 00000000..b6b76cc1 --- /dev/null +++ b/.gwt/work/events/21/215bdfaf9d46e011c7d37404b214b69e8a00f57e20a633d8b83fb0cdc177be06.jsonl @@ -0,0 +1 @@ +{"id":"8ccb9351-36cc-4bc3-b31b-13cd13153f8f","work_item_id":"work-work-issue-531-0392c924","kind":"update","title":null,"intent":null,"summary":"Codex started work on Issue #531 fix(lsp): restore direct file writes for create_class when UNITY_CLI_LSP_MODE=off (work/issue-531)","progress_summary":null,"status_category":"active","owner":"Issue #531","next_action":null,"agent_session_id":"73eacf45-d040-4a89-9248-92a2a4b84455","agent_id":"codex","display_name":"Codex","board_entry_id":"35aa48e3-d136-4242-a98b-9a18e2a0b54f","execution_container":{"branch":"work/issue-531","worktree_path":".","pr_number":null,"pr_url":null,"pr_state":null},"related_work_item_id":null,"updated_at":"2026-10-24T18:18:39.212275500Z"} diff --git a/.gwt/work/events/41/4196dd04aaea04d8da8a77d2538309ad740793073ab9990c46bdec6a56112946.jsonl b/.gwt/work/events/41/4196dd04aaea04d8da8a77d2538309ad740793073ab9990c46bdec6a56112946.jsonl new file mode 100644 index 00000000..d57a47e7 --- /dev/null +++ b/.gwt/work/events/41/4196dd04aaea04d8da8a77d2538309ad740793073ab9990c46bdec6a56112946.jsonl @@ -0,0 +1 @@ +{"id":"a858e378-e1b1-4c5b-a3ed-15cfe79b9100","work_item_id":"work-work-issue-531-0392c924","kind":"done","title":"create_class の LSP off 直接保存互換性","intent":"実装確定。fresh canonical 検証と develop への配信","summary":"ミヤビ・ホンダ: off のみ直接保存、他モードと他ツールはLSPを維持。User Verification Result: n/a (autonomous)。Agent Visual Check: n/a (no UI surface)。","progress_summary":"Issue #531 の最小修正・回帰テスト・README / CHANGELOG を確定。RED と対象7件GREEN、.NET846件PASS。最終記録を含めcommit/push後に全5品質ゲートとReady PR / required CI / auto-mergeを実行する。","status_category":"done","owner":"Issue #531","next_action":null,"agent_session_id":"73eacf45-d040-4a89-9248-92a2a4b84455","agent_id":"codex","display_name":"Codex","board_entry_id":null,"execution_container":{"branch":"work/issue-531","worktree_path":".","pr_number":null,"pr_url":null,"pr_state":null},"related_work_item_id":null,"updated_at":"2026-10-24T18:30:01.895439200Z"} diff --git a/.gwt/work/events/aa/aa5f23ab6a628246df69325da5a4a989644343d63dfe9a7c181891134d30cc94.jsonl b/.gwt/work/events/aa/aa5f23ab6a628246df69325da5a4a989644343d63dfe9a7c181891134d30cc94.jsonl new file mode 100644 index 00000000..f703a6be --- /dev/null +++ b/.gwt/work/events/aa/aa5f23ab6a628246df69325da5a4a989644343d63dfe9a7c181891134d30cc94.jsonl @@ -0,0 +1 @@ +{"id":"013aff22-37c5-4eea-aab1-7cc6717f6109","work_item_id":"work-work-issue-531-0392c924","kind":"update","title":"Issue #531 の要件実現と develop への反映","intent":"仕様と既存ソース、配信状態の確認","summary":null,"progress_summary":null,"status_category":null,"owner":"Issue #531","next_action":null,"agent_session_id":"73eacf45-d040-4a89-9248-92a2a4b84455","agent_id":"codex","display_name":"Codex","board_entry_id":null,"execution_container":{"branch":"work/issue-531","worktree_path":".","pr_number":null,"pr_url":null,"pr_state":null},"related_work_item_id":null,"updated_at":"2026-10-24T18:19:35.997615400Z"} diff --git a/.gwt/work/events/b3/b3b3f32586fffd2d9a5fb001f68cd9526372c56f33d8a02ec5e99ce38c9417b6.jsonl b/.gwt/work/events/b3/b3b3f32586fffd2d9a5fb001f68cd9526372c56f33d8a02ec5e99ce38c9417b6.jsonl new file mode 100644 index 00000000..445e9fc0 --- /dev/null +++ b/.gwt/work/events/b3/b3b3f32586fffd2d9a5fb001f68cd9526372c56f33d8a02ec5e99ce38c9417b6.jsonl @@ -0,0 +1 @@ +{"id":"badcc7fc-41b3-4d5e-b6c3-385765066987","work_item_id":"work-work-issue-531-0392c924","kind":"start","title":"fix(lsp): restore direct file writes for create_class when UNITY_CLI_LSP_MODE=off","intent":"Check Board for latest updates","summary":"Codex is running","progress_summary":null,"status_category":"active","owner":"Issue #531","next_action":"Check Board for latest updates","agent_session_id":"73eacf45-d040-4a89-9248-92a2a4b84455","agent_id":"codex","display_name":"Codex","board_entry_id":null,"execution_container":{"branch":"work/issue-531","worktree_path":".","pr_number":null,"pr_url":null,"pr_state":null},"related_work_item_id":null,"updated_at":"2026-10-24T18:18:30.184208500Z"} diff --git a/.gwt/work/events/e5/e551452969fa7abf345fa088b19ccbd0f7bf553fc9480f4c3bdbc3588cd39113.jsonl b/.gwt/work/events/e5/e551452969fa7abf345fa088b19ccbd0f7bf553fc9480f4c3bdbc3588cd39113.jsonl new file mode 100644 index 00000000..4100befc --- /dev/null +++ b/.gwt/work/events/e5/e551452969fa7abf345fa088b19ccbd0f7bf553fc9480f4c3bdbc3588cd39113.jsonl @@ -0,0 +1 @@ +{"id":"aea5b08f-0093-4c33-b8d9-43eb751da473","work_item_id":"work-work-issue-531-0392c924","kind":"update","title":"create_class の LSP off 直接保存互換性","intent":"Issue #531 の回帰テストと最小修正","summary":null,"progress_summary":"要件・コメント・関連 PR・base を監査。既存配信ソースはなく AC-1〜5 は未達。","status_category":null,"owner":"Issue #531","next_action":null,"agent_session_id":"73eacf45-d040-4a89-9248-92a2a4b84455","agent_id":"codex","display_name":"Codex","board_entry_id":null,"execution_container":{"branch":"work/issue-531","worktree_path":".","pr_number":null,"pr_url":null,"pr_state":null},"related_work_item_id":null,"updated_at":"2026-10-24T18:23:07.506524500Z"} diff --git a/.gwt/work/events/ea/ea0fade0a0d296e167bd46afcbd4029b99381b59d7033beffb39ce628d2de75c.jsonl b/.gwt/work/events/ea/ea0fade0a0d296e167bd46afcbd4029b99381b59d7033beffb39ce628d2de75c.jsonl new file mode 100644 index 00000000..9e3f3a37 --- /dev/null +++ b/.gwt/work/events/ea/ea0fade0a0d296e167bd46afcbd4029b99381b59d7033beffb39ce628d2de75c.jsonl @@ -0,0 +1 @@ +{"id":"d6dc4d7f-8464-4818-945b-f411c5fb6563","work_item_id":"work-work-issue-531-0392c924","kind":"claim","title":null,"intent":null,"summary":"現在の状態: ミヤビ・ホンダが Issue #531 を担当します。Spec Status: IMPLEMENTATION-GAP。fetch 後の HEAD / origin/work/issue-531 / origin/develop は 1d021ff で一致し、関連 PR はありません。\n\n理由: local_create_class が常に local_lsp_write を呼ぶため、LSP off の互換性が未回復です。\n\nPM default ruling (override available): create_class の off 分岐だけ直接保存に戻し、auto / required / 未設定と他の書き込みツールは維持します。受け入れ基準に必要な最小変更です。\n\n次: local_tools.rs の回帰テストと実装、README / CHANGELOG を更新し、fresh verify.run と Ready PR / CI auto-merge で develop へ届けます。","progress_summary":null,"status_category":"active","owner":"Issue #531","next_action":null,"agent_session_id":"73eacf45-d040-4a89-9248-92a2a4b84455","agent_id":"codex","display_name":"Codex","board_entry_id":"0e247f06-214d-4344-bc39-4f575f7dcf34","execution_container":{"branch":"work/issue-531","worktree_path":".","pr_number":null,"pr_url":null,"pr_state":null},"related_work_item_id":null,"updated_at":"2026-10-24T18:22:56.021314Z"} diff --git a/.gwt/work/events/fe/fe76bf77160ab704e97786695107821a020ee3650aa525564a0c3e3425e24607.jsonl b/.gwt/work/events/fe/fe76bf77160ab704e97786695107821a020ee3650aa525564a0c3e3425e24607.jsonl new file mode 100644 index 00000000..04a44163 --- /dev/null +++ b/.gwt/work/events/fe/fe76bf77160ab704e97786695107821a020ee3650aa525564a0c3e3425e24607.jsonl @@ -0,0 +1 @@ +{"id":"14714aa0-7e1d-4624-88da-94031fe0c861","work_item_id":"work-work-issue-531-0392c924","kind":"update","title":null,"intent":null,"summary":"現在の状態: ミヤビ・ホンダが create_class の off 限定直接保存と README / CHANGELOG を実装しました。新テストは修正前に requires LSP で RED、修正後は対象7件 PASS。.NET テスト846件も PASS。\n\n理由: 既存のテンプレート・パス検証・重複拒否を再利用することで互換性だけを戻しました。auto / required / 未設定の LSP 結果保持は既存の Unix CLI 統合テストを3モードへ拡張しています。\n\n次: 最終 Work を記録してソースを push 後、全5品質ゲートを fresh verify.run で実行し、Ready PR と既存自動マージで配信します。User Verification Result: n/a (autonomous)、Agent Visual Check: n/a (no UI surface)。","progress_summary":null,"status_category":"active","owner":"Issue #531","next_action":null,"agent_session_id":"73eacf45-d040-4a89-9248-92a2a4b84455","agent_id":"codex","display_name":"Codex","board_entry_id":"f60a31b2-32f6-4b5d-b9cd-ee4b17d7eed8","execution_container":{"branch":"work/issue-531","worktree_path":".","pr_number":null,"pr_url":null,"pr_state":null},"related_work_item_id":null,"updated_at":"2026-10-24T18:29:31.923733700Z"} diff --git a/CHANGELOG.md b/CHANGELOG.md index ce5d6e82..60777887 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## [Unreleased] +### Fixes + +- `create_class` once again writes the generated `.cs` file directly when + `UNITY_CLI_LSP_MODE=off`, restoring the v0.18.1 behavior. Other modes keep + the LSP write path and its result fields; other C# write tools still require + the LSP ([#531](https://github.com/akiojin/unity-cli/issues/531)). + ## [0.19.0] - 2026-10-19 ### Breaking changes and migration diff --git a/README.md b/README.md index 88898cbc..8262b276 100644 --- a/README.md +++ b/README.md @@ -279,7 +279,7 @@ policy, required CI, and skill documentation changes. | `UNITY_CLI_HOST` | Unity Editor host | `127.0.0.1` | | `UNITY_CLI_PORT` | Unity Editor port | `6400` | | `UNITY_CLI_TIMEOUT_MS` | Command timeout (ms) | `30000` | -| `UNITY_CLI_LSP_MODE` | LSP mode (`off` / `auto` / `required`). `auto` prefers the C# LSP for symbol search and falls back to the local index (`backend: "index"`, `fallbackReason`) | `auto` | +| `UNITY_CLI_LSP_MODE` | LSP mode (`off` / `auto` / `required`). `auto` prefers the C# LSP for symbol search and falls back to the local index (`backend: "index"`, `fallbackReason`). With `off`, `create_class` writes files directly; other C# write tools still require the LSP. | `auto` | | `UNITY_CLI_LSP_AUTO_TIMEOUT_MS` | Upper bound (ms) for an `auto` symbol search to wait for the LSP before falling back to the local index | `10000` | | `UNITY_CLI_TOOLS_ROOT` | Downloaded tools root directory | OS default | | `UNITY_CLI_NO_AUTO_UPDATE` | Disable background auto-update (`1` to disable) | *(unset)* | diff --git a/src/tooling/local_tools.rs b/src/tooling/local_tools.rs index 54c869e6..ca5c84c8 100644 --- a/src/tooling/local_tools.rs +++ b/src/tooling/local_tools.rs @@ -948,6 +948,16 @@ fn local_create_class(params: &Value) -> Result { let body = render_class_template(name, namespace, inherits); + if env::var("UNITY_CLI_LSP_MODE").is_ok_and(|mode| mode.eq_ignore_ascii_case("off")) { + if let Some(parent) = abs_path.parent() { + fs::create_dir_all(parent) + .with_context(|| format!("Failed to create directory: {}", parent.display()))?; + } + fs::write(&abs_path, &body) + .with_context(|| format!("Failed to write file: {}", abs_path.display()))?; + return Ok(json!({ "success": true, "path": rel_path, "content": body })); + } + // Write through the same LSP path as `create_csharp_file` so the generated // class is validated and the post-write pipeline sees an LSP result. let mut result = local_lsp_write( @@ -1924,6 +1934,7 @@ mod tests { #[test] fn create_class_rejects_existing_file() { let _guard = env_lock().lock().expect("lock should succeed"); + let _mode = ScopedEnv::set("UNITY_CLI_LSP_MODE", "off"); let tmp = tempfile::tempdir().expect("temp dir should be created"); write_file( &tmp.path().join("Assets/Scripts/Existing.cs"), @@ -2181,23 +2192,52 @@ namespace Demo.Game } #[test] - fn create_class_requires_lsp_and_does_not_write_directly() { + fn create_class_writes_directly_when_lsp_is_off() { let _guard = env_lock().lock().expect("lock should succeed"); let tmp = tempfile::tempdir().expect("temp dir should be created"); - std::env::set_var("UNITY_PROJECT_ROOT", tmp.path()); - std::env::set_var("UNITY_CLI_LSP_MODE", "off"); + let _root = ScopedEnv::set("UNITY_PROJECT_ROOT", tmp.path().to_str().unwrap()); + let _mode = ScopedEnv::set("UNITY_CLI_LSP_MODE", "off"); let result = maybe_execute_local_tool( "create_class", &json!({"name":"EnemyAI","folder":"Assets/Scripts/AI"}), ) - .expect("tool should be handled"); + .expect("tool should be handled") + .expect("create_class should write directly when LSP is off"); - assert!(result.is_err(), "create_class should fail when LSP is off"); - assert!(result.unwrap_err().to_string().contains("requires LSP")); - assert!(!tmp.path().join("Assets/Scripts/AI/EnemyAI.cs").exists()); + assert_eq!(result["success"], true); + assert_eq!(result["path"], "Assets/Scripts/AI/EnemyAI.cs"); + assert_eq!(result["content"], "public class EnemyAI\n{\n}\n"); + assert_eq!( + std::fs::read_to_string(tmp.path().join("Assets/Scripts/AI/EnemyAI.cs")).unwrap(), + result["content"].as_str().unwrap() + ); + } - std::env::remove_var("UNITY_PROJECT_ROOT"); - std::env::remove_var("UNITY_CLI_LSP_MODE"); + #[test] + fn create_class_lsp_off_preserves_unityengine_using_and_path_override() { + let _guard = env_lock().lock().expect("lock should succeed"); + let tmp = tempfile::tempdir().expect("temp dir should be created"); + let _root = ScopedEnv::set("UNITY_PROJECT_ROOT", tmp.path().to_str().unwrap()); + let _mode = ScopedEnv::set("UNITY_CLI_LSP_MODE", "OFF"); + + for inherits in ["MonoBehaviour", "ScriptableObject"] { + let path = format!("Packages/Game/{inherits}Sample.cs"); + let result = maybe_execute_local_tool( + "create_class", + &json!({ + "name":"Sample", "namespace":"Game", "inherits":inherits, "path":path + }), + ) + .expect("tool should be handled") + .expect("Unity class should be created without LSP"); + + let content = std::fs::read_to_string(tmp.path().join(&path)).unwrap(); + assert_eq!(result["success"], true); + assert_eq!(result["path"], path); + assert_eq!(result["content"], content); + assert!(content.starts_with("using UnityEngine;\n\nnamespace Game\n")); + assert!(content.contains(&format!("public class Sample : {inherits}"))); + } } } diff --git a/tests/csharp_post_write.rs b/tests/csharp_post_write.rs index 16a6aa95..15af17f8 100644 --- a/tests/csharp_post_write.rs +++ b/tests/csharp_post_write.rs @@ -37,7 +37,18 @@ impl Fixture { } fn call(&self, tool: &str, params: &Value, port: u16) -> Value { - let output = Command::new(env!("CARGO_BIN_EXE_unity-cli")) + self.call_with_lsp_mode(tool, params, port, None) + } + + fn call_with_lsp_mode( + &self, + tool: &str, + params: &Value, + port: u16, + mode: Option<&str>, + ) -> Value { + let mut command = Command::new(env!("CARGO_BIN_EXE_unity-cli")); + command .args([ "--output", "json", @@ -61,9 +72,11 @@ impl Fixture { .env("UNITY_CLI_EDITORS_DIR", self.root.path().join("editors")) .env("UNITY_PROJECT_ROOT", self.root.path().join("wrong-project")) .env("UNITY_CLI_PORT", "1") - .env_remove("UNITY_CLI_LSP_MODE") - .output() - .unwrap(); + .env_remove("UNITY_CLI_LSP_MODE"); + if let Some(mode) = mode { + command.env("UNITY_CLI_LSP_MODE", mode); + } + let output = command.output().unwrap(); let envelope: Value = serde_json::from_slice(&output.stdout).unwrap(); let payload = envelope["data"].clone(); assert_eq!( @@ -244,8 +257,14 @@ fn post_write_tools_use_explicit_endpoint_and_include_compile_results() { #[test] fn create_class_writes_through_lsp_and_keeps_output_shape() { + for mode in [None, Some("auto"), Some("required")] { + assert_create_class_lsp_output(mode); + } +} + +fn assert_create_class_lsp_output(mode: Option<&str>) { let fixture = Fixture::new(); - let response = fixture.call( + let response = fixture.call_with_lsp_mode( "create_class", &json!({ "name": "EnemyAI", @@ -254,6 +273,7 @@ fn create_class_writes_through_lsp_and_keeps_output_shape() { "folder": "Assets/Scripts/AI" }), 1, + mode, ); assert_eq!(response["success"], true, "{response}"); assert_eq!(response["backend"], "lsp", "{response}"); From 1d03ecb2a1349677bb26dbcc9015919104504501 Mon Sep 17 00:00:00 2001 From: akiojin Date: Sun, 25 Oct 2026 03:51:43 +0900 Subject: [PATCH 2/2] chore(work): record issue 531 pull request --- ...7aa54f36cc104370c7be65768d86551d8c68c15b4f84736b1d51d32.jsonl | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gwt/work/events/2f/2f13b40dc7aa54f36cc104370c7be65768d86551d8c68c15b4f84736b1d51d32.jsonl diff --git a/.gwt/work/events/2f/2f13b40dc7aa54f36cc104370c7be65768d86551d8c68c15b4f84736b1d51d32.jsonl b/.gwt/work/events/2f/2f13b40dc7aa54f36cc104370c7be65768d86551d8c68c15b4f84736b1d51d32.jsonl new file mode 100644 index 00000000..19845917 --- /dev/null +++ b/.gwt/work/events/2f/2f13b40dc7aa54f36cc104370c7be65768d86551d8c68c15b4f84736b1d51d32.jsonl @@ -0,0 +1 @@ +{"id":"3c5a3aaa-7274-4abe-8604-429772bca4de","work_item_id":"work-work-issue-531-0392c924","kind":"pr","title":null,"intent":null,"summary":null,"progress_summary":null,"status_category":"done","owner":null,"next_action":null,"agent_session_id":"73eacf45-d040-4a89-9248-92a2a4b84455","agent_id":null,"display_name":null,"board_entry_id":null,"execution_container":{"branch":"work/issue-531","worktree_path":".","pr_number":532,"pr_url":"https://github.com/akiojin/unity-cli/pull/532","pr_state":"OPEN"},"related_work_item_id":null,"updated_at":"2026-10-24T18:48:04.173512900Z"}