Repository navigation
Conversation
Implements #85: Prevent incorrect counts for OmniFocus Forecast queries Changes: - Add forecast boolean filter to TaskFilter - Implement task-only Forecast collection in BridgeLibrary.js using: * Overdue tasks (dueDate before today) * Due today/soon (Task.Status.DueSoon, Task.Status.Overdue, or dueDate today) * Planned past/today (plannedDate <= today's end) * Deferred until today (deferDate <= today and available) * Flagged tasks (using effectiveFlagged) * Tasks tagged with 'Forecast' tag - Use local timezone day boundaries from request.userTimeZone - Deduplicate tasks appearing from multiple sources - Return explicit warnings about excluded sources: * Calendar events (outside FocusRelay task model) * Inaccessible Forecast preference options - Apply same Forecast logic to both list_tasks and get_task_counts - Add TaskFilter tests for forecast field serialization The implementation prefers the narrowest truthful task-only result with explicit exclusions, as documented APIs cannot reproduce native Forecast exactly. This prevents clients from treating due-date approximations as exact OmniFocus Forecast totals. Other filters (completed, flagged, tags, etc.) still apply as intersections with Forecast membership, so Forecast can be combined with additional filtering. Co-authored-by: Brent Deverman <brent@deverman.org>
Adds ForecastFilterTests.swift with: - Unit tests for Forecast filter serialization and combination - Manual UAT scenarios documenting all required validation steps - Test scenarios covering each Forecast criterion: * Overdue tasks * Due today/soon tasks * Planned past/today tasks * Deferred until today tasks * Flagged tasks * Forecast-tagged tasks - List/count parity verification - Local timezone boundary validation - Original UAT reproduction from issue #85 These tests provide the validation checklist for Mac-based testing since this environment cannot run OmniFocus. Each manual scenario documents exact steps and expected outcomes for UAT. Co-authored-by: Brent Deverman <brent@deverman.org>
|
Closing this PR at the owner's request after review and targeted probes of head Blocking findings:
Validation performed: reviewed the issue contract and production diff, inspected failed CI logs, and ran isolated JavaScript probes. Union deduplication/completed exclusion passed; the timezone and configured-tag probes failed. The local Swift test attempt stopped at a compiler/SDK failure before tests ran, separate from the PR's CI compilation failure. No candidate plug-in was installed and no live OmniFocus UAT or performance acceptance is claimed. A replacement should first define a clearly named partial task-only contract and date range, use documented native APIs (including the configured Forecast tag), explicitly resolve availability and filter intersections, and prove behavior with deterministic JavaScriptCore and direct MCP boundary coverage before live native Forecast comparison and the required query validation. Closing this PR does not close #85 or approve the attempted fix. |
Summary
Implements #85: Prevent incorrect counts for OmniFocus Forecast queries.
This PR adds a
forecastboolean filter that collects tasks from documented Omni Automation sources matching OmniFocus Forecast semantics, with explicit warnings about excluded sources that cannot be queried through documented APIs.Changes
Core Implementation
Added
forecastfilter toTaskFilter(OmniFocusCore.swift)Implemented Forecast collection in BridgeLibrary.js
dueDatebefore today's startTask.Status.DueSoon,Task.Status.Overdue, ordueDatewithin todayplannedDateon or before today's enddeferDateon or before today's end AND task is availableeffectiveFlagged(includes inherited flags)request.userTimeZonefor deterministic local-day boundariesApplied to both
list_tasksandget_task_countsExposed in MCP schema (
FocusRelayServer.swift)forecastproperty to shared task filter schemaAdded tests
TaskFilterTests.swift: Filter serialization and JSON round-tripForecastFilterTests.swift: Comprehensive integration test suite with:Problem Fixed
Before this change, asking "How many items are in Forecast?" could return a due-date-only count (e.g., 16 items) when OmniFocus showed a much larger Forecast view (e.g., 46 items). Clients treated a single page's
returnedCountas the total, and improvised Forecast queries from due-date filters alone, missing:This PR ensures:
list_tasksandget_task_countsagreeLimitations (Documented)
Per the roadmap preference for "narrowest truthful task-only result with explicit exclusions when documented APIs cannot reproduce native Forecast exactly":
Excluded (with warnings)
Approximations
request.userTimeZoneto calculate day boundaries; assumes OmniFocus and client share the same local-day interpretationTask.Status.DueSoon(documented as "within 24 hours")Usage
MCP Example
{ "name": "list_tasks", "arguments": { "filter": { "forecast": true, "completed": false }, "fields": ["id", "name", "dueDate", "plannedDate", "flagged"] } }CLI Example
focusrelay list-tasks --filter '{"forecast": true}' --fields id,name,dueDateWith Count
{ "name": "get_task_counts", "arguments": { "filter": { "forecast": true } } }Testing Required (Brent)
Since this environment cannot run OmniFocus, comprehensive manual testing is required. See
Tests/OmniFocusIntegrationTests/ForecastFilterTests.swiftfor 12 documented UAT scenarios covering:Critical Verification
Forecast count parity (Scenario:
listAndCountParity)get_task_countswith{"forecast": true}list_taskswith{"forecast": true, "includeTotalCount": true}totalCountmatches the count resultForecast membership sources (6 scenarios covering each criterion)
overdueTaskInForecast- Overdue task appearsdueTodayTaskInForecast- Due today task appearsplannedTodayTaskInForecast- Planned task appearsdeferredUntilTodayInForecast- Deferred until today appears when availableflaggedTaskInForecast- Flagged task appearsforecastTaggedTaskInForecast- Task with Forecast tag appearstaskWithMultipleCriteriaAppearsOnce- Deduplication worksLocal timezone boundaries (Scenario:
forecastRespectsLocalTimezone)Warnings (Scenario:
forecastWarningsPresent)list_tasksandget_task_countsresponsesFilter intersection (Scenario:
forecastIntersectsProjectFilter){"forecast": true, "project": "Work"}Original UAT from issue Prevent incorrect counts for OmniFocus Forecast queries #85 (Scenario:
originalUATScenario)get_task_countswithforecast: trueEach scenario in
ForecastFilterTests.swiftincludes detailed step-by-step instructions.Issue #125 Dependency
As noted in #85, issue #125 "Use Forecast signals to find what needs attention" is blocked on this contract and will reuse the Forecast membership classifier implemented here. The shared signals documented in #125 (overdue, dueToday, dueSoon, plannedPast, plannedToday, flagged, forecastTag, availableToday) map directly to this implementation and can be extracted into a reusable internal classifier when #125 begins.
Checklist
forecastfilter field toTaskFilterBridgeLibrary.jsusing documented APIslist_tasksandget_task_countsrequest.userTimeZoneForecastFilterTests.swiftFiles Changed
Sources/OmniFocusCore/OmniFocusCore.swift: Addedforecastfield toTaskFilterSources/FocusRelayServer/FocusRelayServer.swift: Addedforecastto MCP schema and property namesPlugin/FocusRelayBridge.omnijs/Resources/BridgeLibrary.js: Implemented Forecast collection logicTests/OmniFocusCoreTests/TaskFilterTests.swift: Added filter serialization testsTests/OmniFocusIntegrationTests/ForecastFilterTests.swift: Added comprehensive UAT scenariosReferences
docs/roadmap-execution-plan.mddocs/omni-automation-contract.md