fascicle
AGENTS.md4 starsChanged 15 days agoApache-2.0View on GitHub
Instructions for any coding agent (Claude Code, Codex, Cursor, Windsurf, Amp) operating in this repository. If your task is to construct a new agent that consumes the published fascicle package (rather than to modify this repo), stop here and follow docs/blueprint.md — the recommended architecture for Fascicle apps: one composition layer, three adoption tiers, markdown prompts, normalized module contracts, stub-engine testing, and ast-grep rules that enforce the boundaries. docs/leaf-arm-spine.md is the layering decision guide and examples/newsroom/main.ts the vocabulary tour. The…
- Reads credentials
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,439 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.
The file
Show its rules and text
We split this AGENTS.md into the rules it gives an agent
16 rules. About 0 are common, 6 are distinctive, and 10 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈6
Distinctive · estimate
This author's own choices. Worth a look.
10
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 11
pnpm check:allis the single source of truth for "done": If it exits 0, your work is complete. - Distinctive: line 17
Before declaring a task finished:
- Very specific: line 20
If it fails, read .check/summary.json to find which check failed.
- Very specific: line 21
Read the corresponding per-tool JSON (.check/lint.json, .check/dead.json, etc) for structured diagnostics.
- Very specific: line 53
Functional/procedural: No classes. Use modules, closures, and plain data.
- Very specific: line 54
Named exports: No default exports. Enforced by rules/no-default-export.yml.
- Very specific: line 57
File extensions: import with .js even from .ts files (NodeNext resolution).
- Distinctive: line 112
Cross-module access is sealed two ways: Every module is reachable only through its barrel:
- Very specific: line 119
Adding a module: create src/<module>/index.ts, add #<module> to the root package.json imports, tsconfig.json paths, and vitest.config.ts …
- Distinctive: line 124
Do not disable lint rules to pass the check.
- Distinctive: line 125
Do not add dependencies casually.
- Distinctive: line 126
Do not add a file that is not imported by something.
- Very specific: line 127
Do not skip writing tests for new behavior.
- Very specific: line 128
Do not bypass pnpm check:all by running individual tools and claiming done.
- Distinctive: line 129
Do not re-baseline to clear a failure you introduced.
- Very specific: line 138
Use these during implementation for real-time dead-code, boundary, and structural feedback, rather than waiting for the final pnpm check.
≈ “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.
Files
AGENTS.md
Instructions for any coding agent (Claude Code, Codex, Cursor, Windsurf, Amp) operating in this repository.
Building an app on Fascicle?
If your task is to construct a new agent that consumes the published fascicle package (rather than to modify this repo), stop here and follow docs/blueprint.md — the recommended architecture for Fascicle apps: one composition layer, three adoption tiers, markdown prompts, normalized module contracts, stub-engine testing, and ast-grep rules that enforce the boundaries. docs/leaf-arm-spine.md is the layering decision guide and examples/newsroom/main.ts the vocabulary tour. The canonical worked example is examples/pr-improve/ (rationale in its docs/architecture.md); copy its rules/ directory to enforce the blueprint in your own app. The rest of this file is about developing Fascicle itself.
The contract
pnpm check:all is the single source of truth for "done". If it exits 0, your work is complete. If it exits non-zero, it is not. No other signal counts.
The suite is checkride, configured in checkride.config.json. Each check occupies a named slot. A handful of slots run this repo's own commands (deps, exemplars, examples, prose-health, types-app, types-e2e, e2e, learn), and everything else is a checkride built-in.
pnpm check runs the default set (every slot except the opt-in ones) and is what you should use in tight feedback loops. pnpm check:all adds the opt-in slots (Stryker mutation, the build → publint/attw/pack/smoke/snippets packaging gate, the learn walk through the course, and the Playwright e2e slot) and is the gate before declaring done. The one slot neither of them runs is security, the dependency audit. It runs only as pnpm check:security, and the note on its entry in checkride.config.json says why.
Before declaring a task finished:
- Run
pnpm check:all. - If it fails, read
.check/summary.jsonto find which check failed. - Read the corresponding per-tool JSON (
.check/lint.json,.check/dead.json, etc) for structured diagnostics. - Fix the root cause, not the symptom.
- Re-run
pnpm check:all.
The same reports exist for a red CI run. checkride prints one pass/fail line per slot and writes the tool's actual output to .check/, so the workflow log alone will tell you that types failed without telling you why. Every workflow that runs the suite uploads .check/ as an artifact when it fails, so steps 2 and 3 work the same way against a remote run: download the artifact from the run page, open summary.json, then read the per-tool report it names. The coverage/ and mutation/ HTML trees are left out because they are the bulk of the directory and nobody reads them during triage.
The baseline
checkride.baseline.json grandfathers the diagnostics that predate the gate, so a slot reporting N baselined (grandfathered) is green. Only findings absent from the baseline fail a run, which is what makes the gate ratchet: existing debt is frozen, new debt is blocked.
Never run checkride baseline to make a failure go away. Re-baselining is a deliberate act (recording paid-down debt, or absorbing a tool's fingerprint change) and belongs in its own commit with the reason stated. A finding your change introduced gets fixed, not grandfathered.
Tight feedback loops
During iteration, use narrower commands for faster turnaround:
pnpm check # default set (excludes the opt-in slots)
pnpm check --bail # stop at first failure
pnpm check --only types,lint # just the fast checks
pnpm check --changed # affected-only, against the git diff
pnpm check --include mutation # default set plus opt-in mutation
pnpm check:security # the dependency audit, which no other run includes
pnpm test:watch # watch-mode tests while implementing
pnpm exec tsc --noEmit # just typespnpm check:all (every slot but security, the opt-in ones included) is the final gate before declaring done. Stryker's mutation slot is the slowest and is opt-in for exactly this reason; incremental mode keeps re-runs cheap once the shared baseline at stryker.incremental.json is up to date. build re-bundles, then publint, attw, pack, smoke, and snippets validate the npm packaging against dist/ (manifest, type resolution, import liveness, doc snippets vs the built .d.ts), so surface changes fail here instead of at release time. learn walks the course in docs/learn/ against the same dist/: it writes each lesson's files into a scratch project, runs the lesson's commands, and holds what they print to the lesson's text, so a change that breaks a lesson fails here too. When you add or remove a code block in a lesson, update the block index that scripts/check-learn.mjs reads it by.
Conventions
- TypeScript strict mode. Everything. No
any, no!non-null assertions without justification. - Functional/procedural. No classes. Use modules, closures, and plain data. Enforced by
rules/no-class.yml. - Named exports. No default exports. Enforced by
rules/no-default-export.yml. - Naming:
snake_casefor variables and functions,PascalCasefor types and interfaces,SCREAMING_SNAKE_CASEfor constants. Exports are enforced byrules/snake-case-exports.yml. A factory's trailing argument is named for whether the caller can drop it. It isXConfigwhen the argument is required (retry(inner, config),loop(config),compose(inner, config)), andXOptionswhen it can be omitted (sequence(children, options?),pipe(inner, fn, options?)). The two tests coincide insrc/coreandsrc/composites, because an argument that carries a required field is one you cannot leave out. The display label is spellednamein either case, and it always means display, because identity is theid. - Function docs. Every module-level function carries a
/** ... */block directly above it: one line saying what it is, then optional paragraphs on why it behaves the way it does. Comments inside function bodies use//. Comments state what the code does now, in the present tense, with no design-history references. Enforced byrules/function-comment-required.yml. - File extensions: import with
.jseven from.tsfiles (NodeNext resolution). - Prose style. Markdown and TypeScript doc comments are linted by vale as the
proseslot; the config is.vale.iniand the rules are plain YAML in.vale/styles/Repo/.e.g.,andi.e.,are house style and want both periods and the comma. No sentence opening on "there is"/"there are" (name the subject that acts instead). No hyphen after an-lyadverb. An aside takes round brackets, never a pair of em dashes (a single em dash between clauses is a pause, and pauses are fine). One semicolon to a sentence, and one colon. None of the generated-prose fingerprints inDrift.yml. The project name is capitalized in prose (Fascicle) and lowercase only as an identifier (thefasciclepackage, thefascicle/*subpaths, thefascicle-viewerbinary). The rules belong to this repo: disagree with one and edit or delete it rather than suppressing it line by line. The rules police mechanics; the exemplars carry the voice. - Runtime messages are prose too. Vale maps
ts = js, which lints doc comments and leaves the code alone, so it never sees a string literal. The two mechanical rules a user-facing message needs as much as a paragraph does are mirrored as ast-grep inrules/no-latin-abbrev-in-strings.ymlandrules/no-em-dash-pair-in-strings.yml, and they run in thestructslot. The rest is judgment. A message says what happened, then says what to do, and it carries the value the reader has to act on. The shape to copy isno provider specified: pass `provider` to generate() or set `defaults.provider` (configured: anthropic, ollama). - Tests colocated in
__tests__/: unit tests forfoo.tslive at__tests__/foo.test.tsnext to it (for example,src/core/branch.ts↔src/core/__tests__/branch.test.ts). Cross-cutting tests (integration, signal handling, fixtures) live undersrc/<module>/test/instead, which is outside the source-semantics rules and spell-check. - Coverage floor: 70% lines/functions/branches/statements. Raise it as the codebase matures.
- Architectural boundaries are enforced. The rest of
rules/uses ast-grep to police layer separation betweencore,engine, adapters, and theclaude_cliprovider — for example, no adapter imports incore, no cross-composer imports, noprocess.envreads outside the audited exceptions, no provider SDKs outsidesrc/engine/providers/, nochild_processin the engine outsideclaude_clior inacpoutsiderun_command.fallow.tomladds a directory-level default-deny boundary DAG on top. Readrules/and thefallow.tomlboundaries before adding a cross-module import.
Prose voice
.vale/exemplars/ holds the voice: how a sentence should sound. Every file is the
author's own published writing, verbatim, and none of it is later than 2022. Read
all of them before writing or editing prose (markdown, doc comments); together they
run about 4.2k tokens. When your draft and an exemplar disagree about how a sentence
should sound, the exemplar wins.
They are hand-written and human-owned. Never edit, rewrite, add to, or generate one.
The exemplars check (scripts/check-exemplars.mjs) fails on any difference against
HEAD, whitespace included, so a stray formatting pass trips it the same way a rewrite
would. A human adding one deliberately runs EXEMPLARS_UNLOCKED=1 pnpm check.
The directory is not linted and does not ship to npm. Vale checks mechanics, and a sentence in the author's own voice is not something a mechanical rule should overrule.
For form rather than voice, read what this repo already does. A doc comment follows
the ones already under src/, a release note follows CHANGELOG.md, a troubleshooting
entry follows docs/troubleshooting.md, and a design proposal follows
examples/pr-improve/SPEC.md.
Prose shape
Vale checks one sentence and the exemplars carry the voice. Neither can see the way
a whole file goes wrong, which is prose that passes every rule while every clause
gets packed into a modifier until the reader has to reconstruct the verb. The
prose-health check measures that directly, over docs/ and the top-level
markdown, and node scripts/prose-health.mjs --report prints the table.
It ratchets each file against .prose-health.json instead of against a target,
which is what lets a table of exports score a tenth of what an essay scores and
stay green forever, as long as it doesn't get worse. Re-baselining is a deliberate
act with the same weight as checkride baseline, so it belongs in its own commit
with the reason stated.
The number is not the goal, and you raise it honestly by rewriting a compressed sentence into a finite clause. Inserting the word the metric counts also raises it, and makes the prose worse. The method, the traps that break markdown structure, and the reason there's no regex for the constructions this can't count are in .claude/skills/prose/SKILL.md.
Source layout
This is a single package. All source lives under src/, organized as deep modules: src/<module>/ (core, engine, composites, agents, adapters, mcp, acp, stdio, ui, otel, policy, schema, testing, viewer), each with a barrel index.ts that is its only public face. The umbrella surface sits at the src/ root (index.ts, model_call.ts, forward_standard_env.ts); that is what bundles to npm as fascicle. src/diagram_cli.ts and src/diagram_bin.ts sit beside them but aren't part of that surface. They're the fascicle-diagram bin (its decisions, then its process glue), and tsdown bundles diagram_bin.ts as its own entry, which only the bin shim from scripts/build.mjs imports. One doc ships as well: the build copies docs/learn/agent-primer.md into the package as dist/AGENTS.md, with its links pointed at the release's tag on GitHub, so an edit to the primer changes what every install reads. The acp, adapters, agents, mcp, otel, stdio, testing, ui, and viewer modules are additionally published as fascicle/<module> subpaths. The 7 apps under examples/*/ are the only other workspace members; they depend on the library via fascicle: workspace:*.
Barrels are import/export only. An index.ts contains only import, export … from, export { … }, and export type statements (bare side-effect imports are fine). No runtime logic: module logic lives in a named sibling file (for example, create_engine in create_engine.ts, start_viewer in start_viewer.ts) that the barrel re-exports. Enforced by rules/no-logic-in-barrel.yml.
Cross-module access is sealed two ways. Every module is reachable only through its barrel:
- Use the
#<module>import alias for cross-module imports, never a relative path that escapes the module dir. Useimport { x } from '#core', neverimport { x } from '../core/x.js'. The#-aliases are declared in the rootpackage.jsonimports(mapping each tosrc/<module>/index.ts), mirrored intsconfig.jsonpathsandvitest.config.tsresolve.alias.otelhas no alias yet because no sibling module imports it. Because only barrels are mapped, deep imports through the alias are impossible;rules/no-cross-module-relative-import.ymlcloses the relative-path channel. (Cross-cutting harnesses undersrc/<module>/test/are exempt — they are spawned as child node processes that cannot resolve#-aliases, so they reach siblings relatively.) engineimportscoretype-only (import type), the one value exception beingsrc/engine/errors.tsre-exportingaborted_error.- Examples import the published surface (
fascicleand itsfascicle/*subpaths), so they are copy-pasteable by npm consumers. Resolved via roottsconfig.jsonpaths+vitest.config.tsresolve.alias. - Dependencies live in the one root
package.json. Runtime deps are direct;ai,zod, and every provider SDK are optional peers. The "core depends on no npm packages" / "engine (outside providers/) depends on no npm packages" invariants are enforced at the import level byrules/no-core-npm-dep.ymlandrules/no-engine-npm-dep.yml(the single manifest can no longer express per-module dependency shape). - Boundaries are enforced by
rules/*.yml+fallow.toml. The fallow[[boundaries.rules]]DAG is directory-level default-deny (each module lists the siblings it may import); the umbrella (src/*.ts) is unconstrained. Mirrors the ast-grep rules. - Adding a module: create
src/<module>/index.ts, add#<module>to the rootpackage.jsonimports,tsconfig.jsonpaths, andvitest.config.tsresolve.alias, and add a[[boundaries.rules]]entry tofallow.toml.pnpm checkmust still exit 0. - One set of root configs.
tsconfig.json,vitest.config.ts,fallow.toml,cspell.json,stryker.config.mjs, andrules/glob acrosssrc/**.
What NOT to do
- Do not disable lint rules to pass the check. If a rule is wrong for a case, discuss first or use a scoped inline suppression with a comment explaining why.
- Do not add dependencies casually. Every new dep is surface area. Fallow will catch unused ones.
- Do not add a file that is not imported by something. Fallow will flag it.
- Do not skip writing tests for new behavior. Stryker runs as the opt-in
mutationslot (pnpm check:all) and will catch tests that pass trivially. - Do not bypass
pnpm check:allby running individual tools and claiming done. - Do not re-baseline to clear a failure you introduced. See "The baseline" above.
MCP tools available
Both are wired in .mcp.json:
fallow— structured codebase analysis. Callanalyze,check_changed,find_dupes,check_health,fix_preview,fix_apply, orproject_info.ast-grep— structural code search and rule authoring. Useful when adding or debugging rules inrules/or finding AST-level patterns the plain grep tools can't express.
Use these during implementation for real-time dead-code, boundary, and structural feedback, rather than waiting for the final pnpm check.
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
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.

