eslint-plugin-ai-context-header
An ESLint plugin that enforces structured file header comments for Vue 2/3 / React / TypeScript / JavaScript projects.
Supported file extensions: .vue, .ts, .js, .tsx, .jsx.
By requiring standardized annotation fields at the top of every file, it enables AI tools and developers to instantly understand a file's role, platform constraints, state dependencies, async boundaries, and side effects — without reading the implementation.
Motivation
In AI-assisted development, context windows are limited. IDEs and AI tools prioritize the beginning of each file. Structured file header comments deliver the most critical constraints at the lowest cost, effectively replacing design documents that tend to go stale.
Installation
npm install --save-dev eslint-plugin-ai-context-header
Quick Setup
ESLint 9 / 10 (eslint.config.js, flat config) — Recommended
ESLint 9 defaults to flat config; ESLint 10 removed .eslintrc entirely. Use the built-in flat/* presets:
// eslint.config.js
import aiContextHeader from 'eslint-plugin-ai-context-header';
export default [
aiContextHeader.configs['flat/recommended-vue'],
// or: aiContextHeader.configs['flat/recommended-react']
// or: aiContextHeader.configs['flat/recommended-universal']
];
Manual flat config:
import aiContextHeader from 'eslint-plugin-ai-context-header';
export default [
{
plugins: { 'ai-context-header': aiContextHeader },
rules: {
'ai-context-header/require-file-header': ['warn', { framework: 'vue' }],
'ai-context-header/no-deprecated-import': 'warn',
},
},
];
Vue .vue files require vue-eslint-parser in your ESLint config:
import aiContextHeader from 'eslint-plugin-ai-context-header';
import vueParser from 'vue-eslint-parser';
export default [
{
files: ['**/*.vue'],
languageOptions: {
parser: vueParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
},
...aiContextHeader.configs['flat/recommended-vue'],
},
{
files: ['**/*.{js,ts,tsx,jsx}'],
...aiContextHeader.configs['flat/recommended-vue'],
},
];
Compatibility: ESLint
^7 || ^8 || ^9 || ^10. ESLint 10 requires Node.js^20.19.0 || ^22.13.0 || >=24(the plugin itself runs on Node 14+ with older ESLint versions).
Vue Project — ESLint 7 / 8 only (.eslintrc.cjs)
module.exports = {
plugins: ['ai-context-header'],
// Option A: use a preset (warn level, good for gradual adoption)
extends: ['plugin:ai-context-header/recommended-vue'],
// Option B: manual config
rules: {
'ai-context-header/require-file-header': ['warn', { framework: 'vue' }],
},
};
React Project — ESLint 7 / 8 only (.eslintrc.cjs)
module.exports = {
plugins: ['ai-context-header'],
extends: ['plugin:ai-context-header/recommended-react'],
};
Monorepo (Vue + React) — ESLint 7 / 8 only
module.exports = {
plugins: ['ai-context-header'],
extends: ['plugin:ai-context-header/recommended-universal'],
};
Available Presets
All presets include both require-file-header and no-deprecated-import rules.
Legacy presets (.eslintrc, ESLint 7–8; ESLint 9 with @eslint/eslintrc FlatCompat):
| Preset | Framework | Level | Description |
|---|---|---|---|
plugin:ai-context-header/recommended |
Vue | warn | Backward-compatible alias for recommended-vue |
plugin:ai-context-header/strict |
Vue | error | Backward-compatible alias for strict-vue |
plugin:ai-context-header/recommended-vue |
Vue | warn | Explicit Vue preset |
plugin:ai-context-header/strict-vue |
Vue | error | Explicit Vue preset |
plugin:ai-context-header/recommended-react |
React | warn | React preset |
plugin:ai-context-header/strict-react |
React | error | React preset |
plugin:ai-context-header/recommended-universal |
Vue + React | warn | Monorepo preset |
plugin:ai-context-header/strict-universal |
Vue + React | error | Monorepo preset |
Flat presets (eslint.config.js, ESLint 9–10):
| Config key | Framework | Level |
|---|---|---|
configs['flat/recommended'] |
Vue | warn |
configs['flat/strict'] |
Vue | error |
configs['flat/recommended-vue'] |
Vue | warn |
configs['flat/strict-vue'] |
Vue | error |
configs['flat/recommended-react'] |
React | warn |
configs['flat/strict-react'] |
React | error |
configs['flat/recommended-universal'] |
Vue + React | warn |
configs['flat/strict-universal'] |
Vue + React | error |
Field Reference
@type (required)
Declares the file's responsibility type. This is the most important field — it tells AI tools and developers the design intent and which rules apply.
| Value | Framework | Description |
|---|---|---|
page |
Vue / React | Route-level page component; responsible for data orchestration and page layout |
component |
Vue / React | Reusable UI component; not directly responsible for routing or data fetching |
composable |
Vue | Logic reuse unit built with Vue Composition API |
hook |
React | React custom Hook encapsulating reusable state and side effect logic |
api |
All | Request wrapper functions for backend communication |
util |
All | Pure utility functions with no side effects |
store |
All | Global state management module (Pinia / Vuex / Zustand / Redux, etc.) |
@platform (required for page / component)
Declares the target platform. Helps AI determine available APIs, styling approaches, and compatibility constraints.
| Value | Description |
|---|---|
wap |
Mobile H5 |
pc |
Desktop Web |
miniapp |
Mini Program (WeChat / Alipay, etc.) |
all |
Cross-platform compatible |
web |
General Web (common in React projects) |
mobile |
Mobile (React Native, etc.) |
<!-- Single platform -->
<!--
@type: page
@platform: wap
-->
<!-- Multiple platforms: use a comma, not | (| in field values triggers unfilledField) -->
<!--
@type: component
@platform: wap, pc
-->
@scope (required for component)
Declares the component's usage scope. Helps AI assess the impact radius before refactoring.
| Value | Description |
|---|---|
internal |
Used only within the current module; safe to refactor freely |
shared |
Shared across modules; consumers should be notified of changes |
global |
Used across the entire project; changes require extra caution |
@route (required for page)
Declares the page's route path. Helps AI understand the page's position in the application and assists with routing analysis and navigation logic generation.
// Static route
@route: /user/profile
// Dynamic route
@route: /article/:id
// Nested route
@route: /dashboard/settings/account
@state (required for page / component / composable / hook / store)
Declares the external state sources the file depends on. Helps AI build a dependency graph to quickly locate affected files when refactoring a store or state logic.
| Example Value | Description |
|---|---|
props-only |
No external state; all data comes from props (ideal for components) |
none |
No state dependency (suitable for pure utility functions) |
userStore |
Depends on a state module named userStore |
userStore, settingsStore |
Depends on multiple state modules, comma-separated |
@async (required for page / composable / hook / api)
Declares whether the file contains async operations and lists key async function names. Helps AI determine the scope of error handling, loading states, and test mocks.
| Example Value | Description |
|---|---|
true |
Contains async operations (without listing specifics) |
false |
No async operations |
getUserInfo, updateProfile |
Lists key async function names |
@sideEffect (required for page / component / composable / hook / api)
Declares the side effects produced by the file. This is the most valuable field for AI — side effects are hard to infer from code structure alone, and a single line lets AI avoid accidentally breaking them during refactoring.
| Example Value | Description |
|---|---|
none |
No side effects (explicitly stating this is encouraged) |
localStorage |
Reads or writes local storage |
cookie |
Reads or writes cookies |
analytics |
Triggers analytics / tracking events |
localStorage, analytics |
Multiple side effects, comma-separated |
modifies global CSS variables |
Free-form description of the side effect |
@description (required for all types)
A brief description of the file's functionality. Especially important when the file name alone does not fully convey the intent.
// Good: reveals non-obvious implementation detail
@description: Manages paginated list scroll-loading with debounce and reset support
// Redundant: just repeats the file name, adds no value
@description: User list component
@deprecated (optional)
Marks the file as deprecated and describes the replacement. The plugin will automatically warn in two places:
- When editing the deprecated file itself (warning at the header comment)
- When another file imports the deprecated file (warning at the import statement)
/*
* @type: util
* @description: Legacy date formatter
* @deprecated: Use ./dateFormat instead — supports i18n
*/
Comment Templates
Templates use /* (single-star block comment) for .ts / .js / .tsx / .jsx files, and <!-- --> for .vue files. This avoids conflicts with JSDoc plugins while still being parsed by the rule.
When --fix inserts a new header, every field includes an inline hint in [...] describing what value to write. Fill in the actual value and remove the brackets. This makes it easy for both humans and AI tools to complete the header correctly.
Vue: page type (.vue)
<!--
@type: page
@description: [one-line summary of this page's purpose]
@platform: [wap | pc | miniapp | all — target runtime platform]
@route: [route path, e.g. /user/profile or /article/:id]
@state: [store names this page reads/writes, e.g. userStore — or "none"]
@async: [key async function names — or true/false]
@sideEffect: [side effects: localStorage | cookie | analytics | none]
-->
Vue: component type (.vue)
<!--
@type: component
@description: [one-line summary of this component's purpose]
@platform: [wap | pc | miniapp | all — target runtime platform]
@scope: [internal — local only | shared — cross-module | global — project-wide]
@state: [store names this component depends on — or "props-only" / "none"]
@sideEffect: [side effects: localStorage | cookie | analytics | none]
-->
Vue: composable type (.ts)
/*
* @type: composable
* @description: [one-line summary of what this composable encapsulates]
* @state: [store names this composable reads/writes — or "none"]
* @async: [key async function names — or true/false]
* @sideEffect: [side effects: localStorage | cookie | analytics | none]
*/
React: page type (.tsx)
/*
* @type: page
* @description: [one-line summary of this page's purpose]
* @platform: [web | mobile | all — target runtime platform]
* @route: [route path, e.g. /user/profile or /article/:id]
* @state: [store/hook names this page depends on — or "none"]
* @async: [key async function names — or true/false]
* @sideEffect: [side effects: localStorage | cookie | analytics | none]
*/
React: component type (.tsx)
/*
* @type: component
* @description: [one-line summary of this component's purpose]
* @platform: [web | mobile | all — target runtime platform]
* @scope: [internal — local only | shared — cross-module | global — project-wide]
* @state: [store/hook names this component depends on — or "props-only" / "none"]
* @sideEffect: [side effects: localStorage | cookie | analytics | none]
*/
React: hook type (.ts)
/*
* @type: hook
* @description: [one-line summary of what this hook encapsulates]
* @state: [store names this hook reads/writes — or "none"]
* @async: [key async function names — or true/false]
* @sideEffect: [side effects: localStorage | cookie | analytics | none]
*/
General: api type (.ts)
/*
* @type: api
* @description: [one-line summary of what API endpoints this file wraps]
* @async: true
* @sideEffect: [side effects: cache | analytics | none]
*/
General: util type (.ts)
/*
* @type: util
* @description: [one-line summary of the utility functions in this file]
*/
General: store type (.ts)
/*
* @type: store
* @description: [one-line summary of what state this module manages]
* @state: [brief description of the core state shape or key properties]
*/
Default Required Fields
Vue (framework: 'vue', default)
| type | Required fields |
|---|---|
| page | @description, @platform, @route, @state, @async, @sideEffect |
| component | @description, @platform, @scope, @state, @sideEffect |
| composable | @description, @state, @async, @sideEffect |
| api | @description, @async, @sideEffect |
| util | @description |
| store | @description, @state |
React (framework: 'react')
| type | Required fields |
|---|---|
| page | @description, @platform, @route, @state, @async, @sideEffect |
| component | @description, @platform, @scope, @state, @sideEffect |
| hook | @description, @state, @async, @sideEffect |
| api | @description, @async, @sideEffect |
| util | @description |
| store | @description, @state |
composableis not a valid type in React mode;hookis not a valid type in Vue mode. Universal mode (framework: 'universal') supports all types from both frameworks.
Rule Behavior Summary
require-file-header reports these message types:
| Message | When |
|---|---|
missingHeader |
No header comment block at the top of the file |
missingType |
Header exists but @type is missing or still a placeholder (e.g. page | component) |
invalidType |
@type value is not in validTypes |
missingField |
A required field for the declared @type is absent |
unfilledField |
A required field exists but its value still contains | (template placeholder not replaced) |
deprecatedFile |
File header contains @deprecated (warning only; field checks continue) |
Note:
\|in field values is treated as an unfilled placeholder. Use commas for multiple items (e.g.@platform: wap, pc), not pipe separators. Pipe characters only appear in--fixtemplate hints like[wap \| pc \| all].
Deprecated File Detection
The plugin provides two complementary deprecation rules, enabled by default in all presets.
File-level: warning when editing a deprecated file
/*
* @type: util
* @description: Legacy date formatter
* @deprecated: Use ./dateFormat instead
*/
export function formatDate() {}
// ↑ ESLint warning: This file is marked as deprecated: Use ./dateFormat instead
Import-level: warning when importing a deprecated file
import { formatDate } from './oldFormat';
// ^^^^^^^^^^^
// ESLint warning: The imported file "./oldFormat" is deprecated: Use ./dateFormat instead
no-deprecated-importonly handles relative imports starting with./or../. Node modules and path aliases (e.g.@/) are automatically ignored.
Control the rule level independently:
rules: {
'ai-context-header/no-deprecated-import': 'error',
}
Advanced Configuration
Full Option Reference
rules: {
'ai-context-header/require-file-header': ['warn', {
// ① Target framework — controls which default types and templates are loaded.
// 'vue' (default) | 'react' | 'universal'
// Omit this field entirely when using a fully custom setup (see below).
framework: 'vue',
// ② Per-type required fields (shallow-merged with framework defaults).
// key = the @type value written in the file header
// value = list of fields that MUST be present when @type matches the key
// An empty array [] means "only @type is required; no other fields enforced".
typeFieldMap: {
service: ['@description', '@async', '@sideEffect'],
},
// ③ Per-type --fix templates (shallow-merged with framework defaults).
// Used when the file has no header at all and ESLint --fix is run.
// Embed inline hints in [...] so AI can fill values without extra context.
templates: {
service: `/*\n * @type: service\n * @description: [one-line summary]\n * @async: [true/false]\n * @sideEffect: [none | ...]\n */\n`,
},
// ④ Allowed @type values. Defaults to all keys in typeFieldMap.
// Writing a @type value not in this list triggers an `invalidType` error.
// Set to [] to allow any @type value (disables type validation entirely).
validTypes: ['page', 'component', 'service'],
// ⑤ When true, files with no @type field are silently skipped.
// Useful for gradual adoption on legacy codebases.
ignoreIfNoType: false,
// ⑥ Hint text shown in ESLint error messages for each missing field.
// Built-in hints exist for standard fields (@description, @state, etc.).
// Add entries here for any custom fields in your typeFieldMap.
// Merged with built-in hints; your values take priority.
fieldHints: {
'@method': 'HTTP method: GET | POST | PUT | DELETE | PATCH.',
'@path': 'Route path this handler is mounted at, e.g. /api/users/:id.',
'@env': 'Required environment variables, e.g. DB_URL, REDIS_HOST.',
},
}]
}
How typeFieldMap Works
Important: typeFieldMap keys are validators, not file classifiers.
The plugin does not automatically infer which type a file belongs to by reading its filename or directory path. Instead:
- The developer writes
@type: routerin the file header manually (or via--fix). - The plugin reads the
@typevalue, looks uptypeFieldMap['router'], and checks that all listed fields are present.
File has @type: router → plugin checks @description, @method, @path are present
File has @type: service → plugin checks @description, @async, @sideEffect are present
File has @type: unknown → plugin reports invalidType error (not in validTypes)
The typeFieldMap answers: "given that a file declared itself as X, what fields must it have?"
It does not answer: "which files are X?"
Pattern 1 — Extend the Built-in Types
Add custom types on top of a framework preset. Useful when most of your files fit the built-in types, but you have a few special categories.
// eslint.config.js
export default [
{
plugins: { 'ai-context-header': aiContextHeader },
rules: {
'ai-context-header/require-file-header': ['warn', {
framework: 'vue',
// Merged with Vue defaults; adds 'service' without removing 'page', 'composable', etc.
typeFieldMap: {
service: ['@description', '@async', '@sideEffect'],
},
templates: {
service: `/*\n * @type: service\n * @description: [one-line summary]\n * @async: [true/false]\n * @sideEffect: [none | cache | analytics]\n */\n`,
},
}],
},
},
];
Pattern 2 — Per-directory Type Enforcement (Recommended for Node.js / Custom Projects)
Use ESLint's files glob patterns to apply different rule configurations to different directories. This is the correct way to make --fix automatically insert the right template for each directory.
// eslint.config.js
export default [
// Route handlers: src/router/**
{
files: ['src/router/**/*.js'],
plugins: { 'ai-context-header': aiContextHeader },
rules: {
'ai-context-header/require-file-header': ['warn', {
validTypes: ['router'],
typeFieldMap: {
router: ['@description', '@method', '@path'],
},
templates: {
router: `/*\n * @type: router\n * @description: [one-line summary]\n * @method: [GET | POST | PUT | DELETE]\n * @path: [route path, e.g. /api/users/:id]\n */\n`,
},
}],
},
},
// Middleware: src/middleware/**
{
files: ['src/middleware/**/*.js'],
plugins: { 'ai-context-header': aiContextHeader },
rules: {
'ai-context-header/require-file-header': ['warn', {
validTypes: ['middleware'],
typeFieldMap: {
middleware: ['@description', '@sideEffect'],
},
templates: {
middleware: `/*\n * @type: middleware\n * @description: [one-line summary]\n * @sideEffect: [none | localStorage | analytics]\n */\n`,
},
}],
},
},
// Scripts: scripts/**
{
files: ['scripts/**/*.js'],
plugins: { 'ai-context-header': aiContextHeader },
rules: {
'ai-context-header/require-file-header': ['warn', {
validTypes: ['script'],
typeFieldMap: {
script: ['@description'],
},
templates: {
script: `/*\n * @type: script\n * @description: [one-line summary of what this script does]\n */\n`,
},
}],
},
},
];
With this setup:
- Running
--fixonsrc/router/userRouter.jsinserts theroutertemplate automatically. - Writing
@type: middlewarein a router file triggers aninvalidTypeerror. - Each directory has its own enforced contract.
Pattern 3 — Fully Custom Type Taxonomy
Replace the built-in type list by providing a complete typeFieldMap and matching validTypes. Custom entries are shallow-merged with framework defaults — to use only your own types, set validTypes explicitly and override every type you do not want:
rules: {
'ai-context-header/require-file-header': ['warn', {
// framework defaults to 'vue' if omitted; pass only the types you need
validTypes: ['controller', 'service', 'repository', 'model', 'config'],
typeFieldMap: {
controller: ['@description', '@async', '@sideEffect'],
service: ['@description', '@async'],
repository: ['@description'],
model: ['@description'],
config: ['@description'],
},
}],
}
Built-in types like
page/composableremain in the merged map unless you restrictvalidTypes. UsevalidTypesas the whitelist to enforce only your taxonomy.
Pattern 4 — Only Enforce Header Existence (No Field Validation)
Use this when you just want every file to have a comment block, without enforcing specific fields.
rules: {
'ai-context-header/require-file-header': ['warn', {
typeFieldMap: {}, // No field requirements for any type
validTypes: [], // Any @type value is accepted
ignoreIfNoType: true, // Files without @type are silently skipped
}],
}
Pattern 5 — Partial Strictness (Some Types Strict, Others Loose)
rules: {
'ai-context-header/require-file-header': ['warn', {
typeFieldMap: {
page: ['@description', '@route', '@state'], // strict
service: ['@description'], // minimal
legacy: [], // @type presence only; no field check
},
}],
}
Pattern 6 — Gradual Adoption on a Legacy Codebase
// Phase 1: warn only; skip files with no @type entirely
rules: {
'ai-context-header/require-file-header': ['warn', {
framework: 'vue',
ignoreIfNoType: true,
}],
}
// Phase 2: add a pre-commit hook to enforce headers on new/modified files only
// (use lint-staged to run ESLint --fix on staged files)
// Phase 3: once all files are annotated, remove ignoreIfNoType and switch to 'error'
rules: {
'ai-context-header/require-file-header': ['error', {
framework: 'vue',
}],
}
AI-Assisted Workflow
The plugin is designed to work seamlessly with AI coding assistants. The recommended workflow for completing file headers is:
Step 1 — Run --fix to insert the template
eslint src/router/userRouter.js --fix
The plugin inserts a template with inline hints for every field:
/*
* @type: router
* @description: [one-line summary of what this router handles]
* @method: [HTTP method: GET | POST | PUT | DELETE | PATCH]
* @path: [route path, e.g. /api/users/:id]
*/
Step 2 — Ask AI to fill in the values
Give the file (with the template) to your AI tool and say:
"Fill in the file header. Replace each
[...]placeholder with the actual value based on the code below."
The AI reads the inline hints directly from the file — no extra documentation needed, minimal token cost.
Step 3 — Verify with ESLint
eslint src/router/userRouter.js
If any field still contains [...] or | (unfilled placeholder), the plugin reports an unfilledField error.
Built-in Field Hints (shown in ESLint error messages)
When a required field is missing, the error message includes a hint explaining what to write:
[@type: composable] Missing required field: @state. Store/hook names this file depends on (e.g. userStore), or "none" if stateless.
| Field | Built-in hint |
|---|---|
@description |
Write a one-line summary of what this file does. |
@platform |
Target runtime: wap | pc | miniapp | all (Vue) or web | mobile | all (React). |
@scope |
Usage range: internal (this module only) | shared (cross-module) | global (project-wide). |
@route |
Route path this page is mounted at, e.g. /user/profile or /article/:id. |
@state |
Store/hook names this file depends on (e.g. userStore), or "none" if stateless. |
@async |
List key async function names, or just true/false. |
@sideEffect |
Describe side effects: localStorage | cookie | analytics | none. |
For custom fields, add hints via the fieldHints option:
'ai-context-header/require-file-header': ['warn', {
typeFieldMap: {
router: ['@description', '@method', '@path'],
},
fieldHints: {
'@method': 'HTTP method: GET | POST | PUT | DELETE | PATCH.',
'@path': 'Route path this handler is mounted at, e.g. /api/users/:id.',
},
}]
Error message for custom fields with hints:
[@type: router] Missing required field: @method. HTTP method: GET | POST | PUT | DELETE | PATCH.
Gradual Adoption Strategy
- Phase 1 —
warnlevel +ignoreIfNoType: true: only report files that have a comment block but are missing required fields - Phase 2 — Enforce on new files via a pre-commit hook (e.g.
lint-stagedwitheslint --fix) - Phase 3 — After backfilling legacy files, remove
ignoreIfNoTypeand switch toerrorlevel
See Pattern 6 in Advanced Configuration for example configs per phase.