wicked-brain 0.8.2 → 0.9.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/package.json
CHANGED
|
@@ -194,6 +194,7 @@ const actions = {
|
|
|
194
194
|
link_health: () => db.linkHealth(),
|
|
195
195
|
tag_frequency: () => ({ tags: db.tagFrequency() }),
|
|
196
196
|
search_misses: (p) => ({ misses: db.searchMisses(p) }),
|
|
197
|
+
wiki_list: (p) => db.wikiList(p),
|
|
197
198
|
// LSP actions
|
|
198
199
|
"lsp-health": () => lsp.health(),
|
|
199
200
|
"lsp-symbols": (p) => lsp.symbols(p),
|
|
@@ -12,6 +12,19 @@ function extractBodyExcerpt(content, maxLen = 300) {
|
|
|
12
12
|
return body.trim().slice(0, maxLen);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Derives the source type from a document path.
|
|
17
|
+
* - Paths starting with "wiki/" → "wiki"
|
|
18
|
+
* - Paths starting with "memory/" or "memories/" → "memory"
|
|
19
|
+
* - Everything else → "chunk"
|
|
20
|
+
*/
|
|
21
|
+
export function deriveSourceType(path) {
|
|
22
|
+
const normalized = (path ?? "").replace(/\\/g, "/");
|
|
23
|
+
if (normalized.startsWith("wiki/")) return "wiki";
|
|
24
|
+
if (normalized.startsWith("memory/") || normalized.startsWith("memories/")) return "memory";
|
|
25
|
+
return "chunk";
|
|
26
|
+
}
|
|
27
|
+
|
|
15
28
|
function escapeFtsQuery(query) {
|
|
16
29
|
return query
|
|
17
30
|
.trim()
|
|
@@ -357,10 +370,11 @@ export class SqliteSearch {
|
|
|
357
370
|
|
|
358
371
|
const rows = rawRows.slice(offset, offset + limit).map((row) => {
|
|
359
372
|
const body_excerpt = extractBodyExcerpt(row.raw_content ?? "");
|
|
373
|
+
const source_type = deriveSourceType(row.path);
|
|
360
374
|
delete row.raw_content;
|
|
361
375
|
delete row.composite_score;
|
|
362
376
|
delete row.boosted_score;
|
|
363
|
-
return { ...row, body_excerpt };
|
|
377
|
+
return { ...row, source_type, body_excerpt };
|
|
364
378
|
});
|
|
365
379
|
|
|
366
380
|
const countRow = this.#db
|
|
@@ -427,7 +441,7 @@ export class SqliteSearch {
|
|
|
427
441
|
LIMIT ?
|
|
428
442
|
`)
|
|
429
443
|
.all(escaped, limit);
|
|
430
|
-
allResults.push(...rows);
|
|
444
|
+
allResults.push(...rows.map((r) => ({ ...r, source_type: deriveSourceType(r.path) })));
|
|
431
445
|
} finally {
|
|
432
446
|
this.#db.prepare(`DETACH DATABASE ${attached}`).run();
|
|
433
447
|
}
|
|
@@ -872,6 +886,83 @@ export class SqliteSearch {
|
|
|
872
886
|
return row || null;
|
|
873
887
|
}
|
|
874
888
|
|
|
889
|
+
/**
|
|
890
|
+
* List wiki articles with metadata (no full content).
|
|
891
|
+
* Optional FTS5 keyword filter.
|
|
892
|
+
* @param {object} opts
|
|
893
|
+
* @param {string|null} [opts.query] - Optional FTS5 query to filter articles
|
|
894
|
+
* @param {number} [opts.limit=50]
|
|
895
|
+
* @returns {{ articles: Array<{ path: string, title: string|null, description: string|null, tags: string[], word_count: number }> }}
|
|
896
|
+
*/
|
|
897
|
+
wikiList({ query = null, limit = 50 } = {}) {
|
|
898
|
+
let rows;
|
|
899
|
+
|
|
900
|
+
if (query) {
|
|
901
|
+
const escaped = escapeFtsQuery(query);
|
|
902
|
+
if (!escaped) return { articles: [] };
|
|
903
|
+
|
|
904
|
+
rows = this.#db.prepare(`
|
|
905
|
+
SELECT d.path, d.frontmatter, d.content
|
|
906
|
+
FROM documents_fts f
|
|
907
|
+
JOIN documents d ON d.id = f.id
|
|
908
|
+
WHERE documents_fts MATCH ?
|
|
909
|
+
AND d.path LIKE 'wiki/%'
|
|
910
|
+
ORDER BY rank
|
|
911
|
+
LIMIT ?
|
|
912
|
+
`).all(escaped, limit);
|
|
913
|
+
} else {
|
|
914
|
+
rows = this.#db.prepare(`
|
|
915
|
+
SELECT path, frontmatter, content
|
|
916
|
+
FROM documents
|
|
917
|
+
WHERE path LIKE 'wiki/%'
|
|
918
|
+
ORDER BY path
|
|
919
|
+
LIMIT ?
|
|
920
|
+
`).all(limit);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const articles = rows.map((row) => {
|
|
924
|
+
const fm = row.frontmatter || SqliteSearch.#extractFrontmatter(row.content) || "";
|
|
925
|
+
const title = this.#extractFrontmatterField(fm, "title") || null;
|
|
926
|
+
const description = this.#extractFrontmatterField(fm, "description") || null;
|
|
927
|
+
const tags = this.#parseTags(fm);
|
|
928
|
+
const word_count = (row.content || "").split(/\s+/).filter(Boolean).length;
|
|
929
|
+
return { path: row.path, title, description, tags, word_count };
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
return { articles };
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* Parse tags from frontmatter string.
|
|
937
|
+
* Supports space-separated inline, JSON array, and YAML block list formats.
|
|
938
|
+
*/
|
|
939
|
+
#parseTags(fm) {
|
|
940
|
+
if (!fm) return [];
|
|
941
|
+
|
|
942
|
+
// Inline: tags: tag1 tag2 tag3 or tags: ["tag1","tag2"]
|
|
943
|
+
const inlineMatch = fm.match(/^tags:[ \t]+(\S.*)$/m);
|
|
944
|
+
if (inlineMatch) {
|
|
945
|
+
const raw = inlineMatch[1].trim();
|
|
946
|
+
if (raw.startsWith("[")) {
|
|
947
|
+
try {
|
|
948
|
+
return JSON.parse(raw).map(String);
|
|
949
|
+
} catch {
|
|
950
|
+
return raw.replace(/[\[\]"]/g, "").split(/[\s,]+/).filter(Boolean);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return raw.split(/\s+/).filter(Boolean);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// YAML block list
|
|
957
|
+
const blockMatch = fm.match(/^tags:\s*\n((?:\s+-\s+.+\n?)+)/m);
|
|
958
|
+
if (blockMatch) {
|
|
959
|
+
const listLines = blockMatch[1].match(/^\s+-\s+(.+)$/gm) || [];
|
|
960
|
+
return listLines.map((line) => line.replace(/^\s+-\s+/, "").trim()).filter(Boolean);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
return [];
|
|
964
|
+
}
|
|
965
|
+
|
|
875
966
|
close() {
|
|
876
967
|
this.#db.close();
|
|
877
968
|
}
|
package/server/package.json
CHANGED
|
@@ -88,11 +88,20 @@ Digital brain: {brain_id} | {total} indexed items | {chunks} chunks, {wiki} wiki
|
|
|
88
88
|
|
|
89
89
|
- **Search/explore**: use `wicked-brain:search` — replaces Grep, Glob, and Agent(Explore) for any open-ended search
|
|
90
90
|
- **Answer questions**: use `wicked-brain:query` — replaces Agent(Explore) for conceptual questions
|
|
91
|
+
- **Wiki catalog**: use `wicked-brain:read` at depth 0/1 to browse wiki articles progressively
|
|
91
92
|
- **Surface context**: call `wicked-brain:agent` (context) at the start of any new topic
|
|
92
93
|
- **Capture learnings**: call `wicked-brain:agent` (session-teardown) at session end
|
|
93
94
|
- **Store a decision/pattern/gotcha**: call `wicked-brain:memory` (store mode)
|
|
94
95
|
- **Available agents**: consolidate, context, session-teardown, onboard (via `wicked-brain:agent`)
|
|
95
96
|
|
|
97
|
+
### Search result source types
|
|
98
|
+
|
|
99
|
+
Brain search/query results include `source_type` and `path` fields:
|
|
100
|
+
|
|
101
|
+
- **`wiki`** — Synthesized knowledge. Read deeper with `wicked-brain:read {path} depth=2`.
|
|
102
|
+
- **`chunk`** — Raw indexed content. The search excerpt is usually sufficient.
|
|
103
|
+
- **`memory`** — Experiential learnings. Compact; excerpt is usually enough.
|
|
104
|
+
|
|
96
105
|
### Rules (follow strictly)
|
|
97
106
|
|
|
98
107
|
- **ALWAYS check the brain BEFORE using Grep, Glob, Read, or Agent(Explore)** — for any find, search, explore, explain, or "what is/how does" request
|
|
@@ -103,6 +112,7 @@ Digital brain: {brain_id} | {total} indexed items | {chunks} chunks, {wiki} wiki
|
|
|
103
112
|
- Do NOT read brain files directly — always go through skills and agents
|
|
104
113
|
- Always pass `session_id` with search/query calls for access tracking
|
|
105
114
|
- Capture non-obvious decisions, patterns, and gotchas with `wicked-brain:memory`
|
|
115
|
+
- When search results include `source_type: wiki`, follow up with `wicked-brain:read` at depth 1-2
|
|
106
116
|
```
|
|
107
117
|
|
|
108
118
|
### Step 4: Emit bus event
|