Skip to content

feat: the task tools can list overdue tasks, with the days overdue EXO-91122 - #669

Merged
Jihed525 merged 7 commits into
developfrom
backport/EXO-91122-overdue-task-filter
Oct 12, 2026
Merged

Jihed525 merged 7 commits into
developfrom
backport/EXO-91122-overdue-task-filter

Conversation

@Jihed525

@Jihed525 Jihed525 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Why: an AI agent asked "what's late?" had to fetch every task and compare due dates itself, and missed overdue tasks when it didn't know today's date (EXO-91122). The task tools now answer it directly. The TASK specialist of Meeds-io/ai#351 (EXO-91055) calls list_tasks with overdue_only, so this PR merges before it.

How:

  • TaskQuery#setOverdueAt(startOfToday): open tasks whose dueDate (the date the Tasks UI shows, never endDate) is strictly before the start of the user's day. A task due today, a completed task and a task with no due date are not overdue.
  • list_tasks and list_assigned_tasks take an overdue_only boolean (ai-tool-definitions.json). annotations and require_approval are unchanged. The cut-off is today in the time zone of the user's profile (McpToolUtils.getUserTimeZone), the server's when the profile has none.
  • Each open task past its due date carries days_overdue (TaskModel), in whole days in that time zone.
  • TaskMcpTool#getTaskQuery builds the query of list_tasks for both the list and its count:
    • a project's tasks behind project.canView, or every task the user can access (setAccessible), personal tasks included, as before;
    • the count now follows the same filters, hide_completed_tasks and overdue_only included, where it used to count every task.
  • The overdue list_assigned_tasks selects the user's tasks as assignee or coworker, the same scope as Task.getUncompletedTasks.
  • Both overdue listings put the most overdue tasks first (due date ascending, then identifier, through TaskService#findTasks), so a truncated page keeps the worst ones. Their descriptions say how to get the rest: list_tasks pages with offset while count is above the tasks received; list_assigned_tasks, which has no offset, takes a larger limit.

Tests:

  • TestTaskDAO#testFindOverdueTasksBeforeTheStartOfTheUsersDay runs the criterion through JPA on HSQLDB.
  • TestTaskDAO#testFindOverdueTasksMostOverdueFirstAcrossPages runs the order through JPA over two pages, with tasks created against their due dates so that only the due date order passes.
  • TaskMcpToolTest pins the queries, the counts, days_overdue around midnight across time zones, and the profile time zone.
  • Mutants on the committed tree, each killed: lt → lte, the open-task condition removed, a task due today counted, the server's zone always used, the count of every task; the overdue order inverted or removed in the tool; no ORDER BY, and ascending turned descending, in CommonJPADAO.
  • daysOverdueAcrossTimeZoneTransitionsMatchADayByDayCount and theOverdueCutOffIsTheStartOfTheDayWhenTheZoneSkipsMidnight pin days_overdue and the cut-off across daylight-saving changes and a skipped midnight, against a day-by-day count.
  • mvn -B verify -Pcoverage at ebac71b79 on develop 37688705f (full reactor, no install): BUILD SUCCESS, 238 tests, 0 failures, coverage checks met.

Known limit: a due date given without a time through the MCP task tools was observed stored at midnight in the server's time zone (EXO-91055 testing; not re-verified in this PR, and not checked for the Tasks UI date picker), while the cut-off here is the start of the user's day. For a user west of the server's zone, such a task is counted overdue on its due day itself, and its days_overdue is one more; east of it the day is right. That storage behaviour predates this PR.

Knowledge: Meeds-io/eng-standards#382 (draft, stacked on #362, until this change reaches develop).

This change is classified N1, computed at ebac71b79. The permission checks of list_tasks (canView, setAccessible) move into one getTaskQuery shared by the list and the count, and list_assigned_tasks gains its own user-scoped query. Its approver must be an Archi/Dev who knows it is N1, not an approval on AI review alone; author ≠ approver.

Backport of #667 from feature/ai-contribution, picked as it is, plus one declared commit (ebac71b79: the test imports the captor it uses, which develop's copy of TaskMcpToolTest lacked). The reviewer's evidence (source, picks, patch-ids, build) is a PR comment.

🤖 Generated with Claude Code

@Jihed525
Jihed525 requested a review from ahamdi October 12, 2026 09:32
@Jihed525

Jihed525 commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Source

Picks on this branch (cherry-pick -x; each pick's git show -U0 | git patch-id --stable equals its source's: 6/6)
42e194c69 8af81b489 2b9aa5acb 81ed6b810 334fa7ce0 efa067732, picked from the feature/ai-contribution commits f2cd923e8 ea5e344c4 c2915f79a 67a2a3919 0cd75225c d71567872 (the six commits task#667 merged as 51aaba5d8).

Declared commit: ebac71b79 — TaskMcpToolTest imports org.mockito.ArgumentCaptor, which the feature branch's copy of the test got from EXO-90661 and develop's copy lacks; test code only, one line.

Adaptations: no conflict hunk, no POM change; one declared commit, below.

Verification (head ebac71b79 on develop 37688705f, mvn -B verify -Pcoverage, full reactor, no install: BUILD SUCCESS — 238 tests, 0 failures, coverage checks met)

Landing: one task, squash.

Level: N1, as the source PR's: the permission checks of list_tasks move into one query shared by the list and its count, as the source PR states. The approver must be an Architect or Developer who knows it is N1 and is not the author.

@ahamdi ahamdi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI review — Round #1

🟡 Medium — the PR body's test run and classification are stated at d71567872, which this PR does not contain

mvn -pl services -am test -Dtest='TestTaskDAO,TaskMcpToolTest' at d71567872: 25 + 66 tests, 0 failures.
This change is classified N1, computed at d71567872.

d71567872 is the last feature/ai-contribution commit of task#667; this branch carries its pick efa067732, which did not compile on develop until the declared commit ebac71b79. At this head TaskMcpToolTest has 65 @Test methods, not 66 (createPersonalTaskShouldAssignTheCallerWhenNoAssigneeIsGiven exists only on the FB), and TaskMcpTool / ai-tool-definitions.json also differ from d71567872. The body is the squash commit's message on develop, so it would land a run on a SHA outside develop's history with a count that is wrong for the code it describes (pr-conventions.md §5). The evidence comment already has a run at the head (mvn -B verify -Pcoverage at ebac71b79, 238 tests).

Fix: state the run at ebac71b79 in the body — the targeted command's result line at this head (25 + 65), or the evidence comment's full-reactor line — and write the classification as computed at ebac71b79.


🟢 Nit — the evidence comment's Picks line lists 42e194c69 … efa067732 as the "FB commits"; those are this branch's picks. Their sources (the -x trailers) are f2cd923e8 ea5e344c4 c2915f79a 67a2a3919 0cd75225c d71567872, all on feature/ai-contribution. The patch-ids do match one to one (6/6, recomputed), so only the label is off.


Verified conform

  • Pick equivalence: each of the six picks has the same git patch-id --stable as its feature/ai-contribution source; the only other commit is the declared one-line import ebac71b79 (test code). There are no conflict hunks.
  • ACL on list_tasks: getTaskQuery keeps projectService.getProject → EntityNotFoundException→ObjectNotFoundException and project.canView(aclIdentity)→IllegalAccessException for a project, and setAccessible(aclIdentity) without one. The list and the count now share that one query, so the count cannot select tasks the list does not. countTaskModels and getTasks go through the same path.
  • list_assigned_tasks overdue scope: setAssigneeOrCoworker builds assignee IN (…) OR coworker IN (…). TaskDAOImpl#buildPath joins coworker with JoinType.LEFT, so a task with no coworker still matches on its assignee. findEntities selects distinct and buildCountQuery uses countDistinct. This is the scope of the Task.getUncompletedTasks named query.
  • Due date semantics: setOverdueAt filters on dueDate (lt, strict) plus completed = false, never endDate. This is the date the UI binds, so the enddate-column-is-not-the-ui-due-date pitfall does not apply. The cut-off is the start of the user's day in their profile zone (McpToolUtils.getUserTimeZone), with the server's zone as fallback.
  • Ordering: CommonJPADAO#findEntities applies the dueDate ASC, id ASC OrderBy list. testFindOverdueTasksMostOverdueFirstAcrossPages runs it through JPA/HSQLDB over two pages, with identifiers that run against the due dates, so only the due date order passes.
  • Tool binding: McpToolCallbackProviderService binds the tool arguments by name (snake → camel), so adding overdueOnly before offset in listTasks does not shift any argument. No caller in ai or processes uses the changed Java signatures. annotations and require_approval are unchanged.
  • Knowledge:: Meeds-io/eng-standards#382 is open and a draft, stacked on #362 (ku/pitfalls-eva-orchestration). It carries a knowledge/pitfalls/task/ file and its change fragment for this delivery.
  • There is no schema, i18n or REST surface in the diff.

The PR is good work. The new query criterion is pinned through the real engine (lt vs lte, a task due today, a completed task, no due date, another user's task). The DST and skipped-midnight cases are checked against a day-by-day count. The count of list_tasks now follows the filters it lists. The tool descriptions tell the agent how to reach the rest of a truncated, most-overdue-first page. The known limit on time-less due dates is written down instead of being left hidden.

Classification: N1. The permission checks of list_tasks (canView, setAccessible) move into the shared getTaskQuery, and the input schemas of two existing MCP tools change. An Architect or Developer who knows it is N1 must validate this PR, and it must not be merged on the AI review alone; author ≠ approver.

🤖 Generated with Claude Code

@Jihed525

Copy link
Copy Markdown
Contributor Author

Round 1, both taken; no commit, the head stays ebac71b79.

  • 🟡 The body states the run at ebac71b79: the full-reactor line of the evidence comment (mvn -B verify -Pcoverage on develop 37688705f, 238 tests, 0 failures), and the classification as computed at ebac71b79. No SHA outside develop's history remains in it.
  • 🟢 The evidence comment's Picks line now reads the six picks as this branch's commits, picked from their feature/ai-contribution sources f2cd923e8 to d71567872.

@Jihed525
Jihed525 requested a review from ahamdi October 12, 2026 14:35

@ahamdi ahamdi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

AI review — Round #2 (final)

Finding (Round #1) Status
🟡 The PR body's test run and classification were stated at d71567872 ✅ Fixed: the live body now gives mvn -B verify -Pcoverage at ebac71b79 (238 tests, 0 failures) and the classification as computed at ebac71b79. No SHA outside develop's history remains.
🟢 The evidence comment labelled the picks as "FB commits" ✅ Fixed: the Picks line now names this branch's six picks and their feature/ai-contribution sources f2cd923e8 … d71567872.

No commit since Round #1: the head is still ebac71b79, so the code and the conform points verified in Round #1 still hold.

All findings from previous rounds are resolved. Nothing is outstanding from the AI review side.

Classification: N1. The permission checks of list_tasks (canView, setAccessible) move into the shared getTaskQuery, and the input schemas of two existing MCP tools change. An Architect or Developer who knows it is N1 must validate this PR, and it must not be merged on the AI review alone; author ≠ approver.

🤖 Generated with Claude Code

bmestrallet and others added 7 commits October 12, 2026 15:42
…O-91122

Asked what was late, a small model compared due dates with today across
many tasks and missed a task ten days overdue; that task was a personal
one, which no per-project list shows.

list_tasks and list_assigned_tasks take overdue_only: the open tasks whose
due date (the one the Tasks UI shows, never the end date) is before the
start of today in the user's time zone; a task due today is not overdue.
list_tasks without a project already spans every task the user can access,
personal tasks included, so no separate list_overdue_tasks tool: one
more tool would only duplicate that query in the catalogue. Every open
task past its due date now carries days_overdue, counted in whole days in
the user's time zone. The condition lives on TaskQuery (setOverdueAt) and
is tested through the engine.

list_tasks counted every task of its scope whatever its filters; its count
is now the count of the query it lists.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit f2cd923)


A test asserts the user scope of the moved queries on both tools, and
the engine test leaves another user's overdue task out. The count of
the project activity tool reuses the one query builder.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit ea5e344)
…ver's only without one EXO-91122

The other tests stub getCurrentUserZone, so nothing ran the real lookup:
a change that always took the server's zone passed them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit c2915f7)
… how to page EXO-91122

The overdue_only listings were cut to one page ordered by last activity.
They now order by due date ascending (ties on the identifier); the change
log and unscheduled listings reuse the user's time zone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 67a2a39)
…s, so only the due date order passes EXO-91122

The tasks were created from the most overdue to the least, so the
identifier order gave the same pages as the due date order: a listing
ordered by identifier, or by no order at all, passed. They are now
created from the least overdue to the most, the tie first, and the
Javadoc counts sixteen tasks and a second page of six.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 0cd7522)
…EXO-91122

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit d715678)
…op's copy of the test lacked EXO-91122

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Jihed525
Jihed525 force-pushed the backport/EXO-91122-overdue-task-filter branch from ebac71b to 12f771d Compare October 12, 2026 14:42
@Jihed525
Jihed525 enabled auto-merge (squash) October 12, 2026 14:42
@Jihed525
Jihed525 merged commit 712f75b into develop Oct 12, 2026
3 checks passed
@Jihed525
Jihed525 deleted the backport/EXO-91122-overdue-task-filter branch October 12, 2026 14:44
@sonarqubecloud

Copy link
Copy Markdown

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.

3 participants