// backlog.el — Work item management subsystem. // // The backlog is Neuron's task queue and project roadmap in one. Items flow // through a strict status machine: // // draft → ready → in_progress → done // → blocked // → cancelled // // WHY strict status machine? // Without guardrails, backlogs become unactionable lists of wishes. // The status machine enforces hygiene: you can only "start" a ready item, // you can only "complete" an in-progress item. This keeps the backlog // trustworthy as a source of truth. // // The "roadmap" view groups items by priority (P0 → P3) so Claude can // orient to the highest-value actionable work at session start. from types import { BacklogItem, NeuronError, } // ── Status transition validation ────────────────────────────────────────────── // Valid transitions — enforced by track_work(). // Any other transition is a NeuronError::InvalidInput. // // start : ready → in_progress // complete: in_progress → done // block : in_progress → blocked // unblock : blocked → ready // cancel : * → cancelled (any status can be cancelled) // ready : draft → ready (promote from draft) @accessor fn valid_transition(current_status: String, action: String) -> Result { if action == "start" { if current_status == "ready" { return Ok("in_progress") } return Err(NeuronError::InvalidInput("can only start a ready item")) } if action == "complete" { if current_status == "in_progress" { return Ok("done") } return Err(NeuronError::InvalidInput("can only complete an in_progress item")) } if action == "block" { if current_status == "in_progress" { return Ok("blocked") } return Err(NeuronError::InvalidInput("can only block an in_progress item")) } if action == "unblock" { if current_status == "blocked" { return Ok("ready") } return Err(NeuronError::InvalidInput("can only unblock a blocked item")) } if action == "ready" { if current_status == "draft" { return Ok("ready") } return Err(NeuronError::InvalidInput("can only promote a draft to ready")) } if action == "cancel" { // Any status can be cancelled. return Ok("cancelled") } Err(NeuronError::InvalidInput("unknown action")) } // ── Public API ──────────────────────────────────────────────────────────────── // plan_work — create a new backlog item. // // New items start in "draft" status. Use track_work(action="ready") to // promote them to the actionable queue. This two-step prevents half-baked // ideas from polluting the ready queue. @manager fn plan_work( title: String, description: String, item_type: String, priority: String, project: String, tags: [String], depends_on: [String], ) -> Result { let id = native_uuid() let now = native_now() let item = BacklogItem { id: id, title: title, description: description, item_type: item_type, priority: priority, status: "draft", project: project, tags: tags, depends_on: depends_on, created_at: now, updated_at: now, } native_store_backlog_item(item)? native_emit("backlog.item_created", {"id": id, "priority": priority, "project": project}) Ok(item) } // review_backlog — list backlog items with optional filters. // // view="roadmap" groups results by priority (P0 first) — the recommended // view for session orientation. Without a view, returns a flat list. @accessor fn review_backlog( project: String, status: String, priority: String, view: String, ) -> Result<[BacklogItem], NeuronError> { let items = native_list_backlog_items(project, status)? // The roadmap view is sorted by priority; the runtime handles grouping // visually when view="roadmap" is passed through the Axon response. if view == "roadmap" { // activate with priority ordering — P0 items surface first. let ordered = activate BacklogItem where "project:{project} status:{status} priority:{priority} order:priority" return Ok(ordered) } Ok(items) } // get_backlog_item — retrieve one backlog item by ID. @accessor fn get_backlog_item(id: String) -> Result { let item = native_get_backlog_item(id)? Ok(item) } // track_work — transition a backlog item's status. // // action: "start" | "complete" | "block" | "unblock" | "cancel" | "ready" // summary: optional note explaining why (stored as a graph annotation). @manager fn track_work( item_id: String, action: String, summary: String, ) -> Result { let item = native_get_backlog_item(item_id)? let new_status = valid_transition(item.status, action)? let now = native_now() let updated = BacklogItem { id: item.id, title: item.title, description: item.description, item_type: item.item_type, priority: item.priority, status: new_status, project: item.project, tags: item.tags, depends_on: item.depends_on, created_at: item.created_at, updated_at: now, } native_update_backlog_item(updated)? native_emit("backlog.item_transitioned", { "id": item_id, "action": action, "new_status": new_status, "summary": summary, }) Ok(updated) }