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: 16 additions & 4 deletions .fallowrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
// website/ is its own package (its own package.json, lockfile and pnpm root), built by Astro
// and deployed to GitHub Pages; nothing in the product imports it.
"ignorePatterns": ["website/**"],
// why: tsc's file arguments in the package.json scripts are tsconfig files and the
// --tsBuildInfoFile caches under node_modules/.cache/tsc, never code that runs, so none is an
// entry point; without this Fallow warns on every run that it skipped the three caches.
"ignoreCommandEntries": ["tsc"],
"entry": [
"src/cli/main.ts",
"src/daemon/main.ts",
Expand Down Expand Up @@ -80,7 +84,10 @@
// of that. The two are expected to diverge, not be merged; see fallow's own
// guidance for code-duplication ("leave it duplicated when the similarity is
// accidental and likely to diverge").
"ignore": ["e2e/fake-driver/fake-driver.ts"]
// why: src/drivers/android/profiles/ holds one data table per emulator profile, the hw.*
// keys its AVD gets; the tables are meant to look alike, and sharing their common rows
// would hide which value each profile sets.
"ignore": ["e2e/fake-driver/fake-driver.ts", "src/drivers/android/profiles/**"]
// Common additions (uncomment to enable):
// "ignore": [
// "**/lib/**", // for repos that publish transpiled output to lib/
Expand All @@ -91,14 +98,19 @@
},
"rules": {
"unused-dependencies": "warn",
"stale-suppressions": "error"
"stale-suppressions": "error",
// why: a suppression is a claim that Fallow cannot see a use; the `-- <reason>` says which
// use, so a reader can check it and a stale one can be judged.
"require-suppression-reason": "error"
},
"health": {
"thresholdOverrides": [
{
"files": [".agents/scripts/*.mjs"],
"maxCrap": 100,
"reason": "Glue scripts for the delivery pipeline: git and transcript walks, option parsing, a Markdown shape check. They have no coverage pipeline, so CRAP is complexity squared; each is run against real PRs and transcripts when it changes, and none holds product logic."
"maxCyclomatic": 12,
"maxCognitive": 15,
"maxCrap": 156,
"reason": "Glue scripts for the delivery pipeline: git and transcript walks, option parsing, a Markdown shape check. Their tests spawn them rather than import them and there is no coverage pipeline, so Fallow counts 0% coverage and CRAP is cyclomatic squared plus cyclomatic. Cyclomatic 12 and cognitive 15 are the real limits; maxCrap 156 is the CRAP of a cyclomatic-12 function at 0% coverage (12 * 12 + 12), so CRAP never fails a function the cyclomatic ceiling passes."
},
{
"files": ["src/agent-device/host/agent-device-supervisor.test.ts"],
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Fallow analysis
run: pnpm fallow --ci
run: pnpm fallow --ci --format github-annotations

# why: checked on the PR's merge commit, so a PR whose bump main has already taken (two PRs
# moving 34 to 35) fails here although its own branch passes.
Expand Down
10 changes: 8 additions & 2 deletions docs/internal/agent-rules/toolchain.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,14 @@ list and this one the same.
(`.agents/scripts/commit-msg-check.mjs`) refuses a subject on
`<kind>/<n>` that is not `<type>(<scope>): <what> (#<n>)`, and on
`bug/<n>-repro` one that is not `test: reproduce #<n> — <claim>`.
- **Push hook:** Fallow, then the e2e typecheck.
- **CI:** what `pnpm check` runs, and Fallow, on every push. The `pnpm
- **Push hook:** Fallow's audit (the findings the branch adds against
`origin/main`), then the e2e typecheck.
- **CI:** what `pnpm check` runs, and Fallow, on every push. CI's Fallow
(`fallow --ci`) checks the whole tree and fails on any dead-code finding
or clone group, `warn` rules included; complexity fails above the
`.fallowrc.json` limits, and refactoring targets never fail. Its findings
show as annotations on the PR. A `fallow-ignore` comment names its reason
after ` -- `, and one that no longer hides a finding fails. The `pnpm
check` steps run as parallel jobs (static checks, unit tests, the timing
tests, the e2e lane in three shards); the Quality check passes only when all of them
do. The console lane runs in four shards
Expand Down
4 changes: 2 additions & 2 deletions e2e/helpers/agent-device-targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ export const ANDROID: Target = {
],
};

export interface DeviceRow {
interface DeviceRow {
readonly id: string;
readonly driverDeviceId: string;
// why: the address agent-device names the device by (an iOS UDID, an Android adb serial); on Android it is not the driver's device id.
readonly address: string;
readonly state: string;
}
export interface LeaseRow {
interface LeaseRow {
readonly id: string;
readonly deviceId: string;
readonly ownerId: string;
Expand Down
4 changes: 2 additions & 2 deletions e2e/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export { DEVICE_READY_MS, waitFor } from "./wait.js";
export { events } from "./events.js";
export type { RecordedEvent } from "./events.js";
export { waitForDeviceState, waitForLeaseCount } from "./status.js";
export { SCREEN_TAGS, readScreenFrames, type ScreenFrame } from "./screen-frames.js";
export { SCREEN_TAGS, readScreenFrames } from "./screen-frames.js";
export { REPO_ROOT } from "./repo-root.js";
export type { CliResult } from "./cli.js";
export {
Expand All @@ -41,4 +41,4 @@ export {
waitForFree,
warmUp,
} from "./agent-device-targets.js";
export type { DeviceRow, LeaseRow, RequestRow, Target } from "./agent-device-targets.js";
export type { RequestRow, Target } from "./agent-device-targets.js";
6 changes: 3 additions & 3 deletions e2e/helpers/physical-presence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ import { waitFor } from "./wait.js";

export const ENROLLED = ["com.example.mail", "com.example.maps"] as const;
export const AGENT_APPS = ["com.agent.one", "com.agent.two"] as const;
export const RECLAIM_FAILURE: FakeDriverErrorSpec = { message: "device busy", type: "generic" };
const RECLAIM_FAILURE: FakeDriverErrorSpec = { message: "device busy", type: "generic" };

export const GRACE_HEALTH = { probeIntervalMs: 200, stableObservations: 2 };
const GRACE_HEALTH = { probeIntervalMs: 200, stableObservations: 2 };

export type AbsentReason = "locked" | "untrusted" | "not-present";

Expand Down Expand Up @@ -69,7 +69,7 @@ function scriptedExtras(script: DeviceScript | undefined) {
};
}

export function iosScript(
function iosScript(
devices: readonly ScriptedSeed[],
options: { readonly failReclaim?: boolean } = {},
): FakeDriverPlatformScript {
Expand Down
4 changes: 2 additions & 2 deletions e2e/real-proxy/lane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { REPO_ROOT } from "../helpers/repo-root.js";
import { waitFor } from "../helpers/wait.js";

export const PROXY_URL = "http://127.0.0.1:4180";
export const GATEWAY_PORT = 4700;
const GATEWAY_PORT = 4700;
export const ISSUER = "http://127.0.0.1:5556/dex";
export const CLIENT_ID = "simlock-real-proxy";
const CLIENT_ID = "simlock-real-proxy";
export const ALICE = { email: "alice@example.test", password: "password" } as const;

export interface LaneDaemon {
Expand Down
2 changes: 1 addition & 1 deletion e2e/real-proxy/ports.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createServer } from "node:net";

export const LANE_PORTS = [4180, 5556, 4700] as const;
const LANE_PORTS = [4180, 5556, 4700] as const;

function isFree(port: number): Promise<boolean> {
return new Promise((resolve) => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
"@types/ws": "8.18.1",
"@vitejs/plugin-react": "^6.1.1",
"dependency-cruiser": "18.4.0",
"fallow": "^3.6.0",
"fallow": "^3.35.0",
"lefthook": "^2.1.10",
"oxfmt": "^0.59.0",
"oxlint": "^1.74.0",
Expand Down
85 changes: 48 additions & 37 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion src/agent-device/gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,6 @@ function leaseFacts(ctx: Context, entry: FleetLease): FleetLeaseFacts {
};
}

// fallow-ignore-next-line unused-export -- reached through the namespace of definition.ts's dynamic import.
export function createGateway(ctx: Context): GatewayModule {
const parts = createGatewayAgentDeviceFrontParts(
fleetPort(ctx),
Expand Down
1 change: 1 addition & 0 deletions src/agent-device/host/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ export class AgentDeviceHost {
return identity === undefined ? null : { tenantId: identity.principal };
}

// fallow-ignore-next-line unused-class-member -- RoutedHostLink calls it as this.hosts[role].forward, a computed key Fallow does not follow.
async forward(request: Request, identity: Identity): Promise<Response> {
const status = await this.status();
if (status.state === "unavailable") throw new Error(status.reason);
Expand Down
2 changes: 0 additions & 2 deletions src/agent-device/host/upstream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ export interface AgentDeviceUpstream {
export class SupervisorAgentDeviceUpstream implements AgentDeviceUpstream {
constructor(readonly supervisor: { availability(): AgentDeviceAvailability }) {}

// fallow-ignore-next-line unused-class-member -- reached through the AgentDeviceUpstream port by the host.
current(): AgentDeviceUpstreamState {
const availability = this.supervisor.availability();
switch (availability.state) {
Expand All @@ -37,7 +36,6 @@ export class SupervisorAgentDeviceUpstream implements AgentDeviceUpstream {
}
}

// fallow-ignore-next-line unused-class-member -- reached through the AgentDeviceUpstream port by the host.
async fetch(request: Request): Promise<Response> {
return globalThis.fetch(request);
}
Expand Down
1 change: 0 additions & 1 deletion src/agent-device/testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ export class FakeAgentDeviceHealth implements AgentDeviceHealthProbe {
this.#answer = { error };
}

// fallow-ignore-next-line unused-class-member -- reached through the AgentDeviceHealthProbe port by the supervisor.
async read(baseUrl: string): Promise<unknown> {
this.reads.push(baseUrl);
if (this.#answer === "never") return new Promise<never>(() => undefined);
Expand Down
1 change: 0 additions & 1 deletion src/agent-device/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,5 +191,4 @@ export function createWorkerWith(
};
}

// fallow-ignore-next-line unused-export -- reached through the namespace of definition.ts's dynamic import.
export const createWorker = createWorkerWith({});
2 changes: 0 additions & 2 deletions src/core/component-installer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,6 @@ export class ComponentInstaller {
* reads the registry, and writes nothing. A driver whose listing rejects is left out and
* logged, and the other platforms are still listed.
*/
// fallow-ignore-next-line unused-class-member -- reached through the Dispatcher's `components` option, typed as a Pick of this class.
async list(platform?: Platform): Promise<readonly InstalledComponentListing[]> {
const listed = await Promise.all(
this.options.drivers.select(platform).map(async (driver) => {
Expand Down Expand Up @@ -466,7 +465,6 @@ export class ComponentInstaller {
* Simlock device in `list` and in a removal's checks, so a removal cannot start under a
* device the registry does not hold yet (ADR 0010 §8).
*/
// fallow-ignore-next-line unused-class-member -- reached through DeviceProvisioner's `components` option, typed as a Pick of this class.
claimProvision(spec: Pick<DeviceSpec, "osVersion" | "platform">): () => void {
const key = componentKey(spec.platform, spec.osVersion);
const removal = this.#removals.get(spec.platform);
Expand Down
1 change: 0 additions & 1 deletion src/core/quarantine-coordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ export class QuarantineCoordinator {
}

// No `notifyAvailability`: a quarantined virtual device still counts as running, and a physical device counts toward no capacity, so no waiter can be satisfied by this.
// fallow-ignore-next-line unused-class-member -- called through ReclaimCoordinator's quarantine port.
async enter(failure: QuarantinePurgeFailure): Promise<void> {
const nextRetryAt = this.options.clock.now() + this.options.config.retryBackoffMs;
await this.options.decisions.run(async () => {
Expand Down
1 change: 0 additions & 1 deletion src/core/reclaim-coordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,6 @@ export class ReclaimCoordinator {
return recovered;
}

// fallow-ignore-next-line unused-class-member -- called through LeaseReleaseCoordinator's reclaim port.
async reclaimReturned(
deviceId: string,
osVersion?: string,
Expand Down
1 change: 0 additions & 1 deletion src/core/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -910,7 +910,6 @@ export class Registry implements LeaseRequestStore<LeaseGrant> {
* opens: nothing in a new process drives a wait the old one started, so an open record from
* before the restart would otherwise stay open with nothing to settle it.
*/
// fallow-ignore-next-line unused-class-member -- called through LeaseStartup's registry port (LeaseStartupRegistry).
async failOpenLeaseRequests(
failure: LeaseRequestFailure,
): Promise<readonly LeaseRequestRecord[]> {
Expand Down
Loading
Loading