feat(task): add field expansion and aggregation to task list#91
Conversation
- --fields pulls extra fields (completed, assignee, due_on, ...) into the listing via opt_fields, removing the need for per-task `task get` calls - --incomplete-only / --completed-only exclusive completion filters - --assignee none filters unassigned tasks; assignee filtering now also works for project listings (resolves "me" client-side) - --count and --group-by assignee|completed return precomputed aggregates - deprecate -c/--completed (help text now matches its actual behavior of excluding completed tasks) Closes #88
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughWalkthroughThis PR adds field expansion, completion/assignee filtering, and count/group-by aggregation to the ChangesTask List Scan Features
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant CLI as task list command
participant Query as parseTaskListQuery
participant Fields as buildOptFields
participant API as fetchTaskPage
participant Filter as applyCompletionFilter/applyAssigneeFilter
participant Summary as summarizeTasks
CLI->>Query: parse CLI options
Query-->>CLI: TaskListQuery
CLI->>Fields: buildOptFields(query, clientAssignee)
Fields-->>CLI: opt_fields string
CLI->>API: fetchTaskPage(options, opt_fields)
API-->>CLI: raw tasks
CLI->>Filter: apply completion/assignee filters
Filter-->>CLI: filtered tasks
alt count or group-by requested
CLI->>Summary: summarizeTasks(tasks, groupBy)
Summary-->>CLI: total/groups summary
else
CLI->>CLI: toTaskRows and render columns
end
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces advanced scan-style options to the task list command, including filtering by completion state, assignee, field expansion, and aggregation (counting and grouping). It also updates the documentation and adds comprehensive tests. The code review feedback correctly identifies a high-severity issue in toTaskRows where nested field paths (such as assignee.name) specified in --fields will fail to resolve and return null due to simple bracket notation, and provides a solid suggestion to resolve nested properties dynamically.
Greptile SummaryThis PR adds scan-style query capabilities to
Confidence Score: 4/5The new scan-mode paths are correct and well-covered by tests; the All of the new The server-side filter gap for Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant CLI as CLI (task list)
participant PQ as parseTaskListQuery
participant BOF as buildOptFields
participant FTP as fetchTaskPage
participant API as Asana API
participant CF as applyCompletionFilter / applyAssigneeFilter
participant SUM as summarizeTasks / toTaskRows
CLI->>PQ: options (fields, completedOnly, incompleteOnly, count, groupBy, assignee)
PQ-->>CLI: TaskListQuery (validated/normalized) or throws ValidationError
CLI->>BOF: query + clientAssignee flag
BOF-->>CLI: opt_fields string (or undefined for legacy path)
CLI->>FTP: client, options, workspace, params, clientAssignee
FTP->>API: findByProject / findAll with opt_fields + completed_since
API-->>FTP: task data
FTP-->>CLI: task[]
alt "clientAssignee = true"
CLI->>API: "users.me() (only if assignee = me)"
API-->>CLI: current user GID
CLI->>CF: applyAssigneeFilter(tasks, gid or none)
CF-->>CLI: filtered tasks
end
CLI->>CF: applyCompletionFilter(tasks, query)
CF-->>CLI: filtered tasks
alt --count or --group-by
CLI->>SUM: summarizeTasks(tasks, groupBy?)
SUM-->>CLI: "{total, groups?}"
CLI->>CLI: "formatOutput({summary})"
else scan mode
CLI->>SUM: toTaskRows(tasks, columns)
SUM-->>CLI: flattened rows
CLI->>CLI: "formatOutput({tasks})"
else legacy mode
CLI->>CLI: "formatOutput({tasks}) raw"
end
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant CLI as CLI (task list)
participant PQ as parseTaskListQuery
participant BOF as buildOptFields
participant FTP as fetchTaskPage
participant API as Asana API
participant CF as applyCompletionFilter / applyAssigneeFilter
participant SUM as summarizeTasks / toTaskRows
CLI->>PQ: options (fields, completedOnly, incompleteOnly, count, groupBy, assignee)
PQ-->>CLI: TaskListQuery (validated/normalized) or throws ValidationError
CLI->>BOF: query + clientAssignee flag
BOF-->>CLI: opt_fields string (or undefined for legacy path)
CLI->>FTP: client, options, workspace, params, clientAssignee
FTP->>API: findByProject / findAll with opt_fields + completed_since
API-->>FTP: task data
FTP-->>CLI: task[]
alt "clientAssignee = true"
CLI->>API: "users.me() (only if assignee = me)"
API-->>CLI: current user GID
CLI->>CF: applyAssigneeFilter(tasks, gid or none)
CF-->>CLI: filtered tasks
end
CLI->>CF: applyCompletionFilter(tasks, query)
CF-->>CLI: filtered tasks
alt --count or --group-by
CLI->>SUM: summarizeTasks(tasks, groupBy?)
SUM-->>CLI: "{total, groups?}"
CLI->>CLI: "formatOutput({summary})"
else scan mode
CLI->>SUM: toTaskRows(tasks, columns)
SUM-->>CLI: flattened rows
CLI->>CLI: "formatOutput({tasks})"
else legacy mode
CLI->>CLI: "formatOutput({tasks}) raw"
end
Reviews (2): Last reviewed commit: "chore: apply AI code review suggestions" | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/lib/task-list-query.ts (1)
205-215: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueNested ternary flagged by static analysis.
Cosmetic only; extracting to an if/else would satisfy the linter without behavior change.
♻️ Suggested refactor
for (const task of tasks) { - const key = groupBy === 'completed' - ? (task.completed ? 'completed' : 'incomplete') - : (task.assignee?.name ?? task.assignee?.gid ?? 'unassigned') + let key: string + if (groupBy === 'completed') { + key = task.completed ? 'completed' : 'incomplete' + } + else { + key = task.assignee?.name ?? task.assignee?.gid ?? 'unassigned' + } counts.set(key, (counts.get(key) ?? 0) + 1) }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/task-list-query.ts` around lines 205 - 215, The task grouping logic in the task list query uses a conditional expression inline to choose the grouping key, which is what the linter is flagging. Refactor the key selection in the task iteration within task-list-query’s grouping flow into a simple if/else (or equivalent local variable assignment) before updating the counts Map, and keep the rest of the count aggregation, sorting, and groups mapping behavior unchanged.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/task-list-query.ts`:
- Around line 29-31: The `toTaskRows` mapper is treating dotted field names like
`assignee.name` as direct properties, so nested values are coming back null even
though `buildOptFields` allows them. Update `toTaskRows` to resolve column
values via dotted-path lookup for any field name it receives, or add explicit
handling for each supported nested field, and keep the existing
`FIELD_NAME_PATTERN`/`buildOptFields` behavior aligned with that lookup logic.
---
Nitpick comments:
In `@src/lib/task-list-query.ts`:
- Around line 205-215: The task grouping logic in the task list query uses a
conditional expression inline to choose the grouping key, which is what the
linter is flagging. Refactor the key selection in the task iteration within
task-list-query’s grouping flow into a simple if/else (or equivalent local
variable assignment) before updating the counts Map, and keep the rest of the
count aggregation, sorting, and groups mapping behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b4132551-8df8-48f9-bd35-0a0be2b61512
📒 Files selected for processing (9)
.gitignoredocs/content/en/2.features/1.task-management.mddocs/content/ko/2.features/1.task-management.mdsrc/commands/task.tssrc/constants/errorIds.tssrc/lib/task-list-query.tssrc/types/index.tstest/commands/task-list-scan.test.tstest/lib/task-list-query.test.ts
There was a problem hiding this comment.
1 issue found across 9 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/lib/task-list-query.ts">
<violation number="1" location="src/lib/task-list-query.ts:184">
P2: Nested `--fields` values (for example `assignee.name`) currently render as `null` in task rows, so field expansion is incomplete for dotted opt_fields. This happens because `toTaskRows` uses direct bracket lookup for non-`assignee` columns instead of resolving dot paths.</violation>
</file>
Architecture diagram
sequenceDiagram
participant CLI as CLI Runner
participant Cmd as task list Command
participant Parser as parseTaskListQuery
participant Client as Asana Client
participant API as Asana REST API
participant Users as users.me
participant Output as Output Formatter
Note over CLI,Output: Task List Request Flow with Scan Options
CLI->>Cmd: execute "task list" with options
Cmd->>Parser: parseTaskListQuery(options)
alt Conflicting completion filters
Parser-->>Cmd: CONFLICTING_OPTIONS error
else Invalid field name
Parser-->>Cmd: INVALID_FIELD_NAME error
else Invalid group-by
Parser-->>Cmd: INVALID_GROUP_BY error
else Valid query
Parser-->>Cmd: TaskListQuery { fields, assignee, ... }
end
Cmd->>Cmd: needsClientAssigneeFilter(query, hasProject)
alt Client-side assignee filter needed (project or "none")
Note over Cmd: assignee filter runs post-fetch
end
Cmd->>Cmd: buildOptFields(query, clientAssignee)
alt Scan options active
Note over Cmd: Sets opt_fields: [name, completed, assignee.name, assignee.gid, due_on, ...]
else Legacy compact mode
Note over Cmd: No opt_fields sent (undefined)
end
Cmd->>Cmd: Determine API parameters (completed_since for incomplete-only)
Cmd->>Client: fetchTaskPage(client, options, params, clientAssignee)
alt Project listing
Client->>API: tasks.findByProject(projectGid, params)
API-->>Client: Raw task array
else Workspace with concrete assignee
Client->>API: tasks.findAll({ assignee, workspace, ...params })
API-->>Client: Raw task array
else Workspace only
Client->>API: tasks.findAll({ workspace, ...params })
API-->>Client: Raw task array
end
Client-->>Cmd: taskList (raw tasks)
alt Client-side assignee filter needed
opt Resolve "me" to GID
Client->>Users: users.me()
Users-->>Client: { gid: "10", name: "Alice" }
end
Cmd->>Cmd: applyAssigneeFilter(tasks, assignee)
Note over Cmd: Filters tasks by assignee.gid or "none" (unassigned)
end
opt Completion filter set (and not satisfied server-side)
Cmd->>Cmd: applyCompletionFilter(tasks, query)
Note over Cmd: Filters tasks by completed: true/false
end
alt Count or group-by mode
Cmd->>Cmd: summarizeTasks(tasks, groupBy)
Note over Cmd: Returns { summary: { total, groups[] } }
Cmd->>Output: formatOutput({ summary }, format)
Output-->>CLI: JSON/YAML summary
else No tasks found
Cmd-->>CLI: Yellow "No tasks found" message
else Table mode with field expansion
Cmd->>Cmd: effectiveColumns(query, clientAssignee)
Cmd->>Cmd: toTaskRows(tasks, columns)
Note over Cmd: Flattens assignee to name, null for missing
Cmd->>Output: formatOutput({ tasks }, format)
Output-->>CLI: Formatted table/JSON
else Legacy compact mode
Cmd->>Output: formatOutput({ tasks: rawList }, format)
Output-->>CLI: Default listing (no opt_fields)
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
- resolve dotted --fields paths (e.g. assignee.name) in toTaskRows via a nested getter instead of a flat key lookup - correct the module docblock: validation helpers print to stderr before throwing, so they are not pure
|
/gemini review |
|
|
Warning Gemini encountered an error creating the review. You can try again by commenting |



Summary
Implements the interface-gap fixes from the agent-interface benchmark in #88:
task listnow supports field expansion, exclusive completion filters, unassigned filtering, and precomputed aggregates, collapsing scan-style questions from N×task getround-trips to a single call.--fields completed,assignee,due_on— pulls extra fields into the listing viaopt_fields(assignee flattened to a display name, missing values kept asnullfor aligned tabular output)--incomplete-only/--completed-only— exclusive completion filters (--incomplete-onlymaps to the server-sidecompleted_since=now; conflicts are rejected with a structuredCONFLICTING_OPTIONSerror)--assignee none— unassigned-only; assignee filtering now also works for project listings (meis resolved client-side viausers.me)--countand--group-by assignee|completed— return{ summary: { total, groups } }instead of rows (AXI principle 4), so the benchmark's aggregation task costs 1 round-trip-c/--completedis deprecated: its help text claimed it includes completed tasks while the implementation passedcompleted_since=now(excluding them). Wire behavior is preserved; the help text now tells the truth and points to--incomplete-only.The legacy compact listing (no scan option) is unchanged — no
opt_fieldsis sent and the raw rows are printed as before.Test plan
test/lib/task-list-query.test.ts— 25 unit tests over the pure query helpers (parsing/validation, opt_fields construction, filters, row flattening, aggregation)test/commands/task-list-scan.test.ts— 8 command-level tests with a mocked client verifying both the request parameters (opt_fields,completed_since) and shaped output, including the legacy-request regression guardCloses #88
Summary by cubic
Adds field expansion, exclusive completion filters, unassigned filtering, and aggregation to
task listso N×task getscans become a single request. Supports dotted fields (e.g.,assignee.name) and leaves the legacy compact listing unchanged.New Features
--fields completed,assignee,due_onexpandsopt_fields; supports dotted paths likeassignee.name; flattensassigneeto a display name; missing values arenull.--incomplete-onlyand--completed-onlyare exclusive; incomplete usescompleted_since=nowwhere possible.--assignee nonefilters unassigned;-a meis resolved client-side for project listings; assignee filters now work for projects.--countand--group-by assignee|completedreturn{ summary: { total, groups } }instead of rows.Migration
-c/--completedis deprecated and documented as excluding completed tasks; use--incomplete-onlyinstead.Written for commit 9b7ddf4. Summary will update on new commits.
Summary by CodeRabbit