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/briefing.js
DELETED
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VEKTOR briefing() date scoping — v1.3.7
|
|
3
|
-
* Adds `since` param to briefing() — filter to last N days/hours
|
|
4
|
-
* or an explicit ISO timestamp.
|
|
5
|
-
*
|
|
6
|
-
* Drop-in replacement for existing briefing() implementation.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { VektorError, ERR } from './errors.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Parse a `since` value into a Unix timestamp (seconds).
|
|
13
|
-
* Accepts:
|
|
14
|
-
* '1d' → last 1 day
|
|
15
|
-
* '7d' → last 7 days
|
|
16
|
-
* '12h' → last 12 hours
|
|
17
|
-
* '30m' → last 30 minutes
|
|
18
|
-
* '2026-01-01T00:00:00Z' → explicit ISO date
|
|
19
|
-
* 1735689600 → raw unix timestamp
|
|
20
|
-
*/
|
|
21
|
-
export function parseSince(since) {
|
|
22
|
-
if (!since) return null;
|
|
23
|
-
|
|
24
|
-
// Raw unix timestamp — guard against Date.now() (ms) being passed accidentally
|
|
25
|
-
// If number > 9999999999 it's milliseconds (year ~2001 in seconds) — convert it
|
|
26
|
-
if (typeof since === 'number') {
|
|
27
|
-
return since > 9_999_999_999 ? Math.floor(since / 1000) : since;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Relative shorthand: 7d, 12h, 30m
|
|
31
|
-
const rel = String(since).match(/^(\d+)(d|h|m)$/);
|
|
32
|
-
if (rel) {
|
|
33
|
-
const n = parseInt(rel[1]);
|
|
34
|
-
const unit = rel[2];
|
|
35
|
-
const seconds = unit === 'd' ? n * 86400 : unit === 'h' ? n * 3600 : n * 60;
|
|
36
|
-
return Math.floor(Date.now() / 1000) - seconds;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ISO date string
|
|
40
|
-
const ts = Date.parse(since);
|
|
41
|
-
if (!isNaN(ts)) return Math.floor(ts / 1000);
|
|
42
|
-
|
|
43
|
-
throw new VektorError(
|
|
44
|
-
ERR.MEMORY_READ_FAILED,
|
|
45
|
-
`Invalid since value: "${since}". Use "7d", "12h", "30m", ISO date, or unix timestamp.`
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* briefing({ since, limit, minImportance })
|
|
51
|
-
* Returns a structured summary of recent memories for agent context injection.
|
|
52
|
-
*
|
|
53
|
-
* @param {object} db
|
|
54
|
-
* @param {string} agentId
|
|
55
|
-
* @param {string} namespace
|
|
56
|
-
* @param {Function} summariseFn - LLM summarise function (existing REM summariser)
|
|
57
|
-
* @param {object} opts
|
|
58
|
-
* @param {string|number} opts.since - time filter (default: '1d')
|
|
59
|
-
* @param {number} opts.limit - max memories (default: 20)
|
|
60
|
-
* @param {number} opts.minImportance - filter by importance score (default: 0)
|
|
61
|
-
* @param {boolean} opts.raw - return raw memories, skip LLM summary
|
|
62
|
-
* @returns {object} { period, memoryCount, pinned, summary, memories }
|
|
63
|
-
*/
|
|
64
|
-
export async function briefing(db, agentId, namespace, summariseFn, opts = {}) {
|
|
65
|
-
const {
|
|
66
|
-
since = '1d',
|
|
67
|
-
limit = 20,
|
|
68
|
-
minImportance = 0,
|
|
69
|
-
raw = false
|
|
70
|
-
} = opts;
|
|
71
|
-
|
|
72
|
-
const sinceTs = parseSince(since);
|
|
73
|
-
const sinceClause = sinceTs ? `AND created_at >= ${sinceTs}` : '';
|
|
74
|
-
const importanceClause = minImportance > 0 ? `AND importance >= ${minImportance}` : '';
|
|
75
|
-
|
|
76
|
-
// Fetch scoped memories
|
|
77
|
-
const memories = db.prepare(`
|
|
78
|
-
SELECT id, content, summary, importance, pinned, created_at
|
|
79
|
-
FROM memories
|
|
80
|
-
WHERE agent_id = ? AND namespace = ?
|
|
81
|
-
${sinceClause}
|
|
82
|
-
${importanceClause}
|
|
83
|
-
ORDER BY importance DESC, created_at DESC
|
|
84
|
-
LIMIT ?
|
|
85
|
-
`).all(agentId, namespace, limit);
|
|
86
|
-
|
|
87
|
-
// Always fetch pinned regardless of time window — hard cap 50 to prevent context bomb
|
|
88
|
-
// (an agent running for months could accumulate hundreds of pinned memories)
|
|
89
|
-
const pinnedLimit = 50;
|
|
90
|
-
const pinned = db.prepare(`
|
|
91
|
-
SELECT id, content, summary, importance, created_at
|
|
92
|
-
FROM memories
|
|
93
|
-
WHERE agent_id = ? AND namespace = ? AND pinned = 1
|
|
94
|
-
ORDER BY importance DESC
|
|
95
|
-
LIMIT ${pinnedLimit}
|
|
96
|
-
`).all(agentId, namespace);
|
|
97
|
-
|
|
98
|
-
if (raw) {
|
|
99
|
-
return {
|
|
100
|
-
period: since,
|
|
101
|
-
memoryCount: memories.length,
|
|
102
|
-
pinned,
|
|
103
|
-
memories
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Build prompt context for LLM summary
|
|
108
|
-
const context = [
|
|
109
|
-
pinned.length > 0
|
|
110
|
-
? `PINNED (permanent):\n${pinned.map(m => `- ${m.summary || m.content}`).join('\n')}`
|
|
111
|
-
: null,
|
|
112
|
-
memories.length > 0
|
|
113
|
-
? `RECENT (${since}):\n${memories.map(m => `- [${m.importance?.toFixed(2)}] ${m.summary || m.content}`).join('\n')}`
|
|
114
|
-
: 'No recent memories in this period.'
|
|
115
|
-
].filter(Boolean).join('\n\n');
|
|
116
|
-
|
|
117
|
-
const summary = await summariseFn(
|
|
118
|
-
`Produce a concise agent briefing from these memories. Lead with pinned facts, then recent activity.\n\n${context}`
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
period: since,
|
|
123
|
-
memoryCount: memories.length,
|
|
124
|
-
pinnedCount: pinned.length,
|
|
125
|
-
pinned,
|
|
126
|
-
memories,
|
|
127
|
-
summary
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Integration into Memory class:
|
|
133
|
-
*
|
|
134
|
-
* async briefing(opts = {}) {
|
|
135
|
-
* return briefing(this.db, this.agentId, this.namespace, this._summarise.bind(this), opts);
|
|
136
|
-
* }
|
|
137
|
-
*
|
|
138
|
-
* Usage:
|
|
139
|
-
* await memory.briefing();
|
|
140
|
-
* // → last 24h summary (default)
|
|
141
|
-
*
|
|
142
|
-
* await memory.briefing({ since: '7d' });
|
|
143
|
-
* // → last 7 days
|
|
144
|
-
*
|
|
145
|
-
* await memory.briefing({ since: '2026-01-01', minImportance: 0.7 });
|
|
146
|
-
* // → since Jan 1st, high importance only
|
|
147
|
-
*
|
|
148
|
-
* await memory.briefing({ raw: true, since: '1h' });
|
|
149
|
-
* // → raw memory objects, no LLM call
|
|
150
|
-
*/
|
package/cerebellum.js
DELETED
|
@@ -1,439 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* cloak_cerebellum.js
|
|
3
|
-
* Pre-write enforcer — checks every write against known error patterns in
|
|
4
|
-
* Vektor's causal graph. Auto-creates [RESOLVED_BY] edges when a fix matches
|
|
5
|
-
* an open error node. Zero extra tool calls from Claude.
|
|
6
|
-
*
|
|
7
|
-
* Two responsibilities:
|
|
8
|
-
* 1. CHECK — before a write, recall causal error patterns and warn if matched
|
|
9
|
-
* 2. RESOLVE — if new write content matches an error pattern, auto-write
|
|
10
|
-
* [RESOLVED_BY] causal edge to close the loop
|
|
11
|
-
*
|
|
12
|
-
* Architecture: CLOAK layer → Vektor causal graph (MAGMA §3.4)
|
|
13
|
-
* Research: MAGMA arXiv:2601.03236, OpenWolf cerebrum Do-Not-Repeat pattern
|
|
14
|
-
*
|
|
15
|
-
* Key design decision: cerebellum NEVER blocks a write, it only warns.
|
|
16
|
-
* Claude Code must remain in control — cerebellum is advisory, not a gatekeeper.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
'use strict';
|
|
20
|
-
|
|
21
|
-
const crypto = require('crypto');
|
|
22
|
-
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Similarity threshold for pattern matching
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
const SIMILARITY_THRESHOLD = 0.72;
|
|
27
|
-
|
|
28
|
-
// Max error patterns to check per write
|
|
29
|
-
const MAX_PATTERNS_TO_CHECK = 8;
|
|
30
|
-
|
|
31
|
-
// Minimum content length to bother checking — skip tiny edits (console.log,
|
|
32
|
-
// closing braces, single-line comments). Not worth a Vektor round-trip.
|
|
33
|
-
const MIN_CHECK_LENGTH = 50;
|
|
34
|
-
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
|
-
// LRU warning cache — prevents slamming Vektor on rapid multi-file writes
|
|
37
|
-
// (MultiEdit, code generation loops, etc.)
|
|
38
|
-
//
|
|
39
|
-
// Key: SHA-1 of content.slice(0,300) — cheap, collision-safe for this use
|
|
40
|
-
// Value: { warnings, cachedAt }
|
|
41
|
-
// TTL: 60 seconds — patterns don't change that fast within a session
|
|
42
|
-
// Max: 100 entries — bounded memory, evicts oldest on overflow
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
const CACHE_TTL_MS = 60_000;
|
|
45
|
-
const CACHE_MAX = 100;
|
|
46
|
-
const _warningCache = new Map(); // key → { warnings, cachedAt }
|
|
47
|
-
|
|
48
|
-
function _cacheKey(content) {
|
|
49
|
-
return crypto.createHash('sha1').update(content.slice(0, 300)).digest('hex');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function _cacheGet(key) {
|
|
53
|
-
const entry = _warningCache.get(key);
|
|
54
|
-
if (!entry) return null;
|
|
55
|
-
if (Date.now() - entry.cachedAt > CACHE_TTL_MS) {
|
|
56
|
-
_warningCache.delete(key);
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
return entry.warnings;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function _cacheSet(key, warnings) {
|
|
63
|
-
// Evict oldest entry if at capacity
|
|
64
|
-
if (_warningCache.size >= CACHE_MAX) {
|
|
65
|
-
_warningCache.delete(_warningCache.keys().next().value);
|
|
66
|
-
}
|
|
67
|
-
_warningCache.set(key, { warnings, cachedAt: Date.now() });
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
// Pattern storage key prefix — namespaces cerebellum nodes in Vektor
|
|
72
|
-
// ---------------------------------------------------------------------------
|
|
73
|
-
const ERROR_PREFIX = '[CLOAK_CEREBELLUM_ERROR]';
|
|
74
|
-
const RESOLVED_PREFIX = '[CLOAK_CEREBELLUM_RESOLVED]';
|
|
75
|
-
const DNR_PREFIX = '[CLOAK_CEREBELLUM_DNR]'; // Do-Not-Repeat
|
|
76
|
-
|
|
77
|
-
// ---------------------------------------------------------------------------
|
|
78
|
-
// Core: check a write against known error patterns
|
|
79
|
-
// ---------------------------------------------------------------------------
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* checkWrite({ content, filePath, memory })
|
|
83
|
-
* Called from the PreToolUse hook before every write.
|
|
84
|
-
* Returns warnings if the write matches known error patterns.
|
|
85
|
-
*
|
|
86
|
-
* @param {string} content - The content being written
|
|
87
|
-
* @param {string} filePath - The file being written to
|
|
88
|
-
* @param {object} memory - Vektor memory instance
|
|
89
|
-
* @returns {object} - { warnings: [], shouldProceed: true }
|
|
90
|
-
*/
|
|
91
|
-
async function checkWrite({ content, filePath, memory } = {}) {
|
|
92
|
-
if (!memory) throw new Error('cloak_cerebellum: memory instance is required');
|
|
93
|
-
if (!content) return { warnings: [], shouldProceed: true, cached: false };
|
|
94
|
-
|
|
95
|
-
// Bypass: tiny writes aren't worth a Vektor round-trip
|
|
96
|
-
// (closing braces, console.logs, single-line cosmetic edits)
|
|
97
|
-
if (content.length < MIN_CHECK_LENGTH) {
|
|
98
|
-
return { warnings: [], shouldProceed: true, bypassed: 'too_small' };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Cache hit: same content checked recently — return instantly, no Vektor call
|
|
102
|
-
const key = _cacheKey(content);
|
|
103
|
-
const cached = _cacheGet(key);
|
|
104
|
-
if (cached !== null) {
|
|
105
|
-
return {
|
|
106
|
-
warnings : cached,
|
|
107
|
-
shouldProceed: true,
|
|
108
|
-
hasBlockers : cached.some(w => w.severity === 'block'),
|
|
109
|
-
cached : true,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const warnings = [];
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
// Query causal error graph for patterns similar to this write
|
|
117
|
-
const errorPatterns = await memory.recall(
|
|
118
|
-
`${ERROR_PREFIX} ${content.slice(0, 300)}`,
|
|
119
|
-
MAX_PATTERNS_TO_CHECK
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
if (!errorPatterns || errorPatterns.length === 0) {
|
|
123
|
-
_cacheSet(key, []); // cache the empty result too — prevents repeat Vektor calls
|
|
124
|
-
return { warnings: [], shouldProceed: true, cached: false };
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
for (const pattern of errorPatterns) {
|
|
128
|
-
if (pattern.score < SIMILARITY_THRESHOLD) continue;
|
|
129
|
-
|
|
130
|
-
// Parse the stored pattern
|
|
131
|
-
const parsed = parsePatternNode(pattern.content);
|
|
132
|
-
if (!parsed) continue;
|
|
133
|
-
|
|
134
|
-
// Skip if this pattern is already resolved
|
|
135
|
-
if (parsed.resolvedBy) continue;
|
|
136
|
-
|
|
137
|
-
warnings.push({
|
|
138
|
-
patternId : pattern.id,
|
|
139
|
-
description : parsed.description,
|
|
140
|
-
originalFile: parsed.file,
|
|
141
|
-
severity : parsed.severity || 'warn',
|
|
142
|
-
hint : parsed.fix || 'See error pattern for details',
|
|
143
|
-
score : Math.round(pattern.score * 100) / 100,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Also check Do-Not-Repeat rules (always enforced regardless of similarity)
|
|
148
|
-
const dnrRules = await memory.recall(
|
|
149
|
-
`${DNR_PREFIX}`,
|
|
150
|
-
5
|
|
151
|
-
);
|
|
152
|
-
|
|
153
|
-
if (dnrRules) {
|
|
154
|
-
for (const rule of dnrRules) {
|
|
155
|
-
const parsed = parseDNRNode(rule.content);
|
|
156
|
-
if (!parsed) continue;
|
|
157
|
-
|
|
158
|
-
// Check if the pattern keyword appears in the write content
|
|
159
|
-
if (content.toLowerCase().includes(parsed.keyword.toLowerCase())) {
|
|
160
|
-
warnings.push({
|
|
161
|
-
patternId : rule.id,
|
|
162
|
-
description : `Do-Not-Repeat: ${parsed.rule}`,
|
|
163
|
-
severity : 'block',
|
|
164
|
-
hint : parsed.alternative || 'Avoid this pattern',
|
|
165
|
-
score : 1.0,
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
} catch (err) {
|
|
172
|
-
// Non-fatal — Vektor query failure should not block writes
|
|
173
|
-
console.warn('[cloak_cerebellum] Pattern check failed:', err.message);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Cache the result for 60s — rapid multi-file writes skip Vektor entirely
|
|
177
|
-
_cacheSet(key, warnings);
|
|
178
|
-
|
|
179
|
-
return {
|
|
180
|
-
warnings,
|
|
181
|
-
shouldProceed: true,
|
|
182
|
-
hasBlockers : warnings.some(w => w.severity === 'block'),
|
|
183
|
-
cached : false,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ---------------------------------------------------------------------------
|
|
188
|
-
// Core: record a new error pattern into the causal graph
|
|
189
|
-
// ---------------------------------------------------------------------------
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* recordError({ description, filePath, errorType, fix, severity, memory })
|
|
193
|
-
* Store a new error pattern so future writes can be warned.
|
|
194
|
-
*
|
|
195
|
-
* @param {string} description - Human-readable description of the error
|
|
196
|
-
* @param {string} filePath - File where error occurred
|
|
197
|
-
* @param {string} errorType - e.g. 'null_reference', 'type_mismatch'
|
|
198
|
-
* @param {string} fix - What fixed it (optional)
|
|
199
|
-
* @param {'warn'|'block'} severity
|
|
200
|
-
* @param {object} memory - Vektor memory instance
|
|
201
|
-
* @returns {object} - { id, written }
|
|
202
|
-
*/
|
|
203
|
-
async function recordError({ description, filePath, errorType, fix, severity = 'warn', memory } = {}) {
|
|
204
|
-
if (!memory) throw new Error('cloak_cerebellum: memory instance is required');
|
|
205
|
-
if (!description) throw new Error('cloak_cerebellum: description is required');
|
|
206
|
-
|
|
207
|
-
const patternStr = [
|
|
208
|
-
ERROR_PREFIX,
|
|
209
|
-
`error_type:${errorType || 'unknown'}`,
|
|
210
|
-
`file:${filePath || 'unknown'}`,
|
|
211
|
-
`description:${description}`,
|
|
212
|
-
`severity:${severity}`,
|
|
213
|
-
fix ? `fix:${fix}` : null,
|
|
214
|
-
`recorded_at:${new Date().toISOString()}`,
|
|
215
|
-
`resolved:false`,
|
|
216
|
-
].filter(Boolean).join(' | ');
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
await memory.remember(patternStr);
|
|
220
|
-
return { written: true, pattern: patternStr };
|
|
221
|
-
} catch (err) {
|
|
222
|
-
console.error('[cloak_cerebellum] Failed to record error:', err.message);
|
|
223
|
-
return { written: false, error: err.message };
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// ---------------------------------------------------------------------------
|
|
228
|
-
// Core: auto-resolve — detect when a write fixes an open error
|
|
229
|
-
// Called from PostToolUse after a successful write.
|
|
230
|
-
// This is the key improvement over OpenWolf — resolution is automatic,
|
|
231
|
-
// not dependent on Claude remembering to call a separate tool.
|
|
232
|
-
// ---------------------------------------------------------------------------
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* autoResolve({ content, filePath, memory })
|
|
236
|
-
* After a successful write, check if it resolves any open error patterns.
|
|
237
|
-
* If yes, write a [RESOLVED_BY] causal edge to close the loop.
|
|
238
|
-
* This feeds the decay weighting in MAGMA's causal graph.
|
|
239
|
-
*
|
|
240
|
-
* @param {string} content - Content that was written
|
|
241
|
-
* @param {string} filePath - File that was written
|
|
242
|
-
* @param {object} memory - Vektor memory instance
|
|
243
|
-
* @returns {object} - { resolved: [], written: number }
|
|
244
|
-
*/
|
|
245
|
-
async function autoResolve({ content, filePath, memory } = {}) {
|
|
246
|
-
if (!memory || !filePath) return { resolved: [], written: 0 };
|
|
247
|
-
|
|
248
|
-
const resolved = [];
|
|
249
|
-
|
|
250
|
-
try {
|
|
251
|
-
// Query by FILE PATH not content — avoids the semantic gap where
|
|
252
|
-
// "Null reference on user.id" and "if (user && user.id)" have
|
|
253
|
-
// completely different embeddings despite one fixing the other.
|
|
254
|
-
// Pull all open errors on this file, then let REM cycle verify.
|
|
255
|
-
const candidates = await memory.recall(
|
|
256
|
-
`${ERROR_PREFIX} file:${filePath}`,
|
|
257
|
-
MAX_PATTERNS_TO_CHECK
|
|
258
|
-
);
|
|
259
|
-
|
|
260
|
-
if (!candidates || candidates.length === 0) return { resolved: [], written: 0 };
|
|
261
|
-
|
|
262
|
-
for (const candidate of candidates) {
|
|
263
|
-
// File path match is already a strong signal — use lower threshold
|
|
264
|
-
// than content matching. 0.5 avoids completely unrelated file path
|
|
265
|
-
// substring matches while catching real file-based errors.
|
|
266
|
-
if (candidate.score < 0.5) continue;
|
|
267
|
-
|
|
268
|
-
const parsed = parsePatternNode(candidate.content);
|
|
269
|
-
if (!parsed || parsed.resolvedBy) continue; // already resolved
|
|
270
|
-
|
|
271
|
-
// Only auto-resolve if the stored error references this exact file
|
|
272
|
-
if (parsed.file && parsed.file !== filePath &&
|
|
273
|
-
parsed.file !== require('path').basename(filePath)) continue;
|
|
274
|
-
|
|
275
|
-
// Write resolution node — this is the [RESOLVED_BY] causal edge
|
|
276
|
-
const resolutionStr = [
|
|
277
|
-
RESOLVED_PREFIX,
|
|
278
|
-
`resolves_pattern:${candidate.id}`,
|
|
279
|
-
`original_error:${parsed.description}`,
|
|
280
|
-
`resolved_by_file:${filePath || 'unknown'}`,
|
|
281
|
-
`resolved_at:${new Date().toISOString()}`,
|
|
282
|
-
`[RESOLVED_BY]`, // explicit causal edge marker for HippoRAG decay weighting
|
|
283
|
-
].join(' | ');
|
|
284
|
-
|
|
285
|
-
try {
|
|
286
|
-
await memory.remember(resolutionStr);
|
|
287
|
-
resolved.push({
|
|
288
|
-
patternId : candidate.id,
|
|
289
|
-
description: parsed.description,
|
|
290
|
-
resolution : resolutionStr,
|
|
291
|
-
});
|
|
292
|
-
} catch (err) {
|
|
293
|
-
console.warn('[cloak_cerebellum] Failed to write resolution:', err.message);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
} catch (err) {
|
|
298
|
-
console.warn('[cloak_cerebellum] Auto-resolve query failed:', err.message);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
return { resolved, written: resolved.length };
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
// ---------------------------------------------------------------------------
|
|
305
|
-
// Do-Not-Repeat rules — project-level conventions
|
|
306
|
-
// ---------------------------------------------------------------------------
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* addDNRRule({ keyword, rule, alternative, memory })
|
|
310
|
-
* Store a Do-Not-Repeat rule. Checked on every write regardless of similarity.
|
|
311
|
-
*
|
|
312
|
-
* @param {string} keyword - Trigger keyword/pattern to watch for
|
|
313
|
-
* @param {string} rule - Human description of what not to do
|
|
314
|
-
* @param {string} alternative - What to do instead
|
|
315
|
-
* @param {object} memory
|
|
316
|
-
*/
|
|
317
|
-
async function addDNRRule({ keyword, rule, alternative, memory } = {}) {
|
|
318
|
-
if (!memory) throw new Error('cloak_cerebellum: memory instance is required');
|
|
319
|
-
if (!keyword) throw new Error('cloak_cerebellum: keyword is required');
|
|
320
|
-
if (!rule) throw new Error('cloak_cerebellum: rule is required');
|
|
321
|
-
|
|
322
|
-
const dnrStr = [
|
|
323
|
-
DNR_PREFIX,
|
|
324
|
-
`keyword:${keyword}`,
|
|
325
|
-
`rule:${rule}`,
|
|
326
|
-
alternative ? `alternative:${alternative}` : null,
|
|
327
|
-
`added_at:${new Date().toISOString()}`,
|
|
328
|
-
].filter(Boolean).join(' | ');
|
|
329
|
-
|
|
330
|
-
try {
|
|
331
|
-
await memory.remember(dnrStr);
|
|
332
|
-
return { written: true };
|
|
333
|
-
} catch (err) {
|
|
334
|
-
console.error('[cloak_cerebellum] Failed to add DNR rule:', err.message);
|
|
335
|
-
return { written: false, error: err.message };
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// ---------------------------------------------------------------------------
|
|
340
|
-
// Claude Code hook integration
|
|
341
|
-
// ---------------------------------------------------------------------------
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* handleHookPayload({ payload, memory })
|
|
345
|
-
* Routes Claude Code PreToolUse/PostToolUse hooks to checkWrite / autoResolve.
|
|
346
|
-
*/
|
|
347
|
-
async function handleHookPayload({ payload, memory } = {}) {
|
|
348
|
-
if (!memory || !payload) return null;
|
|
349
|
-
|
|
350
|
-
const hookName = payload?.hook_event_name || payload?.event;
|
|
351
|
-
const tool = payload?.tool_name;
|
|
352
|
-
const input = payload?.tool_input;
|
|
353
|
-
const output = payload?.tool_response;
|
|
354
|
-
|
|
355
|
-
const isWrite = ['Write', 'Edit', 'MultiEdit', 'Bash'].includes(tool);
|
|
356
|
-
|
|
357
|
-
switch (hookName) {
|
|
358
|
-
case 'PreToolUse':
|
|
359
|
-
case 'pre_tool_use': {
|
|
360
|
-
if (!isWrite) return null;
|
|
361
|
-
|
|
362
|
-
// Bug fix: MultiEdit sends input.edits = [{ old_string, new_string }]
|
|
363
|
-
// not a flat input.new_string. Concatenate all new_string blocks so
|
|
364
|
-
// cerebellum actually sees the content being written in refactors.
|
|
365
|
-
let content = input?.new_string || input?.content || input?.command || '';
|
|
366
|
-
if (!content && input?.edits && Array.isArray(input.edits)) {
|
|
367
|
-
content = input.edits.map(e => e.new_string || '').filter(Boolean).join('\n');
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
const filePath = input?.file_path || input?.path || '';
|
|
371
|
-
return checkWrite({ content, filePath, memory });
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
case 'PostToolUse':
|
|
375
|
-
case 'post_tool_use': {
|
|
376
|
-
if (!isWrite) return null;
|
|
377
|
-
const success = !output?.error;
|
|
378
|
-
if (!success) return null;
|
|
379
|
-
|
|
380
|
-
// Same MultiEdit fix on the post-write resolve path
|
|
381
|
-
let content = input?.new_string || input?.content || '';
|
|
382
|
-
if (!content && input?.edits && Array.isArray(input.edits)) {
|
|
383
|
-
content = input.edits.map(e => e.new_string || '').filter(Boolean).join('\n');
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
const filePath = input?.file_path || input?.path || '';
|
|
387
|
-
return autoResolve({ content, filePath, memory });
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
default:
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// ---------------------------------------------------------------------------
|
|
396
|
-
// Parsers — extract structured data from Vektor node strings
|
|
397
|
-
// ---------------------------------------------------------------------------
|
|
398
|
-
function parsePatternNode(str) {
|
|
399
|
-
if (!str || !str.includes(ERROR_PREFIX)) return null;
|
|
400
|
-
const parts = str.split(' | ');
|
|
401
|
-
const get = key => {
|
|
402
|
-
const part = parts.find(p => p.startsWith(`${key}:`));
|
|
403
|
-
return part ? part.slice(key.length + 1) : null;
|
|
404
|
-
};
|
|
405
|
-
return {
|
|
406
|
-
errorType : get('error_type'),
|
|
407
|
-
file : get('file'),
|
|
408
|
-
description: get('description'),
|
|
409
|
-
severity : get('severity'),
|
|
410
|
-
fix : get('fix'),
|
|
411
|
-
resolvedBy: str.includes(RESOLVED_PREFIX) || get('resolved') === 'true' ? true : null,
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
function parseDNRNode(str) {
|
|
416
|
-
if (!str || !str.includes(DNR_PREFIX)) return null;
|
|
417
|
-
const parts = str.split(' | ');
|
|
418
|
-
const get = key => {
|
|
419
|
-
const part = parts.find(p => p.startsWith(`${key}:`));
|
|
420
|
-
return part ? part.slice(key.length + 1) : null;
|
|
421
|
-
};
|
|
422
|
-
return {
|
|
423
|
-
keyword : get('keyword'),
|
|
424
|
-
rule : get('rule'),
|
|
425
|
-
alternative: get('alternative'),
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
module.exports = {
|
|
430
|
-
checkWrite,
|
|
431
|
-
recordError,
|
|
432
|
-
autoResolve,
|
|
433
|
-
addDNRRule,
|
|
434
|
-
handleHookPayload,
|
|
435
|
-
SIMILARITY_THRESHOLD,
|
|
436
|
-
ERROR_PREFIX,
|
|
437
|
-
RESOLVED_PREFIX,
|
|
438
|
-
DNR_PREFIX,
|
|
439
|
-
};
|