Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"}
Original file line number Diff line number Diff line change
@@ -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"}
Original file line number Diff line number Diff line change
@@ -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"}
Original file line number Diff line number Diff line change
@@ -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"}
Original file line number Diff line number Diff line change
@@ -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"}
Original file line number Diff line number Diff line change
@@ -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"}
Original file line number Diff line number Diff line change
@@ -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"}
Original file line number Diff line number Diff line change
@@ -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"}
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)* |
Expand Down
58 changes: 49 additions & 9 deletions src/tooling/local_tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,16 @@ fn local_create_class(params: &Value) -> Result<Value> {

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(
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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}")));
}
}
}
30 changes: 25 additions & 5 deletions tests/csharp_post_write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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!(
Expand Down Expand Up @@ -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",
Expand All @@ -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}");
Expand Down
Loading