---------------------------------------------------------------------- This is the API documentation for the cc_pushback library. ---------------------------------------------------------------------- ## Classes Core classes FeedbackStore(store: 'FileStateStore') -> 'None' Persistent store for collected feedback over a :class:`FileStateStore`. Layers the ``feedback_events`` table (extended with the ``origin_path`` display-hint column) onto cc-transcript's file-mtime ledger and adds the ``triage`` verdict table (the judge package's verdict mechanism pinned to cc-pushback's column names), the ``refinement`` table, the ``pair_evidence`` table, and the ``accepted_pushback``, ``pair_evidence_latest``, and ``refined_pairs`` views. Verdicts, refinements, and evidence key on the content-derived dedup key, so they survive a database rebuild. Example: >>> async with await FeedbackStore.open(FeedbackStore.default_path()) as store: ... await store.record_file_scan(str(path), mtime, candidates) ## FeedbackStore Methods Methods for the FeedbackStore class default_path() -> 'Path' Returns the default database path, ``~/.cc-pushback/feedback.db``. open(path: 'Path') -> 'Self' Opens (creating if needed) the feedback database at ``path``. record_file_scan(self, path: 'str', mtime: 'float', candidates: 'Sequence[FeedbackCandidate]') -> 'int' Records a scanned file and its candidates in one transaction. The platform store's ingestion, plus the scanned path lands in each row's ``origin_path`` — a display hint only (the dashboard's project labels); transcript resolution always goes through discovery by session UUID. Args: path: The scanned file's path. mtime: The file's modification time at scan. candidates: The candidates extracted from the file. Returns: The number of newly inserted feedback events. unrefined(self, *, prompt_version: 'int', model: 'str', limit: 'int | None' = None) -> 'list[dict[str, object]]' Returns accepted pushback events lacking a refinement at ``(prompt_version, model)``. Surfaces the columns the refine prompt needs — ``dedup_key``, ``source_kind``, ``text``, ``context_json``, and the judge's ``what_claude_did`` hint — oldest first. Args: prompt_version: The refine prompt version the refinement must carry. model: The resolved model name the refinement must carry. limit: When set, the maximum number of rows to return. Returns: One dict per accepted, unrefined event. record_refinement(self, key: 'DedupKey', refinement: 'Refinement', *, prompt_version: 'int', model: 'str') -> 'None' Records one event's atomic refined pairs in a single transaction. Keyed by ``(dedup_key, prompt_version, model, pair_index)`` so re-running over a fully refined corpus is a no-op and every pair of one event commits together. Args: key: The refined event's dedup key. refinement: The atomic pairs to persist. prompt_version: The refine prompt version that produced them. model: The resolved model name that produced them. unenriched(self, *, enrich_version: 'int', enrich_model: 'str', extractor_version: 'int', limit: 'int | None' = None) -> 'list[dict[str, object]]' Returns refined pairs lacking code evidence at the given enrich generation. A pair counts as enriched when it carries its own ``pair_evidence`` row at exactly ``(enrich_version, enrich_model, extractor_version)`` or its refine generation carries the ``pair_index=-1`` no-code sentinel there. Pairs come from the latest refine generation only, so a refine re-run resurfaces its new pairs here automatically — and so does bumping any version in the key. Args: enrich_version: The enrich prompt version the evidence must carry. enrich_model: The resolved model name the evidence must carry. extractor_version: The platform's deterministic-extraction version. limit: When set, the maximum number of rows to return. Returns: One dict per unenriched pair with the columns the enrich prompt and anchor resolution need, oldest event first. record_evidence(self, key: 'DedupKey', evidence: 'CodeEvidence', *, refine_version: 'int', refine_model: 'str', pair_index: 'int', enrich_version: 'int', enrich_model: 'str', extractor_version: 'int', source: 'Source | None') -> 'None' Records one pair's code evidence, idempotently, under the full generation key. Keyed by ``(dedup_key, refine_version, refine_model, pair_index, enrich_version, enrich_model, extractor_version)`` with ``INSERT OR IGNORE``, so re-running over an enriched corpus is a no-op and bumping any version in the key re-derives. ``pair_index=-1`` encodes the no-code sentinel covering every pair of the refine generation. Args: key: The enriched event's dedup key. evidence: The code evidence to persist. refine_version: The refine prompt version of the annotated pair. refine_model: The resolved refine model of the annotated pair. pair_index: The annotated pair's index, or ``-1`` for the sentinel. enrich_version: The enrich prompt version that produced the evidence. enrich_model: The resolved model name that produced it. extractor_version: The platform's deterministic-extraction version. source: Where the correction came from, or None when there is none. pairs(self) -> 'list[dict[str, object]]' Returns every row of the ``refined_pairs`` view, the pipeline's deliverable. candidates(self) -> 'list[dict[str, object]]' Returns one row per event with its latest judge verdict and refine summary. Powers the dashboard's candidate view across every pipeline status — refined, accepted-but-unrefined, judge-rejected noise, and unjudged. The verdict and refine columns are ``NULL`` for events that have not reached that stage. Returns: One dict per event: the event columns plus the latest judge verdict (``category``, ``is_pushback``, ``confidence``, ``judge_version``, ``judge_model``, ``what_claude_did``), the latest auditor side (``auditor_is_pushback``), the judge ``flipped`` flag, and the refine summary (``pair_count``, ``refine_version``, ``refine_model``). lineage(self, dedup_key: 'str') -> 'dict[str, object]' Returns one event with all its triage verdicts and latest refined pairs. Reads ``feedback_events``, ``triage``, and ``refinement`` directly — the views drop the auditor, the older judge versions, and the payload the lineage needs. Args: dedup_key: The event's content-derived key. Returns: The event columns plus ``verdicts`` (every judge and auditor row, oldest first) and ``pairs`` (the latest refinement generation, by ``pair_index``), or ``{}`` when no event carries the key. triage_stats(self, *, prompt_version: 'int') -> 'TriageStats' Returns triage coverage and acceptance at ``prompt_version``. ## Dataclasses Data-holding classes MigrationReport(migrated: 'int', skipped: 'int') -> None The outcome of one ``migrate-corpus`` pass. Attributes: migrated: How many rows were converted to the new context schema. skipped: How many rows already carried it. ScanReport(scanned: 'int', inserted: 'int') -> None The outcome of one scan pass. Attributes: scanned: The number of transcripts parsed and recorded. inserted: The number of newly inserted feedback events. ## Functions Public functions detect(events: 'Sequence[TranscriptEvent]') -> 'list[FeedbackCandidate]' Runs every detector over one transcript's events. Args: events: The transcript's full ordered event stream. Returns: Every feedback candidate the detectors found, in detector order. ## Async Functions Asynchronous functions migrate_corpus(store: 'FeedbackStore') -> 'MigrationReport' Converts a legacy corpus in place, idempotently. Adds the ``event_uuid`` column (backfilled from the legacy ``origin_uuid``) and the ``triage.fidelity`` column (existing verdicts were judged on baked summaries, so they default to ``'summary'`` and stay re-judgeable via ``triage --refresh-summary``), then rewrites every legacy ``context_json`` snapshot into a ``cc-transcript.context/1`` document. Args: store: The open feedback store to migrate. Returns: The pass's migrated/skipped row counts. ## Other Additional exports scan(store: 'FeedbackStore', roots: 'Sequence[Path]', *, full: 'bool' = False) -> 'ScanReport' Scans transcripts under ``roots`` for feedback, incrementally. Each transcript is parsed only when new or modified since the last scan (unless ``full``), parsing runs concurrently across files, and every candidate is inserted idempotently. A transcript that fails to parse — for example one Claude Code is still appending to — is silently skipped by the parser and left unrecorded, so the next scan retries it. Args: store: The store to read mtimes from and write candidates to. roots: The directories to search recursively for transcripts. full: When set, re-scan every transcript, ignoring recorded mtimes. Returns: The :class:`ScanReport` for this pass. ---------------------------------------------------------------------- This is the CLI documentation for the package. ---------------------------------------------------------------------- ## CLI: cc-pushback ``` Usage: cc-pushback [OPTIONS] COMMAND [ARGS]... Collect developer pushback signals from existing Claude Code transcripts. Options: --version Show the version and exit. --help Show this message and exit. Commands: audit Audit a seeded stratified sample of the current prompt... enrich Ground every refined pair in the code it complains about. eval Compute the mechanical metrics for the current prompt... list List recent feedback events, newest first. migrate-corpus Convert a pre-2.0 corpus in place to the cc-transcript... pairs Print the refined training pairs — the pipeline's... refine Refine every accepted pushback event into atomic... scan Scan transcripts for feedback, incrementally. stats Print ingestion counts by source kind and triage coverage. triage Judge every stored candidate lacking a verdict at the... view-samples Serve the training-pairs dashboard: refined pairs and... ``` ### cc-pushback scan ``` Usage: cc-pushback scan [OPTIONS] Scan transcripts for feedback, incrementally. Each transcript is parsed only when new or modified since the last scan, and every candidate is inserted with ``INSERT OR IGNORE`` keyed by a content digest, so re-running ``scan`` over unchanged inputs is a no-op. Recording a file and inserting its candidates commit in one transaction. Options: --transcripts DIRECTORY Transcript directories to scan. Defaults to ~/.claude/projects. --full Re-scan every transcript, ignoring recorded mtimes. --db FILE Database path. Defaults to ~/.cc- pushback/feedback.db. --help Show this message and exit. ``` ### cc-pushback stats ``` Usage: cc-pushback stats [OPTIONS] Print ingestion counts by source kind and triage coverage. Options: --db FILE Database path. Defaults to ~/.cc-pushback/feedback.db. --help Show this message and exit. ``` ### cc-pushback list ``` Usage: cc-pushback list [OPTIONS] List recent feedback events, newest first. Options: --source [transcript_message|plan_review|interrupt_rejection|review_comment] Restrict to one source kind. --limit INTEGER Maximum events to show. [default: 20] --db FILE Database path. Defaults to ~/.cc- pushback/feedback.db. --help Show this message and exit. ``` ### cc-pushback triage ``` Usage: cc-pushback triage [OPTIONS] Judge every stored candidate lacking a verdict at the current prompt version. Incremental and idempotent: verdicts persist per row as soon as each call completes, failed rows stay pending and are retried on the next run, and re- running over a fully judged corpus is a no-op. With ``--refresh-summary``, rows judged at summary fidelity are re-judged; a full-fidelity verdict replaces the summary one once the row's window hydrates again. Options: --model [small|medium|large] Judge model tier. [default: medium] --limit INTEGER Judge at most this many rows this pass. --concurrency INTEGER Maximum concurrent claude subshells. [default: 8] --refresh-summary Also re-judge rows whose verdict was recorded at summary fidelity. --db FILE Database path. Defaults to ~/.cc- pushback/feedback.db. --help Show this message and exit. ``` ### cc-pushback audit ``` Usage: cc-pushback audit [OPTIONS] Audit a seeded stratified sample of the current prompt version's verdicts. The auditor is a stronger model, blind to the judge's verdicts; its labels are keyed independently of the judge's prompt version, so they accumulate across iterations and re-auditing a sampled row costs nothing. Options: --accepts INTEGER Audit budget for judge-accepted rows. [default: 60] --rejects INTEGER Audit budget for judge-rejected rows. [default: 60] --seed INTEGER Deterministic sampling seed (iteration number). [default: 1] --model [small|medium|large] Auditor model tier. [default: large] --concurrency INTEGER Maximum concurrent claude subshells. [default: 8] --db FILE Database path. Defaults to ~/.cc- pushback/feedback.db. --help Show this message and exit. ``` ### cc-pushback eval ``` Usage: cc-pushback eval [OPTIONS] Compute the mechanical metrics for the current prompt version. No LLM calls. Recomputes everything from raw verdicts: the golden-set gate, audited precision and reject contamination over the reproduced uniform core, the cumulative-pool secondary estimates, per-kind tables, and (with ``--compare- to``) verdict flips against an earlier prompt version. Options: --seed INTEGER The seed the audit ran with. [default: 1] --accepts INTEGER The audit's accept budget. [default: 60] --rejects INTEGER The audit's reject budget. [default: 60] --compare-to INTEGER Earlier prompt version for flip analysis. --json Emit the full metrics as JSON. --db FILE Database path. Defaults to ~/.cc-pushback/feedback.db. --help Show this message and exit. ``` ### cc-pushback refine ``` Usage: cc-pushback refine [OPTIONS] Refine every accepted pushback event into atomic training pairs. Incremental and idempotent: pairs commit per event as soon as each call completes, failed events stay pending and are retried on the next run, and re-running over a fully refined corpus is a no-op. Options: --model [small|medium|large] Refiner model tier. [default: medium] --limit INTEGER Refine at most this many events this pass. --concurrency INTEGER Maximum concurrent claude subshells. [default: 8] --db FILE Database path. Defaults to ~/.cc- pushback/feedback.db. --help Show this message and exit. ``` ### cc-pushback enrich ``` Usage: cc-pushback enrich [OPTIONS] Ground every refined pair in the code it complains about. Harvests candidate incorrect edits and their later corrections (from the session, or from git history) around each pair's pushback anchor, then has an LLM pick the one edit the complaint faults, copied verbatim. Expired transcripts and editless windows persist free ``no_code`` rows with no LLM call. Incremental and idempotent: evidence persists per pair as soon as each row resolves, failed pairs stay pending and are retried on the next run, and a refine re-run resurfaces its new pairs here automatically. Options: --model [small|medium|large] Linking model tier. [default: medium] --limit INTEGER Enrich at most this many pairs this pass. --concurrency INTEGER Maximum concurrent claude subshells. [default: 8] --db FILE Database path. Defaults to ~/.cc- pushback/feedback.db. --help Show this message and exit. ``` ### cc-pushback migrate-corpus ``` Usage: cc-pushback migrate-corpus [OPTIONS] Convert a pre-2.0 corpus in place to the cc-transcript 2.0 shapes. One-time and idempotent: legacy ``context_json`` snapshots become ``cc- transcript.context/1`` documents (previews only, summary fidelity, ``origin='migrated'``), the ``event_uuid`` and ``triage.fidelity`` columns are added, and rows already in the new schema are skipped. Options: --db FILE Database path. Defaults to ~/.cc-pushback/feedback.db. --help Show this message and exit. ``` ### cc-pushback pairs ``` Usage: cc-pushback pairs [OPTIONS] Print the refined training pairs — the pipeline's deliverable. Each pair is one atomic complaint: a faithful re-synthesis of what Claude did, the verbatim user excerpt, and the distilled one-sentence complaint. Options: --jsonl Emit full pairs as JSON lines for fine-tuning export. --db FILE Database path. Defaults to ~/.cc-pushback/feedback.db. --help Show this message and exit. ``` ### cc-pushback view-samples ``` Usage: cc-pushback view-samples [OPTIONS] Serve the training-pairs dashboard: refined pairs and their full lineage. Opens an interactive dashboard listing the refined pairs (the pipeline's deliverable) and every candidate behind them, with a detail pane that walks one candidate's lineage — detector hit, judge verdicts across versions, the auditor's agreement, the refiner's atomic split, and the golden gate. It is served over a transient HTTP server whose URL is printed; press Ctrl-C to stop. The corpus narrative is written by the ``claude`` CLI when ``--llm`` is set and ``claude`` is installed, falling back to deterministic heuristics. Options: --db FILE Database path. Defaults to ~/.cc-pushback/feedback.db. --llm / --no-llm Summarize with the claude CLI when it is on PATH, else use heuristics. [default: llm] --model TEXT Model for the claude CLI summary. [default: claude- sonnet-4-6] --port INTEGER Port to serve on; 0 picks a free one. [default: 0] --open Open the dashboard in a browser once serving. --help Show this message and exit. ```