mindlore 0.0.1 → 0.1.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/scripts/init.cjs CHANGED
@@ -14,7 +14,7 @@ const path = require('path');
14
14
 
15
15
  // ── Constants ──────────────────────────────────────────────────────────
16
16
 
17
- const { MINDLORE_DIR, DB_NAME, DIRECTORIES } = require('./lib/constants.cjs');
17
+ const { MINDLORE_DIR, DB_NAME, DIRECTORIES, homedir } = require('./lib/constants.cjs');
18
18
 
19
19
  const TEMPLATE_FILES = ['INDEX.md', 'log.md'];
20
20
 
@@ -84,11 +84,51 @@ function copyTemplates(baseDir, packageRoot) {
84
84
 
85
85
  // ── Step 3: Create FTS5 database ───────────────────────────────────────
86
86
 
87
+ function migrateDatabase(dbPath, Database) {
88
+ const db = new Database(dbPath);
89
+ try {
90
+ // Check if FTS5 table has the new schema (7 columns with slug, description, etc.)
91
+ const info = db.pragma('table_info(mindlore_fts)');
92
+ const columns = info.map((r) => r.name);
93
+ if (!columns.includes('slug') || !columns.includes('description')) {
94
+ log('Upgrading FTS5 schema (2 → 7 columns, porter stemmer)...');
95
+ db.exec('DROP TABLE IF EXISTS mindlore_fts');
96
+ db.exec(`
97
+ CREATE VIRTUAL TABLE mindlore_fts
98
+ USING fts5(path UNINDEXED, slug, description, type UNINDEXED, category, title, content, tokenize='porter unicode61');
99
+ `);
100
+ // Clear hashes so full re-index happens
101
+ db.exec('DELETE FROM file_hashes');
102
+ db.close();
103
+ return true;
104
+ }
105
+ } catch (_err) {
106
+ // table_info fails on FTS5 virtual tables in some versions — recreate
107
+ db.exec('DROP TABLE IF EXISTS mindlore_fts');
108
+ db.exec(`
109
+ CREATE VIRTUAL TABLE mindlore_fts
110
+ USING fts5(path UNINDEXED, slug, description, type UNINDEXED, category, title, content, tokenize='porter unicode61');
111
+ `);
112
+ db.exec('DELETE FROM file_hashes');
113
+ db.close();
114
+ return true;
115
+ }
116
+ db.close();
117
+ return false;
118
+ }
119
+
87
120
  function createDatabase(baseDir) {
88
121
  const dbPath = path.join(baseDir, DB_NAME);
89
122
  if (fs.existsSync(dbPath)) {
90
- log('Database already exists, skipping');
91
- return false;
123
+ let Database;
124
+ try { Database = require('better-sqlite3'); } catch (_err) { return false; }
125
+ const migrated = migrateDatabase(dbPath, Database);
126
+ if (migrated) {
127
+ log('FTS5 schema upgraded — run index to rebuild');
128
+ } else {
129
+ log('Database already exists, schema OK');
130
+ }
131
+ return migrated;
92
132
  }
93
133
 
94
134
  let Database;
@@ -105,7 +145,7 @@ function createDatabase(baseDir) {
105
145
 
106
146
  db.exec(`
107
147
  CREATE VIRTUAL TABLE IF NOT EXISTS mindlore_fts
108
- USING fts5(path, content, tokenize='unicode61');
148
+ USING fts5(path UNINDEXED, slug, description, type UNINDEXED, category, title, content, tokenize='porter unicode61');
109
149
  `);
110
150
 
111
151
  db.exec(`
@@ -124,7 +164,7 @@ function createDatabase(baseDir) {
124
164
 
125
165
  function mergeHooks(packageRoot) {
126
166
  const settingsPath = path.join(
127
- require('./lib/constants.cjs').homedir(),
167
+ homedir(),
128
168
  '.claude',
129
169
  'settings.json'
130
170
  );
@@ -239,7 +279,59 @@ function addSchemaToProjectDocs() {
239
279
  return false;
240
280
  }
241
281
 
242
- // ── Step 6: Add .mindlore/ to .gitignore ───────────────────────────────
282
+ // ── Step 6: Register skills ────────────────────────────────────────────
283
+
284
+ function registerSkills(packageRoot, plugin) {
285
+ const skillsDir = path.join(homedir(), '.claude', 'skills');
286
+ ensureDir(skillsDir);
287
+
288
+ if (!plugin.skills || plugin.skills.length === 0) return 0;
289
+
290
+ let added = 0;
291
+ for (const skill of plugin.skills) {
292
+ const skillSrcDir = path.join(packageRoot, path.dirname(skill.path));
293
+ const skillDestDir = path.join(skillsDir, skill.name);
294
+
295
+ ensureDir(skillDestDir);
296
+ const entries = fs.readdirSync(skillSrcDir, { withFileTypes: true });
297
+ for (const entry of entries) {
298
+ if (!entry.isFile()) continue;
299
+ fs.copyFileSync(
300
+ path.join(skillSrcDir, entry.name),
301
+ path.join(skillDestDir, entry.name)
302
+ );
303
+ }
304
+ added++;
305
+ }
306
+
307
+ return added;
308
+ }
309
+
310
+ // ── Step 7: Install better-sqlite3 if needed ──────────────────────────
311
+
312
+ function ensureBetterSqlite3() {
313
+ try {
314
+ require('better-sqlite3');
315
+ return true;
316
+ } catch (_err) {
317
+ try {
318
+ const { execSync } = require('child_process');
319
+ log('Installing better-sqlite3 (native dependency)...');
320
+ execSync('npm install better-sqlite3 --no-save', {
321
+ cwd: process.cwd(),
322
+ stdio: 'pipe',
323
+ timeout: 120000,
324
+ });
325
+ return true;
326
+ } catch (_installErr) {
327
+ log('WARNING: Could not install better-sqlite3. FTS5 search disabled.');
328
+ log(' Run manually: npm install better-sqlite3');
329
+ return false;
330
+ }
331
+ }
332
+ }
333
+
334
+ // ── Step 8: Add .mindlore/ to .gitignore ───────────────────────────────
243
335
 
244
336
  function addToGitignore() {
245
337
  const gitignorePath = path.join(process.cwd(), '.gitignore');
@@ -263,9 +355,15 @@ function main() {
263
355
  const args = process.argv.slice(2);
264
356
  const command = args[0];
265
357
 
358
+ if (command === 'uninstall') {
359
+ require('./uninstall.cjs');
360
+ return;
361
+ }
362
+
266
363
  if (command && command !== 'init') {
267
364
  console.log(`Unknown command: ${command}`);
268
365
  console.log('Usage: npx mindlore init [--recommended]');
366
+ console.log(' npx mindlore uninstall [--all]');
269
367
  process.exit(1);
270
368
  }
271
369
 
@@ -291,11 +389,20 @@ function main() {
291
389
  : 'All templates already in place'
292
390
  );
293
391
 
294
- // Step 3: Database
392
+ // Step 3: better-sqlite3 (before DB creation so it's available)
393
+ ensureBetterSqlite3();
394
+
395
+ // Step 4: Database
295
396
  const dbCreated = createDatabase(baseDir);
296
397
  log(dbCreated ? 'Created FTS5 database' : 'Database already exists');
297
398
 
298
- // Step 4: Hooks
399
+ // Read plugin.json once for hooks + skills
400
+ const pluginPath = path.join(packageRoot, 'plugin.json');
401
+ const plugin = fs.existsSync(pluginPath)
402
+ ? JSON.parse(fs.readFileSync(pluginPath, 'utf8'))
403
+ : {};
404
+
405
+ // Step 5: Hooks
299
406
  const hooksAdded = mergeHooks(packageRoot);
300
407
  if (typeof hooksAdded === 'number' && hooksAdded > 0) {
301
408
  log(`Registered ${hooksAdded} hooks in ~/.claude/settings.json`);
@@ -303,7 +410,7 @@ function main() {
303
410
  log('Hooks already registered (or settings.json not found)');
304
411
  }
305
412
 
306
- // Step 5: SCHEMA.md in projectDocFiles
413
+ // Step 6: SCHEMA.md in projectDocFiles
307
414
  const schemaAdded = addSchemaToProjectDocs();
308
415
  log(
309
416
  schemaAdded
@@ -311,7 +418,15 @@ function main() {
311
418
  : 'SCHEMA.md already in project settings'
312
419
  );
313
420
 
314
- // Step 6: .gitignore
421
+ // Step 7: Skills
422
+ const skillsAdded = registerSkills(packageRoot, plugin);
423
+ log(
424
+ skillsAdded > 0
425
+ ? `Registered ${skillsAdded} skills in ~/.claude/skills/`
426
+ : 'Skills already registered'
427
+ );
428
+
429
+ // Step 8: .gitignore
315
430
  const gitignoreAdded = addToGitignore();
316
431
  log(
317
432
  gitignoreAdded
@@ -13,7 +13,7 @@ const path = require('path');
13
13
  // ── Constants ──────────────────────────────────────────────────────────
14
14
 
15
15
  const { DB_NAME } = require('./lib/constants.cjs');
16
- const { sha256, getAllMdFiles } = require('../hooks/lib/mindlore-common.cjs');
16
+ const { sha256, getAllMdFiles, openDatabase, parseFrontmatter, extractFtsMetadata, SQL_FTS_INSERT } = require('../hooks/lib/mindlore-common.cjs');
17
17
 
18
18
  // ── Main ───────────────────────────────────────────────────────────────
19
19
 
@@ -26,17 +26,12 @@ function main() {
26
26
  process.exit(1);
27
27
  }
28
28
 
29
- let Database;
30
- try {
31
- Database = require('better-sqlite3');
32
- } catch (_err) {
29
+ const db = openDatabase(dbPath);
30
+ if (!db) {
33
31
  console.error(' better-sqlite3 not installed. Run: npm install better-sqlite3');
34
32
  process.exit(1);
35
33
  }
36
34
 
37
- const db = new Database(dbPath);
38
- db.pragma('journal_mode = WAL');
39
-
40
35
  // Prepare statements
41
36
  const getHash = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?');
42
37
  const upsertHash = db.prepare(`
@@ -47,7 +42,7 @@ function main() {
47
42
  last_indexed = excluded.last_indexed
48
43
  `);
49
44
  const deleteFts = db.prepare('DELETE FROM mindlore_fts WHERE path = ?');
50
- const insertFts = db.prepare('INSERT INTO mindlore_fts (path, content) VALUES (?, ?)');
45
+ const insertFts = db.prepare(SQL_FTS_INSERT);
51
46
 
52
47
  // Get all .md files
53
48
  const mdFiles = getAllMdFiles(baseDir);
@@ -71,8 +66,10 @@ function main() {
71
66
  }
72
67
 
73
68
  // Update FTS5
69
+ const { meta, body } = parseFrontmatter(content);
70
+ const { slug, description, type, category, title } = extractFtsMetadata(meta, body, filePath, baseDir);
74
71
  deleteFts.run(filePath);
75
- insertFts.run(filePath, content);
72
+ insertFts.run(filePath, slug, description, type, category, title, body);
76
73
 
77
74
  // Update hash
78
75
  upsertHash.run(filePath, hash, now);
@@ -13,6 +13,7 @@ const fs = require('fs');
13
13
  const path = require('path');
14
14
 
15
15
  const { DB_NAME } = require('./lib/constants.cjs');
16
+ const { openDatabase } = require('../hooks/lib/mindlore-common.cjs');
16
17
  const MAX_RESULTS = 3;
17
18
 
18
19
  // ── Helpers ────────────────────────────────────────────────────────────
@@ -46,16 +47,12 @@ function main() {
46
47
  process.exit(1);
47
48
  }
48
49
 
49
- let Database;
50
- try {
51
- Database = require('better-sqlite3');
52
- } catch (_err) {
50
+ const db = openDatabase(dbPath, { readonly: true });
51
+ if (!db) {
53
52
  console.error(' better-sqlite3 not installed.');
54
53
  process.exit(1);
55
54
  }
56
55
 
57
- const db = new Database(dbPath, { readonly: true });
58
-
59
56
  try {
60
57
  // Sanitize query for FTS5 (escape special chars)
61
58
  const sanitized = query.replace(/['"(){}[\]*:^~!]/g, ' ').trim();
@@ -116,10 +116,10 @@ class HealthChecker {
116
116
  }
117
117
  const content = fs.readFileSync(indexPath, 'utf8');
118
118
  const lines = content.trim().split('\n');
119
- if (lines.length > 30) {
119
+ if (lines.length > 60) {
120
120
  return {
121
121
  warn: true,
122
- detail: `${lines.length} lines (should be ~15-20, consider trimming)`,
122
+ detail: `${lines.length} lines (should be ~15-60, consider trimming)`,
123
123
  };
124
124
  }
125
125
  return { ok: true, detail: `${lines.length} lines` };
@@ -147,9 +147,25 @@ class HealthChecker {
147
147
  const hashResult = db
148
148
  .prepare('SELECT count(*) as cnt FROM file_hashes')
149
149
  .get();
150
+
151
+ // Verify 7-column schema (slug, description, type, category, title, content + path)
152
+ let schemaOk = true;
153
+ try {
154
+ db.prepare('SELECT slug, description, category, title FROM mindlore_fts LIMIT 0').run();
155
+ } catch (_err) {
156
+ schemaOk = false;
157
+ }
158
+
159
+ if (!schemaOk) {
160
+ return {
161
+ warn: true,
162
+ detail: `${result.cnt} indexed, ${hashResult.cnt} hashes — OLD SCHEMA (run: npx mindlore init to upgrade)`,
163
+ };
164
+ }
165
+
150
166
  return {
151
167
  ok: true,
152
- detail: `${result.cnt} indexed, ${hashResult.cnt} hashes`,
168
+ detail: `${result.cnt} indexed, ${hashResult.cnt} hashes, 7-col schema`,
153
169
  };
154
170
  } catch (err) {
155
171
  return { ok: false, detail: `FTS5 error: ${err.message}` };
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * mindlore uninstall — Remove Mindlore hooks, skills, and optionally project data.
6
+ *
7
+ * Usage: npx mindlore uninstall [--all]
8
+ *
9
+ * --all: also remove .mindlore/ project data (without flag, only hooks + skills)
10
+ */
11
+
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const { homedir } = require('./lib/constants.cjs');
15
+
16
+ function log(msg) {
17
+ console.log(` ${msg}`);
18
+ }
19
+
20
+ // ── Remove hooks from settings.json ────────────────────────────────────
21
+
22
+ function removeHooks() {
23
+ const settingsPath = path.join(homedir(), '.claude', 'settings.json');
24
+
25
+ if (!fs.existsSync(settingsPath)) {
26
+ log('No settings.json found, skipping hooks');
27
+ return 0;
28
+ }
29
+
30
+ let settings;
31
+ try {
32
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
33
+ } catch (_err) {
34
+ log('Could not parse settings.json, skipping hooks');
35
+ return 0;
36
+ }
37
+
38
+ if (!settings.hooks) return 0;
39
+
40
+ let removed = 0;
41
+ for (const event of Object.keys(settings.hooks)) {
42
+ const entries = settings.hooks[event];
43
+ if (!Array.isArray(entries)) continue;
44
+
45
+ const filtered = entries.filter((entry) => {
46
+ const hooks = entry.hooks || [];
47
+ const hasMindlore = hooks.some(
48
+ (h) => (h.command || '').includes('mindlore-')
49
+ );
50
+ // Also check flat format (legacy)
51
+ const flatMindlore = (entry.command || '').includes('mindlore-');
52
+
53
+ if (hasMindlore || flatMindlore) {
54
+ removed++;
55
+ return false;
56
+ }
57
+ return true;
58
+ });
59
+
60
+ settings.hooks[event] = filtered;
61
+
62
+ // Clean up empty arrays
63
+ if (settings.hooks[event].length === 0) {
64
+ delete settings.hooks[event];
65
+ }
66
+ }
67
+
68
+ if (removed > 0) {
69
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
70
+ }
71
+
72
+ return removed;
73
+ }
74
+
75
+ // ── Remove skills from ~/.claude/skills/ ───────────────────────────────
76
+
77
+ function removeSkills() {
78
+ const skillsDir = path.join(homedir(), '.claude', 'skills');
79
+ if (!fs.existsSync(skillsDir)) return 0;
80
+
81
+ const mindloreSkills = fs
82
+ .readdirSync(skillsDir)
83
+ .filter((d) => d.startsWith('mindlore-'));
84
+
85
+ let removed = 0;
86
+ for (const skill of mindloreSkills) {
87
+ const skillPath = path.join(skillsDir, skill);
88
+ fs.rmSync(skillPath, { recursive: true, force: true });
89
+ removed++;
90
+ }
91
+
92
+ return removed;
93
+ }
94
+
95
+ // ── Remove SCHEMA.md from projectDocFiles ──────────────────────────────
96
+
97
+ function removeFromProjectDocs() {
98
+ const projectSettingsPath = path.join(process.cwd(), '.claude', 'settings.json');
99
+ if (!fs.existsSync(projectSettingsPath)) return false;
100
+
101
+ let settings;
102
+ try {
103
+ settings = JSON.parse(fs.readFileSync(projectSettingsPath, 'utf8'));
104
+ } catch (_err) {
105
+ return false;
106
+ }
107
+
108
+ if (!settings.projectDocFiles) return false;
109
+
110
+ const before = settings.projectDocFiles.length;
111
+ settings.projectDocFiles = settings.projectDocFiles.filter(
112
+ (p) => !p.includes('mindlore')
113
+ );
114
+
115
+ if (settings.projectDocFiles.length < before) {
116
+ fs.writeFileSync(
117
+ projectSettingsPath,
118
+ JSON.stringify(settings, null, 2),
119
+ 'utf8'
120
+ );
121
+ return true;
122
+ }
123
+ return false;
124
+ }
125
+
126
+ // ── Remove .mindlore/ project data ─────────────────────────────────────
127
+
128
+ function removeProjectData() {
129
+ const mindloreDir = path.join(process.cwd(), '.mindlore');
130
+ if (!fs.existsSync(mindloreDir)) {
131
+ log('No .mindlore/ directory in current project');
132
+ return false;
133
+ }
134
+
135
+ fs.rmSync(mindloreDir, { recursive: true, force: true });
136
+ return true;
137
+ }
138
+
139
+ // ── Main ───────────────────────────────────────────────────────────────
140
+
141
+ function main() {
142
+ const args = process.argv.slice(2);
143
+ const removeAll = args.includes('--all');
144
+
145
+ console.log('\n Mindlore — Uninstall\n');
146
+
147
+ // Hooks
148
+ const hooksRemoved = removeHooks();
149
+ log(
150
+ hooksRemoved > 0
151
+ ? `Removed ${hooksRemoved} hooks from ~/.claude/settings.json`
152
+ : 'No hooks found'
153
+ );
154
+
155
+ // Skills
156
+ const skillsRemoved = removeSkills();
157
+ log(
158
+ skillsRemoved > 0
159
+ ? `Removed ${skillsRemoved} skills from ~/.claude/skills/`
160
+ : 'No skills found'
161
+ );
162
+
163
+ // Project doc files
164
+ const docsRemoved = removeFromProjectDocs();
165
+ log(
166
+ docsRemoved
167
+ ? 'Removed SCHEMA.md from project settings'
168
+ : 'No project doc references found'
169
+ );
170
+
171
+ // Project data (only with --all)
172
+ if (removeAll) {
173
+ const dataRemoved = removeProjectData();
174
+ log(
175
+ dataRemoved
176
+ ? 'Removed .mindlore/ project data'
177
+ : 'No .mindlore/ directory found'
178
+ );
179
+ } else {
180
+ log('.mindlore/ project data kept (use --all to remove)');
181
+ }
182
+
183
+ console.log('\n Done! Mindlore has been uninstalled.\n');
184
+ }
185
+
186
+ main();