#!/usr/bin/env node
/**
* why-analysis.js — Discover WHY corrections happen using the combined graph
*
* Cross-domain analysis that neither codetree nor ratchet can do alone:
* 1. Do corrections cluster at module boundaries?
* 2. What file roles get corrected most?
* 3. What loop types dominate?
* 4. Which modules have worst correction density?
* 5. Are there structural patterns that predict corrections?
* 6. Rules that apply to structurally connected files
*/
const { evalScheme, isCogServerUp } = require('../cogserver')
function parseList (str) {
if (!str) return []
return str.replace(/^"|"$/g, '').split('|').filter(Boolean)
}
async function main () {
if (!await isCogServerUp()) {
console.log('CogServer not running')
process.exit(1)
}
// Load srfi-1
await evalScheme('(use-modules (srfi srfi-1))')
console.log('=== WHY ANALYSIS — Correction Patterns from AtomSpace ===\n')
// ── 1. Module boundary analysis ──
console.log('── 1. Do corrections cluster at module boundaries? ──')
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 = new Set(parseList(loopFileStr))
const importFileStr = await evalScheme(
'(string-join (delete-duplicates (map (lambda (e) (cog-name (cog-outgoing-atom (cog-outgoing-atom e 1) 0))) (cog-incoming-by-type (BondNode "imports") (quote EdgeLink)))) "|")')
const importFiles = new Set(parseList(importFileStr))
const loopsAtBoundary = [...loopFiles].filter(f => importFiles.has(f)).length
const loopsInternal = loopFiles.size - loopsAtBoundary
console.log(` Files with loops: ${loopFiles.size}`)
console.log(` Files with loops AND cross-module imports: ${loopsAtBoundary}`)
console.log(` Files with loops but NO imports: ${loopsInternal}`)
if (loopFiles.size > 0) {
console.log(` Boundary correction rate: ${(loopsAtBoundary / loopFiles.size * 100).toFixed(0)}%`)
}
// ── 2. File role analysis ──
console.log('\n── 2. Which file roles get corrected most? ──')
const roleCounts = {}
for (const f of loopFiles) {
const role = await evalScheme(`(let ((v (cog-value (ConceptNode "${f}") (Predicate "role")))) (if v (cog-value-ref v 0) "unknown"))`)
const clean = (role || 'unknown').replace(/"/g, '')
roleCounts[clean] = (roleCounts[clean] || 0) + 1
}
for (const [role, count] of Object.entries(roleCounts).sort((a, b) => b[1] - a[1])) {
console.log(` ${String(count).padStart(4)} loop-files role=${role}`)
}
// ── 3. Loop type distribution ──
console.log('\n── 3. What types of loops dominate? ──')
const typeStr = await evalScheme(
'(string-join (map (lambda (e) (let ((n (cog-outgoing-atom (cog-outgoing-atom e 1) 0))) (let ((v (cog-value n (Predicate "type")))) (if v (cog-value-ref v 0) "unknown")))) (cog-incoming-by-type (BondNode "loop-in-file") (quote EdgeLink))) "|")')
const typeCounts = {}
for (const t of parseList(typeStr)) {
const clean = t.replace(/"/g, '')
typeCounts[clean] = (typeCounts[clean] || 0) + 1
}
for (const [type, count] of Object.entries(typeCounts).sort((a, b) => b[1] - a[1])) {
console.log(` ${String(count).padStart(4)} ${type}`)
}
// ── 4. Module correction density ──
console.log('\n── 4. Which modules have worst correction density? ──')
const containStr = await evalScheme(
'(string-join (map (lambda (e) (string-append (cog-name (cog-outgoing-atom (cog-outgoing-atom e 1) 0)) "::" (cog-name (cog-outgoing-atom (cog-outgoing-atom e 1) 1)))) (cog-incoming-by-type (BondNode "contains") (quote EdgeLink))) "|")')
const modFileCounts = {}
for (const pair of parseList(containStr)) {
const mod = pair.split('::')[0]
modFileCounts[mod] = (modFileCounts[mod] || 0) + 1
}
const modLoopCounts = {}
for (const f of loopFiles) {
for (const mod of Object.keys(modFileCounts)) {
const prefix = mod.replace('mod:', '')
if (f.includes(prefix + '/')) {
modLoopCounts[mod] = (modLoopCounts[mod] || 0) + 1
break
}
}
}
const modDensity = Object.entries(modFileCounts)
.map(([mod, files]) => ({
mod: mod.replace('mod:', ''),
files,
loops: modLoopCounts[mod] || 0,
density: (modLoopCounts[mod] || 0) / files
}))
.filter(m => m.files > 2)
.sort((a, b) => b.density - a.density)
for (const m of modDensity.slice(0, 12)) {
const bar = '█'.repeat(Math.round(m.density * 20))
console.log(` ${m.mod.padEnd(22)} ${(m.density * 100).toFixed(0).padStart(3)}% ${bar} (${m.loops} loops / ${m.files} files)`)
}
// ── 5. Structural risk patterns ──
console.log('\n── 5. Structural risk patterns ──')
// Files with exports that also have rules
const exportFileStr = await evalScheme(
'(string-join (delete-duplicates (map (lambda (e) (cog-name (cog-outgoing-atom (cog-outgoing-atom e 1) 0))) (cog-incoming-by-type (BondNode "exports") (quote EdgeLink)))) "|")')
const exportFiles = new Set(parseList(exportFileStr))
const ruleFileStr = 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 "rule-for-file") (quote EdgeLink)))) "|")')
const ruleFiles = new Set(parseList(ruleFileStr))
const exportWithRules = [...exportFiles].filter(f => ruleFiles.has(f))
const exportWithLoops = [...exportFiles].filter(f => loopFiles.has(f))
console.log(` Files that export public API: ${exportFiles.size}`)
console.log(` Of those, files with learned rules: ${exportWithRules.length} (${(exportWithRules.length / exportFiles.size * 100).toFixed(0)}%)`)
console.log(` Of those, files with correction loops: ${exportWithLoops.length} (${(exportWithLoops.length / exportFiles.size * 100).toFixed(0)}%)`)
// ── 6. Top risk files (combined structural + behavioral) ──
console.log('\n── 6. Highest risk files (exports + loops + rules) ──')
// Count rules per file
const ruleCountStr = await evalScheme(
'(string-join (map (lambda (e) (cog-name (cog-outgoing-atom (cog-outgoing-atom e 1) 1))) (cog-incoming-by-type (BondNode "rule-for-file") (quote EdgeLink))) "|")')
const ruleCountMap = {}
for (const f of parseList(ruleCountStr)) ruleCountMap[f] = (ruleCountMap[f] || 0) + 1
// Count loops per file
const loopCountStr = 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 loopCountMap = {}
for (const f of parseList(loopCountStr)) loopCountMap[f] = (loopCountMap[f] || 0) + 1
// Combined risk score: loops * 3 + rules * 1 + exports * 2
const allFiles = new Set([...loopFiles, ...ruleFiles, ...exportFiles])
const riskScores = [...allFiles].map(f => ({
file: f.replace('file:', ''),
loops: loopCountMap[f] || 0,
rules: ruleCountMap[f] || 0,
exports: exportFiles.has(f) ? 1 : 0,
risk: (loopCountMap[f] || 0) * 3 + (ruleCountMap[f] || 0) * 1 + (exportFiles.has(f) ? 2 : 0)
}))
.filter(f => f.risk > 3)
.sort((a, b) => b.risk - a.risk)
for (const f of riskScores.slice(0, 15)) {
console.log(` risk=${String(f.risk).padStart(3)} ${f.file} (${f.loops} loops, ${f.rules} rules${f.exports ? ', exports API' : ''})`)
}
// ── 7. Key insight ──
console.log('\n── 7. Key Insights ──')
const totalLoopEdges = parseInt(await evalScheme('(length (cog-incoming-by-type (BondNode "loop-in-file") (quote EdgeLink)))')) || 0
const totalRuleEdges = parseInt(await evalScheme('(length (cog-incoming-by-type (BondNode "rule-for-file") (quote EdgeLink)))')) || 0
const totalExportEdges = parseInt(await evalScheme('(length (cog-incoming-by-type (BondNode "exports") (quote EdgeLink)))')) || 0
console.log(` Graph: ${totalLoopEdges} loop edges, ${totalRuleEdges} rule edges, ${totalExportEdges} export edges`)
console.log(` Module boundary corrections: ${loopsAtBoundary}/${loopFiles.size} (${(loopsAtBoundary / loopFiles.size * 100).toFixed(0)}%)`)
console.log(` Public API files with problems: ${exportWithRules.length}/${exportFiles.size}`)
const topType = Object.entries(typeCounts).sort((a, b) => b[1] - a[1])[0]
if (topType) console.log(` Most common loop type: ${topType[0]} (${topType[1]} occurrences)`)
const worstMod = modDensity[0]
if (worstMod) console.log(` Worst module by density: ${worstMod.mod} (${(worstMod.density * 100).toFixed(0)}% of files have loops)`)
}
main().catch(err => {
console.error('Analysis failed:', err.message)
process.exit(1)
})