compact-agent 1.9.0 → 1.10.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.
@@ -0,0 +1,100 @@
1
+ /**
2
+ * MemPalace — high-level API.
3
+ *
4
+ * Combines the global (cross-project) and project (per-repo) stores into
5
+ * one façade. Most callers use these functions rather than poking the
6
+ * JsonStore directly because they handle the scope selection:
7
+ *
8
+ * - Reads with scope: 'both' search both stores and merge results
9
+ * - Writes with scope: 'global' or 'project' target one
10
+ * - Writes with scope: 'both' rejected at runtime — every drawer lives
11
+ * in exactly one store
12
+ *
13
+ * This module is the boundary between the storage internals (types.ts,
14
+ * store.ts, search.ts) and the agent-facing tools (src/tools/memory.ts)
15
+ * + slash commands.
16
+ */
17
+ import { JsonStore } from './store.js';
18
+ import type { Drawer, Tunnel, KGTriple, Scope, SearchOptions, SearchHit, WingMeta, RoomMeta } from './types.js';
19
+ export declare function getGlobalStore(): JsonStore;
20
+ /**
21
+ * Per-project store keyed by `cwd`. If the cwd changes mid-session (the
22
+ * user `/cd`s elsewhere), the next call gets a fresh store pointing at
23
+ * the new location. The old one stays valid for any held references.
24
+ */
25
+ export declare function getProjectStore(cwd: string): JsonStore;
26
+ /**
27
+ * Heuristic: should a piece of content live in the global or project
28
+ * store? Used when the agent calls memory_add with scope: 'auto'.
29
+ *
30
+ * Signals pushing GLOBAL: content mentions the user by name, talks about
31
+ * preferences, recurring patterns, identity, or cross-project facts.
32
+ * Default lean is PROJECT since per-repo facts dominate by volume.
33
+ */
34
+ export declare function inferScope(content: string, tags: string[]): Scope;
35
+ export interface AddDrawerInput {
36
+ wing: string;
37
+ room: string;
38
+ content: string;
39
+ tags?: string[];
40
+ importance?: number;
41
+ scope?: Scope | 'auto';
42
+ sourceSessionId?: string;
43
+ cwd: string;
44
+ }
45
+ export declare function addDrawer(input: AddDrawerInput): Drawer;
46
+ export declare function getDrawer(id: string, cwd: string): Drawer | null;
47
+ export declare function listDrawers(opts: {
48
+ wing?: string;
49
+ room?: string;
50
+ tag?: string;
51
+ scope?: Scope;
52
+ cwd: string;
53
+ }): Drawer[];
54
+ /**
55
+ * Search both stores by default and merge results, re-sorted by score.
56
+ * Specify scope to limit to one. Returns the top `limit` hits across
57
+ * the union.
58
+ */
59
+ export declare function search(query: string, cwd: string, opts?: SearchOptions): SearchHit[];
60
+ /**
61
+ * Link two drawers with a labelled relation. Both drawers must live in
62
+ * the same store — we don't cross global ↔ project boundaries because
63
+ * the link would silently break if either side is moved/deleted.
64
+ */
65
+ export declare function linkDrawers(fromId: string, toId: string, relation: string, cwd: string): Tunnel;
66
+ export declare function traverseTunnels(startId: string, cwd: string, maxDepth?: number): {
67
+ drawer: Drawer;
68
+ depth: number;
69
+ via: string[];
70
+ }[];
71
+ export declare function kgAdd(triple: {
72
+ subject: string;
73
+ predicate: string;
74
+ object: string;
75
+ confidence?: number;
76
+ sourceSessionId?: string;
77
+ sourceDrawerId?: string;
78
+ }, scope: Scope, cwd: string): KGTriple;
79
+ export declare function kgQuery(q: {
80
+ subject?: string;
81
+ predicate?: string;
82
+ object?: string;
83
+ }, cwd: string, scope?: Scope): KGTriple[];
84
+ export declare function kgTimeline(cwd: string, limit?: number, scope?: Scope): KGTriple[];
85
+ export declare function listWings(cwd: string, scope?: Scope): {
86
+ global: WingMeta[];
87
+ project: WingMeta[];
88
+ };
89
+ export declare function listRooms(cwd: string, wing?: string, scope?: Scope): {
90
+ global: RoomMeta[];
91
+ project: RoomMeta[];
92
+ };
93
+ export declare function stats(cwd: string): {
94
+ global: ReturnType<JsonStore['stats']>;
95
+ project: ReturnType<JsonStore['stats']>;
96
+ globalPath: string;
97
+ projectPath: string;
98
+ projectExists: boolean;
99
+ };
100
+ export type { Drawer, Tunnel, KGTriple, Scope, SearchHit, SearchOptions, WingMeta, RoomMeta } from './types.js';
@@ -0,0 +1,196 @@
1
+ /**
2
+ * MemPalace — high-level API.
3
+ *
4
+ * Combines the global (cross-project) and project (per-repo) stores into
5
+ * one façade. Most callers use these functions rather than poking the
6
+ * JsonStore directly because they handle the scope selection:
7
+ *
8
+ * - Reads with scope: 'both' search both stores and merge results
9
+ * - Writes with scope: 'global' or 'project' target one
10
+ * - Writes with scope: 'both' rejected at runtime — every drawer lives
11
+ * in exactly one store
12
+ *
13
+ * This module is the boundary between the storage internals (types.ts,
14
+ * store.ts, search.ts) and the agent-facing tools (src/tools/memory.ts)
15
+ * + slash commands.
16
+ */
17
+ import { existsSync } from 'node:fs';
18
+ import { JsonStore, globalStorePath, projectStorePath } from './store.js';
19
+ // Lazily-constructed singletons — we don't want to create the global file
20
+ // just by importing the module. They materialize on first read or write.
21
+ let _global = null;
22
+ let _project = null;
23
+ let _projectCwd = null;
24
+ export function getGlobalStore() {
25
+ if (!_global)
26
+ _global = new JsonStore(globalStorePath(), 'global');
27
+ return _global;
28
+ }
29
+ /**
30
+ * Per-project store keyed by `cwd`. If the cwd changes mid-session (the
31
+ * user `/cd`s elsewhere), the next call gets a fresh store pointing at
32
+ * the new location. The old one stays valid for any held references.
33
+ */
34
+ export function getProjectStore(cwd) {
35
+ if (!_project || _projectCwd !== cwd) {
36
+ _project = new JsonStore(projectStorePath(cwd), 'project');
37
+ _projectCwd = cwd;
38
+ }
39
+ return _project;
40
+ }
41
+ /**
42
+ * Heuristic: should a piece of content live in the global or project
43
+ * store? Used when the agent calls memory_add with scope: 'auto'.
44
+ *
45
+ * Signals pushing GLOBAL: content mentions the user by name, talks about
46
+ * preferences, recurring patterns, identity, or cross-project facts.
47
+ * Default lean is PROJECT since per-repo facts dominate by volume.
48
+ */
49
+ export function inferScope(content, tags) {
50
+ const lc = content.toLowerCase();
51
+ const tagsLc = tags.map((t) => t.toLowerCase());
52
+ // User-modeling signals → global
53
+ const userKeywords = [
54
+ 'i prefer', 'i like', 'i always', 'i never', 'the user',
55
+ 'my style', 'my workflow', 'rsfit',
56
+ ];
57
+ for (const k of userKeywords) {
58
+ if (lc.includes(k))
59
+ return 'global';
60
+ }
61
+ if (tagsLc.some((t) => ['preference', 'user', 'identity', 'workflow'].includes(t))) {
62
+ return 'global';
63
+ }
64
+ // Default: project-scoped. Codebase-specific facts outnumber cross-
65
+ // project facts in most workflows.
66
+ return 'project';
67
+ }
68
+ export function addDrawer(input) {
69
+ const tags = input.tags || [];
70
+ const resolvedScope = input.scope === 'auto' || !input.scope
71
+ ? inferScope(input.content, tags)
72
+ : input.scope;
73
+ const store = resolvedScope === 'global' ? getGlobalStore() : getProjectStore(input.cwd);
74
+ return store.addDrawer({
75
+ wing: input.wing,
76
+ room: input.room,
77
+ content: input.content,
78
+ tags,
79
+ importance: input.importance ?? 0.5,
80
+ sourceSessionId: input.sourceSessionId,
81
+ });
82
+ }
83
+ export function getDrawer(id, cwd) {
84
+ return getGlobalStore().getDrawer(id) ?? getProjectStore(cwd).getDrawer(id);
85
+ }
86
+ export function listDrawers(opts) {
87
+ const { wing, room, tag, cwd, scope = 'both' } = opts;
88
+ const filter = { wing, room, tag };
89
+ if (scope === 'global')
90
+ return getGlobalStore().listDrawers(filter);
91
+ if (scope === 'project')
92
+ return getProjectStore(cwd).listDrawers(filter);
93
+ return [...getGlobalStore().listDrawers(filter), ...getProjectStore(cwd).listDrawers(filter)];
94
+ }
95
+ // ── Search (the main agent-facing query) ──────────────────
96
+ /**
97
+ * Search both stores by default and merge results, re-sorted by score.
98
+ * Specify scope to limit to one. Returns the top `limit` hits across
99
+ * the union.
100
+ */
101
+ export function search(query, cwd, opts = {}) {
102
+ const scope = opts.scope ?? 'both';
103
+ const limit = opts.limit ?? 20;
104
+ const hits = [];
105
+ if (scope === 'global' || scope === 'both') {
106
+ hits.push(...getGlobalStore().search(query, { ...opts, limit: limit * 2 }));
107
+ }
108
+ if (scope === 'project' || scope === 'both') {
109
+ hits.push(...getProjectStore(cwd).search(query, { ...opts, limit: limit * 2 }));
110
+ }
111
+ hits.sort((a, b) => b.score - a.score);
112
+ return hits.slice(0, limit);
113
+ }
114
+ // ── Tunnels (drawer relationships) ────────────────────────
115
+ /**
116
+ * Link two drawers with a labelled relation. Both drawers must live in
117
+ * the same store — we don't cross global ↔ project boundaries because
118
+ * the link would silently break if either side is moved/deleted.
119
+ */
120
+ export function linkDrawers(fromId, toId, relation, cwd) {
121
+ // Try both stores to find the source drawer's scope
122
+ if (getGlobalStore().getDrawer(fromId)) {
123
+ if (!getGlobalStore().getDrawer(toId)) {
124
+ throw new Error(`linkDrawers: ${toId} not found in global scope (cross-scope tunnels not supported)`);
125
+ }
126
+ return getGlobalStore().addTunnel(fromId, toId, relation);
127
+ }
128
+ if (getProjectStore(cwd).getDrawer(fromId)) {
129
+ if (!getProjectStore(cwd).getDrawer(toId)) {
130
+ throw new Error(`linkDrawers: ${toId} not found in project scope (cross-scope tunnels not supported)`);
131
+ }
132
+ return getProjectStore(cwd).addTunnel(fromId, toId, relation);
133
+ }
134
+ throw new Error(`linkDrawers: source drawer ${fromId} not found in either scope`);
135
+ }
136
+ export function traverseTunnels(startId, cwd, maxDepth = 3) {
137
+ // Detect which scope the start drawer lives in
138
+ if (getGlobalStore().getDrawer(startId)) {
139
+ return getGlobalStore().traverse(startId, maxDepth);
140
+ }
141
+ return getProjectStore(cwd).traverse(startId, maxDepth);
142
+ }
143
+ // ── Knowledge graph ───────────────────────────────────────
144
+ export function kgAdd(triple, scope, cwd) {
145
+ if (scope === 'both')
146
+ throw new Error('kgAdd: scope must be global or project');
147
+ const store = scope === 'global' ? getGlobalStore() : getProjectStore(cwd);
148
+ return store.addTriple({
149
+ subject: triple.subject,
150
+ predicate: triple.predicate,
151
+ object: triple.object,
152
+ confidence: triple.confidence ?? 1.0,
153
+ sourceSessionId: triple.sourceSessionId,
154
+ sourceDrawerId: triple.sourceDrawerId,
155
+ });
156
+ }
157
+ export function kgQuery(q, cwd, scope = 'both') {
158
+ const out = [];
159
+ if (scope === 'global' || scope === 'both')
160
+ out.push(...getGlobalStore().queryTriples(q));
161
+ if (scope === 'project' || scope === 'both')
162
+ out.push(...getProjectStore(cwd).queryTriples(q));
163
+ return out;
164
+ }
165
+ export function kgTimeline(cwd, limit = 20, scope = 'both') {
166
+ const out = [];
167
+ if (scope === 'global' || scope === 'both')
168
+ out.push(...getGlobalStore().recentTriples(limit * 2));
169
+ if (scope === 'project' || scope === 'both')
170
+ out.push(...getProjectStore(cwd).recentTriples(limit * 2));
171
+ out.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
172
+ return out.slice(0, limit);
173
+ }
174
+ // ── Inventory / status ────────────────────────────────────
175
+ export function listWings(cwd, scope = 'both') {
176
+ return {
177
+ global: scope !== 'project' ? getGlobalStore().listWings() : [],
178
+ project: scope !== 'global' ? getProjectStore(cwd).listWings() : [],
179
+ };
180
+ }
181
+ export function listRooms(cwd, wing, scope = 'both') {
182
+ return {
183
+ global: scope !== 'project' ? getGlobalStore().listRooms(wing) : [],
184
+ project: scope !== 'global' ? getProjectStore(cwd).listRooms(wing) : [],
185
+ };
186
+ }
187
+ export function stats(cwd) {
188
+ return {
189
+ global: getGlobalStore().stats(),
190
+ project: getProjectStore(cwd).stats(),
191
+ globalPath: globalStorePath(),
192
+ projectPath: projectStorePath(cwd),
193
+ projectExists: existsSync(projectStorePath(cwd)),
194
+ };
195
+ }
196
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mempalace/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * MemPalace text search — tokenized, field-weighted scoring.
3
+ *
4
+ * Design goals:
5
+ * 1. Zero dependencies (no FTS index, no embedding model)
6
+ * 2. Reasonable ranking out of the box: title-ish fields outrank body
7
+ * 3. Tolerant to short queries (single keyword should still rank well)
8
+ * 4. Tolerant to plural / case variations via simple stemming
9
+ *
10
+ * We tokenize on non-alphanumeric, lowercase everything, drop tokens
11
+ * shorter than 2 chars and a small stopword list, and score each drawer
12
+ * by summing per-token field weights:
13
+ *
14
+ * tags × 4 (semantic tags are the strongest signal)
15
+ * room × 3
16
+ * wing × 2
17
+ * content × 1 (matches anywhere in body)
18
+ *
19
+ * Importance acts as a multiplier on the final score so the user can
20
+ * boost high-signal drawers via the importance field. Recency also
21
+ * lightly boosts via a half-life of 90 days.
22
+ *
23
+ * This is good enough until the store gets large; the search() entry
24
+ * point is the swap-in for a real FTS / embedding search later.
25
+ */
26
+ import type { Drawer, SearchOptions, SearchHit } from './types.js';
27
+ /**
28
+ * Tokenize a string into lowercase, deduplicated, non-stopword tokens of
29
+ * length ≥ 2. Very lightweight stemming: trailing 's' is stripped to fold
30
+ * "drawer"/"drawers" together. Aggressive stemming (Porter, etc.) would
31
+ * cost a lot for marginal recall gains at this scale.
32
+ */
33
+ export declare function tokenize(s: string): string[];
34
+ /**
35
+ * Top-level search. Filters by scope/wing/room/tags first, then scores
36
+ * each remaining drawer and returns the top `limit` by score.
37
+ */
38
+ export declare function searchDrawers(drawers: Drawer[], query: string, opts?: SearchOptions): SearchHit[];
@@ -0,0 +1,133 @@
1
+ /**
2
+ * MemPalace text search — tokenized, field-weighted scoring.
3
+ *
4
+ * Design goals:
5
+ * 1. Zero dependencies (no FTS index, no embedding model)
6
+ * 2. Reasonable ranking out of the box: title-ish fields outrank body
7
+ * 3. Tolerant to short queries (single keyword should still rank well)
8
+ * 4. Tolerant to plural / case variations via simple stemming
9
+ *
10
+ * We tokenize on non-alphanumeric, lowercase everything, drop tokens
11
+ * shorter than 2 chars and a small stopword list, and score each drawer
12
+ * by summing per-token field weights:
13
+ *
14
+ * tags × 4 (semantic tags are the strongest signal)
15
+ * room × 3
16
+ * wing × 2
17
+ * content × 1 (matches anywhere in body)
18
+ *
19
+ * Importance acts as a multiplier on the final score so the user can
20
+ * boost high-signal drawers via the importance field. Recency also
21
+ * lightly boosts via a half-life of 90 days.
22
+ *
23
+ * This is good enough until the store gets large; the search() entry
24
+ * point is the swap-in for a real FTS / embedding search later.
25
+ */
26
+ const STOPWORDS = new Set([
27
+ 'a', 'an', 'and', 'or', 'but', 'the', 'is', 'are', 'was', 'were', 'be',
28
+ 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will',
29
+ 'would', 'should', 'could', 'may', 'might', 'must', 'can', 'this',
30
+ 'that', 'these', 'those', 'of', 'in', 'on', 'at', 'to', 'for', 'with',
31
+ 'by', 'from', 'as', 'into', 'about', 'it', 'its', 'i', 'you', 'we',
32
+ 'they', 'them', 'me', 'my', 'your', 'our', 'their', 'his', 'her',
33
+ ]);
34
+ /**
35
+ * Tokenize a string into lowercase, deduplicated, non-stopword tokens of
36
+ * length ≥ 2. Very lightweight stemming: trailing 's' is stripped to fold
37
+ * "drawer"/"drawers" together. Aggressive stemming (Porter, etc.) would
38
+ * cost a lot for marginal recall gains at this scale.
39
+ */
40
+ export function tokenize(s) {
41
+ const out = new Set();
42
+ for (const raw of s.toLowerCase().split(/[^a-z0-9]+/)) {
43
+ if (raw.length < 2)
44
+ continue;
45
+ if (STOPWORDS.has(raw))
46
+ continue;
47
+ // Tiny stemmer: strip trailing 's' on words ≥ 4 chars (avoids "is"/"as" issues)
48
+ const stem = raw.length >= 4 && raw.endsWith('s') ? raw.slice(0, -1) : raw;
49
+ out.add(stem);
50
+ }
51
+ return [...out];
52
+ }
53
+ /**
54
+ * Score a single drawer against a tokenized query. Returns 0 if no match,
55
+ * otherwise a positive score (higher = better).
56
+ *
57
+ * The `matchedFields` array tracks which fields contributed so callers
58
+ * can use it for diagnostics / highlighting.
59
+ */
60
+ function scoreDrawer(drawer, queryTokens, matchedFields) {
61
+ if (queryTokens.length === 0)
62
+ return 0;
63
+ const contentTokens = new Set(tokenize(drawer.content));
64
+ const tagSet = new Set(drawer.tags.flatMap(tokenize));
65
+ const wingTokens = new Set(tokenize(drawer.wing));
66
+ const roomTokens = new Set(tokenize(drawer.room));
67
+ let score = 0;
68
+ let anyMatch = false;
69
+ for (const qt of queryTokens) {
70
+ if (tagSet.has(qt)) {
71
+ score += 4;
72
+ matchedFields.add('tags');
73
+ anyMatch = true;
74
+ }
75
+ if (roomTokens.has(qt)) {
76
+ score += 3;
77
+ matchedFields.add('room');
78
+ anyMatch = true;
79
+ }
80
+ if (wingTokens.has(qt)) {
81
+ score += 2;
82
+ matchedFields.add('wing');
83
+ anyMatch = true;
84
+ }
85
+ if (contentTokens.has(qt)) {
86
+ score += 1;
87
+ matchedFields.add('content');
88
+ anyMatch = true;
89
+ }
90
+ }
91
+ if (!anyMatch)
92
+ return 0;
93
+ // Importance multiplier — 0.5 default gives no boost, 1.0 → 1.5×, 0 → 0.5×
94
+ score *= 1 + (drawer.importance - 0.5);
95
+ // Recency bonus — half-life of 90 days. Drawers from today get +50%,
96
+ // 90-day-old drawers get +25%, older fade toward baseline.
97
+ const ageDays = (Date.now() - new Date(drawer.updatedAt).getTime()) / (1000 * 60 * 60 * 24);
98
+ const recencyBoost = 0.5 * Math.pow(0.5, ageDays / 90);
99
+ score *= 1 + recencyBoost;
100
+ return score;
101
+ }
102
+ /**
103
+ * Top-level search. Filters by scope/wing/room/tags first, then scores
104
+ * each remaining drawer and returns the top `limit` by score.
105
+ */
106
+ export function searchDrawers(drawers, query, opts = {}) {
107
+ const tokens = tokenize(query);
108
+ if (tokens.length === 0)
109
+ return [];
110
+ let candidates = drawers;
111
+ if (opts.wing)
112
+ candidates = candidates.filter((d) => d.wing === opts.wing.toLowerCase());
113
+ if (opts.room)
114
+ candidates = candidates.filter((d) => d.room === opts.room.toLowerCase());
115
+ if (opts.tags && opts.tags.length > 0) {
116
+ const required = opts.tags.map((t) => t.toLowerCase());
117
+ candidates = candidates.filter((d) => required.every((t) => d.tags.includes(t)));
118
+ }
119
+ if (opts.minImportance !== undefined) {
120
+ candidates = candidates.filter((d) => d.importance >= opts.minImportance);
121
+ }
122
+ const hits = [];
123
+ for (const d of candidates) {
124
+ const matchedFields = new Set();
125
+ const score = scoreDrawer(d, tokens, matchedFields);
126
+ if (score > 0) {
127
+ hits.push({ drawer: d, score, matchedFields: [...matchedFields] });
128
+ }
129
+ }
130
+ hits.sort((a, b) => b.score - a.score);
131
+ return hits.slice(0, opts.limit ?? 20);
132
+ }
133
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/mempalace/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,77 @@
1
+ import type { Drawer, Tunnel, KGTriple, Scope, WingMeta, RoomMeta, SearchOptions, SearchHit } from './types.js';
2
+ /** ~/.crowcoder/memory/store.json — cross-project knowledge */
3
+ export declare function globalStorePath(): string;
4
+ /** <cwd>/.crowcoder/memory/store.json — per-repo knowledge */
5
+ export declare function projectStorePath(cwd: string): string;
6
+ /**
7
+ * Time-ordered ID: hex timestamp + 8 random hex chars. Sorts naturally by
8
+ * creation time, no UUID library needed. ~96 bits of entropy total which
9
+ * is overkill for a single-user store but keeps collision chances at
10
+ * effective zero even across machines.
11
+ */
12
+ export declare function newId(prefix: string): string;
13
+ /**
14
+ * A single-file MemPalace store backed by JSON. Mutations are immediately
15
+ * persisted to disk — there's no in-memory cache to flush. Trades a small
16
+ * amount of throughput for crash safety, which matters more for a memory
17
+ * system.
18
+ */
19
+ export declare class JsonStore {
20
+ private readonly path;
21
+ readonly scope: Scope;
22
+ constructor(path: string, scope: Scope);
23
+ private read;
24
+ private write;
25
+ addDrawer(input: Omit<Drawer, 'id' | 'createdAt' | 'updatedAt' | 'scope'>): Drawer;
26
+ getDrawer(id: string): Drawer | null;
27
+ updateDrawer(id: string, patch: Partial<Omit<Drawer, 'id' | 'createdAt' | 'scope'>>): Drawer | null;
28
+ deleteDrawer(id: string): boolean;
29
+ listDrawers(filter?: {
30
+ wing?: string;
31
+ room?: string;
32
+ tag?: string;
33
+ }): Drawer[];
34
+ listWings(): WingMeta[];
35
+ listRooms(wing?: string): RoomMeta[];
36
+ addTunnel(fromDrawerId: string, toDrawerId: string, relation: string): Tunnel;
37
+ /**
38
+ * Find all tunnels touching a drawer, in either direction. Useful for
39
+ * "what is this drawer related to?" queries.
40
+ */
41
+ findTunnels(drawerId: string): {
42
+ outgoing: Tunnel[];
43
+ incoming: Tunnel[];
44
+ };
45
+ /**
46
+ * Walk outgoing tunnels from a start drawer for up to maxDepth hops,
47
+ * returning every drawer reached and the path that got us there.
48
+ * Bounded by visited-set so cycles don't loop forever.
49
+ */
50
+ traverse(startId: string, maxDepth?: number): {
51
+ drawer: Drawer;
52
+ depth: number;
53
+ via: string[];
54
+ }[];
55
+ deleteTunnel(id: string): boolean;
56
+ addTriple(t: Omit<KGTriple, 'id' | 'createdAt' | 'scope'>): KGTriple;
57
+ /**
58
+ * Query triples with optional s/p/o filters. Any field left undefined
59
+ * acts as a wildcard. Matching is case-insensitive equality on each
60
+ * specified field.
61
+ */
62
+ queryTriples(q: {
63
+ subject?: string;
64
+ predicate?: string;
65
+ object?: string;
66
+ }): KGTriple[];
67
+ /** Most-recent triples first. For "what have I been learning?" views. */
68
+ recentTriples(limit?: number): KGTriple[];
69
+ search(query: string, opts?: SearchOptions): SearchHit[];
70
+ stats(): {
71
+ drawers: number;
72
+ tunnels: number;
73
+ triples: number;
74
+ wings: number;
75
+ rooms: number;
76
+ };
77
+ }