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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions .github/workflows/package-smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
paths:
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'packages/**'
- 'tsdown.config.ts'
- 'scripts/build-android-helper.sh'
- 'scripts/build-xcuitest-apple.sh'
Expand All @@ -25,6 +27,8 @@ on:
paths:
- 'package.json'
- 'pnpm-lock.yaml'
- 'pnpm-workspace.yaml'
- 'packages/**'
- 'tsdown.config.ts'
- 'scripts/build-android-helper.sh'
- 'scripts/build-xcuitest-apple.sh'
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ prose in this repo, including this file.
## Principles (expensive lessons — each cost an incident)

- Guarantees erode at path boundaries. Any new dispatch path or fast path classifies its cells in
`src/contracts/interaction-guarantees.ts` first; the typechecker forces completeness, you supply
`packages/contracts/src/interaction-guarantees.ts` first; the typechecker forces completeness, you supply
honesty. ADR 0011.
- A registry claim is not a semantic check: never mark a cell `runner` without reading whether the
Swift code implements the guarantee's *definition*, not just a similar-sounding behavior.
Expand Down Expand Up @@ -54,7 +54,7 @@ declaration site rather than any map someone wrote down:
capabilities, MCP/CLI projection, batch policy, timeout policy — ADR 0008)
- daemon route ownership + request-policy traits: `src/daemon/daemon-command-registry.ts`
(parity-tested)
- interaction dispatch paths × guarantees: `src/contracts/interaction-guarantees.ts` (ADR 0011)
- interaction dispatch paths × guarantees: `packages/contracts/src/interaction-guarantees.ts` (ADR 0011)
- command names: `src/command-catalog.ts` — never re-create command string sets in handlers
- capabilities: `src/core/capabilities.ts` is the only home for command/device support checks

Expand All @@ -72,10 +72,10 @@ Invariants here are self-declaring gates. The correct response to a failure is t
the new thing — never to suppress or allowlist it.

- public CLI flags must be classified: `scripts/integration-progress-model.ts`
- guarantee matrix completeness + honesty: `src/contracts/__tests__/interaction-guarantees.test.ts`
- guarantee matrix completeness + honesty: `src/__tests__/contracts/interaction-guarantees.test.ts`
(gap waivers need a `trackingIssue`; the pin list changes only in reviewed diffs)
- every enforced/delegated matrix cell needs a contract scenario:
`src/contracts/__tests__/interaction-contract-coverage.test.ts` + `test/integration/interaction-contract/`
`src/__tests__/contracts/interaction-contract-coverage.test.ts` + `test/integration/interaction-contract/`
- interaction responses build only through `buildInteractionResponseData` (construction-guard test)
- every command declares a timeout policy on its descriptor (timeout-policy completeness test)
- TS/Swift rule parity: golden tables under `contracts/fixtures/`, consumed by vitest and the gated
Expand Down
10 changes: 5 additions & 5 deletions CONTEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ task touches:
center coordinate when a frame is available. This keeps target selection semantic while avoiding
`XCUIElement.tap()` post-action element re-resolution after normal navigation. tvOS remains
focus/remote-driven.
- Guarantee cell: one (dispatch path, guarantee) entry in `src/contracts/interaction-guarantees.ts`,
- Guarantee cell: one (dispatch path, guarantee) entry in `packages/contracts/src/interaction-guarantees.ts`,
classified as runtime/runner/delegated/inapplicable/waived. Completeness is a compile error;
honesty is gate-tested.
- Owned waiver: a `gap:`-prefixed waived cell carrying a `trackingIssue` URL. Waivers are diffable
Expand Down Expand Up @@ -227,7 +227,7 @@ task touches:

ADR 0011 (interaction guarantee contract) is the interaction-semantics counterpart of ADR 0008's
registry thesis: the dispatch-path × guarantee matrix is declared once in
`src/contracts/interaction-guarantees.ts`, completeness is type-enforced, honesty and coverage are
`packages/contracts/src/interaction-guarantees.ts`, completeness is type-enforced, honesty and coverage are
gate-enforced, and cross-language rules are pinned by golden parity tables. New dispatch paths and
guarantees are whole-matrix decisions, not local edits.

Expand Down Expand Up @@ -311,16 +311,16 @@ The perfect-shape refactor is complete and merged. Its end-state:
theirs.
- Type-cycle growth (R9). R4 keeps the VALUE import graph acyclic, so every remaining cycle is
created by type-only imports — free at runtime, invisible to R5/R6, and the largest single
obstacle to reading a subsystem in isolation: inside a strongly-connected component of 102 files,
obstacle to reading a subsystem in isolation: inside a strongly-connected component of 76 files,
no file has a self-contained slice. `TYPE_CYCLE_BASELINE`, derived from the zone ceilings in
`scripts/layering/daemon-modularity.ts`, ratchets it for **growth only**, deliberately unlike R6: reducing it
is a real refactor rather than a file move, so a hard equality would turn every unrelated
improvement into a baseline edit. A shrunk tree is reported in the success line instead of
failing. Hubs by in-component dependents: `runtime-contract.ts` (25),
`commands/runtime-types.ts` (21), `backend.ts` (15), `commands/runtime-common.ts` (12).
- Daemon modularity migration (R10). The same tooling-only declaration records R7 at 30
writer-owned fields / 42 owner-file claims, R9's 102 members by zone (`commands` 33,
`daemon-server` 30, `platforms` 19, `core` 12, root 5, `contracts` 2, `client` 1), and the four
writer-owned fields / 42 owner-file claims, R9's 76 members by zone (`commands` 33,
`daemon-server` 20, `core` 10, `platforms` 7, root 5, `client` 1), and the four
production importers of `daemon/types.ts` from outside daemon. R7 counts and external importers may
only shrink; no zone may grow inside R9, and replay/Maestro/replay-test engine files remain outside
it. This per-zone migration ratchet is intentionally stricter than R9's ordinary total-growth
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import XCTest

// Swift port of buildScrollGesturePlan from src/contracts/scroll-gesture.ts.
// Swift port of buildScrollGesturePlan from packages/contracts/src/scroll-gesture.ts.
//
// This is a deliberate two-place invariant: the daemon keeps the TS implementation (for Android,
// recording, and reported-pixels), and the runner places the gesture with this Swift copy. The
// parity test vectors at the bottom of this file mirror src/contracts/scroll-gesture.test.ts —
// parity test vectors at the bottom of this file mirror
// packages/contracts/src/scroll-gesture.test.ts —
// if you change the math in either language, update the other and both vector sets.
//
// All inputs here are positive (reference dims, travel, center), so Swift's `.rounded()`
Expand Down Expand Up @@ -64,7 +65,7 @@ func runnerScrollGesturePlan(

#if AGENT_DEVICE_RUNNER_UNIT_TESTS
extension RunnerTests {
// Cross-language parity vectors mirroring src/contracts/scroll-gesture.test.ts. Keep these
// Cross-language parity vectors mirroring packages/contracts/src/scroll-gesture.test.ts. Keep these
// in sync with the vitest vectors so the two buildScrollGesturePlan implementations cannot drift.

func testRunnerScrollGesturePlanMapsRelativeAmount() throws {
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0011-interaction-guarantee-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ parity only starts once the fixture tables (Layer 2) and contract scenarios
unwatched cell becomes impossible; an unproven cell is at least a visible,
owned claim.

`src/contracts/interaction-guarantees.ts` declares both axes and requires
`packages/contracts/src/interaction-guarantees.ts` declares both axes and requires
every cell to be classified:

```ts
Expand Down
4 changes: 2 additions & 2 deletions docs/adr/0012-interactive-replay.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Normative summary, one entry per decision. The binding contracts, amendments, an
`ref`/`exact` or `ref`/`label-fallback`, `direct-ios`/`not-observed`; coordinate dispatches and
executed maestro-fallbacks carry none. Disclosed alternatives are pre-action diagnostics, never
issued refs. Enforced as ADR 0011's `resolutionDisclosure` guarantee row
(`src/contracts/interaction-guarantees.ts`).
(`packages/contracts/src/interaction-guarantees.ts`).
3. **Recording writes `target-v1` identity evidence; replay verifies it before acting.** One
versioned JSON comment per element-targeting action carries identity (unique id, else
role+label, plus a leaf-anchored ancestry prefix), disambiguation signals (`sibling`,
Expand Down Expand Up @@ -1216,7 +1216,7 @@ zero on the happy path and paying only where reality diverged from the recording
a hint beats silently guessing" (`src/cli/parser/command-suggestions.ts:16-17`). Selector resolution
took the opposite default, and ADR 0011's own registry records that choice precisely: the
`disambiguation` cell for `runtime-selector` is classified `{ kind: 'runtime', via:
'...selectors-resolve.ts#resolveSelectorChain' }` (`src/contracts/interaction-guarantees.ts:176-179`)
'...selectors-resolve.ts#resolveSelectorChain' }` (`packages/contracts/src/interaction-guarantees.ts:176-179`)
— proving the heuristic runs consistently across paths, not that the caller is told it ran. That
default is not being revisited here; see the rejected hard-reject alternative below for why.
- **(f) Issue #1037 / PR #1040 is the direct, partial precedent.** A UNIQUE-but-wrong match (Apple
Expand Down
4 changes: 2 additions & 2 deletions docs/adr/0013-unified-gesture-plans.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ the Android planned-touch executor.

## Decision

Public gesture inputs normalize once in `src/contracts/gesture-normalization.ts`. This is the
Public gesture inputs normalize once in `packages/contracts/src/gesture-normalization.ts`. This is the
explicit public compatibility boundary: canonical semantic intent is produced before entering the
runtime. Deprecated arguments that have been removed (timed `swipe`, timed `gesture fling`,
`gesture rotate` `velocity`) are rejected with actionable `INVALID_ARGS` messages rather than
Expand All @@ -30,7 +30,7 @@ that do not appear on public surfaces; Maestro timed swipes use
`internal.gestureExecutionProfile: 'endpoint-hold'` to preserve iOS fast-swipe-then-hold behavior
while still routing through the canonical `pan` input.

The runtime plans canonical intent in `src/contracts/gesture-plan.ts`. Contact topology is separate
The runtime plans canonical intent in `packages/contracts/src/gesture-plan.ts`. Contact topology is separate
from motion:

- one contact: pan or fling with a complete pointer trajectory and an explicit execution profile;
Expand Down
4 changes: 2 additions & 2 deletions docs/adr/0018-unified-event-journal.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ intentional compatibility change rather than hidden under a byte-compatibility c

Normative summary of the proposal; contracts and rationale below.

- One **event catalog** (`src/contracts/events.ts`) is the single declaration site for every
- One **event catalog** (`packages/contracts/src/events.ts`) is the single declaration site for every
diagnostic/telemetry event kind: key, subsystem, default level, and derivation traits. Emitting a
kind not in the catalog is a compile error; consumers derive kind sets from traits, never from
parsing kind names.
Expand Down Expand Up @@ -100,7 +100,7 @@ genuinely telemetry. Progress is not one of them (decision 3).

### 1. Event catalog: kinds are declared data

`src/contracts/events.ts` exports one `EVENT_CATALOG` — a const object with one entry per event
`packages/contracts/src/events.ts` exports one `EVENT_CATALOG` — a const object with one entry per event
kind, keyed by today's diagnostics phase strings (no renames in this ADR):

```ts
Expand Down
4 changes: 2 additions & 2 deletions docs/agents/cli-flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A new flag touches only the layers that need to understand it. Stop at the layer where it stops
mattering — threading it further is the common failure, not stopping too early.

1. `src/contracts/cli-flags.ts`: add to `CliFlags`; add the definition to the matching
1. `packages/contracts/src/cli-flags.ts`: add to `CliFlags`; add the definition to the matching
`src/commands/cli-grammar/flag-definitions-*.ts` owner and the relevant group in `flag-groups.ts`
(for example `SNAPSHOT_FLAGS`). Then update the command family metadata/schema that exposes the
flag; find the owner with
Expand All @@ -26,7 +26,7 @@ mattering — threading it further is the common failure, not stopping too early
9. `scripts/integration-progress-model.ts`: classify the flag (device-observable vs
intentionally-outside). The architecture-progress gate fails CI on unclassified public flags.
10. If the flag changes interaction semantics, revisit the affected cells in
`src/contracts/interaction-guarantees.ts` (scope with `appliesTo` when the flag exists only on
`packages/contracts/src/interaction-guarantees.ts` (scope with `appliesTo` when the flag exists only on
some commands).

Command-only flags (like `find --first`) that never reach the platform layer usually stop at
Expand Down
2 changes: 1 addition & 1 deletion docs/agents/contract-projection-output-economy-spike.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Selected commands:
- `app-switcher`
- `tv-remote`

These commands already have closed neutral results in `src/contracts/navigation.ts`, typed
These commands already have closed neutral results in `packages/contracts/src/navigation.ts`, typed
`CommandResultMap` entries, executable definitions, Node client methods, and MCP output schemas.
They do not overlap the broad-return client methods narrowed under #1183 (since closed).

Expand Down
2 changes: 1 addition & 1 deletion docs/agents/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ The output tells you which gates to run and which live scenarios claim the behav
are loaded through `import()`.
- **live scenario owners** — the iOS simulator coverage manifest's owning scenario for each of
those commands, when that manifest is in the tree.
- **guarantee-matrix rows** — the ADR 0011 cells (`src/contracts/interaction-guarantees.ts`) whose
- **guarantee-matrix rows** — the ADR 0011 cells (`packages/contracts/src/interaction-guarantees.ts`) whose
`via` names the file, i.e. the guarantees your edit is the implementation of.

Lists are bounded (`--limit`, default 10) and always disclose what they hid; `--json` is
Expand Down
6 changes: 3 additions & 3 deletions fallow-baselines/health.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
"count": 1
}
},
"src/contracts/result-serialization.ts": {
"src/utils/result-serialization.ts": {
"crap_moderate": {
"count": 1
}
Expand Down Expand Up @@ -597,7 +597,7 @@
"src/utils/timeouts.ts:high impact",
"src/commands/interaction/output.ts:high impact",
"src/snapshot/snapshot-lines.ts:high impact",
"src/contracts/result-serialization.ts:high impact",
"src/utils/result-serialization.ts:high impact",
"src/utils/rect-center.ts:high impact",
"src/platforms/apple/core/app-launch.ts:complexity",
"src/utils/parsing.ts:high impact",
Expand Down Expand Up @@ -636,4 +636,4 @@
"src/daemon/client/daemon-client-lifecycle.ts:complexity",
"src/client/client-companion-tunnel-worker.ts:complexity"
]
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,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/kernel && tsc -p tsconfig.json && tsc -p examples/sdk/tsconfig.json",
"typecheck": "tsc -b packages/kernel packages/contracts && 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 @@ -245,6 +245,7 @@
"yaml": "^2.9.0"
},
"devDependencies": {
"@agent-device/contracts": "workspace:*",
"@agent-device/kernel": "workspace:*",
"@chenglou/freerange": "^0.0.1",
"@stryker-mutator/core": "9.6.1",
Expand Down
60 changes: 60 additions & 0 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "@agent-device/contracts",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "Shared agent-device command, snapshot, provider, and platform vocabulary. Internal workspace package bundled into the published agent-device artifact.",
"dependencies": {
"@agent-device/kernel": "workspace:*"
},
"exports": {
"./client": {
"types": "./src/facades/client.ts",
"default": "./src/facades/client.ts"
},
"./command": {
"types": "./src/facades/command.ts",
"default": "./src/facades/command.ts"
},
"./device": {
"types": "./src/facades/device.ts",
"default": "./src/facades/device.ts"
},
"./interaction": {
"types": "./src/facades/interaction.ts",
"default": "./src/facades/interaction.ts"
},
"./capture": {
"types": "./src/facades/capture.ts",
"default": "./src/facades/capture.ts"
},
"./platform": {
"types": "./src/facades/platform.ts",
"default": "./src/facades/platform.ts"
},
"./settings": {
"types": "./src/settings.ts",
"default": "./src/settings.ts"
},
"./session": {
"types": "./src/facades/session.ts",
"default": "./src/facades/session.ts"
},
"./recording": {
"types": "./src/facades/recording.ts",
"default": "./src/facades/recording.ts"
},
"./observability": {
"types": "./src/facades/observability.ts",
"default": "./src/facades/observability.ts"
},
"./remote": {
"types": "./src/facades/remote.ts",
"default": "./src/facades/remote.ts"
},
"./replay": {
"types": "./src/facades/replay.ts",
"default": "./src/facades/replay.ts"
}
}
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { daemonRuntimeSchema, type SessionRuntimeHints } from '@agent-device/kernel/contracts';
import { AppError } from '@agent-device/kernel/errors';
import { isRecord } from '../utils/parsing.ts';
import { isRecord } from './json.ts';

export const DEFAULT_BATCH_MAX_STEPS = 100;

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
SnapshotUnchanged,
SnapshotVisibility,
} from '@agent-device/kernel/snapshot';
import type { ScreenshotResultData } from '../utils/screenshot-result.ts';
import type { ScreenshotResultData } from './snapshot-types.ts';
import type {
AgentDeviceIdentifiers,
AgentDeviceRequestOverrides,
Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// The public API vocabulary for the read commands that resolve a selector (get / is / find).

import type { FindLocator } from '../selectors/find.ts';
import type { FindLocator } from './snapshot-types.ts';
import type {
FindSnapshotCommandOptions,
SelectorSnapshotCommandOptions,
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// inside the daemon module that happens to consume them first.

import type { DeviceInfo } from '@agent-device/kernel/device';
import type { DaemonRequest, LeaseBackend } from '@agent-device/kernel/contracts';
import type { LeaseBackend } from '@agent-device/kernel/contracts';
import type { DeviceInventoryRequest } from './device-inventory.ts';

export type DeviceLease = {
Expand All @@ -23,7 +23,8 @@ export type DeviceLease = {
};

export type LeaseLifecycleContext = {
req: DaemonRequest;
flags?: Readonly<Record<string, unknown>>;
cwd?: string;
};

export type LeaseLifecycleProvider = {
Expand Down
File renamed without changes.
22 changes: 22 additions & 0 deletions packages/contracts/src/diff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type SnapshotDiffLine = {
kind: 'added' | 'removed' | 'unchanged';
text: string;
/**
* Plain ref body (`e12`) of the current-tree node behind an added line.
* Only populated with `withRefs`; removed and unchanged lines never carry it.
*/
ref?: string;
};
export type SnapshotDiffSummary = {
additions: number;
removals: number;
unchanged: number;
};

export type DiffSnapshotCommandResult = {
mode: 'snapshot';
baselineInitialized: boolean;
summary: SnapshotDiffSummary;
lines: SnapshotDiffLine[];
warnings?: string[];
};
File renamed without changes.
6 changes: 6 additions & 0 deletions packages/contracts/src/facades/capture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export * from '../diff.ts';
export * from '../screenshot.ts';
export * from '../snapshot-capture-annotations.ts';
export * from '../snapshot-diagnostics.ts';
export * from '../snapshot-types.ts';
export * from '../viewport.ts';
Loading
Loading