- Never touch
buck-out/directly: norm,mv, or edits. To inspect an output, get its path frombuck build --show-output/--outand read it read-only. - Never call
buck2directly:tine buckis the only entry point; everybuckhere and in the docs is shorthand for that. - Never
buck build | tail: redirect the full output to a log file, print the path so the user can follow along, and check the exit status separately.tine buck log what-ran --failed --show-std-errprints the full stderr of the actions that failed in the last build, which the build output truncates. - Keep
buck run tine//tools:checkgreen. - Start every new file with the SPDX header, in the file's comment syntax, after a shebang if there is
one:
SPDX-FileCopyrightText: Amutable GmbH <https://amutable.com/>andSPDX-License-Identifier: MPL-2.0. A file that cannot carry one, such as data, is listed inREUSE.tomlinstead;checkrunsreuseand fails on a file with neither. - Line break documents and plans at 109 columns.
- Comments explain why, not what; say each thing once.
Cell layout and architecture: the design plan. User guides are under
docs/user/, design documents under docs/design/.
- A unit test is
<driver>_test.pybeside its driver, run by apython.testin the same package. Passboxwhen the tests need its interpreter or packages. - An assertion about what a build produced is a
box.sh_testbeside the target that produced it, taking artifacts as$(location)arguments so it runs against pinned tools rather than the host's. - Python sources reach a suite in another package through
depson apython.library, neverexport_file. - Python drivers use
python.library/python.binary. Never declare the prelude's bootstrap target or a-tycheck by hand; the macros do both. - A driver importing third-party modules lists the
boxesits check resolves against, one check per box.typecheck = Falseis for vendored sources only.
mise.toml puts ./bin and ./tools on PATH; every pinned tool is declared in tine.lock.json.
tine buck runs Buck with the mounts declared by tine mount in .buck/tine-mounts.toml.
- Build everything:
buck build tine//... catalog//..., which runs no tests. The catalog is a cell of its own, sotine//...leaves it out. - Lint (format, lint, type-check, whole-graph analysis):
buck run tine//tools:lint - Check (lint plus the fast test suites):
buck run tine//tools:check - Auto-format + auto-fix:
buck run tine//tools:fmt - Analyze part of the graph without building it:
buck bxl tine//tools/graph.bxl:analyze -- --pattern <pattern>;lintalready runs it over everything. - Tests:
buck test tine//..., or one suite:buck test tine//image:test. Anything needing an example image carries theimagelabel, the boot smokes additionallyvm;--exclude imageis the seconds-long half thatcheckruns,--include imagethe rest, which CI runs as its own step. - Catalog lock:
buck run tine//tools:refresh-catalog;tools:verify-catalogasserts the committed lock matches. - Example image digests:
examples/image:reproducibility-testasserts the digests committed inexamples/image/expected.json, and on a failure writesactual.<arch>.jsonbeside it;buck run tine//examples/image:refresh-expectedaccepts them (usually with--amend). Anything that can alter what the example images build needs this: a package they install, an image or UKI rule, a driver that writes into a root. The test carries theimagelabel, socheckdoes not run it and only CI's image step catches a stale file. - Full CI pipeline:
tools/ci.sh, fail-fast. Run individual groups by stating their names, e.g.tools/ci.sh check build. It covers one release of the catalog, the default one unlessTINE_TEST_DISTROnames another, such asarch.rolling. - rpm importer:
buck run tine//tools:importer -- <verb>; see importer.md. - BuildRequires cycle analysis:
buck run tine//tools:scc -- <branch label>; background in self-host-approaches.md.
starlark_fmt owns the layout of every .bzl and BUCK file and wraps at 160 columns.
- Leave a trailing comma after the last argument to keep a signature or call broken across lines.
- It sorts dict keys unconditionally; mark a dict whose order is a contract (
ddi.bzl:partitionfeeds partition UUIDs)# @unsorted-dict-items. - It drops load symbols it cannot see used; annotate type-only imports
# @unused.
A macro forwards arguments it does not read as **kwargs; one it reads is a typed parameter, never a
kwargs lookup, and its helpers take those values as parameters rather than the whole dict. Such a
parameter defaults to None, which Buck reads as the attribute's own default, and is annotated
X | Select if a caller may reach it with a select().
ctx.attrs belongs to the rule implementation that owns those attributes. _impl reads whatever it likes
from it; every other function takes what it needs as typed parameters.
- Use cell-relative
tine//...labels for same-cell targets, neverroot//.... - Never add a
.buckroothere. Buck takes the furthest ancestor.buckconfig, and.buckrootwould stop that search, keeping this checkout out of a workspace it supplies thetinecell to. - The flip side: a stale
.buckconfigin a parent directory captures the project, and//...then matches nothing while still exiting 0. If a command mysteriously finds nothing, checktine buck root --kind projectbefore anything else.
- Use PEP 257 docstring style: Short single-line summary, blank line, multi-line body.
- Don't use
TypeVar; use the 3.12+ generics syntax. Neverfrom __future__ import annotations. UseSelffor a class's own type, not a string type. - Use context managers (
ExitStack/AsyncExitStackwhere needed) for anything that needs cleanup. - Use
pathlib.Pathand prefer its methods overos.*. The exceptions arebox/sandbox.pyandbox/isolation.py: use string paths andos.paththere to keep startup imports to the absolute minimum. - Never import
.bzlfiles into Python tests, including throughrunpy.
Buck hands drivers project-relative paths and runs them at the project root, which a build sandbox mounts
at /tine/project. Keep them relative; making one absolute is a decision that needs a reason.
.absolute()only when something downstream changes the frame of reference: a chroot, a subprocess given its owncwd=, a path written into a spec another process consumes, or a library documented as needing it (libdnf5). Say which in a comment..resolve()follows symlinks on top of that, so reach for it only when following them is the point:source_overlay()inrootfs.pyresolves the source tree to compare it with the mount points that statmount() returns, which have their symlinks resolved.
- Commits are signed off, but the trailer is appended automatically: never add it by hand.
- Never mention an AI or agent in a commit message or PR body, and never add a
Co-Authored-By: Claudeor "Generated with Claude Code" trailer. - A subject under 60 characters,
area: what changed, imperative. Then at most one short paragraph wrapped at 80 columns: what was wrong or missing, then what this does about it, and nothing else. - Write it like you would say it to a colleague at their desk. Short words, plain verbs, no bullet lists.
- Never restate the diff: the reader has it, what they lack is the reason. Cut every sentence that is not the problem or the fix.
A whole commit body, in full:
The daemon kept a stale mount digest, so a second build reused a namespace
that no longer matched. Compare the digest on startup and kill the daemon
when it differs.