lore-memory 0.5.0 → 0.5.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/README.md CHANGED
@@ -143,6 +143,13 @@ Add the MCP server to your Claude Code config (`~/.claude/settings.json`):
143
143
  }
144
144
  ```
145
145
 
146
+ ### šŸ›”ļø Autonomous AI Integration (Zero-Prompt)
147
+ To make Claude autonomously respect your codebase rules without you ever having to ask it, drop a `CLAUDE.md` (or `.cursorrules` / `.clinerules`) file in the root of your project with this exact instruction:
148
+
149
+ > **"Before you modify, create, or explain any file in this codebase, you must ALWAYS execute the `lore_why` tool on that file path first to check if there are any architectural invariants, gotchas, or decisions you need to respect."**
150
+
151
+ Once added, the AI will autonomously query Lore in the background before it writes a single line of code, ensuring it never violates your team's architectural constraints.
152
+
146
153
  ---
147
154
 
148
155
  ## šŸ”’ Privacy & Data
package/bin/lore.js CHANGED
@@ -130,6 +130,7 @@ program
130
130
  .option('--tradeoffs <tradeoffs>', 'Tradeoffs')
131
131
  .option('--tags <tags>', 'Comma-separated tags')
132
132
  .option('--files <files>', 'Comma-separated file paths')
133
+ .option('--file <file>', 'Alias for --files')
133
134
  .action(require('../src/commands/log'));
134
135
 
135
136
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lore-memory",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Persistent project memory for developers. Captures decisions, invariants, gotchas, and graveyard entries — automatically and manually — and injects them into AI coding sessions.",
5
5
  "main": "bin/lore.js",
6
6
  "bin": {
@@ -39,19 +39,25 @@ function graph(filepath, options) {
39
39
  const index = readIndex();
40
40
  const normalized = path.relative(projectRoot, path.resolve(filepath)).replace(/\\/g, '/');
41
41
 
42
- const imports = g.imports[normalized] || [];
42
+ const importsArr = g.imports[normalized];
43
43
  const importedBy = g.importedBy[normalized] || [];
44
44
 
45
- if (imports.length === 0 && importedBy.length === 0) {
46
- console.log(chalk.yellow(`No graph data for ${filepath}`));
47
- console.log(chalk.dim(' Run: lore graph --build to build the dependency graph'));
45
+ if (importsArr === undefined) {
46
+ console.log(chalk.yellow(`${filepath} is not in the dependency graph.`));
47
+ console.log(chalk.dim(' Run: lore graph --build to index project files'));
48
48
  return;
49
49
  }
50
50
 
51
+ const imports = importsArr;
51
52
  const entryCount = (file) => (index.files[file] || []).length;
52
53
 
53
54
  console.log(chalk.cyan(`\nšŸ“– ${filepath}\n`));
54
55
 
56
+ if (imports.length === 0 && importedBy.length === 0) {
57
+ console.log(chalk.dim(' No internal project imports or dependents found.'));
58
+ return;
59
+ }
60
+
55
61
  if (imports.length > 0) {
56
62
  console.log(chalk.bold('Imports:'));
57
63
  for (const dep of imports) {
@@ -14,6 +14,13 @@ async function log(options) {
14
14
 
15
15
  let type, title, context, alternatives, tradeoffs, tags, files;
16
16
 
17
+ // If --title or --context is given without the other, error rather than falling through to interactive
18
+ if ((options.title && !options.context) || (!options.title && options.context)) {
19
+ const missing = !options.title ? '--title' : '--context';
20
+ console.error(chalk.red(`${missing} is required when using non-interactive mode`));
21
+ process.exit(1);
22
+ }
23
+
17
24
  // Inline mode: all three required fields provided as flags
18
25
  if (options.type && options.title && options.context) {
19
26
  type = options.type;
@@ -22,7 +29,8 @@ async function log(options) {
22
29
  alternatives = options.alternatives ? [options.alternatives] : [];
23
30
  tradeoffs = options.tradeoffs || '';
24
31
  tags = options.tags ? options.tags.split(',').map(t => t.trim()).filter(Boolean) : [];
25
- files = options.files ? options.files.split(',').map(f => f.trim()).filter(Boolean) : [];
32
+ const rawFiles = options.files || options.file || '';
33
+ files = rawFiles ? rawFiles.split(',').map(f => f.trim()).filter(Boolean) : [];
26
34
  } else {
27
35
  // Interactive mode
28
36
  const recentFiles = getRecentFiles();
@@ -38,13 +38,18 @@ function score() {
38
38
  console.log();
39
39
 
40
40
  // Coverage
41
- const cColor = result.coverage >= 70 ? chalk.green : result.coverage >= 40 ? chalk.yellow : chalk.red;
42
- console.log(cColor(`Coverage ${result.coverage}/100`));
43
- console.log(chalk.dim(` ${bar(result.coverage)} ${result.coveredModules}/${result.activeModules} active modules documented`));
44
- if (result.topUnlogged.length > 0) {
45
- console.log(chalk.yellow(' Highest risk unlogged modules:'));
46
- for (const { module: mod, commits } of result.topUnlogged) {
47
- console.log(chalk.yellow(` ${mod} — ${commits} commits`));
41
+ if (result.activeModules === 0) {
42
+ console.log(chalk.dim(`Coverage N/A`));
43
+ console.log(chalk.dim(` No active modules in recent git history`));
44
+ } else {
45
+ const cColor = result.coverage >= 70 ? chalk.green : result.coverage >= 40 ? chalk.yellow : chalk.red;
46
+ console.log(cColor(`Coverage ${result.coverage}/100`));
47
+ console.log(chalk.dim(` ${bar(result.coverage)} ${result.coveredModules}/${result.activeModules} active modules documented`));
48
+ if (result.topUnlogged.length > 0) {
49
+ console.log(chalk.yellow(' Highest risk unlogged modules:'));
50
+ for (const { module: mod, commits } of result.topUnlogged) {
51
+ console.log(chalk.yellow(` ${mod} — ${commits} commits`));
52
+ }
48
53
  }
49
54
  }
50
55
  console.log();
@@ -28,7 +28,9 @@ async function serve(options) {
28
28
  const uiPort = options.port || 3333;
29
29
  try {
30
30
  const { startDashboard } = require('./ui');
31
- startDashboard(uiPort);
31
+ // Pass quiet: true to prevent stdout corruption of the MCP protocol stream
32
+ // and prevent the UI from calling process.exit() if the port is in use.
33
+ startDashboard(uiPort, { quiet: true, openBrowser: false });
32
34
  } catch (e) {
33
35
  process.stderr.write(chalk.yellow(`⚠ Could not start UI dashboard: ${e.message}\n`));
34
36
  }
@@ -16,7 +16,7 @@ async function openBrowser(url) {
16
16
  await open(url);
17
17
  }
18
18
 
19
- function createApp(portNum) {
19
+ function createApp(portNum, options = {}) {
20
20
  const app = express();
21
21
  const PORT = portNum || 3333;
22
22
 
@@ -153,28 +153,40 @@ function createApp(portNum) {
153
153
  });
154
154
 
155
155
  const server = app.listen(PORT, () => {
156
- const url = `http://localhost:${PORT}`;
157
- console.log(chalk.green(`\nšŸš€ Lore UI Dashboard running at ${chalk.bold(url)}\n`));
158
- console.log(chalk.cyan(` Press Ctrl+C to stop the server.`));
159
-
160
- // Use native exec to open browser to avoid ESM import issues with 'open'
161
- const { exec } = require('child_process');
162
- const startPath = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
163
- exec(`${startPath} ${url}`, (err) => {
164
- if (err) {
165
- console.log(chalk.dim(` (Could not open browser automatically. Please visit ${url} manually)`));
156
+ if (!options.quiet) {
157
+ const url = `http://localhost:${PORT}`;
158
+ console.log(chalk.green(`\nšŸš€ Lore UI Dashboard running at ${chalk.bold(url)}\n`));
159
+ console.log(chalk.cyan(` Press Ctrl+C to stop the server.`));
160
+
161
+ // Use native exec to open browser to avoid ESM import issues with 'open'
162
+ if (options.openBrowser !== false) {
163
+ const { exec } = require('child_process');
164
+ const startPath = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
165
+ exec(`${startPath} ${url}`, (err) => {
166
+ if (err) {
167
+ console.log(chalk.dim(` (Could not open browser automatically. Please visit ${url} manually)`));
168
+ }
169
+ });
166
170
  }
167
- });
171
+ }
168
172
  });
169
173
 
170
174
  server.on('error', (e) => {
171
175
  if (e.code === 'EADDRINUSE') {
172
- console.error(chalk.red(`\nPort ${PORT} is already in use by another process.`));
173
- console.error(chalk.yellow(`Use 'lore ui --port <number>' to specify a different port.\n`));
174
- process.exit(1);
176
+ if (!options.quiet) {
177
+ console.error(chalk.red(`\nPort ${PORT} is already in use by another process.`));
178
+ console.error(chalk.yellow(`Use 'lore ui --port <number>' to specify a different port.\n`));
179
+ process.exit(1);
180
+ } else {
181
+ process.stderr.write(chalk.yellow(`\n⚠ Lore UI dashboard port ${PORT} is in use; dashboard disabled for this instance.\n`));
182
+ }
175
183
  } else {
176
- console.error(chalk.red(`\nFailed to start server: ${e.message}\n`));
177
- process.exit(1);
184
+ if (!options.quiet) {
185
+ console.error(chalk.red(`\nFailed to start server: ${e.message}\n`));
186
+ process.exit(1);
187
+ } else {
188
+ process.stderr.write(chalk.red(`\n⚠ Failed to start Lore UI dashboard: ${e.message}\n`));
189
+ }
178
190
  }
179
191
  });
180
192
 
@@ -186,13 +198,13 @@ function createApp(portNum) {
186
198
  * @param {number} port
187
199
  * @returns {object} Express server instance
188
200
  */
189
- function startDashboard(port) {
190
- return createApp(port || 3333);
201
+ function startDashboard(port, options = {}) {
202
+ return createApp(port || 3333, options);
191
203
  }
192
204
 
193
205
  function ui(options) {
194
206
  requireInit();
195
- createApp(options.port || 3333);
207
+ createApp(options.port || 3333, { quiet: false, openBrowser: true });
196
208
  }
197
209
 
198
210
  module.exports = ui;
@@ -36,7 +36,8 @@ function generateId(type, title) {
36
36
  .split(/\s+/)
37
37
  .filter(Boolean)
38
38
  .slice(0, 3)
39
- .join('-');
39
+ .join('-')
40
+ .slice(0, 40);
40
41
  const ts = Math.floor(Date.now() / 1000);
41
42
  return `${type}-${words}-${ts}`;
42
43
  }
package/src/lib/scorer.js CHANGED
@@ -53,7 +53,7 @@ function getModulesWithEntries(index) {
53
53
  }
54
54
 
55
55
  function calcCoverage(activeModules, modulesWithEntries) {
56
- if (activeModules.length === 0) return 100;
56
+ if (activeModules.length === 0) return 50; // neutral — no data, displayed as N/A
57
57
  const covered = activeModules.filter(m => modulesWithEntries.has(m)).length;
58
58
  return Math.round((covered / activeModules.length) * 100);
59
59
  }