@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.
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+
3
+ const { loadEnv } = require('./env');
4
+
5
+ /**
6
+ * Hooks CLI dispatcher.
7
+ * Usage: daxiom hooks <subcommand> [args...]
8
+ *
9
+ * Subcommands:
10
+ * pre-task - Search DAXIOM for relevant patterns before agent task
11
+ * post-task - Store learnings after agent task completion
12
+ * session-start - Inject SOUL.md + DAXIOM context on session start
13
+ * pre-compact - Capture transcript before context compaction
14
+ * summarize - Process and embed daily session logs
15
+ */
16
+ async function dispatch(args) {
17
+ // Load env before any hook runs
18
+ loadEnv();
19
+
20
+ const subcommand = args[0];
21
+ const subArgs = args.slice(1);
22
+
23
+ switch (subcommand) {
24
+ case 'pre-task': {
25
+ const { run } = require('./pre-task');
26
+ return run(subArgs);
27
+ }
28
+ case 'post-task': {
29
+ const { run } = require('./post-task');
30
+ return run(subArgs);
31
+ }
32
+ case 'session-start': {
33
+ const { run } = require('./session-start');
34
+ return run(subArgs);
35
+ }
36
+ case 'pre-compact': {
37
+ const { run } = require('./pre-compact');
38
+ return run(subArgs);
39
+ }
40
+ case 'summarize': {
41
+ const { run } = require('./summarize');
42
+ return run(subArgs);
43
+ }
44
+ default:
45
+ console.log('daxiom hooks — Claude Code hook handlers');
46
+ console.log('');
47
+ console.log('Subcommands:');
48
+ console.log(' pre-task Search DAXIOM before agent task');
49
+ console.log(' post-task Store learnings after agent task');
50
+ console.log(' session-start Inject context on session start');
51
+ console.log(' pre-compact Capture transcript before compaction');
52
+ console.log(' summarize Process daily session logs');
53
+ console.log('');
54
+ console.log('Env: DAXIOM_DATABASE_URL or DATABASE_URL, GEMINI_API_KEY');
55
+ console.log('Auto-loads from ~/.daxiom/.env if env vars not set.');
56
+ }
57
+ }
58
+
59
+ module.exports = { dispatch };
@@ -0,0 +1,186 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const { init, close, getPool, storeLearnedPattern, storeSelfManaged, updateTiers, KNOWN_CONTEXTS } = require('../index');
5
+
6
+ /**
7
+ * Infer context tags from text by scanning for known context/namespace names.
8
+ */
9
+ function inferContexts(text) {
10
+ if (!text || !KNOWN_CONTEXTS) return [];
11
+ const lower = text.toLowerCase();
12
+ return KNOWN_CONTEXTS.filter(ctx => lower.includes(ctx));
13
+ }
14
+
15
+ function parseArgs(args) {
16
+ const parsed = {};
17
+ for (let i = 0; i < args.length; i++) {
18
+ if (args[i].startsWith('--') && i + 1 < args.length) {
19
+ const key = args[i].slice(2);
20
+ parsed[key] = args[i + 1];
21
+ i++;
22
+ }
23
+ }
24
+ return parsed;
25
+ }
26
+
27
+ /**
28
+ * Post-task hook: store learnings in DAXIOM after agent task completion.
29
+ *
30
+ * @param {string[]} args - CLI args after "hooks post-task"
31
+ */
32
+ async function run(args) {
33
+ const parsed = parseArgs(args);
34
+
35
+ if (!parsed.task) {
36
+ console.error('Usage: daxiom hooks post-task --task "description" --success true [--topology mesh] [--agents 5] [--agent-type Y]');
37
+ process.exit(1);
38
+ }
39
+
40
+ const success = parsed.success !== 'false';
41
+ const topology = parsed.topology || 'unknown';
42
+ const agentCount = parseInt(parsed.agents) || 0;
43
+ const filesModified = parseInt(parsed.files) || 0;
44
+ const sparcPhases = parsed.sparc || '';
45
+ const taskType = parsed.type || 'general';
46
+
47
+ const explicitContexts = parsed.context ? parsed.context.split(',').map(c => c.trim()) : [];
48
+ const inferredContexts = inferContexts(parsed.task);
49
+ const contexts = [...new Set([...explicitContexts, ...inferredContexts])];
50
+
51
+ const agentId = parsed['agent-id'] || parsed.agentId
52
+ || process.env.CLAUDE_FLOW_AGENT_ID || process.env.AGENT_ID || null;
53
+ const agentType = parsed['agent-type'] || parsed.agentType
54
+ || process.env.CLAUDE_FLOW_AGENT_TYPE || process.env.AGENT_TYPE || null;
55
+
56
+ try {
57
+ await init();
58
+
59
+ const patternName = parsed.task
60
+ .toLowerCase().replace(/[^a-z0-9\s]/g, '').trim()
61
+ .split(/\s+/).slice(0, 6).join('_');
62
+
63
+ const compactText = [
64
+ `Task: ${parsed.task}.`,
65
+ `Outcome: ${success ? 'success' : 'failure'}.`,
66
+ topology !== 'unknown' ? `Topology: ${topology}.` : '',
67
+ agentCount > 0 ? `Agents: ${agentCount}.` : '',
68
+ filesModified > 0 ? `Files modified: ${filesModified}.` : '',
69
+ sparcPhases ? `SPARC phases: ${sparcPhases}.` : '',
70
+ ].filter(Boolean).join(' ');
71
+
72
+ const result = await storeLearnedPattern({
73
+ patternName: `task_${patternName}`,
74
+ description: parsed.task,
75
+ compactText,
76
+ patternClass: taskType,
77
+ namespace: 'learned',
78
+ category: 'agent-task',
79
+ tier: 'warm',
80
+ tags: [
81
+ success ? 'success' : 'failure',
82
+ topology, taskType, ...contexts,
83
+ ].filter(t => t && t !== 'unknown'),
84
+ metadata: {
85
+ success, topology, agentCount, filesModified,
86
+ sparcPhases, taskType, contexts,
87
+ completedAt: new Date().toISOString(),
88
+ },
89
+ agentId, agentType,
90
+ });
91
+
92
+ console.log(JSON.stringify({ stored: true, ...result, patternName: `task_${patternName}`, success }));
93
+
94
+ // Auto-changelog for successful tasks
95
+ if (success) {
96
+ const today = new Date().toISOString().slice(0, 10);
97
+ const changelogName = `${today}-${patternName}`;
98
+ const repo = contexts[0] || path.basename(process.cwd()) || 'claude-flow';
99
+ try {
100
+ await storeSelfManaged({
101
+ patternClass: 'changelog', name: changelogName,
102
+ description: `[auto] ${parsed.task}`, namespace: repo,
103
+ tags: [taskType, success ? 'success' : 'failure', ...contexts],
104
+ repo, skipCoherence: true,
105
+ });
106
+ console.error(`[DAXIOM] Auto-changelog: ${changelogName}`);
107
+ } catch (clErr) {
108
+ if (!clErr.message.includes('duplicate')) {
109
+ console.error(`[DAXIOM] Changelog error: ${clErr.message}`);
110
+ }
111
+ }
112
+ }
113
+
114
+ // Auto-close matching TODO
115
+ if (success && parsed.task) {
116
+ const p = getPool();
117
+ const client = await p.connect();
118
+ try {
119
+ const todoName = parsed.task.toLowerCase().replace(/[^a-z0-9\s-]/g, '').trim().split(/\s+/).slice(0, 8).join('-');
120
+ const closed = await client.query(`
121
+ UPDATE tribal_intelligence.patterns
122
+ SET tier = 'cold',
123
+ metadata = jsonb_set(COALESCE(metadata, '{}'), '{status}', '"done"'),
124
+ updated_at = NOW()
125
+ WHERE pattern_class = 'todo'
126
+ AND metadata->>'status' = 'open'
127
+ AND (pattern_name = $1 OR pattern_name LIKE $2)
128
+ RETURNING pattern_name
129
+ `, [todoName, `wire-${todoName}%`]);
130
+ if (closed.rowCount > 0) {
131
+ console.error(`[DAXIOM] Auto-closed TODOs: ${closed.rows.map(r => r.pattern_name).join(', ')}`);
132
+ }
133
+ } catch (todoErr) {
134
+ console.error(`[DAXIOM] TODO close error: ${todoErr.message}`);
135
+ } finally {
136
+ client.release();
137
+ }
138
+ }
139
+
140
+ // Auto-bump build-plan-item
141
+ if (success && parsed.task) {
142
+ const p = getPool();
143
+ const client = await p.connect();
144
+ try {
145
+ const taskLower = parsed.task.toLowerCase();
146
+ const wireMatch = taskLower.match(/wire[d]?\s+([a-z0-9-]+)/);
147
+ if (wireMatch) {
148
+ const improvementName = wireMatch[1];
149
+ const bumped = await client.query(`
150
+ UPDATE tribal_intelligence.patterns
151
+ SET confidence = LEAST(1.0, confidence + 0.1),
152
+ usage_count = usage_count + 1,
153
+ success_count = success_count + 1,
154
+ metadata = jsonb_set(COALESCE(metadata, '{}'), '{last_wired}', to_jsonb(NOW()::text)),
155
+ updated_at = NOW()
156
+ WHERE pattern_class = 'build-plan-item'
157
+ AND pattern_name = $1
158
+ RETURNING pattern_name, round(confidence::numeric, 2) as conf
159
+ `, [improvementName]);
160
+ if (bumped.rowCount > 0) {
161
+ console.error(`[DAXIOM] Auto-bumped: ${bumped.rows[0].pattern_name} → ${bumped.rows[0].conf}`);
162
+ }
163
+ }
164
+ } catch (bpErr) {
165
+ console.error(`[DAXIOM] Build-plan bump error: ${bpErr.message}`);
166
+ } finally {
167
+ client.release();
168
+ }
169
+ }
170
+
171
+ // Periodic tier updates (1 in 10 chance)
172
+ if (Math.random() < 0.1) {
173
+ const tierResult = await updateTiers();
174
+ if (tierResult.promoted + tierResult.demotedToWarm + tierResult.demotedToCold > 0) {
175
+ console.error(`[DAXIOM] Tier updates: ${JSON.stringify(tierResult)}`);
176
+ }
177
+ }
178
+ } catch (err) {
179
+ console.error('DAXIOM post-task error:', err.message);
180
+ console.log(JSON.stringify({ stored: false, error: err.message }));
181
+ } finally {
182
+ await close();
183
+ }
184
+ }
185
+
186
+ module.exports = { run };
@@ -0,0 +1,128 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const MEMORY_DIR = path.join(os.homedir(), '.claude', 'memory');
8
+ const TRANSCRIPT_LINES = 60;
9
+ const TRANSCRIPT_MAX_CHARS = 3000;
10
+ const ALLOWED_PREFIXES = [os.homedir(), '/tmp', '/private/tmp'];
11
+
12
+ function isPathAllowed(p) {
13
+ return ALLOWED_PREFIXES.some(prefix => p.startsWith(prefix));
14
+ }
15
+
16
+ function ensureDir(dir) {
17
+ if (!fs.existsSync(dir)) {
18
+ fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
19
+ }
20
+ }
21
+
22
+ function todayLogPath() {
23
+ const d = new Date();
24
+ const name = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}.md`;
25
+ return path.join(MEMORY_DIR, name);
26
+ }
27
+
28
+ function timestamp() {
29
+ return new Date().toISOString().replace('T', ' ').replace(/\.\d+Z$/, '');
30
+ }
31
+
32
+ function extractTranscript(transcriptPath) {
33
+ if (!transcriptPath || !fs.existsSync(transcriptPath)) return null;
34
+ if (!isPathAllowed(transcriptPath)) return '[transcript path outside allowed directories]';
35
+ try {
36
+ const raw = fs.readFileSync(transcriptPath, 'utf8');
37
+ const lines = raw.trim().split('\n').filter(Boolean);
38
+ const recent = lines.slice(-TRANSCRIPT_LINES);
39
+ const texts = [];
40
+ for (const line of recent) {
41
+ try {
42
+ const obj = JSON.parse(line);
43
+ if (obj.role === 'assistant' && Array.isArray(obj.content)) {
44
+ for (const block of obj.content) {
45
+ if (block.type === 'text' && block.text) texts.push(block.text);
46
+ }
47
+ } else if (obj.role === 'assistant' && typeof obj.content === 'string') {
48
+ texts.push(obj.content);
49
+ }
50
+ } catch { /* skip malformed */ }
51
+ }
52
+ return texts.join('\n').slice(-TRANSCRIPT_MAX_CHARS) || null;
53
+ } catch { return null; }
54
+ }
55
+
56
+ async function embedToDaxiom(context, trigger) {
57
+ try {
58
+ const { init, close, storeLearnedPattern, KNOWN_CONTEXTS } = require('../index');
59
+ await init();
60
+ const lower = context.toLowerCase();
61
+ const detectedContexts = (KNOWN_CONTEXTS || []).filter(ctx => lower.includes(ctx));
62
+ const result = await storeLearnedPattern({
63
+ patternName: `session_${Date.now()}`,
64
+ description: `Session context captured before ${trigger} compaction`,
65
+ compactText: context.substring(0, 2000),
66
+ patternClass: 'session-context',
67
+ namespace: 'session-memory',
68
+ category: 'pre-compaction',
69
+ tier: 'hot',
70
+ tags: ['session', 'compaction', trigger, ...detectedContexts],
71
+ metadata: {
72
+ trigger, contexts: detectedContexts,
73
+ capturedAt: new Date().toISOString(),
74
+ charCount: context.length,
75
+ },
76
+ });
77
+ await close();
78
+ return result;
79
+ } catch { return null; }
80
+ }
81
+
82
+ /**
83
+ * Pre-compact hook: capture transcript context before compaction.
84
+ * Reads stdin JSON from Claude Code: { "trigger": "auto"|"manual", "transcript_path": "..." }
85
+ * Saves to local daily log + embeds to DAXIOM.
86
+ *
87
+ * @param {string[]} args - unused (stdin-driven)
88
+ */
89
+ async function run(args) {
90
+ let input = {};
91
+ try {
92
+ const stdin = fs.readFileSync(0, 'utf8').trim();
93
+ if (stdin) input = JSON.parse(stdin);
94
+ } catch { /* No stdin */ }
95
+
96
+ const trigger = input.trigger || 'unknown';
97
+ const transcriptPath = input.transcript_path || null;
98
+
99
+ const context = extractTranscript(transcriptPath);
100
+ if (!context) {
101
+ process.stdout.write(JSON.stringify({ saved: false, reason: 'no transcript content' }));
102
+ return;
103
+ }
104
+
105
+ ensureDir(MEMORY_DIR);
106
+ const logPath = todayLogPath();
107
+ const isNew = !fs.existsSync(logPath);
108
+
109
+ const entry = [
110
+ isNew ? `# Session Log - ${new Date().toISOString().split('T')[0]}\n` : '',
111
+ `## Pre-Compaction Capture (${trigger})`,
112
+ `**Time:** ${timestamp()}`,
113
+ `**Trigger:** ${trigger}`,
114
+ '', '### Recent Context', '```', context, '```', '', '---', '',
115
+ ].join('\n');
116
+
117
+ fs.appendFileSync(logPath, entry, { mode: 0o600 });
118
+
119
+ const daxiomResult = await embedToDaxiom(context, trigger);
120
+
121
+ process.stdout.write(JSON.stringify({
122
+ saved: true, localLog: logPath,
123
+ charsCaptured: context.length, trigger,
124
+ daxiom: daxiomResult ? { id: daxiomResult.id, action: daxiomResult.action } : null,
125
+ }));
126
+ }
127
+
128
+ module.exports = { run };
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const { init, close, searchWithSalience, recordUsage } = require('../index');
4
+
5
+ /**
6
+ * Pre-task hook: search DAXIOM for patterns relevant to the incoming task.
7
+ * Outputs JSON context that agents can use for informed decision-making.
8
+ *
9
+ * @param {string[]} args - CLI args after "hooks pre-task"
10
+ */
11
+ async function run(args) {
12
+ // Parse: everything not a flag is the task description
13
+ const flagArgs = {};
14
+ const descParts = [];
15
+ for (let i = 0; i < args.length; i++) {
16
+ if (args[i] === '--agent-type' && i + 1 < args.length) {
17
+ flagArgs.agentType = args[++i];
18
+ } else if (args[i] === '--limit' && i + 1 < args.length) {
19
+ flagArgs.limit = parseInt(args[++i]);
20
+ } else if (args[i] === '--threshold' && i + 1 < args.length) {
21
+ flagArgs.threshold = parseFloat(args[++i]);
22
+ } else if (!args[i].startsWith('--')) {
23
+ descParts.push(args[i]);
24
+ }
25
+ }
26
+
27
+ const taskDescription = descParts.join(' ');
28
+ if (!taskDescription) {
29
+ console.log(JSON.stringify({ daxiom_intel: false, message: 'No task description provided' }));
30
+ return;
31
+ }
32
+
33
+ try {
34
+ await init();
35
+
36
+ const results = await searchWithSalience(taskDescription, {
37
+ limit: flagArgs.limit || 8,
38
+ threshold: flagArgs.threshold || 0.5,
39
+ decay: 0.03,
40
+ candidatePool: 30,
41
+ });
42
+
43
+ if (results.length === 0) {
44
+ console.log(JSON.stringify({
45
+ daxiom_intel: false,
46
+ message: 'No matching patterns in DAXIOM',
47
+ task: taskDescription,
48
+ }));
49
+ return;
50
+ }
51
+
52
+ // Group by namespace
53
+ const grouped = {};
54
+ for (const r of results) {
55
+ if (!grouped[r.namespace]) grouped[r.namespace] = [];
56
+ grouped[r.namespace].push({
57
+ name: r.patternName,
58
+ similarity: r.similarity,
59
+ salience: r.salience,
60
+ class: r.patternClass,
61
+ tier: r.tier,
62
+ confidence: r.confidence,
63
+ description: r.description.substring(0, 200),
64
+ tags: r.tags || [],
65
+ });
66
+ }
67
+
68
+ const output = {
69
+ daxiom_intel: true,
70
+ ranking: 'salience',
71
+ patterns_found: results.length,
72
+ avg_similarity: parseFloat((results.reduce((s, r) => s + r.similarity, 0) / results.length).toFixed(3)),
73
+ top_match: {
74
+ name: results[0].patternName,
75
+ namespace: results[0].namespace,
76
+ similarity: results[0].similarity,
77
+ salience: results[0].salience,
78
+ },
79
+ by_namespace: grouped,
80
+ task: taskDescription,
81
+ };
82
+
83
+ console.log(JSON.stringify(output, null, 2));
84
+
85
+ // Record usage for top 3 patterns
86
+ for (const r of results.slice(0, 3)) {
87
+ try { await recordUsage(r.id, true); } catch { /* non-fatal */ }
88
+ }
89
+ } catch (err) {
90
+ console.error('DAXIOM pre-task error:', err.message);
91
+ console.log(JSON.stringify({ daxiom_intel: false, error: err.message }));
92
+ } finally {
93
+ await close();
94
+ }
95
+ }
96
+
97
+ module.exports = { run };
@@ -0,0 +1,108 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ const SOUL_FILE = path.join(os.homedir(), '.claude', 'SOUL.md');
8
+ const MEMORY_DIR = path.join(os.homedir(), '.claude', 'memory');
9
+ const SOUL_LINES = 60;
10
+ const LOG_LINES = 40;
11
+ const DAXIOM_PATTERNS = 3;
12
+
13
+ function readSoul() {
14
+ if (!fs.existsSync(SOUL_FILE)) return null;
15
+ try {
16
+ const lines = fs.readFileSync(SOUL_FILE, 'utf8').split('\n');
17
+ return lines.slice(0, SOUL_LINES).join('\n');
18
+ } catch { return null; }
19
+ }
20
+
21
+ function readRecentLog() {
22
+ if (!fs.existsSync(MEMORY_DIR)) return null;
23
+ try {
24
+ const files = fs.readdirSync(MEMORY_DIR)
25
+ .filter(f => f.endsWith('.md')).sort().reverse();
26
+ if (files.length === 0) return null;
27
+ const latest = path.join(MEMORY_DIR, files[0]);
28
+ const lines = fs.readFileSync(latest, 'utf8').split('\n');
29
+ const tail = lines.slice(-LOG_LINES).join('\n');
30
+ return { name: files[0], content: tail };
31
+ } catch { return null; }
32
+ }
33
+
34
+ async function queryDaxiom(source) {
35
+ try {
36
+ const { init, close, searchPatterns } = require('../index');
37
+ await init();
38
+ const results = await searchPatterns(
39
+ 'recent session context decisions and working state',
40
+ { limit: DAXIOM_PATTERNS, threshold: 0.3, namespace: 'session-memory' }
41
+ );
42
+ await close();
43
+ if (results.length === 0) return null;
44
+ return results.map(r => ({
45
+ description: r.description,
46
+ metadata: r.metadata,
47
+ similarity: r.similarity,
48
+ }));
49
+ } catch { return null; }
50
+ }
51
+
52
+ /**
53
+ * Session-start hook: inject SOUL.md + recent log + DAXIOM context.
54
+ * Reads stdin JSON from Claude Code: { "source": "startup"|"resume"|"clear"|"compact" }
55
+ * Outputs JSON: { "additionalContext": "..." }
56
+ *
57
+ * @param {string[]} args - unused (stdin-driven)
58
+ */
59
+ async function run(args) {
60
+ let input = {};
61
+ try {
62
+ const stdin = fs.readFileSync(0, 'utf8').trim();
63
+ if (stdin) input = JSON.parse(stdin);
64
+ } catch { /* No stdin */ }
65
+
66
+ const source = input.source || 'startup';
67
+ const parts = [];
68
+
69
+ const soul = readSoul();
70
+ if (soul) {
71
+ parts.push(`SOUL IDENTITY (from ${path.basename(SOUL_FILE)}):\n${soul}\n[Full: ${SOUL_FILE}]`);
72
+ }
73
+
74
+ const log = readRecentLog();
75
+ if (log) {
76
+ parts.push(`RECENT SESSION LOG (${log.name}):\n${log.content}`);
77
+ }
78
+
79
+ if (source === 'compact' || source === 'startup') {
80
+ const daxiomContext = await queryDaxiom(source);
81
+ if (daxiomContext && daxiomContext.length > 0) {
82
+ const daxiomText = daxiomContext.map((p, i) => {
83
+ const meta = p.metadata || {};
84
+ const captured = meta.capturedAt ? ` (captured: ${meta.capturedAt})` : '';
85
+ return `${i + 1}. ${p.description}${captured}`;
86
+ }).join('\n');
87
+ parts.push(`DAXIOM SESSION MEMORY (${daxiomContext.length} patterns):\n${daxiomText}`);
88
+ }
89
+ }
90
+
91
+ if (source === 'compact') {
92
+ parts.push(
93
+ 'NOTE: Context was just compacted. The above is restored from SOUL.md, ' +
94
+ 'local session logs, and DAXIOM semantic memory. For full project context, ' +
95
+ 'read CLAUDE.md and relevant source files.'
96
+ );
97
+ }
98
+
99
+ if (parts.length === 0) {
100
+ process.stdout.write('{}');
101
+ return;
102
+ }
103
+
104
+ const context = parts.join('\n\n---\n\n');
105
+ process.stdout.write(JSON.stringify({ additionalContext: context }));
106
+ }
107
+
108
+ module.exports = { run };