-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathsmoke-github-create-tools.php
More file actions
370 lines (321 loc) · 17.8 KB
/
Copy pathsmoke-github-create-tools.php
File metadata and controls
370 lines (321 loc) · 17.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
<?php
/**
* Pure-PHP smoke test for GitHub create chat/pipeline tools.
*
* Run: php tests/smoke-github-create-tools.php
*/
declare( strict_types=1 );
namespace DataMachine\Engine\AI\Tools {
class BaseTool
{
public array $registered = array();
protected function registerTool( string $name, array $definition_callback, array $contexts, array $options = array() ): void
{
$this->registered[ $name ] = array(
'definition_callback' => $definition_callback,
'contexts' => $contexts,
'options' => $options,
);
}
protected function buildErrorResponse( string $message, string $tool_name ): array
{
return array(
'success' => false,
'error' => $message,
'tool_name' => $tool_name,
);
}
}
}
namespace DataMachineCode\Abilities {
class GitHubAbilities
{
public static array $addLabelsCalls = array();
public static array $removeLabelCalls = array();
public static function getRegisteredRepos(): array
{
return array(
array(
'owner' => 'Extra-Chill',
'repo' => 'data-machine-code',
'label' => 'Data Machine Code',
),
);
}
public static function addLabels( array $input ): array|\WP_Error
{
self::$addLabelsCalls[] = $input;
$labels = $input['labels'] ?? array();
if (! is_array($labels) || '' === (string) ( $labels[0] ?? '' ) ) {
return new \WP_Error('missing_labels', 'At least one label is required.');
}
return array(
'success' => true,
'applied_labels' => $labels,
);
}
public static function removeLabel( array $input ): array|\WP_Error
{
self::$removeLabelCalls[] = $input;
$label = (string) ( $input['label'] ?? '' );
if ('' === $label ) {
return new \WP_Error('missing_label', 'A single label is required.');
}
return array(
'success' => true,
'removed_label' => $label,
);
}
}
}
namespace {
if (! defined('ABSPATH') ) {
define('ABSPATH', __DIR__ . '/');
}
$GLOBALS['dmc_tool_ability_calls'] = array();
class DMC_Test_Ability
{
public function __construct( private string $name )
{
}
public function execute( array $input ): array
{
$GLOBALS['dmc_tool_ability_calls'][] = array(
'name' => $this->name,
'input' => $input,
);
if ('datamachine/create-github-pull-request' === $this->name ) {
return array(
'success' => true,
'kind' => 'pull_request',
'repo' => 'Extra-Chill/data-machine-code',
'number' => 261,
'pull_number' => 261,
'url' => 'https://github.com/Extra-Chill/data-machine-code/pull/261',
'html_url' => 'https://github.com/Extra-Chill/data-machine-code/pull/261',
'pull_request' => array( 'number' => 261 ),
);
}
return array(
'success' => true,
'kind' => 'issue',
'repo' => 'Extra-Chill/data-machine-code',
'number' => 262,
'issue_number' => 262,
'url' => 'https://github.com/Extra-Chill/data-machine-code/issues/262',
'html_url' => 'https://github.com/Extra-Chill/data-machine-code/issues/262',
);
}
}
function wp_get_ability( string $name ): ?DMC_Test_Ability
{
if (in_array($name, array( 'datamachine/create-github-issue', 'datamachine/create-github-pull-request' ), true) ) {
return new DMC_Test_Ability($name);
}
return null;
}
if (! class_exists('WP_Error') ) {
class WP_Error
{
public function __construct( private string $code = '', private string $message = '', private mixed $data = null )
{
}
public function get_error_message(): string
{
return $this->message;
}
public function get_error_code(): string
{
return $this->code;
}
public function get_error_data(): mixed
{
return $this->data;
}
}
}
function is_wp_error( $thing ): bool
{
return $thing instanceof WP_Error;
}
include __DIR__ . '/../inc/Tools/GitHubIssueTool.php';
include __DIR__ . '/../inc/Tools/GitHubPullRequestTool.php';
include __DIR__ . '/../inc/Tools/GitHubTools.php';
use DataMachineCode\Tools\GitHubIssueTool;
use DataMachineCode\Tools\GitHubTools;
use DataMachineCode\Tools\GitHubPullRequestTool;
$failures = array();
$assert = function ( string $label, bool $cond ) use ( &$failures ): void {
if ($cond ) {
echo " ok {$label}\n";
return;
}
$failures[] = $label;
echo " fail {$label}\n";
};
$assert_array_items = function ( array $schema, string $path ) use ( &$assert_array_items, $assert ): void {
if ('array' === ( $schema['type'] ?? null ) ) {
$assert("{$path} array schema declares items", isset($schema['items']) && is_array($schema['items']));
}
foreach ( $schema['properties'] ?? array() as $property => $property_schema ) {
if (is_array($property_schema) ) {
$assert_array_items($property_schema, "{$path}.properties.{$property}");
}
}
if (isset($schema['items']) && is_array($schema['items']) ) {
$assert_array_items($schema['items'], "{$path}.items");
}
};
echo "GitHub create tools - smoke\n";
$issue_tool = new GitHubIssueTool();
$pr_tool = new GitHubPullRequestTool();
$github_tools = new GitHubTools();
$assert('create_github_issue tool is registered', isset($issue_tool->registered['create_github_issue']));
$assert('create_github_issue is available in chat', in_array('chat', $issue_tool->registered['create_github_issue']['contexts'] ?? array(), true));
$assert('create_github_issue is available in pipeline', in_array('pipeline', $issue_tool->registered['create_github_issue']['contexts'] ?? array(), true));
$assert('create_github_issue links to create issue ability', 'datamachine/create-github-issue' === ( $issue_tool->registered['create_github_issue']['options']['ability'] ?? '' ));
$assert('create_github_pull_request tool is registered', isset($pr_tool->registered['create_github_pull_request']));
$assert('create_github_pull_request is available in chat', in_array('chat', $pr_tool->registered['create_github_pull_request']['contexts'] ?? array(), true));
$assert('create_github_pull_request is available in pipeline', in_array('pipeline', $pr_tool->registered['create_github_pull_request']['contexts'] ?? array(), true));
$assert('create_github_pull_request links to create PR ability', 'datamachine/create-github-pull-request' === ( $pr_tool->registered['create_github_pull_request']['options']['ability'] ?? '' ));
$assert('add_label_to_issue tool is registered', isset($github_tools->registered['add_label_to_issue']));
$assert('add_label_to_issue is available in chat', in_array('chat', $github_tools->registered['add_label_to_issue']['contexts'] ?? array(), true));
$assert('add_label_to_issue is available in pipeline', in_array('pipeline', $github_tools->registered['add_label_to_issue']['contexts'] ?? array(), true));
$assert('remove_label_from_issue tool is registered', isset($github_tools->registered['remove_label_from_issue']));
$assert('remove_label_from_issue is available in chat', in_array('chat', $github_tools->registered['remove_label_from_issue']['contexts'] ?? array(), true));
$assert('remove_label_from_issue is available in pipeline', in_array('pipeline', $github_tools->registered['remove_label_from_issue']['contexts'] ?? array(), true));
$list_issues_definition = $github_tools->getListIssuesDefinition();
$assert('list_github_issues uses default duplicate guard', 'repeatable' !== ( $list_issues_definition['runtime']['duplicate_policy'] ?? '' ));
$issue_definition = $issue_tool->getToolDefinition();
$issue_params = $issue_definition['parameters']['properties'] ?? array();
$assert('create_github_issue exposes assignees from ability schema', array_key_exists('assignees', $issue_params));
$assert('create_github_issue exposes milestone from ability schema', array_key_exists('milestone', $issue_params));
$pr_definition = $pr_tool->getToolDefinition();
$pr_params = $pr_definition['parameters']['properties'] ?? array();
$pr_required = $pr_definition['parameters']['required'] ?? array();
$assert('create_github_pull_request requires repo', in_array('repo', $pr_required, true));
$assert('create_github_pull_request requires title', in_array('title', $pr_required, true));
$assert('create_github_pull_request requires head', in_array('head', $pr_required, true));
foreach ( array( 'repo', 'title', 'head', 'base', 'body', 'draft', 'labels', 'maintainer_can_modify' ) as $param ) {
$assert("create_github_pull_request exposes {$param}", array_key_exists($param, $pr_params));
}
$issue_result = $issue_tool->handle_tool_call(
array(
'repo' => 'Extra-Chill/data-machine-code',
'title' => 'Issue title',
'body' => 'Issue body',
'labels' => array( 'bug' ),
'assignees' => array( 'chubes4' ),
'milestone' => 7,
)
);
$assert('create_github_issue returns direct ability success', true === ( $issue_result['success'] ?? false ));
$assert('create_github_issue response names tool', 'create_github_issue' === ( $issue_result['tool_name'] ?? '' ));
$assert('create_github_issue response identifies issue kind', 'issue' === ( $issue_result['kind'] ?? '' ));
$assert('create_github_issue response exposes canonical issue URL', 'https://github.com/Extra-Chill/data-machine-code/issues/262' === ( $issue_result['url'] ?? '' ));
$pr_result = $pr_tool->handle_tool_call(
array(
'repo' => 'Extra-Chill/data-machine-code',
'title' => 'PR title',
'head' => 'fix/github-pr-tool',
'base' => 'main',
'body' => 'PR body',
'labels' => array( 'needs-review' ),
'draft' => true,
'maintainer_can_modify' => false,
'job_id' => 123,
'run_artifacts' => array( 'required_tool_names' => array( 'agent_daily_memory' ) ),
'run_artifact_egress_policy' => array(
'daily_memory' => array( 'egress' => array( 'bundle-file', 'pr-body' ) ),
),
'bundle_root' => 'bundles/world-creator',
)
);
$assert('create_github_pull_request returns direct ability success', true === ( $pr_result['success'] ?? false ));
$assert('create_github_pull_request response names tool', 'create_github_pull_request' === ( $pr_result['tool_name'] ?? '' ));
$assert('create_github_pull_request response identifies PR kind', 'pull_request' === ( $pr_result['kind'] ?? '' ));
$assert('create_github_pull_request response exposes canonical PR URL', 'https://github.com/Extra-Chill/data-machine-code/pull/261' === ( $pr_result['url'] ?? '' ));
$assert('create_github_pull_request URL is distinguishable from branch URL', ! str_contains($pr_result['url'] ?? '', '/tree/'));
$pr_call = $GLOBALS['dmc_tool_ability_calls'][1] ?? array();
$assert('create_github_pull_request calls PR ability', 'datamachine/create-github-pull-request' === ( $pr_call['name'] ?? '' ));
$assert('create_github_pull_request forwards labels', array( 'needs-review' ) === ( $pr_call['input']['labels'] ?? null ));
$assert('create_github_pull_request forwards maintainer_can_modify', false === ( $pr_call['input']['maintainer_can_modify'] ?? null ));
$assert('create_github_pull_request forwards job context', 123 === ( $pr_call['input']['job_id'] ?? null ));
$assert('create_github_pull_request forwards run artifacts', array( 'required_tool_names' => array( 'agent_daily_memory' ) ) === ( $pr_call['input']['run_artifacts'] ?? null ));
$assert(
'create_github_pull_request forwards run artifact policy',
array( 'daily_memory' => array( 'egress' => array( 'bundle-file', 'pr-body' ) ) ) === ( $pr_call['input']['run_artifact_egress_policy'] ?? null )
);
$assert('create_github_pull_request forwards bundle root', 'bundles/world-creator' === ( $pr_call['input']['bundle_root'] ?? null ));
$add_label_definition = $github_tools->getAddLabelToIssueDefinition();
$remove_label_definition = $github_tools->getRemoveLabelFromIssueDefinition();
$manage_issue_definition = $github_tools->getManageIssueDefinition();
$assert('add_label_to_issue requires repo issue_number label', array( 'repo', 'issue_number', 'label' ) === ( $add_label_definition['parameters']['required'] ?? array() ));
$assert('remove_label_from_issue requires repo issue_number label', array( 'repo', 'issue_number', 'label' ) === ( $remove_label_definition['parameters']['required'] ?? array() ));
$assert('manage_github_issue warns update labels replace full set', str_contains($manage_issue_definition['parameters']['properties']['labels']['description'] ?? '', 'REPLACES the entire existing label set'));
$assert('manage_github_issue points to surgical label tools', str_contains($manage_issue_definition['description'] ?? '', 'add_label_to_issue') && str_contains($manage_issue_definition['description'] ?? '', 'remove_label_from_issue'));
$add_label_result = $github_tools->handleAddLabelToIssue(
array(
'repo' => 'Extra-Chill/data-machine-code',
'issue_number' => 328,
'label' => 'status:idea-ready',
)
);
$assert('add_label_to_issue returns standard success envelope', true === ( $add_label_result['success'] ?? false ) && 'add_label_to_issue' === ( $add_label_result['tool_name'] ?? '' ));
$assert('add_label_to_issue dispatches to addLabels with single label array', array( 'status:idea-ready' ) === ( \DataMachineCode\Abilities\GitHubAbilities::$addLabelsCalls[0]['labels'] ?? null ));
$assert('add_label_to_issue forwards issue_number', 328 === (int) ( \DataMachineCode\Abilities\GitHubAbilities::$addLabelsCalls[0]['issue_number'] ?? 0 ));
$remove_label_result = $github_tools->handleRemoveLabelFromIssue(
array(
'repo' => 'Extra-Chill/data-machine-code',
'issue_number' => 328,
'label' => 'status:idea-ready',
)
);
$assert('remove_label_from_issue returns standard success envelope', true === ( $remove_label_result['success'] ?? false ) && 'remove_label_from_issue' === ( $remove_label_result['tool_name'] ?? '' ));
$assert('remove_label_from_issue dispatches to removeLabel', 'status:idea-ready' === ( \DataMachineCode\Abilities\GitHubAbilities::$removeLabelCalls[0]['label'] ?? '' ));
$assert('remove_label_from_issue forwards issue_number', 328 === (int) ( \DataMachineCode\Abilities\GitHubAbilities::$removeLabelCalls[0]['issue_number'] ?? 0 ));
$missing_add_label_result = $github_tools->handleAddLabelToIssue(
array(
'repo' => 'Extra-Chill/data-machine-code',
'issue_number' => 328,
)
);
$assert('add_label_to_issue validation errors use error envelope', false === ( $missing_add_label_result['success'] ?? true ) && 'add_label_to_issue' === ( $missing_add_label_result['tool_name'] ?? '' ));
$missing_remove_label_result = $github_tools->handleRemoveLabelFromIssue(
array(
'repo' => 'Extra-Chill/data-machine-code',
'issue_number' => 328,
)
);
$assert('remove_label_from_issue validation errors use error envelope', false === ( $missing_remove_label_result['success'] ?? true ) && 'remove_label_from_issue' === ( $missing_remove_label_result['tool_name'] ?? '' ));
$tool_definitions = array(
'create_github_issue' => $issue_definition,
'create_github_pull_request' => $pr_definition,
);
foreach ( $github_tools->registered as $tool_name => $registration ) {
$callback = $registration['definition_callback'] ?? null;
if (is_callable($callback) ) {
$tool_definitions[ $tool_name ] = $callback();
}
}
foreach ( $tool_definitions as $tool_name => $definition ) {
foreach ( $definition['parameters'] ?? array() as $parameter => $parameter_schema ) {
if (is_array($parameter_schema) ) {
$assert_array_items($parameter_schema, "{$tool_name}.parameters.{$parameter}");
}
}
}
$plugin_source = file_get_contents(__DIR__ . '/../data-machine-code.php');
$assert('legacy system ability function is removed', ! str_contains($plugin_source, 'datamachine_code_register_system_abilities'));
$assert('legacy create-github-issue registration is removed from plugin bootstrap', ! str_contains($plugin_source, "wp_register_ability(\n\t\t'datamachine/create-github-issue'"));
$assert('plugin instantiates PR create tool', str_contains($plugin_source, 'new \\DataMachineCode\\Tools\\GitHubPullRequestTool();'));
if (! empty($failures) ) {
echo "\nFAIL: " . count($failures) . " assertion(s)\n";
foreach ( $failures as $failure ) {
echo " - {$failure}\n";
}
exit(1);
}
echo "\nOK\n";
exit(0);
}