vektor-slipstream 1.4.4 → 2.0.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.
- package/README.md +67 -306
- package/package.json +14 -146
- package/CHANGELOG.md +0 -139
- package/LICENSE +0 -33
- package/TENETS.md +0 -189
- package/audn-log.js +0 -143
- package/axon.js +0 -389
- package/boot-patch.js +0 -33
- package/boot-screen.html +0 -210
- package/briefing.js +0 -150
- package/cerebellum.js +0 -439
- package/cloak-behaviour.js +0 -596
- package/cloak-captcha.js +0 -541
- package/cloak-core.js +0 -499
- package/cloak-identity.js +0 -484
- package/cloak-index.js +0 -261
- package/cloak-llms.js +0 -163
- package/cloak-pattern-store.js +0 -471
- package/cloak-recorder-auto.js +0 -297
- package/cloak-recorder-snippet.js +0 -119
- package/cloak-turbo-quant.js +0 -357
- package/cloak-warmup.js +0 -240
- package/cortex.js +0 -221
- package/detect-hardware.js +0 -181
- package/entity-resolver.js +0 -298
- package/errors.js +0 -66
- package/examples/example-claude-mcp.js +0 -220
- package/examples/example-langchain-researcher.js +0 -82
- package/examples/example-openai-assistant.js +0 -84
- package/examples/examples-README.md +0 -161
- package/export-import.js +0 -221
- package/forget.js +0 -148
- package/inspect.js +0 -199
- package/mistral/README-mistral.md +0 -123
- package/mistral/mistral-bridge.js +0 -218
- package/mistral/mistral-setup.js +0 -220
- package/mistral/vektor-tool-manifest.json +0 -41
- package/models/model_quantized.onnx +0 -0
- package/models/vocab.json +0 -1
- package/namespace.js +0 -186
- package/pin.js +0 -91
- package/slipstream-core-extended.js +0 -134
- package/slipstream-core.js +0 -1
- package/slipstream-db.js +0 -140
- package/slipstream-embedder.js +0 -338
- package/sovereign.js +0 -142
- package/token.js +0 -322
- package/types/index.d.ts +0 -269
- package/vektor-banner-loader.js +0 -109
- package/vektor-cli.js +0 -259
- package/vektor-licence-prompt.js +0 -128
- package/vektor-licence.js +0 -192
- package/vektor-setup.js +0 -270
- package/vektor-slipstream.dxt +0 -0
- package/vektor-tui.js +0 -373
- package/visualize.js +0 -235
package/cloak-pattern-store.js
DELETED
|
@@ -1,471 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* cloak-pattern-store.js — Tiered self-improving pattern library
|
|
5
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
6
|
-
* Stores behaviour patterns in SQLite with win/loss scoring, tier management,
|
|
7
|
-
* auto-pruning, and duplicate detection. Patterns get smarter over time.
|
|
8
|
-
*
|
|
9
|
-
* Tiers:
|
|
10
|
-
* elite (max 10) — 3+ wins, 0 losses. Used 70% of sessions.
|
|
11
|
-
* active (max 30) — Positive score, in rotation. Used 25% of sessions.
|
|
12
|
-
* probation (max 20) — New or declining. Used 5% of sessions.
|
|
13
|
-
* retired (deleted) — Score < -3, stale, or duplicate.
|
|
14
|
-
*
|
|
15
|
-
* Pruning rules:
|
|
16
|
-
* - Score drops to -3 → immediate retirement
|
|
17
|
-
* - No win in 30 days + score ≤ 0 → retirement
|
|
18
|
-
* - Pattern age > 90 days + score < 5 → retirement (sites change)
|
|
19
|
-
* - Active tier > 30 → prune lowest scored
|
|
20
|
-
* - Duplicate (>92% similarity) → discard on arrival
|
|
21
|
-
* - Elite tier full + new 3-win → bump lowest elite to active
|
|
22
|
-
* ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
const path = require('path');
|
|
26
|
-
const os = require('os');
|
|
27
|
-
const fs = require('fs');
|
|
28
|
-
const crypto = require('crypto');
|
|
29
|
-
|
|
30
|
-
const DB_PATH = process.env.VEKTOR_PATTERN_DB || path.join(os.homedir(), '.vektor', 'pattern-store.db');
|
|
31
|
-
|
|
32
|
-
// Tier weights for random selection (elite gets most traffic)
|
|
33
|
-
const TIER_WEIGHTS = { elite: 0.70, active: 0.25, probation: 0.05 };
|
|
34
|
-
|
|
35
|
-
// Tier size caps
|
|
36
|
-
const TIER_CAPS = { elite: 10, active: 30, probation: 20 };
|
|
37
|
-
|
|
38
|
-
// Pruning thresholds
|
|
39
|
-
const PRUNE = {
|
|
40
|
-
minScore: -3, // retire immediately below this
|
|
41
|
-
staleDays: 30, // retire if no win in this many days AND score ≤ 0
|
|
42
|
-
ageDays: 90, // retire if older than this AND score < 5
|
|
43
|
-
similarityThresh: 0.92, // discard new pattern if >92% similar to existing elite
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
let _db = null;
|
|
47
|
-
|
|
48
|
-
// ── DB initialisation ─────────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
function getDB() {
|
|
51
|
-
if (_db) return _db;
|
|
52
|
-
|
|
53
|
-
const Database = require('better-sqlite3');
|
|
54
|
-
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
|
55
|
-
_db = new Database(DB_PATH);
|
|
56
|
-
|
|
57
|
-
_db.pragma('journal_mode = WAL');
|
|
58
|
-
_db.pragma('synchronous = NORMAL');
|
|
59
|
-
|
|
60
|
-
_db.exec(`
|
|
61
|
-
CREATE TABLE IF NOT EXISTS patterns (
|
|
62
|
-
id TEXT PRIMARY KEY,
|
|
63
|
-
name TEXT NOT NULL,
|
|
64
|
-
category TEXT NOT NULL DEFAULT 'custom',
|
|
65
|
-
tier TEXT NOT NULL DEFAULT 'probation',
|
|
66
|
-
score INTEGER NOT NULL DEFAULT 0,
|
|
67
|
-
wins INTEGER NOT NULL DEFAULT 0,
|
|
68
|
-
losses INTEGER NOT NULL DEFAULT 0,
|
|
69
|
-
sessions INTEGER NOT NULL DEFAULT 0,
|
|
70
|
-
created_at INTEGER NOT NULL,
|
|
71
|
-
last_win_at INTEGER,
|
|
72
|
-
last_used_at INTEGER,
|
|
73
|
-
description TEXT,
|
|
74
|
-
profile TEXT NOT NULL,
|
|
75
|
-
fingerprint TEXT NOT NULL
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
CREATE TABLE IF NOT EXISTS pattern_log (
|
|
79
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
80
|
-
pattern_id TEXT NOT NULL,
|
|
81
|
-
outcome TEXT NOT NULL,
|
|
82
|
-
site TEXT,
|
|
83
|
-
score_delta INTEGER NOT NULL,
|
|
84
|
-
logged_at INTEGER NOT NULL
|
|
85
|
-
);
|
|
86
|
-
|
|
87
|
-
CREATE INDEX IF NOT EXISTS idx_tier ON patterns(tier);
|
|
88
|
-
CREATE INDEX IF NOT EXISTS idx_category ON patterns(category);
|
|
89
|
-
CREATE INDEX IF NOT EXISTS idx_score ON patterns(score DESC);
|
|
90
|
-
`);
|
|
91
|
-
|
|
92
|
-
return _db;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ── Fingerprint for duplicate detection ───────────────────────────────────────
|
|
96
|
-
// Summarises a pattern's velocity/scroll profile as a compact vector
|
|
97
|
-
// so we can detect near-duplicates before adding to the store.
|
|
98
|
-
|
|
99
|
-
function _fingerprintPattern(events) {
|
|
100
|
-
if (!events?.length) return '0'.repeat(32);
|
|
101
|
-
|
|
102
|
-
// Build a 16-bucket histogram of: scroll velocity, move speed, pause frequency
|
|
103
|
-
const buckets = new Float32Array(16);
|
|
104
|
-
let prev = null;
|
|
105
|
-
let scrollCount = 0, moveCount = 0, pauseCount = 0;
|
|
106
|
-
let totalScrollDy = 0;
|
|
107
|
-
|
|
108
|
-
for (const e of events) {
|
|
109
|
-
if (e.type === 'scroll') {
|
|
110
|
-
scrollCount++;
|
|
111
|
-
totalScrollDy += Math.abs(e.dy || 0);
|
|
112
|
-
// Bucket by scroll velocity (dy / dt)
|
|
113
|
-
const vel = prev ? Math.abs(e.dy || 0) / Math.max(1, e.dt - (prev.dt || 0)) : 0;
|
|
114
|
-
const b = Math.min(7, Math.floor(vel / 10));
|
|
115
|
-
buckets[b]++;
|
|
116
|
-
} else if (e.type === 'move') {
|
|
117
|
-
moveCount++;
|
|
118
|
-
buckets[8 + Math.min(7, Math.floor(Math.random() * 8))]++;
|
|
119
|
-
} else if (e.type === 'pause') {
|
|
120
|
-
pauseCount++;
|
|
121
|
-
}
|
|
122
|
-
prev = e;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Normalise buckets
|
|
126
|
-
const total = events.length || 1;
|
|
127
|
-
for (let i = 0; i < 16; i++) buckets[i] /= total;
|
|
128
|
-
|
|
129
|
-
// Pack into hex string
|
|
130
|
-
return Buffer.from(buckets.buffer).toString('hex').slice(0, 32);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function _similarityScore(fpA, fpB) {
|
|
134
|
-
if (fpA === fpB) return 1.0;
|
|
135
|
-
// Simple hamming-based similarity on hex fingerprints
|
|
136
|
-
let matches = 0;
|
|
137
|
-
const len = Math.min(fpA.length, fpB.length);
|
|
138
|
-
for (let i = 0; i < len; i++) {
|
|
139
|
-
if (fpA[i] === fpB[i]) matches++;
|
|
140
|
-
}
|
|
141
|
-
return matches / len;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// ── Core operations ───────────────────────────────────────────────────────────
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Add a new pattern to the store (starts in probation).
|
|
148
|
-
* Runs duplicate check — returns null if too similar to existing elite.
|
|
149
|
-
*
|
|
150
|
-
* @param {object} pattern — { name, category, description, events, viewport }
|
|
151
|
-
* @returns {{ id, tier, duplicate } | null}
|
|
152
|
-
*/
|
|
153
|
-
function addPattern(pattern) {
|
|
154
|
-
const db = getDB();
|
|
155
|
-
|
|
156
|
-
const events = pattern.events || [];
|
|
157
|
-
const fingerprint = _fingerprintPattern(events);
|
|
158
|
-
const id = crypto.randomUUID();
|
|
159
|
-
|
|
160
|
-
// Duplicate check against elite + active patterns
|
|
161
|
-
const existing = db.prepare(
|
|
162
|
-
`SELECT fingerprint FROM patterns WHERE tier IN ('elite','active')`
|
|
163
|
-
).all();
|
|
164
|
-
|
|
165
|
-
for (const row of existing) {
|
|
166
|
-
const sim = _similarityScore(fingerprint, row.fingerprint);
|
|
167
|
-
if (sim >= PRUNE.similarityThresh) {
|
|
168
|
-
return { id: null, tier: null, duplicate: true, similarity: sim };
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
db.prepare(`
|
|
173
|
-
INSERT INTO patterns
|
|
174
|
-
(id, name, category, tier, score, wins, losses, sessions,
|
|
175
|
-
created_at, last_win_at, last_used_at, description, profile, fingerprint)
|
|
176
|
-
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
177
|
-
`).run(
|
|
178
|
-
id,
|
|
179
|
-
pattern.name || 'unnamed-' + Date.now(),
|
|
180
|
-
pattern.category || 'custom',
|
|
181
|
-
'probation',
|
|
182
|
-
0, 0, 0, 0,
|
|
183
|
-
Date.now(), null, null,
|
|
184
|
-
pattern.description || '',
|
|
185
|
-
JSON.stringify({ events, viewport: pattern.viewport }),
|
|
186
|
-
fingerprint
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
_prune(db);
|
|
190
|
-
|
|
191
|
-
return { id, tier: 'probation', duplicate: false };
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Record an outcome for a pattern after a session.
|
|
196
|
-
* Updates score, tier, win/loss counts, triggers pruning.
|
|
197
|
-
*
|
|
198
|
-
* @param {string} patternId
|
|
199
|
-
* @param {'pass'|'block'|'captcha'|'unknown'} outcome
|
|
200
|
-
* @param {string} site — hostname of the target
|
|
201
|
-
*/
|
|
202
|
-
function recordOutcome(patternId, outcome, site = '') {
|
|
203
|
-
const db = getDB();
|
|
204
|
-
const row = db.prepare('SELECT * FROM patterns WHERE id = ?').get(patternId);
|
|
205
|
-
if (!row) return null;
|
|
206
|
-
|
|
207
|
-
const delta = outcome === 'pass' ? 2 :
|
|
208
|
-
outcome === 'block' ? -2 :
|
|
209
|
-
outcome === 'captcha' ? -1 : 0;
|
|
210
|
-
|
|
211
|
-
const newScore = row.score + delta;
|
|
212
|
-
const newWins = outcome === 'pass' ? row.wins + 1 : row.wins;
|
|
213
|
-
const newLosses = (outcome === 'block' || outcome === 'captcha') ? row.losses + 1 : row.losses;
|
|
214
|
-
const lastWinAt = outcome === 'pass' ? Date.now() : row.last_win_at;
|
|
215
|
-
|
|
216
|
-
// Determine new tier
|
|
217
|
-
let tier = row.tier;
|
|
218
|
-
if (newScore <= PRUNE.minScore) {
|
|
219
|
-
tier = 'retired';
|
|
220
|
-
} else if (newWins >= 3 && newLosses === 0 && tier === 'active') {
|
|
221
|
-
// Promote to elite if elite has room
|
|
222
|
-
const eliteCount = db.prepare(`SELECT COUNT(*) as n FROM patterns WHERE tier = 'elite'`).get().n;
|
|
223
|
-
if (eliteCount < TIER_CAPS.elite) tier = 'elite';
|
|
224
|
-
else {
|
|
225
|
-
// Bump lowest-scored elite to active to make room
|
|
226
|
-
const lowestElite = db.prepare(
|
|
227
|
-
`SELECT id FROM patterns WHERE tier = 'elite' ORDER BY score ASC LIMIT 1`
|
|
228
|
-
).get();
|
|
229
|
-
if (lowestElite && newScore > row.score) {
|
|
230
|
-
db.prepare(`UPDATE patterns SET tier = 'active' WHERE id = ?`).run(lowestElite.id);
|
|
231
|
-
tier = 'elite';
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
} else if (tier === 'probation' && newScore > 0 && row.sessions >= 3) {
|
|
235
|
-
tier = 'active';
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
db.prepare(`
|
|
239
|
-
UPDATE patterns SET
|
|
240
|
-
score = ?, wins = ?, losses = ?, sessions = sessions + 1,
|
|
241
|
-
last_win_at = ?, last_used_at = ?, tier = ?
|
|
242
|
-
WHERE id = ?
|
|
243
|
-
`).run(newScore, newWins, newLosses, lastWinAt, Date.now(), tier, patternId);
|
|
244
|
-
|
|
245
|
-
db.prepare(`
|
|
246
|
-
INSERT INTO pattern_log (pattern_id, outcome, site, score_delta, logged_at)
|
|
247
|
-
VALUES (?,?,?,?,?)
|
|
248
|
-
`).run(patternId, outcome, site, delta, Date.now());
|
|
249
|
-
|
|
250
|
-
// If retired, remove immediately
|
|
251
|
-
if (tier === 'retired') {
|
|
252
|
-
db.prepare('DELETE FROM patterns WHERE id = ?').run(patternId);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
_prune(db);
|
|
256
|
-
|
|
257
|
-
return { id: patternId, tier, score: newScore, wins: newWins, losses: newLosses };
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Pick a pattern for use, weighted by tier.
|
|
262
|
-
* @param {string} category — filter by category (optional)
|
|
263
|
-
* @returns {{ id, name, events, viewport, tier } | null}
|
|
264
|
-
*/
|
|
265
|
-
function pickPattern(category = null) {
|
|
266
|
-
const db = getDB();
|
|
267
|
-
|
|
268
|
-
// Roll for tier
|
|
269
|
-
const roll = Math.random();
|
|
270
|
-
let tier;
|
|
271
|
-
if (roll < TIER_WEIGHTS.elite) tier = 'elite';
|
|
272
|
-
else if (roll < TIER_WEIGHTS.elite + TIER_WEIGHTS.active) tier = 'active';
|
|
273
|
-
else tier = 'probation';
|
|
274
|
-
|
|
275
|
-
// Try preferred tier first, fall back up if empty
|
|
276
|
-
const tierOrder = tier === 'elite' ? ['elite', 'active', 'probation'] :
|
|
277
|
-
tier === 'active' ? ['active', 'elite', 'probation'] :
|
|
278
|
-
['probation', 'active', 'elite'];
|
|
279
|
-
|
|
280
|
-
for (const t of tierOrder) {
|
|
281
|
-
const where = category
|
|
282
|
-
? `tier = '${t}' AND category = '${category.replace(/'/g,"''")}'`
|
|
283
|
-
: `tier = '${t}'`;
|
|
284
|
-
|
|
285
|
-
const rows = db.prepare(
|
|
286
|
-
`SELECT * FROM patterns WHERE ${where} ORDER BY score DESC`
|
|
287
|
-
).all();
|
|
288
|
-
|
|
289
|
-
if (!rows.length) continue;
|
|
290
|
-
|
|
291
|
-
// Weighted random within tier — higher score = higher probability
|
|
292
|
-
const minScore = Math.min(...rows.map(r => r.score));
|
|
293
|
-
const weights = rows.map(r => Math.max(0.1, r.score - minScore + 1));
|
|
294
|
-
const total = weights.reduce((a, b) => a + b, 0);
|
|
295
|
-
let pick = Math.random() * total;
|
|
296
|
-
for (let i = 0; i < rows.length; i++) {
|
|
297
|
-
pick -= weights[i];
|
|
298
|
-
if (pick <= 0) {
|
|
299
|
-
const profile = JSON.parse(rows[i].profile);
|
|
300
|
-
return {
|
|
301
|
-
id: rows[i].id,
|
|
302
|
-
name: rows[i].name,
|
|
303
|
-
tier: rows[i].tier,
|
|
304
|
-
score: rows[i].score,
|
|
305
|
-
events: profile.events,
|
|
306
|
-
viewport: profile.viewport,
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Fallback: return last row
|
|
312
|
-
const last = rows[rows.length - 1];
|
|
313
|
-
const profile = JSON.parse(last.profile);
|
|
314
|
-
return { id: last.id, name: last.name, tier: last.tier, score: last.score,
|
|
315
|
-
events: profile.events, viewport: profile.viewport };
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
return null; // no patterns in store
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
/**
|
|
322
|
-
* Get all patterns summary (for MCP tool / stats).
|
|
323
|
-
*/
|
|
324
|
-
function storeStats() {
|
|
325
|
-
const db = getDB();
|
|
326
|
-
const counts = db.prepare(`
|
|
327
|
-
SELECT tier, COUNT(*) as n, AVG(score) as avg_score,
|
|
328
|
-
SUM(wins) as total_wins, SUM(losses) as total_losses
|
|
329
|
-
FROM patterns GROUP BY tier
|
|
330
|
-
`).all();
|
|
331
|
-
|
|
332
|
-
const total = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
|
|
333
|
-
const log = db.prepare(
|
|
334
|
-
'SELECT COUNT(*) as n FROM pattern_log WHERE logged_at > ?'
|
|
335
|
-
).get(Date.now() - 7 * 24 * 3600 * 1000).n;
|
|
336
|
-
|
|
337
|
-
return {
|
|
338
|
-
total,
|
|
339
|
-
tiers: counts,
|
|
340
|
-
sessionsLast7d: log,
|
|
341
|
-
caps: TIER_CAPS,
|
|
342
|
-
pruneRules: PRUNE,
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* List patterns with full stats.
|
|
348
|
-
*/
|
|
349
|
-
function listPatterns(tier = null) {
|
|
350
|
-
const db = getDB();
|
|
351
|
-
const sql = tier
|
|
352
|
-
? `SELECT id,name,category,tier,score,wins,losses,sessions,created_at,last_win_at,description FROM patterns WHERE tier = ? ORDER BY score DESC`
|
|
353
|
-
: `SELECT id,name,category,tier,score,wins,losses,sessions,created_at,last_win_at,description FROM patterns ORDER BY tier, score DESC`;
|
|
354
|
-
return tier ? db.prepare(sql).all(tier) : db.prepare(sql).all();
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// ── Auto-pruning ──────────────────────────────────────────────────────────────
|
|
358
|
-
|
|
359
|
-
function _prune(db) {
|
|
360
|
-
const now = Date.now();
|
|
361
|
-
|
|
362
|
-
// 1. Retire stale: no win in 30 days AND score ≤ 0
|
|
363
|
-
const staleThresh = now - PRUNE.staleDays * 24 * 3600 * 1000;
|
|
364
|
-
db.prepare(`
|
|
365
|
-
DELETE FROM patterns
|
|
366
|
-
WHERE score <= 0
|
|
367
|
-
AND tier != 'retired'
|
|
368
|
-
AND (last_win_at IS NULL OR last_win_at < ?)
|
|
369
|
-
AND created_at < ?
|
|
370
|
-
`).run(staleThresh, staleThresh);
|
|
371
|
-
|
|
372
|
-
// 2. Retire aged: > 90 days old AND score < 5
|
|
373
|
-
const ageThresh = now - PRUNE.ageDays * 24 * 3600 * 1000;
|
|
374
|
-
db.prepare(`
|
|
375
|
-
DELETE FROM patterns WHERE created_at < ? AND score < 5
|
|
376
|
-
`).run(ageThresh);
|
|
377
|
-
|
|
378
|
-
// 3. Cap active tier — keep top 30 by score
|
|
379
|
-
const activeCount = db.prepare(`SELECT COUNT(*) as n FROM patterns WHERE tier = 'active'`).get().n;
|
|
380
|
-
if (activeCount > TIER_CAPS.active) {
|
|
381
|
-
const toDelete = activeCount - TIER_CAPS.active;
|
|
382
|
-
db.prepare(`
|
|
383
|
-
DELETE FROM patterns WHERE id IN (
|
|
384
|
-
SELECT id FROM patterns WHERE tier = 'active' ORDER BY score ASC LIMIT ?
|
|
385
|
-
)
|
|
386
|
-
`).run(toDelete);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// 4. Cap probation tier — keep top 20 by score, prefer newer
|
|
390
|
-
const probCount = db.prepare(`SELECT COUNT(*) as n FROM patterns WHERE tier = 'probation'`).get().n;
|
|
391
|
-
if (probCount > TIER_CAPS.probation) {
|
|
392
|
-
const toDelete = probCount - TIER_CAPS.probation;
|
|
393
|
-
db.prepare(`
|
|
394
|
-
DELETE FROM patterns WHERE id IN (
|
|
395
|
-
SELECT id FROM patterns WHERE tier = 'probation'
|
|
396
|
-
ORDER BY score ASC, created_at ASC LIMIT ?
|
|
397
|
-
)
|
|
398
|
-
`).run(toDelete);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// 5. Prune log table — keep last 1000 entries
|
|
402
|
-
const logCount = db.prepare('SELECT COUNT(*) as n FROM pattern_log').get().n;
|
|
403
|
-
if (logCount > 1000) {
|
|
404
|
-
db.prepare(`
|
|
405
|
-
DELETE FROM pattern_log WHERE id IN (
|
|
406
|
-
SELECT id FROM pattern_log ORDER BY logged_at ASC LIMIT ?
|
|
407
|
-
)
|
|
408
|
-
`).run(logCount - 1000);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
/**
|
|
413
|
-
* Force a full prune pass (callable from MCP tool).
|
|
414
|
-
*/
|
|
415
|
-
function pruneNow() {
|
|
416
|
-
const db = getDB();
|
|
417
|
-
const before = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
|
|
418
|
-
_prune(db);
|
|
419
|
-
const after = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
|
|
420
|
-
return { removed: before - after, remaining: after };
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/**
|
|
424
|
-
* Seed the store with built-in patterns from cloak-behaviour.js.
|
|
425
|
-
* Only seeds if store is empty. Built-ins start in 'active' tier with score 1.
|
|
426
|
-
*/
|
|
427
|
-
function seedBuiltins() {
|
|
428
|
-
const db = getDB();
|
|
429
|
-
const existing = db.prepare('SELECT COUNT(*) as n FROM patterns').get().n;
|
|
430
|
-
if (existing > 0) return { seeded: 0, reason: 'store already has patterns' };
|
|
431
|
-
|
|
432
|
-
const { PATTERN_LIBRARY } = require('./cloak-behaviour');
|
|
433
|
-
let seeded = 0;
|
|
434
|
-
|
|
435
|
-
for (const [name, pattern] of Object.entries(PATTERN_LIBRARY)) {
|
|
436
|
-
const fingerprint = _fingerprintPattern(pattern.events);
|
|
437
|
-
const id = crypto.randomUUID();
|
|
438
|
-
db.prepare(`
|
|
439
|
-
INSERT INTO patterns
|
|
440
|
-
(id, name, category, tier, score, wins, losses, sessions,
|
|
441
|
-
created_at, last_win_at, last_used_at, description, profile, fingerprint)
|
|
442
|
-
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
443
|
-
`).run(
|
|
444
|
-
id, name, pattern.category,
|
|
445
|
-
'active', // built-ins start active, not probation
|
|
446
|
-
1, 0, 0, 0,
|
|
447
|
-
Date.now(), null, null,
|
|
448
|
-
pattern.description || '',
|
|
449
|
-
JSON.stringify({ events: pattern.events, viewport: pattern.viewport }),
|
|
450
|
-
fingerprint
|
|
451
|
-
);
|
|
452
|
-
seeded++;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
return { seeded };
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
module.exports = {
|
|
459
|
-
addPattern,
|
|
460
|
-
recordOutcome,
|
|
461
|
-
pickPattern,
|
|
462
|
-
storeStats,
|
|
463
|
-
listPatterns,
|
|
464
|
-
pruneNow,
|
|
465
|
-
seedBuiltins,
|
|
466
|
-
};
|
|
467
|
-
|
|
468
|
-
/** @internal — test use only. Resets the cached DB connection. */
|
|
469
|
-
function _resetDB() { _db = null; }
|
|
470
|
-
|
|
471
|
-
module.exports._resetDB = _resetDB;
|