mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
fix/private-ax-transition-settle
1464 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a0f7114149 | docs: require complete change migrations (#1500) | ||
|
|
cd9a7ce41b |
test(android): add comprehensive emulator E2E coverage (#1482)
* test(android): add catalog emulator smoke coverage * test(android): use stable snapshot diff mutation * test(android): assert actual back destination * test(android): separate keyboard and fill IMEs * fix(ci): keep Android timing report in one shell * refactor(test): simplify simulator e2e coverage * test(android): assert stable diff landmarks * fix(android): release snapshot helper gracefully * fix(android): fully release snapshot helper runtime * test(android): report coverage classifications * fix(android): stabilize accessibility root capture * fix(android): bound UiAutomation connection * ci: upload worktree daemon diagnostics * fix(android): cancel stalled wait captures * fix(android): bound helper fallback lifecycle * fix(android): harden emulator e2e lifecycle * fix: align e2e changes with kernel package * test(android): prove alert helper reuse directly * fix(android): cancel stalled settle captures * fix(android): separate helper retirement budgets |
||
|
|
809968d940 |
Simplify screenshot diff output (#1495)
* refactor: simplify screenshot diff output * test: update screenshot diff cli output * chore: remove unused screenshot geometry helpers * fix: version screenshot diff result contract * fix: preserve screenshot diff result compatibility |
||
|
|
2e6849ddb6 | test: stabilize parallel provider scenarios (#1497) | ||
|
|
7ef83bc8bc | refactor: restore pngjs codec (#1498) | ||
|
|
47540f1f07 | refactor: consolidate daemon errors (#1496) | ||
|
|
117cf4ff68 |
test(ios): fix nightly fixture selector drift (#1492)
* test(ios): fix nightly fixture selector drift * test(ios): harden fixture selector coverage |
||
|
|
76453add71 |
refactor: pnpm workspace + @agent-device/kernel pilot (#1490 W0) (#1494)
* refactor: pnpm workspace + @agent-device/kernel pilot (#1490 W0) Extend the workspace with packages/* and move the kernel behind an enforced public API: packages/kernel with nine consumer-earned subpath exports (errors, device, snapshot, contracts, collections, rect, redaction, daemon-error, bounds — the last absorbed from utils as Rect vocabulary). Every kernel import repo-wide becomes the @agent-device/kernel/<sub> specifier; kernel tests move to src/__tests__/kernel/ and exercise the package surface. The root declares the package in devDependencies (workspace:*), tsdown bundles it (noExternal) so the published artifact and its runtime dependency manifest are unchanged. Gate rewiring in the same change, per the W0 brief: - R1 kernel-sink retires (physically subsumed); new R11 package-boundaries guards no-root-back-imports, relative tunnelling past exports maps, undeclared workspace deps, and non-exported subpaths, with runtime resolution pins via import.meta.resolve. - resolveImportEdges and mutation ownership follow workspace specifiers through exports maps, keeping R4 cycle checks, depgraph, and derived test ownership connected across the seam (kernel-errors still owns 495 tests). listSourceFiles includes packages/*/src. - kernel becomes an unranked zone; mutation registry, stryker mutate globs, and the mutation-affected workflow path filter move to packages/kernel/src/errors.ts. - check:affected gains packages/ ownership (manifests fail open); vitest and coverage include packages/*/src; fallow ignores packages/** (its resolver cannot follow workspace specifiers). - The affected-selector CI job installs dependencies: its closure now crosses workspace specifiers, and the R8 relative exception is unsafe for production src files (Node ESM does not realpath, so dual specifier/relative loads would instantiate modules twice). The R8 zero-dep set is pinned empty with that rationale. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * fix: address W0 review — mutation sandbox, exports-map resolution, tsc -b Review findings on #1494, all five: 1. contracts-schema-public.test.ts reads the kernel source at its packages/ path (fs access invisible to the codemod and typecheck). 2. Mutation lane: Stryker sandboxes the tree but pnpm's node_modules symlink resolves @agent-device/* back to the real repo, so mutants in the sandbox never load and vitest.related finds no tests. vitest.mutation.config.ts now aliases each EXPORTED specifier to its source (derived from exports maps, never a wildcard), keeping resolution inside the mutated tree. Validated: kernel-errors module runs end to end (dry run 3,984 tests, mutants killed, exit 0). 3. Layering/depgraph resolve workspace specifiers through the exports-derived map (workspaceSpecifierTargets) instead of reconstructing paths, so '.'-facade packages resolve; the positional fallback remains only for map-less fixtures (P0 pin). 4. Per-package project references implemented: packages/kernel is composite (emitDeclarationOnly -> dist-types, gitignored), the root references it, and typecheck becomes tsc -b — probed to catch type errors on both sides under TypeScript 7 native. 5. R11's relative-route exception now requires membership in an actual R8 zero-dep job closure (zeroDepClosureFiles walks entries), not mere scripts/ placement — closing the dual-instantiation bypass. Also from review discussion: daemon-error moves out of the kernel package to src/client/ — its consumers (cli, client facade) rehydrate wire DaemonErrors client-side; the daemon only produces them. Kernel drops to 8 exported subpaths before any of them ship. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * refactor: one exports-map reader for mutation alias and ownership Fallow flagged workspaceExportAliases (cognitive 15, CRAP 90). The manifest-reading logic already exists as workspaceSpecifierTargets in scripts/layering/package-boundaries.ts, so both the Stryker sandbox alias table and the mutation ownership walker now consume it instead of carrying near-clones. Behavior unchanged; mutation suite 45/45 and changed-code fallow green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * fix: composite kernel without a root references edge FreeRange runs plain `tsc -p tsconfig.json`, and a root `references` entry makes non-build-mode TypeScript demand the referenced project's built declarations (TS6305) — a standing "build first" tax on every plain -p consumer (fr, editors). Keep the per-package composite project and build it in typecheck (`tsc -b packages/kernel` before the root and examples/sdk passes), but drop the root references edge: root consumption resolves through exports to source, identical to runtime and to the bundler. Probed: plain -p green with no prebuilt output; kernel-side type errors still caught by its own build. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * fix: R11 uses the layering parser; mutation config is a fallow entry Review blockers on #1494: - R11's private single-quote regex could miss a double-quoted or re-export route into packages/*/src. specifierSites now delegates to the layering model's parseImports (both quote styles, side-effect imports, re-exports, dynamic imports), with direct regressions for each formerly-invisible form. - vitest.mutation.config.ts becomes a declared fallow entry instead of a tolerated unused-file finding: the full-repo audit now reports it reachable (unused files 2 -> 1; the remainder predates this PR). FreeRange clean-checkout evidence: with packages/kernel/dist-types and every *.tsbuildinfo deleted, `pnpm check:freerange` reports 0 findings on this head — the TS6305 topology died with the root references edge in the previous commit; check:freerange has no build precondition. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
7f991e4ae9 | fix: update qs for Dependabot alert #55 (#1493) | ||
|
|
b4487f57a7 | 0.20.3 v0.20.3 | ||
|
|
ee12f4d5bc |
fix: publish replay refs after response projection (#1489)
* fix: publish replay refs after response projection * fix: retain replay device lock through finalization * fix: verify Apple text wait misses with snapshot * fix: lock replay device before open effects * fix: lock replay device before open preparation |
||
|
|
f0d656d169 |
refactor(kernel): relocate non-foundational stragglers before packaging (#1491)
W-pre of #1490: kernel's file set becomes its public API before it moves to packages/. Three files drifted into the sink because R1 pushes shared code down, not because they are foundational: - audio-probe-support -> src/contracts/ (capability policy consumed by platform plugins and interactor registration) - input-validation -> src/contracts/ (lowest-rank consumer is contracts/gesture-input) - observability-redaction -> src/commands/observability/runtime/ (colocated with its only consumer, diagnostics-format) Pure moves; no behavior change. Kernel retains errors, device, snapshot, contracts, collections, rect, redaction, daemon-error. Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
adcbdda8f0 |
perf: speed up unit tests and streamline checks (#1488)
* perf: speed up unit tests and streamline checks * fix: validate canonical packaging workflows |
||
|
|
9e75eaba3e |
fix: preserve clear state before iOS launch URLs (#1486)
* fix: preserve clear state before iOS launch URLs * test: prove iOS clear-state launch URL flow * fix: await iOS keyboard dismissal |
||
|
|
2316fd32c5 |
test: pin daemon modularity migration contracts (#1487)
* test: pin daemon modularity migration contracts * refactor: tighten daemon modularity ratchets * test: pin reporter live hook isolation |
||
|
|
12fd36e786 |
test: add scroll-edge-state suite, close mutation gap (#1455) (#1474)
* test: add scroll-edge-state suite, close mutation gap (#1455) scroll-edge-state.ts had no dedicated test file and scored 28.75% mutation (92/320) — the worst of the five tracked decision kernels. Adds src/utils/scroll-edge-state.test.ts covering every exported function's decision branches (captureScrollEdgeState, runScrollEdgePasses, formatScrollEdgeMessage) plus the private container-selection/scope/signature logic they exercise. Raises the scoped mutation score to 95.31% (305/320); the 15 remaining survivors are named, justified-equivalent mutants (documented in the PR). * refactor(test): split scroll-edge-state suite, drop signature test-gaming Splits the 1,323-line scroll-edge-state.test.ts into three behavior-focused sibling suites (container selection, scope naming, pass orchestration) plus a shared fixtures module, per AGENTS.md's per-file LOC tripwire and sibling-test convention. Also removes the dead `signature`/buildScrollStateSignature/collectSubtreeNodes/ hasAncestor production code: it had zero consumers anywhere in the repo, and the tests pinning its private string format were the mutation-score-gaming flagged in review — with it gone, there's nothing left to game. Addresses review feedback on PR #1474 (#1455). * refactor(test): split scroll-edge-state suite further, tighten unreachable branch Splits container-selection.test.ts (562 LOC, over the 500-LOC extract tripwire) into four narrower suites by durable question: container existence, hidden-edge decisions, target resolution, and broad selection. Adds a scrollNode() fixture builder to cut inline node-literal repetition across the suite. Removes the "captureNodes resolves to undefined" test: the real contract (both production call sites, and captureScrollEdgeState's own captureNodes type) always resolves to a real array, so this exercised an unreachable branch behind a type-unsound cast. Tightens analyzeScrollEdgeState's parameter type to drop the now-provably-unreachable `| undefined`. Simplifies buildScrollContainerScope to filter out non-string identifier/label fields instead of mapping them to an empty-string placeholder — behaviorally identical (the placeholder always failed isUsefulScope's length check anyway) but removes a StringLiteral mutation surface that a synthetic fallback could never legitimately kill without coupling a test to Stryker's specific replacement text. Addresses further review feedback on PR #1474 (#1455). |
||
|
|
1cfd391478 |
feat: expose Limrun device session capabilities (#1485)
* feat: expose Limrun device session capabilities * fix: preserve Limrun provider layering * test: cover Limrun device session facade * test: focus Limrun device session coverage * refactor: tighten Limrun device session facade |
||
|
|
8b1caed2df |
fix: relaunch iOS simulator app URLs atomically (#1483)
* fix: relaunch iOS simulator app URLs atomically * fix: align iOS URL relaunch lifecycle ownership |
||
|
|
90e900a54b | 0.20.2 v0.20.2 | ||
|
|
885c1486bb |
test(ci): single-retry policy for enumerated contention-flaky files (timeouts only) (#1448)
* test(ci): single-retry policy for enumerated contention-flaky files * fix: satisfy fallow * test(ci): read failures through a lane reporter so timeouts stay distinguishable * test(ci): cover the lane reporter and drop its duplicated boilerplate * chore(fallow): own the retry lane's tool-loaded export seams * test(ci): block retries on non-test failures and classify timeouts structurally * test(ci): decide retry eligibility from runner metadata and route gate verdicts through blockers * refactor(ci): name the retry policy's rules in code instead of comments * test(ci): mark runner-aborted timeouts inside the runner instead of inferring them * test(ci): make timeout provenance a per-run secret, not a writable flag Cover direct task.meta mutation in the real child-Vitest fixture gate. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(ci): retry the failed files in the first run's project and coverage modes Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix: drop deleted repo-health file from the retry list after #1480 Rebase onto main post-#1480: the SkillGym/repo-health descope deleted scripts/repo-health/run.test.ts, whose CONTENTION_RETRY_FILES entry would now fail this PR's own missing-file check, and inlined the slow-test budgets into the reporter, resolving the budgets-module import. Envelope comments now point at scripts/lib/lane-envelope.ts instead of the closed #1430. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
d5eb785e6f |
docs: propose daemon module boundaries (#1451)
* docs: propose daemon module boundaries * docs: preserve repair close retry state in proposal * docs: align session boundary probe with handoff |
||
|
|
53e4be5f86 |
Remove SkillGym suite and repo-health snapshot infrastructure (#1480)
* chore: drop SkillGym and the repo-health aggregator (#1412 descope) Remove the SkillGym harness (test/skillgym/), its check-affected lane, package scripts, and devDependency — the help-conformance bench is now the single non-gating small-model oracle. skills/ markdown classifies as docs in the affected-check selector instead of failing open. Remove scripts/repo-health: its only gating assertion duplicated the Layering Guard job, its case-count metric imported the deleted SkillGym suite, and its sole planned consumer (#1424 / PR #1477) was closed with the Track C descope on #1412. Verified: check-affected node --test suites, oxfmt, oxlint, tsc, check:layering, fallow audit vs origin/main, and the full unit suite (unit-core + subprocess-stub) all pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * fix(scripts): fold slow-test budgets into the reporter for production-exports The Fallow production-exports gate flagged all three budget exports: their in-file consumer (SLOW_TEST_RATCHET) and the repo-health entry point that kept the module reachable were both removed in the descope, leaving the config-loaded reporter as the only consumer — invisible to --production analysis. The data-only module's second consumer is gone, so per the boundaries-are-earned norm the constants move into the reporter instead of gaining a suppression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep * docs: align skills/ format policy and purge last SkillGym mention Address both P2 review findings on #1480: the testing-matrix row and the selector's formatGate both still claimed oxfmt covers skills/, while selectChecks classifies skills/*.md docs-only (oxfmt ignores **/*.md, so the claim was a no-op even before). The matrix now states the docs-only policy and formatGate drops the dead underSkills fact. The merged examples/README.md index (from #1469) loses its skillgym mention. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUv7bvbWNryuXgSBuqTtep --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
6ab59630e4 |
ci(android): trust cached fixture APKs (#1479)
* ci(android): trust cached fixture APKs * fix(ci): keep Android emulator script lines self-contained * fix(ci): assert Android fixture smoke evidence * fix(ci): harden Android fixture smoke cache path * test(ci): verify Android fixture foreground app * fix(ci): read Android snapshot response envelope |
||
|
|
630dc7c99b |
feat(examples): add runnable Node.js SDK examples under examples/sdk/ (#1469)
* feat(examples): add runnable Node.js SDK examples under examples/sdk/ examples/test-app is a fixture and the repo's only prior examples/ content; the real SDK usage patterns lived only in website/docs/docs/client-api.md with no runnable script anywhere. Adds four standalone, typechecked examples covering the minimum surface from #1463: root client session (create -> open -> snapshot/tap -> close with typed error handling), agent-device/metro (normalizeBaseUrl/resolveRuntimeTransport), agent-device/contracts (centerOfRect on a snapshot node), and agent-device/batch (runBatch for a custom transport). Each imports the published `agent-device/...` subpaths rather than relative src/ paths. examples/sdk/tsconfig.json path-maps those subpaths to src/sdk/ so `pnpm typecheck` (now also run against this tsconfig) checks the examples in CI without a prior build, workspace link, or publish step. Running an example for real still resolves `agent-device` as a self-referencing package after `pnpm build`. src/__tests__/client-api-examples-drift.test.ts guards the examples against drifting from client-api.md's subpath API manifest in both directions, picked up automatically by the existing unit-core vitest project (no new script or workflow needed). examples/README.md indexes the new examples and notes that test-app/ remains a fixture, not an example; it is not renamed or moved. Refs #1463 * fix: address Fallow findings on the new SDK examples Fallow flagged the four examples/sdk/*.ts files as unused files (not reachable from any entry point) and three functions as high complexity. - Register the examples as manual entry points in .fallowrc.json, matching how other standalone scripts (scripts/patch-xcuitest-runner-icon.ts, scripts/runner-request-count/run.ts) are already declared. - Reduce complexity in client-session.ts and contracts-result.ts by extracting device-resolution/error-reporting and rect-assertion helpers out of main(). - Reduce complexity in the drift guard's parseSubpathManifest by splitting bullet-matching and backtick-name extraction into their own functions. Verified: pnpm check:fallow --base <PR base sha> now reports no issues, and pnpm check:tooling / pnpm test:unit stay green. Refs #1463 * fix: compile client-api.md's actual code snippets, not just its symbol manifest Addresses review feedback on #1463's drift guard: the existing guard only parsed the doc's "Public subpath API" bullet manifest and compared imported symbol names, so a fenced ```ts snippet could drift or stop compiling without the guard noticing. Added test/integration/client-api-doc-snippets.test.ts, which extracts every fenced ```ts block from client-api.md and typechecks it against the real agent-device/* sources (reusing examples/sdk/tsconfig.json's existing paths mapping, read via `tsc --showConfig` so there's one source of truth). Free identifiers that continue a `client`/`snapshot` from an earlier snippet are stubbed — typed against the real SDK return type, not `any`, so continuation snippets still get real checking. Lives in the Node integration lane (test/integration/*.test.ts), not vitest's unit-core: it spawns a real tsc Program, well past the unit suite's 2.5s budget. Running this check against the existing doc surfaced real, pre-existing snippet bugs (unrelated to the new examples), fixed here: - "sessions.artifacts": `result.cloudArtifacts` accessed without narrowing the `CloudArtifactsResult | DaemonArtifactsResult` union first. - "Device cloud sessions": `platform`/`device` were passed into the client constructor config, which doesn't accept them; moved to the `apps.open()` call where those fields actually belong. - "Android ADB providers": the inline `exec` handler had no parameter types, so it failed under strict/noImplicitAny; annotated with the real `AndroidAdbExecutorOptions` type. Two further gaps the check surfaced are pre-existing product/API-surface questions out of scope for this PR (not the new examples), so they're allowlisted in KNOWN_DOC_GAPS with comments rather than silently patched: - "Remote Metro helpers" documents prepareRemoteMetro/reloadRemoteMetro/ stopMetroTunnel/resolveRemoteConfigProfile as public, but none of them are exported from agent-device/metro or agent-device/remote-config today. - "Web sessions"/audio probe pass `platform` to `observability.network()`/ `.audio()`, but NetworkOptions/AudioOptions have no `platform` field even though the CLI's network/audio commands accept `--platform`. Refs #1463 * fix: close the doc-snippet compiler's stubbing hole and the two suppressed gaps Addresses the second round of review feedback on #1463's drift guard: 1. stubFreeNamesAndRecompile auto-stubbed every "Cannot find name" as `any`, so a typo like `cliet.apps.open()` would silently pass on the second compile. It now only stubs identifiers in an explicit allowlist (KNOWN_FREE_NAME_STUB_TYPES) — the real SDK-derived continuations (`client`, `androidClient`, `snapshot`) plus the doc's own invented host-glue names, each typed precisely rather than loosely. Anything else is left as a real compile failure. Added a regression test that feeds a `cliet` typo through the guard and asserts it fails. 2. KNOWN_DOC_GAPS filtered six real compiler errors out of the final assertion while the test claimed every snippet compiles. Investigated both and fixed the actual contracts instead of suppressing them: - `prepareMetroRuntime`/`reloadMetro` (src/metro/client-metro.ts) and `stopMetroTunnel` (src/metro/metro.ts) already existed and matched the doc's described workflow almost exactly (same result shape) but were never re-exported from `agent-device/metro`; same for `resolveRemoteConfigProfile` and `agent-device/remote-config`. Added the four exports and fixed the doc's stale function names (`prepareRemoteMetro`/`reloadRemoteMetro`) and one stale field name (`profileKey` -> `companionProfileKey` on the prepare call) to match. - `NetworkOptions`/`AudioOptions` (src/contracts/client-observability.ts) had no `platform` field even though the CLI's `network`/`audio` commands accept `--platform` for the same use case, and the client methods already forward the options object to the daemon generically (`executeCommand('network'|'audio', options)`) — so this was a type gap, not a runtime one. Switched both from AgentDeviceRequestOverrides to DeviceCommandBaseOptions (matching PerfOptions' existing pattern), closing the gap for real instead of stripping `platform` from the doc. - The "Android installFromSource()" snippet was missing its `createAgentDeviceClient` import outright; added it. KNOWN_DOC_GAPS is gone — every fenced snippet now compiles for real, and the test's assertion matches what it claims. 3. Switched the raw `execFileSync` calls to `runCmdSync` from src/utils/exec.ts, per AGENTS.md's process-execution invariant (this is a .ts integration test, not a packaging fixture that needs to stay dependency-free). Refs #1463 * docs: fix stale reloadRemoteMetro() prose reference to reloadMetro() The prose right after the Remote Metro helpers snippet still named the old function; the compile guard only checks the fenced snippet, not surrounding prose, so it didn't catch this leftover from the prior rename. Refs #1463 --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
255deb6c28 |
ci: fold single-grep jobs into steps, call named pnpm scripts (#1465)
* ci: fold single-grep jobs into steps, call named pnpm scripts - Merge ios-runner-swift-compat and no-test-di-seams (each just checkout + one rg assertion) into steps of a new static-checks job, keeping each step's own failure message. Removes two job-scheduling/ checkout overheads and two PR status-check lines. - Replace the layering-guard job's inlined copies of check:layering and depgraph:test with the named pnpm scripts, removing the silent-drift risk between the workflow and package.json. - Fix the same drift in conformance-regenerate.yml, which inlined maestro:conformance:regenerate byte-for-byte. - Leave affected-selector's inline node invocation as-is: R8's zero-dep closure check (scripts/layering/zero-dep-jobs.ts) finds a job's entry scripts by matching literal paths in the run: block, so switching to `pnpm check:affected:test` would zero out its entries and make R8 fail closed. Documented inline why this one stays inlined. - Leave publish-mcp-registry.yml's sync-mcp-metadata --check alone: that job never runs the setup-node-pnpm action, so pnpm isn't provisioned there at all. Refs #1462 * ci: teach R8 to resolve pnpm script names, drop affected-selector's inline copy R8's zero-dep-job entry scan matched literal script paths in a run: block, so a bare `pnpm <script>` invocation found zero entries and R8 failed closed — the reason affected-selector kept an inline node command instead of calling pnpm check:affected:test (#1462). zeroDepJobs now also resolves a pnpm script name against package.json and scans the resolved command for entry paths, so affected-selector can call the named script like every other job. Also replaced the other workflows' inlined copies of named package.json scripts (test:replay:*, perf, perf:android, maestro:conformance:differential, check:mcp-metadata, size) with their pnpm names, keeping each job's CI-specific trailing flags — found via a repo-wide sweep for any run: block whose text duplicates a scripts entry. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L67kDSTAJwaoLCwANEJFRM * fix: revert publish-mcp-registry pnpm regression, recurse R8 alias resolution publish-mcp-registry.yml's job only provisions Node via actions/setup-node, never the repo's setup-node-pnpm action, so pnpm is never installed there — the earlier sweep's `pnpm check:mcp-metadata` would have broken the release path. Reverted to the direct node invocation with a comment explaining why, matching the PR's own stated rationale for leaving it alone. zeroDepJobs' pnpm-alias resolution only expanded one level: a resolved script that itself invoked another named pnpm script had its entries silently dropped from R8's closure. resolveRunEntries now recurses through chained aliases with a per-chain visited set, so a nested alias's entries are found and a cycle stops re-expanding a repeated name instead of recursing forever. Added coverage for both the chained and cyclic cases. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L67kDSTAJwaoLCwANEJFRM --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
4e4ecdea0d |
test(ios): expand simulator e2e coverage (#1408)
* test(ios): expand simulator e2e coverage * test(ios): make coverage checks host portable * test(ios): handle deep link confirmation * test(ios): fix deep link prompt selector * ci: stabilize full simulator nightly * test(ios): stabilize permission prompt lifecycle * test(e2e): wait for route-specific landmarks * test(e2e): reset permissions from inactive app * test(e2e): redeliver trusted cold deep links * test(ios): verify orientation native readback * test(ios): stabilize simulator permission coverage * test(ios): wait for tab target after deep link * test(ios): paginate full event timeline * test(ios): simplify event pagination coverage * test(ios): stabilize simulator e2e coverage * test(ios): model simulator recorder lifetime * ci(test-app): cache fixture dependencies * fix(ci): isolate test app cache by node * fix(ios): settle fixture route navigation * test(ci): waive unbenchmarked ios system UI help * fix(ios): tolerate delayed simulator scale lookup * 0.20.1 * test(ci): remove superseded system UI waiver * fix(ios): harden simulator e2e reliability * chore: clarify Apple runner CI steps * fix(ios): wait before fixture home snapshot * fix(ios): require exact catalog navigation * chore(ci): format rebased workflows * fix(ios): retry unobserved fixture navigation * refactor(test): remove iOS e2e workarounds * fix(ci): verify fixture artifact provenance * fix(ci): align fixture artifact fingerprints * fix(ci): use unified Android helper packager * perf(ci): scope fixture build concurrency * chore: format fixture artifact tests * test(ios): update split Apple coverage owner * fix(ios): accept deep-link confirmation alerts |
||
|
|
6544e9a0c5 |
obs: repo-health snapshot command aggregating existing analyzers into one JSON (#1471)
* obs: add repo-health snapshot command aggregating existing analyzers into one JSON Adds `pnpm repo-health [--json]`, an offline command that aggregates the signals this repo already computes into one deterministic JSON snapshot by reusing each analyzer (depgraph, layering ratchets, coverage json-summary, size-report, fallow baselines, slow-test ratchet, bench/skillgym registries) rather than reimplementing any metric. Carries mandatory v1 provenance (schemaVersion, commit, per-analyzer content hashes, input provenance) so #1424 can persist history. Component metrics (instability/abstractness/main-sequence distance) are observatory-only. The only gating behaviour is the depgraph-vs-layering R6 consistency assertion, already wired into the Layering Guard job via scripts/depgraph/model.test.ts. Closes #1423 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * repo-health: hash read artifacts and flag staleness in provenance Coverage and size are artifacts repo-health reads but does not produce, so they carry no producing-commit stamp. Previously provenance recorded only their paths and the current HEAD, so a snapshot taken after a source edit without rerunning those producers would pair prior metrics with the new SHA — #1424 would persist a false commit-indexed history entry. Now each read artifact is bound via artifactProvenance() to its content hash (so history keys on the bytes the metrics came from, not a commit they may predate) and an explicit `stale` flag (true when a production source file is newer than the artifact). The coverage/size analyzer-config hashes are added to provenance.tool, and the human summary marks stale artifacts. Adds a pure stale-artifact regression test. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * repo-health: bind artifact freshness to the whole producer input set The stale check derived freshness only from listSourceFiles() (src/*.ts) and applied it to both coverage and size. Coverage also depends on tests and vitest config; size-report.mjs reads package.json and runs npm pack — so a change to a non-src producer input could leave an old artifact marked stale:false while provenance recorded the current HEAD, a commit the metrics predate. Each read artifact now declares its full producer input set (PRODUCER_INPUTS) and is flagged stale when ANY tracked input in that set is newer than the artifact (via git ls-files mtimes). Freshness that cannot be proven — an empty observable input set — is reported stale, never falsely fresh. The input set is recorded as provenance.inputs.*.producerInputs. Adds isArtifactStale() with a regression covering a non-src input newer than the artifact and the unprovable case. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * repo-health: prove artifact freshness from a stamped producer commit; route git through runCmdSync Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
d6d2e09529 |
test(kernel): cover all six errors.ts exports, kernel-errors mutation 55.19% → 86.26% (#1475)
* test(kernel): cover all six errors.ts exports with table-driven cases (#1456) Extends errors.test.ts beyond normalizeError/toAppErrorCode: - AppError construction and NormalizedError/AppError instanceof contract - asAppError identity/wrap/duck-typing behavior (cross-realm errors, non-Error throws, fallback code precedence) - isAgentDeviceError real-instance-only contract - retriableForErrorCode and defaultHintForCode, table-driven over KNOWN_APP_ERROR_CODES (the module's own source of truth) rather than a hand-copied code subset - normalizeAgentDeviceError delegation to normalizeError - a cross-cutting composition test asserting the retriable/hint contract request-router.ts's enrichDaemonError and the CLI's printHumanError actually rely on No production changes; mutation re-run and PR to follow. * test(kernel): pin maybeEnrichCommandFailedMessage gating and regex boundaries (#1456) Adds targeted cases for branches the table-driven/contract tests didn't reach: the COMMAND_FAILED/processExitError/string-stderr gating in maybeEnrichCommandFailedMessage, GENERIC_EXIT_MESSAGE's `-?\d+$` boundary (negative code, trailing text, non-numeric code, whitespace in the tool token), and STDERR_NOISE_PREFIX's zero-or-more-whitespace semantics for both the adb/xcrun/simctl and "error:" groups. * test(kernel): close remaining mutation gaps in firstStderrLine/detail helpers (#1456) Each addition here was verified against a hand-applied mutation (matching Stryker's actual survivor diffs) before landing, confirming it fails on the mutant and passes on the original: - firstStderrLine: line-trim-before-prefix-match (a real skip-pattern line ahead of the leading-whitespace one is required, since redactDiagnosticData already .trim()s the whole stderr string once before firstStderrLine ever sees it), and the >200-char truncation branch (both sides of the boundary: 200 exact vs 201+). - stringDetail/booleanDetail: reject a wrong-typed diagnosticId/logPath/ hint/retriable instead of surfacing it. - stripDiagnosticMeta: details drops to undefined once stripping the known meta keys leaves nothing behind. * style(kernel): apply oxfmt to errors.test.ts Fixes the Lint & Format CI failure on PR #1456 — format:check flagged this file, oxfmt --write resolves it with no behavior change. * test(kernel): split errors.test.ts into focused sibling suites (#1456) Per review feedback: the growing errors.test.ts hit 601 lines, past AGENTS.md's extract-before-500 threshold (tests are not exempt). Splits along the reviewer's requested seams, moving tests verbatim with no assertion changes: - errors-message.test.ts (234 lines) — stderr/message normalization: maybeEnrichCommandFailedMessage, firstStderrLine, the GENERIC_EXIT_MESSAGE/STDERR_NOISE_PREFIX regex boundaries. - errors-metadata.test.ts (157 lines) — hint/diagnosticId/logPath/ retriable/supportedOn lifting and stripping, divergence passthrough, the stringDetail/booleanDetail/stripDiagnosticMeta type-guard contracts, and the normalizeError/retriableForErrorCode/ defaultHintForCode cross-cutting contract. - errors-code-policy.test.ts (228 lines) — AppError construction, asAppError/isAgentDeviceError duck-typing contract, toAppErrorCode, the retriableForErrorCode/defaultHintForCode table-driven policy tests, and normalizeAgentDeviceError. Also repoints scripts/mutation/ownership.test.ts's two references to the deleted errors.test.ts path at errors-code-policy.test.ts — the mutation ownership deriver reads the file from disk to follow its import graph, so a stale path silently resolves to "owns nothing" instead of failing loudly. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
ce7b7a93d9 |
feat: expose Limrun runtime (#1476)
* feat: expose Limrun runtime * fix: address Limrun export feedback |
||
|
|
389f8fc70c |
test(apple): split the 3,487-line core index.test.ts along its import boundaries (#1472)
* test(apple): split apple/core index.test.ts into per-module files (#1459) apple/core/__tests__/index.test.ts had grown to 3,487 lines / 100 test() call sites across nine unrelated production modules with zero describe() blocks, mirroring the aggregation disease android already fixed with its per-concern split. Cut along the existing import boundaries: apps.test.ts, interactions.test.ts, simulator.test.ts, physical-device-screenshot.test.ts, screenshot.test.ts, screenshot-status-bar.test.ts, devicectl.test.ts. config.ts and runner/runner-client.ts have no dedicated file since no test's primary subject is one of their own exports (their symbols are only referenced as constants/mocked dependencies inside other modules' tests). Move-only: every test body is byte-identical to the original apart from relocated shared setup. Device fixtures and the xcrun-stub harness used by 2+ files moved to a new apple-core-stub-helpers.ts; each split file keeps only the vi.mock() blocks its own tests actually exercise (narrowed from the original's blanket five-module mock, verified safe since the dropped mocks always defaulted to real-passthrough behavior anyway). Verified 100 test() call sites total across the new files (matches original), and vitest.config.ts's SUBPROCESS_STUB_TESTS glob updated to the new file list. Per repo convention, these process-spawn suites are timeout-flaky under CPU contention; a timeout on an unrelated file is not a regression, only assertion failures are. * style: run oxfmt on apps.test.ts CI's Lint & Format check caught a multi-line import in the new apps.test.ts header that oxfmt wants collapsed onto one line. |
||
|
|
e0b8463ef8 |
feat(scripts): blast-radius query — dependents, owning gates, live-coverage owners (#1425) (#1470)
* feat(scripts): blast-radius query over the depgraph model (#1425) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(scripts): keep --limit bound to its value in depgraph affected Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
ba1a5efbc6 |
refactor(layering): declare R1-R3 as a policy table, and test them (#1449)
R1-R3 were three hand-written predicate functions. Each was short, but each
buried its boundary in control flow: you had to read the early-returns to learn
that R3 tolerates dynamic imports, or that R1 opens exactly one door. They are
now data in scripts/layering/zone-policy.ts -- which zones a boundary governs,
which import kinds it tolerates, which path prefixes are its declared seam --
walked by one small evaluator. A fourth zone boundary becomes a table entry
rather than a fourth predicate to keep consistent with the other three.
R1 is deliberately two entries rather than one with a special case, because
"kernel may import contracts type-only" and "kernel may import nothing else at
all" are two statements, and writing them separately is what makes the single
open door visible.
The refactor exposed a real gap: checkLayeringRules had NO unit test. The only
thing exercising R1-R3 was the real tree, which is clean, so a rule that had
silently stopped matching would have looked exactly like a rule being obeyed.
zone-policy.test.ts now asserts each boundary fires and each documented
exemption holds, including that src/daemon/client/ is excluded from the daemon
seam. Verified end-to-end by injecting one violation per rule plus an exempt
file: the gate reports 4 zone-policy violations (R3 twice, catching the
daemon/client case), ignores the type-only and dynamic edges, and exits 1.
Also records in docs/dependency-graph-findings.md the result of spiking
eslint-plugin-boundaries under oxlint's jsPlugins, so nobody repeats it. It
does work -- jsPlugins loads npm ESLint plugins with no ESLint install, and
R1-R3 are all expressible once you know importKind: "value" and
settings["boundaries/dependency-nodes"]. Not adopted: no ratchet mechanism (the
thing that took R6 from 61 to 7 incrementally), it cannot express R4-R9 so the
architecture would be defined in two places, it misreads inline `{ type Foo }`
specifiers as value imports (one false positive on providers/limrun/android.ts),
message interpolation renders empty under the current selector syntax, and it
costs 230 transitive packages on an API documented as alpha.
No behaviour change: 932 source files, R6 = 7, R7 = 41 fields, R8 clean,
R9 = 102, all identical to before.
Claude-Session: https://claude.ai/code/session_01Bfu8HofkhybiAm5LECfqur
Co-authored-by: Claude <noreply@anthropic.com>
|
||
|
|
a0aa02579b |
build(android): unify the 4 helper build/package scripts behind one parameterized pair (#1466)
* build(android): unify snapshot/ime helper build+package scripts Replace the four ~75%-duplicated shell scripts with one parameterized build script and one parameterized package script, mirroring scripts/build-xcuitest-apple.sh's env-var-driven pattern. The helper is selected via AGENT_DEVICE_ANDROID_HELPER or a first positional arg; per-helper differences (HELPER_DIR/PACKAGE_NAME, snapshot's test-compile+run step, ime's aapt2 resource-compile step, and the manifest JSON fields) live in small case blocks. All package.json entry points keep their names and output paths. Verified byte-level equivalence between main and this branch: identical unzip -l listings, identical classes.dex SHA-256 for both helpers, and identical manifest fields (only the per-signing-run sha256 differs). Fixes #1461 * chore: drop stale android/multitouch-helper .gitignore entries The multitouch helper was consolidated away in #1281; these two lines were never cleaned up. |
||
|
|
6f476b54b7 |
refactor(daemon): table-ify handleSessionCommands (#1458) (#1467)
Replace the 18-branch if-chain in handleSessionCommands with a handler table keyed by session-routed command name, satisfies-checked against DescriptorSessionRouteCommandName (derived from command-descriptor registry entries with route: 'session'). A missing handler entry is now a compile error instead of a runtime routing gap caught only by expectHandlerResponse. Command-kind groupings (inventory/state/ observability/replay) map several command names to the same shared handler reference, preserving the former getSessionCommandKind clustering without the runtime lookup. Deletes the fallow-ignore-next-line complexity waiver the if-chain required. getSessionCommandKind itself is no longer called from production code (only exercised by its own unit test), so it's added to the fallow production-exports allowlist alongside the existing test-only-consumer entries. Claude-Session: https://claude.ai/code/session_01U7JRr87E2QguHF36XrVbph Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
666d5ae4fd |
test(replay): split session-replay-vars.test.ts by actual subject (#1460) (#1468)
* test(replay): split session-replay-vars.test.ts by actual subject (#1460) session-replay-vars.test.ts contained zero references to buildReplayBuiltinVars (the module it was named after) — it actually held tests of replay/vars.ts, replay/script.ts, and runReplayScriptFile. Redistribute each test to where its subject actually lives: - vars.ts unit tests move to new src/replay/__tests__/vars.test.ts - script.ts env-directive/actionLines tests move into script.test.ts (none were duplicates of existing coverage there) - runReplayScriptFile tests merge into a new sibling, session-replay-runtime-maestro.test.ts, since they depend on a vi.mock('../../../core/dispatch.ts') configuration incompatible with the one already in session-replay-runtime.test.ts - one genuine duplicate (`--update` env-directive regression test) is deleted in favor of the equivalent already in session-replay-runtime.test.ts No production code changed. * test(replay): reduce complexity of a moved runReplayScriptFile test The Fallow Code Quality gate flagged the "dispatches resolved literals with file env overridden by CLI" test in the new session-replay-runtime-maestro.test.ts for exceeding the cyclomatic complexity threshold, since fallow audits it as newly-added code in this file. Extract the unresolved-interpolation check into a helper and destructure the captured calls to cut branching, with identical assertions. --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
b2b116fefb |
fix(replay): resolve Maestro failures and resize progress (#1450)
* fix(replay): secure Maestro failure diagnostics * refactor(replay): keep Maestro env API compatible * fix(replay): preserve diagnostic contracts * test(replay): verify resize cleanup rows * fix(replay): distinguish terminal resize reflow |
||
|
|
0557fee626 |
test: smoke every package.json export subpath from the packed package (#1452)
The installed-package test only exercised 3 of 11 export subpaths (io, metro, and contracts as a bare side-effect import), so a broken subpath export shipped silently despite external consumers importing all 11. The consumer script now derives the subpath list from the installed package.json exports map and runs a named-export smoke invocation per subpath; a subpath added or removed without updating the smoke table fails the deepEqual gate. remote-config is type-only at runtime, so module resolution is its entire check. |
||
|
|
7402a40bac |
test: enumerate error-code recovery quizzes in a unit-lane gate (#1445)
* test: enumerate error-code recovery quizzes in a unit-lane gate Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: mark recovery quizzes structurally and derive retriability from the enumeration Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
61f8696d28 |
test(ci): gate PRs on changed-line coverage (#1418) (#1447)
* test(ci): gate PRs on changed-line coverage (#1418) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor: split coverage-changed model into small helpers for fallow Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(ci): simplify coverage-changed reporting and CLI surface Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
edca35d122 |
chore(deps): Renovate config, packageManager-derived pnpm in CI, repo-wide format (#1444)
* chore(deps): add Renovate config and enforce packageManager pnpm version in CI Refs #1422 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * chore: bump pnpm to 11.17.0 and format the whole repo with oxfmt format/format:check drop their hand-maintained path list: oxfmt already skips node_modules and honors .gitignore, so the only exclusion list is .oxfmtrc.json ignorePatterns. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(mutation): accept either quote style in the affected-lane path filter Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * chore(deps): keep fixture-app runtime deps as individual Renovate PRs Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
e89ef9e69b |
test: gate public command surface against commands.md reference (#1446)
* test: gate public command surface against commands.md reference Enumerate PUBLIC_COMMANDS against website/docs/docs/commands.md in both directions with a waivered unit-lane gate, and document the drift found on main (doctor, react-native). Refs #1420 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: recognize tilde-fenced code blocks in command-doc gate Refs #1420 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
c984bd5902 |
fix(ci): upload hidden diagnostics (#1443)
* fix(ci): upload hidden diagnostics * fix(ci): capture all hidden diagnostics |
||
|
|
006c4cadc9 |
test: nightly parser fuzz lane — parser input fails as typed AppErrors, never hangs (#1414) (#1438)
* test: nightly parser fuzz lane with typed-AppError invariant (#1414) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): run envelope, artifact promotion, and harness self-check tests (#1414) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): shared scheduled-lane envelope on every terminal path, watchdog after ready (#1414) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): envelope for malformed options; add scheduled-lane health consumer (#1414, #1430) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(lanes): actions:read scope, terminal error envelope, first-due grace (#1414, #1430) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(lanes): anchor first-run grace to schedule registration, use exec helper in tests (#1414, #1430) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(lanes): portable POSIX pickaxe pattern for schedule registration (#1414, #1430) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(fuzz): fast-check generators over the shared hazard list, drop the bundled lane-health work (#1414) - Strip scripts/scheduled-lane/* and scheduled-lane-health.yml: that watcher is #1430's own deliverable and collides with PR #1439's implementation of the same lane. What this lane owes (a per-run envelope) moves into scripts/fuzz/envelope.ts. - Rebase onto #1437 and rebuild the generator layer on fast-check: cases come from arbitraries sharing SELECTOR_VALUE_HAZARDS with the property suite, and counterexamples are shrunk, so a failure names a minimal input plus fast-check's seed/path instead of a 20k-char random string. - Route harness.test.ts into the serialized subprocess-stub project. - Drop the AGENT_DEVICE_FUZZ_STARTUP_DELAY_MS test seam: the ready handshake is now proven by a case budget far below real worker startup. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): replay the regression corpus through the worker watchdog (#1414) A promoted hang case used to wedge the unit job until the CI timeout, because corpus replay called checkCase in-process. It now goes through the same worker-backed watchdog the nightly lane uses, so such a case fails against a 5s per-case budget; the file moves to the serialized subprocess-stub project with the rest of the worker-driven fuzz tests. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): let the watchdog outlive vitest's default case timeout (#1414) A wedged parser was surfacing as a bare 'Test timed out in 5000ms' instead of the named hang: failure that says which input wedged, because the file's vitest timeout was shorter than the watchdog budget times the number of replayed cases. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(fuzz): complete drift provenance in the lane envelope (#1414) configHash now covers every input that decides what a seed generates (generate.ts and the shared property arbitraries, not just the arbitraries/targets/invariant), and tool records fast-check's installed version. A generation-loop edit or a fast-check upgrade previously changed the case set while the envelope looked unchanged. A test recomputes the hash with each input omitted so a future omission fails. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
e545544dfa |
test(daemon): seeded concurrency torture lane for session/lease/lock invariants (#1439)
* test(daemon): seeded concurrency torture lane for session/lease/lock invariants Refs #1416 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): drive torture lane through real lock plan + review fixes - derive each op's lock plan from production resolveRequestExecutionLockKeys via a fake device-inventory provider, so reverting the router's same-device serialization trips the overlap invariant (verified) - assert exact replay: full scheduler trace, terminal outcome, contention - assert real same-device lock contention in the sweep + a forced 2-client case - split harness into bindings/invariants/envelope modules (all <500 LOC) - emit #1430 scheduled-lane envelope (schema/SHA/hash/seed range/duration/result) and upload it from the nightly workflow Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): pass claim data as plain view accessors (fallow) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): gate lock plan on shouldLockSessionExecution; sweep replay + forced-device contention; whole-lane envelope Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): add real-scope runLocked serialization guard; whole-lane envelope duration Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): allow seed 0 replay; add seed-0 regression (TORTURE_SEED must accept 0) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * obs(#1430): add scheduled-lane freshness/cadence health watcher Discovers schedule: workflows from .github/workflows/, reads recent scheduled runs via the GitHub API, and opens/pings a tracking issue when a lane misses or fails two consecutive cadences. Pure model unit-tested and gated on PRs; API I/O + issue open/ping run nightly. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): give scheduled-lane watcher a two-cadence newborn grace Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): anchor lane grace on schedule-introduction, not workflow age Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): derive schedule-activation semantically via git, through runCmdSync Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): add merge-commit regression pinning first-parent + committer time Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): pin scheduled-lane-health issue-write route via stubbed fetch Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): unbundle #1430 watcher; make torture lane nightly-only Strip the scheduled-lane-health watcher (scripts + workflow + PR gate) — it is #1430's deliverable and collides with PR #1438's workflow of the same filename; keep only this lane's #1430 envelope writer. Move the torture lane under test/integration/nightly/ so it is out of the test:integration:node glob, and run it via an explicit, disclosed PR step plus the nightly sweep. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * docs: fix stale torture-lane paths after nightly/ move Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(daemon): adopt shared lane-envelope for torture lane Rebase onto main (post-#1441) and replace the lane-local LaneEnvelope dialect with the shared scripts/lib/lane-envelope.ts builder, so the #1430 health watcher parses one schema: commitSha->commit, sourceHash-> configHash, seedRange/runs moved into the typed data payload, and the sweep encoded as seed "<start>-<end>". Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
8cce0ef6b8 |
test: ratchet mutation score over enumerated decision kernels (#1441)
* test: ratchet mutation score over enumerated decision kernels Adds a Stryker (vitest runner) mutation lane scoped to the decision kernels, a per-module baseline with tool/config provenance, and a ratchet that only lets scores rise. Non-gating until two consecutive stable weekly sweeps. Refs #1415 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * chore: declare the mutation test-scope seam for production-export analysis Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: own kernel tests in the mutation registry and ship the #1430 lane envelope - restore bench:help-conformance, broken by a formatting-path edit - kernel test files select their module on PRs (registry `tests` + workflow paths), asserted to reach the kernel through the import graph - every mutation run writes the standard scheduled-lane artifact envelope - move src/utils/__tests__/errors.test.ts beside its source per the mirror rule Refs #1415, #1430 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: derive kernel test ownership and land the scheduled-lane health monitor Ownership of a kernel's tests is now computed from the static import graph (scripts/mutation/ownership.ts) instead of a hand-listed set, so a test that reaches a kernel indirectly -- src/__tests__/daemon-error.test.ts through src/daemon.ts -- selects that kernel on a PR. The PR lane triggers on every src test and shards the derived modules, keeping wall clock at one module. The lane envelope (#1430) is now written on every exit path with the stage it reached, so a crash before any mutant runs is distinguishable from a lane that never ran. Adds the derived cadence monitor (scripts/lane-health, daily workflow): scheduled lanes are enumerated from .github/workflows/ and reported dark, failing, or never-run against their own cron cadence. * fix: merge only Stryker reports from a shard directory The shard artifacts now carry the lane envelope beside mutation.json, and the merge globbed every .json under the download path, so the ratchet job fed the envelope to the report parser and died after the mutants had already run. * fix(mutation): fail on incomplete shard sets and envelope pre-run failures Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(mutation): downgrade a passing envelope when a later lane step fails Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * refactor(mutation): shard by registry, defer the PR lane, drop the bundled watcher Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * docs: describe registry sharding and the deferred PR mutation lane Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(mutation): make the pre-graduation tooling exception select real mutants Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(mutation): give the worktree fixture commits their own identity Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
d747ef6230 |
test: frozen replay-compat corpus with expected verdicts (#1417) (#1436)
* test: frozen replay-compat corpus with expected verdicts (#1417) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: pin replay-compat corpus bytes to released blobs and assert via parseReplayInput Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: lock replay-compat provenance kind by corpus area and verify it in CI Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * docs: describe corpus provenance-kind lock and CI job Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: prune replay-compat corpus to minimal witnesses per shipped form Reviewer feedback on #1436: the mechanism earns its place, the dataset did not. Drop the 30 corpus entries whose bytes repeat a syntactic form or a migration refusal another entry already witnesses (platform twins and adjacent-release re-recordings), leaving 22 deliberate entries; make note required and state per entry which form or refusal it is the sole witness of. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: address corpus review nits (typed coverage list, cap rationale, derived-citation note) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * docs: split corpus rule — form from the release, verdict from today's parser Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * docs: format corpus README emphasis markers Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
4c02b6ad2b |
fix(cli): reject excess positionals (#1433)
* fix(cli): reject excess positionals * fix: preserve get ref labels in arity checks --------- Co-authored-by: Andrew Barnes <169967362+Bortlesboat@users.noreply.github.com> Co-authored-by: Michał Pierzchała <thymikee@gmail.com> |
||
|
|
fcaa6c995c |
refactor(contracts): declare shared vocabulary below its consumers, ratchet what remains (#1435)
* refactor(contracts): declare the public API vocabulary below its consumers
The layering gate's largest remaining cluster was 28 type-only inversions from a
single edge: `commands/` declaring itself in terms of `client/client-types.ts`.
R2 forbids the reverse import, so a shape both surfaces need has to sit below
both. The command/device vocabulary — connection config, the device and session
views, and every per-command Options/Result — now lives in
`contracts/client-api.ts`; `client/client-types.ts` keeps the `AgentDeviceClient`
facade and re-exports the rest through one wildcard.
R6 total: 42 -> 18. No new inversion in any pair.
The published surface is unchanged, and that is verified rather than asserted:
the built `index.d.ts` exports the same 216 type names as main, byte-identical.
Eight shapes deliberately did NOT move, because each is stated in terms of a
HIGHER-ranked zone: `ScrollOptions` (ScrollInputDirection, commands/), the four
navigation Options plus `AgentDeviceCommandClient` (navigation-projection,
commands/), and the two Metro result aliases (metro/). Declaring those in
contracts/ would trade 28 commands->client edges for contracts->commands and
contracts->metro ones — the foundation depending on the layers above it, worse in
kind even though fewer in number. This is measured, not assumed: moving the whole
file to contracts/ first took the gate from 42 to 48, which is how the floor was
found.
Two keystone moves made the other 84 movable:
- `RemoteConnectionProfileFields` joined its sibling `CloudProviderProfileFields`
in contracts/remote-config-fields.ts. It was the root of the base chain
(AgentDeviceClientConfig -> AgentDeviceRequestOverrides ->
DeviceCommandBaseOptions -> every per-command Options), so one rank-4
declaration was pinning ~80 shapes up with it.
- `DaemonBatchStep` moved to contracts/batch-step.ts. Its `runtime` field was
written `DaemonRequest['runtime']`, dragging the whole daemon request type in to
say `SessionRuntimeHints` — the same type, three zones lower.
`CompanionTunnelScope`/`MetroBridgeScope` also moved to contracts/, since the
vocabulary needs the scope shape and it sat next to client-local env-var names.
Six pass-through re-exports in client-types.ts are suppressed per-name with the
reason inline: they exist only to publish contracts/kernel types through the
package entrypoint wildcard, every internal consumer imports them from the
declaring module, so "no consumer" is correct and not actionable — deleting them
would remove names from the public types.
`pnpm check` green, 4488 unit tests. Findings doc records the sequencing for the
last 5: the upstream declarations have to come down before the shapes that need
them can.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bfu8HofkhybiAm5LECfqur
* docs: drop the graph viewer, keep the query that replaces it
The rendered dependency-graph viewer is not being merged (PR #1409 closed). It cost
~2200 lines plus a Fallow exemption for a 920-line canvas renderer, and nobody —
human or agent — reached a conclusion from the picture. Every finding in this
document came from short queries against the gate's own model.
This file pointed at the `claude/depgraph-viewer` branch for the tooling, which
would have dangled once that branch is deleted. Replaced with the thing that was
actually load-bearing: a throwaway probe script, inlined, that re-derives the
numbers from `scripts/layering/model.ts` and nothing else. Verified verbatim — it
reproduces TYPE_INVERSION_BASELINE exactly, which is also the check that tells you
whether either side has gone stale.
Two numbers in the summary table were stale, describing an intermediate state
rather than what shipped: R6 said "35 across 4" (actually 18 across 5 after the
vocabulary move) and ranked coverage said "729 of 894" (actually 888 of 901). Both
corrected, along with the file/edge counts in the header.
Also notes the deduplication detail that makes the query agree with the gate: each
file pair counts once, so a raw edge count reads higher.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bfu8HofkhybiAm5LECfqur
* refactor(contracts): move the four keystones that pinned the rest of the inversions
R6 type-only spine inversions: 18 -> 7, and every one of the 7 that remains is a
deliberate architectural position rather than a misplaced declaration.
Four keystones moved to contracts/, each of which was pinning a much larger set:
- `CommandFlags` (was core/dispatch-context.ts). One rank-2 declaration holding the
daemon's request type and every recorded action above it. Its last non-contracts
dependency was `DaemonBatchStep`, already moved in 3fdbfe0.
- `SessionAction` (was daemon/types.ts). replay/ (6 modules) and compat/maestro/
read and write session scripts; declaring the shape inside the daemon made both
depend on the server to describe a file format neither asks it to produce. The
daemon still owns the recording — only the shape moved.
- `TargetAnnotationV1` shape (was replay/target-identity.ts). ADR 0012 target
evidence, written by 8 daemon modules and read by commands/; the parsing and
classification logic stays in replay/.
- `ScrollInputDirection` and the Metro prepare/reload result payloads, which
unblocked `ScrollOptions` and `MetroPrepareResult`/`MetroReloadResult`.
`DaemonRequest` also split into the three shapes it had been conflating: the
kernel WIRE shape (`flags?: Record<string, unknown>`, because a process boundary
cannot enforce a vocabulary), the new `contracts/command-request.ts`
`CommandRequest` (wire shape with flags typed — what a command surface needs), and
the daemon's own refinement (+ `internal?: DaemonRequestInternal`, carrying
SessionState callbacks and the admitted lease). core/command-descriptor/ had been
importing the third to read `command`, `positionals` and `flags`.
Two things deliberately NOT moved, because moving them would add coupling rather
than remove it, and the baseline now argues both:
- `DaemonCommandDescriptor`/`DaemonCommandRoute` — the route type is
`keyof typeof DAEMON_ROUTE_HANDLERS`, derived from what the server implements.
Moving it down means re-declaring route names in contracts plus a gate to prove
the handler map still covers them. ADR 0003/0008 own that boundary.
- `AgentDeviceClient` — used as an opaque handle by 4 files. The facade is built
from commands/'s own NAVIGATION_COMMAND_PROJECTIONS, so this is a genuine
zone-level cycle; breaking it is a design call about where that registry belongs.
R5 is zero here: nothing imports the client at runtime, only its type.
Also records the largest structural finding, which R6 does not measure: cycles by
edge kind are 1 (value only), 87 (value + type-only), 1 (value + dynamic), 213
(all). At runtime the graph is a clean DAG; the 87-file type-level cluster means
no one of those files' types can be read in isolation. Hubs are
runtime-contract.ts, commands/runtime-types.ts, backend.ts,
commands/runtime-common.ts. Not attempted here — it is a different and much larger
change.
`pnpm check` green, 4488 unit tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Bfu8HofkhybiAm5LECfqur
* feat(layering): ratchet type-cycle growth (R9), and rule out a narrower client port
R9: the largest strongly-connected component over value + type-only edges may not
grow. R4 keeps the VALUE graph acyclic, so every cycle counted here is created by
type-only imports - free at runtime, invisible to R5/R6, and the largest single
obstacle to reading a subsystem in isolation: inside a component of 102 files, no
file has a self-contained slice.
Baseline set to 102, which is what THIS branch achieves - main carries 107 and the
boundary moves here bring it to 102. An earlier revision baselined 87, measured
against an older main; after rebasing onto
|
||
|
|
8670a88afa |
test: property-based testing foundation (fast-check) over parse/print and geometry kernels (#1437)
* test: property-based testing foundation over parse/print and geometry kernels (#1413) Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test: derive selector and replay command domains from their owning registries Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> |
||
|
|
e8b779cb32 |
fix(daemon): keep close-time script-save failures from leaking the session/device claim (#1392)
* fix(daemon): keep close-time script-save failures from leaking the session/device claim A close-time script write (implicit from `open --save-script`, or this close's own `--save-script`) that refuses to publish (e.g. a no-clobber target-exists AppError) threw uncaught out of `handleCloseCommand`, skipping lease release, device-claim release, and `sessionStore.delete` entirely — while the `close` action had already been recorded with no rollback. Live-repro'd over the real CLI against an Android emulator: this single gap explained both symptoms split out of #1384 into #1391 — a lingering `DEVICE_IN_USE` claim after a failed `close`, and a published `.ad` rewritten with duplicated trailing `close` lines when the same close was retried (each attempt re-recorded a `close` action on top of the one never rolled back from the prior failure). Catch the write failure, roll back the just-recorded `close` action (mirroring the existing repair-armed commit-failure pattern), and let teardown (lease release, device-claim clear, session delete) complete regardless — exactly as an ordinary platform-close failure already doesn't block them. The failure is still surfaced to the caller, but after teardown, with a corrected hint: retrying the same close is no longer meaningful since the session is now gone. Fixes #1391 * refactor(daemon): shrink handleCloseCommand/runSessionCloseTeardown under fallow's complexity gate CI's fallow code-quality check flagged handleCloseCommand (126 lines, 19 cyclomatic / 16 cognitive) and runSessionCloseTeardown (73 lines) as exceeding the large-function/high-complexity thresholds after the prior commit's fix. Extract runCloseTeardownAndRelease (teardown + lease release + claim clear + delete + ordered error surfacing) and buildCloseSuccessResponse (final response shaping) out of handleCloseCommand, and finalizeOrdinaryCloseScript out of runSessionCloseTeardown. No behavior change — same control flow, split into named, independently-readable steps; fallow now reports 0 complexity findings for this diff. * fix(daemon): preserve the write error's structured details in the close-time save failure Review feedback on #1392 (thymikee): toOrdinaryCloseSaveScriptFailure rebuilt the AppError from only the original message, dropping its machine-readable details.reason ("script_target_exists"), details.path, and cause. A caller dispatching on those fields (or reading the CLI's --json error.details) lost them even though the underlying write failure carried them. Preserve the original error's details/cause, overriding only the close-specific hint and retriable:false. Extends the #1391 regression test to assert the routed close response still carries reason/path. * refactor(daemon): drop the vestigial close-time rollback, add router-level #1391 coverage Review feedback on #1392 (thymikee), P2 items: - The close-time save-script failure's session.actions rollback (finalizeOrdinaryCloseScript) was left over from an earlier design where a failed save could keep the session alive for retry. It never does now — runCloseTeardownAndRelease always tears the session down regardless of the outcome — so there is no surviving session for a later write to duplicate the close action on. Drop the rollback; the durable events.ndjson entry (which the rollback never touched anyway) and the in-memory action now agree, both accurately recording that the close happened. - Add a request-router-level regression (request-router-typed-error.test.ts, alongside the existing repair-close BLOCKER 2 test it mirrors) proving the normalized JSON error shape a real client sees: top-level retriable:false, details.reason/path preserved, and the session torn down — not just that handleCloseCommand throws the right AppError when called directly. * test(daemon): assert the durable close event survives a failed close-time save Review feedback on #1392 (thymikee), final P2 item: the previous commit removed the actions rollback because there's no surviving session to duplicate the close action on, but nothing actually asserted the durable events.ndjson action.recorded:close event stays put. Flush and read it back so a future rollback or event-order change can't silently recreate the in-memory/durable mismatch the removed rollback used to paper over asymmetrically. * test(daemon): assert the retained session's in-memory close action, not just the durable event Review feedback on #1392 (thymikee): the durable-event assertion alone doesn't catch a reintroduced session.actions.length = actionsBeforeClose rollback, because that event is queued (and durable) before the write even attempts — a regression there would leave the assertion passing while silently reintroducing the in-memory/durable mismatch. Retain the session object past handleCloseCommand (store.delete only drops the map entry, not the object a local variable still points at) and assert its actions array contains exactly one close entry, matching the durable event count. Verified by temporarily reintroducing the old rollback locally: this assertion fails (0 !== 1) where the prior durable-only check did not, then reverted. * refactor(daemon): model repair close retry as receipt * refactor(daemon): merge blockingError to state, not explain, the save-script exclusion Following up on the comment-trimming pass already on this branch: the device-claim condition (!platformCloseError && !cleanupAggregate) and the two-line throw sequence right below it both needed a paragraph explaining why saveScriptError is excluded from one but not the other. Merge platformCloseError and cleanupAggregate into a single named blockingError — its name now states the exclusion the comment used to argue for, and the throw sequence collapses from two ifs to one. Trimmed the remaining long docblocks in this file the same way: state what's non-obvious in 1-3 lines instead of re-deriving it in prose. * refactor(daemon): clarify close script finalization |