codemem 0.47.0-beta.2 → 0.47.0-beta.3
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 +155 -286
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ACCOUNT_CONNECTION_MIGRATION_STATEMENTS, ACCOUNT_UNLINK_MIGRATION_STATEMENTS, AUTH_LINK_AUTHORITY_CUTOVER_SQL, ContextOnlyReplayError, CoordinatorAccountLinkError, DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, EXTRACTION_BENCHMARK_QUALITY_WEIGHTS, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, ObserverOutputError, ObserverOutputTransportError, OwnerProfileEnrollmentError, PROMPT_TRANSPORT_PROTOCOL_RANGE, REF_BACKFILL_JOB, RawEventIngestValidationError, RawEventSweeper, RefBackfillRunner, RemoteCoordinatorRequestError, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, TRUSTED_HOOK_MAPPER_OPTIONS, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, arePromptTransportProtocolRangesCompatible, atomicReplaceConfigFile, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildPiFlushSignalFromEvent, buildRawEventEnvelopeFromHook, buildRawEventEnvelopeFromPiEvent, buildTieredObserverSelection, buildViewerIdentityTarget, classifyPromptTransportFailure, clonePromptPackAttempt, collectOperationalStatus, compareMemoryRoleReports, connect, connectReadOnly, coordinatorAuthControllerReviewAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantIdentityGroupAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorQualifyOwnerAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewInviteAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeDeviceAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, describePiObserverStatus, detectInstallKind, draftDistillRule, enrollOwnerProfile, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, expandUserPath, exportMemories, extractApplyPatchPaths, extractionReplayObserverIdentity, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getCodememConfigPath, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getUpdateStatus, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, importPiSessions, ingestRawEvents, initDatabase, isAutomaticRecallMeasurement, isCoordinatorControllerAttestationReference, isEmbeddingDisabled, isExplicitUpdateInstallEligible, isReleaseVersionForChannel, judgeDistillReport, linkCoordinatorAccount, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadRuntimeSigningPublicKey, loadSqliteVec, mdnsEnabled, mutateCodememConfigFile, normalizeCoordinatorAccountLinkOrigin, normalizePromptTransportProtocolRange, observerBaseUrlForProviderOverride, planReplicationOpsAgePrune, probeCodememViewerLiveness, probeCodememViewerLiveness as probeCodememViewerLiveness$1, probeRequiredNativeRuntime, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readAccountLinkDevice, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorAccountLinkStatus, readCoordinatorOwnerReviewLocalEvidence, readCoordinatorSyncConfig, readImportPayload, recordAutomaticRecall, recordPromptPackArtifacts, recordPromptPackTerminal,
|
|
2
|
+
import { ACCOUNT_CONNECTION_MIGRATION_STATEMENTS, ACCOUNT_UNLINK_MIGRATION_STATEMENTS, AUTH_LINK_AUTHORITY_CUTOVER_SQL, ContextOnlyReplayError, CoordinatorAccountLinkError, DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, EXTRACTION_BENCHMARK_QUALITY_WEIGHTS, FILE_CONTEXT_TRANSPORT_MAX_BYTES, FILE_CONTEXT_TRANSPORT_PROFILE, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, ObserverOutputError, ObserverOutputTransportError, OwnerProfileEnrollmentError, PROMPT_TRANSPORT_PROTOCOL_RANGE, REF_BACKFILL_JOB, RawEventIngestValidationError, RawEventSweeper, RefBackfillRunner, RemoteCoordinatorRequestError, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, TRUSTED_HOOK_MAPPER_OPTIONS, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, arePromptTransportProtocolRangesCompatible, atomicReplaceConfigFile, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildPiFlushSignalFromEvent, buildRawEventEnvelopeFromHook, buildRawEventEnvelopeFromPiEvent, buildTieredObserverSelection, buildViewerIdentityTarget, classifyPromptTransportFailure, clonePromptPackAttempt, collectOperationalStatus, compareMemoryRoleReports, connect, connectReadOnly, coordinatorAuthControllerReviewAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantIdentityGroupAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorQualifyOwnerAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewInviteAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeDeviceAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, createFileContextRunner, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, describePiObserverStatus, detectInstallKind, dispatchFileContextTransport, draftDistillRule, enrollOwnerProfile, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, expandUserPath, exportMemories, extractApplyPatchPaths, extractionReplayObserverIdentity, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getCodememConfigPath, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getUpdateStatus, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, importPiSessions, ingestRawEvents, initDatabase, isAutomaticRecallMeasurement, isCoordinatorControllerAttestationReference, isEmbeddingDisabled, isExplicitUpdateInstallEligible, isReleaseVersionForChannel, judgeDistillReport, linkCoordinatorAccount, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadRuntimeSigningPublicKey, loadSqliteVec, mdnsEnabled, mutateCodememConfigFile, normalizeCoordinatorAccountLinkOrigin, normalizePromptTransportProtocolRange, observerBaseUrlForProviderOverride, parseFileContextTransportRequest, planReplicationOpsAgePrune, probeCodememViewerLiveness, probeCodememViewerLiveness as probeCodememViewerLiveness$1, probeRequiredNativeRuntime, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readAccountLinkDevice, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorAccountLinkStatus, readCoordinatorOwnerReviewLocalEvidence, readCoordinatorSyncConfig, readImportPayload, recordAutomaticRecall, recordPromptPackArtifacts, recordPromptPackTerminal, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveComparablePath, resolveDbPath, resolveHookProject, resolveKeyPaths, resolvePiAgentDir, resolvePiObserverConfig, resolveProject, resolveProjectRoot, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, searchPiSessions, setPeerProjectFilter, syncPassPreflight, tryUpdateRetrievalDelivery, updatePeerAddresses, upgradeCoordinatorAccountSchemaFile, upgradeCoordinatorAuthSchemaFile, vacuumDatabase, viewerUrl, writeCodememConfigFile } from "@codemem/core";
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import omelette from "omelette";
|
|
5
|
-
import { randomInt, randomUUID } from "node:crypto";
|
|
6
5
|
import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
7
6
|
import { homedir, networkInterfaces } from "node:os";
|
|
8
7
|
import { dirname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
|
|
9
|
-
import { styleText } from "node:util";
|
|
8
|
+
import { isDeepStrictEqual, 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";
|
|
@@ -79,6 +79,54 @@ function emitJsonError(errorCode, message, exitCode = 1) {
|
|
|
79
79
|
process.exitCode = exitCode;
|
|
80
80
|
}
|
|
81
81
|
//#endregion
|
|
82
|
+
//#region src/commands/claude-hook-file-context-transport.ts
|
|
83
|
+
/** Internal packaged-hook transport. Normal CLI invocation keeps its original guards/output. */
|
|
84
|
+
function runClaudeFileContextTransport(payload, opts) {
|
|
85
|
+
const parsed = parseFileContextTransportRequest(payload);
|
|
86
|
+
if (!parsed.success) throw new Error("invalid file-context transport request");
|
|
87
|
+
let request = parsed.data;
|
|
88
|
+
if ([
|
|
89
|
+
"1",
|
|
90
|
+
"true",
|
|
91
|
+
"yes",
|
|
92
|
+
"on"
|
|
93
|
+
].includes(String(process.env.CODEMEM_PLUGIN_IGNORE ?? "").trim().toLowerCase())) return {
|
|
94
|
+
ok: true,
|
|
95
|
+
attempt_id: request.attempt_id,
|
|
96
|
+
request_id: request.request_id,
|
|
97
|
+
result: { continue: true },
|
|
98
|
+
delivery_pending: false
|
|
99
|
+
};
|
|
100
|
+
const disabled = (value) => [
|
|
101
|
+
"0",
|
|
102
|
+
"false",
|
|
103
|
+
"off"
|
|
104
|
+
].includes(String(value ?? "").trim().toLowerCase());
|
|
105
|
+
if (disabled(process.env.CODEMEM_RETRIEVAL_LEDGER)) request = {
|
|
106
|
+
...request,
|
|
107
|
+
ledger_enabled: false
|
|
108
|
+
};
|
|
109
|
+
if (request.action === "lookup" && disabled(process.env.CODEMEM_FILE_CONTEXT)) {
|
|
110
|
+
const { relative_path: _path, project: _project, file_mtime_ms: _mtime, ...base } = request;
|
|
111
|
+
request = {
|
|
112
|
+
...base,
|
|
113
|
+
action: "skip",
|
|
114
|
+
reason: "file_context_disabled"
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const dbPath = resolveDbPath(resolveDbOpt(opts));
|
|
118
|
+
if (resolve(dbPath) !== resolve(request.db_path) || !isDeepStrictEqual(buildViewerIdentityTarget(), request.identity_target)) throw new Error("file-context target mismatch");
|
|
119
|
+
const store = new MemoryStore(dbPath);
|
|
120
|
+
try {
|
|
121
|
+
if (!store.hasCurrentIdentity()) throw new Error("file-context identity changed");
|
|
122
|
+
return dispatchFileContextTransport(store, request);
|
|
123
|
+
} finally {
|
|
124
|
+
try {
|
|
125
|
+
store.close();
|
|
126
|
+
} catch {}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//#endregion
|
|
82
130
|
//#region src/commands/claude-hook-plugin-log.ts
|
|
83
131
|
/**
|
|
84
132
|
* Append-only plugin event log used by `claude-hook-inject` and
|
|
@@ -130,24 +178,12 @@ function logHookEvent(message) {
|
|
|
130
178
|
//#endregion
|
|
131
179
|
//#region src/commands/claude-hook-file-context.ts
|
|
132
180
|
var FILE_GATE_MIN_BYTES = 1500;
|
|
133
|
-
var FETCH_LIMIT = 40;
|
|
134
|
-
var DISPLAY_LIMIT = 15;
|
|
135
|
-
var MTIME_FRESH_TOLERANCE_MS = 3e5;
|
|
136
181
|
var SMALL_FILE_BYPASS_PATTERNS = [
|
|
137
182
|
/\.(json|jsonc|toml|ya?ml)$/i,
|
|
138
183
|
/\.env(\.|$)/i,
|
|
139
184
|
/(^|\/)dockerfile(\.|$)/i,
|
|
140
185
|
/\.config\.(js|ts|mjs|cjs|json)$/i
|
|
141
186
|
];
|
|
142
|
-
var KIND_ICONS = {
|
|
143
|
-
decision: "⚖️",
|
|
144
|
-
bugfix: "🔴",
|
|
145
|
-
feature: "🟢",
|
|
146
|
-
refactor: "🔄",
|
|
147
|
-
discovery: "🔵",
|
|
148
|
-
change: "✅",
|
|
149
|
-
exploration: "🔬"
|
|
150
|
-
};
|
|
151
187
|
function emitJson$2(value) {
|
|
152
188
|
console.log(JSON.stringify(value));
|
|
153
189
|
}
|
|
@@ -187,105 +223,6 @@ function statFile(absPath) {
|
|
|
187
223
|
return null;
|
|
188
224
|
}
|
|
189
225
|
}
|
|
190
|
-
function parseJsonArray(value) {
|
|
191
|
-
if (!value) return [];
|
|
192
|
-
try {
|
|
193
|
-
const parsed = JSON.parse(value);
|
|
194
|
-
if (!Array.isArray(parsed)) return [];
|
|
195
|
-
return parsed.filter((item) => typeof item === "string");
|
|
196
|
-
} catch {
|
|
197
|
-
return [];
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
function normalizePathForCompare(path) {
|
|
201
|
-
return path.replace(/\\/g, "/");
|
|
202
|
-
}
|
|
203
|
-
function scoreRow(row, normalizedTarget, idx) {
|
|
204
|
-
const filesModified = parseJsonArray(row.files_modified);
|
|
205
|
-
const inModified = filesModified.some((f) => normalizePathForCompare(f) === normalizedTarget);
|
|
206
|
-
let score = 0;
|
|
207
|
-
if (inModified) score += 2;
|
|
208
|
-
if (filesModified.length <= 1) score += 2;
|
|
209
|
-
else if (filesModified.length <= 3) score += 1;
|
|
210
|
-
return {
|
|
211
|
-
row,
|
|
212
|
-
score,
|
|
213
|
-
idx
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
function scoreAndDedupe(rows, targetPath, limit) {
|
|
217
|
-
const normalizedTarget = normalizePathForCompare(targetPath);
|
|
218
|
-
const scored = rows.map((row, idx) => scoreRow(row, normalizedTarget, idx));
|
|
219
|
-
const bestPerSession = /* @__PURE__ */ new Map();
|
|
220
|
-
for (const item of scored) {
|
|
221
|
-
const existing = bestPerSession.get(item.row.session_id);
|
|
222
|
-
if (!existing || item.score > existing.score || item.score === existing.score && item.idx < existing.idx) bestPerSession.set(item.row.session_id, item);
|
|
223
|
-
}
|
|
224
|
-
const deduped = Array.from(bestPerSession.values());
|
|
225
|
-
deduped.sort((a, b) => b.score - a.score || a.idx - b.idx);
|
|
226
|
-
return deduped.slice(0, limit).map((s) => s.row);
|
|
227
|
-
}
|
|
228
|
-
function compactTime(timeStr) {
|
|
229
|
-
return timeStr.toLowerCase().replace(" am", "a").replace(" pm", "p");
|
|
230
|
-
}
|
|
231
|
-
function formatTime(epochMs) {
|
|
232
|
-
return new Date(epochMs).toLocaleString("en-US", {
|
|
233
|
-
hour: "numeric",
|
|
234
|
-
minute: "2-digit",
|
|
235
|
-
hour12: true
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
function formatDate(epochMs) {
|
|
239
|
-
return new Date(epochMs).toLocaleString("en-US", {
|
|
240
|
-
month: "short",
|
|
241
|
-
day: "numeric",
|
|
242
|
-
year: "numeric"
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
function formatTimeline(rows, filePath, staleness) {
|
|
246
|
-
const safePath = filePath.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
247
|
-
const enriched = rows.map((row) => ({
|
|
248
|
-
row,
|
|
249
|
-
epochMs: Date.parse(row.created_at)
|
|
250
|
-
})).filter((item) => Number.isFinite(item.epochMs) && item.epochMs > 0);
|
|
251
|
-
const byDay = /* @__PURE__ */ new Map();
|
|
252
|
-
for (const item of enriched) {
|
|
253
|
-
const day = formatDate(item.epochMs);
|
|
254
|
-
const bucket = byDay.get(day);
|
|
255
|
-
if (bucket) bucket.push(item);
|
|
256
|
-
else byDay.set(day, [item]);
|
|
257
|
-
}
|
|
258
|
-
const sortedDays = Array.from(byDay.entries()).sort((a, b) => {
|
|
259
|
-
return Math.min(...a[1].map((i) => i.epochMs)) - Math.min(...b[1].map((i) => i.epochMs));
|
|
260
|
-
});
|
|
261
|
-
const ids = rows.map((r) => r.id);
|
|
262
|
-
const lines = [`This file (${safePath}) has prior codemem observations. The Read result below is unchanged.`, `- Fetch full bodies on demand: memory.get_observations([${ids.join(", ")}]).`];
|
|
263
|
-
if (staleness) {
|
|
264
|
-
const driftMinutes = Math.max(1, Math.round((staleness.fileMtimeMs - staleness.newestObservationMs) / 6e4));
|
|
265
|
-
lines.unshift(`Heads up: this file was modified ~${driftMinutes} min after the most recent observation below. Past entries may be partially stale — verify against the Read result before relying on them.`);
|
|
266
|
-
}
|
|
267
|
-
for (const [day, dayItems] of sortedDays) {
|
|
268
|
-
const chronological = [...dayItems].sort((a, b) => a.epochMs - b.epochMs);
|
|
269
|
-
lines.push(`### ${day}`);
|
|
270
|
-
for (const { row, epochMs } of chronological) {
|
|
271
|
-
const title = (row.title || "Untitled").replace(/[\r\n\t]+/g, " ").replace(/\s+/g, " ").trim().slice(0, 160);
|
|
272
|
-
const icon = KIND_ICONS[row.kind] ?? "❔";
|
|
273
|
-
const time = compactTime(formatTime(epochMs));
|
|
274
|
-
lines.push(`${row.id} ${time} ${icon} (${row.kind}) ${title}`);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
return lines.join("\n");
|
|
278
|
-
}
|
|
279
|
-
function queryByFile(dbPath, relativePath, project, limit) {
|
|
280
|
-
const store = new MemoryStore(dbPath);
|
|
281
|
-
try {
|
|
282
|
-
const opts = { limit };
|
|
283
|
-
if (project) opts.project = project;
|
|
284
|
-
return store.findByFile(relativePath, opts);
|
|
285
|
-
} finally {
|
|
286
|
-
store.close();
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
226
|
function resolveProject$1(payload) {
|
|
290
227
|
const cwd = typeof payload.cwd === "string" ? payload.cwd : null;
|
|
291
228
|
return resolveHookProject(cwd, payload.project);
|
|
@@ -294,73 +231,45 @@ function sourceSessionId(payload) {
|
|
|
294
231
|
const value = payload.session_id;
|
|
295
232
|
return typeof value === "string" && value.trim() ? value.trim() : null;
|
|
296
233
|
}
|
|
297
|
-
|
|
298
|
-
if (envTruthy$4(process.env.CODEMEM_PLUGIN_IGNORE)) return continueResult$2();
|
|
299
|
-
const filePath = extractFilePath(payload);
|
|
300
|
-
if (!filePath) return continueResult$2();
|
|
301
|
-
const startedAt = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
|
|
302
|
-
const attemptId = (deps.createAttemptId ?? randomUUID)();
|
|
303
|
-
const resolveDb = deps.resolveDb ?? resolveDbPath;
|
|
234
|
+
function fileContextStore(opts, deps) {
|
|
304
235
|
let resolvedDbPath = null;
|
|
305
236
|
let activeStore = null;
|
|
306
|
-
const
|
|
307
|
-
resolvedDbPath ??= resolveDb(resolveDbOpt(opts));
|
|
308
|
-
|
|
309
|
-
return activeStore;
|
|
237
|
+
const dbPath = () => {
|
|
238
|
+
resolvedDbPath ??= (deps.resolveDb ?? resolveDbPath)(resolveDbOpt(opts));
|
|
239
|
+
return resolvedDbPath;
|
|
310
240
|
};
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
} catch {}
|
|
346
|
-
};
|
|
347
|
-
const updateDelivery = (status) => {
|
|
348
|
-
if (!envNotDisabled$3(process.env.CODEMEM_RETRIEVAL_LEDGER || "1")) return;
|
|
349
|
-
try {
|
|
350
|
-
resolvedDbPath ??= resolveDb(resolveDbOpt(opts));
|
|
351
|
-
if (deps.updateDelivery) deps.updateDelivery(resolvedDbPath, attemptId, status);
|
|
352
|
-
else tryUpdateRetrievalDelivery(getStore().db, attemptId, status);
|
|
353
|
-
} catch {}
|
|
354
|
-
};
|
|
355
|
-
if (!envNotDisabled$3(process.env.CODEMEM_FILE_CONTEXT || "1")) {
|
|
356
|
-
record({
|
|
357
|
-
retrievalStatus: "skipped",
|
|
358
|
-
deliveryStatus: "not_attempted",
|
|
359
|
-
failureCode: "file_context_disabled",
|
|
360
|
-
failureStage: "configuration"
|
|
361
|
-
});
|
|
362
|
-
return finish(continueResult$2());
|
|
363
|
-
}
|
|
241
|
+
return {
|
|
242
|
+
dbPath,
|
|
243
|
+
getStore: () => {
|
|
244
|
+
activeStore ??= (deps.createStore ?? ((path) => new MemoryStore(path)))(dbPath());
|
|
245
|
+
return activeStore;
|
|
246
|
+
},
|
|
247
|
+
finish: (result) => {
|
|
248
|
+
try {
|
|
249
|
+
activeStore?.close();
|
|
250
|
+
} catch {}
|
|
251
|
+
activeStore = null;
|
|
252
|
+
return result;
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function fileContextRunner(payload, store, deps) {
|
|
257
|
+
const findByFile = deps.queryByFile;
|
|
258
|
+
const recordAttempt = deps.recordAttempt;
|
|
259
|
+
const updateDelivery = deps.updateDelivery;
|
|
260
|
+
return createFileContextRunner({
|
|
261
|
+
getStore: store.getStore,
|
|
262
|
+
sourceSessionId: sourceSessionId(payload),
|
|
263
|
+
ledgerEnabled: () => envNotDisabled$3(process.env.CODEMEM_RETRIEVAL_LEDGER || "1"),
|
|
264
|
+
now: deps.now,
|
|
265
|
+
createAttemptId: deps.createAttemptId,
|
|
266
|
+
log: logHookEvent,
|
|
267
|
+
...findByFile ? { findByFile: (path, options) => findByFile(store.dbPath(), path, options.project ?? null, options.limit) } : {},
|
|
268
|
+
...recordAttempt ? { recordAttempt: (input) => recordAttempt(store.dbPath(), input) } : {},
|
|
269
|
+
...updateDelivery ? { updateDelivery: (attemptId, status) => updateDelivery(store.dbPath(), attemptId, status) } : {}
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
function fileContextPath(filePath, payload) {
|
|
364
273
|
const cwd = typeof payload.cwd === "string" && payload.cwd.trim() ? payload.cwd : process.cwd();
|
|
365
274
|
const expandedPath = expandHome$2(filePath);
|
|
366
275
|
const absolutePath = isAbsolute(expandedPath) ? expandedPath : resolve(cwd, expandedPath);
|
|
@@ -368,133 +277,78 @@ async function buildClaudeFileContext(payload, opts, deps = {}) {
|
|
|
368
277
|
const escapesCwd = relativePath === ".." || relativePath.startsWith("../") || isAbsolute(relativePath);
|
|
369
278
|
if (!relativePath || escapesCwd) {
|
|
370
279
|
logHookEvent(`file_context.skip reason=outside_cwd path=${JSON.stringify(filePath)} cwd=${JSON.stringify(cwd)}`);
|
|
371
|
-
|
|
372
|
-
retrievalStatus: "skipped",
|
|
373
|
-
deliveryStatus: "not_attempted",
|
|
374
|
-
failureCode: "outside_cwd",
|
|
375
|
-
failureStage: "path_validation"
|
|
376
|
-
});
|
|
377
|
-
return finish(continueResult$2());
|
|
280
|
+
return null;
|
|
378
281
|
}
|
|
282
|
+
return {
|
|
283
|
+
absolutePath,
|
|
284
|
+
relativePath
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
async function buildClaudeFileContext(payload, opts, deps = {}) {
|
|
288
|
+
if (envTruthy$4(process.env.CODEMEM_PLUGIN_IGNORE)) return continueResult$2();
|
|
289
|
+
const filePath = extractFilePath(payload);
|
|
290
|
+
if (!filePath) return continueResult$2();
|
|
291
|
+
const store = fileContextStore(opts, deps);
|
|
292
|
+
const runner = fileContextRunner(payload, store, deps);
|
|
293
|
+
if (!envNotDisabled$3(process.env.CODEMEM_FILE_CONTEXT || "1")) return store.finish(runner.skip({ reason: "file_context_disabled" }));
|
|
294
|
+
const path = fileContextPath(filePath, payload);
|
|
295
|
+
if (!path) return store.finish(runner.skip({ reason: "outside_cwd" }));
|
|
296
|
+
const { absolutePath, relativePath } = path;
|
|
379
297
|
const minBytes = Number.parseInt(process.env.CODEMEM_FILE_CONTEXT_MIN_BYTES ?? `${FILE_GATE_MIN_BYTES}`, 10);
|
|
380
298
|
const minBytesEffective = Number.isFinite(minBytes) && minBytes >= 0 ? minBytes : FILE_GATE_MIN_BYTES;
|
|
381
299
|
const stat = (deps.statFile ?? statFile)(absolutePath);
|
|
382
300
|
if (!stat) {
|
|
383
301
|
logHookEvent(`file_context.skip reason=stat_failed path=${JSON.stringify(relativePath)}`);
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
deliveryStatus: "not_attempted",
|
|
387
|
-
failureCode: "stat_failed",
|
|
388
|
-
failureStage: "file_access",
|
|
302
|
+
return store.finish(runner.skip({
|
|
303
|
+
reason: "stat_failed",
|
|
389
304
|
repositoryPaths: [relativePath]
|
|
390
|
-
});
|
|
391
|
-
return finish(continueResult$2());
|
|
305
|
+
}));
|
|
392
306
|
}
|
|
393
307
|
const bypassSizeGate = SMALL_FILE_BYPASS_PATTERNS.some((p) => p.test(relativePath));
|
|
394
308
|
if (stat.sizeBytes < minBytesEffective && !bypassSizeGate) {
|
|
395
309
|
logHookEvent(`file_context.skip reason=below_size_gate path=${JSON.stringify(relativePath)} size=${stat.sizeBytes} gate=${minBytesEffective}`);
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
deliveryStatus: "not_attempted",
|
|
399
|
-
failureCode: "below_size_gate",
|
|
400
|
-
failureStage: "size_gate",
|
|
401
|
-
repositoryPaths: [relativePath]
|
|
402
|
-
});
|
|
403
|
-
return finish(continueResult$2());
|
|
404
|
-
}
|
|
405
|
-
const project = resolveProject$1(payload);
|
|
406
|
-
const queryFn = deps.queryByFile ?? queryByFile;
|
|
407
|
-
let rows = [];
|
|
408
|
-
try {
|
|
409
|
-
resolvedDbPath ??= resolveDb(resolveDbOpt(opts));
|
|
410
|
-
rows = deps.queryByFile ? queryFn(resolvedDbPath, relativePath, project, FETCH_LIMIT) : getStore().findByFile(relativePath, {
|
|
411
|
-
limit: FETCH_LIMIT,
|
|
412
|
-
...project ? { project } : {}
|
|
413
|
-
});
|
|
414
|
-
} catch (err) {
|
|
415
|
-
logHookEvent(`codemem claude-hook-file-context query failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
416
|
-
record({
|
|
417
|
-
retrievalStatus: "failed",
|
|
418
|
-
deliveryStatus: "not_attempted",
|
|
419
|
-
failureCode: "query_failed",
|
|
420
|
-
failureStage: "retrieval",
|
|
421
|
-
project,
|
|
422
|
-
filters: project ? { project } : void 0,
|
|
310
|
+
return store.finish(runner.skip({
|
|
311
|
+
reason: "below_size_gate",
|
|
423
312
|
repositoryPaths: [relativePath]
|
|
424
|
-
});
|
|
425
|
-
return finish(continueResult$2());
|
|
426
|
-
}
|
|
427
|
-
if (rows.length === 0) {
|
|
428
|
-
logHookEvent(`file_context.skip reason=no_observations path=${JSON.stringify(relativePath)} project=${JSON.stringify(project ?? "")}`);
|
|
429
|
-
record({
|
|
430
|
-
retrievalStatus: "no_results",
|
|
431
|
-
deliveryStatus: "not_attempted",
|
|
432
|
-
project,
|
|
433
|
-
filters: project ? { project } : void 0,
|
|
434
|
-
repositoryPaths: [relativePath]
|
|
435
|
-
});
|
|
436
|
-
return finish(continueResult$2());
|
|
437
|
-
}
|
|
438
|
-
const top = scoreAndDedupe(rows, relativePath, DISPLAY_LIMIT);
|
|
439
|
-
if (top.length === 0) {
|
|
440
|
-
logHookEvent(`file_context.skip reason=no_top_after_dedupe path=${JSON.stringify(relativePath)} candidates=${rows.length}`);
|
|
441
|
-
record({
|
|
442
|
-
retrievalStatus: "succeeded",
|
|
443
|
-
deliveryStatus: "not_attempted",
|
|
444
|
-
candidateIds: rows.map((row) => row.id),
|
|
445
|
-
candidateCount: rows.length,
|
|
446
|
-
selectedIds: [],
|
|
447
|
-
failureCode: "no_top_after_dedupe",
|
|
448
|
-
failureStage: "selection",
|
|
449
|
-
project,
|
|
450
|
-
filters: project ? { project } : void 0,
|
|
451
|
-
repositoryPaths: [relativePath]
|
|
452
|
-
});
|
|
453
|
-
return finish(continueResult$2());
|
|
454
|
-
}
|
|
455
|
-
let staleness = null;
|
|
456
|
-
if (stat.mtimeMs > 0) {
|
|
457
|
-
const newestObservationMs = top.reduce((max, row) => {
|
|
458
|
-
const epoch = Date.parse(row.created_at);
|
|
459
|
-
return Number.isFinite(epoch) && epoch > max ? epoch : max;
|
|
460
|
-
}, 0);
|
|
461
|
-
if (newestObservationMs > 0 && stat.mtimeMs > newestObservationMs + MTIME_FRESH_TOLERANCE_MS) staleness = {
|
|
462
|
-
fileMtimeMs: stat.mtimeMs,
|
|
463
|
-
newestObservationMs
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
record({
|
|
467
|
-
retrievalStatus: "succeeded",
|
|
468
|
-
deliveryStatus: "not_attempted",
|
|
469
|
-
candidateIds: rows.map((row) => row.id),
|
|
470
|
-
candidateCount: rows.length,
|
|
471
|
-
selectedIds: top.map((row) => row.id),
|
|
472
|
-
project,
|
|
473
|
-
filters: project ? { project } : void 0,
|
|
474
|
-
repositoryPaths: [relativePath]
|
|
475
|
-
});
|
|
476
|
-
let timeline;
|
|
477
|
-
try {
|
|
478
|
-
timeline = formatTimeline(top, relativePath, staleness);
|
|
479
|
-
} catch {
|
|
480
|
-
updateDelivery("failed");
|
|
481
|
-
return finish(continueResult$2());
|
|
313
|
+
}));
|
|
482
314
|
}
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
additionalContext: timeline
|
|
489
|
-
} });
|
|
315
|
+
return store.finish(runner.run({
|
|
316
|
+
relativePath,
|
|
317
|
+
project: resolveProject$1(payload),
|
|
318
|
+
fileMtimeMs: stat.mtimeMs
|
|
319
|
+
}));
|
|
490
320
|
}
|
|
491
321
|
var claudeHookFileContextCmd = new Command("claude-hook-file-context").configureHelp(helpStyle).description("Return Claude PreToolUse:Read additionalContext from per-file observation timeline");
|
|
492
322
|
addDbOption(claudeHookFileContextCmd);
|
|
323
|
+
claudeHookFileContextCmd.addOption(new Option("--transport-profile").hideHelp());
|
|
324
|
+
claudeHookFileContextCmd.addOption(new Option("--transport").hideHelp());
|
|
493
325
|
var claudeHookFileContextCommand = claudeHookFileContextCmd.action(async (opts) => {
|
|
326
|
+
if (opts.transportProfile) {
|
|
327
|
+
process.stdout.write(`${JSON.stringify(FILE_CONTEXT_TRANSPORT_PROFILE)}\n`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
494
330
|
let raw = "";
|
|
495
|
-
for await (const chunk of process.stdin)
|
|
331
|
+
for await (const chunk of process.stdin) {
|
|
332
|
+
raw += String(chunk);
|
|
333
|
+
if (opts.transport && Buffer.byteLength(raw) > FILE_CONTEXT_TRANSPORT_MAX_BYTES) {
|
|
334
|
+
emitError$2({
|
|
335
|
+
error: "parse_error",
|
|
336
|
+
message: "transport payload too large"
|
|
337
|
+
});
|
|
338
|
+
process.exitCode = 1;
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
496
342
|
const trimmed = raw.trim();
|
|
497
343
|
if (!trimmed) {
|
|
344
|
+
if (opts.transport) {
|
|
345
|
+
process.stdout.write(`${JSON.stringify({
|
|
346
|
+
error: "parse_error",
|
|
347
|
+
message: "transport request required"
|
|
348
|
+
})}\n`);
|
|
349
|
+
process.exitCode = 1;
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
498
352
|
emitJson$2(continueResult$2());
|
|
499
353
|
return;
|
|
500
354
|
}
|
|
@@ -518,8 +372,23 @@ var claudeHookFileContextCommand = claudeHookFileContextCmd.action(async (opts)
|
|
|
518
372
|
process.exitCode = 1;
|
|
519
373
|
return;
|
|
520
374
|
}
|
|
521
|
-
|
|
375
|
+
await emitFileContext(payload, opts);
|
|
522
376
|
});
|
|
377
|
+
async function emitFileContext(payload, opts) {
|
|
378
|
+
try {
|
|
379
|
+
if (opts.transport) {
|
|
380
|
+
process.stdout.write(`${JSON.stringify(runClaudeFileContextTransport(payload, opts))}\n`);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
emitJson$2(await buildClaudeFileContext(payload, opts));
|
|
384
|
+
} catch {
|
|
385
|
+
emitError$2({
|
|
386
|
+
error: "file_context_failed",
|
|
387
|
+
message: "file context unavailable"
|
|
388
|
+
});
|
|
389
|
+
process.exitCode = 1;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
523
392
|
//#endregion
|
|
524
393
|
//#region src/commands/hook-ingest-live-spool.ts
|
|
525
394
|
function spoolLiveHookPayload(dir, payload, logPrefix) {
|