Skip to content

feat: Sentiness v2 global spine (Phase V1)#17

Merged
yan-vidal merged 14 commits into
mainfrom
feat/sentiness-v2-global-spine
Jun 16, 2026
Merged

feat: Sentiness v2 global spine (Phase V1)#17
yan-vidal merged 14 commits into
mainfrom
feat/sentiness-v2-global-spine

Conversation

@yan-vidal

Copy link
Copy Markdown
Contributor

Summary

Implements Phase V1 (global spine) of the Sentiness v2 design — the engine now runs against a project with zero node_modules, using version-pinned checks fetched into a global cache, with determinism guaranteed by a committed sentiness.lock.

  • Config schema v2 (feat(core)!): catalog + zones + engine pin; cross-field validation (exactly one of version/path), zone/trigger checks; v1 configs rejected with a migration hint.
  • Cache + artifact store: cache-slot path computation and npm install of a pinned artifact into a slot (marker + best-effort integrity), with directory-aware in-memory FS rename.
  • sentiness.lock: Zod schema + LockManager (load/save sorted, satisfies with a dependency-free caret/tilde/exact range check).
  • Registry from cache (feat(core)!): CheckRegistry.fromResolved imports each check from its cache slot (versioned) or a local package (path-linked) instead of the project's node_modules; callers go through a buildRegistry helper.
  • Checks bundle their npm tool: biome/eslint/knip/jscpd declare their tool as an exact dependency; CheckContext/ExecFileOptions gain binPaths; NodeProcessRunner prepends slot .bin dirs to PATH.
  • sentiness install + --cache-root: resolves ranges → exact, writes the lock, warms the cache (--frozen requires a satisfying lock); the engine entrypoint threads --cache-root; the global bin is removed from @sentiness/core.
  • New @sentiness/cli launcher: owns the global sentiness bin with no dependency on @sentiness/core; fetches the pinned engine into ~/.sentiness/cache (or SENTINESS_HOME) and spawns it, forwarding args + exit code; SENTINESS_ENGINE_PATH bypasses the fetch for local dev.
  • Dogfood V1: this repo's sentiness.config.json is now v2 (path-linked biome/knip/deps-diff) with a generated sentiness.lock.
  • Bug fix found while dogfooding: path-linked checks now resolve their entry via the package's own exports/main (require.resolve(<dir>) ignores exports).

Test Plan

  • pnpm typecheck — all packages clean
  • pnpm lint — clean
  • pnpm build — all packages
  • pnpm test — green except 4 pre-existing init.test.ts failures (v1-init config gap, documented)
  • pnpm check:release-packages — 15 public packages (incl. new @sentiness/cli)
  • Manual dogfood: install writes lock; install --frozen exits 0 (satisfying) / 3 (bad lock); launcher-driven check --tier=fast runs the local engine and forwards exit code

Known out-of-scope debt (pre-existing v2-migration, not V1)

  • sentiness init still emits a v1 config → 4 red init.test.ts cases.
  • pnpm test:e2e red: examples/demo-project config is still v1 / node_modules-based.

Both predate this branch (config v2 landed earlier) and belong to a later phase (full E2E = TV4.3, V4). See docs/progress.md.

🤖 Generated with Claude Code

yan-vidal and others added 14 commits June 15, 2026 14:50
Implementable spec (CLAUDE.md task template) for the v2 evolution: a thin
global launcher (@sentiness/cli) + version-pinned engine in a global cache,
zero node_modules in target projects, sentiness.lock for determinism, zoned
config for polyglot monorepos, override->host->fetch(npm) tool resolution,
and one cross-language proof (check-clippy, detect-only). Drops the v1
node_modules model (no dual-mode). Supersedes docs/multi-language-spec.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bite-sized TDD plan for spec tasks TV1.1-TV1.7: config v2, cache + artifact
store, sentiness.lock, registry-from-cache, npm-tool bundling, install command,
thin launcher, plus a dogfood smoke task. V2/V3/V4 get their own plans.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace v1 config fixtures (schemaVersion 1.0, enabled: true) with valid
v2 fixtures across reporter, runner, baseline, check, install-skill,
pending, registry, doctor, and init-config test files. Export a
DEFAULT_CONFIG constant from config.ts (minimal v2 ResolvedConfig with
empty checks) so that programmatic test fixtures can spread it instead
of constructing the full shape inline.

Remaining failures deferred to module-logic tasks:
- doctor.test.ts (3): registry.ts still gates on checkConfig.enabled (v1)
- init-config.test.ts (5): same registry.ts enabled-gate
- init.test.ts (4): init.ts writes schemaVersion 1.0 / enabled:true configs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dules

CheckRegistry.fromConfig (which resolved @sentiness/check-<id> from the
project's node_modules) is replaced by fromResolved, which imports each check
from its cache slot (versioned entries) or a local package (path-linked
entries). Callers go through a new buildRegistry helper; command test fixtures
move from node_modules packages to path-linked checks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each npm-tool check (biome, eslint, knip, jscpd) now declares its tool as an
exact dependency so the tool is pinned by the check version and materialized
into the check's cache slot. CheckContext/ExecFileOptions gain an optional
binPaths, and NodeProcessRunner prepends those slot .bin dirs ahead of the cwd
node_modules/.bin chain on PATH.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rypoint

install resolves the engine + check version ranges to exact versions, writes
sentiness.lock, and materializes the cache slots (npm tools included via the
shared EXTERNAL_TOOL_PACKAGES map). --frozen requires an existing lock that
satisfies the config. The engine entrypoint now extracts --cache-root from argv
and threads it through CommandDeps; buildRegistry resolves slots under it. The
global bin is removed from @sentiness/core (it moves to the launcher).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The launcher owns the global sentiness bin with no dependency on
@sentiness/core. It reads the project's config + sentiness.lock, fetches the
pinned engine into ~/.sentiness/cache (or SENTINESS_HOME), and spawns it with
--cache-root, forwarding args and the exit code. SENTINESS_ENGINE_PATH bypasses
the fetch for local engine development. Tiny local Node FileSystem/ProcessRunner
(stdio inherit, for live streaming)/Logger implementations keep it self-contained.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
require.resolve of an absolute directory path ignores the package "exports"
field (only main/index.js), but the check packages ship exports without main, so
every path-linked check failed to load with "Cannot find module". linkedEntryFile
now reads the linked package's own package.json and resolves the "." entry from
exports (string or conditions) or main. Found while dogfooding V1 in this repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
sentiness.config.json moves to schema v2 with path-linked biome/knip/deps-diff
checks and an engine pin; sentiness.lock is generated by `sentiness install`.
.sentiness/home/ (the local dogfood cache) is gitignored and excluded from Biome.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant