Skip to content

Commit b811f43

Browse files
authored
Merge pull request #594 from Extra-Chill/fix-github-label-system-task
Add deterministic GitHub issue label task
2 parents 9bb3663 + 35fdfcb commit b811f43

5 files changed

Lines changed: 423 additions & 1 deletion

File tree

data-machine-code.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ function datamachine_code_load_chat_tools() {
317317
add_filter(
318318
'datamachine_tasks', function ( array $tasks ): array {
319319
$tasks['github_create_issue'] = \DataMachineCode\Tasks\GitHubIssueTask::class;
320+
$tasks['github_update_issue_labels'] = \DataMachineCode\Tasks\GitHubIssueLabelsTask::class;
320321
$tasks['worktree_cleanup_chunk'] = \DataMachineCode\Tasks\WorktreeCleanupChunkTask::class;
321322
$tasks['worktree_cleanup'] = \DataMachineCode\Tasks\WorktreeCleanupTask::class;
322323
$tasks['workspace_disk_emergency_cleanup'] = \DataMachineCode\Tasks\WorkspaceDiskEmergencyCleanupTask::class;

inc/Abilities/GitHubAbilities.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1943,7 +1943,7 @@ private static function getCurrentAgentSlug(): string {
19431943
}
19441944
}
19451945

1946-
$agent_slug = PermissionHelper::get_acting_agent_slug();
1946+
$agent_slug = (string) PermissionHelper::get_acting_agent_slug();
19471947
if ( '' !== trim($agent_slug) ) {
19481948
return sanitize_text_field($agent_slug);
19491949
}
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
<?php
2+
/**
3+
* GitHub issue label update system task.
4+
*
5+
* @package DataMachineCode\Tasks
6+
*/
7+
8+
namespace DataMachineCode\Tasks;
9+
10+
use DataMachine\Engine\AI\System\Tasks\SystemTask;
11+
use DataMachineCode\Abilities\GitHubAbilities;
12+
13+
defined('ABSPATH') || exit;
14+
15+
class GitHubIssueLabelsTask extends SystemTask {
16+
17+
/**
18+
* Task type identifier.
19+
*
20+
* @return string
21+
*/
22+
public function getTaskType(): string {
23+
return 'github_update_issue_labels';
24+
}
25+
26+
/**
27+
* Task metadata for Data Machine system-task surfaces.
28+
*
29+
* @return array<string,mixed>
30+
*/
31+
public static function getTaskMeta(): array {
32+
return array(
33+
'label' => 'GitHub Issue Label Update',
34+
'description' => 'Deterministically add and remove GitHub issue labels without replacing the full label set.',
35+
'setting_key' => null,
36+
'default_enabled' => true,
37+
'supports_run' => false,
38+
'mutates' => true,
39+
'params_schema' => array(
40+
'type' => 'object',
41+
'required' => array( 'repo', 'issue_number' ),
42+
'properties' => array(
43+
'repo' => array( 'type' => 'string' ),
44+
'issue_number' => array( 'type' => 'integer' ),
45+
'add_labels' => array(
46+
'type' => 'array',
47+
'items' => array( 'type' => 'string' ),
48+
),
49+
'remove_labels' => array(
50+
'type' => 'array',
51+
'items' => array( 'type' => 'string' ),
52+
),
53+
),
54+
),
55+
);
56+
}
57+
58+
/**
59+
* This task can infer the fetched GitHub issue when embedded after a fetch step.
60+
*
61+
* @return bool
62+
*/
63+
public function needsPipelineContext(): bool {
64+
return true;
65+
}
66+
67+
/**
68+
* Execute surgical GitHub issue label updates.
69+
*
70+
* @param int $jobId Job ID.
71+
* @param array $params Task params.
72+
* @return void
73+
*/
74+
public function executeTask( int $jobId, array $params ): void {
75+
$params = $this->withFetchedGitHubIssueContext($params);
76+
$repo = trim( (string) ( $params['repo'] ?? '' ) );
77+
$issue_number = (int) ( $params['issue_number'] ?? 0 );
78+
$add_labels = $this->normalizeLabels($params['add_labels'] ?? array());
79+
$remove_labels = $this->normalizeLabels($params['remove_labels'] ?? array());
80+
81+
if ( '' === $repo || $issue_number <= 0 ) {
82+
$this->failJob($jobId, 'GitHub issue label update requires repo and issue_number.');
83+
return;
84+
}
85+
86+
if ( array() === $add_labels && array() === $remove_labels ) {
87+
$this->failJob($jobId, 'GitHub issue label update requires at least one add_labels or remove_labels entry.');
88+
return;
89+
}
90+
91+
$removed = array();
92+
$results = array();
93+
94+
foreach ( $remove_labels as $label ) {
95+
$result = GitHubAbilities::removeLabel(
96+
array(
97+
'repo' => $repo,
98+
'issue_number' => $issue_number,
99+
'label' => $label,
100+
)
101+
);
102+
if ( is_wp_error($result) ) {
103+
$this->failJob($jobId, $result->get_error_message());
104+
return;
105+
}
106+
$removed[] = $label;
107+
$results[] = array(
108+
'action' => 'remove',
109+
'label' => $label,
110+
'result' => $result,
111+
);
112+
}
113+
114+
$added = array();
115+
if ( array() !== $add_labels ) {
116+
$result = GitHubAbilities::addLabels(
117+
array(
118+
'repo' => $repo,
119+
'issue_number' => $issue_number,
120+
'labels' => $add_labels,
121+
)
122+
);
123+
if ( is_wp_error($result) ) {
124+
$this->failJob($jobId, $result->get_error_message());
125+
return;
126+
}
127+
$added = $add_labels;
128+
$results[] = array(
129+
'action' => 'add',
130+
'labels' => $add_labels,
131+
'result' => $result,
132+
);
133+
}
134+
135+
$this->completeJob(
136+
$jobId,
137+
array(
138+
'success' => true,
139+
'repo' => $repo,
140+
'issue_number' => $issue_number,
141+
'added_labels' => $added,
142+
'removed_labels' => $removed,
143+
'results' => $results,
144+
)
145+
);
146+
}
147+
148+
/**
149+
* Normalize a scalar or array of labels to a de-duplicated list.
150+
*
151+
* @param mixed $labels Raw labels.
152+
* @return array<int,string>
153+
*/
154+
private function normalizeLabels( mixed $labels ): array {
155+
if ( is_string($labels) ) {
156+
$labels = array( $labels );
157+
}
158+
if ( ! is_array($labels) ) {
159+
return array();
160+
}
161+
162+
$normalized = array();
163+
foreach ( $labels as $label ) {
164+
$label = trim( (string) $label );
165+
if ( '' !== $label ) {
166+
$normalized[] = $label;
167+
}
168+
}
169+
170+
return array_values(array_unique($normalized));
171+
}
172+
173+
/**
174+
* Fill repo and issue_number from the first GitHub issue packet when omitted.
175+
*
176+
* @param array<string,mixed> $params Raw params.
177+
* @return array<string,mixed>
178+
*/
179+
private function withFetchedGitHubIssueContext( array $params ): array {
180+
$repo_missing = '' === trim( (string) ( $params['repo'] ?? '' ) );
181+
$number_missing = (int) ( $params['issue_number'] ?? 0 ) <= 0;
182+
if ( ! $repo_missing && ! $number_missing ) {
183+
return $params;
184+
}
185+
186+
$data_packets = is_array($params['data_packets'] ?? null) ? $params['data_packets'] : array();
187+
foreach ( $data_packets as $packet ) {
188+
if ( ! is_array($packet) ) {
189+
continue;
190+
}
191+
$metadata = is_array($packet['metadata'] ?? null) ? $packet['metadata'] : array();
192+
if ( 'github' !== (string) ( $metadata['source_type'] ?? '' ) || 'issues' !== (string) ( $metadata['github_type'] ?? '' ) ) {
193+
continue;
194+
}
195+
196+
if ( $repo_missing && ! empty($metadata['github_repo']) ) {
197+
$params['repo'] = (string) $metadata['github_repo'];
198+
}
199+
if ( $number_missing && (int) ( $metadata['github_number'] ?? 0 ) > 0 ) {
200+
$params['issue_number'] = (int) $metadata['github_number'];
201+
}
202+
return $params;
203+
}
204+
205+
return $params;
206+
}
207+
}

tests/smoke-github-create-abilities.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,42 @@ public static function get( string $key, string $default_value = '' ): string
5555
}
5656

5757
namespace DataMachineCode\Support {
58+
class PermissionHelper
59+
{
60+
public static function can_manage(): bool
61+
{
62+
return \DataMachine\Abilities\PermissionHelper::can_manage();
63+
}
64+
65+
public static function get_acting_agent_slug(): ?string
66+
{
67+
return \DataMachine\Abilities\PermissionHelper::get_acting_agent_slug();
68+
}
69+
70+
public static function get_acting_agent_id(): ?int
71+
{
72+
return \DataMachine\Abilities\PermissionHelper::get_acting_agent_id();
73+
}
74+
75+
public static function get_runtime_context(): array
76+
{
77+
return \DataMachine\Abilities\PermissionHelper::get_runtime_context();
78+
}
79+
80+
public static function get_execution_principal(): mixed
81+
{
82+
return null;
83+
}
84+
}
85+
86+
class PluginSettings
87+
{
88+
public static function get( string $key, string $default_value = '' ): string
89+
{
90+
return \DataMachine\Core\PluginSettings::get($key, $default_value);
91+
}
92+
}
93+
5894
class GitHubCredentialResolver
5995
{
6096
public static string $mode = 'pat';

0 commit comments

Comments
 (0)