Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions inc/Abilities/WorkspaceAbilities.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,71 @@ private function registerAbilities(): void {
)
);

wp_register_ability(
'datamachine/workspace-grep',
array(
'label' => 'Search Workspace Files',
'description' => 'Search text files within a workspace repository using a regular expression pattern.',
'category' => 'datamachine-code-workspace',
'input_schema' => array(
'type' => 'object',
'properties' => array(
'repo' => array(
'type' => 'string',
'description' => 'Workspace handle: `<repo>` (primary) or `<repo>@<branch-slug>` (worktree).',
),
'pattern' => array(
'type' => 'string',
'description' => 'Regular expression pattern to search for.',
),
'path' => array(
'type' => 'string',
'description' => 'Optional relative file or directory path to search within.',
),
'include' => array(
'type' => 'string',
'description' => 'Optional glob pattern to limit matching file paths.',
),
'max_results' => array(
'type' => 'integer',
'description' => 'Maximum number of matches to return (default 100, max 500).',
),
'context_lines' => array(
'type' => 'integer',
'description' => 'Number of surrounding lines to include for each match (default 0, max 10).',
),
),
'required' => array( 'repo', 'pattern' ),
),
'output_schema' => array(
'type' => 'object',
'properties' => array(
'success' => array( 'type' => 'boolean' ),
'repo' => array( 'type' => 'string' ),
'path' => array( 'type' => 'string' ),
'pattern' => array( 'type' => 'string' ),
'count' => array( 'type' => 'integer' ),
'truncated' => array( 'type' => 'boolean' ),
'matches' => array(
'type' => 'array',
'items' => array(
'type' => 'object',
'properties' => array(
'path' => array( 'type' => 'string' ),
'line' => array( 'type' => 'integer' ),
'text' => array( 'type' => 'string' ),
'context' => array( 'type' => 'array' ),
),
),
),
),
),
'execute_callback' => array( self::class, 'grepFiles' ),
'permission_callback' => fn() => PermissionHelper::can_manage(),
'meta' => array( 'show_in_rest' => true ),
)
);

// -----------------------------------------------------------------
// Mutating abilities (show_in_rest = false, CLI-only).
// -----------------------------------------------------------------
Expand Down Expand Up @@ -1884,6 +1949,37 @@ public static function listDirectory( array $input ): array|\WP_Error {
);
}

/**
* Search workspace files.
*
* @param array $input Input parameters with 'repo', 'pattern', optional 'path', 'include', 'max_results', 'context_lines'.
* @return array Result.
*/
public static function grepFiles( array $input ): array|\WP_Error {
if ( RemoteWorkspaceBackend::should_handle() ) {
return ( new RemoteWorkspaceBackend() )->grep(
$input['repo'] ?? '',
$input['pattern'] ?? '',
$input['path'] ?? null,
$input['include'] ?? null,
isset( $input['max_results'] ) ? (int) $input['max_results'] : 100,
isset( $input['context_lines'] ) ? (int) $input['context_lines'] : 0
);
}

$workspace = new Workspace();
$reader = new WorkspaceReader( $workspace );

return $reader->grep(
$input['repo'] ?? '',
$input['pattern'] ?? '',
$input['path'] ?? null,
$input['include'] ?? null,
isset( $input['max_results'] ) ? (int) $input['max_results'] : 100,
isset( $input['context_lines'] ) ? (int) $input['context_lines'] : 0
);
}

/**
* Clone a git repository into the workspace.
*
Expand Down
108 changes: 108 additions & 0 deletions inc/Cli/Commands/WorkspaceCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,114 @@ function ( $entry ) {
);
}

/**
* Search files in a workspace repo.
*
* ## OPTIONS
*
* <repo>
* : Repository directory name or worktree handle.
*
* <pattern>
* : Regular expression pattern to search for.
*
* [<path>]
* : Relative file or directory path to search within.
*
* [--include=<glob>]
* : Optional glob pattern to limit matching file paths.
*
* [--max-results=<count>]
* : Maximum number of matches to return.
* ---
* default: 100
* ---
*
* [--context-lines=<count>]
* : Number of surrounding lines to include for each match.
* ---
* default: 0
* ---
*
* [--format=<format>]
* : Output format.
* ---
* default: table
* options:
* - table
* - json
* - csv
* - yaml
* ---
*
* ## EXAMPLES
*
* wp datamachine-code workspace grep homeboy "function_name" --include="*.php"
*
* @subcommand grep
*/
public function grep( array $args, array $assoc_args ): void {
if ( empty( $args[0] ) || ! isset( $args[1] ) ) {
WP_CLI::error( 'Usage: wp datamachine-code workspace grep <repo> <pattern> [<path>]' );
return;
}

$ability = wp_get_ability( 'datamachine/workspace-grep' );
if ( ! $ability ) {
WP_CLI::error( 'Workspace grep ability not available.' );
return;
}

$input = array(
'repo' => $args[0],
'pattern' => $args[1],
);

if ( ! empty( $args[2] ) ) {
$input['path'] = $args[2];
}

if ( isset( $assoc_args['include'] ) ) {
$input['include'] = $assoc_args['include'];
}

if ( isset( $assoc_args['max-results'] ) ) {
$input['max_results'] = (int) $assoc_args['max-results'];
}

if ( isset( $assoc_args['context-lines'] ) ) {
$input['context_lines'] = (int) $assoc_args['context-lines'];
}

$result = $ability->execute( $input );
if ( is_wp_error( $result ) ) {
WP_CLI::error( $result->get_error_message() );
return;
}

$matches = (array) ( $result['matches'] ?? array() );
if ( empty( $matches ) ) {
WP_CLI::log( 'No matches.' );
return;
}

$items = array_map(
function ( $row ) {
return array(
'path' => $row['path'] ?? '',
'line' => $row['line'] ?? 0,
'text' => $row['text'] ?? '',
);
},
$matches
);

$this->format_items( $items, array( 'path', 'line', 'text' ), $assoc_args, 'path' );
if ( ! empty( $result['truncated'] ) ) {
WP_CLI::warning( 'Results truncated. Increase --max-results for more matches.' );
}
}

/**
* Write a file to a workspace repo.
*
Expand Down
90 changes: 90 additions & 0 deletions inc/Tools/WorkspaceTools.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public function check_configuration( $configured, $tool_id ) {
'workspace_show',
'workspace_ls',
'workspace_read',
'workspace_grep',
);

if ( ! in_array( $tool_id, $workspace_tools, true ) ) {
Expand All @@ -61,6 +62,7 @@ public function __construct() {
$this->registerTool( 'workspace_show', array( $this, 'getShowDefinition' ), $contexts, array( 'ability' => 'datamachine/workspace-show' ) );
$this->registerTool( 'workspace_ls', array( $this, 'getLsDefinition' ), $contexts, array( 'ability' => 'datamachine/workspace-ls' ) );
$this->registerTool( 'workspace_read', array( $this, 'getReadDefinition' ), $contexts, array( 'ability' => 'datamachine/workspace-read' ) );
$this->registerTool( 'workspace_grep', array( $this, 'getGrepDefinition' ), $contexts, array( 'ability' => 'datamachine/workspace-grep' ) );
}

/**
Expand Down Expand Up @@ -264,6 +266,49 @@ public function handleRead( array $parameters ): array {
);
}

/**
* Handle workspace_grep tool call.
*
* @param array $parameters Tool parameters.
* @return array
*/
public function handleGrep( array $parameters ): array {
$ability = wp_get_ability( 'datamachine/workspace-grep' );

if ( ! $ability ) {
return $this->buildErrorResponse( 'Workspace grep ability not available.', 'workspace_grep' );
}

$input = array(
'repo' => $parameters['repo'] ?? '',
'pattern' => $parameters['pattern'] ?? '',
);

foreach ( array( 'path', 'include' ) as $key ) {
if ( isset( $parameters[ $key ] ) ) {
$input[ $key ] = $parameters[ $key ];
}
}

foreach ( array( 'max_results', 'context_lines' ) as $key ) {
if ( isset( $parameters[ $key ] ) ) {
$input[ $key ] = (int) $parameters[ $key ];
}
}

$result = $ability->execute( $input );

if ( is_wp_error( $result ) ) {
return $this->buildErrorResponse( $result->get_error_message(), 'workspace_grep' );
}

return array(
'success' => true,
'data' => $result,
'tool_name' => 'workspace_grep',
);
}

/**
* Primary tool definition for convention compatibility.
*
Expand Down Expand Up @@ -411,4 +456,49 @@ public function getReadDefinition(): array {
),
);
}

/**
* Tool definition for workspace_grep.
*
* @return array
*/
public function getGrepDefinition(): array {
return array(
'class' => __CLASS__,
'method' => 'handleGrep',
'description' => 'Search text files in a workspace repository using a regular expression pattern.',
'parameters' => array(
'repo' => array(
'type' => 'string',
'required' => true,
'description' => 'Workspace repository directory name or worktree handle.',
),
'pattern' => array(
'type' => 'string',
'required' => true,
'description' => 'Regular expression pattern to search for.',
),
'path' => array(
'type' => 'string',
'required' => false,
'description' => 'Optional relative file or directory path inside the repository.',
),
'include' => array(
'type' => 'string',
'required' => false,
'description' => 'Optional glob pattern to limit matching file paths.',
),
'max_results' => array(
'type' => 'integer',
'required' => false,
'description' => 'Maximum number of matches to return (default 100, max 500).',
),
'context_lines' => array(
'type' => 'integer',
'required' => false,
'description' => 'Number of surrounding lines to include for each match (default 0, max 10).',
),
),
);
}
}
Loading