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
5 changes: 5 additions & 0 deletions .fallowrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@
"file": "scripts/mutation/ownership.ts",
"exports": ["ownedTestFiles"]
},
{
"comment": "ADR 0010 shared-hint pin: the facade re-export's only consumer is src/__tests__/is-argument-surface-parity.test.ts, which asserts the daemon and CLI `is` surfaces raise the SAME hint text. The constant is live inside the package (predicates.ts raises it); a production consumer of the re-export would defeat the point, since it exists so neither surface copies the string.",
"file": "packages/selectors/src/index.ts",
"exports": ["IS_PREDICATE_USAGE_HINT"]
},
{
"comment": "Help-benchmark conformance seams: helpTopicIds feeds the topic-coverage gate and PRIVATE_AX_RECOVERY_SAMPLE feeds the bench sample producers; both consumers are test-tree files outside --production analysis.",
"file": "{src/cli/parser/cli-help.ts,scripts/help-conformance-sample-outputs.mjs}",
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/mutation-affected.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ on:
- 'src/daemon/ref-frame.ts'
- 'src/commands/interaction/runtime/settle.ts'
- 'src/utils/scroll-edge-state.ts'
- 'src/selectors/**'
- 'packages/selectors/src/**'
- 'packages/ad-script/src/internal/target-annotation-serde.ts'
- 'src/snapshot/snapshot-occlusion.ts'
- 'src/**/*.test.ts'
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ declaration site rather than any map someone wrote down:
`src/daemon.ts` stays a thin router and `src/daemon/request-router.ts` orchestration-only; command
logic belongs in handlers. New daemon handler-family commands update the daemon command registry.

Shared selector parsing/matching/resolution lives in `src/selectors`; request cancellation/progress
Shared selector parsing/matching/resolution lives in `@agent-device/selectors`; request cancellation/progress
primitives in `src/request`; cross-layer platform and command data contracts in `src/contracts`. CLI
grammar owns flag declarations under `src/commands/cli-grammar`; cross-surface CLI schema composition
lives in `src/cli-schema`.
Expand Down Expand Up @@ -103,7 +103,7 @@ the new thing — never to suppress or allowlist it.
- iOS simulator-set scoping is iOS-specific: `iosSimulatorDeviceSet` must not hide the host macOS
desktop target when `--platform macos` or `--target desktop` is requested.
- Use `inferFillText` (`src/daemon/action-utils.ts`), `uniqueStrings` (`@agent-device/kernel/collections`),
and `evaluateIsPredicate` (`src/selectors/predicates.ts`) rather than reimplementing them.
and `evaluateIsPredicate` (`@agent-device/selectors`) rather than reimplementing them.
- Do not update `skills/**/SKILL.md` for command behavior or workflow guidance unless the user asks.
Skills are thin routers to versioned CLI help; they must not carry behavior details.

Expand Down Expand Up @@ -216,7 +216,7 @@ connect errors, retry policy, or command typing, start in
`collectReplaySelectorCandidates` (`src/daemon/handlers/session-replay-heal.ts`) can rank it in a
divergence report (`session-replay-divergence.ts`). ADR 0012 retired `--update`'s silent
rewrite-on-heal; there is no automated write path to hook into.
- New selector keys stay centralized in `src/selectors/parse.ts`; new `is` predicates belong in
- New selector keys stay centralized in the private parser under `packages/selectors`; new `is` predicates belong in
`evaluateIsPredicate`.
- On macOS, snapshot rects are absolute in window space. Point-based runner interactions translate
through the interaction root frame — do not assume app-origin `(0,0)`. Prefer selector or `@ref`
Expand Down
15 changes: 8 additions & 7 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ The perfect-shape refactor is complete and merged. Its end-state:
it ranks an explicit target spine — as rank groups, lowest (kernel sink) to highest, where `A ◄ B`
means B may not be outranked by A (the back-edge order the gate rejects), NOT that every displayed
import exists:
`{ contracts, request, selectors, platforms, utils, replay, recording, snapshot, screenshot-diff } ◄ core ◄ { commands, cli-schema, mcp } ◄ { client, daemon-server, compat, remote, metro, sdk } ◄ daemon-client ◄ cli` (the former rank-0 kernel zone lives in `packages/kernel` since #1490 W0, the former `cloud-webdriver` leaf lives behind the single `@agent-device/provider-webdriver` facade since W1b, Limrun lives behind the single `@agent-device/provider-limrun` facade since W1d, and the dependency-free XML codec lives behind the single `@agent-device/xml` facade; R11 package-boundaries owns these physical seams) —
`{ contracts, request, selectors, platforms, utils, replay, recording, snapshot, screenshot-diff } ◄ core ◄ { commands, cli-schema, mcp } ◄ { client, daemon-server, compat, remote, metro, sdk } ◄ daemon-client ◄ cli` (the former rank-0 kernel zone lives in `packages/kernel` since #1490 W0, and shared selectors now live behind the private `@agent-device/selectors` package between `@agent-device/ad-script` and `@agent-device/ad-replay`; the former `cloud-webdriver` leaf lives behind the single `@agent-device/provider-webdriver` facade since W1b, Limrun lives behind the single `@agent-device/provider-limrun` facade since W1d, and the dependency-free XML codec lives behind the single `@agent-device/xml` facade; R11 package-boundaries owns these physical seams) —
and rejects every back-edge within it. Only `(root)` is unranked among `src/` zones
(`UNRANKED_ZONES` in `scripts/layering/model.ts`): it holds the entrypoints and the composition
roots that wire the command surface into the daemon, and R2 forbids `daemon/` from importing
Expand Down Expand Up @@ -327,7 +327,7 @@ The perfect-shape refactor is complete and merged. Its end-state:
it. This per-zone ratchet is intentionally stricter than R9's ordinary total-growth rule: even
moving cycle membership into a zone at its ceiling must be justified by lowering another ceiling
or changing the baseline explicitly. The #1478 extraction arc (P0–P5) completed against these
ratchets: engines live behind the `packages/{maestro,replay-test,ad-replay}` façades, engines
ratchets: engines live behind the `packages/{maestro,replay-test,ad-replay,selectors}` façades, engines
cannot import daemon/platform/provider implementations, and no logical module may deep-import
another module's `internal/` tree. The P6 platform-modularity phases were measured and deferred
at the #1478 checkpoint (2026-08-04): the Apple perf edge no longer participates in any cycle,
Expand Down Expand Up @@ -378,11 +378,12 @@ when landing it, satisfy the gate where one exists.
axis of change. The one gated slice of this norm is tests: CI forbids test-only DI seams — a
missing seam gets added as a real one or not at all.
- **Module seams** (the #1478 extraction's durable rules). State crosses seams as immutable
values, authority crosses as capabilities, and both only narrow; a seam exists when its port has
two real adapters (normally the daemon adapter and a deterministic in-memory adapter running the
same contract suite), otherwise it is indirection. Calls go down through module façades and back
through ports; no inter-module event bus — ADR 0018's journal is an observation channel, never a
coordination mechanism. No engine receives `DaemonRequest`, `DaemonError`, `SessionStore`,
values, authority crosses seams as capabilities, and both only narrow; a capability seam earns
its port only when it has two real adapters, normally the daemon adapter and a deterministic
adapter running the same contract suite. A pure shared kernel instead stays behind its direct
package façade; the selector engine is the worked example. Calls go down through module façades
and back through ports; no inter-module event bus — ADR 0018's journal is an observation channel,
never a coordination mechanism. No engine receives `DaemonRequest`, `DaemonError`, `SessionStore`,
mutable `SessionState`, provider handles, or concrete platform implementations; the daemon
adapter closes each capability over one already-admitted request, so an engine cannot express a
session name, acquire a lock, or select a provider scope. Session state keeps three consistency
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0010-error-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ agent failures (selector/ref misses), and consumers that drop fields (MCP tool e
default from `defaultHintForCode` would mislead; it is omitted where the default suffices —
mass-adding boilerplate hints is worse than the default. Shared failure modes get shared hint
constants next to the code that detects them (`selectorFailureHint`, `STALE_REF_HINT` in
`src/selectors/resolve.ts`; `resolveIosDevicectlHint`; `bootFailureHint`), not copy-pasted
`packages/selectors/src/internal/resolve.ts`; `resolveIosDevicectlHint`; `bootFailureHint`), not copy-pasted
strings. Re-wraps preserve an existing hint rather than clobbering it.
4. **Wrapping external tool failures.** Prefer `exec.ts` errors as-is. A hand-rolled wrap of an
`allowFailure` result must carry `{ stdout, stderr, exitCode, processExitError: true }` so
Expand Down
6 changes: 3 additions & 3 deletions docs/adr/0012-interactive-replay.md
Original file line number Diff line number Diff line change
Expand Up @@ -1012,7 +1012,7 @@ The tombstone itself expires after its bounded window, after which the key is fu
> POSITIONAL rather than the command name, so it cannot be settled statically: it allows `--record`
> in the grammar and validates **dynamically** in the daemon (`handleFindCommands`), rejecting
> `--record` on a mutating `find … click|fill|focus|type` with `INVALID_ARGS` before any device work. Both
> halves read one shared predicate (`isReadOnlyFindAction`, `src/selectors/find.ts`), so the routing and
> halves read one shared predicate (`isReadOnlyFindAction`, `@agent-device/selectors`), so the routing and
> the validation can never disagree about which sub-actions observe.
>
> `--record` and `--no-record` express opposite intents for the same action and are **mutually exclusive**:
Expand Down Expand Up @@ -1189,7 +1189,7 @@ zero on the happy path and paying only where reality diverged from the recording
live and replay alike. `resolveSelectorInteractionTarget` calls `resolveSelectorChain(..., {
disambiguateAmbiguous: true })` on every press/click/fill (`resolution.ts:170-183`); when a selector
matches N>1 nodes, `accumulateDisambiguationCandidate`/`compareDisambiguationCandidates`
(`src/selectors/resolve.ts:181-285`) silently pick a winner — visible candidates over
(`packages/selectors/src/internal/resolve.ts:181-285`) silently pick a winner — visible candidates over
off-screen ones, then deepest node, then smallest on-screen area, only an exact tie failing.
`describeResolvedInteractionNode` (`resolution.ts:227-249`), the response's entire identity payload,
carries `node`/`selectorChain`/`refLabel`/`targetHittable`/`hint` — no match count, no signal a
Expand Down Expand Up @@ -1262,7 +1262,7 @@ both `.ad` and Maestro paths) grounds the same conclusions from the caller's sea
(`session-replay-runtime.ts:349-369`) puts only `replayPath` + `step` in the error details.
- **The same failure class reports differently per format.** An `.ad` selector miss is
`COMMAND_FAILED` with the targeted hint "Run snapshot -i ... or use find ..."
(`selectorFailureHint`, `src/selectors/resolve.ts:110-113`, thrown at `resolution.ts:213-217`);
(`selectorFailureHint`, `packages/selectors/src/internal/resolve.ts:110-113`, thrown at `resolution.ts:213-217`);
the equivalent Maestro miss is `ELEMENT_NOT_FOUND` constructed with no hint
(`src/compat/maestro/runtime-interactions.ts:644-652`), falling through to the generic default
"Retry with --debug and inspect diagnostics log for details." (`defaultHintForCode`,
Expand Down
2 changes: 1 addition & 1 deletion docs/agents/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The mapping it encodes, for when you need to run a gate directly or reason about
| `.ad` grammar (`src/replay/script.ts`, gesture arity, replay vars) | `pnpm exec vitest run --project unit-core test/replay-compat` — the frozen replay-compat corpus asserts which released script surfaces still parse; a flipped verdict is edited in `test/replay-compat/manifest.ts`, never in the script. Adding or re-pinning a corpus entry also runs `pnpm check:replay-compat`, which re-derives each entry from its release tag in git history |
| Anything in `src/`, `test/` | `pnpm format` (`skills/` is Markdown-only guidance: oxfmt ignores `**/*.md`, and the affected-check selector classifies it docs-only) |
| Workspace package source (`packages/*/src/**`) | Root format/lint/typecheck plus layering (R11 package-boundaries); Vitest resolves affected tests through the module graph; package manifests/tsconfigs fail open to the full set |
| A decision kernel or its tests (`packages/kernel/src/errors.ts`, `src/daemon/ref-frame.ts`, `src/commands/interaction/runtime/settle.ts`, `src/utils/scroll-edge-state.ts`, `src/selectors/`) | `pnpm mutation:affected --base origin/main` (minutes; GitHub runs it per PR — see the mutation ratchet section) |
| A decision kernel or its tests (`packages/kernel/src/errors.ts`, `src/daemon/ref-frame.ts`, `src/commands/interaction/runtime/settle.ts`, `src/utils/scroll-edge-state.ts`, `packages/selectors/src/`) | `pnpm mutation:affected --base origin/main` (minutes; GitHub runs it per PR — see the mutation ratchet section) |

Two traps worth naming:

Expand Down
1 change: 0 additions & 1 deletion examples/sdk/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"agent-device/install-source": ["../../src/sdk/install-source.ts"],
"agent-device/android-adb": ["../../src/sdk/android-adb.ts"],
"agent-device/contracts": ["../../src/sdk/contracts.ts"],
"agent-device/selectors": ["../../src/sdk/selectors.ts"],
"agent-device/finders": ["../../src/sdk/finders.ts"]
}
},
Expand Down
20 changes: 6 additions & 14 deletions fallow-baselines/health.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@
"count": 1
}
},
"src/__tests__/test-utils/in-memory-replay-selector-port.ts": {
"complexity_moderate": {
"count": 2
},
"crap_moderate": {
"count": 2
}
},
"src/cli-schema/cli-config.ts": {
"crap_moderate": {
"count": 1
Expand Down Expand Up @@ -469,25 +461,25 @@
"count": 1
}
},
"src/selectors/build.ts": {
"packages/selectors/src/internal/build.ts": {
"complexity_high": {
"count": 1
}
},
"src/selectors/match.ts": {
"packages/selectors/src/internal/match.ts": {
"crap_high": {
"count": 1
}
},
"src/selectors/parse.ts": {
"packages/selectors/src/internal/parse.ts": {
"complexity_moderate": {
"count": 1
},
"crap_moderate": {
"count": 2
}
},
"src/selectors/predicates.ts": {
"packages/selectors/src/internal/predicates.ts": {
"crap_moderate": {
"count": 1
}
Expand Down Expand Up @@ -590,7 +582,7 @@
"src/daemon/context.ts:high impact",
"src/daemon/handlers/session.ts:complexity",
"src/replay/script-utils.ts:high impact",
"src/selectors/predicates.ts:high impact",
"packages/selectors/src/internal/predicates.ts:high impact",
"src/daemon/android-snapshot-freshness.ts:high impact",
"src/platforms/boot-diagnostics.ts:complexity",
"src/compat/maestro/support.ts:high impact",
Expand Down Expand Up @@ -628,7 +620,7 @@
"src/utils/source-value.ts:high impact",
"src/request/cancel.ts:high impact",
"src/commands/interaction/selectors.ts:untested risk",
"src/selectors/build.ts:high impact",
"packages/selectors/src/internal/build.ts:high impact",
"src/kernel/redaction.ts:high impact",
"src/utils/rect-visibility.ts:high impact",
"packages/provider-webdriver/src/webdriver-utils.ts:high impact",
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
"check:unit": "pnpm check:contention-retry && pnpm test:unit && pnpm test:smoke",
"check": "pnpm check:tooling && pnpm check:fallow && pnpm check:unit",
"prepack": "pnpm check:mcp-metadata && pnpm package:npm",
"typecheck": "tsc -b packages/xml packages/kernel packages/contracts packages/ad-script packages/ad-replay packages/maestro packages/replay-test packages/provider-webdriver packages/provider-limrun && tsc -p tsconfig.json && tsc -p examples/sdk/tsconfig.json",
"typecheck": "tsc -b packages/xml packages/kernel packages/contracts packages/ad-script packages/selectors packages/ad-replay packages/maestro packages/replay-test packages/provider-webdriver packages/provider-limrun && tsc -p tsconfig.json && tsc -p examples/sdk/tsconfig.json",
"test-app:install": "pnpm install --dir examples/test-app",
"test-app:start": "pnpm --dir examples/test-app start",
"test-app:ios": "pnpm --dir examples/test-app ios",
Expand Down Expand Up @@ -247,6 +247,7 @@
"@agent-device/ad-script": "workspace:*",
"@agent-device/contracts": "workspace:*",
"@agent-device/kernel": "workspace:*",
"@agent-device/selectors": "workspace:*",
"@agent-device/maestro": "workspace:*",
"@agent-device/provider-limrun": "workspace:*",
"@agent-device/provider-webdriver": "workspace:*",
Expand Down
3 changes: 2 additions & 1 deletion packages/ad-replay/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"dependencies": {
"@agent-device/ad-script": "workspace:*",
"@agent-device/contracts": "workspace:*",
"@agent-device/kernel": "workspace:*"
"@agent-device/kernel": "workspace:*",
"@agent-device/selectors": "workspace:*"
},
"exports": {
".": {
Expand Down
20 changes: 3 additions & 17 deletions packages/ad-replay/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,9 @@
* `runAdReplay` is the `.ad` step loop; `AdReplayStepRuntime` is the runtime
* capability bag the daemon adapter
* (`session-replay-runtime-engine-adapter.ts`) implements to thread it,
* including the `ReplaySelectorPort` instance every daemon call site that
* threads a port value names by the SAME type. Two adapters implement the
* port: the production adapter (`src/daemon/replay-selector-port.ts`) and
* the in-memory adapter for this package's own contract suite
* (`src/__tests__/test-utils/in-memory-replay-selector-port.ts` — relocated
* there, #1478 P5 stage D, because package-internal code may not "reach back
* into root `src/`", R11, once its only remaining consumer was a root test).
* including selector resolution through the direct `@agent-device/selectors`
* package engine. Selector parsing and matching stay behind that package's
* string-only façade; no adapter value crosses this boundary.
*
* `./internal/target-verification.ts`'s four policy functions
* (`planPostResolutionTargetVerification`, `planPreDispatchTargetVerification`,
Expand Down Expand Up @@ -105,13 +101,3 @@ export type {
AdReplayGuardMismatchEvidence,
AdReplayLandmarkMismatchEvidence,
} from './internal/target-verification.ts';

export type {
ReplayRecordedTargetDisambiguation,
ReplayRecordedTargetPolicy,
ReplayRecordedTargetResolution,
ReplaySelectorCandidateOptions,
ReplaySelectorExpressionOutcome,
ReplaySelectorGrammar,
ReplaySelectorPort,
} from './internal/selector-port.ts';
3 changes: 0 additions & 3 deletions packages/ad-replay/src/internal/__tests__/step-loop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { runAdReplay } from '../step-loop.ts';
import type { AdReplayStepRuntime } from '../runtime-port-types.ts';
import type { SessionAction } from '@agent-device/contracts/session';
import type { TargetAnnotationV1 } from '@agent-device/contracts/replay';
import type { ReplaySelectorPort } from '../selector-port.ts';

/**
* #1554 fold-in: `resolveSuppressedTerminalCloseIndex` (the pure structural
Expand Down Expand Up @@ -63,7 +62,6 @@ function createFakeRuntime(params: { isRepairArmed?: () => boolean } = {}): {
const dispatched: string[] = [];
let armCount = 0;
const runtime: AdReplayStepRuntime = {
port: {} as ReplaySelectorPort,
beginTargetVerification: () => ({ kind: 'inactive' }),
captureObservation: async () => {
throw new Error('captureObservation: not used by this fixture (no targetEvidence)');
Expand Down Expand Up @@ -173,7 +171,6 @@ test("a post-dispatch target-binding mismatch reports the pre-step artifact snap

let receivedArtifactPaths: readonly string[] | undefined;
const runtime: AdReplayStepRuntime = {
port: {} as ReplaySelectorPort,
// Only `waitAction` carries `targetEvidence`, so this is only ever
// called for it — routed to the #1349 deferred-landmark path, which
// dispatches with a guard WITHOUT any capture/classify round trip.
Expand Down
Loading
Loading