codemem 0.40.0-alpha.1 → 0.40.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.
package/dist/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import { DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, REF_BACKFILL_JOB, RawEventSweeper, RefBackfillRunner, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildAuthHeaders, buildBaseUrl, buildDistillReport, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, compareMemoryRoleReports, connect, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, draftDistillRule, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, exportMemories, extractApplyPatchPaths, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, initDatabase, isEmbeddingDisabled, judgeDistillReport, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadSqliteVec, mdnsEnabled, planReplicationOpsAgePrune, projectMatchesFilter, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveDbPath, resolveHookProject, resolveProject, resolveProjectRoot, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, setPeerProjectFilter, stripJsonComments, stripPrivateObj, stripTrailingCommas, syncPassPreflight, updatePeerAddresses, vacuumDatabase, writeCodememConfigFile } from "@codemem/core";
2
+ import { DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, REF_BACKFILL_JOB, RawEventSweeper, RefBackfillRunner, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildAuthHeaders, buildBaseUrl, buildDistillReport, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, clonePromptPackAttempt, compareMemoryRoleReports, connect, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, draftDistillRule, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, exportMemories, extractApplyPatchPaths, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, initDatabase, isEmbeddingDisabled, judgeDistillReport, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadSqliteVec, mdnsEnabled, planReplicationOpsAgePrune, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, recordPromptPackArtifacts, recordPromptPackTerminal, recordRetrievalSurface, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveDbPath, resolveHookProject, resolveProject, resolveProjectRoot, resolveRetrievalSession, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, setPeerProjectFilter, stripJsonComments, stripPrivateObj, stripTrailingCommas, syncPassPreflight, tryUpdateRetrievalDelivery, updatePeerAddresses, vacuumDatabase, writeCodememConfigFile } from "@codemem/core";
3
3
  import { Command, Option } from "commander";
4
4
  import omelette from "omelette";
5
+ import { randomInt, randomUUID } from "node:crypto";
5
6
  import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
6
7
  import { homedir, networkInterfaces } from "node:os";
7
- import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
8
+ import { dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
8
9
  import { styleText } from "node:util";
9
- import { randomInt, randomUUID } from "node:crypto";
10
10
  import * as p from "@clack/prompts";
11
11
  import { serve } from "@hono/node-server";
12
12
  import { execFileSync, spawn, spawnSync } from "node:child_process";
@@ -287,11 +287,77 @@ function queryByFile(dbPath, relativePath, project, limit) {
287
287
  function resolveProject$1(payload) {
288
288
  return resolveHookProject(typeof payload.cwd === "string" ? payload.cwd : null, payload.project);
289
289
  }
290
+ function sourceSessionId(payload) {
291
+ const value = payload.session_id;
292
+ return typeof value === "string" && value.trim() ? value.trim() : null;
293
+ }
290
294
  async function buildClaudeFileContext(payload, opts, deps = {}) {
291
295
  if (envTruthy$3(process.env.CODEMEM_PLUGIN_IGNORE)) return continueResult$2();
292
- if (!envNotDisabled$2(process.env.CODEMEM_FILE_CONTEXT || "1")) return continueResult$2();
293
296
  const filePath = extractFilePath(payload);
294
297
  if (!filePath) return continueResult$2();
298
+ const startedAt = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
299
+ const attemptId = (deps.createAttemptId ?? randomUUID)();
300
+ const resolveDb = deps.resolveDb ?? resolveDbPath;
301
+ let resolvedDbPath = null;
302
+ let activeStore = null;
303
+ const getStore = () => {
304
+ resolvedDbPath ??= resolveDb(resolveDbOpt(opts));
305
+ activeStore ??= (deps.createStore ?? ((dbPath) => new MemoryStore(dbPath)))(resolvedDbPath);
306
+ return activeStore;
307
+ };
308
+ const finish = (result) => {
309
+ try {
310
+ activeStore?.close();
311
+ } catch {}
312
+ activeStore = null;
313
+ return result;
314
+ };
315
+ const record = (attemptInput) => {
316
+ if (!envNotDisabled$2(process.env.CODEMEM_RETRIEVAL_LEDGER || "1")) return;
317
+ try {
318
+ resolvedDbPath ??= resolveDb(resolveDbOpt(opts));
319
+ const completedAt = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
320
+ const ledgerInput = {
321
+ ...attemptInput,
322
+ attemptId,
323
+ surface: "file_context",
324
+ trigger: "automatic",
325
+ startedAt: startedAt.toISOString(),
326
+ completedAt: completedAt.toISOString(),
327
+ latencyMs: Math.max(0, completedAt.getTime() - startedAt.getTime()),
328
+ recorderVersion: "claude-file-context-v1",
329
+ source: "claude",
330
+ streamId: sourceSessionId(payload),
331
+ sourceSessionId: sourceSessionId(payload),
332
+ mode: "claude_pre_tool_use_read"
333
+ };
334
+ if (deps.recordAttempt) deps.recordAttempt(resolvedDbPath, ledgerInput);
335
+ else {
336
+ const store = getStore();
337
+ recordRetrievalSurface(store.db, {
338
+ ...ledgerInput,
339
+ sessionId: resolveRetrievalSession(store.db, "claude", ledgerInput.sourceSessionId)
340
+ });
341
+ }
342
+ } catch {}
343
+ };
344
+ const updateDelivery = (status) => {
345
+ if (!envNotDisabled$2(process.env.CODEMEM_RETRIEVAL_LEDGER || "1")) return;
346
+ try {
347
+ resolvedDbPath ??= resolveDb(resolveDbOpt(opts));
348
+ if (deps.updateDelivery) deps.updateDelivery(resolvedDbPath, attemptId, status);
349
+ else tryUpdateRetrievalDelivery(getStore().db, attemptId, status);
350
+ } catch {}
351
+ };
352
+ if (!envNotDisabled$2(process.env.CODEMEM_FILE_CONTEXT || "1")) {
353
+ record({
354
+ retrievalStatus: "skipped",
355
+ deliveryStatus: "not_attempted",
356
+ failureCode: "file_context_disabled",
357
+ failureStage: "configuration"
358
+ });
359
+ return finish(continueResult$2());
360
+ }
295
361
  const cwd = typeof payload.cwd === "string" && payload.cwd.trim() ? payload.cwd : process.cwd();
296
362
  const expandedPath = expandHome$3(filePath);
297
363
  const absolutePath = isAbsolute(expandedPath) ? expandedPath : resolve(cwd, expandedPath);
@@ -299,38 +365,89 @@ async function buildClaudeFileContext(payload, opts, deps = {}) {
299
365
  const escapesCwd = relativePath === ".." || relativePath.startsWith("../") || isAbsolute(relativePath);
300
366
  if (!relativePath || escapesCwd) {
301
367
  logHookEvent(`file_context.skip reason=outside_cwd path=${JSON.stringify(filePath)} cwd=${JSON.stringify(cwd)}`);
302
- return continueResult$2();
368
+ record({
369
+ retrievalStatus: "skipped",
370
+ deliveryStatus: "not_attempted",
371
+ failureCode: "outside_cwd",
372
+ failureStage: "path_validation"
373
+ });
374
+ return finish(continueResult$2());
303
375
  }
304
376
  const minBytes = Number.parseInt(process.env.CODEMEM_FILE_CONTEXT_MIN_BYTES ?? `${FILE_GATE_MIN_BYTES}`, 10);
305
377
  const minBytesEffective = Number.isFinite(minBytes) && minBytes >= 0 ? minBytes : FILE_GATE_MIN_BYTES;
306
378
  const stat = (deps.statFile ?? statFile)(absolutePath);
307
379
  if (!stat) {
308
380
  logHookEvent(`file_context.skip reason=stat_failed path=${JSON.stringify(relativePath)}`);
309
- return continueResult$2();
381
+ record({
382
+ retrievalStatus: "skipped",
383
+ deliveryStatus: "not_attempted",
384
+ failureCode: "stat_failed",
385
+ failureStage: "file_access",
386
+ repositoryPaths: [relativePath]
387
+ });
388
+ return finish(continueResult$2());
310
389
  }
311
390
  const bypassSizeGate = SMALL_FILE_BYPASS_PATTERNS.some((p) => p.test(relativePath));
312
391
  if (stat.sizeBytes < minBytesEffective && !bypassSizeGate) {
313
392
  logHookEvent(`file_context.skip reason=below_size_gate path=${JSON.stringify(relativePath)} size=${stat.sizeBytes} gate=${minBytesEffective}`);
314
- return continueResult$2();
393
+ record({
394
+ retrievalStatus: "skipped",
395
+ deliveryStatus: "not_attempted",
396
+ failureCode: "below_size_gate",
397
+ failureStage: "size_gate",
398
+ repositoryPaths: [relativePath]
399
+ });
400
+ return finish(continueResult$2());
315
401
  }
316
402
  const project = resolveProject$1(payload);
317
- const resolveDb = deps.resolveDb ?? resolveDbPath;
318
403
  const queryFn = deps.queryByFile ?? queryByFile;
319
404
  let rows = [];
320
405
  try {
321
- rows = queryFn(resolveDb(resolveDbOpt(opts)), relativePath, project, FETCH_LIMIT);
406
+ resolvedDbPath ??= resolveDb(resolveDbOpt(opts));
407
+ rows = deps.queryByFile ? queryFn(resolvedDbPath, relativePath, project, FETCH_LIMIT) : getStore().findByFile(relativePath, {
408
+ limit: FETCH_LIMIT,
409
+ ...project ? { project } : {}
410
+ });
322
411
  } catch (err) {
323
412
  logHookEvent(`codemem claude-hook-file-context query failed: ${err instanceof Error ? err.message : String(err)}`);
324
- return continueResult$2();
413
+ record({
414
+ retrievalStatus: "failed",
415
+ deliveryStatus: "not_attempted",
416
+ failureCode: "query_failed",
417
+ failureStage: "retrieval",
418
+ project,
419
+ filters: project ? { project } : void 0,
420
+ repositoryPaths: [relativePath]
421
+ });
422
+ return finish(continueResult$2());
325
423
  }
326
424
  if (rows.length === 0) {
327
425
  logHookEvent(`file_context.skip reason=no_observations path=${JSON.stringify(relativePath)} project=${JSON.stringify(project ?? "")}`);
328
- return continueResult$2();
426
+ record({
427
+ retrievalStatus: "no_results",
428
+ deliveryStatus: "not_attempted",
429
+ project,
430
+ filters: project ? { project } : void 0,
431
+ repositoryPaths: [relativePath]
432
+ });
433
+ return finish(continueResult$2());
329
434
  }
330
435
  const top = scoreAndDedupe(rows, relativePath, DISPLAY_LIMIT);
331
436
  if (top.length === 0) {
332
437
  logHookEvent(`file_context.skip reason=no_top_after_dedupe path=${JSON.stringify(relativePath)} candidates=${rows.length}`);
333
- return continueResult$2();
438
+ record({
439
+ retrievalStatus: "succeeded",
440
+ deliveryStatus: "not_attempted",
441
+ candidateIds: rows.map((row) => row.id),
442
+ candidateCount: rows.length,
443
+ selectedIds: [],
444
+ failureCode: "no_top_after_dedupe",
445
+ failureStage: "selection",
446
+ project,
447
+ filters: project ? { project } : void 0,
448
+ repositoryPaths: [relativePath]
449
+ });
450
+ return finish(continueResult$2());
334
451
  }
335
452
  let staleness = null;
336
453
  if (stat.mtimeMs > 0) {
@@ -343,13 +460,30 @@ async function buildClaudeFileContext(payload, opts, deps = {}) {
343
460
  newestObservationMs
344
461
  };
345
462
  }
346
- const timeline = formatTimeline(top, relativePath, staleness);
463
+ record({
464
+ retrievalStatus: "succeeded",
465
+ deliveryStatus: "not_attempted",
466
+ candidateIds: rows.map((row) => row.id),
467
+ candidateCount: rows.length,
468
+ selectedIds: top.map((row) => row.id),
469
+ project,
470
+ filters: project ? { project } : void 0,
471
+ repositoryPaths: [relativePath]
472
+ });
473
+ let timeline;
474
+ try {
475
+ timeline = formatTimeline(top, relativePath, staleness);
476
+ } catch {
477
+ updateDelivery("failed");
478
+ return finish(continueResult$2());
479
+ }
347
480
  logHookEvent(`file_context.ok path=${JSON.stringify(relativePath)} candidates=${rows.length} surfaced=${top.length} project=${JSON.stringify(project ?? "")} stale=${staleness ? "true" : "false"}`);
348
- return { hookSpecificOutput: {
481
+ updateDelivery("handed_off");
482
+ return finish({ hookSpecificOutput: {
349
483
  hookEventName: "PreToolUse",
350
484
  permissionDecision: "allow",
351
485
  additionalContext: timeline
352
- } };
486
+ } });
353
487
  }
354
488
  var claudeHookFileContextCmd = new Command("claude-hook-file-context").configureHelp(helpStyle).description("Return Claude PreToolUse:Read additionalContext from per-file observation timeline");
355
489
  addDbOption(claudeHookFileContextCmd);
@@ -4636,6 +4770,9 @@ function parseStrictPositiveId(value) {
4636
4770
  const n = Number(value.trim());
4637
4771
  return Number.isFinite(n) && n >= 1 && Number.isInteger(n) ? n : null;
4638
4772
  }
4773
+ function resolveOpenAIResponsesOverride(cliEnabled, configured) {
4774
+ return cliEnabled === true ? true : configured;
4775
+ }
4639
4776
  function showMemoryAction(idStr, opts) {
4640
4777
  const memoryId = parseStrictPositiveId(idStr);
4641
4778
  if (memoryId === null) {
@@ -5063,10 +5200,11 @@ function createMemoryExtractionReplayCommand() {
5063
5200
  const observerConfigWithOverrides = {
5064
5201
  ...observerConfig,
5065
5202
  observerTemperature: observerTemperature ?? observerConfig.observerTemperature,
5066
- observerOpenAIUseResponses: opts.openaiResponses === true,
5067
- observerReasoningEffort: opts.reasoningEffort?.trim() || null,
5068
- observerReasoningSummary: opts.reasoningSummary?.trim() || null,
5069
- observerMaxOutputTokens: maxOutputTokens ?? observerConfig.observerMaxTokens
5203
+ observerOpenAIUseResponses: resolveOpenAIResponsesOverride(opts.openaiResponses, observerConfig.observerOpenAIUseResponses),
5204
+ observerReasoningEffort: opts.reasoningEffort === void 0 ? observerConfig.observerReasoningEffort : opts.reasoningEffort.trim() || null,
5205
+ observerReasoningSummary: opts.reasoningSummary === void 0 ? observerConfig.observerReasoningSummary : opts.reasoningSummary.trim() || null,
5206
+ observerMaxOutputTokens: maxOutputTokens ?? observerConfig.observerMaxOutputTokens ?? observerConfig.observerMaxTokens,
5207
+ observerExplicitConfigKeys: maxOutputTokens === null ? observerConfig.observerExplicitConfigKeys : [...new Set([...observerConfig.observerExplicitConfigKeys ?? [], "observerMaxOutputTokens"])]
5070
5208
  };
5071
5209
  const observer = new ObserverClient(observerConfigWithOverrides);
5072
5210
  const result = opts.observerTierRouting === true ? await replayBatchExtractionWithTierRouting(resolveDbOpt(opts), observerConfigWithOverrides, {
@@ -5139,6 +5277,15 @@ function reconcileExtractionBenchmarkStatus(input) {
5139
5277
  initialQuality: input.initialQuality
5140
5278
  };
5141
5279
  }
5280
+ function summarizeBenchmarkReasoning(runs, fallback) {
5281
+ const source = runs[0] ?? fallback;
5282
+ const reasoningEfforts = new Set(runs.map((run) => run.reasoningEffort));
5283
+ const reasoningSummaries = new Set(runs.map((run) => run.reasoningSummary));
5284
+ return {
5285
+ reasoningEffort: reasoningEfforts.size > 1 ? "mixed" : source.reasoningEffort,
5286
+ reasoningSummary: reasoningSummaries.size > 1 ? "mixed" : source.reasoningSummary
5287
+ };
5288
+ }
5142
5289
  function createMemoryExtractionBenchmarkCommand() {
5143
5290
  const cmd = new Command("extraction-benchmark").configureHelp(helpStyle).description("Run the formal extraction replay benchmark set and print a cost/quality scoreboard").requiredOption("--benchmark <id>", "benchmark profile id").option("--observer-provider <provider>", "override observer provider for this benchmark run").option("--observer-model <model>", "override observer model for this benchmark run").option("--observer-tier-routing", "use replay-only benchmark-backed observer tier routing").option("--openai-responses", "use OpenAI Responses API for this benchmark run").option("--reasoning-effort <level>", "set OpenAI reasoning.effort for this benchmark run (responses path)").option("--reasoning-summary <mode>", "set OpenAI reasoning.summary for this benchmark run (responses path)").option("--max-output-tokens <n>", "override OpenAI max_output_tokens for this benchmark run (responses path)").option("--observer-temperature <value>", "override observer temperature for this benchmark run").option("--transcript-budget <chars>", "override replay transcript budget in characters for this benchmark run").option("--repetitions <n>", "run every benchmark batch 1-10 times to measure model stability", "1");
5144
5291
  addDbOption(cmd);
@@ -5170,10 +5317,11 @@ function createMemoryExtractionBenchmarkCommand() {
5170
5317
  observerProvider: opts.observerProvider?.trim() || observerConfig.observerProvider,
5171
5318
  observerModel: opts.observerModel?.trim() || observerConfig.observerModel,
5172
5319
  observerTemperature: observerTemperature ?? observerConfig.observerTemperature,
5173
- observerOpenAIUseResponses: opts.openaiResponses === true,
5174
- observerReasoningEffort: opts.reasoningEffort?.trim() || null,
5175
- observerReasoningSummary: opts.reasoningSummary?.trim() || null,
5176
- observerMaxOutputTokens: maxOutputTokens ?? observerConfig.observerMaxTokens
5320
+ observerOpenAIUseResponses: resolveOpenAIResponsesOverride(opts.openaiResponses, observerConfig.observerOpenAIUseResponses),
5321
+ observerReasoningEffort: opts.reasoningEffort === void 0 ? observerConfig.observerReasoningEffort : opts.reasoningEffort.trim() || null,
5322
+ observerReasoningSummary: opts.reasoningSummary === void 0 ? observerConfig.observerReasoningSummary : opts.reasoningSummary.trim() || null,
5323
+ observerMaxOutputTokens: maxOutputTokens ?? observerConfig.observerMaxOutputTokens ?? observerConfig.observerMaxTokens,
5324
+ observerExplicitConfigKeys: maxOutputTokens === null ? observerConfig.observerExplicitConfigKeys : [...new Set([...observerConfig.observerExplicitConfigKeys ?? [], "observerMaxOutputTokens"])]
5177
5325
  };
5178
5326
  const observer = new ObserverClient(observerConfigWithOverrides);
5179
5327
  const runs = [];
@@ -5343,16 +5491,20 @@ function createMemoryExtractionBenchmarkCommand() {
5343
5491
  })
5344
5492
  };
5345
5493
  const uniqueObserverKeys = Array.from(new Set(runs.map((run) => `${run.provider}::${run.model}::${run.transport}`)));
5494
+ const benchmarkReasoning = summarizeBenchmarkReasoning(runs, {
5495
+ reasoningEffort: observer.reasoningEffort,
5496
+ reasoningSummary: observer.reasoningSummary
5497
+ });
5346
5498
  const observerSummary = opts.observerTierRouting === true ? {
5347
5499
  provider: uniqueObserverKeys.length === 1 ? runs[0]?.provider ?? observer.provider : "mixed",
5348
5500
  model: uniqueObserverKeys.length === 1 ? runs[0]?.model ?? observer.model : "mixed",
5349
5501
  transport: uniqueObserverKeys.length === 1 ? runs[0]?.transport ?? "unknown" : "mixed",
5350
5502
  tierRouting: true,
5351
5503
  openaiUseResponses: uniqueObserverKeys.length === 1 ? runs[0]?.openaiUseResponses ?? observer.openaiUseResponses : null,
5352
- reasoningEffort: uniqueObserverKeys.length === 1 ? runs[0]?.reasoningEffort ?? observer.reasoningEffort : "mixed",
5353
- reasoningSummary: uniqueObserverKeys.length === 1 ? runs[0]?.reasoningSummary ?? observer.reasoningSummary : "mixed",
5354
- maxOutputTokens: uniqueObserverKeys.length === 1 ? runs[0]?.maxOutputTokens ?? observer.maxOutputTokens : null,
5355
- temperature: uniqueObserverKeys.length === 1 ? runs[0]?.temperature ?? observer.temperature : null,
5504
+ reasoningEffort: benchmarkReasoning.reasoningEffort,
5505
+ reasoningSummary: benchmarkReasoning.reasoningSummary,
5506
+ maxOutputTokens: uniqueObserverKeys.length === 1 ? runs[0]?.maxOutputTokens ?? null : null,
5507
+ temperature: uniqueObserverKeys.length === 1 ? runs[0]?.temperature ?? null : null,
5356
5508
  transcriptBudget: transcriptBudget ?? null,
5357
5509
  selectedObservers: uniqueObserverKeys
5358
5510
  } : {
@@ -5361,10 +5513,10 @@ function createMemoryExtractionBenchmarkCommand() {
5361
5513
  transport: runs[0]?.transport ?? observer.getStatus().runtime,
5362
5514
  tierRouting: false,
5363
5515
  openaiUseResponses: observer.openaiUseResponses,
5364
- reasoningEffort: observer.reasoningEffort,
5365
- reasoningSummary: observer.reasoningSummary,
5366
- maxOutputTokens: observer.maxOutputTokens,
5367
- temperature: observer.temperature,
5516
+ reasoningEffort: benchmarkReasoning.reasoningEffort,
5517
+ reasoningSummary: benchmarkReasoning.reasoningSummary,
5518
+ maxOutputTokens: runs[0]?.maxOutputTokens ?? null,
5519
+ temperature: runs[0]?.temperature ?? null,
5368
5520
  transcriptBudget: transcriptBudget ?? null,
5369
5521
  selectedObservers: uniqueObserverKeys
5370
5522
  };
@@ -5390,10 +5542,10 @@ function createMemoryExtractionBenchmarkCommand() {
5390
5542
  `Transport: ${observerSummary.transport}`,
5391
5543
  `Tier routing: ${opts.observerTierRouting === true ? "yes" : "no"}`,
5392
5544
  `OpenAI Responses: ${observerSummary.openaiUseResponses === null ? "mixed" : observerSummary.openaiUseResponses ? "yes" : "no"}`,
5393
- `Reasoning effort: ${observerSummary.reasoningEffort ?? "none"}`,
5394
- `Reasoning summary: ${observerSummary.reasoningSummary ?? "none"}`,
5395
- `Max output tokens: ${observerSummary.maxOutputTokens ?? "mixed"}`,
5396
- `Temperature: ${observerSummary.temperature ?? "mixed"}`,
5545
+ `Reasoning effort: ${observerSummary.reasoningEffort ?? "not transmitted"}`,
5546
+ `Reasoning summary: ${observerSummary.reasoningSummary ?? "not transmitted"}`,
5547
+ `Max output tokens: ${observerSummary.transport === "codex_consumer" ? "not transmitted" : observerSummary.maxOutputTokens ?? "mixed"}`,
5548
+ `Temperature: ${observerSummary.transport === "mixed" ? "mixed" : observerSummary.temperature ?? "not transmitted"}`,
5397
5549
  `Transcript budget override: ${transcriptBudget ?? "default"}`,
5398
5550
  `Repetitions: ${summary.repetitions}`,
5399
5551
  `Shape-quality passes: ${summary.shapeQualityPasses}/${summary.shapeQualityTotal}`,
@@ -5511,6 +5663,127 @@ memoryCommand.addCommand(createMemoryRelinkReportCommand());
5511
5663
  memoryCommand.addCommand(createMemoryRelinkPlanCommand());
5512
5664
  //#endregion
5513
5665
  //#region src/commands/pack.ts
5666
+ var MAX_INTERNAL_LEDGER_INPUT_BYTES = 16 * 1024;
5667
+ var INTERNAL_LEDGER_INPUT_TIMEOUT_MS = 1e3;
5668
+ var FORBIDDEN_INTERNAL_KEYS = new Set([
5669
+ "body",
5670
+ "context",
5671
+ "pack",
5672
+ "pack_text",
5673
+ "path",
5674
+ "preview",
5675
+ "prompt",
5676
+ "query",
5677
+ "raw_prompt",
5678
+ "title"
5679
+ ]);
5680
+ var INTERNAL_LEDGER_KEYS = new Set([
5681
+ "action",
5682
+ "attempt_id",
5683
+ "started_at",
5684
+ "source",
5685
+ "stream_id",
5686
+ "source_session_id",
5687
+ "prompt_number",
5688
+ "request_id",
5689
+ "retrieval_status",
5690
+ "delivery_status",
5691
+ "failure_code",
5692
+ "failure_stage",
5693
+ "original_attempt_id"
5694
+ ]);
5695
+ var INTERNAL_LEDGER_ACTIONS = new Set([
5696
+ "record",
5697
+ "delivery",
5698
+ "cache_reuse"
5699
+ ]);
5700
+ var INTERNAL_RETRIEVAL_STATUSES = new Set(["skipped", "failed"]);
5701
+ var INTERNAL_DELIVERY_STATUSES = new Set([
5702
+ "handed_off",
5703
+ "failed",
5704
+ "unknown"
5705
+ ]);
5706
+ async function readInternalLedgerPayload() {
5707
+ const stdin = process.stdin;
5708
+ if (stdin.isTTY) throw new PackUsageError("internal ledger metadata requires piped stdin JSON");
5709
+ return parseInternalLedgerPayload(await new Promise((resolve, reject) => {
5710
+ let value = "";
5711
+ const cleanup = () => {
5712
+ clearTimeout(timer);
5713
+ stdin.off("data", onData);
5714
+ stdin.off("end", onEnd);
5715
+ stdin.off("error", onError);
5716
+ };
5717
+ const fail = (error) => {
5718
+ cleanup();
5719
+ stdin.pause();
5720
+ reject(error);
5721
+ };
5722
+ const onData = (chunk) => {
5723
+ value += String(chunk);
5724
+ if (Buffer.byteLength(value, "utf8") > MAX_INTERNAL_LEDGER_INPUT_BYTES) fail(new PackUsageError("internal ledger metadata exceeds 16384 bytes"));
5725
+ };
5726
+ const onEnd = () => {
5727
+ cleanup();
5728
+ resolve(value);
5729
+ };
5730
+ const onError = (error) => fail(error);
5731
+ const timer = setTimeout(() => fail(new PackUsageError("internal ledger metadata read timed out")), INTERNAL_LEDGER_INPUT_TIMEOUT_MS);
5732
+ stdin.on("data", onData);
5733
+ stdin.once("end", onEnd);
5734
+ stdin.once("error", onError);
5735
+ }));
5736
+ }
5737
+ function parseInternalLedgerPayload(raw) {
5738
+ if (Buffer.byteLength(raw, "utf8") > MAX_INTERNAL_LEDGER_INPUT_BYTES) throw new PackUsageError("internal ledger metadata exceeds 16384 bytes");
5739
+ let value;
5740
+ try {
5741
+ value = JSON.parse(raw);
5742
+ } catch {
5743
+ throw new PackUsageError("internal ledger metadata must be valid JSON");
5744
+ }
5745
+ if (value == null || typeof value !== "object" || Array.isArray(value)) throw new PackUsageError("internal ledger metadata must be an object");
5746
+ for (const key of Object.keys(value)) {
5747
+ if (FORBIDDEN_INTERNAL_KEYS.has(key)) throw new PackUsageError(`internal ledger metadata rejects sensitive field: ${key}`);
5748
+ if (!INTERNAL_LEDGER_KEYS.has(key)) throw new PackUsageError(`internal ledger metadata contains unsupported field: ${key}`);
5749
+ }
5750
+ const payload = value;
5751
+ if (typeof payload.attempt_id !== "string") throw new PackUsageError("internal ledger metadata requires attempt_id");
5752
+ if (payload.action != null && !INTERNAL_LEDGER_ACTIONS.has(payload.action)) throw new PackUsageError("internal ledger action is invalid");
5753
+ if (payload.retrieval_status != null && !INTERNAL_RETRIEVAL_STATUSES.has(payload.retrieval_status)) throw new PackUsageError("internal ledger retrieval_status is invalid");
5754
+ if (payload.delivery_status != null && !INTERNAL_DELIVERY_STATUSES.has(payload.delivery_status)) throw new PackUsageError("internal ledger delivery_status is invalid");
5755
+ if (payload.prompt_number != null && (!Number.isInteger(payload.prompt_number) || payload.prompt_number < 0)) throw new PackUsageError("internal ledger prompt_number must be a non-negative integer");
5756
+ for (const key of [
5757
+ "started_at",
5758
+ "source",
5759
+ "stream_id",
5760
+ "source_session_id",
5761
+ "request_id",
5762
+ "failure_code",
5763
+ "failure_stage",
5764
+ "original_attempt_id"
5765
+ ]) {
5766
+ const field = payload[key];
5767
+ if (field != null && (typeof field !== "string" || field.length > 512)) throw new PackUsageError(`internal ledger metadata field ${key} is invalid`);
5768
+ if (typeof field === "string" && (posix.isAbsolute(field) || win32.isAbsolute(field))) throw new PackUsageError(`internal ledger metadata rejects absolute paths in field: ${key}`);
5769
+ }
5770
+ return payload;
5771
+ }
5772
+ function attemptMetadata(payload) {
5773
+ return {
5774
+ attemptId: payload.attempt_id,
5775
+ startedAt: payload.started_at ?? (/* @__PURE__ */ new Date()).toISOString(),
5776
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
5777
+ source: payload.source ?? "opencode",
5778
+ streamId: payload.stream_id ?? null,
5779
+ sourceSessionId: payload.source_session_id ?? null,
5780
+ promptNumber: payload.prompt_number ?? null,
5781
+ requestId: payload.request_id ?? null
5782
+ };
5783
+ }
5784
+ function handleInstrumentedPackLedger(db, payload, context, filters, artifacts) {
5785
+ return recordPromptPackArtifacts(db, attemptMetadata(payload), context, filters, artifacts);
5786
+ }
5514
5787
  function describeCandidate(candidate) {
5515
5788
  const scoreParts = [
5516
5789
  candidate.scores.combined_score != null ? `combined=${candidate.scores.combined_score.toFixed(2)}` : null,
@@ -5584,24 +5857,46 @@ async function withStore(opts, errorCode, run) {
5584
5857
  async function packAction(context, opts) {
5585
5858
  await withStore(opts, "pack_failed", async (store) => {
5586
5859
  const { limit, budget, filters, renderOptions } = buildPackRequestOptions(opts, { envProject: process.env.CODEMEM_PROJECT });
5587
- const result = await store.buildMemoryPackAsync(context, limit, budget, filters, renderOptions);
5588
- if (opts.json) {
5589
- console.log(JSON.stringify(result, null, 2));
5590
- return;
5591
- }
5592
- p.intro(`Memory pack for "${context}"`);
5593
- if (result.items.length === 0) {
5594
- p.log.warn("No relevant memories found.");
5595
- p.outro("done");
5860
+ let result;
5861
+ if (opts.internalLedger) {
5862
+ const artifacts = await store.buildMemoryPackWithTraceAsync(context, limit, budget, filters, renderOptions);
5863
+ result = artifacts.response;
5864
+ let artifactFingerprint;
5865
+ try {
5866
+ artifactFingerprint = promptPackArtifactFingerprint(store.db, context, filters, artifacts);
5867
+ } catch {}
5868
+ let ledgerOutcome;
5869
+ try {
5870
+ const ledgerPayload = await readInternalLedgerPayload();
5871
+ ledgerOutcome = handleInstrumentedPackLedger(store.db, ledgerPayload, context, filters, artifacts);
5872
+ } catch {}
5873
+ emitPackResult(context, opts, result, artifactFingerprint, ledgerOutcome?.ok === false && ledgerOutcome.reason === "idempotency_conflict" ? ledgerOutcome : void 0);
5596
5874
  return;
5597
- }
5598
- const metrics = result.metrics;
5599
- p.log.info(`${metrics.total_items} items, ~${metrics.pack_tokens} tokens` + (metrics.fallback_used ? " (fallback)" : "") + ` [fts:${metrics.sources.fts} sem:${metrics.sources.semantic} fuzzy:${metrics.sources.fuzzy}]`);
5600
- for (const item of result.items) p.log.step(`#${item.id} ${item.kind} ${item.title}`);
5601
- p.note(result.pack_text, "pack_text");
5602
- p.outro("done");
5875
+ } else result = await store.buildMemoryPackAsync(context, limit, budget, filters, renderOptions);
5876
+ emitPackResult(context, opts, result);
5603
5877
  });
5604
5878
  }
5879
+ function emitPackResult(context, opts, result, ledgerArtifactFingerprint, ledgerOutcome) {
5880
+ if (opts.json) {
5881
+ console.log(JSON.stringify({
5882
+ ...result,
5883
+ ...ledgerArtifactFingerprint ? { ledger_artifact_fingerprint: ledgerArtifactFingerprint } : {},
5884
+ ...ledgerOutcome ? { ledger_outcome: ledgerOutcome } : {}
5885
+ }, null, 2));
5886
+ return;
5887
+ }
5888
+ p.intro(`Memory pack for "${context}"`);
5889
+ if (result.items.length === 0) {
5890
+ p.log.warn("No relevant memories found.");
5891
+ p.outro("done");
5892
+ return;
5893
+ }
5894
+ const metrics = result.metrics;
5895
+ p.log.info(`${metrics.total_items} items, ~${metrics.pack_tokens} tokens` + (metrics.fallback_used ? " (fallback)" : "") + ` [fts:${metrics.sources.fts} sem:${metrics.sources.semantic} fuzzy:${metrics.sources.fuzzy}]`);
5896
+ for (const item of result.items) p.log.step(`#${item.id} ${item.kind} ${item.title}`);
5897
+ p.note(result.pack_text, "pack_text");
5898
+ p.outro("done");
5899
+ }
5605
5900
  async function traceAction(context, opts) {
5606
5901
  await withStore(opts, "pack_trace_failed", async (store) => {
5607
5902
  const { limit, budget, filters, renderOptions } = buildPackRequestOptions(opts, { envProject: process.env.CODEMEM_PROJECT });
@@ -5614,6 +5909,7 @@ async function traceAction(context, opts) {
5614
5909
  });
5615
5910
  }
5616
5911
  var packCmd = addPackRequestOptions(new Command("pack").enablePositionalOptions().configureHelp(helpStyle).description("Build a context-aware memory pack").argument("<context>", "context string to search for"));
5912
+ packCmd.addOption(new Option("--internal-ledger").hideHelp());
5617
5913
  addDbOption(packCmd);
5618
5914
  addJsonOption(packCmd);
5619
5915
  packCmd.action(packAction);
@@ -5623,6 +5919,38 @@ addJsonOption(traceCmd);
5623
5919
  traceCmd.action(traceAction);
5624
5920
  packCmd.addCommand(traceCmd);
5625
5921
  var packCommand = packCmd;
5922
+ function handlePromptPackLedger(db, payload) {
5923
+ const metadata = attemptMetadata(payload);
5924
+ if (payload.action === "delivery") {
5925
+ const status = payload.delivery_status;
5926
+ if (!status) throw new PackUsageError("delivery action requires delivery_status");
5927
+ const outcome = tryUpdateRetrievalDelivery(db, payload.attempt_id, status);
5928
+ if (!outcome.ok) throw new Error(outcome.reason);
5929
+ return outcome.value;
5930
+ }
5931
+ if (payload.action === "cache_reuse") {
5932
+ if (!payload.original_attempt_id) throw new PackUsageError("cache_reuse action requires original_attempt_id");
5933
+ const outcome = clonePromptPackAttempt(db, payload.original_attempt_id, metadata);
5934
+ if (!outcome.ok) throw new Error(outcome.reason);
5935
+ return outcome.value;
5936
+ }
5937
+ if (payload.action === "record") {
5938
+ if (!payload.retrieval_status || !payload.failure_code || !payload.failure_stage) throw new PackUsageError("record action requires retrieval_status, failure_code, and failure_stage");
5939
+ const outcome = recordPromptPackTerminal(db, metadata, payload.retrieval_status, payload.failure_code, payload.failure_stage);
5940
+ if (!outcome.ok) throw new Error(outcome.reason);
5941
+ return outcome.value;
5942
+ }
5943
+ throw new PackUsageError("internal ledger action is invalid");
5944
+ }
5945
+ async function promptPackLedgerAction(opts) {
5946
+ await withStore(opts, "prompt_pack_ledger_failed", async (store) => {
5947
+ const payload = await readInternalLedgerPayload();
5948
+ handlePromptPackLedger(store.db, payload);
5949
+ });
5950
+ }
5951
+ var ledgerCmd = new Command("prompt-pack-ledger").description("Record internal prompt-pack lifecycle metadata").action(promptPackLedgerAction);
5952
+ addDbOption(ledgerCmd);
5953
+ var promptPackLedgerCommand = ledgerCmd;
5626
5954
  //#endregion
5627
5955
  //#region src/commands/recent.ts
5628
5956
  var cmd = new Command("recent").configureHelp(helpStyle).description("Show recent memories").option("--limit <n>", "max results", "5").option("--project <project>", "project identifier (defaults to git repo root)").option("--all-projects", "search across all projects").option("--kind <kind>", "filter by memory kind");
@@ -6134,10 +6462,20 @@ function sqliteVecFailureDiagnostics(error, dbPath) {
6134
6462
  }
6135
6463
  async function runServeCoordinatorMaintenance(store, dependencies) {
6136
6464
  const projectShares = await dependencies.advancePendingProjectShares(store, { limit: 3 });
6465
+ const coordinatorEnrollment = await dependencies.reconcileConfiguredCoordinatorEnrollment(store);
6466
+ const enrollmentIssues = coordinatorEnrollment.issues ?? 0;
6467
+ const recipientPolicies = await dependencies.reconcileRecipientPolicyProjects(store, { limit: 3 });
6137
6468
  if (projectShares.failed > 0) throw new Error(`share operation maintenance failed for ${projectShares.failed} of ${projectShares.processed} operations`);
6469
+ if (coordinatorEnrollment.failedGroups > 0 || enrollmentIssues > 0) {
6470
+ const groupLabel = coordinatorEnrollment.failedGroups === 1 ? "group" : "groups";
6471
+ const issueLabel = enrollmentIssues === 1 ? "issue" : "issues";
6472
+ throw new Error(`coordinator enrollment maintenance failed for ${coordinatorEnrollment.failedGroups} ${groupLabel} with ${enrollmentIssues} reconciliation ${issueLabel}`);
6473
+ }
6474
+ if (recipientPolicies.failed > 0) throw new Error(`recipient policy maintenance failed for ${recipientPolicies.failed} of ${recipientPolicies.processed} projects`);
6138
6475
  return {
6139
6476
  projectShares,
6140
- recipientPolicies: await dependencies.reconcileRecipientPolicyProjects(store, { limit: 3 })
6477
+ coordinatorEnrollment,
6478
+ recipientPolicies
6141
6479
  };
6142
6480
  }
6143
6481
  async function startBackgroundViewer(invocation) {
@@ -6172,7 +6510,7 @@ async function startBackgroundViewer(invocation) {
6172
6510
  p.outro(`Viewer started in background (pid ${child.pid}) at http://${invocation.host}:${invocation.port}`);
6173
6511
  }
6174
6512
  async function startForegroundViewer(invocation) {
6175
- const { advancePendingProjectShares, createApp, createSyncApp, closeStore, getStore, reconcileRecipientPolicyProjects } = await import("@codemem/server");
6513
+ const { advancePendingProjectShares, createApp, createSyncApp, closeStore, getStore, reconcileConfiguredCoordinatorEnrollment, reconcileRecipientPolicyProjects } = await import("@codemem/server");
6176
6514
  const { serve } = await import("@hono/node-server");
6177
6515
  if (invocation.dbPath) process.env.CODEMEM_DB = invocation.dbPath;
6178
6516
  if (invocation.configPath) process.env.CODEMEM_CONFIG = invocation.configPath;
@@ -6266,6 +6604,7 @@ async function startForegroundViewer(invocation) {
6266
6604
  onAfterCoordinatorRefresh: async () => {
6267
6605
  await runServeCoordinatorMaintenance(store, {
6268
6606
  advancePendingProjectShares,
6607
+ reconcileConfiguredCoordinatorEnrollment,
6269
6608
  reconcileRecipientPolicyProjects
6270
6609
  });
6271
6610
  },
@@ -6875,9 +7214,38 @@ function fmtTokens(n) {
6875
7214
  var statsCmd = new Command("stats").configureHelp(helpStyle).description("Show database statistics");
6876
7215
  addDbOption(statsCmd);
6877
7216
  addJsonOption(statsCmd);
7217
+ statsCmd.addOption(new Option("-a, --attribution", "show local retrieval attribution diagnostics"));
7218
+ function renderAttributionDiagnostics(report) {
7219
+ const evidence = report.evidenceCompleteness;
7220
+ return [
7221
+ "Retrieval attribution diagnostics (local, bounded)",
7222
+ `Lifecycle: ${report.lifecycle.requestedAttempts} requested, ${report.lifecycle.selectedAttempts} selected, ${report.lifecycle.handedOffAttempts} handed off`,
7223
+ `Exposures: ${report.lifecycle.selectedExposures} selected, ${report.lifecycle.handedOffExposures} handed off`,
7224
+ `Evidence: ${[
7225
+ `${evidence.assessedAttempts} assessed`,
7226
+ `${evidence.unassessedAttempts} unassessed`,
7227
+ `${evidence.assessedKnownAttempts} known`,
7228
+ `${evidence.assessedUnknownAttempts} unknown`,
7229
+ `${evidence.assessmentStatusIndeterminateAttempts} status indeterminate`,
7230
+ `${evidence.assessmentDetailsIncompleteAttempts} details incomplete`,
7231
+ `${evidence.assessmentRowsInvalid} invalid rows`,
7232
+ `${evidence.assessmentRowsOmittedByLimit} omitted by limit`
7233
+ ].join(", ")}`,
7234
+ `Latency: ${report.overhead.latencySampleCount} samples, ${report.overhead.averageLatencyMs ?? "unknown"} ms average`,
7235
+ `Steering: ${report.sourceLocationSteering.assessmentCount} source-location assessments, ${report.sourceLocationSteering.matchedPathCount} matched paths`,
7236
+ `Findings: ${report.findings.stale} stale, ${report.findings.harmful} harmful`,
7237
+ "Limitations:",
7238
+ ...report.limitations.map((limitation) => `- ${limitation}`)
7239
+ ].join("\n");
7240
+ }
6878
7241
  var statsCommand = statsCmd.action((opts) => {
6879
7242
  const store = new MemoryStore(resolveDbPath(resolveDbOpt(opts)));
6880
7243
  try {
7244
+ if (opts.attribution) {
7245
+ const report = getAttributionDiagnostics(store.db);
7246
+ console.log(opts.json ? JSON.stringify(report, null, 2) : renderAttributionDiagnostics(report));
7247
+ return;
7248
+ }
6881
7249
  const result = store.stats();
6882
7250
  if (opts.json) {
6883
7251
  console.log(JSON.stringify(result, null, 2));
@@ -7981,6 +8349,7 @@ program.addCommand(embedCommand);
7981
8349
  program.addCommand(recentCommand);
7982
8350
  program.addCommand(searchCommand);
7983
8351
  program.addCommand(packCommand);
8352
+ program.addCommand(promptPackLedgerCommand, { hidden: true });
7984
8353
  program.addCommand(showMemoryCommand, { hidden: true });
7985
8354
  program.addCommand(forgetMemoryCommand, { hidden: true });
7986
8355
  program.addCommand(rememberMemoryCommand, { hidden: true });