// Materialize only CSS text already returned in real REA Evidence. No target access. import fs from 'node:fs/promises' import path from 'node:path' import crypto from 'node:crypto' import postcss from 'postcss' const input = path.resolve(process.argv[2] || '.tmp/rea-workbuddy-css-evidence.json') const output = path.resolve(process.argv[3] || '.tmp/rea-css-recovered') const evidence = JSON.parse(await fs.readFile(input, 'utf8')) if (evidence.operation !== 'inspect_electron_page' || evidence.parameters?.include_style_sources !== true || !evidence.normalized_result?.styles) throw new Error('Expected actual REA CSS capture Evidence') const selection = JSON.parse(await fs.readFile('.tmp/rea-style-selection.json', 'utf8')) const nodes = new Map(selection.snapshot.nodes.map(node => [node.artifact_id, node])) const inventory = new Map(selection.snapshot.occurrences.map(occurrence => [occurrence.logical_path, { occurrence, node: nodes.get(occurrence.artifact_id) }])) const files = [] const usedNames = new Map() const families = ['cr-tool-call', 'question-floating', 'cb-plan-task-wait-icon', 'cb-plan-tool-container', 'cb-task-tool-renderer', 'cr-reasoning', 'artifact-content', 'markdown-editor-container'] const coverage = Object.fromEntries(families.map(family => [family, []])) await fs.mkdir(output, { recursive: true }) for (const style of evidence.normalized_result.styles.items) { if (!style.source.included) continue const artifact = style.source.artifact const bytes = Buffer.from(artifact.text, 'utf8') const sha256 = crypto.createHash('sha256').update(bytes).digest('hex') if (sha256 !== artifact.sha256 || bytes.length !== artifact.bytes || artifact.media_type !== 'text/css') throw new Error('REA CSS artifact digest/size/type mismatch') const logicalPath = style.file_path?.replace(/\\/g, '/').split('/app.asar/')[1] let filename = style.is_inline || !logicalPath ? `inline-${sha256.slice(0, 16)}.css` : path.posix.basename(logicalPath) if (!filename.endsWith('.css') || !/^[a-zA-Z0-9._-]+$/.test(filename)) throw new Error('Invalid CSS output filename') if (usedNames.has(filename) && usedNames.get(filename) !== sha256) filename = filename.replace(/\.css$/, `-${sha256.slice(0, 16)}.css`) usedNames.set(filename, sha256) const known = logicalPath && !style.is_inline ? inventory.get(logicalPath) : undefined const asarComparison = known ? { status: known.node?.sha256 === sha256 && known.node?.size === bytes.length ? 'match' : 'mismatch', evidenceId: selection.parentEvidenceId, artifactId: known.occurrence.artifact_id, rootSha256: selection.snapshot.manifest.root_sha256, expectedSha256: known.node?.sha256, expectedBytes: known.node?.size, } : undefined const root = postcss.parse(artifact.text, { from: filename }) let rules = 0 const counts = Object.fromEntries(families.map(family => [family, 0])) root.walkRules(rule => { rules++ for (const family of families) if (rule.selector.includes(family)) counts[family]++ }) const destination = path.join(output, filename) await fs.writeFile(destination, bytes) if (crypto.createHash('sha256').update(await fs.readFile(destination)).digest('hex') !== sha256) throw new Error('Written CSS digest mismatch') for (const family of families) if (counts[family]) coverage[family].push({ file: filename, rules: counts[family] }) files.push({ file: filename, filePath: style.file_path, frameId: style.frame_id, styleSheetKey: style.style_sheet_key, isInline: style.is_inline, sha256, bytes: bytes.length, rules, ...(asarComparison ? { asarComparison } : {}) }) } files.sort((a, b) => a.file.localeCompare(b.file)) const capturedPaths = new Set(files.filter(file => file.asarComparison).map(file => file.filePath.replace(/\\/g, '/').split('/app.asar/')[1])) const manifest = { sourceEvidenceId: evidence.evidence_id, sourceEvidencePath: input, provider: evidence.provider, target: evidence.normalized_result.target, captureWindow: evidence.normalized_result.capture_window, runtimeStyles: { total: evidence.normalized_result.styles.total, excluded: evidence.normalized_result.styles.excluded, sourceFailures: evidence.normalized_result.styles.source_failures, truncated: evidence.normalized_result.styles.truncated }, files, bytes: files.reduce((total, file) => total + file.bytes, 0), matchedAsarArtifacts: files.filter(file => file.asarComparison?.status === 'match').length, mismatchedAsarArtifacts: files.filter(file => file.asarComparison?.status === 'mismatch').map(file => file.file), selectedInventoryNotObserved: [...inventory.keys()].filter(key => !capturedPaths.has(key)), selectorCoverage: coverage, limitations: ['Scope is styles loaded in the selected Electron capture window, not every packaged CSS file.', 'Inline/constructed styles are runtime text and do not establish original file identity.', 'Only the prior selected ASAR inventory has been compared; other CSS remains runtime-attributed.', 'A mismatch is preserved as runtime CSS and does not establish original ASAR byte identity; its cause has not been determined.'] } await fs.writeFile(path.join(output, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n') console.log(JSON.stringify({ evidenceId: manifest.sourceEvidenceId, output, files: files.length, bytes: manifest.bytes, matchedAsarArtifacts: manifest.matchedAsarArtifacts, mismatchedAsarArtifacts: manifest.mismatchedAsarArtifacts, selectedInventoryNotObserved: manifest.selectedInventoryNotObserved, selectorCoverage: coverage }, null, 2))