Three implementation plans with iterative cross-model refinement: lore-service (5 iterations): HTTP service layer exposing lore's SQLite data via REST/SSE for integration with external tools (dashboards, IDE extensions, chat agents). Covers authentication, rate limiting, caching strategy, and webhook-driven sync triggers. work-item-status-graphql (7 iterations + TDD appendix): Detailed implementation plan for the GraphQL-based work item status enrichment feature (now implemented). Includes the TDD appendix with test-first development specifications covering GraphQL client, adaptive pagination, ingestion orchestration, CLI display, and robot mode output. time-decay-expert-scoring (iteration 5 feedback): Updates to the existing time-decay scoring plan incorporating feedback on decay curve parameterization, recency weighting for discussion contributions, and staleness detection thresholds. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
118 lines
6.5 KiB
Markdown
118 lines
6.5 KiB
Markdown
**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<String>`
|
||
+ [ ] 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<serde_json::Value>
|
||
+ pub async fn query(...) -> Result<GraphqlQueryResult>
|
||
+ pub struct GraphqlQueryResult { pub data: serde_json::Value, pub had_errors: bool, pub first_error_message: Option<String> }
|
||
```
|
||
|
||
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<String>,
|
||
+ pub status: Vec<String>, // 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). |