mindlore 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +1 -1
  2. package/dist/scripts/init.js +33 -21
  3. package/dist/scripts/init.js.map +1 -1
  4. package/dist/scripts/lib/episodes.d.ts +3 -3
  5. package/dist/scripts/lib/episodes.d.ts.map +1 -1
  6. package/dist/scripts/lib/episodes.js +3 -3
  7. package/dist/scripts/lib/episodes.js.map +1 -1
  8. package/dist/scripts/mindlore-backup.d.ts +1 -0
  9. package/dist/scripts/mindlore-backup.d.ts.map +1 -1
  10. package/dist/scripts/mindlore-backup.js +125 -24
  11. package/dist/scripts/mindlore-backup.js.map +1 -1
  12. package/dist/scripts/mindlore-episodes.js +2 -2
  13. package/dist/scripts/mindlore-episodes.js.map +1 -1
  14. package/dist/tests/backup.test.js +12 -7
  15. package/dist/tests/backup.test.js.map +1 -1
  16. package/dist/tests/diary.test.js +3 -3
  17. package/dist/tests/diary.test.js.map +1 -1
  18. package/dist/tests/nomination.test.d.ts +2 -0
  19. package/dist/tests/nomination.test.d.ts.map +1 -0
  20. package/dist/tests/nomination.test.js +94 -0
  21. package/dist/tests/nomination.test.js.map +1 -0
  22. package/dist/tests/session-focus.test.js +82 -0
  23. package/dist/tests/session-focus.test.js.map +1 -1
  24. package/dist/tests/supersedes-chain.test.d.ts +2 -0
  25. package/dist/tests/supersedes-chain.test.d.ts.map +1 -0
  26. package/dist/tests/supersedes-chain.test.js +109 -0
  27. package/dist/tests/supersedes-chain.test.js.map +1 -0
  28. package/hooks/lib/mindlore-common.cjs +142 -2
  29. package/hooks/mindlore-index.cjs +17 -14
  30. package/hooks/mindlore-pre-compact.cjs +2 -2
  31. package/hooks/mindlore-search.cjs +4 -2
  32. package/hooks/mindlore-session-end.cjs +92 -0
  33. package/hooks/mindlore-session-focus.cjs +30 -3
  34. package/package.json +1 -1
  35. package/plugin.json +8 -8
  36. package/skills/mindlore-evolve/SKILL.md +8 -4
  37. package/skills/mindlore-explore/SKILL.md +8 -4
  38. package/skills/mindlore-log/SKILL.md +65 -8
  39. package/templates/config.json +3 -2
@@ -16,8 +16,10 @@ function main() {
16
16
  const filePath = readHookStdin(['path', 'file_path']);
17
17
  if (!filePath) return;
18
18
 
19
- // Only process .md files inside .mindlore/
20
- if (!filePath.includes(MINDLORE_DIR) || !filePath.endsWith('.md')) return;
19
+ // Only process .md files inside .mindlore/ (resolved path check prevents traversal)
20
+ if (!filePath.endsWith('.md')) return;
21
+ const resolvedFile = path.resolve(filePath);
22
+ if (!resolvedFile.includes(path.sep + MINDLORE_DIR + path.sep) && !resolvedFile.includes(path.sep + MINDLORE_DIR)) return;
21
23
 
22
24
  const fileName = path.basename(filePath);
23
25
  if (SKIP_FILES.has(fileName)) return;
@@ -60,18 +62,19 @@ function main() {
60
62
  const { meta, body } = parseFrontmatter(content);
61
63
  const { slug, description, type, category, title, tags, quality, dateCaptured } = extractFtsMetadata(meta, body, filePath, baseDir);
62
64
 
63
- // Update FTS5
64
- db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
65
- insertFtsRow(db, { path: filePath, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project: getProjectName() });
66
-
67
- // Update hash
68
- db.prepare(
69
- `INSERT INTO file_hashes (path, content_hash, last_indexed)
70
- VALUES (?, ?, ?)
71
- ON CONFLICT(path) DO UPDATE SET
72
- content_hash = excluded.content_hash,
73
- last_indexed = excluded.last_indexed`
74
- ).run(filePath, hash, new Date().toISOString());
65
+ // Update FTS5 + hash atomically
66
+ const updateIndex = db.transaction(() => {
67
+ db.prepare('DELETE FROM mindlore_fts WHERE path = ?').run(filePath);
68
+ insertFtsRow(db, { path: filePath, slug, description, type, category, title, content: body, tags, quality, dateCaptured, project: getProjectName() });
69
+ db.prepare(
70
+ `INSERT INTO file_hashes (path, content_hash, last_indexed)
71
+ VALUES (?, ?, ?)
72
+ ON CONFLICT(path) DO UPDATE SET
73
+ content_hash = excluded.content_hash,
74
+ last_indexed = excluded.last_indexed`
75
+ ).run(filePath, hash, new Date().toISOString());
76
+ });
77
+ updateIndex();
75
78
  } finally {
76
79
  db.close();
77
80
  }
@@ -21,8 +21,8 @@ function main() {
21
21
  const indexScript = path.join(__dirname, '..', 'scripts', 'mindlore-fts5-index.cjs');
22
22
  if (fs.existsSync(indexScript)) {
23
23
  try {
24
- const { execSync } = require('child_process');
25
- execSync(`node "${indexScript}" "${baseDir}"`, {
24
+ const { spawnSync } = require('child_process');
25
+ spawnSync('node', [indexScript, baseDir], {
26
26
  timeout: 10000,
27
27
  stdio: 'pipe',
28
28
  });
@@ -80,7 +80,9 @@ function searchDb(dbPath, keywords, Database) {
80
80
 
81
81
  for (const kw of keywords) {
82
82
  try {
83
- const r = matchStmt.get(row.path, '"' + kw + '"');
83
+ const sanitized = kw.replace(/["*(){}[\]^~:]/g, '');
84
+ if (!sanitized) continue;
85
+ const r = matchStmt.get(row.path, '"' + sanitized + '"');
84
86
  if (r) {
85
87
  hits++;
86
88
  totalRank += r.rank;
@@ -110,7 +112,7 @@ function searchDb(dbPath, keywords, Database) {
110
112
  */
111
113
  function searchEpisodesFts(db, keywords) {
112
114
  try {
113
- const ftsQuery = keywords.map(kw => '"' + kw + '"').join(' OR ');
115
+ const ftsQuery = keywords.map(kw => '"' + kw.replace(/["*(){}[\]^~:]/g, '') + '"').filter(q => q !== '""').join(' OR ');
114
116
  const rows = db.prepare(
115
117
  "SELECT title, category, slug, tags FROM mindlore_fts WHERE type = 'episode' AND mindlore_fts MATCH ? LIMIT 2"
116
118
  ).all(ftsQuery);
@@ -143,6 +143,9 @@ function main() {
143
143
  // v0.4.0: Write bare episode to episodes table
144
144
  writeBareEpisode(baseDir, project, commits, changedFiles, reads);
145
145
 
146
+ // Obsidian auto-export (if vault configured)
147
+ syncObsidian(baseDir);
148
+
146
149
  // Git auto-commit + push for global ~/.mindlore/ only
147
150
  syncGlobalRepo();
148
151
  }
@@ -220,6 +223,95 @@ function writeBareEpisode(baseDir, project, commits, changedFiles, reads) {
220
223
  }
221
224
  }
222
225
 
226
+ const EXPORT_DIRS = ['analyses', 'decisions', 'diary', 'raw', 'sources', 'domains', 'connections', 'insights', 'learnings'];
227
+
228
+ /**
229
+ * Load obsidian-helpers from compiled dist (single source of truth for wikilink conversion).
230
+ * Returns null if helpers not available (e.g. dev environment without build).
231
+ */
232
+ function loadObsidianHelpers() {
233
+ try {
234
+ // Resolve from package root (hooks/ is sibling to dist/)
235
+ const hookDir = __dirname;
236
+ const pkgRoot = path.dirname(hookDir);
237
+ const helpersPath = path.join(pkgRoot, 'dist', 'scripts', 'lib', 'obsidian-helpers.js');
238
+ if (!fs.existsSync(helpersPath)) return null;
239
+ return require(helpersPath);
240
+ } catch (_err) {
241
+ return null;
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Export a single .md file to Obsidian vault with wikilink conversion.
247
+ * Uses obsidian-helpers.convertToWikilinks for consistent behavior.
248
+ * Returns true if file was exported.
249
+ */
250
+ function exportMdFile(srcPath, destPath, convertFn) {
251
+ try {
252
+ const destStat = fs.statSync(destPath);
253
+ const srcStat = fs.statSync(srcPath);
254
+ if (srcStat.mtimeMs <= destStat.mtimeMs) return false;
255
+ } catch (_err) {
256
+ // dest doesn't exist — proceed with export
257
+ }
258
+ let content = fs.readFileSync(srcPath, 'utf8');
259
+ content = convertFn(content);
260
+ fs.writeFileSync(destPath, content, 'utf8');
261
+ return true;
262
+ }
263
+
264
+ /**
265
+ * Auto-export .md files to Obsidian vault if configured.
266
+ * Skips if no vault configured, vault missing, or nothing changed since last export.
267
+ */
268
+ function syncObsidian(baseDir) {
269
+ try {
270
+ const configPath = path.join(baseDir, 'config.json');
271
+ if (!fs.existsSync(configPath)) return;
272
+
273
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
274
+ const vaultPath = config?.obsidian?.vault;
275
+ if (!vaultPath || typeof vaultPath !== 'string') return;
276
+ if (!fs.existsSync(vaultPath)) return;
277
+
278
+ const helpers = loadObsidianHelpers();
279
+ // Fallback regex if helpers unavailable (strips path prefixes like the canonical version)
280
+ const convertFn = helpers?.convertToWikilinks
281
+ ?? ((c) => c.replace(/\[([^\]]+)\]\((?:\.\.?\/)?(?:[\w-]+\/)*([^/)]+)\.md\)/g, '[[$2]]'));
282
+
283
+ const destBase = path.join(vaultPath, 'mindlore');
284
+ let exported = 0;
285
+
286
+ for (const dir of EXPORT_DIRS) {
287
+ const srcDir = path.join(baseDir, dir);
288
+ if (!fs.existsSync(srcDir)) continue;
289
+
290
+ const destDir = path.join(destBase, dir);
291
+ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
292
+
293
+ for (const file of fs.readdirSync(srcDir).filter(f => f.endsWith('.md') && !f.startsWith('_'))) {
294
+ if (exportMdFile(path.join(srcDir, file), path.join(destDir, file), convertFn)) exported++;
295
+ }
296
+ }
297
+
298
+ for (const rootFile of ['INDEX.md', 'log.md']) {
299
+ const srcPath = path.join(baseDir, rootFile);
300
+ if (!fs.existsSync(srcPath)) continue;
301
+ if (!fs.existsSync(destBase)) fs.mkdirSync(destBase, { recursive: true });
302
+ if (exportMdFile(srcPath, path.join(destBase, rootFile), convertFn)) exported++;
303
+ }
304
+
305
+ if (exported > 0) {
306
+ config.obsidian.lastExport = new Date().toISOString();
307
+ config.obsidian.lastExportCount = exported;
308
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
309
+ }
310
+ } catch (err) {
311
+ process.stderr.write(`[mindlore] obsidian sync failed: ${err?.message ?? err}\n`);
312
+ }
313
+ }
314
+
223
315
  /**
224
316
  * Auto-commit and push ~/.mindlore/ if it has a .git directory.
225
317
  * Only runs for the global scope — project .mindlore/ is in the project's own git.
@@ -10,13 +10,14 @@
10
10
 
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
- const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, queryRecentEpisodes } = require('./lib/mindlore-common.cjs');
13
+ const { findMindloreDir, readConfig, openDatabase, hasEpisodesTable, queryRecentEpisodes, querySupersededChains, formatSupersededChains, queryMultiSessionEpisodes, formatMultiSessionEpisodes, getAllMdFiles } = require('./lib/mindlore-common.cjs');
14
14
 
15
15
  function main() {
16
16
  const baseDir = findMindloreDir();
17
17
  if (!baseDir) return; // No .mindlore/ found, silently skip
18
18
 
19
19
  const output = [];
20
+ const config = readConfig(baseDir);
20
21
 
21
22
  // Inject INDEX.md
22
23
  const indexPath = path.join(baseDir, 'INDEX.md');
@@ -41,7 +42,6 @@ function main() {
41
42
  }
42
43
 
43
44
  // Reflect trigger
44
- const config = readConfig(baseDir);
45
45
  const threshold = config?.reflect?.threshold ?? 5;
46
46
  if (diaryFiles.length >= threshold) {
47
47
  output.push(`[Mindlore] ${diaryFiles.length} diary entry birikti — \`/mindlore-log reflect\` calistirmayi dusun.`);
@@ -70,7 +70,6 @@ function main() {
70
70
  if (db) {
71
71
  try {
72
72
  if (hasEpisodesTable(db)) {
73
- const config = readConfig(baseDir);
74
73
  const maxEpisodes = config?.session_focus?.max_episodes ?? 3;
75
74
  const project = path.basename(process.cwd());
76
75
  const episodes = queryRecentEpisodes(db, { project, limit: maxEpisodes });
@@ -83,6 +82,22 @@ function main() {
83
82
  });
84
83
  output.push(`[Mindlore Episodes]\n${lines.join('\n')}`);
85
84
  }
85
+
86
+ // v0.4.1: Enriched multi-session episodes
87
+ const multiDays = config?.session_focus?.multi_session_days ?? 3;
88
+ const enriched = queryMultiSessionEpisodes(db, { project, days: multiDays, limit: 20 });
89
+ if (enriched.length > 0) {
90
+ const formatted = formatMultiSessionEpisodes(enriched);
91
+ if (formatted) {
92
+ output.push(`[Mindlore Recent Activity]\n${formatted}`);
93
+ }
94
+ }
95
+
96
+ // v0.4.1: Supersedes chain display
97
+ const chains = querySupersededChains(db, { project, days: 7, limit: 5 });
98
+ if (chains.length > 0) {
99
+ output.push(`[Mindlore Supersedes]\n${formatSupersededChains(chains)}`);
100
+ }
86
101
  }
87
102
  } finally {
88
103
  db.close();
@@ -90,6 +105,18 @@ function main() {
90
105
  }
91
106
  } catch (_err) { /* graceful skip */ }
92
107
 
108
+ // v0.4.1: Lightweight stale content check (monitors fallback)
109
+ try {
110
+ const allFiles = getAllMdFiles(baseDir);
111
+ const thirtyDaysAgo = Date.now() - (30 * 24 * 60 * 60 * 1000);
112
+ const staleCount = allFiles.reduce((count, f) => {
113
+ try { return fs.statSync(f).mtimeMs < thirtyDaysAgo ? count + 1 : count; } catch { return count; }
114
+ }, 0);
115
+ if (staleCount > 3) {
116
+ output.push(`[Mindlore: ${staleCount} dosya 30+ gundur guncellenmemis — \`/mindlore-evolve\` dusun]`);
117
+ }
118
+ } catch (_healthErr) { /* skip */ }
119
+
93
120
  if (output.length > 0) {
94
121
  process.stdout.write(output.join('\n\n') + '\n');
95
122
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mindlore",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "AI-native knowledge system for Claude Code",
5
5
  "type": "commonjs",
6
6
  "bin": {
package/plugin.json CHANGED
@@ -1,42 +1,42 @@
1
1
  {
2
2
  "name": "mindlore",
3
3
  "description": "AI-native knowledge system for Claude Code. Persistent, searchable, evolving knowledge base with FTS5.",
4
- "version": "0.3.0",
4
+ "version": "0.4.2",
5
5
  "skills": [
6
6
  {
7
7
  "name": "mindlore-ingest",
8
8
  "path": "skills/mindlore-ingest/SKILL.md",
9
- "description": "Add new knowledge sources (URL, text, file, PDF, GitHub repo)"
9
+ "description": "Add new knowledge sources to .mindlore/ knowledge base. Supports: URL (web page to markdown), text (inline paste), file (local .md/.txt/.pdf), PDF (markitdown extraction), GitHub repo (README + structure analysis). Includes 6-point quality gate: frontmatter validation, slug uniqueness, content length, source_type tagging, quality heuristic assignment, and FTS5 indexing. Auto-generates frontmatter with type, slug, date, tags, and quality fields."
10
10
  },
11
11
  {
12
12
  "name": "mindlore-health",
13
13
  "path": "skills/mindlore-health/SKILL.md",
14
- "description": "Run 16-point structural health check on .mindlore/ knowledge base"
14
+ "description": "Run 16-point structural health check on .mindlore/ knowledge base. 12 deterministic script checks: INDEX.md sync, FTS5 row count, frontmatter validation, orphan file detection, slug uniqueness, directory structure, log.md integrity, config.json schema, episodes table health, content-hash dedup status, cross-reference consistency, file naming conventions. 4 LLM-powered checks: domain coverage gaps, stale content detection, tag taxonomy coherence, knowledge graph connectivity."
15
15
  },
16
16
  {
17
17
  "name": "mindlore-query",
18
18
  "path": "skills/mindlore-query/SKILL.md",
19
- "description": "Search, ask, stats, brief — compounding knowledge pipeline"
19
+ "description": "Search, ask, stats, brief — compounding knowledge pipeline. Modes: search (FTS5 full-text + episodes recall), ask (LLM-powered Q&A grounded in knowledge base), stats (episode counts, domain coverage, freshness metrics), brief (executive summary of recent activity). Combines FTS5 text search with episodic memory for richer recall. Supports project-scoped and global queries."
20
20
  },
21
21
  {
22
22
  "name": "mindlore-log",
23
23
  "path": "skills/mindlore-log/SKILL.md",
24
- "description": "Session logging, pattern extraction, wiki updates"
24
+ "description": "Session logging, pattern extraction, and wiki updates. Modes: diary (LLM session analysis to enriched episodes: decisions, discoveries, frictions, learnings, preferences, events), reflect (scan episodes for recurring patterns, 3-tier confidence assessment, nomination pipeline for rule proposals), status (recent sessions summary with trends), save (structured delta + log.md append + domain wiki update). Episodes-powered since v0.4.0."
25
25
  },
26
26
  {
27
27
  "name": "mindlore-decide",
28
28
  "path": "skills/mindlore-decide/SKILL.md",
29
- "description": "Record and list decisions with context, alternatives, rationale"
29
+ "description": "Record and list architectural, tool, format, and strategy decisions with full context. Captures: alternatives considered, rationale, trade-offs, and outcome. Supports supersedes chain — when a decision replaces an older one, links them with reason. Episodes integration: decisions stored as kind:decision episodes with structured body. List mode shows active decisions with supersedes history."
30
30
  },
31
31
  {
32
32
  "name": "mindlore-evolve",
33
33
  "path": "skills/mindlore-evolve/SKILL.md",
34
- "description": "Knowledge schema co-evolution — scan domains+sources, suggest updates"
34
+ "description": "Knowledge schema co-evolution — scan domains and sources for inconsistencies, suggest structural updates. Detects: orphaned references, outdated cross-links, missing domain coverage, INDEX.md drift, stale source summaries. Proposes: new domain pages, source updates, INDEX.md reorganization, CLAUDE.md convention changes. Default scope: project + global (--scope project|global|all)."
35
35
  },
36
36
  {
37
37
  "name": "mindlore-explore",
38
38
  "path": "skills/mindlore-explore/SKILL.md",
39
- "description": "Discover unexpected connections between knowledge sources"
39
+ "description": "Discover unexpected connections between knowledge sources — undirected exploration mode. Cross-references domains, sources, analyses, and episodes to find non-obvious relationships. Identifies: shared entities across sources, pattern convergence between domains, episodic evidence supporting or contradicting domain claims. Default scope: project + global (--scope project|global|all)."
40
40
  }
41
41
  ],
42
42
  "hooks": [
@@ -11,10 +11,14 @@ Knowledge schema co-evolution. Karpathy's 4th operation (ingest/query/health/**e
11
11
 
12
12
  ## Scope
13
13
 
14
- Determine target using `getActiveMindloreDir()` logic:
15
- - Default (no flag): scan project `.mindlore/`
16
- - `--global`: scan `~/.mindlore/`
17
- - Never hardcode `.mindlore/` path always resolve dynamically
14
+ Default: `--scope all` (project + global birlikte taranır).
15
+ Options: `--scope project` | `--scope global` | `--scope all`
16
+
17
+ 1. Determine scope from argument (default: all)
18
+ 2. If `all`: scan both project `.mindlore/` and global `~/.mindlore/`
19
+ 3. If `project`: scan only project-scoped content (current CWD's `.mindlore/`)
20
+ 4. If `global`: scan only global `~/.mindlore/` content
21
+ - Never hardcode `.mindlore/` path — always resolve dynamically via `getActiveMindloreDir()`
18
22
 
19
23
  ## Trigger
20
24
 
@@ -11,10 +11,14 @@ Discover unexpected connections between knowledge sources. Undirected exploratio
11
11
 
12
12
  ## Scope
13
13
 
14
- Determine target using `getActiveMindloreDir()` logic:
15
- - Default (no flag): explore project `.mindlore/`
16
- - `--global`: explore `~/.mindlore/` + project cross-reference (most valuable mode)
17
- - Never hardcode `.mindlore/` path always resolve dynamically
14
+ Default: `--scope all` (project + global birlikte taranır — en değerli mod).
15
+ Options: `--scope project` | `--scope global` | `--scope all`
16
+
17
+ 1. Determine scope from argument (default: all)
18
+ 2. If `all`: explore both project `.mindlore/` and global `~/.mindlore/` + cross-reference
19
+ 3. If `project`: explore only project-scoped content (current CWD's `.mindlore/`)
20
+ 4. If `global`: explore only global `~/.mindlore/` content
21
+ - Never hardcode `.mindlore/` path — always resolve dynamically via `getActiveMindloreDir()`
18
22
 
19
23
  ## Trigger
20
24
 
@@ -86,7 +86,16 @@ LLM-driven pattern extraction from episodes → persistent learnings.
86
86
  - Recurring frictions (same blocker/error)
87
87
  - Discovery patterns (assumptions that keep breaking)
88
88
  - Workflow patterns that worked well
89
- 5. **Structured report output:**
89
+ 5. **3-Tier Confidence Assessment:**
90
+ For each detected pattern, count occurrences across episodes:
91
+
92
+ | Tekrar | Tier | Aksiyon |
93
+ |--------|------|---------|
94
+ | 1x | Note | Sessiz — episode olarak kalır, raporda göster |
95
+ | 2x | Learning | `kind: learning` episode oluştur, learnings/ dosyasına yaz |
96
+ | 3x+ | Nomination | `kind: nomination, status: staged, source: reflect` episode oluştur |
97
+
98
+ 6. **Structured report output:**
90
99
 
91
100
  ```
92
101
  ── Reflect Raporu (son {days} gün, {N} episode) ──
@@ -101,25 +110,73 @@ Decisions ({count}):
101
110
  - {summary}
102
111
 
103
112
  Patterns:
104
- - "{pattern_description}" → kural adayı
113
+ - "CO-EVOLUTION sync hatası" → 3x tekrar → NOMINATION (staged)
114
+ - "ESM import sorunu" → 2x tekrar → LEARNING
115
+ - "Test mock karmaşıklığı" → 1x → NOTE
105
116
 
106
117
  Önerilen:
107
118
  [ ] {rule} ({repeat_count}x, {confidence} confidence)
108
119
  ```
109
120
 
110
- 6. User approves write to `learnings/{topic}.md`
111
- 7. Format: `YAPMA:` / `BEST PRACTICE:` / `KRITIK:` prefixed rules
112
- 8. Update relevant domain page if pattern relates to an existing domain
113
- 9. Mark processed episodes: future reflect skips already-processed timeranges
114
- 10. Append to `log.md`: `| {date} | reflect | {N} episodes processed, {M} learnings written |`
121
+ 7. **Nomination oluşturma (3x+ tekrar):**
122
+ - `kind: nomination`, `status: staged`, `source: reflect`
123
+ - Body formatı:
124
+ ```markdown
125
+ ## Target: learnings
126
+ ## Rule
127
+ YAPMA: Schema değişikliğinde tek dosyayı güncelleme — CO-EVOLUTION sync zorunlu
128
+ ## Evidence
129
+ - ep-xxx: episodes.ts güncellendi ama common.cjs unutuldu (2026-04-10)
130
+ - ep-yyy: Aynı hata tekrar (2026-04-12)
131
+ - ep-zzz: Test'te yakalandı (2026-04-13)
132
+ ## Confidence
133
+ 3x tekrar, 3 gün içinde
134
+ ```
135
+ - Target options: `learnings` | `claude.md` | `domain:{slug}`
136
+
137
+ 8. **Pending nominations check:**
138
+ Reflect başlarken staged nomination'ları kontrol et:
139
+ ```sql
140
+ SELECT id, summary, body, created_at FROM episodes
141
+ WHERE kind = 'nomination' AND status = 'staged' AND project = ?
142
+ ORDER BY created_at ASC
143
+ ```
144
+ Varsa kullanıcıya sun:
145
+ ```
146
+ ── Bekleyen Nomination'lar ({N} adet) ──
147
+ 1. "CO-EVOLUTION sync zorunlu" (staged 2 gün önce)
148
+ Target: learnings | Confidence: 3x
149
+ 2. "Test before commit" (staged 5 gün önce)
150
+ Target: claude.md | Confidence: 4x
151
+
152
+ Onaylamak istediğin numara(lar)ı seç, veya 'skip' de:
153
+ ```
154
+
155
+ 9. **Nomination approval flow:**
156
+ Kullanıcı onaylarsa:
157
+ - `status: staged → approved`
158
+ - Target'a göre yaz:
159
+ - `learnings` → ilgili `learnings/{topic}.md` dosyasına YAPMA/BEST PRACTICE ekle
160
+ - `claude.md` → ilgili projenin CLAUDE.md'sine kural ekle (kullanıcıya göster, onay al)
161
+ - `domain:{slug}` → ilgili domain sayfasına ekle
162
+ Kullanıcı reddederse:
163
+ - `status: staged → rejected`
164
+ - Body'ye `## Rejection Reason\n{kullanıcı açıklaması}` ekle
165
+
166
+ 10. User approves new learnings → write to `learnings/{topic}.md`
167
+ 11. Format: `YAPMA:` / `BEST PRACTICE:` / `KRITIK:` prefixed rules
168
+ 12. Update relevant domain page if pattern relates to an existing domain
169
+ 13. Mark processed episodes: future reflect skips already-processed timeranges
170
+ 14. Append to `log.md`: `| {date} | reflect | {N} episodes processed, {M} learnings written |`
115
171
 
116
172
  **Fallback:** Also reads non-archived delta files if episodes table is empty (backward compat with v0.3 deltas).
117
173
 
118
174
  **Rules:**
119
- - NEVER write learnings without user approval
175
+ - NEVER write learnings or nominations without user approval
120
176
  - Group related patterns into existing topic files (don't create one file per pattern)
121
177
  - Reflect scans both project + global diary/ in `--all` mode
122
178
  - Deduplication: same pattern found in both episodes and deltas → episodes win
179
+ - Nominations with `status: staged` are hidden from default queries — only reflect sees them
123
180
 
124
181
  ### status
125
182
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.0",
2
+ "version": "0.4.2",
3
3
  "models": {
4
4
  "ingest": "haiku",
5
5
  "evolve": "sonnet",
@@ -10,6 +10,7 @@
10
10
  "threshold": 5
11
11
  },
12
12
  "session_focus": {
13
- "max_episodes": 3
13
+ "max_episodes": 3,
14
+ "multi_session_days": 3
14
15
  }
15
16
  }