feat: attach project context to read issues

This commit is contained in:
zenchantlive 2026-02-11 19:44:55 -08:00
parent 0b127b5404
commit fb3f6c3e55
2 changed files with 22 additions and 5 deletions

View file

@ -3,11 +3,14 @@ import path from 'node:path';
import { parseIssuesJsonl } from './parser'; import { parseIssuesJsonl } from './parser';
import { canonicalizeWindowsPath } from './pathing'; import { canonicalizeWindowsPath } from './pathing';
import type { BeadIssue } from './types'; import { buildProjectContext } from './project-context';
import type { BeadIssueWithProject, ProjectSource } from './types';
export interface ReadIssuesOptions { export interface ReadIssuesOptions {
projectRoot?: string; projectRoot?: string;
includeTombstones?: boolean; includeTombstones?: boolean;
projectSource?: ProjectSource;
projectAddedAt?: string | null;
} }
export function resolveIssuesJsonlPathCandidates(projectRoot: string = process.cwd()): string[] { export function resolveIssuesJsonlPathCandidates(projectRoot: string = process.cwd()): string[] {
@ -21,15 +24,23 @@ export function resolveIssuesJsonlPath(projectRoot: string = process.cwd()): str
return resolveIssuesJsonlPathCandidates(projectRoot)[0]; return resolveIssuesJsonlPathCandidates(projectRoot)[0];
} }
export async function readIssuesFromDisk(options: ReadIssuesOptions = {}): Promise<BeadIssue[]> { export async function readIssuesFromDisk(options: ReadIssuesOptions = {}): Promise<BeadIssueWithProject[]> {
const candidates = resolveIssuesJsonlPathCandidates(options.projectRoot); const projectRoot = options.projectRoot ?? process.cwd();
const candidates = resolveIssuesJsonlPathCandidates(projectRoot);
const project = buildProjectContext(projectRoot, {
source: options.projectSource ?? 'local',
addedAt: options.projectAddedAt ?? null,
});
for (const issuesPath of candidates) { for (const issuesPath of candidates) {
try { try {
const jsonl = await fs.readFile(issuesPath, 'utf8'); const jsonl = await fs.readFile(issuesPath, 'utf8');
return parseIssuesJsonl(jsonl, { return parseIssuesJsonl(jsonl, {
includeTombstones: options.includeTombstones ?? false, includeTombstones: options.includeTombstones ?? false,
}); }).map((issue) => ({
...issue,
project,
}));
} catch (error) { } catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') { if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
continue; continue;

View file

@ -5,7 +5,7 @@ import os from 'node:os';
import path from 'node:path'; import path from 'node:path';
import { readIssuesFromDisk, resolveIssuesJsonlPath, resolveIssuesJsonlPathCandidates } from '../../src/lib/read-issues'; import { readIssuesFromDisk, resolveIssuesJsonlPath, resolveIssuesJsonlPathCandidates } from '../../src/lib/read-issues';
import { sameWindowsPath } from '../../src/lib/pathing'; import { canonicalizeWindowsPath, sameWindowsPath, toDisplayPath, windowsPathKey } from '../../src/lib/pathing';
test('resolveIssuesJsonlPath appends .beads/issues.jsonl using windows-safe pathing', () => { test('resolveIssuesJsonlPath appends .beads/issues.jsonl using windows-safe pathing', () => {
const resolved = resolveIssuesJsonlPath('C:/Repo/Project'); const resolved = resolveIssuesJsonlPath('C:/Repo/Project');
@ -38,6 +38,12 @@ test('readIssuesFromDisk parses JSONL issues from disk', async () => {
assert.equal(issues.length, 1); assert.equal(issues.length, 1);
assert.equal(issues[0].id, 'bb-1'); assert.equal(issues[0].id, 'bb-1');
assert.equal(issues[0].priority, 0); assert.equal(issues[0].priority, 0);
assert.equal(issues[0].project.root, canonicalizeWindowsPath(root));
assert.equal(issues[0].project.key, windowsPathKey(root));
assert.equal(issues[0].project.displayPath, toDisplayPath(root));
assert.equal(issues[0].project.name, path.basename(canonicalizeWindowsPath(root)));
assert.equal(issues[0].project.source, 'local');
assert.equal(issues[0].project.addedAt, null);
}); });
test('readIssuesFromDisk returns empty list when issues file does not exist', async () => { test('readIssuesFromDisk returns empty list when issues file does not exist', async () => {