@plur-ai/core 0.1.1 → 0.2.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/README.md +77 -32
- package/dist/index.d.ts +302 -104
- package/dist/index.js +525 -14
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @plur-ai/core
|
|
2
2
|
|
|
3
|
-
The engram engine — store, recall, and inject AI memory.
|
|
3
|
+
The engram engine — store, recall, and inject AI memory. Local-first, zero API calls for search, plain YAML storage.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install @plur-ai/core
|
|
@@ -10,9 +10,28 @@ npm install @plur-ai/core
|
|
|
10
10
|
import { Plur } from '@plur-ai/core'
|
|
11
11
|
|
|
12
12
|
const plur = new Plur()
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
|
|
14
|
+
// Learn from a correction
|
|
15
|
+
plur.learn('toEqual() in Vitest is strict — use toMatchObject() for partial matching', {
|
|
16
|
+
type: 'behavioral',
|
|
17
|
+
scope: 'project:my-app',
|
|
18
|
+
domain: 'dev/testing'
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Recall (hybrid: BM25 + embeddings via RRF, zero cost)
|
|
22
|
+
const results = await plur.recallHybrid('vitest assertion matching')
|
|
23
|
+
|
|
24
|
+
// Inject relevant engrams into agent context
|
|
25
|
+
const { directives, consider, count, tokens_used } = plur.inject('Write tests for the user service', {
|
|
26
|
+
scope: 'project:my-app',
|
|
27
|
+
budget: 2000
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Feedback trains the system
|
|
31
|
+
plur.feedback(results[0].id, 'positive')
|
|
32
|
+
|
|
33
|
+
// Sync across machines
|
|
34
|
+
plur.sync('git@github.com:you/plur-memory.git')
|
|
16
35
|
```
|
|
17
36
|
|
|
18
37
|
## API
|
|
@@ -23,11 +42,11 @@ console.log(injection.directives) // injected context, ready to prepend
|
|
|
23
42
|
const plur = new Plur({ path: '/custom/storage/path' })
|
|
24
43
|
```
|
|
25
44
|
|
|
26
|
-
Defaults to
|
|
45
|
+
Defaults to `~/.plur/`. Override with `PLUR_PATH` env var or `options.path`.
|
|
27
46
|
|
|
28
47
|
### `learn(statement, context?)`
|
|
29
48
|
|
|
30
|
-
Create an engram.
|
|
49
|
+
Create an engram. Detects conflicts with existing engrams in the same scope.
|
|
31
50
|
|
|
32
51
|
```typescript
|
|
33
52
|
plur.learn('Always run lint before committing', {
|
|
@@ -38,14 +57,24 @@ plur.learn('Always run lint before committing', {
|
|
|
38
57
|
})
|
|
39
58
|
```
|
|
40
59
|
|
|
41
|
-
###
|
|
60
|
+
### Search methods
|
|
42
61
|
|
|
43
|
-
|
|
62
|
+
Five search modes, from fastest to most accurate:
|
|
63
|
+
|
|
64
|
+
| Method | Speed | API calls | Best for |
|
|
65
|
+
|--------|-------|-----------|----------|
|
|
66
|
+
| `recall(query)` | Instant | None | Quick keyword lookup |
|
|
67
|
+
| `recallSemantic(query)` | ~200ms | None | Meaning-based search (local embeddings) |
|
|
68
|
+
| `recallHybrid(query)` | ~200ms | None | **Best default** — BM25 + embeddings via RRF |
|
|
69
|
+
| `recallAsync(query, { llm })` | ~1s | 1 LLM call | LLM-assisted semantic filtering |
|
|
70
|
+
| `recallExpanded(query, { llm })` | ~3s | 3-5 LLM calls | Query expansion + hybrid + RRF merge |
|
|
71
|
+
|
|
72
|
+
All accept the same options:
|
|
44
73
|
|
|
45
74
|
```typescript
|
|
46
|
-
const results = plur.
|
|
75
|
+
const results = await plur.recallHybrid('deployment process', {
|
|
47
76
|
scope: 'project:myapp', // includes global + matching scopes
|
|
48
|
-
domain: 'software',
|
|
77
|
+
domain: 'software', // prefix match
|
|
49
78
|
limit: 10,
|
|
50
79
|
min_strength: 0.5,
|
|
51
80
|
})
|
|
@@ -53,7 +82,7 @@ const results = plur.recall('deployment process', {
|
|
|
53
82
|
|
|
54
83
|
### `inject(task, options?)`
|
|
55
84
|
|
|
56
|
-
Select and score engrams within a token budget. Returns
|
|
85
|
+
Select and score engrams within a token budget. Returns formatted strings ready to prepend to a system prompt.
|
|
57
86
|
|
|
58
87
|
```typescript
|
|
59
88
|
const { directives, consider, count, tokens_used } = plur.inject('refactor the auth module', {
|
|
@@ -64,50 +93,56 @@ const { directives, consider, count, tokens_used } = plur.inject('refactor the a
|
|
|
64
93
|
|
|
65
94
|
### `feedback(id, signal)`
|
|
66
95
|
|
|
67
|
-
Rate an engram's usefulness.
|
|
96
|
+
Rate an engram's usefulness. Trains injection relevance over time.
|
|
68
97
|
|
|
69
98
|
```typescript
|
|
70
99
|
plur.feedback('ENG-001', 'positive') // +0.05 retrieval strength
|
|
71
100
|
plur.feedback('ENG-002', 'negative') // -0.10 retrieval strength
|
|
72
|
-
plur.feedback('ENG-003', 'neutral') // signal recorded, no strength change
|
|
73
101
|
```
|
|
74
102
|
|
|
75
103
|
### `forget(id, reason?)`
|
|
76
104
|
|
|
77
|
-
Retire an engram.
|
|
105
|
+
Retire an engram. History preserved, excluded from recall and injection.
|
|
78
106
|
|
|
79
107
|
```typescript
|
|
80
108
|
plur.forget('ENG-001', 'API changed')
|
|
81
109
|
```
|
|
82
110
|
|
|
83
|
-
### `
|
|
111
|
+
### `sync(remote?)`
|
|
84
112
|
|
|
85
|
-
|
|
113
|
+
Git-based sync across machines. Initializes on first call, commits + push/pull on subsequent calls.
|
|
86
114
|
|
|
87
115
|
```typescript
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
116
|
+
// First time — init repo and push
|
|
117
|
+
plur.sync('git@github.com:you/plur-memory.git')
|
|
118
|
+
|
|
119
|
+
// Later — commit, pull, push
|
|
120
|
+
plur.sync()
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Check sync status (no changes made)
|
|
125
|
+
const status = plur.syncStatus()
|
|
126
|
+
// { initialized, remote, dirty, branch, ahead, behind }
|
|
94
127
|
```
|
|
95
128
|
|
|
96
|
-
### `timeline(query?)`
|
|
129
|
+
### `capture(summary, context?)` / `timeline(query?)`
|
|
97
130
|
|
|
98
|
-
|
|
131
|
+
Episodic memory — record what happened, query the timeline.
|
|
99
132
|
|
|
100
133
|
```typescript
|
|
101
|
-
|
|
102
|
-
since: new Date('2025-01-01'),
|
|
134
|
+
plur.capture('Deployed v2.0 to production', {
|
|
103
135
|
agent: 'claude-code',
|
|
104
|
-
|
|
136
|
+
session_id: 'abc123',
|
|
137
|
+
tags: ['deploy'],
|
|
105
138
|
})
|
|
139
|
+
|
|
140
|
+
const episodes = plur.timeline({ since: new Date('2025-01-01'), agent: 'claude-code' })
|
|
106
141
|
```
|
|
107
142
|
|
|
108
143
|
### `ingest(content, options?)`
|
|
109
144
|
|
|
110
|
-
Extract engram candidates from text using pattern matching.
|
|
145
|
+
Extract engram candidates from text using pattern matching.
|
|
111
146
|
|
|
112
147
|
```typescript
|
|
113
148
|
const candidates = plur.ingest(markdownContent, {
|
|
@@ -115,7 +150,6 @@ const candidates = plur.ingest(markdownContent, {
|
|
|
115
150
|
scope: 'project:myapp',
|
|
116
151
|
source: 'docs/architecture.md',
|
|
117
152
|
})
|
|
118
|
-
// set extract_only: false (default) to auto-save candidates as engrams
|
|
119
153
|
```
|
|
120
154
|
|
|
121
155
|
### `installPack(source)` / `exportPack(...)` / `listPacks()`
|
|
@@ -130,10 +164,21 @@ plur.exportPack(engrams, './output', { name: 'my-pack', version: '1.0.0' })
|
|
|
130
164
|
|
|
131
165
|
### `status()`
|
|
132
166
|
|
|
133
|
-
Return system health.
|
|
134
|
-
|
|
135
167
|
```typescript
|
|
136
|
-
const { engram_count, episode_count, pack_count, storage_root } = plur.status()
|
|
168
|
+
const { engram_count, episode_count, pack_count, storage_root, config } = plur.status()
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Storage
|
|
172
|
+
|
|
173
|
+
Everything is plain YAML. Open it, read it, edit it.
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
~/.plur/
|
|
177
|
+
├── engrams.yaml # learned knowledge
|
|
178
|
+
├── episodes.yaml # session timeline
|
|
179
|
+
├── candidates.yaml # pending engrams
|
|
180
|
+
├── config.yaml # settings
|
|
181
|
+
└── packs/ # installed engram packs
|
|
137
182
|
```
|
|
138
183
|
|
|
139
184
|
## License
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,9 @@ declare const EngramSchema: z.ZodObject<{
|
|
|
48
48
|
contraindications: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
49
49
|
source_patterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
50
50
|
derivation_count: z.ZodDefault<z.ZodNumber>;
|
|
51
|
+
pack: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
52
|
+
abstract: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
53
|
+
derived_from: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
51
54
|
knowledge_type: z.ZodOptional<z.ZodObject<{
|
|
52
55
|
memory_class: z.ZodEnum<["semantic", "episodic", "procedural", "metacognitive"]>;
|
|
53
56
|
cognitive_level: z.ZodEnum<["remember", "understand", "apply", "analyze", "evaluate", "create"]>;
|
|
@@ -59,6 +62,23 @@ declare const EngramSchema: z.ZodObject<{
|
|
|
59
62
|
cognitive_level: "remember" | "understand" | "apply" | "analyze" | "evaluate" | "create";
|
|
60
63
|
}>>;
|
|
61
64
|
domain: z.ZodOptional<z.ZodString>;
|
|
65
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
66
|
+
activation: z.ZodDefault<z.ZodObject<{
|
|
67
|
+
retrieval_strength: z.ZodNumber;
|
|
68
|
+
storage_strength: z.ZodNumber;
|
|
69
|
+
frequency: z.ZodNumber;
|
|
70
|
+
last_accessed: z.ZodString;
|
|
71
|
+
}, "strip", z.ZodTypeAny, {
|
|
72
|
+
retrieval_strength: number;
|
|
73
|
+
storage_strength: number;
|
|
74
|
+
frequency: number;
|
|
75
|
+
last_accessed: string;
|
|
76
|
+
}, {
|
|
77
|
+
retrieval_strength: number;
|
|
78
|
+
storage_strength: number;
|
|
79
|
+
frequency: number;
|
|
80
|
+
last_accessed: string;
|
|
81
|
+
}>>;
|
|
62
82
|
relations: z.ZodOptional<z.ZodObject<{
|
|
63
83
|
broader: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
64
84
|
narrower: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
|
|
@@ -75,21 +95,56 @@ declare const EngramSchema: z.ZodObject<{
|
|
|
75
95
|
related?: string[] | undefined;
|
|
76
96
|
conflicts?: string[] | undefined;
|
|
77
97
|
}>>;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
98
|
+
associations: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
99
|
+
target_type: z.ZodEnum<["engram", "document"]>;
|
|
100
|
+
target: z.ZodString;
|
|
101
|
+
strength: z.ZodNumber;
|
|
102
|
+
type: z.ZodEnum<["semantic", "temporal", "causal", "co_accessed"]>;
|
|
103
|
+
updated_at: z.ZodOptional<z.ZodString>;
|
|
83
104
|
}, "strip", z.ZodTypeAny, {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
105
|
+
type: "semantic" | "temporal" | "causal" | "co_accessed";
|
|
106
|
+
target_type: "engram" | "document";
|
|
107
|
+
target: string;
|
|
108
|
+
strength: number;
|
|
109
|
+
updated_at?: string | undefined;
|
|
88
110
|
}, {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
111
|
+
type: "semantic" | "temporal" | "causal" | "co_accessed";
|
|
112
|
+
target_type: "engram" | "document";
|
|
113
|
+
target: string;
|
|
114
|
+
strength: number;
|
|
115
|
+
updated_at?: string | undefined;
|
|
116
|
+
}>, "many">>;
|
|
117
|
+
knowledge_anchors: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
118
|
+
path: z.ZodString;
|
|
119
|
+
relevance: z.ZodDefault<z.ZodEnum<["primary", "supporting", "example"]>>;
|
|
120
|
+
snippet: z.ZodOptional<z.ZodString>;
|
|
121
|
+
snippet_extracted_at: z.ZodOptional<z.ZodString>;
|
|
122
|
+
}, "strip", z.ZodTypeAny, {
|
|
123
|
+
path: string;
|
|
124
|
+
relevance: "primary" | "supporting" | "example";
|
|
125
|
+
snippet?: string | undefined;
|
|
126
|
+
snippet_extracted_at?: string | undefined;
|
|
127
|
+
}, {
|
|
128
|
+
path: string;
|
|
129
|
+
relevance?: "primary" | "supporting" | "example" | undefined;
|
|
130
|
+
snippet?: string | undefined;
|
|
131
|
+
snippet_extracted_at?: string | undefined;
|
|
132
|
+
}>, "many">>;
|
|
133
|
+
dual_coding: z.ZodOptional<z.ZodEffects<z.ZodObject<{
|
|
134
|
+
example: z.ZodOptional<z.ZodString>;
|
|
135
|
+
analogy: z.ZodOptional<z.ZodString>;
|
|
136
|
+
}, "strip", z.ZodTypeAny, {
|
|
137
|
+
example?: string | undefined;
|
|
138
|
+
analogy?: string | undefined;
|
|
139
|
+
}, {
|
|
140
|
+
example?: string | undefined;
|
|
141
|
+
analogy?: string | undefined;
|
|
142
|
+
}>, {
|
|
143
|
+
example?: string | undefined;
|
|
144
|
+
analogy?: string | undefined;
|
|
145
|
+
}, {
|
|
146
|
+
example?: string | undefined;
|
|
147
|
+
analogy?: string | undefined;
|
|
93
148
|
}>>;
|
|
94
149
|
provenance: z.ZodOptional<z.ZodObject<{
|
|
95
150
|
origin: z.ZodString;
|
|
@@ -120,61 +175,90 @@ declare const EngramSchema: z.ZodObject<{
|
|
|
120
175
|
negative?: number | undefined;
|
|
121
176
|
neutral?: number | undefined;
|
|
122
177
|
}>>;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
178
|
+
/** Typed entity references extracted from statement. Enables graph queries. */
|
|
179
|
+
entities: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
180
|
+
name: z.ZodString;
|
|
181
|
+
type: z.ZodEnum<["person", "organization", "technology", "concept", "project", "tool", "place", "event", "standard", "other"]>;
|
|
182
|
+
uri: z.ZodOptional<z.ZodString>;
|
|
128
183
|
}, "strip", z.ZodTypeAny, {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
snippet_extracted_at?: string | undefined;
|
|
184
|
+
type: "person" | "organization" | "technology" | "concept" | "project" | "tool" | "place" | "event" | "standard" | "other";
|
|
185
|
+
name: string;
|
|
186
|
+
uri?: string | undefined;
|
|
133
187
|
}, {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
snippet_extracted_at?: string | undefined;
|
|
188
|
+
type: "person" | "organization" | "technology" | "concept" | "project" | "tool" | "place" | "event" | "standard" | "other";
|
|
189
|
+
name: string;
|
|
190
|
+
uri?: string | undefined;
|
|
138
191
|
}>, "many">>;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
192
|
+
/** Temporal validity window. When is this knowledge true? */
|
|
193
|
+
temporal: z.ZodOptional<z.ZodObject<{
|
|
194
|
+
learned_at: z.ZodString;
|
|
195
|
+
valid_from: z.ZodOptional<z.ZodString>;
|
|
196
|
+
valid_until: z.ZodOptional<z.ZodString>;
|
|
197
|
+
ingested_at: z.ZodOptional<z.ZodString>;
|
|
145
198
|
}, "strip", z.ZodTypeAny, {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
updated_at?: string | undefined;
|
|
199
|
+
learned_at: string;
|
|
200
|
+
valid_from?: string | undefined;
|
|
201
|
+
valid_until?: string | undefined;
|
|
202
|
+
ingested_at?: string | undefined;
|
|
151
203
|
}, {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
204
|
+
learned_at: string;
|
|
205
|
+
valid_from?: string | undefined;
|
|
206
|
+
valid_until?: string | undefined;
|
|
207
|
+
ingested_at?: string | undefined;
|
|
208
|
+
}>>;
|
|
209
|
+
/** Automatic usage tracking. Injections, hits, misses. */
|
|
210
|
+
usage: z.ZodOptional<z.ZodObject<{
|
|
211
|
+
injections: z.ZodDefault<z.ZodNumber>;
|
|
212
|
+
hits: z.ZodDefault<z.ZodNumber>;
|
|
213
|
+
misses: z.ZodDefault<z.ZodNumber>;
|
|
214
|
+
last_hit_at: z.ZodOptional<z.ZodString>;
|
|
161
215
|
}, "strip", z.ZodTypeAny, {
|
|
162
|
-
|
|
163
|
-
|
|
216
|
+
injections: number;
|
|
217
|
+
hits: number;
|
|
218
|
+
misses: number;
|
|
219
|
+
last_hit_at?: string | undefined;
|
|
164
220
|
}, {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
221
|
+
injections?: number | undefined;
|
|
222
|
+
hits?: number | undefined;
|
|
223
|
+
misses?: number | undefined;
|
|
224
|
+
last_hit_at?: string | undefined;
|
|
225
|
+
}>>;
|
|
226
|
+
/** Episodic context: emotional weight, confidence, trigger. */
|
|
227
|
+
episodic: z.ZodOptional<z.ZodObject<{
|
|
228
|
+
emotional_weight: z.ZodDefault<z.ZodNumber>;
|
|
229
|
+
confidence: z.ZodDefault<z.ZodNumber>;
|
|
230
|
+
trigger_context: z.ZodOptional<z.ZodString>;
|
|
231
|
+
journal_ref: z.ZodOptional<z.ZodString>;
|
|
232
|
+
}, "strip", z.ZodTypeAny, {
|
|
233
|
+
emotional_weight: number;
|
|
234
|
+
confidence: number;
|
|
235
|
+
trigger_context?: string | undefined;
|
|
236
|
+
journal_ref?: string | undefined;
|
|
170
237
|
}, {
|
|
171
|
-
|
|
172
|
-
|
|
238
|
+
emotional_weight?: number | undefined;
|
|
239
|
+
confidence?: number | undefined;
|
|
240
|
+
trigger_context?: string | undefined;
|
|
241
|
+
journal_ref?: string | undefined;
|
|
173
242
|
}>>;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
243
|
+
/** Exchange marketplace metadata: fitness, adoption, diversity. */
|
|
244
|
+
exchange: z.ZodOptional<z.ZodObject<{
|
|
245
|
+
fitness_score: z.ZodOptional<z.ZodNumber>;
|
|
246
|
+
environmental_diversity: z.ZodDefault<z.ZodNumber>;
|
|
247
|
+
adoption_count: z.ZodDefault<z.ZodNumber>;
|
|
248
|
+
contradiction_rate: z.ZodDefault<z.ZodNumber>;
|
|
249
|
+
}, "strip", z.ZodTypeAny, {
|
|
250
|
+
environmental_diversity: number;
|
|
251
|
+
adoption_count: number;
|
|
252
|
+
contradiction_rate: number;
|
|
253
|
+
fitness_score?: number | undefined;
|
|
254
|
+
}, {
|
|
255
|
+
fitness_score?: number | undefined;
|
|
256
|
+
environmental_diversity?: number | undefined;
|
|
257
|
+
adoption_count?: number | undefined;
|
|
258
|
+
contradiction_rate?: number | undefined;
|
|
259
|
+
}>>;
|
|
260
|
+
/** Extensible key-value data for domain-specific fields. */
|
|
261
|
+
structured_data: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
178
262
|
}, "strip", z.ZodTypeAny, {
|
|
179
263
|
type: "behavioral" | "architectural" | "procedural" | "terminological";
|
|
180
264
|
status: "active" | "dormant" | "retired" | "candidate";
|
|
@@ -185,23 +269,16 @@ declare const EngramSchema: z.ZodObject<{
|
|
|
185
269
|
visibility: "private" | "public" | "template";
|
|
186
270
|
statement: string;
|
|
187
271
|
derivation_count: number;
|
|
272
|
+
pack: string | null;
|
|
273
|
+
abstract: string | null;
|
|
274
|
+
derived_from: string | null;
|
|
275
|
+
tags: string[];
|
|
188
276
|
activation: {
|
|
189
277
|
retrieval_strength: number;
|
|
190
278
|
storage_strength: number;
|
|
191
279
|
frequency: number;
|
|
192
280
|
last_accessed: string;
|
|
193
281
|
};
|
|
194
|
-
feedback_signals: {
|
|
195
|
-
positive: number;
|
|
196
|
-
negative: number;
|
|
197
|
-
neutral: number;
|
|
198
|
-
};
|
|
199
|
-
knowledge_anchors: {
|
|
200
|
-
path: string;
|
|
201
|
-
relevance: "primary" | "supporting" | "example";
|
|
202
|
-
snippet?: string | undefined;
|
|
203
|
-
snippet_extracted_at?: string | undefined;
|
|
204
|
-
}[];
|
|
205
282
|
associations: {
|
|
206
283
|
type: "semantic" | "temporal" | "causal" | "co_accessed";
|
|
207
284
|
target_type: "engram" | "document";
|
|
@@ -209,13 +286,26 @@ declare const EngramSchema: z.ZodObject<{
|
|
|
209
286
|
strength: number;
|
|
210
287
|
updated_at?: string | undefined;
|
|
211
288
|
}[];
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
289
|
+
knowledge_anchors: {
|
|
290
|
+
path: string;
|
|
291
|
+
relevance: "primary" | "supporting" | "example";
|
|
292
|
+
snippet?: string | undefined;
|
|
293
|
+
snippet_extracted_at?: string | undefined;
|
|
294
|
+
}[];
|
|
295
|
+
feedback_signals: {
|
|
296
|
+
positive: number;
|
|
297
|
+
negative: number;
|
|
298
|
+
neutral: number;
|
|
299
|
+
};
|
|
216
300
|
rationale?: string | undefined;
|
|
217
301
|
contraindications?: string[] | undefined;
|
|
218
302
|
source_patterns?: string[] | undefined;
|
|
303
|
+
episodic?: {
|
|
304
|
+
emotional_weight: number;
|
|
305
|
+
confidence: number;
|
|
306
|
+
trigger_context?: string | undefined;
|
|
307
|
+
journal_ref?: string | undefined;
|
|
308
|
+
} | undefined;
|
|
219
309
|
knowledge_type?: {
|
|
220
310
|
memory_class: "procedural" | "semantic" | "episodic" | "metacognitive";
|
|
221
311
|
cognitive_level: "remember" | "understand" | "apply" | "analyze" | "evaluate" | "create";
|
|
@@ -227,16 +317,40 @@ declare const EngramSchema: z.ZodObject<{
|
|
|
227
317
|
related: string[];
|
|
228
318
|
conflicts: string[];
|
|
229
319
|
} | undefined;
|
|
320
|
+
temporal?: {
|
|
321
|
+
learned_at: string;
|
|
322
|
+
valid_from?: string | undefined;
|
|
323
|
+
valid_until?: string | undefined;
|
|
324
|
+
ingested_at?: string | undefined;
|
|
325
|
+
} | undefined;
|
|
326
|
+
dual_coding?: {
|
|
327
|
+
example?: string | undefined;
|
|
328
|
+
analogy?: string | undefined;
|
|
329
|
+
} | undefined;
|
|
230
330
|
provenance?: {
|
|
231
331
|
origin: string;
|
|
232
332
|
chain: string[];
|
|
233
333
|
signature: string | null;
|
|
234
334
|
license: string;
|
|
235
335
|
} | undefined;
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
336
|
+
entities?: {
|
|
337
|
+
type: "person" | "organization" | "technology" | "concept" | "project" | "tool" | "place" | "event" | "standard" | "other";
|
|
338
|
+
name: string;
|
|
339
|
+
uri?: string | undefined;
|
|
340
|
+
}[] | undefined;
|
|
341
|
+
usage?: {
|
|
342
|
+
injections: number;
|
|
343
|
+
hits: number;
|
|
344
|
+
misses: number;
|
|
345
|
+
last_hit_at?: string | undefined;
|
|
239
346
|
} | undefined;
|
|
347
|
+
exchange?: {
|
|
348
|
+
environmental_diversity: number;
|
|
349
|
+
adoption_count: number;
|
|
350
|
+
contradiction_rate: number;
|
|
351
|
+
fitness_score?: number | undefined;
|
|
352
|
+
} | undefined;
|
|
353
|
+
structured_data?: Record<string, unknown> | undefined;
|
|
240
354
|
}, {
|
|
241
355
|
type: "behavioral" | "architectural" | "procedural" | "terminological";
|
|
242
356
|
status: "active" | "dormant" | "retired" | "candidate";
|
|
@@ -250,22 +364,55 @@ declare const EngramSchema: z.ZodObject<{
|
|
|
250
364
|
contraindications?: string[] | undefined;
|
|
251
365
|
source_patterns?: string[] | undefined;
|
|
252
366
|
derivation_count?: number | undefined;
|
|
367
|
+
pack?: string | null | undefined;
|
|
368
|
+
abstract?: string | null | undefined;
|
|
369
|
+
derived_from?: string | null | undefined;
|
|
370
|
+
episodic?: {
|
|
371
|
+
emotional_weight?: number | undefined;
|
|
372
|
+
confidence?: number | undefined;
|
|
373
|
+
trigger_context?: string | undefined;
|
|
374
|
+
journal_ref?: string | undefined;
|
|
375
|
+
} | undefined;
|
|
253
376
|
knowledge_type?: {
|
|
254
377
|
memory_class: "procedural" | "semantic" | "episodic" | "metacognitive";
|
|
255
378
|
cognitive_level: "remember" | "understand" | "apply" | "analyze" | "evaluate" | "create";
|
|
256
379
|
} | undefined;
|
|
257
380
|
domain?: string | undefined;
|
|
381
|
+
tags?: string[] | undefined;
|
|
382
|
+
activation?: {
|
|
383
|
+
retrieval_strength: number;
|
|
384
|
+
storage_strength: number;
|
|
385
|
+
frequency: number;
|
|
386
|
+
last_accessed: string;
|
|
387
|
+
} | undefined;
|
|
258
388
|
relations?: {
|
|
259
389
|
broader?: string[] | undefined;
|
|
260
390
|
narrower?: string[] | undefined;
|
|
261
391
|
related?: string[] | undefined;
|
|
262
392
|
conflicts?: string[] | undefined;
|
|
263
393
|
} | undefined;
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
394
|
+
temporal?: {
|
|
395
|
+
learned_at: string;
|
|
396
|
+
valid_from?: string | undefined;
|
|
397
|
+
valid_until?: string | undefined;
|
|
398
|
+
ingested_at?: string | undefined;
|
|
399
|
+
} | undefined;
|
|
400
|
+
associations?: {
|
|
401
|
+
type: "semantic" | "temporal" | "causal" | "co_accessed";
|
|
402
|
+
target_type: "engram" | "document";
|
|
403
|
+
target: string;
|
|
404
|
+
strength: number;
|
|
405
|
+
updated_at?: string | undefined;
|
|
406
|
+
}[] | undefined;
|
|
407
|
+
knowledge_anchors?: {
|
|
408
|
+
path: string;
|
|
409
|
+
relevance?: "primary" | "supporting" | "example" | undefined;
|
|
410
|
+
snippet?: string | undefined;
|
|
411
|
+
snippet_extracted_at?: string | undefined;
|
|
412
|
+
}[] | undefined;
|
|
413
|
+
dual_coding?: {
|
|
414
|
+
example?: string | undefined;
|
|
415
|
+
analogy?: string | undefined;
|
|
269
416
|
} | undefined;
|
|
270
417
|
provenance?: {
|
|
271
418
|
origin: string;
|
|
@@ -278,27 +425,24 @@ declare const EngramSchema: z.ZodObject<{
|
|
|
278
425
|
negative?: number | undefined;
|
|
279
426
|
neutral?: number | undefined;
|
|
280
427
|
} | undefined;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
snippet_extracted_at?: string | undefined;
|
|
286
|
-
}[] | undefined;
|
|
287
|
-
associations?: {
|
|
288
|
-
type: "semantic" | "temporal" | "causal" | "co_accessed";
|
|
289
|
-
target_type: "engram" | "document";
|
|
290
|
-
target: string;
|
|
291
|
-
strength: number;
|
|
292
|
-
updated_at?: string | undefined;
|
|
428
|
+
entities?: {
|
|
429
|
+
type: "person" | "organization" | "technology" | "concept" | "project" | "tool" | "place" | "event" | "standard" | "other";
|
|
430
|
+
name: string;
|
|
431
|
+
uri?: string | undefined;
|
|
293
432
|
}[] | undefined;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
433
|
+
usage?: {
|
|
434
|
+
injections?: number | undefined;
|
|
435
|
+
hits?: number | undefined;
|
|
436
|
+
misses?: number | undefined;
|
|
437
|
+
last_hit_at?: string | undefined;
|
|
297
438
|
} | undefined;
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
439
|
+
exchange?: {
|
|
440
|
+
fitness_score?: number | undefined;
|
|
441
|
+
environmental_diversity?: number | undefined;
|
|
442
|
+
adoption_count?: number | undefined;
|
|
443
|
+
contradiction_rate?: number | undefined;
|
|
444
|
+
} | undefined;
|
|
445
|
+
structured_data?: Record<string, unknown> | undefined;
|
|
302
446
|
}>;
|
|
303
447
|
type Engram = z.infer<typeof EngramSchema>;
|
|
304
448
|
type KnowledgeAnchor = z.infer<typeof KnowledgeAnchorSchema>;
|
|
@@ -351,8 +495,8 @@ declare const PackManifestSchema: z.ZodObject<{
|
|
|
351
495
|
}>>;
|
|
352
496
|
}, "strip", z.ZodTypeAny, {
|
|
353
497
|
version: string;
|
|
354
|
-
license: string;
|
|
355
498
|
tags: string[];
|
|
499
|
+
license: string;
|
|
356
500
|
name: string;
|
|
357
501
|
description?: string | undefined;
|
|
358
502
|
creator?: string | undefined;
|
|
@@ -373,8 +517,8 @@ declare const PackManifestSchema: z.ZodObject<{
|
|
|
373
517
|
}, {
|
|
374
518
|
version: string;
|
|
375
519
|
name: string;
|
|
376
|
-
license?: string | undefined;
|
|
377
520
|
tags?: string[] | undefined;
|
|
521
|
+
license?: string | undefined;
|
|
378
522
|
description?: string | undefined;
|
|
379
523
|
creator?: string | undefined;
|
|
380
524
|
metadata?: {
|
|
@@ -402,6 +546,21 @@ interface PackInfo {
|
|
|
402
546
|
}
|
|
403
547
|
declare function listPacks(packsDir: string): PackInfo[];
|
|
404
548
|
|
|
549
|
+
interface SyncStatus {
|
|
550
|
+
initialized: boolean;
|
|
551
|
+
remote: string | null;
|
|
552
|
+
dirty: boolean;
|
|
553
|
+
branch: string | null;
|
|
554
|
+
ahead: number;
|
|
555
|
+
behind: number;
|
|
556
|
+
}
|
|
557
|
+
interface SyncResult {
|
|
558
|
+
action: 'initialized' | 'committed' | 'synced' | 'up-to-date';
|
|
559
|
+
message: string;
|
|
560
|
+
remote: string | null;
|
|
561
|
+
files_changed: number;
|
|
562
|
+
}
|
|
563
|
+
|
|
405
564
|
declare const EpisodeSchema: z.ZodObject<{
|
|
406
565
|
id: z.ZodString;
|
|
407
566
|
summary: z.ZodString;
|
|
@@ -522,6 +681,33 @@ interface TimelineQuery {
|
|
|
522
681
|
search?: string;
|
|
523
682
|
}
|
|
524
683
|
|
|
684
|
+
/** Build searchable text from all engram fields */
|
|
685
|
+
declare function engramSearchText(engram: Engram): string;
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Non-blocking version check against npm registry.
|
|
689
|
+
* Caches result in memory — one fetch per process lifetime.
|
|
690
|
+
* Never throws or blocks startup.
|
|
691
|
+
*/
|
|
692
|
+
interface VersionCheckResult {
|
|
693
|
+
current: string;
|
|
694
|
+
latest: string | null;
|
|
695
|
+
updateAvailable: boolean;
|
|
696
|
+
checkedAt: number | null;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Check npm for a newer version. Fetches once, caches forever (process lifetime).
|
|
700
|
+
* Fire-and-forget: call at startup, read later via getCachedUpdateCheck().
|
|
701
|
+
*/
|
|
702
|
+
declare function checkForUpdate(packageName: string, currentVersion: string, onResult?: (result: VersionCheckResult) => void): Promise<VersionCheckResult>;
|
|
703
|
+
/**
|
|
704
|
+
* Read the cached version check result. Returns null if checkForUpdate() hasn't
|
|
705
|
+
* completed yet. This is the zero-cost read path for assemblers.
|
|
706
|
+
*/
|
|
707
|
+
declare function getCachedUpdateCheck(packageName: string): VersionCheckResult | null;
|
|
708
|
+
/** Clear cache (for testing). */
|
|
709
|
+
declare function clearVersionCache(): void;
|
|
710
|
+
|
|
525
711
|
interface IngestOptions {
|
|
526
712
|
source?: string;
|
|
527
713
|
extract_only?: boolean;
|
|
@@ -560,6 +746,14 @@ declare class Plur {
|
|
|
560
746
|
recallAsync(query: string, options: RecallOptions & {
|
|
561
747
|
llm: LlmFunction;
|
|
562
748
|
}): Promise<Engram[]>;
|
|
749
|
+
/** Search engrams using local embeddings (transformers.js). Async, no API calls. */
|
|
750
|
+
recallSemantic(query: string, options?: Omit<RecallOptions, 'mode' | 'llm'>): Promise<Engram[]>;
|
|
751
|
+
/** Hybrid search: BM25 + embeddings merged via Reciprocal Rank Fusion. Async, no API calls. */
|
|
752
|
+
recallHybrid(query: string, options?: Omit<RecallOptions, 'mode' | 'llm'>): Promise<Engram[]>;
|
|
753
|
+
/** Expanded search: LLM query expansion + hybrid search + RRF merge. Opt-in, requires LLM function. */
|
|
754
|
+
recallExpanded(query: string, options: RecallOptions & {
|
|
755
|
+
llm: LlmFunction;
|
|
756
|
+
}): Promise<Engram[]>;
|
|
563
757
|
/** Filter engrams by scope/domain/strength (shared by both modes) */
|
|
564
758
|
private _filterEngrams;
|
|
565
759
|
/** Reactivate accessed engrams (bump retrieval strength, frequency, last_accessed) */
|
|
@@ -593,8 +787,12 @@ declare class Plur {
|
|
|
593
787
|
};
|
|
594
788
|
/** List all installed packs. */
|
|
595
789
|
listPacks(): ReturnType<typeof listPacks>;
|
|
790
|
+
/** Sync engrams to git. Initializes repo on first call, commits + push/pull on subsequent calls. */
|
|
791
|
+
sync(remote?: string): SyncResult;
|
|
792
|
+
/** Get git sync status without making changes. */
|
|
793
|
+
syncStatus(): SyncStatus;
|
|
596
794
|
/** Return system health info. */
|
|
597
795
|
status(): StatusResult;
|
|
598
796
|
}
|
|
599
797
|
|
|
600
|
-
export { type Association, type CaptureContext, type Engram, type Episode, type IngestCandidate, type IngestOptions, type InjectOptions, type InjectionResult, type KnowledgeAnchor, type LearnContext, type LlmFunction, type PackManifest, Plur, type PlurConfig, type RecallOptions, type StatusResult, type TimelineQuery };
|
|
798
|
+
export { type Association, type CaptureContext, type Engram, type Episode, type IngestCandidate, type IngestOptions, type InjectOptions, type InjectionResult, type KnowledgeAnchor, type LearnContext, type LlmFunction, type PackManifest, Plur, type PlurConfig, type RecallOptions, type StatusResult, type SyncResult, type SyncStatus, type TimelineQuery, type VersionCheckResult, checkForUpdate, clearVersionCache, engramSearchText, getCachedUpdateCheck };
|
package/dist/index.js
CHANGED
|
@@ -102,7 +102,48 @@ var FeedbackSignalsSchema = z2.object({
|
|
|
102
102
|
negative: z2.number().int().default(0),
|
|
103
103
|
neutral: z2.number().int().default(0)
|
|
104
104
|
});
|
|
105
|
+
var EntityRefSchema = z2.object({
|
|
106
|
+
name: z2.string(),
|
|
107
|
+
type: z2.enum([
|
|
108
|
+
"person",
|
|
109
|
+
"organization",
|
|
110
|
+
"technology",
|
|
111
|
+
"concept",
|
|
112
|
+
"project",
|
|
113
|
+
"tool",
|
|
114
|
+
"place",
|
|
115
|
+
"event",
|
|
116
|
+
"standard",
|
|
117
|
+
"other"
|
|
118
|
+
]),
|
|
119
|
+
uri: z2.string().url().optional()
|
|
120
|
+
});
|
|
121
|
+
var TemporalSchema = z2.object({
|
|
122
|
+
learned_at: z2.string(),
|
|
123
|
+
valid_from: z2.string().optional(),
|
|
124
|
+
valid_until: z2.string().optional(),
|
|
125
|
+
ingested_at: z2.string().optional()
|
|
126
|
+
});
|
|
127
|
+
var UsageStatsSchema = z2.object({
|
|
128
|
+
injections: z2.number().int().default(0),
|
|
129
|
+
hits: z2.number().int().default(0),
|
|
130
|
+
misses: z2.number().int().default(0),
|
|
131
|
+
last_hit_at: z2.string().optional()
|
|
132
|
+
});
|
|
133
|
+
var EpisodicFieldsSchema = z2.object({
|
|
134
|
+
emotional_weight: z2.number().int().min(1).max(10).default(5),
|
|
135
|
+
confidence: z2.number().int().min(1).max(10).default(5),
|
|
136
|
+
trigger_context: z2.string().optional(),
|
|
137
|
+
journal_ref: z2.string().optional()
|
|
138
|
+
});
|
|
139
|
+
var ExchangeMetadataSchema = z2.object({
|
|
140
|
+
fitness_score: z2.number().min(0).max(1).optional(),
|
|
141
|
+
environmental_diversity: z2.number().int().default(0),
|
|
142
|
+
adoption_count: z2.number().int().default(0),
|
|
143
|
+
contradiction_rate: z2.number().min(0).max(1).default(0)
|
|
144
|
+
});
|
|
105
145
|
var EngramSchema = z2.object({
|
|
146
|
+
// Identity
|
|
106
147
|
id: z2.string().regex(/^(ENG|ABS)-[A-Za-z0-9-]+$/),
|
|
107
148
|
version: z2.number().int().min(1).default(2),
|
|
108
149
|
status: z2.enum(["active", "dormant", "retired", "candidate"]),
|
|
@@ -110,24 +151,49 @@ var EngramSchema = z2.object({
|
|
|
110
151
|
type: z2.enum(["behavioral", "terminological", "procedural", "architectural"]),
|
|
111
152
|
scope: z2.string(),
|
|
112
153
|
visibility: z2.enum(["private", "public", "template"]).default("private"),
|
|
154
|
+
// Content
|
|
113
155
|
statement: z2.string().min(1),
|
|
114
156
|
rationale: z2.string().optional(),
|
|
115
157
|
contraindications: z2.array(z2.string()).optional(),
|
|
158
|
+
// Lineage
|
|
116
159
|
source_patterns: z2.array(z2.string()).optional(),
|
|
117
160
|
derivation_count: z2.number().int().min(0).default(1),
|
|
161
|
+
pack: z2.string().nullable().default(null),
|
|
162
|
+
abstract: z2.string().nullable().default(null),
|
|
163
|
+
derived_from: z2.string().nullable().default(null),
|
|
164
|
+
// Classification
|
|
118
165
|
knowledge_type: KnowledgeTypeSchema.optional(),
|
|
119
166
|
domain: z2.string().optional(),
|
|
167
|
+
tags: z2.array(z2.string()).default([]),
|
|
168
|
+
// Activation (ACT-R model)
|
|
169
|
+
activation: ActivationSchema.default({
|
|
170
|
+
retrieval_strength: 0.7,
|
|
171
|
+
storage_strength: 1,
|
|
172
|
+
frequency: 0,
|
|
173
|
+
last_accessed: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
|
|
174
|
+
}),
|
|
175
|
+
// Relations & grounding
|
|
120
176
|
relations: RelationsSchema.optional(),
|
|
121
|
-
activation: ActivationSchema.default({ retrieval_strength: 0.7, storage_strength: 1, frequency: 0, last_accessed: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10) }),
|
|
122
|
-
provenance: ProvenanceSchema.optional(),
|
|
123
|
-
feedback_signals: FeedbackSignalsSchema.default({ positive: 0, negative: 0, neutral: 0 }),
|
|
124
|
-
knowledge_anchors: z2.array(KnowledgeAnchorSchema).default([]),
|
|
125
177
|
associations: z2.array(AssociationSchema).default([]),
|
|
178
|
+
knowledge_anchors: z2.array(KnowledgeAnchorSchema).default([]),
|
|
126
179
|
dual_coding: DualCodingSchema.optional(),
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
180
|
+
// Provenance
|
|
181
|
+
provenance: ProvenanceSchema.optional(),
|
|
182
|
+
// Feedback
|
|
183
|
+
feedback_signals: FeedbackSignalsSchema.default({ positive: 0, negative: 0, neutral: 0 }),
|
|
184
|
+
// === NEW OPTIONAL FIELDS (v2.1) ===
|
|
185
|
+
/** Typed entity references extracted from statement. Enables graph queries. */
|
|
186
|
+
entities: z2.array(EntityRefSchema).optional(),
|
|
187
|
+
/** Temporal validity window. When is this knowledge true? */
|
|
188
|
+
temporal: TemporalSchema.optional(),
|
|
189
|
+
/** Automatic usage tracking. Injections, hits, misses. */
|
|
190
|
+
usage: UsageStatsSchema.optional(),
|
|
191
|
+
/** Episodic context: emotional weight, confidence, trigger. */
|
|
192
|
+
episodic: EpisodicFieldsSchema.optional(),
|
|
193
|
+
/** Exchange marketplace metadata: fitness, adoption, diversity. */
|
|
194
|
+
exchange: ExchangeMetadataSchema.optional(),
|
|
195
|
+
/** Extensible key-value data for domain-specific fields. */
|
|
196
|
+
structured_data: z2.record(z2.string(), z2.unknown()).optional()
|
|
131
197
|
});
|
|
132
198
|
|
|
133
199
|
// src/schemas/pack.ts
|
|
@@ -275,11 +341,25 @@ var STOP_WORDS = /* @__PURE__ */ new Set([
|
|
|
275
341
|
function ftsTokenize(text) {
|
|
276
342
|
return text.toLowerCase().replace(/[^\w\s]/g, " ").split(/\s+/).filter((w) => w.length > 2).filter((w) => !STOP_WORDS.has(w));
|
|
277
343
|
}
|
|
344
|
+
function engramSearchText(engram) {
|
|
345
|
+
const parts = [engram.statement];
|
|
346
|
+
if (engram.domain) parts.push(engram.domain.replace(/\./g, " "));
|
|
347
|
+
if (engram.tags.length > 0) parts.push(engram.tags.join(" "));
|
|
348
|
+
if (engram.entities) {
|
|
349
|
+
for (const e of engram.entities) {
|
|
350
|
+
parts.push(e.name);
|
|
351
|
+
if (e.type !== "other") parts.push(e.type);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (engram.temporal) {
|
|
355
|
+
if (engram.temporal.valid_from) parts.push(engram.temporal.valid_from);
|
|
356
|
+
if (engram.temporal.valid_until) parts.push(engram.temporal.valid_until);
|
|
357
|
+
}
|
|
358
|
+
if (engram.rationale) parts.push(engram.rationale);
|
|
359
|
+
return parts.join(" ");
|
|
360
|
+
}
|
|
278
361
|
function ftsScore(engram, queryTokens) {
|
|
279
|
-
const
|
|
280
|
-
const domainTokens = engram.domain ? ftsTokenize(engram.domain.replace(/\./g, " ")) : [];
|
|
281
|
-
const tagTokens = engram.tags.map((t) => t.toLowerCase());
|
|
282
|
-
const allTerms = [...statementTokens, ...domainTokens, ...tagTokens];
|
|
362
|
+
const allTerms = ftsTokenize(engramSearchText(engram));
|
|
283
363
|
let matches = 0;
|
|
284
364
|
for (const qt of queryTokens) {
|
|
285
365
|
if (allTerms.some((t) => t.includes(qt) || qt.includes(t))) matches++;
|
|
@@ -608,7 +688,14 @@ async function agenticSearch(engrams, query, limit, llm) {
|
|
|
608
688
|
return agenticRerank(candidates, query, limit, llm);
|
|
609
689
|
}
|
|
610
690
|
async function agenticRerank(candidates, query, limit, llm) {
|
|
611
|
-
const numbered = candidates.map((e, i) =>
|
|
691
|
+
const numbered = candidates.map((e, i) => {
|
|
692
|
+
const extra = [];
|
|
693
|
+
if (e.entities?.length) extra.push(`entities: ${e.entities.map((x) => x.name).join(", ")}`);
|
|
694
|
+
if (e.temporal?.valid_from) extra.push(`valid from: ${e.temporal.valid_from}`);
|
|
695
|
+
if (e.temporal?.valid_until) extra.push(`valid until: ${e.temporal.valid_until}`);
|
|
696
|
+
const suffix = extra.length > 0 ? ` (${extra.join("; ")})` : "";
|
|
697
|
+
return `${i + 1}. [${e.id}] ${e.statement}${suffix}`;
|
|
698
|
+
}).join("\n");
|
|
612
699
|
const prompt = `You are a memory retrieval system. Given a query and a list of memories, select the ${limit} most relevant memories. Return ONLY the numbers of the relevant memories, comma-separated, in order of relevance (most relevant first).
|
|
613
700
|
|
|
614
701
|
Query: "${query}"
|
|
@@ -641,6 +728,197 @@ Rules:
|
|
|
641
728
|
}
|
|
642
729
|
}
|
|
643
730
|
|
|
731
|
+
// src/embeddings.ts
|
|
732
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
|
|
733
|
+
import { join as join2 } from "path";
|
|
734
|
+
var embedPipeline = null;
|
|
735
|
+
async function getEmbedder() {
|
|
736
|
+
if (!embedPipeline) {
|
|
737
|
+
const { pipeline } = await import("@huggingface/transformers");
|
|
738
|
+
embedPipeline = await pipeline("feature-extraction", "Xenova/bge-small-en-v1.5", {
|
|
739
|
+
dtype: "fp32"
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
return embedPipeline;
|
|
743
|
+
}
|
|
744
|
+
async function embed(text) {
|
|
745
|
+
const embedder = await getEmbedder();
|
|
746
|
+
const result = await embedder(text, { pooling: "cls", normalize: true });
|
|
747
|
+
return new Float32Array(result.data);
|
|
748
|
+
}
|
|
749
|
+
function cosineSimilarity(a, b) {
|
|
750
|
+
let dot = 0;
|
|
751
|
+
for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
|
|
752
|
+
return dot;
|
|
753
|
+
}
|
|
754
|
+
function loadCache(cachePath) {
|
|
755
|
+
if (!existsSync5(cachePath)) return {};
|
|
756
|
+
try {
|
|
757
|
+
return JSON.parse(readFileSync4(cachePath, "utf8"));
|
|
758
|
+
} catch {
|
|
759
|
+
return {};
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function saveCache(cachePath, cache2) {
|
|
763
|
+
const dir = cachePath.substring(0, cachePath.lastIndexOf("/"));
|
|
764
|
+
if (!existsSync5(dir)) mkdirSync2(dir, { recursive: true });
|
|
765
|
+
writeFileSync3(cachePath, JSON.stringify(cache2));
|
|
766
|
+
}
|
|
767
|
+
function hashStatement(statement) {
|
|
768
|
+
let hash = 0;
|
|
769
|
+
for (let i = 0; i < statement.length; i++) {
|
|
770
|
+
hash = (hash << 5) - hash + statement.charCodeAt(i) | 0;
|
|
771
|
+
}
|
|
772
|
+
return hash.toString(36);
|
|
773
|
+
}
|
|
774
|
+
async function embeddingSearch(engrams, query, limit, storagePath) {
|
|
775
|
+
if (engrams.length === 0) return [];
|
|
776
|
+
const cachePath = storagePath ? join2(storagePath, ".embeddings-cache.json") : ".embeddings-cache.json";
|
|
777
|
+
const cache2 = loadCache(cachePath);
|
|
778
|
+
const queryEmbedding = await embed(query);
|
|
779
|
+
const similarities = [];
|
|
780
|
+
for (const engram of engrams) {
|
|
781
|
+
const searchText = engramSearchText(engram);
|
|
782
|
+
const hash = hashStatement(searchText);
|
|
783
|
+
let engramEmbedding;
|
|
784
|
+
if (cache2[engram.id]?.hash === hash) {
|
|
785
|
+
engramEmbedding = new Float32Array(cache2[engram.id].embedding);
|
|
786
|
+
} else {
|
|
787
|
+
engramEmbedding = await embed(searchText);
|
|
788
|
+
cache2[engram.id] = {
|
|
789
|
+
hash,
|
|
790
|
+
embedding: Array.from(engramEmbedding)
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
const score = cosineSimilarity(queryEmbedding, engramEmbedding);
|
|
794
|
+
similarities.push({ engram, score });
|
|
795
|
+
}
|
|
796
|
+
saveCache(cachePath, cache2);
|
|
797
|
+
similarities.sort((a, b) => b.score - a.score);
|
|
798
|
+
return similarities.slice(0, limit).map((s) => s.engram);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/hybrid-search.ts
|
|
802
|
+
function rrfMerge(resultSets, k = 60) {
|
|
803
|
+
const scores = /* @__PURE__ */ new Map();
|
|
804
|
+
for (const results of resultSets) {
|
|
805
|
+
for (let rank = 0; rank < results.length; rank++) {
|
|
806
|
+
const engram = results[rank];
|
|
807
|
+
const existing = scores.get(engram.id);
|
|
808
|
+
const rrfScore = 1 / (k + rank + 1);
|
|
809
|
+
if (existing) {
|
|
810
|
+
existing.score += rrfScore;
|
|
811
|
+
} else {
|
|
812
|
+
scores.set(engram.id, { engram, score: rrfScore });
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return Array.from(scores.values()).sort((a, b) => b.score - a.score).map((s) => s.engram);
|
|
817
|
+
}
|
|
818
|
+
var AGGREGATION_PATTERNS = [
|
|
819
|
+
/how many/i,
|
|
820
|
+
/how much/i,
|
|
821
|
+
/total (?:number|amount|cost|time|hours|days|spent)/i,
|
|
822
|
+
/all (?:the|my)/i,
|
|
823
|
+
/every (?:time|instance)/i,
|
|
824
|
+
/(?:count|sum|add up|combine|altogether)/i,
|
|
825
|
+
/in the (?:past|last) (?:week|month|year|few)/i,
|
|
826
|
+
/did I (?:attend|visit|go to|buy|spend|do)/i
|
|
827
|
+
];
|
|
828
|
+
function isAggregationQuery(query) {
|
|
829
|
+
return AGGREGATION_PATTERNS.some((p) => p.test(query));
|
|
830
|
+
}
|
|
831
|
+
async function hybridSearch(engrams, query, limit, storagePath) {
|
|
832
|
+
if (engrams.length === 0) return [];
|
|
833
|
+
const exhaustive = isAggregationQuery(query);
|
|
834
|
+
const effectiveLimit = exhaustive ? Math.max(limit, 50) : limit;
|
|
835
|
+
const bm25Limit = Math.min(engrams.length, exhaustive ? effectiveLimit * 5 : effectiveLimit * 3);
|
|
836
|
+
const embLimit = Math.min(engrams.length, exhaustive ? effectiveLimit * 3 : effectiveLimit * 2);
|
|
837
|
+
const [bm25Results, embResults] = await Promise.all([
|
|
838
|
+
Promise.resolve(searchEngrams(engrams, query, bm25Limit)),
|
|
839
|
+
embeddingSearch(engrams, query, embLimit, storagePath)
|
|
840
|
+
]);
|
|
841
|
+
const merged = rrfMerge([bm25Results, embResults]);
|
|
842
|
+
return merged.slice(0, effectiveLimit);
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// src/query-expansion.ts
|
|
846
|
+
var EXPANSION_PROMPT = `You are a search query expander. Given a user query, generate 3 alternative search queries that capture the same intent but use different words, synonyms, or related concepts.
|
|
847
|
+
|
|
848
|
+
Return ONLY the 3 queries, one per line, nothing else. No numbering, no explanations.
|
|
849
|
+
|
|
850
|
+
Query: "{query}"`;
|
|
851
|
+
var AGGREGATION_EXPANSION_PROMPT = `You are a search query expander for an aggregation query. The user wants to COUNT or TOTAL something across multiple conversations. Generate 5 search queries that will find ALL individual instances \u2014 each query should target a different way the item might have been mentioned.
|
|
852
|
+
|
|
853
|
+
For example, if the query is "How many weddings did I attend?", generate queries like:
|
|
854
|
+
- wedding ceremony attended
|
|
855
|
+
- went to wedding reception
|
|
856
|
+
- wedding invitation RSVP
|
|
857
|
+
- marriage celebration
|
|
858
|
+
- attended wedding of
|
|
859
|
+
|
|
860
|
+
Return ONLY the 5 queries, one per line. No numbering, no explanations.
|
|
861
|
+
|
|
862
|
+
Query: "{query}"`;
|
|
863
|
+
var AGGREGATION_PATTERNS2 = [
|
|
864
|
+
/how many/i,
|
|
865
|
+
/how much/i,
|
|
866
|
+
/total (?:number|amount|cost|time|hours|days|spent)/i,
|
|
867
|
+
/all (?:the|my)/i,
|
|
868
|
+
/every (?:time|instance)/i,
|
|
869
|
+
/(?:count|sum|add up|combine|altogether)/i,
|
|
870
|
+
/in the (?:past|last) (?:week|month|year|few)/i,
|
|
871
|
+
/did I (?:attend|visit|go to|buy|spend|do)/i
|
|
872
|
+
];
|
|
873
|
+
function isAggregationQuery2(query) {
|
|
874
|
+
return AGGREGATION_PATTERNS2.some((p) => p.test(query));
|
|
875
|
+
}
|
|
876
|
+
function parseVariants(response, original) {
|
|
877
|
+
const variants = response.split("\n").map((line) => line.replace(/^\d+[\.\)]\s*/, "").trim()).filter((line) => line.length > 3 && line.length < 200).slice(0, 3);
|
|
878
|
+
return [original, ...variants];
|
|
879
|
+
}
|
|
880
|
+
function rrfMerge2(resultSets, k = 60) {
|
|
881
|
+
const scores = /* @__PURE__ */ new Map();
|
|
882
|
+
for (const results of resultSets) {
|
|
883
|
+
for (let rank = 0; rank < results.length; rank++) {
|
|
884
|
+
const engram = results[rank];
|
|
885
|
+
const existing = scores.get(engram.id);
|
|
886
|
+
const rrfScore = 1 / (k + rank + 1);
|
|
887
|
+
if (existing) {
|
|
888
|
+
existing.score += rrfScore;
|
|
889
|
+
} else {
|
|
890
|
+
scores.set(engram.id, { engram, score: rrfScore });
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
return Array.from(scores.values()).sort((a, b) => b.score - a.score).map((s) => s.engram);
|
|
895
|
+
}
|
|
896
|
+
async function expandedSearch(engrams, query, limit, llm, storagePath) {
|
|
897
|
+
if (engrams.length === 0) return [];
|
|
898
|
+
const aggregation = isAggregationQuery2(query);
|
|
899
|
+
let variants;
|
|
900
|
+
try {
|
|
901
|
+
const promptTemplate = aggregation ? AGGREGATION_EXPANSION_PROMPT : EXPANSION_PROMPT;
|
|
902
|
+
const prompt = promptTemplate.replace("{query}", query);
|
|
903
|
+
const response = await llm(prompt);
|
|
904
|
+
variants = parseVariants(response, query);
|
|
905
|
+
if (aggregation) {
|
|
906
|
+
const extra = response.split("\n").map((line) => line.replace(/^\d+[\.\)]\s*/, "").trim()).filter((line) => line.length > 3 && line.length < 200).slice(0, 5);
|
|
907
|
+
variants = [query, ...extra];
|
|
908
|
+
}
|
|
909
|
+
} catch {
|
|
910
|
+
variants = [query];
|
|
911
|
+
}
|
|
912
|
+
const perVariantLimit = aggregation ? Math.max(limit, 50) : limit;
|
|
913
|
+
const searchPromises = variants.map(
|
|
914
|
+
(v) => hybridSearch(engrams, v, perVariantLimit, storagePath)
|
|
915
|
+
);
|
|
916
|
+
const resultSets = await Promise.all(searchPromises);
|
|
917
|
+
const merged = rrfMerge2(resultSets);
|
|
918
|
+
const effectiveLimit = aggregation ? Math.max(limit, 50) : limit;
|
|
919
|
+
return merged.slice(0, effectiveLimit);
|
|
920
|
+
}
|
|
921
|
+
|
|
644
922
|
// src/packs.ts
|
|
645
923
|
import * as fs2 from "fs";
|
|
646
924
|
import * as path from "path";
|
|
@@ -718,6 +996,203 @@ ${manifest.description || ""}
|
|
|
718
996
|
return { path: outputDir, engram_count: engrams.length };
|
|
719
997
|
}
|
|
720
998
|
|
|
999
|
+
// src/sync.ts
|
|
1000
|
+
import { execSync } from "child_process";
|
|
1001
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
1002
|
+
import { join as join4 } from "path";
|
|
1003
|
+
var GITIGNORE = `# PLUR \u2014 derived/cache files (regenerated automatically)
|
|
1004
|
+
embeddings/
|
|
1005
|
+
*.db
|
|
1006
|
+
*.sqlite
|
|
1007
|
+
exchange/
|
|
1008
|
+
`;
|
|
1009
|
+
function git(args, cwd) {
|
|
1010
|
+
return execSync(`git ${args}`, { cwd, encoding: "utf8", timeout: 3e4 }).trim();
|
|
1011
|
+
}
|
|
1012
|
+
function gitSafe(args, cwd) {
|
|
1013
|
+
try {
|
|
1014
|
+
return git(args, cwd);
|
|
1015
|
+
} catch {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
function isGitRepo(root) {
|
|
1020
|
+
return existsSync7(join4(root, ".git"));
|
|
1021
|
+
}
|
|
1022
|
+
function hasGitCli() {
|
|
1023
|
+
try {
|
|
1024
|
+
execSync("git --version", { encoding: "utf8", timeout: 5e3 });
|
|
1025
|
+
return true;
|
|
1026
|
+
} catch {
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
function getRemote(root) {
|
|
1031
|
+
return gitSafe("remote get-url origin", root);
|
|
1032
|
+
}
|
|
1033
|
+
function isDirty(root) {
|
|
1034
|
+
const status = gitSafe("status --porcelain", root);
|
|
1035
|
+
return status !== null && status.length > 0;
|
|
1036
|
+
}
|
|
1037
|
+
function countDiff(root, direction) {
|
|
1038
|
+
const tracking = gitSafe("rev-parse --abbrev-ref @{u}", root);
|
|
1039
|
+
if (!tracking) return 0;
|
|
1040
|
+
const flag = direction === "ahead" ? "left" : "right";
|
|
1041
|
+
const count = gitSafe(`rev-list --${flag}-only --count HEAD...@{u}`, root);
|
|
1042
|
+
return count ? parseInt(count, 10) : 0;
|
|
1043
|
+
}
|
|
1044
|
+
function getSyncStatus(root) {
|
|
1045
|
+
if (!isGitRepo(root)) {
|
|
1046
|
+
return { initialized: false, remote: null, dirty: false, branch: null, ahead: 0, behind: 0 };
|
|
1047
|
+
}
|
|
1048
|
+
const branch = gitSafe("rev-parse --abbrev-ref HEAD", root);
|
|
1049
|
+
const remote = getRemote(root);
|
|
1050
|
+
if (remote) gitSafe("fetch origin --quiet", root);
|
|
1051
|
+
return {
|
|
1052
|
+
initialized: true,
|
|
1053
|
+
remote,
|
|
1054
|
+
dirty: isDirty(root),
|
|
1055
|
+
branch,
|
|
1056
|
+
ahead: countDiff(root, "ahead"),
|
|
1057
|
+
behind: countDiff(root, "behind")
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
function initRepo(root) {
|
|
1061
|
+
git("init", root);
|
|
1062
|
+
writeFileSync5(join4(root, ".gitignore"), GITIGNORE);
|
|
1063
|
+
git("add -A", root);
|
|
1064
|
+
git('commit -m "Initial PLUR engram store"', root);
|
|
1065
|
+
}
|
|
1066
|
+
function commitChanges(root) {
|
|
1067
|
+
if (!isDirty(root)) return 0;
|
|
1068
|
+
git("add -A", root);
|
|
1069
|
+
const diff = gitSafe("diff --cached --stat --shortstat", root);
|
|
1070
|
+
const now = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
|
|
1071
|
+
git(`commit -m "plur sync ${now}"`, root);
|
|
1072
|
+
const match = diff?.match(/(\d+) file/);
|
|
1073
|
+
return match ? parseInt(match[1], 10) : 1;
|
|
1074
|
+
}
|
|
1075
|
+
function pullRebase(root) {
|
|
1076
|
+
const result = gitSafe("pull --rebase origin main", root);
|
|
1077
|
+
if (result !== null) return true;
|
|
1078
|
+
gitSafe("rebase --abort", root);
|
|
1079
|
+
const mergeResult = gitSafe("pull origin main --no-edit", root);
|
|
1080
|
+
if (mergeResult !== null) return true;
|
|
1081
|
+
git("add -A", root);
|
|
1082
|
+
gitSafe('commit -m "plur sync: merge conflict resolved (kept both)"', root);
|
|
1083
|
+
return true;
|
|
1084
|
+
}
|
|
1085
|
+
function sync(root, remote) {
|
|
1086
|
+
if (!hasGitCli()) {
|
|
1087
|
+
throw new Error("git is not installed. Install git to enable sync.");
|
|
1088
|
+
}
|
|
1089
|
+
if (!isGitRepo(root)) {
|
|
1090
|
+
initRepo(root);
|
|
1091
|
+
if (remote) {
|
|
1092
|
+
git(`remote add origin ${remote}`, root);
|
|
1093
|
+
const branch = git("rev-parse --abbrev-ref HEAD", root);
|
|
1094
|
+
git(`push -u origin ${branch}`, root);
|
|
1095
|
+
return { action: "initialized", message: `Initialized and pushed to ${remote}`, remote, files_changed: 0 };
|
|
1096
|
+
}
|
|
1097
|
+
return {
|
|
1098
|
+
action: "initialized",
|
|
1099
|
+
message: "Initialized local git repo. Call plur.sync with remote to enable cross-device sync.",
|
|
1100
|
+
remote: null,
|
|
1101
|
+
files_changed: 0
|
|
1102
|
+
};
|
|
1103
|
+
}
|
|
1104
|
+
const existingRemote = getRemote(root);
|
|
1105
|
+
if (remote && !existingRemote) {
|
|
1106
|
+
git(`remote add origin ${remote}`, root);
|
|
1107
|
+
const filesChanged2 = commitChanges(root);
|
|
1108
|
+
const branch = git("rev-parse --abbrev-ref HEAD", root);
|
|
1109
|
+
git(`push -u origin ${branch}`, root);
|
|
1110
|
+
return { action: "synced", message: `Remote added and pushed to ${remote}`, remote, files_changed: filesChanged2 };
|
|
1111
|
+
}
|
|
1112
|
+
if (!existingRemote) {
|
|
1113
|
+
const filesChanged2 = commitChanges(root);
|
|
1114
|
+
if (filesChanged2 === 0) {
|
|
1115
|
+
return { action: "up-to-date", message: 'No changes to commit. Add a remote with plur.sync({ remote: "..." }) to enable cross-device sync.', remote: null, files_changed: 0 };
|
|
1116
|
+
}
|
|
1117
|
+
return { action: "committed", message: `Committed ${filesChanged2} file(s) locally.`, remote: null, files_changed: filesChanged2 };
|
|
1118
|
+
}
|
|
1119
|
+
const filesChanged = commitChanges(root);
|
|
1120
|
+
gitSafe("fetch origin --quiet", root);
|
|
1121
|
+
const behind = countDiff(root, "behind");
|
|
1122
|
+
const aheadBefore = countDiff(root, "ahead");
|
|
1123
|
+
if (behind > 0) {
|
|
1124
|
+
pullRebase(root);
|
|
1125
|
+
}
|
|
1126
|
+
const aheadAfter = countDiff(root, "ahead");
|
|
1127
|
+
if (aheadAfter > 0) {
|
|
1128
|
+
gitSafe("push origin", root);
|
|
1129
|
+
}
|
|
1130
|
+
if (filesChanged === 0 && behind === 0 && aheadBefore === 0) {
|
|
1131
|
+
return { action: "up-to-date", message: "Already in sync.", remote: existingRemote, files_changed: 0 };
|
|
1132
|
+
}
|
|
1133
|
+
const parts = [];
|
|
1134
|
+
if (filesChanged > 0) parts.push(`${filesChanged} file(s) committed`);
|
|
1135
|
+
if (behind > 0) parts.push(`pulled ${behind} remote commit(s)`);
|
|
1136
|
+
if (aheadAfter === 0 && aheadBefore > 0) parts.push("pushed");
|
|
1137
|
+
return {
|
|
1138
|
+
action: "synced",
|
|
1139
|
+
message: `Synced. ${parts.join(", ")}.`,
|
|
1140
|
+
remote: existingRemote,
|
|
1141
|
+
files_changed: filesChanged
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// src/version-check.ts
|
|
1146
|
+
var cache = /* @__PURE__ */ new Map();
|
|
1147
|
+
async function checkForUpdate(packageName, currentVersion, onResult) {
|
|
1148
|
+
const cached = cache.get(packageName);
|
|
1149
|
+
if (cached) {
|
|
1150
|
+
if (onResult) onResult(cached);
|
|
1151
|
+
return cached;
|
|
1152
|
+
}
|
|
1153
|
+
const result = { current: currentVersion, latest: null, updateAvailable: false, checkedAt: null };
|
|
1154
|
+
try {
|
|
1155
|
+
const controller = new AbortController();
|
|
1156
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
1157
|
+
const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
1158
|
+
signal: controller.signal,
|
|
1159
|
+
headers: { Accept: "application/json" }
|
|
1160
|
+
});
|
|
1161
|
+
clearTimeout(timeout);
|
|
1162
|
+
if (!res.ok) {
|
|
1163
|
+
cache.set(packageName, result);
|
|
1164
|
+
return result;
|
|
1165
|
+
}
|
|
1166
|
+
const data = await res.json();
|
|
1167
|
+
if (!data.version) {
|
|
1168
|
+
cache.set(packageName, result);
|
|
1169
|
+
return result;
|
|
1170
|
+
}
|
|
1171
|
+
result.latest = data.version;
|
|
1172
|
+
result.updateAvailable = isNewer(data.version, currentVersion);
|
|
1173
|
+
result.checkedAt = Date.now();
|
|
1174
|
+
} catch {
|
|
1175
|
+
}
|
|
1176
|
+
cache.set(packageName, result);
|
|
1177
|
+
if (onResult) onResult(result);
|
|
1178
|
+
return result;
|
|
1179
|
+
}
|
|
1180
|
+
function getCachedUpdateCheck(packageName) {
|
|
1181
|
+
return cache.get(packageName) ?? null;
|
|
1182
|
+
}
|
|
1183
|
+
function clearVersionCache() {
|
|
1184
|
+
cache.clear();
|
|
1185
|
+
}
|
|
1186
|
+
function isNewer(a, b) {
|
|
1187
|
+
const pa = a.split(".").map(Number);
|
|
1188
|
+
const pb = b.split(".").map(Number);
|
|
1189
|
+
for (let i = 0; i < 3; i++) {
|
|
1190
|
+
if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true;
|
|
1191
|
+
if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false;
|
|
1192
|
+
}
|
|
1193
|
+
return false;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
721
1196
|
// src/index.ts
|
|
722
1197
|
var INGEST_PATTERNS = [
|
|
723
1198
|
{ re: /(?:we decided|the decision is|agreed to)\s+(.+?)\.?$/gim, type: "architectural" },
|
|
@@ -798,6 +1273,30 @@ var Plur = class {
|
|
|
798
1273
|
this._reactivateResults(results);
|
|
799
1274
|
return results;
|
|
800
1275
|
}
|
|
1276
|
+
/** Search engrams using local embeddings (transformers.js). Async, no API calls. */
|
|
1277
|
+
async recallSemantic(query, options) {
|
|
1278
|
+
const filtered = this._filterEngrams(options);
|
|
1279
|
+
const limit = options?.limit ?? 20;
|
|
1280
|
+
const results = await embeddingSearch(filtered, query, limit, this.paths.root);
|
|
1281
|
+
this._reactivateResults(results);
|
|
1282
|
+
return results;
|
|
1283
|
+
}
|
|
1284
|
+
/** Hybrid search: BM25 + embeddings merged via Reciprocal Rank Fusion. Async, no API calls. */
|
|
1285
|
+
async recallHybrid(query, options) {
|
|
1286
|
+
const filtered = this._filterEngrams(options);
|
|
1287
|
+
const limit = options?.limit ?? 20;
|
|
1288
|
+
const results = await hybridSearch(filtered, query, limit, this.paths.root);
|
|
1289
|
+
this._reactivateResults(results);
|
|
1290
|
+
return results;
|
|
1291
|
+
}
|
|
1292
|
+
/** Expanded search: LLM query expansion + hybrid search + RRF merge. Opt-in, requires LLM function. */
|
|
1293
|
+
async recallExpanded(query, options) {
|
|
1294
|
+
const filtered = this._filterEngrams(options);
|
|
1295
|
+
const limit = options?.limit ?? 20;
|
|
1296
|
+
const results = await expandedSearch(filtered, query, limit, options.llm, this.paths.root);
|
|
1297
|
+
this._reactivateResults(results);
|
|
1298
|
+
return results;
|
|
1299
|
+
}
|
|
801
1300
|
/** Filter engrams by scope/domain/strength (shared by both modes) */
|
|
802
1301
|
_filterEngrams(options) {
|
|
803
1302
|
let engrams = loadEngrams(this.paths.engrams);
|
|
@@ -943,6 +1442,14 @@ var Plur = class {
|
|
|
943
1442
|
listPacks() {
|
|
944
1443
|
return listPacks(this.paths.packs);
|
|
945
1444
|
}
|
|
1445
|
+
/** Sync engrams to git. Initializes repo on first call, commits + push/pull on subsequent calls. */
|
|
1446
|
+
sync(remote) {
|
|
1447
|
+
return sync(this.paths.root, remote);
|
|
1448
|
+
}
|
|
1449
|
+
/** Get git sync status without making changes. */
|
|
1450
|
+
syncStatus() {
|
|
1451
|
+
return getSyncStatus(this.paths.root);
|
|
1452
|
+
}
|
|
946
1453
|
/** Return system health info. */
|
|
947
1454
|
status() {
|
|
948
1455
|
const engrams = loadEngrams(this.paths.engrams);
|
|
@@ -958,5 +1465,9 @@ var Plur = class {
|
|
|
958
1465
|
}
|
|
959
1466
|
};
|
|
960
1467
|
export {
|
|
961
|
-
Plur
|
|
1468
|
+
Plur,
|
|
1469
|
+
checkForUpdate,
|
|
1470
|
+
clearVersionCache,
|
|
1471
|
+
engramSearchText,
|
|
1472
|
+
getCachedUpdateCheck
|
|
962
1473
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plur-ai/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"dist"
|
|
9
9
|
],
|
|
10
10
|
"dependencies": {
|
|
11
|
+
"@huggingface/transformers": "^3.8.1",
|
|
11
12
|
"js-yaml": "^4.1.0",
|
|
12
13
|
"zod": "^3.23.0"
|
|
13
14
|
},
|