Imported from liken-sh/liken (
AGENTS.md). Install upstream withnpx skills add liken-sh/liken. Copyright stays with the author.
Working on liken
liken is a public OS distribution. Its repositories are documents as
much as they are code, so write everything here for a reader.
This is a literate project
This repository has very little ordinary program code. It is mostly shell scripts, configuration, manifests, and build automation, and these files are the documentation. Write them in a literate style, with comments that give instruction, explanation, and commentary. A reader who reads the repository from top to bottom should learn how a Linux system boots and how Kubernetes takes control after that.
The voice rules for everything the project publishes are in the brand repository and arrive with the theme submodule:
@docs/themes/brand/voice.md
The comment rules there say to teach the domain, not the syntax. In this
repository, the domain is how a system boots. Do not explain what
mkdir -p does. Explain why the kernel does not mount /proc on its
own, why k3s needs cgroups, and why an initramfs is a cpio archive.
Some explanations are too big for a comment, for example a design decision that spans several files, or a survey of alternatives. Put those explanations in a markdown document next to the thing they describe, organized by domain.
Errors include their source's text
An error that wraps a tool, a daemon socket, a bus answer, or a provider
includes that source's own text word for word: its stderr, its
response body, or its error string. The wrapped error and the status
field or record that the failure writes both include it, so a person
reads the cause from the log or the status without opening a shell.
Commit messages
A comment describes the system as it is now. A commit message describes what one change does and why. Write it in ASD-STE100, like the rest of the prose here. The message is a record for a person who reads it during review or during a bisect, so keep it plain and short.
Use this form:
Add a mount(8) so the kubelet can run mount helpers
The kubelet runs a program named mount to mount a volume. The image had
none, so the name fell through to busybox, which mounts with the raw
syscall and never runs a helper. An inline nfs volume failed.
/sbin/mount sorts an option list into the flag word and the data string,
then runs /sbin/mount.<fstype> when one exists. A mount with no helper
takes the same path it took before.
The lab mounted an NFS export with no version in its options and got
vers=4.2. Both smoke drills stayed green.
Closes #123
Follow these rules for the subject line:
- Write it in the imperative. It must complete the sentence "This commit will ...". Write "Add nodePortCIDRs to the cluster network spec". Do not write "A cluster names the networks its NodePorts answer on".
- Name the change. A reader of
git log --onelinemust learn what the commit does without opening it. A subject that only a person who read the diff can understand is wrong. - Keep it to 72 characters, on one line, with no period at the end.
Follow these rules for the body:
- Give the problem, then the change, then the evidence. Say what was wrong or missing, say what this change does about it, and say what the lab measured when a drill ran. Three paragraphs is the target and five is the limit. Wrap at 72 columns.
- Do not personify a program. Software has no intentions and makes no discoveries. A program reads, writes, starts, refuses, and fails. It does not find, want, believe, learn, or concede.
- Cut aphorisms and metaphors. Cut any sentence that is there because it sounds good.
- Do not narrate the session. The message describes the change. It does not describe the order in which you found things, and it does not report how the work felt.
- Do not restate the diff. No file lists, no checklists, and no test plan. Name a measurement, not the tests you ran.
- Name the issue when there is one, with "Closes #1234".
- Close the plan it builds. When a commit builds a milestone, the
same commit moves the document to
plans/completed/and dates its header. Do not wait for a later drill. - Do not call the work "comprehensive" and do not claim a "root cause".
A small change gets a subject line and nothing else. Do not write a body to make a one-line change look larger.
The commits before 2026-07-25 do not follow these rules. They use declarative subject lines that read as riddles, they give programs intentions, and their bodies run long. That style is not the model. Do not copy a message out of the log, and do not match the tone of the commit you are building on. The history stays as it is, because a rewrite would break every link and hash that names it.
Organization
Organize the repository by domain, not by kind. Name each directory for
the part of the system it is, for example the kernel, the init, or the
image. Each directory must contain everything that domain needs: scripts,
configuration, and documentation together. Do not create one shared
scripts/ directory for every domain.
The manual
The docs domain is the website: the front page of liken.sh and the user
manual under /docs/. The manual is written in ASD-STE100, plain technical
English: short sentences, one instruction per sentence, no metaphor.
docs/README.md explains the domain.
When you change what an operator sees or does, evaluate whether the manual must change with it, and make both changes together. The cases to check:
- A
likenCLI command or flag changes: updatedocs/content/docs/reference/cli.md, and check the guides that run the command. - An operational flow changes (install, adoption, adding machines,
upgrades, rollback): update the guide in
docs/content/docs/guides/. - The release channel's layout or artifacts change: update
docs/content/docs/reference/release-channel.md. - A CRD schema changes: the Machine and Cluster reference pages regenerate from the schemas at build time, so the schema's own descriptions are the fix. Write them knowing they become the manual.
A change that only touches internals needs no manual change. The repository's comments describe it.
Licensing
liken's own code uses the MIT license, but a release also redistributes
other projects' binaries, and several of these use the GPL or LGPL
license. This never changes liken's own license, because the components
are aggregated, not linked. But it does require the release channel to
ship third-party notices with the binaries and to offer each component's
source from the same channel. The licensing domain owns both tasks: every
release bundles its LICENSES.md file as an artifact, and the release
workflow publishes its source mirror to sources/<component>/<version>/.
When a vendored pin changes, update licensing/ at the same time: the
source pins in licensing/sources.sh and the notices in
licensing/NOTICES.md. Those files explain the reasoning.
licensing/sources.sh --repin writes the digests that a bump moved, and
refuses the case it must not guess at: a URL whose filename contains the
version.
Version pins
Every domain that vendors something pins it by version, and by digest
when its upstream publishes no checksum of its own. make versions asks
every upstream what it has now and prints the answer beside each pin.
Each pin belongs to a domain, so the latest.sh beside that domain's
fetch.sh holds the address of its upstream. Run it alone and it reports
its own pins. Run it with --bump and it writes the new version, the new
digest, and the matching source pin.
plans/completed/48-check-and-update-dependency-pins.md gives the
reasons, and each script explains what its own upstream calls a release.