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
51 changes: 49 additions & 2 deletions .fallowrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@
"file": "packages/provider-*/test/package-smoke.mjs",
"exports": ["*"]
},
{
"comment": "A published provider package's default export is its plugin contract; src/plugins/load.ts imports the package by name at runtime and reads `default`.",
"file": "packages/provider-*/src/index.ts",
"exports": ["default"]
},
{
"comment": "Android perf mechanics are selected through the lazy platform host so importing agent-device does not eagerly load adb mechanics. Fallow cannot follow the dynamic property read in src/platform-runtime-perf-host.ts.",
"file": "packages/platform-android/src/perf.ts",
Expand All @@ -92,6 +97,11 @@
"file": "packages/platform-apple/src/core/perf.ts",
"exports": ["buildAppleFrameSamplingMetadata", "buildAppleMemorySamplingMetadata"]
},
{
"comment": "packages/platform-apple/src/lifecycle.ts loads this module on demand and reads the member off the memoized import() promise, which Fallow cannot follow.",
"file": "packages/platform-apple/src/expo-dev-client-launch.ts",
"exports": ["resolveExpoDevClientLaunchUrl"]
},
{
"comment": "These host-process helpers are consumed only by integration leak-oracle support and host-process unit tests; Fallow's production graph intentionally excludes both consumers.",
"file": "packages/host-kit/src/process.ts",
Expand Down Expand Up @@ -286,6 +296,15 @@
"file": "packages/contracts/src/facades/snapshot.ts",
"exports": ["findSnapshotScopeRange"]
},
{
"comment": "Same façade-exhaustiveness rule (scripts/layering/facade-surface.test.ts): these façades must re-export every symbol of ../screenshot.ts, ../remote-config-fields.ts and ../snapshot-visibility.ts, whose production callers import the source module directly.",
"file": "packages/contracts/src/facades/{capture,remote,snapshot}.ts",
"exports": [
"SCREENSHOT_ACTION_FLAG_KEYS",
"PROVIDER_PROFILE_FIELD_FLAG_ALIASES",
"collectViewportRects"
]
},
{
"comment": "Test fixtures: every *.fixtures.ts builds doubles for co-located *.test.ts files (several import `vi` directly) and none is imported by production source, so --production analysis sees no consumer for any of them. Scoped to unused-exports rather than ignorePatterns so fixtures stay inside every other Fallow rule (health, dead-code, cycles); `*` rather than today's three symbols because the property being asserted is per-file, not per-symbol.",
"file": "**/*.fixtures.ts",
Expand Down Expand Up @@ -349,14 +368,42 @@
"exports": ["getSessionCommandKind"]
},
{
"comment": "Workspace-package symbols whose only consumers are test-tree files, which --production analysis does not see. Each import is the package specifier, so the façade entry is load-bearing: parseTargetAnnotationV1Payload/TARGET_ANNOTATION_MAX_FIELD_BYTES from src/daemon/__tests__/session-target-evidence.test.ts, MAESTRO_COMPAT_* from src/cli/parser/__tests__/maestro-support-matrix.test.ts, createAppLogLiveHandle from src/__tests__/test-utils/app-log-live-handle.ts (its production sibling createAppLogLiveHandleFromFinish wraps it, and scripts/layering/platform-package-policy.test.ts pins the specifier form).",
"comment": "Workspace-package symbols whose only consumers are test-tree files, which --production analysis does not see. Each import is the package specifier, so the façade entry is load-bearing: parseTargetAnnotationV1Payload/TARGET_ANNOTATION_MAX_FIELD_BYTES from src/daemon/__tests__/session-target-evidence.test.ts, MAESTRO_COMPAT_* from src/cli/parser/__tests__/maestro-support-matrix.test.ts, SCRIPT_FLAG_COMMANDS/scriptFlagEntries from src/commands/replay/script-flag-admission.test.ts (the #3197 two-way drift guard between the .ad grammar and the CLI schema), createAppLogLiveHandle from src/__tests__/test-utils/app-log-live-handle.ts (its production sibling createAppLogLiveHandleFromFinish wraps it, and scripts/layering/platform-package-policy.test.ts pins the specifier form).",
"file": "packages/{ad-script,maestro,capture-kit}/src/index.ts",
"exports": [
"parseTargetAnnotationV1Payload",
"TARGET_ANNOTATION_MAX_FIELD_BYTES",
"MAESTRO_COMPAT_LIMITATIONS",
"MAESTRO_COMPAT_SUPPORTED_CAPABILITIES",
"createAppLogLiveHandle"
"createAppLogLiveHandle",
"SCRIPT_FLAG_COMMANDS",
"scriptFlagEntries"
]
},
{
"comment": "src/daemon/__tests__/request-runtime-binding.test.ts drives the daemon's exact bind through the recovery authority, whose cancellation race is what rejects; production reaches it only through finishRecoveredDurableCapture and recoverDurableCaptureResource in the same package.",
"file": "packages/capture-kit/src/durable-capture/index.ts",
"exports": ["acquireDurableCaptureRecoveryAuthorityBeforeDeadline"]
},
{
"comment": "The collected copy's path is part of the stop-sequence contract (ADR 0024 §2.3) beside nativeRecordingPath; provider-scenario and Apple recovery tests assert pulls land on it, while production derives it inside the stop sequence.",
"file": "packages/capture-kit/src/recording/stop-sequence.ts",
"exports": ["collectedRecordingPath"]
},
{
"comment": "Staged managed-local lease foundations (ADR 0021, #2284/#2308): src/daemon/request-runtime-binding.ts enters request admission for managed-local owners, but the allocator activation unit that would construct these journals, stores, claims and admissions never landed. Remove with the feature, not export by export: its owner kind is part of persisted device-claim and durable-resource records.",
"file": "{packages/managed-allocation/src/{journal,record,store}.ts,src/daemon/device/device-claim-allocator.ts,src/daemon/managed-device-allocation/lease-admission.ts,src/managed-device-reachability.ts}",
"exports": [
"createAllocationOperationJournal",
"ALLOCATION_OPERATION_SCHEMA_VERSION",
"newAllocationOperation",
"decodeAllocationOperationRecord",
"bindingFenceFor",
"createAllocationOperationStore",
"acquireAllocatorHeldDeviceClaim",
"releaseAllocatorHeldClaim",
"createManagedLeaseAdmission",
"createManagedLeaseReachability"
]
},
{
Expand Down
8 changes: 8 additions & 0 deletions fallow-baselines/health.json
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,14 @@
"crap_high": {
"count": 1
}
},
"packages/maestro/test/conformance/conformance-normalize.ts": {
"complexity_critical": {
"count": 2
},
"crap_critical": {
"count": 2
}
}
},
"runtime_coverage_findings": [],
Expand Down
4 changes: 4 additions & 0 deletions packages/capture-kit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
"types": "./src/capture-admission/audio-probe-admission-ledger.ts",
"default": "./src/capture-admission/audio-probe-admission-ledger.ts"
},
"./audio-probe-descriptor-fixtures": {
"types": "./src/audio-probe-descriptor.fixtures.ts",
"default": "./src/audio-probe-descriptor.fixtures.ts"
},
"./audio-probe-recovery": {
"types": "./src/capture-admission/audio-probe-resource-recovery.ts",
"default": "./src/capture-admission/audio-probe-resource-recovery.ts"
Expand Down
10 changes: 10 additions & 0 deletions packages/capture-kit/src/audio-probe-descriptor.fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {
hostAudioProbeDescriptorCodec,
type HostAudioProbeDescriptor,
} from './audio-probe-descriptor.ts';
import { encodeDurableDescriptor } from './durable-resource-envelope.ts';

/** The descriptor a live host audio probe persists, for seeding a record an earlier daemon left. */
export function encodeHostAudioProbeDescriptor(descriptor: HostAudioProbeDescriptor) {
return encodeDurableDescriptor(hostAudioProbeDescriptorCodec, descriptor);
}
1 change: 0 additions & 1 deletion packages/capture-kit/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ export {
} from './app-log-runtime.ts';
export { createAppLogLiveHandle, createAppLogLiveHandleFromFinish } from './app-log-live-handle.ts';
export { createHostAudioProbeCaptureOperations } from './audio-probe-runtime.ts';
export { hostAudioProbeDescriptorCodec } from './audio-probe-descriptor.ts';
export { decodeDurableDescriptor } from './durable-descriptor-codec.ts';
export { createScreenRecordingLiveHandle } from './screen-recording-live-handle.ts';
export { createScreenRecordingCompletion } from './screen-recording-completion.ts';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { mkdtempForTestSync } from '../tmp-dir.fixtures.ts';
import { shellQuoteIfNeeded } from '@agent-device/kernel/device-shell';
import type { compareDifferentialCases } from './conformance-harness.ts';

type DifferentialCase = Parameters<typeof compareDifferentialCases>[0][number];

export function swiftToolchainAvailable(): boolean {
if (process.platform !== 'darwin') return false;
/* c8 ignore start */
try {
execFileSync('swift', ['--version'], { stdio: 'ignore' });
return true;
} catch {
return false;
}
/* c8 ignore stop */
}

export function writeDifferentialFailureArtifact(input: {
testCase: DifferentialCase;
seed: number;
counterexamplePath: string;
}): { directory: string; casePath: string; replayCommand: string } {
const directory = mkdtempForTestSync('ios-snapshot-fuzz-');
const casePath = path.join(directory, 'case.json');
fs.writeFileSync(casePath, JSON.stringify({ cases: [input.testCase] }, null, 2) + '\n');
const replayCommand = [
'node --experimental-strip-types',
'packages/capture-kit/src/ios-snapshot-engine/replay.ts',
shellQuoteIfNeeded(casePath),
].join(' ');
fs.writeFileSync(
path.join(directory, 'replay-command.txt'),
replayCommand + '\nseed=' + String(input.seed) + '\npath=' + input.counterexamplePath + '\n',
);
return { directory, casePath, replayCommand };
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import type { IosSnapshotAcquisition } from '@agent-device/contracts/ios-snapshot';
import {
Expand Down Expand Up @@ -61,18 +59,6 @@ type DifferentialMismatch = Readonly<{
typescript: unknown;
}>;

export function swiftToolchainAvailable(): boolean {
if (process.platform !== 'darwin') return false;
/* c8 ignore start */
try {
execFileSync('swift', ['--version'], { stdio: 'ignore' });
return true;
} catch {
return false;
}
/* c8 ignore stop */
}

/* c8 ignore start */
export function compareDifferentialCases(
cases: readonly DifferentialCase[],
Expand Down Expand Up @@ -376,23 +362,3 @@ export function canonicalNodes(nodes: readonly RawSnapshotNode[]): CanonicalNode
function canonicalRect(rect: Rect): Rect {
return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
}

export function writeDifferentialFailureArtifact(input: {
testCase: DifferentialCase;
seed: number;
counterexamplePath: string;
}): { directory: string; casePath: string; replayCommand: string } {
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'ios-snapshot-fuzz-'));
const casePath = path.join(directory, 'case.json');
fs.writeFileSync(casePath, JSON.stringify({ cases: [input.testCase] }, null, 2) + '\n');
const replayCommand = [
'node --experimental-strip-types',
'packages/capture-kit/src/ios-snapshot-engine/replay.ts',
JSON.stringify(casePath),
].join(' ');
fs.writeFileSync(
path.join(directory, 'replay-command.txt'),
replayCommand + '\nseed=' + String(input.seed) + '\npath=' + input.counterexamplePath + '\n',
);
return { directory, casePath, replayCommand };
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
canonicalNodes,
runTypeScriptCase,
runnerPresentationAgrees,
writeDifferentialFailureArtifact,
} from './conformance-harness.ts';
import { writeDifferentialFailureArtifact } from './conformance-harness.fixtures.ts';
import {
acquisitionForGoldenCase,
normalizeGoldenNodes,
Expand Down
6 changes: 3 additions & 3 deletions packages/capture-kit/src/ios-snapshot-engine/engine.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
import { toIosSnapshotEngineErrorDetails } from './types.ts';
import {
IosSnapshotEngineError,
presentIosInteractiveSnapshot,
buildIosInteractiveSnapshotPresentation,
presentIosSnapshot,
publishIosSnapshot,
} from './index.ts';
Expand Down Expand Up @@ -342,13 +342,13 @@ test('a source-declared disabled node is not actionable without hittability evid

test('interactive compaction stays available through the engine boundary', () => {
const rowRect = { x: 16, y: 80, width: 288, height: 52 };
const compacted = presentIosInteractiveSnapshot([
const compacted = buildIosInteractiveSnapshotPresentation([
node(0, 'Application', 'App', viewport),
node(1, 'Table', 'Settings', { x: 0, y: 40, width: 320, height: 200 }, 0),
node(2, 'Cell', 'General', rowRect, 1, 2),
node(3, 'Button', 'General', rowRect, 2, 3),
node(4, 'StaticText', 'General', rowRect, 3, 4),
]);
]).nodes;

assert.deepEqual(
compacted.map((entry) => entry.type),
Expand Down
7 changes: 1 addition & 6 deletions packages/capture-kit/src/ios-snapshot-engine/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
export { presentIosSnapshot, publishIosSnapshot } from './engine.ts';
export { presentIosRunnerSnapshot } from './runner-presentation.ts';
export {
buildIosInteractiveSnapshotPresentation,
presentIosInteractiveSnapshot,
} from './semantic-index.ts';
export { collectIosStructuralIdentifierSuppression } from './noise-structural.ts';
export { mergeReplacement, updateReplacement } from './tree.ts';
export { buildIosInteractiveSnapshotPresentation } from './semantic-index.ts';
export { IosSnapshotEngineError, toIosSnapshotEngineErrorDetails } from './types.ts';
export type { SnapshotTreeRuleContext } from './tree.ts';
2 changes: 1 addition & 1 deletion packages/capture-kit/src/ios-snapshot-engine/noise.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { describe, expect, test } from 'vitest';

import type { RawSnapshotNode } from '@agent-device/kernel/snapshot';

import { collectIosStructuralIdentifierSuppression } from '@agent-device/capture-kit/ios-snapshot-engine';
import type { SnapshotTreeRuleContext } from '@agent-device/capture-kit/ios-snapshot-engine';
import { collectIosStructuralIdentifierSuppression } from './noise-structural.ts';

type ReadCounter = { reads: number };

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { expect, test } from 'vitest';
import { attachRefs, type RawSnapshotNode } from '@agent-device/kernel/snapshot';
import { presentIosInteractiveSnapshot } from '@agent-device/capture-kit/ios-snapshot-engine';
import { buildIosInteractiveSnapshotPresentation } from '@agent-device/capture-kit/ios-snapshot-engine';

function buildSnapshotState(data: { nodes?: RawSnapshotNode[]; backend?: 'xctest' }) {
return {
nodes: attachRefs(presentIosInteractiveSnapshot(data.nodes ?? [])),
nodes: attachRefs(buildIosInteractiveSnapshotPresentation(data.nodes ?? []).nodes),
backend: data.backend,
createdAt: Date.now(),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@ const IOS_PRESENTATION_RULES: Array<
collectIosPresentationNoiseSuppression,
];

export function presentIosInteractiveSnapshot(nodes: RawSnapshotNode[]): RawSnapshotNode[] {
return buildIosInteractiveSnapshotPresentation(nodes).nodes;
}

export type IosInteractiveSnapshotPresentation = {
nodes: RawSnapshotNode[];
/** Canonical presented representatives for every semantic source index; pure noise maps to []. */
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect, test } from 'vitest';
import type { RawSnapshotNode } from '@agent-device/kernel/snapshot';
import { presentIosInteractiveSnapshot } from '@agent-device/capture-kit/ios-snapshot-engine';
import { buildIosInteractiveSnapshotPresentation } from '@agent-device/capture-kit/ios-snapshot-engine';

test('a disabled navigation title field is promoted to a Button without its hittability', () => {
const nodes: RawSnapshotNode[] = [
Expand Down Expand Up @@ -49,7 +49,7 @@ test('a disabled navigation title field is promoted to a Button without its hitt
},
];

const presented = presentIosInteractiveSnapshot(nodes);
const presented = buildIosInteractiveSnapshotPresentation(nodes).nodes;
const affordance = presented.find((node) => node.identifier === 'DisplayNameTextField');

expect(affordance).toMatchObject({ type: 'Button', label: 'Team Standup', enabled: true });
Expand Down
2 changes: 1 addition & 1 deletion packages/capture-kit/src/ios-snapshot-engine/tree.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect, test } from 'vitest';
import type { RawSnapshotNode } from '@agent-device/kernel/snapshot';
import { mergeReplacement, updateReplacement } from '@agent-device/capture-kit/ios-snapshot-engine';
import { mergeReplacement, updateReplacement } from './tree.ts';

test('replacement updates derive patches from the composed node', () => {
const node: RawSnapshotNode = { index: 1, type: 'Table', hiddenContentBelow: true };
Expand Down
Loading
Loading