OverviewFilesCommitsIssuesDocsReleasesPeers

godot-keet commits / 6718b8d

Add extractor tests + daemon: orphan rooms as solo clusters
CI copies updated keet-daemon which now includes orphan rooms (no MI pairs) as their own single-room clusters instead of filtering them out. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6718b8d3dd22f7ede14a49fc0f736cc398c9007a
by plankatron <fleeky@gmail.com> · 2026-02-23T13:43:39.000Z

Changed files (2) +176 -0

tests/debug-extractors.mjs +101 -0
……@@ -0,0 +1,101 @@
1/**
2 * debug-extractors.mjs — Call graph.compute with debug logging for each algo
3 * Tests what the daemon actually returns for each extractor type
4 */
5import Pipe from 'bare-pipe'
6import FramedStream from 'framed-stream'
7import b4a from 'b4a'
8
9let rpcId = 0
10const pipe = new Pipe('/tmp/keet-daemon.sock')
11const framed = new FramedStream(pipe)
12const pending = new Map()
13
14framed.on('data', buf => {
15 const msg = JSON.parse(b4a.toString(buf))
16 const cb = pending.get(msg.id)
17 if (cb) { pending.delete(msg.id); cb(msg) }
18})
19
20function call (method, params = {}) {
21 return new Promise((resolve, reject) => {
22 const id = ++rpcId
23 const timeout = setTimeout(() => { pending.delete(id); reject(new Error('timeout: ' + method)) }, 30000)
24 pending.set(id, msg => {
25 clearTimeout(timeout)
26 if (msg.ok !== undefined && msg.data !== undefined) msg.result = msg.data
27 if (msg.error) reject(new Error(msg.error.message || String(msg.error)))
28 else resolve(msg.result)
29 })
30 framed.write(b4a.from(JSON.stringify({ jsonrpc: '2.0', id, method, params })))
31 })
32}
33
34// Get rooms first
35const rooms = await call('rooms')
36console.log(`Total rooms: ${rooms.length}\n`)
37
38// Test each room for the raw data that extractors need
39console.log('=== RAW MEMBER DATA (what creator/size extractors see) ===\n')
40for (const r of rooms.slice(0, 5)) {
41 const rid = r.roomId || r.id
42 const title = r.title || '(untitled)'
43 try {
44 const members = await call('members', { roomId: rid })
45 const list = Array.isArray(members) ? members : []
46 const admin = list.find(m => m.role === 'admin')
47 const adminRaw = list.find(m => m.role === 1)
48 console.log(`${title}: ${list.length} members, admin(string)=${admin?.memberId?.slice(0,12)||'none'}, admin(num)=${adminRaw?.memberId?.slice(0,12)||'none'}`)
49 } catch (err) {
50 console.log(`${title}: ERROR — ${err.message}`)
51 }
52}
53
54console.log('\n=== RAW MESSAGE DATA (what activity extractor sees) ===\n')
55for (const r of rooms.slice(0, 5)) {
56 const rid = r.roomId || r.id
57 const title = r.title || '(untitled)'
58 // Try 'read' instead of 'messages'
59 try {
60 const msgs = await call('read', { roomId: rid, limit: 1 })
61 const list = Array.isArray(msgs) ? msgs : (msgs?.messages || msgs?.data || [])
62 if (list.length > 0) {
63 const m = list[0]
64 console.log(`${title}: ${list.length} msg, ts=${m.timestamp||m.ts||'none'}, keys=${Object.keys(m).slice(0,8).join(',')}`)
65 } else {
66 console.log(`${title}: 0 messages`)
67 }
68 } catch (err) {
69 console.log(`${title}: ERROR — ${err.message}`)
70 }
71}
72
73// Now test graph.compute with each algo, checking full result
74console.log('\n=== GRAPH.COMPUTE RESULTS ===\n')
75const algos = [
76 {name: 'creator', features: [{type: 'creator', weight: 1.0}], threshold: 0.1, minSize: 2},
77 {name: 'activity', features: [{type: 'activity', weight: 1.0}], threshold: 0.1, minSize: 2},
78 {name: 'size', features: [{type: 'size', weight: 1.0}], threshold: 0.1, minSize: 2},
79]
80
81for (const algo of algos) {
82 try {
83 const result = await call('graph.compute', { force: true, algo })
84 const c = result.clusters || []
85 const e = result.edges || []
86 console.log(`${algo.name}: ${c.length} clusters, ${e.length} edges`)
87 if (c.length > 0) {
88 for (const cl of c) {
89 console.log(` cluster: label="${cl.label}", rooms=${(cl.rooms||[]).length}`)
90 }
91 }
92 if (e.length > 0) {
93 console.log(` top edge: ${JSON.stringify(e[0])}`)
94 }
95 } catch (err) {
96 console.log(`${algo.name}: ERROR — ${err.message}`)
97 }
98}
99
100pipe.destroy()
101setTimeout(() => Bare.exit(0), 500)
tests/test-extractors.mjs +75 -0
……@@ -0,0 +1,75 @@
1/**
2 * test-extractors.mjs — Debug which keet-core APIs work for graph extractors
3 */
4import Pipe from 'bare-pipe'
5import FramedStream from 'framed-stream'
6import b4a from 'b4a'
7
8let rpcId = 0
9const pipe = new Pipe('/tmp/keet-daemon.sock')
10const framed = new FramedStream(pipe)
11const pending = new Map()
12
13framed.on('data', buf => {
14 const msg = JSON.parse(b4a.toString(buf))
15 const cb = pending.get(msg.id)
16 if (cb) { pending.delete(msg.id); cb(msg) }
17})
18
19function call (method, params = {}) {
20 return new Promise((resolve, reject) => {
21 const id = ++rpcId
22 const timeout = setTimeout(() => { pending.delete(id); reject(new Error('timeout: ' + method)) }, 10000)
23 pending.set(id, msg => {
24 clearTimeout(timeout)
25 if (msg.ok !== undefined && msg.data !== undefined) msg.result = msg.data
26 if (msg.error) reject(new Error(msg.error.message || String(msg.error)))
27 else resolve(msg.result)
28 })
29 framed.write(b4a.from(JSON.stringify({ jsonrpc: '2.0', id, method, params })))
30 })
31}
32
33const rooms = await call('rooms')
34console.log(`Total rooms: ${rooms.length}\n`)
35
36// Test first 3 non-DM rooms
37let tested = 0
38for (const r of rooms) {
39 if (tested >= 3) break
40 const rid = r.roomId || r.id
41 const title = r.title || '(untitled)'
42
43 console.log(`--- ${title} (${rid.slice(0, 12)}...) ---`)
44
45 // Test members
46 try {
47 const members = await call('members', { roomId: rid })
48 const list = Array.isArray(members) ? members : (members?.members || members?.data || [])
49 console.log(` members: ${list.length}`)
50 if (list.length > 0) {
51 const m = list[0]
52 console.log(` first: ${JSON.stringify({id: m.id, memberId: m.memberId, role: m.role, status: m.status}).slice(0, 120)}`)
53 }
54 } catch (err) {
55 console.log(` members: ERROR — ${err.message}`)
56 }
57
58 // Test chat messages
59 try {
60 const msgs = await call('messages', { roomId: rid, limit: 1 })
61 const list = Array.isArray(msgs) ? msgs : (msgs?.messages || msgs?.data || [])
62 console.log(` messages: ${list.length}`)
63 if (list.length > 0) {
64 const msg = list[0]
65 console.log(` first: ts=${msg.timestamp || msg.ts || 'none'}, keys=${Object.keys(msg).join(',')}`)
66 }
67 } catch (err) {
68 console.log(` messages: ERROR — ${err.message}`)
69 }
70
71 tested++
72}
73
74pipe.destroy()
75setTimeout(() => Bare.exit(0), 500)