beadboard/src/lib/read-issues.ts
zenchantlive ce4700849b Fix: Security, reliability, and code quality improvements from PR review
Critical Security Fixes:
- Fix command injection vulnerability in Windows shims (beadboard.cmd, bb.cmd)
  - Added path validation to block traversal (.. and root-relative paths)
  - Added quotes around env var to prevent command injection

Reliability Fixes:
- Fix agent cache null safety bug
  - Fixed callBdAgentShow() to check for cache misses (null check, expiration)
  - Fixed getCachedAgent to properly return entry.data or null
- Fix null body crashes in mail ack route
  - Added null check before casting body to object
  - Returns 400 error instead of 500 for invalid requests

BD Compliance Fixes:
- Fix read-issues to use BD audit record path
  - Ensures all writes go through bd audit record
  - Maintains watcher/SSE parity and Dolt commit tracking

Code Quality Fixes:
- Fix path canonicalization violations
  - Use canonicalizeWindowsPath() and windowsPathKey() from pathing module
  - Prevents Windows edge cases and ensures machine-reproducible paths
- Fix typo: mobile-fronted → mobile-frontend
- Pin GitHub Actions tags
  - softprops/action-gh-release@v1 → specific commit hash
- Register pr14 test in package.json (already registered)

Testing:
- Refactor broad exception handlers in Python scripts
  - Replace except Exception: with specific exceptions
  - Allows KeyboardInterrupt and SystemExit to propagate correctly
  - All tests passing
2026-03-05 16:33:10 -08:00

67 lines
2.2 KiB
TypeScript

import path from 'node:path';
import { buildProjectContext } from './project-context';
import { readIssuesViaDolt } from './read-issues-dolt';
import type { BeadIssueWithProject, ProjectSource } from './types';
export interface ReadIssuesOptions {
projectRoot?: string;
includeTombstones?: boolean;
projectSource?: ProjectSource;
projectAddedAt?: string | null;
preferBd?: boolean;
skipAgentFilter?: boolean;
}
export function resolveIssuesJsonlPathCandidates(projectRoot: string = process.cwd()): string[] {
const baseDir = path.resolve(projectRoot, '.beads');
return [
path.join(baseDir, 'issues.jsonl'),
path.join(baseDir, 'issues.jsonl.new'),
];
}
export function resolveIssuesJsonlPath(projectRoot: string = process.cwd()): string {
return resolveIssuesJsonlPathCandidates(projectRoot)[0];
}
/**
* Write issues to disk using BD audit record when available.
* This ensures all writes go through the BD audit system for watcher/SSE parity.
*/
export async function writeIssuesToDisk(
issues: BeadIssueWithProject[],
options: ReadIssuesOptions = {}
): Promise<void> {
const projectRoot = options.projectRoot ?? process.cwd();
const issuesJson = JSON.stringify(issues, null, 2);
try {
const { execFileSync } = await import('child_process');
execFileSync('bd', ['audit', 'record', '--stdin'], {
input: issuesJson,
stdio: ['pipe', 'pipe', 'pipe'],
});
} catch {
const issuesPath = resolveIssuesJsonlPath(projectRoot);
const { writeFile } = await import('node:fs/promises');
await writeFile(issuesPath, issuesJson, 'utf8');
}
}
export async function readIssuesFromDisk(options: ReadIssuesOptions = {}): Promise<BeadIssueWithProject[]> {
const projectRoot = options.projectRoot ?? process.cwd();
const project = buildProjectContext(projectRoot, {
source: options.projectSource ?? 'local',
addedAt: options.projectAddedAt ?? null,
});
// Dolt-only: throw if unreachable
const viaDolt = await readIssuesViaDolt(projectRoot, options);
if (viaDolt !== null) {
return viaDolt.map((issue) => ({ ...issue, project }));
}
// No fallback - fail fast to indicate Dolt is not running
throw new Error('Dolt unreachable - ensure Dolt is running: bd dolt start');
}