memorix 0.6.3 → 0.6.5

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/cli/index.js CHANGED
@@ -52,6 +52,9 @@ function sanitizeProjectId(projectId) {
52
52
  return projectId.replace(/\//g, "--").replace(/[<>:"|?*\\]/g, "_");
53
53
  }
54
54
  async function getProjectDataDir(projectId, baseDir) {
55
+ if (projectId === "__invalid__") {
56
+ throw new Error("Cannot create data directory for invalid project");
57
+ }
55
58
  const base = baseDir ?? DEFAULT_DATA_DIR;
56
59
  const dirName = sanitizeProjectId(projectId);
57
60
  const dataDir = path2.join(base, dirName);
@@ -1223,6 +1226,7 @@ __export(detector_exports, {
1223
1226
  });
1224
1227
  import { execSync } from "child_process";
1225
1228
  import { existsSync } from "fs";
1229
+ import os2 from "os";
1226
1230
  import path3 from "path";
1227
1231
  function detectProject(cwd) {
1228
1232
  const basePath = cwd ?? process.cwd();
@@ -1233,11 +1237,77 @@ function detectProject(cwd) {
1233
1237
  const name2 = id2.split("/").pop() ?? path3.basename(rootPath);
1234
1238
  return { id: id2, name: name2, gitRemote, rootPath };
1235
1239
  }
1240
+ if (!isValidProjectRoot(rootPath)) {
1241
+ console.error(`[memorix] Skipped invalid project root: ${rootPath}`);
1242
+ return { id: "__invalid__", name: "unknown", rootPath };
1243
+ }
1236
1244
  const name = path3.basename(rootPath);
1237
1245
  const id = `local/${name}`;
1238
1246
  console.error(`[memorix] Warning: no git remote found at ${rootPath}, using fallback projectId: ${id}`);
1239
1247
  return { id, name, rootPath };
1240
1248
  }
1249
+ function isValidProjectRoot(dirPath) {
1250
+ const resolved = path3.resolve(dirPath);
1251
+ const home = path3.resolve(os2.homedir());
1252
+ if (resolved === home) return false;
1253
+ if (resolved === path3.parse(resolved).root) return false;
1254
+ const basename2 = path3.basename(resolved).toLowerCase();
1255
+ const knownNonProjectDirs = /* @__PURE__ */ new Set([
1256
+ // IDE / editor config dirs
1257
+ ".vscode",
1258
+ ".cursor",
1259
+ ".windsurf",
1260
+ ".kiro",
1261
+ ".codex",
1262
+ ".gemini",
1263
+ ".claude",
1264
+ ".github",
1265
+ ".git",
1266
+ // OS / system dirs
1267
+ "desktop",
1268
+ "documents",
1269
+ "downloads",
1270
+ "pictures",
1271
+ "videos",
1272
+ "music",
1273
+ "appdata",
1274
+ "application data",
1275
+ "library",
1276
+ // Package manager / tool dirs
1277
+ "node_modules",
1278
+ ".npm",
1279
+ ".yarn",
1280
+ ".pnpm-store",
1281
+ ".config",
1282
+ ".local",
1283
+ ".cache",
1284
+ ".ssh",
1285
+ ".memorix"
1286
+ ]);
1287
+ if (knownNonProjectDirs.has(basename2)) {
1288
+ const parent = path3.resolve(path3.dirname(resolved));
1289
+ if (parent === home || parent === path3.parse(parent).root) {
1290
+ return false;
1291
+ }
1292
+ }
1293
+ const projectIndicators = [
1294
+ "package.json",
1295
+ "Cargo.toml",
1296
+ "go.mod",
1297
+ "pyproject.toml",
1298
+ "setup.py",
1299
+ "pom.xml",
1300
+ "build.gradle",
1301
+ "Makefile",
1302
+ "CMakeLists.txt",
1303
+ "composer.json",
1304
+ "Gemfile",
1305
+ ".git",
1306
+ "README.md",
1307
+ "README"
1308
+ ];
1309
+ return projectIndicators.some((f) => existsSync(path3.join(resolved, f)));
1310
+ }
1241
1311
  function findPackageRoot(cwd) {
1242
1312
  let dir = path3.resolve(cwd);
1243
1313
  const root = path3.parse(dir).root;
@@ -3164,7 +3234,7 @@ __export(installers_exports, {
3164
3234
  });
3165
3235
  import * as fs3 from "fs/promises";
3166
3236
  import * as path5 from "path";
3167
- import * as os2 from "os";
3237
+ import * as os3 from "os";
3168
3238
  import { createRequire } from "module";
3169
3239
  function resolveHookCommand() {
3170
3240
  if (process.platform === "win32") {
@@ -3269,7 +3339,7 @@ function getProjectConfigPath(agent, projectRoot) {
3269
3339
  }
3270
3340
  }
3271
3341
  function getGlobalConfigPath(agent) {
3272
- const home = os2.homedir();
3342
+ const home = os3.homedir();
3273
3343
  switch (agent) {
3274
3344
  case "claude":
3275
3345
  case "copilot":
@@ -3284,7 +3354,7 @@ function getGlobalConfigPath(agent) {
3284
3354
  }
3285
3355
  async function detectInstalledAgents() {
3286
3356
  const agents = [];
3287
- const home = os2.homedir();
3357
+ const home = os3.homedir();
3288
3358
  const claudeDir = path5.join(home, ".claude");
3289
3359
  try {
3290
3360
  await fs3.access(claudeDir);
@@ -5029,9 +5099,10 @@ function normalizeCursor(payload, event) {
5029
5099
  return result;
5030
5100
  }
5031
5101
  function normalizeHookInput(payload) {
5102
+ const directEvent = typeof payload.event === "string" ? EVENT_MAP[payload.event] : void 0;
5032
5103
  const agent = detectAgent(payload);
5033
5104
  const rawEventName = extractEventName(payload, agent);
5034
- const event = EVENT_MAP[rawEventName] ?? "post_tool";
5105
+ const event = directEvent ?? EVENT_MAP[rawEventName] ?? "post_tool";
5035
5106
  const timestamp = payload.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
5036
5107
  let agentSpecific = {};
5037
5108
  switch (agent) {
@@ -5064,6 +5135,16 @@ var init_normalizer = __esm({
5064
5135
  "use strict";
5065
5136
  init_esm_shims();
5066
5137
  EVENT_MAP = {
5138
+ // Identity mappings — already-normalized event names
5139
+ // This allows direct payloads like { event: 'session_start' } to work
5140
+ session_start: "session_start",
5141
+ user_prompt: "user_prompt",
5142
+ post_edit: "post_edit",
5143
+ post_command: "post_command",
5144
+ post_tool: "post_tool",
5145
+ pre_compact: "pre_compact",
5146
+ session_end: "session_end",
5147
+ post_response: "post_response",
5067
5148
  // Claude Code / VS Code Copilot
5068
5149
  SessionStart: "session_start",
5069
5150
  UserPromptSubmit: "user_prompt",
@@ -5324,13 +5405,32 @@ async function handleHookEvent(input) {
5324
5405
  "discovery": 2,
5325
5406
  "how-it-works": 1
5326
5407
  };
5327
- const scored = allObs.map((obs, i) => ({
5328
- obs,
5329
- priority: PRIORITY_ORDER[obs.type ?? ""] ?? 0,
5330
- recency: i
5331
- // higher index = more recent
5332
- })).sort((a, b) => {
5333
- if (b.priority !== a.priority) return b.priority - a.priority;
5408
+ const LOW_QUALITY_PATTERNS2 = [
5409
+ /^Session activity/i,
5410
+ /^Updated \S+\.\w+$/i,
5411
+ // "Updated foo.ts" — too generic
5412
+ /^Created \S+\.\w+$/i,
5413
+ // "Created bar.js"
5414
+ /^Deleted \S+\.\w+$/i,
5415
+ /^Modified \S+\.\w+$/i
5416
+ ];
5417
+ const isLowQuality2 = (title) => LOW_QUALITY_PATTERNS2.some((p3) => p3.test(title));
5418
+ const scored = allObs.map((obs, i) => {
5419
+ const title = obs.title ?? "";
5420
+ const hasFacts = (obs.facts?.length ?? 0) > 0;
5421
+ const hasSubstance = title.length > 20 || hasFacts;
5422
+ const quality = isLowQuality2(title) ? 0.1 : hasSubstance ? 1 : 0.5;
5423
+ return {
5424
+ obs,
5425
+ priority: PRIORITY_ORDER[obs.type ?? ""] ?? 0,
5426
+ quality,
5427
+ recency: i
5428
+ // higher index = more recent
5429
+ };
5430
+ }).sort((a, b) => {
5431
+ const scoreA = a.priority * a.quality;
5432
+ const scoreB = b.priority * b.quality;
5433
+ if (scoreB !== scoreA) return scoreB - scoreA;
5334
5434
  return b.recency - a.recency;
5335
5435
  });
5336
5436
  const top = scored.slice(0, 5);
@@ -5750,13 +5850,130 @@ var init_dashboard = __esm({
5750
5850
  }
5751
5851
  });
5752
5852
 
5853
+ // src/cli/commands/cleanup.ts
5854
+ var cleanup_exports = {};
5855
+ __export(cleanup_exports, {
5856
+ default: () => cleanup_default
5857
+ });
5858
+ import { defineCommand as defineCommand10 } from "citty";
5859
+ function isLowQuality(title) {
5860
+ return LOW_QUALITY_PATTERNS.some((p3) => p3.test(title.trim()));
5861
+ }
5862
+ var LOW_QUALITY_PATTERNS, cleanup_default;
5863
+ var init_cleanup = __esm({
5864
+ "src/cli/commands/cleanup.ts"() {
5865
+ "use strict";
5866
+ init_esm_shims();
5867
+ init_detector();
5868
+ init_persistence();
5869
+ LOW_QUALITY_PATTERNS = [
5870
+ /^Session activity/i,
5871
+ /^Updated \S+\.\w+$/i,
5872
+ /^Created \S+\.\w+$/i,
5873
+ /^Deleted \S+\.\w+$/i,
5874
+ /^Modified \S+\.\w+$/i,
5875
+ /^Ran command:/i,
5876
+ /^Read file:/i
5877
+ ];
5878
+ cleanup_default = defineCommand10({
5879
+ meta: {
5880
+ name: "cleanup",
5881
+ description: "Remove low-quality auto-generated observations"
5882
+ },
5883
+ args: {
5884
+ dry: {
5885
+ type: "boolean",
5886
+ description: "Preview only \u2014 do not delete anything",
5887
+ default: false
5888
+ },
5889
+ force: {
5890
+ type: "boolean",
5891
+ description: "Delete without confirmation",
5892
+ default: false
5893
+ }
5894
+ },
5895
+ async run({ args }) {
5896
+ const project = detectProject();
5897
+ if (project.id === "__invalid__") {
5898
+ console.error("\u274C Not in a valid project directory.");
5899
+ process.exit(1);
5900
+ }
5901
+ console.log(`
5902
+ \u{1F4E6} Project: ${project.name} (${project.id})
5903
+ `);
5904
+ const dataDir = await getProjectDataDir(project.id);
5905
+ const allObs = await loadObservationsJson(dataDir);
5906
+ if (allObs.length === 0) {
5907
+ console.log("\u2705 No observations found \u2014 nothing to clean up.");
5908
+ return;
5909
+ }
5910
+ const lowQuality = allObs.filter((o) => isLowQuality(o.title ?? ""));
5911
+ const highQuality = allObs.filter((o) => !isLowQuality(o.title ?? ""));
5912
+ const seen = /* @__PURE__ */ new Set();
5913
+ const duplicates = [];
5914
+ const unique = [];
5915
+ for (const obs of highQuality) {
5916
+ const key = `${obs.type}|${obs.title}|${obs.entityName}`;
5917
+ if (seen.has(key)) {
5918
+ duplicates.push(obs);
5919
+ } else {
5920
+ seen.add(key);
5921
+ unique.push(obs);
5922
+ }
5923
+ }
5924
+ const toRemove = [...lowQuality, ...duplicates];
5925
+ console.log(`\u{1F4CA} Analysis:`);
5926
+ console.log(` Total observations: ${allObs.length}`);
5927
+ console.log(` \u{1F7E2} High quality: ${unique.length}`);
5928
+ console.log(` \u{1F534} Low quality: ${lowQuality.length}`);
5929
+ console.log(` \u{1F7E1} Duplicates: ${duplicates.length}`);
5930
+ console.log(` \u{1F5D1}\uFE0F To remove: ${toRemove.length}`);
5931
+ console.log();
5932
+ if (toRemove.length === 0) {
5933
+ console.log("\u2705 All observations are high quality \u2014 nothing to clean up!");
5934
+ return;
5935
+ }
5936
+ console.log("\u{1F50D} Examples of items to remove:");
5937
+ toRemove.slice(0, 10).forEach((o) => {
5938
+ const tag = isLowQuality(o.title ?? "") ? "(low-quality)" : "(duplicate)";
5939
+ console.log(` ${tag} #${o.id ?? "?"} "${o.title}" [${o.type}]`);
5940
+ });
5941
+ if (toRemove.length > 10) {
5942
+ console.log(` ... and ${toRemove.length - 10} more`);
5943
+ }
5944
+ console.log();
5945
+ if (args.dry) {
5946
+ console.log("\u{1F512} Dry run \u2014 no changes made.");
5947
+ return;
5948
+ }
5949
+ if (!args.force) {
5950
+ const readline = await import("readline");
5951
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
5952
+ const answer = await new Promise((resolve2) => {
5953
+ rl.question(`\u26A0\uFE0F Delete ${toRemove.length} observations? (y/N) `, resolve2);
5954
+ });
5955
+ rl.close();
5956
+ if (answer.trim().toLowerCase() !== "y") {
5957
+ console.log("\u274C Cancelled.");
5958
+ return;
5959
+ }
5960
+ }
5961
+ const removeIds = new Set(toRemove.map((o) => JSON.stringify(o)));
5962
+ const remaining = allObs.filter((o) => !removeIds.has(JSON.stringify(o)));
5963
+ await saveObservationsJson(dataDir, remaining);
5964
+ console.log(`\u2705 Removed ${toRemove.length} observations. ${remaining.length} remaining.`);
5965
+ }
5966
+ });
5967
+ }
5968
+ });
5969
+
5753
5970
  // src/cli/index.ts
5754
5971
  init_esm_shims();
5755
- import { defineCommand as defineCommand10, runMain } from "citty";
5972
+ import { defineCommand as defineCommand11, runMain } from "citty";
5756
5973
  import { createRequire as createRequire2 } from "module";
5757
5974
  var require2 = createRequire2(import.meta.url);
5758
5975
  var pkg = require2("../../package.json");
5759
- var main = defineCommand10({
5976
+ var main = defineCommand11({
5760
5977
  meta: {
5761
5978
  name: "memorix",
5762
5979
  version: pkg.version,
@@ -5768,7 +5985,8 @@ var main = defineCommand10({
5768
5985
  sync: () => Promise.resolve().then(() => (init_sync(), sync_exports)).then((m) => m.default),
5769
5986
  hook: () => Promise.resolve().then(() => (init_hook(), hook_exports)).then((m) => m.default),
5770
5987
  hooks: () => Promise.resolve().then(() => (init_hooks(), hooks_exports)).then((m) => m.default),
5771
- dashboard: () => Promise.resolve().then(() => (init_dashboard(), dashboard_exports)).then((m) => m.default)
5988
+ dashboard: () => Promise.resolve().then(() => (init_dashboard(), dashboard_exports)).then((m) => m.default),
5989
+ cleanup: () => Promise.resolve().then(() => (init_cleanup(), cleanup_exports)).then((m) => m.default)
5772
5990
  },
5773
5991
  run() {
5774
5992
  }