diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c20cbb283..3bb104e33 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,6 +154,20 @@ jobs: - name: Test audit-dial-protocol lexer run: node --test scripts/audit-dial-protocol.test.mjs + # Structural guard against god-files. No hand-written source file may + # grow past its ratcheted budget, and no NEW file may exceed 800 lines. + # High churn x large file was the top regression predictor across v10 + # rc.8 -> rc.15: dkg-agent.ts (2,069 lines) was edited in 1 of every 6 + # commits, and the rc.15 "mixin split" relocated its bulk into several + # NEW 3,000-line files. See `scripts/audit-file-size.mjs` for the data + # and ratchet semantics; per-file budgets live in + # `scripts/file-size-baseline.json` and only ever shrink. + - name: Audit source file size (ratcheting cap) + run: node scripts/audit-file-size.mjs + + - name: Test audit-file-size evaluator + run: node --test scripts/audit-file-size.test.mjs + - name: Install dependencies run: pnpm install --frozen-lockfile diff --git a/RELEASE_PROCESS.md b/RELEASE_PROCESS.md index 513c0ca3a..baa2bd655 100644 --- a/RELEASE_PROCESS.md +++ b/RELEASE_PROCESS.md @@ -40,6 +40,12 @@ Current process keeps these aligned: ## 4) Pre-release tagging workflow +> **Gate:** do not run the tag commands below until the **RC cut checklist +> (§11)** is green. Tagging is the mechanical step at the *end* of a release; +> the checklist is what makes the tag trustworthy. Cutting RCs faster than they +> can be validated is how rc.13 → rc.14 → rc.15 ended up tagged inside two days, +> with rc.14 a same-day emergency for an `eth_getLogs` request storm. + From repo root: ```bash @@ -188,3 +194,73 @@ Promote only after successful: - isolated local update run - canary network runtime validation - release-asset verification (`markitdown-*` binaries present on the GitHub Release) + +## 11) RC cut checklist (stabilization gate) + +Run this before tagging **every** `beta.N` / `rc.N`. Copy it into the release +tracking issue (or the release PR description) and tick each box on the exact +`main` commit you intend to tag. If a box can't be ticked, the RC is not ready — +fix the cause, don't tag around it. + +**Cadence rule (read first):** do **not** cut a new RC while the *previous* RC +still has an open sev-1, an unresolved revert, or has not completed its soak +window. The point of an RC is to be exercised; back-to-back same-day RCs (rc.13 +→ rc.15 in two days) mean nothing soaked long enough to surface the next +regression. One RC in flight at a time. + +### A. Stabilization (the previous RC earned this one) + +- [ ] Previous RC soaked on the canary/devnet cohort for the agreed window + (default **≥ 48h**) with **no new sev-1**. +- [ ] Every revert and explicit "regression" fix from this window has a + regression test landed **in the same PR that fixed it** (so it cannot + silently come back — see the rc.15 "restore SPARQL filterability" and the + `#904/#905/#913/#915` QA-found UI bugs for what escapes without this). +- [ ] No open sev-1 / data-loss / chain-state issues against the target commit. +- [ ] Rollback verified: `dkg rollback` returns the node to the prior slot + cleanly (don't discover this during an incident). + +### B. Correctness gates (all green on the tagged commit, not "mostly") + +- [ ] `ci.yml` green on the exact commit: build, Tornado unit + (core + storage + chain), Blazegraph live integration, **Kosava node-ui + Playwright devnet e2e**, and the EVM integration matrix. +- [ ] `pnpm check:file-size` green — no god-file regressions + (`scripts/audit-file-size.mjs`; budgets in `scripts/file-size-baseline.json`). +- [ ] Deliberately-red PROD-BUG sentinels reviewed against + `.test-audit/BUGS_FOUND.md` — **no newly-red sentinel** beyond the known + inventory, and any sentinel that went green is converted to a normal + passing test. +- [ ] Devnet release-validation run passed for this RC (the + `scripts/devnet-rc-release-validation.sh` analog), and the validation + script itself is current — not patched mid-run. + +### C. Review hygiene (stop the round-N treadmill) + +- [ ] No PR merged into this RC with **unresolved Codex / reviewer threads**. +- [ ] Any PR that needed **≥ 5 review rounds** carries a one-paragraph design + note (or was re-scoped/split) — 5+ rounds means the design was wrong, not + that the diff needed more polish. ~17% of this window's commits were + "address review round N"; that is the cost being controlled here. +- [ ] No PR over ~400 lines of diff merged without an explicit reviewer waiver. + +### D. Versioning & release artifacts + +- [ ] Package versions aligned for the channel (§3): `package.json`, + `packages/cli/package.json`, `packages/evm-module/package.json`, + `packages/mcp-server/package.json`. +- [ ] ABIs regenerated and committed if any Solidity source changed (§7); + `abi-freshness` CI green. +- [ ] `CHANGELOG.md` curated for the full `..` range (not a raw + commit dump). +- [ ] Builder-impacting changes ship an upgrade guide + `docs/UPGRADE__TO_.md` (§9), cross-linked from + `docs/RELEASE.md`. +- [ ] Named **release owner** recorded on the tracking issue, and a one-line + rollback/abort plan stated. + +> **Track the trend, not just the boxes.** The signal that this gate is working +> is the `fix:` vs `feat:` commit ratio falling over successive RCs (it ran +> ~4:1 across rc.8 → rc.15). If it isn't moving, the defects are being created +> upstream of this checklist — reinforce design review and PR sizing, not the +> gate. diff --git a/package.json b/package.json index 1e98da8ae..58908ac2e 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "mcp": "node packages/cli/dist/cli.js mcp serve", "check:npm-metadata": "node scripts/check-npm-metadata.mjs", "check:deny-list": "node scripts/check-dependency-deny-list.mjs", + "check:file-size": "node scripts/audit-file-size.mjs", "index": "node packages/cli/dist/cli.js index", "test:evm-integration": "./scripts/test-evm-integration.sh", "test:evm": "./scripts/test-evm-integration.sh all", diff --git a/scripts/audit-file-size.mjs b/scripts/audit-file-size.mjs new file mode 100644 index 000000000..ad4a3d432 --- /dev/null +++ b/scripts/audit-file-size.mjs @@ -0,0 +1,286 @@ +#!/usr/bin/env node +/** + * Audit: cap hand-written source file size with a ratcheting baseline. + * + * Why this exists + * --------------- + * Across v10 rc.8 -> rc.15 the biggest source files were also the biggest + * change-magnets. `packages/agent/src/dkg-agent.ts` was 2,069 lines and was + * edited in 239 of the window's 1,488 commits -- one in every six. The other + * top churn hotspots, `cli/src/daemon/lifecycle.ts` (2,864) and + * `query/src/dkg-query-engine.ts` (2,731), tell the same story. High churn x + * large file is the strongest predictor of where the next regression lands: + * when every feature edits the same 2,800-line file, every feature can break + * every other. + * + * The rc.15 "extract into mixin holder" refactor confirmed the failure mode + * rather than fixing it -- it relocated dkg-agent.ts's bulk into NEW giant + * files (`dkg-agent-lifecycle.ts` at 3,842, `dkg-agent-publish.ts` at 3,285, + * `dkg-agent-swm-host.ts` at 3,021) instead of shrinking the surface. A guard + * that only watched one filename would have called that a win. + * + * What it does + * ------------ + * Walks every `.ts` / `.tsx` file under `packages//src/**` (hand-written + * source only -- generated typechain bindings, dist output, `.d.ts`, and test + * files are excluded) and compares each file's line count to its budget: + * + * - A file recorded in `scripts/file-size-baseline.json` gets its budget + * from that snapshot (its size at adoption time, rounded up to the next + * ROUND lines so routine maintenance has headroom but a feature's worth of + * growth does not). These are the large files we already live with. + * - Every other file gets DEFAULT_MAX_LINES. New code must stay small. + * + * A file over its budget fails the build. The baseline is a one-way ratchet: + * a file may shrink below its budget (and `--write` will record the smaller + * number), but it may never grow past it, and no NEW file may cross + * DEFAULT_MAX_LINES. + * + * Regenerating the baseline + * ------------------------- + * pnpm check:file-size --write + * + * Run this AFTER a legitimate extraction shrinks a file (to ratchet its budget + * down), or -- rarely, and with reviewer sign-off -- to record a deliberately + * large new file. CI runs the read-only check and never writes. + * + * This is a coarse structural smell, not a style rule. It does not judge what + * is in a file, only that no single unit is allowed to keep accreting + * responsibility unchecked. + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +// New hand-written source files may not exceed this many lines. +export const DEFAULT_MAX_LINES = 800; + +// When recording an existing oversized file in the baseline, round its current +// size up to the next multiple of this many lines. Gives 1..ROUND lines of +// maintenance headroom without leaving room for a whole new responsibility. +export const BASELINE_ROUND = 50; + +const ROOT_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const PACKAGES_DIR = path.join(ROOT_DIR, 'packages'); +const BASELINE_PATH = path.join(ROOT_DIR, 'scripts', 'file-size-baseline.json'); + +const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx']); + +// Directory names pruned during the walk -- generated or non-source trees that +// must never be gated. `typechain` holds Solidity->TS bindings regenerated by +// hardhat; `dist` is build output; the rest are tooling/vendored. +const PRUNED_DIRS = new Set(['node_modules', 'dist', '.turbo', 'typechain', '__generated__']); + +// Path fragments that exclude a file even if it slips past the dir prune. +const EXCLUDED_FRAGMENTS = ['/dist/', '/node_modules/', '/.turbo/', '/typechain/', '/__generated__/']; + +/** + * True for a hand-written source file we want to gate. Operates on a + * forward-slash relative path so it is platform-independent and unit-testable. + */ +export function isSourceFile(relPath) { + const p = relPath.replaceAll('\\', '/'); + if (!p.includes('/src/')) return false; + if (!SOURCE_EXTENSIONS.has(path.posix.extname(p))) return false; + if (p.endsWith('.d.ts')) return false; + if (/\.(test|spec)\.tsx?$/.test(p)) return false; + if (/\.(gen|generated)\.tsx?$/.test(p)) return false; + if (EXCLUDED_FRAGMENTS.some((frag) => p.includes(frag))) return false; + return true; +} + +/** + * Logical line count. Matches `wc -l` for files with a trailing newline (the + * common case) and is +1 for files without one (which is the more accurate + * count anyway -- the last line still has content). + */ +export function countLines(content) { + if (content.length === 0) return 0; + const lines = content.split('\n'); + if (lines[lines.length - 1] === '') lines.pop(); // drop trailing-newline artifact + return lines.length; +} + +export function roundUpTo(n, step) { + return Math.ceil(n / step) * step; +} + +/** + * Budget to record for a file of `lines` length: the next ROUND boundary + * strictly above its current size, so there is always at least one line of + * headroom and at most ROUND. + */ +export function budgetForSize(lines, round = BASELINE_ROUND) { + return roundUpTo(lines + 1, round); +} + +/** Budget a given file is held to: its baseline entry, else the default cap. */ +export function budgetFor(relPath, baselineFiles, defaultMax = DEFAULT_MAX_LINES) { + return Object.prototype.hasOwnProperty.call(baselineFiles, relPath) + ? baselineFiles[relPath] + : defaultMax; +} + +/** + * Pure evaluation. `measured` is an array of `{ path, lines }`. Returns the + * over-budget violations (sorted largest-first) and non-failing "approaching + * the cap" warnings for new files. + */ +export function evaluate(measured, baselineFiles, options = {}) { + const defaultMax = options.defaultMax ?? DEFAULT_MAX_LINES; + const warnAt = options.warnAt ?? defaultMax - BASELINE_ROUND; // tight "about to cross" band + const violations = []; + const warnings = []; + + for (const { path: relPath, lines } of measured) { + const inBaseline = Object.prototype.hasOwnProperty.call(baselineFiles, relPath); + const budget = inBaseline ? baselineFiles[relPath] : defaultMax; + if (lines > budget) { + violations.push({ + path: relPath, + lines, + budget, + kind: inBaseline ? 'baselined-file-grew' : 'new-file-over-cap', + }); + } else if (!inBaseline && lines >= warnAt) { + warnings.push({ path: relPath, lines, budget }); + } + } + + violations.sort((a, b) => b.lines - a.lines); + warnings.sort((a, b) => b.lines - a.lines); + return { violations, warnings }; +} + +function walkSourceFiles(dir) { + const out = []; + const stack = [dir]; + while (stack.length > 0) { + const current = stack.pop(); + let entries; + try { + entries = fs.readdirSync(current, { withFileTypes: true }); + } catch { + continue; + } + for (const entry of entries) { + const abs = path.join(current, entry.name); + if (entry.isDirectory()) { + if (!PRUNED_DIRS.has(entry.name)) stack.push(abs); + } else if (entry.isFile()) { + const rel = path.relative(ROOT_DIR, abs).replaceAll('\\', '/'); + if (isSourceFile(rel)) out.push(rel); + } + } + } + out.sort(); + return out; +} + +function measureFiles(relPaths, rootDir = ROOT_DIR) { + return relPaths.map((rel) => ({ + path: rel, + lines: countLines(fs.readFileSync(path.join(rootDir, rel), 'utf8')), + })); +} + +function loadBaseline() { + try { + const raw = JSON.parse(fs.readFileSync(BASELINE_PATH, 'utf8')); + return raw.files ?? {}; + } catch { + return {}; + } +} + +function writeBaseline(measured) { + const files = {}; + for (const { path: relPath, lines } of measured) { + if (lines > DEFAULT_MAX_LINES) files[relPath] = budgetForSize(lines); + } + const sorted = Object.fromEntries( + Object.entries(files).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)), + ); + const payload = { + _comment: + 'Ratchet baseline for scripts/audit-file-size.mjs. DO NOT hand-edit. ' + + 'Regenerate with `pnpm check:file-size --write`. Each entry is the ' + + 'maximum line count that file is allowed; budgets only ratchet DOWN as ' + + 'files shrink. A new entry means a new oversized file landed -- that ' + + 'should be justified in code review, not waved through.', + defaultMaxLines: DEFAULT_MAX_LINES, + roundTo: BASELINE_ROUND, + files: sorted, + }; + fs.writeFileSync(BASELINE_PATH, `${JSON.stringify(payload, null, 2)}\n`); + return sorted; +} + +async function main(argv = process.argv.slice(2)) { + const write = argv.includes('--write'); + const measured = measureFiles(walkSourceFiles(PACKAGES_DIR)); + + if (write) { + const files = writeBaseline(measured); + console.log( + `file-size baseline written: ${Object.keys(files).length} file(s) over ` + + `${DEFAULT_MAX_LINES} lines recorded in ` + + `${path.relative(ROOT_DIR, BASELINE_PATH)}.`, + ); + return 0; + } + + const baselineFiles = loadBaseline(); + const { violations, warnings } = evaluate(measured, baselineFiles); + + for (const w of warnings) { + console.warn( + `WARN ${w.path} is ${w.lines} lines -- approaching the ` + + `${DEFAULT_MAX_LINES}-line cap. Consider extracting before it crosses.`, + ); + } + + if (violations.length === 0) { + console.log( + `file-size check passed: ${measured.length} source file(s) scanned, ` + + `${Object.keys(baselineFiles).length} held to ratcheted budgets, none over.`, + ); + return 0; + } + + console.error('\nFile-size guard failed:\n'); + for (const v of violations) { + console.error(` x ${v.path}`); + if (v.kind === 'new-file-over-cap') { + console.error(` ${v.lines} lines -- over the ${v.budget}-line cap for new files.`); + console.error( + ' Split it into focused modules before merging. A single source ' + + 'file this large is a change-magnet (see the script header for the ' + + 'rc.8->rc.15 data).', + ); + } else { + console.error(` ${v.lines} lines -- grew past its ratcheted budget of ${v.budget}.`); + console.error( + ' This file is already oversized; it may shrink but not grow. ' + + 'Extract the new code into its own module instead of adding here.', + ); + } + } + console.error( + '\nThe budget is a one-way ratchet: oversized files may shrink, never grow,\n' + + `and new files must stay under ${DEFAULT_MAX_LINES} lines. If a file ` + + 'legitimately must\nexceed its budget (rare), run `pnpm check:file-size ' + + '--write` to re-baseline\nand justify the size in code review.\n', + ); + return 1; +} + +// Run only when invoked directly, so the test file can import the pure helpers +// without triggering a full repository scan + process.exit. +const invokedDirectly = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href; +if (invokedDirectly) { + const exitCode = await main(); + process.exit(exitCode); +} + +export { main }; diff --git a/scripts/audit-file-size.test.mjs b/scripts/audit-file-size.test.mjs new file mode 100644 index 000000000..59327ca9c --- /dev/null +++ b/scripts/audit-file-size.test.mjs @@ -0,0 +1,154 @@ +/** + * Unit tests for the pure helpers powering `scripts/audit-file-size.mjs`. + * + * Run with: node --test scripts/audit-file-size.test.mjs + * + * The guard's value depends entirely on (a) measuring the right files -- + * hand-written source, never generated typechain bindings or tests -- and + * (b) the ratchet semantics: a baselined file may shrink but not grow, and a + * new file must stay under the cap. Both are covered below so a refactor of + * the scanner can't silently start waving large files through. + */ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { + DEFAULT_MAX_LINES, + BASELINE_ROUND, + isSourceFile, + countLines, + roundUpTo, + budgetForSize, + budgetFor, + evaluate, +} from './audit-file-size.mjs'; + +describe('isSourceFile', () => { + it('accepts hand-written package source', () => { + assert.equal(isSourceFile('packages/agent/src/dkg-agent.ts'), true); + assert.equal(isSourceFile('packages/node-ui/src/ui/Shell/PanelRight.tsx'), true); + }); + + it('rejects files outside a /src/ tree', () => { + assert.equal(isSourceFile('packages/cli/scripts/thing.ts'), false); + assert.equal(isSourceFile('scripts/audit-file-size.mjs'), false); + }); + + it('rejects generated typechain bindings even under a src-shaped path', () => { + assert.equal(isSourceFile('packages/evm-module/typechain/contracts/Storage.ts'), false); + assert.equal(isSourceFile('packages/chain/src/typechain/Foo.ts'), false); + }); + + it('rejects tests, declarations, generated, and build output', () => { + assert.equal(isSourceFile('packages/agent/src/dkg-agent.test.ts'), false); + assert.equal(isSourceFile('packages/agent/src/dkg-agent.spec.tsx'), false); + assert.equal(isSourceFile('packages/agent/src/types.d.ts'), false); + assert.equal(isSourceFile('packages/agent/src/schema.generated.ts'), false); + assert.equal(isSourceFile('packages/agent/dist/src/dkg-agent.ts'), false); + }); + + it('rejects non-ts extensions', () => { + assert.equal(isSourceFile('packages/agent/src/styles.css'), false); + assert.equal(isSourceFile('packages/agent/src/data.json'), false); + }); + + it('normalizes backslash paths (Windows)', () => { + assert.equal(isSourceFile('packages\\agent\\src\\dkg-agent.ts'), true); + }); +}); + +describe('countLines', () => { + it('is 0 for empty content', () => { + assert.equal(countLines(''), 0); + }); + + it('matches wc -l for trailing-newline files', () => { + assert.equal(countLines('a\n'), 1); + assert.equal(countLines('a\nb\n'), 2); + assert.equal(countLines('a\n\n'), 2); + assert.equal(countLines('\n'), 1); + }); + + it('counts the final line when there is no trailing newline', () => { + assert.equal(countLines('a'), 1); + assert.equal(countLines('a\nb'), 2); + }); +}); + +describe('roundUpTo / budgetForSize', () => { + it('rounds up to the next step', () => { + assert.equal(roundUpTo(2864, 50), 2900); + assert.equal(roundUpTo(2900, 50), 2900); + assert.equal(roundUpTo(801, 50), 850); + }); + + it('always leaves 1..ROUND lines of headroom', () => { + assert.equal(budgetForSize(2864), 2900); // 36 lines headroom + assert.equal(budgetForSize(2850), 2900); // 50 lines headroom + assert.equal(budgetForSize(2900), 2950); // on a boundary -> still grows + assert.ok(budgetForSize(1234) > 1234); + assert.ok(budgetForSize(1234) - 1234 <= BASELINE_ROUND); + }); +}); + +describe('budgetFor', () => { + const baseline = { 'packages/agent/src/dkg-agent.ts': 2100 }; + + it('uses the baseline entry when present', () => { + assert.equal(budgetFor('packages/agent/src/dkg-agent.ts', baseline), 2100); + }); + + it('falls back to the default cap otherwise', () => { + assert.equal(budgetFor('packages/agent/src/new-file.ts', baseline), DEFAULT_MAX_LINES); + }); +}); + +describe('evaluate', () => { + it('passes a new file under the cap with no warning', () => { + const { violations, warnings } = evaluate([{ path: 'a/src/x.ts', lines: 400 }], {}); + assert.equal(violations.length, 0); + assert.equal(warnings.length, 0); + }); + + it('warns (but does not fail) a new file approaching the cap', () => { + const { violations, warnings } = evaluate([{ path: 'a/src/x.ts', lines: 770 }], {}); + assert.equal(violations.length, 0); + assert.equal(warnings.length, 1); + assert.equal(warnings[0].path, 'a/src/x.ts'); + }); + + it('fails a NEW file over the cap', () => { + const { violations } = evaluate([{ path: 'a/src/big.ts', lines: 900 }], {}); + assert.equal(violations.length, 1); + assert.equal(violations[0].kind, 'new-file-over-cap'); + assert.equal(violations[0].budget, DEFAULT_MAX_LINES); + }); + + it('lets a baselined file sit at or below its budget', () => { + const baseline = { 'a/src/big.ts': 2900 }; + const { violations } = evaluate([{ path: 'a/src/big.ts', lines: 2890 }], baseline); + assert.equal(violations.length, 0); + }); + + it('fails a baselined file that grew past its budget', () => { + const baseline = { 'a/src/big.ts': 2900 }; + const { violations } = evaluate([{ path: 'a/src/big.ts', lines: 2950 }], baseline); + assert.equal(violations.length, 1); + assert.equal(violations[0].kind, 'baselined-file-grew'); + assert.equal(violations[0].budget, 2900); + }); + + it('sorts violations largest-first', () => { + const { violations } = evaluate( + [ + { path: 'a/src/small.ts', lines: 850 }, + { path: 'a/src/huge.ts', lines: 4000 }, + ], + {}, + ); + assert.deepEqual( + violations.map((v) => v.path), + ['a/src/huge.ts', 'a/src/small.ts'], + ); + }); +}); diff --git a/scripts/file-size-baseline.json b/scripts/file-size-baseline.json new file mode 100644 index 000000000..88f6d7fd9 --- /dev/null +++ b/scripts/file-size-baseline.json @@ -0,0 +1,79 @@ +{ + "_comment": "Ratchet baseline for scripts/audit-file-size.mjs. DO NOT hand-edit. Regenerate with `pnpm check:file-size --write`. Each entry is the maximum line count that file is allowed; budgets only ratchet DOWN as files shrink. A new entry means a new oversized file landed -- that should be justified in code review, not waved through.", + "defaultMaxLines": 800, + "roundTo": 50, + "files": { + "packages/adapter-hermes/src/setup.ts": 1950, + "packages/adapter-openclaw/src/ChatTurnWriter.ts": 4100, + "packages/adapter-openclaw/src/DkgChannelPlugin.ts": 2850, + "packages/adapter-openclaw/src/DkgMemoryPlugin.ts": 1150, + "packages/adapter-openclaw/src/DkgNodePlugin.ts": 3850, + "packages/adapter-openclaw/src/dkg-client.ts": 1350, + "packages/adapter-openclaw/src/setup.ts": 1800, + "packages/agent/src/dkg-agent-base.ts": 1150, + "packages/agent/src/dkg-agent-ccl.ts": 900, + "packages/agent/src/dkg-agent-cg-registry.ts": 1100, + "packages/agent/src/dkg-agent-cg-resolve.ts": 1700, + "packages/agent/src/dkg-agent-context-graph.ts": 1950, + "packages/agent/src/dkg-agent-crypto.ts": 2450, + "packages/agent/src/dkg-agent-endorse.ts": 950, + "packages/agent/src/dkg-agent-join.ts": 1400, + "packages/agent/src/dkg-agent-lifecycle.ts": 3850, + "packages/agent/src/dkg-agent-ownership.ts": 1050, + "packages/agent/src/dkg-agent-publish.ts": 3300, + "packages/agent/src/dkg-agent-query.ts": 950, + "packages/agent/src/dkg-agent-registry.ts": 1850, + "packages/agent/src/dkg-agent-swm-host.ts": 3050, + "packages/agent/src/dkg-agent-swm-substrate.ts": 1550, + "packages/agent/src/dkg-agent-types.ts": 1000, + "packages/agent/src/dkg-agent.ts": 2100, + "packages/agent/src/finalization-handler.ts": 1300, + "packages/agent/src/generic-sql-source.ts": 900, + "packages/agent/src/p2p/messenger.ts": 1050, + "packages/chain/src/chain-adapter.ts": 1400, + "packages/chain/src/evm-adapter-base.ts": 1900, + "packages/chain/src/evm-adapter-publish.ts": 850, + "packages/chain/src/mock-adapter.ts": 1800, + "packages/cli/src/api-client.ts": 1900, + "packages/cli/src/auth.ts": 1800, + "packages/cli/src/config.ts": 1450, + "packages/cli/src/daemon/auto-update.ts": 1550, + "packages/cli/src/daemon/http-utils.ts": 1250, + "packages/cli/src/daemon/lifecycle.ts": 2900, + "packages/cli/src/daemon/local-agents.ts": 1050, + "packages/cli/src/daemon/manifest.ts": 950, + "packages/cli/src/daemon/openclaw.ts": 1600, + "packages/cli/src/daemon/routes/agent-chat.ts": 1200, + "packages/cli/src/daemon/routes/assertion.ts": 3950, + "packages/cli/src/daemon/routes/context-graph.ts": 1800, + "packages/cli/src/daemon/routes/hermes.ts": 1050, + "packages/cli/src/daemon/routes/memory.ts": 2450, + "packages/cli/src/daemon/routes/openclaw.ts": 1100, + "packages/cli/src/daemon/routes/query.ts": 1300, + "packages/cli/src/daemon/routes/status.ts": 1300, + "packages/cli/src/mcp-setup.ts": 2200, + "packages/core/src/message-stream-pool.ts": 1100, + "packages/core/src/node.ts": 1750, + "packages/core/src/protocol-router.ts": 1450, + "packages/mcp-dkg/src/client.ts": 1350, + "packages/network-sim/src/components/ControlPanel.tsx": 1200, + "packages/node-ui/src/chat-memory.ts": 1600, + "packages/node-ui/src/db.ts": 3000, + "packages/node-ui/src/ui/api.ts": 2850, + "packages/node-ui/src/ui/components/Shell/PanelRight/PanelRightContainer.tsx": 900, + "packages/node-ui/src/ui/pages/Operations.tsx": 1700, + "packages/node-ui/src/ui/views/DashboardView.tsx": 900, + "packages/node-ui/src/ui/views/MemoryLayerView.tsx": 850, + "packages/node-ui/src/ui/views/ProjectView.tsx": 1000, + "packages/node-ui/src/ui/views/project/components/overview.tsx": 1000, + "packages/node-ui/src/ui/views/project/components/subgraph.tsx": 1800, + "packages/node-ui/src/ui/views/project/helpers.ts": 900, + "packages/publisher/src/ack-collector.ts": 950, + "packages/publisher/src/async-lift-publisher-impl.ts": 1100, + "packages/publisher/src/dkg-publisher.ts": 4800, + "packages/publisher/src/metadata.ts": 1600, + "packages/publisher/src/storage-ack-handler.ts": 1300, + "packages/publisher/src/workspace-handler.ts": 1650, + "packages/query/src/dkg-query-engine.ts": 2750 + } +}