feat(KB-022): add GitHub issue status badges to task cards

- Extend core types with GitHub issue tracking fields (issueNumber, issueStatus, lastIssueSync)
- Add TaskStore.getTaskGitHubIssueInfo() to fetch issue details from GitHub API
- Create server-side API endpoint for issue status lookup with caching
- Build GitHubBadge component with color-coded status indicators (open/closed)
- Integrate badges into TaskCard with hover state linking to GitHub issues
- Add CSS styles for badge positioning and visual polish
- Include comprehensive tests for GitHubBadge and TaskCard badge rendering
This commit is contained in:
gsxdsm
2026-03-29 20:12:13 -07:00
parent 37afb24d60
commit b935f310ce
12 changed files with 930 additions and 30 deletions

View File

@@ -1206,6 +1206,57 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
});
}
/**
* Update or clear Issue information for a task.
* Updates task.json atomically and emits `task:updated` event.
*
* @param id - The task ID
* @param issueInfo - The Issue info to set, or null to clear
* @returns The updated task
*/
async updateIssueInfo(
id: string,
issueInfo: import("./types.js").IssueInfo | null,
): Promise<Task> {
return this.withTaskLock(id, async () => {
const dir = this.taskDir(id);
const task = await this.readTaskJson(dir);
const prevIssueNumber = task.issueInfo?.number;
const prevIssueState = task.issueInfo?.state;
if (issueInfo) {
task.issueInfo = issueInfo;
task.log.push({
timestamp: new Date().toISOString(),
action: "Issue linked",
outcome: `Issue #${issueInfo.number}: ${issueInfo.url}`,
});
} else {
task.issueInfo = undefined;
if (prevIssueNumber) {
task.log.push({
timestamp: new Date().toISOString(),
action: "Issue unlinked",
outcome: `Issue #${prevIssueNumber} removed`,
});
}
}
task.updatedAt = new Date().toISOString();
await this.atomicWriteTaskJson(dir, task);
if (this.watcher) this.taskCache.set(id, { ...task });
// Only emit if Issue info actually changed
if (prevIssueNumber !== issueInfo?.number || prevIssueState !== issueInfo?.state) {
this.emit("task:updated", task);
}
return task;
});
}
/**
* Read all historical agent log entries for a task from its agent log file.
* Returns entries in chronological order (oldest first).