claude-code-memory-explorer 0.13.0 → 0.14.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 CHANGED
@@ -35,7 +35,7 @@ PORT=8080 Custom port (default: 3544, falls back if busy)
35
35
  --open Open browser on start
36
36
  ```
37
37
 
38
- The config dir can also be set via the `CLAUDE_CONFIG_DIR` environment variable. Switch projects at runtime with the picker (Shift+P) or `?project=/path`.
38
+ The config dir can also be set via the `CLAUDE_CONFIG_DIR` environment variable; the analyzer's headless `claude` run inherits it. Switch projects at runtime with the picker (Shift+P) or `?project=/path`.
39
39
 
40
40
  ## License
41
41
 
@@ -221,6 +221,7 @@ function execNoShell(cmd, args, opts = {}) {
221
221
  let argv = args;
222
222
  const spawnOpts = {
223
223
  cwd: opts.cwd || undefined,
224
+ env: opts.env,
224
225
  timeout: opts.timeout,
225
226
  windowsHide: true,
226
227
  stdio: ['ignore', 'pipe', 'pipe'],
@@ -258,7 +259,9 @@ function execNoShell(cmd, args, opts = {}) {
258
259
  child.on('error', reject);
259
260
  child.on('close', (code, signal) => {
260
261
  if (signal) return reject(new Error(`Command terminated by ${signal}: ${stderr.trim()}`));
261
- if (code !== 0) return reject(new Error(stderr.trim() || `Command exited with code ${code}`));
262
+ // The claude CLI reports its own failures on stdout, so stderr alone is
263
+ // often empty and the caller would surface only the exit code.
264
+ if (code !== 0) return reject(new Error(stderr.trim() || stdout.trim() || `Command exited with code ${code}`));
262
265
  resolve({ stdout, stderr });
263
266
  });
264
267
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-memory-explorer",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Memory explorer dashboard for Claude Code",
5
5
  "main": "server.js",
6
6
  "bin": {
package/public/app.js CHANGED
@@ -1361,7 +1361,7 @@ function findingPrompt(f) {
1361
1361
  } else if (paths.length > 1) {
1362
1362
  header = 'In the Claude Code instruction files listed below, apply this fix:';
1363
1363
  } else if (f.scope === 'user') {
1364
- const path = stackData.find((s) => s.id === 'user-claude-md')?.path || '~/.claude/CLAUDE.md';
1364
+ const path = stackData.find((s) => s.id === 'user-claude-md')?.path || summaryData.userClaudeMd;
1365
1365
  header = `In the user-level Claude Code instructions at ${path}, apply this fix:`;
1366
1366
  } else {
1367
1367
  const dir = memoryDirPath();
@@ -2098,8 +2098,9 @@ document.addEventListener('keydown', (e) => {
2098
2098
  e.preventDefault();
2099
2099
  fwd(e);
2100
2100
  }
2101
- // Its own branch: the Alt+digit case below requires !ctrlKey.
2102
- if (e.ctrlKey && e.altKey && !e.shiftKey && !e.metaKey && e.key.toLowerCase() === 'p') {
2101
+ // Own branch: the Alt+digit case below requires !ctrlKey. The hub owns the Ctrl+Alt+letter
2102
+ // keymap and ignores unbound letters.
2103
+ if (e.ctrlKey && e.altKey && !e.shiftKey && !e.metaKey && /^[a-z]$/i.test(e.key)) {
2103
2104
  e.preventDefault();
2104
2105
  fwd(e);
2105
2106
  }
package/server.js CHANGED
@@ -25,7 +25,13 @@ const expandHome = (p) => (typeof p === 'string' ? p : '').replace(/^~/, os.home
25
25
  const PORT = getArg('port') || process.env.PORT || 3544;
26
26
  const AUTO_OPEN = process.argv.includes('--open');
27
27
  const claudeDirArg = getArg('dir') || process.env.CLAUDE_CONFIG_DIR || process.env.CLAUDE_DIR;
28
- const CLAUDE_DIR = claudeDirArg ? expandHome(claudeDirArg) : path.join(os.homedir(), '.claude');
28
+ const DEFAULT_CLAUDE_DIR = path.join(os.homedir(), '.claude');
29
+ const CLAUDE_DIR = claudeDirArg ? expandHome(claudeDirArg) : DEFAULT_CLAUDE_DIR;
30
+ const USER_CLAUDE_MD = path.join(CLAUDE_DIR, 'CLAUDE.md');
31
+ // Claude Code keeps .claude.json beside ~/.claude by default but inside CLAUDE_CONFIG_DIR
32
+ // when that is set, so the default dir must stay unset for the CLI rather than be spelled out.
33
+ const IS_DEFAULT_CLAUDE_DIR = path.resolve(CLAUDE_DIR) === path.resolve(DEFAULT_CLAUDE_DIR);
34
+ const CLI_ENV = IS_DEFAULT_CLAUDE_DIR ? process.env : { ...process.env, CLAUDE_CONFIG_DIR: CLAUDE_DIR };
29
35
  const projectDirArg = getArg('project');
30
36
 
31
37
  // #endregion CLI_ARGS
@@ -233,8 +239,7 @@ function discoverMemorySources(projectPath) {
233
239
  }
234
240
 
235
241
  // 2. User CLAUDE.md
236
- const userClaudeMd = path.join(CLAUDE_DIR, 'CLAUDE.md');
237
- const userInfo = fileInfo(userClaudeMd);
242
+ const userInfo = fileInfo(USER_CLAUDE_MD);
238
243
  if (userInfo) {
239
244
  sources.push({
240
245
  id: 'user-claude-md',
@@ -843,7 +848,7 @@ const RUBRIC_CLAUDE_MD = [
843
848
  const RUBRIC_CLAUDE_MD_USER = [
844
849
  'User-level CLAUDE.md criteria (global instructions, loaded in every session of every project):',
845
850
  '- Universality: every rule must hold on any project. Flag rules referencing one specific repo, stack, tool chain, or project path — they belong in that project\'s CLAUDE.md (kind: demote).',
846
- '- Machine verification only: check that tools the file names exist on PATH (command -v / which / where) and that files, skills, or agents it references exist under ~/.claude. NEVER verify against the working directory\'s repository — it is an arbitrary project and proves nothing about a global rule.',
851
+ `- Machine verification only: check that tools the file names exist on PATH (command -v / which / where) and that files, skills, or agents it references exist under ${CLAUDE_DIR}. NEVER verify against the working directory's repository — it is an arbitrary project and proves nothing about a global rule.`,
847
852
  '- Self-consistency: flag sections, tags, or mnemonics that contradict or duplicate each other within the file and its imports.',
848
853
  '- Executability: flag standards name-dropped without concrete behaviors ("follow style guide X") and rules the model cannot act on — require the 2-3 specific behaviors the user actually wants.',
849
854
  '- Flag emphasis inflation (all-caps, NEVER/ALWAYS without stakes) and vague or no-op rules the model cannot act on.',
@@ -970,7 +975,7 @@ const REVIEWER_COMMON = [
970
975
  // directory is an arbitrary project and must not influence a global-file audit.
971
976
  const REVIEWER_COMMON_USER = [
972
977
  'You review the user-level (global) CLAUDE.md that loads in every session of every project; the parent passes its name and full content.',
973
- 'Verify before judging, against the machine only: check tools the file names with `command -v` / `which` / `where` (the only Bash commands you may run), and check files, skills, or agents it references under ~/.claude with your read tools. NEVER read or judge against the repository in your working directory — it is one arbitrary project and proves nothing about a global rule.',
978
+ `Verify before judging, against the machine only: check tools the file names with \`command -v\` / \`which\` / \`where\` (the only Bash commands you may run), and check files, skills, or agents it references under ${CLAUDE_DIR} with your read tools. NEVER read or judge against the repository in your working directory — it is one arbitrary project and proves nothing about a global rule.`,
974
979
  'Also judge whether the content is still needed at all: if the tool it references is gone from the machine or the file it points to no longer exists, say so explicitly.',
975
980
  'A check you could not run is not evidence of absence. If a probe is denied by permissions or errors out, report that claim as UNCHECKED and say why — never turn a failed check into a stale or invalidate finding.',
976
981
  ];
@@ -1019,7 +1024,7 @@ const ANALYSIS_AGENTS = {
1019
1024
  [RUBRIC_CLAUDE_MD, RUBRIC_IMPORTS],
1020
1025
  ),
1021
1026
  'claude-md-user-reviewer': reviewerAgent(
1022
- 'Reviews and verifies the user-level (global) CLAUDE.md against the user rubric, PATH, and ~/.claude — never against the working repository.',
1027
+ `Reviews and verifies the user-level (global) CLAUDE.md against the user rubric, PATH, and ${CLAUDE_DIR} — never against the working repository.`,
1023
1028
  [RUBRIC_CLAUDE_MD_USER, RUBRIC_IMPORTS],
1024
1029
  REVIEWER_COMMON_USER,
1025
1030
  ),
@@ -1039,7 +1044,7 @@ function runClaudeAnalysis(prompt, model, cwd) {
1039
1044
  try {
1040
1045
  // cwd = the project root so the agent can verify claims against the actual
1041
1046
  // files with its read-only tools (Read/Glob/Grep need no permission grants).
1042
- child = spawn('claude', args, { cwd: cwd || os.tmpdir(), windowsHide: true });
1047
+ child = spawn('claude', args, { cwd: cwd || os.tmpdir(), env: CLI_ENV, windowsHide: true });
1043
1048
  } catch (e) {
1044
1049
  return resolve({ ok: false, error: `Failed to spawn claude: ${e.message}` });
1045
1050
  }
@@ -1313,7 +1318,9 @@ app.get('/api/summary', (_req, res) => {
1313
1318
  for (const f of sources) scopeChars[f.scope] = (scopeChars[f.scope] || 0) + (f.chars || 0);
1314
1319
  const totalChars = sources.reduce((s, f) => s + (f.chars || 0), 0) + skillDesc.chars;
1315
1320
  // ids: which sources make up the footprint, so the client can highlight them
1316
- res.json({ totalFiles, totalLines, totalBytes, totalChars, scopeChars, skillDesc, alwaysLoaded, conditional, onDemand, ids: sources.map(s => s.id) });
1321
+ // userClaudeMd: where the user-level file lives even when it does not exist yet, so the
1322
+ // client can name it in fix prompts without assuming ~/.claude
1323
+ res.json({ totalFiles, totalLines, totalBytes, totalChars, scopeChars, skillDesc, alwaysLoaded, conditional, onDemand, ids: sources.map(s => s.id), userClaudeMd: USER_CLAUDE_MD });
1317
1324
  });
1318
1325
 
1319
1326
  app.get('/api/stack', (_req, res) => {