Skip to main content

A tool for processing BYU CS code recording files.

Project description

code_recorder_processor

code_recorder_processor processes *.recording.jsonl.gz files produced by the current jetbrains-recorder and vscode-recorder implementations. It reconstructs the edited document, compares that reconstruction to a template, and reports both suspicious activity (large external pastes, rapid AI-style paste bursts, time-limit violations) and engagement metrics (active editing time, time away from the editor, and typed-vs-pasted character counts) to help TAs and instructors review student work. An approved-pastes allowlist lets you exempt code students are expected to paste. See Reading the Output for an annotated example of what a run prints.

Scope

The processor is designed around the current recorder implementations, not around the historical examples in this repository.

Current schema expectations:

  • Modern edit events use type: "edit".
  • Status events use typed records such as type: "focusStatus".
  • Events include timestamp, document, offset, oldFragment, and newFragment.
  • Newer recorders also include recorderVersion.

Compatibility behavior:

  • Older recordings that omit type on edit events are still accepted.
  • If a mixed recording contains both modern typed edits and later stale legacy untyped edits, the processor prefers the typed stream.
  • Example recordings in recordings/ are fixtures, not the schema source of truth.

The canonical schema reference for this repository is docs/recording-schema.md.

Installation

For development inside this repository:

uv sync --dev

For running commands in the repo without a global install, prefer:

uv run cr_proc --help

To install the CLI globally from a local checkout:

uv tool install .

After that, the cr_proc command is available directly:

cr_proc --help

If you want the global command to track local source changes while developing:

uv tool install --editable .

Quick Start

The simplest invocation is to pass only recordings. When --template is omitted, the processor looks for a matching template file next to each recording.

Single recording:

uv run cr_proc path/to/student.recording.jsonl.gz

Multiple recordings:

uv run cr_proc recordings/*.recording.jsonl.gz

Explicit template file:

uv run cr_proc student.recording.jsonl.gz --template template.py

Template directory:

uv run cr_proc recordings/*.recording.jsonl.gz --template templates/

Write reconstructed output:

uv run cr_proc student.recording.jsonl.gz --write reconstructed.py
uv run cr_proc recordings/*.recording.jsonl.gz --write output/

Compare to submitted files:

uv run cr_proc student.recording.jsonl.gz --submitted submitted.py
uv run cr_proc recordings/*.recording.jsonl.gz --submitted submissions/

Write JSON results:

uv run cr_proc recordings/*.recording.jsonl.gz --output-json results.json

Playback mode:

uv run cr_proc student.recording.jsonl.gz --playback

This opens a windowed viewer. Use the left/right arrow keys to step through edits, Space to play or pause, and Home/End to jump to the beginning or final state. The viewer is generated as a local HTML page and opened in your default browser.

Select a specific document from a multi-document recording:

uv run cr_proc multi-file.recording.jsonl.gz --document src/main.py

CLI Reference

Core inputs:

  • inputs: One or more recording files or glob patterns.
  • --template PATH: Optional template file or template directory.
  • --document NAME: Optional override for which document inside the recording should be processed. This matches the recorded document path or filename and is not another local file input.

Outputs:

  • --write PATH: Write reconstructed code. In single-file mode this can be a file or a directory. In batch mode it must be a directory.
  • --output-json PATH: Write structured JSON results.
  • --submitted PATH: Compare reconstructed code to a submitted file or a directory of submitted files.

Verification and filtering:

  • --time-limit MINUTES: Flag recordings whose active editing time exceeds the limit.
  • --filter-file FILE: Exclude recordings matching a path, filename, or base filename.
  • --filter-function-generation: Suppress suspicious autocomplete findings that are recognized as IDE-generated boilerplate function stubs.
  • --approved-pastes FILE: Treat the contents of FILE as approved code that students may legitimately paste (provided helpers, lecture/reading snippets). A flagged paste is suppressed when its content (whitespace-insensitive) is contained in the approved material. Repeatable; pass several files as needed. Suppressed pastes are removed from the suspicious list but reported as an audit count (Approved pastes suppressed: N, and approved_paste_suppressions in JSON output).

Playback:

  • --playback: Open a browser-based windowed playback viewer.
  • --playback-speed FLOAT: Playback speed multiplier.
  • --playback-start-event N: Start playback from a later applied-event index.

Compatibility aliases:

  • Legacy positional-template usage still works.
  • --template-dir, --output-file, --output-dir, --submitted-file, and --submitted-dir are still accepted as compatibility aliases.

Template Resolution

When the processor needs a template, it resolves it in this order:

  1. --template <file> uses that exact file.
  2. --template <directory> searches that directory for the best filename or stem match to the recorded document.
  3. If --template is omitted, the processor searches the recording's parent directory.
  4. Legacy positional-template mode treats the last positional argument as a template file when it does not look like a recording path.

--document affects this process by telling the processor which recorded document to treat as the target before template matching happens. It only selects data already present in the recording.

If no matching template is found, processing still continues by falling back to the recording snapshot as the reconstruction seed.

Output Behavior

Normal user-facing output goes to stderr:

  • time summaries
  • character metrics (typed vs. pasted vs. deleted characters, and a typed fraction)
  • time away from editor (window unfocused time)
  • suspicious-event summaries
  • template mismatch diffs
  • submitted-file comparison summaries
  • warnings

Character metrics classify each inserted character as typed or pasted using a width-based rule (single keystrokes, auto-indent whitespace, and IDE auto-paired brackets/quotes count as typed; larger inserts count as pasted). The counts are independent of the suspicious-event heuristics, so they stay stable as detectors change. They appear per file and as a combined roll-up, and in JSON as char_metrics / combined_char_metrics.

Time measures (three distinct numbers)

The tool reports three independent time concepts; do not conflate them:

  1. Time span (first to last edit) — raw wall-clock from the first to the last recorded event. Excludes nothing.
  2. Elapsed editing time (time_info.minutes_elapsed) — the span minus the idle blocks where the file was closed and reopened (each reopen emits a file-open snapshot; the gap before it is dropped). It does not deduct idle time while the file stayed open, whether focused or switched away.
  3. Time away from editor (focus_info / combined_focus_info) — total time the editor window was unfocused (blurred), computed by pairing window blur/focus events. The file stays open during these; this is "switched to something else" time. Reported as unfocused_seconds + num_unfocused_intervals. It is a standalone signal and does not affect measures 1 or 2.

Known item flagged for review: --time-limit currently compares the limit against measure 2 (file-closed-deducted editing time), not measure 1 (the wall-clock span). For timed in-class work the file stays open so the two are ~equal; on take-homes they diverge sharply. Whether the limit should gate on the span instead is an open question — behavior is intentionally left unchanged for now. See check_time_limit in src/cr_proc/api/verify.py.

Reconstructed code is written only when --write is used.

JSON output is written only when --output-json is used.

Reading the Output (for TAs and instructors)

Every run prints a human-readable summary to stderr. Here is a real single-recording run that was flagged:

Elapsed editing time: 15.742 minutes
Time span (first to last edit): 19.99 minutes
Characters: 127 typed, 2286 pasted, 272 deleted (typed fraction 5%)
Editors: jetbrains

Suspicious events detected:
  Events #52-#114 (AI rapid paste): 44 lines, 2140 chars
  Events #131-#136 (AI rapid paste): 3 lines, 146 chars

Line by line:

  • Elapsed editing time / Time span — two of the three time measures (see Time measures above). Here the student was active for ~16 minutes within a ~20-minute window.
  • Characters: … (typed fraction 5%) — only 5% of inserted characters were hand-typed; 2286 of 2413 arrived as pastes. A very low typed fraction is a strong, at-a-glance triage signal.
  • Editors — which IDE recorder(s) produced the recording.
  • Suspicious events detected — one line per flagged event: the event index (or index range for a cluster), the reason, and its size. Events #52-#114 (AI rapid paste): 44 lines means 44 lines were pasted in rapid succession — the JetBrains AI-autocomplete acceptance pattern. The indices let you locate the event in --playback or the JSON.

Other lines you may see, depending on the recording and flags:

  • Time away from editor: 86.92 minutes across 54 switches — the third time measure (window unfocused time); appears only when the recording contains window focus/blur events.
  • Approved pastes suppressed: 1 (events #80) — a paste matched the --approved-pastes allowlist and was not flagged. Shown for transparency so you can confirm the allowlist is doing what you expect.
  • Success! No suspicious events detected. — nothing was flagged.
  • Warning: … — e.g. a template mismatch, a recovered initial state, or "the user likely has AI autocomplete enabled" (transient insert/delete suggestion previews were seen — a hint, not a flag).

A batch run (multiple recordings) ends with a combined summary:

BATCH SUMMARY: Processed 4 files
================================================================================
Verified: 4/4

COMBINED TIME REPORT (4 recordings):
Total elapsed editing time: 82.737 minutes
Overall time span: 18739.29 minutes
Combined characters: 4267 typed, 5144 pasted, 2441 deleted (typed fraction 45%)

JSON output

--output-json PATH writes the same information in machine-readable form. Below is a real single-recording result, abbreviated (the reconstructed source and one larger cluster are trimmed):

{
  "version": "0.2.11",
  "batch_mode": false,
  "total_files": 1,
  "verified_count": 1,
  "all_verified": true,
  "files": [
    {
      "jsonl_file": ".../cs111-homework0-CPT.recording.jsonl.gz",
      "document": "/.../homework0.py",
      "verified": true,
      "time_info": {
        "minutes_elapsed": 15.742,
        "first_timestamp": "2026-01-15T01:21:35.360168Z",
        "last_timestamp": "2026-01-15T01:41:34.626218Z",
        "exceeds_limit": false,
        "time_limit_minutes": null
      },
      "suspicious_events": [
        {
          "event_index": 131,
          "event_indices": [131, 135, 136],
          "line_count": 3,
          "char_count": 146,
          "reason": "AI rapid paste",
          "newFragment": "3 pasted lines in under one second"
        }
      ],
      "char_metrics": {
        "typed_chars": 127,
        "pasted_chars": 2286,
        "deleted_chars": 272,
        "typed_fraction": 0.0526
      },
      "editors": ["jetbrains"],
      "reconstructed_code": "<full reconstructed source ...>"
    }
  ],
  "combined_char_metrics": {
    "typed_chars": 127, "pasted_chars": 2286,
    "deleted_chars": 272, "typed_fraction": 0.0526
  }
}

Field reference:

  • Top level: version, batch_mode, total_files, verified_count, all_verified, and the batch roll-ups combined_time_info, combined_char_metrics, combined_focus_info, total_approved_paste_suppressions (each present when applicable).
  • files[] per recording: jsonl_file, document, verified, time_info, suspicious_events, template_diff, reconstructed_code, editors, and — when present — char_metrics, focus_info, approved_paste_suppressions, recorder_versions, warnings, submitted_comparison.
  • time_info: minutes_elapsed (the editing time, measure 2 above), first_timestamp/last_timestamp (whose difference is the span, measure 1), exceeds_limit, time_limit_minutes.
  • char_metrics: typed_chars, pasted_chars, deleted_chars, typed_fraction (0–1).
  • focus_info: { "unfocused_seconds": 5215.2, "num_unfocused_intervals": 54 } — present only when the recording has focus events.
  • suspicious_events[]: event_index (plus event_indices for a cluster), line_count, char_count, reason (see glossary below), and newFragment (or diff for a conflicting snapshot).
  • approved_paste_suppressions: { "count": 1, "event_indices": [80] } — present only when --approved-pastes suppressed something.

Suspicious Activity Detection

Each suspicious event carries a reason. The labels you will see:

  • external paste — a large block inserted at once (≥8 lines; or ≥6 lines and ≥250 chars; or ≥3 lines and ≥350 chars) that does not already appear in the document or the approved-pastes material. Typical of pasting from an external source. VS Code emits a paste as one such event.
  • AI rapid paste — 3 or more multi-line inserts within one second, reported as a cluster (with its event index range). This is the JetBrains AI-autocomplete acceptance pattern (whole lines appearing in rapid succession). Note: this timing-based check fires regardless of whether the content is reused, so rapidly duplicating one's own code 3+ times within a second can still appear here.
  • complete statement auto-complete (AI assistance) — a small (10–300 char) multi-line insert that suddenly completes a structure such as a def/class. Typical of an IDE/AI completing a whole statement at once.
  • conflicting snapshot — a file-open snapshot whose contents do not match the replayed document state; reported with a diff instead of newFragment.

The processor also reports time-limit violations for single recordings and combined batch activity (see Time measures).

With -g/--filter-function-generation, IDE-generated boilerplate is recognized and suppressed rather than flagged: def …: pass/... stubs and if __name__ == "__main__": skeletons (empty body or a lone entry-point call).

Reuse awareness suppresses the external paste and complete statement auto-complete flags for content that already appeared earlier in the same recording or an overlapping recording in the batch — including code that was typed and later moved or re-pasted, even when it was typed and deleted character-by-character. Moving one's own existing code is not flagged. (The AI rapid paste timing check is the exception noted above.)

A separate warning — "the user likely has AI autocomplete enabled" — is emitted when transient insert/delete suggestion previews are seen (a JetBrains suggestion shown then withdrawn). This is context, not a flagged event.

These checks are heuristic. They are intended to surface recordings for review, not to act as a standalone disciplinary decision engine.

Development

Run tests:

uv run pytest -q

Run the bundled example recording:

uv run cr_proc recordings/cs111-homework0/cs111-homework0-ISC.recording.jsonl.gz

CI and Release

GitHub Actions uses uv, not Poetry.

  • CI installs dependencies with uv sync --locked --dev.
  • CI currently runs on Python 3.11 and 3.14.
  • The publish workflow builds distributions with uv build.

Repository Fixtures

The bundled recordings are documented in recordings/README.md. Those files are useful for regression tests and examples, but some were created with older recorder versions and intentionally exercise compatibility paths.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cr_proc-0.2.12.tar.gz (36.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cr_proc-0.2.12-py3-none-any.whl (41.9 kB view details)

Uploaded Python 3

File details

Details for the file cr_proc-0.2.12.tar.gz.

File metadata

  • Download URL: cr_proc-0.2.12.tar.gz
  • Upload date:
  • Size: 36.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for cr_proc-0.2.12.tar.gz
Algorithm Hash digest
SHA256 cfb7056c26c708a624d3ea87924c03ce710f8504431fd72ba8628ef1e6f6d184
MD5 b243f0e449e51b390afa1d632760a910
BLAKE2b-256 45702820b681c6c646bce7b37a96c70e8ef52e10f6c565fd5debcf24754e7500

See more details on using hashes here.

File details

Details for the file cr_proc-0.2.12-py3-none-any.whl.

File metadata

  • Download URL: cr_proc-0.2.12-py3-none-any.whl
  • Upload date:
  • Size: 41.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for cr_proc-0.2.12-py3-none-any.whl
Algorithm Hash digest
SHA256 e575aded2e7ed0e73bb89819ba047d6d07f04b9624e46d4885d1ccc27db39ea7
MD5 c596d425b7020e37492253277f7c20d9
BLAKE2b-256 9ac672a8ce81b6c127719b971e18334a5afe567b2dfaa0446ab2935e0f29c3b7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page