mindlore 0.2.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.
Files changed (102) hide show
  1. package/README.md +8 -8
  2. package/SCHEMA.md +15 -2
  3. package/dist/scripts/init.d.ts +1 -1
  4. package/dist/scripts/init.js +98 -67
  5. package/dist/scripts/init.js.map +1 -1
  6. package/dist/scripts/lib/constants.d.ts +27 -0
  7. package/dist/scripts/lib/constants.d.ts.map +1 -1
  8. package/dist/scripts/lib/constants.js +74 -1
  9. package/dist/scripts/lib/constants.js.map +1 -1
  10. package/dist/scripts/lib/schemas.d.ts +93 -0
  11. package/dist/scripts/lib/schemas.d.ts.map +1 -0
  12. package/dist/scripts/lib/schemas.js +108 -0
  13. package/dist/scripts/lib/schemas.js.map +1 -0
  14. package/dist/scripts/mindlore-fts5-index.js +3 -4
  15. package/dist/scripts/mindlore-fts5-index.js.map +1 -1
  16. package/dist/scripts/mindlore-fts5-search.d.ts +1 -1
  17. package/dist/scripts/mindlore-fts5-search.js +87 -74
  18. package/dist/scripts/mindlore-fts5-search.js.map +1 -1
  19. package/dist/scripts/uninstall.d.ts +1 -1
  20. package/dist/scripts/uninstall.js +27 -17
  21. package/dist/scripts/uninstall.js.map +1 -1
  22. package/dist/tests/compounding.test.js +2 -2
  23. package/dist/tests/compounding.test.js.map +1 -1
  24. package/dist/tests/cwd-changed.test.d.ts +2 -0
  25. package/dist/tests/cwd-changed.test.d.ts.map +1 -0
  26. package/dist/tests/cwd-changed.test.js +62 -0
  27. package/dist/tests/cwd-changed.test.js.map +1 -0
  28. package/dist/tests/dedup.test.js +4 -4
  29. package/dist/tests/dedup.test.js.map +1 -1
  30. package/dist/tests/dont-repeat.test.d.ts +2 -0
  31. package/dist/tests/dont-repeat.test.d.ts.map +1 -0
  32. package/dist/tests/dont-repeat.test.js +100 -0
  33. package/dist/tests/dont-repeat.test.js.map +1 -0
  34. package/dist/tests/e2e-pipeline.test.d.ts +2 -0
  35. package/dist/tests/e2e-pipeline.test.d.ts.map +1 -0
  36. package/dist/tests/e2e-pipeline.test.js +220 -0
  37. package/dist/tests/e2e-pipeline.test.js.map +1 -0
  38. package/dist/tests/evolve.test.d.ts +2 -0
  39. package/dist/tests/evolve.test.d.ts.map +1 -0
  40. package/dist/tests/evolve.test.js +105 -0
  41. package/dist/tests/evolve.test.js.map +1 -0
  42. package/dist/tests/explore.test.d.ts +2 -0
  43. package/dist/tests/explore.test.d.ts.map +1 -0
  44. package/dist/tests/explore.test.js +146 -0
  45. package/dist/tests/explore.test.js.map +1 -0
  46. package/dist/tests/fts5.test.js +7 -7
  47. package/dist/tests/fts5.test.js.map +1 -1
  48. package/dist/tests/global-layer.test.d.ts +2 -0
  49. package/dist/tests/global-layer.test.d.ts.map +1 -0
  50. package/dist/tests/global-layer.test.js +152 -0
  51. package/dist/tests/global-layer.test.js.map +1 -0
  52. package/dist/tests/helpers/db.d.ts +14 -1
  53. package/dist/tests/helpers/db.d.ts.map +1 -1
  54. package/dist/tests/helpers/db.js +3 -3
  55. package/dist/tests/helpers/db.js.map +1 -1
  56. package/dist/tests/hook-smoke.test.js +2 -2
  57. package/dist/tests/hook-smoke.test.js.map +1 -1
  58. package/dist/tests/init.test.js +24 -0
  59. package/dist/tests/init.test.js.map +1 -1
  60. package/dist/tests/post-read.test.d.ts +2 -0
  61. package/dist/tests/post-read.test.d.ts.map +1 -0
  62. package/dist/tests/post-read.test.js +69 -0
  63. package/dist/tests/post-read.test.js.map +1 -0
  64. package/dist/tests/quality-populate.test.d.ts +2 -0
  65. package/dist/tests/quality-populate.test.d.ts.map +1 -0
  66. package/dist/tests/quality-populate.test.js +85 -0
  67. package/dist/tests/quality-populate.test.js.map +1 -0
  68. package/dist/tests/reflect.test.d.ts +2 -0
  69. package/dist/tests/reflect.test.d.ts.map +1 -0
  70. package/dist/tests/reflect.test.js +122 -0
  71. package/dist/tests/reflect.test.js.map +1 -0
  72. package/dist/tests/schemas.test.d.ts +2 -0
  73. package/dist/tests/schemas.test.d.ts.map +1 -0
  74. package/dist/tests/schemas.test.js +87 -0
  75. package/dist/tests/schemas.test.js.map +1 -0
  76. package/dist/tests/search-hook.test.js +3 -3
  77. package/dist/tests/search-hook.test.js.map +1 -1
  78. package/dist/tests/upgrade.test.d.ts +2 -0
  79. package/dist/tests/upgrade.test.d.ts.map +1 -0
  80. package/dist/tests/upgrade.test.js +91 -0
  81. package/dist/tests/upgrade.test.js.map +1 -0
  82. package/hooks/lib/mindlore-common.cjs +66 -5
  83. package/hooks/lib/types.d.ts +56 -0
  84. package/hooks/mindlore-cwd-changed.cjs +57 -0
  85. package/hooks/mindlore-dont-repeat.cjs +222 -0
  86. package/hooks/mindlore-fts5-sync.cjs +6 -9
  87. package/hooks/mindlore-index.cjs +3 -3
  88. package/hooks/mindlore-post-read.cjs +97 -0
  89. package/hooks/mindlore-read-guard.cjs +27 -4
  90. package/hooks/mindlore-search.cjs +73 -52
  91. package/hooks/mindlore-session-end.cjs +43 -1
  92. package/hooks/mindlore-session-focus.cjs +14 -0
  93. package/package.json +3 -2
  94. package/plugin.json +24 -1
  95. package/skills/mindlore-decide/SKILL.md +8 -0
  96. package/skills/mindlore-evolve/SKILL.md +81 -0
  97. package/skills/mindlore-explore/SKILL.md +84 -0
  98. package/skills/mindlore-health/SKILL.md +8 -0
  99. package/skills/mindlore-ingest/SKILL.md +19 -4
  100. package/skills/mindlore-log/SKILL.md +30 -13
  101. package/skills/mindlore-query/SKILL.md +8 -0
  102. package/templates/SCHEMA.md +15 -2
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * mindlore-post-read — PostToolUse hook (matcher: "Read")
6
+ *
7
+ * After a file is read, estimate its token count
8
+ * and store in _session-reads.json for the read-guard to reference.
9
+ *
10
+ * Does NOT output anything (pure bookkeeping).
11
+ * PostToolUse stdout goes to debug log only — no inject needed.
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { findMindloreDir } = require('./lib/mindlore-common.cjs');
17
+
18
+ const CODE_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.py', '.rs', '.go', '.java', '.c', '.cpp', '.h', '.css', '.scss', '.sql', '.sh', '.yaml', '.yml', '.json', '.toml', '.xml', '.cjs', '.mjs']);
19
+ const PROSE_EXTS = new Set(['.md', '.txt', '.rst', '.adoc']);
20
+
21
+ function estimateTokens(charCount, ext) {
22
+ const ratio = CODE_EXTS.has(ext) ? 3.5 : PROSE_EXTS.has(ext) ? 4.0 : 3.75;
23
+ return Math.ceil(charCount / ratio);
24
+ }
25
+
26
+ function main() {
27
+ const baseDir = findMindloreDir();
28
+ if (!baseDir) return;
29
+
30
+ let input = '';
31
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
32
+ process.stdin.setEncoding('utf8');
33
+ process.stdin.on('error', () => process.exit(0));
34
+ process.stdin.on('data', chunk => input += chunk);
35
+ process.stdin.on('end', () => {
36
+ clearTimeout(stdinTimeout);
37
+ try {
38
+ const data = JSON.parse(input || '{}');
39
+ const toolInput = data.tool_input || {};
40
+ const toolOutput = data.tool_output || {};
41
+
42
+ const filePath = toolInput.file_path || toolInput.path || '';
43
+ if (!filePath) return process.exit(0);
44
+
45
+ // Skip .mindlore/ internals
46
+ const resolved = path.resolve(filePath);
47
+ if (resolved.startsWith(path.resolve(baseDir))) return process.exit(0);
48
+
49
+ // Get content length from tool output or read file
50
+ let charCount = 0;
51
+ if (toolOutput.content) {
52
+ charCount = typeof toolOutput.content === 'string'
53
+ ? toolOutput.content.length
54
+ : JSON.stringify(toolOutput.content).length;
55
+ } else {
56
+ // Fallback: read file size
57
+ try {
58
+ const stat = fs.statSync(resolved);
59
+ charCount = stat.size;
60
+ } catch { return process.exit(0); }
61
+ }
62
+
63
+ if (charCount === 0) return process.exit(0);
64
+
65
+ const ext = path.extname(filePath).toLowerCase();
66
+ const tokens = estimateTokens(charCount, ext);
67
+
68
+ // Update _session-reads.json with token info
69
+ const diaryDir = path.join(baseDir, 'diary');
70
+ const readsPath = path.join(diaryDir, '_session-reads.json');
71
+ let reads = {};
72
+ if (fs.existsSync(readsPath)) {
73
+ try { reads = JSON.parse(fs.readFileSync(readsPath, 'utf8')); } catch { reads = {}; }
74
+ }
75
+
76
+ const normalizedPath = path.resolve(filePath);
77
+ const key = normalizedPath;
78
+
79
+ if (typeof reads[key] === 'number') {
80
+ // Upgrade from old format (just count) to new format (object)
81
+ reads[key] = { count: reads[key], tokens, chars: charCount };
82
+ } else if (reads[key] && typeof reads[key] === 'object') {
83
+ reads[key].tokens = tokens;
84
+ reads[key].chars = charCount;
85
+ } else {
86
+ reads[key] = { count: 1, tokens, chars: charCount };
87
+ }
88
+
89
+ fs.writeFileSync(readsPath, JSON.stringify(reads, null, 2), 'utf8');
90
+ } catch {
91
+ // Silent fail
92
+ }
93
+ process.exit(0);
94
+ });
95
+ }
96
+
97
+ main();
@@ -4,7 +4,7 @@
4
4
  /**
5
5
  * mindlore-read-guard — PreToolUse hook (if: "Read")
6
6
  *
7
- * OpenWolf repeated-read pattern: detects files read multiple times
7
+ * Repeated-read detection: detects files read multiple times
8
8
  * in the same session and emits a soft warning.
9
9
  * Does NOT block (exit 0) — advisory only.
10
10
  *
@@ -46,8 +46,24 @@ function main() {
46
46
  }
47
47
 
48
48
  const normalizedPath = path.resolve(filePath);
49
- const count = (reads[normalizedPath] || 0) + 1;
50
- reads[normalizedPath] = count;
49
+ const existing = reads[normalizedPath];
50
+
51
+ // Support both old format (number) and new format (object with tokens)
52
+ let count, tokens;
53
+ if (typeof existing === 'number') {
54
+ count = existing + 1;
55
+ tokens = 0;
56
+ reads[normalizedPath] = { count, tokens: 0, chars: 0 };
57
+ } else if (existing && typeof existing === 'object') {
58
+ count = (existing.count || 0) + 1;
59
+ tokens = existing.tokens || 0;
60
+ existing.count = count;
61
+ reads[normalizedPath] = existing;
62
+ } else {
63
+ count = 1;
64
+ tokens = 0;
65
+ reads[normalizedPath] = { count, tokens: 0, chars: 0 };
66
+ }
51
67
 
52
68
  // Write updated reads
53
69
  fs.writeFileSync(readsPath, JSON.stringify(reads, null, 2), 'utf8');
@@ -55,7 +71,14 @@ function main() {
55
71
  // Warn on repeated reads (2nd+ time)
56
72
  if (count > 1) {
57
73
  const basename = path.basename(filePath);
58
- process.stdout.write(`[Mindlore: ${basename} bu session'da ${count}. kez okunuyor. Değişiklik yoksa tekrar okumayı atlayabilirsin.]\n`);
74
+ const tokenInfo = tokens > 0 ? ` (~${tokens} token)` : '';
75
+ const totalWaste = tokens > 0 ? ` Toplam tekrar: ~${tokens * (count - 1)} token.` : '';
76
+ process.stdout.write(JSON.stringify({
77
+ hookSpecificOutput: {
78
+ hookEventName: 'PreToolUse',
79
+ additionalContext: `[Mindlore: ${basename}${tokenInfo} bu session'da ${count}. kez okunuyor.${totalWaste} Değişiklik yoksa tekrar okumayı atlayabilirsin.]`
80
+ }
81
+ }));
59
82
  }
60
83
  }
61
84
 
@@ -10,7 +10,7 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
- const { findMindloreDir, DB_NAME, requireDatabase, extractHeadings, readHookStdin } = require('./lib/mindlore-common.cjs');
13
+ const { getAllDbs, requireDatabase, extractHeadings, readHookStdin } = require('./lib/mindlore-common.cjs');
14
14
 
15
15
  const MAX_RESULTS = 3;
16
16
  const MIN_QUERY_WORDS = 3;
@@ -59,30 +59,20 @@ function extractKeywords(text) {
59
59
  return [...new Set(words)].slice(0, 8);
60
60
  }
61
61
 
62
- function main() {
63
- const userMessage = readHookStdin(['prompt', 'content', 'message', 'query']);
64
- if (!userMessage || userMessage.length < MIN_QUERY_WORDS) return;
65
-
66
- const baseDir = findMindloreDir();
67
- if (!baseDir) return;
68
-
69
- const dbPath = path.join(baseDir, DB_NAME);
70
- if (!fs.existsSync(dbPath)) return;
71
-
72
- const keywords = extractKeywords(userMessage);
73
- if (keywords.length < MIN_QUERY_WORDS) return;
74
-
75
- const Database = requireDatabase();
76
- if (!Database) return;
77
-
62
+ /**
63
+ * Search a single DB and return scored results with their baseDir.
64
+ */
65
+ function searchDb(dbPath, keywords, Database) {
66
+ const baseDir = path.dirname(dbPath);
78
67
  const db = new Database(dbPath, { readonly: true });
68
+ const results = [];
79
69
 
80
70
  try {
81
- // Per-keyword scoring (like old knowledge-search.cjs)
82
- // Count how many keywords match each document
83
71
  const allPaths = db.prepare('SELECT DISTINCT path FROM mindlore_fts').all();
84
- const scores = [];
85
72
  const matchStmt = db.prepare('SELECT rank FROM mindlore_fts WHERE path = ? AND mindlore_fts MATCH ?');
73
+ const metaStmt = db.prepare(
74
+ 'SELECT slug, description, category, title, tags FROM mindlore_fts WHERE path = ?'
75
+ );
86
76
 
87
77
  for (const row of allPaths) {
88
78
  let hits = 0;
@@ -101,50 +91,81 @@ function main() {
101
91
  }
102
92
 
103
93
  if (hits >= MIN_KEYWORD_HITS) {
104
- scores.push({ path: row.path, hits, totalRank });
94
+ const meta = metaStmt.get(row.path) || {};
95
+ results.push({ path: row.path, hits, totalRank, baseDir, meta });
105
96
  }
106
97
  }
98
+ } catch (_err) {
99
+ // FTS5 query error — silently skip
100
+ } finally {
101
+ db.close();
102
+ }
107
103
 
108
- // Sort: most keyword hits first, then best rank
109
- scores.sort((a, b) => b.hits - a.hits || a.totalRank - b.totalRank);
110
- const relevant = scores.slice(0, MAX_RESULTS);
104
+ return results;
105
+ }
111
106
 
112
- if (relevant.length === 0) return;
107
+ function main() {
108
+ const userMessage = readHookStdin(['prompt', 'content', 'message', 'query']);
109
+ if (!userMessage || userMessage.length < MIN_QUERY_WORDS) return;
113
110
 
114
- // Build rich inject output
115
- const metaStmt = db.prepare(
116
- 'SELECT slug, description, category, title, tags FROM mindlore_fts WHERE path = ?'
117
- );
111
+ const dbPaths = getAllDbs();
112
+ if (dbPaths.length === 0) return;
118
113
 
119
- const output = [];
120
- for (const r of relevant) {
121
- const meta = metaStmt.get(r.path) || {};
122
- const relativePath = path.relative(baseDir, r.path).replace(/\\/g, '/');
114
+ const keywords = extractKeywords(userMessage);
115
+ if (keywords.length < MIN_QUERY_WORDS) return;
123
116
 
124
- let headings = [];
125
- if (fs.existsSync(r.path)) {
126
- const content = fs.readFileSync(r.path, 'utf8');
127
- headings = extractHeadings(content, 5);
128
- }
117
+ const Database = requireDatabase();
118
+ if (!Database) return;
129
119
 
130
- const category = meta.category || path.dirname(relativePath).split('/')[0];
131
- const title = meta.title || meta.slug || path.basename(r.path, '.md');
132
- const description = meta.description || '';
120
+ // Layered search: project DB first, global DB second
121
+ // Project results appear first in output (higher priority)
122
+ const allScores = [];
123
+ for (const dbPath of dbPaths) {
124
+ allScores.push(...searchDb(dbPath, keywords, Database));
125
+ }
133
126
 
134
- const headingStr = headings.length > 0 ? `\nBasliklar: ${headings.join(', ')}` : '';
135
- const tagsStr = meta.tags ? `\nTags: ${meta.tags}` : '';
136
- output.push(
137
- `[Mindlore: ${category}/${title}] ${description}\nDosya: ${relativePath}${tagsStr}${headingStr}`
138
- );
127
+ // Sort: most keyword hits first, then best rank
128
+ allScores.sort((a, b) => b.hits - a.hits || a.totalRank - b.totalRank);
129
+
130
+ // Deduplicate by full path (project version wins — appears first in sort)
131
+ const seen = new Set();
132
+ const unique = [];
133
+ for (const r of allScores) {
134
+ const normalized = path.resolve(r.path);
135
+ if (!seen.has(normalized)) {
136
+ seen.add(normalized);
137
+ unique.push(r);
139
138
  }
139
+ }
140
140
 
141
- if (output.length > 0) {
142
- process.stdout.write(output.join('\n\n') + '\n');
141
+ const relevant = unique.slice(0, MAX_RESULTS);
142
+ if (relevant.length === 0) return;
143
+
144
+ // Build rich inject output
145
+ const output = [];
146
+ for (const r of relevant) {
147
+ const meta = r.meta || {};
148
+ const relativePath = path.relative(r.baseDir, r.path).replace(/\\/g, '/');
149
+
150
+ let headings = [];
151
+ if (fs.existsSync(r.path)) {
152
+ const content = fs.readFileSync(r.path, 'utf8');
153
+ headings = extractHeadings(content, 5);
143
154
  }
144
- } catch (_err) {
145
- // FTS5 query error silently skip
146
- } finally {
147
- db.close();
155
+
156
+ const category = meta.category || path.dirname(relativePath).split('/')[0];
157
+ const title = meta.title || meta.slug || path.basename(r.path, '.md');
158
+ const description = meta.description || '';
159
+
160
+ const headingStr = headings.length > 0 ? `\nBasliklar: ${headings.join(', ')}` : '';
161
+ const tagsStr = meta.tags ? `\nTags: ${meta.tags}` : '';
162
+ output.push(
163
+ `[Mindlore: ${category}/${title}] ${description}\nDosya: ${relativePath}${tagsStr}${headingStr}`
164
+ );
165
+ }
166
+
167
+ if (output.length > 0) {
168
+ process.stdout.write(output.join('\n\n') + '\n');
148
169
  }
149
170
  }
150
171
 
@@ -12,7 +12,7 @@
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
14
  const { execSync } = require('child_process');
15
- const { findMindloreDir } = require('./lib/mindlore-common.cjs');
15
+ const { findMindloreDir, globalDir } = require('./lib/mindlore-common.cjs');
16
16
 
17
17
  function formatDate(date) {
18
18
  const y = date.getFullYear();
@@ -129,6 +129,48 @@ function main() {
129
129
  const logEntry = `| ${now.toISOString().slice(0, 10)} | session-end | delta-${dateStr}.md |\n`;
130
130
  fs.appendFileSync(logPath, logEntry, 'utf8');
131
131
  }
132
+
133
+ // Git auto-commit + push for global ~/.mindlore/ only
134
+ syncGlobalRepo();
135
+ }
136
+
137
+ /**
138
+ * Auto-commit and push ~/.mindlore/ if it has a .git directory.
139
+ * Only runs for the global scope — project .mindlore/ is in the project's own git.
140
+ * Push failure is graceful (offline support).
141
+ */
142
+ function syncGlobalRepo() {
143
+ const gDir = globalDir();
144
+ const gitDir = path.join(gDir, '.git');
145
+ if (!fs.existsSync(gitDir)) return;
146
+
147
+ try {
148
+ // Check for changes
149
+ const status = execSync('git status --porcelain', {
150
+ cwd: gDir,
151
+ encoding: 'utf8',
152
+ timeout: 5000,
153
+ }).trim();
154
+
155
+ if (!status) return; // nothing to commit
156
+
157
+ execSync('git add -A', { cwd: gDir, timeout: 5000, stdio: 'pipe' });
158
+ const now = new Date().toISOString().slice(0, 19);
159
+ execSync(`git commit -m "mindlore auto-sync ${now}"`, {
160
+ cwd: gDir,
161
+ timeout: 10000,
162
+ stdio: 'pipe',
163
+ });
164
+
165
+ // Push — graceful fail if no remote or offline
166
+ try {
167
+ execSync('git push', { cwd: gDir, timeout: 15000, stdio: 'pipe' });
168
+ } catch (_pushErr) {
169
+ // Offline or no remote — silently continue
170
+ }
171
+ } catch (_err) {
172
+ // Git not available or commit failed — silently continue
173
+ }
132
174
  }
133
175
 
134
176
  main();
@@ -34,6 +34,20 @@ function main() {
34
34
  output.push(`[Mindlore Delta: ${deltaName}]\n${deltaContent}`);
35
35
  }
36
36
 
37
+ // Version check: compare .version (installed) vs .pkg-version (package)
38
+ // Both are flat strings written by init — no JSON parse needed on session start
39
+ const versionPath = path.join(baseDir, '.version');
40
+ const pkgVersionPath = path.join(baseDir, '.pkg-version');
41
+ try {
42
+ if (fs.existsSync(versionPath) && fs.existsSync(pkgVersionPath)) {
43
+ const installed = fs.readFileSync(versionPath, 'utf8').trim();
44
+ const pkgVersion = fs.readFileSync(pkgVersionPath, 'utf8').trim();
45
+ if (pkgVersion && pkgVersion !== installed) {
46
+ output.push(`[Mindlore: Guncelleme mevcut (${installed} → ${pkgVersion}). \`npx mindlore init\` calistirin.]`);
47
+ }
48
+ }
49
+ } catch (_err) { /* skip */ }
50
+
37
51
  if (output.length > 0) {
38
52
  process.stdout.write(output.join('\n\n') + '\n');
39
53
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindlore",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "AI-native knowledge system for Claude Code",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -42,7 +42,8 @@
42
42
  "node": ">=20.0.0"
43
43
  },
44
44
  "dependencies": {
45
- "better-sqlite3": "^11.0.0"
45
+ "better-sqlite3": "^11.0.0",
46
+ "zod": "^4.3.6"
46
47
  },
47
48
  "devDependencies": {
48
49
  "@types/better-sqlite3": "^7.6.13",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mindlore",
3
3
  "description": "AI-native knowledge system for Claude Code. Persistent, searchable, evolving knowledge base with FTS5.",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "skills": [
6
6
  {
7
7
  "name": "mindlore-ingest",
@@ -27,6 +27,16 @@
27
27
  "name": "mindlore-decide",
28
28
  "path": "skills/mindlore-decide/SKILL.md",
29
29
  "description": "Record and list decisions with context, alternatives, rationale"
30
+ },
31
+ {
32
+ "name": "mindlore-evolve",
33
+ "path": "skills/mindlore-evolve/SKILL.md",
34
+ "description": "Knowledge schema co-evolution — scan domains+sources, suggest updates"
35
+ },
36
+ {
37
+ "name": "mindlore-explore",
38
+ "path": "skills/mindlore-explore/SKILL.md",
39
+ "description": "Discover unexpected connections between knowledge sources"
30
40
  }
31
41
  ],
32
42
  "hooks": [
@@ -66,6 +76,19 @@
66
76
  "event": "PreToolUse",
67
77
  "script": "hooks/mindlore-read-guard.cjs",
68
78
  "if": "Read"
79
+ },
80
+ {
81
+ "event": "CwdChanged",
82
+ "script": "hooks/mindlore-cwd-changed.cjs"
83
+ },
84
+ {
85
+ "event": "PostToolUse",
86
+ "script": "hooks/mindlore-post-read.cjs"
87
+ },
88
+ {
89
+ "event": "PreToolUse",
90
+ "script": "hooks/mindlore-dont-repeat.cjs",
91
+ "if": "Write|Edit"
69
92
  }
70
93
  ]
71
94
  }
@@ -2,6 +2,14 @@
2
2
 
3
3
  Record and list decisions in the `.mindlore/decisions/` directory.
4
4
 
5
+ ## Scope
6
+
7
+ Determine target using `getActiveMindloreDir()` logic:
8
+ - If CWD has `.mindlore/` → write to project scope
9
+ - Otherwise → write to global `~/.mindlore/`
10
+ - List mode: shows decisions from active scope (use `--all` for both)
11
+ - Never hardcode `.mindlore/` path — always resolve dynamically
12
+
5
13
  ## Trigger
6
14
 
7
15
  User says `/mindlore-decide record` or `/mindlore-decide list`.
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: mindlore-evolve
3
+ description: Knowledge schema co-evolution — scan domains+sources, detect inconsistencies, suggest updates
4
+ effort: medium
5
+ allowed-tools: [Read, Write, Edit, Bash, Grep, Glob]
6
+ ---
7
+
8
+ # /mindlore-evolve
9
+
10
+ Knowledge schema co-evolution. Karpathy's 4th operation (ingest/query/health/**evolve**).
11
+
12
+ ## Scope
13
+
14
+ Determine target using `getActiveMindloreDir()` logic:
15
+ - Default (no flag): scan project `.mindlore/`
16
+ - `--global`: scan `~/.mindlore/`
17
+ - Never hardcode `.mindlore/` path — always resolve dynamically
18
+
19
+ ## Trigger
20
+
21
+ User says `/mindlore-evolve`, "knowledge evolve", "bilgi sistemi evrimle", "sema guncelle".
22
+
23
+ ## Modes
24
+
25
+ ### scan (default)
26
+
27
+ Scan all domains and sources for inconsistencies.
28
+
29
+ **Flow:**
30
+ 1. Read INDEX.md to get domain and source file lists
31
+ 2. Read all domain files (from `domains/`)
32
+ 3. Read all source files (from `sources/`)
33
+ 4. Detect issues:
34
+ - **Orphan files:** .md files in content directories not listed in INDEX.md
35
+ - **Missing references:** Source exists but no domain mentions it
36
+ - **Stale domains:** Source updated more recently than referencing domain
37
+ - **Tag inconsistencies:** Tags in frontmatter don't match content
38
+ - **Missing cross-references:** Related sources not linked
39
+ 5. Report findings as a table:
40
+
41
+ ```
42
+ | # | Type | File | Issue | Suggested Fix |
43
+ |---|------|------|-------|---------------|
44
+ | 1 | orphan | sources/old.md | Not in INDEX.md | Add to INDEX or delete |
45
+ | 2 | missing-ref | sources/react-hooks.md | No domain reference | Add to domains/frontend.md |
46
+ ```
47
+
48
+ 6. If `--dry-run`: stop here, no change suggestions
49
+
50
+ ### apply
51
+
52
+ Apply suggested changes with user approval.
53
+
54
+ **Flow:**
55
+ 1. Run scan first (reuse findings)
56
+ 2. For each finding, show proposed change (diff format)
57
+ 3. Wait for user approval before each change
58
+ 4. Apply approved changes
59
+ 5. Update INDEX.md with new entries
60
+ 6. Append EVOLVE entry to log.md
61
+ 7. Max 2 domain updates per run (prevent scope creep)
62
+
63
+ **Rules:**
64
+ - NEVER make automatic changes — always require user approval
65
+ - Show diff preview before applying
66
+ - After changes, run `node dist/scripts/mindlore-fts5-index.js` for FTS5 sync
67
+ - Log every change to log.md with timestamp
68
+
69
+ ## Output Format
70
+
71
+ ```
72
+ Mindlore Evolve — Scan Results
73
+
74
+ Scope: project (.mindlore/)
75
+ Files scanned: 12 sources, 5 domains
76
+ Issues found: 3
77
+
78
+ [table of findings]
79
+
80
+ Run `/mindlore-evolve apply` to fix with approval.
81
+ ```
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: mindlore-explore
3
+ description: Discover unexpected connections between sources — undirected knowledge exploration
4
+ effort: medium
5
+ allowed-tools: [Read, Write, Edit, Bash, Grep, Glob]
6
+ ---
7
+
8
+ # /mindlore-explore
9
+
10
+ Discover unexpected connections between knowledge sources. Undirected exploration — unlike query (directed search).
11
+
12
+ ## Scope
13
+
14
+ Determine target using `getActiveMindloreDir()` logic:
15
+ - Default (no flag): explore project `.mindlore/`
16
+ - `--global`: explore `~/.mindlore/` + project cross-reference (most valuable mode)
17
+ - Never hardcode `.mindlore/` path — always resolve dynamically
18
+
19
+ ## Trigger
20
+
21
+ User says `/mindlore-explore`, "knowledge explore", "baglanti kesfet", "cross-reference bul".
22
+
23
+ ## Flow
24
+
25
+ 1. Read all source and domain files from active scope
26
+ 2. Cross-match by tag + content:
27
+ - Files sharing tags but not referencing each other
28
+ - Sources covering similar topics from different angles
29
+ - Sources that could bridge between domains
30
+ 3. Rank connections by strength:
31
+ - **high**: 3+ shared tags + content overlap
32
+ - **medium**: 2 shared tags or significant content similarity
33
+ - **low**: 1 shared tag + weak content match
34
+ 4. Show findings to user
35
+ 5. On approval, write to `connections/` directory
36
+
37
+ ## Connection File Format
38
+
39
+ Written to `connections/` with frontmatter:
40
+
41
+ ```markdown
42
+ ---
43
+ type: connection
44
+ slug: connection-source-a-source-b
45
+ date_created: 2026-04-12
46
+ sources: [source-a.md, source-b.md]
47
+ domains: [domain-x.md]
48
+ strength: high
49
+ tags: [shared-tag-1, shared-tag-2]
50
+ ---
51
+
52
+ ## Connection
53
+
54
+ [Why these sources are related — LLM explanation]
55
+
56
+ ## Action Suggestion
57
+
58
+ [What could be done — domain update, new analysis, etc.]
59
+ ```
60
+
61
+ ## Rules
62
+
63
+ - Check for duplicate connections before writing (same source pair)
64
+ - Show findings before writing — user approval required
65
+ - Update INDEX.md with new connections entry
66
+ - Append EXPLORE entry to log.md
67
+ - Strength is LLM-assessed based on tag overlap + content similarity
68
+
69
+ ## Output Format
70
+
71
+ ```
72
+ Mindlore Explore — Discovered Connections
73
+
74
+ Scope: project (.mindlore/)
75
+ Sources scanned: 12
76
+ Connections found: 3 (2 new, 1 existing)
77
+
78
+ | # | Sources | Strength | Why |
79
+ |---|---------|----------|-----|
80
+ | 1 | react-hooks.md + agent-orchestration.md | medium | Both discuss state management patterns |
81
+ | 2 | karpathy-kb.md + search-retrieval.md | high | FTS5 + knowledge architecture overlap |
82
+
83
+ Write connections? (y/n)
84
+ ```
@@ -9,6 +9,14 @@ allowed-tools: [Bash, Read]
9
9
 
10
10
  Run the 16-point structural health check on the `.mindlore/` knowledge base.
11
11
 
12
+ ## Scope
13
+
14
+ Determine target using `getActiveMindloreDir()` logic:
15
+ - Default (no flag): check project `.mindlore/`, fall back to global
16
+ - `--global`: check only `~/.mindlore/`
17
+ - `--all`: check both project + global scopes, report each separately
18
+ - Never hardcode `.mindlore/` path — always resolve dynamically
19
+
12
20
  ## Trigger
13
21
 
14
22
  User says "health check", "mindlore health", "bilgi sistemi kontrol", "saglik kontrolu".