From 133e74ce8bcc4b9351778eb1be0f646c8e9b2e31 Mon Sep 17 00:00:00 2001 From: Jason Mulligan Date: Sun, 10 May 2026 08:24:48 -0400 Subject: [PATCH] docs: restructure AGENTS.md to match retrieved file layout --- AGENTS.md | 448 ++++++++++++++++++++++++++---------------------------- 1 file changed, 218 insertions(+), 230 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1a3e07b..f32836a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,31 +1,118 @@ -# Agent Guidelines for filesize.js +# AGENTS.md -## Project Overview +Rules and principles for agents working on **this** project. + +--- + +## 1. Core Rules + +### 1.0 Document Conventions + +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. + +### 1.1 Forbidden Patterns + +The following are **strictly prohibited**: + +- Hardcoded secrets, API keys, or credentials. +- `eval()`, `Function` constructor, or command injection. +- `*` imports. +- Mutating a list while iterating over it. +- Prototype pollution (write-only symbol access). + +### 1.2 Security Rules + +Follow the [OWASP Top 10](https://owasp.org/www-project-top-10/) for every piece of code written: + +- No XSS (returns plain strings only, no HTML/JS generation). +- No SSRF (no network requests). +- No ReDoS (simple regex, no catastrophic backtracking). +- Input validation on all user-provided values. +- The `symbols` option allows user-controlled objects but only reads from them (safe). + +### 1.3 Git Operations + +- **Never rebase under any circumstance without explicit agreement from the user.** Never assume your decision is correct. +- Never force push. + +### 1.4 Core Principles + +- **DRY**: Extract repeated logic into functions, classes, or utilities. +- **KISS**: Prefer simple, readable code over clever solutions. +- **YAGNI**: Do NOT build features, abstractions, or configurations not required by the current spec. +- **Single Responsibility**: Each module, class, and function must have one reason to change. + +--- + +## 2. Project Context filesize.js is a lightweight, high-performance file size utility that converts bytes to human-readable strings. It supports multiple unit standards (SI, IEC, JEDEC), localization, and various output formats. +### 2.0 Expected Project Layout + +``` +filesize.js/ +├── src/ +│ ├── filesize.js # Main implementation (filesize + partial) +│ ├── helpers.js # Helper functions (5 exported functions) +│ └── constants.js # Constants, symbols, lookup tables +├── tests/ +│ └── unit/ +│ ├── filesize-helpers.test.js # Helper function tests +│ └── filesize.test.js # Main function tests +├── dist/ # Built distributions (generated) +├── types/ # TypeScript definitions +└── docs/ # Documentation +``` + **Key Facts:** -- **Single source file**: `src/filesize.js` (285 lines) -- **Helper functions**: `src/helpers.js` (215 lines) -- **Constants**: `src/constants.js` (81 lines) +- **Single source file**: `src/filesize.js` +- **Helper functions**: `src/helpers.js` +- **Constants**: `src/constants.js` - **Zero dependencies**: Uses only native JavaScript APIs -- **100% test coverage**: 149 tests passing +- **100% test coverage**: ~149 tests passing -## Coding Conventions +### 2.1 Quick Commands -- **JSDoc**: Use JSDoc standard for all functions and classes -- **Naming**: - - Functions: camelCase (`handleZeroValue`, `applyPrecisionHandling`) - - Constants: UPPER_SNAKE_CASE (`IEC`, `JEDEC`, `BINARY_POWERS`) -- **Testing**: - - Unit tests in `tests/unit/` using `node:test` + node:assert - - Use `describe()` and `it()` from `node:test` +| Command | Purpose | +|---------|---------| +| `npm test` | Full test suite (lint + node:test) | +| `npm run build` | Build all distributions | +| `npm run lint` | Check code style | +| `npm run fix` | Fix lint and format code | + +--- + +## 3. JavaScript Conventions + +### 3.1 Language & Tooling + +- **JavaScript**: ES Modules (no CommonJS in src/) - **Linting**: oxlint (fast Rust-based linter) - **Formatting**: oxfmt (fast Rust-based formatter) -- **Principles**: DRY, KISS, YAGNI, SOLID -- **Security**: OWASP best practices +- **Testing**: `node:test` + node:assert with `--experimental-test-coverage` +- **Build**: rollup (produces CJS, ESM, UMD, minified outputs) + +### 3.2 Style -### Code Style +- **JSDoc**: Use JSDoc standard for all functions and classes +- **Functions**: camelCase (`handleZeroValue`, `applyPrecisionHandling`) +- **Constants**: UPPER_SNAKE_CASE (`IEC`, `JEDEC`, `BINARY_POWERS`) +- All exported functions MUST have JSDoc documentation. + +### 3.3 Error Handling + +- Raise typed errors. `filesize("invalid")` throws `TypeError: "Invalid number"`. +- No `eval`, `Function` constructor, or command injection. + +### 3.4 Testing + +- Unit tests live in `tests/unit/`. +- Use `describe()` and `it()` from `node:test`. +- Each public function or class must have at least one test. +- **100%** statement, branch, function, and line coverage required. + +### 3.5 Code Style Template ```javascript // Function with JSDoc @@ -52,33 +139,18 @@ import { } from "./helpers.js"; ``` -## Project Structure - -``` -filesize.js/ -├── src/ -│ ├── filesize.js # Main implementation (filesize + partial) -│ ├── helpers.js # Helper functions (5 exported functions) -│ └── constants.js # Constants, symbols, lookup tables -├── tests/ -│ └── unit/ -│ └── filesize-helpers.test.js # Helper function tests -│ └── filesize.test.js # Main function tests -├── dist/ # Built distributions (generated) -├── types/ # TypeScript definitions -└── docs/ # Documentation -``` +--- -## Core Architecture +## 4. Core Architecture -### Data Flow +### 4.1 Data Flow ``` Input → Validation → Standard Normalization → Exponent Calculation → Value Conversion → Formatting → Output Generation ``` -### Key Components +### 4.2 Key Components 1. **`filesize(arg, options)`**: Orchestrator — delegates validation, exponent, value calc, rounding, formatting, and output dispatch 2. **`partial(options)`**: Creates pre-configured formatter with immutable frozen options @@ -93,61 +165,17 @@ Input → Validation → Standard Normalization → Exponent Calculation 11. **`decorateResult()`**: Negation, custom symbols, full form assembly 12. **`formatOutput()`**: Array/object/string output dispatch -### Standard Selection +### 4.3 Standard Selection - **SI (default)**: Base 10, uses JEDEC symbols (kB, MB, GB) - **IEC**: Base 1024, binary prefixes (KiB, MiB, GiB) - **JEDEC**: Base 1024, uses traditional symbols (KB, MB, GB) -## Common Tasks - -### Adding a New Feature - -1. Add constants to `src/constants.js` if needed -2. Implement logic in `src/filesize.js` or `src/helpers.js` -3. Add JSDoc documentation -4. Write unit tests in `tests/unit/` -5. Write integration tests in `tests/integration/` -6. Run `npm test` to verify 100% coverage maintained -7. Run `npm run build` to update distributions +--- -### Fixing a Bug +## 5. API Reference -1. Write a failing test first -2. Implement the fix -3. Verify test passes -4. Run full test suite -5. Check for regressions - -### Modifying `partial()` - -**Important**: `partial()` uses destructuring to freeze option values for immutability: -```javascript -export function partial({ - bits = false, - pad = false, - base = -1, - round = 2, - // ... other options -} = {}) { - return (arg) => - filesize(arg, { - bits, - pad, - base, - round, - // ... same options - }); -} -``` - -- Destructuring extracts and freezes primitive values at creation time -- Prevents mutations to original options object from affecting created formatters -- Simpler than deep cloning while maintaining immutability for all option types - -## API Reference - -### Options Object +### 5.1 Options Object | Option | Type | Default | Description | |--------|------|---------|-------------| @@ -167,78 +195,14 @@ export function partial({ | `roundingMethod` | string | `"round"` | Math.round, floor, or ceil | | `precision` | number | `0` | Significant digits | -### Output Formats +### 5.2 Output Formats - **string**: `"1.5 KB"` (default) - **array**: `[1.5, "KB"]` - **object**: `{value: 1.5, symbol: "KB", exponent: 1, unit: "KB"}` - **exponent**: `1` -## Testing Guidelines - -### Running Tests - -```bash -npm test # Full test suite (lint + node:test) -npm run test:watch # Live test watching -``` - -### Test Structure - -```javascript -import assert from 'node:assert'; -import { describe, it } from 'node:test'; -import { filesize } from '../../src/filesize.js'; - -describe('Feature', () => { - it('should do something', () => { - const result = filesize(1024); - assert.strictEqual(result, "1.02 kB"); - }); -}); -``` - -### Coverage Requirements - -- **100%** statement, branch, function, and line coverage required -- No uncovered lines allowed -- Coverage reported by Node's built-in test runner with `--experimental-test-coverage` - -## Build Process - -```bash -npm run build # Build all distributions -npm run dev # Development mode with live reload -npm run build:watch # Watch mode -npm run build:analyze # Bundle size analysis -``` - -### Distribution Files - -- `dist/filesize.cjs` - CommonJS -- `dist/filesize.js` - ES Module -- `dist/filesize.min.js` - Minified ES Module -- `dist/filesize.umd.js` - UMD (browser) -- `dist/filesize.umd.min.js` - Minified UMD - -## Performance Considerations - -### Fast Operations (>10M ops/sec) -- Basic conversions: `filesize(1024)` -- Large numbers: `filesize(1073741824)` -- Standard output formats - -### Slower Operations (<100K ops/sec) -- Locale formatting: `filesize(1024, {locale: "en-US"})` - -### Optimization Tips -1. Cache `partial()` formatters for reuse -2. Avoid locale formatting in performance-critical code -3. Use `object` output for fastest structured data access - -## Common Patterns - -### Creating Formatters +### 5.3 Creating Formatters ```javascript import { partial } from 'filesize'; @@ -248,23 +212,37 @@ const formatBits = partial({bits: true}); const formatPrecise = partial({round: 3, pad: true}); ``` -### Error Handling +### 5.4 Modifying `partial()` +**Important**: `partial()` uses destructuring to freeze option values for immutability: ```javascript -try { - filesize("invalid"); -} catch (error) { - // TypeError: "Invalid number" -} - -try { - partial({ exponent: NaN }); // Works - NaN is preserved via destructuring -} catch (error) { - // No error - destructuring handles all primitive values +export function partial({ + bits = false, + pad = false, + base = -1, + round = 2, + // ... other options +} = {}) { + return (arg) => + filesize(arg, { + bits, + pad, + base, + round, + // ... same options + }); } ``` -### Padding with Negative Numbers +- Destructuring extracts and freezes primitive values at creation time +- Prevents mutations to original options object from affecting created formatters +- Simpler than deep cloning while maintaining immutability for all option types + +--- + +## 6. Common Patterns + +### 6.1 Padding with Negative Numbers When using `pad: true` with negative numbers, the decimal separator detection must skip the minus sign: ```javascript @@ -274,14 +252,14 @@ const x = separator || (resultStr.slice(1).match(/(\D)/g) || []).pop() || PERIOD Without this, `-1.00` would split on `-` instead of `.`, producing incorrect output like `-10 kB` instead of `-1.00 kB`. -### BigInt Support +### 6.2 BigInt Support ```javascript filesize(BigInt(1024)); // "1.02 kB" filesize(BigInt("10000000000000000000")); // Works with huge numbers ``` -### Auto-Exponent Pattern +### 6.3 Auto-Exponent Pattern When checking if the exponent should auto-calculate, use a named variable for clarity and coverage: ```javascript @@ -298,7 +276,7 @@ This pattern is used in: - `helpers.js`: `calculateOptimizedValue()` bits auto-increment - `helpers.js`: `applyPrecisionHandling()` scientific notation fix -### Forced Exponent Behavior +### 6.4 Forced Exponent Behavior When `exponent` is explicitly set (not `-1` or `NaN`): - Bits auto-increment is **disabled** in `calculateOptimizedValue()` @@ -311,32 +289,7 @@ filesize(1024, { exponent: 0, bits: true }); // "8192 bit" (not auto-incremented filesize(1024, { exponent: 0 }); // "1024 B" (not auto-incremented to kB) ``` -### Security - -The library follows OWASP best practices and is secure for production use: - -**Secure Patterns:** -- No `eval`, `Function` constructor, or command injection -- No prototype pollution (read-only symbol access, deep cloning in `partial()`) -- No XSS (returns plain strings only, no HTML/JS generation) -- No SSRF (no network requests) -- No ReDoS (simple regex, no catastrophic backtracking) -- Input validation on all user-provided values - -**Security Considerations:** -- The `symbols` option allows user-controlled objects but only reads from them (safe) -- On Node.js <17, `partial()` uses JSON cloning which cannot serialize `NaN`/`Infinity` - throws clear error instead of silent data loss -- The `symbols` option allows user-controlled objects but only reads from them (safe) - -**See `docs/TECHNICAL_DOCUMENTATION.md` for full security details.** - -## Build System - -The `ensureNewline()` plugin in `rollup.config.js` uses `generateBundle()` (not `renderChunk()`) to add trailing newlines. This preserves sourcemaps in minified builds by modifying the bundle after sourcemap generation. - -## Documentation Standards - -### JSDoc Template +### 6.5 JSDoc Template ```javascript /** @@ -351,24 +304,23 @@ The `ensureNewline()` plugin in `rollup.config.js` uses `generateBundle()` (not */ ``` -### When to Update Docs +--- -- Add/modify function parameters -- Change default values -- Add new features -- Update examples +## 7. Git Conventions -## Git Workflow +### 7.1 Commit Messages -1. Create feature branch: `git checkout -b feature/name` -2. Make changes -3. Run tests: `npm test` -4. Build: `npm run build` -5. Commit: `git commit -m "type: description"` -6. Push: `git push origin feature/name` +Follow [Conventional Commits](https://www.conventionalcommits.org/): -### Commit Message Types +``` +feat: add new output format option +fix: correct rounding on negative pad values +docs: update AGENTS.md with new patterns +test: add coverage for bits auto-increment +chore: update rollup config +``` +**Commit Message Types:** - `feat`: New feature - `fix`: Bug fix - `docs`: Documentation @@ -376,38 +328,74 @@ The `ensureNewline()` plugin in `rollup.config.js` uses `generateBundle()` (not - `build`: Build system changes - `test`: Test additions/fixes -## Troubleshooting +### 7.2 Branching + +- Feature branches: `feat/` or `fix/`. +- Never commit directly to `main` or `master`. Always create a feature branch first, then open a PR targeting `master`. + +### 7.3 Agent Workflow + +When auditing or modifying AGENTS.md (or any file): +1. Create a feature branch: `git checkout -b docs/` (or `feat/`, `fix/`). +2. Make changes and commit on the feature branch. +3. Push the feature branch and open a PR with `gh pr create --base master`. +4. Never commit or push directly to `main` or `master`. + +--- -### Tests Fail After Build -Run `npm test` before committing. The build process doesn't run tests automatically. +## 8. Operational Rules -### Coverage Drops Below 100% -Check the c8 report for uncovered lines. Add tests for missing branches. +### 8.1 Coverage -### Linting Errors -Run `npm run lint:fix` to auto-fix common issues. +The test runner enforces **100% code coverage** via `--experimental-test-coverage`. Every new function or class needs test coverage. No exceptions. -### Formatting -Run `npm run format:fix` to format code with oxfmt. +```bash +npm test # Full test suite (lint + node:test with coverage) +npm run test:watch # Live test watching +``` -### Build Output Files -Ensure output files end with newlines (configured in `rollup.config.js` with `ensureNewline()` plugin). +### 8.2 Build Process -## Quick Reference +```bash +npm run build # Build all distributions +npm run dev # Development mode with live reload +npm run build:watch # Watch mode +npm run build:analyze # Bundle size analysis +``` + +**Distribution Files:** +- `dist/filesize.cjs` - CommonJS +- `dist/filesize.js` - ES Module +- `dist/filesize.min.js` - Minified ES Module +- `dist/filesize.umd.js` - UMD (browser) +- `dist/filesize.umd.min.js` - Minified UMD + +**Build System**: The `ensureNewline()` plugin in `rollup.config.js` uses `generateBundle()` (not `renderChunk()`) to add trailing newlines. This preserves sourcemaps in minified builds by modifying the bundle after sourcemap generation. + +### 8.3 Performance + +### Fast Operations (>10M ops/sec) +- Basic conversions: `filesize(1024)` +- Large numbers: `filesize(1073741824)` +- Standard output formats + +### Slower Operations (<100K ops/sec) +- Locale formatting: `filesize(1024, {locale: "en-US"})` + +### Optimization Tips +1. Cache `partial()` formatters for reuse +2. Avoid locale formatting in performance-critical code +3. Use `object` output for fastest structured data access -**Files to modify:** -- `src/filesize.js` - Main logic (95% of changes) -- `src/helpers.js` - Helper functions -- `src/constants.js` - Constants/lookup tables +--- -**Commands:** -- `npm test` - Run everything -- `npm run build` - Build distributions -- `npm run lint` - Check code style -- `npm run format:fix` - Format code +## 9. Checklist Before Marking a TODO Complete -**Key constraints:** -- No external dependencies -- 100% test coverage required -- JSDoc on all exported functions -- ES Modules only (no CommonJS in src/) +- [ ] JSDoc on all exported functions +- [ ] Unit tests written and passing +- [ ] 100% code coverage maintained +- [ ] `npm test` passes (lint + tests) +- [ ] `npm run build` succeeds +- [ ] No hardcoded secrets or credentials introduced +- [ ] Zero external dependencies added +- [ ] ES Modules only (no CommonJS in src/)