Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,17 @@ Every feature has been hardened with a comprehensive test suite - over **5,300 t

## ✨ What's New

### Latest: v3.13.0 (August 2026)
### Latest: v3.13.1 (August 2026)

- **Sync/async-transparent hook dispatch (#264)** β€” an async `before`/`after` handler now composes on any target: the call promotes per-invocation to an async pipeline instead of refusing (before) or leaking a pending Promise (after), and a promoted synchronous return is a guarded Promise so an unawaited consumer fails loudly with `HOOK_PROMOTED_RESULT_NOT_AWAITED` instead of yielding `NaN` far from the cause. Removing the async hook restores the original synchronous contract.
- **`api.slothlet.api.leaves()` (#266)** β€” enumerate the api paths a module owns, read from the loader's ownership records rather than by walking the live api object: complete under lazy at any depth, scoped to the caller (module-private members redacted; host-only `{ includePrivate: true }`), with `{ details: true }` tagging each path `function` / `namespace` / `data`.
- **Module-private exports + injectable importer (#269, #267)** β€” with permissions enabled, `_`/`__`-prefixed exports are enforced as private to their own module (host default-deny, opt-out `permissions.private.host: "allow"`) and reserved-named files/exports are refused at load; separately, `slothlet({ import })` routes leaf loads through a consumer's test runner so leaf execution attributes correctly in coverage.
- [View full v3.13.0 Changelog](./docs/changelog/v3/v3.13.0.md)
- **Dev-environment detection fix (#270)** β€” `./devcheck` now reads the CLI form `node --conditions=slothlet-dev` from `process.execArgv` (the same channel vitest uses to pass conditions to its workers), and treats a package installed under any `node_modules` path segment as installed. A correctly-configured dev run against `src/` no longer aborts with a false `process.exit(1)`, and a git or tarball install β€” which ships `src/` without a built `dist/` β€” no longer self-terminates inside a consuming project.
- [View full v3.13.1 Changelog](./docs/changelog/v3/v3.13.1.md)

### Recent Releases

- **v3.13.0** (August 2026) β€” Sync/async-transparent hook dispatch, `api.slothlet.api.leaves()` for module-scoped path enumeration, and permission-enforced module-private (`_`/`__`) exports plus an injectable importer that attributes leaf execution in consumer coverage ([Changelog](./docs/changelog/v3/v3.13.0.md))
- **v3.12.3** (August 2026) β€” Composition & attribution correctness: every read/call attributed to the responsible module (identity survives `await`, per-flow concurrency, redacted enumeration), `apiPath` matches the composed surface, faithful lazy resolution (thenable wrappers, deep chains, file+dir collisions), and collisions follow the documented `api.collision` table ([Changelog](./docs/changelog/v3/v3.12.3.md))
- **v3.12.2** (July 2026) β€” Type generation types both JS and TS leaves faithfully: generated `.d.ts` carries JSDoc `@param`/`@returns` types instead of `any` and compiles for TS leaves referencing local named types; plus a consumer-coverage testing guide ([Changelog](./docs/changelog/v3/v3.12.2.md))
- **v3.12.1** (July 2026) β€” Security patch: nested values of `scope({ protect, owners })` context keys are now guarded to depth β€” `context.auth.userId = …` throws `CONTEXT_KEY_PROTECTED` with the full path β€” plus the `./devcheck` export now ships the file the npm whitelist never included ([Changelog](./docs/changelog/v3/v3.12.1.md))
- **v3.12.0** (July 2026) β€” Security-and-observability: permission enforcement fails closed on an absent/forged caller (opt-out `permissions.failOpenOnAbsentCaller`), inter-module construction + class-instance methods are permission-checked, the engine-internal `handlers/`/`factories/` subpaths leave `exports`, an opt-in control-surface `seal()`, owner-locked/write-protected context keys via `scope({ protect, owners })`, `impl:warning`/`impl:error` diagnostic lifecycle events, and nested `shutdown`/`destroy` leaves no longer dropped ([Changelog](./docs/changelog/v3/v3.12.0.md))

πŸ“š **For complete version history and detailed release notes, see [docs/changelog/](./docs/changelog/) folder.**

Expand Down
18 changes: 11 additions & 7 deletions devcheck.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,19 @@ const isCI = !!(

if (existsSync(srcPath) && !existsSync(distPath) && !isCI) {
const nodeEnv = process.env.NODE_ENV?.toLowerCase();
const nodeOptions = process.env.NODE_OPTIONS || "";
// Dev resolver conditions arrive either via NODE_OPTIONS (env) or on the CLI, where Node puts
// them in process.execArgv β€” which is also how vitest passes conditions to its workers. Fold
// both into one haystack so the CLI/worker form is detected, not just the env form. (#270)
const conditionFlags = (process.env.NODE_OPTIONS || "") + " " + process.execArgv.join(" ");

// Check if running from node_modules (parent folder is node_modules)
const parentFolder = path.basename(path.dirname(__dirname));
const isInstalledPackage = parentFolder === "node_modules";
// Detect an installed copy by a `node_modules` segment anywhere above this file. Matching only
// basename(dirname(__dirname)) misses a scoped install β€” node_modules/@cldmv/slothlet, whose
// parent is the `@cldmv` scope dir, not `node_modules`. (#270)
const isInstalledPackage = __dirname.split(path.sep).includes("node_modules");

// Parse conditions from NODE_OPTIONS
const hasSlothletDev = nodeOptions.indexOf("--conditions=slothlet-dev") !== -1;
const hasGenericDev = nodeOptions.indexOf("--conditions=development") !== -1;
// Parse conditions from NODE_OPTIONS / execArgv
const hasSlothletDev = conditionFlags.includes("slothlet-dev");
const hasGenericDev = conditionFlags.includes("--conditions=development") || conditionFlags.includes("--conditions development");
const hasDevEnv = nodeEnv === "dev" || nodeEnv === "development";

// Only check if we're in the slothlet repo (not installed in node_modules)
Expand Down
63 changes: 63 additions & 0 deletions docs/changelog/v3/v3.13.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Slothlet v3.13.1 Changelog

**Release Date**: August 2026
**Release Type**: Patch
**Branch**: `release/3.13.1`

---

## Overview

Version 3.13.1 is a maintenance patch centered on `devcheck.mjs`, the import-time development-environment tripwire that calls `process.exit(1)` when it believes a source checkout is being run without the `slothlet-dev` resolver condition. Two ways the guard misjudged its environment are fixed (#270, merged via PR #281): it read the active resolver conditions only from `process.env.NODE_OPTIONS` and missed the command-line form Node records in `process.execArgv` β€” the same channel vitest uses to hand conditions to its workers β€” so a correctly configured developer could hit a false `exit(1)`; and its installed-package guard inspected only the immediate grandparent directory name, which is the scope directory for slothlet's own scoped install, so a git or tarball install (which ships `src/` but no `dist/`) could trip the guard inside a consumer. A spawn-based regression suite accompanies the fix.

The release also rolls up four routine devDependency bumps and regenerates the committed declaration tree so it matches the source shipped in v3.13.0. No source module changed in this release, and there are no breaking changes.

---

## πŸ› Bug Fixes

### `devcheck.mjs` misjudged its environment two ways (#270)

`devcheck.mjs` runs at import time and only acts inside a narrow window β€” `src/` present, `dist/` absent, and not running under CI β€” where its job is to catch a slothlet source checkout being run without the dev resolver condition set. Two independent misjudgements inside that window are corrected.

**Dev-condition detection now reads `execArgv`, not just `NODE_OPTIONS`.** The guard decided whether the `slothlet-dev` condition was opted in by scanning `process.env.NODE_OPTIONS` alone. Node records a condition passed on the command line β€” `node --conditions=slothlet-dev file.mjs` β€” in `process.execArgv`, not in `NODE_OPTIONS`, and that CLI form is exactly how vitest forwards conditions to its worker processes. A developer using the CLI or worker form was therefore read as unconfigured, and the guard fired a spurious `process.exit(1)` that killed the run. Detection now folds `process.execArgv` into the same haystack as `NODE_OPTIONS`, so the env form and the CLI/worker form are both recognized.

**The installed-package guard now detects a `node_modules` segment at any depth.** The check that suppresses the tripwire for a copy installed under `node_modules` β€” where it must never act β€” tested `basename(dirname(__dirname)) === "node_modules"`. For slothlet's own scoped install the file sits at `node_modules/@cldmv/slothlet/devcheck.mjs`, whose grandparent is the `@cldmv` scope directory rather than `node_modules`, so the guard never recognized the install and never tripped for the common scoped case (harmless there, since an npm install ships `dist/` and the outer window stays closed). The real hazard is a git or tarball install: it ships `src/` with no built `dist/`, so the outer window is open, and a scoped layout defeated the `node_modules` check β€” a consumer could take a spurious `exit(1)` at import time. Detection now tests for a `node_modules` path segment anywhere above the file, so scoped and unscoped installs are recognized regardless of nesting depth.

`devcheck.mjs` lives at the package root and is outside the coverage `include` (`src/**`), so this fix and its tests carry no coverage-gate impact.

---

## πŸ§ͺ Tests

### `devcheck` guard regression suite (#270)

A new spawn-based suite (`tests/vitests/suites/devcheck/devcheck.test.vitest.mjs`, 6 tests) launches `devcheck.mjs` as a child process under controlled `execArgv` / `NODE_OPTIONS` / working-directory conditions and asserts on its exit code and emitted message: the baseline `exit(1)` when no dev condition is set at all; non-firing when `--conditions=slothlet-dev` arrives via `execArgv` (the CLI/worker form) and the preserved `NODE_OPTIONS` path; the generic-dev redirect message for `--conditions=development`; and non-firing for both a scoped (`node_modules/@cldmv/slothlet`) and an unscoped (`node_modules/slothlet`) install layout. Because `devcheck.mjs` sits outside the coverage `include`, the suite has no effect on the coverage gate.

---

## πŸ“š Documentation

- **NEW:** [docs/changelog/v3/v3.13.1.md](./v3.13.1.md) β€” this changelog.
- README β€” refreshed **What's New**.

---

## πŸ”§ Tooling

- **Declaration tree regenerated to match shipped source.** No source module changed in this release; the committed `.d.mts` files under `types/` are regenerated so they match the source v3.13.0 actually shipped, whose declarations had been generated from earlier source. The visible deltas β€” `SlothletError`'s `originalError` parameter widened from `Error` to `unknown` and a private `#describeThrown` rendering helper, plus declaration updates for the hook / api / permission / version managers and `slothlet.d.mts` β€” are that regeneration, not new behavior in 3.13.1.

### Dependency updates

- `esbuild` 0.28.1 β†’ 0.28.2 (#276)
- `globals` 17.8.0 β†’ 17.9.0 (#277)
- `@types/node` 26.1.2 β†’ 26.2.0 (#278)
- `eslint` 10.8.0 β†’ 10.8.1 (#279)

All four are **devDependencies**. The Dependabot bumps updated only the lockfile within the existing caret ranges, so the declared `devDependencies` floors are unchanged and `npm ci` installs the newly resolved (tested) versions.

---

## Upgrade notes

- **No breaking changes.** `devcheck.mjs` only ever acts in a slothlet source checkout β€” its guard stays inert once `dist/` is present or the copy is installed under `node_modules` β€” so this fix removes false positives and requires no consumer action.
Loading
Loading