lore-memory 0.1.1 → 0.3.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.
@@ -0,0 +1,321 @@
1
+ :root {
2
+ --bg-color: #050505;
3
+ --panel-bg: #111111;
4
+ --text-main: #00FF41; /* CRT Green */
5
+ --text-muted: #008F11;
6
+ --accent-decision: #00FFFF; /* Cyan */
7
+ --accent-invariant: #FF003C; /* Red */
8
+ --accent-gotcha: #FFB000; /* Amber */
9
+ --accent-graveyard: #555555;
10
+ --border: 1px solid #008F11;
11
+ --font-pixel: 'Press Start 2P', cursive;
12
+ --font-code: 'Fira Code', 'Courier New', monospace;
13
+ --glitch-offset: 2px;
14
+ }
15
+
16
+ * {
17
+ box-sizing: border-box;
18
+ margin: 0;
19
+ padding: 0;
20
+ }
21
+
22
+ body {
23
+ background-color: var(--bg-color);
24
+ color: var(--text-main);
25
+ font-family: var(--font-code);
26
+ font-size: 14px;
27
+ line-height: 1.6;
28
+ overflow: hidden; /* App feels like a terminal */
29
+ }
30
+
31
+ /* CRT Scanline Overlay Effect */
32
+ .crt-overlay {
33
+ position: fixed;
34
+ top: 0;
35
+ left: 0;
36
+ width: 100vw;
37
+ height: 100vh;
38
+ background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
39
+ background-size: 100% 4px, 3px 100%;
40
+ pointer-events: none;
41
+ z-index: 999;
42
+ }
43
+
44
+ /* Layout */
45
+ .layout {
46
+ display: flex;
47
+ height: 100vh;
48
+ }
49
+
50
+ /* Sidebar */
51
+ .sidebar {
52
+ width: 350px;
53
+ background-color: var(--panel-bg);
54
+ border-right: var(--border);
55
+ padding: 30px;
56
+ display: flex;
57
+ flex-direction: column;
58
+ }
59
+
60
+ .logo pre {
61
+ font-family: monospace;
62
+ color: var(--text-main);
63
+ font-size: 8px; /* Fit the ASCII art */
64
+ white-space: pre;
65
+ margin-bottom: 5px;
66
+ line-height: 1.2;
67
+ }
68
+
69
+ .subtitle {
70
+ font-family: var(--font-code);
71
+ color: var(--text-muted);
72
+ font-size: 12px;
73
+ letter-spacing: 2px;
74
+ margin-bottom: 40px;
75
+ text-align: center;
76
+ }
77
+
78
+ .nav-links {
79
+ list-style: none;
80
+ }
81
+
82
+ .nav-links li {
83
+ margin-bottom: 15px;
84
+ }
85
+
86
+ .nav-links a {
87
+ color: var(--text-muted);
88
+ text-decoration: none;
89
+ font-family: var(--font-pixel);
90
+ font-size: 10px;
91
+ display: block;
92
+ padding: 10px;
93
+ border: 1px solid transparent;
94
+ transition: all 0.2s ease;
95
+ }
96
+
97
+ .nav-links a:hover, .nav-links a.active {
98
+ color: var(--bg-color);
99
+ background-color: var(--text-main);
100
+ border: 1px solid var(--text-main);
101
+ box-shadow: 0 0 10px var(--text-main);
102
+ }
103
+
104
+ /* Main Content Area */
105
+ .content {
106
+ flex: 1;
107
+ padding: 40px;
108
+ overflow-y: auto;
109
+ position: relative;
110
+ }
111
+
112
+ .view {
113
+ display: none;
114
+ animation: flicker 0.15s ease-in 1;
115
+ }
116
+
117
+ .view.active {
118
+ display: block;
119
+ }
120
+
121
+ h1 {
122
+ font-family: var(--font-pixel);
123
+ font-size: 24px;
124
+ margin-bottom: 30px;
125
+ color: var(--text-main);
126
+ text-shadow: 0 0 5px var(--text-main);
127
+ }
128
+
129
+ /* Dashboard Score Components */
130
+ .score-card {
131
+ border: var(--border);
132
+ padding: 40px;
133
+ text-align: center;
134
+ margin-bottom: 30px;
135
+ background: repeating-linear-gradient(
136
+ 45deg,
137
+ transparent,
138
+ transparent 10px,
139
+ rgba(0, 143, 17, 0.05) 10px,
140
+ rgba(0, 143, 17, 0.05) 20px
141
+ );
142
+ }
143
+
144
+ .main-score {
145
+ font-family: var(--font-pixel);
146
+ font-size: 64px;
147
+ text-shadow: 0 0 15px var(--text-main);
148
+ }
149
+
150
+ .muted {
151
+ font-size: 24px;
152
+ color: var(--text-muted);
153
+ }
154
+
155
+ .metrics-grid {
156
+ display: grid;
157
+ grid-template-columns: 1fr 1fr 1fr;
158
+ gap: 20px;
159
+ margin-bottom: 40px;
160
+ }
161
+
162
+ .metric h3 {
163
+ font-family: var(--font-pixel);
164
+ font-size: 10px;
165
+ margin-bottom: 15px;
166
+ color: var(--text-muted);
167
+ }
168
+
169
+ .progress-bar {
170
+ height: 20px;
171
+ border: var(--border);
172
+ padding: 2px;
173
+ margin-bottom: 10px;
174
+ }
175
+
176
+ .progress-bar .fill {
177
+ height: 100%;
178
+ background-color: var(--text-main);
179
+ width: 0%;
180
+ transition: width 1s ease-out;
181
+ }
182
+
183
+ .tips-container {
184
+ border-top: var(--border);
185
+ padding-top: 20px;
186
+ }
187
+
188
+ .terminal-list {
189
+ list-style: none;
190
+ }
191
+ .terminal-list li::before {
192
+ content: "> ";
193
+ color: var(--accent-gotcha);
194
+ }
195
+
196
+ /* Knowledge Base Entries */
197
+ .controls {
198
+ display: flex;
199
+ gap: 15px;
200
+ margin-bottom: 20px;
201
+ }
202
+
203
+ input, select, button {
204
+ background: var(--bg-color);
205
+ color: var(--text-main);
206
+ border: var(--border);
207
+ padding: 10px;
208
+ font-family: var(--font-code);
209
+ font-size: 14px;
210
+ }
211
+
212
+ input:focus, select:focus {
213
+ outline: none;
214
+ box-shadow: 0 0 8px var(--text-muted);
215
+ }
216
+
217
+ .entries-grid, .drafts-container {
218
+ display: flex;
219
+ flex-direction: column;
220
+ gap: 20px;
221
+ }
222
+
223
+ .entry-card {
224
+ border: var(--border);
225
+ padding: 20px;
226
+ background-color: rgba(17, 17, 17, 0.8);
227
+ }
228
+
229
+ .entry-header {
230
+ display: flex;
231
+ justify-content: space-between;
232
+ border-bottom: 1px dashed var(--text-muted);
233
+ padding-bottom: 10px;
234
+ margin-bottom: 15px;
235
+ }
236
+
237
+ .type-badge {
238
+ font-family: var(--font-pixel);
239
+ font-size: 10px;
240
+ padding: 4px 8px;
241
+ }
242
+
243
+ .type-decision { color: var(--bg-color); background-color: var(--accent-decision); }
244
+ .type-invariant { color: var(--bg-color); background-color: var(--accent-invariant); }
245
+ .type-gotcha { color: var(--bg-color); background-color: var(--accent-gotcha); }
246
+ .type-graveyard { color: var(--text-main); background-color: var(--accent-graveyard); }
247
+
248
+ .entry-title {
249
+ font-weight: bold;
250
+ font-size: 16px;
251
+ margin-bottom: 10px;
252
+ }
253
+
254
+ .entry-meta {
255
+ color: var(--text-muted);
256
+ font-size: 12px;
257
+ margin-top: 15px;
258
+ }
259
+
260
+ /* Draft Specifics */
261
+ .draft-actions {
262
+ display: flex;
263
+ gap: 10px;
264
+ margin-top: 15px;
265
+ }
266
+
267
+ .btn {
268
+ cursor: pointer;
269
+ font-family: var(--font-pixel);
270
+ font-size: 8px;
271
+ text-transform: uppercase;
272
+ transition: all 0.2s;
273
+ }
274
+
275
+ .btn-accept {
276
+ border-color: var(--text-main);
277
+ color: var(--text-main);
278
+ }
279
+ .btn-accept:hover { background: var(--text-main); color: var(--bg-color); }
280
+
281
+ .btn-delete {
282
+ border-color: var(--accent-invariant);
283
+ color: var(--accent-invariant);
284
+ }
285
+ .btn-delete:hover { background: var(--accent-invariant); color: var(--bg-color); }
286
+
287
+ /* Graph Container */
288
+ #network-container {
289
+ width: 100%;
290
+ height: 600px;
291
+ border: var(--border);
292
+ background-color: var(--panel-bg);
293
+ }
294
+
295
+ /* Animations */
296
+ @keyframes flicker {
297
+ 0% { opacity: 0; }
298
+ 10% { opacity: 0.5; }
299
+ 20% { opacity: 0; }
300
+ 50% { opacity: 1; }
301
+ 60% { opacity: 0.8; }
302
+ 100% { opacity: 1; }
303
+ }
304
+
305
+ .toast {
306
+ position: fixed;
307
+ bottom: 20px;
308
+ right: 20px;
309
+ background-color: var(--text-main);
310
+ color: var(--bg-color);
311
+ padding: 15px 30px;
312
+ font-family: var(--font-code);
313
+ font-weight: bold;
314
+ border: 1px solid white;
315
+ z-index: 1000;
316
+ transition: opacity 0.3s;
317
+ }
318
+ .toast.hidden {
319
+ opacity: 0;
320
+ pointer-events: none;
321
+ }
@@ -47,11 +47,11 @@ function extractComments(code, filePath) {
47
47
  * Saves passing comments as drafts.
48
48
  * @param {string} absFilePath
49
49
  * @param {string} projectRoot
50
- * @returns {object[]} created drafts
50
+ * @returns {Promise<object[]>} created drafts
51
51
  */
52
- function mineFile(absFilePath, projectRoot) {
52
+ async function mineFile(absFilePath, projectRoot) {
53
53
  let code = '';
54
- try { code = fs.readFileSync(absFilePath, 'utf8'); } catch (e) { return []; }
54
+ try { code = await fs.readFile(absFilePath, 'utf8'); } catch (e) { return []; }
55
55
 
56
56
  const relativePath = path.relative(projectRoot, absFilePath).replace(/\\/g, '/');
57
57
  const comments = extractComments(code, absFilePath);
@@ -92,7 +92,7 @@ function mineFile(absFilePath, projectRoot) {
92
92
  * @param {string[]} ignore
93
93
  * @returns {number} total drafts created
94
94
  */
95
- function mineDirectory(absDirPath, projectRoot, ignore) {
95
+ async function mineDirectory(absDirPath, projectRoot, ignore) {
96
96
  const { globSync } = require('glob');
97
97
  const ignoreList = ignore || ['node_modules', 'dist', '.git', '.lore', 'coverage'];
98
98
  const ignorePats = ignoreList.map(i => `${i}/**`);
@@ -105,7 +105,8 @@ function mineDirectory(absDirPath, projectRoot, ignore) {
105
105
 
106
106
  let total = 0;
107
107
  for (const file of files) {
108
- total += mineFile(file, projectRoot).length;
108
+ const drafts = await mineFile(file, projectRoot);
109
+ total += drafts.length;
109
110
  }
110
111
  return total;
111
112
  }
@@ -29,9 +29,9 @@ function startWatcher(options = {}) {
29
29
 
30
30
  const log = options.logFile
31
31
  ? (msg) => {
32
- const plain = msg.replace(/\x1B\[[0-9;]*m/g, '');
33
- fs.appendFileSync(options.logFile, `${new Date().toISOString()} ${plain}\n`);
34
- }
32
+ const plain = msg.replace(/\x1B\[[0-9;]*m/g, '');
33
+ fs.appendFileSync(options.logFile, `${new Date().toISOString()} ${plain}\n`);
34
+ }
35
35
  : (msg) => console.log(msg);
36
36
 
37
37
  if (!options.quiet) {
@@ -56,46 +56,46 @@ function startWatcher(options = {}) {
56
56
  awaitWriteFinish: { stabilityThreshold: 500, pollInterval: 100 },
57
57
  });
58
58
 
59
- watcher.on('unlink', (relPath) => {
59
+ watcher.on('unlink', async (relPath) => {
60
60
  const abs = path.join(projectRoot, relPath);
61
- const draft = signals.onFileDeletion(abs, projectRoot);
61
+ const draft = await signals.onFileDeletion(abs, projectRoot);
62
62
  if (draft) recordDraft(draft, abs);
63
63
  });
64
64
 
65
- watcher.on('unlinkDir', (relPath) => {
65
+ watcher.on('unlinkDir', async (relPath) => {
66
66
  const abs = path.join(projectRoot, relPath);
67
- const draft = signals.onDirectoryDeletion(abs, projectRoot);
67
+ const draft = await signals.onDirectoryDeletion(abs, projectRoot);
68
68
  if (draft) recordDraft(draft, abs);
69
69
  });
70
70
 
71
- watcher.on('add', (relPath) => {
71
+ watcher.on('add', async (relPath) => {
72
72
  const abs = path.join(projectRoot, relPath);
73
- const draft = signals.onNewFile(abs, projectRoot);
73
+ const draft = await signals.onNewFile(abs, projectRoot);
74
74
  if (draft) recordDraft(draft, abs);
75
75
  });
76
76
 
77
- watcher.on('change', (relPath) => {
77
+ watcher.on('change', async (relPath) => {
78
78
  const abs = path.join(projectRoot, relPath);
79
79
 
80
80
  // Repeated edit tracking
81
- const editDraft = signals.trackFileEdit(abs, projectRoot);
81
+ const editDraft = await signals.trackFileEdit(abs, projectRoot);
82
82
  if (editDraft) recordDraft(editDraft, abs);
83
83
 
84
84
  // package.json changes
85
85
  if (relPath.endsWith('package.json')) {
86
- const pkgDrafts = signals.onPackageJsonChange(abs, projectRoot);
86
+ const pkgDrafts = await signals.onPackageJsonChange(abs, projectRoot);
87
87
  for (const d of pkgDrafts) recordDraft(d, abs);
88
88
  }
89
89
 
90
90
  // Comment mining + graph update for source files
91
91
  if (/\.(js|ts|jsx|tsx|py|go|rs)$/.test(relPath)) {
92
- const commentDrafts = mineFile(abs, projectRoot);
92
+ const commentDrafts = await mineFile(abs, projectRoot);
93
93
  if (commentDrafts.length > 0) {
94
94
  draftCount += commentDrafts.length;
95
95
  log(`${chalk.dim(`[${timestamp()}]`)} Mined ${commentDrafts.length} comment${commentDrafts.length === 1 ? '' : 's'} from ${chalk.yellow(relPath)} — queued for review`);
96
96
  }
97
97
 
98
- try { updateGraphForFile(abs, projectRoot); } catch (e) {}
98
+ try { updateGraphForFile(abs, projectRoot); } catch (e) { }
99
99
  }
100
100
  });
101
101
 
@@ -104,15 +104,15 @@ function startWatcher(options = {}) {
104
104
  let gitWatcher = null;
105
105
  if (fs.existsSync(path.join(projectRoot, '.git'))) {
106
106
  gitWatcher = chokidar.watch(commitMsgPath, { persistent: true, ignoreInitial: true });
107
- gitWatcher.on('change', () => {
107
+ gitWatcher.on('change', async () => {
108
108
  try {
109
- const message = fs.readFileSync(commitMsgPath, 'utf8').trim();
110
- const drafts = signals.onCommitMessage(message, projectRoot);
109
+ const message = await fs.readFile(commitMsgPath, 'utf8');
110
+ const drafts = await signals.onCommitMessage(message.trim(), projectRoot);
111
111
  for (const d of drafts) {
112
112
  draftCount++;
113
113
  log(`${chalk.dim(`[${timestamp()}]`)} Commit signal: "${message.slice(0, 60)}" — queued for review`);
114
114
  }
115
- } catch (e) {}
115
+ } catch (e) { }
116
116
  });
117
117
  }
118
118
 
@@ -22,7 +22,7 @@ function makeDraft(overrides) {
22
22
  };
23
23
  }
24
24
 
25
- function onFileDeletion(filepath, projectRoot) {
25
+ async function onFileDeletion(filepath, projectRoot) {
26
26
  const relativePath = path.relative(projectRoot, filepath).replace(/\\/g, '/');
27
27
 
28
28
  // Try to check line count via git
@@ -32,7 +32,7 @@ function onFileDeletion(filepath, projectRoot) {
32
32
  encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
33
33
  });
34
34
  lines = parseInt(out.trim(), 10) || 0;
35
- } catch (e) {}
35
+ } catch (e) { }
36
36
 
37
37
  if (lines > 0 && lines < 100) return null;
38
38
 
@@ -48,7 +48,7 @@ function onFileDeletion(filepath, projectRoot) {
48
48
  return draft;
49
49
  }
50
50
 
51
- function onDirectoryDeletion(dirpath, projectRoot) {
51
+ async function onDirectoryDeletion(dirpath, projectRoot) {
52
52
  const relativePath = path.relative(projectRoot, dirpath).replace(/\\/g, '/');
53
53
  const name = path.basename(relativePath);
54
54
  const draft = makeDraft({
@@ -62,7 +62,7 @@ function onDirectoryDeletion(dirpath, projectRoot) {
62
62
  return draft;
63
63
  }
64
64
 
65
- function onNewFile(filepath, projectRoot) {
65
+ async function onNewFile(filepath, projectRoot) {
66
66
  const relativePath = path.relative(projectRoot, filepath).replace(/\\/g, '/');
67
67
  const name = path.basename(relativePath);
68
68
  const lower = name.toLowerCase();
@@ -97,7 +97,7 @@ function onNewFile(filepath, projectRoot) {
97
97
  return null;
98
98
  }
99
99
 
100
- function onPackageJsonChange(filepath, projectRoot) {
100
+ async function onPackageJsonChange(filepath, projectRoot) {
101
101
  const relativePath = path.relative(projectRoot, filepath).replace(/\\/g, '/');
102
102
  if (!relativePath.endsWith('package.json')) return [];
103
103
 
@@ -108,9 +108,9 @@ function onPackageJsonChange(filepath, projectRoot) {
108
108
  encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'],
109
109
  });
110
110
  prev = JSON.parse(prevRaw);
111
- } catch (e) {}
111
+ } catch (e) { }
112
112
 
113
- try { curr = fs.readJsonSync(filepath); } catch (e) { return []; }
113
+ try { curr = await fs.readJson(filepath); } catch (e) { return []; }
114
114
 
115
115
  const prevDeps = Object.assign({}, prev.dependencies || {}, prev.devDependencies || {});
116
116
  const currDeps = Object.assign({}, curr.dependencies || {}, curr.devDependencies || {});
@@ -143,7 +143,7 @@ function onPackageJsonChange(filepath, projectRoot) {
143
143
  return drafts;
144
144
  }
145
145
 
146
- function onCommitMessage(message, projectRoot) {
146
+ async function onCommitMessage(message, projectRoot) {
147
147
  const lower = message.toLowerCase();
148
148
  const signals = [
149
149
  { re: /\b(replac|switch(ed|ing)|migrat)\b/, type: 'decision', confidence: 0.8 },
@@ -171,12 +171,12 @@ function onCommitMessage(message, projectRoot) {
171
171
  }
172
172
 
173
173
  // Track repeated edits to detect gotcha-worthy files
174
- function trackFileEdit(filepath, projectRoot) {
174
+ async function trackFileEdit(filepath, projectRoot) {
175
175
  const relativePath = path.relative(projectRoot, filepath).replace(/\\/g, '/');
176
176
  const statePath = path.join(LORE_DIR, 'watch-state.json');
177
177
 
178
178
  let state = { edits: {} };
179
- try { state = fs.readJsonSync(statePath); } catch (e) {}
179
+ try { state = await fs.readJson(statePath); } catch (e) { }
180
180
  if (!state.edits) state.edits = {};
181
181
 
182
182
  const now = Date.now();
@@ -186,7 +186,7 @@ function trackFileEdit(filepath, projectRoot) {
186
186
  state.edits[relativePath].push(now);
187
187
  state.edits[relativePath] = state.edits[relativePath].filter(t => t > weekAgo);
188
188
 
189
- try { fs.writeJsonSync(statePath, state, { spaces: 2 }); } catch (e) {}
189
+ try { await fs.writeJson(statePath, state, { spaces: 2 }); } catch (e) { }
190
190
 
191
191
  if (state.edits[relativePath].length >= 5) {
192
192
  const name = path.basename(relativePath);
@@ -201,7 +201,7 @@ function trackFileEdit(filepath, projectRoot) {
201
201
  saveDraft(draft);
202
202
  // Reset to avoid spam
203
203
  state.edits[relativePath] = [];
204
- try { fs.writeJsonSync(statePath, state, { spaces: 2 }); } catch (e) {}
204
+ try { await fs.writeJson(statePath, state, { spaces: 2 }); } catch (e) { }
205
205
  return draft;
206
206
  }
207
207
  return null;