Skip to content

feat: add search/find command for tasks#1662

Open
withsivram wants to merge 1 commit intoeyaltoledano:mainfrom
withsivram:feat/task-search-issue-1453
Open

feat: add search/find command for tasks#1662
withsivram wants to merge 1 commit intoeyaltoledano:mainfrom
withsivram:feat/task-search-issue-1453

Conversation

@withsivram
Copy link

@withsivram withsivram commented Mar 20, 2026

Summary

Adds task-master search <query> (aliased as find) that searches tasks across title, description, details, and testStrategy fields using case-insensitive substring matching.

CLI

  • New SearchCommand at apps/cli/src/commands/search.command.ts
  • Registered in CommandRegistry under the 'task' category
  • Supports --status, --tag, --include-subtasks, --format/--json/--compact, --silent, and --project flags
  • When --include-subtasks is given, also searches subtask fields and lists matching subtasks under their parent

MCP

  • New search_tasks tool at apps/mcp/src/tools/tasks/search-tasks.tool.ts
  • Registered in mcp-server/src/tools/tool-registry.js
  • Parameters: projectRoot, query, status, includeSubtasks, tag
  • Returns matches with id, title, status, priority, description, matchingSubtasks, and aggregate stats

Test plan

  • task-master search "auth" returns tasks with "auth" in title/description/details
  • task-master search "auth" --status done filters by status
  • task-master search "auth" --include-subtasks searches and displays subtask matches
  • task-master find "auth" works as alias
  • MCP search_tasks tool returns correct results via Claude/Cursor

Closes #1453

🤖 Generated with Claude Code

Summary by CodeRabbit

New Features

  • Introduced a new task search command enabling users to search and filter tasks by query, status, and tags
  • Search supports multiple output formats (text, JSON, compact) and optional subtask inclusion
  • Integrated search functionality across CLI and MCP tools for cross-platform task discovery

Adds `task-master search <query>` (aliased as `find`) that searches tasks
across title, description, details, and testStrategy fields using
case-insensitive substring matching.

CLI changes:
- New `SearchCommand` at apps/cli/src/commands/search.command.ts
- Registered in CommandRegistry under the 'task' category
- Exported from @tm/cli index
- Supports --status, --tag, --include-subtasks, --format/--json/--compact,
  --silent, and --project flags
- When --include-subtasks is given, also searches subtask title/description/
  details/testStrategy and lists matching subtasks under their parent

MCP changes:
- New `search_tasks` tool at apps/mcp/src/tools/tasks/search-tasks.tool.ts
- Registered in mcp-server/src/tools/tool-registry.js (toolRegistry + standardTools)
- Parameters: projectRoot, query, status, includeSubtasks, tag
- Returns matches with id, title, status, priority, description, and
  matchingSubtasks plus aggregate stats

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 20, 2026 16:33
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

This PR introduces a task search feature accessible via CLI and MCP tool. Users can search tasks by matching queries case-insensitively against title, description, details, and test-strategy fields, with optional filtering by status and tag. Results include matching subtasks when enabled.

Changes

Cohort / File(s) Summary
CLI Search Command
apps/cli/src/command-registry.ts, apps/cli/src/commands/search.command.ts, apps/cli/src/index.ts
Added new SearchCommand class implementing CLI task search with <query> argument, options for status/tag/includeSubtasks/format, and text/json/compact output modes. Registered command in CommandRegistry with category: 'task' and exported via index.
MCP Search Tool
apps/mcp/src/tools/tasks/search-tasks.tool.ts, apps/mcp/src/tools/tasks/index.ts
Added registerSearchTasksTool for MCP server exposing search_tasks tool with Zod-validated inputs (projectRoot, query, status, includeSubtasks, tag). Matches query against task fields and returns structured JSON results with match metadata and stats.
Tool Registry Integration
mcp-server/src/tools/tool-registry.js
Registered search_tasks tool in registry mapping and added to standardTools array.

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI/MCP as CLI or MCP Server
    participant TmCore as TmCore
    participant TaskData as Task Data

    User->>CLI/MCP: search command with query + filters
    activate CLI/MCP
    CLI/MCP->>CLI/MCP: validate status/format options
    CLI/MCP->>TmCore: tasks.list(tag, includeSubtasks: true)
    activate TmCore
    TmCore->>TaskData: fetch all tasks & subtasks
    TmCore-->>CLI/MCP: return task list
    deactivate TmCore
    CLI/MCP->>CLI/MCP: match query against title/description/details/testStrategy
    CLI/MCP->>CLI/MCP: filter by status (if provided)
    CLI/MCP->>CLI/MCP: filter subtasks & include parent if match found
    CLI/MCP->>CLI/MCP: format results (text/json/compact)
    CLI/MCP-->>User: return matching tasks with metadata
    deactivate CLI/MCP
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a search/find command for tasks across multiple files (CLI command, MCP tool, and registry updates).
Linked Issues check ✅ Passed The PR implements all core requirements from issue #1453: CLI and MCP search through title, description, details, test-strategy, and subtasks with case-insensitive substring matching and optional status/tag filtering.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the search feature: CLI command registration, command implementation, MCP tool registration, and tool implementation with no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new task search capability across both the CLI and MCP surfaces, enabling local substring-based search over task fields (and optionally subtasks) to address #1453.

Changes:

  • Introduces task-master search <query> with find alias, plus filtering/output flags.
  • Adds a new MCP tool search_tasks and registers it with the MCP server tool registry.
  • Wires new command/tool exports and registrations across CLI/MCP registries.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
mcp-server/src/tools/tool-registry.js Registers search_tasks in the MCP server tool registry and standard tools list.
apps/mcp/src/tools/tasks/search-tasks.tool.ts Implements search_tasks MCP tool with query + optional status/subtask/tag filtering.
apps/mcp/src/tools/tasks/index.ts Exports registerSearchTasksTool from the tasks tool barrel.
apps/cli/src/index.ts Exports the new SearchCommand from the CLI package entry.
apps/cli/src/commands/search.command.ts Implements the new search/find CLI command, including formatting and subtask display.
apps/cli/src/command-registry.ts Registers the search command under the task category.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +371 to +379
// Build a flat list of tasks (with subtasks collapsed under the parent) for the table
const tasksForTable = result.matches.map(({ task }) => task);

console.log(
ui.createTaskTable(tasksForTable, {
showSubtasks: options.includeSubtasks,
showDependencies: true
})
);
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In text output, passing showSubtasks: options.includeSubtasks to ui.createTaskTable(...) will render all subtasks for a matching parent task, not just the subtasks that matched the search query. This conflicts with the PR description (“lists matching subtasks under their parent”) and can produce noisy output. Consider either filtering task.subtasks down to matchingSubtasks before building tasksForTable, or keep the table unexpanded and rely solely on the dedicated “Matching subtasks” section.

Copilot uses AI. Check for mistakes.
Comment on lines +229 to +232
// Fetch all tasks (with subtasks so we can search them)
const listResult = await this.tmCore.tasks.list({
tag: options.tag,
includeSubtasks: true
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tasks.list({ includeSubtasks: true }) is called unconditionally, even when --include-subtasks is not set. Since tm-core supports excluding subtasks (includeSubtasks: false), this will do extra I/O and memory work for the common case where only top-level fields are searched. Consider passing includeSubtasks: options.includeSubtasks === true (or false when not needed) so subtasks are only loaded when they will actually be searched/displayed.

Suggested change
// Fetch all tasks (with subtasks so we can search them)
const listResult = await this.tmCore.tasks.list({
tag: options.tag,
includeSubtasks: true
// Fetch all tasks; include subtasks only when they will be searched
const listResult = await this.tmCore.tasks.list({
tag: options.tag,
includeSubtasks: options.includeSubtasks === true

Copilot uses AI. Check for mistakes.
Comment on lines +213 to +266
private async searchTasks(
query: string,
options: SearchCommandOptions
): Promise<SearchTasksResult> {
if (!this.tmCore) {
throw new Error('TmCore not initialized');
}

const queryLower = query.toLowerCase();

// Parse optional status filter
const statusFilter =
options.status
? options.status.split(',').map((s) => s.trim() as TaskStatus)
: undefined;

// Fetch all tasks (with subtasks so we can search them)
const listResult = await this.tmCore.tasks.list({
tag: options.tag,
includeSubtasks: true
});

const matches: TaskMatch[] = [];

for (const task of listResult.tasks) {
// Apply status filter if specified
if (statusFilter && !statusFilter.includes(task.status)) {
continue;
}

const taskMatches = taskMatchesQuery(task, queryLower);

// Check subtasks
const matchingSubtasks: Subtask[] = options.includeSubtasks
? (task.subtasks || []).filter((st) =>
subtaskMatchesQuery(st, queryLower)
)
: [];

// Include the task if the task itself matches, or if any subtask matched
// (when --include-subtasks is set)
if (taskMatches || matchingSubtasks.length > 0) {
matches.push({ task, matchingSubtasks });
}
}

return {
query,
matches,
totalTasks: listResult.total,
tag: listResult.tag,
storageType: listResult.storageType as Exclude<StorageType, 'auto'>
};
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are existing Vitest specs for other CLI commands (e.g. list.command.spec.ts, loop.command.spec.ts), but this new command doesn’t introduce any tests for query matching, status filtering, format flag precedence (--format vs --json/--compact), or --include-subtasks behavior. Adding a search.command.spec.ts would help prevent regressions in the matching logic and output selection.

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +122
// Fetch all tasks (with subtasks for subtask search)
const listResult = await tmCore.tasks.list({
tag,
includeSubtasks: true
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tmCore.tasks.list({ includeSubtasks: true }) is executed even when includeSubtasks is not requested. This forces loading subtasks for every search, which can be avoidable overhead on large task sets. Consider setting includeSubtasks: includeSubtasks === true (or false when not needed) so the storage layer can exclude subtasks when you’re only searching top-level task fields.

Suggested change
// Fetch all tasks (with subtasks for subtask search)
const listResult = await tmCore.tasks.list({
tag,
includeSubtasks: true
// Fetch all tasks (optionally with subtasks for subtask search)
const listResult = await tmCore.tasks.list({
tag,
includeSubtasks: includeSubtasks === true

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
apps/mcp/src/tools/tasks/search-tasks.tool.ts (2)

119-123: Minor: Subtasks are always fetched even when not searched.

The includeSubtasks: true is always passed to tmCore.tasks.list() regardless of whether args.includeSubtasks is set. This means subtasks are loaded even when they won't be searched. While this keeps the code simple and the overhead is minimal for most projects, you could conditionally set this if performance becomes a concern with large task sets.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mcp/src/tools/tasks/search-tasks.tool.ts` around lines 119 - 123, The
call to tmCore.tasks.list currently always passes includeSubtasks: true, causing
subtasks to be fetched even when not requested; update the code around the
tmCore.tasks.list invocation to conditionally set includeSubtasks based on the
incoming args (e.g., args.includeSubtasks) — either build the options object and
only include includeSubtasks when args.includeSubtasks is truthy or set
includeSubtasks: !!args.includeSubtasks — so subtasks are fetched only when the
user asked for them.

49-70: Consider extracting shared search helpers to @tm/core.

The matchesQuery, taskMatchesQuery, and subtaskMatchesQuery functions are duplicated verbatim between this file and apps/cli/src/commands/search.command.ts. While both CLI and MCP are presentation layers, these pure matching functions could be extracted to @tm/core to avoid duplication and ensure consistent behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/mcp/src/tools/tasks/search-tasks.tool.ts` around lines 49 - 70, The
matching helpers (matchesQuery, taskMatchesQuery, subtaskMatchesQuery) are
duplicated in this file and apps/cli/src/commands/search.command.ts; extract
these pure functions into a shared module in `@tm/core` (e.g., export
matchesQuery, taskMatchesQuery, subtaskMatchesQuery from a new search utilities
file) then replace the local definitions with imports from `@tm/core` so both CLI
and MCP use the same implementations and avoid duplication.
apps/cli/src/commands/search.command.ts (1)

371-379: Consider clarifying subtask display behavior in text format.

The table via createTaskTable displays all subtasks of matching tasks (when showSubtasks: true), not just the subtasks that matched the query. The "Matching subtasks" section below (lines 382-398) lists only the matching ones. This dual display could be intentional for context, but users might expect the table to show only matching subtasks.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cli/src/commands/search.command.ts` around lines 371 - 379, The table
currently builds tasksForTable from result.matches.map(({ task }) => task) and
then calls ui.createTaskTable(..., { showSubtasks: options.includeSubtasks }),
which displays all subtasks when showSubtasks is true; change tasksForTable
construction so that for each match you include the parent task but, if
options.includeSubtasks is true, replace its subtasks array with only the
subtasks that actually matched (use the matchedSubtasks or equivalent field on
each match) so the table mirrors the "Matching subtasks" list; keep the original
full-subtask behavior when includeSubtasks is false or when no matchedSubtasks
data exists.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/cli/src/commands/search.command.ts`:
- Around line 85-95: The docstring for subtaskMatchesQuery is misleading because
the function checks title, description, details, and testStrategy via
matchesQuery; update the comment above function subtaskMatchesQuery to list all
four fields (title, description, details, testStrategy) that are searched (or
reword to "matches query against subtask fields: title, description, details,
testStrategy") so the documentation matches the implementation.

---

Nitpick comments:
In `@apps/cli/src/commands/search.command.ts`:
- Around line 371-379: The table currently builds tasksForTable from
result.matches.map(({ task }) => task) and then calls ui.createTaskTable(..., {
showSubtasks: options.includeSubtasks }), which displays all subtasks when
showSubtasks is true; change tasksForTable construction so that for each match
you include the parent task but, if options.includeSubtasks is true, replace its
subtasks array with only the subtasks that actually matched (use the
matchedSubtasks or equivalent field on each match) so the table mirrors the
"Matching subtasks" list; keep the original full-subtask behavior when
includeSubtasks is false or when no matchedSubtasks data exists.

In `@apps/mcp/src/tools/tasks/search-tasks.tool.ts`:
- Around line 119-123: The call to tmCore.tasks.list currently always passes
includeSubtasks: true, causing subtasks to be fetched even when not requested;
update the code around the tmCore.tasks.list invocation to conditionally set
includeSubtasks based on the incoming args (e.g., args.includeSubtasks) — either
build the options object and only include includeSubtasks when
args.includeSubtasks is truthy or set includeSubtasks: !!args.includeSubtasks —
so subtasks are fetched only when the user asked for them.
- Around line 49-70: The matching helpers (matchesQuery, taskMatchesQuery,
subtaskMatchesQuery) are duplicated in this file and
apps/cli/src/commands/search.command.ts; extract these pure functions into a
shared module in `@tm/core` (e.g., export matchesQuery, taskMatchesQuery,
subtaskMatchesQuery from a new search utilities file) then replace the local
definitions with imports from `@tm/core` so both CLI and MCP use the same
implementations and avoid duplication.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bffd3fde-4467-436d-aafd-96ee061a229e

📥 Commits

Reviewing files that changed from the base of the PR and between 2d1211b and fc1ef5f.

📒 Files selected for processing (6)
  • apps/cli/src/command-registry.ts
  • apps/cli/src/commands/search.command.ts
  • apps/cli/src/index.ts
  • apps/mcp/src/tools/tasks/index.ts
  • apps/mcp/src/tools/tasks/search-tasks.tool.ts
  • mcp-server/src/tools/tool-registry.js

Comment on lines +85 to +95
/**
* Return true if a subtask matches the query on title or description.
*/
function subtaskMatchesQuery(subtask: Subtask, queryLower: string): boolean {
return (
matchesQuery(subtask.title, queryLower) ||
matchesQuery(subtask.description, queryLower) ||
matchesQuery(subtask.details, queryLower) ||
matchesQuery(subtask.testStrategy, queryLower)
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix misleading docstring for subtaskMatchesQuery.

The docstring states the function matches "title or description" but the implementation actually checks four fields: title, description, details, and testStrategy.

📝 Proposed fix
 /**
- * Return true if a subtask matches the query on title or description.
+ * Return true if a subtask matches the query on any of the searched fields.
+ * Searched fields: title, description, details, testStrategy
  */
 function subtaskMatchesQuery(subtask: Subtask, queryLower: string): boolean {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/cli/src/commands/search.command.ts` around lines 85 - 95, The docstring
for subtaskMatchesQuery is misleading because the function checks title,
description, details, and testStrategy via matchesQuery; update the comment
above function subtaskMatchesQuery to list all four fields (title, description,
details, testStrategy) that are searched (or reword to "matches query against
subtask fields: title, description, details, testStrategy") so the documentation
matches the implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: search/find in tasks - title, description, details, test-strategy, sub-tasks.

2 participants