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
20 changes: 9 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,21 +59,19 @@ intentionally accepting a finding.
Code quality (production exports): `pnpm check:production-exports` runs Fallow's native
production graph, which excludes test/story/dev files, and fails when a new export has no
production consumer. This includes the test-only-export bug class that shipped in #1199's first
revision, while also catching exports that are unreachable from every graph. Fallow's
revision, while also catching exports that are unreachable from every graph. It is intentionally
baseline-free: there is no grandfather file, so a new unused production export fails loudly. Fallow's
`ignoreExportsUsedInFile` option in the gate's inherited config keeps exports with a real
same-file consumer out of this report without weakening the general Fallow audit. The checked-in
native baseline lives at `fallow-baselines/production-unused-exports.json`.
same-file consumer out of this report without weakening the general Fallow audit.

Fix a finding by wiring the export into production or removing the unnecessary export/code. For
an intentional test seam, explain why beside the declaration and keep the reviewed entry in the
production-export baseline. An inline
an intentional test seam or other non-production consumer, add a JSDoc `@internal` tag with a short
justification beside the declaration. An inline
`// fallow-ignore-next-line unused-export` is not suitable here: the general test-inclusive graph
sees the test consumer and correctly reports that suppression as stale. Run
`pnpm check:production-exports:baseline` only for a deliberate reviewed baseline migration or to
remove stale entries; additions accept new production-unreachable exports and should be rare.
Production usage reached only through dynamic property access remains invisible to a static
import graph, so register those exports in `.fallowrc.json` `ignoreExports` instead (as with the
daemon route handlers loaded through `typeof import()`).
sees the test consumer and correctly reports that suppression as stale. Production usage reached
only through dynamic property access remains invisible to a static import graph, so register those
exports in `.fallowrc.json` `ignoreExports` instead (as with the daemon route handlers loaded
through `typeof import()`).

Optional device selectors for tests:

Expand Down
62 changes: 0 additions & 62 deletions fallow-baselines/production-unused-exports.json

This file was deleted.

3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,7 @@
"check:affected": "node --experimental-strip-types scripts/check-affected/run.ts",
"check:affected:test": "node --experimental-strip-types --test scripts/check-affected/model.test.ts scripts/check-affected/run.test.ts",
"check:layering": "node --experimental-strip-types --test scripts/layering/model.test.ts && node --experimental-strip-types scripts/layering/check.ts",
"check:production-exports": "fallow dead-code --config fallow-production-exports.json --production --unused-exports --baseline fallow-baselines/production-unused-exports.json --fail-on-issues",
"check:production-exports:baseline": "fallow dead-code --config fallow-production-exports.json --production --unused-exports --save-baseline fallow-baselines/production-unused-exports.json --summary",
"check:production-exports": "fallow dead-code --config fallow-production-exports.json --production --unused-exports --fail-on-issues",
"check:bundle-owner-files": "node --experimental-strip-types scripts/check-bundle-owner-files.ts",
"check:quick": "pnpm lint && pnpm typecheck",
"sync:mcp-metadata": "node scripts/sync-mcp-metadata.mjs",
Expand Down
3 changes: 3 additions & 0 deletions src/cli/parser/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ type FinalizeArgsOptions = ParseArgsOptions & {
defaultFlags?: Partial<CliFlags>;
};

/**
* @internal High-level argv parser used by unit tests and build scripts.
*/
export function parseArgs(argv: string[], options?: FinalizeArgsOptions): ParsedArgs {
return finalizeParsedArgs(parseRawArgs(argv), options);
}
Expand Down
3 changes: 3 additions & 0 deletions src/cli/parser/command-suggestions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ const COMMAND_ALIAS_SUGGESTIONS: Record<string, CommandAliasSuggestion> = {
'close-session': { command: 'close', example: 'close' },
};

/**
* @internal Exposes the curated suggestion map for drift/parity tests.
*/
export function listCommandAliasSuggestionEntries(): Array<[string, CommandAliasSuggestion]> {
return Object.entries(COMMAND_ALIAS_SUGGESTIONS);
}
Expand Down
6 changes: 6 additions & 0 deletions src/cloud-webdriver/aws-device-farm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ export type AwsDeviceFarmWebDriverRuntimeOptions = {
prepareSession?: CloudWebDriverRuntimeOptions['prepareSession'];
};

/**
* @internal AWS Device Farm capability builder used by integration tests.
*/
export function getAwsDeviceFarmWebDriverCapabilities(
platform: CloudWebDriverPlatform,
): CloudWebDriverProviderCapabilities {
Expand All @@ -110,6 +113,9 @@ export function getAwsDeviceFarmWebDriverCapabilities(
});
}

/**
* @internal AWS Device Farm runtime factory used by integration tests.
*/
export function createAwsDeviceFarmWebDriverRuntime(
options: AwsDeviceFarmWebDriverRuntimeOptions,
): ProviderDeviceRuntime {
Expand Down
6 changes: 6 additions & 0 deletions src/cloud-webdriver/browserstack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ export type BrowserStackCapabilitiesOptions = {
configured?: Record<string, unknown>;
};

/**
* @internal BrowserStack capability builder used by integration tests.
*/
export function getBrowserStackWebDriverCapabilities(
platform: CloudWebDriverPlatform,
): CloudWebDriverProviderCapabilities {
Expand All @@ -87,6 +90,9 @@ export function getBrowserStackWebDriverCapabilities(
});
}

/**
* @internal BrowserStack runtime factory used by integration tests.
*/
export function createBrowserStackWebDriverRuntime(
options: BrowserStackWebDriverRuntimeOptions,
): ProviderDeviceRuntime {
Expand Down
3 changes: 3 additions & 0 deletions src/commands/command-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export function listMcpCommandMetadata(): AnyCommandMetadata[] {
});
}

/**
* @internal Introspection helper used by command surface parity tests.
*/
export function listCommandMetadataNames(): CommandName[] {
return [...commandMetadataMap.keys()].sort();
}
Expand Down
3 changes: 3 additions & 0 deletions src/commands/command-surface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export async function runCommand(
return await getCommandDefinition(name).invoke(client, input);
}

/**
* @internal Introspection helper used by command surface parity tests.
*/
export function listExecutableCommandNames(): CommandName[] {
return [...commandMap.keys()].sort();
}
Expand Down
5 changes: 3 additions & 2 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ import {
type SystemCommands,
} from './system/runtime/index.ts';

export { ref, selector } from './interaction/runtime/selector-read.ts';

export type {
BoundRuntimeCommand,
CommandResult,
Expand Down Expand Up @@ -77,6 +75,9 @@ export type BoundAgentDeviceCommands = {
observability: BoundObservabilityCommands;
};

/**
* @internal Runtime command catalog used by parity/type tests.
*/
export const commands: AgentDeviceCommands = {
capture: captureCommands,
selectors: selectorCommands,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
localCommandPolicy,
type CommandSessionStore,
} from '../../../../../runtime.ts';
import { ref } from '../../../../index.ts';
import { ref } from '../../selector-read.ts';
import { makeSnapshotState } from '../../../../../__tests__/test-utils/index.ts';

export function selectorSnapshot(): SnapshotState {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/interaction/runtime/gestures.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import { ref, selector } from '../../index.ts';
import { ref, selector } from './selector-read.ts';
import { AppError } from '../../../kernel/errors.ts';
import {
createInteractionDevice,
Expand Down
3 changes: 2 additions & 1 deletion src/commands/interaction/runtime/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import { commands, selector } from '../../index.ts';
import { commands } from '../../index.ts';
import { selector } from './selector-read.ts';
import { createInteractionDevice, selectorSnapshot } from './__tests__/test-utils/index.ts';

test('runtime interaction commands are available from the command namespace', async () => {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/interaction/runtime/interactions.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import type { AgentDeviceBackend } from '../../../backend.ts';
import { ref, selector } from '../../index.ts';
import { ref, selector } from './selector-read.ts';
import { createLocalArtifactAdapter } from '../../../io.ts';
import {
createAgentDevice,
Expand Down
2 changes: 1 addition & 1 deletion src/commands/interaction/runtime/resolution.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import type { BackendSnapshotOptions } from '../../../backend.ts';
import { ref, selector } from '../../index.ts';
import { ref, selector } from './selector-read.ts';
import { resolveActionableTouchResolution } from '../../../core/interaction-targeting.ts';
import { tryResolveRefNode } from './resolution.ts';
import { makeSnapshotState } from '../../../__tests__/test-utils/index.ts';
Expand Down
2 changes: 1 addition & 1 deletion src/commands/interaction/runtime/selector-read.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
localCommandPolicy,
type CommandSessionStore,
} from '../../../runtime.ts';
import { ref, selector } from '../../index.ts';
import { ref, selector } from './selector-read.ts';
import { makeSnapshotState } from '../../../__tests__/test-utils/index.ts';
import { createSelectorDevice, selectorReadSnapshot } from './__tests__/test-utils/index.ts';

Expand Down
6 changes: 6 additions & 0 deletions src/commands/interaction/runtime/selector-read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,16 @@ export type IsSelectorCommandOptions = CommandContext &
target: SelectorTarget;
};

/**
* @internal Target helper used by tests/examples; runtime callers compose `ElementTarget` directly.
*/
export function selector(expression: string): SelectorTarget {
return { kind: 'selector', selector: expression };
}

/**
* @internal Target helper used by tests/examples; runtime callers compose `ElementTarget` directly.
*/
export function ref(refInput: string, options: { fallbackLabel?: string } = {}): RefTarget {
return {
kind: 'ref',
Expand Down
12 changes: 12 additions & 0 deletions src/core/command-descriptor/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1159,6 +1159,9 @@ export const commandDescriptors = RAW_COMMAND_DESCRIPTORS.map((descriptor) => {
/** The literal union of every registered command name. */
export type Command = (typeof commandDescriptors)[number]['name'];

/**
* @internal Introspection helper used by parity tests.
*/
export function listDescriptorCatalogCommandNames<Group extends CommandCatalogGroup>(
group: Group,
): Array<DescriptorCommandNameForCatalogGroup<Group>> {
Expand All @@ -1181,6 +1184,9 @@ export function listDescriptorCatalogEntries<Group extends CommandCatalogGroup>(
);
}

/**
* @internal Introspection helper used by parity tests.
*/
export function listDescriptorDispatchCommandNames(): DescriptorDispatchCommandName[] {
return commandDescriptors
.filter((descriptor) => 'dispatch' in descriptor && descriptor.dispatch !== undefined)
Expand All @@ -1195,6 +1201,9 @@ export function listMcpExposedCommandNames(): DescriptorCliCommandName[] {
.sort();
}

/**
* @internal Introspection helper used by parity tests.
*/
export function listCapabilityCheckedCommandNames(): DescriptorCliCommandName[] {
return commandDescriptors
.filter((descriptor) => isCapabilityCheckedCliCommand(descriptor))
Expand Down Expand Up @@ -1282,6 +1291,9 @@ export function resolveCommandResponseDataTransform(
return RESPONSE_DATA_TRANSFORM_BY_COMMAND.get(command);
}

/**
* @internal Introspection helper used by parity tests.
*/
export function listCommandResponseDataTransforms(): Array<{
command: string;
transform: CommandResponseDataTransform;
Expand Down
3 changes: 3 additions & 0 deletions src/core/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,9 @@ const DISPATCH_HANDLERS: Record<DispatchCommand, DispatchHandler> = {
read: ({ device, positionals, context }) => handleReadCommand(device, positionals, context),
};

/**
* @internal Introspection helper used by parity tests.
*/
export function listRegisteredDispatchCommandNames(): string[] {
return Object.keys(DISPATCH_HANDLERS).sort();
}
Expand Down
3 changes: 3 additions & 0 deletions src/core/lease-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,9 @@ export function leaseScopeToConnectionMetadata(
return Object.keys(connection).length > 0 ? connection : undefined;
}

/**
* @internal Lease diagnostics seam exposed for unit tests.
*/
export function buildLeaseDiagnosticsContext(
leaseScope: LeaseScope | undefined,
): LeaseDiagnosticsContext | undefined {
Expand Down
5 changes: 4 additions & 1 deletion src/core/platform-plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,10 @@ export function tryGetPlugin(platform: Platform): PlatformPlugin | undefined {
return registry.get(platform);
}

/** The leaf platforms that currently carry a plugin, in registration order. */
/**
* @internal The leaf platforms that currently carry a plugin, in registration order.
* Exposed for parity tests.
*/
export function registeredPlatforms(): Platform[] {
return [...registry.keys()];
}
2 changes: 1 addition & 1 deletion src/daemon/__tests__/lease-context.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import assert from 'node:assert/strict';
import { test } from 'vitest';
import {
buildLeaseDiagnosticsContext,
buildSessionLeaseFromRequest,
resolveRunnerLogicalLeaseContext,
resolveRequestOrSessionLeaseScope,
type SessionLease,
} from '../lease-context.ts';
import { buildLeaseDiagnosticsContext } from '../../core/lease-scope.ts';
import type { DaemonRequest } from '../types.ts';

test('buildSessionLeaseFromRequest captures complete request lease scope', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import {
type ReplayTargetGuardDenotation,
} from '../../../replay/target-identity-node.ts';
import { makeSnapshotState } from '../../../__tests__/test-utils/index.ts';
import { ref as interactionRef, selector } from '../../../commands/index.ts';
import {
ref as interactionRef,
selector,
} from '../../../commands/interaction/runtime/selector-read.ts';
import { createInteractionDevice } from '../../../commands/interaction/runtime/__tests__/test-utils/index.ts';
import { classifyReplayTarget } from '../session-replay-target-classification.ts';

Expand Down
8 changes: 1 addition & 7 deletions src/daemon/lease-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,14 @@ import type { RunnerLogicalLeaseContext } from '../core/runner-lease-context.ts'
import { stripUndefined } from '../utils/parsing.ts';
import {
DEFAULT_PROXY_LEASE_TTL_MS,
buildLeaseDiagnosticsContext,
findMissingProxyLeaseFields,
isProxyLeaseScope,
leaseScopeFromRequest,
type LeaseDiagnosticsContext,
type LeaseScope,
} from '../core/lease-scope.ts';

export {
DEFAULT_PROXY_LEASE_TTL_MS,
buildLeaseDiagnosticsContext,
findMissingProxyLeaseFields,
isProxyLeaseScope,
};
export { DEFAULT_PROXY_LEASE_TTL_MS, findMissingProxyLeaseFields, isProxyLeaseScope };
export type { LeaseDiagnosticsContext, LeaseScope };

export type SessionLease = {
Expand Down
Loading
Loading