mirror of
https://github.com/callstack/agent-device.git
synced 2026-09-14 20:06:34 +08:00
t3code/cleanup-claim-tests
19 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1f9d940bff |
refactor(capture-kit): complete ADR 0019 end state — relocate snapshot and recording zones (#2385)
* refactor(capture-kit): relocate snapshot and recording zones into capture-kit
Move the ADR 0019 end-state capture zones into @agent-device/capture-kit:
- src/snapshot/** -> packages/capture-kit/src/snapshot/** (presentation,
freshness, scroll-edge-state, ios-snapshot-runtime, android occlusion)
- src/recording/** -> packages/capture-kit/src/recording/**
- src/core/snapshot-{chrome,state,tree-ingestion,node-lookup}.ts ->
packages/capture-kit/src/
- src/snapshot-quality/ test -> capture-kit presentation tree (directory
retires with its last file)
Pure renames: import re-pointing and gate updates follow in the next commit.
The snapshot-desktop-surface test parks in src/__tests__/ because it pins
the root eager-import-closure walker.
* refactor(capture-kit): re-point capture and recording consumers to the new subpaths
Rewires every consumer of the relocated snapshot/recording modules to the new @agent-device/capture-kit subpath exports, adds the 23 subpath entries to the capture-kit exports map, fixes the moved recording-scripts test's __dirname-relative paths for the deeper location, and records the completed migration in ADR 0019's end state.
* chore(gates): align layering, mutation, fallow and CI gates with the capture-kit relocation
Moves the executable-policy roots, presentation-owner constant, zone ranks, authority fixture, mutation sharding globs, stryker aliases, fallow baselines and the iOS workflow's android-owned paths-ignore entry onto the new packages/capture-kit paths, and extends the planted-red coverage to the new presentation-owner subpath.
* chore: point capture-domain source-of-truth comments at the relocated capture-kit modules
* test: point shutdown recording mock at capture-kit and cover interactor acquisition presentation
* test(capture-kit): update upstream presentation test imports
* chore(gates): follow relocated snapshot assembly in R74
* test(daemon): freeze prewarm deadline assertion clocks
|
||
|
|
dcd8b65d4c |
refactor(daemon): split src/daemon/types.ts into request types and session state (#2346)
* refactor(daemon): split daemon/types.ts into request and session-state modules `src/daemon/types.ts` served two audiences from one file: the dispatch request shape and the daemon's live session record. It also sat in the only daemon type cycle — it imported `RefFrame` from `ref-frame.ts`, which imported `SessionState` back — so neither file could be read in isolation. Three modules replace it, each importing only downward: - `daemon-request-wire.ts` declares `DaemonWireRequest`: a dispatched request with no `internal` key and no property path to `SessionState` or `DeviceLease`, so a consumer can read a request's command, flags and public metadata without depending on the session record. - `daemon-request.ts` adds the daemon-only half (`DaemonRequestInternal`, which stays unexported) plus the response vocabulary. - `session-state.ts` owns `SessionState` and the shapes only it holds. The cycle is cut by `ref-frame-slot.ts`, declared below both `ref-frame.ts` and `session-state.ts`: it owns the frame VALUE (the class stays unexported, so the type remains nominal and unconstructible from outside), while `ref-frame.ts` keeps every lifetime transition and every `session.refFrame` write. No behavior change: every importer moves to the module owning the symbol it uses, with no re-export shim at the old path. `client-normalizers.ts` takes `SessionRuntimeHints` from `@agent-device/kernel/contracts`, which declares it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ujrc8LYmvM249WY8921J1Y * test(daemon): assert the wire request shape cannot reach session state A type-level walk over `DaemonWireRequest` fails `tsc` if the shape regains an `internal` key or grows a property path back to `SessionState` or `DeviceLease`. Positive controls over `DaemonRequest` prove the walk finds both when they are there, so a walk that never matches anything cannot pass by accident. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ujrc8LYmvM249WY8921J1Y * test(daemon): keep the three over-budget test files at their base length Splitting `daemon/types.ts` turns one combined import into two in every file that used both halves. Three of those test files are already over the 1,000-line tripwire, where the size ratchet allows no growth, so each sheds one line that was carrying nothing: - `snapshot-handler.test.ts` and `find.test.ts` each drop a `toHaveLength` assertion an adjacent `toEqual` on an explicit array literal already makes. - `session-replay-repair-transaction.test.ts` names the filtered close actions instead of wrapping the expression across three lines inside `expect`. No assertion is weakened and no test content is removed. Splitting these files along the modules they mirror is the standing remedy, but none of those modules split here, so it stays out of this change and is tracked in #2353. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ujrc8LYmvM249WY8921J1Y * chore(gates): point the daemon modularity and wire-compat gates at the split modules R7 now locates the `SessionState` declaration by the declaration itself rather than by a recorded path: `sessionStateWritePressure` measures the merge-base tree too, and that tree still declares it in `daemon/types.ts` — a path constant would measure it as zero pressure and bank the headroom. R10's external-importer ratchet covers all three modules that replaced `daemon/types.ts`, so moving a symbol between them cannot reopen the boundary to a new outside zone. The recorded membership is unchanged: `client-normalizers.ts` and `remote/daemon-artifacts.ts` both import `daemon-request.ts` only. The daemon RPC closure gate waives `DaemonRequest`, `DaemonResponse` and `DaemonArtifact` by path, so those three keys follow the declarations to `daemon-request.ts`. `DaemonRequest`'s rationale now says what it is — the server-side narrowing of the kernel declaration that fixes the wire shape — rather than calling it a re-export alias. The `live-state-shape` and session-resource declaration sites move with `SessionState`; the depgraph lookalike fixture takes a new plausible path now that `daemon/session-state.ts` is the real root. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ujrc8LYmvM249WY8921J1Y --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
ba6c818d81 |
spike(daemon): give the ADR-0014 ref frame private ownership (#2296)
* refactor(daemon): make the ADR 0014 ref frame one owned value
The four `refFrame*` fields on `SessionState` were policed only by the R7
ownership table: any daemon module could write them, and only a full-graph AST
scan could say whose write it was. They are now one `RefFrame` value whose brand
key is private to `src/daemon/ref-frame.ts`, so a module outside that file cannot
construct one and cannot edit the one a session holds; the transitions replace it
whole. Every transition, rejection reason and epoch rule is unchanged.
Readers moved to the accessors ref-frame.ts exports (`refFrameState`,
`refFrameScope`, `refFrameEpoch`, plus a new `refFrameTree` and `refFrame`).
`internal-observation.ts` drops its four-field lineage copy and its field-by-field
comparison: frame identity is now one `===`.
Seen red: with the empty-result early return removed from
`markSessionPartialRefsIssued`, the new frame-identity assertion in
session-snapshot.test.ts fails; restored, it passes. A planted foreign writer
module was rejected by tsc (TS2741 missing brand, TS2540 read-only property)
before deletion.
* docs(depgraph): note the ref frame outgrew its R7 row
* refactor(daemon): make the ref frame nominal, not symbol-branded
A symbol brand on a plain object type stops construction from nothing, but not
`{ ...refFrame(session), state: 'active' }`: object spread copies the symbol key,
so any daemon module could mint an incoherent frame (active state, stale tree)
out of a coherent one and it type-checked. Proven before the fix with a throwaway
module doing exactly that write: tsc reported nothing.
The frame is now a class with `#`-private fields behind getters. That makes the
type nominal, so no object literal is assignable to it — the same probe now fails
with TS2739 (`missing #fields, scope, generation, expired`). Construction stays
inside ref-frame.ts, and the four claim sites (ADR 0014, the SessionState field
doc, and the two in the R7 owner table) now say what the type does and does not
judge: it cannot see a whole frame moved unchanged, which is why the R7 row stays.
Expiry is idempotent by identity again. `expired()` returns THIS frame when the
frame is already expired, rather than an equal copy, which is what the lineage
check in internal-observation.ts compares with `===`. Seen red: with that early
return removed, the tightened ref-frame test fails with "Values have same
structure but are not reference-equal"; green with it.
Also: the ADR 0014 stale-ref help sample seeds its epoch through a real frame
activation again, instead of leaning on the pre-frame snapshotGeneration
fallback, and a find test drops a `?? []` that can no longer be reached.
Behavior is unchanged: same frame contents, same transitions, same admission.
* chore(gates): collapse the four ADR 0014 R7 rows into the owned refFrame value
R7's owner table listed `refFrameState`, `refFrameScope`, `refFrameTree` and
`refFrameGeneration` as four fields that had to be written together by one
module; the code now carries them as one nominal value, so the table carries one
row. R10 follows: 19 writer-owned fields to 16, 22 owner claims to 19.
The row itself stays. The type stops construction, editing and spread-derivation
of a frame outside ref-frame.ts, but it cannot judge a whole frame moved
unchanged — clearing the field, or assigning another session's frame — and the
table can. The comments say that rather than claiming full enforcement.
Seen red: a planted `session.refFrame = undefined` in snapshot-session.ts fails
R7 with "owned by src/daemon/ref-frame.ts"; green once reverted.
* style: apply oxfmt
* refactor(daemon): keep ref-frame expiry module-private
`RefFrame` exposed a public `expired()` method, so any module holding a
frame could derive a new valid one and install it through a reconstructed
session record, past the R7 field scan. Expiry is now a static on the
unexported class, reachable only inside ref-frame.ts; the frame's surface is
four getters. A type-level regression pins that no outside module can
construct, spread, edit, or derive a frame (tsc covers src tests, so a
directive that stops erroring fails typecheck).
* test(daemon): hold the three accessor migrations within the size ratchet
Each file grew by exactly its new ref-frame import; one blank line between
mock blocks goes so the files stay at their merge-base length.
|
||
|
|
947582a3cc | refactor(daemon): move interaction and find routes behind facade (#2178) (#2228) | ||
|
|
81a9cb2b3c |
feat(daemon): establish interaction application facade (#2205)
* feat: establish interaction application facade (#2177) * fix(daemon): narrow interaction runtime request seam |
||
|
|
b042045522 |
refactor(output): split presentation owners (#2202)
* refactor(output): split presentation owners * fix(output): keep candidate rendering in surface owners |
||
|
|
010f09bf0d | refactor(daemon): move open lifecycle behind session facade (#2201) | ||
|
|
9abcd7fe03 |
refactor: move Apple platform family into package (#2118)
* refactor: move Apple platform family into package * fix: preserve Apple facade sync contracts * fix: complete Apple W4 rebase review fixes |
||
|
|
ddb415a2c7 |
refactor: sink package-closed src modules into existing packages (#2106)
* refactor: sink package-closed src modules into existing packages Move closed modules into contracts, kernel, capture-kit, and ad-script, and declare DaemonCommandDescriptor in core so R6/R9 can pin the remaining provider-webdriver type cycle. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: keep contracts and capture-kit off generic sinks Move interaction-outcome, snapshot warning rendering, and inventory ALS behind focused owners, and plant R18/R70 domain-shape gates so they cannot return as package export-map growth. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: drop moved implementation comments from owner modules Names, types, and tests already carry those invariants; the relocated files should not keep review-history or control-flow narration. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor: drop the empty snapshot-quality layering zone W1 moved the verdict into capture-kit and this PR moved warning rendering into snapshot-presentation, so the ranked zone no longer has production files. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
ed8d5efb00 |
fix(daemon): address cwd-scoped sessions by their store key, not their public name (#2068)
* fix(daemon): address cwd-scoped sessions by their store key, not their public name An implicitly cwd-scoped session is NAMED `default` and STORED under `cwd:<hash>:default`. Three surfaces built caller-facing text from the name, so each pointed at something that does not exist: - `DEVICE_IN_USE` reported `session "default"` and its recovery hint said `agent-device close --session default`. `--session` marks the session explicit, which disables cwd scoping, so following the hint addressed a different, absent session: `SESSION_NOT_FOUND`, and the device stayed held — the residual half of #2031 that #2057's superseded-daemon reconciliation does not cover. - `session list` reported `sessionStateDir` and `runnerLogPath` under `<state>/sessions/default`, a directory that is never created; the session's real artifacts sit in `<state>/sessions/cwd_<hash>_default`, which is what `open` already answers with. `SessionStore.entries()` exposes the key alongside the record, and `buildSessionRecoveryHint` takes the session's address explicitly. Call sites that only hold the session the current request named pass its name — the address there — so their text is unchanged, as are explicitly named sessions everywhere. Live before/after on an iOS simulator: `open` (cwd-scoped default), then `open --session qa` on the same device. Before, the hint's own `close --session default` answered `SESSION_NOT_FOUND` and the retry failed again; after, the hint names `cwd:8bea844ab16aa9b3:default`, that close releases the device, and the retry opens. Refs #2031, #1394 * fix(daemon): thread the resolved session address through every recovery producer The store key a request resolves — `cwd:<hash>:default` for an implicit session — was known upstream but dropped before the selector-conflict and lock-conflict producers, which then named `SessionState.name`. Both emitted `close --session default`, the unreachable recovery this PR fixes for DEVICE_IN_USE: `--session` marks the session explicit, so that command addresses a different session. `SessionRef` ({ address, session }) now carries the pair, so a recovery producer cannot be handed a record whose address was never resolved. `buildSessionRecoveryHint`, `assertSessionSelectorMatches` and `applyRequestLockPolicy` take it; `prepareLockedRequestBinding` and the Maestro replay route build it from the store key they already hold. Replaces the broad `SessionStore.entries()` with the store's own narrow lookups — `lookup`, `findByDevice`, `listRefs` — so callers receive the session with its address instead of enumerating raw key/record tuples. `session list` reports `address` alongside `name`, through the client contract and serializer, so its discovery output names a value `--session` accepts. Regressions run the production routes, not the helpers: router-level device-in-use, selector-conflict, lock-policy-conflict and `session list` cases open an implicit session and read the address back off the store, plus a typed-Maestro selector-conflict case. Each was observed red against the pre-fix behavior it pins. Refs #2031, #1394. * fix(daemon): doctor names same-device sessions by address; green the fallow gate - sessionChecks enumerated same-device sessions by SessionState.name: the printed close --session default was the exact unreachable recovery this PR removes elsewhere, the name-based dedupe hid two cwd-scoped default sessions from each other, and the evidence sessionStateDir pointed at <state>/sessions/default, which never exists. Candidates now come from listRefs() and report addresses. - findByDevice joins values/delete in .fallowrc.json usedClassMembers (same resolution false positive on this class; the call site is session-open-execution.ts). - the scoped-paths test narrows its response once instead of nine optional-chaining hops, which tripped the CRAP gate. * style: format .fallowrc.json --------- Co-authored-by: Michał Pierzchała <thymikee@gmail.com> |
||
|
|
735ab7672a |
refactor(daemon): one capture-input builder and one admit-then-bind step (#1876)
Behaviour-neutral. No descriptor changes platform execution, the cutover table is untouched, and no contract surface is added. - buildRuntimeCaptureInput moves to its own module so every request-bound capture consumer builds CaptureSnapshotInput one way. - The admit-then-bind sequence in the snapshot/diff resolver becomes one named step, ready for the selector units' second caller. - handlers/find.ts splits into focused target-capture and match-resolution concepts (600 -> 346 lines); behaviour unchanged. Co-authored-by: agent <agent@local> |
||
|
|
c7565cb1f8 |
refactor(snapshot): clean snapshot ownership (#1754)
* refactor(snapshot): clean snapshot ownership * fix(snapshot): address ownership review feedback |
||
|
|
f5d9789764 |
feat: enforce local device claims and reconcile stale owners (#1735)
* feat: enforce local device claims * fix: address device claim review feedback * fix: persist canonical daemon claim state directory |
||
|
|
9c25bc66f4 |
docs(cli): advertise open --foreground and snapshot --actions in the workflow card (#1682)
* docs(cli): advertise open --foreground and snapshot --actions in the workflow card open --foreground (#1670/#1671) and snapshot -i --actions (#1665) shipped with no mention in the compact `help workflow` card, so a planning model never discovers either. Add one terse line each: the foreground fast-path in Bootstrap, and the merged-element custom-action guidance in Validation and evidence. Stays under the 9,000-byte compact-card budget (8493 -> 8908 bytes). Adds two help-conformance bench cases per the repo's changed-guidance rule: foreground-attach-single-sim (correct plan starts with `open --foreground` in an unambiguous single-sim scenario, fail-closed alternative forbidden) and merged-card-actions-not-directly-invokable (a merged Bluesky-style feed card's actions list is evidence, not a selector). Both use a real pinned sample rebuilt through the production snapshot renderer. * fix(scripts): accept flag order in the foreground-attach conformance matcher Flag order after `open` isn't semantically meaningful (`open --platform ios --foreground` is exactly as correct as `open --foreground --platform ios`), but startsWithForegroundOpen required --foreground to be the literal next token after `open`. Rescoring the completed repeat=3 bench report shows this docked codex:gpt-5.4-mini on all 3 trials even though its plan was config-order noise, not a real deviation -- the no-positional/no-device guarantee already comes from the forbidden checks. Loosened to require --foreground anywhere on the open line; foreground-attach-single-sim now scores 54/54 across both runners. * fix: close workflow help conformance gaps |
||
|
|
3937036e5e |
feat: support --settle on scroll and back (#1638) (#1650)
* feat: support --settle on scroll and back (#1638) Scroll-then-observe and back-then-observe are legitimate agent pairs, but the post-action observation registry never grew past the touch commands, so `--settle` on either was rejected with INVALID_ARGS — burning a tool call each in AppControlBench's bsky-16. Both commands now carry the `settle` descriptor trait, and every surface derives from it rather than a hand list: CLI allowed flags, MCP/SDK input fields, the flag-sourced timeout envelope, and MCP ref-pinning. The CLI flag/metadata helpers moved out of the interaction family into post-action-observation-grammar.ts (back is a system command), and SETTLE_REF_ISSUING_TOOLS became a derivation — a hand list would have silently stopped pinning the new commands' refs. settleAfterInteraction and the new settleObservationCommand are two entry points over one engine: same loop, storage, hints, and diff bounds, with the target-less path supplying its own baseline and no proximity point. The daemon reaches that command through the runtime surface, never by importing `commands/` (R2) — the same seam the touch handlers use for press/fill — and generic-settle.ts is loaded through a lazy `await import` returning a closure, so the interaction runtime subgraph stays out of this dispatcher's static graph (a static edge folded ~18 files into the daemon-server type cycle; R10 caught it). Both of generic-settle's orderings are load-bearing and tested: the baseline is frozen before dispatch (and before the Android dialog preflight), and the observation runs after markDeferredInteractionOutcome so settle's first capture folds in the #1542 stabilization rather than racing it. The ADR 0014 "a settled diff publishes refs" rule moved to settle-ref-issuance.ts, shared by both routes. One divergence is deliberate: scroll/back resolve no element, so the diff baseline is the session's STORED pre-action tree — "settled tree vs the last tree you observed" — not press's freshly resolved pre-action capture. Both commands also switch to preserve-daemon on timeout, which changes the non-settle path too: with --settle their dominant hang mode is now a wedged accessibility bridge, and a timed-out capture must not reset the daemon and lose every session (#1105). The reviewed-set gate records it. Live-validated on an iOS 26.2 simulator (Settings): scroll --settle settled in 1786ms with a +6/-6 diff carrying fresh refs; back --settle in 771ms with +15/-6. Alternating cost runs, one call vs the pair it replaces: scroll 2.9-3.0s vs 5.3-5.6s, back 3.1-3.2s vs 4.7-5.1s. Those include the #1627 deep-capture extension. * fix: render settled-diff refs paste-ready in CLI output A settled diff activates a PARTIAL ref frame (ADR 0014), which admits only the pinned `@eN~s<gen>` form of the refs it issued. The unchanged-interactive tail already rendered that way, but the diff's own added lines rendered the bare `@eN` embedded in the snapshot line — so a CLI caller who copied the ref the diff just handed them got `plain_ref_requires_complete_frame` and had to append the generation by hand. Added lines now render pinned when the response carries `refsGeneration`, exactly like the tail. Removed lines render verbatim: they name elements that just left the screen, and `SettleDiffLine` never gives them a ref. This is not new to scroll/back — press/click/fill/longpress had the same gap since #1101. MCP was never affected: its ref-pin store rewrites plain refs on the way in, which is why the model never sees a suffix. Live: `scroll down --settle` now emits `+ @e14~s218078 [cell] "Game Center"`, and `press @e14~s218078` copied straight out of that line taps successfully. * test: record the pinned-diff-ref bytes in the output-economy baseline Rendering added diff-line refs pinned costs 8 bytes in the two settle CLI text samples (two `~s<gen>` suffixes). The output-economy baseline is the tripwire for exactly this, so the increase takes an explicit reviewed waiver rather than a silent baseline bump — the same one the settled TAIL's pins already carry, for the same ADR 0014 reason. Only `bytes` moves: lines, refs, hints, and shape are unchanged, which is the evidence that this is a suffix on existing refs and not a new payload. Caught by CI, not locally: `pnpm test:unit` runs unit-core and subprocess-stub only, while the Coverage lane runs every vitest project. * test: prove the generic settle degrades when its runtime cannot be built `createGenericSettleRuntime` catches and returns undefined so an observation that cannot even start does not fail an action that already succeeded. That was a claim in a docstring with nothing behind it — the one changed line the coverage gate reported uncovered (95/96). The test puts the session in the state the catch exists for: the router handed us a session that is no longer in the store, so building the settle runtime throws SESSION_NOT_FOUND. The response keeps its scroll result and simply carries no settle payload. Removing the try/catch fails it. * build: teach fallow that vi.mock reaches pinOwnProcessStartTime dynamically Not from this PR: #1642 added `pinOwnProcessStartTime` on main, and its three consumers reach it the only way a Vitest module mock can — `vi.mock(path, async (importOriginal) => (await import('...')).pinOwnProcessStartTime(...))`. Dependency analysis cannot follow that dynamic import to a consumer, so the export reads as dead the moment any PR pulls that file into its audit scope. This PR is the one that did. The entry records the consumers by path and the reason, matching the daemon route-handler entry directly above it, which exists for the same dynamic-`import()` limitation. * refactor: adopt the best of the parallel #1653 implementation Two sessions independently built #1638 (PR #1650 and PR #1653) and converged on the same architecture — trait in the registry, one engine with two entry points, runtime-command seam, lazy import, preserve-daemon, stored-baseline honesty. #1650 continues; this folds in what #1653 did better: - The agent-facing help core loop (cli-help.ts) now names scroll and back as settle-capable. Without this, the benchmarked closed-grammar help line kept instructing agents that --settle is only for press/click/fill/longpress — actively steering the AppControlBench models away from what #1638 shipped. - issueSettleRefs moves into session-snapshot.ts, beside the partial-frame primitive it wraps, deleting the single-function settle-ref-issuance module. - Their seam tests: back reader→writer settle plumbing, back CLI settle rendering, and a trait-less generic command (home) ignoring a stray settle flag rather than observing or rejecting. What #1650 had that #1653 lacked, for the record: the SETTLE_REF_ISSUING_TOOLS registry derivation (without it, MCP never pins a scroll/back settle diff's refs and the partial frame rejects every follow-up), BackCommandResult.settle in contracts, back's MCP output schema, paste-ready pinned diff refs, and the docs/changelog/baseline surfaces. * bench: help-conformance case for settled scroll-to-find planning The #1638 extension of the closed --settle grammar to scroll/back is the feature's entire payoff — collapsing scroll-then-observe into one call — and the closed command list is an enumerated N whose enumerator is this bench. The regex over the help text proves the sentence exists; this case checks whether a model plans differently because of it. One focused case, deliberately not coached: a pinned visible-first snapshot (rendered by formatSnapshotText, pinned by the sample-producers gate) whose wanted row is summarized off-screen with no ref anywhere in the output. The tempting pre-#1638 plan is `scroll` plus a separate `snapshot -i`; acceptance is the single settled call. Scoring was verified against eight plan shapes in both directions before recording. Model-backed record (claude-haiku-4-5, 3 trials, current help): 0/3 — but the decomposition is the finding. Settle eligibility GENERALIZED (3/3 trials put --settle on scroll unprompted; the mutation-suffix framing concern did not materialize) and the two-call habit is residual (1/3). All three trials failed on `scroll @e3 down --settle` — the pre-existing #1366 scroll-takes-no-target confusion, which the live CLI recovers with a dedicated hint but a single-shot bench cannot. The recorded gap is therefore a first-30 doc gap (nothing teaches that scroll takes no target), not a settle-eligibility gap; tuning the case until it passes would just delete the evidence. |
||
|
|
8ba5f9b8de |
fix: surface AMBIGUOUS_MATCH candidates and name find's supported actions (#1602)
* fix: surface AMBIGUOUS_MATCH candidates and name find's supported actions (#1597) AMBIGUOUS_MATCH errors now list the matching candidates (ref, role, label/identifier) rendered the same way as snapshot -i lines, capped at 5 with a "+N more" marker. buildAmbiguousMatchError (the single producer, src/daemon/handlers/find.ts) reuses formatSnapshotLine to build the list; formatAmbiguousMatchCandidateLines (src/utils/output.ts) renders it unconditionally on both text surfaces an agent actually reads (CLI printHumanError and MCP formatToolErrorText) — previously the candidates lived only in details, which neither surface printed. find's "Unsupported find action: X" (e.g. from `find <text> press`) now attaches a hint naming every action find actually supports and the two-step recovery shape: run find "<text>" to resolve the ref, then dispatch the gesture as its own command (press @eNN). The hint is a single exported constant (UNSUPPORTED_FIND_ACTION_HINT) shared by both throw sites — packages/selectors' raw-token parser and the CLI's typed reader (src/commands/interaction/selectors.ts) — so they can't drift. Matching semantics are unchanged; ambiguous rejection stays by-design. The help-conformance corpus's AMBIGUOUS_MATCH quiz is updated: its premise ("candidate refs were not shown") no longer holds, but with 3 identically-labeled candidates the lesson (don't guess a specific ref) still holds. * fix: guard the AMBIGUOUS_MATCH candidate renderer against device-domain shapes Review on #1602 (P2): formatAmbiguousMatchCandidateLines ran for every normalized error and stringified details.candidates unconditionally, but device-domain AMBIGUOUS_MATCH/APP_NOT_INSTALLED errors (findBootedAppleSimulatorWithApp, src/core/dispatch-resolve.ts) reuse that key for { id, name } device objects with no `matches` field — CLI and MCP would have printed "Candidates: [object Object]" for those. The renderer now requires numeric details.matches AND every candidate to be a string before rendering anything, restricting it to buildAmbiguousMatchError's element-match shape; unrecognized shapes render nothing, same as before this feature existed. Added regression tests against the exact device-error shape on both text surfaces. Also unexports AMBIGUOUS_MATCH_CANDIDATE_LIMIT (fallow flagged it as an unused production export) — it has no consumer outside find.ts. |
||
|
|
2e74b789fd |
feat: verify device cloud connections (#1564)
* feat: verify device cloud connections * refactor: unify connect provider adapters * refactor: separate connect verification facts * fix: tighten connect provider verification * fix: use neutral cloud connection wording * perf: deduplicate local affected checks * refactor: simplify affected check runner * refactor: derive connect workflow from verification |
||
|
|
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> |
||
|
|
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> |