#!/usr/bin/env node
/**
* classify-files.js — Add language, domain, and role metadata to all loop-files
*
* Disambiguates the 467 "unknown" role files by examining extensions and paths.
* Stores classification as Values on file atoms in AtomSpace.
*/
const { evalScheme, isCogServerUp } = require('./cogserver')
function classifyFile (f) {
// Language from extension
let lang = 'unknown'
if (/\.(cpp|cc|h|hpp)$/.test(f)) lang = 'cpp'
else if (/\.gd$/.test(f)) lang = 'gdscript'
else if (/\.(glsl|gdshader|shader)$/.test(f)) lang = 'glsl'
else if (/\.py$/.test(f)) lang = 'python'
else if (/\.js$/.test(f)) lang = 'javascript'
else if (/\.json$/.test(f)) lang = 'json'
else if (/\.md$/.test(f)) lang = 'markdown'
else if (/\.scm$/.test(f)) lang = 'scheme'
else if (/\.(toml|yaml|yml)$/.test(f)) lang = 'config'
else if (/\.(tscn|tres)$/.test(f)) lang = 'godot-scene'
// Domain from path
let domain = 'unknown'
if (f.includes('/code/godot/')) domain = 'godot'
else if (f.includes('/memory/') || f.includes('MEMORY.md')) domain = 'claude-memory'
else if (f.includes('.claude/')) domain = 'claude-config'
else if (f.includes('knowledge-ratchet')) domain = 'ratchet'
else if (f.includes('hyper-fleeky/') || /^(pear-git|hypernote|hyper-|wirecore|codetree|trust-map)/.test(f)) domain = 'p2p-stack'
// Role from path patterns
let role = 'unknown'
if (/\/test\/|\.test\./.test(f)) role = 'test'
else if (f.includes('/src/')) role = 'src'
else if (f.includes('/lib/')) role = 'lib'
else if (f.includes('/bin/')) role = 'bin'
else if (/\/examples?\//.test(f)) role = 'example'
else if (f.includes('/docs/') || f.endsWith('.md')) role = 'docs'
else if (f.includes('/scenes/')) role = 'scene'
else if (f.includes('/shaders/') || f.includes('/addons/')) role = 'addon'
else if (/\.(json|toml|yaml|yml|cfg)$/.test(f)) role = 'config'
else if (/(index|main)\.(js|gd|cpp)$/.test(f)) role = 'entry'
return { lang, domain, role }
}
function esc (s) {
return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
}
async function main () {
if (!await isCogServerUp()) {
console.log('CogServer not running')
process.exit(1)
}
await evalScheme('(use-modules (srfi srfi-1))')
// Get all loop-files
const loopFileStr = await evalScheme(
'(string-join (delete-duplicates (map (lambda (e) (cog-name (cog-outgoing-atom (cog-outgoing-atom e 1) 1))) (cog-incoming-by-type (BondNode "loop-in-file") (quote EdgeLink)))) "|")')
const loopFiles = loopFileStr.replace(/^"|"$/g, '').split('|').filter(Boolean)
console.log('Classifying ' + loopFiles.length + ' files...')
const batch = []
const stats = { lang: {}, domain: {}, role: {} }
for (const f of loopFiles) {
const { lang, domain, role } = classifyFile(f)
stats.lang[lang] = (stats.lang[lang] || 0) + 1
stats.domain[domain] = (stats.domain[domain] || 0) + 1
stats.role[role] = (stats.role[role] || 0) + 1
const node = '(ConceptNode "' + esc(f) + '")'
batch.push('(cog-set-value! ' + node + ' (Predicate "language") (StringValue "' + lang + '"))')
batch.push('(cog-set-value! ' + node + ' (Predicate "domain") (StringValue "' + domain + '"))')
batch.push('(cog-set-value! ' + node + ' (Predicate "role") (StringValue "' + role + '"))')
if (batch.length >= 90) {
await evalScheme('(begin ' + batch.join(' ') + ')')
batch.length = 0
}
}
if (batch.length > 0) {
await evalScheme('(begin ' + batch.join(' ') + ')')
}
console.log('\nClassified ' + loopFiles.length + ' files\n')
console.log('By language:')
for (const [k, v] of Object.entries(stats.lang).sort((a, b) => b[1] - a[1])) {
console.log(' ' + String(v).padStart(4) + ' ' + k)
}
console.log('\nBy domain:')
for (const [k, v] of Object.entries(stats.domain).sort((a, b) => b[1] - a[1])) {
console.log(' ' + String(v).padStart(4) + ' ' + k)
}
console.log('\nBy role:')
for (const [k, v] of Object.entries(stats.role).sort((a, b) => b[1] - a[1])) {
console.log(' ' + String(v).padStart(4) + ' ' + k)
}
// Now answer the deeper questions
console.log('\n=== CROSS-DOMAIN: Correction rate by language ===')
// Which languages have the most loops per file?
const langLoops = {}
const langFiles = {}
for (const f of loopFiles) {
const { lang } = classifyFile(f)
langFiles[lang] = (langFiles[lang] || new Set()).add ? langFiles[lang] || new Set() : new Set()
if (!langFiles[lang].add) langFiles[lang] = new Set()
langFiles[lang].add(f)
}
// Count actual loops (not unique files) per language
const allLoopEdges = await evalScheme(
'(string-join (map (lambda (e) (cog-name (cog-outgoing-atom (cog-outgoing-atom e 1) 1))) (cog-incoming-by-type (BondNode "loop-in-file") (quote EdgeLink))) "|")')
const loopEdgeFiles = allLoopEdges.replace(/^"|"$/g, '').split('|').filter(Boolean)
for (const f of loopEdgeFiles) {
const { lang } = classifyFile(f)
langLoops[lang] = (langLoops[lang] || 0) + 1
}
for (const [lang, loops] of Object.entries(langLoops).sort((a, b) => b[1] - a[1])) {
const files = stats.lang[lang] || 1
console.log(' ' + lang.padEnd(15) + String(loops).padStart(4) + ' loop-edges across ' + String(files).padStart(3) + ' files (' + (loops / files).toFixed(1) + ' loops/file)')
}
console.log('\n=== CROSS-DOMAIN: Correction rate by domain ===')
const domainLoops = {}
for (const f of loopEdgeFiles) {
const { domain } = classifyFile(f)
domainLoops[domain] = (domainLoops[domain] || 0) + 1
}
for (const [domain, loops] of Object.entries(domainLoops).sort((a, b) => b[1] - a[1])) {
const files = stats.domain[domain] || 1
console.log(' ' + domain.padEnd(15) + String(loops).padStart(4) + ' loop-edges across ' + String(files).padStart(3) + ' files (' + (loops / files).toFixed(1) + ' loops/file)')
}
}
main().catch(err => { console.error(err.message); process.exit(1) })