codemem 0.38.0-alpha.1 → 0.38.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/README.md CHANGED
@@ -24,6 +24,7 @@ codemem --help
24
24
  codemem setup --opencode-only
25
25
  codemem stats
26
26
  codemem search "query"
27
+ codemem distill --limit 10
27
28
  codemem serve start
28
29
  codemem mcp
29
30
  ```
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
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, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildAuthHeaders, buildBaseUrl, 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, ensureDeviceIdentity, ensureSchemaBootstrapped, exportMemories, extractApplyPatchPaths, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getExtractionBenchmarkProfile, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, initDatabase, isEmbeddingDisabled, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadSqliteVec, mdnsEnabled, planReplicationOpsAgePrune, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorSyncConfig, readImportPayload, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveDbPath, resolveHookProject, resolveProject, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, 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, 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, exportMemories, extractApplyPatchPaths, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getExtractionBenchmarkProfile, 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, setPeerProjectFilter, stripJsonComments, stripPrivateObj, stripTrailingCommas, syncPassPreflight, updatePeerAddresses, vacuumDatabase, writeCodememConfigFile } from "@codemem/core";
3
3
  import { Command, Option } from "commander";
4
4
  import omelette from "omelette";
5
5
  import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
@@ -3722,6 +3722,272 @@ scanSecretsCmd.action((opts) => {
3722
3722
  });
3723
3723
  dbCommand.addCommand(scanSecretsCmd);
3724
3724
  //#endregion
3725
+ //#region src/commands/distill.ts
3726
+ var DistillUsageError = class extends Error {};
3727
+ function collectKind(value, previous) {
3728
+ return [...previous, ...value.split(",")].map((kind) => kind.trim()).filter(Boolean);
3729
+ }
3730
+ function parsePositiveInteger(value, name) {
3731
+ const parsed = Number.parseInt(value, 10);
3732
+ if (!Number.isFinite(parsed) || parsed < 1 || String(parsed) !== value.trim()) throw new DistillUsageError(`${name} must be a positive integer`);
3733
+ return parsed;
3734
+ }
3735
+ function readContextFile(path, displayPath, scope) {
3736
+ if (!existsSync(path)) return null;
3737
+ const text = readFileSync(path, "utf8");
3738
+ return text.trim() ? {
3739
+ path: displayPath,
3740
+ text,
3741
+ scope
3742
+ } : null;
3743
+ }
3744
+ function loadDefaultContextDocuments(includeProjectContext, cwd = process.cwd()) {
3745
+ const projectRoot = resolveProjectRoot(cwd) ?? cwd;
3746
+ return [includeProjectContext ? readContextFile(join(projectRoot, "AGENTS.md"), "AGENTS.md", "project") : null, readContextFile(join(homedir(), ".config", "opencode", "AGENTS.md"), "~/.config/opencode/AGENTS.md", "user")].filter((document) => document != null);
3747
+ }
3748
+ function shouldIncludeProjectContext(opts) {
3749
+ if (opts.allProjects) return false;
3750
+ const currentProject = resolveProject(process.cwd());
3751
+ if (!currentProject) return false;
3752
+ const targetProject = opts.project ? resolveProject(process.cwd(), opts.project) : process.env.CODEMEM_PROJECT?.trim() || currentProject;
3753
+ if (!targetProject) return false;
3754
+ return projectMatchesFilter(targetProject, currentProject);
3755
+ }
3756
+ function buildFilters(opts) {
3757
+ if (opts.allProjects && opts.project) throw new DistillUsageError("--project cannot be combined with --all-projects");
3758
+ if (opts.allProjects) return void 0;
3759
+ const defaultProject = process.env.CODEMEM_PROJECT?.trim() || null;
3760
+ const project = opts.project ? resolveProject(process.cwd(), opts.project) : defaultProject ?? resolveProject(process.cwd());
3761
+ return project ? { project } : void 0;
3762
+ }
3763
+ var JUDGE_OVERFETCH_FACTOR = 3;
3764
+ var JUDGE_OVERFETCH_MAX_EXTRA = 20;
3765
+ function judgeFetchLimit(limit) {
3766
+ return Math.min(limit * JUDGE_OVERFETCH_FACTOR, limit + JUDGE_OVERFETCH_MAX_EXTRA);
3767
+ }
3768
+ async function runDistill(store, opts, limit) {
3769
+ const minRecurrence = parsePositiveInteger(opts.minRecurrence, "min recurrence");
3770
+ return buildDistillReport(store, {
3771
+ candidate: {
3772
+ includeDocumented: opts.includeDocumented ?? false,
3773
+ maxEvidenceItems: opts.explain ? 10 : 5
3774
+ },
3775
+ contextDocuments: loadDefaultContextDocuments(shouldIncludeProjectContext(opts)),
3776
+ corpus: {
3777
+ filters: buildFilters(opts) ?? null,
3778
+ kinds: opts.kind.length > 0 ? opts.kind : void 0
3779
+ },
3780
+ limit,
3781
+ minRecurrence
3782
+ });
3783
+ }
3784
+ /**
3785
+ * B-side worthiness gate: an LLM judges each deterministic candidate and
3786
+ * routine-activity clusters are dropped. Recurrence alone cannot make this
3787
+ * call — releases, review passes, and context lookups recur by definition.
3788
+ */
3789
+ async function judgeReport(report) {
3790
+ const client = new ObserverClient();
3791
+ return judgeDistillReport(report, async (system, user) => {
3792
+ return (await client.observe(system, user)).raw;
3793
+ });
3794
+ }
3795
+ function renderDistillReport(report, explain = false) {
3796
+ if (report.candidates.length === 0) return "No distill candidates found.";
3797
+ const lines = [`Distill candidates (${report.candidates.length})`, ""];
3798
+ for (const [index, candidate] of report.candidates.entries()) {
3799
+ lines.push(`${index + 1}. [${candidate.scope}] score=${candidate.score.toFixed(3)} recurrence=${candidate.recurrence} target=${candidate.suggested_target}`);
3800
+ lines.push(` projects: ${candidate.projects.join(", ") || "(none)"}`);
3801
+ lines.push(` concepts: ${candidate.concepts.join(", ") || "(none)"}`);
3802
+ lines.push(` members: ${candidate.member_ids.join(", ")}`);
3803
+ const judge = candidate.judge;
3804
+ if (judge) lines.push(` judge: ${judge.verdict}${judge.reason ? ` — ${judge.reason}` : ""}`);
3805
+ if (explain) for (const evidence of candidate.evidence) lines.push(` - ${evidence}`);
3806
+ lines.push("");
3807
+ }
3808
+ return lines.join("\n").trimEnd();
3809
+ }
3810
+ function resolveCandidateTarget(candidate) {
3811
+ if (candidate.scope === "user") return {
3812
+ path: join(homedir(), ".config", "opencode", "AGENTS.md"),
3813
+ display: "~/.config/opencode/AGENTS.md"
3814
+ };
3815
+ return {
3816
+ path: join(resolveProjectRoot(process.cwd()) ?? process.cwd(), "AGENTS.md"),
3817
+ display: "AGENTS.md"
3818
+ };
3819
+ }
3820
+ async function draftTopCandidate(report, opts) {
3821
+ const candidate = report.candidates[0];
3822
+ if (!candidate) {
3823
+ if (opts.json) console.log(JSON.stringify({
3824
+ draft: null,
3825
+ reason: "no_candidates"
3826
+ }, null, 2));
3827
+ else {
3828
+ p.intro("codemem distill");
3829
+ p.log.warn("No distill candidates to draft.");
3830
+ p.outro("done");
3831
+ }
3832
+ return;
3833
+ }
3834
+ if (candidate.scope === "project") {
3835
+ const currentProject = resolveProject(process.cwd());
3836
+ if (!(currentProject != null && candidate.projects.every((project) => projectMatchesFilter(project, currentProject)))) {
3837
+ const projects = candidate.projects.join(", ") || "(unknown)";
3838
+ if (opts.json) console.log(JSON.stringify({
3839
+ draft: null,
3840
+ reason: "project_mismatch",
3841
+ representative_id: candidate.representative_id,
3842
+ projects: candidate.projects
3843
+ }, null, 2));
3844
+ else {
3845
+ p.intro("codemem distill draft");
3846
+ p.log.warn(`The top candidate belongs to project "${projects}", not this checkout. Run distill from that repo to draft its AGENTS.md rule.`);
3847
+ p.outro("nothing written");
3848
+ }
3849
+ return;
3850
+ }
3851
+ }
3852
+ const target = resolveCandidateTarget(candidate);
3853
+ const current = existsSync(target.path) ? readFileSync(target.path, "utf8") : "";
3854
+ let rule;
3855
+ let raw;
3856
+ try {
3857
+ const client = new ObserverClient();
3858
+ const drafted = await draftDistillRule(candidate, async (system, user) => {
3859
+ return (await client.observe(system, user)).raw;
3860
+ });
3861
+ rule = drafted.rule;
3862
+ raw = drafted.raw;
3863
+ } catch (error) {
3864
+ const guidance = `drafting needs a configured observer model: ${error instanceof Error ? error.message : String(error)}`;
3865
+ if (opts.json) emitJsonError("draft_failed", guidance, 1);
3866
+ else {
3867
+ p.log.error(guidance);
3868
+ process.exitCode = 1;
3869
+ }
3870
+ return;
3871
+ }
3872
+ if (!rule) {
3873
+ if (opts.json) console.log(JSON.stringify({
3874
+ draft: null,
3875
+ reason: "model_declined",
3876
+ representative_id: candidate.representative_id,
3877
+ raw
3878
+ }, null, 2));
3879
+ else {
3880
+ p.intro("codemem distill draft");
3881
+ p.log.warn("The model declined to draft a rule for the top candidate (too vague).");
3882
+ p.outro("done");
3883
+ }
3884
+ return;
3885
+ }
3886
+ const applied = applyDistillRule(current, rule);
3887
+ const diff = renderUnifiedDiff(target.display, current, applied.text);
3888
+ if (opts.json) {
3889
+ const wrote = Boolean(opts.apply && applied.changed);
3890
+ if (wrote) {
3891
+ mkdirSync(dirname(target.path), { recursive: true });
3892
+ writeFileSync(target.path, applied.text);
3893
+ }
3894
+ console.log(JSON.stringify({ draft: {
3895
+ target: target.display,
3896
+ scope: candidate.scope,
3897
+ representative_id: candidate.representative_id,
3898
+ recurrence: candidate.recurrence,
3899
+ score: candidate.score,
3900
+ rule,
3901
+ diff,
3902
+ already_present: !applied.changed,
3903
+ applied: wrote
3904
+ } }, null, 2));
3905
+ return;
3906
+ }
3907
+ p.intro("codemem distill draft");
3908
+ p.log.info(`Top candidate [${candidate.scope}] recurrence=${candidate.recurrence} → ${target.display}`);
3909
+ p.log.step(`Proposed rule:\n ${rule}`);
3910
+ if (!applied.changed) {
3911
+ p.log.warn("This rule is already present in the target file.");
3912
+ p.outro("nothing to apply");
3913
+ return;
3914
+ }
3915
+ console.log(diff);
3916
+ if (!opts.apply) {
3917
+ p.outro(`dry run — re-run with --apply to write ${target.display}`);
3918
+ return;
3919
+ }
3920
+ const confirmed = await p.confirm({ message: `Apply this change to ${target.display}?` });
3921
+ if (p.isCancel(confirmed) || !confirmed) {
3922
+ p.outro("aborted — nothing written");
3923
+ return;
3924
+ }
3925
+ mkdirSync(dirname(target.path), { recursive: true });
3926
+ writeFileSync(target.path, applied.text);
3927
+ p.outro(`wrote ${target.display}`);
3928
+ }
3929
+ async function distillAction(opts) {
3930
+ let store = null;
3931
+ try {
3932
+ store = new MemoryStore(resolveDbPath(resolveDbOpt(opts)));
3933
+ const limit = parsePositiveInteger(opts.limit, "limit");
3934
+ const judging = opts.judge !== false;
3935
+ let result = await runDistill(store, opts, judging ? judgeFetchLimit(limit) : limit);
3936
+ if (judging) {
3937
+ try {
3938
+ result = await judgeReport(result);
3939
+ } catch (error) {
3940
+ const message = error instanceof Error ? error.message : String(error);
3941
+ result = {
3942
+ ...result,
3943
+ metadata: {
3944
+ ...result.metadata,
3945
+ judged: false,
3946
+ judge_error: message
3947
+ }
3948
+ };
3949
+ }
3950
+ if (result.candidates.length > limit) result = {
3951
+ ...result,
3952
+ candidates: result.candidates.slice(0, limit),
3953
+ metadata: {
3954
+ ...result.metadata,
3955
+ candidate_count: limit
3956
+ }
3957
+ };
3958
+ }
3959
+ if (opts.draft || opts.apply) {
3960
+ await draftTopCandidate(result, opts);
3961
+ return;
3962
+ }
3963
+ if (opts.json) {
3964
+ console.log(JSON.stringify(result, null, 2));
3965
+ return;
3966
+ }
3967
+ p.intro("codemem distill");
3968
+ console.log(renderDistillReport(result, opts.explain ?? false));
3969
+ if (result.metadata.judged) p.log.info(`judge dropped ${result.metadata.routine_filtered_count ?? 0} routine-activity candidate(s)`);
3970
+ else if (result.metadata.judge_error) p.log.warn(`worthiness judgment skipped (needs a configured observer model): ${result.metadata.judge_error}. Candidates are unfiltered; recurring routine activity may rank high.`);
3971
+ p.outro("review candidates before editing context files");
3972
+ } catch (error) {
3973
+ const message = error instanceof Error ? error.message : String(error);
3974
+ const usageError = error instanceof DistillUsageError;
3975
+ if (opts.json) {
3976
+ emitJsonError(usageError ? "usage_error" : "distill_failed", message, usageError ? 2 : 1);
3977
+ return;
3978
+ }
3979
+ p.log.error(message);
3980
+ process.exitCode = usageError ? 2 : 1;
3981
+ } finally {
3982
+ store?.close();
3983
+ }
3984
+ }
3985
+ var cmd$3 = new Command("distill").configureHelp(helpStyle).description("Mine recurring memories into reviewable context candidates").option("-p, --project <project>", "project identifier (defaults to git repo root)").option("-A, --all-projects", "mine memories across all projects").option("-k, --kind <kind>", "memory kind to mine (repeat or comma-separate)", collectKind, []).option("-m, --min-recurrence <n>", "minimum member count per candidate", "2").option("-l, --limit <n>", "max candidates", "10").option("-e, --explain", "include evidence snippets in human output").option("--include-documented", "include candidates already represented in context files").option("--no-judge", "skip the observer-model worthiness judgment that drops routine-activity clusters").option("-D, --draft", "draft an AGENTS.md rule for the top candidate and show the diff").option("--apply", "write the drafted rule to the target file (implies --draft; prompts to confirm; with --json applies immediately without prompting)");
3986
+ addDbOption(cmd$3);
3987
+ addJsonOption(cmd$3);
3988
+ cmd$3.action(distillAction);
3989
+ var distillCommand = cmd$3;
3990
+ //#endregion
3725
3991
  //#region src/commands/embed.ts
3726
3992
  function parseOptionalPositiveInt(value) {
3727
3993
  if (!value) return void 0;
@@ -7454,6 +7720,7 @@ completion.on("command", ({ reply }) => {
7454
7720
  "config",
7455
7721
  "coordinator",
7456
7722
  "db",
7723
+ "distill",
7457
7724
  "embed",
7458
7725
  "enqueue-raw-event",
7459
7726
  "export-memories",
@@ -7535,6 +7802,7 @@ program.addCommand(claudeHookFileContextCommand);
7535
7802
  program.addCommand(codexHookInjectCommand);
7536
7803
  program.addCommand(codexHookIngestCommand);
7537
7804
  program.addCommand(dbCommand);
7805
+ program.addCommand(distillCommand);
7538
7806
  program.addCommand(exportMemoriesCommand);
7539
7807
  program.addCommand(importMemoriesCommand);
7540
7808
  program.addCommand(statsCommand);