mirror of
https://github.com/stablyai/orca.git
synced 2026-09-23 08:02:31 +00:00
* test(worktrees): cover id: selector path-spelling parity with path: (#16243) The renderer can only address a workspace by id (toRuntimeWorktreeSelector always emits id:<repoId>::<path>), and the runtime matches that id byte for byte while a path: selector has always compared through normalizeRuntimePathForComparison. A stored id that spells its path differently from `git worktree list` therefore resolves for the CLI and answers selector_not_found for the UI, which reads that as a stale local mirror, calls forgetLocal, reports success, and lets the row return on the next catalog refresh: a silent delete. These tests fail on both resolution sites -- the fleet `id:` branch of resolveWorktreeSelector and the scoped resolveScopedWorktreeIdRow a host-qualified removal takes -- and pin what must stay closed: an exact repo id (STA-4343), host qualification, dot segments neither selector canonicalizes, and a refusal rather than a guess when two rows spell one path. 13 failing, 35 passing. * fix(worktrees): resolve id: worktree selectors by path equivalence (#16243) worktreeIdComparisonKey names one repo, one filesystem location, and one folder-workspace instance, folding exactly the path spellings normalizeRuntimePathForComparison already folds for a path: selector -- and nothing more, so dot segments stay unresolved for both shapes. Both id: resolution sites consult it only after an exact match finds nothing: the fleet branch of resolveWorktreeSelector and resolveScopedWorktreeIdRow, which a host-qualified removal takes. runtimeWorktreeIdsEqual now derives from the same key so the runtime has one normalizer rather than a parallel one. Not a pure refactor at that last site: runtimeWorktreeIdsEqual used to normalize-compare ids that parse but carry an empty repoId or an empty path ('::/p', or 'repo::' against 'repo::/'), and worktreeIdComparisonKey returns null for those, so across its call sites (PTY identity, refresh, mutation queue) such ids now compare byte-exact instead. That narrows matching rather than widening it, no real worktree carries such an id, and it is the behavior #15616 guarantees for malformed ids -- but it is a behavior delta, not just a tidy-up. Perf (#14399): the exact match is still tried first and still wins outright, so a resolvable id costs exactly what it did before. Neither site adds a scan -- the fleet branch re-filters the array it had already listed, the scoped lookup re-filters the single owning repo's projected rows -- so an explicit id still never scans every repo. Fail-closed behavior is unchanged: the repo id compares exactly (STA-4343), host qualification is untouched, the folder-workspace instance suffix stays part of the path, and a scoped lookup with two equivalent rows refuses instead of guessing. The bare unprefixed selector branch keeps byte-exact id matching, since only the id: shape reaches a renderer caller. Shares src/shared/worktree/id.ts with the open #15616, which introduces worktreeIdComparisonKey for the same divergence in lineage pruning and authoritative-scan purging; this adopts that helper rather than adding a second one. Complementary to the open #16295, which makes the miss visible; this removes the miss. * chore(worktrees): satisfy oxfmt and oxlint on #16243 tests oxfmt --check flagged both new test files and oxlint's unicorn/no-useless-fallback-in-spread flagged the store mock; the full lint and format gates now match the pre-change baseline. * test(worktrees): pin Windows spellings and malformed-id exactness (#16243) Review found two axes the first pass left unproven at the two id: resolution sites. Both are the invariants the open #15616 guarantees for the shared worktreeIdComparisonKey it introduces for #15598, so violating either here would break a contract a sibling PR depends on. Windows: #15598's whole defect is that one checkout is recorded under both `D:\Agentic\game2` and `D:/Agentic/game2`. The fleet branch, the scoped removal lookup, and the key itself now each resolve the backslash spelling against the forward-slash spelling git reports, and fold drive-letter case -- while a backslash inside a POSIX path stays a filename character and a POSIX root stays case-sensitive, exactly as normalizeRuntimePathForComparison already decides for a path: selector. Malformed ids keep exact matching at both sites: an id with no repo boundary or an empty path still refuses, and the scoped lookup still refuses it without scanning. Four of these fail without the production change (three fleet/removal Windows cases and the scoped one); the malformed-id and POSIX-backslash cases are invariant guards that hold either way. Verified: 58 passed in the three files; 18 fail with the production hunks reverted; orca-runtime.test.ts and worktree-teardown-unstopped-pty.test.ts green (1270 passed | 1 skipped); pnpm tc:node clean. * test(worktrees): pin Windows id: spelling folds and fleet ambiguity refusal (#16243) The Windows backslash spelling now rides the ID_SPELLINGS rows, so it is driven through both id: sites -- resolveWorktreeSelector and the scoped removal target -- and compared against what the same workspace's path: selector resolves, rather than only through worktreeIdComparisonKey. That is the spelling #15598/#15616 found in the wild and the one the owner's Windows client produces. The fleet path's ambiguity refusal had no test: two same-repo rows spelling one directory, an id: matching neither exactly, must reject selector_ambiguous. It is the fail-closed guard on a delete-capable resolver, and the property a later refactor is most likely to turn into a silent pick. Also records two limits at the source instead of leaving them to be rediscovered: a UNC or WSL root never folds into a drive-letter location (while Windows' two WSL UNC aliases do name one location), and a folder-workspace id keeps a trailing slash placed before the ::workspace:<uuid> suffix, so that spelling stays exact-match-only. Neither behavior changes here. The file docblock overclaimed parity. path: collapses duplicate same-host registrations to the first row while a folded id: refuses them; the contract this file pins is path-spelling parity, not dedup parity, and the divergence is deliberate because this resolver also serves delete. Non-vacuity, verified by temporarily reverting the production hunks: neutralizing both id: fallbacks turns 10 of these tests red, including both new Windows rows and the ambiguity refusal (it degrades to selector_not_found). Making the fleet fallback pick the first folded match instead of collecting all of them turns the ambiguity test red on its own. The remaining cases -- malformed ids, dot segments, the POSIX backslash, the folder-workspace slash -- pass against the pre-fix code too: they guard against future widening rather than proving this fix. Drops the two Windows cases the ID_SPELLINGS row subsumes. The drive-letter case test asserted only the Windows half its name promised; it now also pins that a POSIX root does NOT fold case, since an unconditional lowercase would merge /data/Foo with /data/foo on the platform CI runs on. Fixture paths use the upstream-attested /srv/projects prefix (and a neutral plugin-host leaf) instead of a local install root; the spelling variations the tests exist to pin -- doubled separator, dot segment, trailing slash, uppercase POSIX, cafe NFC/NFD, and the Windows D: rows -- are unchanged in form. * docs(worktrees): trim the id: selector test header and document the comparison key (#16243)