Perspective: a developer onboarding to contribute to Imhotep, and a developer evaluating whether to adopt the tooling in their own repositories, team workflows, or CI responsibilities.
Imhotep has a compelling and unusually ambitious core: declarative UI geometry contracts, dense and fluent authoring styles, finite first-order logic evaluation, structured diagnostics, Playwright integration, property runners, and a package split that mostly reflects the conceptual model. The design promise is high, and several implementation choices already support that promise: broad tests, typed diagnostic shapes, canonical lowering, deterministic evaluation, and real browser fixture coverage.
The current adoption risk is trust, not ambition. A skeptical developer will find working internals next to misleading quick-start instructions, stale examples, package metadata drift, a contaminated lockfile, and several places where syntax support appears ahead of runtime semantics. That mismatch matters because this project sells correctness. If a tool promises First Order Logic Geometric Contract and Property Testing for the Web, every documented example, diagnostic, and CI signal needs to be boringly reproducible.
The most important next step is a hardening pass that makes the public story match the implementation. Fix the CLI and quick-start path, modernize examples, remove stale “not wired yet” expectations, clean the lockfile, add portable E2E paths, align package versions/peers, and explicitly label or hide internal surfaces. After that, focus on semantic correctness gaps: variable-bound domains, topology predicate semantics, diagnostic metric propagation, and world-schema consolidation.
- The repository has a real product thesis: layout assertions as relational contracts, not screenshot diffs or imperative Playwright coordinate checks. `README.md:3` explains spatial, semantic, dimensional, and property-based layout assertions through fluent API or dense DSL.
- The package split mostly matches the mental model: `imhotep-core`, `imhotep-dsl`, `imhotep-solver`, `imhotep-playwright`, `imhotep-cdp`, `imhotep-reporter`, `imhotep-geometry`, `imhotep-topology`, and related packages each have recognizable responsibilities. See the package table in `README.md:15-32`.
- The public fluent path is compact from a user perspective: `const ui = await imhotep(page)`, register assertions, then `await ui.checkAll()`. This is the right product shape for adoption. See `README.md:5-13`.
- The test surface is broad: unit tests, property-style tests, integration tests, Playwright E2E fixtures, docs-example tests, external smoke, and benchmarks are all represented under `packages/*/src` and root scripts in `package.json:9-18`.
- Diagnostics are treated as a product surface rather than incidental thrown errors. Public tests assert diagnostic schema fields, source references, fix hints, LLM output, and traceability.
- CI now exists under `.gitea/workflows/ci.yml` and targets `master`, with separate lint, build/typecheck, unit, E2E, external smoke, and packaging stages.
- The primary package READMEs now exist for `packages/imhotep`, `packages/imhotep-playwright`, `packages/imhotep-dsl`, and `packages/imhotep-core`.
- Recent barrel export classification in `packages/imhotep-playwright/src/index.ts` is a good governance step. The `@public` and `@internal` markers make intent visible.
But the published `imhotep` meta-package has no `bin` field in `packages/imhotep/package.json:14-28`, while the executable is defined by a separate package, `imhotep-cli`, in `packages/imhotep-cli/package.json:16-18`.
That means `npx imhotep init` is not a reliable consumer command unless npm resolves to `imhotep-cli` through unpublished/local workspace behavior. A team trying this from a clean project may hit “could not determine executable to run” or install the library package without a binary.
The README says to install `imhotep` before running init. The scaffolder skips files that already exist, including `package.json`, via `safeWrite()` in `packages/imhotep-cli/src/init.ts:16-23` and the package write in `packages/imhotep-cli/src/init.ts:53-57`.
If a user starts in an existing project or creates a package before init, the preset package scripts and dependencies may not be written. The result is an apparently successful scaffold with missing setup.
Recommendation: either scaffold into an empty directory first, or make `init` merge known-safe fields into existing `package.json`. At minimum, print a clear warning when package metadata is skipped and document `--force` or `--merge` behavior if added.
Preset package JSON sets `"type": "module"` in `packages/imhotep-cli/src/presets/react-playwright.ts:95-108` and `packages/imhotep-cli/src/presets/vue-vitest.ts:115-127`, but generated `imhotep.config.js` uses `module.exports` in `react-playwright.ts:6-24` and `vue-vitest.ts:6-26`.
Recommendation: generate `imhotep.config.cjs`, or generate ESM config with `export default`. The root `imhotep.config.js:1-19` has the same scaffold-residue smell and should be removed or converted if it is intentional.
The actual API used by tests is `const ui = await imhotep(page)` and then synchronous assertion registration followed by `await ui.checkAll()`, as seen in `react-playwright.ts:29-37`.
`examples/page-test.js:4-5` says the primary V1.0 pattern is `ui.extract()` plus Playwright assertions. `examples/page-test.js:7-8` and `examples/responsive-test.js:7-8` use CommonJS `require`, while the repo root is ESM in `package.json:5`.
Worse, the docs-example test suite preserves the stale expectation that examples should not use the now-working public API: `packages/imhotep-fixtures/src/docs-examples.test.ts:51-52`, `docs-examples.test.ts:65-66`, `docs-examples.test.ts:76-78`, `docs-examples.test.ts:88-89`, and `docs-examples.test.ts:134-137` assert that examples avoid `ui.expect`, `checkAll`, or higher-level APIs.
Recommendation: rewrite examples to ESM and current API usage. Then invert the docs tests: they should assert that canonical examples use `await imhotep(page)`, `ui.expect`, `ui.spec`, `checkAll`, and failure diagnostics correctly.
But parse guidance in diagnostics says selectors must be single-quoted strings, e.g. `packages/imhotep-playwright/src/extraction.ts:1522-1527`. E2E tests also use quoted selectors, such as `packages/imhotep-fixtures/src/e2e-semantic-dsl.test.ts:65-77`.
The property example in `README.md:90-96` calls `imhotepFixture(...).forAllProps(...)`, but fixture handles expose `forAllInputs` and `exhaustivelyForAllInputs` in `packages/imhotep-playwright/src/public.ts:882-907`. `forAllProps` exists for component/story targets at `public.ts:776-819`, not fixture targets.
Recommendation: make README examples executable and cover them with docs tests. If the desired DSL syntax is unquoted selectors, implement it explicitly and test ambiguity. If not, document quoted selectors consistently.
That will not run on CI, another developer’s machine, or another checkout path. Most neighboring tests use shared fixture helpers or `import.meta.url`, so this is likely accidental.
Even if `npm install` works locally, a public/release lockfile should not encode paths into a sibling checkout. This is exactly the kind of artifact that causes skeptical adopters to question release hygiene.
Recommendation: choose the release policy. For lockstep releases, use `^1.1.0` or exact `1.1.0` internal ranges. For independently versioned packages, document compatibility and add cross-version smoke tests.
The meta-package allows Playwright `^1.40.0` in `packages/imhotep/package.json:45-48`, while `imhotep-playwright` requires `^1.59.1` in `packages/imhotep-playwright/package.json:30-33`.
Recommendation: align peer dependency ranges. If `1.40` is genuinely supported, prove it in CI. If `1.59.1` is required for extraction/runtime behavior, make the meta-package match.
The DSL supports syntax such as `descendants($card, '.title')` and preserves `parentVar`; see `packages/imhotep-dsl/src/compiler.ts:940-959` and tests in `packages/imhotep-dsl/src/fol-dense-combinations.test.ts:391-403`.
However, the runtime resolver interface is `resolve(domain: DomainRef)` with no binding environment in `packages/imhotep-solver/src/logic-engine.ts:99-101`, and `SelectorDomainResolver.resolve()` simply returns `this.domains.get(domain.selector ?? domain.domain)` in `packages/imhotep-playwright/src/extraction.ts:958-960`.
That means scoped runtime semantics like “titles under this card” likely degrade into global `.title` resolution or cannot resolve variable-only domains. For a FOL contract system, this is a high-value correctness gap because the syntax implies relational scoping.
Recommendation: extend domain resolution to receive the current binding environment, or precompute dependent domains keyed by parent subject ID. Add E2E tests for `descendants($var, selector)`, `children($var)`, and failing cases where a global match would pass but scoped semantics should fail.
The solver descriptor declares `inStackingContext` with arity 1 in `packages/imhotep-solver/src/predicates.ts:83`, while the Playwright FOL compiler emits `inStackingContext(subjectVar, refVar)` for relation options in `packages/imhotep-playwright/src/fol-compiler.ts:326-331`.
The grammar also supports both standalone topology assertions and relation options around `packages/imhotep-dsl/src/grammar.ts:1305-1360`. Topology package tests model a two-argument context comparison, e.g. `packages/imhotep-topology/src/topology.test.ts:390-405`.
Recommendation: decide the product semantics. If `inStackingContext` is unary, the relation option should not emit a reference argument. If it is binary, update predicate metadata/evaluation and tests. Do not leave “same context” behavior as a known accepted failure in E2E.
The conceptual architecture includes `imhotep-core/src/pipeline.ts`, `imhotep-extractor/src/planner.ts`, canonical adapters, CDP extraction, fast extraction, and Playwright-specific orchestration. The actual public `checkAll()` path runs primarily through `packages/imhotep-playwright/src/check-all.ts`, `fol-compiler.ts`, and `extraction.ts`.
This is understandable in a growing framework, but it increases onboarding cost. A contributor can easily modify a generic-looking pipeline or planner and not affect the production public API.
- Core geometry world schema in `packages/imhotep-core/src/world.ts`.
- Canonical world schema in `packages/imhotep-core/src/canonical.ts`.
- CDP snapshot/world extraction in `packages/imhotep-cdp/src/extractor.ts`.
- Playwright fast-path world builder in `packages/imhotep-playwright/src/world-builder.ts`.
- Solver predicates that sometimes reach through optional/adapted fields using `as any`, e.g. `packages/imhotep-solver/src/predicates.ts:163-169` and `predicates.ts:242-262`.
The result is that a predicate author must know whether a fact is present in fast extraction, CDP extraction, canonical adaptation, test mocks, and solver-world adaptation.
Recommendation: define one canonical solver input contract and make every extraction path adapt into it before predicate evaluation. Add a “fact availability matrix” for each predicate so contributors can see which extraction facts are required and which paths provide them.
`packages/imhotep-core/src/integration-mocks.ts:8-14` imports types from `imhotep-solver` and `imhotep-state`, but `packages/imhotep-core/package.json:1-61` lists no dependencies.
This file is not exported from the core barrel, but it is still source under a publishable package and can end up in dist output. It violates the expected direction of dependency flow: core should not know about solver/state.
Recommendation: move integration mocks into a test-only package or `packages/imhotep-core/test-support` that is not included in production package files. Alternatively add explicit dependencies and accept the architectural coupling, but that seems worse.
`packages/imhotep-playwright/src/index.ts:64-98` labels pools, page wrapper, environment helpers, and target resolution as `@internal`, but still exports them from the public package root.
This is better than accidental export, but it is still a semver hazard. Users can import internals directly from `imhotep-playwright`, and package managers will treat them as public unless documentation and export maps say otherwise.
Recommendation: move internals to explicit subpaths such as `imhotep-playwright/internal` or remove them from the package root before a stable release. If they must remain for tests/tooling, document that `@internal` exports are not semver-stable.
`packages/imhotep-playwright/src/index.ts:34-52` exports renderer registry and renderer descriptors (`react`, `vue`, `storybook`, `custom`), but `packages/imhotep/src/index.ts:1-26` does not re-export those. A user following `import { imhotep } from 'imhotep'` may hit missing APIs for component/story/custom renderer workflows.
Recommendation: decide whether `imhotep` is the single recommended import path. If yes, re-export the stable Playwright public API from the meta-package. If no, the README should clearly say when to use `imhotep` versus `imhotep-playwright`.
Large files are not automatically wrong, but these contain multiple responsibilities: parsing, AST shaping, extraction planning, world adaptation, public handles, diagnostics mapping, and evaluation orchestration.
Recommendation: split by responsibility after the current hardening pass. Good seams include extraction planning versus browser execution, diagnostic mapping versus evaluation, public types versus public factory construction, and predicate families by geometry/topology/size/alignment.
The project’s value proposition depends heavily on actionable diagnostics. A schema-valid diagnostic with empty measured context is less useful than a plain Playwright assertion in many failure investigations.
`evaluatePredicate()` preserves `predicateResult.metrics` in `packages/imhotep-solver/src/logic-engine.ts:837-843`, but `evaluateAnd`, `evaluateOr`, `evaluateNot`, and `evaluateImplies` construct new `FormulaResult` objects without carrying operand metrics in `logic-engine.ts:526-667`.
Recommendation: define a metric aggregation policy for connectives. For failing `and`, preserve the failing side’s metrics. For failing `or`, preserve both failed sides if possible. For `not`, preserve operand metrics when it fails because the operand passed.
The core diagnostic union and default categorization live in `packages/imhotep-core/src/diagnostics.ts`, while reporter code metadata is duplicated in `packages/imhotep-reporter/src/codes.ts`.
This can drift. One concrete example identified during review: reporter metadata classifies topology unsupported behavior differently from core fallback categorization.
The root `test` script is `npm run test --workspaces` in `package.json:12`. Many workspace tests run `node --test dist/**/*.test.js` without compiling first, for example `packages/imhotep-dsl/package.json:19-22`, `packages/imhotep-solver/package.json:19-22`, and `packages/imhotep/package.json:29-32`.
Recommendation: either make `npm test` run `npm run build` first at the root, or make every package test script compile what it runs. A fresh clone should not be able to pass or fail tests based on stale `dist` output.
Given the lockfile contamination noted above, using `npm install` may currently be pragmatic. But for a release-quality repository, CI should prove lockfile reproducibility.
Recommendation: document one reliable targeted path, likely by building first and using `--config packages/imhotep-fixtures/playwright.config.ts --grep ...` or pointing at the compiled test if that is the intended mode.
`scripts/external-smoke.mjs:16-37` installs dependencies via `file:` paths to package directories. This is valuable, but it does not fully prove package tarball contents. CI packs packages later in `.gitea/workflows/ci.yml:81-88`, but does not appear to install and run from those tarballs.
Recommendation: add a publish-smoke mode that runs `npm pack`, installs the generated `.tgz` files into a clean temp project, and executes the same smoke. This catches missing `files`, bad exports, missing README/package metadata, and package dependency drift.
The fixture harness validates named categories in `packages/imhotep-fixtures/src/harness.ts`, while several pages used by E2E tests live outside that registry. This means duplicate `data-testid`s, missing required fixtures, or structure drift can slip through for some pages.
Package READMEs exist for `imhotep`, `imhotep-playwright`, `imhotep-dsl`, and `imhotep-core`. Other publishable packages have package metadata but no package-level usage/status docs.
That is acceptable if those packages are internal implementation details, but then they should be marked private or clearly described as internal. If they are public, npm browsers need enough context to evaluate them.
Recommendation: classify all packages as public, internal-but-published, or private. Add short READMEs for public packages and mark truly internal packages private if they should not be adopted directly.
The meta-package uses explicit `types` and `import` conditions in `packages/imhotep/package.json:19-28`, while other packages use `types` and `default`, e.g. `packages/imhotep-playwright/package.json:24-29` and `packages/imhotep-core/package.json:23-43`.
This may work in Node ESM, but inconsistent export condition shapes make behavior harder to reason about across bundlers and TypeScript module resolution modes.
There are many `any` and `as any` usages in parser/runtime boundary code, especially `packages/imhotep-playwright/src/public.ts`, `packages/imhotep-playwright/src/extraction.ts`, `packages/imhotep-dsl/src/grammar.ts`, and `packages/imhotep-solver/src/predicates.ts`.
Some of this is expected around JS proxies, browser evaluation, parser AST construction, and adapted world schemas. The risk is not that `any` exists; the risk is that a contributor cannot tell which casts are intentional boundary escapes versus accidental type debt.
Recommendation: do not chase zero `any` blindly. Instead, add local typed boundary interfaces for proxy metadata, browser-injected globals, adapted world extensions, and parser node unions. Add comments only where a cast is truly necessary.
- Is `npx imhotep init` intended to be the canonical CLI command, or should the CLI package name be user-facing?
- Is `imhotep` intended to be the only recommended import path, or should advanced users import `imhotep-playwright` directly?
- Are all packages intended to be published independently, or should packages like `imhotep-cdp`, `imhotep-extractor`, `imhotep-state`, `imhotep-topology`, and `imhotep-bench` be private/internal?
- Should dense DSL support unquoted CSS selectors, or should all examples use quoted selectors?
- What is the intended runtime semantics for `descendants($var, selector)` and `children($var)`?
- Is `inStackingContext` unary (“has any stacking context”) or binary (“shares/is in a specific context”)?
- Which world schema is canonical for predicate authors?
- Should CI test against the minimum supported Playwright version or only the latest supported version?
- Should package tests be runnable from a fresh checkout without an explicit build?
- Are `@internal` root exports semver-stable or explicitly unsupported?
- Should external smoke validate local package directories, packed tarballs, or both?
Imhotep is promising enough to justify high standards. The core concept and much of the implementation already look like a serious framework rather than a toy wrapper around Playwright. The current weaknesses are fixable, but they are highly visible: a broken/misleading CLI path, stale examples, lockfile contamination, hardcoded local paths, and semantics that parse before they truly resolve.
For a developer evaluating adoption, I would recommend a short hardening sprint before team rollout. For a developer onboarding to contribute, I would start with documentation truthfulness and reproducibility first, then move into variable-bound domain semantics and diagnostic fidelity. Those improvements would make the project live up much more closely to its stated goal: First Order Logic Geometric Contract and Property Testing for the Web.