@lucieri/daxiom 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 +122 -0
- package/bin/daxiom.js +4 -0
- package/package.json +47 -0
- package/src/agents.js +100 -0
- package/src/cli.js +423 -0
- package/src/connection.js +43 -0
- package/src/constants.js +91 -0
- package/src/embeddings.js +52 -0
- package/src/hooks/env.js +52 -0
- package/src/hooks/index.js +59 -0
- package/src/hooks/post-task.js +186 -0
- package/src/hooks/pre-compact.js +128 -0
- package/src/hooks/pre-task.js +97 -0
- package/src/hooks/session-start.js +108 -0
- package/src/hooks/summarize.js +141 -0
- package/src/index.js +67 -0
- package/src/patterns.js +339 -0
- package/src/quality-gates.js +183 -0
- package/src/self-manage.js +105 -0
- package/src/tiers.js +305 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { init, close, getPool } = require('./connection');
|
|
4
|
+
const { getEmbedding } = require('./embeddings');
|
|
5
|
+
const { checkCoherence, runQualityGates } = require('./quality-gates');
|
|
6
|
+
const { searchPatterns, storeLearnedPattern, getStats } = require('./patterns');
|
|
7
|
+
const {
|
|
8
|
+
updateTiers, getConfidenceTierDistribution, promotePatternConfidence,
|
|
9
|
+
demoteWithRollback, scanRollbackCandidates, decayPatterns, getDriftReport, previewDecay,
|
|
10
|
+
} = require('./tiers');
|
|
11
|
+
const { storeSelfManaged, listByClass, updateStatus } = require('./self-manage');
|
|
12
|
+
const { getAgentProfile, routeToAgent } = require('./agents');
|
|
13
|
+
const { SELF_MGMT_CLASSES } = require('./constants');
|
|
14
|
+
|
|
15
|
+
const { loadEnv } = require('./hooks/env');
|
|
16
|
+
loadEnv();
|
|
17
|
+
|
|
18
|
+
const args = process.argv.slice(2);
|
|
19
|
+
const cmd = args[0];
|
|
20
|
+
|
|
21
|
+
// Hooks subcommand dispatches separately (has its own init flow)
|
|
22
|
+
if (cmd === 'hooks') {
|
|
23
|
+
const { dispatch } = require('./hooks');
|
|
24
|
+
dispatch(args.slice(1)).catch(err => {
|
|
25
|
+
console.error('Hook error:', err.message);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
});
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
(async () => {
|
|
32
|
+
try {
|
|
33
|
+
await init();
|
|
34
|
+
|
|
35
|
+
switch (cmd) {
|
|
36
|
+
case 'search': {
|
|
37
|
+
const query = args.slice(1).join(' ') || 'ruvbot plugin lifecycle';
|
|
38
|
+
console.log(`Searching: "${query}"`);
|
|
39
|
+
const results = await searchPatterns(query, { limit: 5 });
|
|
40
|
+
for (const r of results) {
|
|
41
|
+
console.log(` ${r.similarity} | ${r.namespace.padEnd(12)}| ${r.patternName}`);
|
|
42
|
+
}
|
|
43
|
+
if (results.length === 0) console.log(' No results above threshold');
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
case 'stats': {
|
|
48
|
+
const stats = await getStats();
|
|
49
|
+
console.log('DAXIOM Pattern Stats:');
|
|
50
|
+
for (const row of stats) {
|
|
51
|
+
console.log(` ${row.namespace}/${row.tier}: ${row.count} patterns, avg_conf=${row.avg_confidence}, usage=${row.total_usage}`);
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
case 'tiers': {
|
|
57
|
+
const result = await updateTiers();
|
|
58
|
+
console.log('Tier updates:', result);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
case 'store': {
|
|
63
|
+
const skipCoherence = args.includes('--skip-coherence');
|
|
64
|
+
const agentIdCli = args.includes('--agent-id') ? args[args.indexOf('--agent-id') + 1] : null;
|
|
65
|
+
const agentTypeCli = args.includes('--agent-type') ? args[args.indexOf('--agent-type') + 1] : null;
|
|
66
|
+
const filteredArgs = args.filter((a, i, arr) =>
|
|
67
|
+
a !== '--skip-coherence' &&
|
|
68
|
+
a !== '--agent-id' && a !== '--agent-type' &&
|
|
69
|
+
!(i > 0 && (arr[i-1] === '--agent-id' || arr[i-1] === '--agent-type'))
|
|
70
|
+
);
|
|
71
|
+
const name = filteredArgs[1] || 'test_pattern';
|
|
72
|
+
const desc = filteredArgs.slice(2).join(' ') || 'Test pattern from CLI';
|
|
73
|
+
const result = await storeLearnedPattern({
|
|
74
|
+
patternName: name, description: desc, compactText: desc,
|
|
75
|
+
skipCoherence, agentId: agentIdCli, agentType: agentTypeCli,
|
|
76
|
+
});
|
|
77
|
+
if (result.action === 'rejected') {
|
|
78
|
+
console.log('REJECTED:', result.reason);
|
|
79
|
+
} else {
|
|
80
|
+
console.log('Stored:', result);
|
|
81
|
+
}
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
case 'todo': {
|
|
86
|
+
const name = args[1];
|
|
87
|
+
// If no name or starts with --, list TODOs instead
|
|
88
|
+
if (!name || name.startsWith('--')) {
|
|
89
|
+
const ns = args.includes('--namespace') ? args[args.indexOf('--namespace') + 1] : null;
|
|
90
|
+
const st = args.includes('--status') ? args[args.indexOf('--status') + 1] : 'open';
|
|
91
|
+
const rows = await listByClass('todo', { namespace: ns, status: st });
|
|
92
|
+
if (rows.length === 0) { console.log(' No TODOs found' + (ns ? ` for ${ns}` : '') + ` (status=${st})`); break; }
|
|
93
|
+
console.log(`TODOs${ns ? ` [${ns}]` : ''} (${rows.length}, status=${st}):`);
|
|
94
|
+
for (const r of rows) {
|
|
95
|
+
const prio = r.metadata?.priority ? `[${r.metadata.priority}]` : '';
|
|
96
|
+
const date = r.created_at ? new Date(r.created_at).toISOString().slice(0, 10) : '';
|
|
97
|
+
console.log(` ${date} ${r.namespace.padEnd(14)} ${prio.padEnd(8)} ${r.pattern_name}`);
|
|
98
|
+
}
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
const flagIdx = args.findIndex(a => a.startsWith('--'));
|
|
102
|
+
const descParts = args.slice(2, flagIdx > 1 ? flagIdx : undefined);
|
|
103
|
+
const desc = descParts.join(' ') || name;
|
|
104
|
+
const repo = args.includes('--repo') ? args[args.indexOf('--repo') + 1] : null;
|
|
105
|
+
const priority = args.includes('--priority') ? args[args.indexOf('--priority') + 1] : 'medium';
|
|
106
|
+
const ns = args.includes('--namespace') ? args[args.indexOf('--namespace') + 1] : 'daxiom';
|
|
107
|
+
const result = await storeSelfManaged({
|
|
108
|
+
patternClass: 'todo', name, description: desc, namespace: ns,
|
|
109
|
+
tags: ['todo'], status: 'open', repo, priority,
|
|
110
|
+
});
|
|
111
|
+
console.log(`TODO ${result.action}: ${name} → ${ns} (${result.id})`);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
case 'changelog': {
|
|
116
|
+
const name = args[1];
|
|
117
|
+
// If no name or name starts with --, list changelogs instead
|
|
118
|
+
if (!name || name.startsWith('--')) {
|
|
119
|
+
const ns = args.includes('--namespace') ? args[args.indexOf('--namespace') + 1] : null;
|
|
120
|
+
const lim = args.includes('--limit') ? parseInt(args[args.indexOf('--limit') + 1]) : 20;
|
|
121
|
+
const rows = await listByClass('changelog', { namespace: ns, limit: lim });
|
|
122
|
+
if (rows.length === 0) { console.log(' No changelogs found' + (ns ? ` for ${ns}` : '')); break; }
|
|
123
|
+
console.log(`CHANGELOGS${ns ? ` [${ns}]` : ''} (${rows.length}):`);
|
|
124
|
+
for (const r of rows) {
|
|
125
|
+
const repo = r.metadata?.repo ? `(${r.metadata.repo})` : '';
|
|
126
|
+
const date = r.created_at ? new Date(r.created_at).toISOString().slice(0, 10) : '';
|
|
127
|
+
console.log(` ${date} ${r.namespace.padEnd(14)} ${r.pattern_name} ${repo}`);
|
|
128
|
+
}
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
131
|
+
const desc = args.slice(2).filter(a => !a.startsWith('--')).join(' ') || name;
|
|
132
|
+
const repo = args.includes('--repo') ? args[args.indexOf('--repo') + 1] : null;
|
|
133
|
+
const ns = args.includes('--namespace') ? args[args.indexOf('--namespace') + 1] : 'daxiom';
|
|
134
|
+
const sessionId = args.includes('--session') ? args[args.indexOf('--session') + 1] : null;
|
|
135
|
+
const meta = { stored_at: new Date().toISOString() };
|
|
136
|
+
if (repo) meta.repo = repo;
|
|
137
|
+
if (sessionId) meta.session_id = sessionId;
|
|
138
|
+
const result = await storeSelfManaged({
|
|
139
|
+
patternClass: 'changelog', name, description: desc, namespace: ns,
|
|
140
|
+
tags: ['changelog', new Date().toISOString().slice(0, 10)], repo,
|
|
141
|
+
});
|
|
142
|
+
console.log(`Changelog ${result.action}: ${name} → ${ns} (${result.id})`);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
case 'plan': {
|
|
147
|
+
const name = args[1];
|
|
148
|
+
if (!name) { console.log('Usage: daxiom plan <name> <description> [--repo X] [--namespace X]'); break; }
|
|
149
|
+
const desc = args.slice(2).filter(a => !a.startsWith('--')).join(' ') || name;
|
|
150
|
+
const repo = args.includes('--repo') ? args[args.indexOf('--repo') + 1] : null;
|
|
151
|
+
const ns = args.includes('--namespace') ? args[args.indexOf('--namespace') + 1] : 'daxiom';
|
|
152
|
+
const result = await storeSelfManaged({
|
|
153
|
+
patternClass: 'build-plan', name, description: desc, namespace: ns,
|
|
154
|
+
tags: ['build-plan'], status: 'active', repo,
|
|
155
|
+
});
|
|
156
|
+
console.log(`Plan ${result.action}: ${name} → ${ns} (${result.id})`);
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
case 'decide': {
|
|
161
|
+
const name = args[1];
|
|
162
|
+
if (!name) { console.log('Usage: daxiom decide <name> <rationale> [--repo X] [--namespace X]'); break; }
|
|
163
|
+
const desc = args.slice(2).filter(a => !a.startsWith('--')).join(' ') || name;
|
|
164
|
+
const repo = args.includes('--repo') ? args[args.indexOf('--repo') + 1] : null;
|
|
165
|
+
const ns = args.includes('--namespace') ? args[args.indexOf('--namespace') + 1] : 'daxiom';
|
|
166
|
+
const result = await storeSelfManaged({
|
|
167
|
+
patternClass: 'decision', name, description: desc, namespace: ns,
|
|
168
|
+
tags: ['decision', 'adr'], repo,
|
|
169
|
+
});
|
|
170
|
+
console.log(`Decision ${result.action}: ${name} → ${ns} (${result.id})`);
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
case 'incident': {
|
|
175
|
+
const name = args[1];
|
|
176
|
+
if (!name) { console.log('Usage: daxiom incident <name> <description> [--repo X] [--namespace X]'); break; }
|
|
177
|
+
const desc = args.slice(2).filter(a => !a.startsWith('--')).join(' ') || name;
|
|
178
|
+
const repo = args.includes('--repo') ? args[args.indexOf('--repo') + 1] : null;
|
|
179
|
+
const ns = args.includes('--namespace') ? args[args.indexOf('--namespace') + 1] : 'daxiom';
|
|
180
|
+
const result = await storeSelfManaged({
|
|
181
|
+
patternClass: 'incident', name, description: desc, namespace: ns,
|
|
182
|
+
tags: ['incident', 'postmortem'], status: 'resolved', repo,
|
|
183
|
+
});
|
|
184
|
+
console.log(`Incident ${result.action}: ${name} → ${ns} (${result.id})`);
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
case 'milestone': {
|
|
189
|
+
const name = args[1];
|
|
190
|
+
if (!name) { console.log('Usage: daxiom milestone <name> <description> [--repo X] [--namespace X]'); break; }
|
|
191
|
+
const desc = args.slice(2).filter(a => !a.startsWith('--')).join(' ') || name;
|
|
192
|
+
const repo = args.includes('--repo') ? args[args.indexOf('--repo') + 1] : null;
|
|
193
|
+
const ns = args.includes('--namespace') ? args[args.indexOf('--namespace') + 1] : 'daxiom';
|
|
194
|
+
const result = await storeSelfManaged({
|
|
195
|
+
patternClass: 'milestone', name, description: desc, namespace: ns,
|
|
196
|
+
tags: ['milestone'], repo,
|
|
197
|
+
});
|
|
198
|
+
console.log(`Milestone ${result.action}: ${name} → ${ns} (${result.id})`);
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
case 'namespaces': {
|
|
203
|
+
const p = getPool();
|
|
204
|
+
const client = await p.connect();
|
|
205
|
+
try {
|
|
206
|
+
const res = await client.query(`
|
|
207
|
+
SELECT namespace,
|
|
208
|
+
COUNT(*) as total,
|
|
209
|
+
COUNT(*) FILTER (WHERE pattern_class = 'changelog') as changelogs,
|
|
210
|
+
COUNT(*) FILTER (WHERE pattern_class = 'todo') as todos,
|
|
211
|
+
COUNT(*) FILTER (WHERE pattern_class = 'decision') as decisions,
|
|
212
|
+
COUNT(*) FILTER (WHERE pattern_class = 'milestone') as milestones,
|
|
213
|
+
COUNT(*) FILTER (WHERE pattern_class = 'incident') as incidents,
|
|
214
|
+
COUNT(*) FILTER (WHERE pattern_class = 'learned') as learned
|
|
215
|
+
FROM tribal_intelligence.patterns
|
|
216
|
+
GROUP BY namespace ORDER BY total DESC
|
|
217
|
+
`);
|
|
218
|
+
console.log('NAMESPACE REGISTRY:');
|
|
219
|
+
console.log(' ' + 'Namespace'.padEnd(18) + 'Total'.padStart(6) + ' CLogs TODOs Decs Miles Incid Learn');
|
|
220
|
+
console.log(' ' + '-'.repeat(80));
|
|
221
|
+
for (const r of res.rows) {
|
|
222
|
+
console.log(
|
|
223
|
+
' ' + r.namespace.padEnd(18)
|
|
224
|
+
+ String(r.total).padStart(6)
|
|
225
|
+
+ String(r.changelogs).padStart(7)
|
|
226
|
+
+ String(r.todos).padStart(7)
|
|
227
|
+
+ String(r.decisions).padStart(6)
|
|
228
|
+
+ String(r.milestones).padStart(7)
|
|
229
|
+
+ String(r.incidents).padStart(7)
|
|
230
|
+
+ String(r.learned).padStart(7)
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
} finally { client.release(); }
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
case 'list': {
|
|
238
|
+
const cls = args[1];
|
|
239
|
+
if (!cls) {
|
|
240
|
+
console.log('Usage: daxiom list <pattern_class> [--namespace X] [--status X]');
|
|
241
|
+
console.log('Classes: ' + SELF_MGMT_CLASSES.join(', ') + ', learned, or any pattern_class');
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
const ns = args.includes('--namespace') ? args[args.indexOf('--namespace') + 1] : null;
|
|
245
|
+
const st = args.includes('--status') ? args[args.indexOf('--status') + 1] : null;
|
|
246
|
+
const rows = await listByClass(cls, { namespace: ns, status: st });
|
|
247
|
+
if (rows.length === 0) { console.log(` No ${cls} patterns found`); break; }
|
|
248
|
+
console.log(`${cls.toUpperCase()} patterns (${rows.length}):`);
|
|
249
|
+
for (const r of rows) {
|
|
250
|
+
const status = r.metadata?.status ? `[${r.metadata.status}]` : '';
|
|
251
|
+
const repo = r.metadata?.repo ? `(${r.metadata.repo})` : '';
|
|
252
|
+
const date = r.created_at ? new Date(r.created_at).toISOString().slice(0, 10) : '';
|
|
253
|
+
console.log(` ${date} ${status.padEnd(10)} ${r.pattern_name} ${repo}`);
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
case 'done': {
|
|
259
|
+
const pid = args[1];
|
|
260
|
+
if (!pid) { console.log('Usage: daxiom done <pattern_id_or_uuid>'); break; }
|
|
261
|
+
const result = await updateStatus(pid, 'done');
|
|
262
|
+
if (result) { console.log(`Marked done: ${result.pattern_name}`); }
|
|
263
|
+
else { console.log(`Pattern not found: ${pid}`); }
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
case 'coherence': {
|
|
268
|
+
const text = args.slice(1).join(' ');
|
|
269
|
+
if (!text) { console.log('Usage: daxiom coherence <text>'); break; }
|
|
270
|
+
console.log(`Checking coherence for: "${text}"`);
|
|
271
|
+
const p = getPool();
|
|
272
|
+
const client = await p.connect();
|
|
273
|
+
try {
|
|
274
|
+
await client.query('SET jit = off');
|
|
275
|
+
const emb = await getEmbedding(text);
|
|
276
|
+
const vec = '[' + emb.join(',') + ']';
|
|
277
|
+
const gate = await checkCoherence(client, vec, 'learned', 'learned');
|
|
278
|
+
console.log(`Action: ${gate.action.toUpperCase()}`);
|
|
279
|
+
if (gate.reason) console.log(`Reason: ${gate.reason}`);
|
|
280
|
+
console.log('Nearest neighbors:');
|
|
281
|
+
for (const n of gate.neighbors) {
|
|
282
|
+
console.log(` ${n.similarity} | ${n.namespace.padEnd(12)}| ${n.pattern_class.padEnd(12)}| ${n.pattern_name}`);
|
|
283
|
+
}
|
|
284
|
+
} finally { client.release(); }
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
case 'tier-check': {
|
|
289
|
+
const dist = await getConfidenceTierDistribution();
|
|
290
|
+
console.log('CONFIDENCE TIER DISTRIBUTION:');
|
|
291
|
+
console.log(` Platinum (>=0.95): ${dist.platinum || 0} patterns`);
|
|
292
|
+
console.log(` Gold (>=0.85): ${dist.gold || 0} patterns`);
|
|
293
|
+
console.log(` Silver (>=0.75): ${dist.silver || 0} patterns`);
|
|
294
|
+
console.log(` Bronze (>=0.70): ${dist.bronze || 0} patterns`);
|
|
295
|
+
console.log(` Expired (<0.70): ${dist.expired || 0} patterns (need revalidation)`);
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
case 'tier-promote': {
|
|
300
|
+
const pid = args[1];
|
|
301
|
+
if (!pid) { console.log('Usage: daxiom tier-promote <pattern_id_or_uuid>'); break; }
|
|
302
|
+
const result = await promotePatternConfidence(pid);
|
|
303
|
+
if (result.success) {
|
|
304
|
+
console.log(`Promoted: ${result.patternName} → ${result.newConfidence.toFixed(2)} (${result.tier})`);
|
|
305
|
+
} else { console.log(`Error: ${result.error}`); }
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
case 'agent-profile': {
|
|
310
|
+
const aid = args[1];
|
|
311
|
+
if (!aid) { console.log('Usage: daxiom agent-profile <agent_id>'); break; }
|
|
312
|
+
const profile = await getAgentProfile(aid);
|
|
313
|
+
if (!profile) { console.log(`No patterns found for agent: ${aid}`); break; }
|
|
314
|
+
console.log(`AGENT PROFILE: ${profile.agentId}`);
|
|
315
|
+
for (const p of profile.profiles) {
|
|
316
|
+
console.log(` Type: ${p.agentType || 'unknown'} | Patterns: ${p.patternsCreated} | Conf: ${p.avgConfidence.toFixed(4)} | Success: ${p.avgSuccessRate.toFixed(4)}`);
|
|
317
|
+
}
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
case 'agent-route': {
|
|
322
|
+
const taskDesc = args.slice(1).join(' ');
|
|
323
|
+
if (!taskDesc) { console.log('Usage: daxiom agent-route <task description>'); break; }
|
|
324
|
+
console.log(`Routing task: "${taskDesc}"`);
|
|
325
|
+
const agents = await routeToAgent(taskDesc);
|
|
326
|
+
if (agents.length === 0) { console.log(' No agents with attribution data found.'); break; }
|
|
327
|
+
for (let i = 0; i < agents.length; i++) {
|
|
328
|
+
const a = agents[i];
|
|
329
|
+
console.log(` #${i + 1} ${a.agentId} (${a.agentType || 'unknown'}) score=${a.score.toFixed(4)}`);
|
|
330
|
+
}
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
case 'decay': {
|
|
335
|
+
const baseRate = args.includes('--rate') ? parseFloat(args[args.indexOf('--rate') + 1]) : 0.02;
|
|
336
|
+
const minIdle = args.includes('--min-idle') ? parseFloat(args[args.indexOf('--min-idle') + 1]) : 1.0;
|
|
337
|
+
const floor = args.includes('--floor') ? parseFloat(args[args.indexOf('--floor') + 1]) : 0.0;
|
|
338
|
+
const result = await decayPatterns({ baseRate, minIdleDays: minIdle, floor });
|
|
339
|
+
console.log(`Decayed: ${result.patternsDecayed} | Avg conf: ${result.avgNewConfidence.toFixed(4)} | Tier changes: ${result.tierChanges}`);
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
case 'drift': {
|
|
344
|
+
const limit = args.includes('--limit') ? parseInt(args[args.indexOf('--limit') + 1]) : 20;
|
|
345
|
+
const minIdle = args.includes('--min-idle') ? parseFloat(args[args.indexOf('--min-idle') + 1]) : 1.0;
|
|
346
|
+
const report = await getDriftReport({ limit, minIdleDays: minIdle });
|
|
347
|
+
if (report.length === 0) { console.log(' No drifting patterns found'); break; }
|
|
348
|
+
for (const r of report) {
|
|
349
|
+
console.log(` drift=${r.driftScore.toFixed(4)} | conf=${r.confidence.toFixed(2)} | ${r.tier} | ${r.namespace}:${r.patternName}`);
|
|
350
|
+
}
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
case 'decay-preview': {
|
|
355
|
+
const pid = args[1];
|
|
356
|
+
if (!pid) { console.log('Usage: daxiom decay-preview <pattern_id_or_uuid>'); break; }
|
|
357
|
+
const preview = await previewDecay(pid);
|
|
358
|
+
if (!preview) { console.log(`Pattern not found: ${pid}`); break; }
|
|
359
|
+
console.log(`${preview.patternName}: ${preview.currentConfidence.toFixed(4)} → ${preview.decayedConfidence.toFixed(4)} (${preview.currentTier} → ${preview.projectedTier})`);
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
case 'demote': {
|
|
364
|
+
const pid = args[1];
|
|
365
|
+
if (!pid) { console.log('Usage: daxiom demote <pattern_id> [--amount 0.1] [--threshold 0.3] [--reason "failure"]'); break; }
|
|
366
|
+
const amount = args.includes('--amount') ? parseFloat(args[args.indexOf('--amount') + 1]) : 0.1;
|
|
367
|
+
const threshold = args.includes('--threshold') ? parseFloat(args[args.indexOf('--threshold') + 1]) : 0.3;
|
|
368
|
+
const reason = args.includes('--reason') ? args[args.indexOf('--reason') + 1] : 'failure';
|
|
369
|
+
const result = await demoteWithRollback(pid, { amount, threshold, reason });
|
|
370
|
+
if (!result.success) { console.log(`Error: ${result.error}`); break; }
|
|
371
|
+
console.log(`Demoted: ${result.patternName} ${result.oldConfidence.toFixed(2)} → ${result.newConfidence.toFixed(2)} (${result.tier})`);
|
|
372
|
+
if (result.rollbackTriggered) console.log(` ROLLBACK TRIGGERED: archived`);
|
|
373
|
+
break;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
case 'rollback-check': {
|
|
377
|
+
const threshold = args.includes('--threshold') ? parseFloat(args[args.indexOf('--threshold') + 1]) : 0.3;
|
|
378
|
+
const candidates = await scanRollbackCandidates({ threshold });
|
|
379
|
+
if (candidates.length === 0) { console.log(' No patterns below rollback threshold'); break; }
|
|
380
|
+
for (const c of candidates) {
|
|
381
|
+
console.log(` conf=${c.confidence.toFixed(2)} | ${c.tier} | ${c.namespace}:${c.patternName}${c.archived ? ' [ARCHIVED]' : ''}`);
|
|
382
|
+
}
|
|
383
|
+
break;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
case 'quality-check': {
|
|
387
|
+
const content = args.slice(1).join(' ');
|
|
388
|
+
if (!content) { console.log('Usage: daxiom quality-check <content> [--class X] [--namespace X]'); break; }
|
|
389
|
+
const cls = args.includes('--class') ? args[args.indexOf('--class') + 1] : 'learned';
|
|
390
|
+
const ns = args.includes('--namespace') ? args[args.indexOf('--namespace') + 1] : 'learned';
|
|
391
|
+
const filtered = content.replace(/--class\s+\S+/g, '').replace(/--namespace\s+\S+/g, '').trim();
|
|
392
|
+
const result = runQualityGates(filtered, ns, cls, [], {});
|
|
393
|
+
console.log(`${result.allowed ? 'ALLOWED' : 'BLOCKED'}: ${result.summary}`);
|
|
394
|
+
for (const r of result.results) {
|
|
395
|
+
console.log(` [${r.level.toUpperCase()}] ${r.gate}: ${r.reason}`);
|
|
396
|
+
}
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
default:
|
|
401
|
+
console.log('daxiom — Self-Learning Second Brain SDK v0.2.0');
|
|
402
|
+
console.log('');
|
|
403
|
+
console.log('Knowledge: search | store | coherence | quality-check | stats');
|
|
404
|
+
console.log('Tiers: tiers | tier-check | tier-promote');
|
|
405
|
+
console.log('Agents: agent-profile | agent-route');
|
|
406
|
+
console.log('Self-mgmt: todo | plan | changelog | decide | incident | milestone | list | done');
|
|
407
|
+
console.log('Registry: namespaces');
|
|
408
|
+
console.log('Rollback: demote | rollback-check');
|
|
409
|
+
console.log('Decay: decay | drift | decay-preview');
|
|
410
|
+
console.log('Hooks: hooks pre-task | hooks post-task | hooks session-start | hooks pre-compact | hooks summarize');
|
|
411
|
+
console.log('');
|
|
412
|
+
console.log('All self-mgmt commands accept --namespace <ns> to target a specific repo.');
|
|
413
|
+
console.log('todo/changelog with no args list entries. Use --namespace to filter.');
|
|
414
|
+
console.log('Hooks auto-load env from ~/.daxiom/.env if DATABASE_URL not set.');
|
|
415
|
+
console.log('');
|
|
416
|
+
console.log('Namespaces: daxiom, daxbot, daxiom-app, daxiom-db, ruvector, claude-flow');
|
|
417
|
+
}
|
|
418
|
+
} catch (err) {
|
|
419
|
+
console.error('Error:', err.message);
|
|
420
|
+
} finally {
|
|
421
|
+
await close();
|
|
422
|
+
}
|
|
423
|
+
})();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Pool } = require('pg');
|
|
4
|
+
|
|
5
|
+
let pool = null;
|
|
6
|
+
|
|
7
|
+
function getPool() {
|
|
8
|
+
if (pool) return pool;
|
|
9
|
+
|
|
10
|
+
const url = process.env.DAXIOM_DATABASE_URL || process.env.DATABASE_URL;
|
|
11
|
+
if (!url) throw new Error('DAXIOM_DATABASE_URL or DATABASE_URL required');
|
|
12
|
+
|
|
13
|
+
const dbUrl = new URL(url);
|
|
14
|
+
pool = new Pool({
|
|
15
|
+
host: dbUrl.hostname,
|
|
16
|
+
port: parseInt(dbUrl.port) || 5432,
|
|
17
|
+
database: dbUrl.pathname.slice(1),
|
|
18
|
+
user: dbUrl.username,
|
|
19
|
+
password: decodeURIComponent(dbUrl.password),
|
|
20
|
+
max: 3,
|
|
21
|
+
idleTimeoutMillis: 30000,
|
|
22
|
+
ssl: false,
|
|
23
|
+
});
|
|
24
|
+
pool.on('error', (err) => console.error('[DAXIOM Pool]', err.message));
|
|
25
|
+
return pool;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function init() {
|
|
29
|
+
const p = getPool();
|
|
30
|
+
const client = await p.connect();
|
|
31
|
+
await client.query('SELECT 1');
|
|
32
|
+
client.release();
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function close() {
|
|
37
|
+
if (pool) {
|
|
38
|
+
await pool.end();
|
|
39
|
+
pool = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { getPool, init, close };
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const EMBEDDING_DIM = 1536;
|
|
4
|
+
const RATE_LIMIT_MS = 160;
|
|
5
|
+
const DEFAULT_SEARCH_LIMIT = 5;
|
|
6
|
+
const DEFAULT_THRESHOLD = 0.3;
|
|
7
|
+
|
|
8
|
+
// Coherence gate thresholds (hallucination prevention)
|
|
9
|
+
const COHERENCE_DUPLICATE_THRESHOLD = 0.95;
|
|
10
|
+
const COHERENCE_CONTRADICTION_THRESHOLD = 0.85;
|
|
11
|
+
|
|
12
|
+
// Confidence tier system (4-tier evolutionary scoring)
|
|
13
|
+
const CONFIDENCE_TIERS = {
|
|
14
|
+
PLATINUM: { min: 0.95, label: 'platinum', action: 'auto-apply' },
|
|
15
|
+
GOLD: { min: 0.85, label: 'gold', action: 'auto-apply-with-commit' },
|
|
16
|
+
SILVER: { min: 0.75, label: 'silver', action: 'suggest-only' },
|
|
17
|
+
BRONZE: { min: 0.70, label: 'bronze', action: 'learning-only' },
|
|
18
|
+
};
|
|
19
|
+
const CONFIDENCE_EXPIRED_THRESHOLD = 0.70;
|
|
20
|
+
const CONFIDENCE_SUCCESS_DELTA = 0.05;
|
|
21
|
+
const CONFIDENCE_FAILURE_DELTA = -0.10;
|
|
22
|
+
|
|
23
|
+
// Quality gate constants
|
|
24
|
+
const KNOWN_CLASSES = [
|
|
25
|
+
'learned', 'api', 'architecture', 'cli', 'plugin', 'security',
|
|
26
|
+
'build-plan', 'build-plan-item', 'changelog', 'todo', 'decision',
|
|
27
|
+
'incident', 'milestone', 'code', 'config', 'error-fix',
|
|
28
|
+
'tool', 'guidance', 'interface', 'algorithm', 'integration',
|
|
29
|
+
'ui', 'adapter', 'skill', 'session-context', 'session-summary',
|
|
30
|
+
'wire_integrate', 'general',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const KNOWN_NAMESPACES = [
|
|
34
|
+
// Core DAXIOM ecosystem
|
|
35
|
+
'daxiom', // SDK npm package
|
|
36
|
+
'daxbot', // AI assistant npm package (was ruvbot)
|
|
37
|
+
'daxiom-app', // MakerKit Next.js frontend
|
|
38
|
+
'daxiom-db', // DigitalOcean PostgreSQL+RuVector instance
|
|
39
|
+
// Upstream / tooling
|
|
40
|
+
'ruvector', // Rust PG extension source repo
|
|
41
|
+
'claude-flow', // Agent orchestration tool
|
|
42
|
+
// Legacy (kept for backward compat with existing patterns)
|
|
43
|
+
'ruvbot', // Deprecated — use daxbot
|
|
44
|
+
'makerkit', // Deprecated — use daxiom-app
|
|
45
|
+
// Cross-cutting
|
|
46
|
+
'learned', // Cross-repo learnings
|
|
47
|
+
'session-memory', // Session context patterns
|
|
48
|
+
// Plugins / ecosystem
|
|
49
|
+
'forge', 'vibe-cast', 'agentic-qe',
|
|
50
|
+
'plugin-ecosystem', 'plugin-prime-radiant', 'gastown',
|
|
51
|
+
'plugin-code-intelligence', 'plugin-test-intelligence',
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const SECURITY_PATTERNS = [
|
|
55
|
+
/(?:sk-[a-zA-Z0-9]{20,})/,
|
|
56
|
+
/(?:AKIA[0-9A-Z]{16})/,
|
|
57
|
+
/(?:ghp_[a-zA-Z0-9]{36})/,
|
|
58
|
+
/(?:postgresql:\/\/[^:]+:[^@]+@)/,
|
|
59
|
+
/(?:password|secret|token)\s*[:=]\s*['"][^'"]{8,}/i,
|
|
60
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/,
|
|
61
|
+
/\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/,
|
|
62
|
+
/\b\d{3}[-]?\d{2}[-]?\d{4}\b/,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const CONTENT_MIN_LENGTH = 20;
|
|
66
|
+
const CONTENT_MAX_LENGTH = 10000;
|
|
67
|
+
|
|
68
|
+
const SELF_MGMT_CLASSES = ['build-plan', 'changelog', 'todo', 'decision', 'incident', 'milestone'];
|
|
69
|
+
|
|
70
|
+
// Alias for backward compat with hooks (they reference KNOWN_CONTEXTS)
|
|
71
|
+
const KNOWN_CONTEXTS = KNOWN_NAMESPACES;
|
|
72
|
+
|
|
73
|
+
module.exports = {
|
|
74
|
+
EMBEDDING_DIM,
|
|
75
|
+
RATE_LIMIT_MS,
|
|
76
|
+
DEFAULT_SEARCH_LIMIT,
|
|
77
|
+
DEFAULT_THRESHOLD,
|
|
78
|
+
COHERENCE_DUPLICATE_THRESHOLD,
|
|
79
|
+
COHERENCE_CONTRADICTION_THRESHOLD,
|
|
80
|
+
CONFIDENCE_TIERS,
|
|
81
|
+
CONFIDENCE_EXPIRED_THRESHOLD,
|
|
82
|
+
CONFIDENCE_SUCCESS_DELTA,
|
|
83
|
+
CONFIDENCE_FAILURE_DELTA,
|
|
84
|
+
KNOWN_CLASSES,
|
|
85
|
+
KNOWN_NAMESPACES,
|
|
86
|
+
KNOWN_CONTEXTS,
|
|
87
|
+
SECURITY_PATTERNS,
|
|
88
|
+
CONTENT_MIN_LENGTH,
|
|
89
|
+
CONTENT_MAX_LENGTH,
|
|
90
|
+
SELF_MGMT_CLASSES,
|
|
91
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const https = require('https');
|
|
4
|
+
const { EMBEDDING_DIM } = require('./constants');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate embedding via Gemini embedding-001 API.
|
|
8
|
+
* Truncates input to 2000 chars, returns float array at EMBEDDING_DIM dimensions.
|
|
9
|
+
*/
|
|
10
|
+
async function getEmbedding(text) {
|
|
11
|
+
const key = process.env.GEMINI_API_KEY || process.env.DAXIOM_GEMINI_KEY;
|
|
12
|
+
if (!key) throw new Error('GEMINI_API_KEY or DAXIOM_GEMINI_KEY required');
|
|
13
|
+
|
|
14
|
+
const truncated = text.substring(0, 2000);
|
|
15
|
+
const body = JSON.stringify({
|
|
16
|
+
model: 'models/gemini-embedding-001',
|
|
17
|
+
content: { parts: [{ text: truncated }] },
|
|
18
|
+
outputDimensionality: EMBEDDING_DIM,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const req = https.request({
|
|
23
|
+
hostname: 'generativelanguage.googleapis.com',
|
|
24
|
+
path: `/v1beta/models/gemini-embedding-001:embedContent?key=${key}`,
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
'Content-Length': Buffer.byteLength(body),
|
|
29
|
+
},
|
|
30
|
+
}, (res) => {
|
|
31
|
+
const chunks = [];
|
|
32
|
+
res.on('data', (c) => chunks.push(c));
|
|
33
|
+
res.on('end', () => {
|
|
34
|
+
try {
|
|
35
|
+
const data = JSON.parse(Buffer.concat(chunks).toString());
|
|
36
|
+
if (data?.embedding?.values) {
|
|
37
|
+
resolve(data.embedding.values);
|
|
38
|
+
} else {
|
|
39
|
+
reject(new Error(`Gemini error: ${JSON.stringify(data?.error || data)}`));
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {
|
|
42
|
+
reject(new Error(`Parse error: ${e.message}`));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
req.on('error', reject);
|
|
47
|
+
req.write(body);
|
|
48
|
+
req.end();
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { getEmbedding };
|
package/src/hooks/env.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Load DAXIOM environment variables from standard locations.
|
|
9
|
+
* Priority: existing env > ~/.daxiom/.env > project .env
|
|
10
|
+
*
|
|
11
|
+
* Only loads DATABASE_URL and GEMINI_API_KEY if not already set.
|
|
12
|
+
*/
|
|
13
|
+
function loadEnv() {
|
|
14
|
+
// Already have what we need
|
|
15
|
+
if (process.env.DATABASE_URL || process.env.DAXIOM_DATABASE_URL) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const candidates = [
|
|
20
|
+
path.join(os.homedir(), '.daxiom', '.env'),
|
|
21
|
+
path.join(os.homedir(), '.daxiom', 'env'),
|
|
22
|
+
path.join(process.cwd(), '.env'),
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
for (const envPath of candidates) {
|
|
26
|
+
if (!fs.existsSync(envPath)) continue;
|
|
27
|
+
try {
|
|
28
|
+
const lines = fs.readFileSync(envPath, 'utf8').split('\n');
|
|
29
|
+
for (const line of lines) {
|
|
30
|
+
const trimmed = line.trim();
|
|
31
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
32
|
+
const eqIdx = trimmed.indexOf('=');
|
|
33
|
+
if (eqIdx < 1) continue;
|
|
34
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
35
|
+
let val = trimmed.slice(eqIdx + 1).trim();
|
|
36
|
+
// Strip surrounding quotes
|
|
37
|
+
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
|
38
|
+
val = val.slice(1, -1);
|
|
39
|
+
}
|
|
40
|
+
// Only set if not already in env
|
|
41
|
+
if (!process.env[key]) {
|
|
42
|
+
process.env[key] = val;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
break; // Stop at first found file
|
|
46
|
+
} catch {
|
|
47
|
+
// Skip unreadable files
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { loadEnv };
|