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.
Repository layout
Section titled “Repository layout”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.tomlThere is no src/ wrapper. Packaging discovers workfold* directly from the
repository root.
Set up the environment
Section titled “Set up the environment”nix developuv sync --extra devRun the checkout without installing another copy:
uv run workfold --helpuv run python -m workfold --versionWithout Nix, install uv separately and run uv sync --extra dev with a
compatible Python interpreter.
Run quality checks
Section titled “Run quality checks”uv run pytestruff check .ruff format --check .uv run pyrightTests 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 packages
Section titled “Build packages”Build wheel and source distributions with:
uv builduv 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.
Work on the documentation
Section titled “Work on the documentation”Install, validate, build, or serve the Astro Starlight site with:
nix run .#docs-installnix run .#docs-checknix run .#docs-buildnix run .#docs-devThe site documents the terminal CLI. Do not add an HTML report, web server, or browser renderer while those remain outside the MVP.
Implementation boundaries
Section titled “Implementation boundaries”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.