chainlesschain 0.37.9 → 0.37.11
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 +309 -19
- package/bin/chainlesschain.js +4 -0
- package/package.json +1 -1
- package/src/commands/a2a.js +374 -0
- package/src/commands/audit.js +286 -0
- package/src/commands/auth.js +387 -0
- package/src/commands/bi.js +240 -0
- package/src/commands/browse.js +184 -0
- package/src/commands/cowork.js +317 -0
- package/src/commands/did.js +376 -0
- package/src/commands/economy.js +375 -0
- package/src/commands/encrypt.js +233 -0
- package/src/commands/evolution.js +398 -0
- package/src/commands/export.js +125 -0
- package/src/commands/git.js +215 -0
- package/src/commands/hmemory.js +273 -0
- package/src/commands/hook.js +260 -0
- package/src/commands/import.js +259 -0
- package/src/commands/init.js +184 -0
- package/src/commands/instinct.js +202 -0
- package/src/commands/llm.js +155 -4
- package/src/commands/lowcode.js +320 -0
- package/src/commands/mcp.js +302 -0
- package/src/commands/memory.js +282 -0
- package/src/commands/note.js +187 -0
- package/src/commands/org.js +505 -0
- package/src/commands/p2p.js +274 -0
- package/src/commands/plugin.js +451 -0
- package/src/commands/sandbox.js +366 -0
- package/src/commands/search.js +237 -0
- package/src/commands/session.js +238 -0
- package/src/commands/skill.js +254 -201
- package/src/commands/sync.js +249 -0
- package/src/commands/tokens.js +214 -0
- package/src/commands/wallet.js +416 -0
- package/src/commands/workflow.js +359 -0
- package/src/commands/zkp.js +277 -0
- package/src/index.js +93 -1
- package/src/lib/a2a-protocol.js +371 -0
- package/src/lib/agent-coordinator.js +273 -0
- package/src/lib/agent-economy.js +369 -0
- package/src/lib/app-builder.js +377 -0
- package/src/lib/audit-logger.js +364 -0
- package/src/lib/bi-engine.js +299 -0
- package/src/lib/bm25-search.js +322 -0
- package/src/lib/browser-automation.js +216 -0
- package/src/lib/cowork/ab-comparator-cli.js +180 -0
- package/src/lib/cowork/code-knowledge-graph-cli.js +232 -0
- package/src/lib/cowork/debate-review-cli.js +144 -0
- package/src/lib/cowork/decision-kb-cli.js +153 -0
- package/src/lib/cowork/project-style-analyzer-cli.js +168 -0
- package/src/lib/cowork-adapter.js +106 -0
- package/src/lib/crypto-manager.js +246 -0
- package/src/lib/did-manager.js +270 -0
- package/src/lib/ensure-utf8.js +59 -0
- package/src/lib/evolution-system.js +508 -0
- package/src/lib/git-integration.js +220 -0
- package/src/lib/hierarchical-memory.js +471 -0
- package/src/lib/hook-manager.js +387 -0
- package/src/lib/instinct-manager.js +190 -0
- package/src/lib/knowledge-exporter.js +302 -0
- package/src/lib/knowledge-importer.js +293 -0
- package/src/lib/llm-providers.js +325 -0
- package/src/lib/mcp-client.js +413 -0
- package/src/lib/memory-manager.js +211 -0
- package/src/lib/note-versioning.js +244 -0
- package/src/lib/org-manager.js +424 -0
- package/src/lib/p2p-manager.js +317 -0
- package/src/lib/pdf-parser.js +96 -0
- package/src/lib/permission-engine.js +374 -0
- package/src/lib/plan-mode.js +333 -0
- package/src/lib/plugin-manager.js +430 -0
- package/src/lib/project-detector.js +53 -0
- package/src/lib/response-cache.js +156 -0
- package/src/lib/sandbox-v2.js +503 -0
- package/src/lib/service-container.js +183 -0
- package/src/lib/session-manager.js +189 -0
- package/src/lib/skill-loader.js +274 -0
- package/src/lib/sync-manager.js +347 -0
- package/src/lib/token-tracker.js +200 -0
- package/src/lib/wallet-manager.js +348 -0
- package/src/lib/workflow-engine.js +503 -0
- package/src/lib/zkp-engine.js +241 -0
- package/src/repl/agent-repl.js +259 -124
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook Manager — Lifecycle hook registration, execution, and statistics for CLI.
|
|
3
|
+
* Manages hooks that trigger on system events (IPC, tools, sessions, git, etc.).
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook priority levels — lower values run first.
|
|
10
|
+
*/
|
|
11
|
+
export const HookPriority = {
|
|
12
|
+
SYSTEM: 0,
|
|
13
|
+
HIGH: 100,
|
|
14
|
+
NORMAL: 500,
|
|
15
|
+
LOW: 900,
|
|
16
|
+
MONITOR: 1000,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Hook execution types.
|
|
21
|
+
*/
|
|
22
|
+
export const HookType = {
|
|
23
|
+
SYNC: "sync",
|
|
24
|
+
ASYNC: "async",
|
|
25
|
+
COMMAND: "command",
|
|
26
|
+
SCRIPT: "script",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* All supported hook event names.
|
|
31
|
+
*/
|
|
32
|
+
export const HookEvents = {
|
|
33
|
+
PreIPCCall: "PreIPCCall",
|
|
34
|
+
PostIPCCall: "PostIPCCall",
|
|
35
|
+
IPCError: "IPCError",
|
|
36
|
+
PreToolUse: "PreToolUse",
|
|
37
|
+
PostToolUse: "PostToolUse",
|
|
38
|
+
ToolError: "ToolError",
|
|
39
|
+
SessionStart: "SessionStart",
|
|
40
|
+
SessionEnd: "SessionEnd",
|
|
41
|
+
PreCompact: "PreCompact",
|
|
42
|
+
PostCompact: "PostCompact",
|
|
43
|
+
UserPromptSubmit: "UserPromptSubmit",
|
|
44
|
+
AssistantResponse: "AssistantResponse",
|
|
45
|
+
AgentStart: "AgentStart",
|
|
46
|
+
AgentStop: "AgentStop",
|
|
47
|
+
TaskAssigned: "TaskAssigned",
|
|
48
|
+
TaskCompleted: "TaskCompleted",
|
|
49
|
+
PreFileAccess: "PreFileAccess",
|
|
50
|
+
PostFileAccess: "PostFileAccess",
|
|
51
|
+
FileModified: "FileModified",
|
|
52
|
+
MemorySave: "MemorySave",
|
|
53
|
+
MemoryLoad: "MemoryLoad",
|
|
54
|
+
AuditLog: "AuditLog",
|
|
55
|
+
ComplianceCheck: "ComplianceCheck",
|
|
56
|
+
DataSubjectRequest: "DataSubjectRequest",
|
|
57
|
+
PreGitCommit: "PreGitCommit",
|
|
58
|
+
PostGitCommit: "PostGitCommit",
|
|
59
|
+
PreGitPush: "PreGitPush",
|
|
60
|
+
CIFailure: "CIFailure",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Ensure hooks table exists in the database.
|
|
65
|
+
*/
|
|
66
|
+
export function ensureHookTables(db) {
|
|
67
|
+
db.exec(`
|
|
68
|
+
CREATE TABLE IF NOT EXISTS hooks (
|
|
69
|
+
id TEXT PRIMARY KEY,
|
|
70
|
+
event TEXT NOT NULL,
|
|
71
|
+
name TEXT NOT NULL,
|
|
72
|
+
type TEXT NOT NULL DEFAULT 'sync',
|
|
73
|
+
priority INTEGER NOT NULL DEFAULT 500,
|
|
74
|
+
handler TEXT,
|
|
75
|
+
matcher TEXT,
|
|
76
|
+
timeout INTEGER DEFAULT 5000,
|
|
77
|
+
enabled INTEGER DEFAULT 1,
|
|
78
|
+
description TEXT,
|
|
79
|
+
execution_count INTEGER DEFAULT 0,
|
|
80
|
+
error_count INTEGER DEFAULT 0,
|
|
81
|
+
total_execution_time REAL DEFAULT 0,
|
|
82
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
83
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
84
|
+
)
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Register a new hook.
|
|
90
|
+
*/
|
|
91
|
+
export function registerHook(db, hookConfig) {
|
|
92
|
+
ensureHookTables(db);
|
|
93
|
+
|
|
94
|
+
const {
|
|
95
|
+
event,
|
|
96
|
+
name,
|
|
97
|
+
type = HookType.SYNC,
|
|
98
|
+
priority = HookPriority.NORMAL,
|
|
99
|
+
handler,
|
|
100
|
+
matcher,
|
|
101
|
+
timeout = 5000,
|
|
102
|
+
enabled = true,
|
|
103
|
+
description,
|
|
104
|
+
} = hookConfig;
|
|
105
|
+
|
|
106
|
+
if (!event) {
|
|
107
|
+
throw new Error("Hook event is required");
|
|
108
|
+
}
|
|
109
|
+
if (!name) {
|
|
110
|
+
throw new Error("Hook name is required");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Validate event name
|
|
114
|
+
if (!Object.values(HookEvents).includes(event)) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Invalid hook event: ${event}. Use one of: ${Object.values(HookEvents).join(", ")}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Validate type
|
|
121
|
+
if (!Object.values(HookType).includes(type)) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Invalid hook type: ${type}. Use one of: ${Object.values(HookType).join(", ")}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const id = `hook-${crypto.randomBytes(8).toString("hex")}`;
|
|
128
|
+
|
|
129
|
+
db.prepare(
|
|
130
|
+
`INSERT INTO hooks (id, event, name, type, priority, handler, matcher, timeout, enabled, description)
|
|
131
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
132
|
+
).run(
|
|
133
|
+
id,
|
|
134
|
+
event,
|
|
135
|
+
name,
|
|
136
|
+
type,
|
|
137
|
+
priority,
|
|
138
|
+
handler || null,
|
|
139
|
+
matcher || null,
|
|
140
|
+
timeout,
|
|
141
|
+
enabled ? 1 : 0,
|
|
142
|
+
description || null,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
id,
|
|
147
|
+
event,
|
|
148
|
+
name,
|
|
149
|
+
type,
|
|
150
|
+
priority,
|
|
151
|
+
handler,
|
|
152
|
+
matcher,
|
|
153
|
+
timeout,
|
|
154
|
+
enabled: !!enabled,
|
|
155
|
+
description,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Unregister (remove) a hook by ID.
|
|
161
|
+
*/
|
|
162
|
+
export function unregisterHook(db, hookId) {
|
|
163
|
+
ensureHookTables(db);
|
|
164
|
+
const result = db.prepare("DELETE FROM hooks WHERE id = ?").run(hookId);
|
|
165
|
+
return result.changes > 0;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* List hooks with optional filters.
|
|
170
|
+
*/
|
|
171
|
+
export function listHooks(db, options = {}) {
|
|
172
|
+
ensureHookTables(db);
|
|
173
|
+
const { event, enabledOnly = false } = options;
|
|
174
|
+
|
|
175
|
+
if (event && enabledOnly) {
|
|
176
|
+
return db
|
|
177
|
+
.prepare(
|
|
178
|
+
"SELECT * FROM hooks WHERE event = ? AND enabled = 1 ORDER BY priority ASC",
|
|
179
|
+
)
|
|
180
|
+
.all(event);
|
|
181
|
+
}
|
|
182
|
+
if (event) {
|
|
183
|
+
return db
|
|
184
|
+
.prepare("SELECT * FROM hooks WHERE event = ? ORDER BY priority ASC")
|
|
185
|
+
.all(event);
|
|
186
|
+
}
|
|
187
|
+
if (enabledOnly) {
|
|
188
|
+
return db
|
|
189
|
+
.prepare("SELECT * FROM hooks WHERE enabled = 1 ORDER BY priority ASC")
|
|
190
|
+
.all();
|
|
191
|
+
}
|
|
192
|
+
return db.prepare("SELECT * FROM hooks ORDER BY priority ASC").all();
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get a single hook by ID.
|
|
197
|
+
*/
|
|
198
|
+
export function getHook(db, hookId) {
|
|
199
|
+
ensureHookTables(db);
|
|
200
|
+
return db.prepare("SELECT * FROM hooks WHERE id = ?").get(hookId);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Compile a matcher pattern into a test function.
|
|
205
|
+
* Supports:
|
|
206
|
+
* - null/undefined → matches everything
|
|
207
|
+
* - Pipe-separated patterns: "Edit|Write" matches "Edit" or "Write"
|
|
208
|
+
* - Wildcards: "*" matches any chars, "?" matches one char
|
|
209
|
+
* - Regex strings starting with "/": "/^Pre/" matches "PreIPCCall"
|
|
210
|
+
*/
|
|
211
|
+
export function compileMatcher(pattern) {
|
|
212
|
+
if (!pattern) {
|
|
213
|
+
return () => true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Regex pattern (starts and ends with /)
|
|
217
|
+
if (pattern.startsWith("/") && pattern.lastIndexOf("/") > 0) {
|
|
218
|
+
const lastSlash = pattern.lastIndexOf("/");
|
|
219
|
+
const regexBody = pattern.slice(1, lastSlash);
|
|
220
|
+
const flags = pattern.slice(lastSlash + 1);
|
|
221
|
+
try {
|
|
222
|
+
const re = new RegExp(regexBody, flags);
|
|
223
|
+
return (value) => re.test(value);
|
|
224
|
+
} catch (_err) {
|
|
225
|
+
// Invalid regex — fall through to wildcard matching
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Pipe-separated patterns (e.g. "Edit|Write")
|
|
230
|
+
if (pattern.includes("|")) {
|
|
231
|
+
const parts = pattern.split("|").map((p) => p.trim());
|
|
232
|
+
const matchers = parts.map((p) => compileMatcher(p));
|
|
233
|
+
return (value) => matchers.some((m) => m(value));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Wildcard pattern (* and ?)
|
|
237
|
+
const escaped = pattern
|
|
238
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
239
|
+
.replace(/\*/g, ".*")
|
|
240
|
+
.replace(/\?/g, ".");
|
|
241
|
+
const re = new RegExp(`^${escaped}$`);
|
|
242
|
+
return (value) => re.test(value);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Execute a single hook with context.
|
|
247
|
+
* Returns { success, result, error, executionTime }.
|
|
248
|
+
*/
|
|
249
|
+
export async function executeHook(hook, context = {}) {
|
|
250
|
+
const start = Date.now();
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
const type = hook.type || HookType.SYNC;
|
|
254
|
+
|
|
255
|
+
if (type === HookType.COMMAND || type === HookType.SCRIPT) {
|
|
256
|
+
// Command/script hooks execute a shell command
|
|
257
|
+
const { execSync } = await import("child_process");
|
|
258
|
+
const cmd = hook.handler || "";
|
|
259
|
+
if (!cmd) {
|
|
260
|
+
return {
|
|
261
|
+
success: false,
|
|
262
|
+
result: null,
|
|
263
|
+
error: "No handler command specified",
|
|
264
|
+
executionTime: 0,
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
const env = {
|
|
268
|
+
...process.env,
|
|
269
|
+
HOOK_EVENT: hook.event,
|
|
270
|
+
HOOK_CONTEXT: JSON.stringify(context),
|
|
271
|
+
};
|
|
272
|
+
const output = execSync(cmd, {
|
|
273
|
+
encoding: "utf-8",
|
|
274
|
+
timeout: hook.timeout || 5000,
|
|
275
|
+
env,
|
|
276
|
+
});
|
|
277
|
+
const executionTime = Date.now() - start;
|
|
278
|
+
return {
|
|
279
|
+
success: true,
|
|
280
|
+
result: output.trim(),
|
|
281
|
+
error: null,
|
|
282
|
+
executionTime,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// For sync/async hooks with a handler function string
|
|
287
|
+
if (hook.handlerFn && typeof hook.handlerFn === "function") {
|
|
288
|
+
const result = await Promise.resolve(hook.handlerFn(context));
|
|
289
|
+
const executionTime = Date.now() - start;
|
|
290
|
+
return { success: true, result, error: null, executionTime };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// No executable handler
|
|
294
|
+
const executionTime = Date.now() - start;
|
|
295
|
+
return { success: true, result: null, error: null, executionTime };
|
|
296
|
+
} catch (err) {
|
|
297
|
+
const executionTime = Date.now() - start;
|
|
298
|
+
return { success: false, result: null, error: err.message, executionTime };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Execute all hooks for a given event, in priority order.
|
|
304
|
+
* Returns array of { hookId, hookName, success, result, error, executionTime }.
|
|
305
|
+
*/
|
|
306
|
+
export async function executeHooks(db, eventName, context = {}) {
|
|
307
|
+
const hooks = listHooks(db, { event: eventName, enabledOnly: true });
|
|
308
|
+
const results = [];
|
|
309
|
+
|
|
310
|
+
for (const hook of hooks) {
|
|
311
|
+
// Check matcher against context
|
|
312
|
+
if (hook.matcher) {
|
|
313
|
+
const matchFn = compileMatcher(hook.matcher);
|
|
314
|
+
const target =
|
|
315
|
+
context.target ||
|
|
316
|
+
context.channel ||
|
|
317
|
+
context.tool ||
|
|
318
|
+
context.file ||
|
|
319
|
+
eventName;
|
|
320
|
+
if (!matchFn(target)) {
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const outcome = await executeHook(hook, context);
|
|
326
|
+
results.push({
|
|
327
|
+
hookId: hook.id,
|
|
328
|
+
hookName: hook.name,
|
|
329
|
+
...outcome,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Update stats
|
|
333
|
+
updateHookStats(db, hook.id, {
|
|
334
|
+
executionTime: outcome.executionTime,
|
|
335
|
+
success: outcome.success,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return results;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Get hook execution statistics.
|
|
344
|
+
*/
|
|
345
|
+
export function getHookStats(db) {
|
|
346
|
+
ensureHookTables(db);
|
|
347
|
+
const hooks = db
|
|
348
|
+
.prepare(
|
|
349
|
+
"SELECT id, event, name, execution_count, error_count, total_execution_time FROM hooks ORDER BY execution_count DESC",
|
|
350
|
+
)
|
|
351
|
+
.all();
|
|
352
|
+
|
|
353
|
+
return hooks.map((h) => ({
|
|
354
|
+
id: h.id,
|
|
355
|
+
event: h.event,
|
|
356
|
+
name: h.name,
|
|
357
|
+
executionCount: h.execution_count || 0,
|
|
358
|
+
errorCount: h.error_count || 0,
|
|
359
|
+
avgExecutionTime:
|
|
360
|
+
h.execution_count > 0
|
|
361
|
+
? Math.round((h.total_execution_time / h.execution_count) * 100) / 100
|
|
362
|
+
: 0,
|
|
363
|
+
totalExecutionTime: h.total_execution_time || 0,
|
|
364
|
+
}));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Update hook statistics after execution.
|
|
369
|
+
*/
|
|
370
|
+
export function updateHookStats(
|
|
371
|
+
db,
|
|
372
|
+
hookId,
|
|
373
|
+
{ executionTime = 0, success = true } = {},
|
|
374
|
+
) {
|
|
375
|
+
ensureHookTables(db);
|
|
376
|
+
|
|
377
|
+
const hook = getHook(db, hookId);
|
|
378
|
+
if (!hook) return;
|
|
379
|
+
|
|
380
|
+
const newCount = (hook.execution_count || 0) + 1;
|
|
381
|
+
const newErrorCount = (hook.error_count || 0) + (success ? 0 : 1);
|
|
382
|
+
const newTotalTime = (hook.total_execution_time || 0) + executionTime;
|
|
383
|
+
|
|
384
|
+
db.prepare(
|
|
385
|
+
"UPDATE hooks SET execution_count = ?, error_count = ?, total_execution_time = ?, updated_at = datetime('now') WHERE id = ?",
|
|
386
|
+
).run(newCount, newErrorCount, newTotalTime, hookId);
|
|
387
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instinct Manager — learns user preferences from agent interactions.
|
|
3
|
+
* Tracks patterns like preferred tools, coding style, response format, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Ensure instincts table exists.
|
|
8
|
+
*/
|
|
9
|
+
export function ensureInstinctsTable(db) {
|
|
10
|
+
db.exec(`
|
|
11
|
+
CREATE TABLE IF NOT EXISTS instincts (
|
|
12
|
+
id TEXT PRIMARY KEY,
|
|
13
|
+
category TEXT NOT NULL,
|
|
14
|
+
pattern TEXT NOT NULL,
|
|
15
|
+
confidence REAL DEFAULT 0.5,
|
|
16
|
+
occurrences INTEGER DEFAULT 1,
|
|
17
|
+
last_seen TEXT DEFAULT (datetime('now')),
|
|
18
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
19
|
+
)
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function generateId() {
|
|
24
|
+
const hex = () =>
|
|
25
|
+
Math.floor(Math.random() * 0x10000)
|
|
26
|
+
.toString(16)
|
|
27
|
+
.padStart(4, "0");
|
|
28
|
+
return `${hex()}${hex()}-${hex()}-${hex()}-${hex()}-${hex()}${hex()}${hex()}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Instinct categories.
|
|
33
|
+
*/
|
|
34
|
+
export const INSTINCT_CATEGORIES = {
|
|
35
|
+
TOOL_PREFERENCE: "tool_preference",
|
|
36
|
+
CODING_STYLE: "coding_style",
|
|
37
|
+
RESPONSE_FORMAT: "response_format",
|
|
38
|
+
LANGUAGE: "language",
|
|
39
|
+
WORKFLOW: "workflow",
|
|
40
|
+
BEHAVIOR: "behavior",
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Record an instinct observation.
|
|
45
|
+
* If an instinct with the same category+pattern exists, increment its confidence and occurrences.
|
|
46
|
+
*/
|
|
47
|
+
export function recordInstinct(db, category, pattern) {
|
|
48
|
+
ensureInstinctsTable(db);
|
|
49
|
+
|
|
50
|
+
// Check if exists
|
|
51
|
+
const existing = db
|
|
52
|
+
.prepare("SELECT * FROM instincts WHERE category = ? AND pattern = ?")
|
|
53
|
+
.get(category, pattern);
|
|
54
|
+
|
|
55
|
+
if (existing) {
|
|
56
|
+
// Boost confidence (asymptotic approach to 1.0)
|
|
57
|
+
const newConfidence = Math.min(
|
|
58
|
+
0.99,
|
|
59
|
+
existing.confidence + (1 - existing.confidence) * 0.1,
|
|
60
|
+
);
|
|
61
|
+
db.prepare(
|
|
62
|
+
"UPDATE instincts SET confidence = ?, occurrences = occurrences + 1, last_seen = datetime('now') WHERE id = ?",
|
|
63
|
+
).run(newConfidence, existing.id);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
id: existing.id,
|
|
67
|
+
category,
|
|
68
|
+
pattern,
|
|
69
|
+
confidence: newConfidence,
|
|
70
|
+
occurrences: (existing.occurrences || 1) + 1,
|
|
71
|
+
isNew: false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Create new instinct
|
|
76
|
+
const id = generateId();
|
|
77
|
+
db.prepare(
|
|
78
|
+
"INSERT INTO instincts (id, category, pattern, confidence, occurrences) VALUES (?, ?, ?, ?, ?)",
|
|
79
|
+
).run(id, category, pattern, 0.5, 1);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
id,
|
|
83
|
+
category,
|
|
84
|
+
pattern,
|
|
85
|
+
confidence: 0.5,
|
|
86
|
+
occurrences: 1,
|
|
87
|
+
isNew: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get all instincts, optionally filtered by category.
|
|
93
|
+
*/
|
|
94
|
+
export function getInstincts(db, options = {}) {
|
|
95
|
+
ensureInstinctsTable(db);
|
|
96
|
+
|
|
97
|
+
let sql = "SELECT * FROM instincts";
|
|
98
|
+
const params = [];
|
|
99
|
+
|
|
100
|
+
if (options.category) {
|
|
101
|
+
sql += " WHERE category = ?";
|
|
102
|
+
params.push(options.category);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
sql += " ORDER BY confidence DESC";
|
|
106
|
+
|
|
107
|
+
if (options.limit) {
|
|
108
|
+
sql += " LIMIT ?";
|
|
109
|
+
params.push(options.limit);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return db.prepare(sql).all(...params);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get top instincts (confidence >= threshold).
|
|
117
|
+
*/
|
|
118
|
+
export function getStrongInstincts(db, threshold = 0.7) {
|
|
119
|
+
ensureInstinctsTable(db);
|
|
120
|
+
return db
|
|
121
|
+
.prepare(
|
|
122
|
+
"SELECT * FROM instincts WHERE confidence >= ? ORDER BY confidence DESC",
|
|
123
|
+
)
|
|
124
|
+
.all(threshold);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Delete an instinct by ID or prefix.
|
|
129
|
+
*/
|
|
130
|
+
export function deleteInstinct(db, id) {
|
|
131
|
+
ensureInstinctsTable(db);
|
|
132
|
+
const result = db
|
|
133
|
+
.prepare("DELETE FROM instincts WHERE id LIKE ?")
|
|
134
|
+
.run(`${id}%`);
|
|
135
|
+
return result.changes > 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Reset all instincts (clear the table).
|
|
140
|
+
*/
|
|
141
|
+
export function resetInstincts(db) {
|
|
142
|
+
ensureInstinctsTable(db);
|
|
143
|
+
const result = db.prepare("DELETE FROM instincts WHERE 1=1").run();
|
|
144
|
+
return result.changes;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Decay instincts that haven't been seen recently.
|
|
149
|
+
* Reduces confidence of old instincts over time.
|
|
150
|
+
*/
|
|
151
|
+
export function decayInstincts(db, daysThreshold = 30) {
|
|
152
|
+
ensureInstinctsTable(db);
|
|
153
|
+
// Simple decay: multiply confidence by 0.9 for old instincts
|
|
154
|
+
const rows = db.prepare("SELECT * FROM instincts").all();
|
|
155
|
+
let decayed = 0;
|
|
156
|
+
|
|
157
|
+
const cutoff = new Date();
|
|
158
|
+
cutoff.setDate(cutoff.getDate() - daysThreshold);
|
|
159
|
+
const cutoffStr = cutoff.toISOString().replace("T", " ").slice(0, 19);
|
|
160
|
+
|
|
161
|
+
for (const row of rows) {
|
|
162
|
+
if (row.last_seen && row.last_seen < cutoffStr) {
|
|
163
|
+
const newConfidence = Math.max(0.1, (row.confidence || 0.5) * 0.9);
|
|
164
|
+
db.prepare("UPDATE instincts SET confidence = ? WHERE id = ?").run(
|
|
165
|
+
newConfidence,
|
|
166
|
+
row.id,
|
|
167
|
+
);
|
|
168
|
+
decayed++;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return decayed;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Generate a system prompt fragment from strong instincts.
|
|
177
|
+
*/
|
|
178
|
+
export function generateInstinctPrompt(db) {
|
|
179
|
+
const strong = getStrongInstincts(db, 0.6);
|
|
180
|
+
if (strong.length === 0) return "";
|
|
181
|
+
|
|
182
|
+
const lines = ["Based on learned preferences:"];
|
|
183
|
+
for (const inst of strong) {
|
|
184
|
+
lines.push(
|
|
185
|
+
`- [${inst.category}] ${inst.pattern} (confidence: ${(inst.confidence * 100).toFixed(0)}%)`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return lines.join("\n");
|
|
190
|
+
}
|