mindlore 0.0.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/LICENSE +21 -0
- package/README.md +128 -0
- package/SCHEMA.md +221 -0
- package/hooks/lib/mindlore-common.cjs +90 -0
- package/hooks/mindlore-fts5-sync.cjs +91 -0
- package/hooks/mindlore-index.cjs +96 -0
- package/hooks/mindlore-post-compact.cjs +46 -0
- package/hooks/mindlore-pre-compact.cjs +48 -0
- package/hooks/mindlore-search.cjs +133 -0
- package/hooks/mindlore-session-end.cjs +68 -0
- package/hooks/mindlore-session-focus.cjs +42 -0
- package/package.json +55 -0
- package/plugin.json +47 -0
- package/scripts/init.cjs +335 -0
- package/scripts/lib/constants.cjs +49 -0
- package/scripts/mindlore-fts5-index.cjs +115 -0
- package/scripts/mindlore-fts5-search.cjs +122 -0
- package/scripts/mindlore-health-check.cjs +320 -0
- package/skills/mindlore-health/SKILL.md +55 -0
- package/skills/mindlore-ingest/SKILL.md +102 -0
- package/templates/INDEX.md +12 -0
- package/templates/log.md +4 -0
package/LICENSE
ADDED
|
@@ -0,0 +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.
|
package/README.md
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Mindlore
|
|
2
|
+
|
|
3
|
+
AI-native knowledge system for [Claude Code](https://claude.ai/claude-code).
|
|
4
|
+
|
|
5
|
+
Persistent, searchable, evolving knowledge base that compounds across sessions.
|
|
6
|
+
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
|
|
10
|
+
## Why
|
|
11
|
+
|
|
12
|
+
Claude Code forgets everything between sessions. Your corrections, discoveries, and decisions vanish. Mindlore gives Claude a persistent memory:
|
|
13
|
+
|
|
14
|
+
- **Knowledge persists** across sessions via FTS5-indexed Markdown files
|
|
15
|
+
- **Search happens automatically** — hooks inject relevant context as you work
|
|
16
|
+
- **Knowledge compounds** — query answers become searchable for future sessions
|
|
17
|
+
|
|
18
|
+
## Why Mindlore?
|
|
19
|
+
|
|
20
|
+
| Feature | Mindlore | Typical KB CLI | Wiki Compilers | Multi-Agent Memory |
|
|
21
|
+
|---------|----------|---------------|----------------|-------------------|
|
|
22
|
+
| Zero workflow change | Hook-based, invisible | Manual commands | Manual compile | Agent orchestration |
|
|
23
|
+
| Persistent search | FTS5 auto-indexed | Manual index | No search | Vector DB overhead |
|
|
24
|
+
| Knowledge compounding | Writeback loop | No | No | Partial |
|
|
25
|
+
| Token efficient | Progressive disclosure | Full dump | Full dump | Varies |
|
|
26
|
+
| Setup time | `npx mindlore init` | Config + setup | Complex | Complex |
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx mindlore init
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
That's it. Mindlore creates a `.mindlore/` directory, sets up hooks, and starts working.
|
|
35
|
+
|
|
36
|
+
To add your first source:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
/mindlore-ingest https://example.com/article
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Features
|
|
43
|
+
|
|
44
|
+
| Skill | Version | Description |
|
|
45
|
+
|-------|---------|-------------|
|
|
46
|
+
| `/mindlore-ingest` | v0.1 | Add knowledge sources (URL, text, file, PDF) |
|
|
47
|
+
| `/mindlore-health` | v0.1 | 16-point structural health check |
|
|
48
|
+
| `/mindlore-query` | v0.2 | Search and retrieve knowledge (4 modes) |
|
|
49
|
+
| `/mindlore-log` | v0.2 | Session logging with reflect and status |
|
|
50
|
+
| `/mindlore-decide` | v0.2 | Decision records with supersedes chain |
|
|
51
|
+
| `/mindlore-evolve` | v0.3 | Schema co-evolution and structural updates |
|
|
52
|
+
| `/mindlore-explore` | v0.3 | Cross-reference discovery between sources |
|
|
53
|
+
|
|
54
|
+
## Architecture
|
|
55
|
+
|
|
56
|
+
Knowledge flows through a compiler-like pipeline:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
raw/ Immutable source captures (URL dumps, pasted text)
|
|
60
|
+
|
|
|
61
|
+
sources/ Processed summaries (one per ingested source)
|
|
62
|
+
|
|
|
63
|
+
domains/ Topic wiki pages (accumulated knowledge by subject)
|
|
64
|
+
|
|
|
65
|
+
insights/ Query writebacks (answers that become searchable)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Nine directories, each mapping to a frontmatter `type`:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
.mindlore/
|
|
72
|
+
├── raw/ # Immutable captures
|
|
73
|
+
├── sources/ # Processed summaries
|
|
74
|
+
├── domains/ # Topic wikis
|
|
75
|
+
├── analyses/ # Large syntheses (3+ sources)
|
|
76
|
+
├── insights/ # Short Q&A writebacks
|
|
77
|
+
├── connections/ # Cross-cutting links
|
|
78
|
+
├── learnings/ # Persistent rules from reflect
|
|
79
|
+
├── diary/ # Session deltas
|
|
80
|
+
├── decisions/ # Decision records
|
|
81
|
+
├── INDEX.md # Navigation map (~15 lines)
|
|
82
|
+
├── SCHEMA.md # LLM specification
|
|
83
|
+
└── mindlore.db # FTS5 search database
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Installation
|
|
87
|
+
|
|
88
|
+
### Minimal (default)
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npx mindlore init
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Requires: Node.js 20+, `better-sqlite3` (installed automatically).
|
|
95
|
+
|
|
96
|
+
### Recommended
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npx mindlore init --recommended
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Also suggests installing:
|
|
103
|
+
- **markitdown** — better web/document extraction (URL, DOCX, YouTube)
|
|
104
|
+
- **context-mode** — token savings for large sessions
|
|
105
|
+
|
|
106
|
+
## Hooks
|
|
107
|
+
|
|
108
|
+
Mindlore works through 7 Claude Code lifecycle hooks (v0.1):
|
|
109
|
+
|
|
110
|
+
| Event | Hook | What it does |
|
|
111
|
+
|-------|------|-------------|
|
|
112
|
+
| SessionStart | session-focus | Injects last delta + INDEX |
|
|
113
|
+
| UserPromptSubmit | search | FTS5 search, top 3 results |
|
|
114
|
+
| FileChanged | index | Sync changed files to FTS5 |
|
|
115
|
+
| FileChanged | fts5-sync | Incremental batch re-index |
|
|
116
|
+
| SessionEnd | session-end | Write delta to diary/ |
|
|
117
|
+
| PreCompact | pre-compact | FTS5 flush before compaction |
|
|
118
|
+
| PostCompact | post-compact | Re-inject context |
|
|
119
|
+
|
|
120
|
+
## Inspired By
|
|
121
|
+
|
|
122
|
+
- [Andrej Karpathy](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) — LLM Knowledge Bases concept
|
|
123
|
+
- [Spisak](https://github.com/nickspisak) — Practical second brain implementations
|
|
124
|
+
- [Letta](https://github.com/letta-ai/letta-code) — Context repository pattern validation
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
[MIT](LICENSE)
|
package/SCHEMA.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# Mindlore Schema
|
|
2
|
+
|
|
3
|
+
This file is the single source of truth for how the `.mindlore/` knowledge base works.
|
|
4
|
+
It is written for LLM agents — not humans. Every rule here must be followed exactly.
|
|
5
|
+
|
|
6
|
+
## 1. Identity
|
|
7
|
+
|
|
8
|
+
Mindlore is an AI-native knowledge system. It stores, indexes, and evolves knowledge
|
|
9
|
+
across Claude Code sessions. Knowledge lives in `.mindlore/` as plain Markdown files
|
|
10
|
+
with YAML frontmatter. Search is powered by FTS5 (SQLite full-text search).
|
|
11
|
+
|
|
12
|
+
## 2. Directory Structure
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
.mindlore/
|
|
16
|
+
├── raw/ # Immutable source captures (URL dumps, pasted text)
|
|
17
|
+
├── sources/ # Processed source summaries (one per ingested source)
|
|
18
|
+
├── domains/ # Topic wiki pages (entities + concepts)
|
|
19
|
+
├── analyses/ # Large syntheses (200+ lines, 3+ sources)
|
|
20
|
+
├── insights/ # Short Q&A writebacks (<200 lines, 1-2 sources)
|
|
21
|
+
├── connections/ # Cross-cutting links between 2+ sources
|
|
22
|
+
├── learnings/ # Persistent rules extracted from reflect (topic-based)
|
|
23
|
+
├── diary/ # Session logs, delta files (delta-YYYY-MM-DD-HHmm.md)
|
|
24
|
+
├── decisions/ # Decision records with context and rationale
|
|
25
|
+
├── INDEX.md # Minimal navigation map (~15-20 lines, fixed size)
|
|
26
|
+
├── SCHEMA.md # This file (copied from repo template)
|
|
27
|
+
├── log.md # Operation log (append-only)
|
|
28
|
+
└── mindlore.db # FTS5 search database (SQLite)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Directory Rules
|
|
32
|
+
|
|
33
|
+
- Each directory corresponds to exactly one frontmatter `type` value
|
|
34
|
+
- `type: raw` → `raw/`, `type: source` → `sources/`, etc.
|
|
35
|
+
- Files MUST live in the directory matching their `type`
|
|
36
|
+
- The health check script validates this cross-reference
|
|
37
|
+
|
|
38
|
+
## 3. Frontmatter
|
|
39
|
+
|
|
40
|
+
Every `.md` file in `.mindlore/` MUST have YAML frontmatter. Format:
|
|
41
|
+
|
|
42
|
+
```yaml
|
|
43
|
+
---
|
|
44
|
+
slug: kebab-case-unique-identifier
|
|
45
|
+
type: raw|source|domain|analysis|insight|connection|learning|decision|diary
|
|
46
|
+
title: Human-readable title
|
|
47
|
+
tags: [tag1, tag2]
|
|
48
|
+
---
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Required Fields (all types)
|
|
52
|
+
|
|
53
|
+
| Field | Format | Rule |
|
|
54
|
+
|-------|--------|------|
|
|
55
|
+
| `slug` | kebab-case | Unique within directory. Used as filename: `{slug}.md` |
|
|
56
|
+
| `type` | enum | Must match the parent directory (see Section 2) |
|
|
57
|
+
|
|
58
|
+
### Type-Specific Fields
|
|
59
|
+
|
|
60
|
+
| Type | Required Fields | Optional |
|
|
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 |
|
|
70
|
+
| `diary` | slug, type, date | — (hook adds automatically) |
|
|
71
|
+
|
|
72
|
+
### Field Value Rules
|
|
73
|
+
|
|
74
|
+
- `quality`: `high` | `medium` | `low`
|
|
75
|
+
- `confidence`: `high` | `medium` | `low` (how certain is this analysis)
|
|
76
|
+
- `ingested`: `true` | `false` (has this source been processed into domains)
|
|
77
|
+
- `sources_used`: list of slugs referenced in the analysis
|
|
78
|
+
- `supersedes`: slug of the decision this one replaces
|
|
79
|
+
- `date`: ISO 8601 date (YYYY-MM-DD)
|
|
80
|
+
|
|
81
|
+
## 4. Seven Operations
|
|
82
|
+
|
|
83
|
+
### 4.1 Ingest (skill: /mindlore-ingest)
|
|
84
|
+
|
|
85
|
+
Add new knowledge. Flow: capture → raw/ → process → sources/ → update domains/ → FTS5.
|
|
86
|
+
|
|
87
|
+
- URL mode: markitdown CLI (if available) or WebFetch → raw/ → Sonnet summarizes → sources/
|
|
88
|
+
- Text mode: user paste → raw/ → summarize → sources/
|
|
89
|
+
- PDF mode: CC Read tool (max 20 pages/request) → raw/ → summarize → sources/
|
|
90
|
+
- **markitdown is NOT used for PDF** — quality is poor. Use CC Read tool or Marker/Chandra (v0.3+)
|
|
91
|
+
|
|
92
|
+
### 4.2 Query (skill: /mindlore-query, v0.2 — PLANNED, not yet implemented)
|
|
93
|
+
|
|
94
|
+
Search and retrieve knowledge. Four modes:
|
|
95
|
+
- `search`: FTS5 keyword search, return top 3 matches with snippets
|
|
96
|
+
- `ask`: Natural language question → FTS5 → read relevant files → synthesize answer
|
|
97
|
+
- `stats`: Knowledge base statistics (counts by type, recent activity)
|
|
98
|
+
- `brief`: Quick context on a topic (read domain page if exists)
|
|
99
|
+
|
|
100
|
+
### 4.3 Health (skill: /mindlore-health)
|
|
101
|
+
|
|
102
|
+
Run 16-point structural check:
|
|
103
|
+
- 9 directory existence checks
|
|
104
|
+
- SCHEMA.md parse validation
|
|
105
|
+
- INDEX.md format check (~15-20 lines)
|
|
106
|
+
- mindlore.db FTS5 integrity
|
|
107
|
+
- Orphan file detection (files not in FTS5)
|
|
108
|
+
- Frontmatter validation (type-directory cross-reference)
|
|
109
|
+
|
|
110
|
+
### 4.4 Log (skill: /mindlore-log, v0.2 — PLANNED, not yet implemented)
|
|
111
|
+
|
|
112
|
+
Session logging with four modes:
|
|
113
|
+
- `log`: Write session/task record to diary/
|
|
114
|
+
- `reflect`: Scan old deltas, extract patterns, move to learnings/
|
|
115
|
+
- `status`: Recent N sessions summary, trends, open items
|
|
116
|
+
- `save`: Structured delta + log.md append + wiki update
|
|
117
|
+
|
|
118
|
+
### 4.5 Decide (skill: /mindlore-decide, v0.2 — PLANNED, not yet implemented)
|
|
119
|
+
|
|
120
|
+
Record decisions with context, options considered, rationale, and outcome.
|
|
121
|
+
Supports `supersedes` chain for decision evolution.
|
|
122
|
+
|
|
123
|
+
### 4.6 Evolve (skill: /mindlore-evolve, v0.3 — PLANNED, not yet implemented)
|
|
124
|
+
|
|
125
|
+
Schema co-evolution. Scan domains + sources, suggest structural updates.
|
|
126
|
+
Run monthly or after major changes.
|
|
127
|
+
|
|
128
|
+
### 4.7 Explore (skill: /mindlore-explore, v0.3 — PLANNED, not yet implemented)
|
|
129
|
+
|
|
130
|
+
Discover unexpected connections between sources. Cross-reference analysis.
|
|
131
|
+
|
|
132
|
+
## 5. Search Behavior
|
|
133
|
+
|
|
134
|
+
### FTS5 Search (hooks + scripts)
|
|
135
|
+
|
|
136
|
+
- Database: `.mindlore/mindlore.db`
|
|
137
|
+
- Table: `mindlore_fts` (columns: path, content)
|
|
138
|
+
- Dedup: `file_hashes` table with SHA256 content-hash
|
|
139
|
+
- Tokenizer: `unicode61`
|
|
140
|
+
- Max results: 3 per query (BM25 ranking)
|
|
141
|
+
- Hook injects: file path + first 2 headings
|
|
142
|
+
|
|
143
|
+
### Search Flow (UserPromptSubmit hook)
|
|
144
|
+
|
|
145
|
+
1. Extract keywords from user prompt
|
|
146
|
+
2. Query FTS5 with BM25 ranking
|
|
147
|
+
3. Return max 3 results as stderr additionalContext
|
|
148
|
+
4. Agent reads full file only if needed (progressive disclosure)
|
|
149
|
+
|
|
150
|
+
## 6. Compounding
|
|
151
|
+
|
|
152
|
+
Knowledge compounds when outputs become inputs:
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
Query answer → writeback to insights/ → FTS5 indexes → next query finds it
|
|
156
|
+
Reflect → patterns to learnings/ → session-focus injects → agent applies
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Writeback Triggers
|
|
160
|
+
|
|
161
|
+
Offer to save when:
|
|
162
|
+
- Comparison table generated (X vs Y)
|
|
163
|
+
- Architectural decision or evaluation made
|
|
164
|
+
- 3+ sources synthesized
|
|
165
|
+
- User says "save this" or "remember this"
|
|
166
|
+
|
|
167
|
+
### Writeback Rules
|
|
168
|
+
|
|
169
|
+
- Short answer (<200 lines) → insights/
|
|
170
|
+
- Large synthesis (200+ lines, 3+ sources) → analyses/
|
|
171
|
+
- Cross-cutting link → connections/
|
|
172
|
+
|
|
173
|
+
## 7. Learnings
|
|
174
|
+
|
|
175
|
+
Persistent rules extracted from reflect operations.
|
|
176
|
+
Organized by topic: `git.md`, `testing.md`, `security.md`, etc.
|
|
177
|
+
|
|
178
|
+
### Format
|
|
179
|
+
|
|
180
|
+
```markdown
|
|
181
|
+
---
|
|
182
|
+
slug: testing
|
|
183
|
+
type: learning
|
|
184
|
+
title: Testing Learnings
|
|
185
|
+
tags: [testing, jest, mock]
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
# Testing Learnings
|
|
189
|
+
|
|
190
|
+
- YAPMA: Error handling testinde mock'u dolayli tetikleme
|
|
191
|
+
- BEST PRACTICE: Side-effect'li moduller eklerken TUM test'lerde mock ekle
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Rules
|
|
195
|
+
|
|
196
|
+
- One file per topic (not per lesson)
|
|
197
|
+
- Append new learnings to existing topic file
|
|
198
|
+
- Use `YAPMA:` / `BEST PRACTICE:` / `KRITIK:` prefixes
|
|
199
|
+
- Reflect skill proposes, user approves before writing
|
|
200
|
+
|
|
201
|
+
## 8. Naming Conventions
|
|
202
|
+
|
|
203
|
+
### Files
|
|
204
|
+
|
|
205
|
+
- Filename = `{slug}.md` (kebab-case)
|
|
206
|
+
- Diary: `delta-YYYY-MM-DD-HHmm.md`
|
|
207
|
+
- No spaces, no uppercase in filenames
|
|
208
|
+
|
|
209
|
+
### Slugs
|
|
210
|
+
|
|
211
|
+
- kebab-case only: `my-analysis-topic`
|
|
212
|
+
- Unique within directory
|
|
213
|
+
- Descriptive but concise (3-5 words max)
|
|
214
|
+
|
|
215
|
+
### INDEX.md
|
|
216
|
+
|
|
217
|
+
- Fixed size: ~15-20 lines
|
|
218
|
+
- Domain list (entities/concepts headings)
|
|
219
|
+
- Stats line: "N source, N analysis, N total"
|
|
220
|
+
- Last 5 added (initially empty)
|
|
221
|
+
- NO full file listing — discovery via FTS5
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared utilities for mindlore hooks.
|
|
5
|
+
* Eliminates duplication of findMindloreDir, getLatestDelta, sha256, etc.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const crypto = require('crypto');
|
|
11
|
+
const os = require('os');
|
|
12
|
+
|
|
13
|
+
const MINDLORE_DIR = '.mindlore';
|
|
14
|
+
const DB_NAME = 'mindlore.db';
|
|
15
|
+
const SKIP_FILES = new Set(['INDEX.md', 'SCHEMA.md', 'log.md']);
|
|
16
|
+
|
|
17
|
+
function findMindloreDir() {
|
|
18
|
+
const projectDir = path.join(process.cwd(), MINDLORE_DIR);
|
|
19
|
+
if (fs.existsSync(projectDir)) return projectDir;
|
|
20
|
+
|
|
21
|
+
const globalDir = path.join(os.homedir(), MINDLORE_DIR);
|
|
22
|
+
if (fs.existsSync(globalDir)) return globalDir;
|
|
23
|
+
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getLatestDelta(diaryDir) {
|
|
28
|
+
if (!fs.existsSync(diaryDir)) return null;
|
|
29
|
+
|
|
30
|
+
const deltas = fs
|
|
31
|
+
.readdirSync(diaryDir)
|
|
32
|
+
.filter((f) => f.startsWith('delta-') && f.endsWith('.md'))
|
|
33
|
+
.sort()
|
|
34
|
+
.reverse();
|
|
35
|
+
|
|
36
|
+
if (deltas.length === 0) return null;
|
|
37
|
+
return path.join(diaryDir, deltas[0]);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function sha256(content) {
|
|
41
|
+
return crypto.createHash('sha256').update(content, 'utf8').digest('hex');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function requireDatabase() {
|
|
45
|
+
try {
|
|
46
|
+
return require('better-sqlite3');
|
|
47
|
+
} catch (_err) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function openDatabase(dbPath, opts) {
|
|
53
|
+
const Database = requireDatabase();
|
|
54
|
+
if (!Database) return null;
|
|
55
|
+
|
|
56
|
+
const db = new Database(dbPath, opts);
|
|
57
|
+
if (!opts || !opts.readonly) {
|
|
58
|
+
db.pragma('journal_mode = WAL');
|
|
59
|
+
}
|
|
60
|
+
return db;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function getAllMdFiles(dir, skip) {
|
|
64
|
+
const skipSet = skip || SKIP_FILES;
|
|
65
|
+
const results = [];
|
|
66
|
+
if (!fs.existsSync(dir)) return results;
|
|
67
|
+
|
|
68
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const fullPath = path.join(dir, entry.name);
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
results.push(...getAllMdFiles(fullPath, skipSet));
|
|
73
|
+
} else if (entry.name.endsWith('.md') && !skipSet.has(entry.name)) {
|
|
74
|
+
results.push(fullPath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return results;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
MINDLORE_DIR,
|
|
82
|
+
DB_NAME,
|
|
83
|
+
SKIP_FILES,
|
|
84
|
+
findMindloreDir,
|
|
85
|
+
getLatestDelta,
|
|
86
|
+
sha256,
|
|
87
|
+
requireDatabase,
|
|
88
|
+
openDatabase,
|
|
89
|
+
getAllMdFiles,
|
|
90
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-fts5-sync — FileChanged hook (incremental re-index)
|
|
6
|
+
*
|
|
7
|
+
* Handles bulk file changes by checking all .mindlore/ .md files
|
|
8
|
+
* against their content hashes and re-indexing only changed ones.
|
|
9
|
+
*
|
|
10
|
+
* Lightweight complement to mindlore-index.cjs which handles single files.
|
|
11
|
+
* This hook catches cases where multiple files change at once (e.g., git pull).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { MINDLORE_DIR, DB_NAME, sha256, requireDatabase, getAllMdFiles } = require('./lib/mindlore-common.cjs');
|
|
17
|
+
|
|
18
|
+
function main() {
|
|
19
|
+
// Read stdin to check if this is a .mindlore/ file change
|
|
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
|
+
}
|
|
34
|
+
|
|
35
|
+
// Only trigger on .mindlore/ changes (empty filePath = skip)
|
|
36
|
+
if (!filePath || !filePath.includes(MINDLORE_DIR)) return;
|
|
37
|
+
|
|
38
|
+
const baseDir = path.join(process.cwd(), MINDLORE_DIR);
|
|
39
|
+
if (!fs.existsSync(baseDir)) return;
|
|
40
|
+
|
|
41
|
+
const dbPath = path.join(baseDir, DB_NAME);
|
|
42
|
+
if (!fs.existsSync(dbPath)) return;
|
|
43
|
+
|
|
44
|
+
const Database = requireDatabase();
|
|
45
|
+
if (!Database) return;
|
|
46
|
+
|
|
47
|
+
const db = new Database(dbPath);
|
|
48
|
+
db.pragma('journal_mode = WAL');
|
|
49
|
+
|
|
50
|
+
const mdFiles = getAllMdFiles(baseDir);
|
|
51
|
+
let synced = 0;
|
|
52
|
+
|
|
53
|
+
const getHash = db.prepare('SELECT content_hash FROM file_hashes WHERE path = ?');
|
|
54
|
+
const deleteFts = db.prepare('DELETE FROM mindlore_fts WHERE path = ?');
|
|
55
|
+
const insertFts = db.prepare('INSERT INTO mindlore_fts (path, content) VALUES (?, ?)');
|
|
56
|
+
const upsertHash = db.prepare(`
|
|
57
|
+
INSERT INTO file_hashes (path, content_hash, last_indexed)
|
|
58
|
+
VALUES (?, ?, ?)
|
|
59
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
60
|
+
content_hash = excluded.content_hash,
|
|
61
|
+
last_indexed = excluded.last_indexed
|
|
62
|
+
`);
|
|
63
|
+
|
|
64
|
+
const now = new Date().toISOString();
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const transaction = db.transaction(() => {
|
|
68
|
+
for (const file of mdFiles) {
|
|
69
|
+
const content = fs.readFileSync(file, 'utf8').replace(/\r\n/g, '\n');
|
|
70
|
+
const hash = sha256(content);
|
|
71
|
+
|
|
72
|
+
const existing = getHash.get(file);
|
|
73
|
+
if (existing && existing.content_hash === hash) continue;
|
|
74
|
+
|
|
75
|
+
deleteFts.run(file);
|
|
76
|
+
insertFts.run(file, content);
|
|
77
|
+
upsertHash.run(file, hash, now);
|
|
78
|
+
synced++;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
transaction();
|
|
82
|
+
} finally {
|
|
83
|
+
db.close();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (synced > 0) {
|
|
87
|
+
process.stderr.write(`[Mindlore FTS5 Sync: ${synced} files re-indexed]\n`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
main();
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-index — FileChanged hook
|
|
6
|
+
*
|
|
7
|
+
* When a .md file in .mindlore/ changes, update its FTS5 entry.
|
|
8
|
+
* Reads changed file path from stdin (CC FileChanged event).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const { MINDLORE_DIR, DB_NAME, SKIP_FILES, sha256, requireDatabase } = require('./lib/mindlore-common.cjs');
|
|
14
|
+
|
|
15
|
+
function main() {
|
|
16
|
+
let input = '';
|
|
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
|
+
|
|
31
|
+
if (!filePath) return;
|
|
32
|
+
|
|
33
|
+
// Only process .md files inside .mindlore/
|
|
34
|
+
if (!filePath.includes(MINDLORE_DIR) || !filePath.endsWith('.md')) return;
|
|
35
|
+
|
|
36
|
+
const fileName = path.basename(filePath);
|
|
37
|
+
if (SKIP_FILES.has(fileName)) return;
|
|
38
|
+
|
|
39
|
+
// Find the .mindlore dir from the file path
|
|
40
|
+
const mindloreIdx = filePath.indexOf(MINDLORE_DIR);
|
|
41
|
+
const baseDir = filePath.slice(0, mindloreIdx + MINDLORE_DIR.length);
|
|
42
|
+
const dbPath = path.join(baseDir, DB_NAME);
|
|
43
|
+
|
|
44
|
+
if (!fs.existsSync(dbPath)) return;
|
|
45
|
+
|
|
46
|
+
const Database = requireDatabase();
|
|
47
|
+
if (!Database) return;
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(filePath)) {
|
|
50
|
+
// File was deleted — remove from index
|
|
51
|
+
const db = new Database(dbPath);
|
|
52
|
+
db.pragma('journal_mode = WAL');
|
|
53
|
+
try {
|
|
54
|
+
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
55
|
+
db.prepare('DELETE FROM file_hashes WHERE path = ?').run(filePath);
|
|
56
|
+
} finally {
|
|
57
|
+
db.close();
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const content = fs.readFileSync(filePath, 'utf8').replace(/\r\n/g, '\n');
|
|
63
|
+
const hash = sha256(content);
|
|
64
|
+
|
|
65
|
+
const db = new Database(dbPath);
|
|
66
|
+
db.pragma('journal_mode = WAL');
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// Check if content changed
|
|
70
|
+
const existing = db
|
|
71
|
+
.prepare('SELECT content_hash FROM file_hashes WHERE path = ?')
|
|
72
|
+
.get(filePath);
|
|
73
|
+
|
|
74
|
+
if (existing && existing.content_hash === hash) return; // Unchanged
|
|
75
|
+
|
|
76
|
+
// Update FTS5
|
|
77
|
+
db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
|
|
78
|
+
db.prepare('INSERT INTO mindlore_fts (path, content) VALUES (?, ?)').run(
|
|
79
|
+
filePath,
|
|
80
|
+
content
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Update hash
|
|
84
|
+
db.prepare(
|
|
85
|
+
`INSERT INTO file_hashes (path, content_hash, last_indexed)
|
|
86
|
+
VALUES (?, ?, ?)
|
|
87
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
88
|
+
content_hash = excluded.content_hash,
|
|
89
|
+
last_indexed = excluded.last_indexed`
|
|
90
|
+
).run(filePath, hash, new Date().toISOString());
|
|
91
|
+
} finally {
|
|
92
|
+
db.close();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mindlore-post-compact — PostCompact hook
|
|
6
|
+
*
|
|
7
|
+
* After context compaction, re-inject session context:
|
|
8
|
+
* 1. Read INDEX.md
|
|
9
|
+
* 2. Read latest delta
|
|
10
|
+
* 3. Inject via stderr (same as session-focus)
|
|
11
|
+
*
|
|
12
|
+
* This ensures the agent has knowledge context after compaction.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { findMindloreDir, getLatestDelta } = require('./lib/mindlore-common.cjs');
|
|
18
|
+
|
|
19
|
+
function main() {
|
|
20
|
+
const baseDir = findMindloreDir();
|
|
21
|
+
if (!baseDir) return;
|
|
22
|
+
|
|
23
|
+
const output = [];
|
|
24
|
+
|
|
25
|
+
// Re-inject INDEX.md
|
|
26
|
+
const indexPath = path.join(baseDir, 'INDEX.md');
|
|
27
|
+
if (fs.existsSync(indexPath)) {
|
|
28
|
+
const content = fs.readFileSync(indexPath, 'utf8').trim();
|
|
29
|
+
output.push(`[Mindlore INDEX (post-compact)]\n${content}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Re-inject latest delta
|
|
33
|
+
const diaryDir = path.join(baseDir, 'diary');
|
|
34
|
+
const latestDelta = getLatestDelta(diaryDir);
|
|
35
|
+
if (latestDelta) {
|
|
36
|
+
const deltaContent = fs.readFileSync(latestDelta, 'utf8').trim();
|
|
37
|
+
const deltaName = path.basename(latestDelta);
|
|
38
|
+
output.push(`[Mindlore Delta (post-compact): ${deltaName}]\n${deltaContent}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (output.length > 0) {
|
|
42
|
+
process.stderr.write(output.join('\n\n') + '\n');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
main();
|