mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 00:02:56 +00:00
Generate pinned settings slice from PR 19675 and main callers
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,38 @@
|
||||
import { mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs'
|
||||
import { dirname, join, relative, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { isDeepStrictEqual } from 'node:util'
|
||||
import { runProcess } from '../../src/shared/child-process/run-process.ts'
|
||||
export const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
|
||||
export function files(directory: string): string[] {
|
||||
return readdirSync(directory, { withFileTypes: true })
|
||||
.flatMap((entry) => {
|
||||
const path = join(directory, entry.name)
|
||||
return entry.isDirectory() ? files(path) : [relative(root, path).split('\\').join('/')]
|
||||
})
|
||||
.sort()
|
||||
}
|
||||
export function option(name: string, fallback?: string): string {
|
||||
const index = process.argv.indexOf(`--${name}`)
|
||||
const value = index < 0 ? fallback : process.argv[index + 1]
|
||||
if (!value || value.startsWith('--')) throw new Error(`Missing --${name}`)
|
||||
return value
|
||||
}
|
||||
export async function git(...args: string[]): Promise<string> {
|
||||
const result = await runProcess({ program: 'git', args, cwd: root, maxOutputBytes: 8_000_000 })
|
||||
if (result.code !== 0 || result.outputTruncated)
|
||||
throw new Error(`git ${args.join(' ')}: ${result.stderr}`)
|
||||
return result.stdout.trimEnd()
|
||||
}
|
||||
export function emit(name: string, value: unknown): void {
|
||||
const path = resolve(root, option('output', `mobile/rpc-foundation/${name}.json`))
|
||||
if (process.argv.includes('--check')) {
|
||||
if (!isDeepStrictEqual(JSON.parse(readFileSync(path, 'utf8')), value))
|
||||
throw new Error(`Stale artifact: ${path}`)
|
||||
console.log(`${name}: current`)
|
||||
} else {
|
||||
mkdirSync(dirname(path), { recursive: true })
|
||||
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`)
|
||||
console.log(`${name}: ${path}`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { dirname, join, posix } from 'node:path'
|
||||
import { createHash } from 'node:crypto'
|
||||
import ts from 'typescript'
|
||||
import { emit, files, git, option, root } from './rpc-artifact-io.mts'
|
||||
|
||||
const prHead = await git('rev-parse', `${option('pr-head')}^{commit}`)
|
||||
const baseline = await git('rev-parse', 'e80fae0c4d^{commit}')
|
||||
const diff = await git('diff', '--name-only', baseline, prHead, '--', 'mobile')
|
||||
const changed = diff.split('\n').filter((file) => /\.[jt]sx?$/.test(file) && !/\.test\./.test(file))
|
||||
const hash = (text: string): string => createHash('sha256').update(text).digest('hex')
|
||||
const rows: unknown[] = []
|
||||
const sources: { file: string; revision: string; sha256: string }[] = []
|
||||
function inventory(file: string, source: string, revision: string, settingsOnly: boolean): void {
|
||||
const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true)
|
||||
let included = false
|
||||
const visit = (node: ts.Node): void => {
|
||||
if (ts.isCallExpression(node)) {
|
||||
const literal = node.arguments.find(
|
||||
(arg) => ts.isStringLiteral(arg) && /^(settings\.|dictation\.)/.test(arg.text)
|
||||
)
|
||||
const callee = node.expression.getText(sf)
|
||||
const preference =
|
||||
/(?:load|save|set|use)\w*(?:Preference|LinkOpenMode|NotificationsEnabled|DefaultSessionView)/.test(
|
||||
callee
|
||||
)
|
||||
if (literal || (!settingsOnly && preference)) {
|
||||
let owner: ts.Node = node
|
||||
while (
|
||||
owner.parent &&
|
||||
!ts.isFunctionDeclaration(owner) &&
|
||||
!ts.isArrowFunction(owner) &&
|
||||
!ts.isMethodDeclaration(owner)
|
||||
)
|
||||
owner = owner.parent
|
||||
let named = owner
|
||||
while (named.parent && !('name' in named && named.name)) named = named.parent
|
||||
const symbol =
|
||||
'name' in named && named.name ? (named.name as ts.Node).getText(sf) : 'module'
|
||||
const line = sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1
|
||||
const method = literal && ts.isStringLiteral(literal) ? literal.text : callee
|
||||
const fields = new Set<string>(),
|
||||
writes = new Set<string>(),
|
||||
failures = new Set<string>()
|
||||
const evidence = (part: ts.Node): void => {
|
||||
if (ts.isPropertyAccessExpression(part)) fields.add(part.getText(sf))
|
||||
if (
|
||||
ts.isCallExpression(part) &&
|
||||
/^(?:set|save|persist|dispatch|router\.)/.test(part.expression.getText(sf))
|
||||
)
|
||||
writes.add(part.getText(sf))
|
||||
if (ts.isThrowStatement(part) || ts.isCatchClause(part)) failures.add(part.getText(sf))
|
||||
if (
|
||||
ts.isBinaryExpression(part) &&
|
||||
[ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken].includes(
|
||||
part.operatorToken.kind
|
||||
)
|
||||
)
|
||||
failures.add(part.getText(sf))
|
||||
ts.forEachChild(part, evidence)
|
||||
}
|
||||
evidence(owner)
|
||||
const id = `${revision === baseline ? 'main' : 'pr19675'}:${file}:${symbol}:${method}:${line}`
|
||||
rows.push({
|
||||
id,
|
||||
file,
|
||||
symbol,
|
||||
line,
|
||||
revision,
|
||||
method,
|
||||
kind: literal ? 'host-rpc' : 'device-preference',
|
||||
options: literal
|
||||
? node.arguments
|
||||
.slice(node.arguments.indexOf(literal) + 2)
|
||||
.map((arg) => arg.getText(sf))
|
||||
: [],
|
||||
arguments: node.arguments.map((arg) => arg.getText(sf)),
|
||||
consumedFields: [...fields].sort(),
|
||||
writes: [...writes].sort(),
|
||||
failures: [...failures].sort(),
|
||||
evidenceScope:
|
||||
'enclosing function; conservative source expressions, not a validated reader contract',
|
||||
scenarioIds: [`${id}:fulfilled`, `${id}:refused`, `${id}:transport-error`],
|
||||
scenarioStatus: 'planned-step1'
|
||||
})
|
||||
included = true
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit)
|
||||
}
|
||||
visit(sf)
|
||||
if (included) sources.push({ file, revision, sha256: hash(source) })
|
||||
}
|
||||
for (const file of [...files(join(root, 'mobile/src')), ...files(join(root, 'mobile/app'))].filter(
|
||||
(file) => /\.[jt]sx?$/.test(file) && !/\.test\.|\.generated\./.test(file)
|
||||
)) {
|
||||
const source = readFileSync(join(root, file), 'utf8')
|
||||
if (/['"]settings\.(get|set)['"]/.test(source)) inventory(file, source, baseline, true)
|
||||
}
|
||||
const pending = changed.filter(
|
||||
(file) =>
|
||||
file.startsWith('mobile/src/settings/') ||
|
||||
/^mobile\/app\/(?:.*settings|notifications)\.tsx$/.test(file)
|
||||
)
|
||||
const visited = new Set<string>()
|
||||
while (pending.length) {
|
||||
const file = pending.shift()!
|
||||
if (visited.has(file)) continue
|
||||
visited.add(file)
|
||||
const source = await git('show', `${prHead}:${file}`)
|
||||
inventory(file, source, prHead, false)
|
||||
const sf = ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true)
|
||||
for (const node of sf.statements) {
|
||||
if (!ts.isImportDeclaration(node) || !ts.isStringLiteral(node.moduleSpecifier)) continue
|
||||
const specifier = node.moduleSpecifier.text
|
||||
if (
|
||||
!specifier.startsWith('.') ||
|
||||
!/(settings|dictation|preferences|default-session-view)/.test(specifier)
|
||||
)
|
||||
continue
|
||||
const base = posix.normalize(posix.join(dirname(file), specifier))
|
||||
for (const extension of ['.ts', '.tsx']) {
|
||||
try {
|
||||
await git('cat-file', '-e', `${prHead}:${base}${extension}`)
|
||||
pending.push(`${base}${extension}`)
|
||||
break
|
||||
} catch {
|
||||
/* Import resolution tries both source extensions. */
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!rows.length) throw new Error('Empty settings census')
|
||||
emit('settings-19675', {
|
||||
schemaVersion: 1,
|
||||
prHead,
|
||||
baseline,
|
||||
diffSha256: hash(diff),
|
||||
changedFiles: changed,
|
||||
files: sources,
|
||||
operations: rows
|
||||
})
|
||||
Reference in New Issue
Block a user