@memorax/memorax-code 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/bin/memorax-code-plugin-postinstall.mjs +88 -21
  2. package/docs/configuration.md +15 -0
  3. package/docs/troubleshooting.md +26 -0
  4. package/lib/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +1 -1
  5. package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-auto-build.mjs +38 -0
  6. package/lib/memorax-code-backend/dist/automatic-memory-writeback.js +7 -0
  7. package/lib/memorax-code-backend/dist/claude-memory-hook-runtime.js +10 -3
  8. package/lib/memorax-code-backend/dist/codex-memory-hook-runtime.js +10 -3
  9. package/lib/memorax-code-backend/dist/memorax-cli.js +2 -0
  10. package/lib/memorax-code-backend/dist/memory-cli.js +24 -2
  11. package/lib/memorax-code-backend/dist/memory-service.js +3 -1
  12. package/lib/memorax-code-backend/dist/memory-turn-coordinator.js +6 -3
  13. package/lib/memorax-code-backend/dist/memory-viewer-icon.js +1 -0
  14. package/lib/memorax-code-backend/dist/memory-viewer-user-html.js +6 -5
  15. package/lib/memorax-code-backend/dist/memory-writeback-buffer.js +29 -0
  16. package/lib/memorax-code-backend/dist/repository-memory-context.js +24 -3
  17. package/lib/memorax-code-backend/dist/repository-memory-scope.js +81 -33
  18. package/lib/memorax-code-backend/dist/server-cli.js +30 -7
  19. package/lib/memorax-code-backend/dist/server-memory-viewer.js +11 -0
  20. package/lib/memorax-code-backend/package.json +1 -1
  21. package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  22. package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  23. package/lib/memorax-code-claude-adapter/package.json +1 -1
  24. package/lib/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -0
  25. package/lib/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +12 -13
  26. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +4 -0
  27. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +3 -1
  28. package/lib/memorax-code-claude-adapter/src/plugin-install.mjs +1 -0
  29. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  30. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  31. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +1 -1
  32. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-auto-build.mjs +38 -0
  33. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
  34. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -0
  35. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +12 -13
  36. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +4 -0
  37. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +3 -1
  38. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-install.mjs +1 -0
  39. package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
  40. package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
  41. package/lib/memorax-code-codex-adapter/package.json +1 -1
  42. package/lib/memorax-code-codex-adapter/runtime-hooks/memory-skill-reminder.mjs +6 -0
  43. package/lib/memorax-code-codex-adapter/skills/memorax-code/SKILL.md +12 -13
  44. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +4 -0
  45. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +3 -1
  46. package/lib/resolve-claude-command.mjs +154 -1
  47. package/lib/resolve-codex-command.mjs +56 -1
  48. package/package.json +1 -1
@@ -1,12 +1,23 @@
1
1
  import { memoraxConfigFromEnv, } from "./memorax-config.js";
2
- import { repositoryMemoryScopeContainsWorkspace, repositoryMemoryScopeKind, repositoryMemoryScopesMatch, resolveRepositoryMemoryScope, } from "./repository-memory-scope.js";
2
+ import { repositoryMemoryScopeCanUpgradeFromDegradedGit, repositoryMemoryScopeContainsWorkspace, repositoryMemoryScopeKind, repositoryMemoryScopesMatch, resolveRepositoryMemoryScope, } from "./repository-memory-scope.js";
3
+ export function resolvedRepoMemoryWorktree(result) {
4
+ if (!result.ok || !result.memory.scope)
5
+ return undefined;
6
+ return repositoryMemoryScopeKind(result.memory.scope) === "git-repository"
7
+ ? result.memory.scope.boundWorkspaceRoot
8
+ : undefined;
9
+ }
3
10
  const sessionScopes = new WeakMap();
4
11
  const scopeResolutionQueues = new WeakMap();
5
- export function createRepositoryMemorySessionRuntime() {
12
+ export function createRepositoryMemorySessionRuntime(options = {}) {
6
13
  const owner = {};
7
14
  return {
8
15
  async resolve(input) {
9
- return await resolveConfiguredRepositoryMemoryForSession({ owner, ...input });
16
+ return await resolveConfiguredRepositoryMemoryForSession({
17
+ owner,
18
+ onScopeUpgrade: options.onScopeUpgrade,
19
+ ...input,
20
+ });
10
21
  },
11
22
  close() {
12
23
  sessionScopes.delete(owner);
@@ -93,6 +104,16 @@ export async function resolveConfiguredRepositoryMemoryForSession(input) {
93
104
  return scopeResult;
94
105
  if (cached?.scope.baseUserId === configResult.config.userId) {
95
106
  if (!repositoryMemoryScopesMatch(cached.scope, scopeResult.scope)) {
107
+ if (repositoryMemoryScopeCanUpgradeFromDegradedGit(cached.scope, scopeResult.scope)) {
108
+ input.onScopeUpgrade?.({
109
+ client: input.client,
110
+ sessionId,
111
+ previousScope: cached.scope,
112
+ currentScope: scopeResult.scope,
113
+ });
114
+ cached.scope = scopeResult.scope;
115
+ return { ok: true, memory: { config: configResult.config, scope: scopeResult.scope } };
116
+ }
96
117
  cached.mismatch = true;
97
118
  return repositoryScopeMismatch();
98
119
  }
@@ -57,7 +57,10 @@ export async function resolveRepositoryMemoryScope(input) {
57
57
  }),
58
58
  };
59
59
  }
60
- const workspaceName = sanitizeWorkspaceName(basename(workspace));
60
+ const localWorkspace = gitRepository.kind === "degraded"
61
+ ? gitRepository.workspaceRoot
62
+ : workspace;
63
+ const workspaceName = sanitizeWorkspaceName(basename(localWorkspace));
61
64
  if (!workspaceName) {
62
65
  return {
63
66
  ok: false,
@@ -69,18 +72,30 @@ export async function resolveRepositoryMemoryScope(input) {
69
72
  ok: true,
70
73
  scope: memoryScope({
71
74
  baseUserId,
72
- repositoryKey: identityKey("workspace-directory", workspace),
75
+ repositoryKey: identityKey("workspace-directory", localWorkspace),
73
76
  repositorySlug: workspaceName,
74
77
  identitySource: "workspace-directory",
75
78
  scopeKind: "local-directory",
76
- boundWorkspaceRoot: workspace,
79
+ fallbackReason: gitRepository.kind === "degraded" ? gitRepository.reason : undefined,
80
+ boundWorkspaceRoot: localWorkspace,
77
81
  }),
78
82
  };
79
83
  }
80
84
  export function repositoryMemoryScopesMatch(left, right) {
81
85
  return left.repositoryKey === right.repositoryKey
82
86
  && left.baseUserId === right.baseUserId
83
- && left.effectiveUserId === right.effectiveUserId;
87
+ && left.effectiveUserId === right.effectiveUserId
88
+ && left.fallbackReason === right.fallbackReason;
89
+ }
90
+ export function repositoryMemoryScopeCanUpgradeFromDegradedGit(previous, current) {
91
+ return previous.scopeKind === "local-directory"
92
+ && previous.identitySource === "workspace-directory"
93
+ && previous.fallbackReason === "git_metadata_invalid"
94
+ && current.scopeKind === "git-repository"
95
+ && current.fallbackReason === undefined
96
+ && previous.baseUserId === current.baseUserId
97
+ && previous.boundWorkspaceRoot !== undefined
98
+ && previous.boundWorkspaceRoot === current.boundWorkspaceRoot;
84
99
  }
85
100
  export async function repositoryMemoryScopeContainsWorkspace(scope, workspaceRoot) {
86
101
  if (!scope.boundWorkspaceRoot || !workspaceRoot?.trim())
@@ -96,6 +111,11 @@ export async function repositoryMemoryScopeContainsWorkspace(scope, workspaceRoo
96
111
  }
97
112
  if (!pathContains(scope.boundWorkspaceRoot, workspace))
98
113
  return false;
114
+ if (scope.scopeKind === "local-directory" && scope.fallbackReason === "git_metadata_invalid") {
115
+ const gitRepository = await resolveGitRepository(workspace);
116
+ return gitRepository.kind === "degraded"
117
+ && identityKey("workspace-directory", gitRepository.workspaceRoot) === scope.repositoryKey;
118
+ }
99
119
  return scope.scopeKind !== "local-directory"
100
120
  || !await hasGitMarkerBetween(workspace, scope.boundWorkspaceRoot);
101
121
  }
@@ -112,6 +132,7 @@ function memoryScope(input) {
112
132
  repositoryName: input.repositorySlug,
113
133
  identitySource: input.identitySource,
114
134
  scopeKind: input.scopeKind,
135
+ ...(input.fallbackReason ? { fallbackReason: input.fallbackReason } : {}),
115
136
  ...(input.boundWorkspaceRoot ? { boundWorkspaceRoot: input.boundWorkspaceRoot } : {}),
116
137
  };
117
138
  }
@@ -134,8 +155,11 @@ async function resolveGitRepository(workspace) {
134
155
  }
135
156
  if (!marker)
136
157
  return { kind: "none" };
158
+ let directDirectoryMarker = false;
137
159
  try {
138
- const gitDir = await resolveGitDir(marker.path);
160
+ const resolvedGitDir = await resolveGitDir(marker.path);
161
+ directDirectoryMarker = resolvedGitDir.directDirectoryMarker;
162
+ const gitDir = resolvedGitDir.path;
139
163
  await validateGitHead(gitDir);
140
164
  const commonDir = await resolveGitCommonDir(gitDir);
141
165
  await validateGitCommonDir(commonDir);
@@ -149,7 +173,14 @@ async function resolveGitRepository(workspace) {
149
173
  workspaceRoot: marker.workspaceRoot,
150
174
  };
151
175
  }
152
- catch {
176
+ catch (error) {
177
+ if (directDirectoryMarker && canFallbackFromDirectGitDirectory(error)) {
178
+ return {
179
+ kind: "degraded",
180
+ reason: "git_metadata_invalid",
181
+ workspaceRoot: marker.workspaceRoot,
182
+ };
183
+ }
153
184
  return { kind: "invalid" };
154
185
  }
155
186
  }
@@ -191,19 +222,27 @@ async function hasGitMarkerBetween(workspace, boundary) {
191
222
  }
192
223
  }
193
224
  async function resolveGitDir(marker) {
194
- if ((await fs.lstat(marker)).isSymbolicLink())
195
- throw new Error("symbolic Git metadata marker is not allowed");
196
- const markerStat = await fs.stat(marker);
197
- if (markerStat.isDirectory())
198
- return await canonicalDirectory(marker);
225
+ const markerStat = await fs.lstat(marker);
226
+ if (markerStat.isSymbolicLink()) {
227
+ throw new GitMetadataValidationError("symbolic Git metadata marker is not allowed");
228
+ }
229
+ if (markerStat.isDirectory()) {
230
+ return {
231
+ path: await canonicalDirectory(marker),
232
+ directDirectoryMarker: true,
233
+ };
234
+ }
199
235
  if (!markerStat.isFile())
200
- throw new Error("invalid Git metadata marker");
236
+ throw new GitMetadataValidationError("invalid Git metadata marker");
201
237
  const line = await readSingleLine(marker, MAX_GIT_POINTER_BYTES);
202
238
  const match = /^gitdir:[ \t]*(.+)$/i.exec(line);
203
239
  const gitDir = match?.[1]?.trim();
204
240
  if (!gitDir)
205
- throw new Error("invalid Git metadata pointer");
206
- return await canonicalDirectory(resolveMetadataPath(dirname(marker), gitDir));
241
+ throw new GitMetadataValidationError("invalid Git metadata pointer");
242
+ return {
243
+ path: await canonicalDirectory(resolveMetadataPath(dirname(marker), gitDir)),
244
+ directDirectoryMarker: false,
245
+ };
207
246
  }
208
247
  async function resolveGitCommonDir(gitDir) {
209
248
  const path = join(gitDir, "commondir");
@@ -217,34 +256,35 @@ async function resolveGitCommonDir(gitDir) {
217
256
  }
218
257
  const commonDir = (await readSingleLine(path, MAX_GIT_POINTER_BYTES)).trim();
219
258
  if (!commonDir)
220
- throw new Error("invalid Git common directory pointer");
259
+ throw new GitMetadataValidationError("invalid Git common directory pointer");
221
260
  return await canonicalDirectory(resolveMetadataPath(gitDir, commonDir));
222
261
  }
223
262
  function resolveMetadataPath(base, value) {
224
263
  if (value.includes("\0"))
225
- throw new Error("invalid Git metadata path");
264
+ throw new GitMetadataValidationError("invalid Git metadata path");
226
265
  return isAbsolute(value) ? value : resolve(base, value);
227
266
  }
228
267
  async function canonicalDirectory(path) {
229
268
  const canonical = await fs.realpath(path);
230
- if (!(await fs.stat(canonical)).isDirectory())
231
- throw new Error("Git metadata path is not a directory");
269
+ if (!(await fs.stat(canonical)).isDirectory()) {
270
+ throw new GitMetadataValidationError("Git metadata path is not a directory");
271
+ }
232
272
  return canonical;
233
273
  }
234
274
  async function validateGitHead(gitDir) {
235
275
  const head = (await readSingleLine(join(gitDir, "HEAD"), MAX_GIT_POINTER_BYTES)).trim();
236
276
  if (!/^ref: refs\/[^\0\r\n]+$/.test(head) && !/^(?:[a-fA-F0-9]{40}|[a-fA-F0-9]{64})$/.test(head)) {
237
- throw new Error("invalid Git HEAD");
277
+ throw new GitMetadataValidationError("invalid Git HEAD");
238
278
  }
239
279
  }
240
280
  async function validateGitCommonDir(commonDir) {
241
281
  if (!(await fs.stat(join(commonDir, "objects"))).isDirectory()) {
242
- throw new Error("invalid Git objects directory");
282
+ throw new GitMetadataValidationError("invalid Git objects directory");
243
283
  }
244
284
  const hasRefs = await isDirectory(join(commonDir, "refs"));
245
285
  const hasReftable = await isDirectory(join(commonDir, "reftable"));
246
286
  if (!hasRefs && !hasReftable)
247
- throw new Error("invalid Git references directory");
287
+ throw new GitMetadataValidationError("invalid Git references directory");
248
288
  }
249
289
  async function isDirectory(path) {
250
290
  try {
@@ -263,17 +303,18 @@ async function readSingleLine(path, maxBytes) {
263
303
  else if (value.endsWith("\n"))
264
304
  value = value.slice(0, -1);
265
305
  if (!value || value.includes("\0") || value.includes("\r") || value.includes("\n")) {
266
- throw new Error("invalid single-line Git metadata");
306
+ throw new GitMetadataValidationError("invalid single-line Git metadata");
267
307
  }
268
308
  return value;
269
309
  }
270
310
  async function readBoundedTextFile(path, maxBytes) {
271
311
  const file = await fs.stat(path);
272
- if (!file.isFile() || file.size > maxBytes)
273
- throw new Error("invalid Git metadata file");
312
+ if (!file.isFile() || file.size > maxBytes) {
313
+ throw new GitMetadataValidationError("invalid Git metadata file");
314
+ }
274
315
  const value = await fs.readFile(path, "utf8");
275
316
  if (Buffer.byteLength(value, "utf8") > maxBytes || value.includes("\0")) {
276
- throw new Error("invalid Git metadata file");
317
+ throw new GitMetadataValidationError("invalid Git metadata file");
277
318
  }
278
319
  return value;
279
320
  }
@@ -306,7 +347,7 @@ function parseDirectRemoteUrls(source) {
306
347
  continue;
307
348
  const assignment = /^[ \t]*([A-Za-z][A-Za-z0-9-]*)[ \t]*(?:=[ \t]*)?(.*)$/.exec(line);
308
349
  if (!assignment)
309
- throw new Error("invalid Git config assignment");
350
+ throw new GitMetadataValidationError("invalid Git config assignment");
310
351
  if (assignment[1]?.toLowerCase() !== "url")
311
352
  continue;
312
353
  remotes.set(remote, parseGitConfigValue(assignment[2] ?? ""));
@@ -328,7 +369,7 @@ function logicalGitConfigLines(source) {
328
369
  }
329
370
  }
330
371
  if (pending)
331
- throw new Error("unterminated Git config continuation");
372
+ throw new GitMetadataValidationError("unterminated Git config continuation");
332
373
  return lines;
333
374
  }
334
375
  function hasUnescapedTrailingBackslash(value) {
@@ -340,7 +381,7 @@ function hasUnescapedTrailingBackslash(value) {
340
381
  function parseGitConfigSection(line) {
341
382
  const match = /^\[[ \t]*([A-Za-z0-9][A-Za-z0-9.-]*)[ \t]*(?:"((?:[^"\\]|\\.)*)")?[ \t]*\](?:[ \t]*[#;].*)?$/.exec(line);
342
383
  if (!match?.[1])
343
- throw new Error("invalid Git config section");
384
+ throw new GitMetadataValidationError("invalid Git config section");
344
385
  if (!match[2] && match[1].includes(".")) {
345
386
  const separator = match[1].indexOf(".");
346
387
  return {
@@ -378,11 +419,11 @@ function parseGitConfigValue(value) {
378
419
  }
379
420
  const trailing = input.slice(index + 1).trim();
380
421
  if (trailing && !trailing.startsWith("#") && !trailing.startsWith(";")) {
381
- throw new Error("invalid Git config value");
422
+ throw new GitMetadataValidationError("invalid Git config value");
382
423
  }
383
424
  return result;
384
425
  }
385
- throw new Error("unterminated Git config value");
426
+ throw new GitMetadataValidationError("unterminated Git config value");
386
427
  }
387
428
  function unescapeGitConfigText(value) {
388
429
  let result = "";
@@ -394,7 +435,7 @@ function unescapeGitConfigText(value) {
394
435
  }
395
436
  const escaped = value[index + 1];
396
437
  if (escaped === undefined)
397
- throw new Error("invalid Git config escape");
438
+ throw new GitMetadataValidationError("invalid Git config escape");
398
439
  result += unescapeGitConfigCharacter(escaped);
399
440
  index += 1;
400
441
  }
@@ -409,7 +450,7 @@ function unescapeGitConfigCharacter(value) {
409
450
  return "\b";
410
451
  if (value === '"' || value === "\\")
411
452
  return value;
412
- throw new Error("invalid Git config escape");
453
+ throw new GitMetadataValidationError("invalid Git config escape");
413
454
  }
414
455
  function selectedRemoteRepositoryName(remotes) {
415
456
  const origin = remotes.get("origin");
@@ -465,7 +506,7 @@ function repositoryNameFromCommonDir(commonDir) {
465
506
  : commonName.replace(/\.git$/i, "");
466
507
  const normalized = sanitizeWorkspaceName(name);
467
508
  if (!normalized)
468
- throw new Error("Git common directory has no usable repository name");
509
+ throw new GitMetadataValidationError("Git common directory has no usable repository name");
469
510
  return normalized;
470
511
  }
471
512
  function identityKey(source, value) {
@@ -488,3 +529,10 @@ function pathContains(root, candidate) {
488
529
  function isNodeErrorCode(error, code) {
489
530
  return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
490
531
  }
532
+ class GitMetadataValidationError extends Error {
533
+ }
534
+ function canFallbackFromDirectGitDirectory(error) {
535
+ return error instanceof GitMetadataValidationError
536
+ || isNodeErrorCode(error, "ENOENT")
537
+ || isNodeErrorCode(error, "ENOTDIR");
538
+ }
@@ -559,7 +559,12 @@ function printLifecycleResult(report) {
559
559
  if (report.message)
560
560
  backendLog(report.message);
561
561
  if (report.backend) {
562
- backendLog(`Backend: ${report.backend.ok ? green(report.backend.degraded ? "ok (degraded)" : "ok") : red("not ok")}${report.backend.state?.url ? ` ${report.backend.state.url}` : ""}${report.backend.errorCode ? ` code=${report.backend.errorCode}` : ""}${report.backend.error ? ` error=${report.backend.error}` : ""}`);
562
+ const status = !report.backend.ok
563
+ ? red("not ok")
564
+ : report.backend.action === "stop"
565
+ ? green(report.backend.skipped ? "kept running" : "stopped")
566
+ : green(report.backend.degraded ? "ok (degraded)" : "ok");
567
+ backendLog(`Backend: ${status}${report.backend.state?.url ? ` ${report.backend.state.url}` : ""}${report.backend.errorCode ? ` code=${report.backend.errorCode}` : ""}${report.backend.error ? ` error=${report.backend.error}` : ""}`);
563
568
  for (const warning of report.backend.warnings ?? []) {
564
569
  backendLog(`Warning: ${warning.message}${warning.errorCode ? ` code=${warning.errorCode}` : ""}`);
565
570
  }
@@ -666,15 +671,33 @@ function lifecycleGuidance(report) {
666
671
  ];
667
672
  }
668
673
  if (report.action === "uninstall") {
669
- return report.ok
670
- ? [
671
- green("MemoraX Code has been uninstalled from this npm installation."),
672
- green("Restart or refresh Codex so it drops the removed adapter plugin."),
673
- ]
674
- : [
674
+ if (!report.ok) {
675
+ return [
675
676
  red("Uninstall needs attention."),
676
677
  "Run `memorax-code status` and `memorax-code logs` before retrying.",
677
678
  ];
679
+ }
680
+ const clientName = report.codexAdapter && report.claudeAdapter
681
+ ? "Codex and Claude Code"
682
+ : report.codexAdapter
683
+ ? "Codex"
684
+ : report.claudeAdapter
685
+ ? "Claude Code"
686
+ : undefined;
687
+ const npmPackageRemoved = report.npmPackageRemoval?.ok === true
688
+ && report.npmPackageRemoval.skipped !== true;
689
+ return [
690
+ ...(npmPackageRemoved
691
+ ? [green("MemoraX Code has been uninstalled from this npm installation.")]
692
+ : clientName
693
+ ? [green(`MemoraX Code has been uninstalled from ${clientName}.`)]
694
+ : []),
695
+ ...(clientName
696
+ ? [green(report.codexAdapter && report.claudeAdapter
697
+ ? "Restart or refresh Codex and Claude Code so they drop the removed adapter plugins."
698
+ : `Restart or refresh ${clientName} so it drops the removed adapter plugin.`)]
699
+ : []),
700
+ ];
678
701
  }
679
702
  return [];
680
703
  }
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { MEMORAX_ICON_DATA_URL } from "./memory-viewer-icon.js";
2
3
  import { isMemoryProjectId, UNCLASSIFIED_PROJECT_ID } from "./memory-project.js";
3
4
  import { MEMORAX_LOGO_DATA_URL } from "./memory-viewer-logo.js";
4
5
  import { memoryViewerUserHtml } from "./memory-viewer-user-html.js";
@@ -10,6 +11,7 @@ import { memoraxCodeHomeForTrace } from "./trace-config.js";
10
11
  const MEMORY_VIEWER_SESSION_ACTIVITY_WINDOW_MS = 72 * 60 * 60 * 1_000;
11
12
  const MEMORY_VIEWER_ACTIVITY_CUTOFF_GRANULARITY_MS = 60_000;
12
13
  const MEMORY_VIEWER_LOGO = Buffer.from(MEMORAX_LOGO_DATA_URL.slice(MEMORAX_LOGO_DATA_URL.indexOf(",") + 1), "base64");
14
+ const MEMORY_VIEWER_ICON = Buffer.from(MEMORAX_ICON_DATA_URL.slice(MEMORAX_ICON_DATA_URL.indexOf(",") + 1), "base64");
13
15
  export async function handleMemoryViewerRequest(url, req, res, options = {}) {
14
16
  if (req.method === "GET" && url.pathname === "/memory-viewer") {
15
17
  if (!memoryViewerUserClient(url)) {
@@ -36,6 +38,15 @@ export async function handleMemoryViewerRequest(url, req, res, options = {}) {
36
38
  res.end(MEMORY_VIEWER_LOGO);
37
39
  return true;
38
40
  }
41
+ if (req.method === "GET" && url.pathname === "/memory-viewer/favicon.png") {
42
+ res.writeHead(200, {
43
+ "content-type": "image/png",
44
+ "cache-control": "private, max-age=86400",
45
+ "x-content-type-options": "nosniff",
46
+ });
47
+ res.end(MEMORY_VIEWER_ICON);
48
+ return true;
49
+ }
39
50
  if (req.method !== "GET" || url.pathname !== "/memory-viewer/api/summary") {
40
51
  return false;
41
52
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorax-code/backend",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Local memory integration, lifecycle, trace, and diagnostics for MemoraX Code.",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorax-code-claude-adapter",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Memory skill and local hooks for MemoraX Code in Claude Code.",
5
5
  "author": {
6
6
  "name": "MemoraX Code Maintainers"
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "version": 1,
3
- "shellVersion": "0.1.1",
3
+ "shellVersion": "0.1.2",
4
4
  "runtimeAbi": 1
5
5
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorax-code/claude-adapter",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Claude Code Hook adapter for MemoraX Code memory services.",
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolveBackendConnection } from "../../memorax-code-adapter-common/src/backend-connection.mjs";
3
3
  import { readStdinJson } from "../../memorax-code-adapter-common/src/config-utils.mjs";
4
+ import { scheduleMissingRepoMemoryBuild } from "../../memorax-code-adapter-common/src/repo-memory/repo-memory-auto-build.mjs";
4
5
  import { isRepoMemoryJobWorker } from "../../memorax-code-adapter-common/src/repo-memory/repo-memory-job-context.mjs";
5
6
 
6
7
  const RETRIEVAL_BACKEND_TIMEOUT_MS = 12_000;
@@ -29,6 +30,10 @@ try {
29
30
  cwd: stringValue(input.cwd),
30
31
  workspaceKind: stringValue(input.workspace_kind) ?? stringValue(input.workspaceKind),
31
32
  });
33
+ scheduleMissingRepoMemoryBuild(stringValue(response?.repoMemoryWorktree), {
34
+ debugEnv: "MEMORAX_CODE_CLAUDE_HOOK_DEBUG",
35
+ pluginRoot: process.env.CLAUDE_PLUGIN_ROOT,
36
+ });
32
37
  const additionalContext = stringValue(response?.additionalContext);
33
38
  if (additionalContext) {
34
39
  process.stdout.write(`${JSON.stringify({
@@ -1,21 +1,20 @@
1
1
  ---
2
2
  name: memorax-code
3
3
  description: >-
4
- Use this skill as the single entry point for persistent coding and
5
- repository-local memory work, regardless of type, wording, or operation. Use
6
- it whenever a request may involve information that should persist beyond the
7
- current task,
8
- knowledge remembered from prior work, repository memory,
9
- reusable procedures or working rules, or durable user profile and interaction
10
- preferences. This applies even when the user does not call it memory,
11
- including naturally stated habits, preferences,
4
+ Use this skill as the single router for persistent coding and repository-local
5
+ memory. Invoke it whenever a request may involve prior-work knowledge,
6
+ repository memory, reusable procedures or rules, durable profile or
7
+ interaction preferences, or information worth retaining beyond the current
8
+ task. This applies without memory wording, including habits, preferences,
12
9
  checklists, action sequences, prerequisites, gates, exceptions, validation
13
10
  rules, communication style, preferred language, or result presentation.
14
- Determine whether the request belongs to MemoraX Code coding memory,
15
- repository memory, personal procedure memory, personal profile
16
- memory, or no persistent memory, then route it to the appropriate operation.
17
- Prefer this router over any underlying memory workflow. Ask one focused
18
- question only when the memory authority remains ambiguous after routing.
11
+ Classify the request as coding memory, repository memory, personal procedure
12
+ memory, personal profile memory, or no persistent memory, then route it to the
13
+ matching operation. Invoking this router does not require coding-memory
14
+ search. Reuse a relevant coding-memory result already retrieved in this
15
+ conversation; otherwise let the matching operation decide on search. Prefer
16
+ this router over underlying memory workflows. Ask one focused question only when
17
+ memory authority remains ambiguous.
19
18
  ---
20
19
 
21
20
  # MemoraX Code
@@ -71,6 +71,10 @@ anchors: stable/source/path' \
71
71
 
72
72
  If add fails, report the exact failure and do not retry automatically, bypass the CLI, or call MemoraX directly.
73
73
 
74
+ If a successful Add result reports `workspaceScopeFallbackReason: git_metadata_invalid`, malformed or incomplete metadata inside a direct `.git` directory was downgraded to the normalized local folder scope. Add has already been submitted with the reported `effectiveUserId`. Present its `userNotice` once without pausing the current task or asking the user to repair Git first, then continue the current task. After the repository or `.git` metadata is repaired, later Search, Add, and automatic writeback in the same client session automatically use the restored Git repository scope.
75
+
76
+ If `memorax-cli add` reports `workspace_scope_mismatch` or `workspace_scope_unavailable`, do not bypass the scope. Do not change the CLI working directory and retry. Tell the user that the memory was not submitted and no request was sent to MemoraX, then present the CLI's `userAction` in natural language. Continue the current task using only live code and documentation.
77
+
74
78
  ## Exclusions
75
79
 
76
80
  Do not add secrets, credentials, private URLs, raw authorization headers, exact patches, target commits, hidden tests, target diffs, vulnerability details, exploit steps, copied source, long logs, stack traces, raw transcripts, temporary errors, or facts directly recoverable from current files and git history.
@@ -8,7 +8,9 @@ Run the CLI from the active task workspace. The installed Hook and session bindi
8
8
 
9
9
  Coding memory uses `<MemoraX base user ID>@<normalized repository name>` for Git workspaces and the normalized folder name for genuine non-Git directories. MemoraX Code resolves `.git`, `gitdir`, and `commondir` without executing Git. Linked worktrees share one repository scope; another clone, repository, or non-Git directory retains a different local session key even when its readable name matches.
10
10
 
11
- Require a readable active workspace binding. A CLI command from a linked worktree of the bound repository is valid. If the CLI reports `workspace_scope_mismatch`, ask the user to start a task in the target repository or local workspace. Do not retry from an unrelated scope or fall back to an unscoped user id.
11
+ If a successful Search result reports `workspaceScopeFallbackReason: git_metadata_invalid`, malformed or incomplete metadata inside a direct `.git` directory was downgraded to the normalized local folder scope. Search has already run with the reported `effectiveUserId`. Present its `userNotice` once without pausing the current task or asking the user to repair Git first, then continue with the returned memory and live evidence. After the repository or `.git` metadata is repaired, later Search, Add, and automatic writeback in the same client session automatically use the restored Git repository scope.
12
+
13
+ Require a readable active workspace binding. A CLI command from a linked worktree of the bound repository is valid. If `memorax-cli search` reports `workspace_scope_mismatch` or `workspace_scope_unavailable`, do not bypass the scope or fall back to an unscoped user id. Do not change the CLI working directory and retry. Tell the user that memory search was not executed and no request was sent to MemoraX, then present the CLI's `userAction` in natural language. Continue the current task using only live code and documentation.
12
14
 
13
15
  If `memorax-cli` is not on `PATH`, or memory is disabled, unconfigured, or unavailable, report that briefly and continue with live code or documentation. Authenticate through MemoraX Code configuration; never recover credentials from shell history or place tokens in prompts. Treat injected memory as a hypothesis and verify it against the current checkout.
14
16
 
@@ -35,6 +35,7 @@ const STABLE_SHELL_REQUIRED_FILES = Object.freeze([
35
35
  "memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs",
36
36
  "memorax-code-adapter-common/src/hooks/hook-runtime-generation.mjs",
37
37
  "memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs",
38
+ "memorax-code-adapter-common/src/repo-memory/repo-memory-auto-build.mjs",
38
39
  "memorax-code-adapter-common/src/repo-memory/repo-memory-job-context.mjs",
39
40
  "memorax-code-adapter-common/src/repo-memory/repo-procedure-memory-context.mjs",
40
41
  "memorax-code-adapter-common/src/repo-memory/repo-user-profile-context.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorax-code-claude-adapter",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Memory skill and local hooks for MemoraX Code in Claude Code.",
5
5
  "author": {
6
6
  "name": "MemoraX Code Maintainers"
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "version": 1,
3
- "shellVersion": "0.1.1",
3
+ "shellVersion": "0.1.2",
4
4
  "runtimeAbi": 1
5
5
  }
@@ -182,7 +182,7 @@ function combinedReminderContext(options, due, cadenceReminderContext, personalM
182
182
 
183
183
  function memoryReminderContext(options) {
184
184
  const invocation = stringOption(options.memorySkillInvocation) ?? DEFAULT_MEMORY_SKILL_INVOCATION;
185
- return `MemoraX Code reminder: proactively invoke ${invocation} whenever coding memory might help, even when uncertain; when in doubt, run one focused coding-memory search for relevant prior knowledge. Also use ${invocation} for repository-scoped personal memory, and classify the authority before reading or writing.`;
185
+ return `MemoraX Code reminder: proactively invoke ${invocation} whenever coding memory might help, even when uncertain; follow the skill's router to decide whether any memory operation is needed. Also use ${invocation} for repository-scoped personal memory, and classify the authority before reading or writing.`;
186
186
  }
187
187
 
188
188
  function defaultMemoraxCodeHome() {
@@ -0,0 +1,38 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ export function scheduleMissingRepoMemoryBuild(repo, options = {}) {
6
+ try {
7
+ const repoPath = nonEmptyString(repo);
8
+ const pluginRoot = nonEmptyString(options.pluginRoot);
9
+ if (!repoPath || !pluginRoot) return false;
10
+ if (existsSync(join(repoPath, ".repo_memory", "PROFILE.md"))) return false;
11
+
12
+ const jobHookPath = join(pluginRoot, "hooks", "repo-memory-job.mjs");
13
+ if (!existsSync(jobHookPath)) return false;
14
+ const child = spawn(process.execPath, [jobHookPath, "maintain", "--repo", repoPath], {
15
+ cwd: repoPath,
16
+ detached: true,
17
+ env: process.env,
18
+ stdio: "ignore",
19
+ windowsHide: true,
20
+ });
21
+ child.once("error", (error) => debug(options, error));
22
+ child.unref();
23
+ return true;
24
+ } catch (error) {
25
+ debug(options, error);
26
+ return false;
27
+ }
28
+ }
29
+
30
+ function nonEmptyString(value) {
31
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
32
+ }
33
+
34
+ function debug(options, error) {
35
+ if (process.env[options.debugEnv] === "1") {
36
+ console.error(error instanceof Error ? error.message : String(error));
37
+ }
38
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorax-code/claude-adapter",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Claude Code Hook adapter for MemoraX Code memory services.",
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { resolveBackendConnection } from "../memorax-code-adapter-common/src/backend-connection.mjs";
3
3
  import { readStdinJson } from "../memorax-code-adapter-common/src/config-utils.mjs";
4
+ import { scheduleMissingRepoMemoryBuild } from "../memorax-code-adapter-common/src/repo-memory/repo-memory-auto-build.mjs";
4
5
  import { isRepoMemoryJobWorker } from "../memorax-code-adapter-common/src/repo-memory/repo-memory-job-context.mjs";
5
6
 
6
7
  const RETRIEVAL_BACKEND_TIMEOUT_MS = 12_000;
@@ -29,6 +30,10 @@ try {
29
30
  cwd: stringValue(input.cwd),
30
31
  workspaceKind: stringValue(input.workspace_kind) ?? stringValue(input.workspaceKind),
31
32
  });
33
+ scheduleMissingRepoMemoryBuild(stringValue(response?.repoMemoryWorktree), {
34
+ debugEnv: "MEMORAX_CODE_CLAUDE_HOOK_DEBUG",
35
+ pluginRoot: process.env.CLAUDE_PLUGIN_ROOT,
36
+ });
32
37
  const additionalContext = stringValue(response?.additionalContext);
33
38
  if (additionalContext) {
34
39
  process.stdout.write(`${JSON.stringify({