All notable changes to Learning Moments are recorded here. The format follows Keep a Changelog and this project uses Semantic Versioning.
0.5.5 - 2026-07-10
Documentation-only patch clarifying the product's user-control design.
- Elevated time, token, and estimated-cost visibility as a product principle.
- Documented the one-command pause and resume path for uninterrupted work.
- Made explicit that the learning profile, selection policy, and grading rubric can be changed by editing Markdown, with no rebuild or restart.
0.5.4 - 2026-07-10
Patch release improving project transparency, privacy hardening, static coverage, and release safety.
- Added a public code-audit record, security policy, contribution guide, citation metadata, and Dependabot coverage for npm and GitHub Actions.
- Future tag releases now create a GitHub Release after npm publication succeeds.
- Rewrote the README around the product's situated-learning rationale, with a shorter onboarding path, an honest alpha/latency statement, an architecture walkthrough, corrected storage documentation, and explicit privacy boundaries.
- Expanded
npm run checkfrom nine explicitly annotated modules to all production source and release scripts. Node type definitions now track the declared Node 20 runtime floor. - Updated Vitest and its transitive Vite/esbuild dependencies to versions with no known npm audit findings.
- Hardened the default selector and grader instructions against following instructions embedded in diffs or answers.
- Context collection no longer follows untracked symbolic links, preventing a repository symlink from pulling an arbitrary outside file into classifier context.
- Installed
UserPromptSubmithooks now declare a 60-second timeout. Previously Claude Code could terminate the containing hook at its 30-second default before the 45-second grader reached its own clean fail-open timeout. learning-moments doctornow checks data-directory permissions without recreating the retired pre-migrationmoments.jsonlfile.- Packed-release verification now requires the installed CLI version to exactly match
package.jsonrather than accepting any semver-shaped output. - Packed-release verification now uses an isolated temporary npm cache instead of inheriting user-level cache permissions or state.
- The npm publish job now runs type checking and rejects a Git tag that does not match the package version.
0.5.3 - 2026-06-18
Patch release fixing two data-integrity bugs surfaced by a full-codebase review. No on-disk format change beyond telemetry moving to its own file.
- Migration crash could permanently destroy the ledger.
logPath()(the pre-migration unified log) andtelemetryPath()(post-migration Class C) both resolved tomoments.jsonl, so the migration's final rename overwrote its own source with the telemetry-only subset. If the process was killed after that rename but before the.migration-completemarker was written, the nextinitre-read the now-telemetry-only file and overwrote the populatedledger.jsonl/control.jsonlwith empty staging — silently erasing durable events (moment_created,answer_received,grade_created, …). Telemetry now lives in its owntelemetry.jsonl, so migration never overwrites its source; the legacy log is unlinked only after the marker is durable, leaving any crash mid-migration in a clean, fully retryable state. - Missed-injection events flooded the ledger every Stop.
pendingFeedbackMomentonly treatedfeedback_observedas closing a moment, andpendingInjectedMomentonlyanswer_received/skip_recorded. The negative outcomesfeedback_injection_missedandmoment_injection_missed(bothledger-class, retained forever) never closed the moment, so once a Stop logged one, every subsequent Stop in the session re-evaluated the same pending moment and appended another row. Both negative outcomes now close the moment, capping each at one row.
0.5.2 - 2026-05-12
Patch release. Slash command descriptions are now user-facing instead of prompt-to-Claude text, and init clobbers slash command files on every run so changes to defaults land without manual cleanup.
- Slash commands now ship with YAML frontmatter
description:fields aimed at the user reading the slash-command picker, not at Claude. Previously the first line of the prompt body ("Runlearning-moments statusand summarize the result briefly.") was what showed up in the picker — confusing copy if you're not Claude. The bodies still contain instructions to Claude when the command runs. initnow overwrites slash command files unconditionally instead of skipping any file that already exists. Since the tool has no users besides the author, customization-preservation ceremony isn't earning its keep yet; clobbering on every re-init means default changes land without each project needing a manualrm .claude/commands/learning-moments/*.mdstep first. If/when there are external users we'll revisit with a marker-based or content-hash-based check.
0.5.1 - 2026-05-12
Two follow-up commits on the feedback-4 and feedback-5 audit notes. Mostly internal improvements; no on-disk format changes. The default config gains some secret-path ignores, so users who re-run learning-moments init after upgrading get a stronger first-line privacy filter (the existing pattern-based redactor is still in place for content that does reach the diff pipeline).
PostToolBatchno longer holds themoment-claimlock across the classifier call. Phase 1 acquires the lock briefly to read control events, check dedupe by fingerprint, and writeclassifier_called(the claim row). The lock is released beforeclassifyCandidateruns. Phase 2 reacquires the lock aroundmoment_created+ budget read +moment_injected/moment_silencedso the inject/silence decision is still atomic against other in-flight injections. The previous single-lock structure could make a concurrent hook fail-open just because one Claude model call was in flight (default 45s classifier timeout vs. 5s lock acquisition timeout); the slow path now serializes only on the fingerprint dedupe, not on the model call.gitHashObjectsno longer sends files over 1 MB togit hash-object. Large files now use ameta:<size>:<mtimeMs>fingerprint instead of a git blob SHA-1, whichchangedSinceBaselinereads as a normal string comparison.git hash-objectwould otherwise read the whole file even though we never look at its contents — a stray large untracked file that escapedconfig.ignorewould pay the full read cost on every hook fire.PostToolBatchgates the classifier call on the immediate-prompt budget in active mode. With the default 1-question-per-hour budget, the classifier was previously running on every change event and getting silenced shortly afterward; an exhausted prompt budget now short-circuits the hook withimmediate_prompt_budget_exhaustedbefore the ~20s model call. The phase-2 lock still does the authoritative budget check (state can drift between the upstream read and the lock); this is an advisory fast-path. Observe-only mode is deliberately excluded so the ledger keeps capturingmoment_createdrecords the user can review.defaultConfig.ignorenow defaults-out secret-bearing paths and extensions before they can reachcontextForFilesor the redactor:.env,.env.*,.npmrc,.pypirc,.netrc,.aws/**,.ssh/**,.config/gcloud/**, and extensions.pem,.key,.p12,.pfx. Pattern-based redaction is still in place for content that does reach the diff pipeline, but the ignore filter is a stronger first line — a file that's filtered out never gets read at all..env.exampleis also caught by the.env.*glob; we accept that false positive on purpose since the safety win is worth more than classifying example env files.
0.5.0 - 2026-05-12
Substantial internal restructuring. Two themes:
- Log split (#13): the single growing
moments.jsonlis replaced by three retention classes —ledger.jsonl(durable learning history, kept forever),control.jsonl(hot-path state, retained 1h–24h), andmoments.jsonl(telemetry, truncatable at any time). Every event the tool writes is classified at write time; unknown types now throw rather than silently routing. Hot hooks read only the file they need instead of merging everything. A one-time migration runs on firstinitafter upgrading and is a no-op on subsequent runs. - Git-native working-tree snapshots (#8): the eager
snapshot()that hashed every dirty and untracked file with SHA-256 in Node is gone. Path discovery still runs eagerly (onegit status, one ignore filter), but hashing is deferred until a baseline is materialized and is delegated to a single batchedgit hash-object --stdin-pathscall. Git's own blob cache handles clean tracked files; we never open them.
All log-file mutators now share one project-level write lock, so prune/truncate/migrate can't race with appendEvent. dirtyFiles was fixed to use the new path for renames (the previous code recorded the old path) and to pass --untracked-files=all (without it, a brand-new directory collapsed to a single dir/ entry).
No user-visible breaking changes — the on-disk JSONL format is preserved, the marker-gated migration keeps existing repos working until init runs, and the slash commands are unchanged.
src/core/event-registry.jsenumerates every event type the tool writes and assigns each one a retention class (ledger,control, ortelemetry).appendEventnow throws on an unknown type rather than silently writing it, so a new event introduced without a registry decision fails loudly at write time. A test scanssrc/fortype: "..."literals and asserts every one appears in the registry, keeping the table from drifting out of sync. Foundation for the upcoming log split (#13); no on-disk format changes yet.- Path helpers
ledgerPath,controlPath,telemetryPath, andmigrationCompletePathfor the three-class split.appendEventnow routes by retention class — but only after the.migration-completemarker is written. Until then (every repo currently in the wild), writes continue to go to the unifiedmoments.jsonl, so this commit is a no-op for existing users. The marker is written by the migration step (next phase).readEventsmerges the three class files post-migration; pre-migration it still reads the unified file. - One-time log migration triggered by
learning-moments init. Reads the unifiedmoments.jsonl, classifies each row via the event registry, and writesledger.jsonl/control.jsonlplus a telemetry-onlymoments.jsonlvia a staging-and-rename sequence so a crashed run leaves no partial split. Writes.migration-completelast, which is the boolean every reader and writer checks. Idempotent — runninginitagain after migration is a no-op. The migration also cleans up.stagingdebris from a prior aborted run.
- Hot-path readers switched from the merged
readEventsto per-class helpers (readLedger,readControl,readTelemetry).PostToolBatchnow reads only the control file for budget, dedupe, and session-baseline lookup, and only the ledger for the immediate-prompt budget check;UserPromptSubmitandStopread only the ledger. Pre-migration the helpers fall back to filtering the unified log so behavior is unchanged; post-migration each hook touches one small file instead of the merged view.readEventsremains forstatusandmetrics, which need the merged set. learning-moments delete-dataaccepts--logs-only, which truncatesmoments.jsonl(telemetry) while leavingledger.jsonlandcontrol.jsonlintact. Refuses to run before migration since pre-migrationmoments.jsonlis the unified log and would take ledger rows with it. Cost reporting inmetricsis unaffected becauseclassifier_completed/grader_completedcarry the cost numbers and live in the ledger.SessionStartnow compactscontrol.jsonlon every fire. Classifier-budget rows (classifier_called,candidate_already_seen) are dropped after the trailing 1h window the budget and dedupe checks use;session_baseline_createdrows are kept for 24h so hot hooks can still find the latest baseline for a long-running session. Best-effort: any failure here is swallowed so it can't block a session from starting.- Working-tree construction shifted from an eager
snapshot()to a lazyworkspaceContext(cwd, config). Path discovery + filtering still run up front (onegit status+ ignore filter, both cheap), but file hashing is deferred untiltoBaseline()is called and is delegated to a single batchedgit hash-object --stdin-pathsinstead of N file reads + N SHA-256s in Node. Git's own blob cache handles clean tracked files; we never open them. The persisted session-baseline payload changes shape:dirtyFiles→candidates, and the hashes are git blob SHA-1s instead of in-process SHA-256s. Closes #8. - All log-file mutations now share the
moments-jsonllock. PreviouslyappendEventwas the only writer that took the lock;pruneControlLog(SessionStart) andtruncateTelemetry(delete-data --logs-only) rewrote their files without locking, so a hook could append tocontrol.jsonlmid-prune (or tomoments.jsonlmid-truncate) and have the appended row silently dropped by the subsequent rewrite. Migration used to take a separatelog-migrationlock for the same reason — a hook could append to the unified file during the partition phase and lose the row at the final rename. The split-lock setup is now gone:appendEvent, control pruning, telemetry truncation, and migration all serialize against each other through one project-level write lock. session_baseline_createdreclassified fromledger(retained forever) tocontrol(retained 24h). The ledger is for durable learning history (created moments, answers, grades); baselines are operational session state and shouldn't accumulate forever in a file that hot hooks read on every PostToolBatch.PostToolBatchnow finds the latest baseline by readingcontrol.jsonlinstead of the ledger.dirtyFilesnow uses the new path forgit statusrename/copy entries instead of the old path. The previous logic addedstatus[index + 1](the old path) to the candidate set; for a renameR <new>\0<old>\0in-zformat we want the post-rename location, which is the path embedded in the original status entry. Renamed files were effectively being looked up at their pre-rename path, which no longer exists on disk.dirtyFilespasses--untracked-files=all. Without it, a brand-new directory collapses to a singledir/entry ingit statusoutput, so files created inside a newly-created folder never reached the classifier as candidates.
learning-moments statusno longer countsrecall_scheduledevents (a recall feature that was never built — the event type isn't even in the registry, so the count was always 0). The status output now readslastError.error_messageinstead of the nonexistentlastError.reason ?? lastError.message;hook_errorevents have always written the message undererror_message, so the prior code printed an empty string after the timestamp.
- Bumped the vitest
testTimeoutfrom the 5s default to 15s. Hook-flow tests run real git operations against per-test temp directories and were occasionally tipping over the 5s timeout under heavy parallel I/O when all four test files ran at once. Single-worker and isolated runs were comfortably under a second per test; the timeout bump is purely for the parallel case.
0.4.0 - 2026-05-12
Breaking minor release. Tightens the config validation surface (a 0.3.0 changelog claim that wasn't backed by code is now true) and stops snapshot() from doing repository-wide file reads on hot hook paths. Pairs with the safety nets in 0.3.2: any hook failure produced by stricter validation will now fail-open with a logged hook_error event.
parseConfignow rejects unknown top-level and nested keys. The 0.3.0 changelog claimed this behavior already existed — it didn't. The parser was permissive and silently dropped removed fields. It now throwsconfig.X: unknown key "..."on the first offending key, naming every extra key in one pass.learning-moments initdetects an existing.learning-moments/config.jsonthat fails strict parse, moves it aside toconfig.json.bak, and writes the default. Customizations must be merged back by hand from the backup. This is deliberately not an auto-migrator: the visible audit trail of what changed fits the project's posture better than silently stripping fields. A re-run ofinitagainst a valid config is unchanged (no.bakis created).
Migration path for users upgrading from 0.3.x or earlier with a customized config:
npm install -g learning-moments@latest
cd /path/to/your/project
learning-moments init
# Init will report which fields failed validation and back up the old config.
# Merge any customizations from .learning-moments/config.json.bak by hand,
# omitting the fields it complained about (typically `confidence`,
# `generated_markers`, `max_file_excerpt_chars`, `max_transcript_excerpt_chars`,
# `no_hooks_settings_file` — all removed in 0.3.0).snapshot()filters dirty files throughcandidateFiles(...)before opening any of them for hashing. Without this,node_modules,dist/,coverage/, lockfiles, and any other configured-ignored path was hashed on every working-tree snapshot before later being filtered out. The function now accepts an optionalconfigparameter; bothPostToolBatchandSessionStartpass it. Callers that omitconfigget the old behavior (used only in tests).fileHashskips files larger than 1 MB and files whose first 8 KB contains a NUL byte. The NUL-byte probe is the same heuristic lefthook uses to short-circuit binary content before decoding. Skipped files returnnull, which compares equal to the existing missing-file return — they simply don't contribute tochangedSinceBaseline.contextForFilesapplies the same size + binary guards to untracked files before reading them as UTF-8. This is a privacy improvement in addition to a perf one: a binary or oversize untracked file matching no ignore pattern would previously be opened and shipped toclaude -pas text; it now isn't.
0.3.2 - 2026-05-12
Reliability and audit-story tightening across six small fixes. No breaking changes; safe drop-in upgrade from 0.3.1. Lands before the strict-parser change planned for 0.4.0 so users get the fail-open improvements in place first.
- Hook event dispatch now runs inside the fail-open boundary. Previously the unknown-event check in
src/cli.jsthrew beforerunHookwas invoked, so a stale installed hook entry (event renamed in a later version) or a typo would exit non-zero and interrupt the user's Claude Code session. The lookup now happens inside the wrapped action; unknown events produce a loggedhook_errorand a clean exit 0. SessionStartdefers its working-tree snapshot past the disabled/paused checks, mirroring the same reorder already in place forPostToolBatch. Paused or disabled projects no longer pay the cost of hashing every dirty and untracked file on every session start.- The no-hooks settings file used to isolate nested
claude -pcalls is now validated by contents, not just existence. A corrupted or hand-edited file withdisableAllHooks: false(or no field at all) silently re-enabled hooks in nested calls; the inline-fallback path now covers that case alongside missing-file. hook_errorevents no longer include stack traces by default. SettingLEARNING_MOMENTS_DEBUG=1re-enables stack capture in the log and surfaces a one-line failure message on stderr for immediate visibility. Default behavior keeps the persisted record narrower and less likely to embed local paths.
.github/workflows/ci.ymlruns type-check, tests, and audit on Node 20 (the declaredenginesfloor) for every push and pull request tomain. The publish workflow continues to use Node 24 (Trusted Publishing needs npm 11+), so this is the only surface that exercises the minimum supported runtime.
package-lock.jsonversion metadata refreshed to match the currentpackage.jsonversion. The lockfile still said0.3.0after the previous patch bump; no transitive dep changes, only the two"version"fields.
0.3.1 - 2026-05-12
scripts/release-verify.js: packs the project, installs the tarball into a throwaway consumer project, and runslearning-moments auditagainst the installed copy. Catches packaging-shaped bugs that the in-repo audit cannot see — a file referenced by code but missing frompackage.jsonfiles, executable bits stripped during pack, manifest drift. Wired into thereleasenpm script and into the publish workflow (afternpm pack --dry-run, beforenpm publish), so a release tag that would produce a broken tarball now fails CI before the tarball reaches the registry.- Type checking via JSDoc +
tsc --noEmit. The CLI ships as plain JavaScript exactly as before — no compile step, nodist/— but the source files insrc/core/now carry JSDoc annotations on exports and shared data shapes (Config,EventRecord, the hook input types). A newnpm run checkruns the TypeScript compiler in no-emit mode against the JS sources;npm run releaseruns it as part of the pre-publish gate. The config-validation regression fixed in 8a45560 is exactly the bug class this catches at edit time. Initial coverage:validate.js,config.js,log.js,hook-input.js,redaction.js,paths.js,hook-runner.js. Remaining modules can be annotated incrementally; files without// @ts-checkare still skipped.
0.3.0 - 2026-05-12
This release tightens the trust story (package.json now integrity-checked, user answers now redacted before logging or grading), closes several reliability and privacy gaps surfaced by a deep audit (silent hook failures, stale locks, snapshot work on paused projects), and removes aspirational config that no runtime code read.
Breaking: parseConfig will reject configurations that still contain the removed fields. If you have a customized .learning-moments/config.json, re-run learning-moments init or delete the removed keys by hand. See the Removed section.
MANIFEST.jsonandlearning-moments auditnow hashpackage.jsonalongside the other shipped files.package.jsonships with every npm tarball regardless of thefilesarray and controls what gets shipped (files) and what gets installed (bin), so leaving it out of integrity verification meant the most tamper-sensitive metadata was the only thing not protected.- Hook failures now leave a trail. The hook runner previously caught every error and exited 0 silently — correct fail-open behavior, but undebuggable. It now best-effort writes a
hook_errorevent to the log before exiting 0, with the originating hook name, the error message, and the duration. Logging failures themselves are still swallowed so the fail-open contract holds. - Project locks recover from stale holders. The lock implementation in
src/core/lock.jsnow writes the holder's PID, acquisition timestamp, and lock name into aholder.jsoninside the lock directory. On contention, the contender checks the holder's liveness (process.kill(pid, 0)) and the age of the lock; if the PID is no longer running or the lock is older than five minutes, the contender reclaims the lock and retries. Previously a crashed hook left a permanent lock directory that wedged the project until manual cleanup. PostToolBatchnow short-circuits on disabled or paused projects before taking a working-tree snapshot. The previous order — snapshot, then check config — meant paused projects still paid the cost of hashing every dirty and untracked file on every batch event. The classifier-budget check also runs before the snapshot now, so budget-exhausted invocations are cheap too.- User-typed answers are now pattern-redacted before they are logged or sent to the grader. Previously redaction applied only to code diffs; raw answer text was written to
moments.jsonland shipped toclaude -pas-is. A user who pasted a credential into an answer would surface it in both places. The sameredactSecretspatterns already used for diffs now run once on the answer; the redacted text is what travels onward, and theredaction_findingsarray is logged for audit. Skip detection happens against the redacted text; the redaction patterns and the skip keywords don't overlap, so the leading keyword is preserved. README's Privacy section updated to describe the broader coverage. - The classifier's
timingfield is now honored. Previously the injection gate ignored it: a model that returnedtiming: "ask_later"withdelivery: "active"would still interrupt the user immediately, contradicting its own signal. Injection now requirestiming === "ask_now"; otherwise the moment is silenced withreason: "ask_later"(distinct fromobserve_onlyandbudget_or_delivery).
- Unused configuration fields removed from
config.json:context_limits.max_file_excerpt_chars,context_limits.max_transcript_excerpt_chars,ignore.generated_markers, and theconfidenceobject. All four were validated byparseConfigand persisted to disk but never read by any runtime code — editing them inconfig.jsonhad no effect. They are deleted fromdefaultConfigandparseConfig; existing user configs that still contain them will fail validation. If you have a customizedconfig.json, re-runlearning-moments initor remove these keys by hand. When the features behind these knobs land (excerpt limits, generated-file detection, confidence prompts), the knobs come back alongside their implementation. config.claude.no_hooks_settings_fileremoved. The path was technically configurable but the file is always written byinitat.learning-moments/claude-no-hooks-settings.json; nothing else has a legitimate reason to change it.src/core/claude.jsnow resolves the path directly vianoHooksSettingsPath(projectRoot), the same helperinitanddoctoralready used.
0.2.3 - 2026-05-12
0.2.2's publish workflow failed at the npm upgrade step: npm install -g npm@latest running under Node 22's bundled npm 10.9 corrupted itself mid-upgrade (MODULE_NOT_FOUND: promise-retry after the new files partially replaced the old ones — a known npm self-upgrade race).
- Publish workflow bumped to Node 24, which ships with npm 11+ by default. This satisfies Trusted Publishing's npm-version requirement without an in-place npm self-upgrade. A diagnostic
npm --versionstep prints the resolved version into the workflow log for future debugging.
0.2.2 - 2026-05-12
0.2.2 is the first version of the 0.2.x line actually present on npm. 0.2.1 was tagged but never published — the publish workflow ran successfully through tests and provenance signing, but npm publish returned 404 because npm 10 (bundled with Node 20) does not implement Trusted Publishing's OIDC-for-publish-auth path. It silently fell back to token-based publish, found only setup-node's placeholder token, and the registry rejected it.
- Publish workflow now uses Node 22 and explicitly upgrades npm to the latest version before publishing. This guarantees Trusted Publishing support regardless of which point release of Node ships which npm. Also removes the deprecation warning about Node 20 actions.
0.2.1 - 2026-05-12
0.2.1 is the first published version of the 0.2.x line. 0.2.0 was tagged but never reached npm — the publish workflow failed at the test step before npm publish ran. The fix below is the only behavioral difference from the intended 0.2.0; see the 0.2.0 entry for what changed versus 0.1.x.
- CI publish workflow now passes on a fresh runner. Test fixtures that initialize temporary git repositories previously relied on a global
~/.gitconfigforuser.name/user.email; they now configure identity locally on each temp repo so the suite runs cleanly in any environment.
0.2.0 - 2026-05-12
This release rebuilds Learning Moments around a source-executed, zero-dependency CLI so the shipped code can be read end-to-end. It also closes several integrity and privacy gaps found by an adversarial review of the rewrite.
learning-moments auditcommand. Prints install mode, hook entry paths, runtime dependencies, npm lifecycle scripts, and SHA-256 hashes of every shipped file, verified bidirectionally againstMANIFEST.json(missing, mismatched, or unexpected files all fail the audit).--jsonfor machine-readable output.MANIFEST.jsonships with every release as the integrity baseline. Regenerated vianpm run build-manifest.- npm Trusted Publishing with provenance attestations. Each published tarball is cryptographically linked to the GitHub Actions build of a specific commit in this repository; verify with
npm audit signatures. learning-moments initand other commands run from a git checkout (node src/cli.js init) for users who prefer not to install from npm.
- Source-executed CLI. No more
dist/, no build step, no bundler. Thebinfield inpackage.jsonpoints directly atsrc/cli.js, and Claude Code hooks installed byinitinvoke that same file by absolute path. - Plain ESM JavaScript runtime. TypeScript,
commander, andzodremoved; zero runtime dependencies, onlyvitestas devDep. - Audit verification is bidirectional and fail-closed. Missing or unparseable
MANIFEST.jsonexits non-zero rather than silently skipping the check. - Config validation restores numeric bounds: frequency and context-limit fields must be non-negative; Claude call timeouts must be at least one second.
- Glob patterns in
ignore.pathsare validated at config load. Unsupported minimatch features (brace expansion, character classes, extglobs,?single-char wildcard) are rejected with a clear error rather than silently mismatching against paths the user expected excluded.
pause --session <id>now actually pauses the session. Previouslyconfig.paused.sessions[id]was written but never read; classifier calls and prompt injection continued in paused sessions.UserPromptSubmithook now honors both project-scope and session-scope pause. Previously it had no pause check at all, so a paused session would still consume a pending answer through the grader.
- End-to-end inspectability story:
npm audit signaturesverifies the tarball came from this repository's CI;learning-moments auditverifies the files on disk match the manifest that travelled with it. Together they cover public source → CI build → registry → installed files. - No npm install-time scripts (
preinstall,postinstall,prepare, etc.).auditactively reports any that appear.