claude-memory-admin 1.2.0 → 1.4.0
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/README.md +42 -2
- package/package.json +1 -1
- package/public/app.mjs +344 -45
- package/public/index.html +7 -4
- package/public/styles.css +1 -1
- package/public/ui.mjs +5 -2
- package/server.mjs +65 -3
- package/src/checks.mjs +187 -0
- package/src/instructions.mjs +37 -2
- package/src/model.mjs +6 -0
- package/src/pathcheck.mjs +108 -0
- package/src/settings.mjs +23 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
export const MAX_PATH_CHECKS = 200;
|
|
5
|
+
export const MAX_INDEXED_FILES = 60000;
|
|
6
|
+
|
|
7
|
+
export const SKIP_DIRS = new Set([
|
|
8
|
+
'.git', 'node_modules', 'bin', 'obj', 'dist', 'build', 'out', 'target', 'vendor',
|
|
9
|
+
'.next', '.nuxt', '.svelte-kit', '.venv', 'venv', '__pycache__', '.gradle', '.idea',
|
|
10
|
+
'Pods', 'DerivedData', 'coverage', '.turbo', '.cache', '.angular', '.parcel-cache',
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const CODE_SPAN = /`([^`\n]+)`/g;
|
|
14
|
+
const SOURCE_EXT = /\.(mjs|cjs|jsx?|tsx?|json|jsonc|md|mdx|css|scss|less|html|vue|svelte|py|rb|go|rs|java|kt|swift|cs|fs|php|sh|zsh|ya?ml|toml|xml|sql|proto|lock|gradle|csproj|sln)$/i;
|
|
15
|
+
const BARE_EXTENSION = /^\.[A-Za-z0-9]+$/;
|
|
16
|
+
const LINE_SUFFIX = /:\d+(?:[-:]\d+)?$/;
|
|
17
|
+
const GLOB_OR_SHELL = /[*?{}[\]()<>|$=!"'\\]/;
|
|
18
|
+
|
|
19
|
+
function candidate(raw) {
|
|
20
|
+
let token = raw.trim();
|
|
21
|
+
if (!token || /\s/.test(token)) return null;
|
|
22
|
+
if (token.includes('://') || token.startsWith('@')) return null;
|
|
23
|
+
if (GLOB_OR_SHELL.test(token)) return null;
|
|
24
|
+
|
|
25
|
+
token = token.replace(LINE_SUFFIX, '').replace(/[.,;:]+$/, '').replace(/\/+$/, '');
|
|
26
|
+
if (token.startsWith('./')) token = token.slice(2);
|
|
27
|
+
if (!token || token.startsWith('/') || token.startsWith('~')) return null;
|
|
28
|
+
if (token.split('/').includes('..')) return null;
|
|
29
|
+
if (token.includes('...') || token.length > 200) return null;
|
|
30
|
+
if (BARE_EXTENSION.test(token)) return null;
|
|
31
|
+
if (!SOURCE_EXT.test(token)) return null;
|
|
32
|
+
if (token.split('/').some((segment) => SKIP_DIRS.has(segment))) return null;
|
|
33
|
+
|
|
34
|
+
return token;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function pathCandidates(body) {
|
|
38
|
+
const found = new Set();
|
|
39
|
+
for (const match of String(body || '').matchAll(CODE_SPAN)) {
|
|
40
|
+
const token = candidate(match[1]);
|
|
41
|
+
if (token) found.add(token);
|
|
42
|
+
}
|
|
43
|
+
return [...found];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function indexProject(root, { skipDirs = SKIP_DIRS, limit = MAX_INDEXED_FILES } = {}) {
|
|
47
|
+
const byLastSegment = new Map();
|
|
48
|
+
let count = 0;
|
|
49
|
+
let truncated = false;
|
|
50
|
+
|
|
51
|
+
const walk = (dir, prefix) => {
|
|
52
|
+
if (truncated) return;
|
|
53
|
+
let entries;
|
|
54
|
+
try {
|
|
55
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
56
|
+
} catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (count >= limit) {
|
|
61
|
+
truncated = true;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (skipDirs.has(entry.name)) continue;
|
|
65
|
+
|
|
66
|
+
const relative = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
67
|
+
count += 1;
|
|
68
|
+
if (!byLastSegment.has(entry.name)) byLastSegment.set(entry.name, []);
|
|
69
|
+
byLastSegment.get(entry.name).push(relative);
|
|
70
|
+
|
|
71
|
+
if (entry.isDirectory()) walk(path.join(dir, entry.name), relative);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
walk(root, '');
|
|
76
|
+
return { byLastSegment, count, truncated };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolvesInProject(index, token) {
|
|
80
|
+
const matches = index.byLastSegment.get(token.split('/').pop());
|
|
81
|
+
if (!matches) return false;
|
|
82
|
+
if (!token.includes('/')) return true;
|
|
83
|
+
return matches.some((relative) => relative === token || relative.endsWith(`/${token}`));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function verifyPaths(projectDir, memories) {
|
|
87
|
+
const root = path.resolve(projectDir);
|
|
88
|
+
const index = indexProject(root);
|
|
89
|
+
const missing = [];
|
|
90
|
+
let checked = 0;
|
|
91
|
+
let capped = false;
|
|
92
|
+
|
|
93
|
+
for (const memory of memories) {
|
|
94
|
+
for (const token of pathCandidates(memory.body)) {
|
|
95
|
+
if (checked >= MAX_PATH_CHECKS) {
|
|
96
|
+
capped = true;
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
checked += 1;
|
|
100
|
+
if (!resolvesInProject(index, token)) {
|
|
101
|
+
missing.push({ kind: 'stale-path', severity: 'warn', file: memory.file, name: memory.name, token });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
if (capped) break;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { projectDir: root, checked, capped, indexed: index.count, truncated: index.truncated, missing };
|
|
108
|
+
}
|
package/src/settings.mjs
CHANGED
|
@@ -19,6 +19,21 @@ export const USER_SETTINGS = path.join(os.homedir(), '.claude', 'settings.json')
|
|
|
19
19
|
/** Default when nothing sets cleanupPeriodDays; transcripts older than this are swept. */
|
|
20
20
|
export const DEFAULT_CLEANUP_PERIOD_DAYS = 30;
|
|
21
21
|
|
|
22
|
+
export const SETTINGS_SEVERITY = {
|
|
23
|
+
unparseable: 'bad',
|
|
24
|
+
unreadable: 'bad',
|
|
25
|
+
'not-object': 'bad',
|
|
26
|
+
'invalid-auto-memory-directory': 'bad',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function summariseSettings(problems) {
|
|
30
|
+
const list = problems || [];
|
|
31
|
+
return {
|
|
32
|
+
count: list.length,
|
|
33
|
+
severity: list.some((problem) => problem.severity === 'bad') ? 'bad' : list.length ? 'warn' : 'ok',
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
function managedDir() {
|
|
23
38
|
if (process.platform === 'darwin') return '/Library/Application Support/ClaudeCode';
|
|
24
39
|
if (process.platform === 'win32') return 'C:\\Program Files\\ClaudeCode';
|
|
@@ -226,12 +241,19 @@ export function settingsReport(options = {}) {
|
|
|
226
241
|
|
|
227
242
|
const problems = layers
|
|
228
243
|
.filter((layer) => layer.status !== 'ok' && layer.status !== 'absent')
|
|
229
|
-
.map((layer) => ({
|
|
244
|
+
.map((layer) => ({
|
|
245
|
+
kind: layer.status,
|
|
246
|
+
severity: SETTINGS_SEVERITY[layer.status] || 'warn',
|
|
247
|
+
scope: layer.scope,
|
|
248
|
+
file: layer.file,
|
|
249
|
+
detail: layer.error,
|
|
250
|
+
}));
|
|
230
251
|
|
|
231
252
|
const directory = resolveMemoryDirectory(options);
|
|
232
253
|
if (directory && directory.invalid) {
|
|
233
254
|
problems.push({
|
|
234
255
|
kind: 'invalid-auto-memory-directory',
|
|
256
|
+
severity: SETTINGS_SEVERITY['invalid-auto-memory-directory'],
|
|
235
257
|
scope: directory.scope,
|
|
236
258
|
file: directory.file,
|
|
237
259
|
detail: `"${directory.raw}" is ${directory.invalid}`,
|