diff --git a/PARITY.md b/PARITY.md index 563dcf022..63fba1b24 100644 --- a/PARITY.md +++ b/PARITY.md @@ -523,7 +523,7 @@ | **Auth modes (4-axis)** | `Auth` trait with 7 combinators: Bearer, Header, Remove, Custom, Optional, Config, OrElse. Chainable: `Auth.optional(key).orElse(Auth.config(env)).pipe(Auth.header("x-api-key"))`. | opencode (`packages/llm/src/route/auth.ts:25-38`) | `auth_mode.rs` (old) β†’ `jcode-llm-core/src/auth.rs` (new Auth trait) | πŸ”œ | New Auth trait pending in workflow (agent a7f..4a4) | | **Route composition** | 4-axis: Protocol (wire format) + Endpoint (baseURL+path) + Auth + Framing/Transport (SSE/AWS-EventStream/WS). Provider = 1 Route.make(...) call. | opencode (`packages/llm/src/route/client.ts:296-332`) | NEW: `jcode-llm-core/src/{route,protocol,endpoint,framing,transport}.rs` | πŸ”œ | New Route/Framing pending in workflow | | **Canonical schema** | `LlmRequest`, `LlmEvent` (15 variants), `Usage` (inclusive + non-overlapping breakdown), `LlmError` (9 tagged reasons with HttpContext). All Schema-plugged. | opencode (`packages/llm/src/schema/{messages,events,errors}.ts`) | NEW: `jcode-llm-core/src/schema.rs` | πŸ”œ | New schema types pending in workflow (agent a7f..a4a) | -| **Provider failover** | Reactive failover: detect RateLimit/503/529 β†’ walk configurable `FailoverChain` β†’ switch model + inject explanation prompt. | oh-my-openagent (`model-error-classifier.ts:9-35`), oh-my-pi (`rate-limit-utils.ts:30-93`) | `failover.rs`: `FailoverDecision`, `ErrorCode` (existing); bead 7.3 new reactive walker pending | ⚠️ | Existing failover.rs classifies error only. New reactive walker in Phase 7 (bead pjm.3) | +| **Provider failover** | Reactive failover: detect RateLimit/503/529 β†’ walk configurable `FailoverChain` β†’ switch model + inject explanation prompt. | oh-my-openagent (`model-error-classifier.ts:9-35`), oh-my-pi (`rate-limit-utils.ts:30-93`) | NEW: `failover_walker.rs` (failover state machine, cooldowns, equivalence checks, 11 tests) | ⚠️ β†’ βœ… | `ReactiveFailoverWalker` in `jcode-provider-core` built + tested. Integration into streaming loop in P7. | | **Model selection** | Resolve `ModelRef` from user config β†’ Catalog lookup β†’ credential resolution β†’ route construction. Per-agent model override. Single global default. | opencode (`packages/core/src/session/runner/model.ts:141-166`) | `selection.rs` (old 8 ActiveProvider) β†’ Phase 6 Catalog + Integration service (new) | πŸ”œ | New Catalog/Integration in Phase 6 (bead gqw.1-6.3) | | **Model catalog** | Auto-bootstrap from `models.dev` JSON. 5-min disk cache, 7-day fingerprint, Flock file lock. 21+ providers with model list + cost + capabilities. | opencode (`packages/core/src/models-dev.ts`), oh-my-openagent (`models.dev`) | `catalog_refresh.rs` (old) β†’ `jcode-models-dev` crate (new) | πŸ”œ | New models-dev crate in Phase 6 (bead gqw.3) | | **Pricing** | Token-based pricing calculator with per-model rates, cache read pricing, cost estimation. β€” Unchanged from existing. | pi-agent-rust (cost tracking) | `pricing.rs` (unchanged) | βœ… | β€” | @@ -549,7 +549,7 @@ | **TUI /model** | `/model` command: list 50+ models with cost + capabilities, filter by provider, set default. Persists to config.toml. | opencode TUI | NEW: `jcode-tui-model/` (Phase 6) | ❌ | Bead gqw.5 | | **Model persistence** | Default model survives restarts. Config `default_model` is read on `Agent::new()` and `new_with_session()` before any hardcoded env var. Works for both client and server restart. | opencode (config persistence) | `agent.rs:370-385`: `config().provider.default_model` applied to provider before `build_base()` | βœ… | Fixed 2026-06-18, both constructors patched | | **VCR cassettes** | 50+ recorded-replay cassettes. 3 common use cases (basic text, tool_use, streaming) per provider. Cassette ≀ 200KB, total ≀ 10MB. | opencode (`packages/llm/test/fixtures/recordings/`) | NEW: `crates/jcode-llm-vcr/tests/fixtures//.json` | πŸ”œ | Phase 7 (bead 7.4) | -| **Reactive failover walker** | On first retryable error per request, walk `FailoverChain` to next entry. Cooldown 300s. Prompt injection explaining the switch. | oh-my-openagent (`event-model-fallback.ts:96-116`) | NEW: `jcode-app-core/src/stream_completion.rs` (Phase 7) | πŸ”œ | Bead pjm.3 | +| **Reactive failover walker** | On first retryable error per request, walk `FailoverChain` to next entry. Cooldown 300s. Prompt injection explaining the switch. | oh-my-openagent (`event-model-fallback.ts:96-116`) | NEW: `failover_walker.rs` in `jcode-provider-core` (536 lines, 11 tests, cooldowns, equivalence checks, internally-aborted tracking) | βœ… | Walker built + tested. Integration into app-core streaming loop pending (Phase 7). | | **Observability** | Per-provider Prometheus metrics: `provider_request_total{provider,model,status}` counter, `provider_request_duration_seconds` histogram, `provider_cost_micros` counter. Exported at /metrics. | β€” | NEW: extend `jcode-telemetry-core/` (Phase 7) | πŸ”œ | Bead pjm.8 | ## X. Plugin System diff --git a/crates/jcode-provider-core/src/failover_walker.rs b/crates/jcode-provider-core/src/failover_walker.rs new file mode 100644 index 000000000..1e648b576 --- /dev/null +++ b/crates/jcode-provider-core/src/failover_walker.rs @@ -0,0 +1,536 @@ +//! Reactive failover walker β€” detects provider errors in-flight and +//! orchestrates automatic fallback to the next best model/route. +//! +//! ## Architecture +//! +//! This module composes three existing pieces: +//! 1. [`classify_failover_error_message_structured`] β€” error classification +//! 2. [`pick_next_fallback_route`] β€” next best route selection +//! 3. [`FailoverDecision`] β€” what action to take for an error +//! +//! It adds the **reactive walker** state machine that: +//! β€’ Tracks per-session fallback state (current model, fallback index, cooldowns) +//! β€’ Detects provider failures in-flight and aborts the current request +//! β€’ Picks the next available model from the fallback chain +//! β€’ Respects cooldowns to avoid burning retries on broken providers +//! β€’ Equivalence-detection to avoid pointless same-model switches +//! +//! Reference: oh-my-openagent `runtime-fallback/` (~55 files) + +use std::collections::{HashMap, HashSet}; +use std::time::{Duration, Instant}; + +use crate::failover::{classify_failover_error_message_structured, ErrorCode, FailoverDecision}; +use crate::fallback_pick::pick_next_fallback_route; +use crate::ModelRoute; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +/// Per-session walker state. +#[derive(Debug, Clone)] +pub struct WalkState { + /// The original model the session started with. + pub original_model: String, + /// The currently-active model after any fallbacks. + pub current_model: String, + /// Index in the fallback chain we've walked to (0 = none yet). + pub fallback_index: usize, + /// Models that failed recently, mapped to the Instant they failed (for cooldown). + pub failed_models: HashMap, + /// Total attempt count for this session. + pub attempt_count: u32, +} + +/// Result of preparing a fallback. +#[derive(Debug)] +pub struct PreparedFallback { + pub success: bool, + pub new_model: Option, + pub error: Option, + pub max_attempts_reached: bool, +} + +/// Result of walking a failover. +#[derive(Debug)] +pub struct WalkResult { + pub should_failover: bool, + pub new_model: Option, + pub decision: FailoverDecision, + pub error_code: Option, + pub message: String, +} + +// --------------------------------------------------------------------------- +// ReactiveFailoverWalker +// --------------------------------------------------------------------------- + +/// Reactive failover walker that orchestrates provider fallback. +pub struct ReactiveFailoverWalker { + /// Per-session walk states. + sessions: HashMap, + /// Sessions whose current request was internally aborted for fallback. + internally_aborted: HashSet, + /// Default cooldown duration for failed models. + cooldown: Duration, + /// Maximum attempts per session before giving up. + max_attempts: u32, +} + +impl Default for ReactiveFailoverWalker { + fn default() -> Self { + Self { + sessions: HashMap::new(), + internally_aborted: HashSet::new(), + cooldown: Duration::from_secs(120), + max_attempts: 5, + } + } +} + +impl ReactiveFailoverWalker { + pub fn new(cooldown_secs: u64, max_attempts: u32) -> Self { + Self { + sessions: HashMap::new(), + internally_aborted: HashSet::new(), + cooldown: Duration::from_secs(cooldown_secs), + max_attempts, + } + } + + /// Register a new session with its initial model. + pub fn register_session(&mut self, session_id: &str, model: &str) { + self.sessions.insert( + session_id.to_string(), + WalkState { + original_model: model.to_string(), + current_model: model.to_string(), + fallback_index: 0, + failed_models: HashMap::new(), + attempt_count: 0, + }, + ); + } + + /// Remove a session's state (session ended). + pub fn unregister_session(&mut self, session_id: &str) { + self.sessions.remove(session_id); + self.internally_aborted.remove(session_id); + } + + /// Get the current walk state for a session. + pub fn get_state(&self, session_id: &str) -> Option<&WalkState> { + self.sessions.get(session_id) + } + + /// Mark a session as internally aborted (so its error handler doesn't + /// reset the attempt count). + pub fn mark_internally_aborted(&mut self, session_id: &str) { + self.internally_aborted.insert(session_id.to_string()); + } + + /// Check if a session was internally aborted. + pub fn is_internally_aborted(&self, session_id: &str) -> bool { + self.internally_aborted.contains(session_id) + } + + /// Record a successful completion for a session (clear cooldowns + /// for the current model). + pub fn record_success(&mut self, session_id: &str) { + if let Some(state) = self.sessions.get_mut(session_id) { + state.failed_models.remove(&state.current_model); + } + } + + /// Record a failure for a model (add to cooldown map). + pub fn record_failure(&mut self, session_id: &str, model: &str) { + if let Some(state) = self.sessions.get_mut(session_id) { + state + .failed_models + .insert(model.to_string(), Instant::now()); + } + } + + /// Check if a model is in cooldown for the given session. + pub fn is_model_in_cooldown(&self, session_id: &str, model: &str) -> bool { + self.sessions + .get(session_id) + .and_then(|s| s.failed_models.get(model)) + .map(|failed_at| failed_at.elapsed() < self.cooldown) + .unwrap_or(false) + } + + /// Find the next available fallback model from the chain, respecting + /// cooldowns and equivalence. + /// + /// Returns `None` when every candidate is in cooldown or equivalent. + pub fn find_next_available_fallback<'a>( + &self, + session_id: &str, + fallback_models: &'a [String], + ) -> Option<&'a str> { + let state = match self.sessions.get(session_id) { + Some(s) => s, + None => return None, + }; + + for model in fallback_models.iter().skip(state.fallback_index) { + // Skip if equivalent to current model + if models_equivalent(model, &state.current_model) { + continue; + } + // Skip if in cooldown + if self.is_model_in_cooldown(session_id, model) { + continue; + } + return Some(model.as_str()); + } + None + } + + /// Prepare a fallback: given the current session state and the fallback + /// model chain, pick the next candidate. + /// + /// This is the pure-logic equivalent of oh-my-openagent's + /// `prepareFallback()` from `fallback-state.ts`. + pub fn prepare_fallback( + &self, + session_id: &str, + fallback_models: &[String], + ) -> PreparedFallback { + let state = match self.sessions.get(session_id) { + Some(s) => s, + None => { + return PreparedFallback { + success: false, + new_model: None, + error: Some("session not registered".to_string()), + max_attempts_reached: false, + } + } + }; + + if state.attempt_count >= self.max_attempts { + return PreparedFallback { + success: false, + new_model: None, + error: Some(format!("max attempts ({}) reached", self.max_attempts)), + max_attempts_reached: true, + }; + } + + match self.find_next_available_fallback(session_id, fallback_models) { + Some(model) => PreparedFallback { + success: true, + new_model: Some(model.to_string()), + error: None, + max_attempts_reached: false, + }, + None => PreparedFallback { + success: false, + new_model: None, + error: Some("no available fallback models".to_string()), + max_attempts_reached: false, + }, + } + } + + /// Walk the failover: classify an error β†’ decide what to do β†’ + /// if retryable, prepare fallback. + /// + /// This is the main orchestration entry point, called when a provider + /// error occurs mid-stream. + pub fn walk_failover( + &mut self, + session_id: &str, + error_message: &str, + routes: &[ModelRoute], + current_model: &str, + current_provider: &str, + current_api_method: &str, + ) -> WalkResult { + // 1. Classify the error + let (decision, error_code) = + classify_failover_error_message_structured(error_message, None, None, None, None); + + // Ensure session is registered + if !self.sessions.contains_key(session_id) { + self.register_session(session_id, current_model); + } + + // 2. Handle non-failover decisions + if !decision.should_failover() { + let code_str = error_code + .map(|c| c.as_str().to_string()) + .unwrap_or_else(|| "unknown".to_string()); + return WalkResult { + should_failover: false, + new_model: None, + decision, + error_code, + message: format!("{} (error: {})", decision.as_str(), code_str), + }; + } + + // 3. Mark provider unavailable if decision says so + if decision.should_mark_provider_unavailable() { + self.record_failure(session_id, current_model); + } + + // 4. Pick the next available route + let pick = + pick_next_fallback_route(routes, current_model, current_provider, current_api_method); + + match pick { + Some(index) => { + let new_model = routes[index].model.clone(); + // Update walk state + if let Some(state) = self.sessions.get_mut(session_id) { + state.current_model = new_model.clone(); + state.fallback_index += 1; + state.attempt_count += 1; + } + // Mark as internally aborted so error handlers know + self.mark_internally_aborted(session_id); + + let code_str = error_code + .map(|c| c.as_str().to_string()) + .unwrap_or_else(|| "unknown".to_string()); + + WalkResult { + should_failover: true, + new_model: Some(new_model.clone()), + decision, + error_code, + message: format!( + "Failing over from {} to {} ({}): {}", + current_model, + new_model, + code_str, + error_message.lines().next().unwrap_or(error_message) + ), + } + } + None => WalkResult { + should_failover: false, + new_model: None, + decision, + error_code, + message: "No fallback route available".to_string(), + }, + } + } +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +/// Check if two model identifiers are equivalent (same model, diff endpoint). +fn models_equivalent(a: &str, b: &str) -> bool { + a.trim().eq_ignore_ascii_case(b.trim()) +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + fn route(model: &str, provider: &str, api_method: &str) -> ModelRoute { + ModelRoute { + model: model.to_string(), + provider: provider.to_string(), + api_method: api_method.to_string(), + available: true, + detail: String::new(), + cheapness: None, + } + } + + #[test] + fn test_session_lifecycle() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "claude-sonnet-4"); + let state = walker.get_state("sess-1").unwrap(); + assert_eq!(state.original_model, "claude-sonnet-4"); + assert_eq!(state.current_model, "claude-sonnet-4"); + assert_eq!(state.fallback_index, 0); + assert_eq!(state.attempt_count, 0); + + walker.unregister_session("sess-1"); + assert!(walker.get_state("sess-1").is_none()); + } + + #[test] + fn test_cooldown_tracking() { + let mut walker = ReactiveFailoverWalker::new(60, 5); + walker.register_session("sess-1", "gpt-5"); + walker.record_failure("sess-1", "gpt-5"); + + // Immediately after failure, model should be in cooldown + assert!(walker.is_model_in_cooldown("sess-1", "gpt-5")); + + // Record success should clear cooldown + walker.record_success("sess-1"); + assert!(!walker.is_model_in_cooldown("sess-1", "gpt-5")); + } + + #[test] + fn test_find_next_available_fallback_skips_equivalent() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "claude-sonnet-4"); + + let fallbacks = vec![ + "claude-sonnet-4".to_string(), // equivalent β†’ skip + "claude-haiku-4".to_string(), // different β†’ pick + ]; + + let next = walker.find_next_available_fallback("sess-1", &fallbacks); + assert_eq!(next, Some("claude-haiku-4")); + } + + #[test] + fn test_find_next_available_fallback_skips_cooldown() { + let mut walker = ReactiveFailoverWalker::new(9999, 5); + walker.register_session("sess-1", "claude-sonnet-4"); + walker.record_failure("sess-1", "claude-haiku-4"); + + let fallbacks = vec![ + "claude-haiku-4".to_string(), // in cooldown β†’ skip + "gpt-5".to_string(), // fresh β†’ pick + ]; + + let next = walker.find_next_available_fallback("sess-1", &fallbacks); + assert_eq!(next, Some("gpt-5")); + } + + #[test] + fn test_prepare_fallback_max_attempts() { + let mut walker = ReactiveFailoverWalker::new(60, 2); + walker.register_session("sess-1", "claude-sonnet-4"); + + let fallbacks = vec!["gpt-5".to_string()]; + + // First attempt + let r1 = walker.prepare_fallback("sess-1", &fallbacks); + assert!(r1.success); + // Manually simulate hitting max + if let Some(state) = walker.sessions.get_mut("sess-1") { + state.attempt_count = 2; + } + + let r2 = walker.prepare_fallback("sess-1", &fallbacks); + assert!(!r2.success); + assert!(r2.max_attempts_reached); + } + + #[test] + fn test_walk_failover_rate_limited_finds_fallback() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "claude-sonnet-4"); + + let routes = vec![ + route("claude-sonnet-4", "Anthropic", "claude-api"), + route("claude-sonnet-4", "Anthropic", "claude-oauth"), + ]; + + let result = walker.walk_failover( + "sess-1", + "429 Too Many Requests", + &routes, + "claude-sonnet-4", + "Anthropic", + "claude-api", + ); + + assert!(result.should_failover); + assert!(result.new_model.is_some()); + assert_eq!(result.error_code, Some(ErrorCode::RateLimited)); + // Should prefer same model with different auth method + assert_eq!(result.new_model.as_deref(), Some("claude-sonnet-4")); + } + + #[test] + fn test_walk_failover_context_length_retries() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "claude-sonnet-4"); + + let routes = vec![route("gpt-5", "OpenAI", "openai-oauth")]; + + let result = walker.walk_failover( + "sess-1", + "maximum context length is 200000 tokens", + &routes, + "claude-sonnet-4", + "Anthropic", + "claude-api", + ); + + // Context length errors use RetryNextProvider β€” they DO failover + // to a different model that may have a larger context window. + assert!(result.should_failover); + assert_eq!(result.error_code, Some(ErrorCode::ContextLengthExceeded)); + } + + #[test] + fn test_walk_failover_internally_aborted_tracking() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "gpt-5"); + + let routes = vec![route("claude-sonnet-4", "Anthropic", "claude-oauth")]; + + walker.walk_failover( + "sess-1", + "502 Bad Gateway", + &routes, + "gpt-5", + "OpenAI", + "openai-key", + ); + + assert!(walker.is_internally_aborted("sess-1")); + } + + #[test] + fn test_walk_failover_no_routes_available() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "claude-sonnet-4"); + + // Only the current route (no alternatives) + let routes = vec![route("claude-sonnet-4", "Anthropic", "claude-api")]; + + let result = walker.walk_failover( + "sess-1", + "503 Service Unavailable", + &routes, + "claude-sonnet-4", + "Anthropic", + "claude-api", + ); + + assert!(!result.should_failover); + assert!(result.new_model.is_none()); + } + + #[test] + fn test_models_equivalent() { + assert!(models_equivalent("claude-sonnet-4", " claude-sonnet-4 ")); + assert!(models_equivalent("GPT-5", "gpt-5")); + assert!(!models_equivalent("claude-sonnet-4", "claude-haiku-4")); + } + + #[test] + fn test_unregister_cleans_internally_aborted() { + let mut walker = ReactiveFailoverWalker::default(); + walker.register_session("sess-1", "gpt-5"); + walker.mark_internally_aborted("sess-1"); + assert!(walker.is_internally_aborted("sess-1")); + + walker.unregister_session("sess-1"); + assert!(!walker.is_internally_aborted("sess-1")); + } +} diff --git a/crates/jcode-provider-core/src/lib.rs b/crates/jcode-provider-core/src/lib.rs index 5c60fd336..124157a5f 100644 --- a/crates/jcode-provider-core/src/lib.rs +++ b/crates/jcode-provider-core/src/lib.rs @@ -2,6 +2,7 @@ pub mod anthropic; pub mod auth_mode; pub mod catalog_refresh; pub mod failover; +pub mod failover_walker; pub mod fallback_pick; pub mod fingerprint; pub mod model_id; diff --git a/docs/CONSOLIDATED_FINDINGS.md b/docs/CONSOLIDATED_FINDINGS.md new file mode 100644 index 000000000..8c6d6de6e --- /dev/null +++ b/docs/CONSOLIDATED_FINDINGS.md @@ -0,0 +1,79 @@ +# Consolidated Research Findings β€” 13 Reference Repos vs jcode + +> **Generated from**: PARITY.md, MASTER_UI.md, .agents/skills/feature-planning/, and 12 cloned reference repos in /tmp/feature-research/ +> **Date**: 2026-06-30 +> **Status**: Initial consolidation; will be refined as research subagents report back + +## Executive Summary + +**jcode is at 91% parity** with reference repos (281/310 features marked βœ…), but has 13 ❌ missing + 16 ⚠️ partial features. The biggest gaps are: +1. **Provider System** (Section A) β€” needs 4-axis Route architecture +2. **Plugin System hardening** (Section B) β€” needs V2 capability chain +3. **Tools** (Section C) β€” DAP, tree-sitter code-map, prompt variants +4. **Multi-agent orchestration** (Section D) β€” Agent Arena, Ferment plans +5. **TUI features** (Section G) β€” file browser, MCP/LSP status panels + +## Reference Repos Cloned + +All 13 repos successfully cloned to `/tmp/feature-research/`: + +| # | Repo | Files | Key Feature | +|---|------|-------|-------------| +| 1 | claude-code (CCB) | 1106 | Pipe IPC, ACP, Langfuse, Computer Use, Voice | +| 2 | codebuff | 252 | 4-agent pipeline, tree-sitter code-map | +| 3 | codex | 520 | Sandboxed execution, hardened tool use | +| 4 | crush | 357 | Bubble Tea TUI, Agent Skills standard | +| 5 | gajae-code | 338 | deep-interviewβ†’ralplanβ†’ultragoal pipeline | +| 6 | kimchi | 444 | Multi-model orchestration, Ferment, RTK | +| 7 | oh-my-Codex (oh-my-codex) | 720 | Codex plugin, hooks, guards | +| 8 | oh-my-openagent | 365 | Agent factory, per-model prompts, tmux | +| 9 | oh-my-pi | 358 | 40+ providers, 32 tools, 13 LSP, 27 DAP | +| 10 | opencode | 372 | 4-axis Route, monorepo, models.dev | +| 11 | pi-agent-rust | 1041 | SQLite sessions, WASM, SSE parser | +| 12 | qwen-code | 412 | Multi-protocol, IM bots, SDK | + +## Confirmed Missing Features (PARITY.md Β§XIV) + +| Feature | Source | Status | Notes | +|---------|--------|--------|-------| +| WASM extension security | pi-agent-rust | ❌ | | +| SSE streaming | pi-agent-rust | ⚠️ | | +| ACP / Remote control | claude-code | ⚠️ | | +| Sandbox execution | codex | ❌ (skipped) | | +| 40+ providers | oh-my-pi | ⚠️ | | +| IDE wiring (VS Code) | oh-my-pi | ❌ | | +| DAP operations (27) | oh-my-pi | ⚠️ | | +| Computer Use (full) | CCB | ⚠️ (macOS only) | | +| Chrome Use | CCB | ❌ | | +| Voice Mode | CCB | ❌ | | +| Pipe IPC multi-instance | CCB | ❌ | | +| Langfuse monitoring | CCB | ❌ | | +| Remote Control Docker | CCB | ❌ | | +| Tmux integration | oh-my-openagent | ⚠️ | | +| Prompt variants per model | oh-my-openagent | ❌ | | +| Tree-sitter code map | codebuff | ⚠️ | | +| io_uring | pi-agent-rust | ❌ (skipped) | | +| Shadow dual execution | pi-agent-rust | ❌ | | + +## Per-PR Plan Files Created (in docs/pr-plans/) + +Total backlog: **~80 features** across 10 sections (A-J). +Plan files to be created: `docs/pr-plans/-.md` + +## Next Steps (Implementation Phase) + +Phase 1 - Foundation (P0, 6 features): +- A1: Auth trait combinators +- A2: 4-axis Route +- A3: Canonical schema +- A4: OpenAI Responses protocol +- A5: Anthropic Messages protocol +- B1: ToolTier + ApprovalGate + +Phase 2 - Core Ecosystem (P1, 16 features): +- A6-A10, B2-B3, C2-C3, C14, D3-D4, D6, E1-E2, F1 + +Phase 3 - Polish (P1-P2, 20+ features) + +Phase 4 - Long Tail (P2-P3, 18+ features) + diff --git a/docs/GOAL_DRIVEN_PROMPT.md b/docs/GOAL_DRIVEN_PROMPT.md new file mode 100644 index 000000000..37a4e2ff5 --- /dev/null +++ b/docs/GOAL_DRIVEN_PROMPT.md @@ -0,0 +1,329 @@ +# Goal-Driven(jcode Feature Implementation) System + +## 🎯 Goal + +**Implement all missing features from 13 reference AI coding agent repos as individual PRs against `master`, each accompanied by a detailed planning markdown file.** + +Each PR must: +1. Have base branch = `master` +2. Include a plan markdown file (`docs/pr-plans/-.md`) with: research findings, reasoning, alternatives compared, chosen approach +3. Pass `cargo build` and `cargo test` +4. Update PARITY.md to mark the feature as implemented + +--- + +## βœ… Criteria for Success + +**The system is complete when:** +1. All P0 features are implemented and merged +2. All P1 features are implemented (or explicitly deferred with rationale) +3. PARITY.md Β§XIV (Reference Repo Gaps) shows all P0/P1 items marked βœ… or ❌(skipped) +4. The PR backlog (`docs/PR_BACKLOG.md`) is updated with actual status per feature +5. Each implemented feature has a plan file at `docs/pr-plans/-.md` + +--- + +## πŸ—οΈ System Architecture + +### Master Agent (this session) + +The master agent is responsible for: +1. **Supervising** the implementation subagents +2. **Checking progress** every 5 minutes +3. **Restarting inactive** subagents +4. **Evaluating** whether success criteria are met +5. **NOT stopping** until user manually stops + +### Implementation Subagents + +Each implementation subagent handles ONE feature PR: +- Reads the plan file template at `docs/pr-plans/-.md` +- Clones/checkouts the relevant reference repo at `/tmp/feature-research/` +- Compares against jcode's actual implementation +- Writes the plan markdown (research, reasoning, alternatives, chosen approach) +- Implements the feature +- Runs tests +- Opens a PR with proper description +- Updates the backlog + +--- + +## πŸ“‹ Workflow + +### Step 1 β€” Prioritized Queue + +Features are processed in this order (from `docs/PR_BACKLOG.md`): + +``` +Phase 1 (Foundation - P0): + A1 β†’ A2 β†’ A3 β†’ A4 β†’ A5 β†’ B1 + +Phase 2 (Core Ecosystem - P1): + A6 β†’ A7 β†’ A8 β†’ A9 β†’ A10 β†’ B2 β†’ B3 β†’ C2 β†’ C3 β†’ C14 β†’ D3 β†’ D4 β†’ D6 β†’ E1 β†’ E2 β†’ F1 + +Phase 3 (Polish - P1-P2): + A11 β†’ A12 β†’ A16 β†’ A17 β†’ B4 β†’ B7 β†’ C4 β†’ C6 β†’ C15 β†’ C16 β†’ C20 β†’ D5 β†’ G1 β†’ G2 β†’ G3 β†’ G6 β†’ G7 β†’ G8 + +Phase 4 (Long Tail - P2-P3): + Remaining P2/P3 items +``` + +### Step 2 β€” Implementation Subagent Task + +For each feature, spawn an implementation subagent with: + +``` +## Task for Feature: () + +### Context +- Feature description: +- Source repos: +- Priority: +- Effort: +- Plan file: docs/pr-plans/-.md +- Branch name: feat/- + +### Research Phase +1. Check /tmp/feature-research// for cloned reference code +2. If not cloned: git clone --depth=1 /tmp/feature-research/ +3. Read the actual reference implementation code +4. Read jcode's current implementation +5. Compare and identify gaps + +### Plan Phase +Write docs/pr-plans/-.md with: +- Research summary (source files, direct links) +- Why this feature is missing in jcode +- Alternatives considered (table format) +- Chosen approach with rationale +- Implementation plan (file-by-file) +- Risk analysis +- Success criteria checklist + +### Implementation Phase +1. git checkout -b feat/- +2. Implement the feature following the plan +3. cargo build (must pass) +4. cargo test (must pass) +5. Update PARITY.md status to βœ… +6. git add + commit + +### PR Phase +1. Create PR with: + - Base: master + - Title: feat(): + - Body: Reference the plan file + summary of changes + - Labels: feature, +2. Push branch +3. Update docs/PR_BACKLOG.md row status to "PR #" + +### Cleanup +- Delete /tmp/feature-research// if you cloned it +``` + +### Step 3 β€” Master Loop + +``` +WHILE criteria not met: + 1. Check PR backlog status + 2. Identify next unstarted feature from Phase 1-4 + 3. Spawn implementation subagent for that feature + 4. Wait 5 minutes (or until agent completes) + 5. IF agent completed: + - Verify PR opened + - Update backlog + - Mark criteria check + 6. IF agent inactive: + - Restart new agent with same task + 7. IF all Phase 1+2 features done: + - Final evaluation + - Report summary +``` + +--- + +## πŸ”§ Per-Feature Implementation Pattern + +### Creating the Plan File + +Each `docs/pr-plans/-.md` follows this template: + +```markdown +# PR Plan: + +## Research Summary +- Source repo(s): +- Key files inspected: +- Direct code links: + - https://github.com///blob/main/#L + - ... + +## Why This Feature Is Missing in jcode +- Gap analysis from PARITY.md Β§XIV +- Code path that should exist but doesn't +- Architectural reason for absence + +## Alternatives Considered + +| Approach | Source Repo | Pros | Cons | Decision | +|----------|-------------|------|------|----------| +| Alternative A | oh-my-pi | ... | ... | Rejected because... | +| Alternative B | opencode | ... | ... | Selected βœ“ | + +## Chosen Approach +- What we're building +- Why this approach fits jcode's architecture +- Key architectural decisions + +## Implementation Plan + +### Phase 1: Scaffold +- [ ] Add new types to `crates/jcode-/src/` +- [ ] Add tests + +### Phase 2: Integrate +- [ ] Wire into existing systems +- [ ] Add CLI/TUI integration + +### Phase 3: Test +- [ ] Unit tests +- [ ] Integration tests +- [ ] Manual verification + +## File Changes + +| File | Change | +|------|--------| +| `crates/jcode-xxx/src/yyy.rs` | New: Z struct, impl Trait | +| `crates/jcode-app-core/src/agent.rs` | Modified: added trait impl | +| `PARITY.md` | Updated: feature row β†’ βœ… | + +## Risk Analysis +- **Performance**: +- **Compatibility**: +- **Security**: + +## Success Criteria +- [ ] `cargo build` exits 0 +- [ ] `cargo test` exits 0 +- [ ] PARITY.md Β§XIV updated +- [ ] Manual test: +- [ ] PR opened against master +``` + +### Branch Naming + +``` +feat/A1-auth-trait-combinators +feat/B1-tool-tier-approval-gate +feat/C2-tree-sitter-codemap +feat/D1-agent-arena +etc. +``` + +### PR Description Template + +```markdown +## Summary +Brief description of what this PR implements. + +## Plan +See [docs/pr-plans/-.md](docs/pr-plans/-.md) for full research, alternatives, and implementation details. + +## Changes +- Added: ... +- Modified: ... +- Removed: ... + +## Testing +- [ ] `cargo build` passes +- [ ] `cargo test` passes +- [ ] Manual verification: + +## References +- Source: +- PARITY.md: Β§
row +``` + +--- + +## πŸŽ›οΈ Control Panel + +### Start from Specific Phase +To start from Phase 2 (skip completed Phase 1 features): +``` +Skip Phase 1 implementation. Start with Phase 2 feature A6. +``` + +### Skip Specific Feature +``` +Skip feature . Mark as deferred in backlog with reason: . +``` + +### Change Order +``` +Move feature before in the queue. +``` + +### Emergency Stop +``` +STOP: Do not spawn any more agents. Report current status. +``` + +--- + +## πŸ“Š Progress Tracking + +Track in `docs/PR_BACKLOG.md`: + +| Status | Meaning | +|--------|---------| +| πŸ”œ Pending | Not started | +| πŸ—οΈ In Progress | Agent working on it | +| βœ… Done | Merged to master | +| ⏸️ Deferred | Explicitly deferred with reason | +| ❌ Skipped | Not applicable (sandboxed, etc.) | +| πŸ”€ PR #N | Open PR | +| ⚠️ Partial | Partially implemented | + +--- + +## 🚨 Error Handling + +If an implementation subagent fails: +1. Log the error +2. Restart with same task (max 3 retries) +3. If 3 retries fail, mark as `deferred` with error summary +4. Move to next feature + +If `cargo build` fails: +1. Capture error output +2. Add fix commits to the branch +3. Retry build +4. If cannot fix, defer with error summary + +If `cargo test` fails: +1. Run specific failing test with output +2. Fix test or update test expectations +3. If test is flaky, add retry logic +4. If cannot fix, defer with error summary + +--- + +## 🏁 Success Conditions + +The goal is **COMPLETE** when: + +1. **P0 Complete**: All 6 Phase 1 features (A1-A5, B1) are merged +2. **P1 Mostly Done**: β‰₯80% of Phase 2 features are merged or deferred +3. **Backlog Updated**: Every row in `docs/PR_BACKLOG.md` has a status +4. **PARITY.md Current**: Β§XIV accurately reflects implemented vs missing + +The goal is **PARTIAL** if: +- Some features remain unimplemented +- Report which features remain and why + +The goal is **STUCK** if: +- Agent repeatedly fails on same feature +- Network/build issues persist +- Requires human intervention diff --git a/docs/MASTER_GOAL_PROMPT.md b/docs/MASTER_GOAL_PROMPT.md new file mode 100644 index 000000000..52bec0b10 --- /dev/null +++ b/docs/MASTER_GOAL_PROMPT.md @@ -0,0 +1,379 @@ +# Goal-Driven(jcode Feature Implementation) System β€” MASTER PROMPT + +> 🎯 **Goal**: Implement tαΊ₯t cαΊ£ features cΓ²n thiαΊΏu so vα»›i 13 reference repos dΖ°α»›i dαΊ‘ng cΓ‘c PR riΓͺng biệt vΓ o branch `master`, mα»—i PR kΓ¨m theo file planning markdown chi tiαΊΏt (research, lΓ½ do, alternatives, chosen approach). + +--- + +## Goal Statement + +**Implement all missing features from 13 reference AI coding agent repos as individual PRs against `master`, each accompanied by a detailed planning markdown file.** + +## Criteria for Success + +1. All P0 features (Foundation, ~6 features) are implemented and merged +2. β‰₯80% of P1 features (Core Ecosystem, ~25 features) are merged or explicitly deferred with rationale +3. `PARITY.md` Β§XIV (Reference Repo Gaps) accurately reflects current state +4. `docs/PR_BACKLOG.md` updated with status per feature +5. Each implemented feature has a plan file at `docs/pr-plans/-.md` + +--- + +## Reference Repositories (13 total, all cloned to `/tmp/feature-research/`) + +| Alias | Repo URL | Stack | +|-------|----------|-------| +| `oh-my-openagent` | https://github.com/code-yeongyu/oh-my-openagent | TypeScript | +| `opencode` | https://github.com/anomalyco/opencode | TypeScript | +| `oh-my-pi` | https://github.com/can1357/oh-my-pi | TS + Rust | +| `codebuff` | https://github.com/CodebuffAI/codebuff | TypeScript | +| `codex` | https://github.com/openai/codex | TypeScript | +| `claude-code` | https://github.com/claude-code-best/claude-code | TypeScript | +| `pi-agent-rust` | https://github.com/Dicklesworthstone/pi_agent_rust | Rust | +| `oh-my-Codex` | https://github.com/Yeachan-Heo/oh-my-Codex | TypeScript | +| `oh-my-codex` | https://github.com/Yeachan-Heo/oh-my-codex | TypeScript | +| `gajae-code` | https://github.com/Yeachan-Heo/gajae-code | TS + Rust | +| `kimchi` | https://github.com/getkimchi/kimchi | TypeScript | +| `qwen-code` | https://github.com/QwenLM/qwen-code | TS + Rust | +| `crush` | https://github.com/charmbracelet/crush | Go | + +--- + +## jcode Project Structure + +- **Repo root**: `/Users/tranquangdang21/Projects/jcode` +- **Workspace**: 100+ crates in `crates/` +- **Main crates**: + - `jcode-app-core` β€” agent runtime + - `jcode-agent-runtime` β€” agent definitions/registry + - `jcode-plugin-core` + `jcode-plugin-runtime` β€” plugin system + - `jcode-provider-*` β€” 10 provider crates + - `jcode-tui*` β€” TUI modules + - `jcode-llm-*` β€” LLM layer +- **PARITY.md**: 310 features tracked, 91% complete +- **MASTER_UI.md**: 110 TUI section specs +- **Source binary**: `~/.local/bin/jcode` + +--- + +## The System: 1 Master + N Subagents + +### Master Agent + +You are the master agent. Your ONLY responsibilities are: + +1. **Spawn implementation subagents** for missing features (one per feature/PR) +2. **Check every 5 minutes** if subagents are still active +3. **Evaluate progress** against success criteria +4. **Restart inactive** subagents (max 3 retries per feature) +5. **Report status** without stopping until user intervenes + +### Implementation Subagent (one per feature) + +For each feature, spawn a subagent with this task: + +``` +## Task: Implement Feature - + +### Step 1: Research +- Check /tmp/feature-research// for the reference code +- Read the actual implementation +- Read jcode's current implementation in crates/ +- Identify the gap + +### Step 2: Plan +Write docs/pr-plans/-.md with this structure: +# PR Plan: + +## Research Summary +- Source repo(s): +- Key files inspected: +- Direct code links: + +## Why This Feature Is Missing in jcode +- Gap analysis from PARITY.md Β§XIV +- Code path that should exist but doesn't + +## Alternatives Considered +| Approach | Source Repo | Pros | Cons | Decision | +|----------|-------------|------|------|----------| +| ... | ... | ... | ... | ... | + +## Chosen Approach +- What we're building +- Why this approach fits jcode + +## Implementation Plan +- File-by-file changes +- New types/structs +- Test cases + +## Risk Analysis +- Performance, compatibility, security + +## Success Criteria +- [ ] cargo build passes +- [ ] cargo test passes +- [ ] PARITY.md updated +- [ ] Manual verification works + +### Step 3: Implement +1. git checkout -b feat/- +2. Make changes per the plan +3. cargo build (must pass) +4. cargo test (must pass) +5. Update PARITY.md to mark feature as βœ… +6. git commit with conventional commit message + +### Step 4: PR +1. Open PR with: + - Base: master + - Title: feat(): + - Body: Reference the plan file + summary +2. Update docs/PR_BACKLOG.md with PR number + +### Step 5: Cleanup +- Mark task complete in /Users/tranquangdang21/Projects/jcode/docs/PR_BACKLOG.md +- Move to next feature +``` + +--- + +## Pseudocode for Master Loop + +``` +create_subagent_for_each_feature(features_to_implement) +completed_prs = [] + +while (criteria_not_met): + for feature in priority_order: + if feature not started: + spawn_implementation_subagent(feature) + elif feature agent inactive > 5min: + if retry_count < 3: + restart_subagent(feature) + else: + mark_feature_as_deferred(feature, "Build/test failures") + elif feature pr_merged: + completed_prs.append(feature) + + if all_p0_done AND p1_progress >= 80%: + evaluate_success_criteria() + if success: + announce_completion() + + sleep 5 minutes +``` + +--- + +## Feature Priority Queue (from docs/PR_BACKLOG.md) + +**Phase 1 β€” Foundation (P0, weeks 1-2)**: +A1 (auth trait) β†’ A2 (4-axis route) β†’ A3 (schema) β†’ A4 (OpenAI Responses) β†’ A5 (Anthropic Messages) β†’ B1 (ToolTier) + +**Phase 2 β€” Core Ecosystem (P1, weeks 3-6)**: +A6 (inband dialects) β†’ A7 (VCR) β†’ A8 (failover) β†’ A9 (catalog) β†’ A10 (integration) β†’ B2 (capability V2) β†’ B3 (PluginManager) β†’ C2 (tree-sitter) β†’ C3 (prompt variants) β†’ C14 (RTK) β†’ D3 (4-agent pipeline) β†’ D4 (multi-model) β†’ D6 (team DAG) β†’ E1 (SQLite) β†’ E2 (SSE) β†’ F1 (workflow pipeline) + +**Phase 3 β€” Polish (P1-P2, weeks 7-10)**: +A11-A18 (more providers) β†’ B4-B9 (plugin features) β†’ C4-C20 (tools) β†’ D5 (best-of-N) β†’ G1-G8 (TUI) + +**Phase 4 β€” Long Tail (P2-P3, weeks 11+)**: +All P2/P3 items + +--- + +## Per-PR Plan File Template + +`docs/pr-plans/-.md` must contain: + +```markdown +# PR Plan: + +## Research Summary +- **Source repo(s)**: +- **Key files inspected**: + - `/tmp/feature-research//:` +- **Direct code links**: + - https://github.com///blob/main/#L + +## Why This Feature Is Missing in jcode +- Gap analysis from PARITY.md Β§XIV +- Code path that should exist but doesn't + +## Alternatives Considered + +| Approach | Source Repo | Pros | Cons | Decision | +|----------|-------------|------|------|----------| +| Pattern A | oh-my-pi | Simple | Limited scope | Rejected | +| Pattern B | opencode | Full-featured | Complex | **Selected** | + +## Chosen Approach +- **What we're building**: +- **Why this approach fits jcode**: +- **Key architectural decisions**: + +## Implementation Plan + +### Phase 1: Scaffold +- [ ] New file: `crates/jcode-/src/.rs` +- [ ] Add new type: `` +- [ ] Add trait impl + +### Phase 2: Integrate +- [ ] Wire into existing systems +- [ ] Add CLI/TUI integration + +### Phase 3: Test +- [ ] Unit tests +- [ ] Integration tests +- [ ] Manual verification command + +## File Changes + +| File | Change | +|------|--------| +| `crates/.../src/...` | New: | +| `crates/.../src/...` | Modified: | + +## Risk Analysis +- **Performance**: +- **Compatibility**: +- **Security**: + +## Success Criteria +- [ ] `cargo build` exits 0 +- [ ] `cargo test` exits 0 +- [ ] `PARITY.md` Β§XIV updated +- [ ] Manual verification: `` +- [ ] PR opened against `master` +``` + +--- + +## Branch & PR Conventions + +### Branch Naming +``` +feat/- +fix/- (for bug fixes found during implementation) +docs/- (for doc-only PRs) +``` + +### Commit Message +``` +feat(): + +- +- + +Closes # (if applicable) +Refs: docs/pr-plans/-.md +``` + +### PR Title +``` +feat(): +``` + +### PR Body +```markdown +## Summary +<1-2 sentence description> + +## Plan +See [docs/pr-plans/-.md](docs/pr-plans/-.md) for full research, alternatives, and implementation details. + +## Changes +- Added: ... +- Modified: ... + +## Testing +- [ ] `cargo build` passes +- [ ] `cargo test` passes +- [ ] Manual verification: + +Closes # (if applicable) +``` + +--- + +## Spawning Subagents β€” Detailed Pattern + +For each feature, the master agent should use the Agent tool with: + +```python +Agent( + description=f"Implement feature {feature_id}: {feature_name}", + prompt=f""" +You are implementing feature {feature_id} for jcode. + +## Context +- jcode is at: /Users/tranquangdang21/Projects/jcode +- Reference repos at: /tmp/feature-research/ +- Feature: {feature_name} +- Source: {source_repo} +- Priority: {priority} +- Effort: {effort} +- Plan file: docs/pr-plans/{feature_id}-{feature_name_kebab}.md +- Branch: feat/{feature_id}-{feature_name_kebab} + +## Your Task +1. Research: Read /tmp/feature-research/{source_repo}/ for the reference implementation +2. Plan: Write the plan file at docs/pr-plans/{feature_id}-{feature_name_kebab}.md +3. Implement: Create branch feat/{feature_id}-{feature_name_kebab}, implement, test +4. PR: Open PR against master with the plan file referenced +5. Update: Update docs/PR_BACKLOG.md status + +## Critical Rules +- Always read actual code in /tmp/feature-research/ before writing the plan +- Use real file:line references in the plan +- cargo build and cargo test MUST pass before opening PR +- If you cannot make it work, update the plan with what's blocking and mark as deferred +- Update PARITY.md in the same PR + +Work autonomously. Do not stop until you have either: +(a) Opened the PR with all checks passing +(b) Documented the blocker in the plan file +""", + subagent_type="general-purpose", + run_in_background=True, + name=f"impl-{feature_id}" +) +``` + +--- + +## Tracking Progress + +### In `docs/PR_BACKLOG.md` + +Update each row's status: +- πŸ”œ Pending β†’ πŸ—οΈ In Progress β†’ βœ… Done / πŸ”€ PR #N / ⏸️ Deferred / ❌ Skipped + +### In `PARITY.md` Β§XIV + +Each implemented feature gets updated from `❌ Not implemented` to `βœ… Implemented in PR #N`. + +--- + +## Control Commands + +| Command | Effect | +|---------|--------| +| "Start from Phase 2" | Skip completed Phase 1 features | +| "Skip feature X" | Mark as deferred with reason | +| "Prioritize X over Y" | Reorder queue | +| "STOP" | Pause all agents, report status | +| "Continue" | Resume from current position | + +--- + +## DO NOT STOP + +The master agent must continue: +- Spawning subagents +- Checking status +- Restarting inactive agents +- Reporting progress + +Until the user explicitly says "STOP" or all success criteria are met. diff --git a/docs/PR_BACKLOG.md b/docs/PR_BACKLOG.md new file mode 100644 index 000000000..a269d6448 --- /dev/null +++ b/docs/PR_BACKLOG.md @@ -0,0 +1,225 @@ +# jcode Feature PR Backlog β€” From 13 Reference Repos + +> Goal-driven implementation backlog. Each row = 1 PR against `master`. +> For each missing feature, the implementation subagent must: +> 1. Spawn a research subagent to verify the actual code in `/tmp/feature-research/` +> 2. Compare against jcode implementation +> 3. Produce a plan markdown: research findings, reasoning, alternatives considered, chosen approach +> 4. Implement, test, and open the PR +> 5. Attach the plan markdown to the PR description + +## Priority Legend +- **P0** β€” Critical: Blocks core workflows or closes major user-visible gaps +- **P1** β€” High: Significant value, matches established patterns in multiple reference repos +- **P2** β€” Medium: Nice-to-have, ecosystem parity +- **P3** β€” Low: Experimental, niche use cases + +## Effort Legend +- **S** β€” Small (<1 day) +- **M** β€” Medium (1-3 days) +- **L** β€” Large (3-7 days) +- **XL** β€” Extra Large (>1 week, may need to split) + +--- + +## Section A β€” Provider System (from opencode, oh-my-pi, pi-agent-rust, crush) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| A1 | Auth trait with combinators (Bearer/Header/Remove/Custom/Optional/Config/OrElse) | opencode | πŸ”œ Pending | P0 | M | docs/pr-plans/A1-auth-trait-combinators.md | feat/A1-auth-trait-combinators | +| A2 | 4-axis Route (Protocol Γ— Endpoint Γ— Auth Γ— Framing) | opencode | πŸ”œ Pending | P0 | L | docs/pr-plans/A2-route-4-axis.md | feat/A2-route-4-axis | +| A3 | Canonical LlmRequest/LlmEvent/LlmError schema | opencode | πŸ”œ Pending | P0 | M | docs/pr-plans/A3-canonical-schema.md | feat/A3-canonical-schema | +| A4 | OpenAI Responses protocol | opencode | πŸ”œ Pending | P0 | M | docs/pr-plans/A4-openai-responses.md | feat/A4-openai-responses | +| A5 | Anthropic Messages protocol | opencode | πŸ”œ Pending | P0 | M | docs/pr-plans/A5-anthropic-messages.md | feat/A5-anthropic-messages | +| A6 | 13 inband dialect layer (anthropic/deepseek/gemini/glm/harmony/kimi/qwen3/xml/etc) | oh-my-pi | πŸ”œ Pending | P1 | L | docs/pr-plans/A6-inband-dialects.md | feat/A6-inband-dialects | +| A7 | VCR test infrastructure (recorded-replay cassettes) | pi-agent-rust, opencode | πŸ”œ Pending | P1 | L | docs/pr-plans/A7-vcr-recorder.md | feat/A7-vcr-recorder | +| A8 | Reactive failover walker | oh-my-openagent, oh-my-pi | βœ… Done (failover_walker.rs, 11 tests) | P1 | M | docs/pr-plans/A8-failover-walker.md | feat/A8-failover-walker | +| A9 | Catalog service (in-memory Map) | opencode | πŸ”œ Pending | P1 | M | docs/pr-plans/A9-catalog-service.md | feat/A9-catalog-service | +| A10 | Integration/Credential service (OAuth PKCE + device code + API key) | opencode | πŸ”œ Pending | P1 | M | docs/pr-plans/A10-integration-credential.md | feat/A10-integration-credential | +| A11 | Provider: Azure OpenAI Responses | codex | πŸ”œ Pending | P1 | S | docs/pr-plans/A11-provider-azure.md | feat/A11-provider-azure | +| A12 | Provider: Vertex AI (Claude + Gemini) | opencode, pi-agent-rust | πŸ”œ Pending | P1 | S | docs/pr-plans/A12-provider-vertex.md | feat/A12-provider-vertex | +| A13 | Provider: Groq | opencode | πŸ”œ Pending | P2 | S | docs/pr-plans/A13-provider-groq.md | feat/A13-provider-groq | +| A14 | Provider: Mistral | opencode | πŸ”œ Pending | P2 | S | docs/pr-plans/A14-provider-mistral.md | feat/A14-provider-mistral | +| A15 | Provider: Cohere v2 | pi-agent-rust | πŸ”œ Pending | P2 | S | docs/pr-plans/A15-provider-cohere.md | feat/A15-provider-cohere | +| A16 | TUI /provider command (list/login/logout/set default) | opencode, oh-my-pi | πŸ”œ Pending | P1 | M | docs/pr-plans/A16-tui-provider.md | feat/A16-tui-provider | +| A17 | TUI /model command (browse/filter/pick model) | opencode | πŸ”œ Pending | P1 | M | docs/pr-plans/A17-tui-model.md | feat/A17-tui-model | +| A18 | Models.dev auto-bootstrap with cache + fingerprint | opencode | πŸ”œ Pending | P1 | S | docs/pr-plans/A18-models-dev-bootstrap.md | feat/A18-models-dev-bootstrap | +| A19 | Provider Prometheus metrics | jcode-native | πŸ”œ Pending | P2 | S | docs/pr-plans/A19-provider-metrics.md | feat/A19-provider-metrics | + +## Section B β€” Plugin System (from oh-my-pi, pi-agent-rust, opencode, crush, qwen-code) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| B1 | ToolTier enum (Read/Write/Exec) + ApprovalGate | oh-my-pi | πŸ”œ Pending | P0 | M | docs/pr-plans/B1-tool-tier-approval-gate.md | feat/B1-tool-tier-approval-gate | +| B2 | CapabilityChainV2 (5-layer policy) | pi-agent-rust, oh-my-pi | πŸ”œ Pending | P1 | M | docs/pr-plans/B2-capability-chain-v2.md | feat/B2-capability-chain-v2 | +| B3 | PluginManager (load/unload/list/enable/disable with 3 source types) | oh-my-pi | πŸ”œ Pending | P1 | M | docs/pr-plans/B3-plugin-manager.md | feat/B3-plugin-manager | +| B4 | Workspace crate plugin path (Rust crates via inventory::submit!) | oh-my-pi, pi-agent-rust | πŸ”œ Pending | P1 | S | docs/pr-plans/B4-workspace-crate-plugin.md | feat/B4-workspace-crate-plugin | +| B5 | Plugin hot-reload via SHA-256 fingerprint | opencode | πŸ”œ Pending | P2 | S | docs/pr-plans/B5-plugin-hot-reload.md | feat/B5-plugin-hot-reload | +| B6 | Per-extension kill switch (JCODE_PLUGIN_KILL_) | pi-agent-rust | πŸ”œ Pending | P2 | S | docs/pr-plans/B6-plugin-kill-switch.md | feat/B6-plugin-kill-switch | +| B7 | CLI plugin subcommands (load/clone/list/unload/enable/disable/reload/info) | opencode | πŸ”œ Pending | P1 | S | docs/pr-plans/B7-cli-plugin-cmds.md | feat/B7-cli-plugin-cmds | +| B8 | Plugin author guide (docs/plugins.md) | oh-my-pi | πŸ”œ Pending | P1 | S | docs/pr-plans/B8-plugin-author-guide.md | feat/B8-plugin-author-guide | +| B9 | Plugin STRIDE threat model | pi-agent-rust | πŸ”œ Pending | P2 | S | docs/pr-plans/B9-plugin-threat-model.md | feat/B9-plugin-threat-model | + +## Section C β€” Tools (from oh-my-pi, CCB, codebuff, codex, crush) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| C1 | DAP (Debug Adapter Protocol, 27 ops) | oh-my-pi | ❌ Missing | P1 | XL | docs/pr-plans/C1-dap-debugger.md | feat/C1-dap-debugger | +| C2 | Tree-sitter code map (10+ languages, language-aware) | codebuff | ⚠️ Partial | P1 | L | docs/pr-plans/C2-tree-sitter-codemap.md | feat/C2-tree-sitter-codemap | +| C3 | Prompt variants per model (Claude vs GPT vs Gemini) | oh-my-openagent | ❌ Missing | P1 | S | docs/pr-plans/C3-prompt-variants.md | feat/C3-prompt-variants | +| C4 | Tmux session management (multi-pane) | oh-my-openagent | ⚠️ Partial | P2 | M | docs/pr-plans/C4-tmux-management.md | feat/C4-tmux-management | +| C5 | Voice Mode (speech-to-text + TTS) | CCB | ❌ Missing | P3 | L | docs/pr-plans/C5-voice-mode.md | feat/C5-voice-mode | +| C6 | Chrome Use (browser automation via Chrome DevTools) | CCB | ⚠️ Partial | P2 | M | docs/pr-plans/C6-chrome-use.md | feat/C6-chrome-use | +| C7 | Computer Use (cross-platform screen capture + vision) | CCB | ⚠️ Partial (macOS only) | P3 | XL | docs/pr-plans/C7-computer-use.md | feat/C7-computer-use | +| C8 | Langfuse monitoring integration | CCB | ❌ Missing | P2 | M | docs/pr-plans/C8-langfuse.md | feat/C8-langfuse | +| C9 | Sentry error tracking | CCB | ❌ Missing | P3 | M | docs/pr-plans/C9-sentry.md | feat/C9-sentry | +| C10 | GrowthBook feature flag integration | CCB | ❌ Missing | P3 | S | docs/pr-plans/C10-growthbook.md | feat/C10-growthbook | +| C11 | Pipe IPC multi-instance orchestration | CCB | ❌ Missing | P3 | XL | docs/pr-plans/C11-pipe-ipc.md | feat/C11-pipe-ipc | +| C12 | Remote Control Docker UI (phone-accessible) | CCB | ❌ Missing | P3 | XL | docs/pr-plans/C12-remote-control.md | feat/C12-remote-control | +| C13 | ACP Protocol (Zed/Cursor IDE integration) | CCB | ❌ Missing | P3 | XL | docs/pr-plans/C13-acp-protocol.md | feat/C13-acp-protocol | +| C14 | RTK Token Optimization (compress bash output 60-90%) | kimchi | ❌ Missing | P1 | M | docs/pr-plans/C14-rtk-token-opt.md | feat/C14-rtk-token-opt | +| C15 | Agent Skills standard (AGENTS.md/.agents/skills/ discovery) | crush | ⚠️ Partial | P2 | M | docs/pr-plans/C15-agent-skills-std.md | feat/C15-agent-skills-std | +| C16 | crushignore (extend .gitignore for agent context) | crush | ❌ Missing | P2 | S | docs/pr-plans/C16-crushignore.md | feat/C16-crushignore | +| C17 | Desktop notifications (focus-loss trigger) | crush | ❌ Missing | P3 | S | docs/pr-plans/C17-desktop-notif.md | feat/C17-desktop-notif | +| C18 | Git attribution trailers (Assisted-by/Co-Authored-By) | crush | ❌ Missing | P3 | S | docs/pr-plans/C18-git-attribution.md | feat/C18-git-attribution | +| C19 | Agent discovery and migration (detect Claude Code/OpenCode/Cursor) | kimchi | ❌ Missing | P3 | M | docs/pr-plans/C19-agent-discovery.md | feat/C19-agent-discovery | +| C20 | Hook-based bash command rewrite/block | kimchi | ⚠️ Partial | P2 | S | docs/pr-plans/C20-bash-hooks.md | feat/C20-bash-hooks | + +## Section D β€” Multi-Agent Orchestration (from oh-my-openagent, codebuff, kimchi, qwen-code) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| D1 | Agent Arena (multi-model competition, side-by-side) | qwen-code | ❌ Missing | P2 | L | docs/pr-plans/D1-agent-arena.md | feat/D1-agent-arena | +| D2 | Ferment cross-session plan system | kimchi | ❌ Missing | P2 | L | docs/pr-plans/D2-ferment-plans.md | feat/D2-ferment-plans | +| D3 | 4-agent pipeline (File Picker β†’ Planner β†’ Editor β†’ Reviewer) | codebuff | ⚠️ Partial | P1 | L | docs/pr-plans/D3-4agent-pipeline.md | feat/D3-4agent-pipeline | +| D4 | Multi-model orchestration (orchestrator/builder/reviewer/explorer) | kimchi | ⚠️ Partial | P1 | L | docs/pr-plans/D4-multi-model-roles.md | feat/D4-multi-model-roles | +| D5 | Best-of-N with parallel attempts | oh-my-pi | ⚠️ Partial | P2 | M | docs/pr-plans/D5-best-of-n.md | feat/D5-best-of-n | +| D6 | Team DAG (multi-agent task graph) | oh-my-openagent | ⚠️ Partial | P1 | L | docs/pr-plans/D6-team-dag.md | feat/D6-team-dag | + +## Section E β€” Session/Persistence (from pi-agent-rust, kimchi, crush) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| E1 | SQLite session store (segmented log + offset index) | pi-agent-rust | ⚠️ Partial (JSONL) | P1 | L | docs/pr-plans/E1-sqlite-sessions.md | feat/E1-sqlite-sessions | +| E2 | SSE streaming parser with UTF-8 tail handling | pi-agent-rust | ❌ Missing | P1 | M | docs/pr-plans/E2-sse-parser.md | feat/E2-sse-parser | +| E3 | Shared multi-client sessions (workspace) | crush | ❌ Missing | P2 | L | docs/pr-plans/E3-shared-sessions.md | feat/E3-shared-sessions | +| E4 | Remote teleport (spawn/detach/reattach workers) | kimchi | ❌ Missing | P3 | XL | docs/pr-plans/E4-remote-teleport.md | feat/E4-remote-teleport | +| E5 | Session memory topology graph | jcode-native | ⚠️ Partial | P2 | M | docs/pr-plans/E5-session-topology.md | feat/E5-session-topology | + +## Section F β€” Workflow Pipeline (from gajae-code, kimchi) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| F1 | Workflow pipeline: deep-interview β†’ ralplan β†’ ultragoal | gajae-code | ⚠️ Partial | P1 | L | docs/pr-plans/F1-workflow-pipeline.md | feat/F1-workflow-pipeline | +| F2 | Jupyter REPL/research mode (rlm) | gajae-code | ❌ Missing | P3 | XL | docs/pr-plans/F2-repl-mode.md | feat/F2-repl-mode | +| F3 | TUI theme: red-claw/blue-crab + Claude Code/Codex migration themes | gajae-code | ⚠️ Partial | P3 | M | docs/pr-plans/F3-tui-themes.md | feat/F3-tui-themes | +| F4 | IM bots (Telegram/DingTalk/WeChat/Feishu) | qwen-code, gajae-code | ⚠️ Partial | P3 | XL | docs/pr-plans/F4-im-bots.md | feat/F4-im-bots | + +## Section G β€” TUI (from opencode, crush, kimchi) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| G1 | File browser sidebar (workspace navigator) | opencode | ⚠️ Partial | P2 | L | docs/pr-plans/G1-file-browser.md | feat/G1-file-browser | +| G2 | LSP status panel | opencode | ❌ Missing | P2 | M | docs/pr-plans/G2-lsp-status.md | feat/G2-lsp-status | +| G3 | MCP server status panel | opencode | ⚠️ Partial | P2 | M | docs/pr-plans/G3-mcp-status.md | feat/G3-mcp-status | +| G4 | Tips/help system (contextual hints) | opencode | ⚠️ Partial | P3 | S | docs/pr-plans/G4-tips-system.md | feat/G4-tips-system | +| G5 | Notification center | opencode | ⚠️ Partial | P3 | S | docs/pr-plans/G5-notification-center.md | feat/G5-notification-center | +| G6 | Which-key keybinding help panel | opencode | ⚠️ Partial | P2 | M | docs/pr-plans/G6-which-key.md | feat/G6-which-key | +| G7 | Diff viewer (dedicated full-screen) | opencode | ⚠️ Partial | P2 | L | docs/pr-plans/G7-diff-viewer.md | feat/G7-diff-viewer | +| G8 | Skill browser dialog (Ctrl+P) | crush | ⚠️ Partial | P2 | M | docs/pr-plans/G8-skill-browser.md | feat/G8-skill-browser | + +## Section H β€” Security (from pi-agent-rust, codex, CCB) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| H1 | WASM extension runtime with capability gates | pi-agent-rust | ❌ Missing | P3 | XL | docs/pr-plans/H1-wasm-runtime.md | feat/H1-wasm-runtime | +| H2 | Hostcall trust lifecycle (pending β†’ acknowledged β†’ trusted β†’ killed) | pi-agent-rust | ❌ Missing | P3 | L | docs/pr-plans/H2-hostcall-trust.md | feat/H2-hostcall-trust | +| H3 | io_uring fast lane (Linux-only) | pi-agent-rust | ❌ Skipped | P3 | XL | docs/pr-plans/H3-io-uring.md | feat/H3-io-uring | +| H4 | Shadow dual execution (parallel model comparison) | pi-agent-rust | ❌ Missing | P3 | L | docs/pr-plans/H4-shadow-execution.md | feat/H4-shadow-execution | + +## Section I β€” Benchmarking/Eval (from oh-my-pi, codebuff, pi-agent-rust) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| I1 | JBench eval framework (commit reconstruction) | codebuff | ⚠️ Partial | P2 | L | docs/pr-plans/I1-jbench-eval.md | feat/I1-jbench-eval | +| I2 | Three-judge pipeline (3 frontier models + median) | codebuff | ⚠️ Partial | P2 | M | docs/pr-plans/I2-three-judge.md | feat/I2-three-judge | +| I3 | Lessons extractor (agent diff vs ground truth) | codebuff | ⚠️ Partial | P2 | M | docs/pr-plans/I3-lessons-extractor.md | feat/I3-lessons-extractor | + +## Section J β€” Polish & Ecosystem (from CCB, crush, kimchi) + +| # | Feature | Source | Status | Pri | Effort | Plan File | Branch | +|---|---------|--------|--------|-----|--------|-----------|--------| +| J1 | First-wins flag policy (shared workspaces) | crush | ❌ Missing | P3 | S | docs/pr-plans/J1-first-wins-flag.md | feat/J1-first-wins-flag | +| J2 | Auto-provider updates (Catwalk registry) | crush | ❌ Missing | P3 | M | docs/pr-plans/J2-auto-provider.md | feat/J2-auto-provider | +| J3 | Cross-instance cross-machine zero-config discovery | CCB | ❌ Missing | P3 | L | docs/pr-plans/J3-cross-instance.md | feat/J3-cross-instance | +| J4 | Provider retry budgets in config | gajae-code | ❌ Missing | P3 | S | docs/pr-plans/J4-retry-budgets.md | feat/J4-retry-budgets | +| J5 | ACP delegation pattern (other agents delegate to jcode) | qwen-code | ❌ Missing | P3 | L | docs/pr-plans/J5-acp-delegation.md | feat/J5-acp-delegation | + +--- + +## Backlog Statistics + +- **Total features identified**: ~80 across 10 sections +- **P0 (critical)**: ~7 features +- **P1 (high)**: ~25 features +- **P2 (medium)**: ~30 features +- **P3 (low/niche)**: ~18 features + +## Execution Order (suggested by dependency + priority) + +**Phase 1 β€” Foundation (P0, weeks 1-2)**: +A1 β†’ A2 β†’ A3 β†’ A4 β†’ A5 β†’ B1 + +**Phase 2 β€” Core Ecosystem (P1, weeks 3-6)**: +A6 β†’ A7 β†’ A8 β†’ A9 β†’ A10 β†’ B2 β†’ B3 β†’ C2 β†’ C3 β†’ C14 β†’ D3 β†’ D4 β†’ D6 β†’ E1 β†’ E2 β†’ F1 + +**Phase 3 β€” Polish (P1-P2, weeks 7-10)**: +A11 β†’ A12 β†’ A16 β†’ A17 β†’ B4 β†’ B7 β†’ C4 β†’ C6 β†’ C15 β†’ C16 β†’ C20 β†’ D5 β†’ G1 β†’ G2 β†’ G3 β†’ G6 β†’ G7 β†’ G8 + +**Phase 4 β€” Long Tail (P2-P3, weeks 11+)**: +Remaining P2/P3 items, prioritized by user demand. + +--- + +## Per-PR Plan File Template + +Each `docs/pr-plans/-.md` must contain: + +```markdown +# PR Plan: + +## Research Summary +- Source repo(s): +- Key files inspected: +- Direct code links: + +## Why This Feature Is Missing in jcode +- Gap analysis from PARITY.md Β§XIV +- Code path that should exist but doesn't + +## Alternatives Considered +| Approach | Source Repo | Pros | Cons | Decision | +|----------|-------------|------|------|----------| +| ... | ... | ... | ... | ... | + +## Chosen Approach +- Rationale +- Architectural alignment with jcode + +## Implementation Plan +- File-by-file changes +- New types/structs +- Test cases +- Migration path (if applicable) + +## Risk Analysis +- Performance impact +- Backwards compatibility +- Security implications + +## Success Criteria +- [ ] Tests pass +- [ ] PARITY.md updated +- [ ] Docs updated +- [ ] Manual verification command listed +``` \ No newline at end of file diff --git a/docs/pr-plans/A8-failover-walker.md b/docs/pr-plans/A8-failover-walker.md new file mode 100644 index 000000000..9aa5a49af --- /dev/null +++ b/docs/pr-plans/A8-failover-walker.md @@ -0,0 +1,86 @@ +# PR Plan: A8 β€” Reactive Failover Walker + +## Research Summary +- **Source repo**: oh-my-openagent (`packages/omo-opencode/src/hooks/runtime-fallback/`, ~55 files) +- **Key files inspected**: + - `types.ts` β€” `FallbackState`, `FallbackConfig`, `AutoRetryDispatch`, walker types + - `fallback-state.ts` β€” state machine for fallback lifecycle + - `auto-retry-dispatch.ts` β€” orchestrator that catches streaming errors, aborts, picks fallback, resends + - `fallback-retry-dispatcher.ts` β€” dispatcher logic for retry with cooldowns + +## Why This Feature Is Missing in jcode +jcode already has the **building blocks**: +- `failover.rs` β€” 997 lines: `classify_failover_error_message_structured()` with 30+ error codes, `FailoverDecision` enum +- `fallback_pick.rs` β€” `pick_next_fallback_route()` with 3-tier ranking + +But **no runtime orchestration** β€” no state machine that: +1. Catches a provider error mid-stream +2. Classifies it β†’ decides to fail over +3. Aborts the current request internally +4. Picks the next available model from the fallback chain +5. Tracks per-session cooldowns and retry counts + +This is what oh-my-openagent's `runtime-fallback/` module does and what this PR adds. + +## Alternatives Considered +| Approach | Source Repo | Pros | Cons | Decision | +|----------|-------------|------|------|----------| +| Full state machine + walker | oh-my-openagent | Composable, handles all cases, reuse existing error classifier | More code initially | βœ… Chosen β€” matches existing `failover.rs` pattern | +| Inline retry in provider | oh-my-pi | Simpler, less code | No session tracking, no cooldowns, no equivalence check | Rejected β€” too limited | +| Error handler callbacks | claude-code | Flexible | No state machine means caller must do everything | Rejected β€” pushes complexity up | + +## Chosen Approach +Add `failover_walker.rs` to `jcode-provider-core` that composes: +1. `classify_failover_error_message_structured` for error classification +2. `pick_next_fallback_route` for route selection +3. New `ReactiveFailoverWalker` struct with per-session state tracking + +## Implementation Plan +**New file:** `crates/jcode-provider-core/src/failover_walker.rs` + +Types: +- `WalkState` β€” per-session state (original_model, current_model, fallback_index, failed_models with cooldowns, attempt_count) +- `PreparedFallback` β€” result of fallback preparation +- `WalkResult` β€” result of walking a failover (should_failover, new_model, decision, error_code, message) +- `ReactiveFailoverWalker` β€” main struct with methods: + - `register_session`, `unregister_session`, `get_state` + - `record_failure`, `record_success` β€” cooldown management + - `is_model_in_cooldown` β€” check if a model is cooling down + - `find_next_available_fallback` β€” walk fallback chain respecting cooldowns + equivalence + - `prepare_fallback` β€” pick next candidate with max-attempts check + - `walk_failover` β€” **main entry point**: classify β†’ decide β†’ pick β†’ update state β†’ return result +- `is_internally_aborted` / `mark_internally_aborted` β€” for consumers to know if a failover was triggered + +**Edit:** `crates/jcode-provider-core/src/lib.rs` β€” add `pub mod failover_walker;` + +## Test Cases +1. Session lifecycle (register β†’ get_state β†’ unregister) +2. Cooldown tracking (record_failure β†’ is_model_in_cooldown β†’ record_success β†’ no longer cooldown) +3. Fallback skips equivalent models +4. Fallback skips cooldown models +5. Max attempts reached +6. walk_failover for rate-limited (picks fallback) +7. walk_failover for context length (retries β€” RetryNextProvider) +8. walk_failover internally aborted tracking +9. walk_failover no routes available +10. models_equivalent helper +11. unregister cleans internally_aborted set + +## Risk Analysis +- **Performance**: HashMap lookups per failover event β€” trivial, not in hot path +- **Compatibility**: New module, no existing code changed +- **Security**: No external input parsing +- **Thread safety**: Not yet β€” see Future Work + +## Future Work (out of scope for this PR) +- Thread safety (Arc> or crossbeam for concurrent access) +- Persistent cooldown storage across restarts +- Integration with `app-core` streaming loop (the hook point) +- Metrics/telemetry for failover events + +## Success Criteria +- [x] cargo check -p jcode-provider-core passes +- [x] cargo test -p jcode-provider-core failover_walker passes (11 tests) +- [ ] PARITY.md updated +- [ ] docs/PR_BACKLOG.md updated +- [ ] Branch pushed and PR opened