refactor(capture-kit): complete ADR 0019 end state — relocate snapshot and recording zones (#2385)

* refactor(capture-kit): relocate snapshot and recording zones into capture-kit

Move the ADR 0019 end-state capture zones into @agent-device/capture-kit:

- src/snapshot/** -> packages/capture-kit/src/snapshot/** (presentation,
  freshness, scroll-edge-state, ios-snapshot-runtime, android occlusion)
- src/recording/** -> packages/capture-kit/src/recording/**
- src/core/snapshot-{chrome,state,tree-ingestion,node-lookup}.ts ->
  packages/capture-kit/src/
- src/snapshot-quality/ test -> capture-kit presentation tree (directory
  retires with its last file)

Pure renames: import re-pointing and gate updates follow in the next commit.
The snapshot-desktop-surface test parks in src/__tests__/ because it pins
the root eager-import-closure walker.

* refactor(capture-kit): re-point capture and recording consumers to the new subpaths

Rewires every consumer of the relocated snapshot/recording modules to the new @agent-device/capture-kit subpath exports, adds the 23 subpath entries to the capture-kit exports map, fixes the moved recording-scripts test's __dirname-relative paths for the deeper location, and records the completed migration in ADR 0019's end state.

* chore(gates): align layering, mutation, fallow and CI gates with the capture-kit relocation

Moves the executable-policy roots, presentation-owner constant, zone ranks, authority fixture, mutation sharding globs, stryker aliases, fallow baselines and the iOS workflow's android-owned paths-ignore entry onto the new packages/capture-kit paths, and extends the planted-red coverage to the new presentation-owner subpath.

* chore: point capture-domain source-of-truth comments at the relocated capture-kit modules

* test: point shutdown recording mock at capture-kit and cover interactor acquisition presentation

* test(capture-kit): update upstream presentation test imports

* chore(gates): follow relocated snapshot assembly in R74

* test(daemon): freeze prewarm deadline assertion clocks
This commit is contained in:
Michał Pierzchała
2026-09-08 12:41:39 +02:00
committed by GitHub
parent a6cf1b1fd4
commit 1f9d940bff
161 changed files with 451 additions and 193 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ on:
- 'packages/*/src/**/*.test.ts'
- 'packages/*/src/**/__tests__/**/*.ts'
- 'packages/platform-android/src/**'
- 'src/snapshot/snapshot-presentation/android/**'
- 'packages/capture-kit/src/snapshot/snapshot-presentation/android/**'
- 'packages/platform-harmonyos/src/**'
- 'packages/platform-linux/src/**'
- 'packages/platform-vega/src/**'
+19 -36
View File
@@ -902,42 +902,25 @@ target, unverified**, not a derived number.
The root files that remain are either declared host-port adapters (e.g. `adb-host`, bound only
by its named root file per Decision §1) or mechanics whose wave has not landed yet — the two are
not distinguished by filename and need per-file classification before a target is set.
- `src/recording`, `src/snapshot`, `src/snapshot-quality`, `src/screenshot-diff`. Corrected
2026-09-03: `packages/capture-kit/src/` already has broad production ownership across
recording (`screen-recording-live-handle.ts`, `screen-recording-completion.ts`,
`screen-recording-options.ts`, consumed by `platform-android`/`platform-apple`/
`platform-harmonyos`/`platform-web`'s `recording/runtime.ts`), screenshot/diff
(`png.ts`, `png-worker-client.ts`, imported by `src/screenshot-diff/screenshot-diff.ts`),
snapshot quality (`snapshot-quality-verdict.ts`, `snapshot-quality-backend-capabilities.ts`
`src/snapshot-quality/` now holds only a cross-package regression test, no production file),
occlusion (`snapshot-occlusion.ts`), audio/app-log (`audio-probe-runtime.ts`,
`app-log-live-handle.ts`, consumed by `platform-android`, `platform-apple`, and
`provider-limrun`), and iOS acquisition/engine (`ios-snapshot-acquisition.ts`,
`ios-snapshot-planning.ts`, `ios-snapshot-engine/`, consumed by
`src/snapshot/ios-snapshot-runtime.ts`). ADR §1's amendment already assigns this capture
domain to `capture-kit`; the domain mechanics are migrated. What remains under `src/` in
these four directories, verified file-by-file at HEAD, is daemon-facing composition with no
existing package equivalent, not unmigrated capture mechanics:
- `src/recording/{output-path,overlay,swift-cache,telemetry,video,video-webm}.ts` — video
playability polling, overlay burn-in, telemetry persistence, and output-path resolution,
imported only by `src/platform-runtime-screen-recording-*.ts` and
`src/daemon/handlers/record-runtime*.ts`. Equivalent: none.
- `src/snapshot/ios-snapshot-runtime.ts` and `snapshot-visibility.ts` — composition over the
capture-kit acquisition/planning/engine/semantics calls above. Equivalent: capture-kit
(mechanics already migrated; these are the composition callers).
- `src/snapshot/{android-replacement-surface-occlusion,rect-coverage,scroll-edge-state,
snapshot-desktop-surface,snapshot-diff,snapshot-evidence,snapshot-label-dedup,
snapshot-lines,snapshot-node-label,snapshot-timeout-policy}.ts` — none import capture-kit.
`android-replacement-surface-occlusion.ts` implements a distinct Android-specific
footprint algorithm, not the generic viewport pruning in capture-kit's
`snapshot-occlusion.ts`. Equivalent: none.
- `src/snapshot-quality/__tests__/warnings.test.ts` — the sole survivor in that directory;
exercises `renderSnapshotQualityWarnings` (root) against capture-kit's
`readSnapshotQualityVerdict`. Equivalent: capture-kit (production logic fully migrated).
- `src/screenshot-diff/*.ts` (region split/overlay/summarization/component composition) —
pixel-diff computation and PNG decode/encode already call capture-kit's `png`/
`png-worker-client`; the root files remain for daemon/CLI diff-report composition.
Equivalent: none (built atop already-migrated capture-kit mechanics).
- `src/recording`, `src/snapshot`, `src/snapshot-quality` — **migrated to
`packages/capture-kit/src/` (2026-09-07)**: the whole trees moved as-is (`recording/`,
`snapshot/` including its `snapshot-presentation/` and `snapshot-freshness/` subtrees),
together with the cross-package `warnings.test.ts` regression test. The four snapshot
modules from `src/core/` (`snapshot-chrome.ts`, `snapshot-state.ts`,
`snapshot-tree-ingestion.ts`, `snapshot-node-lookup.ts`, with their tests) joined the
package root. External consumers (daemon, commands, platform hosts, integration tests)
use package subpath exports (e.g. `@agent-device/capture-kit/snapshot-lines`,
`/recording-video`, `/ios-snapshot-runtime`); `snapshot-tree-ingestion` carries no
subpath because only `snapshot-state.ts` (same package) consumes it. Three tests stayed
at the root — `src/core/__tests__/snapshot-state.test.ts` and
`snapshot-chrome-android-statusbar.test.ts` (pinned to root-owned
`interaction-targeting` and the android UI-hierarchy fixtures) and
`src/__tests__/snapshot-desktop-surface.test.ts` (pinned to the root
eager-import-closure fixtures).
- `src/screenshot-diff/*.ts` (region split/overlay/summarization/component composition) —
pixel-diff computation and PNG decode/encode already call capture-kit's `png`/
`png-worker-client`; the root files remain for daemon/CLI diff-report composition.
Equivalent: none (built atop already-migrated capture-kit mechanics).
- `src/provider-device-runtime.ts`, `src/provider-device-runtimes.ts`,
`src/provider-limrun-runtime.ts`, `src/provider-webdriver.ts`. Mostly already thin: the bulk of
WebDriver and Limrun provider logic lives in `packages/provider-webdriver/src/` and
+5 -5
View File
@@ -456,7 +456,7 @@
"count": 2
}
},
"src/snapshot/snapshot-lines.ts": {
"packages/capture-kit/src/snapshot/snapshot-lines.ts": {
"crap_moderate": {
"count": 1
}
@@ -471,12 +471,12 @@
"count": 1
}
},
"src/snapshot/scroll-edge-state.ts": {
"packages/capture-kit/src/snapshot/scroll-edge-state.ts": {
"crap_moderate": {
"count": 1
}
},
"src/snapshot/snapshot-presentation/text-surface.ts": {
"packages/capture-kit/src/snapshot/snapshot-presentation/text-surface.ts": {
"crap_high": {
"count": 1
}
@@ -550,7 +550,7 @@
"packages/kernel/src/success-text.ts:high impact",
"src/cli.ts:complexity",
"src/commands/interaction/output.ts:high impact",
"src/snapshot/snapshot-lines.ts:high impact",
"packages/capture-kit/src/snapshot/snapshot-lines.ts:high impact",
"packages/kernel/src/rect-center.ts:high impact",
"packages/platform-apple/src/core/app-launch.ts:complexity",
"packages/kernel/src/record.ts:high impact",
@@ -562,7 +562,7 @@
"src/client/screenshot-result.ts:high impact",
"packages/platform-web/src/json-utils.ts:high impact",
"packages/platform-android/src/settings.ts:complexity",
"src/snapshot/snapshot-presentation/text-surface.ts:high impact",
"packages/capture-kit/src/snapshot/snapshot-presentation/text-surface.ts:high impact",
"src/daemon/handlers/session-test-sharding.ts:high impact",
"src/daemon/replay/internal/native-command.ts:complexity",
"packages/platform-apple/src/core/debug-symbols/utils.ts:high impact",
+92
View File
@@ -14,6 +14,10 @@
"types": "./src/index.ts",
"default": "./src/index.ts"
},
"./android-replacement-surface-occlusion": {
"types": "./src/snapshot/android-replacement-surface-occlusion.ts",
"default": "./src/snapshot/android-replacement-surface-occlusion.ts"
},
"./durable-capture": {
"types": "./src/durable-capture/index.ts",
"default": "./src/durable-capture/index.ts"
@@ -34,6 +38,10 @@
"types": "./src/ios-snapshot-planning.ts",
"default": "./src/ios-snapshot-planning.ts"
},
"./ios-snapshot-runtime": {
"types": "./src/snapshot/ios-snapshot-runtime.ts",
"default": "./src/snapshot/ios-snapshot-runtime.ts"
},
"./mobile-snapshot-semantics": {
"types": "./src/mobile-snapshot-semantics.ts",
"default": "./src/mobile-snapshot-semantics.ts"
@@ -66,10 +74,34 @@
"types": "./src/post-gesture-stability.ts",
"default": "./src/post-gesture-stability.ts"
},
"./quality-warnings": {
"types": "./src/snapshot/snapshot-presentation/quality-warnings.ts",
"default": "./src/snapshot/snapshot-presentation/quality-warnings.ts"
},
"./react-native-overlay": {
"types": "./src/react-native-overlay.ts",
"default": "./src/react-native-overlay.ts"
},
"./recording-output-path": {
"types": "./src/recording/output-path.ts",
"default": "./src/recording/output-path.ts"
},
"./recording-overlay": {
"types": "./src/recording/overlay.ts",
"default": "./src/recording/overlay.ts"
},
"./recording-telemetry": {
"types": "./src/recording/telemetry.ts",
"default": "./src/recording/telemetry.ts"
},
"./recording-video": {
"types": "./src/recording/video.ts",
"default": "./src/recording/video.ts"
},
"./repeated-nav-subtree": {
"types": "./src/snapshot/snapshot-presentation/repeated-nav-subtree.ts",
"default": "./src/snapshot/snapshot-presentation/repeated-nav-subtree.ts"
},
"./screenshot-density": {
"types": "./src/screenshot-density.ts",
"default": "./src/screenshot-density.ts"
@@ -82,14 +114,58 @@
"types": "./src/screenshot-overlay.ts",
"default": "./src/screenshot-overlay.ts"
},
"./scroll-edge-state": {
"types": "./src/snapshot/scroll-edge-state.ts",
"default": "./src/snapshot/scroll-edge-state.ts"
},
"./snapshot-chrome": {
"types": "./src/snapshot-chrome.ts",
"default": "./src/snapshot-chrome.ts"
},
"./snapshot-desktop-projection": {
"types": "./src/snapshot-desktop-projection.ts",
"default": "./src/snapshot-desktop-projection.ts"
},
"./snapshot-desktop-surface": {
"types": "./src/snapshot/snapshot-desktop-surface.ts",
"default": "./src/snapshot/snapshot-desktop-surface.ts"
},
"./snapshot-diff": {
"types": "./src/snapshot/snapshot-diff.ts",
"default": "./src/snapshot/snapshot-diff.ts"
},
"./snapshot-evidence": {
"types": "./src/snapshot/snapshot-evidence.ts",
"default": "./src/snapshot/snapshot-evidence.ts"
},
"./snapshot-freshness": {
"types": "./src/snapshot/snapshot-freshness/index.ts",
"default": "./src/snapshot/snapshot-freshness/index.ts"
},
"./snapshot-label-dedup": {
"types": "./src/snapshot/snapshot-label-dedup.ts",
"default": "./src/snapshot/snapshot-label-dedup.ts"
},
"./snapshot-lines": {
"types": "./src/snapshot/snapshot-lines.ts",
"default": "./src/snapshot/snapshot-lines.ts"
},
"./snapshot-node-lookup": {
"types": "./src/snapshot-node-lookup.ts",
"default": "./src/snapshot-node-lookup.ts"
},
"./snapshot-occlusion": {
"types": "./src/snapshot-occlusion.ts",
"default": "./src/snapshot-occlusion.ts"
},
"./snapshot-presentation-android-helper": {
"types": "./src/snapshot/snapshot-presentation/android/helper.ts",
"default": "./src/snapshot/snapshot-presentation/android/helper.ts"
},
"./snapshot-presentation-ios-transitions-fixtures": {
"types": "./src/snapshot/snapshot-presentation/ios/transitions.fixtures.ts",
"default": "./src/snapshot/snapshot-presentation/ios/transitions.fixtures.ts"
},
"./snapshot-quality-backend-capabilities": {
"types": "./src/snapshot-quality-backend-capabilities.ts",
"default": "./src/snapshot-quality-backend-capabilities.ts"
@@ -101,6 +177,22 @@
"./snapshot-rect-projection": {
"types": "./src/snapshot-rect-projection.ts",
"default": "./src/snapshot-rect-projection.ts"
},
"./snapshot-state": {
"types": "./src/snapshot-state.ts",
"default": "./src/snapshot-state.ts"
},
"./snapshot-timeout-policy": {
"types": "./src/snapshot/snapshot-timeout-policy.ts",
"default": "./src/snapshot/snapshot-timeout-policy.ts"
},
"./snapshot-visibility": {
"types": "./src/snapshot/snapshot-visibility.ts",
"default": "./src/snapshot/snapshot-visibility.ts"
},
"./text-surface": {
"types": "./src/snapshot/snapshot-presentation/text-surface.ts",
"default": "./src/snapshot/snapshot-presentation/text-surface.ts"
}
},
"devDependencies": {
@@ -0,0 +1,8 @@
export function likelyPlayableWebmContainer(): Buffer {
// One 16x16 VP8 keyframe generated by ffmpeg. Unlike a marker-only synthetic fixture,
// ffprobe identifies this as a WebM video stream with a 40ms duration.
return Buffer.from(
'GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQJChYECGFOAZwEAAAAAAAHpEU2bdLpNu4tTq4QVSalmU6yBoU27i1OrhBZUrmtTrIHYTbuMU6uEElTDZ1OsggElTbuMU6uEHFO7a1OsggHT7AEAAAAAAABZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmsirXsYMPQkBNgI1MYXZmNjIuMTIuMTAxV0GNTGF2ZjYyLjEyLjEwMUSJiEBEAAAAAAAAFlSua8iuAQAAAAAAAD/XgQFzxYgNIomR4viG6pyBACK1nIN1bmSIgQCGhVZfVlA4g4EBI+ODhAJiWgDgkLCBELqBEJqBAlWwhFW5gQESVMNn/HNzoGPAgGfImkWjh0VOQ09ERVJEh41MYXZmNjIuMTIuMTAxc3PWY8CLY8WIDSKJkeL4hupnyKFFo4dFTkNPREVSRIeUTGF2YzYyLjI4LjEwMSBsaWJ2cHhnyKFFo4hEVVJBVElPTkSHkzAwOjAwOjAwLjA0MDAwMDAwMAAfQ7Z1qOeBAKOjgQAAgBACAJ0BKhAAEAAARwiFhYiFhIgCAgAMDWAA/v+rUIAcU7trkbuPs4EAt4r3gQHxggGm8IED',
'base64',
);
}
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import { mkdtempForTestSync } from '../../__tests__/test-utils/tmp-dir.ts';
import { mkdtempForTestSync } from '../../tmp-dir.fixtures.ts';
vi.mock('@agent-device/host-kit/command', async (importOriginal) => {
const fs = await import('node:fs');
@@ -8,9 +8,9 @@ import { getRecordingOverlaySupportWarning } from '../overlay.ts';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const recordingScriptsDir = path.resolve(
__dirname,
'../../../apple/runner/AgentDeviceRunner/RecordingScripts',
'../../../../../apple/runner/AgentDeviceRunner/RecordingScripts',
);
const recordingTestSupportDir = path.resolve(__dirname, '../../../test/integration/support');
const recordingTestSupportDir = path.resolve(__dirname, '../../../../../test/integration/support');
const SWIFT_TYPECHECK_TIMEOUT_MS = 60_000;
let swiftCompilerPath = 'swiftc';
let swiftSdkPath = '';
@@ -1,7 +1,7 @@
import { afterEach, beforeEach, expect, test, vi } from 'vitest';
import fs from 'node:fs';
import path from 'node:path';
import { mkdtempForTestSync } from '../__tests__/test-utils/tmp-dir.ts';
import { mkdtempForTestSync } from '../tmp-dir.fixtures.ts';
vi.mock(import('@agent-device/host-kit/command'), async (importOriginal) => ({
...(await importOriginal()),
@@ -2,7 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { expect, test } from 'vitest';
import { likelyPlayableWebmContainer } from '../__tests__/test-utils/video-fixtures.ts';
import { mkdtempForTestSync } from '../__tests__/test-utils/tmp-dir.ts';
import { mkdtempForTestSync } from '../tmp-dir.fixtures.ts';
import { hasPlayableWebmStructure } from './video-webm.ts';
const directory = mkdtempForTestSync('agent-device-video-webm-structure-');
@@ -2,7 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { expect, test } from 'vitest';
import { likelyPlayableWebmContainer } from '../__tests__/test-utils/video-fixtures.ts';
import { mkdtempForTestSync } from '../__tests__/test-utils/tmp-dir.ts';
import { mkdtempForTestSync } from '../tmp-dir.fixtures.ts';
import { isPlayableVideo } from './video.ts';
const directory = mkdtempForTestSync('agent-device-video-webm-');
@@ -1,7 +1,7 @@
import { test } from 'vitest';
import assert from 'node:assert/strict';
import { attachRefs, type RawSnapshotNode, type SnapshotNode } from '@agent-device/kernel/snapshot';
import { collectSettleChromeRefs } from '../snapshot-chrome.ts';
import { collectSettleChromeRefs } from './snapshot-chrome.ts';
function refFor(nodes: SnapshotNode[], label: string): string {
const node = nodes.find((candidate) => candidate.label === label);
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import { attachRefs } from '@agent-device/kernel/snapshot';
import { findNodeByLabel, resolveRefLabel } from '../snapshot-node-lookup.ts';
import { findNodeByLabel, resolveRefLabel } from './snapshot-node-lookup.ts';
test('findNodeByLabel matches label, value, and identifier case-insensitively', () => {
const nodes = attachRefs([
@@ -1,6 +1,6 @@
import type { SnapshotState } from '@agent-device/kernel/snapshot';
import { extractNodeText, isMeaningfulLabel } from '@agent-device/contracts/snapshot';
import { findNodeByLabel } from '../snapshot/snapshot-node-label.ts';
import { findNodeByLabel } from './snapshot/snapshot-node-label.ts';
export { findNodeByLabel };
@@ -17,11 +17,11 @@ import {
import {
annotateCoveredSnapshotNodes,
annotateSnapshotNodesCoveredByPolicy,
} from '@agent-device/capture-kit/snapshot-occlusion';
import { coveredAndroidReplacementNodeIndexes } from '../snapshot/android-replacement-surface-occlusion.ts';
import { scopeSnapshotNodes } from '@agent-device/capture-kit/snapshot-desktop-projection';
import { normalizeSnapshotTree, pruneGroupNodes } from '../core/snapshot-tree-ingestion.ts';
import { iosSnapshotComparisonIdentityKey } from '@agent-device/capture-kit/ios-snapshot-planning';
} from './snapshot-occlusion.ts';
import { coveredAndroidReplacementNodeIndexes } from './snapshot/android-replacement-surface-occlusion.ts';
import { scopeSnapshotNodes } from './snapshot-desktop-projection.ts';
import { normalizeSnapshotTree, pruneGroupNodes } from './snapshot-tree-ingestion.ts';
import { iosSnapshotComparisonIdentityKey } from './ios-snapshot-planning.ts';
import type { IosSnapshotComparisonIdentity } from '@agent-device/contracts/ios-snapshot';
/**
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import { normalizeSnapshotTree, pruneGroupNodes } from '../snapshot-tree-ingestion.ts';
import { normalizeSnapshotTree, pruneGroupNodes } from './snapshot-tree-ingestion.ts';
test('pruneGroupNodes drops unlabeled group wrappers and rebalances depth', () => {
const raw = [
@@ -1,9 +1,10 @@
import type { Rect } from '@agent-device/kernel/snapshot';
import fc from 'fast-check';
import { expect, test } from 'vitest';
import { PROPERTY_RUNS } from '../../__tests__/test-utils/property-arbitraries.ts';
import { unionCoverage } from '../rect-coverage.ts';
const PROPERTY_RUNS = 100;
const smallRect = fc.record({
x: fc.integer({ min: -4, max: 20 }),
y: fc.integer({ min: -4, max: 20 }),
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { test, vi } from 'vitest';
import type { SnapshotCaptureAnnotations } from '@agent-device/contracts/capture';
import { makeSnapshotState } from '../../__tests__/test-utils/snapshot-builders.ts';
import { makeSnapshotState } from '../../snapshot-state.fixtures.ts';
import { captureFreshnessRecoveredAttempt } from '../snapshot-freshness/recovery.ts';
import type {
SnapshotFreshnessReason,
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import type { RawSnapshotNode } from '@agent-device/kernel/snapshot';
import { makeSnapshotState } from '../../__tests__/test-utils/snapshot-builders.ts';
import { makeSnapshotState } from '../../snapshot-state.fixtures.ts';
import {
androidFreshnessReason,
buildSnapshotSignatures,
@@ -1,7 +1,7 @@
import { expect, test } from 'vitest';
import { attachRefs, type RawSnapshotNode } from '@agent-device/kernel/snapshot';
import { buildSnapshotVisibility } from '../snapshot-visibility.ts';
import { presentIosInteractiveSnapshot } from '@agent-device/capture-kit/ios-snapshot-engine';
import { presentIosInteractiveSnapshot } from '../../ios-snapshot-engine/index.ts';
function buildSnapshotState(data: { nodes?: RawSnapshotNode[]; backend?: 'xctest' }) {
return {
@@ -2,11 +2,8 @@ import {
IosSnapshotEngineError,
publishIosSnapshot,
toIosSnapshotEngineErrorDetails,
} from '@agent-device/capture-kit/ios-snapshot-engine';
import {
createIosSnapshotRequest,
deriveIosCaptureHint,
} from '@agent-device/capture-kit/ios-snapshot-planning';
} from '../ios-snapshot-engine/index.ts';
import { createIosSnapshotRequest, deriveIosCaptureHint } from '../ios-snapshot-planning.ts';
import type {
IosAcquisitionResidue,
IosSnapshotFact,
@@ -1,4 +1,4 @@
import { deriveMobileSnapshotHiddenContentHints } from '@agent-device/capture-kit/mobile-snapshot-semantics';
import { deriveMobileSnapshotHiddenContentHints } from '../../mobile-snapshot-semantics.ts';
import {
createSnapshotVisibility,
isScrollableNodeLike,
@@ -1,5 +1,5 @@
import { expect, test } from 'vitest';
import { presentIosInteractiveSnapshot } from '@agent-device/capture-kit/ios-snapshot-engine';
import { presentIosInteractiveSnapshot } from '../../../ios-snapshot-engine/index.ts';
import {
closedComposerWithRetainedActionShelfNodes,
closedComposerWithRetainedRegularTreeActionNodes,
@@ -1,6 +1,6 @@
import { expect, test } from 'vitest';
import { elementClassicRoomListNodes, legitimatelyLabeledCellNodes } from './rows.fixtures.ts';
import { presentIosInteractiveSnapshot } from '@agent-device/capture-kit/ios-snapshot-engine';
import { presentIosInteractiveSnapshot } from '../../../ios-snapshot-engine/index.ts';
test('iOS row presentation associates generic room cells with their descendant titles', () => {
const nodes = presentIosInteractiveSnapshot(elementClassicRoomListNodes);
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import { readSnapshotQualityVerdict } from '@agent-device/capture-kit/snapshot-quality-verdict';
import { renderSnapshotQualityWarnings } from '../../snapshot/snapshot-presentation/quality-warnings.ts';
import { readSnapshotQualityVerdict } from '../../snapshot-quality-verdict.ts';
import { renderSnapshotQualityWarnings } from './quality-warnings.ts';
const sharedRecoveryReason =
'iOS XCTest snapshot failed while serializing the accessibility tree. Error kAXErrorIllegalArgument getting snapshot for element <AXUIElementRef 0x1>';
@@ -1,4 +1,4 @@
import { buildMobileSnapshotPresentation } from '@agent-device/capture-kit/mobile-snapshot-semantics';
import { buildMobileSnapshotPresentation } from '../mobile-snapshot-semantics.ts';
import {
usesMobileSnapshotPresentation,
type SnapshotState,
@@ -2,7 +2,7 @@ import type { SnapshotNode } from '@agent-device/kernel/snapshot';
/**
* Android status-bar/navigation-bar chrome identity, shared by the settle-chrome
* classifier (`core/snapshot-chrome.ts`) and the helper content classifier
* classifier (`packages/capture-kit/src/snapshot-chrome.ts`) and the helper content classifier
* (`packages/platform-android/src/snapshot-content-recovery.ts`). SystemUI also hosts actionable
* overlays volume panel, media pickers, the shade itself so chrome is never a
* package-level fact: only the status/nav-bar container's subtree is chrome.
+1 -1
View File
@@ -56,7 +56,7 @@ export const SELECTOR_KEY_NAMES: readonly SelectorKey[] = [...ALL_KEYS];
// Role/element-type words that show up as accessibility roles, not selector keys (e.g. the
// `button` in `button="Push Article"`). Superset of the ROLE_LABELS vocabulary in
// src/snapshot/snapshot-lines.ts (plus a few common role words like list/tab/alert/dialog/header,
// packages/capture-kit/src/snapshot/snapshot-lines.ts (plus a few common role words like list/tab/alert/dialog/header,
// minus valid selector keys such as `text`, which ALL_KEYS short-circuits before this set is
// consulted), kept locally to avoid a selectors -> snapshot layering dependency. Drifts silently
// if ROLE_LABELS grows; that's fine here since this only sharpens a hint, not validates anything.
@@ -35,7 +35,7 @@ import type { ConnectVerification } from '../../src/cli/connection/connect-provi
import type { RemoteConnectionState } from '../../src/remote/remote-connection-state.ts';
import { AppError, normalizeError } from '@agent-device/kernel/errors';
import type { SnapshotQualityVerdict } from '@agent-device/kernel/snapshot';
import { renderSnapshotQualityWarnings } from '../../src/snapshot/snapshot-presentation/quality-warnings.ts';
import { renderSnapshotQualityWarnings } from '@agent-device/capture-kit/quality-warnings';
import { printHumanError } from '../../src/commands/output/error.ts';
import { formatSnapshotText } from '../../src/commands/output/snapshot.ts';
+15 -7
View File
@@ -46,9 +46,9 @@ function authorityFixture(): Map<string, string> {
"import { SessionStore } from './session-store.ts';",
].join('\n'),
'src/daemon/type-consumer.ts': "import type { SessionStore } from './session-store.ts';\n",
'src/snapshot/policy-consumer.ts': [
"import type { SessionState } from '../daemon/session-state.ts';",
"import type { SessionRef } from '../daemon/session-state.ts';",
'packages/capture-kit/src/snapshot/policy-consumer.ts': [
"import type { SessionState } from '../../../../src/daemon/session-state.ts';",
"import type { SessionRef } from '../../../../src/daemon/session-state.ts';",
"import './ordinary-target.ts';",
].join('\n'),
'src/daemon/ordinary-consumer.ts': [
@@ -62,7 +62,7 @@ function authorityFixture(): Map<string, string> {
'src/daemon/session-script-publication-capability.ts':
'export function isSessionRecording() {}\n',
'src/daemon/session-state-store.ts': 'export const SessionState = 1;\n',
'src/snapshot/ordinary-target.ts': 'export const ordinary = 1;\n',
'packages/capture-kit/src/snapshot/ordinary-target.ts': 'export const ordinary = 1;\n',
'packages/contracts/src/facades/client.ts': 'export type ClientShape = string;\n',
'packages/contracts/src/facades/capture.ts': 'export type CaptureShape = string;\n',
'packages/contracts/src/facades/replay.ts': 'export type ReplayShape = string;\n',
@@ -108,7 +108,11 @@ test('authority overlay uses declared roots and symbols, keeps kind separate, an
labels: ['live-state-authority'],
});
assert.deepEqual(
graphEdge(graph, 'src/snapshot/policy-consumer.ts', 'src/snapshot/ordinary-target.ts'),
graphEdge(
graph,
'packages/capture-kit/src/snapshot/policy-consumer.ts',
'packages/capture-kit/src/snapshot/ordinary-target.ts',
),
{ kind: 'value', labels: ['executable-policy'] },
);
assert.deepEqual(
@@ -122,12 +126,16 @@ test('authority overlay uses declared roots and symbols, keeps kind separate, an
const stateEdges = resolveImportEdges(files, authorityWorkspaceTargets()).filter(
(edge) =>
edge.file === 'src/snapshot/policy-consumer.ts' &&
edge.file === 'packages/capture-kit/src/snapshot/policy-consumer.ts' &&
edge.target === 'src/daemon/session-state.ts',
);
assert.equal(stateEdges.length, 2, 'the fixture must exercise raw same-pair imports');
assert.deepEqual(
graphEdge(graph, 'src/snapshot/policy-consumer.ts', 'src/daemon/session-state.ts'),
graphEdge(
graph,
'packages/capture-kit/src/snapshot/policy-consumer.ts',
'src/daemon/session-state.ts',
),
{
kind: 'type',
labels: ['live-state-shape', 'executable-policy'],
+1 -1
View File
@@ -72,7 +72,7 @@ hint: The UI kept changing for the whole settle budget (animation, carousel, or
// Recovered snapshot: the private-ax fallback fired but still exposed
// actionable refs. Warning wording is renderSnapshotQualityWarnings
// (src/snapshot/snapshot-presentation/quality-warnings.ts); lines are the structured snapshot
// (packages/capture-kit/src/snapshot/snapshot-presentation/quality-warnings.ts); lines are the structured snapshot
// renderer (src/commands/output/snapshot.ts formatSnapshotText).
export const PRIVATE_AX_RECOVERY_SAMPLE = {
command: 'agent-device snapshot -i',
+1 -1
View File
@@ -235,7 +235,7 @@ export const ARCHITECTURE_OWNERSHIP = {
{
name: 'snapshot-policy',
kind: 'executable-policy',
roots: ['src/snapshot/'],
roots: ['packages/capture-kit/src/snapshot/'],
forbiddenTargetRoots: ['src/daemon/'],
},
],
@@ -56,7 +56,7 @@ export function contractsImplementationAuthorityViolations(
violation(
file.path,
1,
'contracts may not own snapshot quality warning rendering; that presentation policy belongs in src/snapshot/snapshot-presentation',
'contracts may not own snapshot quality warning rendering; that presentation policy belongs in packages/capture-kit/src/snapshot/snapshot-presentation',
),
);
}
+1 -1
View File
@@ -253,7 +253,7 @@ test('classifyZone separates the ranked spine from intentionally-unranked zones'
// Every satellite zone joined the spine; only the composition root stays out, because R2
// forbids daemon/ from importing commands/ so the files that wire them cannot be ranked.
assert.equal(classifyZone('mcp'), 'ranked');
assert.equal(classifyZone('snapshot'), 'ranked');
assert.equal(classifyZone('screenshot-diff'), 'ranked');
// A zone that is neither ranked nor listed peripheral must be flagged, never
// silently treated as back-edge-free.
assert.equal(classifyZone('not-a-real-zone'), 'unclassified');
-2
View File
@@ -41,13 +41,11 @@ const TARGET_DAG_RANK = new Map([
['command-registry', 1],
['contracts', 1],
['maestro', 1],
['recording', 1],
['replay-test', 1],
['request', 1],
['screenshot-diff', 1],
['selectors', 1],
['session-journal', 1],
['snapshot', 1],
['core', 2],
['cli-schema', 3],
['commands', 3],
@@ -392,11 +392,13 @@ test('the real tree parses, declares, and passes R11', () => {
);
assert.deepEqual([...captureKitPackage.exportTargets.keys()].sort(), [
'@agent-device/capture-kit',
'@agent-device/capture-kit/android-replacement-surface-occlusion',
'@agent-device/capture-kit/durable-capture',
'@agent-device/capture-kit/durable-json',
'@agent-device/capture-kit/ios-snapshot-acquisition',
'@agent-device/capture-kit/ios-snapshot-engine',
'@agent-device/capture-kit/ios-snapshot-planning',
'@agent-device/capture-kit/ios-snapshot-runtime',
'@agent-device/capture-kit/mobile-snapshot-semantics',
'@agent-device/capture-kit/png',
'@agent-device/capture-kit/png-crop',
@@ -405,15 +407,36 @@ test('the real tree parses, declares, and passes R11', () => {
'@agent-device/capture-kit/png-size',
'@agent-device/capture-kit/png-worker-client',
'@agent-device/capture-kit/post-gesture-stability',
'@agent-device/capture-kit/quality-warnings',
'@agent-device/capture-kit/react-native-overlay',
'@agent-device/capture-kit/recording-output-path',
'@agent-device/capture-kit/recording-overlay',
'@agent-device/capture-kit/recording-telemetry',
'@agent-device/capture-kit/recording-video',
'@agent-device/capture-kit/repeated-nav-subtree',
'@agent-device/capture-kit/screenshot-density',
'@agent-device/capture-kit/screenshot-diff-pixels',
'@agent-device/capture-kit/screenshot-overlay',
'@agent-device/capture-kit/scroll-edge-state',
'@agent-device/capture-kit/snapshot-chrome',
'@agent-device/capture-kit/snapshot-desktop-projection',
'@agent-device/capture-kit/snapshot-desktop-surface',
'@agent-device/capture-kit/snapshot-diff',
'@agent-device/capture-kit/snapshot-evidence',
'@agent-device/capture-kit/snapshot-freshness',
'@agent-device/capture-kit/snapshot-label-dedup',
'@agent-device/capture-kit/snapshot-lines',
'@agent-device/capture-kit/snapshot-node-lookup',
'@agent-device/capture-kit/snapshot-occlusion',
'@agent-device/capture-kit/snapshot-presentation-android-helper',
'@agent-device/capture-kit/snapshot-presentation-ios-transitions-fixtures',
'@agent-device/capture-kit/snapshot-quality-backend-capabilities',
'@agent-device/capture-kit/snapshot-quality-verdict',
'@agent-device/capture-kit/snapshot-rect-projection',
'@agent-device/capture-kit/snapshot-state',
'@agent-device/capture-kit/snapshot-timeout-policy',
'@agent-device/capture-kit/snapshot-visibility',
'@agent-device/capture-kit/text-surface',
]);
const provisionKitPackage = packages.find((pkg) => pkg.name === '@agent-device/provision-kit');
@@ -57,6 +57,26 @@ test('R73 rejects an out-of-adapter provider presentation import', () => {
);
});
test('R73 rejects a provider import of the capture-kit presentation runtime subpath', () => {
const result = violations(
new Map([
[
providerHelper,
`import { presentIosSnapshot } from '@agent-device/capture-kit/ios-snapshot-runtime';\nvoid presentIosSnapshot;\n`,
],
]),
);
assert.ok(
result.some(
(entry) =>
entry.rule === PROVIDER_SNAPSHOT_PRESENTATION_RULE &&
entry.file === providerHelper &&
entry.message.includes(IOS_SNAPSHOT_PRESENTATION_OWNER),
),
JSON.stringify(result),
);
});
for (const planted of [
{
name: 'a planted provider residue discard',
@@ -1,7 +1,7 @@
// Catches: a provider-* package acquiring an iOS snapshot outside the capture-kit acquisition
// entrypoint, or presenting it outside src/snapshot/ios-snapshot-runtime.ts — the exact split
// R72's engine convergence closed for the runner layer, mirrored here one layer up for the
// provider packages that call into it.
// entrypoint, or presenting it outside packages/capture-kit/src/snapshot/ios-snapshot-runtime.ts
// — the exact split R72's engine convergence closed for the runner layer, mirrored here one
// layer up for the provider packages that call into it.
// Evidence: 7ee1a5ded7 (#2233) carried provider acquisitions through this one presentation
// owner, the change this policy was written to hold in place.
// Cost: 195 LOC (111 rule + 84 test).
@@ -9,7 +9,7 @@
// reaching presentation only through @agent-device/capture-kit/ios-snapshot-acquisition, and
// never constructing, discarding, or reassigning acquisition residue, no longer matter. An
// exports map cannot replace it: it restricts external specifiers, not the transitive walk
// into src/snapshot/ or a provider-local `residue` property or assignment.
// into packages/capture-kit/src/snapshot/ or a provider-local `residue` property or assignment.
import { parseSync } from 'oxc-parser';
import type { LayeringViolation, ResolvedImportEdge } from './model.ts';
@@ -18,7 +18,8 @@ import { memberPath, propertyName, visitAst } from './layering-ast.ts';
export const PROVIDER_SNAPSHOT_PRESENTATION_RULE = 'R73 provider-snapshot-presentation-ownership';
export const IOS_SNAPSHOT_ACQUISITION_ENTRYPOINT =
'@agent-device/capture-kit/ios-snapshot-acquisition';
export const IOS_SNAPSHOT_PRESENTATION_OWNER = 'src/snapshot/ios-snapshot-runtime.ts';
export const IOS_SNAPSHOT_PRESENTATION_OWNER =
'packages/capture-kit/src/snapshot/ios-snapshot-runtime.ts';
const PROVIDER_SOURCE = /^packages\/provider-[^/]+\/src\//;
const IOS_SNAPSHOT_CAPTURE_KIT_ROOT = 'packages/capture-kit/src/ios-snapshot-';
@@ -109,7 +110,7 @@ function residueViolations(providerFile: string, source: string): LayeringViolat
function isPresentationTarget(target: string): boolean {
return (
target === IOS_SNAPSHOT_PRESENTATION_OWNER ||
target.startsWith('src/snapshot/') ||
target.startsWith('packages/capture-kit/src/snapshot/') ||
(target.startsWith(IOS_SNAPSHOT_CAPTURE_KIT_ROOT) &&
target !== 'packages/capture-kit/src/ios-snapshot-acquisition.ts')
);
@@ -12,7 +12,7 @@ import { workspaceSpecifierTargets } from './package-boundaries.ts';
import { listTrackedProductionSources } from './tracked-sources.ts';
const repoRoot = path.resolve(import.meta.dirname, '../..');
const assemblyFile = 'src/core/snapshot-state.ts';
const assemblyFile = 'packages/capture-kit/src/snapshot-state.ts';
const producerAdapter = 'packages/platform-apple/src/snapshot-source/adapter.ts';
function currentSources(overrides: ReadonlyMap<string, string> = new Map()): Map<string, string> {
@@ -25,7 +25,7 @@ export const SNAPSHOT_ASSEMBLY_PRESENTATION_RULE = 'R74 snapshot-assembly-presen
* refs; it does not present, and it does not know which channel it is holding.
*/
export const SNAPSHOT_ASSEMBLY_FILES: readonly string[] = [
'src/core/snapshot-state.ts',
'packages/capture-kit/src/snapshot-state.ts',
'src/daemon/snapshot-capture.ts',
];
@@ -52,12 +52,12 @@ test('the boundary reports a facet module that reaches back into the daemon', ()
const sources = new Map([
['src/daemon/types.ts', 'export type SessionState = { id: string };\n'],
[
'src/snapshot/snapshot-freshness/android.ts',
"import type { SessionState } from '../../daemon/types.ts';\nexport type X = SessionState;\n",
'packages/capture-kit/src/snapshot/snapshot-freshness/android.ts',
"import type { SessionState } from '../../../../../src/daemon/types.ts';\nexport type X = SessionState;\n",
],
]);
assert.deepEqual(daemonImportsFromSnapshotFacet(sources), [
'src/snapshot/snapshot-freshness/android.ts:1 -> src/daemon/types.ts',
'packages/capture-kit/src/snapshot/snapshot-freshness/android.ts:1 -> src/daemon/types.ts',
]);
});
+8 -2
View File
@@ -61,8 +61,14 @@ export const KERNEL_MODULES: readonly KernelModule[] = [
{
id: 'scroll-edge-state',
label: 'Scroll edge-state detection',
mutate: ['src/snapshot/scroll-edge-state.ts', 'src/snapshot/scroll-edge-state/**/*.ts'],
owns: ['src/snapshot/scroll-edge-state.ts', 'src/snapshot/scroll-edge-state/'],
mutate: [
'packages/capture-kit/src/snapshot/scroll-edge-state.ts',
'packages/capture-kit/src/snapshot/scroll-edge-state/**/*.ts',
],
owns: [
'packages/capture-kit/src/snapshot/scroll-edge-state.ts',
'packages/capture-kit/src/snapshot/scroll-edge-state/',
],
},
{
id: 'selectors',
+7 -2
View File
@@ -80,9 +80,14 @@ test('a lane-tooling diff selects real mutants', () => {
// The weekly sweep is the kernel report; selecting on derived ownership would
// run the full ten-shard sweep on most PRs for a report nobody gates on.
test('a kernel diff selects nothing — only a harness diff spends mutants', () => {
const dir = worktreeWithCommit('kernel', ['src/snapshot/scroll-edge-state.ts']);
const dir = worktreeWithCommit('kernel', [
'packages/capture-kit/src/snapshot/scroll-edge-state.ts',
]);
assert.deepEqual(listAffected(dir), []);
assert.deepEqual(affectedMatrixFor(['src/snapshot/scroll-edge-state.ts']), []);
assert.deepEqual(
affectedMatrixFor(['packages/capture-kit/src/snapshot/scroll-edge-state.ts']),
[],
);
});
test('a docs-only diff selects nothing', () => {
@@ -11,7 +11,7 @@ import {
resetAndroidSnapshotHelperSessions,
} from '@agent-device/platform-android/mechanics';
import { ANDROID_SNAPSHOT_HELPER_FIXTURE_ARTIFACT } from './test-utils/android-snapshot-helper.ts';
import { isAndroidSnapshotTimeoutError } from '../snapshot/snapshot-timeout-policy.ts';
import { isAndroidSnapshotTimeoutError } from '@agent-device/capture-kit/snapshot-timeout-policy';
import '../platform-runtime-android-adb-host.ts';
/**
@@ -9,8 +9,8 @@ import {
snapshotAndroid,
type AndroidAdbExecutor,
} from '@agent-device/platform-android/mechanics';
import { buildSnapshotState } from '../core/snapshot-state.ts';
import { coveredAndroidReplacementNodeIndexes } from '../snapshot/android-replacement-surface-occlusion.ts';
import { buildSnapshotState } from '@agent-device/capture-kit/snapshot-state';
import { coveredAndroidReplacementNodeIndexes } from '@agent-device/capture-kit/android-replacement-surface-occlusion';
import {
ANDROID_SNAPSHOT_HELPER_FIXTURE_ARTIFACT,
androidSnapshotHelperOutput,
@@ -2,7 +2,7 @@ import { test } from 'vitest';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { buildSnapshotState } from '../core/snapshot-state.ts';
import { buildSnapshotState } from '@agent-device/capture-kit/snapshot-state';
import { parseUiHierarchy } from './test-utils/android-ui-hierarchy-fixtures.ts';
import {
AndroidSnapshotPresentationFailure,
+1 -1
View File
@@ -1,6 +1,6 @@
import { test } from 'vitest';
import assert from 'node:assert/strict';
import { buildSnapshotState } from '../core/snapshot-state.ts';
import { buildSnapshotState } from '@agent-device/capture-kit/snapshot-state';
import { createSnapshotVisibility } from '@agent-device/contracts/snapshot';
import {
androidSnapshotPublicationInput,
@@ -11,7 +11,7 @@ const { captureLinuxSurfaceSnapshot, captureMacOsSurfaceSnapshot } = vi.hoisted(
vi.mock('@agent-device/platform-linux', () => ({ captureLinuxSurfaceSnapshot }));
import { createSnapshotRuntimeHost } from './snapshot-desktop-surface.ts';
import { createSnapshotRuntimeHost } from '@agent-device/capture-kit/snapshot-desktop-surface';
const macosDevice = {
id: 'desktop',
@@ -41,8 +41,17 @@ function createHost() {
}
test('desktop snapshot host keeps iOS presentation outside its eager import closure', () => {
const closure = eagerClosureOf(path.join(import.meta.dirname, 'snapshot-desktop-surface.ts'));
expect(closure).not.toContain(path.join(import.meta.dirname, 'ios-snapshot-runtime.ts'));
const entry = path.resolve(
import.meta.dirname,
'../../packages/capture-kit/src/snapshot/snapshot-desktop-surface.ts',
);
const closure = eagerClosureOf(entry);
expect(closure).not.toContain(
path.resolve(
import.meta.dirname,
'../../packages/capture-kit/src/snapshot/ios-snapshot-runtime.ts',
),
);
});
test('Apple snapshot host preserves non-app macOS surface capture and menubar identity', async () => {
+1 -1
View File
@@ -1,5 +1,5 @@
import type { CaptureSnapshotResult } from '@agent-device/contracts/client';
import { dedupeInheritedSnapshotLabels } from '../../snapshot/snapshot-label-dedup.ts';
import { dedupeInheritedSnapshotLabels } from '@agent-device/capture-kit/snapshot-label-dedup';
import { formatSnapshotText } from '../output/snapshot.ts';
import type { CliOutput } from '../command-contract.ts';
import { messageOutput, type CliOutputFormatter } from '../output-common.ts';
+3 -3
View File
@@ -20,9 +20,9 @@ import type { AgentDeviceRuntime, CommandSessionRecord } from '../../../runtime-
import {
buildSnapshotDiff,
countSnapshotComparableLines,
} from '../../../snapshot/snapshot-diff.ts';
import { renderSnapshotQualityWarnings } from '../../../snapshot/snapshot-presentation/quality-warnings.ts';
import { buildSnapshotVisibility } from '../../../snapshot/snapshot-visibility.ts';
} from '@agent-device/capture-kit/snapshot-diff';
import { renderSnapshotQualityWarnings } from '@agent-device/capture-kit/quality-warnings';
import { buildSnapshotVisibility } from '@agent-device/capture-kit/snapshot-visibility';
import { ANDROID_SYSTEM_SURFACE_DISCLOSURE } from '../../../core/android-system-surface-disclosure.ts';
import { formatReactNativeOverlayWarning } from '../../react-native/overlay.ts';
import { now } from '../../runtime-common.ts';

Some files were not shown because too many files have changed in this diff Show More