From 53e5ebac8b95701aff2d6e5dd59552ccf643ffe1 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 17 May 2026 17:16:32 -0400 Subject: [PATCH 1/5] fix: expose pull request URLs in schema --- inc/Abilities/GitHubAbilities.php | 8 ++++++++ tests/smoke-github-create-abilities.php | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/inc/Abilities/GitHubAbilities.php b/inc/Abilities/GitHubAbilities.php index 27b1f2f..617c878 100644 --- a/inc/Abilities/GitHubAbilities.php +++ b/inc/Abilities/GitHubAbilities.php @@ -341,7 +341,15 @@ private function registerAbilities(): void { 'type' => 'object', 'properties' => array( 'success' => array( 'type' => 'boolean' ), + 'kind' => array( 'type' => 'string' ), + 'repo' => array( 'type' => 'string' ), + 'number' => array( 'type' => 'integer' ), + 'pull_number' => array( 'type' => 'integer' ), + 'url' => array( 'type' => 'string' ), + 'html_url' => array( 'type' => 'string' ), + 'reused' => array( 'type' => 'boolean' ), 'pull_request' => array( 'type' => 'object' ), + 'labeling' => array( 'type' => 'object' ), 'error' => array( 'type' => 'string' ), ), ), diff --git a/tests/smoke-github-create-abilities.php b/tests/smoke-github-create-abilities.php index baeb209..aae7185 100644 --- a/tests/smoke-github-create-abilities.php +++ b/tests/smoke-github-create-abilities.php @@ -221,6 +221,11 @@ function wp_remote_retrieve_body( $response ): string { $assert( 'create-github-pull-request exposes labels', array_key_exists( 'labels', $pr_ability['input_schema']['properties'] ?? array() ) ); $assert( 'create-github-pull-request labels schema declares string items', array( 'type' => 'string' ) === ( $pr_ability['input_schema']['properties']['labels']['items'] ?? null ) ); $assert( 'create-github-pull-request exposes maintainer_can_modify', array_key_exists( 'maintainer_can_modify', $pr_ability['input_schema']['properties'] ?? array() ) ); + $pr_output_properties = $pr_ability['output_schema']['properties'] ?? array(); + $assert( 'create-github-pull-request output exposes top-level url', array_key_exists( 'url', $pr_output_properties ) ); + $assert( 'create-github-pull-request output exposes top-level html_url', array_key_exists( 'html_url', $pr_output_properties ) ); + $assert( 'create-github-pull-request output exposes top-level pull_number', array_key_exists( 'pull_number', $pr_output_properties ) ); + $assert( 'create-github-pull-request output exposes top-level reused', array_key_exists( 'reused', $pr_output_properties ) ); $assert( 'create-github-pull-request is hidden from REST', false === ( $pr_ability['meta']['show_in_rest'] ?? null ) ); $assert( 'create-or-update-github-file ability is registered', null !== $file_ability ); From 0ae5f7ba3462f8de4fb2900295c7ebad6ae3ddbe Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 17 May 2026 17:26:40 -0400 Subject: [PATCH 2/5] fix: align create PR schema validation --- homeboy.json | 7 +++++-- inc/Abilities/GitHubAbilities.php | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/homeboy.json b/homeboy.json index 113d735..584ef56 100644 --- a/homeboy.json +++ b/homeboy.json @@ -3,7 +3,10 @@ "changelog_target": "docs/CHANGELOG.md", "extensions": { "wordpress": { - "release_latest_branch": "release-latest" + "release_latest_branch": "release-latest", + "settings": { + "validation_dependencies": "[\"agents-api\",\"data-machine\"]" + } } }, "id": "data-machine-code", @@ -18,4 +21,4 @@ "pattern": "DATAMACHINE_CODE_VERSION',\\s*'([0-9.]+)'" } ] -} \ No newline at end of file +} diff --git a/inc/Abilities/GitHubAbilities.php b/inc/Abilities/GitHubAbilities.php index 617c878..6cd2b0d 100644 --- a/inc/Abilities/GitHubAbilities.php +++ b/inc/Abilities/GitHubAbilities.php @@ -5347,7 +5347,7 @@ public static function getPat( ?array $selector = null ): string { self::$last_auth_error = null; $token = (string) $credential['token']; - self::$token_modes[ $token ] = (string) ( $credential['mode'] ?? 'pat' ); + self::$token_modes[ $token ] = (string) $credential['mode']; return $token; } From bc84cdeb7426e63d8b823042fa5c32b506d3ff47 Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 17 May 2026 17:34:24 -0400 Subject: [PATCH 3/5] fix: run code smoke tests on host --- homeboy.json | 2 +- inc/Abilities/GitHubAbilities.php | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/homeboy.json b/homeboy.json index 584ef56..4de4d0d 100644 --- a/homeboy.json +++ b/homeboy.json @@ -5,7 +5,7 @@ "wordpress": { "release_latest_branch": "release-latest", "settings": { - "validation_dependencies": "[\"agents-api\",\"data-machine\"]" + "test_backend": "host-smoke" } } }, diff --git a/inc/Abilities/GitHubAbilities.php b/inc/Abilities/GitHubAbilities.php index 6cd2b0d..491a279 100644 --- a/inc/Abilities/GitHubAbilities.php +++ b/inc/Abilities/GitHubAbilities.php @@ -531,15 +531,15 @@ private function registerAbilities(): void { 'output_schema' => array( 'type' => 'object', 'properties' => array( - 'success' => array( 'type' => 'boolean' ), - 'repo' => array( 'type' => 'string' ), - 'pull_number' => array( 'type' => 'integer' ), - 'merged' => array( 'type' => 'boolean' ), - 'sha' => array( 'type' => 'string' ), - 'message' => array( 'type' => 'string' ), - 'html_url' => array( 'type' => 'string' ), + 'success' => array( 'type' => 'boolean' ), + 'repo' => array( 'type' => 'string' ), + 'pull_number' => array( 'type' => 'integer' ), + 'merged' => array( 'type' => 'boolean' ), + 'sha' => array( 'type' => 'string' ), + 'message' => array( 'type' => 'string' ), + 'html_url' => array( 'type' => 'string' ), 'local_worktree_cleanup' => array( 'type' => 'object' ), - 'error' => array( 'type' => 'string' ), + 'error' => array( 'type' => 'string' ), ), ), 'execute_callback' => array( self::class, 'mergePullRequest' ), @@ -2477,7 +2477,7 @@ public static function cleanupPullRequest( array $input, ?callable $api_get = nu $encoded_head_branch = implode( '/', array_map( 'rawurlencode', explode( '/', $head_branch ) ) ); $delete_url = sprintf( '%s/repos/%s/git/refs/heads/%s', self::API_BASE, $repo, $encoded_head_branch ); - $deleted = $api_request( 'DELETE', $delete_url, null, $pat ); + $deleted = $api_request( 'DELETE', $delete_url, null, $pat ); if ( is_wp_error( $deleted ) ) { $status = is_array( $deleted->get_error_data() ) ? (int) ( $deleted->get_error_data()['status'] ?? 0 ) : 0; if ( 404 !== $status ) { @@ -2710,9 +2710,9 @@ private static function buildPullRequestCommentInput( array $input ): array { } return array( - 'repo' => $input['repo'] ?? '', - 'issue_number' => (int) ( $input['pull_number'] ?? 0 ), - 'body' => $body, + 'repo' => $input['repo'] ?? '', + 'issue_number' => (int) ( $input['pull_number'] ?? 0 ), + 'body' => $body, 'skip_automation_comment_guard' => true, ); } @@ -5366,7 +5366,7 @@ public static function getCredential( ?array $selector = null ): array|\WP_Error $token = (string) $credential['token']; self::$last_auth_error = null; - self::$token_modes[ $token ] = (string) ( $credential['mode'] ?? 'pat' ); + self::$token_modes[ $token ] = (string) $credential['mode']; return $credential; } From 1b0acd09cbeba03735a749f240f8edad3ac1909b Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 17 May 2026 17:42:01 -0400 Subject: [PATCH 4/5] fix: satisfy GitHub ability static analysis --- inc/Abilities/GitHubAbilities.php | 57 ++++++++++++------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/inc/Abilities/GitHubAbilities.php b/inc/Abilities/GitHubAbilities.php index 491a279..a68062b 100644 --- a/inc/Abilities/GitHubAbilities.php +++ b/inc/Abilities/GitHubAbilities.php @@ -1499,7 +1499,7 @@ public static function createIssue( array $input ): array|\WP_Error { if ( ! empty( $input['assignees'] ) && is_array( $input['assignees'] ) ) { $body['assignees'] = array_map( 'sanitize_text_field', $input['assignees'] ); } - if ( isset( $input['milestone'] ) && null !== $input['milestone'] && '' !== $input['milestone'] ) { + if ( isset( $input['milestone'] ) && '' !== $input['milestone'] ) { $milestone = (int) $input['milestone']; if ( $milestone > 0 ) { $body['milestone'] = $milestone; @@ -1766,9 +1766,9 @@ private static function preparePullRequestRunArtifacts( array $input, string $re foreach ( $file_writes as $file ) { $file_result = self::createOrUpdateFile( array( 'repo' => $repo, - 'file_path' => $file['file_path'] ?? '', - 'content' => $file['content'] ?? '', - 'commit_message' => $file['commit_message'] ?? 'chore: persist Data Machine run artifact', + 'file_path' => $file['file_path'], + 'content' => $file['content'], + 'commit_message' => $file['commit_message'], 'branch' => $head, ) ); @@ -1777,7 +1777,7 @@ private static function preparePullRequestRunArtifacts( array $input, string $re } $committed_files[] = array( - 'file_path' => (string) ( $file_result['content']['path'] ?? ( $file['file_path'] ?? '' ) ), + 'file_path' => (string) ( $file_result['content']['path'] ?? $file['file_path'] ), 'commit_sha' => (string) ( $file_result['commit']['sha'] ?? '' ), 'file_url' => (string) ( $file_result['content']['html_url'] ?? '' ), ); @@ -1831,12 +1831,10 @@ private static function runArtifactEgressPolicyFromInput( array $input, array $a $job_id = (int) ( $input['job_id'] ?? 0 ); if ( $job_id > 0 && class_exists( '\\DataMachine\\Core\\Database\\Jobs\\Jobs' ) ) { - $jobs = new \DataMachine\Core\Database\Jobs\Jobs(); - if ( method_exists( $jobs, 'retrieve_engine_data' ) ) { - $engine_data = $jobs->retrieve_engine_data( $job_id ); - if ( is_array( $engine_data['run_artifact_egress_policy'] ?? null ) ) { - return $engine_data['run_artifact_egress_policy']; - } + $jobs = new \DataMachine\Core\Database\Jobs\Jobs(); + $engine_data = $jobs->retrieve_engine_data( $job_id ); + if ( is_array( $engine_data['run_artifact_egress_policy'] ?? null ) ) { + return $engine_data['run_artifact_egress_policy']; } } @@ -1919,12 +1917,10 @@ private static function mergeProvenanceLabels( array $labels ): array { * Resolve the current Data Machine agent slug when running in agent context. */ private static function getCurrentAgentSlug(): string { - foreach ( array( 'get_runtime_context', 'runtime_context' ) as $method ) { - if ( method_exists( PermissionHelper::class, $method ) ) { - $agent_slug = self::agentSlugFromContext( call_user_func( array( PermissionHelper::class, $method ) ) ); - if ( '' !== $agent_slug ) { - return $agent_slug; - } + if ( method_exists( PermissionHelper::class, 'get_runtime_context' ) ) { + $agent_slug = self::agentSlugFromContext( PermissionHelper::get_runtime_context() ); + if ( '' !== $agent_slug ) { + return $agent_slug; } } @@ -1955,12 +1951,8 @@ private static function getCurrentAgentSlug(): string { } $agents_repo = new \DataMachine\Core\Database\Agents\Agents(); - if ( ! method_exists( $agents_repo, 'get_agent' ) ) { - return ''; - } - - $agent = $agents_repo->get_agent( (int) $agent_id ); - $agent_slug = is_array( $agent ) ? (string) ( $agent['agent_slug'] ?? '' ) : ''; + $agent = $agents_repo->get_agent( (int) $agent_id ); + $agent_slug = is_array( $agent ) ? (string) ( $agent['agent_slug'] ?? '' ) : ''; return '' !== trim( $agent_slug ) ? sanitize_text_field( $agent_slug ) : ''; } @@ -2512,14 +2504,6 @@ private static function cleanupMergedPullRequestWorktree( string $repo, string $ } $workspace = new Workspace(); - if ( ! method_exists( $workspace, 'cleanup_merged_pr_worktree' ) ) { - return array( - 'success' => true, - 'skipped' => true, - 'reason' => 'workspace_cleanup_unsupported', - ); - } - return $workspace->cleanup_merged_pr_worktree( $repo, $head_branch, '' !== $pr_url ? $pr_url : null ); } @@ -2951,8 +2935,8 @@ public static function buildRepoReviewProfile( string $repo, array $options, cal $max_docs = max( 0, (int) ( $options['max_architecture_docs'] ?? 8 ) ); $limits = array( 'max_profile_files' => $max_files, - 'max_file_chars' => max( 1, (int) ( $options['max_file_chars'] ?? 12000 ) ), - 'max_total_chars' => max( 1, (int) ( $options['max_total_chars'] ?? 60000 ) ), + 'max_file_chars' => (int) max( 1, (int) ( $options['max_file_chars'] ?? 12000 ) ), + 'max_total_chars' => (int) max( 1, (int) ( $options['max_total_chars'] ?? 60000 ) ), 'max_architecture_docs' => $max_docs, ); @@ -3572,7 +3556,7 @@ private static function suggestLikelyStaleDocs( array $impacts, array $changed_d } foreach ( $wanted as &$entry ) { - $entry['reasons'] = array_values( array_unique( $entry['reasons'] ?? array() ) ); + $entry['reasons'] = array_values( array_unique( $entry['reasons'] ) ); } return array_values( $wanted ); @@ -4851,7 +4835,8 @@ public static function apiRequest( string $method, string $url, ?array $body, st ); if ( null !== $body ) { - $args['body'] = wp_json_encode( $body ); + $encoded_body = wp_json_encode( $body ); + $args['body'] = false === $encoded_body ? '' : $encoded_body; } $response = wp_remote_request( $url, $args ); @@ -5258,7 +5243,7 @@ public static function normalizePullReviewContext( string $repo, array $pull, ar $repo, $pull, $changed_files, - isset( $context['checks'] ) && is_array( $context['checks'] ) ? $context['checks'] : array(), + $context['checks'] ?? array(), isset( $options['escalation_policy'] ) && is_array( $options['escalation_policy'] ) ? $options['escalation_policy'] : array() ); } From c0fdb44bfaa93d631adb8339f51393e7a5d456fa Mon Sep 17 00:00:00 2001 From: Chris Huber Date: Sun, 17 May 2026 17:46:49 -0400 Subject: [PATCH 5/5] fix: remove stale GitHub ability fallbacks --- inc/Abilities/GitHubAbilities.php | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/inc/Abilities/GitHubAbilities.php b/inc/Abilities/GitHubAbilities.php index a68062b..a4eb9be 100644 --- a/inc/Abilities/GitHubAbilities.php +++ b/inc/Abilities/GitHubAbilities.php @@ -1829,15 +1829,6 @@ private static function runArtifactEgressPolicyFromInput( array $input, array $a } } - $job_id = (int) ( $input['job_id'] ?? 0 ); - if ( $job_id > 0 && class_exists( '\\DataMachine\\Core\\Database\\Jobs\\Jobs' ) ) { - $jobs = new \DataMachine\Core\Database\Jobs\Jobs(); - $engine_data = $jobs->retrieve_engine_data( $job_id ); - if ( is_array( $engine_data['run_artifact_egress_policy'] ?? null ) ) { - return $engine_data['run_artifact_egress_policy']; - } - } - return is_array( $artifacts['run_artifact_egress_policy'] ?? null ) ? $artifacts['run_artifact_egress_policy'] : array(); } @@ -1941,20 +1932,7 @@ private static function getCurrentAgentSlug(): string { } } - if ( ! method_exists( PermissionHelper::class, 'get_acting_agent_id' ) ) { - return ''; - } - - $agent_id = PermissionHelper::get_acting_agent_id(); - if ( empty( $agent_id ) || ! class_exists( '\DataMachine\Core\Database\Agents\Agents' ) ) { - return ''; - } - - $agents_repo = new \DataMachine\Core\Database\Agents\Agents(); - $agent = $agents_repo->get_agent( (int) $agent_id ); - $agent_slug = is_array( $agent ) ? (string) ( $agent['agent_slug'] ?? '' ) : ''; - - return '' !== trim( $agent_slug ) ? sanitize_text_field( $agent_slug ) : ''; + return ''; } /** @@ -2983,7 +2961,7 @@ public static function buildRepoReviewProfile( string $repo, array $options, cal } $paths = array_values( array_unique( $paths ) ); - $remaining_chars = $limits['max_total_chars']; + $remaining_chars = (int) $limits['max_total_chars']; foreach ( $paths as $path ) { if ( $profile['truncation']['included_files'] >= $limits['max_profile_files'] || $remaining_chars <= 0 ) { @@ -3007,7 +2985,7 @@ public static function buildRepoReviewProfile( string $repo, array $options, cal ++$profile['truncation']['included_files']; $profile['truncation']['included_chars'] += $entry['included_chars']; - $remaining_chars = max( 0, $remaining_chars - $entry['included_chars'] ); + $remaining_chars = (int) max( 0, $remaining_chars - $entry['included_chars'] ); if ( ! empty( $entry['truncated'] ) ) { ++$profile['truncation']['truncated_files'];