Skip to content

Commit 888c1b9

Browse files
authored
Merge pull request #504 from Extra-Chill/fix-workspace-git-status-loop
Fix Studio worktree context projection crash
2 parents 3d48737 + 1d5ab67 commit 888c1b9

2 files changed

Lines changed: 41 additions & 25 deletions

File tree

inc/Workspace/WorktreeContextInjector.php

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,10 @@ private static function project_site_agents_md( string $worktree_path, array $pa
10781078
return array();
10791079
}
10801080

1081+
if ( ! self::can_symlink_site_agents_md($source) ) {
1082+
return self::project_virtual_site_agents_md_via_opencode_config($worktree_path);
1083+
}
1084+
10811085
$target = rtrim($worktree_path, '/') . '/' . self::PROJECTED_AGENTS_PATH;
10821086
if ( file_exists($target) || is_link($target) ) {
10831087
return self::project_site_agents_md_via_opencode_config($worktree_path, $source);
@@ -1097,25 +1101,13 @@ private static function project_site_agents_md( string $worktree_path, array $pa
10971101
}
10981102

10991103
$projection_kind = 'symlink';
1100-
if ( self::can_symlink_site_agents_md($source) ) {
1101-
// phpcs:ignore WordPress.WP.AlternativeFunctions.symlink_symlink -- Local checkout projection to a DMC-owned generated file.
1102-
if ( ! symlink($source, $target) ) {
1103-
return new \WP_Error(
1104-
'agents_md_projection_failed',
1105-
sprintf('Failed to symlink site AGENTS.md into worktree: %s', $target),
1106-
array( 'status' => 500 )
1107-
);
1108-
}
1109-
} else {
1110-
$projection_kind = 'inline';
1111-
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
1112-
if ( false === file_put_contents($target, self::render($payload)) ) {
1113-
return new \WP_Error(
1114-
'agents_md_projection_failed',
1115-
sprintf('Failed to write inline site AGENTS.md into worktree: %s', $target),
1116-
array( 'status' => 500 )
1117-
);
1118-
}
1104+
// phpcs:ignore WordPress.WP.AlternativeFunctions.symlink_symlink -- Local checkout projection to a DMC-owned generated file.
1105+
if ( ! symlink($source, $target) ) {
1106+
return new \WP_Error(
1107+
'agents_md_projection_failed',
1108+
sprintf('Failed to symlink site AGENTS.md into worktree: %s', $target),
1109+
array( 'status' => 500 )
1110+
);
11191111
}
11201112

11211113
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
@@ -1146,6 +1138,27 @@ private static function can_symlink_site_agents_md( string $source ): bool {
11461138
return ! str_starts_with($source, '/wordpress/');
11471139
}
11481140

1141+
/**
1142+
* Project Studio PHP-WASM site context through host-visible local files only.
1143+
*
1144+
* Root-level inline AGENTS.md projections for `/wordpress/...` sources can
1145+
* crash Studio's PHP-WASM filesystem sync after WP-CLI exits. The rendered
1146+
* `.claude/CLAUDE.local.md` snapshot was already written by `inject()`, so
1147+
* point OpenCode at that host-visible file instead of creating a root
1148+
* projection from the virtual source path.
1149+
*
1150+
* @param string $worktree_path Absolute path to the worktree directory.
1151+
* @return string[]|\WP_Error Absolute written paths, or WP_Error on failure.
1152+
*/
1153+
private static function project_virtual_site_agents_md_via_opencode_config( string $worktree_path ): array|\WP_Error {
1154+
$local_context = rtrim($worktree_path, '/') . '/' . self::INJECTED_PATHS[0];
1155+
if ( ! is_file($local_context) ) {
1156+
return array();
1157+
}
1158+
1159+
return self::project_site_agents_md_via_opencode_config($worktree_path, $local_context);
1160+
}
1161+
11491162
/**
11501163
* Add the site AGENTS.md to a local OpenCode instructions array.
11511164
*

tests/smoke-worktree-context-injection.php

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,11 @@ function datamachine_code_pos( string $haystack, string $needle, string $label )
175175
)
176176
);
177177
datamachine_code_context_assert(! is_wp_error($virtual_injection), 'virtual context injection succeeds');
178-
datamachine_code_context_assert(! is_link($virtual_agents), 'virtual AGENTS.md projection is not a host symlink');
179-
datamachine_code_context_assert(is_file($virtual_agents), 'virtual AGENTS.md projection is written inline');
180-
datamachine_code_context_assert(str_contains((string) file_get_contents($virtual_agents), '# Virtual Memory'), 'inline virtual projection contains rendered context');
181-
datamachine_code_context_assert(trim(file_get_contents($virtual_root . '/.datamachine/AGENTS.md.source')) === "inline\n/wordpress/AGENTS.md", 'virtual projection marker records inline source');
178+
datamachine_code_context_assert(! file_exists($virtual_agents) && ! is_link($virtual_agents), 'virtual AGENTS.md projection is not written at the worktree root');
179+
$virtual_config = json_decode((string) file_get_contents($virtual_root . '/.opencode/opencode.json'), true);
180+
datamachine_code_context_assert(is_array($virtual_config), 'virtual context writes local OpenCode config');
181+
datamachine_code_context_assert(in_array($virtual_root . '/.claude/CLAUDE.local.md', $virtual_config['instructions'] ?? array(), true), 'virtual context points OpenCode at host-visible local snapshot');
182+
datamachine_code_context_assert(! file_exists($virtual_root . '/.datamachine/AGENTS.md.source'), 'virtual context does not write root projection marker');
182183

183184
$existing_injection = \DataMachineCode\Workspace\WorktreeContextInjector::inject(
184185
$existing_root,
@@ -200,8 +201,8 @@ function datamachine_code_pos( string $haystack, string $needle, string $label )
200201
datamachine_code_context_assert(! file_exists($worktree_root . '/.datamachine/AGENTS.md.source'), 'projection marker is gone after uninject');
201202
datamachine_code_context_assert(! file_exists($worktree_root . '/.opencode/AGENTS.local.md'), 'uninject removes legacy fake OpenCode local snapshot');
202203
$virtual_removed = \DataMachineCode\Workspace\WorktreeContextInjector::uninject($virtual_root);
203-
datamachine_code_context_assert(in_array($virtual_agents, $virtual_removed['removed'], true), 'uninject removes inline virtual AGENTS.md projection');
204-
datamachine_code_context_assert(! file_exists($virtual_agents), 'inline virtual projection is gone after uninject');
204+
datamachine_code_context_assert(in_array($virtual_root . '/.opencode/opencode.json', $virtual_removed['removed'], true), 'uninject removes virtual OpenCode projection config');
205+
datamachine_code_context_assert(! file_exists($virtual_root . '/.opencode/opencode.json'), 'virtual OpenCode projection config is gone after uninject');
205206
$existing_removed = \DataMachineCode\Workspace\WorktreeContextInjector::uninject($existing_root);
206207
datamachine_code_context_assert(! file_exists($existing_root . '/.opencode/opencode.json'), 'uninject removes DMC-created OpenCode projection config');
207208
datamachine_code_context_assert(in_array($existing_root . '/.opencode/opencode.json', $existing_removed['removed'], true), 'removed OpenCode projection config is reported');
@@ -211,6 +212,7 @@ function datamachine_code_pos( string $haystack, string $needle, string $label )
211212
array_map('unlink', glob($existing_root . '/.claude/*') ?: array());
212213
array_map('unlink', glob($existing_root . '/.opencode/*') ?: array());
213214
array_map('unlink', glob($virtual_root . '/.claude/*') ?: array());
215+
array_map('unlink', glob($virtual_root . '/.opencode/*') ?: array());
214216
array_map('rmdir', array_filter(glob($worktree_root . '/*') ?: array(), 'is_dir'));
215217
array_map('rmdir', array_filter(glob($existing_root . '/*') ?: array(), 'is_dir'));
216218
array_map('rmdir', array_filter(glob($virtual_root . '/*') ?: array(), 'is_dir'));
@@ -223,6 +225,7 @@ function datamachine_code_pos( string $haystack, string $needle, string $label )
223225
rmdir($existing_root . '/.opencode');
224226
rmdir($existing_root . '/.datamachine');
225227
rmdir($virtual_root . '/.claude');
228+
rmdir($virtual_root . '/.opencode');
226229
rmdir($virtual_root . '/.datamachine');
227230
rmdir($worktree_root);
228231
rmdir($existing_root);

0 commit comments

Comments
 (0)