Rules and principles for agents working on this project.
When updating this document, append new information or sections. Do NOT delete or overwrite existing content unless explicitly directed. Always ask before making structural changes. When in doubt, keep it.
The following are strictly prohibited:
- Hardcoded secrets, API keys, or credentials.
eval(),exec()at any level.- Star imports (
import * as foo from 'bar'). - Mutating a list while iterating over it.
new Array()— useArray()orArray.from()instead (oxlint will warn).
Follow the OWASP Top 10 for every piece of code written:
- Avoid unsafe operations, validate inputs, prevent injection risks.
- Never store plaintext secrets. Use
process.envor a settings module loaded from env vars. - Validate and sanitize all user input.
- All settings defaults must be production-safe.
- Never rebase under any circumstance without explicit agreement from the user. Never assume your decision is correct.
- Never amend a commit. Use additional commits instead.
- Never force push.
- Skill execution is implied authorization. When a skill is executing — standalone or as part of a chained workflow — the skill's own directives constitute the user's authorization for the git operations it performs (staging, committing, pushing, and opening a PR on the branch it created). Do not halt to request explicit agreement for these. The prohibitions above (rebase, amend, force-push) still apply and are never implied by skill execution.
- DRY (Don't Repeat Yourself): Extract common logic into reusable functions; avoid duplication.
- KISS: Prefer simple, readable code over clever solutions.
- YAGNI (You Ain't Gonna Need It): Implement only what's needed; avoid over-engineering.
- No Unneeded Refactoring: Do not refactor working code for style, naming, or structure unless it directly addresses a bug, improves performance, or is required by the current spec.
- Single Responsibility: Each module, class, and function must have one reason to change.
- Open/Closed: Extend via composition — not by modifying existing logic.
- Dependency Inversion: Depend on abstractions for external services.
tiny-lru is a high-performance, lightweight LRU (Least Recently Used) cache library for JavaScript with O(1) operations and optional TTL support. It is a pure in-memory cache with no network or I/O surface.
├── src/lru.js # Main LRU cache implementation
├── tests/
│ ├── unit/ # Unit tests (lru.test.js)
│ └── integration/ # Integration tests
├── benchmarks/ # Performance benchmarks
├── dist/ # Built distribution files
│ ├── tiny-lru.js # ES Modules
│ ├── tiny-lru.cjs # CommonJS
│ └── tiny-lru.min.js # Minified ESM (repo only, not shipped)
├── types/ # TypeScript definitions (lru.d.ts)
├── docs/ # API.md, CODE_STYLE_GUIDE.md, TECHNICAL_DOCUMENTATION.md
├── .github/ # CI workflow, PR template, dependabot
├── .husky/ # Pre-commit hook
├── rollup.config.js # Build configuration
├── coverage.txt # Coverage report output
└── package.json # Project configuration
Misc details: Source lives in src/lru.js. The build produces ESM, CommonJS, and minified ESM outputs via Rollup. The minified build (dist/tiny-lru.min.js) is available in the repo for local testing but is not shipped via npm.
| Command | Purpose |
|---|---|
npm install |
Install dependencies |
npm run build |
Lint and build (runs lint then rollup) |
npm run rollup |
Build with rollup |
npm run test |
Run lint and tests |
npm run coverage |
Run tests with coverage reporting |
npm run fix |
Fix linting and formatting issues |
npm run lint |
Lint code with oxlint |
- JavaScript: CommonJS, ESM, and minified ESM outputs via Rollup
- Package manager:
npm - Type checking: TypeScript definitions in
types/lru.d.ts - Formatting:
oxfmt(vianpm run fix) - Linting:
oxlint - Testing: Node.js built-in test runner (
node --test) - Git hooks:
pre-commitvia Husky (manages oxfmt, oxlint, tests)
- Indentation: Tabs
- Quotes: Double quotes
- Semicolons: Required
- Array constructor: Avoid
new Array()(oxlint will warn) - Use
Object.create(null)for hash maps to avoid prototype pollution - Use
===and!==for comparisons - Every exported function and class method must have JSDoc with
@paramand@returns
Not applicable — internal LRU operations should not surface errors; handle gracefully. Constructor validation throws TypeError for invalid parameters (negative numbers or wrong types).
Not applicable — the library is synchronous. No async operations or blocking I/O.
- Framework: Node.js built-in test runner (
node --test) - Tests: 173 tests across 27 suites
- Coverage: 100% lines, 99.40% branches, 100% functions
- Test pattern:
tests/**/*.js - All tests must pass with 100% line coverage before merging
- Run:
npm test(lint + tests) ornpm run coveragefor coverage report
lru(max, ttl, resetTtl)- Factory function to create cacheLRUclass - Direct instantiation withnew LRU(max, ttl, resetTtl)- Core methods:
get(),set(),peek(),delete(),has(),clear(),evict() - Array methods:
keys(),values(),entries() - Utility methods:
forEach(),getMany(),hasAll(),hasAny(),cleanup(),toJSON(),stats(),onEvict(),sizeByTTL(),keysByTTL(),valuesByTTL() - Properties:
first,last,max,size,ttl,resetTtl peek(key)- Retrieve value without moving it (no LRU update, no TTL check)
Follow Conventional Commits:
feat: add TTL support to LRU cache
fix: correct LRU eviction order for edge case
docs: update AGENTS.md with new conventions
test: add coverage for keysByTTL method
chore: update rollup configuration
- Main branch is
master. - Feature branches:
feat/<short-desc>orfix/<short-desc>. - Never commit directly to
master. Always create a feature branch first, then open a PR targetingmaster.
When auditing or modifying AGENTS.md (or any file):
- Create a feature branch:
git checkout -b docs/<short-desc>(orfeat/,fix/). - Make changes and commit on the feature branch.
- Push the feature branch and open a PR with
gh pr create --base master. - Never commit or push directly to
master.
- All changes require at least one other reviewer (automated checks are mandatory but not sufficient).
- No merging without passing CI (lint → test).
- PR descriptions must reference related items from design documents.
If a .github/PULL_REQUEST_TEMPLATE.md file exists, it MUST be used when creating PRs. Fill out every section — do not leave any section blank. If a section does not apply, write N/A rather than skipping it.
Session learnings — critical gotchas that affect how code must be written and tested.
Tests must maintain 100% line coverage. Every new function or class needs test coverage. No exceptions.
npm run coverageGenerates coverage.txt via node --test --experimental-test-coverage.
The hook is .husky/pre-commit, installed by husky via the prepare npm script (core.hooksPath = .husky/_). Its body is:
npm run fix && npm run coverage && git add -A
Because the final step is git add -A, files the hook regenerates — coverage.txt, plus anything oxlint --fix or oxfmt --write rewrote — are staged automatically, so the commit proceeds and includes them. The commit does not fail merely because the hook modified a file.
Do not "fix" a hook-modified file by amending: §1.3 forbids amending outright. If a commit lands with content you did not intend to stage, add a follow-up commit instead.
Gotcha: git add -A stages the entire working tree, not just what you staged before committing. Anything untracked and not gitignored gets swept into the commit. Stage selectively and check git status before committing, or expect unrelated files to ride along.
The hook gates on tests indirectly: npm run coverage runs node --test across the whole suite, and set -o pipefail propagates a test failure through the grep/redirect pipeline so the commit fails. Lint failures fail the commit via npm run fix (oxlint exits non-zero on errors it cannot auto-fix). A commit can therefore fail on lint errors or test failures.
There is no coverage threshold gate — the report is regenerated and committed, but no minimum is enforced. "Maintained coverage" is a review convention, not an automated check.
Code that can never execute is a smell. Remove dead code to avoid coverage gaps and confusion. Always verify edge case code is actually reachable before adding special handling.
Discovery notes about the codebase.
- The LRU uses a doubly-linked list with
firstandlastpointers for O(1) operations - TTL is stored per-item as an
expirytimestamp;0means no expiration moveToEnd()is the core method for maintaining LRU order - item is always moved to thelastpositionsetWithEvicted()optimizes updates by avoiding the fullset()path for existing keys- When removing items from the linked list, always clear
prev/nextpointers to allow garbage collection - Methods like
entries()andvalues()should access items directly rather than callingget(), which moves items and can delete expired items mid-iteration - Direct property access (
this.items[key]) should be used instead ofhas()when you need to inspect expired-but-not-yet-deleted items
- All code follows style conventions (tabs, double quotes, semicolons).
- No forbidden patterns used (
new Array(), etc.). - Unit tests written and passing.
- 100% line coverage maintained (
npm run coverage). -
npm run lintpasses with no errors. - No hardcoded secrets or credentials introduced.