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
129 changes: 129 additions & 0 deletions docs/maniphest-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -413,6 +413,132 @@ phabfive maniphest search "My Project" --priority="in:Normal+not:lowered"

**Note**: `not:been:PRIORITY` is functionally equivalent to `never:PRIORITY`.

## Status Filtering

Filter tasks based on their status changes over time. This helps identify tasks that progressed through workflows, track status regressions, and analyze how task completion status evolved.

### Why Use Status Filtering?

Common use cases include:

- **Track completions**: Find tasks that changed to "Resolved"
- **Identify regressions**: Tasks that moved backward from Resolved to Open
- **Find blocked work**: Tasks that are currently Blocked
- **Audit status history**: See complete status change history for tasks
- **Monitor workflow progression**: Find tasks that reached specific milestones

### Status Pattern Types

| Pattern | Description | Example |
|---------|-------------|---------|
| `from:STATUS` | Task changed from STATUS | `from:Open` |
| `from:STATUS:raised` | Task progressed from STATUS | `from:Open:raised` |
| `from:STATUS:lowered` | Task regressed from STATUS | `from:Resolved:lowered` |
| `to:STATUS` | Task changed to STATUS | `to:Resolved` |
| `in:STATUS` | Task is currently at STATUS | `in:Resolved` |
| `been:STATUS` | Task was at STATUS at any point | `been:Resolved` |
| `never:STATUS` | Task was never at STATUS | `never:Blocked` |
| `raised` | Task had any status progression | `raised` |
| `lowered` | Task had any status regression | `lowered` |
| `not:PATTERN` | Negates any pattern above | `not:in:Open`, `not:raised` |

**Negation Prefix `not:`**: Any pattern can be prefixed with `not:` to negate its meaning. This is a general negation operator that works with all pattern types. For example:
- `not:in:Open` - Tasks NOT currently Open
- `not:raised` - Tasks whose status hasn't progressed
- `not:been:Resolved` - Tasks never been Resolved (equivalent to `never:Resolved`)

### Status Values

The tool dynamically fetches status information from your Phabricator/Phorge instance using the `maniphest.querystatuses` API. Standard Phabricator statuses include (in progression order):

- **Open** (0) - Initial state for new tasks
- **Blocked** (1) - Task is blocked/waiting on something
- **Wontfix** (2) - Terminal: Won't be fixed
- **Invalid** (3) - Terminal: Invalid task
- **Duplicate** (4) - Terminal: Duplicate of another task
- **Resolved** (5) - Terminal: Task completed successfully

**Open vs Closed**: Only Open and Blocked are "open" statuses. All others (Wontfix, Invalid, Duplicate, Resolved) are terminal/closed states.

**Status Progression**:
- Moving from a lower number to a higher number is considered **"raised"** (forward progression)
- Moving from a higher number to a lower number is considered **"lowered"** (regression/reopening)
- For example: Open (0) → Resolved (5) is "raised" (task progressed forward)
- For example: Resolved (5) → Open (0) is "lowered" (task was reopened)

**Note**: If your Phabricator/Phorge instance uses custom statuses, the tool will automatically adapt to your configuration.

### Basic Status Examples

```bash
# Find tasks currently Open
phabfive maniphest search "My Project" --status="in:Open"

# Find tasks that were ever Resolved
phabfive maniphest search "My Project" --status="been:Resolved"

# Find tasks that progressed from Open
phabfive maniphest search "My Project" --status="from:Open:raised"

# Find tasks that had any status progression
phabfive maniphest search "My Project" --status=raised
```

### Combining Column, Priority, and Status Filters

You can combine all three filter types for powerful queries:

```bash
# Tasks moved to Done AND were raised from Open AND are currently Resolved
phabfive maniphest search '*' \
--column='to:Done' \
--priority='from:Normal:raised' \
--status='in:Resolved'

# Tasks in progress that have been blocked
phabfive maniphest search "My Project" \
--column="in:In Progress" \
--status="been:Blocked"

# Recently completed tasks that were never blocked
phabfive maniphest search "My Project" \
--status="to:Resolved" \
--updated-after=7 \
--status="never:Blocked"
```

### Status OR/AND Logic

Same as column and priority patterns, status patterns support OR (comma) and AND (plus):

```bash
# Tasks currently Open OR Blocked
phabfive maniphest search "My Project" --status="in:Open,in:Blocked"

# Tasks raised from Open AND currently Resolved
phabfive maniphest search "My Project" --status="from:Open:raised+in:Resolved"
```

### Status Negation Patterns

Use the `not:` prefix to negate status patterns:

```bash
# Tasks NOT currently Open
phabfive maniphest search "My Project" --status="not:in:Open"

# Tasks whose status has NOT progressed
phabfive maniphest search "My Project" --status="not:raised"

# Tasks NOT Resolved AND have been Blocked at some point
phabfive maniphest search "My Project" --status="not:in:Resolved+been:Blocked"

# Tasks that progressed but did NOT reach Resolved
phabfive maniphest search "My Project" --status="raised+not:in:Resolved"
```

**Note**: `not:been:STATUS` is functionally equivalent to `never:STATUS`.

## Viewing Metadata

Use `--show-metadata` to see why tasks matched your filters. This is especially useful when debugging complex filter combinations.
Expand All @@ -421,6 +547,7 @@ Use `--show-metadata` to see why tasks matched your filters. This is especially
phabfive maniphest search '*' \
--column='from:Up Next:forward' \
--priority='been:Normal' \
--status='in:Resolved' \
--show-metadata
```

Expand All @@ -429,11 +556,13 @@ Output includes:
Metadata:
MatchedBoards: ['Development', 'GUNNAR-Core']
MatchedPriority: true
MatchedStatus: true
```

The metadata section shows:
- **MatchedBoards**: Which boards satisfied the `--column` filter (in alphabetical order)
- **MatchedPriority**: Whether the task matched the `--priority` filter
- **MatchedStatus**: Whether the task matched the `--status` filter

This helps you understand exactly why a task appeared in your search results.

Expand Down
32 changes: 30 additions & 2 deletions phabfive/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,23 @@
from:Normal:raised
not:in:High+raised
in:High,been:Unbreak Now!
--show-history Display column and priority transition history for each task
--show-metadata Display filter match metadata (which boards/priority matched)
--status=PATTERNS Filter tasks by status transitions (comma=OR, plus=AND).
Automatically displays status history.
from:STATUS[:direction] - Changed from STATUS
to:STATUS - Changed to STATUS
in:STATUS - Currently at STATUS
been:STATUS - Was at STATUS at any point
never:STATUS - Never was at STATUS
raised - Status progressed forward
lowered - Status moved backward
not:PATTERN - Negates any pattern above
Examples:
been:Open
from:Open:raised
not:in:Resolved+raised
in:Open,been:Resolved
--show-history Display column, priority, and status transition history
--show-metadata Display filter match metadata (which boards/priority/status matched)

Options:
--all Show all fields for a ticket
Expand Down Expand Up @@ -251,6 +266,7 @@ def run(cli_args, sub_args):
from phabfive import passphrase, diffusion, paste, user, repl, maniphest
from phabfive.maniphest_transitions import parse_transition_patterns
from phabfive.priority_transitions import parse_priority_patterns
from phabfive.status_transitions import parse_status_patterns
from phabfive.constants import REPO_STATUS_CHOICES
from phabfive.exceptions import PhabfiveException

Expand Down Expand Up @@ -400,6 +416,17 @@ def run(cli_args, sub_args):
retcode = 1
return retcode

status_patterns = None
if sub_args.get("--status"):
try:
status_patterns = parse_status_patterns(
sub_args["--status"]
)
except Exception as e:
print(f"ERROR: Invalid status filter pattern: {e}", file=sys.stderr)
retcode = 1
return retcode

# Only show history if explicitly requested
show_history = sub_args.get("--show-history", False)

Expand All @@ -411,6 +438,7 @@ def run(cli_args, sub_args):
updated_after=sub_args["--updated-after"],
transition_patterns=transition_patterns,
priority_patterns=priority_patterns,
status_patterns=status_patterns,
show_history=show_history,
show_metadata=show_metadata,
)
Expand Down
Loading