@memorax/memorax-code 0.1.8 → 0.1.10

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 (102) hide show
  1. package/README.md +10 -0
  2. package/bin/memorax-code-codebuddy.mjs +4 -0
  3. package/bin/memorax-code-setup.mjs +182 -65
  4. package/bin/memorax-code.mjs +74 -45
  5. package/docs/configuration.md +82 -22
  6. package/docs/troubleshooting.md +69 -5
  7. package/lib/automatic-update.mjs +233 -0
  8. package/lib/memorax-code-adapter-common/src/automatic-update-state.d.mts +24 -0
  9. package/lib/memorax-code-adapter-common/src/automatic-update-state.mjs +73 -0
  10. package/lib/memorax-code-adapter-common/src/clients/codebuddy-command.mjs +82 -0
  11. package/lib/memorax-code-adapter-common/src/config-utils.d.mts +2 -0
  12. package/lib/memorax-code-adapter-common/src/config-utils.mjs +11 -0
  13. package/lib/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +3 -0
  14. package/lib/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +45 -10
  15. package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-job-worker.mjs +88 -3
  16. package/lib/memorax-code-adapter-common/src/windows-cli-invocation.mjs +11 -1
  17. package/lib/memorax-code-backend/dist/app/backend-server.js +2 -2
  18. package/lib/memorax-code-backend/dist/app/memory-observability.js +2 -2
  19. package/lib/memorax-code-backend/dist/clients/codebuddy/jsonl-history.js +208 -0
  20. package/lib/memorax-code-backend/dist/clients/codebuddy/lifecycle.js +44 -0
  21. package/lib/memorax-code-backend/dist/clients/codebuddy/memory-hook-runtime.js +250 -0
  22. package/lib/memorax-code-backend/dist/clients/codebuddy/turn-id.js +16 -0
  23. package/lib/memorax-code-backend/dist/clients/codex/plugin-install.js +129 -7
  24. package/lib/memorax-code-backend/dist/clients/codex/rollout-turn.js +52 -2
  25. package/lib/memorax-code-backend/dist/config/memorax-code.js +14 -1
  26. package/lib/memorax-code-backend/dist/entrypoints/backend-cli.js +43 -6
  27. package/lib/memorax-code-backend/dist/lifecycle/active-clients.js +3 -0
  28. package/lib/memorax-code-backend/dist/lifecycle/automatic-update-scheduler.js +133 -0
  29. package/lib/memorax-code-backend/dist/lifecycle/client-plugin-removal.js +14 -2
  30. package/lib/memorax-code-backend/dist/lifecycle/client-selection.js +6 -4
  31. package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +48 -9
  32. package/lib/memorax-code-backend/dist/memory/cli.js +4 -2
  33. package/lib/memorax-code-backend/dist/memory/hook-command.js +31 -1
  34. package/lib/memorax-code-backend/dist/memory/reminder-trace-recorder.js +5 -1
  35. package/lib/memorax-code-backend/dist/memory/service.js +12 -0
  36. package/lib/memorax-code-backend/dist/shared/windows-cli-invocation.js +10 -1
  37. package/lib/memorax-code-backend/dist/trace/config.js +12 -0
  38. package/lib/memorax-code-backend/dist/trace/context.js +21 -1
  39. package/lib/memorax-code-backend/dist/trace/store.js +12 -1
  40. package/lib/memorax-code-backend/package.json +1 -1
  41. package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  42. package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  43. package/lib/memorax-code-claude-adapter/package.json +1 -1
  44. package/lib/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +2 -0
  45. package/lib/memorax-code-claude-adapter/src/plugin-install.mjs +3 -0
  46. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  47. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  48. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/automatic-update-state.d.mts +24 -0
  49. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/automatic-update-state.mjs +73 -0
  50. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/clients/codebuddy-command.mjs +82 -0
  51. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.d.mts +2 -0
  52. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.mjs +11 -0
  53. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +3 -0
  54. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +45 -10
  55. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-job-worker.mjs +88 -3
  56. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/windows-cli-invocation.mjs +11 -1
  57. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
  58. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/ensure-backend.mjs +2 -0
  59. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-install.mjs +3 -0
  60. package/lib/memorax-code-codebuddy-adapter/.codebuddy-plugin/plugin.json +7 -0
  61. package/lib/memorax-code-codebuddy-adapter/hooks/common-runtime.mjs +13 -0
  62. package/lib/memorax-code-codebuddy-adapter/hooks/hooks.json +38 -0
  63. package/lib/memorax-code-codebuddy-adapter/hooks/repo-memory-job.mjs +40 -0
  64. package/lib/memorax-code-codebuddy-adapter/hooks/runtime-hook.mjs +279 -0
  65. package/lib/memorax-code-codebuddy-adapter/package.json +9 -0
  66. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/SKILL.md +85 -0
  67. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/claude.yaml +10 -0
  68. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/openai.yaml +7 -0
  69. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/defaults.json +12 -0
  70. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-add.md +88 -0
  71. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-search.md +93 -0
  72. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-read.md +46 -0
  73. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-write.md +120 -0
  74. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-build.md +319 -0
  75. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-read.md +103 -0
  76. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-templates.md +390 -0
  77. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-update.md +127 -0
  78. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/collect_all.py +579 -0
  79. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/detect_updates.py +919 -0
  80. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
  81. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/github_resource_facets.py +512 -0
  82. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +517 -0
  83. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
  84. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
  85. package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/validate_memory.py +248 -0
  86. package/lib/memorax-code-codebuddy-adapter/src/cli.mjs +58 -0
  87. package/lib/memorax-code-codebuddy-adapter/src/config.mjs +277 -0
  88. package/lib/memorax-code-codebuddy-adapter/src/hook-manifest.mjs +47 -0
  89. package/lib/memorax-code-codebuddy-adapter/src/runtime-observation.mjs +65 -0
  90. package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
  91. package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
  92. package/lib/memorax-code-codex-adapter/package.json +1 -1
  93. package/lib/memorax-code-codex-adapter/runtime-hooks/ensure-backend.mjs +4 -1
  94. package/lib/memorax-code-dsh-adapter/package.json +2 -1
  95. package/lib/memorax-code-dsh-adapter/src/profile-lifecycle.mjs +1 -0
  96. package/lib/memorax-code-opencode-adapter/package.json +1 -1
  97. package/lib/npm-invocation.mjs +51 -8
  98. package/lib/resolve-codebuddy-command.mjs +112 -0
  99. package/lib/run-entrypoint.mjs +25 -1
  100. package/lib/windows-cli-invocation.mjs +11 -1
  101. package/lib/windows-user-path.mjs +218 -0
  102. package/package.json +5 -3
@@ -1,4 +1,5 @@
1
1
  import { isRecord } from "../shared/record.js";
2
+ import { codeBuddyPromptDigest, parseCodeBuddyTurnId } from "../clients/codebuddy/turn-id.js";
2
3
  export const MEMORY_HOOK_COMMAND_VERSION = 1;
3
4
  export const INVALID_MEMORY_HOOK_COMMAND = "invalid memory Hook command";
4
5
  const BASE_COMMAND_KEYS = [
@@ -13,6 +14,7 @@ const TURN_START_KEYS = {
13
14
  "claude-code": new Set([...BASE_COMMAND_KEYS, "promptId", "prompt", "transcriptPath"]),
14
15
  opencode: new Set([...BASE_COMMAND_KEYS, "userMessageId", "prompt"]),
15
16
  dsh: new Set(["version", "client", "sessionId", "turn", "startSeq", "cwd", "prompt"]),
17
+ codebuddy: new Set([...BASE_COMMAND_KEYS, "turnId", "prompt", "transcriptPath"]),
16
18
  };
17
19
  const WRITEBACK_KEYS = {
18
20
  codex: new Set([...BASE_COMMAND_KEYS, "turnId", "lastAssistantMessage", "transcriptPath"]),
@@ -39,12 +41,14 @@ const WRITEBACK_KEYS = {
39
41
  "sessionHeader",
40
42
  "events",
41
43
  ]),
44
+ codebuddy: new Set([...BASE_COMMAND_KEYS, "turnId", "transcriptPath"]),
42
45
  };
43
46
  const SKILL_REMINDER_KEYS = {
44
47
  codex: new Set([...BASE_COMMAND_KEYS, "turnId", "transcriptPath", "content", "triggers"]),
45
48
  "claude-code": new Set([...BASE_COMMAND_KEYS, "promptId", "transcriptPath", "content", "triggers"]),
46
49
  dsh: new Set(["version", "client", "sessionId", "turn", "cwd", "content", "triggers"]),
47
50
  opencode: new Set([...BASE_COMMAND_KEYS, "userMessageId", "content", "triggers"]),
51
+ codebuddy: new Set([...BASE_COMMAND_KEYS, "turnId", "transcriptPath", "content", "triggers"]),
48
52
  };
49
53
  export function parseTurnStartCommand(value) {
50
54
  if (!isRecord(value))
@@ -102,6 +106,13 @@ export function parseTurnStartCommand(value) {
102
106
  },
103
107
  };
104
108
  }
109
+ if (base.client === "codebuddy") {
110
+ const turnId = requiredStringField(value, "turnId");
111
+ const transcriptPath = requiredStringField(value, "transcriptPath");
112
+ if (!turnId || !transcriptPath || !validCodeBuddyTurnId(turnId, base.sessionId, prompt))
113
+ return invalidCommand();
114
+ return { ok: true, command: { ...base, client: "codebuddy", turnId, prompt, transcriptPath } };
115
+ }
105
116
  const promptId = requiredStringField(value, "promptId");
106
117
  const transcriptPath = requiredStringField(value, "transcriptPath");
107
118
  if (!promptId || !transcriptPath)
@@ -166,6 +177,13 @@ export function parseWritebackCommand(value) {
166
177
  },
167
178
  };
168
179
  }
180
+ if (base.client === "codebuddy") {
181
+ const turnId = requiredStringField(value, "turnId");
182
+ const transcriptPath = requiredStringField(value, "transcriptPath");
183
+ if (!turnId || !transcriptPath || !validCodeBuddyTurnId(turnId, base.sessionId))
184
+ return invalidCommand();
185
+ return { ok: true, command: { ...base, client: "codebuddy", turnId, transcriptPath } };
186
+ }
169
187
  const lastAssistantMessage = requiredStringField(value, "lastAssistantMessage");
170
188
  if (!lastAssistantMessage)
171
189
  return invalidCommand();
@@ -241,6 +259,13 @@ export function parseSkillReminderCommand(value) {
241
259
  },
242
260
  };
243
261
  }
262
+ if (base.client === "codebuddy") {
263
+ const turnId = requiredStringField(value, "turnId");
264
+ const transcriptPath = requiredStringField(value, "transcriptPath");
265
+ if (!turnId || !transcriptPath || !validCodeBuddyTurnId(turnId, base.sessionId))
266
+ return invalidCommand();
267
+ return { ok: true, command: { ...base, client: "codebuddy", turnId, transcriptPath, content, triggers } };
268
+ }
244
269
  const transcriptPath = requiredStringField(value, "transcriptPath");
245
270
  if (!transcriptPath)
246
271
  return invalidCommand();
@@ -284,7 +309,8 @@ function parseCommandBase(value, allowedKeys) {
284
309
  if (client !== "codex"
285
310
  && client !== "claude-code"
286
311
  && client !== "opencode"
287
- && client !== "dsh")
312
+ && client !== "dsh"
313
+ && client !== "codebuddy")
288
314
  return undefined;
289
315
  const clientKeys = allowedKeys[client];
290
316
  if (!clientKeys || Object.keys(value).some((key) => !clientKeys.has(key)))
@@ -342,6 +368,10 @@ function optionalStringField(value, key) {
342
368
  const field = requiredStringField(value, key);
343
369
  return field ? { ok: true, value: field } : { ok: false };
344
370
  }
371
+ function validCodeBuddyTurnId(turnId, sessionId, prompt) {
372
+ const identity = parseCodeBuddyTurnId({ sessionId, turnId });
373
+ return Boolean(identity && (prompt === undefined || identity.promptDigest === codeBuddyPromptDigest(prompt)));
374
+ }
345
375
  function invalidCommand() {
346
376
  return { ok: false, error: INVALID_MEMORY_HOOK_COMMAND };
347
377
  }
@@ -1,5 +1,5 @@
1
1
  import { parseSkillReminderCommand, } from "./hook-command.js";
2
- import { traceContextFromClaudeHookBody, traceContextFromDshSkillReminder, traceContextFromHookBody, traceContextFromOpenCodeHookBody, } from "../trace/context.js";
2
+ import { traceContextFromClaudeHookBody, traceContextFromDshSkillReminder, traceContextFromHookBody, traceContextFromOpenCodeHookBody, traceContextFromCodeBuddyHookBody, } from "../trace/context.js";
3
3
  import { recordTraceEvent } from "../trace/store.js";
4
4
  export function createMemoryReminderTraceRecorder(options = {}) {
5
5
  return {
@@ -47,6 +47,8 @@ function traceContextForReminder(command) {
47
47
  return traceContextFromClaudeHookBody(command);
48
48
  if (command.client === "dsh")
49
49
  return traceContextFromDshSkillReminder(command);
50
+ if (command.client === "codebuddy")
51
+ return traceContextFromCodeBuddyHookBody(command);
50
52
  return traceContextFromOpenCodeHookBody(command);
51
53
  }
52
54
  function reminderSource(command) {
@@ -56,6 +58,8 @@ function reminderSource(command) {
56
58
  return "claude-hook";
57
59
  if (command.client === "dsh")
58
60
  return "dsh-cordis";
61
+ if (command.client === "codebuddy")
62
+ return "codebuddy-hook";
59
63
  return "opencode-plugin";
60
64
  }
61
65
  async function recordTraceBestEffort(label, promise, diagnosticLogger) {
@@ -3,6 +3,7 @@ import { createCodexMemoryHookRuntime, } from "../clients/codex/memory-hook-runt
3
3
  import { createClaudeMemoryHookRuntime, } from "../clients/claude/memory-hook-runtime.js";
4
4
  import { createDshMemoryHookRuntime, } from "../clients/dsh/memory-hook-runtime.js";
5
5
  import { createOpenCodeMemoryHookRuntime, } from "../clients/opencode/memory-hook-runtime.js";
6
+ import { createCodeBuddyMemoryHookRuntime } from "../clients/codebuddy/memory-hook-runtime.js";
6
7
  import { createMemoryTurnCoordinator } from "./turn-coordinator.js";
7
8
  import { createRepositoryMemorySessionRuntime, } from "./repository-session.js";
8
9
  import { createPendingQuotaNoticeRuntime } from "./quota-notice.js";
@@ -49,6 +50,12 @@ export function createMemoryService(options = {}) {
49
50
  repositoryMemorySession,
50
51
  turnCoordinator,
51
52
  });
53
+ const codeBuddyHook = createCodeBuddyMemoryHookRuntime({
54
+ ...options,
55
+ pendingQuotaNotice,
56
+ repositoryMemorySession,
57
+ turnCoordinator,
58
+ });
52
59
  let closed = false;
53
60
  return {
54
61
  async recordTurnStart(command) {
@@ -61,6 +68,8 @@ export function createMemoryService(options = {}) {
61
68
  return await openCodeHook.recordTurnStart(command);
62
69
  case "dsh":
63
70
  return await dshHook.recordTurnStart(command);
71
+ case "codebuddy":
72
+ return await codeBuddyHook.recordTurnStart(command);
64
73
  }
65
74
  return unsupportedMemoryHookCommand(command);
66
75
  },
@@ -74,6 +83,8 @@ export function createMemoryService(options = {}) {
74
83
  return await openCodeHook.writeback(command);
75
84
  case "dsh":
76
85
  return await dshHook.writeback(command);
86
+ case "codebuddy":
87
+ return await codeBuddyHook.writeback(command);
77
88
  }
78
89
  return unsupportedMemoryHookCommand(command);
79
90
  },
@@ -88,6 +99,7 @@ export function createMemoryService(options = {}) {
88
99
  claudeHook.close();
89
100
  openCodeHook.close();
90
101
  dshHook.close();
102
+ codeBuddyHook.close();
91
103
  turnCoordinator.close();
92
104
  repositoryMemorySession.close();
93
105
  automaticWriteback.close();
@@ -25,6 +25,9 @@ export function resolveWindowsCliInvocation(command, args, options = {}) {
25
25
  resolvedCommand ??= command;
26
26
  if (/\.(?:exe|com)$/i.test(resolvedCommand))
27
27
  return { command: resolvedCommand, args };
28
+ if (isBareCodeBuddyEntrypoint(resolvedCommand, pathApi) && fileExists(resolvedCommand)) {
29
+ return { command: options.nodePath ?? process.execPath, args: [resolvedCommand, ...args] };
30
+ }
28
31
  if (!/\.(?:cmd|bat)$/i.test(resolvedCommand)) {
29
32
  throw new Error(`refusing to execute unsupported Windows command shim: ${resolvedCommand}`);
30
33
  }
@@ -87,12 +90,18 @@ export function selectWindowsCommandCandidate(command, output) {
87
90
  .map((value) => value.trim())
88
91
  .filter(Boolean);
89
92
  const selected = candidates.find((value) => /\.(?:cmd|bat)$/i.test(value))
90
- ?? candidates.find((value) => /\.(?:exe|com)$/i.test(value));
93
+ ?? candidates.find((value) => /\.(?:exe|com)$/i.test(value))
94
+ ?? (path.win32.basename(command).toLowerCase() === "codebuddy"
95
+ ? candidates.find((value) => isBareCodeBuddyEntrypoint(value, path.win32))
96
+ : undefined);
91
97
  if (!selected) {
92
98
  throw new Error(`where.exe did not return a safe executable or command shim for ${command}`);
93
99
  }
94
100
  return selected;
95
101
  }
102
+ function isBareCodeBuddyEntrypoint(value, pathApi) {
103
+ return pathApi.basename(value).toLowerCase() === "codebuddy" && pathApi.extname(value) === "";
104
+ }
96
105
  export function resolveWindowsNpmInvocation(args, env, options = {}) {
97
106
  const platform = options.platform ?? process.platform;
98
107
  if (platform !== "win32")
@@ -4,6 +4,7 @@ import { join } from "node:path";
4
4
  import { loadMemoraxCodeConfig } from "../config/memorax-code.js";
5
5
  import { isTraceClient } from "./context.js";
6
6
  export const TRACE_CLIENTS = ["codex", "claude", "dsh", "opencode"];
7
+ export const TRACE_RUNTIME_CLIENTS = [...TRACE_CLIENTS, "codebuddy"];
7
8
  export const CODEX_TRACE_DEFAULT_CONFIG = {
8
9
  enabled: true,
9
10
  captureContent: true,
@@ -32,17 +33,22 @@ export const OPENCODE_TRACE_DEFAULT_CONFIG = {
32
33
  maxEventChars: 20_000,
33
34
  maxFileBytes: 52_428_800,
34
35
  };
36
+ export const CODEBUDDY_TRACE_DEFAULT_CONFIG = {
37
+ ...OPENCODE_TRACE_DEFAULT_CONFIG,
38
+ };
35
39
  const TRACE_DEFAULT_CONFIGS = {
36
40
  codex: CODEX_TRACE_DEFAULT_CONFIG,
37
41
  claude: CLAUDE_TRACE_DEFAULT_CONFIG,
38
42
  dsh: DSH_TRACE_DEFAULT_CONFIG,
39
43
  opencode: OPENCODE_TRACE_DEFAULT_CONFIG,
44
+ codebuddy: CODEBUDDY_TRACE_DEFAULT_CONFIG,
40
45
  };
41
46
  const TRACE_ENV_PREFIXES = {
42
47
  codex: "MEMORAX_CODE_CODEX_TRACE",
43
48
  claude: "MEMORAX_CODE_CLAUDE_TRACE",
44
49
  dsh: "MEMORAX_CODE_DSH_TRACE",
45
50
  opencode: "MEMORAX_CODE_OPENCODE_TRACE",
51
+ codebuddy: "MEMORAX_CODE_CODEBUDDY_TRACE",
46
52
  };
47
53
  export const TRACE_CLEANUP_DEBOUNCE_MS = 60 * 60 * 1000;
48
54
  export const TRACE_CURRENT_TURN_TTL_MS = 30 * 60 * 1000;
@@ -62,6 +68,9 @@ export function dshTraceConfigFromEnv(env = process.env, fileConfig) {
62
68
  export function openCodeTraceConfigFromEnv(env = process.env, fileConfig) {
63
69
  return clientTraceConfigFromEnv("opencode", env, fileConfig);
64
70
  }
71
+ export function codeBuddyTraceConfigFromEnv(env = process.env, fileConfig) {
72
+ return clientTraceConfigFromEnv("codebuddy", env, fileConfig);
73
+ }
65
74
  export function clientTraceConfigFromEnv(client, env = process.env, fileConfig) {
66
75
  assertTraceClient(client);
67
76
  const config = fileConfig ?? loadMemoraxCodeConfig(memoraxCodeHomeForTrace(env), { warn: () => undefined });
@@ -91,6 +100,9 @@ export function dshTracePaths(memoraxCodeHome = memoraxCodeHomeForTrace(process.
91
100
  export function openCodeTracePaths(memoraxCodeHome = memoraxCodeHomeForTrace(process.env)) {
92
101
  return clientTracePaths("opencode", memoraxCodeHome);
93
102
  }
103
+ export function codeBuddyTracePaths(memoraxCodeHome = memoraxCodeHomeForTrace(process.env)) {
104
+ return clientTracePaths("codebuddy", memoraxCodeHome);
105
+ }
94
106
  export function clientTracePaths(client, memoraxCodeHome = memoraxCodeHomeForTrace(process.env)) {
95
107
  assertTraceClient(client);
96
108
  const root = join(memoraxCodeHome, "debug", "traces", client);
@@ -62,6 +62,26 @@ export function traceContextFromOpenCodeHookBody(body, capturedAt = new Date().t
62
62
  capturedAt,
63
63
  });
64
64
  }
65
+ export function traceContextFromCodeBuddyHookBody(body, capturedAt = new Date().toISOString()) {
66
+ if (!isRecord(body))
67
+ return undefined;
68
+ const sessionId = stringField(body, "session_id") ?? stringField(body, "sessionId");
69
+ if (!sessionId)
70
+ return undefined;
71
+ const cwd = stringField(body, "cwd");
72
+ return pruneTraceContext({
73
+ schemaVersion: "1",
74
+ client: "codebuddy",
75
+ sessionId,
76
+ turnId: stringField(body, "turn_id") ?? stringField(body, "turnId"),
77
+ transcriptPath: stringField(body, "transcript_path") ?? stringField(body, "transcriptPath"),
78
+ cwd,
79
+ memoryProject: resolveMemoryProject(cwd),
80
+ workspaceKind: stringField(body, "workspace_kind") ?? stringField(body, "workspaceKind"),
81
+ contextOrigin: "codebuddy-hook-body",
82
+ capturedAt,
83
+ });
84
+ }
65
85
  export function traceContextFromDshTurnStart(body, capturedAt = new Date().toISOString()) {
66
86
  return traceContextFromDshBody(body, "dsh-cordis-turn-start", capturedAt);
67
87
  }
@@ -129,7 +149,7 @@ function pruneRecord(value) {
129
149
  return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined));
130
150
  }
131
151
  export function isTraceClient(value) {
132
- return value === "codex" || value === "claude" || value === "dsh" || value === "opencode";
152
+ return value === "codex" || value === "claude" || value === "dsh" || value === "opencode" || value === "codebuddy";
133
153
  }
134
154
  function traceContextFromDshBody(body, contextOrigin, capturedAt) {
135
155
  if (!isRecord(body))
@@ -60,6 +60,9 @@ export async function recordCodexTraceEvent(input) {
60
60
  export async function recordClaudeTraceEvent(input) {
61
61
  return recordTraceEventForClient("claude", input);
62
62
  }
63
+ export async function recordCodeBuddyTraceEvent(input) {
64
+ return recordTraceEventForClient("codebuddy", input);
65
+ }
63
66
  export function traceTurnEventId(traceContext, type) {
64
67
  if (!traceContext?.sessionId || !traceContext.turnId)
65
68
  return undefined;
@@ -168,6 +171,12 @@ export async function markCurrentCodexTurnOutcome(traceContext, outcome, options
168
171
  export async function markCurrentClaudeTurnOutcome(traceContext, outcome, options = {}) {
169
172
  return markCurrentTraceTurnOutcome(traceContext, outcome, { ...options, client: "claude" });
170
173
  }
174
+ export async function writeCurrentCodeBuddyTurn(traceContext, options = {}) {
175
+ return writeCurrentTraceTurn(traceContext, { ...options, client: "codebuddy" });
176
+ }
177
+ export async function markCurrentCodeBuddyTurnOutcome(traceContext, outcome, options = {}) {
178
+ return markCurrentTraceTurnOutcome(traceContext, outcome, { ...options, client: "codebuddy" });
179
+ }
171
180
  async function readCurrentTraceTurnRecord(options) {
172
181
  const client = options.client;
173
182
  if (!isTraceClient(client))
@@ -303,7 +312,9 @@ function buildTraceEvent(input, config, now, eventId) {
303
312
  ok: input.ok,
304
313
  outcome: input.outcome,
305
314
  related_turns: input.relatedTurns?.map(relatedTurnJson),
306
- activities: input.activities,
315
+ activities: input.activities === undefined
316
+ ? undefined
317
+ : sanitizeTraceValue(input.activities, config, captureContent),
307
318
  usage: input.usage,
308
319
  session_turn_index: input.sessionTurnIndex,
309
320
  request: input.request === undefined ? undefined : sanitizeTraceValue(input.request, config, captureContent),
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorax-code/backend",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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.8",
3
+ "version": "0.1.10",
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.8",
3
+ "shellVersion": "0.1.10",
4
4
  "runtimeAbi": 1
5
5
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorax-code/claude-adapter",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Claude Code Hook adapter for MemoraX Code memory services.",
@@ -2,6 +2,7 @@
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { readClientHookPluginRoot } from "../../memorax-code-adapter-common/src/config-utils.mjs";
5
6
  import {
6
7
  runEnsureBackendHook,
7
8
  stringValue,
@@ -31,6 +32,7 @@ process.exit(0);
31
32
  function pluginRoot() {
32
33
  return stringValue(process.env.CLAUDE_PLUGIN_ROOT)
33
34
  ?? stringValue(process.env.PLUGIN_ROOT)
35
+ ?? readClientHookPluginRoot()
34
36
  ?? dirname(dirname(fileURLToPath(import.meta.url)));
35
37
  }
36
38
 
@@ -32,6 +32,7 @@ const STABLE_SHELL_REQUIRED_FILES = Object.freeze([
32
32
  "memorax-code-adapter-common/src/hooks/capture-cwd-hook.mjs",
33
33
  "memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs",
34
34
  "memorax-code-adapter-common/src/config-utils.mjs",
35
+ "memorax-code-adapter-common/src/setup-completion.mjs",
35
36
  "memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs",
36
37
  "memorax-code-adapter-common/src/hooks/hook-runtime-generation.mjs",
37
38
  "memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs",
@@ -226,10 +227,12 @@ export function ensureClaudePluginInstalled(options = {}) {
226
227
  }
227
228
 
228
229
  function writeInstalledPluginMetadata(installPath, claudeCommand) {
230
+ const npmExecPath = stringOption(process.env.MEMORAX_CODE_NPM_EXEC_PATH);
229
231
  atomicWriteJson(join(installPath, ".memorax-code-package.json"), {
230
232
  version: 1,
231
233
  memoraxCodeCommand: process.argv[1],
232
234
  claudeCommand,
235
+ ...(npmExecPath ? { npmExecPath } : {}),
233
236
  writtenAt: new Date().toISOString(),
234
237
  });
235
238
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memorax-code-claude-adapter",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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.8",
3
+ "shellVersion": "0.1.10",
4
4
  "runtimeAbi": 1
5
5
  }
@@ -0,0 +1,24 @@
1
+ export type AutomaticUpdateRecord = Readonly<{
2
+ version: 1;
3
+ installedVersion: string;
4
+ nextCheckAt: string;
5
+ }>;
6
+
7
+ export type AutomaticUpdateState =
8
+ | Readonly<{ status: "absent" }>
9
+ | Readonly<{ status: "valid"; record: AutomaticUpdateRecord }>
10
+ | Readonly<{ status: "invalid"; reason: string }>
11
+ | Readonly<{ status: "unsupported"; version: number }>;
12
+
13
+ export const AUTOMATIC_UPDATE_RECORD_VERSION: 1;
14
+ export const AUTOMATIC_UPDATE_CHECK_INTERVAL_MS: number;
15
+ export const AUTOMATIC_UPDATE_RETRY_INTERVAL_MS: number;
16
+
17
+ export function automaticUpdateStatePath(memoraxCodeHome: string): string;
18
+ export function readAutomaticUpdateState(memoraxCodeHome: string): AutomaticUpdateState;
19
+ export function writeAutomaticUpdateState(options: {
20
+ memoraxCodeHome: string;
21
+ nowMs: number;
22
+ installedVersion: string;
23
+ retry: boolean;
24
+ }): AutomaticUpdateRecord;
@@ -0,0 +1,73 @@
1
+ import { join, resolve } from "node:path";
2
+ import {
3
+ readJsonRuntimeRecord,
4
+ writePrivateJsonRecord,
5
+ } from "./runtime-record.mjs";
6
+
7
+ export const AUTOMATIC_UPDATE_RECORD_VERSION = 1;
8
+ export const AUTOMATIC_UPDATE_CHECK_INTERVAL_MS = 8 * 60 * 60 * 1_000;
9
+ export const AUTOMATIC_UPDATE_RETRY_INTERVAL_MS = 15 * 60 * 1_000;
10
+
11
+ const RECORD_KEYS = new Set(["version", "installedVersion", "nextCheckAt"]);
12
+
13
+ export function automaticUpdateStatePath(memoraxCodeHome) {
14
+ return join(resolve(memoraxCodeHome), "runtime", "install", "automatic-update.json");
15
+ }
16
+
17
+ export function readAutomaticUpdateState(memoraxCodeHome) {
18
+ const state = readJsonRuntimeRecord(automaticUpdateStatePath(memoraxCodeHome));
19
+ if (state.status !== "present") return state;
20
+ const value = state.value;
21
+ if (value.version !== AUTOMATIC_UPDATE_RECORD_VERSION) {
22
+ return Number.isSafeInteger(value.version) && value.version > 0
23
+ ? { status: "unsupported", version: value.version }
24
+ : { status: "invalid", reason: "invalid_version" };
25
+ }
26
+ if (Object.keys(value).length !== RECORD_KEYS.size
27
+ || Object.keys(value).some((key) => !RECORD_KEYS.has(key))) {
28
+ return { status: "invalid", reason: "unknown_or_missing_fields" };
29
+ }
30
+ const installedVersion = nonEmptyString(value.installedVersion);
31
+ const nextCheckAt = timestamp(value.nextCheckAt);
32
+ if (!installedVersion || !nextCheckAt) {
33
+ return { status: "invalid", reason: "invalid_fields" };
34
+ }
35
+ return {
36
+ status: "valid",
37
+ record: {
38
+ version: AUTOMATIC_UPDATE_RECORD_VERSION,
39
+ installedVersion,
40
+ nextCheckAt,
41
+ },
42
+ };
43
+ }
44
+
45
+ export function writeAutomaticUpdateState({ memoraxCodeHome, nowMs, installedVersion, retry }) {
46
+ const timestampMs = Number(nowMs);
47
+ const version = nonEmptyString(installedVersion);
48
+ if (!Number.isFinite(timestampMs) || !version) {
49
+ throw new TypeError("automatic update state requires a timestamp and installed version");
50
+ }
51
+ const record = {
52
+ version: AUTOMATIC_UPDATE_RECORD_VERSION,
53
+ installedVersion: version,
54
+ nextCheckAt: new Date(
55
+ timestampMs + (retry ? AUTOMATIC_UPDATE_RETRY_INTERVAL_MS : AUTOMATIC_UPDATE_CHECK_INTERVAL_MS),
56
+ ).toISOString(),
57
+ };
58
+ writePrivateJsonRecord(automaticUpdateStatePath(memoraxCodeHome), record, {
59
+ durableBoundary: memoraxCodeHome,
60
+ });
61
+ return record;
62
+ }
63
+
64
+ function nonEmptyString(value) {
65
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
66
+ }
67
+
68
+ function timestamp(value) {
69
+ const candidate = nonEmptyString(value);
70
+ return candidate && Number.isFinite(Date.parse(candidate))
71
+ ? new Date(Date.parse(candidate)).toISOString()
72
+ : undefined;
73
+ }
@@ -0,0 +1,82 @@
1
+ import { accessSync, constants, existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, win32 } from "node:path";
4
+
5
+ const APP_NAMES = ["WorkBuddy.app", "CodeBuddy.app"];
6
+ const BUNDLED_SEGMENTS = ["Contents", "Resources", "app.asar.unpacked", "cli", "bin", "codebuddy"];
7
+ const WINDOWS_SEGMENTS = [
8
+ ["resources", "app.asar.unpacked", "cli", "bin", "codebuddy"],
9
+ ["resources", "app.asar.unpacked", "cli", "bin", "codebuddy.exe"],
10
+ ];
11
+
12
+ /**
13
+ * Resolve the CodeBuddy executable from the environment, installed adapter
14
+ * metadata, or the WorkBuddy/CodeBuddy application bundle. Hooks are often
15
+ * launched by a GUI process and therefore cannot rely on the user's shell
16
+ * PATH or on npm's environment setup.
17
+ */
18
+ export function resolveHookCodeBuddyCommand({
19
+ env = process.env,
20
+ pluginRoot,
21
+ homeDir = homedir(),
22
+ platform = process.platform,
23
+ pathExists = commandPathExists,
24
+ } = {}) {
25
+ const configured = stringValue(env.MEMORAX_CODE_CODEBUDDY_COMMAND)
26
+ ?? stringValue(env.CODEBUDDY_CLI_PATH)
27
+ ?? stringValue(env.WORKBUDDY_CODEBUDDY_PATH);
28
+ if (configured) return configured;
29
+
30
+ const metadataCommand = readMetadataCommand(stringValue(pluginRoot) ?? stringValue(env.CODEBUDDY_PLUGIN_ROOT));
31
+ if (metadataCommand && pathExists(metadataCommand, platform)) return metadataCommand;
32
+
33
+ if (platform === "darwin") {
34
+ for (const root of [join(homeDir, "Applications"), "/Applications"]) {
35
+ for (const appName of APP_NAMES) {
36
+ const command = join(root, appName, ...BUNDLED_SEGMENTS);
37
+ if (pathExists(command, platform)) return command;
38
+ }
39
+ }
40
+ }
41
+ if (platform === "win32") {
42
+ for (const root of [
43
+ env.LOCALAPPDATA && win32.join(env.LOCALAPPDATA, "Programs", "WorkBuddy"),
44
+ env.LOCALAPPDATA && win32.join(env.LOCALAPPDATA, "Programs", "CodeBuddy"),
45
+ env.LOCALAPPDATA && win32.join(env.LOCALAPPDATA, "WorkBuddy"),
46
+ env.LOCALAPPDATA && win32.join(env.LOCALAPPDATA, "CodeBuddy"),
47
+ env.ProgramFiles && win32.join(env.ProgramFiles, "WorkBuddy"),
48
+ env.ProgramFiles && win32.join(env.ProgramFiles, "CodeBuddy"),
49
+ ].filter(Boolean)) {
50
+ for (const segments of WINDOWS_SEGMENTS) {
51
+ const command = win32.join(root, ...segments);
52
+ if (pathExists(command, platform)) return command;
53
+ }
54
+ }
55
+ }
56
+ return "codebuddy";
57
+ }
58
+
59
+ function readMetadataCommand(pluginRoot) {
60
+ if (!pluginRoot) return undefined;
61
+ try {
62
+ const metadata = JSON.parse(readFileSync(join(pluginRoot, ".memorax-code-package.json"), "utf8"));
63
+ return stringValue(metadata?.codeBuddyCommand);
64
+ } catch {
65
+ return undefined;
66
+ }
67
+ }
68
+
69
+ function commandPathExists(command, platform) {
70
+ if (!command.includes("/") && !command.includes("\\")) return true;
71
+ if (!existsSync(command)) return false;
72
+ try {
73
+ accessSync(command, platform === "win32" ? constants.F_OK : constants.X_OK);
74
+ return true;
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+
80
+ function stringValue(value) {
81
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
82
+ }
@@ -13,6 +13,8 @@ export function readJsonFile(path: string): any;
13
13
  export function readJsonValue(path: string): any;
14
14
  export function readStdinJson(): Promise<any>;
15
15
  export function injectClientHookInput(input: Record<string, unknown>): void;
16
+ export function injectClientHookPluginRoot(pluginRoot: string): void;
17
+ export function readClientHookPluginRoot(): string | undefined;
16
18
  export function sha256(value: string | NodeJS.ArrayBufferView): string;
17
19
  export function stringOption(value: unknown): string | undefined;
18
20
  export function atomicWriteJson(path: string, value: unknown): void;
@@ -24,6 +24,7 @@ const JSON_FILE_LOCK_RETRY_MS = 10;
24
24
  const PROCESS_START_TOLERANCE_MS = 5000;
25
25
  const LOCK_SLEEP_BUFFER = new Int32Array(new SharedArrayBuffer(4));
26
26
  const HOOK_INPUT_SYMBOL = Symbol.for("memorax-code.client-hook.input.v1");
27
+ const HOOK_PLUGIN_ROOT_SYMBOL = Symbol.for("memorax-code.client-hook.plugin-root.v1");
27
28
 
28
29
  export function readAdapterState(path) {
29
30
  const state = readJsonFile(path);
@@ -64,6 +65,16 @@ export function injectClientHookInput(input) {
64
65
  globalThis[HOOK_INPUT_SYMBOL] = input;
65
66
  }
66
67
 
68
+ export function injectClientHookPluginRoot(pluginRoot) {
69
+ const root = stringOption(pluginRoot);
70
+ if (!root) throw new TypeError("client Hook plugin root must be a non-empty string");
71
+ globalThis[HOOK_PLUGIN_ROOT_SYMBOL] = root;
72
+ }
73
+
74
+ export function readClientHookPluginRoot() {
75
+ return stringOption(globalThis[HOOK_PLUGIN_ROOT_SYMBOL]);
76
+ }
77
+
67
78
  export function sha256(value) {
68
79
  return createHash("sha256").update(value).digest("hex");
69
80
  }
@@ -5,6 +5,7 @@ import { isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import {
7
7
  injectClientHookInput,
8
+ injectClientHookPluginRoot,
8
9
  readStdinJson,
9
10
  stringOption,
10
11
  withJsonFileLock,
@@ -47,6 +48,8 @@ export async function runClientHookLauncher({
47
48
  });
48
49
  if (!selection) return;
49
50
  injectClientHookInput(input);
51
+ const originalPluginRoot = stringOption(pluginRoot);
52
+ if (originalPluginRoot) injectClientHookPluginRoot(originalPluginRoot);
50
53
  await import(pathToFileURL(selection.modulePath).href);
51
54
  } catch (error) {
52
55
  if (process.env[debugEnv] === "1") {