mindlore 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +61 -4
- package/SCHEMA.md +71 -12
- package/hooks/lib/mindlore-common.cjs +106 -0
- package/hooks/mindlore-decision-detector.cjs +51 -0
- package/hooks/mindlore-fts5-sync.cjs +13 -24
- package/hooks/mindlore-index.cjs +11 -27
- package/hooks/mindlore-post-compact.cjs +2 -2
- package/hooks/mindlore-pre-compact.cjs +1 -1
- package/hooks/mindlore-read-guard.cjs +62 -0
- package/hooks/mindlore-search.cjs +94 -76
- package/hooks/mindlore-session-end.cjs +74 -8
- package/hooks/mindlore-session-focus.cjs +2 -2
- package/package.json +1 -1
- package/plugin.json +26 -2
- package/scripts/init.cjs +126 -13
- package/scripts/mindlore-fts5-index.cjs +7 -10
- package/scripts/mindlore-fts5-search.cjs +3 -6
- package/scripts/mindlore-health-check.cjs +98 -29
- package/scripts/uninstall.cjs +186 -0
- package/skills/mindlore-decide/SKILL.md +71 -0
- package/skills/mindlore-ingest/SKILL.md +13 -2
- package/skills/mindlore-log/SKILL.md +79 -0
- package/skills/mindlore-query/SKILL.md +125 -0
- package/templates/SCHEMA.md +280 -0
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 mindlore
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 mindlore
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ To add your first source:
|
|
|
44
44
|
| Skill | Version | Description |
|
|
45
45
|
|-------|---------|-------------|
|
|
46
46
|
| `/mindlore-ingest` | v0.1 | Add knowledge sources (URL, text, file, PDF) |
|
|
47
|
-
| `/mindlore-health` | v0.1 |
|
|
47
|
+
| `/mindlore-health` | v0.1 | 18-point structural health check |
|
|
48
48
|
| `/mindlore-query` | v0.2 | Search and retrieve knowledge (4 modes) |
|
|
49
49
|
| `/mindlore-log` | v0.2 | Session logging with reflect and status |
|
|
50
50
|
| `/mindlore-decide` | v0.2 | Decision records with supersedes chain |
|
|
@@ -103,19 +103,76 @@ Also suggests installing:
|
|
|
103
103
|
- **markitdown** — better web/document extraction (URL, DOCX, YouTube)
|
|
104
104
|
- **context-mode** — token savings for large sessions
|
|
105
105
|
|
|
106
|
+
## How It Works
|
|
107
|
+
|
|
108
|
+
Mindlore operates through Claude Code lifecycle hooks — invisible background scripts
|
|
109
|
+
that fire automatically as you work. No commands to run, no workflow changes.
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
┌─────────────────────────────┐
|
|
113
|
+
│ Claude Code Session │
|
|
114
|
+
└──────────────┬──────────────┘
|
|
115
|
+
│
|
|
116
|
+
┌──────────────────────────────────────┼──────────────────────────────────────┐
|
|
117
|
+
│ │ │
|
|
118
|
+
▼ ▼ ▼
|
|
119
|
+
SESSION START DURING SESSION SESSION END
|
|
120
|
+
│ │ │
|
|
121
|
+
├─ session-focus hook ├─ search hook ├─ session-end hook
|
|
122
|
+
│ reads INDEX.md + last delta │ 7-col FTS5 + porter stemmer │ writes delta to diary/
|
|
123
|
+
│ injects into context │ per-keyword scoring, top 3 injected │
|
|
124
|
+
│ │ │
|
|
125
|
+
│ ├─ index + fts5-sync hooks │
|
|
126
|
+
│ │ file changes → FTS5 update │
|
|
127
|
+
│ │ │
|
|
128
|
+
│ ├─ /mindlore-ingest skill │
|
|
129
|
+
│ │ URL → raw/ → sources/ → FTS5 │
|
|
130
|
+
│ │ │
|
|
131
|
+
└────────────────────────────────┴──────────────────────────────────────┘
|
|
132
|
+
│
|
|
133
|
+
┌──────────────┴──────────────┐
|
|
134
|
+
│ NEXT SESSION │
|
|
135
|
+
│ session-focus injects delta │
|
|
136
|
+
│ → knowledge compounds │
|
|
137
|
+
└─────────────────────────────┘
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Key design decisions:**
|
|
141
|
+
|
|
142
|
+
- **Hooks are global** — registered in `~/.claude/settings.json`, active in all projects
|
|
143
|
+
- **Data is per-project** — `.mindlore/` lives in each project directory
|
|
144
|
+
- **No `.mindlore/`?** — hooks silently skip, zero overhead
|
|
145
|
+
- **FTS5 search** — SQLite full-text search with BM25 ranking, no external services
|
|
146
|
+
- **Content-hash dedup** — SHA256 prevents re-indexing unchanged files
|
|
147
|
+
|
|
106
148
|
## Hooks
|
|
107
149
|
|
|
108
|
-
|
|
150
|
+
9 Claude Code lifecycle hooks (v0.2):
|
|
109
151
|
|
|
110
152
|
| Event | Hook | What it does |
|
|
111
153
|
|-------|------|-------------|
|
|
112
154
|
| SessionStart | session-focus | Injects last delta + INDEX |
|
|
113
|
-
| UserPromptSubmit | search | FTS5 search, top 3 results |
|
|
155
|
+
| UserPromptSubmit | search | FTS5 search, top 3 results + tags |
|
|
156
|
+
| UserPromptSubmit | decision-detector | TR+EN decision signal detection |
|
|
114
157
|
| FileChanged | index | Sync changed files to FTS5 |
|
|
115
158
|
| FileChanged | fts5-sync | Incremental batch re-index |
|
|
116
|
-
| SessionEnd | session-end |
|
|
159
|
+
| SessionEnd | session-end | Structured delta (commits, files, reads) |
|
|
117
160
|
| PreCompact | pre-compact | FTS5 flush before compaction |
|
|
118
161
|
| PostCompact | post-compact | Re-inject context |
|
|
162
|
+
| PreToolUse (Read) | read-guard | Repeated-read warning |
|
|
163
|
+
|
|
164
|
+
## Uninstall
|
|
165
|
+
|
|
166
|
+
Remove Mindlore from your system:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
npx mindlore uninstall
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
This removes:
|
|
173
|
+
- Hooks from `~/.claude/settings.json`
|
|
174
|
+
- Skills from `~/.claude/skills/`
|
|
175
|
+
- Optionally: `.mindlore/` project data (asks for confirmation)
|
|
119
176
|
|
|
120
177
|
## Inspired By
|
|
121
178
|
|
package/SCHEMA.md
CHANGED
|
@@ -59,14 +59,14 @@ tags: [tag1, tag2]
|
|
|
59
59
|
|
|
60
60
|
| Type | Required Fields | Optional |
|
|
61
61
|
|------|----------------|----------|
|
|
62
|
-
| `raw` | slug, type, source_url | tags |
|
|
63
|
-
| `source` | slug, type, title, tags, quality, source_url, ingested | date_captured |
|
|
64
|
-
| `domain` | slug, type, title, tags |
|
|
65
|
-
| `analysis` | slug, type, title, tags, confidence, sources_used |
|
|
66
|
-
| `insight` | slug, type, title, tags | sources_used |
|
|
67
|
-
| `connection` | slug, type, title, tags | sources_used |
|
|
68
|
-
| `learning` | slug, type, title, tags |
|
|
69
|
-
| `decision` | slug, type, title, tags | supersedes, status |
|
|
62
|
+
| `raw` | slug, type, source_url | tags, description |
|
|
63
|
+
| `source` | slug, type, title, tags, quality, source_url, ingested | date_captured, description, raw_slug |
|
|
64
|
+
| `domain` | slug, type, title, tags | description, status |
|
|
65
|
+
| `analysis` | slug, type, title, tags, confidence, sources_used | description |
|
|
66
|
+
| `insight` | slug, type, title, tags | sources_used, description |
|
|
67
|
+
| `connection` | slug, type, title, tags | sources_used, description |
|
|
68
|
+
| `learning` | slug, type, title, tags | description |
|
|
69
|
+
| `decision` | slug, type, title, tags | supersedes, status, description |
|
|
70
70
|
| `diary` | slug, type, date | — (hook adds automatically) |
|
|
71
71
|
|
|
72
72
|
### Field Value Rules
|
|
@@ -77,6 +77,9 @@ tags: [tag1, tag2]
|
|
|
77
77
|
- `sources_used`: list of slugs referenced in the analysis
|
|
78
78
|
- `supersedes`: slug of the decision this one replaces
|
|
79
79
|
- `date`: ISO 8601 date (YYYY-MM-DD)
|
|
80
|
+
- `description`: one-line summary (15-30 words). Used in FTS5 search and inject output. Optional but strongly recommended for search quality
|
|
81
|
+
- `raw_slug`: slug of the raw/ file this source was processed from (source→raw traceability)
|
|
82
|
+
- `status`: `stub` | `active` | `archived` (domain maturity indicator)
|
|
80
83
|
|
|
81
84
|
## 4. Seven Operations
|
|
82
85
|
|
|
@@ -140,14 +143,70 @@ Discover unexpected connections between sources. Cross-reference analysis.
|
|
|
140
143
|
- Max results: 3 per query (BM25 ranking)
|
|
141
144
|
- Hook injects: file path + first 2 headings
|
|
142
145
|
|
|
146
|
+
### FTS5 Columns (9-col schema, v0.2)
|
|
147
|
+
|
|
148
|
+
| Column | Indexed | Source |
|
|
149
|
+
|--------|---------|--------|
|
|
150
|
+
| `path` | UNINDEXED | File system path |
|
|
151
|
+
| `slug` | Yes | Frontmatter slug |
|
|
152
|
+
| `description` | Yes | Frontmatter description |
|
|
153
|
+
| `type` | UNINDEXED | Frontmatter type |
|
|
154
|
+
| `category` | Yes | Parent directory name |
|
|
155
|
+
| `title` | Yes | Frontmatter title or first heading |
|
|
156
|
+
| `content` | Yes | Markdown body (sans frontmatter) |
|
|
157
|
+
| `tags` | Yes | Frontmatter tags (comma-separated) |
|
|
158
|
+
| `quality` | UNINDEXED | Frontmatter quality (NULL until 50+ sources) |
|
|
159
|
+
|
|
143
160
|
### Search Flow (UserPromptSubmit hook)
|
|
144
161
|
|
|
145
162
|
1. Extract keywords from user prompt
|
|
146
163
|
2. Query FTS5 with BM25 ranking
|
|
147
|
-
3. Return max 3 results as
|
|
164
|
+
3. Return max 3 results as stdout additionalContext
|
|
148
165
|
4. Agent reads full file only if needed (progressive disclosure)
|
|
149
166
|
|
|
150
|
-
## 6.
|
|
167
|
+
## 6. Wiki vs Diary (Writeback Target Rules)
|
|
168
|
+
|
|
169
|
+
Knowledge goes to one of two layers. The agent MUST pick the correct one.
|
|
170
|
+
|
|
171
|
+
### Wiki Layer (permanent knowledge)
|
|
172
|
+
|
|
173
|
+
Directories: `sources/`, `domains/`, `analyses/`, `insights/`, `connections/`, `learnings/`
|
|
174
|
+
|
|
175
|
+
- Persists across sessions — reference value
|
|
176
|
+
- Indexed by FTS5, discoverable via search hook
|
|
177
|
+
- Updated by ingest, query writeback, reflect, evolve
|
|
178
|
+
- Content should be factual, sourced, and reusable
|
|
179
|
+
|
|
180
|
+
### Diary Layer (session-scoped logs)
|
|
181
|
+
|
|
182
|
+
Directories: `diary/`, `decisions/`
|
|
183
|
+
|
|
184
|
+
- Session-specific: deltas, logs, decision snapshots
|
|
185
|
+
- diary/ entries get `archived: true` after reflect processes them
|
|
186
|
+
- decisions/ are permanent but session-originated (context + rationale)
|
|
187
|
+
- Patterns extracted from diary → moved to `learnings/` (wiki layer)
|
|
188
|
+
|
|
189
|
+
### Selection Rule
|
|
190
|
+
|
|
191
|
+
| Content Type | Target | Example |
|
|
192
|
+
|-------------|--------|---------|
|
|
193
|
+
| Ingested source summary | `sources/` | URL or text summary |
|
|
194
|
+
| Topic wiki page | `domains/` | Consolidated knowledge on a subject |
|
|
195
|
+
| Multi-source synthesis | `analyses/` | Comparison table, architecture decision |
|
|
196
|
+
| Short Q&A answer | `insights/` | Query writeback (<200 lines) |
|
|
197
|
+
| Cross-reference finding | `connections/` | Link between 2+ unrelated sources |
|
|
198
|
+
| Persistent rule/lesson | `learnings/` | YAPMA/BEST PRACTICE from reflect |
|
|
199
|
+
| Session log/delta | `diary/` | What happened this session |
|
|
200
|
+
| Decision record | `decisions/` | Why X was chosen over Y |
|
|
201
|
+
| Raw capture | `raw/` | Immutable original (URL dump, paste) |
|
|
202
|
+
|
|
203
|
+
### Anti-patterns
|
|
204
|
+
|
|
205
|
+
- Do NOT write session-specific notes to `insights/` — use `diary/`
|
|
206
|
+
- Do NOT write permanent rules to `diary/` — use `learnings/`
|
|
207
|
+
- Do NOT write decision rationale to `analyses/` — use `decisions/`
|
|
208
|
+
|
|
209
|
+
## 7. Compounding
|
|
151
210
|
|
|
152
211
|
Knowledge compounds when outputs become inputs:
|
|
153
212
|
|
|
@@ -170,7 +229,7 @@ Offer to save when:
|
|
|
170
229
|
- Large synthesis (200+ lines, 3+ sources) → analyses/
|
|
171
230
|
- Cross-cutting link → connections/
|
|
172
231
|
|
|
173
|
-
##
|
|
232
|
+
## 8. Learnings
|
|
174
233
|
|
|
175
234
|
Persistent rules extracted from reflect operations.
|
|
176
235
|
Organized by topic: `git.md`, `testing.md`, `security.md`, etc.
|
|
@@ -198,7 +257,7 @@ tags: [testing, jest, mock]
|
|
|
198
257
|
- Use `YAPMA:` / `BEST PRACTICE:` / `KRITIK:` prefixes
|
|
199
258
|
- Reflect skill proposes, user approves before writing
|
|
200
259
|
|
|
201
|
-
##
|
|
260
|
+
## 9. Naming Conventions
|
|
202
261
|
|
|
203
262
|
### Files
|
|
204
263
|
|
|
@@ -41,6 +41,82 @@ function sha256(content) {
|
|
|
41
41
|
return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Parse YAML frontmatter from markdown content.
|
|
46
|
+
* Returns { meta: { key: value }, body: string }
|
|
47
|
+
*/
|
|
48
|
+
function parseFrontmatter(content) {
|
|
49
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
50
|
+
if (!match) return { meta: {}, body: content };
|
|
51
|
+
|
|
52
|
+
const meta = {};
|
|
53
|
+
const lines = match[1].split('\n');
|
|
54
|
+
for (const line of lines) {
|
|
55
|
+
const colonIdx = line.indexOf(':');
|
|
56
|
+
if (colonIdx === -1) continue;
|
|
57
|
+
const key = line.slice(0, colonIdx).trim();
|
|
58
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
59
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
60
|
+
value = value.slice(1, -1).split(',').map((s) => s.trim().replace(/^["']|["']$/g, ''));
|
|
61
|
+
}
|
|
62
|
+
if (typeof value === 'string') {
|
|
63
|
+
value = value.replace(/^["']|["']$/g, '');
|
|
64
|
+
}
|
|
65
|
+
meta[key] = value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const bodyStart = content.indexOf('---', 3);
|
|
69
|
+
const body = bodyStart !== -1 ? content.slice(bodyStart + 3).replace(/^\r?\n/, '') : content;
|
|
70
|
+
|
|
71
|
+
return { meta, body };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Extract FTS5 metadata from parsed frontmatter + file path.
|
|
76
|
+
* Returns { slug, description, type, category, title }
|
|
77
|
+
*/
|
|
78
|
+
function extractFtsMetadata(meta, body, filePath, baseDir) {
|
|
79
|
+
const slug = meta.slug || path.basename(filePath, '.md');
|
|
80
|
+
const description = meta.description || '';
|
|
81
|
+
const type = meta.type || '';
|
|
82
|
+
const relativePath = baseDir ? path.relative(baseDir, filePath) : filePath;
|
|
83
|
+
const category = path.dirname(relativePath).split(path.sep)[0] || 'root';
|
|
84
|
+
let title = meta.title || meta.name || '';
|
|
85
|
+
if (!title) {
|
|
86
|
+
const headingMatch = body.match(/^#\s+(.+)/m);
|
|
87
|
+
title = headingMatch ? headingMatch[1].trim() : path.basename(filePath, '.md');
|
|
88
|
+
}
|
|
89
|
+
let tags = '';
|
|
90
|
+
if (meta.tags) {
|
|
91
|
+
tags = Array.isArray(meta.tags) ? meta.tags.join(', ') : String(meta.tags);
|
|
92
|
+
}
|
|
93
|
+
const quality = meta.quality !== undefined && meta.quality !== null ? meta.quality : null;
|
|
94
|
+
return { slug, description, type, category, title, tags, quality };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Shared SQL constants to prevent drift across indexing paths.
|
|
99
|
+
*/
|
|
100
|
+
const SQL_FTS_CREATE =
|
|
101
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS mindlore_fts USING fts5(path UNINDEXED, slug, description, type UNINDEXED, category, title, content, tags, quality UNINDEXED, tokenize='porter unicode61')";
|
|
102
|
+
|
|
103
|
+
const SQL_FTS_INSERT =
|
|
104
|
+
'INSERT INTO mindlore_fts (path, slug, description, type, category, title, content, tags, quality) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)';
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Extract headings (h1-h3) from markdown content.
|
|
108
|
+
*/
|
|
109
|
+
function extractHeadings(content, max) {
|
|
110
|
+
const headings = [];
|
|
111
|
+
for (const line of content.split('\n')) {
|
|
112
|
+
if (/^#{1,3}\s/.test(line)) {
|
|
113
|
+
headings.push(line.replace(/^#+\s*/, '').trim());
|
|
114
|
+
if (headings.length >= max) break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return headings;
|
|
118
|
+
}
|
|
119
|
+
|
|
44
120
|
function requireDatabase() {
|
|
45
121
|
try {
|
|
46
122
|
return require('better-sqlite3');
|
|
@@ -77,6 +153,30 @@ function getAllMdFiles(dir, skip) {
|
|
|
77
153
|
return results;
|
|
78
154
|
}
|
|
79
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Read CC hook stdin and parse JSON envelope.
|
|
158
|
+
* Returns the value of the first matching field, or raw text as fallback.
|
|
159
|
+
* @param {string[]} fields - Priority-ordered field names to extract
|
|
160
|
+
*/
|
|
161
|
+
function readHookStdin(fields) {
|
|
162
|
+
let input = '';
|
|
163
|
+
try {
|
|
164
|
+
input = fs.readFileSync(0, 'utf8').trim();
|
|
165
|
+
} catch (_err) {
|
|
166
|
+
return '';
|
|
167
|
+
}
|
|
168
|
+
if (!input) return '';
|
|
169
|
+
try {
|
|
170
|
+
const parsed = JSON.parse(input);
|
|
171
|
+
for (const f of fields) {
|
|
172
|
+
if (parsed[f]) return parsed[f];
|
|
173
|
+
}
|
|
174
|
+
} catch (_err) {
|
|
175
|
+
// plain text
|
|
176
|
+
}
|
|
177
|
+
return input;
|
|
178
|
+
}
|
|
179
|
+
|
|
80
180
|
module.exports = {
|
|
81
181
|
MINDLORE_DIR,
|
|
82
182
|
DB_NAME,
|
|
@@ -84,6 +184,12 @@ module.exports = {
|
|
|
84
184
|
findMindloreDir,
|
|
85
185
|
getLatestDelta,
|
|
86
186
|
sha256,
|
|
187
|
+
parseFrontmatter,
|
|
188
|
+
extractFtsMetadata,
|
|
189
|
+
readHookStdin,
|
|
190
|
+
SQL_FTS_CREATE,
|
|
191
|
+
SQL_FTS_INSERT,
|
|
192
|
+
extractHeadings,
|
|
87
193
|
requireDatabase,
|
|
88
194
|
openDatabase,
|
|
89
195
|
getAllMdFiles,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-decision-detector — UserPromptSubmit hook
|
|
6
|
+
*
|
|
7
|
+
* Detects decision signals in user messages (TR + EN).
|
|
8
|
+
* Outputs a suggestion to record the decision via /mindlore-decide.
|
|
9
|
+
* Does NOT block (exit 0) — advisory only.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { findMindloreDir, readHookStdin } = require('./lib/mindlore-common.cjs');
|
|
13
|
+
|
|
14
|
+
const SIGNALS_TR = [
|
|
15
|
+
'karar verdik', 'karar verildi', 'kararlastirdik', 'kararlaştırdık',
|
|
16
|
+
'şunu seçtik', 'sunu sectik', 'bunu yapmayalım', 'bunu yapmayalim',
|
|
17
|
+
'yerine', 'tercih ettik', 'onaylandi', 'onaylandı', 'kesinleşti', 'kesinlesti',
|
|
18
|
+
'vazgeçtik', 'vazgectik', 'iptal ettik',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const SIGNALS_EN = [
|
|
22
|
+
'decided', 'decision made', "let's go with", 'lets go with',
|
|
23
|
+
"we'll use", 'well use', 'approved', 'settled on',
|
|
24
|
+
'going with', 'chosen', 'finalized', 'rejected',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function detectDecision(text) {
|
|
28
|
+
const lower = text.toLowerCase();
|
|
29
|
+
for (const signal of SIGNALS_TR) {
|
|
30
|
+
if (lower.includes(signal)) return signal;
|
|
31
|
+
}
|
|
32
|
+
for (const signal of SIGNALS_EN) {
|
|
33
|
+
if (lower.includes(signal)) return signal;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function main() {
|
|
39
|
+
const baseDir = findMindloreDir();
|
|
40
|
+
if (!baseDir) return;
|
|
41
|
+
|
|
42
|
+
const userText = readHookStdin(['prompt', 'content', 'message']);
|
|
43
|
+
if (!userText || userText.length < 10) return;
|
|
44
|
+
|
|
45
|
+
const signal = detectDecision(userText);
|
|
46
|
+
if (signal) {
|
|
47
|
+
process.stdout.write(`[Mindlore: Karar sinyali tespit edildi ("${signal}") — /mindlore-decide record ile kaydetmek ister misin?]\n`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
main();
|
|
@@ -13,46 +13,33 @@
|
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
-
const { MINDLORE_DIR, DB_NAME, sha256,
|
|
16
|
+
const { MINDLORE_DIR, DB_NAME, sha256, openDatabase, getAllMdFiles, parseFrontmatter, extractFtsMetadata, SQL_FTS_INSERT, readHookStdin } = require('./lib/mindlore-common.cjs');
|
|
17
17
|
|
|
18
18
|
function main() {
|
|
19
|
-
|
|
20
|
-
let input = '';
|
|
21
|
-
try {
|
|
22
|
-
input = fs.readFileSync(0, 'utf8').trim();
|
|
23
|
-
} catch (_err) {
|
|
24
|
-
// No stdin — skip
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
let filePath = '';
|
|
28
|
-
try {
|
|
29
|
-
const parsed = JSON.parse(input);
|
|
30
|
-
filePath = parsed.path || parsed.file_path || '';
|
|
31
|
-
} catch (_err) {
|
|
32
|
-
filePath = input;
|
|
33
|
-
}
|
|
19
|
+
const filePath = readHookStdin(['path', 'file_path']);
|
|
34
20
|
|
|
35
21
|
// Only trigger on .mindlore/ changes (empty filePath = skip)
|
|
36
22
|
if (!filePath || !filePath.includes(MINDLORE_DIR)) return;
|
|
37
23
|
|
|
24
|
+
// Skip if this is a single .md file change — mindlore-index.cjs handles those.
|
|
25
|
+
// This hook is for bulk changes (git pull, manual batch edits).
|
|
26
|
+
if (filePath.endsWith('.md')) return;
|
|
27
|
+
|
|
38
28
|
const baseDir = path.join(process.cwd(), MINDLORE_DIR);
|
|
39
29
|
if (!fs.existsSync(baseDir)) return;
|
|
40
30
|
|
|
41
31
|
const dbPath = path.join(baseDir, DB_NAME);
|
|
42
32
|
if (!fs.existsSync(dbPath)) return;
|
|
43
33
|
|
|
44
|
-
const
|
|
45
|
-
if (!
|
|
46
|
-
|
|
47
|
-
const db = new Database(dbPath);
|
|
48
|
-
db.pragma('journal_mode = WAL');
|
|
34
|
+
const db = openDatabase(dbPath);
|
|
35
|
+
if (!db) return;
|
|
49
36
|
|
|
50
37
|
const mdFiles = getAllMdFiles(baseDir);
|
|
51
38
|
let synced = 0;
|
|
52
39
|
|
|
53
40
|
const getHash = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?');
|
|
54
41
|
const deleteFts = db.prepare('DELETE FROM mindlore_fts WHERE path = ?');
|
|
55
|
-
const insertFts = db.prepare(
|
|
42
|
+
const insertFts = db.prepare(SQL_FTS_INSERT);
|
|
56
43
|
const upsertHash = db.prepare(`
|
|
57
44
|
INSERT INTO file_hashes (path, content_hash, last_indexed)
|
|
58
45
|
VALUES (?, ?, ?)
|
|
@@ -72,8 +59,10 @@ function main() {
|
|
|
72
59
|
const existing = getHash.get(file);
|
|
73
60
|
if (existing && existing.content_hash === hash) continue;
|
|
74
61
|
|
|
62
|
+
const { meta, body } = parseFrontmatter(content);
|
|
63
|
+
const { slug, description, type, category, title, tags, quality } = extractFtsMetadata(meta, body, file, baseDir);
|
|
75
64
|
deleteFts.run(file);
|
|
76
|
-
insertFts.run(file,
|
|
65
|
+
insertFts.run(file, slug, description, type, category, title, body, tags, quality);
|
|
77
66
|
upsertHash.run(file, hash, now);
|
|
78
67
|
synced++;
|
|
79
68
|
}
|
|
@@ -84,7 +73,7 @@ function main() {
|
|
|
84
73
|
}
|
|
85
74
|
|
|
86
75
|
if (synced > 0) {
|
|
87
|
-
process.
|
|
76
|
+
process.stdout.write(`[Mindlore FTS5 Sync: ${synced} files re-indexed]\n`);
|
|
88
77
|
}
|
|
89
78
|
}
|
|
90
79
|
|
package/hooks/mindlore-index.cjs
CHANGED
|
@@ -10,24 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
const fs = require('fs');
|
|
12
12
|
const path = require('path');
|
|
13
|
-
const { MINDLORE_DIR, DB_NAME, SKIP_FILES, sha256,
|
|
13
|
+
const { MINDLORE_DIR, DB_NAME, SKIP_FILES, sha256, openDatabase, parseFrontmatter, extractFtsMetadata, SQL_FTS_INSERT, readHookStdin } = require('./lib/mindlore-common.cjs');
|
|
14
14
|
|
|
15
15
|
function main() {
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
input = fs.readFileSync(0, 'utf8').trim();
|
|
19
|
-
} catch (_err) {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
let filePath = '';
|
|
24
|
-
try {
|
|
25
|
-
const parsed = JSON.parse(input);
|
|
26
|
-
filePath = parsed.path || parsed.file_path || '';
|
|
27
|
-
} catch (_err) {
|
|
28
|
-
filePath = input;
|
|
29
|
-
}
|
|
30
|
-
|
|
16
|
+
const filePath = readHookStdin(['path', 'file_path']);
|
|
31
17
|
if (!filePath) return;
|
|
32
18
|
|
|
33
19
|
// Only process .md files inside .mindlore/
|
|
@@ -43,13 +29,10 @@ function main() {
|
|
|
43
29
|
|
|
44
30
|
if (!fs.existsSync(dbPath)) return;
|
|
45
31
|
|
|
46
|
-
const Database = requireDatabase();
|
|
47
|
-
if (!Database) return;
|
|
48
|
-
|
|
49
32
|
if (!fs.existsSync(filePath)) {
|
|
50
33
|
// File was deleted — remove from index
|
|
51
|
-
const db =
|
|
52
|
-
db
|
|
34
|
+
const db = openDatabase(dbPath);
|
|
35
|
+
if (!db) return;
|
|
53
36
|
try {
|
|
54
37
|
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
55
38
|
db.prepare('DELETE FROM file_hashes WHERE path = ?').run(filePath);
|
|
@@ -62,8 +45,8 @@ function main() {
|
|
|
62
45
|
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
63
46
|
const hash = sha256(content);
|
|
64
47
|
|
|
65
|
-
const db =
|
|
66
|
-
db
|
|
48
|
+
const db = openDatabase(dbPath);
|
|
49
|
+
if (!db) return;
|
|
67
50
|
|
|
68
51
|
try {
|
|
69
52
|
// Check if content changed
|
|
@@ -73,12 +56,13 @@ function main() {
|
|
|
73
56
|
|
|
74
57
|
if (existing && existing.content_hash === hash) return; // Unchanged
|
|
75
58
|
|
|
59
|
+
// Parse frontmatter for rich FTS5 columns
|
|
60
|
+
const { meta, body } = parseFrontmatter(content);
|
|
61
|
+
const { slug, description, type, category, title, tags, quality } = extractFtsMetadata(meta, body, filePath, baseDir);
|
|
62
|
+
|
|
76
63
|
// Update FTS5
|
|
77
64
|
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
78
|
-
db.prepare(
|
|
79
|
-
filePath,
|
|
80
|
-
content
|
|
81
|
-
);
|
|
65
|
+
db.prepare(SQL_FTS_INSERT).run(filePath, slug, description, type, category, title, body, tags, quality);
|
|
82
66
|
|
|
83
67
|
// Update hash
|
|
84
68
|
db.prepare(
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* After context compaction, re-inject session context:
|
|
8
8
|
* 1. Read INDEX.md
|
|
9
9
|
* 2. Read latest delta
|
|
10
|
-
* 3. Inject via
|
|
10
|
+
* 3. Inject via stdout (same as session-focus)
|
|
11
11
|
*
|
|
12
12
|
* This ensures the agent has knowledge context after compaction.
|
|
13
13
|
*/
|
|
@@ -39,7 +39,7 @@ function main() {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
if (output.length > 0) {
|
|
42
|
-
process.
|
|
42
|
+
process.stdout.write(output.join('\n\n') + '\n');
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-read-guard — PreToolUse hook (if: "Read")
|
|
6
|
+
*
|
|
7
|
+
* OpenWolf repeated-read pattern: detects files read multiple times
|
|
8
|
+
* in the same session and emits a soft warning.
|
|
9
|
+
* Does NOT block (exit 0) — advisory only.
|
|
10
|
+
*
|
|
11
|
+
* Storage: .mindlore/diary/_session-reads.json
|
|
12
|
+
* Cleanup: session-end hook writes stats to delta then deletes the file.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { findMindloreDir, readHookStdin } = require('./lib/mindlore-common.cjs');
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
const baseDir = findMindloreDir();
|
|
21
|
+
if (!baseDir) return;
|
|
22
|
+
|
|
23
|
+
const filePath = readHookStdin(['file_path', 'path']);
|
|
24
|
+
if (!filePath) return;
|
|
25
|
+
|
|
26
|
+
// Only track CWD-relative files, skip .mindlore/ internals
|
|
27
|
+
const cwd = process.cwd();
|
|
28
|
+
const resolved = path.resolve(filePath);
|
|
29
|
+
if (!resolved.startsWith(cwd)) return;
|
|
30
|
+
if (resolved.startsWith(path.resolve(baseDir))) return;
|
|
31
|
+
|
|
32
|
+
// Load or create session reads tracker
|
|
33
|
+
const diaryDir = path.join(baseDir, 'diary');
|
|
34
|
+
if (!fs.existsSync(diaryDir)) {
|
|
35
|
+
fs.mkdirSync(diaryDir, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const readsPath = path.join(diaryDir, '_session-reads.json');
|
|
39
|
+
let reads = {};
|
|
40
|
+
if (fs.existsSync(readsPath)) {
|
|
41
|
+
try {
|
|
42
|
+
reads = JSON.parse(fs.readFileSync(readsPath, 'utf8'));
|
|
43
|
+
} catch (_err) {
|
|
44
|
+
reads = {};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const normalizedPath = path.resolve(filePath);
|
|
49
|
+
const count = (reads[normalizedPath] || 0) + 1;
|
|
50
|
+
reads[normalizedPath] = count;
|
|
51
|
+
|
|
52
|
+
// Write updated reads
|
|
53
|
+
fs.writeFileSync(readsPath, JSON.stringify(reads, null, 2), 'utf8');
|
|
54
|
+
|
|
55
|
+
// Warn on repeated reads (2nd+ time)
|
|
56
|
+
if (count > 1) {
|
|
57
|
+
const basename = path.basename(filePath);
|
|
58
|
+
process.stderr.write(`[Mindlore: ${basename} bu session'da ${count}. kez okunuyor. Değişiklik yoksa tekrar okumayı atlayabilirsin.]\n`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
main();
|