# lodash/lodash — codebase unpack

> Canonical page: https://codevetter.com/unpack/lodash

This is a deterministic Repo Unpack scan of lodash/lodash — a structural map, not a review, security audit, or maintainer-endorsed description.

- Scanned commit: 2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8 (2026-09-11)
- Last upstream commit: ci: update actions to latest versions (#6279)
- Generated by codevetter 1.12.0 on 2026-09-22
- 64 files scanned · 1.8 MB

## Languages

- JavaScript: 27 files
- YAML: 8 files
- Markdown: 7 files
- HTML: 5 files
- JSON: 2 files

## Repository structure

- lib/ — 21 files
- test/ — 10 files
- .github/ — 9 files
- fp/ — 4 files
- perf/ — 3 files
- doc/ — 1 files

Probable source entrypoint: `lib/` (inferred from the tree, not upstream docs)

## Source layout — lib/

- lib/common/ — 5 files
- lib/fp/ — 12 files
- lib/main/ — 4 files

## Package

- package.json: lodash v4.18.1 — 27 declared dependencies

## Structural health signals

Average score 8.7/10 across 27 files; 5 hotspots. These are deterministic review leads, not proof of a bug.

## Recent history

- 2b5e6f7399a7 (2026-09-11) ci: update actions to latest versions (#6279)
- 0f79302eaf53 (2026-09-09) ci: ossf/scorecard-action update to 2.4.4 (#6278)
- a666ba591064 (2026-07-03) docs: fix some minor docs inconsistencies (#6249)
- 9429a4ee0305 (2026-06-25) ci(playwright): fix browser test flake from test timeout shorter than assertion
- be2b153da476 (2026-06-25) docs: clarify number-word behavior in startCase examples (#6224)

## Overview

This is the lodash monorepo root: the hand-maintained single-file UMD source `lodash.js` (v4.18.1, ~546 KB, one IIFE wrapping a `runInContext` factory), the `fp/` converter that re-wraps every method into immutable auto-curried iteratee-first data-last form, and a small Node build/doc toolchain under `lib/` that copies, minifies, webpacks, and doc-generates artifacts into `dist/` and `doc/`. It is not an application — it is a library plus its release machinery, consumed by downstream repos (`lodash-cli`, the `npm` branch) that call `build:main-modules`/`build:fp-modules` with a target directory. What's distinctive is the test posture: ~1750 QUnit assertions in a single 814 KB `test/test.js` that deliberately sabotages host globals ("bizarro" shims), plus vendored Backbone and Underscore suites run against lodash, driven in Node and across six Playwright browser projects. The repo is now under OpenJS Foundation governance with a published threat model that explicitly scopes prototype pollution and `_.template` code injection as in-scope vulnerability classes.

## System map

There is no server or CLI entrypoint; the shipped artifact is `lodash.js`, declared as `main` in a `private: true` package.json, exported via a three-way UMD tail (AMD `define`, CommonJS `freeModule.exports`, global `root._`). All build logic lives in `lib/`, split into `lib/common/` (file copy, uglify wrapper, error-throwing callback, Hash), `lib/main/` (dist, modules, docdown, site HTML) and `lib/fp/` (webpack dist, `.jst`-templated module emission, FP wiki). The only persistent storage is the filesystem: `dist/` for builds and `doc/README.md` for generated API docs. Eight GitHub Actions workflows cover Node 4–25, four Bun versions, browser matrix, docs generation, dist-sync verification, CodeQL, OSSF Scorecard and Renovate. Deployment is out-of-repo — the Release Team publishes to npm, and `dist/lodash.core.js` is produced by the external `lodash-cli`, not by `npm run build`.
- The package is `private: true` with `main: "lodash.js"` and `engines.node >= 4.0.0`, so the repo itself is never published; it is the source-of-truth checkout that other release tooling consumes. — [package.json#L1-9](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L1-L9)
- `lodash.js` is a single `;(function() { ... }.call(this))` IIFE whose entire API is built inside a `runInContext(context)` factory at line 1449; the module-level `var _ = runInContext()` at line 17232 creates the default instance. — [lodash.js#L9-15](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L9-L15) [lodash.js#L1449-1462](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L1449-L1462) [lodash.js#L17230-17234](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L17230-L17234)
- The export tail branches three ways in strict order — AMD `define.amd` (also assigning `root._` to survive script-tag loading), then `freeModule.exports` for Node/CommonJS, then a bare global assignment — which is why the file works unchanged in every listed CI runtime. — [lodash.js#L17236-17256](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L17236-L17256)
- Runtime-boundary detection is done once at the top level via `freeGlobal`/`freeSelf`/`Function('return this')()` for `root`, plus `freeExports`, `freeModule`, `moduleExports` and `freeProcess` for Node-only features like `Buffer` and `util.types`. — [lodash.js#L431-462](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L431-L462)
- `npm run build` is only `build:main && build:fp`: `lib/main/build-dist.js` copies `lodash.js` → `dist/lodash.js` and minifies it, while `lib/fp/build-dist.js` runs two webpack passes (`fp/_mapping.js` → `dist/mapping.fp.js`, `fp/_convertBrowser.js` → `dist/lodash.fp.js`, both UMD) then minifies. — [package.json#L11-14](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L11-L14) [lib/main/build-dist.js#L24-31](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-dist.js#L24-L31) [lib/fp/build-dist.js#L16-56](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-dist.js#L16-L56)
- Minification is centralized in `lib/common/minify.js`, which shells into `uglify-js@2.7.5` with `unsafe: true`, `pure_getters: true`, `ascii_only: true` and a `/@license/` comment filter from `lib/common/uglify.options.js`. — [lib/common/minify.js#L24-37](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/common/minify.js#L24-L37) [lib/common/uglify.options.js#L9-22](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/common/uglify.options.js#L9-L22) [package.json#L57](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L57)
- All build scripts share the same async shape: `lib/common/file.js` returns `_.partial`-applied `fs.copy`/`minify`/`fs.writeFile` thunks that are handed to `async.series` with `util.pitch` as the terminal callback, and `pitch` simply rethrows any error. — [lib/common/file.js#L21-71](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/common/file.js#L21-L71) [lib/common/util.js#L31-40](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/common/util.js#L31-L40) [lib/main/build-dist.js#L25-28](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-dist.js#L25-L28)
- `lib/common/mapping.js` wraps `fp/_mapping.js` in a null-prototype `Hash` (recursively, via `_.transform` with `Hash.prototype = Object.create(null)`), so build-time lookups like `mapping.skipFixed[name]` can't collide with inherited `Object.prototype` keys. — [lib/common/mapping.js#L1-9](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/common/mapping.js#L1-L9) [lib/common/util.js#L15-23](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/common/util.js#L15-L23)
- Documentation is a two-stage pipeline: `lib/main/build-doc.js` runs `docdown` over `lodash.js` in either `github` or `site` style and writes `doc/README.md`; `lib/main/build-site.js` then feeds that markdown through `marky-markdown` + `cheerio` and emits `doc/<version>.html` with Jekyll front matter. — [lib/main/build-doc.js#L17-35](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-doc.js#L17-L35) [lib/main/build-doc.js#L76-81](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-doc.js#L76-L81) [lib/main/build-site.js#L181-237](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-site.js#L181-L237) [package.json#L16-19](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L16-L19)
- `doc/README.md` is a checked-in 295 KB generated artifact (its first line carries the `v4.18.1` docdown title), and `doc/*.html` is gitignored — so the markdown is versioned but the site HTML is not. — [doc/README.md#L1-3](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/doc/README.md#L1-L3) [.gitignore#L1-4](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.gitignore#L1-L4) [lib/main/build-doc.js#L12-22](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-doc.js#L12-L22)
- Eight workflows exist: `ci-node.yml` (Node 4 through 25 on ubuntu, `npm run validate`), `ci-bun.yml` (Bun 1.0–1.3), `browser-testing.yml` (Playwright), `ci-docs.yml`, `ci-dist-sync.yml`, `codeql.yml`, `scorecards.yml`, `renovate.yml`. — [.github/workflows/ci-node.yml#L14-50](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-node.yml#L14-L50) [.github/workflows/ci-bun.yml#L10-38](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-bun.yml#L10-L38) [.github/workflows/ci-dist-sync.yml#L24-37](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-dist-sync.yml#L24-L37)
- Every third-party action is SHA-pinned (e.g. `actions/checkout@3d3c42e5...` v7.0.1, `github/codeql-action/init@b96794f0...` v4.38.0), and Renovate is self-hosted monthly with `minimumReleaseAge: 14d` and npm major updates disabled. — [.github/workflows/scorecards.yml#L33-58](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/scorecards.yml#L33-L58) [.github/workflows/renovate.yml#L10-22](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/renovate.yml#L10-L22) [renovate.json#L1-17](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/renovate.json#L1-L17)
- `vendor/` carries four vendored third-party trees used only by the browser harnesses — Backbone (source + 7 test files + setup), Underscore (source, min, 7 test files), json-js `json2.js`, and a ~960 KB `firebug-lite` console used by the perf page. — [test/backbone.html#L88-98](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/backbone.html#L88-L98) [test/underscore.html#L383-390](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/underscore.html#L383-L390) [perf/index.html#L42](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/perf/index.html#L42)
- Deployment is explicitly out-of-repo: `GOVERNANCE.md` names a single-member Release Team responsible for npm publishes, and the README documents that `dist/` builds come from `lodash-cli` (`lodash -o ./dist/lodash.js`). — [GOVERNANCE.md#L20-26](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/GOVERNANCE.md#L20-L26) [README.md#L19-25](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/README.md#L19-L25)

## Feature catalog

User-facing surface is the lodash API itself (~300 methods attached to the `lodash` function plus a lazy-evaluating chain wrapper), the `fp` variant, and the four per-method build/doc npm scripts. `fp/_mapping.js` is the declarative feature table that defines the entire FP surface: aliases, arity buckets, rearg orders, spread configs, mutation classes and skip lists. The repo also ships two interactive HTML surfaces — a QUnit test runner with build/loader dropdowns, and a Benchmark.js perf page comparing lodash against Underscore. There is no HTTP API, no routes, and no CLI subcommands; the `lib/*/build-*.js` scripts are the closest thing to commands and take their target directory from the last `process.argv` element.
- The full lodash API is assembled by explicit `lodash.<name> = <fn>` assignment inside `runInContext`, including `lodash.runInContext`, `lodash.VERSION` and the chainable `lodash.prototype.*` sequence methods (`at`, `chain`, `commit`, `next`, `plant`, `reverse`, `toJSON`/`valueOf`/`value`). — [lodash.js#L16900-16960](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L16900-L16960) [lodash.js#L17205-17226](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L17205-L17226)
- Lazy evaluation is a first-class feature: `LazyWrapper.prototype.clone/reverse/value` are installed and a `realNames` registry maps minified function names back to method names so shortcut fusion survives minification. — [lodash.js#L17190-17215](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L17190-L17215)
- `fp/_mapping.js` declares the FP surface across 12 exported tables — `aliasToReal`, `aryMethod`, `aryRearg`, `iterateeAry`, `iterateeRearg`, `methodRearg`, `methodSpread`, `mutate`, `realToAlias`, `remap`, `skipFixed`, `skipRearg`. — [fp/_mapping.js#L1-2](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_mapping.js#L1-L2) [fp/_mapping.js#L71-72](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_mapping.js#L71-L72) [fp/_mapping.js#L121-129](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_mapping.js#L121-L129) [fp/_mapping.js#L264-330](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_mapping.js#L264-L330)
- The FP immutability guarantee is table-driven: `mapping.mutate` classifies mutating methods into `array` (fill, pull*, remove, reverse), `object` (assign*, defaults*, merge*) and `set` (set, setWith, unset, update, updateWith), each wrapped with a different cloner. — [fp/_mapping.js#L225-262](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_mapping.js#L225-L262) [fp/_baseConvert.js#L473-483](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L473-L483)
- `fp/_convertBrowser.js` self-installs when loaded after lodash in a browser: if a global `_` with `runInContext` exists, it reassigns `_ = browserConvert(_.runInContext())`, converting the page's lodash in place. — [fp/_convertBrowser.js#L11-18](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_convertBrowser.js#L11-L18)
- FP conversion is configurable per-call and per-method through five boolean options — `cap`, `curry`, `fixed`, `immutable`, `rearg` — each defaulting to `true`, exposed as `fp.convert({...})` and `fp.someMethod.convert({...})`. — [fp/_baseConvert.js#L152-158](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L152-L158) [fp/_baseConvert.js#L377-396](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L377-L396) [lib/fp/template/doc/wiki.jst#L169-196](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/template/doc/wiki.jst#L169-L196)
- `npm run build:main-modules <target>` copies `dist/lodash.core.js`, `dist/lodash.core.min.js` and `dist/lodash.min.js` into a target directory as `core.js`, `core.min.js` and `lodash.min.js` — the per-method npm package layout. — [lib/main/build-modules.js#L13-34](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-modules.js#L13-L34) [package.json#L15](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L15)
- `npm run build:fp-modules <target>` globs the target's existing `*.js` modules (ignoring `_*.js`, `core.js`, `fp.js`, `index.js`, `lodash.js`), adds alias and remap paths, and emits one `fp/<name>.js` per method chosen from four `.jst` templates (`alias`, `category`, `thru`, `module`). — [lib/fp/build-modules.js#L87-148](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-modules.js#L87-L148) [lib/fp/template/modules/module.jst](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/template/modules/module.jst) [lib/fp/template/modules/thru.jst](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/template/modules/thru.jst)
- `npm run doc:fp` renders the public FP-Guide wiki page from `lib/fp/template/doc/wiki.jst`, computing the method lists at build time with helpers `toFuncList` (sorted, chunked by 5) and `toArgOrder` (index permutation → `(a, b, c)` string). — [lib/fp/build-doc.js#L30-63](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-doc.js#L30-L63) [lib/fp/template/doc/wiki.jst#L69-140](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/template/doc/wiki.jst#L69-L140)
- The QUnit runner page exposes a build selector (lodash dev/prod, lodash-core dev/prod) and a loader selector (none, Curl, Dojo, RequireJS) that rewrite `location.href` query params, letting a human test any build/loader combination in-browser. — [test/asset/test-ui.js#L19-99](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/asset/test-ui.js#L19-L99) [test/asset/test-ui.js#L109-135](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/asset/test-ui.js#L109-L135)
- `perf/index.html` plus `perf/perf.js` is a Benchmark.js A/B suite that loads lodash and a comparison library (default Underscore), scores each into `score.a`/`score.b`, and renders output through an embedded Firebug Lite console. — [perf/perf.js#L86-107](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/perf/perf.js#L86-L107) [perf/asset/perf-ui.js#L36-53](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/perf/asset/perf-ui.js#L36-L53) [perf/index.html#L38-55](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/perf/index.html#L38-L55)
- `test/remove.js` is a small standalone Node utility that takes a `/pattern/flags` string plus a file path and strips matched line groups in place — used to prune generated sources. — [test/remove.js#L8-27](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/remove.js#L8-L27)
- `_.template` is a full templating feature with configurable `escape`/`evaluate`/`interpolate` delimiters and `imports`/`variable`/`sourceURL` options, compiled to a real function via the `Function` constructor. — [lodash.js#L14882-14892](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L14882-L14892) [lodash.js#L1764-1780](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L1764-L1780) [lodash.js#L14988-14996](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L14988-L14996)
- `_.runInContext(context)` lets callers swap built-ins: `contextProps` lists the 30 globals (Array, Buffer, Map, Promise, Set, Symbol, WeakMap, setTimeout, …) that get `_.pick`-ed from `root` and defaulted into the supplied context. — [lodash.js#L297-303](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L297-L303) [lodash.js#L1449-1450](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L1449-L1450) [lodash.js#L1440-1448](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L1440-L1448)

## Data flow

Two distinct flows exist. At library runtime, data enters as plain JS values through method arguments, is normalized by `castPath`/`toKey`/`baseIteratee`, mutated or cloned by `base*` internals, and exits either as a new value or — in chain mode — as a `LazyWrapper`/`lodash` wrapper holding `__wrapped__` and an `__actions__` queue that is only drained on `.value()`. At build time, the input boundary is `lodash.js` plus `fp/_mapping.js`; transforms are `fs.copy`, `uglify.minify`, `webpack`, `docdown` and `_.template`-compiled `.jst` files; the output boundary is the filesystem (`dist/`, `doc/`, or an arbitrary `target` from `process.argv`). The FP path is unusual: `_mapping.js` is pure data that is read three times — by `_baseConvert` at runtime, by `build-modules.js` to decide which template to emit, and by `build-doc.js` to render the wiki.
- Property paths are the main untrusted input boundary: `castPath` converts strings/arrays to key arrays and `toKey` normalizes each segment before any read or write in `baseGet`/`baseSet`/`baseUnset`. — [lodash.js#L4025-4033](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4025-L4033) [lodash.js#L4376-4382](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4376-L4382) [lodash.js#L6406-6422](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L6406-L6422)
- Writes funnel through `assignValue` → `baseAssignValue`, which special-cases the key `__proto__` by using `Object.defineProperty` with an own data descriptor rather than plain assignment, so it lands on the object instead of the prototype. — [lodash.js#L2597-2610](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L2597-L2610)
- Deep merge reads go through `safeGet`, which returns `undefined` for `__proto__` and for `constructor` when it resolves to a function — cutting the read side of the pollution chain. — [lodash.js#L6697-6715](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L6697-L6715)
- Chain state lives on the wrapper instance: `mixin` installs prototype methods that clone `__actions__` via `copyArray`, push `{func, args, thisArg}`, carry `__chain__` forward, and defer execution until `.value()`. — [lodash.js#L15840-15861](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L15840-L15861) [lodash.js#L17222-17226](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L17222-L17226)
- In `_baseConvert`, each method flows through a fixed transform pipeline — immutability wrapper → `castFixed` (arity cap or `flatSpread`) → `castRearg` (argument permutation) → `castCap` (iteratee arity cap) → `castCurry` — with `methodSpread[name].afterRearg` deciding whether fixed or rearg runs first. — [fp/_baseConvert.js#L468-512](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L468-L512)
- The FP converter owns two state sources: a `pristine = func.runInContext()` snapshot used so `.convert()` re-derives from unconverted methods, and a `defaultHolder` placeholder that is stamped onto both the wrapped and original function as `.placeholder`. — [fp/_baseConvert.js#L160-165](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L160-L165) [fp/_baseConvert.js#L388-400](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L388-L400) [fp/_baseConvert.js#L512-516](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L512-L516)
- Conversion output is assembled as `pairs` of `[name, wrappedFn]` — first for every name in `mapping.aryMethod[1..4]`, then for remaining own function keys — and only then assigned back onto `_`, deliberately leaving `_.prototype` untouched so chaining still works. — [fp/_baseConvert.js#L520-560](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L520-L560)
- `_.set`-family immutability in FP is achieved by `cloneByPath`, which walks the resolved path cloning each level but skipping values that are functions, errors or WeakMaps. — [fp/_baseConvert.js#L348-368](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L348-L368)
- Build data flows from `fp/_mapping.js` → `lib/common/mapping.js` (null-prototype Hash) → `lib/fp/build-modules.js`, where `_.union(mapping.aryMethod[1..4])` forms `aryMethods` and drives the `isThru` decision for every emitted module. — [lib/common/mapping.js#L3-9](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/common/mapping.js#L3-L9) [lib/fp/build-modules.js#L15-20](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-modules.js#L15-L20) [lib/fp/build-modules.js#L76-103](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-modules.js#L76-L103)
- `.jst` templates are compiled to functions at require time by `file.globTemplate`, which globs `*.jst`, keys them by basename, and runs each through `_.template` — so `template.module`, `template.alias`, `template.wiki` are lodash templates executed with `{name, mapping}` data. — [lib/common/file.js#L32-37](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/common/file.js#L32-L37) [lib/fp/build-modules.js#L12-13](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-modules.js#L12-L13) [lib/fp/build-doc.js#L11-20](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-doc.js#L11-L20)
- The doc HTML flow mutates markdown through five ordered regex rewrites (uncommenting docdown HTML hints, converting `[source]`/`[npm package]` links after `<h3>`) before Cheerio DOM passes `autoLink`, `renameLodashId`, `removeHorizontalRules`, `removeMarkyAttributes`, `repairMarkyHeaders`, `tidyHighlights`. — [lib/main/build-site.js#L182-218](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-site.js#L182-L218)
- The site build derives its output filename from the document itself — it removes the first `<h1>`, reads the version out of its `<span>`, and writes `doc/<version>.html`; if that span is missing the front matter records `version: null`. — [lib/main/build-site.js#L204-236](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-site.js#L204-L236)
- In the browser harness, the build path is a URL query parameter: `test-ui.js` parses `?build=` and `?loader=`, maps them to file paths, and `test/index.html` `document.write`s those paths as script tags — the page loads whatever path the URL names, including a full remote URL (`ui.isForeign`). — [test/asset/test-ui.js#L8-11](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/asset/test-ui.js#L8-L11) [test/asset/test-ui.js#L109-137](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/asset/test-ui.js#L109-L137) [test/index.html#L191-207](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L191-L207)
- Web Worker coverage round-trips the build path: `test.js` posts `ui.buildPath` to `test/asset/worker.js`, which `importScripts` it and posts back `_.VERSION` (or the caught error message stuffed into a fake `VERSION`), landing in `_._VERSION`. — [test/test.js#L730-738](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L730-L738) [test/asset/worker.js#L5-17](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/asset/worker.js#L5-L17)

## Behavior traces

Four flows matter here: library initialization, an FP conversion, the `npm test` path, and the browser-test path. Library init is entirely synchronous at require time. The Node test path is unusual because `pretest` forces a full build first, meaning `npm test` cannot run without webpack and uglify succeeding. The browser path is a three-stage `document.write` cascade that deliberately corrupts globals before loading lodash, snapshots the result as `lodashBizarro`, restores the globals, then loads the real build and the suite.
- Library startup: the outer IIFE defines constants and regexes → detects `freeGlobal`/`root`/`freeModule` → defines `runInContext` → calls `var _ = runInContext()` at line 17232 → picks one of three export branches. No async work and no I/O occurs. — [lodash.js#L9-15](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L9-L15) [lodash.js#L431-446](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L431-L446) [lodash.js#L17232-17256](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L17232-L17256)
- Inside `runInContext`, the sequence is: default the context from `contextProps` → alias built-ins (`Array`, `Object`, `Function`, …) → derive `maskSrcKey` from `context['__core-js_shared__']` → build `reIsNative` from `funcToString.call(hasOwnProperty)` → define all internals → attach them to `lodash` → return `lodash`. — [lodash.js#L1449-1500](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L1449-L1500) [lodash.js#L16900-16960](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L16900-L16960) [lodash.js#L17218-17228](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L17218-L17228)
- FP conversion of the whole library: `baseConvert(util, name, func, options)` detects `isObj`/`isLib` → builds `config` from the five option flags → snapshots `pristine` → iterates `mapping.aryMethod[1..4]` calling `wrap(key, func, _)` for each → sweeps remaining own function keys attaching only `.convert` → assigns all pairs onto `_` → sets `_.convert` and `_.placeholder` → finally assigns aliases. — [fp/_baseConvert.js#L138-166](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L138-L166) [fp/_baseConvert.js#L520-560](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L520-L560)
- A single method's wrap: `wrap()` resolves `realName` via `aliasToReal` → applies a named special-case wrapper if one exists in `wrappers` (castArray, iteratee, mixin, nthArg, rearg, runInContext) → else applies `wrapImmutable` with the cloner chosen from `mapping.mutate` → then runs the castFixed/castRearg/castCap/castCurry chain → attaches `.convert` and `.placeholder`. — [fp/_baseConvert.js#L199-262](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L199-L262) [fp/_baseConvert.js#L468-518](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L468-L518)
- `npm test` fires `pretest` → `npm run build` → `build:main` (copy + minify) → `build:fp` (webpack mapping, webpack fp, minify) → then `test:main` = `node test/test` and `test:fp` = `node test/test-fp`. — [package.json#L11-14](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L11-L14) [package.json#L20](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L20) [package.json#L26-29](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L26-L29)
- `node test/test` bootstrap: resolve `filePath` from argv (defaulting to `../lodash.js`, then `fs.realpathSync` + `require.resolve`) → build the `ui` object with `isModularize`/`isStrict` flags → poison `global.root` with a throwing getter → require `qunit-extras` → load the previous stable lodash from `node_modules/lodash/lodash.js` as `lodashStable` → `interopRequire(filePath)` as `_`. — [test/test.js#L213-242](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L213-L242) [test/test.js#L320-347](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L320-L347)
- `test.js` then reaches into internals for coverage: it derives `Hash`, `MapCache`, `ListCache` and `Stack` constructors by inspecting `_.memoize.Cache.__data__.hash.constructor` and by capturing the stack argument passed to an `_.isMatchWith` customizer. — [test/test.js#L349-362](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L349-L362)
- At the end of both Node suites the same tail runs: set `QUnit.config.asyncRetries = 10` and `hidepassed = true`, and when `document` is absent set `noglobals = true` then `QUnit.load(); QUnit.start();`. — [test/test.js#L16330-16345](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L16330-L16345) [test/test-fp.js#L1795-1810](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test-fp.js#L1795-L1810)
- `test-fp.js` startup differs: it builds a `convert` shim that dispatches to either `root.fp` (browser, pre-built `dist/lodash.fp.js`) or `require('../fp/_baseConvert.js')` (Node), then produces `fp` either by `_.noConflict()` juggling or by `convert(_.runInContext())`. — [test/test-fp.js#L34-70](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test-fp.js#L34-L70)
- Browser test startup, stage 1: `test/index.html` loads stable lodash + QUnit + qunit-extras + platform + `test-ui.js`, calls `addBizarroMethods()` to null out `Object.create`, `Object.getOwnPropertySymbols`, `Symbol`, `Promise`, `Set`, `WeakMap` and patch `Function.prototype.toString`, then `document.write`s `ui.buildPath`. — [test/index.html#L25-30](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L25-L30) [test/index.html#L49-131](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L49-L131) [test/index.html#L190-194](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L190-L194)
- Stage 2 captures the sabotaged instance as `lodashBizarro`, clears `window._`, calls `removeBizarroMethods()` to restore every global, then `document.write`s either the build + `test.js` directly or the selected AMD loader. — [test/index.html#L196-208](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L196-L208) [test/index.html#L133-188](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L133-L188)
- Stage 3 (AMD path) configures the loader with three package aliases pointing at the same files through different paths — `lodash`, `shimmed` (`./abc/../`) and `underscore` (`./xyz/../`) — loads all three, tags each with `moduleName`, optionally pulls `_baseEach`/`_isIndex`/`_isIterateeCall`, then `require(['test'])` and `QUnit.start()`. — [test/index.html#L214-327](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L214-L327) [test/test.js#L766-796](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L766-L796)
- The Playwright flow is: workflow installs deps and browsers → `npm run build` → `npx http-server -p 9001 &` → `npx playwright test` → each spec navigates to one of four pages and asserts the text `0 failed` appears within 60s, across 6 browser projects. — [.github/workflows/browser-testing.yml#L22-38](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/browser-testing.yml#L22-L38) [test/playwright-runner.spec.js#L5-24](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/playwright-runner.spec.js#L5-L24) [playwright.config.js#L6-30](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/playwright.config.js#L6-L30)
- The dist-sync check trace is: checkout → `npm install` → `npm run build` → `git diff --exit-code dist/`; a non-empty diff emits a `::error::` annotation telling the contributor to rebuild and commit. — [.github/workflows/ci-dist-sync.yml#L24-37](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-dist-sync.yml#L24-L37)
- `npm run doc` traces to `node lib/main/build-doc github` → `_.last(process.argv)` yields `'github'` → `_.defaults({}, config.base, config.github)` → `docdown(options)` → `postprocess` brackets `Symbol.*` references → write `doc/README.md` → then `markdown-doctest doc/*.md` executes every fenced example against a real lodash with `lodash-doc-globals` in scope. — [package.json#L16](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L16) [package.json#L27](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L27) [lib/main/build-doc.js#L63-83](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-doc.js#L63-L83) [.markdown-doctest-setup.js#L1-11](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.markdown-doctest-setup.js#L1-L11)

## Testing signals

Testing is QUnit 2 (`qunitjs` + `qunit-extras`) driven two ways: directly under Node via `node test/test` and `node test/test-fp`, and in browsers via Playwright asserting `0 failed` on four static pages. Coverage of the main library is very broad — 307 QUnit modules and ~1754 assertions in `test/test.js` alone, plus 85/134 for FP — and is supplemented by running the vendored Backbone and Underscore suites against lodash. Style is enforced by JSCS across `lodash.js`, `fp/`, `lib/`, `perf/` and `test/`, and docs are tested by executing every README code fence via `markdown-doctest`. The notable gaps are the `lib/` build scripts themselves and the Playwright runner's coarse assertion.
- `test/test.js` contains 307 `QUnit.module(` and 1754 `QUnit.test(` calls in a single 814 KB file; `test/test-fp.js` adds 85 modules and 134 tests. — [test/test.js#L764-800](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L764-L800) [test/test-fp.js#L1780-1795](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test-fp.js#L1780-L1795)
- `npm run validate` is the CI gate everywhere — it chains `npm run style` (four JSCS invocations) and `npm test` — and is what both `ci-node.yml` and `ci-bun.yml` run. — [package.json#L21-30](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L21-L30) [.github/workflows/ci-node.yml#L58-59](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-node.yml#L58-L59) [.github/workflows/ci-bun.yml#L37-38](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-bun.yml#L37-L38)
- The Node matrix is exhaustive and non-fail-fast: Node 4 through 25 on ubuntu-latest, with a special step installing `npm@3.10.10` for Node 5 because of npm 3 package-format bugs. — [.github/workflows/ci-node.yml#L14-56](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-node.yml#L14-L56)
- Bun CI needs a workaround before it can install: it runs `npm i --lockfile-version 3 --package-lock-only` first because the committed `package-lock.json` is an older lockfile version, flagged with a TODO. — [.github/workflows/ci-bun.yml#L26-34](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-bun.yml#L26-L34)
- Browser coverage runs six Playwright projects (Chromium, Firefox, WebKit, Microsoft Edge via msedge channel, Mobile Safari/iPhone 12, Mobile Chrome/Pixel 5) with `retries: 2` under CI and a 120s per-test timeout chosen to exceed the 60s in-page wait. — [playwright.config.js#L6-30](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/playwright.config.js#L6-L30)
- Each Playwright spec asserts only that the literal text `0 failed` becomes visible — it does not read which tests ran, so a page that silently fails to load its suite and renders nothing will time out rather than report a specific failure. — [test/playwright-runner.spec.js#L5-13](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/playwright-runner.spec.js#L5-L13)
- Cross-library regression coverage: `test/backbone.html` runs Backbone's own 7 test files against lodash after `mixin`-ing 17 lodash methods and aliasing `indexBy`→`keyBy`, `invoke`→`invokeMap`, `rest`→`tail`. — [test/backbone.html#L29-98](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/backbone.html#L29-L98)
- `test/underscore.html` runs Underscore's 7 suites against lodash and carries a large `QUnit.config.excused` map enumerating known, accepted behavioral divergences (e.g. `chunk` size 0, `difference` OO-style, `drop` alias). — [test/underscore.html#L27-45](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/underscore.html#L27-L45) [test/underscore.html#L383-390](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/underscore.html#L383-L390)
- The suite tests lodash against a deliberately hostile environment: `addBizarroMethods` removes `Object.create`/`Symbol`/`getOwnPropertySymbols`, stubs `Promise`/`Set`/`WeakMap` with `noop`, makes `Map` return a plain object on first construction, and fakes `WinRTError` plus `global`/`exports`/`module`. — [test/index.html#L49-131](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L49-L131)
- There is an explicit minification guard test asserting no exported method name is ≤2 characters except `_`, `at`, `eq`, `gt`, `lt` — catching accidentally shipped minified production builds. — [test/test.js#L16311-16327](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L16311-L16327)
- Documentation is executable: `npm run test:doc` runs `markdown-doctest doc/*.md` with a setup file that deletes `global['__core-js_shared__']` and injects the real `_` plus `lodash-doc-globals` into every example's scope. — [package.json#L27](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L27) [.markdown-doctest-setup.js#L1-11](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.markdown-doctest-setup.js#L1-L11) [.github/workflows/ci-docs.yml#L25-29](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-docs.yml#L25-L29)
- JSCS enforces ~40 rules including `requireEarlyReturn`, `disallowKeywords: ["with"]`, `validateQuoteMarks: '`, 2-space indentation and JSDoc type/param/return checks, but with `maxErrors: 2000` so a large backlog can exist without failing. — [.jscsrc#L1-95](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.jscsrc#L1-L95) [package.json#L21-25](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L21-L25)
- Nothing under `lib/` is unit-tested — `style:fp` lints `lib/**/*.js` but no test file requires `lib/common/file.js`, `lib/common/minify.js` or any `build-*.js`; their only verification is that CI's build step doesn't throw. (inferred) — [package.json#L22](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L22) [package.json#L26-29](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L26-L29) [.github/workflows/ci-dist-sync.yml#L30-37](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-dist-sync.yml#L30-L37)
- Coverage tooling is declared (`istanbul`, `coveralls`, `codecov.io` devDependencies and a PhantomJS `__coverage__` collection path in `test.js`) but no npm script or workflow invokes it, so coverage is effectively dead infrastructure. (inferred) — [package.json#L37-40](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L37-L40) [test/test.js#L269-284](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L269-L284)
- CodeQL runs JavaScript analysis on every push/PR to `main` plus weekly on Mondays at 00:00, and OSSF Scorecard runs Tuesdays at 07:20 publishing SARIF to code scanning. — [.github/workflows/codeql.yml#L18-24](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/codeql.yml#L18-L24) [.github/workflows/scorecards.yml#L5-12](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/scorecards.yml#L5-L12)

## Risk map

The security-sensitive surface is narrow but sharp: `_.template` compiles to a real `Function`, and the path-write family (`_.set`, `_.unset`, `_.merge`, `_.defaultsDeep`) has layered prototype-pollution guards that are the subject of prior CVEs. The repo's own `threat-model.md` scopes these precisely. Structurally, the largest risk is coupling: `lodash.js` is 546 KB in one file that every contributor edits, `test/test.js` is 814 KB, and `fp/_mapping.js` is a hand-maintained table that must stay in lockstep with the method list or the FP build silently drifts. `dist/lodash.core.js` is not produced by `npm run build`, so the dist-sync workflow cannot detect a stale core build. CI also pulls two unpinned packages from the network at test time.
- `_.template` builds a source string and evaluates it with `Function(importsKeys, sourceURL + 'return ' + source)`; the project's own threat model calls it insecure, cites CVE-2021-23337, and states it will be removed in v5. — [lodash.js#L14988-14996](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L14988-L14996) [threat-model.md#L47-51](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/threat-model.md#L47-L51)
- `_.template`'s hardening is two regex checks: `sourceURL` has all whitespace collapsed to spaces so a newline can't escape the `//#` comment, and `variable` is rejected if it matches `/[()=,{}\[\]\/\s]/`. — [lodash.js#L14919-14927](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L14919-L14927) [lodash.js#L14960-14966](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L14960-L14966) [lodash.js#L181](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L181)
- `baseSet` returns the object untouched if any path segment is `__proto__`, `constructor` or `prototype` — a blanket block that also silently no-ops legitimate writes to a literal `constructor` key. — [lodash.js#L4036-4043](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4036-L4043)
- `baseUnset` uses a different, narrower rule than `baseSet`: it blocks `__proto__` only when the object lacks an own `__proto__`, and blocks `constructor`/`prototype` only in non-terminal positions — the asymmetry is deliberate but easy to break when editing one and not the other. — [lodash.js#L4382-4404](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4382-L4404)
- Prototype-pollution defense is spread across four unrelated call sites — `baseAssignValue`, `baseSet`, `baseUnset`, `safeGet` — plus `isKey`'s `value !== '__proto__'` check, so a new path-writing method added without touching all of them would be unprotected. (inferred) — [lodash.js#L2597-2610](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L2597-L2610) [lodash.js#L4036-4043](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4036-L4043) [lodash.js#L4382-4404](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4382-L4404) [lodash.js#L6704-6715](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L6704-L6715) [lodash.js#L6418-6422](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L6418-L6422)
- `dist/lodash.core.js` carries the header `Build: lodash --source ./lodash.js core -o ./dist/lodash.core.js` and is produced by the external `lodash-cli`; `npm run build` only regenerates `lodash.js`, `lodash.min.js`, `lodash.fp.js` and `mapping.fp.js`. — [dist/lodash.core.js#L1-9](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/dist/lodash.core.js#L1-L9) [lib/main/build-dist.js#L11-29](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-dist.js#L11-L29) [lib/fp/build-dist.js#L16-54](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-dist.js#L16-L54)
- Because `npm run build` never writes `dist/lodash.core*.js`, `ci-dist-sync.yml`'s `git diff --exit-code dist/` will pass even when the core build is stale relative to an edited `lodash.js` — a silent staleness hole in the sync gate. (inferred) — [.github/workflows/ci-dist-sync.yml#L30-37](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-dist-sync.yml#L30-L37) [lib/main/build-dist.js#L24-31](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-dist.js#L24-L31)
- `browser-testing.yml` installs two packages that are not in `package.json` and are unpinned: `npm install -D @playwright/test@latest` and `npx http-server`, both fetched from the registry on every CI run despite every GitHub Action being SHA-pinned. — [.github/workflows/browser-testing.yml#L22-34](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/browser-testing.yml#L22-L34) [package.json#L30-59](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L30-L59) [playwright.config.js#L1](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/playwright.config.js#L1)
- `npm run doc:fp` invokes `node lib/fp/build-doc` with no target argument, so `_.last(process.argv)` resolves to the script's own path and `fs.writeFile(target, ...)` writes the wiki output next to the build script rather than to an intended doc location. — [package.json#L17](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L17) [lib/fp/build-doc.js#L73-77](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-doc.js#L73-L77) [.github/workflows/ci-docs.yml#L27-29](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-docs.yml#L27-L29)
- The build toolchain is pinned to end-of-life tooling: `uglify-js` exactly `2.7.5`, `webpack ^1.14.0` (using the webpack-1-only `OccurenceOrderPlugin` and `DedupePlugin`), `jscs ^3.0.7` and `istanbul 0.4.5`, while Renovate is configured to never propose major npm updates. — [package.json#L52-59](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L52-L59) [lib/fp/build-dist.js#L24-28](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-dist.js#L24-L28) [renovate.json#L7-13](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/renovate.json#L7-L13)
- The test suite deletes and replaces host globals (`Object.create`, `Symbol`, `Map`, `Promise`, `Set`, `WeakMap`, `Function.prototype.toString`) and restores them by hand in a separate function — an asymmetry that leaks global state into all subsequent tests if `removeBizarroMethods` drifts from `addBizarroMethods`. — [test/index.html#L49-131](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L49-L131) [test/index.html#L133-188](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L133-L188)
- `test/test.js` intentionally defines a non-configurable throwing getter on `global.root` and calls `require('vm').runInNewContext` to build a cross-realm fixture — both are process-level side effects that make the suite hostile to running inside other harnesses. — [test/test.js#L313-320](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L313-L320) [test/test.js#L632-635](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L632-L635)
- The browser harness will load an arbitrary remote origin: `ui.isForeign` is set when the `build` query param matches `^(\w+:)?//`, and `index.html` then `document.write`s that URL as a script src — fine for a localhost runner, dangerous if the page is ever hosted publicly. — [test/asset/test-ui.js#L109-142](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/asset/test-ui.js#L109-L142) [test/index.html#L204-207](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/index.html#L204-L207)
- `test/remove.js` builds a `RegExp` from `process.argv` and does a blind `fs.writeFileSync` over a resolved path with no existence or backup check — a mistyped argument overwrites the target file. — [test/remove.js#L11-27](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/remove.js#L11-L27)
- PhantomJS is dead code: `test/test.js` and `perf/perf.js` still branch on `root.phantom`, create a `require('webpage')` page and call `phantom.exit`, but PhantomJS appears in no devDependency or workflow. — [test/test.js#L269-306](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L269-L306) [perf/perf.js#L14-36](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/perf/perf.js#L14-L36) [package.json#L30-59](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L30-L59)
- `lib/common/util.js:pitch` is the only error handling in the entire build toolchain — it rethrows synchronously from an `async.series` callback, so a failed copy or minify surfaces as an unhandled throw with no cleanup of partially written `dist/` files. (inferred) — [lib/common/util.js#L31-40](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/common/util.js#L31-L40) [lib/main/build-dist.js#L25-28](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-dist.js#L25-L28) [lib/fp/build-dist.js#L49-53](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-dist.js#L49-L53)

## Extension points

The intended way to add a method is: implement it in `lodash.js`, attach it with `lodash.<name> = <name>` in the assignment block, add tests in `test/test.js`, and — if it should exist in FP form — register it in the right `fp/_mapping.js` tables. `fp/_mapping.js` is the real registry: arity bucket, rearg order, iteratee cap, spread config, mutation class, alias and remap are all declarative. `_baseConvert`'s `wrappers` object is the escape hatch for methods whose conversion can't be expressed in tables. At runtime, `_.mixin` and `_.runInContext` are the library's public extension APIs, and `templateSettings` is the configuration hook for `_.template`.
- The method registry is the literal assignment block near the end of `runInContext` — every public method appears as `lodash.<name> = <name>`, and omitting a line there makes an implemented function unreachable. — [lodash.js#L16900-16960](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L16900-L16960) [lodash.js#L17008](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L17008)
- `mapping.aryMethod[1|2|3|4]` is the primary FP registration point: `_baseConvert` iterates exactly these buckets to decide which methods get wrapped, and `lib/fp/build-modules.js` unions them into `aryMethods` to decide `module.jst` vs `thru.jst`. — [fp/_mapping.js#L71-120](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_mapping.js#L71-L120) [fp/_baseConvert.js#L525-532](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L525-L532) [lib/fp/build-modules.js#L15-20](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-modules.js#L15-L20)
- `_baseConvert`'s `wrappers` object is a named override table — `castArray`, `iteratee`, `mixin`, `nthArg`, `rearg`, `runInContext` each get bespoke conversion logic that bypasses the table-driven pipeline. — [fp/_baseConvert.js#L199-262](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L199-L262) [fp/_baseConvert.js#L470-475](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L470-L475)
- `mapping.remap` lets an FP-only method name map onto a different real lodash method, and `build-modules.js` reads it both to emit extra module paths and, via `_.get(mapping.remap, name, name)`, to write the correct `require` inside the generated module. — [fp/_mapping.js#L281-316](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_mapping.js#L281-L316) [lib/fp/build-modules.js#L126-135](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-modules.js#L126-L135) [lib/fp/template/modules/module.jst](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/template/modules/module.jst)
- `mapping.aliasToReal` drives three things at once: alias resolution inside `wrap()`, the `alias.jst` re-export module, and the generated FP-Guide's alias listing. — [fp/_mapping.js#L1-70](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_mapping.js#L1-L70) [fp/_baseConvert.js#L469](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L469) [lib/fp/build-modules.js#L53-55](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-modules.js#L53-L55) [lib/fp/template/doc/wiki.jst#L155-160](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/template/doc/wiki.jst#L155-L160)
- Opting a method out of FP transforms is declarative: add its name to `mapping.skipFixed` (no arity cap) or `mapping.skipRearg` (no argument reordering), both read inside `castFixed`/`castRearg`. — [fp/_mapping.js#L317-357](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_mapping.js#L317-L357) [fp/_baseConvert.js#L315-341](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L315-L341)
- The `lib/fp/template/modules/*.jst` set is the emission plugin surface — `getTemplate()` dispatches to `alias`, `category`, `thru` or `module` based on `isAlias`/`isCategory`/`isThru`, so a new module shape means a new `.jst` plus a branch there. — [lib/fp/build-modules.js#L87-103](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-modules.js#L87-L103) [lib/fp/template/modules/category.jst](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/template/modules/category.jst) [lib/fp/template/modules/alias.jst](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/template/modules/alias.jst)
- `file.globTemplate` auto-discovers templates by glob and keys them by basename, so dropping a new `.jst` into `lib/fp/template/modules/` makes it available as `template.<basename>` without registering it anywhere. — [lib/common/file.js#L32-37](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/common/file.js#L32-L37) [lib/fp/build-modules.js#L12-13](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-modules.js#L12-L13)
- `_.mixin(object, source, options)` is the public plugin API: it walks `baseFunctions(source)`, assigns each onto the target, and when the target is a function also installs a chain-aware prototype method honoring `options.chain`. — [lodash.js#L15828-15862](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L15828-L15862)
- `_.runInContext(context)` is the sandboxing extension point — the `contextProps` array enumerates the 30 substitutable globals, documented with the example of swapping `setTimeout` for `setImmediate`. — [lodash.js#L297-303](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L297-L303) [lodash.js#L1440-1450](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L1440-L1450)
- `lodash.templateSettings` is the documented configuration object for `_.template`'s `escape`/`evaluate`/`interpolate` delimiters, `imports` and `variable`, read at the top of `template()`. — [lodash.js#L1764-1780](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L1764-L1780) [lodash.js#L14882-14892](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L14882-L14892)
- The doc pipeline is configured by the `config` object in `lib/main/build-doc.js`, keyed `base`/`github`/`site` and merged with `_.defaults`; adding a third output style means adding a key there and a matching npm script. — [lib/main/build-doc.js#L17-35](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-doc.js#L17-L35) [lib/main/build-doc.js#L76-81](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-doc.js#L76-L81)
- The Playwright project list in `playwright.config.js` is the browser-matrix registry — adding a browser or device is one entry in `projects`, and adding a page to test is one `test()` block in `test/playwright-runner.spec.js`. — [playwright.config.js#L14-30](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/playwright.config.js#L14-L30) [test/playwright-runner.spec.js#L5-24](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/playwright-runner.spec.js#L5-L24)
- `lib/fp/build-modules.js` hardcodes an 11-entry `categories` list and an `ignored` glob list; a new lodash category or a new private-file naming convention requires editing both arrays. — [lib/fp/build-modules.js#L22-44](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-modules.js#L22-L44)
- `test-ui.js`'s `ui.buildPath` switch is the harness registry for testable builds — the four cases (`lodash`, `lodash-dev`, `lodash-core`, `lodash-core-dev`) must be kept in sync with the `<option>` values written into the toolbar. — [test/asset/test-ui.js#L72-80](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/asset/test-ui.js#L72-L80) [test/asset/test-ui.js#L109-120](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/asset/test-ui.js#L109-L120)

## Contributor handoff

Read `.github/CONTRIBUTING.md` first — it states the core rule that for additions or bug fixes you should only modify `lodash.js` and add tests in `test/`, and explicitly says not to regenerate `dist/` or `doc/`. That rule is in tension with `ci-dist-sync.yml`, which fails the build if `npm run build` changes `dist/`, so a change to `lodash.js` does require a rebuild and commit of `dist/lodash.js` and `dist/lodash.min.js`. The style gate is JSCS with unusual rules (`requireEarlyReturn`, no `with`, single quotes, 2-space indent, JSDoc types required), so match the surrounding code exactly. Never hand-edit `dist/*`, `doc/README.md`, or anything under `vendor/` — they are generated or vendored.
- CONTRIBUTING states the contract directly: "For additions or bug fixes you should only need to modify `lodash.js`. Include updated unit tests in the `test` directory... Don't worry about regenerating the `dist/` or `doc/` files." — [.github/CONTRIBUTING.md#L21-26](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/CONTRIBUTING.md#L21-L26)
- Despite that guidance, `ci-dist-sync.yml` runs `npm run build` then `git diff --exit-code dist/` and fails with an explicit "Run 'npm run build' and commit the updated dist/ files" error — so an agent editing `lodash.js` must also commit the rebuilt `dist/lodash.js` and `dist/lodash.min.js`. — [.github/workflows/ci-dist-sync.yml#L30-37](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-dist-sync.yml#L30-L37) [.github/CONTRIBUTING.md#L24-26](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/CONTRIBUTING.md#L24-L26)
- `dist/lodash.js` is currently byte-identical to `lodash.js` (the build is a plain `fs.copy`), so any `lodash.js` edit produces a mechanically predictable dist diff. — [lib/main/build-dist.js#L11-29](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-dist.js#L11-L29) [dist/lodash.js#L1-9](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/dist/lodash.js#L1-L9)
- Files an agent must read before editing, in order: `.github/CONTRIBUTING.md`, `package.json` scripts, `lodash.js` (the target region plus the `lodash.<name> =` assignment block), `fp/_mapping.js` if the change touches FP, and `threat-model.md` if the change touches `set`/`merge`/`unset`/`template`. — [.github/CONTRIBUTING.md#L21-30](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/CONTRIBUTING.md#L21-L30) [package.json#L10-30](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L10-L30) [lodash.js#L16900-16960](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L16900-L16960) [threat-model.md#L36-51](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/threat-model.md#L36-L51)
- Tests to run locally: `npm test` (which force-builds via `pretest`), or the faster direct `node test/test` and `node test/test-fp` once a build exists; `npm run validate` is the full gate that CI uses. — [package.json#L20](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L20) [package.json#L26-30](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L26-L30) [.github/workflows/ci-node.yml#L58-59](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-node.yml#L58-L59)
- `node test/test <path>` accepts an alternate build path as its last argument, defaulting to `../lodash.js` — this is how you point the suite at `dist/lodash.min.js` or a core build. — [test/test.js#L213-234](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L213-L234)
- The Node suite requires the previous published lodash to be installed at `node_modules/lodash/lodash.js` as `lodashStable`; if it's missing the suite prints "The stable lodash dev dependency should be at least a version behind master branch" and silently returns without running. — [test/test.js#L333-345](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L333-L345) [package.json#L46](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/package.json#L46)
- Style must match JSCS: two-space indent, single quotes (double only to avoid escaping), semicolons required, `with` banned, early returns required, and JSDoc blocks needing types, param descriptions and return types. — [.jscsrc#L8-95](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.jscsrc#L8-L95) [.github/CONTRIBUTING.md#L43-70](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/CONTRIBUTING.md#L43-L70)
- Naming conventions are prescribed: prefer `index`, `array`, `iteratee` over `i`, `arr`, `fn`; JSCS additionally enforces `requireCamelCaseOrUpperCaseIdentifiers`. — [.github/CONTRIBUTING.md#L49-54](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/CONTRIBUTING.md#L49-L54) [.jscsrc#L53](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.jscsrc#L53)
- An optional pre-push hook is available by creating a root `.opt-in` file containing `pre-push`, which runs `npm run validate` on push. — [.github/CONTRIBUTING.md#L72-79](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/CONTRIBUTING.md#L72-L79)
- Safe edit boundary — do not touch: `dist/*` (generated, and `lodash.core*` only by external lodash-cli), `doc/README.md` (docdown output), `vendor/**` (Backbone/Underscore/json2/firebug-lite), `package-lock.json` unless deliberately updating deps. — [lib/main/build-dist.js#L24-29](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-dist.js#L24-L29) [lib/main/build-doc.js#L76-81](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/main/build-doc.js#L76-L81) [dist/lodash.core.js#L1-9](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/dist/lodash.core.js#L1-L9) [test/underscore.html#L383-390](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/underscore.html#L383-L390)
- Trap: adding an FP-visible method means touching `fp/_mapping.js` in up to five places (`aryMethod` bucket, plus any of `aliasToReal`, `remap`, `methodRearg`, `iterateeAry`, `mutate`, `skipFixed`, `skipRearg`) or the FP build and wiki will silently omit or mis-wrap it. (inferred) — [fp/_mapping.js#L71-120](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_mapping.js#L71-L120) [fp/_baseConvert.js#L525-532](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/fp/_baseConvert.js#L525-L532) [lib/fp/build-modules.js#L113-148](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lib/fp/build-modules.js#L113-L148)
- Trap: `test/test.js` is 814 KB and `lodash.js` is 546 KB — read targeted line ranges rather than whole files, and locate work by grepping for `QUnit.module('<methodName>'` or `function base<Name>(`. (inferred) — [test/test.js#L1-10](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/test/test.js#L1-L10) [lodash.js#L1-15](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L1-L15)
- Trap: security-relevant behavior around `__proto__`/`constructor` is asserted by the threat model as intended design — changing `baseSet`, `baseUnset` or `safeGet` guards without reading `threat-model.md` risks reintroducing CVE-2019-10744-class bugs. — [threat-model.md#L36-42](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/threat-model.md#L36-L42) [threat-model.md#L69-77](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/threat-model.md#L69-L77) [lodash.js#L4036-4043](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4036-L4043) [lodash.js#L4382-4404](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/lodash.js#L4382-L4404)
- Security issues go through the GitHub Security tab, not public issues; the TSC handles triage, and `incident_response_plan.md` encodes the full flow including a 5-day acknowledgement target and CVE request step. — [SECURITY.md#L26-36](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/SECURITY.md#L26-L36) [GOVERNANCE.md#L6-18](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/GOVERNANCE.md#L6-L18) [incident_response_plan.md#L14-52](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/incident_response_plan.md#L14-L52)
- `git log` for this checkout shows a single commit touching `dist/` at HEAD (`2b5e6f7 ci: update actions to latest versions`), so treat the dist tree as freshly regenerated relative to `lodash.js`. (inferred) — [dist/lodash.js#L1-9](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/dist/lodash.js#L1-L9) [.github/workflows/ci-dist-sync.yml#L30-37](https://github.com/lodash/lodash/blob/2b5e6f7399a7b48005140b5d5c6bc6c0e62919a8/.github/workflows/ci-dist-sync.yml#L30-L37)

Source repository: https://github.com/lodash/lodash
Full evidence corpus: https://github.com/Codevetter/codevetter/tree/main/benchmarks/repo-unpacks/lodash.json

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
