mindlore 0.6.7 → 0.6.9

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 (164) hide show
  1. package/dist/scripts/cc-memory-bulk-sync.d.ts.map +1 -1
  2. package/dist/scripts/cc-memory-bulk-sync.js +49 -43
  3. package/dist/scripts/cc-memory-bulk-sync.js.map +1 -1
  4. package/dist/scripts/cc-session-sync.d.ts.map +1 -1
  5. package/dist/scripts/cc-session-sync.js +59 -48
  6. package/dist/scripts/cc-session-sync.js.map +1 -1
  7. package/dist/scripts/fetch-raw.js +5 -4
  8. package/dist/scripts/fetch-raw.js.map +1 -1
  9. package/dist/scripts/init.js +8 -7
  10. package/dist/scripts/init.js.map +1 -1
  11. package/dist/scripts/lib/all-migrations.d.ts.map +1 -1
  12. package/dist/scripts/lib/all-migrations.js +4 -1
  13. package/dist/scripts/lib/all-migrations.js.map +1 -1
  14. package/dist/scripts/lib/consolidation.d.ts +4 -3
  15. package/dist/scripts/lib/consolidation.d.ts.map +1 -1
  16. package/dist/scripts/lib/consolidation.js.map +1 -1
  17. package/dist/scripts/lib/constants.d.ts +3 -7
  18. package/dist/scripts/lib/constants.d.ts.map +1 -1
  19. package/dist/scripts/lib/constants.js +11 -9
  20. package/dist/scripts/lib/constants.js.map +1 -1
  21. package/dist/scripts/lib/db-helpers.d.ts +0 -15
  22. package/dist/scripts/lib/db-helpers.d.ts.map +1 -1
  23. package/dist/scripts/lib/db-helpers.js +1 -51
  24. package/dist/scripts/lib/db-helpers.js.map +1 -1
  25. package/dist/scripts/lib/decay.d.ts.map +1 -1
  26. package/dist/scripts/lib/decay.js.map +1 -1
  27. package/dist/scripts/lib/migrations-v068.d.ts +3 -0
  28. package/dist/scripts/lib/migrations-v068.d.ts.map +1 -0
  29. package/dist/scripts/lib/migrations-v068.js +37 -0
  30. package/dist/scripts/lib/migrations-v068.js.map +1 -0
  31. package/dist/scripts/lib/migrations.d.ts.map +1 -1
  32. package/dist/scripts/lib/migrations.js +0 -15
  33. package/dist/scripts/lib/migrations.js.map +1 -1
  34. package/dist/scripts/lib/secure-io.d.ts +11 -0
  35. package/dist/scripts/lib/secure-io.d.ts.map +1 -0
  36. package/dist/scripts/lib/secure-io.js +26 -0
  37. package/dist/scripts/lib/secure-io.js.map +1 -0
  38. package/dist/scripts/lib/session-payload.d.ts.map +1 -1
  39. package/dist/scripts/lib/session-payload.js.map +1 -1
  40. package/dist/scripts/lib/validate-manifest.d.ts +8 -0
  41. package/dist/scripts/lib/validate-manifest.d.ts.map +1 -0
  42. package/dist/scripts/lib/validate-manifest.js +80 -0
  43. package/dist/scripts/lib/validate-manifest.js.map +1 -0
  44. package/dist/scripts/mindlore-fts5-index.d.ts +1 -2
  45. package/dist/scripts/mindlore-fts5-index.d.ts.map +1 -1
  46. package/dist/scripts/mindlore-fts5-index.js +2 -54
  47. package/dist/scripts/mindlore-fts5-index.js.map +1 -1
  48. package/dist/scripts/mindlore-health-check.d.ts.map +1 -1
  49. package/dist/scripts/mindlore-health-check.js +0 -11
  50. package/dist/scripts/mindlore-health-check.js.map +1 -1
  51. package/dist/scripts/validate-manifest-cli.d.ts +2 -0
  52. package/dist/scripts/validate-manifest-cli.d.ts.map +1 -0
  53. package/dist/scripts/validate-manifest-cli.js +38 -0
  54. package/dist/scripts/validate-manifest-cli.js.map +1 -0
  55. package/dist/tests/cc-memory-bulk-sync.test.js +23 -0
  56. package/dist/tests/cc-memory-bulk-sync.test.js.map +1 -1
  57. package/dist/tests/cc-memory-sync.test.js +3 -3
  58. package/dist/tests/cc-session-sync.test.js +25 -0
  59. package/dist/tests/cc-session-sync.test.js.map +1 -1
  60. package/dist/tests/chunks-migration.test.js +1 -1
  61. package/dist/tests/consolidation.test.js +2 -2
  62. package/dist/tests/consolidation.test.js.map +1 -1
  63. package/dist/tests/episode-file.test.js +2 -1
  64. package/dist/tests/episode-file.test.js.map +1 -1
  65. package/dist/tests/episode-kind-constant.test.d.ts +2 -0
  66. package/dist/tests/episode-kind-constant.test.d.ts.map +1 -0
  67. package/dist/tests/episode-kind-constant.test.js +28 -0
  68. package/dist/tests/episode-kind-constant.test.js.map +1 -0
  69. package/dist/tests/fetch-raw.test.js +1 -2
  70. package/dist/tests/fetch-raw.test.js.map +1 -1
  71. package/dist/tests/fts5.test.js +15 -74
  72. package/dist/tests/fts5.test.js.map +1 -1
  73. package/dist/tests/fuzzy.test.js +1 -1
  74. package/dist/tests/git-snapshot.test.js +3 -5
  75. package/dist/tests/git-snapshot.test.js.map +1 -1
  76. package/dist/tests/globalSetup.d.ts +2 -0
  77. package/dist/tests/globalSetup.d.ts.map +1 -0
  78. package/dist/tests/globalSetup.js +36 -0
  79. package/dist/tests/globalSetup.js.map +1 -0
  80. package/dist/tests/helpers/db.d.ts +13 -6
  81. package/dist/tests/helpers/db.d.ts.map +1 -1
  82. package/dist/tests/helpers/db.js +41 -22
  83. package/dist/tests/helpers/db.js.map +1 -1
  84. package/dist/tests/lesson-graduation.test.js +11 -11
  85. package/dist/tests/lesson-graduation.test.js.map +1 -1
  86. package/dist/tests/manifest-v2.test.d.ts +2 -0
  87. package/dist/tests/manifest-v2.test.d.ts.map +1 -0
  88. package/dist/tests/manifest-v2.test.js +74 -0
  89. package/dist/tests/manifest-v2.test.js.map +1 -0
  90. package/dist/tests/migrations-v063.test.js +6 -6
  91. package/dist/tests/migrations-v068.test.d.ts +2 -0
  92. package/dist/tests/migrations-v068.test.d.ts.map +1 -0
  93. package/dist/tests/migrations-v068.test.js +56 -0
  94. package/dist/tests/migrations-v068.test.js.map +1 -0
  95. package/dist/tests/nomination-counts.test.d.ts +2 -0
  96. package/dist/tests/nomination-counts.test.d.ts.map +1 -0
  97. package/dist/tests/nomination-counts.test.js +51 -0
  98. package/dist/tests/nomination-counts.test.js.map +1 -0
  99. package/dist/tests/pre-compact.test.js +2 -1
  100. package/dist/tests/pre-compact.test.js.map +1 -1
  101. package/dist/tests/recall-telemetry.test.js +4 -3
  102. package/dist/tests/recall-telemetry.test.js.map +1 -1
  103. package/dist/tests/rrf.test.js +3 -3
  104. package/dist/tests/schema-version.test.js +3 -7
  105. package/dist/tests/schema-version.test.js.map +1 -1
  106. package/dist/tests/search-engine.test.js +1 -1
  107. package/dist/tests/search-offload.test.js +1 -1
  108. package/dist/tests/search-offload.test.js.map +1 -1
  109. package/dist/tests/sec-regression.test.js +0 -50
  110. package/dist/tests/sec-regression.test.js.map +1 -1
  111. package/dist/tests/secure-io.test.d.ts +2 -0
  112. package/dist/tests/secure-io.test.d.ts.map +1 -0
  113. package/dist/tests/secure-io.test.js +65 -0
  114. package/dist/tests/secure-io.test.js.map +1 -0
  115. package/dist/tests/session-end-cleanup.test.js +3 -20
  116. package/dist/tests/session-end-cleanup.test.js.map +1 -1
  117. package/dist/tests/session-focus.test.js +7 -30
  118. package/dist/tests/session-focus.test.js.map +1 -1
  119. package/dist/tests/similarity.test.js +1 -1
  120. package/dist/tests/skill-path-resolution.test.js +22 -3
  121. package/dist/tests/skill-path-resolution.test.js.map +1 -1
  122. package/dist/tests/triage.test.js +1 -1
  123. package/hooks/lib/constants.cjs +15 -0
  124. package/hooks/lib/mindlore-common.cjs +18 -85
  125. package/hooks/mindlore-fts5-sync.cjs +27 -17
  126. package/hooks/mindlore-index.cjs +3 -2
  127. package/hooks/mindlore-session-end.cjs +5 -21
  128. package/hooks/mindlore-session-focus.cjs +11 -14
  129. package/package.json +3 -5
  130. package/plugin.json +2 -1
  131. package/skills/mindlore-query/SKILL.md +2 -2
  132. package/templates/config.json +1 -1
  133. package/dist/scripts/lib/daemon.d.ts +0 -16
  134. package/dist/scripts/lib/daemon.d.ts.map +0 -1
  135. package/dist/scripts/lib/daemon.js +0 -133
  136. package/dist/scripts/lib/daemon.js.map +0 -1
  137. package/dist/scripts/lib/embedding.d.ts +0 -5
  138. package/dist/scripts/lib/embedding.d.ts.map +0 -1
  139. package/dist/scripts/lib/embedding.js +0 -44
  140. package/dist/scripts/lib/embedding.js.map +0 -1
  141. package/dist/scripts/mindlore-daemon.d.ts +0 -2
  142. package/dist/scripts/mindlore-daemon.d.ts.map +0 -1
  143. package/dist/scripts/mindlore-daemon.js +0 -117
  144. package/dist/scripts/mindlore-daemon.js.map +0 -1
  145. package/dist/tests/daemon-integration.test.d.ts +0 -2
  146. package/dist/tests/daemon-integration.test.d.ts.map +0 -1
  147. package/dist/tests/daemon-integration.test.js +0 -37
  148. package/dist/tests/daemon-integration.test.js.map +0 -1
  149. package/dist/tests/daemon.test.d.ts +0 -2
  150. package/dist/tests/daemon.test.d.ts.map +0 -1
  151. package/dist/tests/daemon.test.js +0 -187
  152. package/dist/tests/daemon.test.js.map +0 -1
  153. package/dist/tests/embedding-hf-integration.test.d.ts +0 -2
  154. package/dist/tests/embedding-hf-integration.test.d.ts.map +0 -1
  155. package/dist/tests/embedding-hf-integration.test.js +0 -52
  156. package/dist/tests/embedding-hf-integration.test.js.map +0 -1
  157. package/dist/tests/embedding.test.d.ts +0 -6
  158. package/dist/tests/embedding.test.d.ts.map +0 -1
  159. package/dist/tests/embedding.test.js +0 -71
  160. package/dist/tests/embedding.test.js.map +0 -1
  161. package/dist/tests/sqlite-vec-v12.test.d.ts +0 -2
  162. package/dist/tests/sqlite-vec-v12.test.d.ts.map +0 -1
  163. package/dist/tests/sqlite-vec-v12.test.js +0 -72
  164. package/dist/tests/sqlite-vec-v12.test.js.map +0 -1
@@ -0,0 +1,15 @@
1
+ 'use strict';
2
+
3
+ const EPISODE_KINDS = [
4
+ 'session', 'decision', 'event', 'preference',
5
+ 'learning', 'friction', 'discovery', 'nomination',
6
+ 'session-summary'
7
+ ];
8
+
9
+ function isValidKind(kind) {
10
+ return typeof kind === 'string' && EPISODE_KINDS.includes(kind);
11
+ }
12
+
13
+ const DB_BUSY_TIMEOUT_MS = 2000;
14
+
15
+ module.exports = { EPISODE_KINDS, isValidKind, DB_BUSY_TIMEOUT_MS };
@@ -9,6 +9,8 @@ const fs = require('fs');
9
9
  const path = require('path');
10
10
  const crypto = require('crypto');
11
11
  const os = require('os');
12
+ const { EPISODE_KINDS, isValidKind, DB_BUSY_TIMEOUT_MS } = require('./constants.cjs');
13
+ const { safeMkdir, safeWriteFile } = require('../../dist/scripts/lib/secure-io.js');
12
14
 
13
15
  const MINDLORE_DIR = '.mindlore';
14
16
  const DB_NAME = 'mindlore.db';
@@ -245,7 +247,7 @@ function openDatabase(dbPath, opts) {
245
247
  const db = new Database(dbPath, { readonly });
246
248
  if (!readonly) {
247
249
  db.pragma('journal_mode = WAL');
248
- db.pragma('busy_timeout = 5000');
250
+ db.pragma(`busy_timeout = ${DB_BUSY_TIMEOUT_MS}`);
249
251
  }
250
252
  return db;
251
253
  } catch (_err) {
@@ -364,14 +366,9 @@ CREATE TABLE IF NOT EXISTS episodes (
364
366
  created_at TEXT NOT NULL
365
367
  )`;
366
368
 
367
- /**
368
- * Valid episode kinds. CO-EVOLUTION: mirrors EPISODE_KINDS in scripts/lib/episodes.ts
369
- */
370
369
  // ~625 tokens context budget for multi-session inject (~4 chars/token)
371
370
  const MULTI_SESSION_TOKEN_CAP_CHARS = 2500;
372
371
 
373
- const EPISODE_KINDS_CJS = ['session', 'decision', 'event', 'preference', 'learning', 'friction', 'discovery', 'nomination', 'session-summary'];
374
-
375
372
  /**
376
373
  * Valid episode statuses. CO-EVOLUTION: mirrors EPISODE_STATUSES in scripts/lib/episodes.ts
377
374
  */
@@ -672,7 +669,7 @@ function _rotateFile(filePath, maxBytes, keepLines) {
672
669
  if (stat.size > maxBytes) {
673
670
  const lines = fs.readFileSync(filePath, 'utf8').trim().split('\n');
674
671
  const tmpPath = filePath + '.tmp';
675
- fs.writeFileSync(tmpPath, lines.slice(-keepLines).join('\n') + '\n', { mode: 0o600 });
672
+ safeWriteFile(tmpPath, lines.slice(-keepLines).join('\n') + '\n');
676
673
  fs.renameSync(tmpPath, filePath);
677
674
  }
678
675
  } catch { /* file may not exist yet */ }
@@ -683,7 +680,7 @@ let _telDirEnsured = false;
683
680
  function _writeTelemetry({ hookName, duration_ms, ok, extra }) {
684
681
  try {
685
682
  if (!_telDirEnsured) {
686
- fs.mkdirSync(GLOBAL_MINDLORE_DIR, { recursive: true, mode: 0o700 });
683
+ safeMkdir(GLOBAL_MINDLORE_DIR);
687
684
  _telDirEnsured = true;
688
685
  }
689
686
  const telPath = path.join(GLOBAL_MINDLORE_DIR, 'telemetry.jsonl');
@@ -733,7 +730,7 @@ function withTelemetrySync(hookName, fn) {
733
730
  return result;
734
731
  }
735
732
 
736
- function withTimeoutDb(db, sql, params = [], { timeoutMs = 3000, mode = 'all' } = {}) {
733
+ function withTimeoutDb(db, sql, params = [], { timeoutMs = DB_BUSY_TIMEOUT_MS, mode = 'all' } = {}) {
737
734
  if (!db) return mode === 'get' ? undefined : [];
738
735
  try {
739
736
  db.pragma(`busy_timeout = ${timeoutMs}`);
@@ -749,18 +746,17 @@ function withTimeoutDb(db, sql, params = [], { timeoutMs = 3000, mode = 'all' }
749
746
  }
750
747
  }
751
748
 
752
- function checkReflectTrigger(db, project, threshold) {
753
- if (threshold === undefined) threshold = 5;
749
+ function getNominationCounts(db, project) {
754
750
  try {
755
751
  const row = withTimeoutDb(db,
756
- "SELECT COUNT(*) as cnt FROM episodes WHERE kind = 'nomination' AND status = 'staged' AND project = ?",
752
+ `SELECT
753
+ SUM(CASE WHEN status='staged' THEN 1 ELSE 0 END) AS staged,
754
+ SUM(CASE WHEN status='approved' AND graduated_at IS NOT NULL THEN 1 ELSE 0 END) AS graduated
755
+ FROM episodes
756
+ WHERE kind='nomination' AND project=?`,
757
757
  [project], { mode: 'get' });
758
- const cnt = row?.cnt ?? 0;
759
- if (cnt >= threshold) {
760
- return `[Mindlore] ${cnt} bekleyen nomination var — \`/mindlore-reflect\` çalıştır`;
761
- }
762
- } catch (_err) { /* episodes table may not exist */ }
763
- return null;
758
+ return { staged: row?.staged ?? 0, graduated: row?.graduated ?? 0 };
759
+ } catch (_err) { return { staged: 0, graduated: 0 }; }
764
760
  }
765
761
 
766
762
  function cleanupExpiredInjectLog(db, ttlMs) {
@@ -773,16 +769,6 @@ function cleanupExpiredInjectLog(db, ttlMs) {
773
769
  return 0;
774
770
  }
775
771
 
776
- function getGraduatedLessonCount(db, project) {
777
- try {
778
- const row = withTimeoutDb(db,
779
- "SELECT COUNT(*) as cnt FROM episodes WHERE kind = 'nomination' AND status = 'approved' AND graduated_at IS NOT NULL AND project = ?",
780
- [project], { mode: 'get' });
781
- return row?.cnt ?? 0;
782
- } catch (_err) { /* columns may not exist yet */ }
783
- return 0;
784
- }
785
-
786
772
  module.exports = {
787
773
  MINDLORE_DIR,
788
774
  GLOBAL_MINDLORE_DIR,
@@ -816,7 +802,9 @@ module.exports = {
816
802
  resolveProject,
817
803
  DEFAULT_MODELS,
818
804
  // Episodes (v0.4.1)
819
- EPISODE_KINDS_CJS,
805
+ EPISODE_KINDS,
806
+ EPISODE_KINDS_CJS: EPISODE_KINDS,
807
+ isValidKind,
820
808
  EPISODE_STATUSES_CJS,
821
809
  SQL_EPISODES_CREATE,
822
810
  SQL_EPISODES_INDEXES,
@@ -833,9 +821,6 @@ module.exports = {
833
821
  STOP_WORDS,
834
822
  extractKeywords,
835
823
  sanitizeKeyword,
836
- // Hybrid search helpers (v0.5.0)
837
- loadSqliteVecCjs,
838
- hasVecTableCjs,
839
824
  // Hook logging (v0.5.1)
840
825
  hookLog,
841
826
  getRecentHookErrors,
@@ -846,10 +831,6 @@ module.exports = {
846
831
  extractSkeleton,
847
832
  // Recall telemetry (v0.5.3)
848
833
  incrementRecallCount,
849
- // Daemon helpers (v0.5.5)
850
- isDaemonRunning,
851
- getDaemonPortFile,
852
- getDaemonPidFile,
853
834
  _rotateFile,
854
835
  isInsideMindloreDir,
855
836
  extractMindloreBaseDir,
@@ -867,8 +848,7 @@ module.exports = {
867
848
  isCorruptionError,
868
849
  recoverCorruptDb,
869
850
  // Lesson graduation (v0.6.7)
870
- checkReflectTrigger,
871
- getGraduatedLessonCount,
851
+ getNominationCounts,
872
852
  cleanupExpiredInjectLog,
873
853
  };
874
854
 
@@ -908,53 +888,6 @@ function getUnpromotedRawFiles(baseDir) {
908
888
  return fs.readdirSync(rawDir).filter(f => f.endsWith('.md') && !sourceNames.has(f));
909
889
  }
910
890
 
911
- function isDaemonRunning(pidFile) {
912
- try {
913
- const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim());
914
- process.kill(pid, 0);
915
- return { running: true, pid };
916
- } catch {
917
- try { fs.unlinkSync(pidFile); } catch { /* stale file already gone */ }
918
- return { running: false };
919
- }
920
- }
921
-
922
- function getDaemonPortFile() {
923
- return path.join(globalDir(), 'mindlore-daemon.port');
924
- }
925
-
926
- function getDaemonPidFile() {
927
- return path.join(globalDir(), 'mindlore-daemon.pid');
928
- }
929
-
930
- /**
931
- * Try to load sqlite-vec extension. Returns true if successful.
932
- * @param {import('better-sqlite3').Database} db
933
- * @returns {boolean}
934
- */
935
- function loadSqliteVecCjs(db) {
936
- try {
937
- const sqliteVec = require('sqlite-vec');
938
- sqliteVec.load(db);
939
- return true;
940
- } catch (_err) {
941
- return false;
942
- }
943
- }
944
-
945
- /**
946
- * Check if documents_vec table exists.
947
- * @param {import('better-sqlite3').Database} db
948
- * @returns {boolean}
949
- */
950
- function hasVecTableCjs(db) {
951
- try {
952
- db.prepare('SELECT slug FROM documents_vec LIMIT 0').run();
953
- return true;
954
- } catch (_err) {
955
- return false;
956
- }
957
- }
958
891
 
959
892
  /**
960
893
  * Increment recall_count and update last_recalled_at for a file in file_hashes.
@@ -37,8 +37,11 @@ function main() {
37
37
 
38
38
  const mdFiles = getAllMdFiles(baseDir);
39
39
 
40
+ const allHashes = new Map();
41
+ for (const row of db.prepare('SELECT path, content_hash FROM file_hashes').all()) {
42
+ allHashes.set(row.path, row.content_hash);
43
+ }
40
44
 
41
- const getHash = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?');
42
45
  const deleteFts = db.prepare('DELETE FROM mindlore_fts WHERE path = ?');
43
46
  const deleteFtsSessions = db.prepare('DELETE FROM mindlore_fts_sessions WHERE path = ?');
44
47
  const insertFtsSessions = db.prepare(SQL_FTS_SESSIONS_INSERT);
@@ -54,25 +57,32 @@ function main() {
54
57
 
55
58
  try {
56
59
  const project = getProjectName();
60
+
61
+ const changedFiles = [];
62
+ for (const file of mdFiles) {
63
+ const content = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
64
+ const hash = sha256(content);
65
+
66
+ const existingHash = allHashes.get(file);
67
+ if (existingHash === hash) continue;
68
+
69
+ const { meta, body } = parseFrontmatter(content);
70
+ const { slug, description, type, category, title, tags, quality, dateCaptured, project: ftsProject } = extractFtsMetadata(meta, body, file, baseDir);
71
+ const resolvedProject = resolveProject(ftsProject, file, project);
72
+ changedFiles.push({ file, hash, slug, description, type, category, title, tags, quality, dateCaptured, resolvedProject, body });
73
+ }
74
+
75
+ // No file I/O inside — minimize lock hold time
57
76
  const transaction = db.transaction(() => {
58
- for (const file of mdFiles) {
59
- const content = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
60
- const hash = sha256(content);
61
-
62
- const existing = getHash.get(file);
63
- if (existing && existing.content_hash === hash) continue;
64
-
65
- const { meta, body } = parseFrontmatter(content);
66
- const { slug, description, type, category, title, tags, quality, dateCaptured, project: ftsProject } = extractFtsMetadata(meta, body, file, baseDir);
67
- const resolvedProject = resolveProject(ftsProject, file, project);
68
- deleteFts.run(file);
69
- deleteFtsSessions.run(file);
70
- if (isSessionCategory(category)) {
71
- insertFtsSessions.run(file, slug, description, type, category, title, body, tags, quality ?? null, dateCaptured ?? null, resolvedProject);
77
+ for (const item of changedFiles) {
78
+ deleteFts.run(item.file);
79
+ deleteFtsSessions.run(item.file);
80
+ if (isSessionCategory(item.category)) {
81
+ insertFtsSessions.run(item.file, item.slug, item.description, item.type, item.category, item.title, item.body, item.tags, item.quality ?? null, item.dateCaptured ?? null, item.resolvedProject);
72
82
  } else {
73
- insertFtsRow(db, { path: file, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project: resolvedProject });
83
+ insertFtsRow(db, { path: item.file, slug: item.slug, description: item.description, type: item.type, category: item.category, title: item.title, content: item.body, tags: item.tags, quality: item.quality, dateCaptured: item.dateCaptured, project: item.resolvedProject });
74
84
  }
75
- upsertHash.run(file, hash, now);
85
+ upsertHash.run(item.file, item.hash, now);
76
86
  }
77
87
  });
78
88
  transaction();
@@ -10,6 +10,7 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
+ const { safeMkdir, safeWriteFile } = require('../dist/scripts/lib/secure-io.js');
13
14
  const { DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getProjectName, resolveProject, globalDir, hookLog, withTelemetry, isInsideMindloreDir, extractMindloreBaseDir } = require('./lib/mindlore-common.cjs');
14
15
 
15
16
  function invalidateSearchCache(db) {
@@ -164,9 +165,9 @@ function indexCcMemory(filePath) {
164
165
 
165
166
  // Copy to ~/.mindlore/memory/{project}/ for git-sync + obsidian
166
167
  const memoryDir = path.join(globalBase, 'memory', projectScope || '_global');
167
- fs.mkdirSync(memoryDir, { recursive: true, mode: 0o700 });
168
+ safeMkdir(memoryDir);
168
169
  const destPath = path.join(memoryDir, path.basename(filePath));
169
- fs.writeFileSync(destPath, cleaned, { encoding: 'utf8', mode: 0o600 });
170
+ safeWriteFile(destPath, cleaned);
170
171
  } finally {
171
172
  db.close();
172
173
  }
@@ -13,6 +13,7 @@ const fs = require('fs');
13
13
  const path = require('path');
14
14
  const os = require('os');
15
15
  const { execFileSync, spawn } = require('child_process');
16
+ const { safeWriteFile, safeWriteJson } = require('../dist/scripts/lib/secure-io.js');
16
17
  const { findMindloreDir, globalDir, getProjectName, openDatabase, ensureEpisodesTable, hasEpisodesTable, insertBareEpisode, insertFtsRow, hookLog, SHARED_EXPORT_DIRS, resolveWin32Bin, withTelemetry, getUnpromotedRawFiles, cleanupExpiredInjectLog } = require('./lib/mindlore-common.cjs');
17
18
 
18
19
  const EXPORT_DIRS = SHARED_EXPORT_DIRS;
@@ -65,23 +66,6 @@ if (process.argv.includes('--worker')) {
65
66
  await safeRunAsync(() => runSyncScript('cc-memory-bulk-sync.js', ['--auto'], 10000, 'CC memory sync'), 'cc-memory-sync');
66
67
  await safeRunAsync(() => runSyncScript('cc-session-sync.js', [], 30000, 'CC session sync'), 'cc-session-sync');
67
68
 
68
- // Embed trigger — detached, fire-and-forget
69
- await safeRunAsync(async () => {
70
- const indexScript = path.join(__dirname, '..', 'dist', 'scripts', 'mindlore-fts5-index.js');
71
- if (fs.existsSync(indexScript)) {
72
- const nodeExe = resolveWin32Bin('node') || process.execPath;
73
- const { spawn: spawnChild } = require('child_process');
74
- const embedProc = spawnChild(nodeExe, [indexScript, '--embed'], {
75
- detached: true,
76
- stdio: 'ignore',
77
- windowsHide: true,
78
- env: { ...process.env, MINDLORE_HOME: baseDir },
79
- });
80
- embedProc.unref();
81
- hookLog('session-end', 'info', 'embed subprocess spawned, pid=' + embedProc.pid);
82
- }
83
- }, 'embed-trigger');
84
-
85
69
  // Raw accumulation warning (moved from main to worker — off hot path)
86
70
  await safeRunAsync(() => {
87
71
  const unpromoted = getUnpromotedRawFiles(baseDir);
@@ -220,7 +204,7 @@ function main() {
220
204
 
221
205
  sections.push('');
222
206
 
223
- fs.writeFileSync(deltaPath, sections.join('\n'), { encoding: 'utf8', mode: 0o600 });
207
+ safeWriteFile(deltaPath, sections.join('\n'));
224
208
 
225
209
  // Append to log.md
226
210
  const logPath = path.join(baseDir, 'log.md');
@@ -235,7 +219,7 @@ function main() {
235
219
  try {
236
220
  const workerData = JSON.stringify({ baseDir, project, commits, changedFiles, reads });
237
221
  const tmpFile = path.join(os.tmpdir(), `mindlore-worker-${Date.now()}.json`);
238
- fs.writeFileSync(tmpFile, workerData, { encoding: 'utf8', mode: 0o600 });
222
+ safeWriteFile(tmpFile, workerData);
239
223
  // Use system node instead of process.execPath — CC's embedded Node
240
224
  // may not work as a standalone binary for detached worker processes.
241
225
  // Resolve full path to avoid shell:true deprecation warning on Windows.
@@ -380,7 +364,7 @@ function writeEpisodeFile(baseDir, project, commits, changedFiles, reads) {
380
364
  lines.push('');
381
365
  }
382
366
 
383
- fs.writeFileSync(filePath, lines.join('\n'), { encoding: 'utf8', mode: 0o600 });
367
+ safeWriteFile(filePath, lines.join('\n'));
384
368
  }
385
369
 
386
370
  let _obsidianHelpersCache = undefined; // undefined = not yet attempted
@@ -477,7 +461,7 @@ function syncObsidian(baseDir) {
477
461
  if (exported > 0) {
478
462
  config.obsidian.lastExport = new Date().toISOString();
479
463
  config.obsidian.lastExportCount = exported;
480
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2), { encoding: 'utf8', mode: 0o600 });
464
+ safeWriteJson(configPath, config);
481
465
  }
482
466
  } catch (err) {
483
467
  hookLog('session-end', 'error', `obsidian internal: ${err?.message ?? err}`);
@@ -10,7 +10,7 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
- const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, querySupersededChains, formatSupersededChains, hookLog, getProjectName, parseFrontmatter, withTelemetry, withTimeoutDb, listSnapshots, isCorruptionError, recoverCorruptDb, checkReflectTrigger, getGraduatedLessonCount } = require('./lib/mindlore-common.cjs');
13
+ const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, querySupersededChains, formatSupersededChains, hookLog, getProjectName, parseFrontmatter, withTelemetry, withTimeoutDb, listSnapshots, isCorruptionError, recoverCorruptDb, getNominationCounts } = require('./lib/mindlore-common.cjs');
14
14
 
15
15
  function truncateSection(content, sectionRegex, keepCount, label) {
16
16
  const match = content.match(sectionRegex);
@@ -98,19 +98,16 @@ function loadDbContent({ db, baseDir, config, output, timings, latestDeltaConten
98
98
  }
99
99
  timings.db_stale = Date.now() - tStale;
100
100
 
101
- // Auto reflect trigger (Q1)
101
+ // Auto reflect trigger (Q1) + Graduated lesson count (Q3)
102
102
  try {
103
- const reflectMsg = checkReflectTrigger(db, project, config?.graduation?.reflectThreshold);
104
- if (reflectMsg) output.push(reflectMsg);
105
- } catch (_reflectErr) { /* graduation not available */ }
106
-
107
- // Graduated lesson count (Q3 — content lives in CLAUDE.md, only show count)
108
- try {
109
- const gradCount = getGraduatedLessonCount(db, project);
110
- if (gradCount > 0) {
111
- output.push(`[Mindlore] ${gradCount} graduated lesson aktif (detay: CLAUDE.md veya /mindlore-reflect)`);
103
+ const counts = getNominationCounts(db, project);
104
+ if (counts.staged >= (config?.graduation?.reflectThreshold ?? 5)) {
105
+ output.push(`[Mindlore] ${counts.staged} bekleyen nomination var \`/mindlore-reflect\` çalıştır`);
112
106
  }
113
- } catch (_lessonErr) { /* graduation not available */ }
107
+ if (counts.graduated > 0) {
108
+ output.push(`[Mindlore Graduation] ${counts.graduated} lesson mezun oldu`);
109
+ }
110
+ } catch (_reflectErr) { /* graduation not available */ }
114
111
  }
115
112
 
116
113
  function main() {
@@ -134,8 +131,8 @@ function main() {
134
131
  const tIndex = Date.now();
135
132
  const indexPath = path.join(baseDir, 'INDEX.md');
136
133
  if (fs.existsSync(indexPath)) {
137
- sourceChars += fs.statSync(indexPath).size;
138
134
  const content = fs.readFileSync(indexPath, 'utf8').trim();
135
+ sourceChars += content.length;
139
136
  output.push(`[Mindlore INDEX]\n${content}`);
140
137
  }
141
138
  timings.index_read = Date.now() - tIndex;
@@ -151,8 +148,8 @@ function main() {
151
148
  if (diaryFiles.length > 0) {
152
149
  const latestName = diaryFiles[diaryFiles.length - 1];
153
150
  const latestPath = path.join(diaryDir, latestName);
154
- sourceChars += fs.statSync(latestPath).size;
155
151
  const deltaContent = fs.readFileSync(latestPath, 'utf8').trim();
152
+ sourceChars += deltaContent.length;
156
153
  latestDeltaContent = deltaContent;
157
154
  const { meta } = parseFrontmatter(deltaContent);
158
155
  const deltaProject = meta.project || null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindlore",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "description": "AI-native knowledge system for Claude Code",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -9,7 +9,6 @@
9
9
  "scripts": {
10
10
  "build": "tsc",
11
11
  "prebuild": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
12
- "pretest": "npm run build",
13
12
  "test": "jest --config jest.config.cjs",
14
13
  "lint": "eslint -c eslint.config.cjs scripts/ hooks/ tests/",
15
14
  "typecheck": "tsc --noEmit",
@@ -23,7 +22,8 @@
23
22
  "cleanup": "node dist/scripts/maintain-cleanup.js",
24
23
  "perf": "node dist/scripts/mindlore-perf.js",
25
24
  "doctor": "node dist/scripts/mindlore-doctor.js",
26
- "audit": "npm audit --audit-level=moderate"
25
+ "audit": "npm audit --audit-level=moderate",
26
+ "validate-manifest": "node dist/scripts/validate-manifest-cli.js"
27
27
  },
28
28
  "keywords": [
29
29
  "claude-code",
@@ -53,7 +53,6 @@
53
53
  "zod": "^4.3.6"
54
54
  },
55
55
  "devDependencies": {
56
- "@huggingface/transformers": "^4.2.0",
57
56
  "@types/better-sqlite3": "^7.6.13",
58
57
  "@types/jest": "^30.0.0",
59
58
  "@types/node": "^25.6.0",
@@ -62,7 +61,6 @@
62
61
  "eslint": "^10.2.0",
63
62
  "globals": "^17.5.0",
64
63
  "jest": "^30.3.0",
65
- "sqlite-vec": "^0.1.9",
66
64
  "ts-jest": "^29.4.9",
67
65
  "typescript": "^6.0.2"
68
66
  },
package/plugin.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
+ "manifestVersion": 2,
2
3
  "name": "mindlore",
3
4
  "description": "AI-native knowledge system for Claude Code. Persistent, searchable, evolving knowledge base with FTS5.",
4
- "version": "0.6.7",
5
+ "version": "0.6.9",
5
6
  "skills": [
6
7
  {
7
8
  "name": "mindlore-ingest",
@@ -59,7 +59,7 @@ Compounding query pipeline — knowledge grows with each answer.
59
59
  **Flow:**
60
60
  1. Parse user question
61
61
  2. FTS5 search → find relevant files (sources + domains + analyses + insights — previous answers INCLUDED)
62
- 3. Read top 3-5 relevant files using ctx_execute_file if context-mode available, else Read
62
+ 3. Read top 3-5 relevant files
63
63
  4. Synthesize answer from found knowledge
64
64
  5. Cite sources: `[kaynak: sources/x.md]` format
65
65
  6. Ask user: "Bu cevabı kaydetmemi ister misin?"
@@ -146,6 +146,6 @@ SSH hardening, firewall rules, audit checks. 5 sources, 2 analyses linked.
146
146
  - search and brief are read-only — no writes
147
147
  - ask writes only with user approval
148
148
  - stats is read-only
149
- - Token strategy: prefer ctx_execute_file (if context-mode installed), fallback to Read
149
+ - Token strategy: Read relevant files directly
150
150
  - Tags filter: `--tags security` works in search and ask modes
151
151
  - Max results: search=5, ask=3-5 (for synthesis), brief=1
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.6.7",
2
+ "version": "0.6.9",
3
3
  "models": {
4
4
  "ingest": "haiku",
5
5
  "evolve": "sonnet",
@@ -1,16 +0,0 @@
1
- interface DaemonOptions {
2
- pidFile?: string;
3
- portFile?: string;
4
- skipModelLoad?: boolean;
5
- mockEmbedding?: number[];
6
- }
7
- interface DaemonServer {
8
- start: () => Promise<void>;
9
- stop: () => Promise<void>;
10
- isRunning: () => boolean;
11
- getPort: () => number | null;
12
- }
13
- /** @deprecated Will be replaced by MCP Server in v0.7 */
14
- export declare function createDaemonServer(options: DaemonOptions): DaemonServer;
15
- export {};
16
- //# sourceMappingURL=daemon.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["../../../scripts/lib/daemon.ts"],"names":[],"mappings":"AAIA,UAAU,aAAa;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAaD,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,SAAS,EAAE,MAAM,OAAO,CAAC;IACzB,OAAO,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED,yDAAyD;AACzD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,YAAY,CAyHvE"}
@@ -1,133 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.createDaemonServer = createDaemonServer;
7
- const net_1 = __importDefault(require("net"));
8
- const fs_1 = __importDefault(require("fs"));
9
- const constants_js_1 = require("./constants.js");
10
- /** @deprecated Will be replaced by MCP Server in v0.7 */
11
- function createDaemonServer(options) {
12
- let server = null;
13
- let port = null;
14
- let embedFn = null;
15
- async function loadModel() {
16
- if (options.skipModelLoad) {
17
- embedFn = async () => options.mockEmbedding ?? new Array(384).fill(0);
18
- return;
19
- }
20
- const { generateEmbedding } = await import('./embedding.js');
21
- embedFn = generateEmbedding;
22
- }
23
- async function handleRequest(req) {
24
- switch (req.type) {
25
- case 'ping':
26
- return { type: 'pong' };
27
- case 'embed': {
28
- if (!embedFn)
29
- return { type: 'error', error: 'Model not loaded' };
30
- if (!req.text)
31
- return { type: 'error', error: 'No text provided' };
32
- try {
33
- const embedding = await embedFn(req.text);
34
- return { type: 'embedding', embedding };
35
- }
36
- catch (e) {
37
- const msg = e instanceof Error ? e.message : String(e);
38
- return { type: 'error', error: msg };
39
- }
40
- }
41
- case 'stop':
42
- return { type: 'stopped' };
43
- default:
44
- return { type: 'error', error: 'Unknown request type' };
45
- }
46
- }
47
- const instance = {
48
- async start() {
49
- await loadModel();
50
- return new Promise((resolve) => {
51
- server = net_1.default.createServer((conn) => {
52
- conn.setTimeout(5000);
53
- conn.on('timeout', () => conn.destroy());
54
- let buffer = '';
55
- const MAX_BUFFER = 1024 * 1024;
56
- conn.on('data', async (data) => {
57
- buffer += data.toString();
58
- if (buffer.length > MAX_BUFFER) {
59
- conn.write(JSON.stringify({ type: 'error', error: 'Buffer overflow' }) + '\n');
60
- conn.destroy();
61
- return;
62
- }
63
- const lines = buffer.split('\n');
64
- buffer = lines.pop() ?? '';
65
- for (const line of lines) {
66
- if (!line.trim())
67
- continue;
68
- try {
69
- const req = JSON.parse(line);
70
- const res = await handleRequest(req);
71
- conn.write(JSON.stringify(res) + '\n');
72
- if (req.type === 'stop') {
73
- setTimeout(() => { void instance.stop(); }, 100);
74
- }
75
- }
76
- catch {
77
- conn.write(JSON.stringify({ type: 'error', error: 'Invalid JSON' }) + '\n');
78
- }
79
- }
80
- });
81
- });
82
- server.maxConnections = 10;
83
- server.listen(0, constants_js_1.DAEMON_HOST, () => {
84
- const addr = server?.address();
85
- if (addr && typeof addr === 'object') {
86
- port = addr.port;
87
- }
88
- if (options.portFile) {
89
- fs_1.default.writeFileSync(options.portFile, String(port), 'utf8');
90
- }
91
- if (options.pidFile) {
92
- fs_1.default.writeFileSync(options.pidFile, String(process.pid), 'utf8');
93
- }
94
- resolve();
95
- });
96
- });
97
- },
98
- async stop() {
99
- return new Promise((resolve) => {
100
- if (options.pidFile) {
101
- try {
102
- fs_1.default.unlinkSync(options.pidFile);
103
- }
104
- catch { /* already gone */ }
105
- }
106
- if (options.portFile) {
107
- try {
108
- fs_1.default.unlinkSync(options.portFile);
109
- }
110
- catch { /* already gone */ }
111
- }
112
- if (server) {
113
- server.close(() => {
114
- server = null;
115
- port = null;
116
- resolve();
117
- });
118
- }
119
- else {
120
- resolve();
121
- }
122
- });
123
- },
124
- isRunning() {
125
- return server !== null && server.listening;
126
- },
127
- getPort() {
128
- return port;
129
- },
130
- };
131
- return instance;
132
- }
133
- //# sourceMappingURL=daemon.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"daemon.js","sourceRoot":"","sources":["../../../scripts/lib/daemon.ts"],"names":[],"mappings":"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"}