mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 23:07:03 +01:00
The transcript analyzer behind /compile-candidate lived only in the working tree. Untracked meant unlinted — pre-commit only inspects files handed to it — so nothing had ever checked it, and it had been abandoned mid-refactor with every parse raising AttributeError: '_Accumulator' object has no attribute 'first_timestamp'. The accumulator had been moved onto the grouped records dataclasses, but three increment sites and parse_session still read flat attributes that no longer existed. parse_session now hands over acc.meta and acc.counts directly; only subagent_count comes from elsewhere, since no transcript line carries it. Two modules broke the 500-line limit, so they were split along seams that were already there. signatures.py takes the text-signature helpers — traces.py was importing them from parse.py, making parse an accidental hub, and now neither imports the other. stats.py takes the pure number and time helpers. parse.py 521 -> 458, detectors.py 559 -> 467. The structural lint findings were refactored, not configured away. _counter_candidates took eight arguments; the five naming which counter field to mine and at what thresholds became a frozen _CounterSpec, so both call sites are one line. _extract_skill carried in_span and a line budget as loop variables across thirteen branches; a _SpanBuffer owns that state now, leaving the walk at four. record() took twelve flat kwargs and now takes one override per record block, with session_meta() so a partial meta override keeps the standard timestamp — a bare SessionMeta(slug=...) would have nulled started_at and quietly changed the per-week arithmetic. All 36 call sites rewritten by an AST pass. tests/factories.py moved into conftest.py, where every other package here keeps shared builders and where name-tests-test does not object. 965 tests pass at 100% statement and branch coverage, every hook is green, and no suppression was added anywhere. `candidates` and `traces skill-finish` both run against the real corpus. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
195 lines
7.0 KiB
Python
195 lines
7.0 KiB
Python
"""Tests for the CLI, config env overrides, and the module entry point."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import runpy
|
|
import sys
|
|
from typing import TYPE_CHECKING
|
|
|
|
import pytest
|
|
|
|
from python_pkg.session_autopsy import cli, config, report, store
|
|
from python_pkg.session_autopsy.records import Observations
|
|
from python_pkg.session_autopsy.tests.conftest import (
|
|
FileFacts,
|
|
assistant_line,
|
|
bash_block,
|
|
invocation,
|
|
prompt_line,
|
|
record,
|
|
skill_block,
|
|
text_block,
|
|
write_transcript,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
from python_pkg.session_autopsy.tests.conftest import AutopsyEnv
|
|
|
|
|
|
def test_config_env_overrides(autopsy_env: AutopsyEnv) -> None:
|
|
"""Both locations follow their environment variables."""
|
|
assert config.autopsy_home() == autopsy_env.home
|
|
assert config.projects_dir() == autopsy_env.projects
|
|
|
|
|
|
def test_config_defaults(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
"""Without overrides, both locations live under ~/.claude."""
|
|
monkeypatch.delenv("CLAUDE_AUTOPSY_HOME", raising=False)
|
|
monkeypatch.delenv("CLAUDE_PROJECTS_DIR", raising=False)
|
|
assert config.autopsy_home().name == "autopsy"
|
|
assert config.projects_dir().name == "projects"
|
|
|
|
|
|
def _seed_transcript(env: AutopsyEnv, session_id: str = "sess-1") -> Path:
|
|
"""Write a small but representative transcript into the projects dir."""
|
|
return write_transcript(
|
|
env.projects,
|
|
session_id,
|
|
[
|
|
assistant_line(skill_block("finish")),
|
|
assistant_line(bash_block("git status"), text_block("checking")),
|
|
prompt_line("wrap it up please"),
|
|
],
|
|
)
|
|
|
|
|
|
def test_ingest_missing_file(
|
|
autopsy_env: AutopsyEnv, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""A nonexistent transcript exits 2 with a stderr message."""
|
|
assert (
|
|
cli.main(["ingest", str(autopsy_env.projects / "nope.jsonl")])
|
|
== cli.EXIT_NO_FILE
|
|
)
|
|
assert "no such transcript" in capsys.readouterr().err
|
|
|
|
|
|
def test_ingest_and_report_regeneration(
|
|
autopsy_env: AutopsyEnv, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""ingest upserts, prints a summary, and regenerates REPORT.md."""
|
|
transcript = _seed_transcript(autopsy_env)
|
|
assert cli.main(["ingest", str(transcript), "--no-report"]) == 0
|
|
out = capsys.readouterr().out
|
|
assert "sess-1" in out
|
|
assert not (autopsy_env.home / report.REPORT_FILE).exists()
|
|
assert cli.main(["ingest", str(transcript), "--quiet"]) == 0
|
|
assert capsys.readouterr().out == ""
|
|
assert (autopsy_env.home / report.REPORT_FILE).is_file()
|
|
|
|
|
|
def test_scan_skip_force_and_nonfile(
|
|
autopsy_env: AutopsyEnv, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""scan analyzes changed transcripts, skips unchanged, honors --force."""
|
|
_seed_transcript(autopsy_env, "s1")
|
|
_seed_transcript(autopsy_env, "s2")
|
|
(autopsy_env.projects / "-home-kuhy" / "dir.jsonl").mkdir()
|
|
assert cli.main(["scan", "--all", "--jobs", "0", "--no-report"]) == 0
|
|
assert "2 analyzed, 0 unchanged" in capsys.readouterr().out
|
|
assert cli.main(["scan", "--no-report"]) == 0
|
|
assert "0 analyzed, 2 unchanged" in capsys.readouterr().out
|
|
assert cli.main(["scan", "--force"]) == 0
|
|
assert "2 analyzed" in capsys.readouterr().out
|
|
|
|
|
|
def test_needs_analysis_on_size_change(autopsy_env: AutopsyEnv) -> None:
|
|
"""A stored (size, mtime) mismatch marks the transcript changed."""
|
|
transcript = _seed_transcript(autopsy_env)
|
|
index = {str(transcript): (transcript.stat().st_size, transcript.stat().st_mtime)}
|
|
assert not cli._needs_analysis(transcript, index)
|
|
index[str(transcript)] = (1, 1.0)
|
|
assert cli._needs_analysis(transcript, index)
|
|
|
|
|
|
def test_parse_one_swallows_oserror(
|
|
autopsy_env: AutopsyEnv, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""A vanished transcript logs to stderr and yields None."""
|
|
assert cli._parse_one(autopsy_env.projects / "gone.jsonl") is None
|
|
assert "skipping" in capsys.readouterr().err
|
|
|
|
|
|
def test_regenerate_report_with_preloaded_records(autopsy_env: AutopsyEnv) -> None:
|
|
"""The records argument skips the store read."""
|
|
assert cli._regenerate_report([]) == 0
|
|
|
|
|
|
def _seed_store(env: AutopsyEnv) -> None:
|
|
"""Store records that produce one deterministic skill candidate."""
|
|
transcript = _seed_transcript(env, "t1")
|
|
store.upsert_records(
|
|
env.home,
|
|
[
|
|
record(
|
|
"t1",
|
|
file=FileFacts(path=str(transcript)),
|
|
obs=Observations(skill_invocations=[invocation()]),
|
|
),
|
|
record("t2", obs=Observations(skill_invocations=[invocation()])),
|
|
record("t3", obs=Observations(skill_invocations=[invocation()])),
|
|
],
|
|
)
|
|
|
|
|
|
def test_report_and_mark_reviewed(
|
|
autopsy_env: AutopsyEnv, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""report writes the file; --mark-reviewed zeroes the badge count."""
|
|
_seed_store(autopsy_env)
|
|
assert cli.main(["report"]) == 0
|
|
assert "candidates" in capsys.readouterr().out
|
|
assert cli.main(["report", "--mark-reviewed"]) == 0
|
|
assert "marked reviewed" in capsys.readouterr().out
|
|
state = json.loads(
|
|
(autopsy_env.home / report.STATE_FILE).read_text(encoding="utf-8")
|
|
)
|
|
assert state["unreviewed_count"] == 0
|
|
|
|
|
|
def test_candidates_json_and_plain(
|
|
autopsy_env: AutopsyEnv, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""candidates renders both machine and human output."""
|
|
_seed_store(autopsy_env)
|
|
assert cli.main(["candidates", "--json"]) == 0
|
|
payload = json.loads(capsys.readouterr().out)
|
|
assert payload[0]["id"] == "skill-finish"
|
|
assert cli.main(["candidates"]) == 0
|
|
assert "skill-finish" in capsys.readouterr().out
|
|
|
|
|
|
def test_traces_unknown_and_output_modes(
|
|
autopsy_env: AutopsyEnv, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""traces exits 3 on unknown ids and writes --out files."""
|
|
_seed_store(autopsy_env)
|
|
assert cli.main(["traces", "nope-123"]) == cli.EXIT_UNKNOWN_ID
|
|
assert "unknown candidate id" in capsys.readouterr().err
|
|
out_file = tmp_path / "traces.txt"
|
|
assert cli.main(["traces", "skill-finish", "--out", str(out_file)]) == 0
|
|
assert "TOOL Skill | finish" in out_file.read_text(encoding="utf-8")
|
|
assert cli.main(["traces", "skill-finish", "--max-invocations", "1"]) == 0
|
|
assert "traces skill-finish" in capsys.readouterr().out
|
|
|
|
|
|
def test_measure_command(
|
|
autopsy_env: AutopsyEnv, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""measure prints the placeholder when nothing is compiled."""
|
|
assert cli.main(["measure"]) == 0
|
|
assert "nothing compiled yet" in capsys.readouterr().out
|
|
|
|
|
|
def test_main_module_entry(
|
|
autopsy_env: AutopsyEnv, monkeypatch: pytest.MonkeyPatch
|
|
) -> None:
|
|
"""python -m python_pkg.session_autopsy dispatches and exits."""
|
|
monkeypatch.setattr(sys, "argv", ["session_autopsy", "measure"])
|
|
with pytest.raises(SystemExit) as excinfo:
|
|
runpy.run_module("python_pkg.session_autopsy", run_name="__main__")
|
|
assert excinfo.value.code == 0
|