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(),Functionconstructor, or command injection.*imports.- Mutating a list while iterating over it.
- Prototype pollution (write-only symbol access).
Follow the OWASP 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
symbolsoption allows user-controlled objects but only reads from them (safe).
- Never rebase under any circumstance without explicit agreement from the user. Never assume your decision is correct.
- Never force push.
- 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.
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.
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 - Helper functions:
src/helpers.js - Constants:
src/constants.js - Zero dependencies: Uses only native JavaScript APIs
- 100% test coverage: ~149 tests passing
| 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 |
- JavaScript: ES Modules (no CommonJS in src/)
- Linting: oxlint (fast Rust-based linter)
- Formatting: oxfmt (fast Rust-based formatter)
- Testing:
node:test+ node:assert with--experimental-test-coverage - Build: rollup (produces CJS, ESM, UMD, minified outputs)
- 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.
- Raise typed errors.
filesize("invalid")throwsTypeError: "Invalid number". - No
eval,Functionconstructor, or command injection.
- Unit tests live in
tests/unit/. - Use
describe()andit()fromnode:test. - Each public function or class must have at least one test.
- 100% statement, branch, function, and line coverage required.
// Function with JSDoc
/**
* Description
* @param {type} param - Description
* @returns {type} Description
*/
export function functionName (param) {
// Implementation
}
// Constants
export const CONSTANT_NAME = "value";
// Imports: group by source, alphabetize
import {
ARRAY,
BIT,
BYTE
} from "./constants.js";
import {
helperFunction
} from "./helpers.js";Input → Validation → Standard Normalization → Exponent Calculation
→ Value Conversion → Formatting → Output Generation
filesize(arg, options): Orchestrator — delegates validation, exponent, value calc, rounding, formatting, and output dispatchpartial(options): Creates pre-configured formatter with immutable frozen optionshandleZeroValue(): Special handling for zero inputcalculateOptimizedValue(): Core conversion logic with bits handlingapplyPrecisionHandling(): Precision + scientific notation correctionapplyNumberFormatting(): Locale, separator, paddinggetBaseConfiguration(): Cached base/standard lookup tablecalculateExponent(): Log-based exponent calculation with clampingapplyRounding(): Rounding + auto-increment ceiling adjustmentresolveSymbol(): Symbol table lookup with SI override for e=1decorateResult(): Negation, custom symbols, full form assemblyformatOutput(): Array/object/string output dispatch
- 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)
| Option | Type | Default | Description |
|---|---|---|---|
bits |
boolean | false |
Calculate bits instead of bytes |
pad |
boolean | false |
Pad decimal places |
base |
number | -1 |
Number base (2, 10, or -1 for auto) |
round |
number | 2 |
Decimal places to round |
locale |
string|boolean | "" |
Locale for formatting |
separator |
string | "" |
Custom decimal separator |
spacer |
string | " " |
Value-unit separator |
symbols |
Object | {} |
Custom unit symbols |
standard |
string | "" |
Unit standard (si, iec, jedec) |
output |
string | "string" |
Output format (string, array, object, exponent) |
fullform |
boolean | false |
Use full unit names |
fullforms |
Array | [] |
Custom full unit names |
exponent |
number | -1 |
Force specific exponent |
roundingMethod |
string | "round" |
Math.round, floor, or ceil |
precision |
number | 0 |
Significant digits |
- string:
"1.5 KB"(default) - array:
[1.5, "KB"] - object:
{value: 1.5, symbol: "KB", exponent: 1, unit: "KB"} - exponent:
1
import { partial } from 'filesize';
const formatBinary = partial({base: 2, standard: "iec"});
const formatBits = partial({bits: true});
const formatPrecise = partial({round: 3, pad: true});Important: partial() uses destructuring to freeze option values for immutability:
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
When using pad: true with negative numbers, the decimal separator detection must skip the minus sign:
// Correct: slice(1) to skip minus sign
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.
filesize(BigInt(1024)); // "1.02 kB"
filesize(BigInt("10000000000000000000")); // Works with huge numbersWhen checking if the exponent should auto-calculate, use a named variable for clarity and coverage:
const autoExponent = exponent === -1 || isNaN(exponent);
if (result[0] === ceil && e < 8 && autoExponent) {
// Auto-increment logic
}This pattern is used in:
filesize.js: rounding-based auto-incrementfilesize.js: precision handlinghelpers.js:calculateOptimizedValue()bits auto-incrementhelpers.js:applyPrecisionHandling()scientific notation fix
When exponent is explicitly set (not -1 or NaN):
- Bits auto-increment is disabled in
calculateOptimizedValue() - Scientific notation normalization is disabled in
applyPrecisionHandling() - Rounding-based auto-increment is disabled in
filesize()
Example:
filesize(1024, { exponent: 0, bits: true }); // "8192 bit" (not auto-incremented to kbit)
filesize(1024, { exponent: 0 }); // "1024 B" (not auto-incremented to kB)/**
* Function description
* @param {type} paramName - Parameter description
* @param {type} [paramName=default] - Optional parameter
* @returns {type} Return description
* @throws {ErrorType} When condition occurs
* @example
* // Example usage
* functionName(arg); // "result"
*/Follow Conventional Commits:
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 featurefix: Bug fixdocs: Documentationrefactor: Code restructuringbuild: Build system changestest: Test additions/fixes
- Feature branches:
feat/<short-desc>orfix/<short-desc>. - Never commit directly to
mainormaster. 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
mainormaster.
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.
The test runner enforces 100% code coverage via --experimental-test-coverage. Every new function or class needs test coverage. No exceptions.
npm test # Full test suite (lint + node:test with coverage)
npm run test:watch # Live test watchingnpm 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 analysisDistribution Files:
dist/filesize.cjs- CommonJSdist/filesize.js- ES Moduledist/filesize.min.js- Minified ES Moduledist/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.
- Basic conversions:
filesize(1024) - Large numbers:
filesize(1073741824) - Standard output formats
- Locale formatting:
filesize(1024, {locale: "en-US"})
- Cache
partial()formatters for reuse - Avoid locale formatting in performance-critical code
- Use
objectoutput for fastest structured data access
- JSDoc on all exported functions
- Unit tests written and passing
- 100% code coverage maintained
-
npm testpasses (lint + tests) -
npm run buildsucceeds - No hardcoded secrets or credentials introduced
- Zero external dependencies added
- ES Modules only (no CommonJS in src/)