@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.
- package/bin/memorax-code-plugin-postinstall.mjs +88 -21
- package/docs/configuration.md +15 -0
- package/docs/troubleshooting.md +26 -0
- package/lib/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +1 -1
- package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-auto-build.mjs +38 -0
- package/lib/memorax-code-backend/dist/automatic-memory-writeback.js +7 -0
- package/lib/memorax-code-backend/dist/claude-memory-hook-runtime.js +10 -3
- package/lib/memorax-code-backend/dist/codex-memory-hook-runtime.js +10 -3
- package/lib/memorax-code-backend/dist/memorax-cli.js +2 -0
- package/lib/memorax-code-backend/dist/memory-cli.js +24 -2
- package/lib/memorax-code-backend/dist/memory-service.js +3 -1
- package/lib/memorax-code-backend/dist/memory-turn-coordinator.js +6 -3
- package/lib/memorax-code-backend/dist/memory-viewer-icon.js +1 -0
- package/lib/memorax-code-backend/dist/memory-viewer-user-html.js +6 -5
- package/lib/memorax-code-backend/dist/memory-writeback-buffer.js +29 -0
- package/lib/memorax-code-backend/dist/repository-memory-context.js +24 -3
- package/lib/memorax-code-backend/dist/repository-memory-scope.js +81 -33
- package/lib/memorax-code-backend/dist/server-cli.js +30 -7
- package/lib/memorax-code-backend/dist/server-memory-viewer.js +11 -0
- package/lib/memorax-code-backend/package.json +1 -1
- package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -0
- package/lib/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +12 -13
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +4 -0
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +3 -1
- package/lib/memorax-code-claude-adapter/src/plugin-install.mjs +1 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +1 -1
- 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
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +12 -13
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +4 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +3 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-install.mjs +1 -0
- package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
- package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-codex-adapter/package.json +1 -1
- package/lib/memorax-code-codex-adapter/runtime-hooks/memory-skill-reminder.mjs +6 -0
- package/lib/memorax-code-codex-adapter/skills/memorax-code/SKILL.md +12 -13
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +4 -0
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +3 -1
- package/lib/resolve-claude-command.mjs +154 -1
- package/lib/resolve-codex-command.mjs +56 -1
- 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({
|
|
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
|
|
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",
|
|
75
|
+
repositoryKey: identityKey("workspace-directory", localWorkspace),
|
|
73
76
|
repositorySlug: workspaceName,
|
|
74
77
|
identitySource: "workspace-directory",
|
|
75
78
|
scopeKind: "local-directory",
|
|
76
|
-
|
|
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
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
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
|
|
206
|
-
return
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
422
|
+
throw new GitMetadataValidationError("invalid Git config value");
|
|
382
423
|
}
|
|
383
424
|
return result;
|
|
384
425
|
}
|
|
385
|
-
throw new
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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",
|
|
@@ -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;
|
|
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,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({
|