Skip to content

Development

Workfold is a Python 3.10+ project managed with uv. Nix provides a reproducible development shell with Python, uv, Ruff, Node.js, pnpm, and release tooling. Rich is confined to the terminal presentation layer; it is not a CLI framework or TUI architecture.

The project deliberately uses a flat package layout:

workfold/
├── workfold/ # Python package and CLI implementation
│ ├── collectors/
│ └── renderers/
├── tests/
├── docs/ # Astro Starlight product documentation
├── README.md # installation and quick-start overview
└── pyproject.toml

There is no src/ wrapper. Packaging discovers workfold* directly from the repository root.

Terminal window
nix develop
uv sync --extra dev

Run the checkout without installing another copy:

Terminal window
uv run workfold --help
uv run python -m workfold --version

Without Nix, install uv separately and run uv sync --extra dev with a compatible Python interpreter.

Terminal window
uv run pytest
ruff check .
ruff format --check .
uv run pyright

Tests use temporary repositories with controlled timestamps for Git integration behavior. Filesystem capability tests must report or skip unavailable creation time rather than substituting POSIX ctime. Linux tests exercise the native statx adapter, nanosecond provenance, identity validation, and final-symlink no-follow behavior.

Build wheel and source distributions with:

Terminal window
uv build
uv run --extra release twine check dist/*

After a push to main passes the complete Test workflow, the Release workflow verifies that the tested commit is still current, creates the alpha release, publishes it to PyPI, and attaches PyInstaller archives for the supported platforms to the GitHub release. A manual dispatch remains available as a guarded fallback. Local development does not need publishing credentials; GitHub uses the protected pypi environment and trusted publishing.

Install, validate, build, or serve the Astro Starlight site with:

Terminal window
nix run .#docs-install
nix run .#docs-check
nix run .#docs-build
nix run .#docs-dev

The site documents the terminal CLI. Do not add an HTML report, web server, or browser renderer while those remain outside the MVP.

Collectors emit normalized, provenance-preserving timestamp observations in record-local batches. The application immediately applies selection, updates partition coverage, coalesces only timestamps from that record, classifies the result, and releases the full provenance unless it belongs to the bounded outside-event list. Git revision output is streamed; commit objects and file-change derivation use bounded internal batches. Filesystem observations flow through one entry at a time, and production runs do not retain the full entry or observation inventory after its counters have been updated. Batch sizes are memory controls, never collection, filtering, or chart semantics.

Configuration resolves time, mode, and the evidence profile as three independent axes. The profile defaults to standard; neither portable nor full expansion may rewrite time or mode.

TOML defaults are resolved before RawOptions validation as built-ins, the platform global file, the nearest selected-path project file, and explicit CLI values. Each setting retains its origin for --show-config; configuration is merged as typed data and is never converted into synthetic command-line arguments. Standalone workfold.toml and [tool.workfold] use the same strict schema. Keep discovery, parsing, precedence, CLI materialization, and final domain validation separate so unknown keys and invalid cross-option states cannot be silently ignored.

Filesystem path inventory and filesystem metadata extraction are distinct boundaries. For a file/symlink scan that respects ignore rules inside a Git worktree, local NUL-delimited git ls-files plumbing supplies tracked, untracked, and ignored path candidates using Git’s standard ignore semantics. Git never supplies filesystem types or timestamps: every included candidate must still pass a current no-follow lstat, followed by the same timestamp adapter used by native traversal. An index-only path absent from the current worktree is therefore not filesystem evidence. Directory scans combine the same inventory’s ignored-directory boundaries with the native no-follow walker, so current and empty directories remain discoverable without entering ignored subtrees. Coverage counts each pruned subtree boundary and explicitly states that its descendant directories were not enumerated; scans that include ignored entries traverse them instead. Scans outside Git and scans that include ignored entries use the native walker alone. Keep the fast inventory and native reference behavior equivalent for requested regular files, and never reintroduce per-entry path resolution for repository/admin containment.

Filesystem record discovery is defined by requested metadata-bearing entry kinds, not by walker implementation details. A directory used only to reach requested files is traversal structure and must not appear as an excluded record merely because the native fallback visited it. Explicitly excluded boundaries, semantic Git administration, and unreadable candidates remain accounted terminal outcomes. Fast Git inventory and native ignore fallback must therefore produce the same eligible-file and coverage counters for the same stable tree.

Schedule classification, display cropping, weekday-column projection, anchored globally aligned time clustering, renderer-neutral report construction, and terminal rendering remain separate stages. Full summary and coverage counters are updated before projection. Empty-day decisions use post-crop weekday counts; only projected markers enter clustering, so an explicitly hidden event cannot anchor or stretch a visible row. Explicitly hidden occupied columns retain a separate reconciled count. The clusterer emits half-open semantic bands and ordered visual runs for ordinary cells. Pathologically busy cells retain exact counts per visual kind instead of an unbounded alternating sequence. Chart sorting stays in memory for normal runs and transparently spills to an ephemeral local disk-backed sorter above its fixed threshold; the temporary data is removed when aggregation finishes. Only the renderer creates Rich objects or assigns green/blue/red styles. Optional grid lines are likewise terminal-only presentation state and never enter the normalized event, classification, aggregation, or report model.

Identity-marker mode projects each visible Git marker to a compact registry ID before chart sorting. Full recorded names/emails are retained once per distinct identity group, while in-memory and SQLite-spilled marker rows carry only that integer. Registry IDs are remapped into deterministic identity order before clustering. Initial-group numbering, composite diamond allocation, and identity-label formatting remain terminal-renderer concerns; collectors and normalized observations never store display codes. Repository-controlled values must be sanitized and added as literal Rich text, never parsed as markup.

The renderer formats sparse band endpoints as HH:MM. Cluster windows have a one-minute minimum so two distinct clusters cannot collapse to the same visible label. Do not round the underlying instant to obtain that label: normalized provenance and outside-event rows retain exact seconds and available nanoseconds. The renderer must emit the matrix first, without a title or subtitle. After the matrix it emits a content-aware symbol key without a heading, a left-aligned working-hours line, and three direct statistic rows without a Summary heading: Events, Schedule, and Calendar. Both split rows partition Events but answer independent questions. The key derives from visible markers after cropping and mentions ×N only if count tokens render. Identity/source mappings are independent of schedule state and appear once. The matrix and key always use the same structural schedule encoding: filled/uppercase inside, hollow/lowercase outside. Color reinforces that encoding, and the conditional outside cue is styled red when available; it never uses an arbitrary contributor’s marker as the global example. Identity mappings use the canonical uppercase/filled code in Git green and occur once; lowercase/hollow variants remain consolidated in the outside cue. Only --verbose adds scope, period, full successful coverage, source/record breakdowns, cluster/compression policy, exact collector choices, Git identity scope, collection extents, and ignore/exclusion policy; it also includes the detailed coverage ledger. --coverage includes the ledger without those verbose details, and evidence-profile resolution must not enable either output mode. The minimal default must still append exception notices for partial collection, explicit narrowing, unsupported capabilities, and nonzero cropping.

Keep record counts, timestamp observations, and plotted activity markers distinct: they intentionally answer different accounting questions. Ordinary cells draw one symbol per activity marker. Exact ×N compaction is a width- or volume-driven renderer fallback and must not alter event, summary, or coverage counts.

Before changing behavior, inspect the CLI help, usage and accuracy documentation, and the tests covering the affected pipeline stage.