mindlore 0.4.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/scripts/init.js +45 -3
- package/dist/scripts/init.js.map +1 -1
- package/dist/scripts/lib/constants.d.ts +15 -0
- package/dist/scripts/lib/constants.d.ts.map +1 -1
- package/dist/scripts/lib/constants.js +16 -1
- package/dist/scripts/lib/constants.js.map +1 -1
- package/dist/scripts/lib/db-helpers.d.ts +15 -0
- package/dist/scripts/lib/db-helpers.d.ts.map +1 -1
- package/dist/scripts/lib/db-helpers.js +51 -0
- package/dist/scripts/lib/db-helpers.js.map +1 -1
- package/dist/scripts/lib/embedding.d.ts +5 -0
- package/dist/scripts/lib/embedding.d.ts.map +1 -0
- package/dist/scripts/lib/embedding.js +44 -0
- package/dist/scripts/lib/embedding.js.map +1 -0
- package/dist/scripts/lib/hybrid-search.d.ts +62 -0
- package/dist/scripts/lib/hybrid-search.d.ts.map +1 -0
- package/dist/scripts/lib/hybrid-search.js +150 -0
- package/dist/scripts/lib/hybrid-search.js.map +1 -0
- package/dist/scripts/lib/migrations-v051.d.ts +3 -0
- package/dist/scripts/lib/migrations-v051.d.ts.map +1 -0
- package/dist/scripts/lib/migrations-v051.js +24 -0
- package/dist/scripts/lib/migrations-v051.js.map +1 -0
- package/dist/scripts/lib/migrations.d.ts +4 -0
- package/dist/scripts/lib/migrations.d.ts.map +1 -0
- package/dist/scripts/lib/migrations.js +40 -0
- package/dist/scripts/lib/migrations.js.map +1 -0
- package/dist/scripts/lib/privacy-filter.d.ts +3 -0
- package/dist/scripts/lib/privacy-filter.d.ts.map +1 -0
- package/dist/scripts/lib/privacy-filter.js +28 -0
- package/dist/scripts/lib/privacy-filter.js.map +1 -0
- package/dist/scripts/lib/schema-version.d.ts +13 -0
- package/dist/scripts/lib/schema-version.d.ts.map +1 -0
- package/dist/scripts/lib/schema-version.js +37 -0
- package/dist/scripts/lib/schema-version.js.map +1 -0
- package/dist/scripts/lib/similarity.d.ts +12 -0
- package/dist/scripts/lib/similarity.d.ts.map +1 -0
- package/dist/scripts/lib/similarity.js +64 -0
- package/dist/scripts/lib/similarity.js.map +1 -0
- package/dist/scripts/lib/synonym.d.ts +4 -0
- package/dist/scripts/lib/synonym.d.ts.map +1 -0
- package/dist/scripts/lib/synonym.js +37 -0
- package/dist/scripts/lib/synonym.js.map +1 -0
- package/dist/scripts/mindlore-fts5-index.d.ts +2 -1
- package/dist/scripts/mindlore-fts5-index.d.ts.map +1 -1
- package/dist/scripts/mindlore-fts5-index.js +71 -5
- package/dist/scripts/mindlore-fts5-index.js.map +1 -1
- package/dist/scripts/mindlore-fts5-search.d.ts +3 -2
- package/dist/scripts/mindlore-fts5-search.d.ts.map +1 -1
- package/dist/scripts/mindlore-fts5-search.js +89 -35
- package/dist/scripts/mindlore-fts5-search.js.map +1 -1
- package/dist/scripts/mindlore-health-check.js +105 -0
- package/dist/scripts/mindlore-health-check.js.map +1 -1
- package/dist/scripts/quality-populate.js +8 -4
- package/dist/scripts/quality-populate.js.map +1 -1
- package/dist/tests/cc-memory-sync.test.d.ts +2 -0
- package/dist/tests/cc-memory-sync.test.d.ts.map +1 -0
- package/dist/tests/cc-memory-sync.test.js +121 -0
- package/dist/tests/cc-memory-sync.test.js.map +1 -0
- package/dist/tests/embedding.test.d.ts +6 -0
- package/dist/tests/embedding.test.d.ts.map +1 -0
- package/dist/tests/embedding.test.js +71 -0
- package/dist/tests/embedding.test.js.map +1 -0
- package/dist/tests/episode-file.test.d.ts +2 -0
- package/dist/tests/episode-file.test.d.ts.map +1 -0
- package/dist/tests/episode-file.test.js +79 -0
- package/dist/tests/episode-file.test.js.map +1 -0
- package/dist/tests/fts5.test.js +82 -0
- package/dist/tests/fts5.test.js.map +1 -1
- package/dist/tests/helpers/db.d.ts +6 -0
- package/dist/tests/helpers/db.d.ts.map +1 -1
- package/dist/tests/helpers/db.js +29 -0
- package/dist/tests/helpers/db.js.map +1 -1
- package/dist/tests/hook-logging.test.d.ts +2 -0
- package/dist/tests/hook-logging.test.d.ts.map +1 -0
- package/dist/tests/hook-logging.test.js +108 -0
- package/dist/tests/hook-logging.test.js.map +1 -0
- package/dist/tests/hybrid-search.test.d.ts +2 -0
- package/dist/tests/hybrid-search.test.d.ts.map +1 -0
- package/dist/tests/hybrid-search.test.js +114 -0
- package/dist/tests/hybrid-search.test.js.map +1 -0
- package/dist/tests/index-cli-embed.test.d.ts +7 -0
- package/dist/tests/index-cli-embed.test.d.ts.map +1 -0
- package/dist/tests/index-cli-embed.test.js +128 -0
- package/dist/tests/index-cli-embed.test.js.map +1 -0
- package/dist/tests/privacy-filter.test.d.ts +2 -0
- package/dist/tests/privacy-filter.test.d.ts.map +1 -0
- package/dist/tests/privacy-filter.test.js +56 -0
- package/dist/tests/privacy-filter.test.js.map +1 -0
- package/dist/tests/schema-version.test.d.ts +2 -0
- package/dist/tests/schema-version.test.d.ts.map +1 -0
- package/dist/tests/schema-version.test.js +127 -0
- package/dist/tests/schema-version.test.js.map +1 -0
- package/dist/tests/search-cli-hybrid.test.d.ts +6 -0
- package/dist/tests/search-cli-hybrid.test.d.ts.map +1 -0
- package/dist/tests/search-cli-hybrid.test.js +103 -0
- package/dist/tests/search-cli-hybrid.test.js.map +1 -0
- package/dist/tests/search-hook.test.js +44 -0
- package/dist/tests/search-hook.test.js.map +1 -1
- package/dist/tests/similarity.test.d.ts +2 -0
- package/dist/tests/similarity.test.d.ts.map +1 -0
- package/dist/tests/similarity.test.js +61 -0
- package/dist/tests/similarity.test.js.map +1 -0
- package/dist/tests/synonym.test.d.ts +2 -0
- package/dist/tests/synonym.test.d.ts.map +1 -0
- package/dist/tests/synonym.test.js +47 -0
- package/dist/tests/synonym.test.js.map +1 -0
- package/dist/tests/token-budget.test.d.ts +2 -0
- package/dist/tests/token-budget.test.d.ts.map +1 -0
- package/dist/tests/token-budget.test.js +32 -0
- package/dist/tests/token-budget.test.js.map +1 -0
- package/hooks/lib/mindlore-common.cjs +120 -0
- package/hooks/mindlore-index.cjs +82 -2
- package/hooks/mindlore-search.cjs +102 -35
- package/hooks/mindlore-session-end.cjs +129 -39
- package/hooks/mindlore-session-focus.cjs +24 -3
- package/package.json +6 -4
- package/plugin.json +1 -1
- package/skills/mindlore-ingest/SKILL.md +7 -1
- package/templates/config.json +20 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for hybrid search CLI path — tests the library functions
|
|
4
|
+
* that --hybrid flag invokes (synonym expansion + hybridSearch integration).
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
12
|
+
const db_js_1 = require("./helpers/db.js");
|
|
13
|
+
const { hybridSearch } = require('../scripts/lib/hybrid-search.js');
|
|
14
|
+
const { expandQuery, loadSynonyms } = require('../scripts/lib/synonym.js');
|
|
15
|
+
const TEST_DIR = path_1.default.join(__dirname, '..', '.test-search-cli-hybrid');
|
|
16
|
+
const DB_PATH = path_1.default.join(TEST_DIR, 'mindlore.db');
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
(0, db_js_1.setupTestDir)(TEST_DIR, ['sources', 'domains']);
|
|
19
|
+
const db = (0, db_js_1.createTestDb)(DB_PATH);
|
|
20
|
+
(0, db_js_1.insertFts)(db, {
|
|
21
|
+
path: path_1.default.join(TEST_DIR, 'sources', 'auth-guide.md'),
|
|
22
|
+
slug: 'auth-guide',
|
|
23
|
+
description: 'Authentication patterns and JWT token management',
|
|
24
|
+
type: 'source',
|
|
25
|
+
category: 'sources',
|
|
26
|
+
title: 'Auth Guide',
|
|
27
|
+
content: 'Authentication login patterns for secure applications with JWT tokens',
|
|
28
|
+
tags: 'auth,security',
|
|
29
|
+
});
|
|
30
|
+
(0, db_js_1.insertFts)(db, {
|
|
31
|
+
path: path_1.default.join(TEST_DIR, 'domains', 'database.md'),
|
|
32
|
+
slug: 'database',
|
|
33
|
+
description: 'SQLite and database optimization techniques',
|
|
34
|
+
type: 'domain',
|
|
35
|
+
category: 'domains',
|
|
36
|
+
title: 'Database',
|
|
37
|
+
content: 'SQLite FTS5 database optimization and indexing strategies',
|
|
38
|
+
tags: 'sqlite,database',
|
|
39
|
+
});
|
|
40
|
+
db.close();
|
|
41
|
+
});
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
(0, db_js_1.teardownTestDir)(TEST_DIR);
|
|
44
|
+
});
|
|
45
|
+
describe('Hybrid Search CLI Path — synonym expansion + hybridSearch', () => {
|
|
46
|
+
test('should find results via synonym expansion through hybridSearch', () => {
|
|
47
|
+
const synonyms = loadSynonyms({
|
|
48
|
+
synonyms: {
|
|
49
|
+
auth: ['authentication', 'login', 'kimlik doğrulama'],
|
|
50
|
+
db: ['database', 'veritabanı', 'sqlite'],
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
const query = expandQuery('auth', synonyms).join(' ');
|
|
54
|
+
const db = new better_sqlite3_1.default(DB_PATH, { readonly: true });
|
|
55
|
+
const results = hybridSearch(db, query, { maxResults: 3 });
|
|
56
|
+
expect(results.length).toBeGreaterThan(0);
|
|
57
|
+
expect(results[0].slug).toBe('auth-guide');
|
|
58
|
+
expect(results[0].score).toBeGreaterThan(0);
|
|
59
|
+
db.close();
|
|
60
|
+
});
|
|
61
|
+
test('should boost results when synonym matches additional terms', () => {
|
|
62
|
+
const synonyms = { db: ['database', 'sqlite'] };
|
|
63
|
+
const expandedQuery = expandQuery('db', synonyms).join(' ');
|
|
64
|
+
const db = new better_sqlite3_1.default(DB_PATH, { readonly: true });
|
|
65
|
+
const results = hybridSearch(db, expandedQuery, { maxResults: 3 });
|
|
66
|
+
expect(results.length).toBeGreaterThan(0);
|
|
67
|
+
expect(results[0].slug).toBe('database');
|
|
68
|
+
db.close();
|
|
69
|
+
});
|
|
70
|
+
test('should return FusedResult format with all expected fields', () => {
|
|
71
|
+
const db = new better_sqlite3_1.default(DB_PATH, { readonly: true });
|
|
72
|
+
const results = hybridSearch(db, 'authentication', { maxResults: 3 });
|
|
73
|
+
expect(results.length).toBeGreaterThan(0);
|
|
74
|
+
expect(results[0]).toMatchObject({
|
|
75
|
+
slug: expect.any(String),
|
|
76
|
+
score: expect.any(Number),
|
|
77
|
+
ftsRank: expect.any(Number),
|
|
78
|
+
});
|
|
79
|
+
db.close();
|
|
80
|
+
});
|
|
81
|
+
test('should respect maxResults option', () => {
|
|
82
|
+
const db = new better_sqlite3_1.default(DB_PATH, { readonly: true });
|
|
83
|
+
const results = hybridSearch(db, 'authentication OR database', { maxResults: 1 });
|
|
84
|
+
expect(results.length).toBe(1);
|
|
85
|
+
db.close();
|
|
86
|
+
});
|
|
87
|
+
test('should return empty array for unmatched query', () => {
|
|
88
|
+
const db = new better_sqlite3_1.default(DB_PATH, { readonly: true });
|
|
89
|
+
const results = hybridSearch(db, 'kubernetes docker', { maxResults: 3 });
|
|
90
|
+
expect(results).toHaveLength(0);
|
|
91
|
+
db.close();
|
|
92
|
+
});
|
|
93
|
+
test('should handle empty synonyms gracefully', () => {
|
|
94
|
+
const synonyms = loadSynonyms({});
|
|
95
|
+
const query = expandQuery('database', synonyms).join(' ');
|
|
96
|
+
expect(query).toBe('database');
|
|
97
|
+
const db = new better_sqlite3_1.default(DB_PATH, { readonly: true });
|
|
98
|
+
const results = hybridSearch(db, query, { maxResults: 3 });
|
|
99
|
+
expect(results.length).toBeGreaterThan(0);
|
|
100
|
+
db.close();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
//# sourceMappingURL=search-cli-hybrid.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-cli-hybrid.test.js","sourceRoot":"","sources":["../../tests/search-cli-hybrid.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -104,4 +104,48 @@ describe('Search Hook — FTS5 Query', () => {
|
|
|
104
104
|
expect(results.length).toBe(2);
|
|
105
105
|
});
|
|
106
106
|
});
|
|
107
|
+
describe('Search Hook Hybrid Integration', () => {
|
|
108
|
+
test('should fall back gracefully when hybrid search is not available', () => {
|
|
109
|
+
// Simulate hook's search function — pure FTS5 path
|
|
110
|
+
// Uses the shared DB already set up in beforeEach
|
|
111
|
+
const { searchFts5 } = require('../scripts/lib/hybrid-search.js');
|
|
112
|
+
const db = new better_sqlite3_1.default(DB_PATH, { readonly: true });
|
|
113
|
+
const results = searchFts5(db, 'react hooks', 3);
|
|
114
|
+
expect(results.length).toBeGreaterThan(0);
|
|
115
|
+
db.close();
|
|
116
|
+
});
|
|
117
|
+
test('should return FusedResult with score when using hybridSearch without vec', () => {
|
|
118
|
+
const { hybridSearch } = require('../scripts/lib/hybrid-search.js');
|
|
119
|
+
const db = new better_sqlite3_1.default(DB_PATH, { readonly: true });
|
|
120
|
+
// No vec table — hybridSearch should fall back to FTS5 and return FusedResult format
|
|
121
|
+
const results = hybridSearch(db, 'react hooks', { maxResults: 3 });
|
|
122
|
+
expect(results.length).toBeGreaterThan(0);
|
|
123
|
+
expect(results[0]).toHaveProperty('slug');
|
|
124
|
+
expect(results[0]).toHaveProperty('score');
|
|
125
|
+
expect(typeof results[0].score).toBe('number');
|
|
126
|
+
expect(results[0].score).toBeGreaterThan(0);
|
|
127
|
+
expect(results[0].slug).toBe('react-hooks');
|
|
128
|
+
db.close();
|
|
129
|
+
});
|
|
130
|
+
test('should expand synonyms and find results via hybridSearch', () => {
|
|
131
|
+
const { hybridSearch } = require('../scripts/lib/hybrid-search.js');
|
|
132
|
+
const { expandQuery } = require('../scripts/lib/synonym.js');
|
|
133
|
+
const db = new better_sqlite3_1.default(DB_PATH, { readonly: true });
|
|
134
|
+
const synonyms = { react: ['reactjs', 'react.js'] };
|
|
135
|
+
const expanded = expandQuery('react', synonyms);
|
|
136
|
+
const query = expanded.join(' ');
|
|
137
|
+
const results = hybridSearch(db, query, { maxResults: 3 });
|
|
138
|
+
expect(results.length).toBeGreaterThan(0);
|
|
139
|
+
expect(results[0].slug).toBe('react-hooks');
|
|
140
|
+
db.close();
|
|
141
|
+
});
|
|
142
|
+
test('should respect project filter in hybridSearch', () => {
|
|
143
|
+
const { hybridSearch } = require('../scripts/lib/hybrid-search.js');
|
|
144
|
+
const db = new better_sqlite3_1.default(DB_PATH, { readonly: true });
|
|
145
|
+
// Search with non-existent project — should return empty
|
|
146
|
+
const results = hybridSearch(db, 'react', { maxResults: 3, project: 'nonexistent-project' });
|
|
147
|
+
expect(results).toHaveLength(0);
|
|
148
|
+
db.close();
|
|
149
|
+
});
|
|
150
|
+
});
|
|
107
151
|
//# sourceMappingURL=search-hook.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-hook.test.js","sourceRoot":"","sources":["../../tests/search-hook.test.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"search-hook.test.js","sourceRoot":"","sources":["../../tests/search-hook.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.test.d.ts","sourceRoot":"","sources":["../../tests/similarity.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
const path_1 = __importDefault(require("path"));
|
|
7
|
+
const db_js_1 = require("./helpers/db.js");
|
|
8
|
+
const similarity_js_1 = require("../scripts/lib/similarity.js");
|
|
9
|
+
const TEST_DIR = path_1.default.join(__dirname, '..', '.test-similarity');
|
|
10
|
+
const DB_PATH = path_1.default.join(TEST_DIR, 'mindlore.db');
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
(0, db_js_1.setupTestDir)(TEST_DIR, ['sources']);
|
|
13
|
+
const db = (0, db_js_1.createTestDbWithMigrations)(DB_PATH);
|
|
14
|
+
(0, db_js_1.insertFts)(db, {
|
|
15
|
+
path: path_1.default.join(TEST_DIR, 'sources', 'react-hooks.md'),
|
|
16
|
+
slug: 'react-hooks',
|
|
17
|
+
description: 'React hooks patterns and useEffect cleanup',
|
|
18
|
+
type: 'source',
|
|
19
|
+
category: 'sources',
|
|
20
|
+
title: 'React Hooks Guide',
|
|
21
|
+
content: 'React hooks patterns and useEffect cleanup for memory leak prevention.',
|
|
22
|
+
tags: 'react,hooks',
|
|
23
|
+
});
|
|
24
|
+
(0, db_js_1.insertFts)(db, {
|
|
25
|
+
path: path_1.default.join(TEST_DIR, 'sources', 'vue-composition.md'),
|
|
26
|
+
slug: 'vue-composition',
|
|
27
|
+
description: 'Vue composition API and reactivity',
|
|
28
|
+
type: 'source',
|
|
29
|
+
category: 'sources',
|
|
30
|
+
title: 'Vue Composition API',
|
|
31
|
+
content: 'Vue 3 composition API patterns and reactive state management.',
|
|
32
|
+
tags: 'vue,composition',
|
|
33
|
+
});
|
|
34
|
+
db.close();
|
|
35
|
+
});
|
|
36
|
+
afterEach(() => {
|
|
37
|
+
(0, db_js_1.teardownTestDir)(TEST_DIR);
|
|
38
|
+
});
|
|
39
|
+
describe('Similarity / Duplicate Detection', () => {
|
|
40
|
+
test('should find similar documents by FTS5 keyword match', () => {
|
|
41
|
+
const results = (0, similarity_js_1.findSimilar)(DB_PATH, 'React hooks useEffect cleanup patterns');
|
|
42
|
+
expect(results.length).toBeGreaterThan(0);
|
|
43
|
+
expect(results[0]?.slug).toBe('react-hooks');
|
|
44
|
+
});
|
|
45
|
+
test('should return empty for unrelated content', () => {
|
|
46
|
+
const results = (0, similarity_js_1.findSimilar)(DB_PATH, 'Kubernetes pod networking and service mesh');
|
|
47
|
+
expect(results.length).toBe(0);
|
|
48
|
+
});
|
|
49
|
+
test('should return similarity score', () => {
|
|
50
|
+
const results = (0, similarity_js_1.findSimilar)(DB_PATH, 'React hooks useEffect');
|
|
51
|
+
if (results.length > 0) {
|
|
52
|
+
expect(results[0]).toHaveProperty('score');
|
|
53
|
+
expect(typeof results[0].score).toBe('number');
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
test('should limit results to maxResults', () => {
|
|
57
|
+
const results = (0, similarity_js_1.findSimilar)(DB_PATH, 'API patterns', { maxResults: 1 });
|
|
58
|
+
expect(results.length).toBeLessThanOrEqual(1);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=similarity.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"similarity.test.js","sourceRoot":"","sources":["../../tests/similarity.test.ts"],"names":[],"mappings":";;;;;AAAA,gDAAwB;AACxB,2CAAuG;AACvG,gEAA2D;AAE3D,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,kBAAkB,CAAC,CAAC;AAChE,MAAM,OAAO,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;AAEnD,UAAU,CAAC,GAAG,EAAE;IACd,IAAA,oBAAY,EAAC,QAAQ,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;IACpC,MAAM,EAAE,GAAG,IAAA,kCAA0B,EAAC,OAAO,CAAC,CAAC;IAC/C,IAAA,iBAAS,EAAC,EAAE,EAAE;QACZ,IAAI,EAAE,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,gBAAgB,CAAC;QACtD,IAAI,EAAE,aAAa;QACnB,WAAW,EAAE,4CAA4C;QACzD,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,SAAS;QACnB,KAAK,EAAE,mBAAmB;QAC1B,OAAO,EAAE,wEAAwE;QACjF,IAAI,EAAE,aAAa;KACpB,CAAC,CAAC;IACH,IAAA,iBAAS,EAAC,EAAE,EAAE;QACZ,IAAI,EAAE,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,oBAAoB,CAAC;QAC1D,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,oCAAoC;QACjD,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,SAAS;QACnB,KAAK,EAAE,qBAAqB;QAC5B,OAAO,EAAE,+DAA+D;QACxE,IAAI,EAAE,iBAAiB;KACxB,CAAC,CAAC;IACH,EAAE,CAAC,KAAK,EAAE,CAAC;AACb,CAAC,CAAC,CAAC;AAEH,SAAS,CAAC,GAAG,EAAE;IACb,IAAA,uBAAe,EAAC,QAAQ,CAAC,CAAC;AAC5B,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,kCAAkC,EAAE,GAAG,EAAE;IAChD,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC/D,MAAM,OAAO,GAAG,IAAA,2BAAW,EAAC,OAAO,EAAE,wCAAwC,CAAC,CAAC;QAC/E,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,2CAA2C,EAAE,GAAG,EAAE;QACrD,MAAM,OAAO,GAAG,IAAA,2BAAW,EAAC,OAAO,EAAE,4CAA4C,CAAC,CAAC;QACnF,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE;QAC1C,MAAM,OAAO,GAAG,IAAA,2BAAW,EAAC,OAAO,EAAE,uBAAuB,CAAC,CAAC;QAC9D,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;YAC5C,MAAM,CAAC,OAAO,OAAO,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAClD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC9C,MAAM,OAAO,GAAG,IAAA,2BAAW,EAAC,OAAO,EAAE,cAAc,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC,CAAC;QACxE,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synonym.test.d.ts","sourceRoot":"","sources":["../../tests/synonym.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const synonym_js_1 = require("../scripts/lib/synonym.js");
|
|
4
|
+
describe('Synonym Expansion', () => {
|
|
5
|
+
const synonyms = {
|
|
6
|
+
auth: ['authentication', 'login', 'kimlik doğrulama'],
|
|
7
|
+
güvenlik: ['security', 'hardening', 'sertleştirme'],
|
|
8
|
+
db: ['database', 'veritabanı', 'sqlite'],
|
|
9
|
+
};
|
|
10
|
+
test('should expand known synonym', () => {
|
|
11
|
+
const result = (0, synonym_js_1.expandQuery)('auth token', synonyms);
|
|
12
|
+
expect(result).toContain('auth');
|
|
13
|
+
expect(result).toContain('authentication');
|
|
14
|
+
expect(result).toContain('login');
|
|
15
|
+
expect(result).toContain('token');
|
|
16
|
+
});
|
|
17
|
+
test('should not duplicate original term', () => {
|
|
18
|
+
const result = (0, synonym_js_1.expandQuery)('auth', synonyms);
|
|
19
|
+
const authCount = result.filter(t => t === 'auth').length;
|
|
20
|
+
expect(authCount).toBe(1);
|
|
21
|
+
});
|
|
22
|
+
test('should handle Turkish synonyms', () => {
|
|
23
|
+
const result = (0, synonym_js_1.expandQuery)('güvenlik audit', synonyms);
|
|
24
|
+
expect(result).toContain('güvenlik');
|
|
25
|
+
expect(result).toContain('security');
|
|
26
|
+
expect(result).toContain('hardening');
|
|
27
|
+
expect(result).toContain('audit');
|
|
28
|
+
});
|
|
29
|
+
test('should return original terms when no synonyms match', () => {
|
|
30
|
+
const result = (0, synonym_js_1.expandQuery)('typescript hooks', synonyms);
|
|
31
|
+
expect(result).toEqual(['typescript', 'hooks']);
|
|
32
|
+
});
|
|
33
|
+
test('should handle empty input', () => {
|
|
34
|
+
const result = (0, synonym_js_1.expandQuery)('', synonyms);
|
|
35
|
+
expect(result).toEqual([]);
|
|
36
|
+
});
|
|
37
|
+
test('should load synonyms from config object', () => {
|
|
38
|
+
const config = { synonyms };
|
|
39
|
+
const loaded = (0, synonym_js_1.loadSynonyms)(config);
|
|
40
|
+
expect(loaded).toEqual(synonyms);
|
|
41
|
+
});
|
|
42
|
+
test('should return empty object when config has no synonyms', () => {
|
|
43
|
+
const loaded = (0, synonym_js_1.loadSynonyms)({});
|
|
44
|
+
expect(loaded).toEqual({});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
//# sourceMappingURL=synonym.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synonym.test.js","sourceRoot":"","sources":["../../tests/synonym.test.ts"],"names":[],"mappings":";;AAAA,0DAAsE;AAEtE,QAAQ,CAAC,mBAAmB,EAAE,GAAG,EAAE;IACjC,MAAM,QAAQ,GAAG;QACf,IAAI,EAAE,CAAC,gBAAgB,EAAE,OAAO,EAAE,kBAAkB,CAAC;QACrD,QAAQ,EAAE,CAAC,UAAU,EAAE,WAAW,EAAE,cAAc,CAAC;QACnD,EAAE,EAAE,CAAC,UAAU,EAAE,YAAY,EAAE,QAAQ,CAAC;KACzC,CAAC;IAEF,IAAI,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACvC,MAAM,MAAM,GAAG,IAAA,wBAAW,EAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QACjC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAC3C,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC9C,MAAM,MAAM,GAAG,IAAA,wBAAW,EAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;QAC1D,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,gCAAgC,EAAE,GAAG,EAAE;QAC1C,MAAM,MAAM,GAAG,IAAA,wBAAW,EAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;QACvD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACrC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;QAC/D,MAAM,MAAM,GAAG,IAAA,wBAAW,EAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;QACzD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACrC,MAAM,MAAM,GAAG,IAAA,wBAAW,EAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;QACzC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACnD,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,CAAC;QAC5B,MAAM,MAAM,GAAG,IAAA,yBAAY,EAAC,MAAM,CAAC,CAAC;QACpC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAClE,MAAM,MAAM,GAAG,IAAA,yBAAY,EAAC,EAAE,CAAC,CAAC;QAChC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-budget.test.d.ts","sourceRoot":"","sources":["../../tests/token-budget.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const constants_js_1 = require("../scripts/lib/constants.js");
|
|
4
|
+
describe('Token Budget', () => {
|
|
5
|
+
test('should have default budget values', () => {
|
|
6
|
+
expect(constants_js_1.DEFAULT_TOKEN_BUDGET.sessionInject).toBe(2000);
|
|
7
|
+
expect(constants_js_1.DEFAULT_TOKEN_BUDGET.searchResults).toBe(1500);
|
|
8
|
+
expect(constants_js_1.DEFAULT_TOKEN_BUDGET.perResult).toBe(500);
|
|
9
|
+
});
|
|
10
|
+
test('should truncate text to approximate token limit', () => {
|
|
11
|
+
// ~4 chars per token heuristic
|
|
12
|
+
const longText = 'a'.repeat(3000);
|
|
13
|
+
const maxChars = 500 * 4; // perResult=500 tokens ≈ 2000 chars
|
|
14
|
+
const truncated = longText.slice(0, maxChars);
|
|
15
|
+
expect(truncated.length).toBeLessThanOrEqual(maxChars);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
describe('Search Hook — FTS5 Fallback OR Optimization', () => {
|
|
19
|
+
test('should build OR-joined FTS5 MATCH query from keywords', () => {
|
|
20
|
+
const keywords = ['react', 'hooks', 'useEffect'];
|
|
21
|
+
const sanitized = keywords.map(kw => kw.replace(/["*(){}[\]^~:]/g, ''));
|
|
22
|
+
const ftsQuery = sanitized.filter(Boolean).map(kw => `"${kw}"`).join(' OR ');
|
|
23
|
+
expect(ftsQuery).toBe('"react" OR "hooks" OR "useEffect"');
|
|
24
|
+
});
|
|
25
|
+
test('should handle keywords with special characters', () => {
|
|
26
|
+
const keywords = ['react*', '"hooks"', 'use:Effect'];
|
|
27
|
+
const sanitized = keywords.map(kw => kw.replace(/["*(){}[\]^~:]/g, ''));
|
|
28
|
+
const ftsQuery = sanitized.filter(Boolean).map(kw => `"${kw}"`).join(' OR ');
|
|
29
|
+
expect(ftsQuery).toBe('"react" OR "hooks" OR "useEffect"');
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=token-budget.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-budget.test.js","sourceRoot":"","sources":["../../tests/token-budget.test.ts"],"names":[],"mappings":";;AAAA,8DAAmE;AAEnE,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,IAAI,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC7C,MAAM,CAAC,mCAAoB,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,CAAC,mCAAoB,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtD,MAAM,CAAC,mCAAoB,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,iDAAiD,EAAE,GAAG,EAAE;QAC3D,+BAA+B;QAC/B,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,QAAQ,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,oCAAoC;QAC9D,MAAM,SAAS,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QAC9C,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,6CAA6C,EAAE,GAAG,EAAE;IAC3D,IAAI,CAAC,uDAAuD,EAAE,GAAG,EAAE;QACjE,MAAM,QAAQ,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;QACjD,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC,CAAC;QACxE,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC7E,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,gDAAgD,EAAE,GAAG,EAAE;QAC1D,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,YAAY,CAAC,CAAC;QACrD,MAAM,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC,CAAC,CAAC;QACxE,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC7E,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -590,6 +590,20 @@ function sanitizeKeyword(kw) {
|
|
|
590
590
|
return clean.length >= 2 ? `"${clean}"` : null;
|
|
591
591
|
}
|
|
592
592
|
|
|
593
|
+
// Matches DIRECTORIES in scripts/lib/constants.ts + 'memory' (v0.5.1 CC memory sync)
|
|
594
|
+
const SHARED_EXPORT_DIRS = ['raw', 'sources', 'domains', 'analyses', 'insights', 'connections', 'learnings', 'diary', 'decisions', 'memory'];
|
|
595
|
+
|
|
596
|
+
function resolveWin32Bin(name) {
|
|
597
|
+
if (process.platform === 'win32') {
|
|
598
|
+
try {
|
|
599
|
+
return require('child_process')
|
|
600
|
+
.execSync(`where ${name}`, { encoding: 'utf8', timeout: 3000 })
|
|
601
|
+
.trim().split('\n')[0].trim();
|
|
602
|
+
} catch (_e) { /* fall through */ }
|
|
603
|
+
}
|
|
604
|
+
return name;
|
|
605
|
+
}
|
|
606
|
+
|
|
593
607
|
module.exports = {
|
|
594
608
|
MINDLORE_DIR,
|
|
595
609
|
GLOBAL_MINDLORE_DIR,
|
|
@@ -633,4 +647,110 @@ module.exports = {
|
|
|
633
647
|
STOP_WORDS,
|
|
634
648
|
extractKeywords,
|
|
635
649
|
sanitizeKeyword,
|
|
650
|
+
// Hybrid search helpers (v0.5.0)
|
|
651
|
+
loadSqliteVecCjs,
|
|
652
|
+
hasVecTableCjs,
|
|
653
|
+
// Hook logging (v0.5.1)
|
|
654
|
+
hookLog,
|
|
655
|
+
getRecentHookErrors,
|
|
656
|
+
// Shared helpers (v0.5.1)
|
|
657
|
+
SHARED_EXPORT_DIRS,
|
|
658
|
+
resolveWin32Bin,
|
|
636
659
|
};
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Try to load sqlite-vec extension. Returns true if successful.
|
|
663
|
+
* @param {import('better-sqlite3').Database} db
|
|
664
|
+
* @returns {boolean}
|
|
665
|
+
*/
|
|
666
|
+
function loadSqliteVecCjs(db) {
|
|
667
|
+
try {
|
|
668
|
+
const sqliteVec = require('sqlite-vec');
|
|
669
|
+
sqliteVec.load(db);
|
|
670
|
+
return true;
|
|
671
|
+
} catch (_err) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* Check if documents_vec table exists.
|
|
678
|
+
* @param {import('better-sqlite3').Database} db
|
|
679
|
+
* @returns {boolean}
|
|
680
|
+
*/
|
|
681
|
+
function hasVecTableCjs(db) {
|
|
682
|
+
try {
|
|
683
|
+
db.prepare('SELECT slug FROM documents_vec LIMIT 0').run();
|
|
684
|
+
return true;
|
|
685
|
+
} catch (_err) {
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// --- Hook Logging (v0.5.1) ---
|
|
691
|
+
|
|
692
|
+
function hookLogPath() { return path.join(globalDir(), 'diary', '_hook-log.jsonl'); }
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Append a structured log entry for any mindlore hook.
|
|
696
|
+
* JSONL format — one JSON object per line.
|
|
697
|
+
* Levels: 'info' | 'warn' | 'error'
|
|
698
|
+
* @param {string} hook - Hook name (e.g. 'session-end', 'search', 'read-guard')
|
|
699
|
+
* @param {'info'|'warn'|'error'} level
|
|
700
|
+
* @param {string} message
|
|
701
|
+
*/
|
|
702
|
+
const HOOK_LOG_MAX_BYTES = 512 * 1024; // 500KB
|
|
703
|
+
const HOOK_LOG_KEEP_LINES = 500;
|
|
704
|
+
|
|
705
|
+
function hookLog(hook, level, message) {
|
|
706
|
+
try {
|
|
707
|
+
const logFile = hookLogPath();
|
|
708
|
+
const entry = JSON.stringify({
|
|
709
|
+
ts: new Date().toISOString(),
|
|
710
|
+
hook,
|
|
711
|
+
level,
|
|
712
|
+
msg: message,
|
|
713
|
+
pid: process.pid,
|
|
714
|
+
});
|
|
715
|
+
// Rotate if file exceeds threshold
|
|
716
|
+
try {
|
|
717
|
+
const stat = fs.statSync(logFile);
|
|
718
|
+
if (stat.size > HOOK_LOG_MAX_BYTES) {
|
|
719
|
+
const lines = fs.readFileSync(logFile, 'utf8').trim().split('\n');
|
|
720
|
+
fs.writeFileSync(logFile, lines.slice(-HOOK_LOG_KEEP_LINES).join('\n') + '\n');
|
|
721
|
+
}
|
|
722
|
+
} catch (_rotateErr) { /* file may not exist yet */ }
|
|
723
|
+
fs.appendFileSync(logFile, entry + '\n');
|
|
724
|
+
} catch (_err) {
|
|
725
|
+
// Best effort — never crash a hook for logging
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Read recent hook errors/warnings since a given ISO date.
|
|
731
|
+
* Returns array of { ts, hook, level, msg } for level 'error' or 'warn'.
|
|
732
|
+
* Used by SessionStart to inject warnings into CC context.
|
|
733
|
+
* @param {string} [since] - ISO date string, defaults to 24h ago
|
|
734
|
+
* @param {number} [limit=10]
|
|
735
|
+
* @returns {Array<{ts: string, hook: string, level: string, msg: string}>}
|
|
736
|
+
*/
|
|
737
|
+
function getRecentHookErrors(since, limit) {
|
|
738
|
+
const maxEntries = limit ?? 10;
|
|
739
|
+
const cutoff = since ?? new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
|
|
740
|
+
const results = [];
|
|
741
|
+
try {
|
|
742
|
+
if (!fs.existsSync(hookLogPath())) return results;
|
|
743
|
+
const lines = fs.readFileSync(hookLogPath(), 'utf8').trim().split('\n');
|
|
744
|
+
for (let i = lines.length - 1; i >= 0 && results.length < maxEntries; i--) {
|
|
745
|
+
if (!lines[i]) continue;
|
|
746
|
+
try {
|
|
747
|
+
const entry = JSON.parse(lines[i]);
|
|
748
|
+
if (entry.ts < cutoff) break; // JSONL is chronological, stop early
|
|
749
|
+
if (entry.level === 'error' || entry.level === 'warn') {
|
|
750
|
+
results.push(entry);
|
|
751
|
+
}
|
|
752
|
+
} catch (_parseErr) { /* skip malformed lines */ }
|
|
753
|
+
}
|
|
754
|
+
} catch (_err) { /* silent */ }
|
|
755
|
+
return results.reverse(); // chronological order
|
|
756
|
+
}
|
package/hooks/mindlore-index.cjs
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
|
-
const { MINDLORE_DIR, DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getProjectName } = require('./lib/mindlore-common.cjs');
|
|
13
|
+
const { MINDLORE_DIR, DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata, insertFtsRow, readHookStdin, getProjectName, globalDir } = require('./lib/mindlore-common.cjs');
|
|
14
14
|
|
|
15
15
|
function main() {
|
|
16
16
|
const filePath = readHookStdin(['path', 'file_path']);
|
|
@@ -19,7 +19,17 @@ function main() {
|
|
|
19
19
|
// Only process .md files inside .mindlore/ (resolved path check prevents traversal)
|
|
20
20
|
if (!filePath.endsWith('.md')) return;
|
|
21
21
|
const resolvedFile = path.resolve(filePath);
|
|
22
|
-
if (!resolvedFile.includes(path.sep + MINDLORE_DIR + path.sep) && !resolvedFile.includes(path.sep + MINDLORE_DIR))
|
|
22
|
+
if (!resolvedFile.includes(path.sep + MINDLORE_DIR + path.sep) && !resolvedFile.includes(path.sep + MINDLORE_DIR)) {
|
|
23
|
+
// CC memory path (~/.claude/projects/*/memory/*.md) — index to global mindlore DB
|
|
24
|
+
const isCcMemory = resolvedFile.includes(path.sep + '.claude' + path.sep + 'projects' + path.sep)
|
|
25
|
+
&& resolvedFile.includes(path.sep + 'memory' + path.sep)
|
|
26
|
+
&& resolvedFile.endsWith('.md');
|
|
27
|
+
if (!isCcMemory) return;
|
|
28
|
+
|
|
29
|
+
// CC memory path — index to global mindlore DB
|
|
30
|
+
indexCcMemory(resolvedFile);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
23
33
|
|
|
24
34
|
const fileName = path.basename(filePath);
|
|
25
35
|
if (SKIP_FILES.has(fileName)) return;
|
|
@@ -80,4 +90,74 @@ function main() {
|
|
|
80
90
|
}
|
|
81
91
|
}
|
|
82
92
|
|
|
93
|
+
function indexCcMemory(filePath) {
|
|
94
|
+
const CC_MEMORY_CATEGORY = 'cc-memory';
|
|
95
|
+
// CC memory constants live in TS (scripts/lib/constants.ts) — CJS hooks can't require TS directly
|
|
96
|
+
const globalBase = globalDir();
|
|
97
|
+
const dbPath = path.join(globalBase, DB_NAME);
|
|
98
|
+
|
|
99
|
+
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
100
|
+
if (!content.trim()) return;
|
|
101
|
+
|
|
102
|
+
// Privacy filter — redact secrets before DB write
|
|
103
|
+
let cleaned = content;
|
|
104
|
+
try {
|
|
105
|
+
const { redactSecrets } = require('../dist/scripts/lib/privacy-filter.js');
|
|
106
|
+
cleaned = redactSecrets(content);
|
|
107
|
+
} catch (_err) {
|
|
108
|
+
// privacy-filter not built — use raw content
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// SHA256 dedup
|
|
112
|
+
const hash = sha256(cleaned);
|
|
113
|
+
const db = openDatabase(dbPath);
|
|
114
|
+
if (!db) return;
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const existing = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?').get(filePath);
|
|
118
|
+
if (existing && existing.content_hash === hash) {
|
|
119
|
+
return; // unchanged — finally handles db.close()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const { meta, body } = parseFrontmatter(cleaned);
|
|
123
|
+
const memType = String(meta.type || 'unknown');
|
|
124
|
+
|
|
125
|
+
// Extract project scope from path: ~/.claude/projects/C--Users-X-proj/memory/
|
|
126
|
+
const projMatch = filePath.match(/projects[/\\]([^/\\]+)[/\\]memory/);
|
|
127
|
+
const projectScope = projMatch ? projMatch[1] : null;
|
|
128
|
+
|
|
129
|
+
const ftsData = extractFtsMetadata(meta, body, filePath, globalBase);
|
|
130
|
+
|
|
131
|
+
// Update FTS5 + hash atomically
|
|
132
|
+
const updateIndex = db.transaction(() => {
|
|
133
|
+
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
134
|
+
insertFtsRow(db, {
|
|
135
|
+
path: filePath,
|
|
136
|
+
...ftsData,
|
|
137
|
+
category: CC_MEMORY_CATEGORY,
|
|
138
|
+
type: memType,
|
|
139
|
+
project: projectScope,
|
|
140
|
+
});
|
|
141
|
+
db.prepare(
|
|
142
|
+
`INSERT INTO file_hashes (path, content_hash, last_indexed, source_type, project_scope)
|
|
143
|
+
VALUES (?, ?, ?, ?, ?)
|
|
144
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
145
|
+
content_hash = excluded.content_hash,
|
|
146
|
+
last_indexed = excluded.last_indexed,
|
|
147
|
+
source_type = excluded.source_type,
|
|
148
|
+
project_scope = excluded.project_scope`
|
|
149
|
+
).run(filePath, hash, new Date().toISOString(), CC_MEMORY_CATEGORY, projectScope);
|
|
150
|
+
});
|
|
151
|
+
updateIndex();
|
|
152
|
+
|
|
153
|
+
// Copy to ~/.mindlore/memory/{project}/ for git-sync + obsidian
|
|
154
|
+
const memoryDir = path.join(globalBase, 'memory', projectScope || '_global');
|
|
155
|
+
fs.mkdirSync(memoryDir, { recursive: true });
|
|
156
|
+
const destPath = path.join(memoryDir, path.basename(filePath));
|
|
157
|
+
fs.writeFileSync(destPath, cleaned, 'utf8');
|
|
158
|
+
} finally {
|
|
159
|
+
db.close();
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
83
163
|
main();
|