agentleFS
TOMOSIA-VIETNAMTOMOSIA-VIETNAM/agent-stack/AGENTS.md

agent-stack

AGENTS.md1 starsChanged 38 days agoApache-2.0View on GitHub

A Claude Code plugin that generates a project's .claude rules, skills and commands by selecting from a curated knowledge base. It never writes rule content with a model. Everything here is arranged around these. A change that weakens either needs to say so out loud, not slip through: 1. Selection is deterministic. Resolution, compatibility checking, composition and validation run in TypeScript under src/. A model maps a request to CLI flags and relays conflicts to the operator. It decides nothing…

  • Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.

    Of the AGENTS.md files we read, the 31,700 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.

  • In this repository2 other files it gives its agents.

The file

We split this AGENTS.md into the rules it gives an agent

2 rules. About 0 are common, 1 is distinctive, and 1 is very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈1

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Conflicts are never resolved silently: When two selected technologies are declared incompatible, the run stops, names the conflict, and …

    line 21
  2. Very specific:

    Keep the CLI's --json shape stable — commands/generate.md parses it.

    line 218

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

agent-stack

A Claude Code plugin that generates a project's .claude rules, skills and commands by selecting from a curated knowledge base. It never writes rule content with a model.

Two invariants

Everything here is arranged around these. A change that weakens either needs to say so out loud, not slip through:

  1. Selection is deterministic. Resolution, compatibility checking, composition and validation run in TypeScript under src/. A model maps a request to CLI flags and relays conflicts to the operator. It decides nothing else and writes no content. Neither does the generator: a selected file is copied byte for byte, with no exception. agent-stack composes no document: it writes no CLAUDE.md. AGENTS.md is copied too, but only when the project has none: it is a template the team fills in, so it belongs to the project from the moment it is written. The approval checklist only narrows that result: it drops artifacts, never adds one, and never touches what a kept artifact contains. An unattended run and an approved one differ in what was dropped and in nothing else.
  2. Conflicts are never resolved silently. When two selected technologies are declared incompatible, the run stops, names the conflict, and prints the flag that waives it. The generator does not pick a winner.

A third holds for the knowledge base: generating is offline. A generate run reads this repository and writes the target directory. Only npm run sync, run deliberately, touches the network.

Commands

npm run check    # typecheck + tests at 100% coverage of src/ + bundle — before every push
npm run try      # generate into .agent-stack-try/ (gitignored) and read the result
npm run sync     # re-copy imported content at the pinned commit

npm run new-framework -- <id> "<Name>"   # catalog entry + templates/framework/ cloned into knowledge/

dist/agent-stack.mjs is committed so the plugin runs without an install step. Rebuild and commit it with any change under src/ — npm run check does the rebuild.

Pipeline

One concern per module. Put a change where the concern already lives:

ModuleOwns
catalog.tsLoading the knowledge base, deriving metadata, lint
upstream.tsWhat was copied from where, and under which licence
resolver.tsExpanding the technology graph over requires, reporting conflicts
selector.tsWhich artifacts apply to a resolved stack
composer.tsDeciding where each file lands, the manifest, the legacy CLAUDE.md block
prompt.tsThe terminal approval checklist, and nothing else
validator.tsCompleteness and consistency findings
emit.tsThe only module that writes or deletes; what is on disk versus the manifest
table.tsBox-drawn tables for human output, and nothing else
cli.tsFlags, human and --json output
bin.tsThe process boundary: argv in, exit code out, nothing else

emit.ts may only remove paths listed in the previous run's manifest, and not one the project has edited since. Never widen that. Everything outside the manifest belongs to the user, and so does anything inside it that the user changed.

One exception, and only one: a CLAUDE.md an earlier version wrote its agent-stack:begin / agent-stack:end block into. Claude Code reads AGENTS.md only while no CLAUDE.md exists, so the block is taken out — and the file removed when nothing else is left in it. It is bounded like the rule it bends: only in a project with a previous manifest, only the text between agent-stack's own markers, and the file only when nothing of the project's remains. A CLAUDE.md with the project's own text, or a CLAUDE.local.md, is never touched; it is reported as agents-md-shadowed.

The same manifest decides what may be written over. inspectTargets marks a target owned when the previous manifest lists it, and exists when something is there that agent-stack never wrote. An exists path is dropped from the selection — not emitted, not in the new manifest — and reported as a waivable warning. Only --overwrite, or a tick in the checklist, takes it over. A run must never acquire a file by being run twice.

The manifest records a sha256 per copied file under checksums. A listed path whose bytes no longer match — or a generated skill directory holding a file the manifest does not list — is modified, and is handled exactly like exists: dropped, warned about, taken back only by --overwrite or a tick. Stale output that is modified is retained, never removed. A deleted file is not an edit. A manifest from before checksums cannot tell, so it behaves as it did when it was written.

check is an unattended generate that writes nothing and reports pendingChanges: what a run would add, update or remove. Keep it built from the same pipeline — a second definition of "up to date" would drift from the generator. It exits 3 when the project is behind.

Nothing is @-imported. Claude Code loads .claude/rules/ on its own — a rule without paths: front matter at launch, one with it only when Claude touches a matching file — and an import would load a path-scoped rule at launch, undoing its scope.

Input

The only input is --framework (repeatable), and the catalog holds nothing but frameworks — Rails, Laravel, FastAPI, React, Vue and NestJS. A framework is the one thing an operator always knows; every extra input is another chance to omit something and have rules vanish without a word.

Two consequences, both deliberate:

  • An artifact is gated on a framework. Ruby style rules are gated on rails, and Active Record rules on rails — never on a ruby or activerecord entry nobody can select. An artifact gated on something absent from the catalog can never be emitted.
  • Selection ignores versions. A rule applies to a framework, not to one of its releases; content that is genuinely version-specific says so in its body.

requires and conflicts_with are still resolved and still enforced — they are what invariants 1 and 2 are made of — but no catalog entry needs them yet.

--yes and --overwrite are not input. They waive a step the run would otherwise take on its own — showing the checklist, and leaving the project's own files alone — the way --accept-conflict waives a conflict. Neither can select an artifact, and no flag should be added that can: what a stack needs is the knowledge base's answer, and what gets written is the checklist's.

Knowledge base

knowledge/
├── catalog.yaml                          the frameworks: requires / conflicts_with
├── upstream.yaml                         what was copied from where, pinned to a commit
├── rules/global/<name>.md                -> .claude/rules/<name>.md
├── rules/framework/<fw>/<name>.md        -> .claude/rules/<fw>-<name>.md
├── skills/framework/<fw>/<name>/SKILL.md -> .claude/skills/<name>/, with its files
├── commands/<name>.md                    -> .claude/commands/<name>.md
├── agent-md/<name>.md                    -> AGENTS.md, written only if absent
└── support/<source>/<upstream path>      -> where upstream.yaml declares, imported content only

The path is the metadata, and the file is never touched. Four things are read off the path and nothing at all is read out of the file:

DerivedFrom
typethe top directory — rules/, skills/, commands/, agent-md/
layersegment 2, when it is global or framework; otherwise global
applies_tothe framework directory under framework/
idthe rest of the path joined with -, minus .md. A skill's is its own directory name

A framework directory that is not a catalog id fails lint by name. A segment that is not lowercase kebab-case fails by name, because it could not survive becoming a filename. Nest as deep as you like below the framework — rails/db/indexes.md is rails-db-indexes.

A skill's id is its own directory name, not the joined path: skill names are a flat namespace Claude matches a task against, so write the framework into the skill's own name (rails-feature, not rails/feature).

A project can select more than one framework, and two of them will want a rule called the same thing. For a rule or a command the framework directory is already part of the id, so rails/conventions.md and laravel/conventions.md emit side by side and never collide. A skill has no such prefix, so two skill directories of the same name are a load-time error naming both files — renaming one behind the author's back would change the name Claude matches against.

Ordering is alphabetical by that id, on disk and in every listing. There is no priority field, and no numeric prefix, because a field that exists only to fight the sort order is one more thing to keep in sync.

Rule = a convention, loaded by Claude Code from .claude/rules/. Skill = a procedure with steps, loaded when the task comes up. Command = something the developer types. AGENTS.md template = a skeleton the team fills in, which Claude Code reads (v2.1.277+).

A top-level directory under knowledge/ that is none of these fails lint by name, so content cannot sit where the loader never looks. claude-md/ was a type until agent-stack stopped writing CLAUDE.md.

The AGENTS.md template is a seed, not output. emit copies it with COPYFILE_EXCL when the project has no AGENTS.md, and otherwise reports it under leftAlone. It is never in the manifest, has no checksum, is never removed and is out of --overwrite's reach: once written, every byte of it is the project's. Two templates matching one stack is an ambiguous-agents-md error — picking one would be resolving a conflict silently.

agent-stack does not read or write front matter. Whatever a file carries is Claude's to read: name and description in a SKILL.md, description in a command, nothing at all in a rule if you want nothing. Put no agent-stack metadata in a file — there is none to put, and anything you add is copied into every generated project. knowledge/README.md is the full reference for the layout.

Imported content

The global layer is copied from other repositories and committed here, pinned to a full 40-character commit SHA. A branch or tag is rejected.

  • Never edit a file under a path upstream.yaml maps to. The next sync overwrites it. Anything an imported file lacks — a description, a layer — is declared in upstream.yaml or derived from the path.
  • Writing your own file beside the imported ones is fine. A sync removes only what the previous sync delivered, recorded in upstream.lock.json, so a file that was never in that record is never a candidate for removal. Commit the lock file with the content it describes — without it, the next sync cannot tell an upstream deletion from your work, and removes nothing at all.
  • An imported file that reaches outside its own directory — /ship spawning the personas in upstream's agents/, a skill linking to ../../references/ — gets that file through support in upstream.yaml: upstream path -> where it lands under .claude/, chosen so every relative link resolves with no imported byte edited. Sync keeps it in knowledge/support/<source>/. A support file is written only while something from its source is, and is otherwise owned, checksummed and protected like any artifact. It is not a content type for this repository's own files.
  • Moving a pin is a reviewable commit: npm run sync -- --ref <sha>, then read the diff of what it brought in.
  • Attribution lives once per generated project, in .claude/agent-stack-manifest.json under imported — repo, commit, licence, licence URL, copyright. That is how the upstream MIT terms are met, so keep those fields accurate and do not add a source that cannot be attributed there.

Working here

  • New behaviour needs a test; a bug fix needs a test that fails before it.
  • Coverage of src/ is held at 100% — statements, branches, functions, lines — by vitest.config.ts, so npm run check fails below it. A branch no input can reach is removed, not excluded: a fallback the types already rule out is noise a reader has to reason past.
  • tests/pipeline.test.ts loads the real knowledge base, so a content change that breaks an invariant fails the suite.
  • Keep the CLI's --json shape stable — commands/generate.md parses it.
  • Rules and skills are advice that will be applied to real code. Prefer guidance that has paid off over guidance that sounds right, be specific enough to act on, and say why when the reason is not obvious.
  • Conventional Commits, imperative subject under 72 characters. The body explains why.

MY_IDEA.md states the project's purpose in plain language — read it for why, and this file for how. What exists today is framework selection, a check for drift, and natural-language input — which lives only in commands/generate.md, mapping a sentence to --framework flags and listing every term it did not use. Full project bootstrap is not built yet.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.