**Highest-Impact Revisions (new, not in your rejected list)** 1. **Critical: Preserve GraphQL partial-error metadata end-to-end (don’t just log it)** Rationale: Right now partial GraphQL errors are warning-only. Agents get no machine-readable signal that status data may be incomplete, which can silently corrupt downstream automation decisions. Exposing partial-error metadata in `FetchStatusResult` and robot sync output makes reliability observable and actionable. ```diff @@ AC-1: GraphQL Client (Unit) - [ ] Partial-data response: if `errors` array is non-empty BUT `data` field is present and non-null, returns `data` and logs warning with first error message + [ ] Partial-data response: if `errors` array is non-empty BUT `data` field is present and non-null, returns `data` and warning metadata (`had_errors=true`, `first_error_message`) + [ ] `GraphqlClient::query()` returns `GraphqlQueryResult { data, had_errors, first_error_message }` @@ AC-3: Status Fetcher (Integration) + [ ] `FetchStatusResult` includes `partial_error_count: usize` and `first_partial_error: Option` + [ ] Partial GraphQL errors increment `partial_error_count` and are surfaced to orchestrator result @@ AC-10: Robot Sync Envelope (E2E) - { "mode": "...", "reason": ..., "enriched": N, "cleared": N, "error": ... } + { "mode": "...", "reason": ..., "enriched": N, "cleared": N, "error": ..., "partial_errors": N, "first_partial_error": null|"..." } @@ File 1: src/gitlab/graphql.rs - pub async fn query(...) -> Result + pub async fn query(...) -> Result + pub struct GraphqlQueryResult { pub data: serde_json::Value, pub had_errors: bool, pub first_error_message: Option } ``` 2. **High: Add adaptive page-size fallback for GraphQL complexity/timeout failures** Rationale: Fixed `first: 100` is brittle on self-hosted instances with stricter complexity/time limits. Adaptive page size (100→50→25→10) improves success rate without retries/backoff and avoids failing an entire project due to one tunable server constraint. ```diff @@ Query Path -query($projectPath: ID!, $after: String) { ... workItems(types: [ISSUE], first: 100, after: $after) ... } +query($projectPath: ID!, $after: String, $first: Int!) { ... workItems(types: [ISSUE], first: $first, after: $after) ... } @@ AC-3: Status Fetcher (Integration) + [ ] Starts with `first=100`; on GraphQL complexity/timeout errors, retries same cursor with smaller page size (50, 25, 10) + [ ] If smallest page size still fails, returns error as today + [ ] Emits warning including page size downgrade event @@ TDD Plan (RED) + 36. `test_fetch_statuses_complexity_error_reduces_page_size` + 37. `test_fetch_statuses_timeout_error_reduces_page_size` ``` 3. **High: Make project path lookup failure non-fatal for the sync** Rationale: Enrichment is optional. If `projects.path_with_namespace` lookup fails for any reason, sync should continue with a structured enrichment error instead of risking full project pipeline failure. ```diff @@ AC-6: Enrichment in Orchestrator (Integration) + [ ] If project path lookup fails/missing, status enrichment is skipped for that project, warning logged, and sync continues + [ ] `status_enrichment_error` captures `"project_path_missing"` (or DB error text) @@ File 6: src/ingestion/orchestrator.rs - let project_path: String = conn.query_row(...)?; + let project_path = conn.query_row(...).optional()?; + if project_path.is_none() { + result.status_enrichment_error = Some("project_path_missing".to_string()); + result.status_enrichment_mode = "fetched".to_string(); // attempted but unavailable locally + emit(ProgressEvent::StatusEnrichmentComplete { enriched: 0, cleared: 0 }); + // continue to discussion sync + } ``` 4. **Medium: Upgrade `--status` from single-value to repeatable multi-value filter** Rationale: Practical usage often needs “active buckets” (`To do` OR `In progress`). Repeatable `--status` with OR semantics dramatically improves usefulness without adding new conceptual surface area. ```diff @@ AC-9: List Issues Filter (E2E) - [ ] `lore list issues --status "In progress"` → only issues where `status_name = 'In progress'` + [ ] `lore list issues --status "In progress"` → unchanged single-value behavior + [ ] Repeatable flags supported: `--status "In progress" --status "To do"` (OR semantics across status values) + [ ] Repeated `--status` remains AND-composed with other filters @@ File 9: src/cli/mod.rs - pub status: Option, + pub status: Vec, // repeatable flag @@ File 8: src/cli/commands/list.rs - if let Some(status) = filters.status { where_clauses.push("i.status_name = ? COLLATE NOCASE"); ... } + if !filters.statuses.is_empty() { /* dynamic OR/IN clause with case-insensitive matching */ } ``` 5. **Medium: Add coverage telemetry (`seen`, `with_status`, `without_status`)** Rationale: `enriched`/`cleared` alone is not enough to judge enrichment health. Coverage counters make it obvious whether a project truly has no statuses, is unsupported, or has unexpectedly low status population. ```diff @@ AC-6: Enrichment in Orchestrator (Integration) + [ ] `IngestProjectResult` gains `statuses_seen: usize` and `statuses_without_widget: usize` + [ ] Enrichment log includes `seen`, `enriched`, `cleared`, `without_widget` @@ AC-10: Robot Sync Envelope (E2E) - status_enrichment: { mode, reason, enriched, cleared, error } + status_enrichment: { mode, reason, seen, enriched, cleared, without_widget, error, partial_errors } @@ File 6: src/ingestion/orchestrator.rs + result.statuses_seen = fetch_result.all_fetched_iids.len(); + result.statuses_without_widget = result.statuses_seen.saturating_sub(result.statuses_enriched); ``` 6. **Medium: Centralize color parsing/render decisions (single helper used by show/list)** Rationale: Color parsing is duplicated in `show.rs` and `list.rs`, which invites drift and inconsistent behavior. One shared helper gives consistent fallback behavior and simpler tests. ```diff @@ File 7: src/cli/commands/show.rs - fn style_with_hex(...) { ...hex parse logic... } + use crate::cli::commands::color::style_with_hex; @@ File 8: src/cli/commands/list.rs - fn colored_cell_hex(...) { ...hex parse logic... } + use crate::cli::commands::color::colored_cell_hex; @@ Files Changed (Summary) + `src/cli/commands/color.rs` (NEW) — shared hex parsing + styling helpers - duplicated hex parsing blocks removed from show/list ``` --- If you want, I can produce a **single consolidated patch-style diff of the plan document itself** (all section edits merged, ready to paste as iteration 7).