From f8ce0dd920787f32c25af5f5d7bb10d1ee38f07d Mon Sep 17 00:00:00 2001 From: "homeboy-ci[bot]" <266378653+homeboy-ci[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:36:03 -0400 Subject: [PATCH 1/4] Surface stale primaries in AGENTS inventory --- inc/Runtime/AgentsMdSections.php | 117 +++++++++++++++- tests/smoke-agents-md-workspace-freshness.php | 129 ++++++++++++++++++ 2 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 tests/smoke-agents-md-workspace-freshness.php diff --git a/inc/Runtime/AgentsMdSections.php b/inc/Runtime/AgentsMdSections.php index ce9ca04d..90f78072 100644 --- a/inc/Runtime/AgentsMdSections.php +++ b/inc/Runtime/AgentsMdSections.php @@ -374,14 +374,21 @@ private static function render_workspace_inventory_section( string $wp ): string $mode = 'compact'; } - $lines = array(); + $lines = array(); + $attention_lines = array(); foreach ( $by_repo as $repo => $bucket ) { - $primary = $bucket['primary']; + $primary = is_array($bucket['primary']) ? $bucket['primary'] : array(); $worktrees = $bucket['worktrees']; $wt_count = count($worktrees); $branch = $primary['branch'] ?? null; $remote = $primary['remote'] ?? null; $branch_str = ( null !== $branch && '' !== $branch ) ? sprintf(' (`%s`)', $branch) : ''; + $freshness = is_array($primary['primary_freshness'] ?? null) ? $primary['primary_freshness'] : null; + + $attention = self::format_primary_freshness_attention($repo, $freshness); + if ( '' !== $attention ) { + $attention_lines[] = $attention; + } if ( 'compact' === $mode ) { $suffix_parts = array(); @@ -389,6 +396,10 @@ private static function render_workspace_inventory_section( string $wp ): string if ( null !== $remote && '' !== $remote ) { $suffix_parts[] = $remote; } + $freshness_badge = self::format_primary_freshness_badge($freshness); + if ( '' !== $freshness_badge ) { + $suffix_parts[] = $freshness_badge; + } $lines[] = sprintf('- **%s**%s — %s', $repo, $branch_str, implode(' · ', $suffix_parts)); continue; } @@ -397,6 +408,10 @@ private static function render_workspace_inventory_section( string $wp ): string if ( null !== $remote && '' !== $remote ) { $header .= ' — ' . $remote; } + $freshness_badge = self::format_primary_freshness_badge($freshness); + if ( '' !== $freshness_badge ) { + $header .= ' · ' . $freshness_badge; + } $lines[] = $header; usort( @@ -415,7 +430,8 @@ private static function render_workspace_inventory_section( string $wp ): string } } - $body = implode("\n", $lines); + $body = implode("\n", $lines); + $attention_block = self::render_primary_freshness_attention_block($attention_lines); $generated_at = gmdate('c'); $workspace_path = $listing['path']; $agent_slug = self::resolve_agent_slug(); @@ -428,6 +444,101 @@ private static function render_workspace_inventory_section( string $wp ): string Refresh this file with `{$wp} datamachine memory compose AGENTS.md{$agent_suffix}` after workspace changes if the inventory looks stale. +{$attention_block} + +{$body} +MD; + } + + private static function primary_freshness_needs_attention( ?array $freshness ): bool { + if ( null === $freshness ) { + return false; + } + + $status = (string) ( $freshness['status'] ?? '' ); + return in_array($status, array( 'stale', 'diverged', 'detached', 'unknown', 'no_upstream', 'ahead' ), true); + } + + private static function primary_freshness_needs_refresh( ?array $freshness ): bool { + if ( null === $freshness ) { + return false; + } + + $status = (string) ( $freshness['status'] ?? '' ); + return in_array($status, array( 'stale', 'diverged' ), true); + } + + private static function format_primary_freshness_badge( ?array $freshness ): string { + if ( ! self::primary_freshness_needs_attention($freshness) ) { + return ''; + } + + $status = (string) ( $freshness['status'] ?? 'unknown' ); + $parts = array( 'primary ' . $status ); + if ( isset($freshness['behind']) && is_numeric($freshness['behind']) && (int) $freshness['behind'] > 0 ) { + $parts[] = sprintf('behind %d', (int) $freshness['behind']); + } + if ( isset($freshness['ahead']) && is_numeric($freshness['ahead']) && (int) $freshness['ahead'] > 0 ) { + $parts[] = sprintf('ahead %d', (int) $freshness['ahead']); + } + + return implode(', ', $parts); + } + + private static function format_primary_freshness_attention( string $repo, ?array $freshness ): string { + if ( ! self::primary_freshness_needs_attention($freshness) ) { + return ''; + } + + $status = (string) ( $freshness['status'] ?? 'unknown' ); + $branch = (string) ( $freshness['branch'] ?? '' ); + $upstream = (string) ( $freshness['upstream'] ?? '' ); + $details = array(); + if ( '' !== $branch ) { + $details[] = sprintf('branch `%s`', $branch); + } + if ( '' !== $upstream ) { + $details[] = sprintf('upstream `%s`', $upstream); + } + if ( isset($freshness['behind']) && is_numeric($freshness['behind']) ) { + $details[] = sprintf('behind %d', (int) $freshness['behind']); + } + if ( isset($freshness['ahead']) && is_numeric($freshness['ahead']) ) { + $details[] = sprintf('ahead %d', (int) $freshness['ahead']); + } + + $line = sprintf('- **%s** primary is `%s`', $repo, $status); + if ( ! empty($details) ) { + $line .= ' (' . implode(', ', $details) . ')'; + } + + $command = (string) ( $freshness['suggested_command'] ?? '' ); + if ( '' !== $command && self::primary_freshness_needs_refresh($freshness) ) { + $line .= sprintf('. Refresh: `%s`', $command); + } + + return $line . '.'; + } + + private static function render_primary_freshness_attention_block( array $attention_lines ): string { + if ( empty($attention_lines) ) { + return ''; + } + + $max_lines = 20; + $shown = array_slice($attention_lines, 0, $max_lines); + $omitted = count($attention_lines) - count($shown); + if ( $omitted > 0 ) { + $shown[] = sprintf('- %d more primary checkout(s) need attention; run `wp datamachine-code workspace list` for the full set.', $omitted); + } + + $body = implode("\n", $shown); + + return << true, + 'path' => '/tmp/dmc-workspace', + 'repos' => array( + array( + 'name' => 'current-repo', + 'repo' => 'current-repo', + 'git' => true, + 'is_worktree' => false, + 'branch' => 'main', + 'remote' => 'https://example.com/current-repo.git', + 'primary_freshness' => array( + 'status' => 'current', + 'branch' => 'main', + 'upstream' => 'origin/main', + 'behind' => 0, + 'ahead' => 0, + ), + ), + array( + 'name' => 'stale-repo', + 'repo' => 'stale-repo', + 'git' => true, + 'is_worktree' => false, + 'branch' => 'trunk', + 'remote' => 'https://example.com/stale-repo.git', + 'primary_freshness' => array( + 'status' => 'stale', + 'branch' => 'trunk', + 'upstream' => 'origin/trunk', + 'behind' => 7, + 'ahead' => 0, + 'suggested_command' => 'wp datamachine-code workspace git pull stale-repo --allow-primary-refresh', + ), + ), + array( + 'name' => 'stale-repo@fix-example', + 'repo' => 'stale-repo', + 'git' => true, + 'is_worktree' => true, + 'branch_slug' => 'fix-example', + 'branch' => 'fix/example', + ), + ), + ); + } + } +} + +namespace { + if ( ! defined('ABSPATH') ) { + define('ABSPATH', '/var/www/html'); + } + + function datamachine_agents_md_enabled(): bool { + return true; + } + + function is_multisite(): bool { + return false; + } + + function apply_filters( string $hook_name, mixed $value, mixed ...$args ): mixed { + return $value; + } + + function is_wp_error( mixed $thing ): bool { + return false; + } + + function assert_contains( string $needle, string $haystack, string $message ): void { + if ( ! str_contains($haystack, $needle) ) { + throw new RuntimeException($message); + } + } + + function assert_not_contains( string $needle, string $haystack, string $message ): void { + if ( str_contains($haystack, $needle) ) { + throw new RuntimeException($message); + } + } + + require_once dirname(__DIR__) . '/inc/Runtime/CommandIntrospector.php'; + require_once dirname(__DIR__) . '/inc/Runtime/AgentsMdSections.php'; + + \DataMachineCode\Runtime\AgentsMdSections::register(); + + $sections = \DataMachine\Engine\AI\SectionRegistry::$sections; + if ( ! isset($sections['workspace-inventory']) ) { + throw new RuntimeException('workspace-inventory section was not registered'); + } + + $rendered = $sections['workspace-inventory']['callback'](); + + assert_contains('**Primary Checkout Attention**', $rendered, 'primary freshness attention block missing'); + assert_contains('These primary checkouts may be stale or unsafe to read.', $rendered, 'primary freshness guidance missing'); + assert_contains('- **stale-repo** primary is `stale` (branch `trunk`, upstream `origin/trunk`, behind 7, ahead 0). Refresh: `wp datamachine-code workspace git pull stale-repo --allow-primary-refresh`.', $rendered, 'stale primary details missing'); + assert_contains('- **stale-repo** (`trunk`) — 1 worktree · https://example.com/stale-repo.git · primary stale, behind 7', $rendered, 'compact stale primary badge missing'); + assert_not_contains('- **current-repo** primary is `current`', $rendered, 'current primary should not appear in attention block'); + + fwrite(STDOUT, "agents-md workspace freshness smoke passed\n"); +} From 61c7981c4c64847919531c8f891b67f7d681d39b Mon Sep 17 00:00:00 2001 From: "homeboy-ci[bot]" <266378653+homeboy-ci[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:50:47 -0400 Subject: [PATCH 2/4] Refresh AGENTS inventory after primary pulls --- inc/Workspace/WorkspaceCoreUtilities.php | 4 +- inc/Workspace/WorkspaceGitOperations.php | 4 + tests/workspace-git-pull-primary-refresh.php | 113 +++++++++++++++++++ 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 tests/workspace-git-pull-primary-refresh.php diff --git a/inc/Workspace/WorkspaceCoreUtilities.php b/inc/Workspace/WorkspaceCoreUtilities.php index c054c409..7051b6f7 100644 --- a/inc/Workspace/WorkspaceCoreUtilities.php +++ b/inc/Workspace/WorkspaceCoreUtilities.php @@ -1091,7 +1091,7 @@ private function protect_directory( string $path ): void { * The payload mirrors the workspace `name`/`repo` taxonomy used elsewhere * in this class: * - * - `op`: one of `clone`, `adopt`, `remove`, `worktree_add`, `worktree_remove`. + * - `op`: one of `clone`, `adopt`, `remove`, `primary_refresh`, `worktree_add`, `worktree_remove`. * - `repo`: bare repository name (no `@` suffix). * - `name`: workspace entry name on disk (`` for primaries, * `@` for worktrees). @@ -1112,7 +1112,7 @@ protected function emit_workspace_changed( string $op, string $repo, string $nam * @since 0.31.0 * * @param array{op: string, repo: string, name: string, path: string} $payload { - * @type string $op One of clone|adopt|remove|worktree_add|worktree_remove. + * @type string $op One of clone|adopt|remove|primary_refresh|worktree_add|worktree_remove. * @type string $repo Bare repository name (no @-suffix). * @type string $name Workspace entry name ( or @). * @type string $path Absolute path of the workspace entry. diff --git a/inc/Workspace/WorkspaceGitOperations.php b/inc/Workspace/WorkspaceGitOperations.php index 42e946d0..8794157b 100644 --- a/inc/Workspace/WorkspaceGitOperations.php +++ b/inc/Workspace/WorkspaceGitOperations.php @@ -121,6 +121,10 @@ public function git_pull( string $handle, bool $allow_dirty = false, bool $allow return $result; } + if ( empty($parsed['is_worktree']) ) { + $this->emit_workspace_changed('primary_refresh', $parsed['repo'], $parsed['dir_name'], $repo_path); + } + return array( 'success' => true, 'message' => trim( (string) $result['output']), diff --git a/tests/workspace-git-pull-primary-refresh.php b/tests/workspace-git-pull-primary-refresh.php new file mode 100644 index 00000000..4baec287 --- /dev/null +++ b/tests/workspace-git-pull-primary-refresh.php @@ -0,0 +1,113 @@ + $operation ); + } + } +} + +namespace { + if ( ! defined('ABSPATH') ) { + define('ABSPATH', '/var/www/html'); + } + + function is_wp_error( mixed $thing ): bool { + return $thing instanceof WP_Error; + } + + final class WP_Error { + public function __construct( public string $code = '', public string $message = '', public array $data = array() ) {} + } +} + +namespace DataMachineCode\Tests { + require_once dirname(__DIR__) . '/inc/Workspace/WorkspaceGitOperations.php'; + + use DataMachineCode\Workspace\WorkspaceGitOperations; + + final class GitPullWorkspaceDouble { + use WorkspaceGitOperations; + + public array $emitted = array(); + public array $commands = array(); + + protected function parse_handle( string $handle ): array { + if ( str_contains($handle, '@') ) { + return array( + 'repo' => strtok($handle, '@'), + 'dir_name' => $handle, + 'is_worktree' => true, + ); + } + + return array( + 'repo' => $handle, + 'dir_name' => $handle, + 'is_worktree' => false, + ); + } + + protected function resolve_repo_path( string $handle ): string { + return '/workspace/' . $handle; + } + + protected function ensure_git_mutation_allowed( string $repo ): true { + return true; + } + + protected function ensure_primary_mutation_allowed( array $parsed, bool $allow_primary_mutation, string $message ): true { + return true; + } + + public function git_status( string $handle ): array { + return array( 'dirty' => 0 ); + } + + protected function run_git( string $repo_path, string $command, int $timeout_seconds = 0 ): array { + $this->commands[] = compact('repo_path', 'command', 'timeout_seconds'); + + return array( 'output' => 'Already up to date.' ); + } + + protected function emit_workspace_changed( string $op, string $repo, string $name, string $path ): void { + $this->emitted[] = compact('op', 'repo', 'name', 'path'); + } + } + + function assert_same( mixed $expected, mixed $actual, string $message ): void { + if ( $expected !== $actual ) { + throw new \RuntimeException($message . ' Expected: ' . var_export($expected, true) . ' Actual: ' . var_export($actual, true)); + } + } + + $primary = new GitPullWorkspaceDouble(); + $result = $primary->git_pull('data-machine-code', false, true); + assert_same(true, $result['success'] ?? null, 'primary pull did not succeed'); + assert_same( + array( + array( + 'op' => 'primary_refresh', + 'repo' => 'data-machine-code', + 'name' => 'data-machine-code', + 'path' => '/workspace/data-machine-code', + ), + ), + $primary->emitted, + 'primary pull did not emit primary_refresh' + ); + + $worktree = new GitPullWorkspaceDouble(); + $result = $worktree->git_pull('data-machine-code@fix-example', false, false); + assert_same(true, $result['success'] ?? null, 'worktree pull did not succeed'); + assert_same(array(), $worktree->emitted, 'worktree pull should not emit primary_refresh'); + + fwrite(STDOUT, "workspace git pull primary refresh passed\n"); +} From 2a6db7357bdd1377d8f666a6de9bf8aacbf7e09d Mon Sep 17 00:00:00 2001 From: "homeboy-ci[bot]" <266378653+homeboy-ci[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:57:12 -0400 Subject: [PATCH 3/4] Fix AGENTS inventory lint --- inc/Runtime/AgentsMdSections.php | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/inc/Runtime/AgentsMdSections.php b/inc/Runtime/AgentsMdSections.php index 90f78072..bd45bdc6 100644 --- a/inc/Runtime/AgentsMdSections.php +++ b/inc/Runtime/AgentsMdSections.php @@ -26,9 +26,6 @@ public static function register(): void { } $registry_class = '\DataMachine\Engine\AI\MemoryFileRegistry'; - if ( ! is_callable(array( $registry_class, 'register' )) ) { - return; - } $register = array( $registry_class, 'register' ); /** @var callable $register */ @@ -149,6 +146,7 @@ private static function render_workspace_policy_intro( string $workspace_path ): * @param string $default Default policy markdown. * @param string $workspace_path Resolved DMC workspace root. */ + /** @var mixed $filtered */ $filtered = apply_filters('datamachine_code_workspace_policy_intro', $default, $workspace_path); if ( ! is_string($filtered) ) { return $default; @@ -175,6 +173,7 @@ private static function render_workspace_policy_section( string $workspace_path, * @param string $workspace_path Resolved DMC workspace root. * @param string $wp WP-CLI command prefix. */ + /** @var mixed $filtered */ $filtered = apply_filters('datamachine_code_workspace_policy_section', $default, $workspace_path, $wp); if ( ! is_string($filtered) ) { return $default; @@ -288,9 +287,6 @@ private static function resolve_wp_cli_cmd(): string { */ private static function register_section( string $file, string $section, int $priority, callable $callback, array $metadata ): void { $registry_class = '\DataMachine\Engine\AI\SectionRegistry'; - if ( ! is_callable(array( $registry_class, 'register' )) ) { - return; - } $register = array( $registry_class, 'register' ); /** @var callable $register */ @@ -431,11 +427,11 @@ private static function render_workspace_inventory_section( string $wp ): string } $body = implode("\n", $lines); - $attention_block = self::render_primary_freshness_attention_block($attention_lines); - $generated_at = gmdate('c'); - $workspace_path = $listing['path']; - $agent_slug = self::resolve_agent_slug(); - $agent_suffix = '' !== $agent_slug ? ' --agent=' . $agent_slug : ''; + $attention_block = self::render_primary_freshness_attention_block($attention_lines); + $generated_at = gmdate('c'); + $workspace_path = $listing['path']; + $agent_slug = self::resolve_agent_slug(); + $agent_suffix = '' !== $agent_slug ? ' --agent=' . $agent_slug : ''; return << Date: Thu, 25 Jun 2026 15:00:42 -0400 Subject: [PATCH 4/4] Align AGENTS inventory assignments --- inc/Runtime/AgentsMdSections.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/inc/Runtime/AgentsMdSections.php b/inc/Runtime/AgentsMdSections.php index bd45bdc6..4732debd 100644 --- a/inc/Runtime/AgentsMdSections.php +++ b/inc/Runtime/AgentsMdSections.php @@ -26,7 +26,7 @@ public static function register(): void { } $registry_class = '\DataMachine\Engine\AI\MemoryFileRegistry'; - $register = array( $registry_class, 'register' ); + $register = array( $registry_class, 'register' ); /** @var callable $register */ call_user_func( @@ -287,7 +287,7 @@ private static function resolve_wp_cli_cmd(): string { */ private static function register_section( string $file, string $section, int $priority, callable $callback, array $metadata ): void { $registry_class = '\DataMachine\Engine\AI\SectionRegistry'; - $register = array( $registry_class, 'register' ); + $register = array( $registry_class, 'register' ); /** @var callable $register */ call_user_func($register, $file, $section, $priority, $callback, $metadata); @@ -427,11 +427,11 @@ private static function render_workspace_inventory_section( string $wp ): string } $body = implode("\n", $lines); - $attention_block = self::render_primary_freshness_attention_block($attention_lines); - $generated_at = gmdate('c'); - $workspace_path = $listing['path']; - $agent_slug = self::resolve_agent_slug(); - $agent_suffix = '' !== $agent_slug ? ' --agent=' . $agent_slug : ''; + $attention_block = self::render_primary_freshness_attention_block($attention_lines); + $generated_at = gmdate('c'); + $workspace_path = $listing['path']; + $agent_slug = self::resolve_agent_slug(); + $agent_suffix = '' !== $agent_slug ? ' --agent=' . $agent_slug : ''; return <<