#!/usr/bin/env ruby # frozen_string_literal: true # audit-stats — deterministic run-level driver for the orchestration IRL audit. # # Enumerates production session transcripts modified since a given date, # reduces each to a delegation fact-sheet (audit/bin/extract), and writes a # run directory under the audit state dir (~/.local/state/os-context/audit): # # run-/stats.tsv one row per audited session (deterministic columns) # run-/flags.md heuristic missed-delegation candidates, per session, # for auditor judgment (mechanical vs judgment-dependent) # run-/sheets/ full fact-sheet JSON per session # # and appends one row to metrics.tsv at the state-dir root — the trend ledger # that the os-status due-check and run-over-run comparison read. Everything in # this file is code-only: no model involvement, reproducible from transcripts. # # Usage: audit-stats [--since YYYY-MM-DD] [--exclude SESSION_ID] [--state-dir DIR] # [--projects-dir DIR] [--dry-run] # # --since defaults to the date of the last metrics.tsv row; the first run must # pass it explicitly. Transcripts written to within the last 10 minutes are # treated as active sessions and deferred to the next run. require "date" require "fileutils" require "json" require "optparse" # `extract` is extensionless (a CLI); its __FILE__ guard makes load safe here. load File.expand_path("extract", __dir__) module OrchAudit DEFAULT_STATE_DIR = File.expand_path("~/.local/state/os-context/audit") DEFAULT_PROJECTS_DIR = File.expand_path("~/.claude/projects") class TranscriptSet # Non-production sessions: eval sandboxes (-tmp-orch-*), headless runs # spawned from session scratchpads (-tmp-claude-*), and sessions whose # cwd was an eval harness directory (…-eval). EXCLUDED_PROJECT_DIRS = [/-tmp-orch-/, /-tmp-claude-/, /-eval\z/].freeze ACTIVE_WINDOW_SECONDS = 600 def initialize(projects_dir:, since:, excluded_sessions: [], now: Time.now) @projects_dir = projects_dir @since = since @excluded_sessions = excluded_sessions @now = now end def paths Dir.glob(File.join(@projects_dir, "*", "*.jsonl")).sort.select { |p| candidate?(p) } end private def candidate?(path) return false if EXCLUDED_PROJECT_DIRS.any? { |re| File.dirname(path).match?(re) } return false if @excluded_sessions.include?(File.basename(path, ".jsonl")) mtime = File.mtime(path) mtime.to_date >= @since && (@now - mtime) > ACTIVE_WINDOW_SECONDS end end class SessionStats MIN_ASSISTANT_TURNS = 8 def initialize(path) @path = path @sheet = FactSheet.new(path).data end attr_reader :path, :sheet def session_id = File.basename(@path, ".jsonl") def project = File.basename(File.dirname(@path)) def trivial? = sheet[:metadata][:assistant_turns] < MIN_ASSISTANT_TURNS def spawns = sheet[:agent_spawns] def flagged_runs = sheet[:missed_delegation_candidates] def spawns_missing_model spawns.count { |s| !s[:model_explicit] } end # A pin mismatch is deterministic evidence of the harness ignoring an # explicit model param (the 2026-07-10 finding): param present, resolved # model known, and the resolved id does not contain the requested tier. def model_pin_mismatches spawns.count do |s| s[:model_explicit] && s[:model_param] && s[:resolved_model] && !s[:resolved_model].to_s.include?(s[:model_param].to_s) end end def sendmessage_followups spawns.sum { |s| s[:rounds] - 1 } end def row m = sheet[:metadata] [ session_id, project, m[:assistant_turns], m[:main_loop_output_tokens], m[:main_loop_share], spawns.size, spawns_missing_model, model_pin_mismatches, sendmessage_followups, flagged_runs.size ] end end class AuditRun STATS_HEADER = %w[ session project assistant_turns main_loop_output_tokens main_loop_share spawns spawns_missing_model model_pin_mismatches sendmessage_followups flagged_runs ].freeze LEDGER_HEADER = %w[ date since sessions trivial_skipped spawns spawns_missing_model model_pin_mismatches sendmessage_followups flagged_runs median_main_loop_share ].freeze def initialize(state_dir:, projects_dir:, since:, excluded_sessions:, today: Date.today) @state_dir = state_dir @since = since @today = today @transcripts = TranscriptSet.new( projects_dir: projects_dir, since: since, excluded_sessions: excluded_sessions ) end def ledger_path = File.join(@state_dir, "metrics.tsv") def run_dir = File.join(@state_dir, "run-#{@today.iso8601}") def plan @transcripts.paths end def execute audited, trivial = partition_sessions write_run_dir(audited) append_ledger(audited, trivial) { run_dir: run_dir, audited: audited.size, trivial: trivial.size } end private def partition_sessions @transcripts.paths.map { |p| SessionStats.new(p) }.partition { |s| !s.trivial? } end def write_run_dir(audited) sheets_dir = File.join(run_dir, "sheets") FileUtils.mkdir_p(sheets_dir) File.write(File.join(run_dir, "stats.tsv"), stats_tsv(audited)) File.write(File.join(run_dir, "flags.md"), flags_md(audited)) audited.each do |s| File.write(File.join(sheets_dir, "#{s.session_id}.json"), JSON.pretty_generate(s.sheet)) end end def stats_tsv(audited) lines = [STATS_HEADER.join("\t")] audited.each { |s| lines << s.row.join("\t") } lines.join("\n") + "\n" end def flags_md(audited) md = +"# Flagged regions for auditor judgment — run #{@today.iso8601}\n\n" md << "Heuristic candidates only; sequential-dependent work is a valid reason not\n" md << "to delegate. Auditors judge mechanical vs judgment-dependent per region.\n" audited.each do |s| md << "\n## #{s.session_id} (#{s.project})\n\n" if s.flagged_runs.empty? md << "No flagged runs.\n" next end s.flagged_runs.each do |m| md << "- **#{m[:tool]} x#{m[:length]}** (jsonl lines #{m[:lines].join('-')}, " md << "#{m[:distinct_targets]} distinct targets)\n" m[:targets_sample].each { |t| md << " - `#{t}`\n" } end end md end def append_ledger(audited, trivial) FileUtils.mkdir_p(@state_dir) File.write(ledger_path, LEDGER_HEADER.join("\t") + "\n") unless File.exist?(ledger_path) File.open(ledger_path, "a") { |f| f.puts(ledger_row(audited, trivial).join("\t")) } end def ledger_row(audited, trivial) [ @today.iso8601, @since.iso8601, audited.size, trivial.size, audited.sum { |s| s.spawns.size }, audited.sum(&:spawns_missing_model), audited.sum(&:model_pin_mismatches), audited.sum(&:sendmessage_followups), audited.sum { |s| s.flagged_runs.size }, median(audited.map { |s| s.sheet[:metadata][:main_loop_share] }) ] end def median(values) return 0 if values.empty? sorted = values.sort mid = sorted.size / 2 (sorted.size.odd? ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2.0).round(3) end end class Ledger def self.last_run_date(path) return nil unless File.exist?(path) last = File.readlines(path).map(&:strip).reject(&:empty?)[1..]&.last last && (Date.iso8601(last.split("\t").first) rescue nil) end end end if $PROGRAM_NAME == __FILE__ options = { state_dir: OrchAudit::DEFAULT_STATE_DIR, projects_dir: OrchAudit::DEFAULT_PROJECTS_DIR, exclude: [], dry_run: false } OptionParser.new do |o| o.banner = "Usage: audit-stats [--since YYYY-MM-DD] [--exclude SESSION_ID] [options]" o.on("--since DATE") { |d| options[:since] = Date.iso8601(d) } o.on("--exclude ID", "Session id to skip (repeatable)") { |id| options[:exclude] << id } o.on("--state-dir DIR") { |d| options[:state_dir] = File.expand_path(d) } o.on("--projects-dir DIR") { |d| options[:projects_dir] = File.expand_path(d) } o.on("--dry-run", "List transcripts that would be audited, then exit") { options[:dry_run] = true } end.parse! since = options[:since] || OrchAudit::Ledger.last_run_date(File.join(options[:state_dir], "metrics.tsv")) abort "No --since given and no prior ledger row to infer it from" unless since run = OrchAudit::AuditRun.new( state_dir: options[:state_dir], projects_dir: options[:projects_dir], since: since, excluded_sessions: options[:exclude] ) if options[:dry_run] puts run.plan exit 0 end result = run.execute puts "Audited #{result[:audited]} sessions (#{result[:trivial]} trivial skipped)" puts "Run dir: #{result[:run_dir]}" puts "Ledger: #{run.ledger_path}" end