Formatting
SkillFiles & storageLets your agent run Remotion formatting and style checks repeatedly until they pass.
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
About this capability
Esposter code formatting — blank-line placement around consts/returns/blocks, the test-file exception, and comment attachment/content rules (comment only exceptional behaviour, describe the present never the history, never argue for the refactor that produced the code, keep error-text quotes, `/** *
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 top-level declarations the blank line is the file's paragraph break and the comment attaches to the declaration below it — the two are doing different jobs, so both stay. 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 repo root" above
resolveRepoRoot()); - 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. -
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 enforces an uppercase first letter on every//line, so a wrapped sentence shows a mid-sentence capital on its continuation line. That's fine, and lowercasing one to read better is a lint error rather than a style choice. What it cannot see is the difference between a prose word and a code identifier, so a wrapped line starting withnode_modules,pnpmoroxlintgets capitalized into a name that does not exist — and--fixwrites it. Rewrap so a line starts with prose; a line opening on a backtick or a bracket is exempt, which is why`pnpm build`may start one.Rewrapping a comment is what creates this, so it is the edit to re-check rather than the original text. Changing a word early in a block reflows every line after it, and an identifier that sat mid-line lands at the front of one — the corruption is written by the pass that was fixing the previous one. After editing any comment, grep the added lines for a line-initial identifier before committing:
git diff -U0 | grep -E '^\+\s*//\s+([A-Z][a-z]+[A-Z-][a-zA-Z]*|(Pnpm|Oxlint|Tsdown|Tinybench|Sdk|Sas))'Two shapes, because one pattern cannot express both. The first catches an identifier with a later capital to anchor on (
ToPrecision,Vue-tsc); a one-word name (Pnpm,Tinybench) has none, so it is caught by enumeration instead — the comments ledger keeps that list, since it only grows when a new tool name turns up. Broadening the first to any capitalized token is not the fix:capitalized-commentscapitalizes every continuation line, so it would match nearly all of them.Most hits are prose (
Non-Vue,Selector-based) or a real PascalCase name; what fails is a camelCase or lowercase one (toPrecision,tinybench,vue-tsc,pnpm).Read the joined sentence, not the new opening word. The fix moves an identifier off the line front by putting prose in front of it, and the prose has to agree with the line above — which the editor is no longer looking at. Both failures are silent: the previous line's article is repeated (
… the FORCE_COLOR level string the/The supports-color convention uses), or its verb loses the object the identifier was (… a fork run stacks/The upperDir becomes a read-only lower). Neither is a lint error and neither is a broken build; a reviewer reads it as a dropped word, because it is one. After a rewrap, read the block start to finish with the leading capitals ignored. Backticking the identifier is the fix that cannot do this — a line opening on a backtick is exempt from the rule, so the sentence is left alone.
Line Endings
- Enforced by
.gitattributes(text eol=lffor.ts/.vue/.js/.json/.md/.yaml/.sh;.bat/.cmd/.ps1are deliberatelycrlf) and settled byoxfmt(pnpm format). Never hand-convert line endings.
Signals
- GitHub stars
- 23
- Forks
- 3
- Last commit
- Sep 2026
- Hacker News mentions
- 20
Advanced
- Catalog kind
- skill
- Gateway key
formatting- Source
- github.com/esposter/esposter