Formatting
SkillFiles & storageApply when writing or editing any file's whitespace or comments. Esposter code formatting, blank-line placement around consts, returns and blocks with the test-file exception, and the comment rules: a comment on its own line above its code with no blank line around it, only exceptional behaviour, the present never the history, `/** */` only on an exported API surface, and the capitalized-comments rewrap re-read.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Formatting skill
What this skill tells your AI
The instructions your AI receives, as published by esposter/esposter in .agents/skills/formatting/SKILL.md and read by ahel’s review.
Cross-cutting whitespace and comment rules for all files. Language/framework-specific structure lives in vue, typescript, file-organization; how to write a skill doc lives in skill-authoring. This skill owns only spacing and comments.
Blank Lines
- No blank lines between consecutive
constassignments — group them tightly. - No blank line before a
returnthat immediately follows aconstin a small function (including composables that return a function directly —returnfollows the last setup line with no gap). - Blank line after a closing
}of anif/for/block statement — unless it is the last statement in its scope or immediately followed by another opening block. (Exception: consecutive top-levelwatch/lifecycle-hook registrations in a Vue<script setup>each get a blank line between them — see thevueskill.) - Blank line between an SFC's top-level blocks — enforced and autofixed by
vue/padding-line-between-blocks, so it survives a comment deleted from a block boundary without anyone having to remember it. - No blank lines within Vue templates. A blank line inserted to visually separate template sections is a smell that the component owns more than one responsibility — extract each section into its own focused child component rather than spacing them apart. See the
vue-page-compositionskill (maximal granularity / one concern per component). - Imports — order and blank lines are autofixed by
perfectionist/sort-imports(packages/configuration/eslint/plugins/perfectionist.js);pnpm lint:fixsettles it.internalPattern: []is what collapses every source (external-pkg,#shared,@vueuse/*,@/) into one bucket per kind, so the fixer produces a contiguousimport typegroup, one blank line, then a contiguous value group. Don't hand-place import blank lines.
Comments
-
A
//comment goes on its own line above the code it describes, never trailing on the same line.const x = f(); // whybecomes a comment line then the statement. Own-line comments read consistently, survive the capitalization hook, and don't push lines past the width limit. (Directive comments that must be inline — a rare// eslint-disable-line— are the only exception.) -
No blank line before or after a
//comment — a comment attaches directly to the code it describes and acts as the separator. Blank lines go between uncommented logical blocks only. This includes functional/directive comments (// oxlint-disable-next-line ...,// @ts-expect-error ..., etc.) — they attach directly to the line they govern with no surrounding blank line.// CORRECT — comment acts as separator const foo = readFoo(input); // Read bar const bar = readBar(input); // WRONG — blank line + comment is redundant const foo = readFoo(input); // Read bar const bar = readBar(input);-
Consecutive
//lines are one comment block — never blank-separate them. A multi-line explanation is a contiguous run of//lines with no gaps; a blank line between two comment lines splits one thought into two and is wrong. This is the same rule as "no blank line after a comment" applied to a comment that is itself the next line.// CORRECT — one contiguous block // Opens a local mic and exposes the live level. // No shared analyser exists to reuse here. export const useThing = () => {}; // WRONG — blank line splits one comment block // Opens a local mic and exposes the live level. // No shared analyser exists to reuse here. export const useThing = () => {}; -
This is a rule about statements inside a block. Between two declarations the blank line is the paragraph break and the comment attaches to the declaration below it — the two are doing different jobs, so both stay. That is the file's top level, and equally a class body or an object literal, where the members are declarations and closing one up against the next is what deleting the blank line does. Inside a function or a
<script setup>body there is only one job to do, and the comment does it. -
Deleting a leading comment takes the separator with it. A comment above a top-level declaration, or directly under the import block, is standing in for the blank line that would otherwise be there — so a pass that removes the comment has to put the blank line back. The import case fails
import/newline-after-importat lint; the declaration case fails nothing at all and just reads as two paragraphs run together. -
Exception —
.test.ts/.test-d.tsfiles: do NOT strip these blank lines. Oxlint'svitestplugin enforcesvitest/padding-around-test-blocks, which requires a blank line arounddescribe/testblocks. A leading comment on such a block sits after that mandatory blank line, so keep it. Blank lines around hooks and between expect groups are convention here rather than enforced — keep them for the same readability reason, but nothing fails if one is missing. Still tighten the comment text itself.
-
-
CRITICAL — comment only exceptional behaviour. A comment earns its place only when it explains something a competent reader could not infer from the code, its names, or the project's own conventions. Never restate an established pattern or anything already documented in a skill or feature doc. The skill/doc is the single source of truth; duplicating it in a comment is noise that rots. Concretely, delete comments that:
- restate a convention covered by a skill (e.g. "a
.test.tsso the barrel generator keeps it out of the public barrel", "the result helper turns the throw into false, per the error-handling convention", "memoized because…" when memoization is the obvious idiom); - paraphrase what a well-named function/variable already says ("// resolve the foo" above
resolveFoo()); - duplicate a rationale already written in a sibling file — state it once at the source, not at every call site.
Keep comments for genuinely non-obvious why: a workaround for a specific external bug/quirk, a subtle ordering/race constraint, an overlayfs/kernel/platform footgun, a security boundary. When in doubt, prefer deleting — a wrong-but-confident comment is worse than none.
- restate a convention covered by a skill (e.g. "a
-
CRITICAL — comments describe the present, never the history. A comment states what the code does and why it does it now, never how it used to work or what it replaced; git is the changelog. Delete any clause that only makes sense as a before/after story —
equivalent to the old X,replaces the former Y,now that Z the old reason is moot,used to …,no longer needed since …— and rewrite it to assert the current behaviour. Migration state is history too: no roadmap phases, no "until X lands", no transitional wiring in a code comment; sweep those in the change that completes the migration, since the roadmap doc is where phase history lives. Mention a rejected alternative only where the reader needs it to not "fix" the code back to it, in one clause.// WRONG — narrates removed behaviour // `foo()` (equivalent to the old `bar()`) provisions both layers. // Now that baz persists its output, the old discarded-buffer reason is moot; the real blocker is nesting. // CORRECT — states the present reason only // `foo()` provisions both layers. // Runs on the host, not the sandbox: a nested sandbox is forbidden inside the outer one.Two narrow exceptions survive because they still help the current reader: (1) a comment quoting the actual external error/warning text a workaround addresses (it's how the next person greps the cause — see below); (2) a regression guard in a test may name the failure mode it defends against, phrased as a present hazard (
coupling both to one check flips this assertion), not as a past state (a regression to the old gate). -
A comment explains the code, never the change that produced it. "Stated once rather than left to drift", "cached because it is read twice", "shared so a control added here reaches both" — these argue for a refactor that has already happened, to a reader who is looking at the result and cannot see the alternative. They are also the convention restated at the call site: reuse, work and identity are the
vueskill's, deduplication isfile-organization's, and a rule copied beside one of its instances is the copy that goes stale. Write what the code does and the non-obvious constraint it is under; if the pass turned up a rule worth stating, state it in the owning skill, where every future reader gets it instead of this one file's reader. -
/** */is for an exported API surface,//for everything else. A doc block on an exported class, interface or helper is what an editor shows at the call site, which a//above the declaration is not; anything internal gets//. Its content obeys every rule above regardless — a doc block that restates the declaration's own name, or claims something typecheck already proves ("correctly implements the interface"), earns nothing and goes.- A paragraph of prose at module scope keeps
/** */, whatever it sits above — the rationale block over adescribeis the case that arises.capitalized-commentsrewrites the first letter of every//line and leaves a block comment alone, so a wrapped sentence comes back capitalized mid-clause one line in three, and a tool name that lands at a wrap (ctix,pnpm) comes back as a name that does not exist. The exported-surface rule is about where an editor shows a block; this is about which syntax survives the fixer, and a paragraph only survives as one.
- A paragraph of prose at module scope keeps
-
Keep comments tight and generic — explain the why in general terms; don't bake in specific example values (versions, IDs, payloads, magic numbers). Prefer a single line, but keep a bulleted list (one item per
//line) when enumerating distinct items rather than cramming them into one sentence. If an example helps, show only the minimal fragment. Applies to//,/* */, and Vue<!-- -->alike. -
Keep error/warning examples — when a comment quotes the actual error or warning text a workaround addresses (e.g.
[Vue warn]: Invalid prop: type check failed), keep that quote — it's how the next person greps for the cause. Trim it to the minimal identifying fragment; drop surrounding example values. -
Don't fight
eslint(capitalized-comments)— oxlint uppercases the first letter of every//line, so an identifier that lands at a line front after a rewrap is silently capitalised; put prose in front of it or backtick it, then re-read the joined sentence against the line above. The grep that finds one, and the two silent ways the fix breaks the sentence:references/capitalized-comments.md.
Line Endings
- Enforced by
.gitattributes(* text=auto eol=lf— every text file checks out LF whatever "core.autocrlf" says on the machine;.bat/.cmd/.ps1are deliberatelycrlf) and settled byoxfmt(pnpm format). Never hand-convert line endings.
Deep Dives
references/capitalized-comments.md— whencapitalized-commentsfires, or after rewrapping a comment block.
Signals
- GitHub stars
- 23
- Forks
- 3
- Last commit
- Sep 2026
- Hacker News mentions
- 20
Advanced
- Catalog kind
- skill
- Gateway key
formatting-esposter- Source
- github.com/esposter/esposter