Skip to content

Latest commit

 

History

History
279 lines (208 loc) · 12.7 KB

AGENTS.md

File metadata and controls

279 lines (208 loc) · 12.7 KB

AGENTS.md — harness

The starting harness a new repo gets scaffolded with: the rules, the stack modules, the moulds and the generator that writes a project's first AGENTS.md. It is applied to itself — the block below was written by its own init-project.

What it is not. It is not a project template and it does not generate code. It writes and maintains one file per project, and touches nothing outside its own markers.

Commands

This project's own, and first on purpose: the highest-return section, and the one almost everyone buries halfway down.

pnpm install --frozen-lockfile   # the lockfile's exact versions, or fail
pnpm run init <repo>             # write that repo's first AGENTS.md
pnpm run propagate <repo>...     # refresh layer 1 where it is already installed
pnpm run sync:global --dest DIR  # deploy agents, commands, reference
pnpm run uninstall <repo>        # take the blocks out again
pnpm run asvs:check              # is the ASVS sheet still current? needs the network
pnpm run asvs:age                # is the ASVS check stale? cuts at 180 days
pnpm run test                    # the tests on their own
pnpm run verify                  # the same thing CI runs

One package, one root. Every command runs from here, and the four that write somewhere else — init, propagate, sync:global, uninstall — are dry run by default and write only with --apply.

⚠️ sync:global demands --dest and has no default: writing into the real configuration would put two sources behind one destination, and the last to run would win in silence.

Before doing anything

  1. README.md — what it does and the opinions it imposes.
  2. layer1.md above the --- — the two questions a rule has to pass before it is written.
  3. reference/traps.md — before touching the environment. It is the only one that prevents an error rather than explaining it afterwards.

📌 The reasoning lives in docs/DECISIONS.md. It travels as the consequence written into the rule itself.

Verification

  1. Verify before asserting, and with whatever consumes it. Never take a file's word about the environment: run the tool that reads it and trigger what it controls. What cannot be checked stays ❓ pending, said out loud.

    Asking is not enough: pnpm config get answered 42 for a key that does not exist, and true for one that changed nothing.

  2. "Done" means verified by CI, not asserted. What has not passed the gate stays pending, and that is said. A test only verifies if it can fail: its expected value comes from the requirement, never from the code under test — neither its output pasted back nor its logic repeated.

    A green on your machine only proves your machine; a value copied from the code, only the code.

What the AI does not decide

  1. .env, credentials and real data dumps: neither read without explicit permission, nor committed. What the agent reads enters the conversation, and from there it does not come back. .env.example is what travels.

  2. The AI installs and configures nothing on its own: it proposes, the owner decides.

    An install or a config change lands in everyone's lockfile and CI, and neither shows in the diff the agent presents.

  3. Nothing destructive without a --dry-run first, showing what would be deleted.

    A git clean -fd took three unrecoverable directories.

  4. The AI commits; the person decides the push. Local commits pile up until the go-ahead, and one go-ahead covers one push, not the next ones.

  5. No AI trace in commits or output: no Co-Authored-By, no "Generated with…".

    Tools add it by default. History says what changed and why, not with which tool.

  6. Conventional Commits, in English: type(scope): summary, with ! for a breaking change. The types: feat · fix · docs · style · refactor · perf · test · build · ci · chore · revert. The body carries only the list of changes and the reason for each — the how is already in the diff.

The environment

  1. An agent session has no Node auto-switch. The hook lives in the shell profile and a non-interactive session never loads it: the session runs on whatever Node it inherited when it was launched, and cd into another project does not change it.

    Start the session from a terminal already on the repo's version — enter the directory, let fnm/nvm switch, then launch the agent. One session per project.

    If that was not done, activate it on every invocation: fnm use <major> then the command. fnm exec --using=N pnpm does not work on Windows — pnpm is a .cmd and fnm exec only launches real executables.

  2. pnpm, never npm.

npm install leaves a package-lock.json next to pnpm-lock.yaml, and two installers disagree in silence.

  1. pnpm settings live in pnpm-workspace.yaml; .npmrc carries registry and authentication only — from pnpm 11 anything else there is ignored, and pnpm 10 still reads it, so it works today and stops on upgrade. Every repo with dependencies sets minimumReleaseAge there.

Continuity

  1. State lives in docs/SESSION.md, written when a task closes — at session close there is no context left — and overwritten: git keeps the history. Nothing the code or git log already tells goes in.

  2. The reasoning lives in docs/DECISIONS.md, append-only. A decision that changes is not edited: another is appended revising it, and the old one is marked, never deleted. Knowing what was believed is part of the record.

  3. An exception is declared in the project's AGENTS.md, with its reason and its scope; failing silently is not an option. Does not apply to security, AI traces or pushing.

Naming

  1. File names, configuration keys and identifiers in English.

Knowledge that is not in this file

  1. Skills are per project. npx autoskills installs them into <repo>/.claude/skills/, which is git-ignored and rebuilt like node_modules; skills-lock.json is committed — it carries each skill's source and hash, and it is what makes three machines rebuild the same set. The developer runs autoskills, not the agent — npx downloads a package, and installing is not the agent's call.

Global skills in ~/.claude/skills/ are the machine's, not the project's.

  1. Official docs and MCPs are consulted and never silenced, at the installed version — from the lockfile, not the ^ range. Context7 is the one every project uses: resolve-library-id, then query-docs at that version. If the MCP does not answer, say so instead of pretending it was consulted.

Review

  1. When work is reviewed, this is the shape. architect before building; reviewer and security over what was built, read-only; tester to verify. Run them whenever you want, together or on their own.

No stack module: the generator did not recognise a stack it has a module for. This is not a failure — a project can be perfectly served by layer 1 alone — but if the stack is one that should be covered, say so rather than filling this in by hand: a module written here does not travel to the next project.

This project

Everything below is yours: the command that updates layers 1 and 2 never touches it.

Paths and domain decisions

  • layer1.md is the product. Everything else exists to deliver it. It has no size cap: the new layer 1 inherits none, and a rule that enters should still make another leave.
  • bin/ holds effects; lib/ holds decisions. A function that decides goes in lib/ so it can be tested without touching disk, and the command above it only reads, writes and prints.
  • reference/ is opened on demand, never loaded as context. Every entry in it must be named by a prompt, a rule or the README — there is a test that fails otherwise.

Where the model tends to assume wrong

  • A guard passes and you conclude it is watching. Symptom: a new check is green on its first run. Cause: it may be green because it looks at nothing. What to do: break it on purpose first — add an orphan file, inflate the count past the cap — and see it turn red with the message you wrote. Every guard here was checked that way.
  • You ship material and nobody opens it. Symptom: a file lands correctly in reference/ and no agent ever reads it. Cause: shipping and reading are different questions and only the first is obvious. What to do: point a prompt, a rule or the README at it — the test for this exists because it happened.
  • A rule delegates to a place you never open. Symptom: a rule says "the values live in the layer 2 module" and no module has them. What to do: when a rule points somewhere, open that place before writing the rule.
  • You rewrite a file having read half of it. Symptom: something that used to be exported is suddenly missing. Cause: writing a whole file from a partial read. What to do: read it to the end, or edit the part you mean.
  • A figure copied instead of measured. Symptom: a count in prose disagrees with the tool. What to do: run the tool and quote it, or delete the figure. Prefer no number to a stale one.

Security level

L1 — no login, no money, no personal data: this repo is text, scripts and their tests, and it runs on the machine of whoever installs it. From L2 up, the security subagent cites versioned requirements rather than inventing them.

Declared exceptions

One, and it is about where a check runs rather than whether it runs.

⚠️ The leak guard's name check does not run in CI, on purpose. leak-guard has two halves: shapes — absolute Windows paths, home paths, paths into the private notebook — which need no list and run everywhere; and names, which need to know what the private repositories are called.

🔴 That list cannot live here, and the reason is circular. To catch a name being published, the guard must hold the name; holding it in a public repo publishes exactly what it defends. So the list stays outside — in a git-ignored .private-names, or in HARNESS_PRIVATE_NAMES — and on a runner it is absent. The guard says so out loud and runs its shape checks: partial, and never silent about being partial.

Scope, and why it is acceptable. Commits are made from machines that have the list, so verify runs the full check before anything reaches a remote — measured on 2026-09-08: green here, with the list loaded. CI is the second net, not the first.

What was rejected, and why. Passing the names to CI as a repository secret would close it — one env: line — but it takes the private names off the owner's machines, which is the single thing this design exists to prevent, and it buys a case that does not occur: committing from a machine without the list. Decision of the owner, 2026-09-08.

It leaves the day the guard learns to recognise the names by shape rather than by list, or the day commits start coming from a machine that has no list.

📌 This repo is in English throughout.

This project's documents

  • AGENTS.md — how work is done here (this file)
  • docs/DECISIONS.md — why it is the way it is
  • SECURITY.md — how a flaw is reported, and what is worth reporting
  • No SESSION.md yet. This repo has had no session close with work half done, and its plan lives in the private notebook. It is created the day its row in the table below becomes true, not before

When to create the next one. Each template is opened when the file is created, so the new file arrives with its own rules inside:

When… → Document
a session ends with work half done → SESSION.md
there are more tasks than fit in your head → TODO.md
some blocks depend on what other blocks produce → ROADMAP.md

For anything not on that list — a RUNBOOK.md, a migration — the recipe is the same three questions: does it answer a question no current file answers, is it known who reads it, and is it born with its own pruning rule? Without all three, it is not created.

State

No state file yet — see the previous section. Until there is one, what a repo this size can be asked is git log --oneline -20, which does not age.