Skip to content

refactor(daemon): publish a single package entrypoint - #3781

Merged
thymikee merged 8 commits into
mainfrom
refactor/daemon-single-entrypoint
Oct 17, 2026
Merged

thymikee merged 8 commits into
mainfrom
refactor/daemon-single-entrypoint

Conversation

@thymikee

@thymikee thymikee commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

What

@agent-device/daemon publishes exactly one package entry: ".". The 18 transitional
./testing/* subpaths are deleted, the layering checker pins that shape for both single-entry
packages under one declaration, and three test-hygiene commits land alongside.

Production behaviour does not change. Outside the manifest, the checker and the removed test-support
surface, this branch touches only test files and docs.

Depends on the consumer migrations in #3728, #3733 and #3742, all of which are on main.

Export surface: 19 → 1

packages/daemon/package.json exports keys
before (origin/main 0563666b0) 19 — "." + 18 ./testing/*
after (this head) 1 — "."

"." is byte-identical to the entry on main (types + default → ./src/index.ts), verified by
diffing the key against origin/main.

No source module was deleted, and none should have been. Each of the 18 removed subpaths pointed
at a module other code still needs: the 15 production targets each have 1–118 non-test importers
(session-state.ts 118, session-store.ts 74, context.ts 58, …, request-finalization.ts 1), and
the 3 src/__tests__/support/ fixtures are each imported relatively by in-package tests that moved
there in #3733. So no Fallow baseline entry needed moving or removing — pnpm check:fallow reports no
issues in the changed files.

R83 / R84

node --experimental-strip-types scripts/layering/check.ts, which measures head and merge-base with
the same function:

head merge-base
R83 package-export-surface (test-only exports) 13 31
R84 unmatched unit tests 152 152

R83 drops by exactly 18 — the 18 deleted subpaths. The 13 remaining are other packages' fixtures,
untouched here. R84 is unchanged at 152. The before half was reproduced independently: the same
check run at 0563666b0 in a clean worktree reports 31 test-only export(s) remain against 31 and
152 unmatched test(s) remain against 152.

The 18 subpaths have zero importers

Every tracked reference to a daemon testing specifier at origin/main — exhaustive git grep over
the whole tree with no path exclusions:

$ git grep -c "daemon/testing" origin/main -- .
origin/main:scripts/layering/model.ts:1
origin/main:scripts/layering/package-boundaries-facade.test.ts:7

Those 8 hits are checker inputs, not consumers. One is a prose example inside the
resolveWorkspaceSpecifier doc comment in model.ts, describing how a pattern key is stored; the
other 7 are planted specifiers that feed R11's façade rule synthetic import sites. That file builds
its own synthetic daemon manifest with its own ./testing/ keys (see its daemonManifest const), so
it never reads the real manifest and does not care whether the daemon publishes the namespace. The
same two files are the only hits at this head, both still valid.

Nothing inside the package imports itself through @agent-device/daemon/testing/*: that grep covers
packages/daemon/**, and package self-imports would have to name the package. The deleted namespace
also appears in no Vitest alias, Stryker config or workflow.

The pin

scripts/layering/package-entry-surface.ts (R83) already held test-kit's single-entry pin. ec8c515a0
extends that one declaration rather than adding a rule beside it: the test-kit-only body became
checkEntrySurface(...), driven by an ENTRY_SURFACES list that checkPackageEntrySurfaces maps
over — test-kit plus the daemon. There is still exactly one entry-surface declaration and one call
site (scripts/layering/check.ts:627); grep -rn "checkTestKitIsolation" scripts/ returns nothing.
Each pinned package is asserted on both halves: exports is exactly "." with every condition
resolving to its declared entry file, and its src/index.ts contains no star re-export.

Planted violations

Both planted in the real tree and run through the real checker (node --experimental-strip-types scripts/layering/check.ts), not just the unit lane.

Extra subpath — re-added "./testing/context" to packages/daemon/package.json:

  [R83 package-export-surface] 1 violation(s):
    packages/daemon/package.json:1 — @agent-device/daemon exports exactly "." and every condition of
    it is ./src/index.ts (found ./testing/context, . -> ./src/index.ts, ./src/index.ts).
::error file=packages/daemon/package.json,line=1,title=Layering drift (R83 package-export-surface)::

export * — appended export * from './server/http/daemon-runtime.ts'; to
packages/daemon/src/index.ts:

  [R83 package-export-surface] 1 violation(s):
::error file=packages/daemon/src/index.ts,line=13,title=Layering drift (R83 package-export-surface)::
  @agent-device/daemon names every export explicitly: `export *` is forbidden.

Both are mirrored as checker tests beside the rule (package-entry-surface.test.ts), each asserting
the violation's file and message and that the clean workspace reports none. The star test plants all
four shapes (export *, export * as ns, export type *, export type * as ns) and pairs them with
the closest negative: an entry whose comment and template literal mention export * while it names
everything, which must pass. Lane: 427 tests, 0 failures.

Crop before scale

src/commands/capture/runtime/screenshot.test.ts gains a case whose backend crops inside
captureScreenshot, mirroring the daemon's bound backend (screenshot-runtime.ts:373 crops the file
before returning), so the 40×20 crop must reach the runtime's 0.5 scale and land at 20×10.

Mutation — move resizePngFileToScale from after the capture to before it in
src/commands/capture/runtime/screenshot.ts, so scaling runs on the pre-capture frame and the crop
lands last:

 FAIL  src/commands/capture/runtime/screenshot.test.ts > runtime screenshot scales the already-cropped backend capture
  - Expected  { "height": 10, "width": 20 }
  + Received  { "height": 20, "width": 40 }

The output is the unscaled crop: the case fails for exactly the order it claims. Reverted; both cases
pass again.

Commits

commit what
ec8c515a0 refactor(layering): generalize package entry pin
15bd48495 refactor(daemon): pin single package entry
96f3abe72 test(capture): pin crop before screenshot scaling
f902cf15e test(daemon): clarify scale metadata screenshot case
40618c28b test(provider-scenarios): share rpc envelope conversion
0d7c8f14f docs(adr): record the daemon's closed testing surface
8d652e315 test(provider-scenarios): keep the rpc envelope conversion's leaf private
ac076f13a refactor(layering): read the entry's star exports as syntax

f902cf15e is a rename only: the title now says --scale suppresses logical density metadata, and
the diff is the one test(...) line — no assertion changed.

40618c28b puts one envelopeToRpcResult(envelope, id, statusCode) in
test/integration/provider-scenarios/harness-rpc.ts, used by both callCommand functions in
harness.ts and harness-proxy.ts, with neither copy of the inline conversion left behind and
JsonRpcResponseEnvelope declared once. 8d652e315 follows up on it: with both call sites converted,
responseToRpcResult had no caller outside its module and pnpm check:fallow failed the branch on
that dead export, so it is now module-private.

0d7c8f14f records the closed surface in ADR 0033 and deletes the prose the gate now enforces rather
than restating it, plus restores the Catches/Evidence/Cost/Kill criterion header the rule file
lost when it was generalised. docs/agents/* and AGENTS.md needed no edit: neither described a
./testing/* export even on main (git grep "testing/" origin/main -- docs/agents/ AGENTS.md is
empty), so there was nothing to delete there.

ac076f13a answers the cubic review thread: the star check matched ^[ \t]*export\s*\* against the
entry's text, so it accepted export type * from — which publishes a module's type surface just the
same — and would have flagged a comment that mentions a star. It now parses the entry and refuses any
ExportAllDeclaration, the one node every star shape builds. Restoring the regex fails the new
export type * case, which is the proof the gap was real. facade-exports.ts records the same failure
mode from #1555, so this follows in-repo precedent.

Gate receipt (exact head ac076f13a)

pnpm check:affected --run --head ac076f13a1295dd5e216e3e9d07f3df929f9dbb6 — all runnable checks passed
(status: passed, exit 0, exactCommit: true, checkoutChanged: false; base 0563666b0;
09:48:01Z → 09:56:47Z). Receipt: .tmp/validation/run-87Srlt/receipt.json.

36 checks passed, 2 deduplicated into the related-test run, 25 skipped as GitHub-authoritative
or parked. The 2 deduplicated are unit and provider-integration, both "covered by related
tests" by the runner's own bookkeeping; the provider-scenarios lane was also run on its own for
this PR — pnpm test:integration:provider, 64 files / 192 tests, all passing.

Passed:

  • format, lint, typecheck, layering, di-seams.
  • fallow, mcp-metadata, build, package, integration-node.
  • macos-coverage, vitest-related, integration-progress, replay-compat, daemon-wire-compat.
  • affected-selector, gate-manifest, gate-manifest-model, depgraph, tmpdir-leaks.
  • tmpdir-leaks-model, coverage-model, coverage-crap-model, wire-compat-model, production-exports.
  • bundle-owner-files, fixture-cache, fixture-fallback, command-docs, docs-links.
  • docs-prose, agent-guidance, xctest-selection, packaged-runner-swift, maestro-conformance.
  • mutation-model.

Individually, on this head: pnpm format/format:check, pnpm check:quick (lint + typecheck),
the layering check (R83 13 / 31, R84 152 / 152, OK), pnpm check:fallow (no issues in the
changed files), pnpm check:gate-manifest (57 checks across 25 lanes), pnpm check:doc-prose,
pnpm check:doc-links, and pnpm test:integration:provider.

🤖 Generated with Claude Code

thymikee and others added 7 commits October 17, 2026 10:55
ADR 0033's daemon section described the transitional `./testing/` entries as
still standing. Record the final state and name the owner of the surface
instead of restating what R83 now enforces, and restore the rule header
package-entry-surface.ts lost when it was generalised.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…vate

Both callCommand functions now convert through envelopeToRpcResult, so
responseToRpcResult has no caller outside harness-rpc.ts. Fallow flagged the
export as dead surface; the conversion stays one function with one entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.34 MB 5.34 MB 0 B
Package (unpacked) 5.34 MB 5.34 MB 0 B
Package (download) 1.61 MB 1.61 MB -5 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.1 ms 26.2 ms -0.9 ms
CLI --help 78.9 ms 75.7 ms -3.2 ms

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 11 files

You’re at about 96% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread scripts/layering/package-entry-surface.ts Outdated
The pin matched `^[ \t]*export\s*\*` against the entry's text, so it refused a
comment that mentions a star and accepted `export type * from`, which publishes a
module's whole type surface just the same. Read the parsed AST instead:
`ExportAllDeclaration` is the one node every star shape builds — `export *`,
`export * as ns`, and both `export type` forms — and prose builds none.

facade-exports.ts records the same failure mode from #1555: a hand-enumerated
export-form scanner misses exactly the form that widens the surface.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@thymikee

Copy link
Copy Markdown
Member Author

This PR is ready at ac076f1. The daemon now has a single package entrypoint, and I found no problems in the diff.

Not blocking: the entry-surface check in scripts/layering/package-entry-surface.ts (https://github.com/callstack/agent-device/blob/ac076f1/scripts/layering/package-entry-surface.ts#L88) passes with nothing parsed if the daemon entry file is moved or renamed, so you could treat a missing entry source as a violation; the daemon entry './src/index.ts' is a bare literal while TEST_KIT_ENTRY is a named constant, so one shared ENTRY_FILE constant would tidy that (https://github.com/callstack/agent-device/blob/ac076f1/scripts/layering/package-entry-surface.ts#L28); and the crop-before-scale screenshot test, the test-title rename and the provider-scenarios dedup are unrelated to this change, but I do not ask for a split. You can take or leave all three.

The earlier thread on the star check missing export type * no longer applies. The check now parses with oxc and refuses ExportAllDeclaration, and the new tests cover the type-star forms and a prose negative, so you can resolve it: #3781 (comment)

I judged this from the diff and the PR body. I did not run the layering lane, the unit tests or the mutation, and I could not read your local receipt. I also did not run the parser, so the export type * node shape is confirmed only from the test.

CI is still pending. Only Smoke Tests is queued, so it has not run and there is no failure to read. The diff changes no production code, and Smoke runs the built CLI and daemon through the bare entry, which is unchanged. It must finish green before merge, and nothing else stands in the way.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 17, 2026
@thymikee
thymikee merged commit a61556b into main Oct 17, 2026
20 checks passed
@thymikee
thymikee deleted the refactor/daemon-single-entrypoint branch October 17, 2026 10:42
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-17 10:43 UTC

thymikee added a commit that referenced this pull request Oct 17, 2026
…ment

* origin/main:
  refactor(daemon): publish a single package entrypoint (#3781)
  fix(daemon-client): wait for an acknowledged quiesce instead of killing the daemon (#3780)
  fix(daemon): refuse leases for providers without a registered runtime (#3773)

# Conflicts:
#	packages/daemon/src/sessions/handlers/__tests__/lease.test.ts
#	src/daemon-client/daemon-client-ensure.ts
thymikee added a commit that referenced this pull request Oct 17, 2026
…refactor/daemon-runtime-no-process-globals

* origin/refactor/host-environment:
  refactor(cli): resolve one transport per run and one host per MCP tool call; default the lease provider cwd to the bound host
  refactor(daemon): publish a single package entrypoint (#3781)
  fix(daemon-client): wait for an acknowledged quiesce instead of killing the daemon (#3780)
  fix(daemon): refuse leases for providers without a registered runtime (#3773)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant