feat: [ENG-2497] Daemon bootstrap wiring + per-project store factory …#567
feat: [ENG-2497] Daemon bootstrap wiring + per-project store factory …#567bao-byterover merged 1 commit intoproj/persis-task-historyfrom
Conversation
|
Claude finished @bao-byterover's task in 2m 21s —— View job PR Review: feat [ENG-2497] Daemon bootstrap wiring + per-project store factory
SummaryScore: 4 / 5 Solid milestone delivery. The module-scope singleton cache is the right pattern for a single-instance daemon, the orphan-detection logic is correct, and the Four things worth addressing before merge: Issues
What's working well
|
| const stores = new Map<string, FileTaskHistoryStore>() | ||
| const auditedProjects = new Set<string>() | ||
|
|
||
| /** Optional logger override for tests — when set, audit triggered inside getStore uses this. */ |
There was a problem hiding this comment.
suggestion (code-smell): The testLoggerForGetStore module-scope mutable leaks test infrastructure into production code. A cleaner alternative is to make getStore accept an optional log override at call-site, or to export a factory that accepts the logger — but that changes the public API. At minimum, wrap the export in a /* @internal */ marker or consolidate the two test-seam exports into one _setTestOverrides({log}) object so the surface area is explicit.
| stores.set(projectPath, store) | ||
| } | ||
|
|
||
| if (!auditedProjects.has(projectPath)) { |
There was a problem hiding this comment.
issue (reliability): The project is added to auditedProjects before the audit promise settles. If the audit fails (e.g. a transient I/O error), the project is permanently marked as audited and will never be retried — even after a daemon restart would normally re-trigger it. Since this is declared "best-effort" that's intentional, but it should be documented explicitly here (one line is enough) so future readers don't treat it as a bug.
| * orphans. Best-effort: never throws to caller. The `log` parameter defaults | ||
| * to `processLog` for production; tests inject a stub. | ||
| */ | ||
| export async function auditTaskHistory( |
There was a problem hiding this comment.
suggestion (simplification): The double-default for log (undefined = undefined → ?? processLog) is redundant. You can drop the explicit undefined default entirely and let callers pass undefined directly — or just default to processLog directly:
| export async function auditTaskHistory( | |
| export async function auditTaskHistory( | |
| projectPath: string, | |
| store: FileTaskHistoryStore, | |
| log: (msg: string) => void = processLog, | |
| ): Promise<void> { | |
| const effectiveLog = log |
This removes the intermediate effectiveLog alias too, reducing noise.
| const FILENAME_PATTERN = /^tsk-(.+)\.json$/ | ||
| const MAX_LISTED_ORPHANS = 5 | ||
|
|
||
| const stores = new Map<string, FileTaskHistoryStore>() |
There was a problem hiding this comment.
nitpick (memory): stores and auditedProjects grow without bound for the lifetime of the daemon process. For a daemon that serves many short-lived projects this is fine; if a project is ever un-registered, the store stays cached. Worth a comment noting that eviction is intentionally deferred (M2.09 compaction, or a future cleanup pass) so readers don't add premature eviction logic.
| getStore(projectPath) | ||
|
|
||
| // Wait for the fire-and-forget audit microtasks to settle. | ||
| await new Promise((resolve) => { |
There was a problem hiding this comment.
issue (reliability): setTimeout(30) to drain the fire-and-forget audit is fragile — on a loaded CI runner or a slow fs the 30 ms window can expire before the audit logs. Consider using a more deterministic flush, e.g. yielding with await new Promise(setImmediate) twice (one tick for the .catch continuation, one for readdir), or exposing a testable auditPromise from the first getStore call. Fix this →
|
|
||
| // Build a store directly against the same path resolution so audit's | ||
| // `readdir(<resolved>/task-history/data)` finds files we save here. | ||
| const {getProjectDataDir} = await import('../../../../src/server/utils/path-utils.js') |
There was a problem hiding this comment.
nitpick (test hygiene): The dynamic await import(...) inside beforeEach works because ES module specifiers are cached, but it's cleaner — and slightly faster — to hoist the import to the top of the file as a static import. The BRV_DATA_DIR env override takes effect at call time inside getProjectDataDir, not at module load time, so static import is safe here.
No description provided.