/** AST anchors and finite reviewable definitions from retained REA text. */ import fs from 'node:fs' import crypto from 'node:crypto' import {parse} from '@babel/parser' const inputs=process.argv.slice(2) const digest = s=>crypto.createHash('sha256').update(s).digest('hex') const output={offsetUnit:'UTF-16 code units, end exclusive',modules:[]} for(const input of inputs){ const d=JSON.parse(fs.readFileSync(input,'utf8')) for(const f of d.normalized_result.files.filter(f=>f.path.endsWith('.js'))){ if(digest(f.text)!==f.sha256)throw new Error('SHA mismatch') const ast=parse(f.text,{sourceType:'unambiguous'}), defs=[] function visit(n,owner=''){ if(!n||typeof n!=='object')return if(n.type==='ClassExpression'||n.type==='ClassDeclaration')owner=n.id?.name||owner if(n.type==='VariableDeclarator'&&n.init?.type==='ClassExpression')owner=n.id.name const name=n.type==='FunctionDeclaration'?n.id?.name:n.type==='ClassMethod'||n.type==='ObjectMethod'?n.key.name:undefined if(name&&(/Expert|expert|SessionMarker|WorkbuddyMeta|resolveArgPath/.test(name)||/Expert/.test(owner)))defs.push({name,owner,start:n.start,end:n.end,sha256:digest(f.text.slice(n.start,n.end)),text:f.text.slice(n.start,n.end)}) for(const[k,v]of Object.entries(n)){if(['loc','comments','tokens'].includes(k))continue;if(Array.isArray(v))v.forEach(c=>visit(c,owner));else if(v&&typeof v==='object')visit(v,owner)} }visit(ast.program) output.modules.push({input,evidenceId:d.evidence_id,path:f.path,sha256:f.sha256,definitions:defs}) } } fs.writeFileSync('.tmp/rea-expert-package-contracts.json',JSON.stringify(output,null,2)) console.log(JSON.stringify({output:'.tmp/rea-expert-package-contracts.json',modules:output.modules.map(({definitions,...m})=>({...m,count:definitions.length}))},null,2))