|
| 1 | +#!/usr/bin/env ruby |
| 2 | +# frozen_string_literal: true |
| 3 | + |
| 4 | +require "find" |
| 5 | +require "pathname" |
| 6 | + |
| 7 | +ROOT = Pathname.new(__dir__).join("..").expand_path |
| 8 | +MAX_NEW_RUST_LINES = 700 |
| 9 | + |
| 10 | +# Existing large files are technical debt, not precedent. Their budgets are |
| 11 | +# pinned to the line counts from the first architecture-ratchet pass; growing |
| 12 | +# one of these files should be an explicit decision, preferably paired with a |
| 13 | +# split or a tighter follow-up budget. |
| 14 | +RUST_LINE_BUDGETS = { |
| 15 | + "crates/adversary-detector/src/proxy.rs" => 730, |
| 16 | + "crates/adversary-detector/src/scanner.rs" => 1387, |
| 17 | + "crates/calciforge/src/adapters/codex_cli.rs" => 752, |
| 18 | + "crates/calciforge/src/adapters/mod.rs" => 1374, |
| 19 | + "crates/calciforge/src/adapters/openclaw_channel.rs" => 1768, |
| 20 | + "crates/calciforge/src/channels/matrix.rs" => 1815, |
| 21 | + "crates/calciforge/src/channels/mock.rs" => 804, |
| 22 | + "crates/calciforge/src/channels/signal.rs" => 1066, |
| 23 | + "crates/calciforge/src/channels/sms.rs" => 958, |
| 24 | + "crates/calciforge/src/channels/telegram.rs" => 2088, |
| 25 | + "crates/calciforge/src/channels/whatsapp.rs" => 1291, |
| 26 | + "crates/calciforge/src/commands.rs" => 4889, |
| 27 | + "crates/calciforge/src/config.rs" => 2364, |
| 28 | + "crates/calciforge/src/config/validator.rs" => 2419, |
| 29 | + "crates/calciforge/src/doctor.rs" => 3580, |
| 30 | + "crates/calciforge/src/install/cli.rs" => 1071, |
| 31 | + "crates/calciforge/src/install/executor.rs" => 3681, |
| 32 | + "crates/calciforge/src/install/linux_hardening.rs" => 751, |
| 33 | + "crates/calciforge/src/install/model.rs" => 819, |
| 34 | + "crates/calciforge/src/install/ssh.rs" => 1124, |
| 35 | + "crates/calciforge/src/install/wizard.rs" => 701, |
| 36 | + "crates/calciforge/src/providers/alloy.rs" => 1126, |
| 37 | + "crates/calciforge/src/proxy/gateway.rs" => 1001, |
| 38 | + "crates/calciforge/src/proxy/handlers.rs" => 2386, |
| 39 | + "crates/host-agent/src/main.rs" => 1288, |
| 40 | + "crates/paste-server/src/lib.rs" => 2623, |
| 41 | + "crates/security-proxy/src/mitm.rs" => 1573, |
| 42 | + "crates/security-proxy/src/proxy.rs" => 2295, |
| 43 | + "crates/security-proxy/src/substitution.rs" => 912, |
| 44 | + "crates/secrets-client/src/fnox_client.rs" => 787 |
| 45 | +}.freeze |
| 46 | + |
| 47 | +WATCH_PATTERNS = { |
| 48 | + "Arc<Mutex" => /Arc<Mutex/, |
| 49 | + "Arc<RwLock" => /Arc<RwLock/, |
| 50 | + "HashMap<String, String>" => /HashMap\s*<\s*String\s*,\s*String\s*>/, |
| 51 | + "Vec<String>" => /Vec\s*<\s*String\s*>/, |
| 52 | + "tokio::spawn" => /tokio::spawn\s*\(/, |
| 53 | + "positional indexing" => /\[[0-9]+\]/, |
| 54 | + "unsafe block" => /unsafe\s*\{/ |
| 55 | +}.freeze |
| 56 | + |
| 57 | +def rust_files |
| 58 | + files = [] |
| 59 | + Find.find(ROOT.join("crates").to_s) do |path| |
| 60 | + next unless path.end_with?(".rs") |
| 61 | + next if path.include?("/target/") |
| 62 | + |
| 63 | + files << Pathname.new(path) |
| 64 | + end |
| 65 | + files.sort |
| 66 | +end |
| 67 | + |
| 68 | +def relative(path) |
| 69 | + path.relative_path_from(ROOT).to_s |
| 70 | +end |
| 71 | + |
| 72 | +failed = false |
| 73 | +pattern_counts = Hash.new(0) |
| 74 | + |
| 75 | +rust_files.each do |file| |
| 76 | + rel = relative(file) |
| 77 | + text = file.read |
| 78 | + line_count = text.lines.count |
| 79 | + budget = RUST_LINE_BUDGETS.fetch(rel, MAX_NEW_RUST_LINES) |
| 80 | + |
| 81 | + if line_count > budget |
| 82 | + warn "#{rel}: #{line_count} lines exceeds architecture budget #{budget}" |
| 83 | + failed = true |
| 84 | + end |
| 85 | + |
| 86 | + WATCH_PATTERNS.each do |name, regex| |
| 87 | + pattern_counts[name] += text.scan(regex).count |
| 88 | + end |
| 89 | +end |
| 90 | + |
| 91 | +missing_budget_files = RUST_LINE_BUDGETS.keys.reject { |path| ROOT.join(path).file? } |
| 92 | +unless missing_budget_files.empty? |
| 93 | + warn "architecture budget references missing files:" |
| 94 | + missing_budget_files.each { |path| warn " #{path}" } |
| 95 | + failed = true |
| 96 | +end |
| 97 | + |
| 98 | +puts "Architecture ratchets:" |
| 99 | +puts " max lines for new Rust modules: #{MAX_NEW_RUST_LINES}" |
| 100 | +puts " pinned large-module budgets: #{RUST_LINE_BUDGETS.length}" |
| 101 | +puts " watched pattern counts:" |
| 102 | +pattern_counts.sort.each do |name, count| |
| 103 | + puts " #{name}: #{count}" |
| 104 | +end |
| 105 | + |
| 106 | +abort("architecture ratchets failed") if failed |
0 commit comments