Imported from porelo-ai/porelo-project (
AGENTS.md). Install upstream withnpx skills add porelo-ai/porelo-project. Copyright stays with the author.
Rules for working in this repository
Shared across the Porelo repositories
BEGIN Shared Area · sha256
df1a9687073cea82· changed 2026-10-18T13:00Z
Identical in every repository project.yml names but porelo-parent, which holds only the parent pom
and no AGENTS.md. The markers carry the SHA-256 of the lines between them (the first 16 hex digits)
and the UTC time that text last changed; change it in the channel first, never in one copy.
Agents and the rules they follow
- When several agents run in parallel, they may communicate through a shared channel. How it
works depends on the local setup and is defined in the optional
.AGENTS.mdor fed into every agent's context when it starts. - Every repository has exactly one responsible agent, and one agent may be responsible for
several. Which one is defined in the optional
.AGENTS.mdor fed into every agent's context when it starts. - A rule that holds for more than one repository is stated generally and shared - here, or in the
shared block of
.AGENTS.mdif it concerns the local setup. A rule is one or two sentences and says only what matters. - A rule belongs in
.AGENTS.mdonly if it concerns local settings that cannot be shared throughAGENTS.md. - An agent writes only in its own repositories and asks the responsible agent for anything from another, in the channel. The coordinating agent may also read the other repositories, but never writes in them.
- Where a channel exists, an agent stays reachable on it while any work is open, and says there at once when it waits on the operator.
- A contract between repositories - an interface, a file format, a path another repository links to - changes only after agreement in the channel or by an operator decision, and in both repositories.
- An ambiguous answer is asked about once, plainly, instead of guessed at. When an answer changes, everything built on the old one is looked for.
Commits and pushes
- While working, commit in logical steps, so a mistake can be rolled back. A task is pushed only when it is finished, squashed into one commit as its last step; several finished tasks may go out in one push, one commit each.
- An edit replaces an exact block, never everything between two landmarks; only the files you
edited are formatted. Before a commit,
git statusis read and only your own files are added, never withgit add -A. - Unless the operator says otherwise, the operator pushes, and agents commit and stop. An instruction to push covers exactly what it names.
- A change that needs another repository's change names that dependency when it is handed over, and is pushed only after the change it needs has built and published.
- A change is ready only when every step its build workflow runs - not only Maven - has passed locally.
- Amend, squash or rebase only commits that are not pushed, checked with
git ls-remote origin refs/heads/main, never with a cachedorigin/main. A pushed commit is repaired by a new commit on the remote's tip, never by a force push. - A commit message is one line of at most 72 characters saying in words what changed, never by a requirement number; the reasoning goes into an issue or a decision. A squash of several changes names what they share, not each of them. Trailers may follow below a blank line, and nothing else.
- An agent's commit names its help in an
Assisted-by: <tool>, <model>trailer, and an agent never writes aSigned-off-by:: the operator vouches for the project's own commits by pushing them. A pull request from outside carries its author'sSigned-off-by:, the Developer Certificate of Origin. - No commit message, changelog entry, issue or document says who asked for a change or who made it
- no "as the operator chose", no agent's name; it says what changed and why.
- A change to what ships or to what a build produces gets its changelog entry in the same commit,
under
[Unreleased]and the heading of its kind. A change only to the CI's own workflows (an action's pin, a schedule, a job's steps) gets none: the changelog is for the people who use what ships. A generated changelog is changed only through its sources, never by hand.
Issues
-
Every open task is an issue in
issues/, named<prefix><nn>-Short-Title.mdwith one of its repository's prefixes; a repository may own several, for subgroups. An issue is one task, says what must be true and how it is judged done, and names a dependency on another repository's issue by repository and number. -
A new issue follows this skeleton, and its index row is added in the same commit; Why and The shape may follow What must be true in a larger issue:
# <PREFIX><nn> — <Short Title> **Status:** Idea | Accepted | Analyzed | Estimated | Backlog | Implementing | Review | Rework; blocked by <repository> <number>, if so. **Size:** XS | S | M | L | XL - one line why (from Analyzed on). **What must be true.** One or two sentences, from the point of view of whoever uses it. ## Plan - The base design, a page at most, read in today's code: what it touches, the steps, the test that goes red first (from Analyzed on). ## Acceptance - How it is judged done: what is measured, and what must be seen to fail. ## Open questions - What the operator must decide; deleted once answered. ## To be checked - What the agent must still measure, read or confirm itself; deleted once done. -
An issue moves through these states, and only the operator, as product owner, sets six of them:
- Idea - anyone brings it: a title and What must be true.
- Accepted or Rejected - the operator only; a rejection says why.
- Analyzed - the agent owning the repository: the Plan (a base design of a page at most, read in the code), the Size, the operator's decisions under Open questions, and what it must still find out itself under To be checked.
- Estimated - the operator only, after reading the design and settling its questions with the agent, who adjusts the plan and the size.
- Backlog - the operator only: it is built in the next round; the order in the index is the priority.
- Implementing - the agent: Rework before Backlog; built locally, its whole workflow green on the VM, then pushed by the coordinator.
- Review - the agent, once it is pushed and published as a snapshot; the operator tests what is published.
- Rework or Finished - the operator only, after the test; a rework says what was found.
An agent never sets Accepted, Rejected, Estimated, Backlog, Rework or Finished; it proposes in the channel. A size or plan that grows clearly while building is said before building on.
-
issues/README.mdis the index, grouped by state in this order: Review, Rework, Implementing, Backlog, Analyzed, Accepted, Idea, Estimated, then Finished and Rejected last; each group a table of number, size, blocked by and what it covers, and in Analyzed also the number of open questions (the group's heading says the state; a note on how far it is goes into what it covers), ordered by what to do next; it changes in the same commit as the issue. An open task is never a TODO in code or a note in a commit message. -
A rejected issue's file is deleted; its number, title and one sentence why stay as a row under Rejected in the index, so the same idea is not brought twice.
-
From Analyzed on, an issue has a size and a plan, read in the code, not guessed. XS is under an hour (one file, no contract); S a few hours (one module and its tests, no contract other repositories read); M about a day (several modules, or one contract change, docs, an acceptance step); L several days (crosses repositories, or a design to decide first); XL is too big to build as one and says how it splits into issues of L or smaller. Plan names the files and classes it touches, the steps and the test that goes red first; a size or a plan that turns out wrong while building is corrected in the issue in the same commit.
-
An open question is one only the operator can answer - a choice of product, scope, account, money or risk - and sits under Open questions, which the index counts in Analyzed: that column is what waits on the operator. From Estimated on an issue has none; one that comes up while building is said in the channel, and the issue waits until it is answered. What the agent can find out itself, by reading, measuring or asking another agent, sits under To be checked and is not counted. An answered question is deleted once its answer is in the criteria, the design or a decision.
-
A finished issue's file is deleted when the operator sets Finished, with every mention of its number, in one commit; its number, title and one sentence of what was built stay as a row under Finished in the index, so what was built can be seen. What outlives it moves first: to
doc/if a user needs it, todoc/decisions.mdif it is a decision, toAGENTS.mdif it is a rule, to.AGENTS.mdif it is true only on one machine. -
doc/decisions.mdopens with an index - subject, one line of what holds, link - and a row is written with its decision. An accepted risk states the exposure, why it stays, and what would change the answer. -
A requirement not yet placed waits in
porelo-projectuntil it is clear which repository builds it, then moves there as an issue. -
Every row of an index opens with an anchor named after its number,
<a id="<number>"></a>, the number lower case; a Finished or Rejected row keeps it. -
An issue number in
issues/is a link to its row in the index, never to its file, which is deleted when the issue is finished:[<NUMBER>](README.md#<number>)in the same repository, the other repository's index on GitHub for another's. The row outlives the file, so the link does not break. The shared tools check that every number is such a link and that its anchor exists. -
Code, comments, test names and anything that ships never cite an issue number; they state the constraint itself, since the issue is deleted once it is finished.
Testing and machines
- Work is tested on local VMs before it is handed over, on rented machines reachable from here only
when the operator says so, and in the GitHub build on every push. Which machines a setup has is
defined in the optional
.AGENTS.mdor fed into every agent's context; machines are rented only through the shared tooling, and nothing else names a provider. - The documents' checks run locally before every hand-over, and on GitHub once a day. Every change is handed
over with
check-shared,check-citations,check-readmes,check-doc-site,check-actionsand the tests that read documents green on the host, on the exact commit, and the code it changed read against the repository's skills; the Shared rules check workflow runs on a daily schedule, on a pull request and by hand, never on a push. - How agents exchange files for a test on a local VM is defined in
.AGENTS.md. Where it says nothing, a test uses only the agent's own artifacts. - A local VM is restarted only when agreed in the channel or asked of the operator. A rented machine reachable from here is restarted only by the operator.
- A rented machine's name says which agent and which run made it. Every run deletes exactly what it created, after a failure too, and never sweeps by age or touches a machine it cannot attribute.
- The scheduled sweep of the CI's machines deletes one only past the
porelo-expirestime it was made with, or one that carries none; it is the one clean-up that is not a run's own. - A script that changes a machine refuses to start over leftovers, removes only what it created - on interrupt too - and exits non-zero naming anything it left.
- Before a run on a shared machine, check what it does to that machine and say so.
- Before a long run, say how long it will take, from a measured time, or say that it is a guess.
- A test that waits on an agent, a model or any paid service fails fast on a loop: it stops as soon as the same failure repeats, instead of waiting out its time, and names what repeated. A run seen looping is reported at once, so the operator can cancel it.
- A test guards what can do harm, not everything that can be wrong. Tests go where a fault costs something: a secret or a credential that leaks, a check that can be passed around, data lost or written twice, work that leaves without review, a command that does the wrong thing, a contract another repository reads, a crash or a hang. There they are thorough: the failure paths too, red first.
- What only reads wrong is fixed, not guarded: a wording, a label, a colour, a layout, the order of an output, a translation, formatting the formatter already checks. Nobody writes a test for it. A test that holds a text verbatim is deleted when the text changes, not changed with it.
- Before a test is written, it names the harm it prevents. If it cannot, it is not written. A finding of a reading that names none is fixed without a test.
- A defect that can do harm is fixed only after a unit or integration test reproduces it. The test fails first, then the fix makes it pass; the rules on tests below hold within the three above.
- A test is trusted only once it has been seen to fail: break the code on purpose, watch the test go red, and restore it. A break that does not compile proves nothing.
- A guard asserts what its reference set is, not only that it has entries: an empty set, one that cannot change, and one nobody reads all pass as green as one that works.
- A test asserts the observable effect, never what the system reports about itself or its internals. A fixture states what the real system produces, never what the code assumes, and is never edited so that a feature has something to show.
- A test depends on nothing outside itself: not on the machine's configuration (a suite calling
git runs with
GIT_CONFIG_GLOBAL=/dev/null), not on wall-clock time, and not on a pinned version written into it - it reads the version from the build. - Before a commit, the full suite runs as its own step and its result is read. A commit is gated on the exit code, never on grepped output, and never chained onto the test run.
- A change handed over says which test levels actually ran - unit tests, local VM, rented
machine - never which ought to have. A change to documents or issues only needs the tests tagged
documents, which run alone; every test that reads a document carries that tag, and a test fails when one does not. - A repository with a workflow runs
porelo-release'scheck-shared,check-citationsand, where there is a documentation chapter,check-doc-site, with the tests taggeddocuments, on every push and pull request, on a GitHub runner; the build skips a change to documents only. - A test result names every skipped test, never just a count.
- A failing check prints what it asked and what it got, never a guessed cause, and its failure path has been made to happen once and read.
Claims and writing
- Measure before you claim, and say what was measured and what inferred. Agreement between agents counts only where each measured, and "I could not get X" only once a second method failed too.
- A command that should have changed something is checked by observing the change - connect, read the file, ask the daemon - never by its exit code alone.
- Every date, time or age written is read from the system (
date -u) first, never from memory. An age is computed from two timestamps that were both read. - When a thing is right in two forms, keep one. The second copy is the one that goes stale.
- Documentation and rules state what is true now: no dates, no history, no stories, and nobody named, only roles. The shared blocks' markers are the one exception.
- Everything written is US English - documentation, issues, comments, commit messages, the channel. Replies to the operator are in the language the operator writes in.
- A finding worth keeping is committed - in an issue, the documentation or a decision - never left only in a conversation or the channel.
- A finding taken from a third-party source is written as the finding, never naming the source, in anything committed.
Security and tools
- A secret never appears in a command line, a log line, a file name or an answer. It reaches a process through its environment or standard input, is stored only encrypted and readable by its owner, never touches a filesystem in CI, and is never promised to be wiped from memory in Java or Dart - only kept in fewer copies for less time.
- Every download follows redirects (
curl -L), since any server may answer with one, and is checked against the digest its source names before it is used. Credentials are never passed on to another host (--location-trustedis never used). - Input from outside - upstream metadata, the environment, a file or an answer from another program - is validated before it reaches a file, a command or a decision.
- A binary run with more rights than its caller is taken by path and refused when it or its directory could have been placed or changed by anybody else.
pkill -fandpgrep -fmatch their own command line, and can kill the shell running them; use a bracket pattern like[p]odman.- A long build or suite is watched through
teeinto a file, never through a pipe intogrep, which holds everything back until the end and looks like a hang. ssh -n host 'bash -s' <<EOFruns nothing and exits 0; a script sent on standard input goes without-n.
Skills
-
Skills come from
https://fuinorg.jfrog.io/artifactory/agent-skills/, one reviewed package per skill, readable without credentials; each repository's own part names only which skills it uses, by slug. -
Fetch, check and unpack them like this, with the skills directory of your own harness (Claude Code reads
~/.claude/skills/<slug>/):BASE=https://fuinorg.jfrog.io/artifactory/agent-skills curl -fsSL $BASE/.skills/skills.json # every slug, latest version curl -fsSL -o s.zip $BASE/<slug>/<version>/<slug>-<version>.zip curl -fsSL $BASE/../api/storage/agent-skills/<slug>/<version>/<slug>-<version>.zip # its sha256 unzip -q -d <skills directory>/<slug> s.zip -
A harness that cannot install a skill reads its
SKILL.mdfrom a directory outside the repository. -
A skill is knowledge, not authority: where it and a measurement disagree, the measurement wins, and a finding from reading code against a skill is a guess until a failing test reproduces it. Whether a skill is loaded is asked of the harness, not read from a directory.
-
The skills a repository names are applied, not only named. They are loaded before its code is changed. The code an issue changes is read against them before it is handed over, and the hand-over names each skill with its version; a finding is fixed with a test seen to fail first, or declined with the reason written in the issue. A repository that names skills for the first time opens an issue reading all of its code against them.
Code and builds
- Fail closed and loud: when a dependency is unreachable, a key is unknown or a check cannot run, stop with a non-zero exit code and say why; inside a program, an exception that says why does the same. Never silently do less.
- A comment says why, never what, in one line where it can. Reasoning that does not fit goes into documentation or a decision, and a small named method is preferred over a comment explaining a block.
- Dot files are not committed.
.gitignoreignores.*and excepts only what a build or the shared code style needs -.github,.gitignore,.gitkeep,.editorconfigand.git-blame-ignore-revs, in a Maven repository also.mvn- and what is true of one machine goes into.AGENTS.md. - Maven is the build system: a Java repository builds, checks and tests with Java and Maven only.
Another build tool only where the platform a repository builds for supports no other - an IntelliJ
plugin, since JetBrains supports only Gradle - named with the reason in its repository's own part. A
file that cannot be Java -
mvnw, or a script that runs where there is no Java yet - is named in its repository's own part with the reason, and no repository keeps a copy of a helper another one has. - Maven modules that belong together - the layers of one program, its packages - are submodules of
one module of packaging
pomnamed for what they share, so the root lists parts, not pieces. Grouping changes onlyparentandrelativePath, never agroupIdorartifactId. - Every directory with a
pom.xmlhas aREADME.md: two or three sentences on what the module is and is not, a link to each of its submodules, and only the few technical facts that matter most, such as what it must never depend on; details are linked indoc/, never repeated.porelo-release check-readmesholds it. - Only what another repository or an outsider builds against is published to Maven Central, each such
module opting in; the root and every grouping module are never published. Every published pom is
flattened, so it names no parent and a consumer needs nothing but the module itself;
porelo-release check-releasesrefuses a release whose published pom still names one. A BOM is imported, never a parent. - Every Porelo Maven repository takes
org.fuin.porelo:porelo-parentas its parent and declares only what is its own; one that publishes declaresflatten-maven-pluginby name in its root, and the parent's management flattens every module.porelo-parentnames no parent and uses nothing of another Porelo repository. - Every Java package with main code is
@NullMarkedand checked by NullAway as an error when it compiles, with a test that fails on an unmarked package. Files.movewithATOMIC_MOVEreplaces a file that already has the target name; where the first of two writers must win, publish withFiles.createLink(link(2)), which fails on an existing name.- Java code carries brief Javadoc on every public type and method; a test method's name reads as a
sentence (never
testXxx) and an assertion states its reason (.as(...)). Every build call in CI names its repositories in the repository itself (Maven:-s settings.xml). - Everything a build runs is pinned: actions by commit with the version beside it, the JDK (from
porelo-machines jdk --github), the build tool's wrapper (Maven, Gradle) and images by version and digest.porelo-release check-actionsenforces it. Dependabot moves only the actions, weekly, in one group, after three days; the operator merges each, as code owner of.github/, once the pull request's light check (no rented machine) passes. Everything else is moved by the repository's agent. - A push that changes only
.github/starts no Build; the next push with content builds with it. - While it is developed, a repository may build against another Porelo repository's snapshot from Sonatype, and its build may fail for a while when that snapshot moves; a release depends only on releases, which the release build checks.
- Packages are built online: offline, the CycloneDX bill of materials skips itself with only a warning, and the package ships without it.
- A publish is believed only once the published index shows the exact version, probed with retries; a snapshot version sorts above the one before it. Retiring a package removes it from the index too.
- Every native executable is built with
-march=x86-64, so it starts on any x86-64 CPU, and the build checks each executable for exactly that instruction set.
END Shared Area · sha256
df1a9687073cea82
Work in this repository
- This repository holds no product code, only
project.ymland the requirements not yet placed. There is no build: it is Markdown. - A requirement moves to the repository that builds it as a handover, not a copy. This
repository's issue prefixes are
PJ, andAfor candidate agents without a repository; they are not inproject.yml, becauseproject.ymlis defined here. project.yml's keys are documented inporelo, underdoc/project-file.md, not here.
Coordinating
The agent working here coordinates the agents of the other repositories.
- A defect found by reading another repository goes to its responsible agent, with the text and the position. Reading does not replace asking: only that agent knows why something is so, or whether a requirement went away finished or abandoned.
- When a shared block changes, compare every copy's hash and text with this one, at the commits the agents name, and once more at their tips before calling it done.
- A requirement here that waits on another repository's issue is checked against that repository, which does not know it is cited. Report every hit, never a truncated search.
- A check keyed on file names cannot verify a rename; ask the history (
git log --diff-filter=D) or the agent. A check over an empty set proves nothing, so assert it is not empty.
