read
Filter and list backlog work items by status or priority, or view the complete content of a specific topic. Age information is included automatically.
Instructions
Read-only access to backlog items - list and view backlog work items
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| topic | No | Topic name to fetch a single backlog item with full content | |
| status | No | Status filter for list operation | |
| priority | No | Priority filter for list operation | |
| showAge | No | Include age information (default: true) |
Implementation Reference
- src/index.ts:784-810 (registration)Registration of the 'read' tool with name, description, and inputSchema (topic, status, priority, showAge). Dispatches to handleBacklogRead via CallToolRequestSchema on line 1005-1006.
{ name: "read", description: "Read-only access to backlog items - list and view backlog work items", inputSchema: { type: "object", properties: { topic: { type: "string", description: "Topic name to fetch a single backlog item with full content", }, status: { type: "string", enum: ["new", "ready", "review", "done", "reopen", "wontfix"], description: "Status filter for list operation", }, priority: { type: "string", enum: ["high", "medium", "low"], description: "Priority filter for list operation", }, showAge: { type: "boolean", description: "Include age information (default: true)", }, }, }, }, - src/index.ts:56-91 (handler)Main handler function handleBacklogRead: fetches a single backlog item by topic with full details (topic, priority, status, version, created, agent, session, description, filepath, age) or lists all items via listBacklogItems.
// Backlog Read Handler async function handleBacklogRead(args: any) { const { topic, showAge = true } = args; // If topic is provided, fetch single item if (topic) { const item = await getBacklogItem(topic); if (!item) { return `Backlog item not found: ${topic}`; } // Return full item details including description const result: any = { topic: item.topic, priority: item.priority, status: item.status, version: item.version, created: item.created, agent: item.agent, session: item.session, description: item.description, filepath: item.filepath }; if (showAge) { result.age = formatBacklogAge(item.created); result.isStale = isBacklogStale(item.created); } return JSON.stringify(result, null, 2); } // Otherwise, list items return await listBacklogItems(args); } - src/backlog-read.ts:5-88 (handler)Alternate implementation of the 'read' tool using @opencode-ai/plugin pattern. Same logic: fetch single item by topic or list all items with age formatting.
export default tool({ description: "Read-only access to backlog items - list and view backlog work items", args: { topic: tool.schema .string() .optional() .describe("Topic name to fetch a single backlog item with full content"), status: tool.schema .enum(["new", "ready", "review", "done", "reopen", "wontfix"]) .optional() .describe("Filter backlog items by status"), priority: tool.schema .enum(["high", "medium", "low"]) .optional() .describe("Filter backlog items by priority"), showAge: tool.schema .boolean() .optional() .describe("Include age information (default: true)"), }, async execute(args, context) { const { topic, status, priority, showAge = true } = args; // If topic is provided, fetch single item if (topic) { const item = await getBacklogItem(topic); if (!item) { return `Backlog item not found: ${topic}`; } // Return full item details including description const result = { topic: item.topic, priority: item.priority, status: item.status, version: item.version, created: item.created, agent: item.agent, session: item.session, description: item.description, filepath: item.filepath }; if (showAge) { result['age'] = formatBacklogAge(item.created); result['isStale'] = isBacklogStale(item.created); } return JSON.stringify(result, null, 2); } // Otherwise, list items const items = await listBacklogItems(status, priority); if (items.length === 0) { return "No backlog items found"; } // Enhance items with age information const enhancedItems = items.map(item => { if (!showAge) return item; return { ...item, age: formatBacklogAge(item.created), isStale: isBacklogStale(item.created) }; }); const columns = [ { key: 'topic' }, { key: 'priority' }, { key: 'status' }, ]; if (showAge) { columns.push({ key: 'age' }, { key: 'isStale' }); } return format(enhancedItems, { columns }); } }); - lib/backlog-shared.ts:344-412 (helper)getBacklogItem helper: resolves a backlog item by topic across new structure, legacy structure, and completed backlog, returning full item data with description.
export async function getBacklogItem(topic: string): Promise<{ topic: string; priority: string; status: string; version: number; created: string; agent: string; session: string; description: string; filepath: string; } | null> { const filename = generateBacklogFilename(topic); const dirpath = getBacklogDir(); const completedPath = getCompletedBacklogDir(); // Check new structure first const newStructurePath = `${dirpath}/${filename}/item.md`; const legacyPath = `${dirpath}/${filename}.md`; const completedPattern = `${completedPath}/${filename}`; let actualPath: string | null = null; // Check active backlog (new structure) if (await fileExists(newStructurePath)) { actualPath = newStructurePath; } // Check active backlog (legacy structure) else if (await fileExists(legacyPath)) { actualPath = legacyPath; } // Check completed backlog (versioned files) else { try { const completedFiles = readdirSync(completedPath); const versionedFile = completedFiles.find(f => f.startsWith(`${filename}-v`) && f.endsWith('.md') ); if (versionedFile) { actualPath = `${completedPath}/${versionedFile}`; } } catch (e) { // Completed directory doesn't exist or is empty } } if (!actualPath) { return null; } // Parse the file const metadata = await parseBacklogFile(actualPath); const fileData = await readBacklogFile(actualPath); // Extract description from body const descriptionMatch = fileData.body.match(/## Description\s+([\s\S]*?)(?=\n---|\n##|$)/); const description = descriptionMatch ? descriptionMatch[1].trim() : ''; return { topic: metadata.topic, priority: metadata.priority, status: metadata.status, version: metadata.version, created: metadata.created, agent: metadata.agent, session: metadata.session, description, filepath: actualPath }; } - lib/backlog-shared.ts:269-316 (helper)listBacklogItems helper: lists all backlog items from active and completed directories with optional status/priority filtering.
export async function listBacklogItems(statusFilter?: string, priorityFilter?: string) { const items = []; const dirs = [ { path: getBacklogDir(), isSubdir: true }, { path: getCompletedBacklogDir(), isSubdir: false } ]; for (const dir of dirs) { try { const entries = readdirSync(dir.path); for (const entry of entries) { let filepath: string; if (dir.isSubdir) { // New structure: .agent/Backlog/<topic>/item.md const itemPath = `${dir.path}/${entry}/item.md`; if (await fileExists(itemPath)) { filepath = itemPath; } else { // Legacy structure: .agent/Backlog/<topic>.md if (entry.endsWith('.md')) { filepath = `${dir.path}/${entry}`; } else { continue; } } } else { // Completed items are always flat .md files if (!entry.endsWith('.md')) continue; filepath = `${dir.path}/${entry}`; } const data = await parseBacklogFile(filepath); // Apply filters if (statusFilter && data.status !== statusFilter) continue; if (priorityFilter && data.priority !== priorityFilter) continue; items.push(data); } } catch (e) { // Directory doesn't exist or is empty } } return items; }