diff --git a/config/oxlint-anti-slop.json b/config/oxlint-anti-slop.json index 3a115684ba9..bba8588d949 100644 --- a/config/oxlint-anti-slop.json +++ b/config/oxlint-anti-slop.json @@ -35,7 +35,7 @@ "anti-slop/no-reflect-apply": "error", "anti-slop/no-reflect-get": "error", "anti-slop/no-runtime-typeof": "off", - "anti-slop/no-shape-in-symbol-names": "off", + "anti-slop/no-shape-in-symbol-names": "error", "anti-slop/no-unknown-parameters": "off", "anti-slop/no-unknown-returns": "off", "anti-slop/no-unknown-type-aliases": "error", @@ -55,6 +55,56 @@ "rules": { "anti-slop/no-module-mocking": "off" } + }, + // The exemptions below are file-scoped rather than inline `oxlint-disable` comments + // because the root lint scan does not load this plugin, so an inline directive naming + // an anti-slop rule always reads back as an unused directive there. + // + // In the screenshot annotator a "shape" is the drawn geometry -- pen, arrow, rect, + // ellipse, highlight. A domain noun, and it pervades every symbol in the module. + // mobile/src/test-support/rpc-recording is the golden recorder engine. recorder-digest.ts + // hashes these files' RAW BYTES into every golden's `recorderSha256` header, so any edit + // here -- a rename or even an added comment -- invalidates all 208 recordings. The exemption + // is config-scoped for that reason: an inline directive would change the bytes it protects. + { + "files": ["**/test-support/rpc-recording/**"], + "rules": { + "anti-slop/no-shape-in-symbol-names": "off" + } + }, + { + "files": ["**/browser-pane/annotate/**"], + "rules": { + "anti-slop/no-shape-in-symbol-names": "off" + } + }, + // lucide exports the icon component as `Shapes`, and the matching REPO_LUCIDE_ICONS key + // is the persisted icon name shared by the desktop picker and mobile. + { + "files": [ + "**/components/repo/repo-icon.tsx", + "**/worktree-list/rows/repo-header-project-actions.tsx", + "**/components/MobileRepoIcon.tsx" + ], + "rules": { + "anti-slop/no-shape-in-symbol-names": "off" + } + }, + // `shapedSidebar` is a persisted onboarding-checklist field and a telemetry enum member; + // renaming it would orphan saved state. + { + "files": ["**/src/shared/constants.ts", "**/src/shared/onboarding-state-types.ts"], + "rules": { + "anti-slop/no-shape-in-symbol-names": "off" + } + }, + // Matching zod's own literal `shape` property is what selects the ZodObject branch of + // RpcSendInput's conditional type. + { + "files": ["**/rpc-contract/rpc-send-params.ts"], + "rules": { + "anti-slop/no-shape-in-symbol-names": "off" + } } ] } diff --git a/config/scripts/agent-lineage-cycle-cleanup-benchmark.mjs b/config/scripts/agent-lineage-cycle-cleanup-benchmark.mjs index 47407764e0f..59f4e387beb 100644 --- a/config/scripts/agent-lineage-cycle-cleanup-benchmark.mjs +++ b/config/scripts/agent-lineage-cycle-cleanup-benchmark.mjs @@ -50,7 +50,7 @@ for (let sample = 0; sample < 500; sample++) { } const results = [] -for (const [shape, count] of [ +for (const [topology, count] of [ ['flat', 1000], ['all-cycles', 1000], ['mixed-cycles', 100], @@ -58,9 +58,9 @@ for (const [shape, count] of [ ['mixed-cycles', 1000] ]) { const rows = Array.from({ length: count }, (_, index) => - row(index, shape === 'flat' ? undefined : index ^ 1) + row(index, topology === 'flat' ? undefined : index ^ 1) ) - if (shape === 'mixed-cycles') { + if (topology === 'mixed-cycles') { rows.unshift(row('root', undefined)) } assert.deepEqual(after(rows), before(rows)) @@ -83,7 +83,7 @@ for (const [shape, count] of [ samples[arm].push({ wallMs, cpuMs: (used.user + used.system) / 30_000 }) } } - results.push({ shape, count, samples }) + results.push({ topology, count, samples }) } console.log( JSON.stringify({ baseline, node: process.version, parityGraphs: 500, results }, null, 2) diff --git a/config/scripts/agent-lineage-reachability-benchmark.mjs b/config/scripts/agent-lineage-reachability-benchmark.mjs index 4122ac18dab..6d542367c18 100644 --- a/config/scripts/agent-lineage-reachability-benchmark.mjs +++ b/config/scripts/agent-lineage-reachability-benchmark.mjs @@ -58,15 +58,19 @@ for (let trial = 0; trial < 5000; trial += 1) { const results = [] for (const count of [8, 32, 128, 512, 1024]) { - for (const shape of ['flat', 'fanout', 'balanced', 'chain']) { + for (const topology of ['flat', 'fanout', 'balanced', 'chain']) { const rows = Array.from({ length: count }, (_, index) => { const parent = - shape === 'fanout' ? 0 : shape === 'balanced' ? Math.floor((index - 1) / 4) : index - 1 + topology === 'fanout' + ? 0 + : topology === 'balanced' + ? Math.floor((index - 1) / 4) + : index - 1 return { paneKey: `pane-${index}`, entry: { orchestration: - index > 0 && shape !== 'flat' ? { parentPaneKey: `pane-${parent}` } : undefined + index > 0 && topology !== 'flat' ? { parentPaneKey: `pane-${parent}` } : undefined } } }) @@ -92,7 +96,7 @@ for (const count of [8, 32, 128, 512, 1024]) { } results.push({ count, - shape, + topology, iterations, meanMicrosecondsPerTree: Object.fromEntries( Object.entries(samples).map(([arm, values]) => [ diff --git a/config/scripts/mobile-markdown-placeholder-benchmark.mjs b/config/scripts/mobile-markdown-placeholder-benchmark.mjs index 20280e5a8d2..dd5cf22d9dc 100644 --- a/config/scripts/mobile-markdown-placeholder-benchmark.mjs +++ b/config/scripts/mobile-markdown-placeholder-benchmark.mjs @@ -40,7 +40,7 @@ function measure(fn, input, repeats) { return samples.sort((a, b) => a - b)[Math.floor(samples.length / 2)] } const results = [] -for (const [shape, input] of [ +for (const [inputCase, input] of [ ['ordinary Markdown', '# Hello\n\n

Use `Array` and bold.

'], ...[2048, 8192, 16384].map((length) => [ `${length} underscore collision`, @@ -49,7 +49,7 @@ for (const [shape, input] of [ ]) { assert.equal(after(input), before(input)) results.push({ - shape, + inputCase, bytes: Buffer.byteLength(input), beforeMs: measure(before, input, 5), afterMs: measure(after, input, 15) diff --git a/config/scripts/redactor-environment-lines-benchmark.mjs b/config/scripts/redactor-environment-lines-benchmark.mjs index 71aebf9fe88..b30e56fee05 100644 --- a/config/scripts/redactor-environment-lines-benchmark.mjs +++ b/config/scripts/redactor-environment-lines-benchmark.mjs @@ -25,7 +25,7 @@ function median(fn, input, repeats) { return samples.sort((a, b) => a - b)[Math.floor(samples.length / 2)] } const rows = [] -for (const [shape, input] of [ +for (const [label, input] of [ ['8KiB blank lines', '\n'.repeat(8192)], ['16KiB blank lines', '\n'.repeat(16384)], ['32KiB blank lines', '\n'.repeat(32768)], @@ -37,7 +37,7 @@ for (const [shape, input] of [ const beforeMs = median(before, input, 3) const afterMs = median(redactString, input, 15) rows.push({ - shape, + label, bytes: Buffer.byteLength(input), beforeMs, afterMs, diff --git a/config/scripts/repo-icon-source-href-benchmark.mjs b/config/scripts/repo-icon-source-href-benchmark.mjs index 76c42d261b4..da560724a23 100644 --- a/config/scripts/repo-icon-source-href-benchmark.mjs +++ b/config/scripts/repo-icon-source-href-benchmark.mjs @@ -36,15 +36,15 @@ function measurePair(source) { const results = [] for (const size of [8192, 16384, 32768]) { - for (const shape of ['no icon', 'rel without href', 'unterminated link starts']) { + for (const variant of ['no icon', 'rel without href', 'unterminated link starts']) { const source = - shape === 'unterminated link starts' + variant === 'unterminated link starts' ? ' interactive login shell -> git', fast: 'env -> git' }, diff --git a/mobile/src/components/MobileRepoIcon.tsx b/mobile/src/components/MobileRepoIcon.tsx index e4f7f9664cf..2ea1f8d573b 100644 --- a/mobile/src/components/MobileRepoIcon.tsx +++ b/mobile/src/components/MobileRepoIcon.tsx @@ -16,6 +16,7 @@ import { Palette, Rocket, Server, + // `Shapes` is lucide's own export name; exempted in config/oxlint-anti-slop.json. Shapes, Sparkles, SquareTerminal, diff --git a/mobile/src/tasks/use-mobile-tasks-provider-view-projection.test.tsx b/mobile/src/tasks/use-mobile-tasks-provider-view-projection.test.tsx index 0eb8d3d32f7..3ab8ae149d4 100644 --- a/mobile/src/tasks/use-mobile-tasks-provider-view-projection.test.tsx +++ b/mobile/src/tasks/use-mobile-tasks-provider-view-projection.test.tsx @@ -243,7 +243,7 @@ function countLinearWork(run: () => void): WorkCounts { } } -function shape(sections: LinearIssueSection[]) { +function summarizeSections(sections: LinearIssueSection[]) { return sections.map((section) => ({ key: section.key, label: section.label, @@ -268,8 +268,8 @@ describe('useMobileTasksProviderViewProjection linear sections', () => { (linearGroupBy) => { const projection = mount({ linearGroupBy }) expect(projection.linearBoardSections).toBe(projection.linearIssueSections) - expect(shape(projection.linearBoardSections)).toEqual( - shape(legacyProjection({ ...DEFAULT_INPUT, linearGroupBy }).boardSections) + expect(summarizeSections(projection.linearBoardSections)).toEqual( + summarizeSections(legacyProjection({ ...DEFAULT_INPUT, linearGroupBy }).boardSections) ) } ) @@ -278,8 +278,12 @@ describe('useMobileTasksProviderViewProjection linear sections', () => { const projection = mount({ linearGroupBy: 'none' }) const legacy = legacyProjection({ ...DEFAULT_INPUT, linearGroupBy: 'none' }) expect(projection.linearBoardSections).not.toBe(projection.linearIssueSections) - expect(shape(projection.linearIssueSections)).toEqual(shape(legacy.listSections)) - expect(shape(projection.linearBoardSections)).toEqual(shape(legacy.boardSections)) + expect(summarizeSections(projection.linearIssueSections)).toEqual( + summarizeSections(legacy.listSections) + ) + expect(summarizeSections(projection.linearBoardSections)).toEqual( + summarizeSections(legacy.boardSections) + ) expect(projection.linearIssueSections.map((section) => section.key)).toEqual(['all']) expect(projection.linearBoardSections.length).toBeGreaterThan(1) expect(projection.linearListEntries.every((entry) => entry.type === 'issue')).toBe(true) @@ -293,8 +297,12 @@ describe('useMobileTasksProviderViewProjection linear sections', () => { linearGroupBy, linearOrderBy: order }) - expect(shape(projection.linearIssueSections)).toEqual(shape(legacy.listSections)) - expect(shape(projection.linearBoardSections)).toEqual(shape(legacy.boardSections)) + expect(summarizeSections(projection.linearIssueSections)).toEqual( + summarizeSections(legacy.listSections) + ) + expect(summarizeSections(projection.linearBoardSections)).toEqual( + summarizeSections(legacy.boardSections) + ) expect(projection.linearIssuesForView.map((issue) => issue.id)).toEqual( legacy.issuesForView.map((issue) => issue.id) ) @@ -310,20 +318,24 @@ describe('useMobileTasksProviderViewProjection transitions', () => { const grouped = rerender({ linearGroupBy: 'status' }) expect(grouped.linearBoardSections).toBe(grouped.linearIssueSections) - expect(shape(grouped.linearBoardSections)).toEqual( - shape(legacyProjection({ ...DEFAULT_INPUT, linearGroupBy: 'status' }).boardSections) + expect(summarizeSections(grouped.linearBoardSections)).toEqual( + summarizeSections( + legacyProjection({ ...DEFAULT_INPUT, linearGroupBy: 'status' }).boardSections + ) ) const assignee = rerender({ linearGroupBy: 'assignee' }) expect(assignee.linearBoardSections).toBe(assignee.linearIssueSections) - expect(shape(assignee.linearBoardSections)).toEqual( - shape(legacyProjection({ ...DEFAULT_INPUT, linearGroupBy: 'assignee' }).boardSections) + expect(summarizeSections(assignee.linearBoardSections)).toEqual( + summarizeSections( + legacyProjection({ ...DEFAULT_INPUT, linearGroupBy: 'assignee' }).boardSections + ) ) const none = rerender({ linearGroupBy: 'none' }) expect(none.linearBoardSections).not.toBe(none.linearIssueSections) - expect(shape(none.linearBoardSections)).toEqual( - shape(legacyProjection({ ...DEFAULT_INPUT, linearGroupBy: 'none' }).boardSections) + expect(summarizeSections(none.linearBoardSections)).toEqual( + summarizeSections(legacyProjection({ ...DEFAULT_INPUT, linearGroupBy: 'none' }).boardSections) ) }) @@ -333,8 +345,8 @@ describe('useMobileTasksProviderViewProjection transitions', () => { const next = rerender({ linearGroupBy: 'priority', linearOrderBy: 'identifier' }) expect(next.linearBoardSections).not.toBe(firstSections) expect(next.linearBoardSections).toBe(next.linearIssueSections) - expect(shape(next.linearBoardSections)).toEqual( - shape( + expect(summarizeSections(next.linearBoardSections)).toEqual( + summarizeSections( legacyProjection({ ...DEFAULT_INPUT, linearGroupBy: 'priority', @@ -369,17 +381,17 @@ describe('useMobileTasksProviderViewProjection transitions', () => { const refreshed = rerender({ linearGroupBy: 'status', items: makeItems(50) }) expect(refreshed.linearBoardSections).not.toBe(sections) expect(refreshed.linearBoardSections).toBe(refreshed.linearIssueSections) - expect(shape(refreshed.linearBoardSections)).toEqual(shape(sections)) + expect(summarizeSections(refreshed.linearBoardSections)).toEqual(summarizeSections(sections)) }) it('does not mutate the shared sections when the list entries are built', () => { const projection = mount({ linearGroupBy: 'status' }) - const before = shape(projection.linearIssueSections) + const before = summarizeSections(projection.linearIssueSections) const entryIssueIds = projection.linearListEntries .filter((entry) => entry.type === 'issue') .map((entry) => (entry.type === 'issue' ? entry.issue.id : '')) expect(entryIssueIds).toHaveLength(50) - expect(shape(projection.linearBoardSections)).toEqual(before) + expect(summarizeSections(projection.linearBoardSections)).toEqual(before) }) }) diff --git a/src/cli/handlers/skills.ts b/src/cli/handlers/skills.ts index 1b068fc80b0..325262a42f5 100644 --- a/src/cli/handlers/skills.ts +++ b/src/cli/handlers/skills.ts @@ -17,7 +17,7 @@ import { UnsafeWindowsBatchArgumentsError, WINDOWS_BATCH_UNSAFE_CHARACTERS_LABEL } from '../../shared/windows-batch-spawn' -import { isSkillsCliAgentKeyShaped, toSkillsCliAgentKeys } from '../../shared/skills-cli-agent-keys' +import { isUsableSkillsCliAgentKey, toSkillsCliAgentKeys } from '../../shared/skills-cli-agent-keys' import { buildAgentFeatureSkillInstallArgs, buildAgentFeatureSkillUpdateArgs @@ -150,7 +150,7 @@ function resolveInstallAgentKeys(flags: Map): string[] if (keys.length === 0) { throw new RuntimeClientError('invalid_argument', 'Missing required --agent') } - const unusable = keys.find((key) => !isSkillsCliAgentKeyShaped(key)) + const unusable = keys.find((key) => !isUsableSkillsCliAgentKey(key)) if (unusable !== undefined) { // Why: the skills CLI drops a value starting with `-`, which leaves it with // no target and installs into every agent it knows. diff --git a/src/main/ai-vault-search/session-search-lifecycle-matrix.test.ts b/src/main/ai-vault-search/session-search-lifecycle-matrix.test.ts index 79cbe70eb7f..220e7d8390d 100644 --- a/src/main/ai-vault-search/session-search-lifecycle-matrix.test.ts +++ b/src/main/ai-vault-search/session-search-lifecycle-matrix.test.ts @@ -47,7 +47,7 @@ const CAN_DENY_READ = process.platform !== 'win32' && process.getuid?.() !== 0 const INTERVAL_MS = 20_000 const SESSIONS = ['aaaaaaaa', 'bbbbbbbb', 'cccccccc'] -type RootShape = { +type RootLayout = { name: string /** Where the unreachable root's transcripts live, and where its files go. */ detachedRoot: (harness: SessionSearchIndexerHarness) => string @@ -59,7 +59,7 @@ type RootShape = { const OPENCLAW_SESSION_DIR = join('agents', 'main', 'sessions') -const ROOT_SHAPES: RootShape[] = [ +const ROOT_LAYOUTS: RootLayout[] = [ { name: 'roots discovery reports one per directory', detachedRoot: (harness) => harness.roots.claudeProjectsDir ?? '', @@ -81,7 +81,7 @@ const ROOT_SHAPES: RootShape[] = [ } ] -type UnreachableShape = { +type UnreachableMode = { name: string needsDeniedRead: boolean /** @@ -97,7 +97,7 @@ type UnreachableShape = { attach: (root: string, transcriptDir: string, parked: string) => Promise } -const UNREACHABLE_SHAPES: UnreachableShape[] = [ +const UNREACHABLE_MODES: UnreachableMode[] = [ { name: 'the root itself is not there', needsDeniedRead: false, @@ -276,8 +276,8 @@ function indexedSessions(): string[] { .sort() } -for (const roots of ROOT_SHAPES) { - for (const unreachable of UNREACHABLE_SHAPES) { +for (const roots of ROOT_LAYOUTS) { + for (const unreachable of UNREACHABLE_MODES) { describe.skipIf(unreachable.needsDeniedRead && !CAN_DENY_READ)( `${roots.name}, ${unreachable.name}`, () => { @@ -298,7 +298,7 @@ for (const roots of ROOT_SHAPES) { // pass, so the setup drives passes until the index has caught up. await driveUntilIndexed(SESSIONS.length * 2) const detachedIds = detachedPaths.map((_path, index) => - roots === ROOT_SHAPES[0] + roots === ROOT_LAYOUTS[0] ? fullSessionId(SESSIONS[index] ?? '') : (SESSIONS[index] ?? '') ) diff --git a/src/main/ai-vault-search/session-search-query-planner.ts b/src/main/ai-vault-search/session-search-query-planner.ts index 6c2c2f3b91c..91e8711fbe3 100644 --- a/src/main/ai-vault-search/session-search-query-planner.ts +++ b/src/main/ai-vault-search/session-search-query-planner.ts @@ -17,7 +17,7 @@ const MAX_TERMS = 64 // A query that quotes something from a transcript: camelCase, SCREAMING_SNAKE, // a dotted or snake_case name, a path, a filename, a PR number, a ticket, code // punctuation, or an error word. -const LITERAL_SHAPE = +const LITERAL_PATTERN = /[A-Za-z0-9_]*[a-z][A-Z][A-Za-z0-9_]*|\b[A-Z][A-Z0-9]{2,}(_[A-Z0-9]+)+\b|\b\w{2,}[._]\w{2,}\b|\b[\w.-]+\/[\w/.-]+\b|\b\w+\.(ts|tsx|js|jsx|py|rs|go|json|md|sh|yml|yaml|toml|c|cc|h|java|sql)\b|#\d{3,}|\b[A-Z]{2,6}-\d{2,}\b|[(){};=]|::|->|--\w|\b(Error|Exception|Traceback|error:|warning:)\b/ const QUOTED = /"[^"]{3,}"|'[^']{3,}'/ @@ -36,7 +36,7 @@ export type SessionSearchQueryPlan = { } export function isLiteralQuery(query: string): boolean { - return QUOTED.test(query) || LITERAL_SHAPE.test(query) + return QUOTED.test(query) || LITERAL_PATTERN.test(query) } /** diff --git a/src/main/ai-vault/session-delete-target.ts b/src/main/ai-vault/session-delete-target.ts index 13320ce39e9..a893e639fad 100644 --- a/src/main/ai-vault/session-delete-target.ts +++ b/src/main/ai-vault/session-delete-target.ts @@ -21,7 +21,7 @@ import type { AiVaultScanOptions } from './session-scanner-types' // Agents whose session IS the directory holding the scanned file: everything // beside it belongs to the same session (rovo's session_context.json, grok's // chat_history.jsonl), so the directory is the only complete delete unit. -const AI_VAULT_DIRECTORY_SHAPED_DELETE_AGENTS = new Set([ +const AI_VAULT_WHOLE_DIRECTORY_DELETE_AGENTS = new Set([ 'rovo', 'grok', 'cline' @@ -109,7 +109,7 @@ function sessionDeleteRemovals(args: { }): readonly AiVaultSessionDeleteRemoval[] | null { const { agent, resolvedPath, matchedRoot, roots } = args - if (AI_VAULT_DIRECTORY_SHAPED_DELETE_AGENTS.has(agent)) { + if (AI_VAULT_WHOLE_DIRECTORY_DELETE_AGENTS.has(agent)) { const sessionDir = dirname(resolvedPath) if (sessionDir === matchedRoot || !isPathInsideOrEqual(matchedRoot, sessionDir)) { return null diff --git a/src/main/browser/browser-cookie-samesite.electron.test.ts b/src/main/browser/browser-cookie-samesite.electron.test.ts index 4f04f93c72e..561bff2a407 100644 --- a/src/main/browser/browser-cookie-samesite.electron.test.ts +++ b/src/main/browser/browser-cookie-samesite.electron.test.ts @@ -33,7 +33,7 @@ type FixtureResult = { afterCookies: JarCookie[] } -type SourceShape = { +type SourceCookieRow = { name: string samesite: number | null is_secure: number @@ -147,18 +147,26 @@ run().catch((error) => { ` } -function readSourceShape(sourceDbPath: string): SourceShape[] { +function readSourceCookieRows(sourceDbPath: string): SourceCookieRow[] { const db = new DatabaseSync(sourceDbPath, { readOnly: true }) try { return db .prepare('SELECT name, samesite, is_secure FROM cookies ORDER BY rowid') - .all() as SourceShape[] + .all() + .map((row) => ({ + name: String(row.name), + samesite: row.samesite === null ? null : Number(row.samesite), + is_secure: Number(row.is_secure) + })) } finally { db.close() } } -async function runFixture(): Promise<{ fixture: FixtureResult; sourceShape: SourceShape[] }> { +async function runFixture(): Promise<{ + fixture: FixtureResult + sourceCookieRows: SourceCookieRow[] +}> { const root = mkdtempSync(join(tmpdir(), 'orca-samesite-enum-')) fixtureRoots.push(root) const bundlePath = join(root, 'cookie-import-samesite.cjs') @@ -176,7 +184,7 @@ async function runFixture(): Promise<{ fixture: FixtureResult; sourceShape: Sour }) ) createChromiumCookieTestDatabase(sourceDbPath, rows).close() - const sourceShape = readSourceShape(sourceDbPath) + const sourceCookieRows = readSourceCookieRows(sourceDbPath) writeFileSync( bundleEntryPath, `export { importCookiesFromBrowser } from ${JSON.stringify(join(process.cwd(), 'src/main/browser/browser-cookie-import.ts'))}` @@ -212,22 +220,23 @@ async function runFixture(): Promise<{ fixture: FixtureResult; sourceShape: Sour const fixtureResult = existsSync(resultPath) ? readFileSync(resultPath, 'utf8') : 'no result' expect(run.error).toBeUndefined() expect(run.status, `${fixtureResult}\n${run.stdout}\n${run.stderr}`).toBe(0) - return { fixture: JSON.parse(fixtureResult) as FixtureResult, sourceShape } + const fixture: FixtureResult = JSON.parse(fixtureResult) + return { fixture, sourceCookieRows } } describe('Chromium SameSite storage enum import', () => { let fixture: FixtureResult - let sourceShape: SourceShape[] + let sourceCookieRows: SourceCookieRow[] beforeAll(async () => { - ;({ fixture, sourceShape } = await runFixture()) + ;({ fixture, sourceCookieRows } = await runFixture()) }, 120_000) it('runs the real Chromium import against the complete synthetic matrix', () => { expect(fixture.step).toBe('import finished') expect(fixture.beforeCookieCount).toBe(0) expect(fixture.importResult.ok).toBe(true) - expect(sourceShape).toEqual( + expect(sourceCookieRows).toEqual( [REJECTION_CONTROL, ...VALID_COMBINATIONS, NULL_CASE].map( ({ name, rawSameSite, secure }) => ({ name, diff --git a/src/main/claude-accounts/runtime-auth-service-readback-identity.test.ts b/src/main/claude-accounts/runtime-auth-service-readback-identity.test.ts index 1cd86014c29..2806d44e82a 100644 --- a/src/main/claude-accounts/runtime-auth-service-readback-identity.test.ts +++ b/src/main/claude-accounts/runtime-auth-service-readback-identity.test.ts @@ -70,7 +70,7 @@ describe('ClaudeRuntimeAuthService', () => { it('rejects wrong-shaped refreshed credentials during read-back', async () => { const runtimeCredentialsPath = join(testState.fakeHomeDir, '.claude', '.credentials.json') const originalCredentials = createClaudeCredentialsJson('user@example.com', 'original') - const wrongShapedRefresh = `${JSON.stringify({ + const malformedRefresh = `${JSON.stringify({ claudeAiOauth: { email: 'user@example.com', expiresAt: Date.now() + 120_000 @@ -91,7 +91,7 @@ describe('ClaudeRuntimeAuthService', () => { settings.activeClaudeManagedAccountId = 'account-1' await service.syncForCurrentSelection() - writeFileSync(runtimeCredentialsPath, wrongShapedRefresh, 'utf-8') + writeFileSync(runtimeCredentialsPath, malformedRefresh, 'utf-8') await service.syncForCurrentSelection() expect(readManagedCredentialsForTest('account-1', managedAuthPath)).toBe(originalCredentials) diff --git a/src/main/codex/codex-structured-item-translation.test.ts b/src/main/codex/codex-structured-item-translation.test.ts index 53cf94e265c..ad63d624f58 100644 --- a/src/main/codex/codex-structured-item-translation.test.ts +++ b/src/main/codex/codex-structured-item-translation.test.ts @@ -806,7 +806,7 @@ describe('codex item bodies', () => { // Both the row label and the run header read top-level input keys only, so a // shape whose detail sits inside `action` renders as the input's raw JSON. const url = 'https://example.com/docs/page' - const shapes: [string, unknown, string, string][] = [ + const cases: [string, unknown, string, string][] = [ ['started', null, '', ''], [ 'search', @@ -823,7 +823,7 @@ describe('codex item bodies', () => { ], ['other', { type: 'other' }, 'other', ''] ] - for (const [name, action, label, brief] of shapes) { + for (const [name, action, label, brief] of cases) { // Codex leaves the item's own `query` empty on most completed searches. const query = name === 'search' || name === 'findInPage' ? 'a sample query' : '' const input = toolCallInput({ type: 'webSearch', id: 'w', query, action }) diff --git a/src/main/cursor/hook-service.ts b/src/main/cursor/hook-service.ts index b07639f108a..ed431509346 100644 --- a/src/main/cursor/hook-service.ts +++ b/src/main/cursor/hook-service.ts @@ -168,13 +168,13 @@ export class CursorHookService { } const cleaned = removeManagedCommands(definitions, isManagedCommand) // Also strip entries with the command at the top level (Cursor schema). - const strippedCursorShape = cleaned.filter( + const strippedTopLevelCommands = cleaned.filter( (definition) => !isManagedCommand(definition.command) ) - if (strippedCursorShape.length === 0) { + if (strippedTopLevelCommands.length === 0) { delete nextHooks[eventName] } else { - nextHooks[eventName] = strippedCursorShape + nextHooks[eventName] = strippedTopLevelCommands } } diff --git a/src/main/git/push-target-validation.ts b/src/main/git/push-target-validation.ts index 055eab8537c..133b1ee8b2b 100644 --- a/src/main/git/push-target-validation.ts +++ b/src/main/git/push-target-validation.ts @@ -1,5 +1,5 @@ import type { GitPushTarget } from '../../shared/worktree/types' -import { assertGitPushTargetShape } from '../../shared/git-push-target-validation' +import { assertValidGitPushTarget } from '../../shared/git-push-target-validation' import { gitExecFileAsync } from './runner' import type { GitExecOptions as GitCommandExecOptions } from './command-runner/git-exec-options' @@ -10,7 +10,7 @@ export async function validateGitPushTarget( target: unknown, options: GitExecOptions = {} ): Promise { - assertGitPushTargetShape(target) + assertValidGitPushTarget(target) await gitExecFileAsync(['check-ref-format', '--branch', target.branchName], { cwd: repoPath, ...options diff --git a/src/main/github/client-stack-merge-guard.test.ts b/src/main/github/client-stack-merge-guard.test.ts index 57cd5df8bfd..c47f786d2ed 100644 --- a/src/main/github/client-stack-merge-guard.test.ts +++ b/src/main/github/client-stack-merge-guard.test.ts @@ -592,9 +592,9 @@ describe('GitHub GraphQL rate-limit guard', () => { }) it.each([ - { stackShape: 'omits stack', stackField: {} }, - { stackShape: 'sets stack to null', stackField: { stack: null } } - ])('keeps legacy merge when an ordinary GitHub response $stackShape', async (scenario) => { + { stackVariant: 'omits stack', stackField: {} }, + { stackVariant: 'sets stack to null', stackField: { stack: null } } + ])('keeps legacy merge when an ordinary GitHub response $stackVariant', async (scenario) => { ghExecFileAsyncMock .mockResolvedValueOnce({ stdout: JSON.stringify({ diff --git a/src/main/github/default-branch-stale-pr.test.ts b/src/main/github/default-branch-stale-pr.test.ts index 4361c9759e7..276d63e38ac 100644 --- a/src/main/github/default-branch-stale-pr.test.ts +++ b/src/main/github/default-branch-stale-pr.test.ts @@ -164,7 +164,7 @@ function primeGitExecForDefaultBranch({ }) } -type RestPRShape = { +type RestPROverrides = { number?: number state?: string merged_at?: string | null @@ -178,7 +178,7 @@ function restPR({ merged_at = null, head_ref = 'master', head_sha = 'stale-master-oid' -}: RestPRShape = {}): Record { +}: RestPROverrides = {}): Record { return { number, title: 'Historical PR', diff --git a/src/main/github/project-view/internals.ts b/src/main/github/project-view/internals.ts index 1c828909669..3bea5b5584d 100644 --- a/src/main/github/project-view/internals.ts +++ b/src/main/github/project-view/internals.ts @@ -17,7 +17,7 @@ import { classifyProjectError, driftError, rateLimitedError, - type GhGraphqlErrorShape + type GhGraphqlError } from './project-error-classification' export { @@ -172,7 +172,7 @@ export async function runGraphql( ...(exec?.host ? { host: exec.host } : {}) }) try { - const parsed = JSON.parse(stdout) as { data?: T; errors?: GhGraphqlErrorShape[] } + const parsed: { data?: T; errors?: GhGraphqlError[] } = JSON.parse(stdout) if (parsed.errors && parsed.errors.length > 0) { return { ok: false, diff --git a/src/main/github/project-view/project-error-classification.ts b/src/main/github/project-view/project-error-classification.ts index 6b3d9a3fe09..a9d12fa44f1 100644 --- a/src/main/github/project-view/project-error-classification.ts +++ b/src/main/github/project-view/project-error-classification.ts @@ -4,14 +4,14 @@ import type { GitHubProjectViewError } from '../../../shared/github/project-result-types' import { githubProjectHost } from '../../../shared/github/project-identity' -export type GhGraphqlErrorShape = { +export type GhGraphqlError = { type?: string message?: string path?: (string | number)[] extensions?: { code?: string } } -export function extractGraphqlErrors(stderr: string, stdout: string): GhGraphqlErrorShape[] { +export function extractGraphqlErrors(stderr: string, stdout: string): GhGraphqlError[] { // `gh api graphql` prints the response JSON to stdout even on GraphQL // errors, and the stderr carries a summary. Try stdout first; if parsing // fails, fall back to stderr. @@ -21,7 +21,7 @@ export function extractGraphqlErrors(stderr: string, stdout: string): GhGraphqlE continue } try { - const parsed = JSON.parse(src) as { errors?: GhGraphqlErrorShape[] } + const parsed: { errors?: GhGraphqlError[] } = JSON.parse(src) if (parsed.errors && parsed.errors.length > 0) { return parsed.errors } @@ -32,7 +32,7 @@ export function extractGraphqlErrors(stderr: string, stdout: string): GhGraphqlE return [] } -export function errorsIndicateParentField(errors: GhGraphqlErrorShape[], stderr: string): boolean { +export function errorsIndicateParentField(errors: GhGraphqlError[], stderr: string): boolean { const lower = stderr.toLowerCase() // Preview-header shape: gh returns a 4xx with "preview" in the message. if (lower.includes('preview') && lower.includes('parent')) { diff --git a/src/main/github/project-view/project-view-item-page.ts b/src/main/github/project-view/project-view-item-page.ts index ca135fded51..e0ea53d059b 100644 --- a/src/main/github/project-view/project-view-item-page.ts +++ b/src/main/github/project-view/project-view-item-page.ts @@ -14,7 +14,7 @@ import { classifyProjectError, driftError, rateLimitedError, - type GhGraphqlErrorShape + type GhGraphqlError } from './project-error-classification' import { ownerQueryRoot } from './project-view-config' import type { RawItem } from './project-view-item-normalization' @@ -47,7 +47,7 @@ export async function fetchItemsPageWithRaw(args: { | { ok: false error: GitHubProjectViewError - rawErrors: GhGraphqlErrorShape[] + rawErrors: GhGraphqlError[] stderr: string } > { @@ -117,7 +117,7 @@ export async function fetchItemsPageWithRaw(args: { stdout = extracted.stdout execFailed = true } - let parsed: { data?: Record; errors?: GhGraphqlErrorShape[] } = {} + let parsed: { data?: Record; errors?: GhGraphqlError[] } = {} try { parsed = JSON.parse(stdout) } catch { diff --git a/src/main/ipc/filesystem/git-remote/branch-mutation-handlers.ts b/src/main/ipc/filesystem/git-remote/branch-mutation-handlers.ts index 2c3273b8c00..1ad9b926e21 100644 --- a/src/main/ipc/filesystem/git-remote/branch-mutation-handlers.ts +++ b/src/main/ipc/filesystem/git-remote/branch-mutation-handlers.ts @@ -8,7 +8,7 @@ import { } from '../../../providers/ssh-git-dispatch' import { resolveRegisteredWorktreePath } from '../../registered-worktree-roots-cache' import { getLocalGitOptionsForRegisteredWorktree } from '../../local-worktree-runtime-options' -import { assertGitPushTargetShape } from '../../../../shared/git-push-target-validation' +import { assertValidGitPushTarget } from '../../../../shared/git-push-target-validation' import { materializeWorktreePushTargetRemote, materializeWorktreePushTargetRemoteSsh @@ -35,7 +35,7 @@ export function registerGitRemoteBranchMutationHandlers(context: FilesystemHandl const publish = args.publish === true if (args.connectionId) { if (args.pushTarget) { - assertGitPushTargetShape(args.pushTarget) + assertValidGitPushTarget(args.pushTarget) } const provider = getSshGitProvider(args.connectionId) if (!provider) { @@ -99,7 +99,7 @@ export function registerGitRemoteBranchMutationHandlers(context: FilesystemHandl ): Promise => { if (args.connectionId) { if (args.pushTarget) { - assertGitPushTargetShape(args.pushTarget) + assertValidGitPushTarget(args.pushTarget) } const provider = getSshGitProvider(args.connectionId) if (!provider) { @@ -159,7 +159,7 @@ export function registerGitRemoteBranchMutationHandlers(context: FilesystemHandl ): Promise => { if (args.connectionId) { if (args.pushTarget) { - assertGitPushTargetShape(args.pushTarget) + assertValidGitPushTarget(args.pushTarget) } const provider = getSshGitProvider(args.connectionId) if (!provider) { diff --git a/src/main/ipc/filesystem/git-remote/sync-handlers.ts b/src/main/ipc/filesystem/git-remote/sync-handlers.ts index a924c393a04..b487d54f39e 100644 --- a/src/main/ipc/filesystem/git-remote/sync-handlers.ts +++ b/src/main/ipc/filesystem/git-remote/sync-handlers.ts @@ -15,7 +15,7 @@ import { } from '../../../providers/ssh-git-dispatch' import { resolveRegisteredWorktreePath } from '../../registered-worktree-roots-cache' import { getLocalGitOptionsForRegisteredWorktree } from '../../local-worktree-runtime-options' -import { assertGitPushTargetShape } from '../../../../shared/git-push-target-validation' +import { assertValidGitPushTarget } from '../../../../shared/git-push-target-validation' import { validateGitForkSyncExpectedUpstream } from '../../../../shared/git-fork-sync' import { materializeWorktreePushTargetRemote, @@ -34,7 +34,7 @@ export function registerGitRemoteSyncHandlers(context: FilesystemHandlerContext) ): Promise => { if (args.connectionId) { if (args.pushTarget) { - assertGitPushTargetShape(args.pushTarget) + assertValidGitPushTarget(args.pushTarget) } const provider = getSshGitProvider(args.connectionId) if (!provider) { @@ -65,7 +65,7 @@ export function registerGitRemoteSyncHandlers(context: FilesystemHandlerContext) ): Promise => { if (args.connectionId) { if (args.pushTarget) { - assertGitPushTargetShape(args.pushTarget) + assertValidGitPushTarget(args.pushTarget) } const provider = getSshGitProvider(args.connectionId) if (!provider) { diff --git a/src/main/ipc/readdir-error-diagnostics.test.ts b/src/main/ipc/readdir-error-diagnostics.test.ts index cf599afbdf1..c1c7041a662 100644 --- a/src/main/ipc/readdir-error-diagnostics.test.ts +++ b/src/main/ipc/readdir-error-diagnostics.test.ts @@ -1,24 +1,27 @@ import { describe, expect, it } from 'vitest' -import { buildReadDirErrorBreadcrumb, describeReadDirPathShape } from './readdir-error-diagnostics' +import { buildReadDirErrorBreadcrumb, classifyReadDirPath } from './readdir-error-diagnostics' -describe('describeReadDirPathShape', () => { +describe('classifyReadDirPath', () => { it('classifies a WSL UNC path without leaking it', () => { - const shape = describeReadDirPathShape('\\\\wsl.localhost\\Ubuntu\\home\\u\\repo', undefined) - expect(shape).toEqual({ hasConnectionId: false, isUNC: true, isWsl: true }) + const classification = classifyReadDirPath( + '\\\\wsl.localhost\\Ubuntu\\home\\u\\repo', + undefined + ) + expect(classification).toEqual({ hasConnectionId: false, isUNC: true, isWsl: true }) }) it('classifies the legacy \\\\wsl$ root as WSL', () => { - expect(describeReadDirPathShape('\\\\wsl$\\Ubuntu\\home', undefined).isWsl).toBe(true) + expect(classifyReadDirPath('\\\\wsl$\\Ubuntu\\home', undefined).isWsl).toBe(true) }) it('classifies a plain network UNC share as UNC but not WSL', () => { - const shape = describeReadDirPathShape('\\\\fileserver\\share\\dir', undefined) - expect(shape).toMatchObject({ isUNC: true, isWsl: false }) - expect(shape.driveLetter).toBeUndefined() + const classification = classifyReadDirPath('\\\\fileserver\\share\\dir', undefined) + expect(classification).toMatchObject({ isUNC: true, isWsl: false }) + expect(classification.driveLetter).toBeUndefined() }) it('extracts an uppercased drive letter for mapped drives', () => { - expect(describeReadDirPathShape('z:\\projects\\repo', undefined)).toEqual({ + expect(classifyReadDirPath('z:\\projects\\repo', undefined)).toEqual({ hasConnectionId: false, isUNC: false, isWsl: false, @@ -27,18 +30,18 @@ describe('describeReadDirPathShape', () => { }) it('flags the SSH connection without recording it', () => { - const shape = describeReadDirPathShape('/remote/repo', 'ssh-1') - expect(shape).toEqual({ hasConnectionId: true, isUNC: false, isWsl: false }) + const classification = classifyReadDirPath('/remote/repo', 'ssh-1') + expect(classification).toEqual({ hasConnectionId: true, isUNC: false, isWsl: false }) }) - it('never includes the raw path in the shape', () => { - const shape = describeReadDirPathShape('\\\\wsl.localhost\\Ubuntu\\secret\\path', 'ssh-9') - expect(JSON.stringify(shape)).not.toContain('secret') + it('never includes the raw path in the classification', () => { + const classification = classifyReadDirPath('\\\\wsl.localhost\\Ubuntu\\secret\\path', 'ssh-9') + expect(JSON.stringify(classification)).not.toContain('secret') }) }) describe('buildReadDirErrorBreadcrumb', () => { - it('captures throw site, error code/name, and path shape', () => { + it('captures throw site, error code/name, and path classification', () => { const breadcrumb = buildReadDirErrorBreadcrumb({ dirPath: '\\\\wsl.localhost\\Ubuntu\\home\\u\\repo', connectionId: undefined, diff --git a/src/main/ipc/readdir-error-diagnostics.ts b/src/main/ipc/readdir-error-diagnostics.ts index dd77dda8837..fc7c54c433f 100644 --- a/src/main/ipc/readdir-error-diagnostics.ts +++ b/src/main/ipc/readdir-error-diagnostics.ts @@ -11,7 +11,7 @@ export type ReadDirThrowSite = 'ssh-provider' | 'authorize' | 'readdir' * even though breadcrumbs are path-redacted downstream, never collecting the * raw path is the safer default. */ -export function describeReadDirPathShape( +export function classifyReadDirPath( dirPath: string, connectionId: string | undefined ): CrashReportBreadcrumbData { @@ -52,6 +52,6 @@ export function buildReadDirErrorBreadcrumb(args: { throwSite: args.throwSite, errorName: args.error instanceof Error ? args.error.name : typeof args.error, ...(errorCode(args.error) ? { errorCode: errorCode(args.error)! } : {}), - ...describeReadDirPathShape(args.dirPath, args.connectionId) + ...classifyReadDirPath(args.dirPath, args.connectionId) } } diff --git a/src/main/ipc/worktree-remote.ts b/src/main/ipc/worktree-remote.ts index ef65f2fcb53..c64710b804c 100644 --- a/src/main/ipc/worktree-remote.ts +++ b/src/main/ipc/worktree-remote.ts @@ -48,7 +48,7 @@ import { resolveWorktreeAddBaseRef } from '../../shared/worktree/base-ref' import { getHostedReviewForBranch } from '../source-control/hosted-review' import type { ForgeProviderId } from '../source-control/forge-provider' import { validateGitPushTarget } from '../git/push-target-validation' -import { assertGitPushTargetShape } from '../../shared/git-push-target-validation' +import { assertValidGitPushTarget } from '../../shared/git-push-target-validation' import { gitExecFileAsync } from '../git/runner' import type { OrcaRuntimeService, @@ -1277,7 +1277,7 @@ export async function prepareWorktreePushTargetSsh( store?: WorktreePushTargetStore, repoId?: string ): Promise { - assertGitPushTargetShape(target) + assertValidGitPushTarget(target) const execGit: GitRemoteExec = (args, cwd) => provider.exec(args, cwd) const { remoteCreated: _ignoredRemoteCreated, ...sanitizedTarget } = target await provider.exec(['check-ref-format', '--branch', target.branchName], repoPath) diff --git a/src/main/native-chat/agent-session-journal/journal-lifecycle-batch-partition.ts b/src/main/native-chat/agent-session-journal/journal-lifecycle-batch-partition.ts index 305fa462f60..f109451edb9 100644 --- a/src/main/native-chat/agent-session-journal/journal-lifecycle-batch-partition.ts +++ b/src/main/native-chat/agent-session-journal/journal-lifecycle-batch-partition.ts @@ -63,14 +63,12 @@ function serializedLifecycleBatchFits( fence: Number.MAX_SAFE_INTEGER, ts: Number.MAX_SAFE_INTEGER, settlementId, - mutations: mutations.map(lifecycleMutationRowShape) + mutations: mutations.map(toLifecycleMutationRow) } return Buffer.byteLength(JSON.stringify(row), 'utf8') + 1 <= MAX_JOURNAL_LIFECYCLE_BATCH_BYTES } -function lifecycleMutationRowShape( - mutation: JournalLifecycleMutationInput -): JournalLifecycleMutation { +function toLifecycleMutationRow(mutation: JournalLifecycleMutationInput): JournalLifecycleMutation { const itemId = agentJournalItemKey(mutation.identity) return mutation.kind === 'item' ? { diff --git a/src/main/native-chat/agent-session-wire/agent-session-history-page-grouping-parity.test.ts b/src/main/native-chat/agent-session-wire/agent-session-history-page-grouping-parity.test.ts index e1e8f68f7bd..677e75edc7d 100644 --- a/src/main/native-chat/agent-session-wire/agent-session-history-page-grouping-parity.test.ts +++ b/src/main/native-chat/agent-session-wire/agent-session-history-page-grouping-parity.test.ts @@ -75,14 +75,14 @@ function item(index: number, sequence: number): AgentJournalRenderItem { } } -/** Every sequence-run shape of `length` items, as run-length compositions. */ -function* runShapes(length: number): Generator { +/** Every run-length composition of `length` items. */ +function* runLengthCompositions(length: number): Generator { if (length === 0) { yield [] return } for (let first = 1; first <= length; first += 1) { - for (const rest of runShapes(length - first)) { + for (const rest of runLengthCompositions(length - first)) { yield [first, ...rest] } } @@ -105,7 +105,7 @@ function buildItems(runs: number[], repeatSequence: boolean): AgentJournalRender it('matches eager grouping at every newest-window limit for every run shape', () => { let cases = 0 for (let length = 0; length <= 7; length += 1) { - for (const runs of runShapes(length)) { + for (const runs of runLengthCompositions(length)) { for (const repeatSequence of [false, true]) { const items = buildItems(runs, repeatSequence) // Every boundary, including 0, each exact group edge, and past the end. @@ -127,7 +127,7 @@ it('matches eager byte bounding at every budget boundary in both directions', () let truncatedCases = 0 let partialCases = 0 for (let length = 1; length <= 6; length += 1) { - for (const runs of runShapes(length)) { + for (const runs of runLengthCompositions(length)) { for (const repeatSequence of [false, true]) { const items = buildItems(runs, repeatSequence) const perItem = historyEntryBytes(items[0]!, submissionBytes) diff --git a/src/main/native-chat/agent-session-wire/provider-frame-disposition.test.ts b/src/main/native-chat/agent-session-wire/provider-frame-disposition.test.ts index a57aa5d9ed1..b1acb14eee3 100644 --- a/src/main/native-chat/agent-session-wire/provider-frame-disposition.test.ts +++ b/src/main/native-chat/agent-session-wire/provider-frame-disposition.test.ts @@ -3,7 +3,7 @@ import { CODEX_APP_SERVER_NOTIFICATION_METHODS } from '../../codex/codex-app-ser import { CLAUDE_STREAM_JSON_FRAME_KINDS } from './claude-stream-json-frame-schema' import { classifyProviderFrame, - isDeltaShapedProviderFrameKind, + isDeltaProviderFrameKind, PROVIDER_FRAME_CLASSIFICATIONS } from './provider-frame-disposition' import { unhandledProviderFrameJournalItem } from './unhandled-provider-frame' @@ -25,7 +25,7 @@ describe('provider frame classification catalog', () => { const deltaKinds = [ ...Object.keys(PROVIDER_FRAME_CLASSIFICATIONS.codex), ...Object.keys(PROVIDER_FRAME_CLASSIFICATIONS.claude) - ].filter(isDeltaShapedProviderFrameKind) + ].filter(isDeltaProviderFrameKind) expect(deltaKinds.length).toBeGreaterThan(0) for (const kind of deltaKinds) { diff --git a/src/main/native-chat/agent-session-wire/provider-frame-disposition.ts b/src/main/native-chat/agent-session-wire/provider-frame-disposition.ts index 548a719ceb1..dea830b1315 100644 --- a/src/main/native-chat/agent-session-wire/provider-frame-disposition.ts +++ b/src/main/native-chat/agent-session-wire/provider-frame-disposition.ts @@ -226,7 +226,7 @@ function itemKind(kind: string): string | null { return kind.startsWith('item:') ? kind.slice('item:'.length) : null } -export function isDeltaShapedProviderFrameKind(kind: string): boolean { +export function isDeltaProviderFrameKind(kind: string): boolean { return notificationKind(kind).toLowerCase().endsWith('delta') } @@ -260,7 +260,7 @@ export function classifyProviderFrame( if (hasProviderError(payload)) { return 'error-surface' } - if (isDeltaShapedProviderFrameKind(kind)) { + if (isDeltaProviderFrameKind(kind)) { return 'stream-into-item' } if (provider === 'claude' && kind === 'message:result') { diff --git a/src/main/observability/redactor.test.ts b/src/main/observability/redactor.test.ts index 0b8ee0f9e61..19324cc08b2 100644 --- a/src/main/observability/redactor.test.ts +++ b/src/main/observability/redactor.test.ts @@ -25,7 +25,7 @@ const SECRETS = { pem: '-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQ\n-----END PRIVATE KEY-----' } -const SHAPES: { label: string; raw: string; tag: string }[] = [ +const PROVIDER_KEY_CASES: { label: string; raw: string; tag: string }[] = [ { label: 'anthropic', raw: SECRETS.anthropic, tag: 'anthropic-key' }, { label: 'openai', raw: SECRETS.openai, tag: 'openai-key' }, { label: 'github', raw: SECRETS.github, tag: 'github-token' }, @@ -37,7 +37,7 @@ const SHAPES: { label: string; raw: string; tag: string }[] = [ ] describe('redactor — provider-key fingerprints', () => { - for (const { label, raw, tag } of SHAPES) { + for (const { label, raw, tag } of PROVIDER_KEY_CASES) { describe(`${label}`, () => { it('redacts when the secret appears as an attribute value', () => { // Bare "" without a labeled-kv keyword nearby — exercises the diff --git a/src/main/persistence/loading-store/persisted-state-redundancy.test.ts b/src/main/persistence/loading-store/persisted-state-redundancy.test.ts index bccef63b39c..9a30b26da38 100644 --- a/src/main/persistence/loading-store/persisted-state-redundancy.test.ts +++ b/src/main/persistence/loading-store/persisted-state-redundancy.test.ts @@ -138,7 +138,7 @@ function writeLegacyFile(dataFile: string): void { /** Inverse of everything this change does, applied to a compact file: what the old serializer * would have written for the same state. */ -function reexpandToLegacyShape(state: PersistedState): PersistedState { +function reexpandToLegacySerialization(state: PersistedState): PersistedState { const expanded = structuredClone(state) for (const map of [expanded.worktreeMeta, expanded.worktreeMetaByIdentity]) { for (const [key, meta] of Object.entries(map ?? {})) { @@ -207,7 +207,7 @@ describe('persisted-state redundancy', () => { // Apples to apples: re-expand the file we just wrote back into the old shape and compare, so // the number is the redundancy alone and not the settings defaults a synthetic fixture lacks. expect(Buffer.byteLength(rewritten)).toBeLessThan( - Buffer.byteLength(JSON.stringify(reexpandToLegacyShape(onDisk))) * 0.6 + Buffer.byteLength(JSON.stringify(reexpandToLegacySerialization(onDisk))) * 0.6 ) // load(save(state)) deep-equals the pre-save state for every field touched. diff --git a/src/main/pi/titlebar-extension-overlay-path.test.ts b/src/main/pi/titlebar-extension-overlay-path.test.ts index db5bfeedbd9..1a4ace7d352 100644 --- a/src/main/pi/titlebar-extension-overlay-path.test.ts +++ b/src/main/pi/titlebar-extension-overlay-path.test.ts @@ -8,7 +8,7 @@ const userDataDir = mkdtempSync(join(tmpdir(), 'orca-pi-overlay-path-userdata-') import { PiTitlebarExtensionService } from './titlebar-extension-service' -const PATH_SHAPED_PTY_ID = [ +const PATH_LIKE_PTY_ID = [ '50c010a2-bc8e-4eb1-8847-5812133ad6df', 'Users', 'dev', @@ -45,7 +45,7 @@ describe('PiTitlebarExtensionService legacy overlay paths', () => { const svc = new PiTitlebarExtensionService() try { - const env = svc.buildPtyEnv(PATH_SHAPED_PTY_ID, piHome, 'pi') + const env = svc.buildPtyEnv(PATH_LIKE_PTY_ID, piHome, 'pi') expect(env.PI_CODING_AGENT_DIR).toBeUndefined() expect(env.ORCA_PI_SOURCE_AGENT_DIR).toBe(piHome) @@ -61,12 +61,12 @@ describe('PiTitlebarExtensionService legacy overlay paths', () => { }) it('clears legacy raw path-shaped daemon overlays during teardown', () => { - const legacyOverlayDir = legacyOverlayPath('pi', PATH_SHAPED_PTY_ID) + const legacyOverlayDir = legacyOverlayPath('pi', PATH_LIKE_PTY_ID) mkdirSync(legacyOverlayDir, { recursive: true }) writeFileSync(join(legacyOverlayDir, 'stale.txt'), 'stale overlay') const svc = new PiTitlebarExtensionService() - svc.clearPty(PATH_SHAPED_PTY_ID) + svc.clearPty(PATH_LIKE_PTY_ID) expect(existsSync(legacyOverlayDir)).toBe(false) }) diff --git a/src/main/runtime/rpc/terminal-output-frame-chunks-equivalence.test.ts b/src/main/runtime/rpc/terminal-output-frame-chunks-equivalence.test.ts index 233aa3e4bc2..85e01116c3c 100644 --- a/src/main/runtime/rpc/terminal-output-frame-chunks-equivalence.test.ts +++ b/src/main/runtime/rpc/terminal-output-frame-chunks-equivalence.test.ts @@ -132,10 +132,10 @@ function* legacyIterateTerminalOutputFrameChunks( } } -type FrameShape = { base64: string; seq: number | 'undefined'; opcode: number | 'undefined' } +type FrameSummary = { base64: string; seq: number | 'undefined'; opcode: number | 'undefined' } -function describeFrames(frames: Iterable): FrameShape[] { - const out: FrameShape[] = [] +function describeFrames(frames: Iterable): FrameSummary[] { + const out: FrameSummary[] = [] for (const frame of frames) { out.push({ base64: Buffer.from(frame.bytes).toString('base64'), @@ -170,10 +170,10 @@ const SURROGATE_EDGES = [ '\udfff\udc00' ] -// Meta shapes exercised against every fixture: no meta, seq-preserved (rawLength === +// Meta variants exercised against every fixture: no meta, seq-preserved (rawLength === // data.length), the delayed-final-seq path (rawLength !== data.length -> OutputSpan), // transformed, and cwd-only. -function metaShapesFor(data: string): { label: string; meta: TerminalOutputMeta | undefined }[] { +function metaVariantsFor(data: string): { label: string; meta: TerminalOutputMeta | undefined }[] { return [ { label: 'no-meta', meta: undefined }, { label: 'seq-only', meta: { seq: 5_000_000 } }, @@ -187,8 +187,8 @@ function metaShapesFor(data: string): { label: string; meta: TerminalOutputMeta } function sweepAll(data: string, label: string): void { - for (const shape of metaShapesFor(data)) { - expectEquivalent(data, shape.meta, `${label} [${shape.label}]`) + for (const variant of metaVariantsFor(data)) { + expectEquivalent(data, variant.meta, `${label} [${variant.label}]`) } } diff --git a/src/main/runtime/terminal-wait-tail-state.ts b/src/main/runtime/terminal-wait-tail-state.ts index 712b301a961..8d0ce1f2e88 100644 --- a/src/main/runtime/terminal-wait-tail-state.ts +++ b/src/main/runtime/terminal-wait-tail-state.ts @@ -32,15 +32,15 @@ export function computeTerminalTailWaitState( partialLine: string, preview: string ): TerminalTailWaitState { - const tailShape = inspectTerminalWaitTail(lines, partialLine) - if (!tailShape.fromTail) { + const tailInspection = inspectTerminalWaitTail(lines, partialLine) + if (!tailInspection.fromTail) { return { waitText: preview, signal: findActionableTerminalWaitBlockedSignal(preview.toLowerCase()), fromTail: false } } - if (!tailShape.mayContainBlockedSignal) { + if (!tailInspection.mayContainBlockedSignal) { // Why: reads waitText only when a signal exists; avoid retaining a rebuilt 256 KiB string in the common case. return { waitText: '', signal: null, fromTail: true } } diff --git a/src/main/skills/skill-bundle-artifacts.ts b/src/main/skills/skill-bundle-artifacts.ts index 08e33a0eeba..f8c19b58185 100644 --- a/src/main/skills/skill-bundle-artifacts.ts +++ b/src/main/skills/skill-bundle-artifacts.ts @@ -18,7 +18,7 @@ export type SkillBundleArtifacts = { } const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/) -const snapshotShape = { +const snapshotFields = { releaseRevision: z.number().int().positive(), packageDigest: sha256Schema, gitTreeSha: z.string().regex(/^[a-f0-9]{40}$/), @@ -38,7 +38,7 @@ const snapshotShape = { ) .min(1) } -const knownSnapshotSchema = z.object(snapshotShape).strict() +const knownSnapshotSchema = z.object(snapshotFields).strict() const manifestSchema = z .object({ schemaVersion: z.literal(2), @@ -47,7 +47,7 @@ const manifestSchema = z .object({ name: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/), sourcePath: z.string().min(1), - ...snapshotShape + ...snapshotFields }) .strict() ) diff --git a/src/main/ssh/ssh-host-key-store.test.ts b/src/main/ssh/ssh-host-key-store.test.ts index 4e18df099e4..835601aeac2 100644 --- a/src/main/ssh/ssh-host-key-store.test.ts +++ b/src/main/ssh/ssh-host-key-store.test.ts @@ -303,7 +303,7 @@ describe('a host key store written by a newer version', () => { const storeFile = join(dir, 'ssh-host-keys.json') const future = JSON.stringify({ version: 99, - hostKeys: [{ shape: 'we do not understand' }] + hostKeys: [{ unrecognized: 'we do not understand' }] }) await writeFile(storeFile, future, 'utf-8') diff --git a/src/main/ssh/ssh-orphan-sweep-pane-state-verdicts.test.ts b/src/main/ssh/ssh-orphan-sweep-pane-state-verdicts.test.ts index 560d18b8b62..9bb42a2cb27 100644 --- a/src/main/ssh/ssh-orphan-sweep-pane-state-verdicts.test.ts +++ b/src/main/ssh/ssh-orphan-sweep-pane-state-verdicts.test.ts @@ -167,9 +167,9 @@ describe('what the host publishes about a pane, read by the sweep', () => { it('records that a backgrounded and a suspended shell are indistinguishable at tpgid/pgid', () => { // The premise of the whole file. If this ever fails, the fixtures drifted and every verdict // below is testing something other than the defect. Pids differ between captures, so the - // comparison is of the shell row's shape: who its parent is, whether it leads its own process - // group, whether that group owns the terminal, and its state flags. - const shellShape = (capture: { rootPid: number; table: readonly string[] }): string => { + // comparison is of the shell row's signature: who its parent is, whether it leads its own + // process group, whether that group owns the terminal, and its state flags. + const shellRowSignature = (capture: { rootPid: number; table: readonly string[] }): string => { const row = parseStrictProcessTableRows(capture.table.join('\n')).find( (candidate) => candidate.pid === capture.rootPid )! @@ -181,19 +181,21 @@ describe('what the host publishes about a pane, read by the sweep', () => { ].join(' ') } - expect(shellShape(CAPTURES.idle)).toBe('ppid=1 leadsOwnGroup=true ownsTerminal=true stat=Ss+') - expect(shellShape(CAPTURES.background)).toBe(shellShape(CAPTURES.idle)) - expect(shellShape(CAPTURES.ctrlz)).toBe(shellShape(CAPTURES.idle)) - expect(shellShape(CAPTURES.foreground)).not.toBe(shellShape(CAPTURES.idle)) + expect(shellRowSignature(CAPTURES.idle)).toBe( + 'ppid=1 leadsOwnGroup=true ownsTerminal=true stat=Ss+' + ) + expect(shellRowSignature(CAPTURES.background)).toBe(shellRowSignature(CAPTURES.idle)) + expect(shellRowSignature(CAPTURES.ctrlz)).toBe(shellRowSignature(CAPTURES.idle)) + expect(shellRowSignature(CAPTURES.foreground)).not.toBe(shellRowSignature(CAPTURES.idle)) // Same premise for the `set +m` captures, minus `ppid`: their harness keeps its parent alive - // rather than reparenting the shell to init, and the ppid is the one field of the shape the - // predicate never reads. - const paneShape = (capture: { rootPid: number; table: readonly string[] }): string => - shellShape(capture).split(' ').slice(1).join(' ') - expect(paneShape(CAPTURES.setMinusMBackground)).toBe(paneShape(CAPTURES.idle)) - expect(paneShape(CAPTURES.nottyGroupMember)).toBe(paneShape(CAPTURES.idle)) - expect(paneShape(CAPTURES.doubleForkedGroupMember)).toBe(paneShape(CAPTURES.idle)) + // rather than reparenting the shell to init, and the ppid is the one field of the signature + // the predicate never reads. + const paneRowSignature = (capture: { rootPid: number; table: readonly string[] }): string => + shellRowSignature(capture).split(' ').slice(1).join(' ') + expect(paneRowSignature(CAPTURES.setMinusMBackground)).toBe(paneRowSignature(CAPTURES.idle)) + expect(paneRowSignature(CAPTURES.nottyGroupMember)).toBe(paneRowSignature(CAPTURES.idle)) + expect(paneRowSignature(CAPTURES.doubleForkedGroupMember)).toBe(paneRowSignature(CAPTURES.idle)) }) it('sweeps an idle shell', async () => { diff --git a/src/main/ssh/ssh-relay-deploy-helpers.test.ts b/src/main/ssh/ssh-relay-deploy-helpers.test.ts index 1d73e188d19..adb14925636 100644 --- a/src/main/ssh/ssh-relay-deploy-helpers.test.ts +++ b/src/main/ssh/ssh-relay-deploy-helpers.test.ts @@ -232,9 +232,9 @@ describe('waitForSentinel', () => { it.each(['ssh2 channel', 'system-SSH child stdio'])( 'forwards write(false), callback settlement, and drain for a %s', - async (shape) => { + async (channelKind) => { const channel = createMockChannel() - if (shape.startsWith('system')) { + if (channelKind.startsWith('system')) { Object.assign(channel, { _process: new EventEmitter() }) } const callback = vi.fn() diff --git a/src/main/ssh/ssh-remote-platform-detection.ts b/src/main/ssh/ssh-remote-platform-detection.ts index 6fd0f87c767..499e088e49c 100644 --- a/src/main/ssh/ssh-remote-platform-detection.ts +++ b/src/main/ssh/ssh-remote-platform-detection.ts @@ -38,7 +38,7 @@ export async function detectRemoteHostPlatform( } // Why: only the PowerShell probe can settle a uname the parser cannot map // (Cygwin, say), so a refused or timed-out channel leaves it unsettled. - const windowsProbeNeverRan = windows.kind === 'failed' && isTransportShapedError(windows.error) + const windowsProbeNeverRan = windows.kind === 'failed' && isTransportFailure(windows.error) if ((uname.kind === 'unsupported' && !windowsProbeNeverRan) || windows.kind === 'unsupported') { const reported = uname.kind === 'unsupported' ? uname.uname : probeUname(windows) console.warn(`[ssh-relay] Remote reported an unsupported platform: ${reported}`) @@ -66,7 +66,7 @@ function undetectedPlatformError( windows: PlatformProbeOutcome ): Error { for (const outcome of [uname, windows]) { - if (outcome.kind === 'failed' && isTransportShapedError(outcome.error)) { + if (outcome.kind === 'failed' && isTransportFailure(outcome.error)) { return wrapProbeError(outcome.error) } } @@ -84,7 +84,7 @@ function undetectedPlatformError( // Why: a refused or timed-out channel explains the failure better than the // other probe's mundane non-zero exit (e.g. "sh: not found" on Windows). -function isTransportShapedError(error: unknown): boolean { +function isTransportFailure(error: unknown): boolean { return ( isSshSessionLimitError(error) || isUnconfirmedSshCommandTermination(error) || diff --git a/src/main/text-generation/commit-message-text-generation-failure-sanitization.test.ts b/src/main/text-generation/commit-message-text-generation-failure-sanitization.test.ts index 6d20998b625..ac6577b2d96 100644 --- a/src/main/text-generation/commit-message-text-generation-failure-sanitization.test.ts +++ b/src/main/text-generation/commit-message-text-generation-failure-sanitization.test.ts @@ -327,7 +327,7 @@ describe('generateCommitMessageFromContext', () => { '401: {"message":"slot 1:/Users/name/alt failed"}', 'Pi CLI command failed with code 1: 401: {"message":"slot 1:[path] failed"}' ] - ])('redacts a %s in provider bodies', async (_shape, stderr, expected) => { + ])('redacts a %s in provider bodies', async (_variant, stderr, expected) => { const result = await generateCommitMessageFromContext( { branch: 'main', diff --git a/src/main/wsl-unc-delete-symlink-repro.test.ts b/src/main/wsl-unc-delete-symlink-repro.test.ts index ace95afe933..a594b444e75 100644 --- a/src/main/wsl-unc-delete-symlink-repro.test.ts +++ b/src/main/wsl-unc-delete-symlink-repro.test.ts @@ -55,7 +55,7 @@ describe('WSL vault intermediate-symlink reproduction', () => { it.each([ ['file-shaped', `${FIXTURE_ROOT}/linked-project/session.json`, false], ['directory-shaped', `${FIXTURE_ROOT}/linked-project/session`, true] - ])('rejects a %s target before removal', async (_shape, target, recursive) => { + ])('rejects a %s target before removal', async (_targetKind, target, recursive) => { const options = { recursive, approvedRoots: [unc(FIXTURE_ROOT)] } let rejection: unknown diff --git a/src/main/wsl-unc-delete.wsl.test.ts b/src/main/wsl-unc-delete.wsl.test.ts index 36175b62626..25036380b00 100644 --- a/src/main/wsl-unc-delete.wsl.test.ts +++ b/src/main/wsl-unc-delete.wsl.test.ts @@ -46,7 +46,7 @@ describe.skipIf(!runRealWsl)('WSL contained delete integration', () => { it.each([ ['file-shaped', 'file-link/session.json', false], ['directory-shaped', 'dir-link/session', true] - ])('rejects a %s escape and preserves all outside entries', async (_shape, path, recursive) => { + ])('rejects a %s escape and preserves all outside entries', async (_label, path, recursive) => { const vaultRoot = `${fixtureRoot}/vault` await expect( diff --git a/src/main/wsl.test.ts b/src/main/wsl.test.ts index 6ef8adbbb61..55327c465e5 100644 --- a/src/main/wsl.test.ts +++ b/src/main/wsl.test.ts @@ -547,10 +547,10 @@ describe('WSL availability cache', () => { it.each([ ['wsl.exe reports WSL unusable', { status: 1 }], ['wsl.exe is not installed', { code: 'ENOENT' }] - ])('holds a definitive failure far longer than a timeout when %s', (_label, errorShape) => { + ])('holds a definitive failure far longer than a timeout when %s', (_label, errorFields) => { vi.useFakeTimers() execFileSyncMock.mockImplementationOnce(() => { - throw Object.assign(new Error('definitive failure'), errorShape) + throw Object.assign(new Error('definitive failure'), errorFields) }) execFileSyncMock.mockReturnValueOnce('') @@ -621,10 +621,10 @@ describe('WSL availability cache', () => { it.each([ ['a definitive failure', { status: 1 }], ['a timeout', { code: 'ETIMEDOUT', status: null, signal: 'SIGTERM' }] - ])('re-probes availability once a distro list succeeds after %s', (_label, errorShape) => { + ])('re-probes availability once a distro list succeeds after %s', (_label, errorFields) => { vi.useFakeTimers() execFileSyncMock.mockImplementationOnce(() => { - throw Object.assign(new Error('probe failed'), errorShape) + throw Object.assign(new Error('probe failed'), errorFields) }) try { diff --git a/src/relay/git-exec-validator.ts b/src/relay/git-exec-validator.ts index 82cc72d1d2e..9f9b5866ebc 100644 --- a/src/relay/git-exec-validator.ts +++ b/src/relay/git-exec-validator.ts @@ -96,7 +96,7 @@ const DIFF_ALLOWED_FLAGS = new Set([ // only those two exact shapes, held to the same remote-name and URL rules the // relay already enforces on every pushTarget-carrying RPC. Everything else -- // set-url, rename, prune, flags before the action -- stays blocked. -function isAllowedRemoteWriteShape(args: string[]): boolean { +function isAllowedRemoteWriteInvocation(args: string[]): boolean { if (args[1] === 'add') { return args.length === 4 && isSafeGitRemoteName(args[2]) && isSafePushTargetRemoteUrl(args[3]) } @@ -197,7 +197,7 @@ export function validateGitExecArgs(args: string[]): void { if ( remoteSubcmd && REMOTE_WRITE_SUBCOMMANDS.has(remoteSubcmd) && - !isAllowedRemoteWriteShape(args) + !isAllowedRemoteWriteInvocation(args) ) { throw new Error('Destructive git remote operations are not allowed via exec') } diff --git a/src/relay/git-handler-branch-diff-equivalence.test.ts b/src/relay/git-handler-branch-diff-equivalence.test.ts index d33886b4f5a..ff399d92a7c 100644 --- a/src/relay/git-handler-branch-diff-equivalence.test.ts +++ b/src/relay/git-handler-branch-diff-equivalence.test.ts @@ -128,10 +128,10 @@ describe('pinned and legacy branch diff equivalence against real Git', () => { for (const entry of compare.entries) { // Exactly what the renderer sends: paths from the compare entry list, // OIDs from the compare summary that produced that same list. - const callerShape = { filePath: entry.path, oldPath: entry.oldPath } - const legacy = await branchDiff(callerShape) + const callerParams = { filePath: entry.path, oldPath: entry.oldPath } + const legacy = await branchDiff(callerParams) const pinned = await branchDiff({ - ...callerShape, + ...callerParams, baseRef: compare.summary.mergeBase, headOid: compare.summary.headOid }) diff --git a/src/relay/git-handler-comparison-operations.ts b/src/relay/git-handler-comparison-operations.ts index e5ebe65365b..f4c1e55fa5e 100644 --- a/src/relay/git-handler-comparison-operations.ts +++ b/src/relay/git-handler-comparison-operations.ts @@ -5,7 +5,7 @@ import { parseBranchDiff } from './git-handler-utils' import { parseNumstat } from '../shared/git-uncommitted-line-stats' import { isNoUpstreamError, normalizeGitErrorMessage } from '../shared/git-remote-error' import { upstreamOnlyCommitsArePatchEquivalent } from '../shared/git-upstream-status' -import { assertGitPushTargetShape } from '../shared/git-push-target-validation' +import { assertValidGitPushTarget } from '../shared/git-push-target-validation' import { getPublishTargetStatus, type GitCommandRunner } from '../shared/git-publish-target-status' import type { GitPushTarget } from '../shared/worktree/types' import { getEffectiveGitUpstreamStatus } from '../shared/git-effective-upstream' @@ -46,7 +46,7 @@ export class GitHandlerComparisonOperations extends GitHandlerOperationContext { try { if (params.pushTarget !== undefined) { - assertGitPushTargetShape(params.pushTarget) + assertValidGitPushTarget(params.pushTarget) const pushTarget = params.pushTarget as GitPushTarget await this.git(['check-ref-format', '--branch', pushTarget.branchName], worktreePath) return await getPublishTargetStatus( diff --git a/src/relay/git-handler-fetch-operations.ts b/src/relay/git-handler-fetch-operations.ts index cd3a86fcb63..fd13cdc07c2 100644 --- a/src/relay/git-handler-fetch-operations.ts +++ b/src/relay/git-handler-fetch-operations.ts @@ -1,6 +1,6 @@ import type { RequestContext } from './dispatcher' import { GitHandlerOperationContext } from './git-handler-operation-context' -import { assertGitPushTargetShape } from '../shared/git-push-target-validation' +import { assertValidGitPushTarget } from '../shared/git-push-target-validation' import type { GitPushTarget } from '../shared/worktree/types' import { normalizeGitErrorMessage, isExecKilledError } from '../shared/git-remote-error' import { syncForkDefaultBranch, validateGitForkSyncExpectedUpstream } from '../shared/git-fork-sync' @@ -21,7 +21,7 @@ export class GitHandlerFetchOperations extends GitHandlerOperationContext { try { try { if (params.pushTarget !== undefined) { - assertGitPushTargetShape(params.pushTarget) + assertValidGitPushTarget(params.pushTarget) const pushTarget = params.pushTarget as GitPushTarget await this.git(['check-ref-format', '--branch', pushTarget.branchName], worktreePath) await this.git(['fetch', '--prune', pushTarget.remoteName], worktreePath) diff --git a/src/relay/git-handler-push-target.ts b/src/relay/git-handler-push-target.ts index 6663b5b3ad3..57a39e632d3 100644 --- a/src/relay/git-handler-push-target.ts +++ b/src/relay/git-handler-push-target.ts @@ -1,4 +1,4 @@ -import { assertGitPushTargetShape } from '../shared/git-push-target-validation' +import { assertValidGitPushTarget } from '../shared/git-push-target-validation' import { resolveConfiguredGitPushTarget, type ResolvedGitPushTarget @@ -15,7 +15,7 @@ export async function resolveRelayPushTarget( if (pushTarget === undefined) { return resolveConfiguredGitPushTarget((args) => git(args, worktreePath)) } - assertGitPushTargetShape(pushTarget) + assertValidGitPushTarget(pushTarget) const explicitTarget: GitPushTarget = pushTarget // Why here and not in the shared resolver: an explicit target arrives over the wire, // so the host re-validates its shape and asks Git to vet the branch name itself. diff --git a/src/relay/git-handler-sync-operations.ts b/src/relay/git-handler-sync-operations.ts index 262517b33cc..9c0922c34df 100644 --- a/src/relay/git-handler-sync-operations.ts +++ b/src/relay/git-handler-sync-operations.ts @@ -3,7 +3,7 @@ import type { RequestContext } from './dispatcher' import { GitHandlerOperationContext } from './git-handler-operation-context' import { resolveRelayPushTarget } from './git-handler-push-target' import { normalizeGitErrorMessage, runPullWithDivergenceFallback } from '../shared/git-remote-error' -import { assertGitPushTargetShape } from '../shared/git-push-target-validation' +import { assertValidGitPushTarget } from '../shared/git-push-target-validation' import type { GitCommandRunner } from '../shared/git-publish-target-status' import type { GitPushTarget } from '../shared/worktree/types' import { resolveEffectiveGitUpstream } from '../shared/git-effective-upstream' @@ -63,7 +63,7 @@ export class GitHandlerSyncOperations extends GitHandlerOperationContext { const worktreePath = params.worktreePath as string const runPull = async (effectiveArgs: string[]): Promise => { if (params.pushTarget !== undefined) { - assertGitPushTargetShape(params.pushTarget) + assertValidGitPushTarget(params.pushTarget) const pushTarget = params.pushTarget as GitPushTarget await this.git(['check-ref-format', '--branch', pushTarget.branchName], worktreePath) await this.git( diff --git a/src/renderer/src/components/editor/rich-markdown-html-superscript-link.ts b/src/renderer/src/components/editor/rich-markdown-html-superscript-link.ts index a60a6e9c777..6d8c2b44f15 100644 --- a/src/renderer/src/components/editor/rich-markdown-html-superscript-link.ts +++ b/src/renderer/src/components/editor/rich-markdown-html-superscript-link.ts @@ -149,7 +149,7 @@ function parseStructuredPayload(value: string): HtmlSuperscriptLinkSource | null } catch { return null } - if (!isCitationShape(candidate)) { + if (!isCitationSource(candidate)) { return null } const parsed = parseHtmlSuperscriptLinkSource(candidate.source) @@ -197,7 +197,7 @@ function hasOnlyAttributes(element: Element, allowed: string[]): boolean { return Array.from(element.attributes).every((attribute) => allowedSet.has(attribute.name)) } -function isCitationShape(value: unknown): value is HtmlSuperscriptLinkSource { +function isCitationSource(value: unknown): value is HtmlSuperscriptLinkSource { if (!value || typeof value !== 'object') { return false } diff --git a/src/renderer/src/components/repo/repo-icon.tsx b/src/renderer/src/components/repo/repo-icon.tsx index 9e9ad0c24ac..b778ead4426 100644 --- a/src/renderer/src/components/repo/repo-icon.tsx +++ b/src/renderer/src/components/repo/repo-icon.tsx @@ -16,6 +16,7 @@ import { Palette, Rocket, Server, + // `Shapes` is lucide-react's own export name; exempted in config/oxlint-anti-slop.json. Shapes, Sparkles, SquareTerminal, diff --git a/src/renderer/src/components/right-sidebar/source-control/commit/discard-confirmation.ts b/src/renderer/src/components/right-sidebar/source-control/commit/discard-confirmation.ts index e9f5a65788e..1a6121b535b 100644 --- a/src/renderer/src/components/right-sidebar/source-control/commit/discard-confirmation.ts +++ b/src/renderer/src/components/right-sidebar/source-control/commit/discard-confirmation.ts @@ -13,9 +13,7 @@ export type DiscardConfirmationCopy = { * Untracked and newly-added paths have no HEAD version to restore, so Orca's discard removes the * working-tree file. Every surface that names the operation must say "delete" for these. */ -export function isDeleteShapedDiscardEntry( - entry: Pick -): boolean { +export function discardDeletesEntryFile(entry: Pick): boolean { return entry.area === 'untracked' || entry.status === 'untracked' || entry.status === 'added' } @@ -24,7 +22,7 @@ export function getDiscardEntryConfirmationCopy( ): DiscardConfirmationCopy { const name = basename(entry.path) - if (isDeleteShapedDiscardEntry(entry)) { + if (discardDeletesEntryFile(entry)) { return { title: translate( 'auto.components.right.sidebar.source.control.discard.confirmation.96c772bee9', diff --git a/src/renderer/src/components/right-sidebar/source-control/commit/source-control-entry-failure-toast.test.ts b/src/renderer/src/components/right-sidebar/source-control/commit/source-control-entry-failure-toast.test.ts index 92f69010c9f..340b6e3a7e5 100644 --- a/src/renderer/src/components/right-sidebar/source-control/commit/source-control-entry-failure-toast.test.ts +++ b/src/renderer/src/components/right-sidebar/source-control/commit/source-control-entry-failure-toast.test.ts @@ -61,7 +61,7 @@ describe('showSourceControlEntryFailureToast', () => { it('says "delete" for an entry whose discard removes the file rather than restoring it', () => { // Why: untracked and added paths have no HEAD version, so the row button and the confirmation // dialog both say "delete" — the failure must not contradict the verb the user pressed. - show({ operation: 'discard', deleteShaped: true }) + show({ operation: 'discard', deletesFile: true }) expect(lastToast().title).toBe('Failed to delete “src/app.ts”') }) diff --git a/src/renderer/src/components/right-sidebar/source-control/commit/source-control-entry-failure-toast.ts b/src/renderer/src/components/right-sidebar/source-control/commit/source-control-entry-failure-toast.ts index 13ab65c6fb8..9668ee1e16a 100644 --- a/src/renderer/src/components/right-sidebar/source-control/commit/source-control-entry-failure-toast.ts +++ b/src/renderer/src/components/right-sidebar/source-control/commit/source-control-entry-failure-toast.ts @@ -26,7 +26,7 @@ export function dismissSourceControlEntryFailureToast(worktreeId: string | null) function entryFailureTitle( operation: SourceControlEntryOperation, filePath: string, - deleteShaped: boolean + deletesFile: boolean ): string { switch (operation) { case 'stage': @@ -42,7 +42,7 @@ function entryFailureTitle( { value0: filePath } ) case 'discard': - return deleteShaped + return deletesFile ? translate( 'auto.components.right.sidebar.SourceControl.entryDeleteFailed', 'Failed to delete “{{value0}}”', @@ -67,7 +67,7 @@ function entryFailureTitle( export function showSourceControlEntryFailureToast({ operation, filePath, - deleteShaped = false, + deletesFile = false, error, worktreeId, worktreeName, @@ -76,7 +76,7 @@ export function showSourceControlEntryFailureToast({ operation: SourceControlEntryOperation filePath: string /** True when this discard deletes the file rather than restoring it — see `discard-confirmation`. */ - deleteShaped?: boolean + deletesFile?: boolean error: unknown /** The worktree the failed attempt ran against. */ worktreeId: string | null @@ -85,7 +85,7 @@ export function showSourceControlEntryFailureToast({ onRetry?: () => void }): void { const isActiveWorktree = useAppStore.getState().activeWorktreeId === worktreeId - const title = entryFailureTitle(operation, filePath, deleteShaped) + const title = entryFailureTitle(operation, filePath, deletesFile) const offerRetry = Boolean(onRetry) && isActiveWorktree entryFailureSlotOwner = { worktreeId } toast.error( diff --git a/src/renderer/src/components/right-sidebar/source-control/commit/use-discard-confirmation.ts b/src/renderer/src/components/right-sidebar/source-control/commit/use-discard-confirmation.ts index f158c971a23..ae1c47398c5 100644 --- a/src/renderer/src/components/right-sidebar/source-control/commit/use-discard-confirmation.ts +++ b/src/renderer/src/components/right-sidebar/source-control/commit/use-discard-confirmation.ts @@ -10,7 +10,7 @@ import { runDiscardAllForArea, type DiscardAllArea } from './discard-all-sequence' -import { isDeleteShapedDiscardEntry } from './discard-confirmation' +import { discardDeletesEntryFile } from './discard-confirmation' import { readIpcErrorMessage } from '@/lib/ipc-error' import { dismissSourceControlEntryFailureToast, @@ -62,7 +62,7 @@ export function useSourceControlDiscardConfirmation({ showSourceControlEntryFailureToast({ operation: 'discard', filePath: entry.path, - deleteShaped: isDeleteShapedDiscardEntry(entry), + deletesFile: discardDeletesEntryFile(entry), error, worktreeId: activeWorktreeId, worktreeName: worktreePath ? basename(worktreePath) : null diff --git a/src/renderer/src/components/shared/useDaemonActions.tsx b/src/renderer/src/components/shared/useDaemonActions.tsx index 8c386b5ef13..93c4477cf66 100644 --- a/src/renderer/src/components/shared/useDaemonActions.tsx +++ b/src/renderer/src/components/shared/useDaemonActions.tsx @@ -223,14 +223,14 @@ export function useDaemonActions(callbacks?: DaemonActionCallbacks): DaemonActio } } -type CopyShape = { +type DaemonActionCopy = { title: string description: React.ReactNode confirmLabel: string busyLabel: string } -function getCopy(kind: DaemonActionKind): CopyShape { +function getCopy(kind: DaemonActionKind): DaemonActionCopy { if (kind === 'restart') { return { title: translate( diff --git a/src/renderer/src/components/sidebar/comment-markdown-native-chat-file-links.ts b/src/renderer/src/components/sidebar/comment-markdown-native-chat-file-links.ts index ee81b859dcc..47ee65b2a72 100644 --- a/src/renderer/src/components/sidebar/comment-markdown-native-chat-file-links.ts +++ b/src/renderer/src/components/sidebar/comment-markdown-native-chat-file-links.ts @@ -164,11 +164,11 @@ function exactFileLink(value: string, allowSpacedRelative: boolean): ParsedTermi if (!parsed) { return null } - const hasPathShape = + const looksLikePath = ROOTED_PATH_PREFIX_PATTERN.test(parsed.pathText) || /[\\/]/.test(parsed.pathText) || /\.[\p{L}][\p{L}\p{N}\p{M}_+-]*$/u.test(parsed.pathText) - if (!hasPathShape) { + if (!looksLikePath) { return null } const explicitLink = { diff --git a/src/renderer/src/components/sidebar/worktree-list/rows/repo-header-project-actions.tsx b/src/renderer/src/components/sidebar/worktree-list/rows/repo-header-project-actions.tsx index ba4751b597c..cfa97732a53 100644 --- a/src/renderer/src/components/sidebar/worktree-list/rows/repo-header-project-actions.tsx +++ b/src/renderer/src/components/sidebar/worktree-list/rows/repo-header-project-actions.tsx @@ -6,6 +6,7 @@ import { FolderInput, FolderTree, Plus, + // `Shapes` is lucide-react's own export name; exempted in config/oxlint-anti-slop.json. Shapes, SlidersHorizontal, Trash2 diff --git a/src/renderer/src/components/terminal-pane/remote-runtime-outage-toast-flood-and-stuck-reconnect.test.ts b/src/renderer/src/components/terminal-pane/remote-runtime-outage-toast-flood-and-stuck-reconnect.test.ts index 9ac752b2b31..65c7a90e72f 100644 --- a/src/renderer/src/components/terminal-pane/remote-runtime-outage-toast-flood-and-stuck-reconnect.test.ts +++ b/src/renderer/src/components/terminal-pane/remote-runtime-outage-toast-flood-and-stuck-reconnect.test.ts @@ -31,7 +31,7 @@ import { REMOTE_RUNTIME_AUTO_RECOVERY_TIMEOUT_MS } from './remote-runtime-pty-re const ELECTRON_IPC_PREFIX = "Error invoking remote method 'runtimeEnvironments:call': " /** A rejection exactly as the renderer sees it after Electron IPC strips custom props. */ -function electronIpcShapedRejection(errorName: string, message: string): Error { +function electronIpcRejection(errorName: string, message: string): Error { return new Error(`${ELECTRON_IPC_PREFIX}${errorName}: ${message}`) } @@ -192,7 +192,7 @@ describe('remote runtime outage: toast flood and stuck reconnect (issue3)', () = const { isRecoverableRemoteRuntimeConnectionError, toRemoteRuntimeClientErrorLike } = await import('../../../../shared/remote-runtime-client-error-classification') const rendererSide = toRemoteRuntimeClientErrorLike( - electronIpcShapedRejection('RemoteRuntimeClientError', TIMEOUT_WITH_TAILSCALE_HINT) + electronIpcRejection('RemoteRuntimeClientError', TIMEOUT_WITH_TAILSCALE_HINT) ) // Electron IPC stripped the code; the fragment list still catches this one. expect(rendererSide.code).toBeUndefined() @@ -201,7 +201,7 @@ describe('remote runtime outage: toast flood and stuck reconnect (issue3)', () = // per-selector RPC queue saturated by 15s-timeout calls) is classified // fatal even though its own code says "retry later". const overload = toRemoteRuntimeClientErrorLike( - electronIpcShapedRejection('RuntimeRpcCallQueueOverloadError', QUEUE_OVERLOAD_RAW) + electronIpcRejection('RuntimeRpcCallQueueOverloadError', QUEUE_OVERLOAD_RAW) ) expect(overload.code).toBeUndefined() // DESIRED: transient capacity pressure during an outage is recoverable, @@ -220,7 +220,7 @@ describe('remote runtime outage: toast flood and stuck reconnect (issue3)', () = runtimeCall.mockImplementation(async (request: { method: string; params?: unknown }) => { if (request.method === 'terminal.send') { sendRejections += 1 - throw electronIpcShapedRejection('RuntimeRpcCallQueueOverloadError', QUEUE_OVERLOAD_RAW) + throw electronIpcRejection('RuntimeRpcCallQueueOverloadError', QUEUE_OVERLOAD_RAW) } return healthyImpl(request) }) @@ -294,7 +294,7 @@ describe('remote runtime outage: toast flood and stuck reconnect (issue3)', () = if (request.method === 'terminal.resolvePane') { throw Object.assign(new Error(fatalMessage), { code: 'unauthorized' }) } - throw electronIpcShapedRejection('RemoteRuntimeClientError', TIMEOUT_WITH_TAILSCALE_HINT) + throw electronIpcRejection('RemoteRuntimeClientError', TIMEOUT_WITH_TAILSCALE_HINT) }) subscriptionCallbacks?.onClose?.() await vi.waitFor(() => expect(onError).toHaveBeenCalled()) diff --git a/src/renderer/src/components/terminal-search-decoration-leak.test.ts b/src/renderer/src/components/terminal-search-decoration-leak.test.ts index 07c4ba998e5..b508d6d6c00 100644 --- a/src/renderer/src/components/terminal-search-decoration-leak.test.ts +++ b/src/renderer/src/components/terminal-search-decoration-leak.test.ts @@ -107,7 +107,7 @@ function openTerminalWithSearch(): SearchHarness { * showed up for some of them, so the regression has to sweep rather than pin * one lucky case. */ -const CONTENT_SHAPES: readonly (readonly [string, string])[] = [ +const CONTENT_LAYOUTS: readonly (readonly [string, string])[] = [ ['matches on two lines', 'needle one\r\nneedle two\r\n'], ['matches on three lines', 'needle one\r\nneedle two\r\nneedle three\r\n'], ['matches on four lines', 'needle a\r\nneedle b\r\nneedle c\r\nneedle d\r\n'], @@ -128,7 +128,7 @@ describe('terminal search decoration cleanup (STA-2707)', () => { document.body.replaceChildren() }) - it.each(CONTENT_SHAPES)( + it.each(CONTENT_LAYOUTS)( 'leaves no highlighted cells after closing search (%s)', async (_name, content) => { // Sweeping the match-navigation count matters: which decoration is the diff --git a/src/renderer/src/hooks/useEditorExternalWatch.ts b/src/renderer/src/hooks/useEditorExternalWatch.ts index 20f02e719d5..c9b7db652f8 100644 --- a/src/renderer/src/hooks/useEditorExternalWatch.ts +++ b/src/renderer/src/hooks/useEditorExternalWatch.ts @@ -7,7 +7,7 @@ import { getEditorExternalWatchTargetKey, selectEditorExternalWatchTargets, type EditorExternalWatchTarget, - type EditorExternalWatchTargetState as EditorExternalWatchTargetStateShape + type EditorExternalWatchTargetState } from './editor-external-watch-targets' import { buildEditorExternalWatchEventHandler, @@ -15,7 +15,7 @@ import { } from './editor-external-watch-event-reconciliation' import { verifyLatchedEditorMoveDestinations } from './editor-external-watch-disk-verification' -export type EditorExternalWatchTargetState = EditorExternalWatchTargetStateShape +export type { EditorExternalWatchTargetState } function warnExternalWatchFailure(target: EditorExternalWatchTarget, err: unknown): void { console.warn('[filesystem-watch] failed to watch worktree', { diff --git a/src/renderer/src/lib/tab-agent-identity-decision-table.test.ts b/src/renderer/src/lib/tab-agent-identity-decision-table.test.ts index 6c543db3594..dc4fa218fa9 100644 --- a/src/renderer/src/lib/tab-agent-identity-decision-table.test.ts +++ b/src/renderer/src/lib/tab-agent-identity-decision-table.test.ts @@ -11,7 +11,7 @@ import type { TuiAgent } from '../../../shared/tui-agent' const AGENTS: readonly TuiAgent[] = ['claude', 'codex'] const SLOT_COUNT = 7 -const SHAPE_COUNT = 3 ** SLOT_COUNT * 4 * 2 +const COMBINATION_COUNT = 3 ** SLOT_COUNT * 4 * 2 const TITLES: readonly string[] = ['', 'zsh', 'Task - claude', 'Task - codex'] type Breakdown = Record< @@ -124,7 +124,7 @@ describe('renderer ladder decision table', () => { const proofFree = runDecisionTable(false) const freshProof = runDecisionTable(true) const result = { - shapes: SHAPE_COUNT, + combinations: COMBINATION_COUNT, proofOmitted: proofFree, freshProof, flippedByAddingProof: proofFree.flipped diff --git a/src/renderer/src/lib/typing-latency/diagnostic-summary.ts b/src/renderer/src/lib/typing-latency/diagnostic-summary.ts index 5cb96d684d9..a5a21fcef46 100644 --- a/src/renderer/src/lib/typing-latency/diagnostic-summary.ts +++ b/src/renderer/src/lib/typing-latency/diagnostic-summary.ts @@ -131,7 +131,7 @@ export type FocusedPaneCensus = { type CountableRecord = Record | null | undefined -export type TypingCensusStoreShape = { +export type TypingCensusStoreView = { worktreesByRepo?: Record | null tabsByWorktree?: Record | null unifiedTabsByWorktree?: Record | null @@ -231,7 +231,7 @@ function collectWorktrees( } export function summarizeTypingScaleCensus(input: { - state: TypingCensusStoreShape | null + state: TypingCensusStoreView | null appVersion: string | null livePaneCount: number | null instrumentedPaneCount: number diff --git a/src/renderer/src/lib/worktree-runtime-owner-index.detected.test.ts b/src/renderer/src/lib/worktree-runtime-owner-index.detected.test.ts index 2191a083e2a..eb868674b64 100644 --- a/src/renderer/src/lib/worktree-runtime-owner-index.detected.test.ts +++ b/src/renderer/src/lib/worktree-runtime-owner-index.detected.test.ts @@ -67,11 +67,11 @@ function buildCase(random: () => number): { worktreesByRepo: Record probeIds: string[] } { - const shape = random() - if (shape < 0.05) { + const roll = random() + if (roll < 0.05) { return { detectedWorktreesByRepo: undefined, worktreesByRepo: {}, probeIds: ['repo-0::absent'] } } - if (shape < 0.1) { + if (roll < 0.1) { return { detectedWorktreesByRepo: {}, worktreesByRepo: {}, probeIds: ['repo-0::absent'] } } const repoCount = 1 + Math.floor(random() * 6) diff --git a/src/renderer/src/runtime/sync-runtime-graph-agent-status-projection.test.ts b/src/renderer/src/runtime/sync-runtime-graph-agent-status-projection.test.ts index 3ae6769bb15..2ac4a8bbfd7 100644 --- a/src/renderer/src/runtime/sync-runtime-graph-agent-status-projection.test.ts +++ b/src/renderer/src/runtime/sync-runtime-graph-agent-status-projection.test.ts @@ -65,19 +65,19 @@ function makeEntry(index: number, overrides: Record = {}): neve } describe('mobile agent-status projection equivalence', () => { - it('matches the whole-array serialization across shapes and cache reuse', () => { + it('matches the whole-array serialization across status maps and cache reuse', () => { resetRuntimeMobileAgentStatusProjectionCacheForTests() - const shapes: AppState['agentStatusByPaneKey'][] = [] - shapes.push({}) - shapes.push({ 'tab-0:leaf-0': makeEntry(0) }) - shapes.push({ 'tab-0:leaf-0': makeEntry(0, { workingMode: 'monitoring' }) }) + const statusMaps: AppState['agentStatusByPaneKey'][] = [] + statusMaps.push({}) + statusMaps.push({ 'tab-0:leaf-0': makeEntry(0) }) + statusMaps.push({ 'tab-0:leaf-0': makeEntry(0, { workingMode: 'monitoring' }) }) const many: AppState['agentStatusByPaneKey'] = {} for (let index = 0; index < 12; index += 1) { many[`tab-${index}:leaf-0`] = makeEntry(index) } - shapes.push(many) + statusMaps.push(many) // Optional fields absent entirely, which the ?? null fallbacks must cover. - shapes.push({ + statusMaps.push({ 'tab-9:leaf-1': makeEntry(9, { agentType: undefined, terminalTitle: undefined, @@ -89,17 +89,17 @@ describe('mobile agent-status projection equivalence', () => { }) }) // Keys deliberately out of insertion order to pin the sort. - shapes.push({ + statusMaps.push({ 'tab-z:leaf-0': makeEntry(2), 'tab-a:leaf-0': makeEntry(1), 'tab-m:leaf-0': makeEntry(3) }) - for (const [index, shape] of shapes.entries()) { + for (const [index, statusMap] of statusMaps.entries()) { expect({ index, - projection: buildRuntimeMobileAgentStatusProjectionForTests(shape) - }).toEqual({ index, projection: referenceProjection(shape) }) + projection: buildRuntimeMobileAgentStatusProjectionForTests(statusMap) + }).toEqual({ index, projection: referenceProjection(statusMap) }) } // Now exercise the cache: replace one entry the way setAgentStatus does and diff --git a/src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts b/src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts index ed1991e60d2..1a163ada0a8 100644 --- a/src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts +++ b/src/renderer/src/runtime/sync-runtime-graph-projection-hot-path.test.ts @@ -256,7 +256,7 @@ describe('editor draft projection on the typing path', () => { }) it('matches the uncached projection byte for byte across draft shapes', () => { - const shapes: Record[] = [ + const draftCases: Record[] = [ {}, { 'file-a': '' }, { 'file-a': 'hello' }, @@ -267,10 +267,10 @@ describe('editor draft projection on the typing path', () => { { 'file-a': 'hello', 'file-b': 'world', 'file-c': 'third' }, { 'file-a': 'HELLO', 'file-c': 'third' } ] - for (const [index, shape] of shapes.entries()) { - expect({ index, projection: buildRuntimeMobileEditorDraftsProjection(shape) }).toEqual({ + for (const [index, draft] of draftCases.entries()) { + expect({ index, projection: buildRuntimeMobileEditorDraftsProjection(draft) }).toEqual({ index, - projection: referenceEditorDraftsProjection(shape) + projection: referenceEditorDraftsProjection(draft) }) } }) @@ -402,7 +402,7 @@ describe('open-files and browser projections', () => { }) it('matches the uncached projections byte for byte across shapes', () => { - const openFileShapes: AppState['openFiles'][] = [ + const openFileCases: AppState['openFiles'][] = [ [] as unknown as AppState['openFiles'], [makeOpenFile(0)] as unknown as AppState['openFiles'], [makeOpenFile(0, { isDirty: true })] as unknown as AppState['openFiles'], @@ -412,14 +412,14 @@ describe('open-files and browser projections', () => { makeOpenFile(2, { isUntitled: true, deleteUntouchedOnClose: true, language: undefined }) ] as unknown as AppState['openFiles'] ] - for (const [index, shape] of openFileShapes.entries()) { - expect({ index, projection: buildRuntimeMobileOpenFilesProjection(shape) }).toEqual({ + for (const [index, openFiles] of openFileCases.entries()) { + expect({ index, projection: buildRuntimeMobileOpenFilesProjection(openFiles) }).toEqual({ index, - projection: referenceOpenFilesProjection(shape) + projection: referenceOpenFilesProjection(openFiles) }) } - const browserShapes: AppState[] = [ + const browserCases: AppState[] = [ makeState({}), makeState({ browserTabsByWorktree: { 'wt-1': [makeBrowserWorkspace(0)] } as never }), makeState({ @@ -437,10 +437,10 @@ describe('open-files and browser projections', () => { browserPagesByWorkspace: { 'ws-9': [makeBrowserPage(9, { url: 'a"b\\c' })] } as never }) ] - for (const [index, shape] of browserShapes.entries()) { - expect({ index, projection: buildRuntimeMobileBrowserProjection(shape) }).toEqual({ + for (const [index, state] of browserCases.entries()) { + expect({ index, projection: buildRuntimeMobileBrowserProjection(state) }).toEqual({ index, - projection: referenceBrowserProjection(shape) + projection: referenceBrowserProjection(state) }) } }) diff --git a/src/renderer/src/store/slices/usage-provider-slices.ts b/src/renderer/src/store/slices/usage-provider-slices.ts index 16855b08a48..5c275ef7e86 100644 --- a/src/renderer/src/store/slices/usage-provider-slices.ts +++ b/src/renderer/src/store/slices/usage-provider-slices.ts @@ -30,13 +30,17 @@ type UsageSnapshot = { recentSessions: object[] } -type UsageShape = { +type UsageProviderTypes< + Scope extends string, + Range extends string, + Snapshot extends UsageSnapshot +> = { scope: Scope range: Range snapshot: Snapshot } -type UsageData> = { +type UsageData> = { scope: T['scope'] range: T['range'] scanState: T['snapshot']['scanState'] | null @@ -47,7 +51,7 @@ type UsageData> = { recentSessions: T['snapshot']['recentSessions'] } -type UsageApi> = { +type UsageApi> = { getScanState: () => Promise setEnabled: (args: { enabled: boolean }) => Promise refresh: (args?: { force?: boolean }) => Promise @@ -61,7 +65,7 @@ type UsageApi> = { type ProviderUsageSlice< Prefix extends string, Name extends string, - T extends UsageShape + T extends UsageProviderTypes > = { [K in keyof UsageData as `${Prefix}Usage${Capitalize}`]: UsageData[K] } & Record<`set${Name}UsageEnabled`, (enabled: boolean) => Promise> & @@ -74,7 +78,7 @@ type ProviderUsageSlice< type UsageProviderConfig< Prefix extends string, Name extends string, - T extends UsageShape + T extends UsageProviderTypes > = { prefix: Prefix name: Name @@ -93,13 +97,13 @@ const usageDataFields = [ 'modelBreakdown', 'projectBreakdown', 'recentSessions' -] as const satisfies readonly (keyof UsageData>)[] +] as const satisfies readonly (keyof UsageData>)[] function usageDataKey(prefix: string, field: string): string { return `${prefix}Usage${field[0].toUpperCase()}${field.slice(1)}` } -function readUsageData>( +function readUsageData>( state: AppState, prefix: string ): UsageData { @@ -109,7 +113,7 @@ function readUsageData>( ) as UsageData } -function createUsagePatch>( +function createUsagePatch>( prefix: string, patch: Partial> ): Partial { @@ -123,7 +127,7 @@ function createUsagePatch>( function createUsageProviderSlice< Prefix extends string, Name extends string, - T extends UsageShape + T extends UsageProviderTypes >( config: UsageProviderConfig ): StateCreator> { @@ -255,18 +259,22 @@ function createUsageProviderSlice< } } -type ClaudeUsageShape = UsageShape -type CodexUsageShape = UsageShape -type OpenCodeUsageShape = UsageShape +type ClaudeUsageTypes = UsageProviderTypes +type CodexUsageTypes = UsageProviderTypes +type OpenCodeUsageTypes = UsageProviderTypes< + OpenCodeUsageScope, + OpenCodeUsageRange, + OpenCodeUsageSnapshot +> -export type ClaudeUsageSlice = ProviderUsageSlice<'claude', 'Claude', ClaudeUsageShape> -export type CodexUsageSlice = ProviderUsageSlice<'codex', 'Codex', CodexUsageShape> -export type OpenCodeUsageSlice = ProviderUsageSlice<'openCode', 'OpenCode', OpenCodeUsageShape> +export type ClaudeUsageSlice = ProviderUsageSlice<'claude', 'Claude', ClaudeUsageTypes> +export type CodexUsageSlice = ProviderUsageSlice<'codex', 'Codex', CodexUsageTypes> +export type OpenCodeUsageSlice = ProviderUsageSlice<'openCode', 'OpenCode', OpenCodeUsageTypes> export const createClaudeUsageSlice = createUsageProviderSlice< 'claude', 'Claude', - ClaudeUsageShape + ClaudeUsageTypes >({ prefix: 'claude', name: 'Claude', @@ -276,7 +284,7 @@ export const createClaudeUsageSlice = createUsageProviderSlice< hasCachedData: (state) => state.hasAnyClaudeData }) -export const createCodexUsageSlice = createUsageProviderSlice<'codex', 'Codex', CodexUsageShape>({ +export const createCodexUsageSlice = createUsageProviderSlice<'codex', 'Codex', CodexUsageTypes>({ prefix: 'codex', name: 'Codex', initialScope: 'orca', @@ -288,7 +296,7 @@ export const createCodexUsageSlice = createUsageProviderSlice<'codex', 'Codex', export const createOpenCodeUsageSlice = createUsageProviderSlice< 'openCode', 'OpenCode', - OpenCodeUsageShape + OpenCodeUsageTypes >({ prefix: 'openCode', name: 'OpenCode', diff --git a/src/shared/agent-feature-install-commands.ts b/src/shared/agent-feature-install-commands.ts index 29d1f60b349..4813c17f807 100644 --- a/src/shared/agent-feature-install-commands.ts +++ b/src/shared/agent-feature-install-commands.ts @@ -1,4 +1,4 @@ -import { isSkillsCliAgentKeyShaped } from './skills-cli-agent-keys' +import { isUsableSkillsCliAgentKey } from './skills-cli-agent-keys' export const ORCA_SKILLS_REPOSITORY_URL = 'https://github.com/stablyai/orca' @@ -35,7 +35,7 @@ export function buildAgentFeatureSkillInstallArgs( } // Why: a value the skills CLI would drop leaves it with no target at all, which // is the same all-agents install as passing no --agent. - const unusable = agents.find((agent) => !isSkillsCliAgentKeyShaped(agent)) + const unusable = agents.find((agent) => !isUsableSkillsCliAgentKey(agent)) if (unusable !== undefined) { throw new Error(`"${unusable}" is not a usable install target.`) } diff --git a/src/shared/agent-resume-launch-command.test.ts b/src/shared/agent-resume-launch-command.test.ts index 5bf4797472b..544cd92cd20 100644 --- a/src/shared/agent-resume-launch-command.test.ts +++ b/src/shared/agent-resume-launch-command.test.ts @@ -17,7 +17,7 @@ const SHELLS: { platform: NodeJS.Platform; shell: AgentStartupShell }[] = [ /** Independent selector oracle — deliberately NOT the implementation's own * predicate, so a regression that shrinks the stripped set cannot also blind * this assertion. */ -function isSelectorShapedToken(token: string): boolean { +function isResumeSelectorToken(token: string): boolean { return ( ['--resume', '--continue', '-r', '-c'].includes(token) || ['--resume=', '--continue=', '-r=', '-c='].some((prefix) => token.startsWith(prefix)) @@ -31,7 +31,7 @@ function expectSingleAuthoritativeResume(command: string, shell: AgentStartupShe if (!tokenized.ok) { return } - const selectors = tokenized.tokens.filter(isSelectorShapedToken) + const selectors = tokenized.tokens.filter(isResumeSelectorToken) expect(selectors).toEqual(['--resume']) const index = tokenized.tokens.indexOf('--resume') expect(tokenized.tokens[index + 1]).toBe(SESSION_ID) diff --git a/src/shared/agent-session-journal-schemas.ts b/src/shared/agent-session-journal-schemas.ts index eaee6bb9a63..1c3ad47d3c6 100644 --- a/src/shared/agent-session-journal-schemas.ts +++ b/src/shared/agent-session-journal-schemas.ts @@ -242,7 +242,7 @@ export function isAdmissibleAgentJournalSubmission( * never reject a row a writer in this build produced. The schemas are * deliberately wider on open string fields, so only this direction holds. */ type Admits = T -export type CanonicalJournalShapesAreAdmissible = [ +export type CanonicalJournalTypesAreAdmissible = [ Admits ? true : false>, Admits ? true : false>, Admits< diff --git a/src/shared/agent-session-record.ts b/src/shared/agent-session-record.ts index a8add5c1128..2248647c3c2 100644 --- a/src/shared/agent-session-record.ts +++ b/src/shared/agent-session-record.ts @@ -337,7 +337,7 @@ export function isAgentSessionRecord(value: unknown): value is AgentSessionRecor return false } const record = value as Partial - const shapeValid = + const fieldsValid = record.schemaVersion === AGENT_SESSION_RECORD_SCHEMA_VERSION && isAgentSessionId(record.sessionId) && isAgentSessionExecutionLocation(record.location) && @@ -356,7 +356,7 @@ export function isAgentSessionRecord(value: unknown): value is AgentSessionRecor record.lease.sessionId === record.sessionId && Number.isSafeInteger(record.createdAt) && Number.isSafeInteger(record.updatedAt) - if (!shapeValid) { + if (!fieldsValid) { return false } const validated = record as AgentSessionRecord diff --git a/src/shared/git-push-target-validation.test.ts b/src/shared/git-push-target-validation.test.ts index 7b5dd096eb8..0215611ce52 100644 --- a/src/shared/git-push-target-validation.test.ts +++ b/src/shared/git-push-target-validation.test.ts @@ -1,19 +1,19 @@ import { describe, expect, it } from 'vitest' -import { assertGitPushTargetShape } from './git-push-target-validation' +import { assertValidGitPushTarget } from './git-push-target-validation' -describe('assertGitPushTargetShape', () => { +describe('assertValidGitPushTarget', () => { it('accepts slash-separated git remote names', () => { expect(() => - assertGitPushTargetShape({ remoteName: 'foo/bar', branchName: 'feature/fix' }) + assertValidGitPushTarget({ remoteName: 'foo/bar', branchName: 'feature/fix' }) ).not.toThrow() }) it('rejects remote names with empty or parent segments', () => { expect(() => - assertGitPushTargetShape({ remoteName: 'foo//bar', branchName: 'feature/fix' }) + assertValidGitPushTarget({ remoteName: 'foo//bar', branchName: 'feature/fix' }) ).toThrow('Invalid git remote name') expect(() => - assertGitPushTargetShape({ remoteName: 'foo/../bar', branchName: 'feature/fix' }) + assertValidGitPushTarget({ remoteName: 'foo/../bar', branchName: 'feature/fix' }) ).toThrow('Invalid git remote name') }) }) diff --git a/src/shared/git-push-target-validation.ts b/src/shared/git-push-target-validation.ts index 4f907d568e7..7fa666136c1 100644 --- a/src/shared/git-push-target-validation.ts +++ b/src/shared/git-push-target-validation.ts @@ -32,7 +32,7 @@ export function isSafePushTargetRemoteUrl(remoteUrl: string): boolean { return GITHUB_CLONE_URL.test(remoteUrl) || GITHUB_SSH_URL.test(remoteUrl) } -export function assertGitPushTargetShape(target: unknown): asserts target is GitPushTarget { +export function assertValidGitPushTarget(target: unknown): asserts target is GitPushTarget { if (typeof target !== 'object' || target === null) { throw new Error('Invalid PR push target.') } diff --git a/src/shared/native-chat-ask.ts b/src/shared/native-chat-ask.ts index 7096db88353..f8655a11d2f 100644 --- a/src/shared/native-chat-ask.ts +++ b/src/shared/native-chat-ask.ts @@ -19,7 +19,7 @@ export function registerQuestionTool(toolName: string, parser: InteractiveQuesti QUESTION_TOOL_PARSERS.set(toolName, parser) } -function parseQuestionsShape(input: unknown): AskPrompt | null { +function parseCanonicalQuestionsInput(input: unknown): AskPrompt | null { if (!input || typeof input !== 'object') { return null } @@ -73,12 +73,12 @@ function parseOptions(raw: unknown): AskOption[] { } for (const name of ['AskUserQuestion', 'ask_user_question', 'askUserQuestion']) { - QUESTION_TOOL_PARSERS.set(name, parseQuestionsShape) + QUESTION_TOOL_PARSERS.set(name, parseCanonicalQuestionsInput) } function parseToolInput(toolName: string | undefined, input: unknown): AskPrompt | null { const parser = toolName ? QUESTION_TOOL_PARSERS.get(toolName) : undefined - return (parser ? parser(input) : null) ?? parseQuestionsShape(input) + return (parser ? parser(input) : null) ?? parseCanonicalQuestionsInput(input) } export function parseAskFromStatus( diff --git a/src/shared/onboarding-state-types.ts b/src/shared/onboarding-state-types.ts index 669c70b94f3..20aefb45e26 100644 --- a/src/shared/onboarding-state-types.ts +++ b/src/shared/onboarding-state-types.ts @@ -9,6 +9,8 @@ export type OnboardingChecklistState = { ranFirstAgent: boolean ranSecondAgentOnSameTask: boolean triedCmdJ: boolean + // Persisted field, also a telemetry enum member in ./telemetry-onboarding-foundation-schemas; + // renaming it would orphan saved state. Rule exemption: config/oxlint-anti-slop.json. shapedSidebar: boolean reviewedDiff: boolean openedPr: boolean diff --git a/src/shared/pane-agent-identity-resolver.test.ts b/src/shared/pane-agent-identity-resolver.test.ts index 2f14a54976a..d11416a1d8f 100644 --- a/src/shared/pane-agent-identity-resolver.test.ts +++ b/src/shared/pane-agent-identity-resolver.test.ts @@ -52,8 +52,8 @@ describe('resolvePaneAgentIdentity', () => { }) describe('run generation separates the bug from the legitimate reclaim', () => { - // Both shapes are `completed hook = A, title = B`. Ordering alone cannot tell them apart. - const shape = (hookRun: number, titleRun: number): PaneAgentEvidence[] => [ + // Both cases are `completed hook = A, title = B`. Ordering alone cannot tell them apart. + const evidenceFor = (hookRun: number, titleRun: number): PaneAgentEvidence[] => [ { source: 'completed-hook', agent: 'claude', run: { authorityId: H, incarnation: hookRun } }, { source: 'title', agent: 'codex', run: { authorityId: H, incarnation: titleRun } } ] @@ -61,7 +61,7 @@ describe('resolvePaneAgentIdentity', () => { it('keeps the completed hook when both belong to the current run', () => { // The reported bug: nothing new started, so the hook is still the truth. const result = resolvePaneAgentIdentity({ - evidence: shape(7, 7), + evidence: evidenceFor(7, 7), currentRun: { authorityId: H, incarnation: 7 } }) expect(result).toMatchObject({ agent: 'claude', source: 'completed-hook' }) @@ -72,7 +72,7 @@ describe('resolvePaneAgentIdentity', () => { // The legitimate reclaim: the pane was reused, so run 7's hook describes an agent that is // no longer there. It is ineligible, not merely outranked. const result = resolvePaneAgentIdentity({ - evidence: shape(7, 8), + evidence: evidenceFor(7, 8), currentRun: { authorityId: H, incarnation: 8 } }) expect(result).toMatchObject({ agent: 'codex', source: 'title' }) @@ -82,11 +82,11 @@ describe('resolvePaneAgentIdentity', () => { it('produces opposite answers from identical evidence, given only the run ids', () => { // The whole point, stated as one assertion. const bug = resolvePaneAgentIdentity({ - evidence: shape(7, 7), + evidence: evidenceFor(7, 7), currentRun: { authorityId: H, incarnation: 7 } }) const reclaim = resolvePaneAgentIdentity({ - evidence: shape(7, 8), + evidence: evidenceFor(7, 8), currentRun: { authorityId: H, incarnation: 8 } }) expect(bug.agent).not.toBe(reclaim.agent) diff --git a/src/shared/plugins/plugin-language-pack-artifact.test.ts b/src/shared/plugins/plugin-language-pack-artifact.test.ts index a313cc33839..1c58189845c 100644 --- a/src/shared/plugins/plugin-language-pack-artifact.test.ts +++ b/src/shared/plugins/plugin-language-pack-artifact.test.ts @@ -5,7 +5,7 @@ import { PLUGIN_LANGUAGE_CATALOG_MAX_DEPTH, PLUGIN_LANGUAGE_CATALOG_MAX_ENTRIES, validatePluginLanguagePackCatalog, - validatePluginLanguagePackCatalogShape, + checkPluginLanguagePackCatalog, pluginLanguageResourceId } from './plugin-language-pack-artifact' @@ -205,7 +205,7 @@ describe('plugin language-pack artifacts', () => { } }) - expect(validatePluginLanguagePackCatalogShape(catalog)).toEqual({ + expect(checkPluginLanguagePackCatalog(catalog)).toEqual({ ok: true, entries: PLUGIN_LANGUAGE_CATALOG_MAX_ENTRIES }) diff --git a/src/shared/plugins/plugin-language-pack-artifact.ts b/src/shared/plugins/plugin-language-pack-artifact.ts index 7bcfacf47d4..b2daf8bc1c7 100644 --- a/src/shared/plugins/plugin-language-pack-artifact.ts +++ b/src/shared/plugins/plugin-language-pack-artifact.ts @@ -37,7 +37,7 @@ export function isPluginLanguagePackRegistration( pack.resourceLanguage === pluginLanguageResourceId(pack.id as `plugin:${string}`) && typeof pack.pluginKey === 'string' && typeof pack.locale === 'string' && - validatePluginLanguagePackCatalogShape(pack.catalog).ok + checkPluginLanguagePackCatalog(pack.catalog).ok ) } @@ -119,7 +119,7 @@ export function validatePluginLanguagePackCatalog(source: unknown): PluginLangua return { ok: true, catalog: result.catalog!, entries: result.entries } } -export function validatePluginLanguagePackCatalogShape( +export function checkPluginLanguagePackCatalog( source: unknown ): PluginLanguagePackValidationResult { const result = walkPluginLanguagePackCatalog(source, false) diff --git a/src/shared/remote-pairing-verification.ts b/src/shared/remote-pairing-verification.ts index 02d58b97bed..47475144b0a 100644 --- a/src/shared/remote-pairing-verification.ts +++ b/src/shared/remote-pairing-verification.ts @@ -31,7 +31,7 @@ function isNonNegativeSafeInteger(value: unknown): value is number { return Number.isSafeInteger(value) && Number(value) >= 0 } -function hasValidRuntimeStatusShape(status: Record): boolean { +function hasValidRuntimeStatus(status: Record): boolean { return ( typeof status.runtimeId === 'string' && status.runtimeId.length > 0 && @@ -103,7 +103,7 @@ export function verifyRemotePairingRuntimeStatus( : 'Update Orca on the remote host before adding it.' } } - if (!hasValidRuntimeStatusShape(status)) { + if (!hasValidRuntimeStatus(status)) { return { ok: false, kind: 'connection-interrupted', diff --git a/src/shared/rpc-contract/repo-update-params.ts b/src/shared/rpc-contract/repo-update-params.ts index 179bb1994dc..3eb1477adb2 100644 --- a/src/shared/rpc-contract/repo-update-params.ts +++ b/src/shared/rpc-contract/repo-update-params.ts @@ -34,12 +34,14 @@ export const RepoUpstream = z .nullable() .optional() -// The return type is inferred on purpose: an explicit z.ZodObject<...z.ZodRawShape> -// annotation widened `updates` to an open record, which erased all 24 named fields -// from RpcParams<'repo.update'> for every typed caller. -export function createRepoUpdateSchema(selectorShape: T) { +// The return type is inferred on purpose: an explicit z.ZodObject<...> annotation +// widened `updates` to an open record, which erased all 24 named fields from +// RpcParams<'repo.update'> for every typed caller. +export function createRepoUpdateSchema>>( + selectorFields: T +) { return z.object({ - ...selectorShape, + ...selectorFields, updates: z.object({ displayName: OptionalString, badgeColor: RepoBadgeColor, diff --git a/src/shared/rpc-contract/rpc-send-params.ts b/src/shared/rpc-contract/rpc-send-params.ts index fcb6d66a359..148f5aa6d6c 100644 --- a/src/shared/rpc-contract/rpc-send-params.ts +++ b/src/shared/rpc-contract/rpc-send-params.ts @@ -21,15 +21,15 @@ type Prettify = { [K in keyof T]: T[K] } & {} /** zod's own input-side key-optionality rule, copied from $InferObjectInput. */ type SendOptionalSchema = { _zod: { optin: 'optional' | 'defaulted' } } -type SendShape = Prettify< +type SendFields = Prettify< { - -readonly [K in keyof Shape as Shape[K] extends SendOptionalSchema ? never : K]: RpcSendInput< - Shape[K] + -readonly [K in keyof Fields as Fields[K] extends SendOptionalSchema ? never : K]: RpcSendInput< + Fields[K] > } & { - -readonly [K in keyof Shape as Shape[K] extends SendOptionalSchema ? K : never]?: RpcSendInput< - Shape[K] - > + -readonly [ + K in keyof Fields as Fields[K] extends SendOptionalSchema ? K : never + ]?: RpcSendInput } > @@ -50,11 +50,11 @@ export type RpcSendInput = ? RpcSendInput[] : // ZodObject is the only schema carrying a `shape`, and matching on it keeps // .strict()/.extend()/.superRefine() results in this branch. - Schema extends { shape: infer Shape } - ? keyof Shape extends never + Schema extends { shape: infer Fields } + ? keyof Fields extends never ? // Mirrors $InferObjectOutput: a no-field object admits no properties. Record - : SendShape + : SendFields : // ZodDiscriminatedUnion extends ZodUnion, so both land here. Schema extends z.ZodUnion ? RpcSendInput diff --git a/src/shared/rpc-contract/ui-update-value-tolerance-params.ts b/src/shared/rpc-contract/ui-update-value-tolerance-params.ts index 1b8ca0c2cd4..308c1183d02 100644 --- a/src/shared/rpc-contract/ui-update-value-tolerance-params.ts +++ b/src/shared/rpc-contract/ui-update-value-tolerance-params.ts @@ -7,13 +7,15 @@ import type { z } from 'zod' * dropped from the payload and the rest of the batch still lands. Unknown KEYS * stay a hard rejection — the parity assertions exist to catch those. */ -export function tolerateUnknownValues(shape: TShape): TShape { - return Object.fromEntries( - Object.entries(shape).map(([key, schema]) => [ - key, - (schema as z.ZodType).catch(() => undefined) - ]) - ) as unknown as TShape +export function tolerateUnknownValues>>( + fields: TFields +): TFields { + const tolerant: Record = {} + for (const [key, schema] of Object.entries(fields)) { + tolerant[key] = schema.catch(() => undefined) + } + // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: the loop copies every key of `fields` and only wraps its schema in `.catch()`, so the result carries exactly `TFields`' keys; Object.entries erases that key identity. + return tolerant as TFields } /** Drops the `undefined` entries `tolerateUnknownValues` leaves behind, so a diff --git a/src/shared/skills-cli-agent-keys.test.ts b/src/shared/skills-cli-agent-keys.test.ts index 4359d879c48..02bdd8b52df 100644 --- a/src/shared/skills-cli-agent-keys.test.ts +++ b/src/shared/skills-cli-agent-keys.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest' import { TUI_AGENT_CONFIG } from './tui-agent-config' import { SKILLS_CLI_AGENT_KEY_BY_TUI_AGENT, - isSkillsCliAgentKeyShaped, + isUsableSkillsCliAgentKey, SKILLS_CLI_UNIVERSAL_AGENT_KEY, toSkillsCliAgentKeys } from './skills-cli-agent-keys' @@ -117,10 +117,10 @@ describe('skills CLI agent keys', () => { it('rejects values the skills CLI would drop, and allows the explicit wildcard', () => { for (const bad of ['-y', '--copy', '', ' ', 'a b', 'a,b']) { - expect(isSkillsCliAgentKeyShaped(bad), bad).toBe(false) + expect(isUsableSkillsCliAgentKey(bad), bad).toBe(false) } for (const good of ['claude-code', 'universal', 'trae-cn', 'inference-sh', '*']) { - expect(isSkillsCliAgentKeyShaped(good), good).toBe(true) + expect(isUsableSkillsCliAgentKey(good), good).toBe(true) } }) diff --git a/src/shared/skills-cli-agent-keys.ts b/src/shared/skills-cli-agent-keys.ts index 3126c0a81e5..8f77675e8c0 100644 --- a/src/shared/skills-cli-agent-keys.ts +++ b/src/shared/skills-cli-agent-keys.ts @@ -68,7 +68,7 @@ export const SKILLS_CLI_UNIVERSAL_AGENT_KEY = 'universal' * emptiness. An unknown-but-plausible key is left to the CLI, which rejects it * loudly with its own valid list before writing anything. */ -export function isSkillsCliAgentKeyShaped(value: string): boolean { +export function isUsableSkillsCliAgentKey(value: string): boolean { return /^(?:\*|[a-z0-9][a-z0-9.-]*)$/i.test(value) } diff --git a/src/shared/telemetry-event-classification.ts b/src/shared/telemetry-event-classification.ts index 23b471fd4f5..cc932fe9411 100644 --- a/src/shared/telemetry-event-classification.ts +++ b/src/shared/telemetry-event-classification.ts @@ -8,32 +8,32 @@ export type EventName = keyof EventMap export type EventProps = EventMap[N] // Why: non-`ZodObject` schemas have no `.shape`; return null so `key in undefined` can't throw at module load. -function eventSchemaShape(schema: z.ZodTypeAny): z.ZodRawShape | null { +// Why `object` and not zod's own field-record type: callers only ask `key in fields`. +function eventSchemaFields(schema: z.ZodTypeAny): object | null { if (schema instanceof z.ZodObject) { return schema.shape } - const shapeBearingSchema = schema as { shape?: unknown } // Why: refined object schemas may expose `.shape` even when refinement breaks `instanceof ZodObject`. - if (shapeBearingSchema.shape && typeof shapeBearingSchema.shape === 'object') { - return shapeBearingSchema.shape as z.ZodRawShape + if ('shape' in schema && typeof schema.shape === 'object' && schema.shape !== null) { + return schema.shape } return null } -function eventsWithShapeKey(key: string): ReadonlySet { +function eventsDeclaringKey(key: string): ReadonlySet { return new Set( (Object.entries(eventSchemas) as [EventName, z.ZodTypeAny][]) .filter(([, schema]) => { - const shape = eventSchemaShape(schema) - return shape !== null && key in shape + const fields = eventSchemaFields(schema) + return fields !== null && key in fields }) .map(([name]) => name) ) } // Cohort injection is gated on this derived set because `.strict()` schemas drop events that don't declare `nth_repo_added`. -const COHORT_EXTENDED_SET = eventsWithShapeKey('nth_repo_added') +const COHORT_EXTENDED_SET = eventsDeclaringKey('nth_repo_added') // Compile-time roster guarding the runtime injection set against silent schema drift. type _CohortExtendedRoster = @@ -78,7 +78,7 @@ export function isCohortExtendedEvent(name: EventName): boolean { } // Events whose schema declares `cohort`: the IPC handler injects cohort only for these — a `.strict()` schema without it would reject the event. -const ONBOARDING_COHORT_SET = eventsWithShapeKey('cohort') +const ONBOARDING_COHORT_SET = eventsDeclaringKey('cohort') // `NonNullable` strips `undefined` introduced by `cohortSchema`'s `.optional()`. export type OnboardingCohort = NonNullable> diff --git a/src/shared/zod-salvage-absence.test.ts b/src/shared/zod-salvage-absence.test.ts index 065c6118ec1..5b6ca28f94f 100644 --- a/src/shared/zod-salvage-absence.test.ts +++ b/src/shared/zod-salvage-absence.test.ts @@ -20,7 +20,7 @@ const CONTAINERS: [string, () => z.ZodType, unknown][] = [ ['salvagingArray', () => salvagingArray(z.string()), ['v']] ] -describe('salvaging containers used bare in an object shape', () => { +describe('salvaging containers used bare in an object schema', () => { it.each(CONTAINERS)('%s is neither optional-in nor optional-out', (_name, build) => { const { optin, optout } = optionalityOf(build()) expect(optin).toBeUndefined() @@ -28,12 +28,12 @@ describe('salvaging containers used bare in an object shape', () => { }) it.each(CONTAINERS)('%s rejects an absent key and an explicit undefined', (_name, build, ok) => { - const shape = z.object({ a: build() }) + const schema = z.object({ a: build() }) - expect(shape.safeParse({}).success).toBe(false) - expect(shape.safeParse({ a: undefined }).success).toBe(false) + expect(schema.safeParse({}).success).toBe(false) + expect(schema.safeParse({ a: undefined }).success).toBe(false) // Why: a positive control, so the two rejections above cannot pass by rejecting everything. - expect(shape.safeParse({ a: ok })).toMatchObject({ success: true }) + expect(schema.safeParse({ a: ok })).toMatchObject({ success: true }) }) it.each(CONTAINERS)( diff --git a/tests/e2e/helpers/host-created-terminal-retention-oracle.ts b/tests/e2e/helpers/host-created-terminal-retention-oracle.ts index b3c4d8c2d21..9a1af6a957b 100644 --- a/tests/e2e/helpers/host-created-terminal-retention-oracle.ts +++ b/tests/e2e/helpers/host-created-terminal-retention-oracle.ts @@ -48,7 +48,7 @@ const HOST_TERMINAL_SURFACE_SEPARATOR = '::' /** Daemon session id form. Deliberately excluded from id-shape classification, * which is why a host-created tab needs its own binding to be preserved — * a `serve-`/`ssh-` shaped id would take an already-correct path instead. */ -function isDaemonShapedPtyId(ptyId: string, worktreeId: string): boolean { +function isDaemonPtyIdForm(ptyId: string, worktreeId: string): boolean { return ( ptyId.startsWith(`${worktreeId}@@`) && !ptyId.startsWith('serve-') && @@ -156,7 +156,7 @@ export async function createHostCliTerminal( throw new Error('Host did not report a leaf id for the CLI-created terminal') } expect( - isDaemonShapedPtyId(ptyId, worktreeId), + isDaemonPtyIdForm(ptyId, worktreeId), `CLI terminal ${ptyId} must carry the daemon id shape this seam excludes from classification` ).toBe(true) await expect diff --git a/tests/e2e/terminal-cjk-ime-committed-text.spec.ts b/tests/e2e/terminal-cjk-ime-committed-text.spec.ts index fb98b2d1975..056929de8bb 100644 --- a/tests/e2e/terminal-cjk-ime-committed-text.spec.ts +++ b/tests/e2e/terminal-cjk-ime-committed-text.spec.ts @@ -73,7 +73,7 @@ const SUBSTITUTION_GROUPS = [ * The two ways a substituted keystroke can reach the renderer. Both are real; only the second one * regressed, and only the second one can regress, which is why running both is the point. */ -const SUBSTITUTION_SHAPES: readonly { +const SUBSTITUTION_ROUTES: readonly { name: string slug: string dispatch: (session: CDPSession, keystroke: SubstitutedKeystroke) => Promise @@ -165,9 +165,9 @@ test.describe('Terminal CJK IME committed text', () => { } }) - for (const shape of SUBSTITUTION_SHAPES) { + for (const route of SUBSTITUTION_ROUTES) { for (const group of SUBSTITUTION_GROUPS) { - test(`sends full-width ${group.label} and never their ASCII form when ${shape.name}`, async ({ + test(`sends full-width ${group.label} and never their ASCII form when ${route.name}`, async ({ orcaPage, testRepoPath }, testInfo) => { @@ -179,7 +179,7 @@ test.describe('Terminal CJK IME committed text', () => { try { await startTerminalImeByteReader(orcaPage, arena.ptyId, reader) for (const keystroke of group.keystrokes) { - await shape.dispatch(arena.session, keystroke) + await route.dispatch(arena.session, keystroke) await orcaPage.waitForTimeout(60) } await dispatchPlainEnter(arena.session) @@ -200,7 +200,7 @@ test.describe('Terminal CJK IME committed text', () => { await closeTerminalImePaneArena( arena, testInfo, - `full-width-${group.label}-${shape.slug}`, + `full-width-${group.label}-${route.slug}`, !completed ) removeTerminalImeByteReader(reader) diff --git a/tests/tools/win-crash-survival-e2e/cli-args.mjs b/tests/tools/win-crash-survival-e2e/cli-args.mjs index e3533342e4d..1276d5e2eb4 100644 --- a/tests/tools/win-crash-survival-e2e/cli-args.mjs +++ b/tests/tools/win-crash-survival-e2e/cli-args.mjs @@ -69,7 +69,7 @@ export function parseArgs(argv) { function validate(opts, exePathFlagPresent, argv) { const errors = [] - errors.push(...validateArgShape(argv)) + errors.push(...validateArgSyntax(argv)) if (!opts.expect) { errors.push('Missing --expect ') } else if (!VALID_PROFILES.has(opts.expect)) { @@ -94,7 +94,7 @@ function validate(opts, exePathFlagPresent, argv) { return errors } -function validateArgShape(argv) { +function validateArgSyntax(argv) { const errors = [] const seen = new Set() for (let index = 0; index < argv.length; index++) {