@ngockhoale/ukit 3.0.11 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +17 -1
  2. package/README.md +1 -0
  3. package/manifests/documentation.yaml +12 -0
  4. package/package.json +1 -1
  5. package/scripts/bench/data-foundation.mjs +368 -50
  6. package/src/cli/commands/doctor.js +232 -3
  7. package/src/cli/commands/feedback.js +64 -1
  8. package/src/cli/commands/install.js +18 -0
  9. package/src/cli/commands/memory.js +42 -37
  10. package/src/cli/commands/telemetry.js +460 -0
  11. package/src/cli/index.js +7 -0
  12. package/src/core/agentRuntime/adapters.js +83 -2
  13. package/src/core/agentRuntime/diagnostics.js +104 -0
  14. package/src/core/agentRuntime/supervisor.js +137 -0
  15. package/src/core/agentRuntime/telemetry.js +204 -0
  16. package/src/core/memory/memoryEmit.js +131 -0
  17. package/src/core/memory/memoryHit.js +1 -1
  18. package/src/core/memory/migrate.js +18 -11
  19. package/src/core/memory/migrateMapping.js +15 -7
  20. package/src/core/memory/mutateMemory.js +22 -4
  21. package/src/core/memory/recordIndex.js +10 -3
  22. package/src/core/memory/recordStore.js +28 -3
  23. package/src/core/memory/retrieval.js +79 -38
  24. package/src/core/memory/store.js +37 -37
  25. package/src/core/memory/storeV2.js +28 -25
  26. package/src/core/memory/storeV2Loader.js +2 -2
  27. package/src/core/observability/adapters/ingest.js +576 -0
  28. package/src/core/observability/analytics/anomalies.js +415 -0
  29. package/src/core/observability/analytics/summary.js +16 -1
  30. package/src/core/observability/emit/config.js +69 -1
  31. package/src/core/observability/emit/crash.js +434 -0
  32. package/src/core/observability/emit/lifecycle.js +349 -0
  33. package/src/core/observability/emit/recorder.js +135 -9
  34. package/src/core/observability/evaluation/aiPacket.js +52 -10
  35. package/src/core/observability/evaluation/outcomes.js +95 -0
  36. package/src/core/observability/evaluation/runner.js +225 -0
  37. package/src/core/observability/privacy/allowlist.js +23 -3
  38. package/src/core/observability/schema/compatibility.js +48 -3
  39. package/src/core/observability/schema/constants.js +5 -0
  40. package/src/core/observability/schema/registry.js +57 -0
  41. package/src/core/observability/schema/validate.js +68 -6
  42. package/src/core/observability/segments/internal.js +42 -8
  43. package/src/core/observability/segments/readSegments.js +35 -1
  44. package/src/core/observability/segments/recovery.js +3 -2
  45. package/src/core/observability/segments/retention.js +137 -33
  46. package/src/core/observability/support/projector.js +88 -18
  47. package/src/core/observability/support/provision.js +160 -0
  48. package/src/core/observability/support/renderer.js +2 -2
  49. package/src/core/observability/support/schedule.js +174 -0
  50. package/template_project/.claude/hooks/auto-allow-bash.sh +7 -1
  51. package/template_project/.claude/hooks/auto-prune-bash.sh +16 -7
  52. package/template_project/.claude/hooks/verification-guard.sh +13 -4
  53. package/template_project/.claude/ukit/runtime/async-lock.mjs +26 -0
@@ -19,9 +19,9 @@ async function storeV2() {
19
19
  return import('./storeV2.js');
20
20
  }
21
21
 
22
- async function v2Active(projectRoot) {
22
+ async function v2Active(projectRoot, { homeDir } = {}) {
23
23
  try {
24
- const config = await loadRuntimeConfig(projectRoot);
24
+ const config = await loadRuntimeConfig(projectRoot, { homeDir });
25
25
  if (config?.memoryV2?.enabled === false) return false;
26
26
  } catch {
27
27
  // unreadable config → default enabled, keep going
@@ -30,7 +30,7 @@ async function v2Active(projectRoot) {
30
30
  const v2 = await storeV2();
31
31
  // loadRecords triggers ensureMigrated (lazy migration) when autoMigrate is
32
32
  // on; records.json existing afterwards — or already — means v2 mode.
33
- await v2.loadRecords(projectRoot);
33
+ await v2.loadRecords(projectRoot, { homeDir });
34
34
  try {
35
35
  await fs.access(buildRuntimePaths(projectRoot).memoryV2RecordsPath);
36
36
  return true;
@@ -271,15 +271,15 @@ function createSearchableText(type, content) {
271
271
  ].filter(Boolean).join('\n');
272
272
  }
273
273
 
274
- export async function exportMemory(projectRoot) {
274
+ export async function exportMemory(projectRoot, { homeDir } = {}) {
275
275
  const runtimePaths = buildRuntimePaths(projectRoot);
276
276
  // FR-023/025: export is a bulk read lane — redact at the store boundary so
277
277
  // every caller (CLI, future lanes) gets defense-in-depth, not just the CLI.
278
- const config = await loadRuntimeConfig(projectRoot);
278
+ const config = await loadRuntimeConfig(projectRoot, { homeDir });
279
279
  const redact = (data) => redactWritePayload({ meta: data }, { config }).payload.meta;
280
- if (await v2Active(projectRoot)) {
280
+ if (await v2Active(projectRoot, { homeDir })) {
281
281
  const v2 = await storeV2();
282
- const items = recordsToMemoryItems(await v2.loadRecords(projectRoot), runtimePaths);
282
+ const items = recordsToMemoryItems(await v2.loadRecords(projectRoot, { homeDir }), runtimePaths);
283
283
  const user = items.find((item) => item.type === 'user')?.content ?? defaultUserMemory();
284
284
  return redact({
285
285
  user,
@@ -314,11 +314,11 @@ async function warnOnCorruptLegacyFiles(runtimePaths) {
314
314
  }
315
315
  }
316
316
 
317
- export async function listMemoryItems(projectRoot) {
317
+ export async function listMemoryItems(projectRoot, { homeDir } = {}) {
318
318
  const runtimePaths = buildRuntimePaths(projectRoot);
319
- if (await v2Active(projectRoot)) {
319
+ if (await v2Active(projectRoot, { homeDir })) {
320
320
  const v2 = await storeV2();
321
- const records = await v2.loadRecords(projectRoot);
321
+ const records = await v2.loadRecords(projectRoot, { homeDir });
322
322
  await warnOnCorruptLegacyFiles(runtimePaths);
323
323
  return recordsToMemoryItems(records, runtimePaths);
324
324
  }
@@ -355,7 +355,7 @@ export async function listMemoryItems(projectRoot) {
355
355
  ];
356
356
  }
357
357
 
358
- async function forgetMemoryItemV2(projectRoot, memoryId, runtimePaths) {
358
+ async function forgetMemoryItemV2(projectRoot, memoryId, runtimePaths, { homeDir } = {}) {
359
359
  const v2 = await storeV2();
360
360
  const { mutateMemory } = await import('./mutateMemory.js');
361
361
  const archiveAll = async (records) => {
@@ -363,7 +363,7 @@ async function forgetMemoryItemV2(projectRoot, memoryId, runtimePaths) {
363
363
  for (const record of records) {
364
364
  if (record.status !== 'archived') {
365
365
  const res = await mutateMemory(
366
- { kind: 'project', projectRoot },
366
+ { kind: 'project', projectRoot, homeDir },
367
367
  { op: 'forget', payload: { id: record.id } },
368
368
  );
369
369
  if (res.status === 'ok') archived += 1;
@@ -373,7 +373,7 @@ async function forgetMemoryItemV2(projectRoot, memoryId, runtimePaths) {
373
373
  };
374
374
 
375
375
  if (memoryId === 'user:user' || memoryId === 'user') {
376
- const userRecords = await v2.queryRecords(projectRoot, { scope: 'user' });
376
+ const userRecords = await v2.queryRecords(projectRoot, { scope: 'user', homeDir });
377
377
  await archiveAll(userRecords);
378
378
  await writeJson(runtimePaths.userMemoryPath, defaultUserMemory());
379
379
  // The reset above always removes user memory — `removed` reports that the
@@ -392,7 +392,7 @@ async function forgetMemoryItemV2(projectRoot, memoryId, runtimePaths) {
392
392
  return { removed: false, type, path: null };
393
393
  }
394
394
 
395
- const records = await v2.loadRecords(projectRoot);
395
+ const records = await v2.loadRecords(projectRoot, { homeDir });
396
396
  const matched = type === 'session'
397
397
  ? records.filter((r) => r.type === 'episode' && (r.meta?.legacyId === rawId || r.id === rawId))
398
398
  : type === 'project'
@@ -420,10 +420,10 @@ async function forgetMemoryItemV2(projectRoot, memoryId, runtimePaths) {
420
420
  return { removed: archived > 0 || legacyRemoved, type: baseDir ? type : null, path: null };
421
421
  }
422
422
 
423
- export async function forgetMemoryItem(projectRoot, memoryId) {
423
+ export async function forgetMemoryItem(projectRoot, memoryId, { homeDir } = {}) {
424
424
  const runtimePaths = buildRuntimePaths(projectRoot);
425
- if (await v2Active(projectRoot)) {
426
- return forgetMemoryItemV2(projectRoot, memoryId, runtimePaths);
425
+ if (await v2Active(projectRoot, { homeDir })) {
426
+ return forgetMemoryItemV2(projectRoot, memoryId, runtimePaths, { homeDir });
427
427
  }
428
428
  if (memoryId === 'user:user' || memoryId === 'user') {
429
429
  await writeJson(runtimePaths.userMemoryPath, defaultUserMemory());
@@ -576,14 +576,14 @@ function patternCandidateFromRecord(record) {
576
576
  };
577
577
  }
578
578
 
579
- async function proposePatternCandidateV2(projectRoot, projectId, candidate) {
579
+ async function proposePatternCandidateV2(projectRoot, projectId, candidate, { homeDir } = {}) {
580
580
  const v2 = await storeV2();
581
581
  const normalizedText = normalizePatternText(candidate?.text);
582
582
  if (!normalizedText) {
583
583
  throw new Error('proposePatternCandidate requires candidate.text');
584
584
  }
585
585
 
586
- const existing = (await v2.queryRecords(projectRoot, { projectId }))
586
+ const existing = (await v2.queryRecords(projectRoot, { projectId, homeDir }))
587
587
  .filter((record) => record.provenance === 'pattern-candidate'
588
588
  || String(record.provenance ?? '').includes('pattern-candidate'))
589
589
  .find((record) => record.meta?.legacyStatus === 'pending'
@@ -612,13 +612,13 @@ async function proposePatternCandidateV2(projectRoot, projectId, candidate) {
612
612
  createdBy: 'propose',
613
613
  projectId,
614
614
  meta: { ...entry, legacyStatus: 'pending' },
615
- });
615
+ }, { homeDir });
616
616
  return entry;
617
617
  }
618
618
 
619
- async function listPendingPatternCandidatesV2(projectRoot, projectId) {
619
+ async function listPendingPatternCandidatesV2(projectRoot, projectId, { homeDir } = {}) {
620
620
  const v2 = await storeV2();
621
- return (await v2.queryRecords(projectRoot, { projectId }))
621
+ return (await v2.queryRecords(projectRoot, { projectId, homeDir }))
622
622
  .filter((record) => record.type === 'derived_fact'
623
623
  && record.status !== 'archived'
624
624
  && (record.meta?.legacyStatus === 'pending'
@@ -628,9 +628,9 @@ async function listPendingPatternCandidatesV2(projectRoot, projectId) {
628
628
  .map(patternCandidateFromRecord);
629
629
  }
630
630
 
631
- async function resolvePatternCandidateV2(projectRoot, projectId, candidateId, decision) {
631
+ async function resolvePatternCandidateV2(projectRoot, projectId, candidateId, decision, { homeDir } = {}) {
632
632
  const v2 = await storeV2();
633
- const records = await v2.queryRecords(projectRoot, { projectId });
633
+ const records = await v2.queryRecords(projectRoot, { projectId, homeDir });
634
634
  // Only candidate records are resolvable — matching every record by meta.id
635
635
  // let a delta-overlay (meta.id 'ov_*') be "approved" into a project_rule or
636
636
  // archived, corrupting the overlay store.
@@ -658,7 +658,7 @@ async function resolvePatternCandidateV2(projectRoot, projectId, candidateId, de
658
658
  // Keep the C11 status field in sync for learning candidates.
659
659
  ...(isLearningCandidate ? { status: 'approved' } : {}),
660
660
  },
661
- });
661
+ }, { homeDir });
662
662
  } else {
663
663
  updated = await v2.updateRecord(projectRoot, target.id, {
664
664
  status: 'archived',
@@ -667,15 +667,15 @@ async function resolvePatternCandidateV2(projectRoot, projectId, candidateId, de
667
667
  legacyStatus: 'rejected',
668
668
  ...(isLearningCandidate ? { status: 'rejected' } : {}),
669
669
  },
670
- });
670
+ }, { homeDir });
671
671
  }
672
672
  return patternCandidateFromRecord(updated ?? target);
673
673
  }
674
674
 
675
- export async function proposePatternCandidate(projectRoot, projectId, candidate) {
675
+ export async function proposePatternCandidate(projectRoot, projectId, candidate, { homeDir } = {}) {
676
676
  const runtimePaths = buildRuntimePaths(projectRoot);
677
- if (await v2Active(projectRoot)) {
678
- const entry = await proposePatternCandidateV2(projectRoot, projectId, candidate);
677
+ if (await v2Active(projectRoot, { homeDir })) {
678
+ const entry = await proposePatternCandidateV2(projectRoot, projectId, candidate, { homeDir });
679
679
  // Mirror into the legacy project file (hygiene + conventions) so the
680
680
  // untouched-on-disk legacy format stays read-compatible.
681
681
  const { filePath, memory } = await readProjectMemoryForId(runtimePaths, projectId);
@@ -719,10 +719,10 @@ export async function proposePatternCandidate(projectRoot, projectId, candidate)
719
719
  return entry;
720
720
  }
721
721
 
722
- export async function listPendingPatternCandidates(projectRoot, projectId) {
722
+ export async function listPendingPatternCandidates(projectRoot, projectId, { homeDir } = {}) {
723
723
  const runtimePaths = buildRuntimePaths(projectRoot);
724
- if (await v2Active(projectRoot)) {
725
- return listPendingPatternCandidatesV2(projectRoot, projectId);
724
+ if (await v2Active(projectRoot, { homeDir })) {
725
+ return listPendingPatternCandidatesV2(projectRoot, projectId, { homeDir });
726
726
  }
727
727
 
728
728
  const { memory } = await readProjectMemoryForId(runtimePaths, projectId);
@@ -730,14 +730,14 @@ export async function listPendingPatternCandidates(projectRoot, projectId) {
730
730
  return patternCandidates.filter((entry) => entry.status === 'pending');
731
731
  }
732
732
 
733
- export async function resolvePatternCandidate(projectRoot, projectId, candidateId, decision) {
733
+ export async function resolvePatternCandidate(projectRoot, projectId, candidateId, decision, { homeDir } = {}) {
734
734
  if (decision !== 'approve' && decision !== 'reject') {
735
735
  throw new Error(`Unknown decision: ${decision}`);
736
736
  }
737
737
 
738
738
  const runtimePaths = buildRuntimePaths(projectRoot);
739
- if (await v2Active(projectRoot)) {
740
- const resolved = await resolvePatternCandidateV2(projectRoot, projectId, candidateId, decision);
739
+ if (await v2Active(projectRoot, { homeDir })) {
740
+ const resolved = await resolvePatternCandidateV2(projectRoot, projectId, candidateId, decision, { homeDir });
741
741
  // Mirror the resolution into the legacy project file.
742
742
  const { filePath, memory } = await readProjectMemoryForId(runtimePaths, projectId);
743
743
  const patternCandidates = Array.isArray(memory.patternCandidates) ? memory.patternCandidates : [];
@@ -781,8 +781,8 @@ export async function resolvePatternCandidate(projectRoot, projectId, candidateI
781
781
  return target;
782
782
  }
783
783
 
784
- export async function countMemoryItems(projectRoot) {
785
- const exported = await exportMemory(projectRoot);
784
+ export async function countMemoryItems(projectRoot, { homeDir } = {}) {
785
+ const exported = await exportMemory(projectRoot, { homeDir });
786
786
  return {
787
787
  projectCount: exported.projects.length,
788
788
  sessionCount: exported.sessions.length,
@@ -26,8 +26,11 @@ const migrationPromises = new Map();
26
26
 
27
27
 
28
28
 
29
- async function ensureMigrated(projectRoot) {
30
- const key = String(projectRoot);
29
+ async function ensureMigrated(projectRoot, { homeDir } = {}) {
30
+ // Keyed on root+home: the registry consulted for alias-collision checks is
31
+ // per-home, so identical roots under different homes must not share an
32
+ // in-flight migration.
33
+ const key = `${String(projectRoot)}::${String(homeDir ?? '')}`;
31
34
  const existing = migrationPromises.get(key);
32
35
  if (existing) {
33
36
  await existing.promise;
@@ -37,7 +40,7 @@ async function ensureMigrated(projectRoot) {
37
40
  const entry = { promise: null, failed: false };
38
41
  entry.promise = (async () => {
39
42
  try {
40
- const config = await loadRuntimeConfig(projectRoot);
43
+ const config = await loadRuntimeConfig(projectRoot, { homeDir });
41
44
  if (config?.memoryV2?.autoMigrate === false) return;
42
45
 
43
46
  const migrate = await import('./migrate.js');
@@ -45,7 +48,7 @@ async function ensureMigrated(projectRoot) {
45
48
  // records.json stranded v1 data forever whenever the file existed
46
49
  // without the marker (a pre-migration write, a crashed first run).
47
50
  if (await migrate.needsMigration(projectRoot)) {
48
- await migrate.runMigration(projectRoot);
51
+ await migrate.runMigration(projectRoot, { homeDir });
49
52
  }
50
53
  } catch {
51
54
  // Lazy migration is best-effort: a failed auto-run must not break reads.
@@ -67,16 +70,16 @@ async function ensureMigrated(projectRoot) {
67
70
  * 'migrating' (auto-migration in-flight or failed), otherwise the
68
71
  * readRecordStore state ('ok'|'empty'|'missing'|'corrupt').
69
72
  */
70
- export async function loadRecordsDetailed(projectRoot) {
73
+ export async function loadRecordsDetailed(projectRoot, { homeDir } = {}) {
71
74
  try {
72
- const config = await loadRuntimeConfig(projectRoot);
75
+ const config = await loadRuntimeConfig(projectRoot, { homeDir });
73
76
  if (config?.memoryV2?.enabled === false) {
74
77
  return { state: 'disabled', records: [] };
75
78
  }
76
79
  } catch {
77
80
  // unreadable config → default enabled, keep going
78
81
  }
79
- const migration = await ensureMigrated(projectRoot);
82
+ const migration = await ensureMigrated(projectRoot, { homeDir });
80
83
  if (migration.failed) {
81
84
  return { state: 'migrating', records: [] };
82
85
  }
@@ -89,8 +92,8 @@ export async function loadRecordsDetailed(projectRoot) {
89
92
  /**
90
93
  * loadRecords(projectRoot) → record[] — tolerant: invalid entries skipped.
91
94
  */
92
- export async function loadRecords(projectRoot) {
93
- const { records } = await loadRecordsDetailed(projectRoot);
95
+ export async function loadRecords(projectRoot, { homeDir } = {}) {
96
+ const { records } = await loadRecordsDetailed(projectRoot, { homeDir });
94
97
  return records;
95
98
  }
96
99
 
@@ -103,10 +106,10 @@ async function readStore(projectRoot) {
103
106
  * saveRecords(projectRoot, records) → void — explicit restore path
104
107
  * (mutateMemory op:'restore'); drops unrecoverable entries.
105
108
  */
106
- export async function saveRecords(projectRoot, records) {
107
- await ensureMigrated(projectRoot);
109
+ export async function saveRecords(projectRoot, records, { homeDir } = {}) {
110
+ await ensureMigrated(projectRoot, { homeDir });
108
111
  const res = await mutateMemory(
109
- { kind: 'project', projectRoot },
112
+ { kind: 'project', projectRoot, homeDir },
110
113
  { op: 'restore', payload: { records } },
111
114
  );
112
115
  if (res.status === 'rejected' || res.status === 'conflict' || res.status === 'corrupt') {
@@ -120,16 +123,16 @@ export async function saveRecords(projectRoot, records) {
120
123
  * Runs ensureMigrated first — a first v2 write must not create records.json
121
124
  * ahead of the v1→v2 migration and strand legacy data.
122
125
  */
123
- export async function addRecord(projectRoot, recordInput) {
126
+ export async function addRecord(projectRoot, recordInput, { homeDir } = {}) {
124
127
  const record = isRawRecord(recordInput)
125
128
  ? normalizeRecord(recordInput)
126
129
  : createRecord(recordInput);
127
130
  if (!record) {
128
131
  throw new Error('addRecord: input could not be normalized into a valid record');
129
132
  }
130
- await ensureMigrated(projectRoot);
133
+ await ensureMigrated(projectRoot, { homeDir });
131
134
  const res = await mutateMemory(
132
- { kind: 'project', projectRoot },
135
+ { kind: 'project', projectRoot, homeDir },
133
136
  { op: 'add', payload: record },
134
137
  );
135
138
  if (res.status === 'ok' || res.status === 'duplicate') return res.record ?? record;
@@ -140,10 +143,10 @@ export async function addRecord(projectRoot, recordInput) {
140
143
  /**
141
144
  * updateRecord(projectRoot, id, patch) → record | null
142
145
  */
143
- export async function updateRecord(projectRoot, id, patch = {}) {
144
- await ensureMigrated(projectRoot);
146
+ export async function updateRecord(projectRoot, id, patch = {}, { homeDir } = {}) {
147
+ await ensureMigrated(projectRoot, { homeDir });
145
148
  const res = await mutateMemory(
146
- { kind: 'project', projectRoot },
149
+ { kind: 'project', projectRoot, homeDir },
147
150
  { op: 'update', payload: { id, patch } },
148
151
  );
149
152
  if (res.status === 'ok') return res.record;
@@ -154,17 +157,17 @@ export async function updateRecord(projectRoot, id, patch = {}) {
154
157
  /**
155
158
  * getRecord(projectRoot, id) → record | null
156
159
  */
157
- export async function getRecord(projectRoot, id) {
158
- await ensureMigrated(projectRoot);
160
+ export async function getRecord(projectRoot, id, { homeDir } = {}) {
161
+ await ensureMigrated(projectRoot, { homeDir });
159
162
  const { records } = await readStore(projectRoot);
160
163
  return records.find((r) => r.id === id) ?? null;
161
164
  }
162
165
 
163
166
  /**
164
- * queryRecords(projectRoot, { type?, scope?, status?, projectId? }) → record[]
167
+ * queryRecords(projectRoot, { type?, scope?, status?, projectId?, homeDir? }) → record[]
165
168
  */
166
- export async function queryRecords(projectRoot, { type, scope, status, projectId } = {}) {
167
- const records = await loadRecords(projectRoot);
169
+ export async function queryRecords(projectRoot, { type, scope, status, projectId, homeDir } = {}) {
170
+ const records = await loadRecords(projectRoot, { homeDir });
168
171
  return records.filter((r) => (type == null || r.type === type)
169
172
  && (scope == null || r.scope === scope)
170
173
  && (status == null || r.status === status)
@@ -174,8 +177,8 @@ export async function queryRecords(projectRoot, { type, scope, status, projectId
174
177
  /**
175
178
  * stats(projectRoot) → { total, byType, byStatus, invalidSkipped }
176
179
  */
177
- export async function stats(projectRoot) {
178
- await ensureMigrated(projectRoot);
180
+ export async function stats(projectRoot, { homeDir } = {}) {
181
+ await ensureMigrated(projectRoot, { homeDir });
179
182
  const { records, invalidSkipped } = await readStore(projectRoot);
180
183
  const byType = {};
181
184
  const byStatus = {};
@@ -15,7 +15,7 @@ import { resolveMemoryStage } from './memoryFlags.js';
15
15
  * unlisted canary project) reports 'missing' so callers take the legacy
16
16
  * lane exactly as if the v2 store were absent.
17
17
  */
18
- export async function loadV2Records(projectRoot, filter = {}, { config, projectId } = {}) {
18
+ export async function loadV2Records(projectRoot, filter = {}, { config, projectId, homeDir } = {}) {
19
19
  if (config && resolveMemoryStage(config, 'eligibility', { projectId }) === 'off') {
20
20
  return { state: 'missing', records: [] };
21
21
  }
@@ -25,7 +25,7 @@ export async function loadV2Records(projectRoot, filter = {}, { config, projectI
25
25
  || typeof storeV2?.loadRecordsDetailed !== 'function') {
26
26
  return { state: 'missing', records: [] };
27
27
  }
28
- const { state, records, generation } = await storeV2.loadRecordsDetailed(projectRoot);
28
+ const { state, records, generation } = await storeV2.loadRecordsDetailed(projectRoot, { homeDir });
29
29
  const { type, scope, status, projectId } = filter ?? {};
30
30
  const filtered = (Array.isArray(records) ? records : []).filter((r) => (
31
31
  (type == null || r.type === type)