Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 0 additions & 94 deletions .eslintrc.json

This file was deleted.

19 changes: 19 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,28 @@ jobs:
- name: Install dependencies
run: npm ci

- name: Validate ESLint config exists
run: |
if [ ! -f "eslint.config.js" ]; then
echo "ERROR: eslint.config.js not found"
echo "See docs/adr/ADR-001-eslint-config-format.md for rationale"
exit 1
fi
# Fail if legacy config exists (should have been deleted)
if [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f ".eslintrc.yaml" ]; then
echo "ERROR: Legacy ESLint config found - delete it"
exit 1
fi

- name: Validate ESLint config loads
run: npx eslint --print-config background.js > /dev/null

- name: Run ESLint
run: npm run lint

- name: Run ESLint config tests
run: npm run test -- tests/config/eslint-config.test.js

- name: Check for security vulnerabilities
run: npm audit --audit-level=moderate

Expand Down
17 changes: 8 additions & 9 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo "Running pre-commit checks..."

echo "🔍 Running pre-commit checks..."
# Validate ESLint config exists and is valid
if [ ! -f "eslint.config.js" ]; then
echo "ERROR: eslint.config.js not found"
exit 1
fi

# Run lint-staged to check only staged files
# Run lint-staged (ESLint on staged files only)
npx lint-staged

# Check coverage delta (ensure coverage doesn't decrease)
echo "📊 Checking test coverage..."
npm run test:coverage -- --changed

echo "✅ Pre-commit checks passed!"
echo "Pre-commit checks passed!"
25 changes: 25 additions & 0 deletions docs/CANONICAL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Hera Canonical Documentation Index

## Purpose
Single source of truth for issues, decisions, and procedures.

## Issues Index

| ID | Title | Status | ADR | Runbook |
|----|-------|--------|-----|---------|
| ISSUE-001 | Storage quota exceeded (8MB error) | OPEN | - | - |
| ISSUE-002 | Console wrapping anti-pattern | RESOLVED | - | - |
| ISSUE-003 | ESLint pre-commit hook failure | RESOLVED | ADR-001 | RUNBOOK-eslint-config |

## ADR Index

| ID | Title | Status | Link |
|----|-------|--------|------|
| ADR-001 | Use ESLint flat config (eslint.config.js) | ACCEPTED | docs/adr/ADR-001-eslint-config-format.md |

## Runbook Index

| ID | Title | Status | Link |
|----|-------|--------|------|
| RUNBOOK-error-collection | Error Collection Runbook | ACTIVE | docs/runbooks/RUNBOOK-error-collection.md |
| RUNBOOK-eslint-config | ESLint Configuration | ACTIVE | docs/runbooks/RUNBOOK-eslint-config.md |
98 changes: 98 additions & 0 deletions docs/adr/ADR-001-eslint-config-format.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# ADR-001: Use ESLint Flat Config (eslint.config.js)

**Status:** ACCEPTED
**Date:** 2026-01-01
**Issue:** ISSUE-003
**Runbook:** [RUNBOOK-eslint-config](../runbooks/RUNBOOK-eslint-config.md)

## Context

PR #5 failed pre-commit hook with error:
```
TypeError: Error while loading rule '@typescript-eslint/no-unused-expressions'
```

### Root Cause Analysis (5 Whys)

1. **Why did the hook fail?** ESLint threw a TypeScript plugin error
2. **Why was TypeScript ESLint used?** A global `eslint.config.mjs` at `~/` extends `typescript-eslint`
3. **Why did global config take precedence?** ESLint searches up directory tree; flat config (`eslint.config.*`) takes precedence over legacy (`.eslintrc.*`)
4. **Why does global config exist?** Developer has global Node.js tooling with TypeScript ESLint at `~/node_modules/`
5. **Why doesn't project isolate from global configs?** **No flat config in project** - only legacy `.eslintrc.json` which was also broken (invalid JSON with comments)

### Contributing Factor

The original `.eslintrc.json` contained JavaScript-style comments (`//`), which are invalid JSON:

```bash
$ node -e "JSON.parse(require('fs').readFileSync('.eslintrc.json', 'utf8'))"
SyntaxError: Expected property name or '}' in JSON at position 287 (line 19 column 5)
```

This caused ESLint to fall back to searching parent directories, eventually finding the global TypeScript config.

## Decision

**Use ESLint flat config format (`eslint.config.js`)** instead of legacy format (`.eslintrc.*`).

### Why Flat Config?

| Factor | Legacy (`.eslintrc.*`) | Flat Config (`eslint.config.js`) |
|--------|------------------------|----------------------------------|
| **Global config isolation** | Searches up directory tree | Project config takes precedence |
| **Comments** | JSON: no, JS: yes | JavaScript: yes |
| **ESLint 9 compatibility** | Deprecated | Native format |
| **Config merging** | Complex cascading | Explicit array composition |
| **Type checking** | None | JSDoc support |

### Why Not Other Options?

| Option | Verdict | Reason |
|--------|---------|--------|
| Fix `.eslintrc.json` (remove comments) | Rejected | Still vulnerable to global config override |
| Convert to `.eslintrc.js` | Rejected | Still searches up directory tree |
| Add `root: true` | Rejected | Legacy format, deprecated in ESLint 9 |
| Use flat config | **Accepted** | Project takes precedence, future-proof |

## Consequences

### Positive
- Project ESLint config takes precedence over global configs
- JavaScript comments are valid (documentation preserved)
- Ready for ESLint 9 (flat config is default)
- Simpler mental model (explicit array, no cascading)

### Negative
- Must install `@eslint/js` and `globals` packages
- Syntax differs from legacy format
- `--ext` flag removed in flat config

### Neutral
- Same linting rules and behavior
- Same ESLint version (8.57.x)

## Implementation

See [RUNBOOK-eslint-config](../runbooks/RUNBOOK-eslint-config.md) for implementation steps.

### Summary of Changes

1. Created `eslint.config.js` (flat config format)
2. Deleted `.eslintrc.json` (broken JSON with comments)
3. Updated `package.json` lint scripts (removed `--ext .js`)
4. Simplified `.husky/pre-commit` (removed test coverage check)
5. Updated `lint-staged` config (removed `vitest related --run`)

## UNIX Rules Applied

- **Rule #5 (Fail Early, Fail Loud):** Global config silently took precedence
- **Rule #7 (Prefer Simple Over Clever):** Flat config is simpler than cascading legacy
- **Rule #8 (Build for Debuggability):** Error message was misleading (TypeScript error in JS project)
- **Rule #12 (Respect the Environment):** Project must isolate from developer's global environment

## References

- [ESLint Flat Config](https://eslint.org/docs/latest/use/configure/configuration-files-new)
- [ESLint Migration Guide](https://eslint.org/docs/latest/use/configure/migration-guide)
- [ESLint 9 Announcement](https://eslint.org/blog/2024/04/eslint-v9.0.0-released/)
- [Configuration File Resolution](https://eslint.org/docs/latest/use/configure/configuration-files#configuration-file-resolution)
112 changes: 112 additions & 0 deletions docs/runbooks/RUNBOOK-eslint-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# RUNBOOK: ESLint Configuration

**Status:** ACTIVE
**Issue:** ISSUE-003
**ADR:** [ADR-001](../adr/ADR-001-eslint-config-format.md)

## Problem Statement

ESLint pre-commit hook fails when:
1. Global ESLint config takes precedence over project config
2. Legacy config uses invalid JSON (comments in `.eslintrc.json`)

## Root Cause

ESLint config resolution order:
1. **Flat config** (`eslint.config.js`) - takes absolute precedence
2. **Legacy config** (`.eslintrc.*`) - searches up directory tree

If project uses legacy format and developer has global flat config, global wins.

## Current Solution

Project uses **flat config** (`eslint.config.js`) which:
- Takes precedence over any parent directory configs
- Supports JavaScript comments natively
- Is ESLint 9 native format

## Verification Procedure

### Step 1: Verify Config Exists

```bash
ls -la eslint.config.js
# Should exist and be non-empty
```

### Step 2: Verify No Legacy Config

```bash
ls .eslintrc* 2>/dev/null && echo "WARN: Legacy config found" || echo "OK: No legacy config"
```

### Step 3: Verify ESLint Works

```bash
npm run lint
# Should run without "TypeError: Error while loading rule" errors
```

### Step 4: Verify Pre-Commit Hook

```bash
# Stage a file
git add eslint.config.js

# Test commit
git commit --dry-run -m "test"
# Should pass without @typescript-eslint errors
```

## Troubleshooting

### Error: `@typescript-eslint/no-unused-expressions`

**Cause:** Global TypeScript ESLint config taking precedence

**Solution:**
1. Verify `eslint.config.js` exists in project root
2. Delete any `.eslintrc.*` files
3. Run `npm run lint` to verify project config used

### Error: `Invalid option '--ext'`

**Cause:** Using legacy CLI options with flat config

**Solution:** Remove `--ext .js` from npm scripts:
```json
{
"lint": "eslint .", // NOT "eslint . --ext .js"
}
```

### Error: `Cannot find module '@eslint/js'`

**Cause:** Missing flat config dependencies

**Solution:**
```bash
npm install --save-dev @eslint/js globals
```

## Prevention Checklist

- [ ] Project uses `eslint.config.js` (not `.eslintrc.*`)
- [ ] No legacy config files in project
- [ ] CI validates ESLint config before running lint
- [ ] Pre-commit hook validates config exists
- [ ] Package.json scripts use flat config CLI syntax

## Related Files

- `eslint.config.js` - Main ESLint configuration
- `.husky/pre-commit` - Git pre-commit hook
- `package.json` - npm scripts and lint-staged config

## History

| Date | Change |
|------|--------|
| 2026-01-01 | Migrated from `.eslintrc.json` to `eslint.config.js` |
| 2026-01-01 | Removed `--ext .js` from npm scripts |
| 2026-01-01 | Simplified pre-commit hook (removed test coverage) |
Loading
Loading