From 127be22f891258020aa5eff1bf36032ceb845bd5 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sun, 5 Jul 2026 17:02:40 -0400 Subject: [PATCH] feat(audit): meta-tooling duration analyzer (8 modules, 10-section renderer) - scripts/audit/analyze_meta_tooling_durations.py (~700 lines) - tests/test_analyze_meta_tooling_durations.py (20 unit tests, all pass) - Era detector with multi-signal weighted-vote clustering - Track indexer reusing git-history-evidence primitives - 3 op-layer indexers (sub-agent tasks, work-prefix commits, session logs) - Statistician with per-era aggregations - 10 Markdown section renderers - CLI: --json-only, --output-dir, --era-window-days, --date Strictly Meta-Tooling domain (docs/guide_meta_boundary.md). Zero src/ changes; zero GUI changes; zero Application-domain impact. Part of conductor/tracks/meta_tooling_duration_analysis_20260705/. --- .../audit/analyze_meta_tooling_durations.py | 1254 +++++++++++++++++ tests/test_analyze_meta_tooling_durations.py | 227 +++ 2 files changed, 1481 insertions(+) create mode 100644 scripts/audit/analyze_meta_tooling_durations.py create mode 100644 tests/test_analyze_meta_tooling_durations.py diff --git a/scripts/audit/analyze_meta_tooling_durations.py b/scripts/audit/analyze_meta_tooling_durations.py new file mode 100644 index 00000000..99571bd1 --- /dev/null +++ b/scripts/audit/analyze_meta_tooling_durations.py @@ -0,0 +1,1254 @@ +#!/usr/bin/env python3 +"""One-shot meta-tooling duration analyzer. + +Walks git history + sub-agent task logs + session logs to produce a +per-era, per-op duration analysis of the meta-tooling that built this +codebase. Emits a 10-section Markdown report + JSON source-of-truth. + +Strictly Meta-Tooling domain (per docs/guide_meta_boundary.md). No +src/ changes; no GUI changes; no Application-domain impact. + +Usage: + uv run python scripts/audit/analyze_meta_tooling_durations.py + uv run python scripts/audit/analyze_meta_tooling_durations.py --json-only + uv run python scripts/audit/analyze_meta_tooling_durations.py --output-dir + uv run python scripts/audit/analyze_meta_tooling_durations.py --era-window-days 7 +""" +from __future__ import annotations + +import argparse +import json +import re +import statistics +import subprocess +import sys +from collections import Counter, defaultdict +from dataclasses import dataclass, field +from datetime import date, datetime, timedelta +from pathlib import Path +from typing import Final + +# 1-space indent throughout per conductor/code_styleguides/python.md §1 + +# --------------------------------------------------------------------------- +# Dataclasses (per conductor/code_styleguides/data_oriented_design.md §8.5) +# --------------------------------------------------------------------------- + + +@dataclass(frozen=True, slots=True) +class Evidence: + """A single piece of evidence pointing to an era boundary.""" + + date: date + weight: str + source: str + note: str + + +@dataclass(frozen=True, slots=True) +class EraBoundary: + """One era in the project's LLM-tooling history.""" + + index: int + start: date + end: date | None + name: str + evidence: list[Evidence] = field(default_factory=list) + + +@dataclass(frozen=True, slots=True) +class TrackRow: + """One track's duration and scope, era-bucketed.""" + + track_id: str + init_sha: str + end_sha: str + init_date: date + end_date: date + duration_days: int + commit_count: int + files_touched: int + era_index: int + status: str + + +@dataclass(frozen=True, slots=True) +class SubAgentTaskOp: + """One sub-agent task log entry (Tier 3 or Tier 4 or Tier 1).""" + + timestamp: datetime + tier: str + log_path: str + duration_s: float + in_track: str | None + era_index: int + + +@dataclass(frozen=True, slots=True) +class CommitPrefixOp: + """One work-prefix commit.""" + + sha: str + date: date + prefix: str + scope: str + message: str + in_track: str | None + era_index: int + + +@dataclass(frozen=True, slots=True) +class SessionOp: + """One chat session (logs//).""" + + session_name: str + start_dt: datetime + end_dt: datetime + duration_min: float + task_count: int + era_index: int + + +@dataclass(frozen=True, slots=True) +class EraStats: + """Aggregate stats for one era.""" + + era: EraBoundary + track_count: int + track_duration_median: float + track_duration_p25: float + track_duration_p75: float + track_commits_median: float + track_files_median: float + subagent_task_count: int + subagent_task_duration_median: float + commit_count: int + commit_count_by_prefix: dict[str, int] + session_count: int + session_duration_median: float + track_less_op_count: int + + +# --------------------------------------------------------------------------- +# Constants +# --------------------------------------------------------------------------- + +_REPO_ROOT: Final[Path] = Path(__file__).resolve().parents[2] +_TRACKS_DIR: Final[Path] = _REPO_ROOT / "conductor" / "tracks" +_ARCHIVE_DIR: Final[Path] = _REPO_ROOT / "conductor" / "archive" +_LOGS_AGENTS_DIR: Final[Path] = _REPO_ROOT / "logs" / "agents" +_LOGS_DIR: Final[Path] = _REPO_ROOT / "logs" + +_DEFAULT_OUTPUT_DIR: Final[Path] = _REPO_ROOT / "docs" / "reports" +_DEFAULT_ARTIFACT_DIR: Final[Path] = _REPO_ROOT / "tests" / "artifacts" + +_DEFAULT_ERA_WINDOW_DAYS: Final[int] = 7 +_DEFAULT_DATE: Final[date] = date(2026, 7, 5) + +_WORK_PREFIX_RE: Final[re.Pattern[str]] = re.compile( + r"^(?Pfeat|fix|refactor|perf|test)(?:\((?P[^)]*)\))?:\s" +) +_SESSION_NAME_RE: Final[re.Pattern[str]] = re.compile( + r"^(?P\d{8})_(?P