mirror of
https://github.com/stablyai/orca.git
synced 2026-10-02 00:02:05 +00:00
#14397 split `shared/types.ts` into 46 per-domain modules but kept the path as a re-export barrel so the import sites did not have to change. This removes the barrel: every consumer now imports from the module that actually declares the type, and `src/shared/types.ts` is deleted. Barrels hide where a type lives, make every consumer look like it depends on the whole domain, and let an unrelated edit invalidate a module that ~2,000 files transitively import. 2,323 import declarations across 2,321 files. Rewritten mechanically: each specifier was resolved to an absolute path via the TypeScript AST and recomputed, rather than string-substituted, so alias forms (`@/../../shared/ types`) and per-specifier `type` modifiers survive. Four cases the mechanical pass had to handle, each found by a gate rather than by reading the diff: - Modules inside `src/shared` import the barrel as `./types`, not `shared/types`. A pre-filter on the latter string skipped 176 of them and left imports dangling at a deleted file, which surfaced as confusing `Property 'x' is optional in type 'Repo' but required in Pick<Repo, ...>` errors rather than "module not found". - The barrel RENAMED one type on the way through (`WorkspaceSource as WorkspaceCreateTelemetrySource`), so the original name in the owning module has to be re-aliased at each consumer. - Three test files put `;(globalThis as ...)` on the line after the import. TypeScript parses that `;` as the import statement's terminator, so replacing through `statement.getEnd()` deletes it and breaks ASI. The rewrite now stops at the module specifier. - A file that already imported directly from a module got a SECOND import from it, because the barrel re-exported those same names — which trips `import/no-duplicates` under `--deny-warnings`. A post-pass merges declarations sharing a specifier and type-only-ness; the `import type` plus `import` pair from one module is left alone, since that form is allowed. Splitting one barrel import into several genuinely adds lines, which pushed `terminal-layout-pty-ownership.ts` to 301 counted lines: its 107-character import must wrap, and neither local type collapses onto one line (101 and 116 characters). Rather than contort a type declaration to fit a line budget, `collectLeafIds` and `pruneLeaves` move to `terminal-pane-layout-tree.ts` — they are pure structural operations on the layout tree and independent of PTY ownership. `visible-worktrees.ts` similarly loses its own mini-barrel re-export of `isDefaultBranchWorkspace`, with the four real consumers repointed at the declaring module. No `max-lines` bypass added. Verified: cold `tsc --noEmit` green on node, cli, and web (buildinfo deleted first — these projects are `composite: true` and reuse stale caches); the full `pnpm lint` green, not just bare oxlint — the narrower local check is what let the duplicate imports reach CI; max-lines ratchet OK at 344.
206 lines
6.5 KiB
TypeScript
206 lines
6.5 KiB
TypeScript
import { isCustomAgentId, type CustomAgentId } from './commit-message-agent-spec'
|
|
import { isTuiAgent } from './tui-agent-config'
|
|
import type { TuiAgent } from './tui-agent'
|
|
|
|
// Why: the variable registry lives in `./source-control-ai-action-variables` for max-lines
|
|
// headroom. It is deliberately not re-exported here — one import path per symbol keeps a
|
|
// grep of that module's consumers complete (the chip row is the one that must not diverge).
|
|
|
|
export type SourceControlTextActionId = 'commitMessage' | 'pullRequest' | 'branchName'
|
|
|
|
export type SourceControlLaunchActionId =
|
|
| 'fixCommitFailure'
|
|
| 'fixPushFailure'
|
|
| 'fixChecks'
|
|
| 'resolveConflicts'
|
|
| 'resolveComments'
|
|
|
|
export type SourceControlActionId = SourceControlTextActionId | SourceControlLaunchActionId
|
|
|
|
export type SourceControlActionRecipe = {
|
|
agentId?: TuiAgent | CustomAgentId | null
|
|
commandInputTemplate?: string
|
|
agentArgs?: string
|
|
}
|
|
|
|
export type SourceControlAiActionDefaults = Partial<
|
|
Record<SourceControlActionId, SourceControlActionRecipe>
|
|
>
|
|
|
|
export const SOURCE_CONTROL_TEXT_ACTION_IDS = [
|
|
'commitMessage',
|
|
'pullRequest',
|
|
'branchName'
|
|
] as const satisfies readonly SourceControlTextActionId[]
|
|
|
|
export const SOURCE_CONTROL_LAUNCH_ACTION_IDS = [
|
|
'fixCommitFailure',
|
|
'fixPushFailure',
|
|
'fixChecks',
|
|
'resolveConflicts',
|
|
'resolveComments'
|
|
] as const satisfies readonly SourceControlLaunchActionId[]
|
|
|
|
export const SOURCE_CONTROL_ACTION_IDS = [
|
|
...SOURCE_CONTROL_TEXT_ACTION_IDS,
|
|
...SOURCE_CONTROL_LAUNCH_ACTION_IDS
|
|
] as const satisfies readonly SourceControlActionId[]
|
|
|
|
export const SOURCE_CONTROL_TEXT_ACTION_LABELS: Record<SourceControlTextActionId, string> = {
|
|
commitMessage: 'Commit message',
|
|
pullRequest: 'Pull request details',
|
|
branchName: 'Branch name'
|
|
}
|
|
|
|
export const SOURCE_CONTROL_LAUNCH_ACTION_LABELS: Record<SourceControlLaunchActionId, string> = {
|
|
fixCommitFailure: 'Commit failure fixes',
|
|
fixPushFailure: 'Push failure fixes',
|
|
fixChecks: 'Broken checks fixes',
|
|
resolveConflicts: 'Conflict resolution',
|
|
resolveComments: 'Review comment resolution'
|
|
}
|
|
|
|
export const SOURCE_CONTROL_ACTION_LABELS: Record<SourceControlActionId, string> = {
|
|
...SOURCE_CONTROL_TEXT_ACTION_LABELS,
|
|
...SOURCE_CONTROL_LAUNCH_ACTION_LABELS
|
|
}
|
|
|
|
export const DEFAULT_SOURCE_CONTROL_ACTION_COMMAND_TEMPLATES: Record<
|
|
SourceControlActionId,
|
|
string
|
|
> = {
|
|
commitMessage: '{basePrompt}',
|
|
pullRequest: '{basePrompt}',
|
|
branchName: '{basePrompt}',
|
|
fixCommitFailure: '{basePrompt}',
|
|
fixPushFailure: '{basePrompt}',
|
|
fixChecks: '{basePrompt}',
|
|
resolveConflicts: '{basePrompt}',
|
|
resolveComments: '{basePrompt}'
|
|
}
|
|
|
|
const ACTION_ID_SET = new Set<string>(SOURCE_CONTROL_ACTION_IDS)
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
}
|
|
|
|
function isSafeRecordKey(key: string): boolean {
|
|
return key !== '' && key !== '__proto__' && key !== 'constructor' && key !== 'prototype'
|
|
}
|
|
|
|
function isSourceControlActionId(value: string): value is SourceControlActionId {
|
|
return ACTION_ID_SET.has(value)
|
|
}
|
|
|
|
export function normalizeSourceControlActionRecipe(
|
|
value: unknown
|
|
): SourceControlActionRecipe | undefined {
|
|
if (!isRecord(value)) {
|
|
return undefined
|
|
}
|
|
|
|
const normalized: SourceControlActionRecipe = {}
|
|
const agentId = value.agentId
|
|
if (
|
|
agentId === null ||
|
|
isTuiAgent(agentId) ||
|
|
(typeof agentId === 'string' && isCustomAgentId(agentId))
|
|
) {
|
|
normalized.agentId = agentId
|
|
}
|
|
if (typeof value.commandInputTemplate === 'string') {
|
|
normalized.commandInputTemplate = value.commandInputTemplate
|
|
}
|
|
if (typeof value.agentArgs === 'string') {
|
|
normalized.agentArgs = value.agentArgs
|
|
}
|
|
return Object.keys(normalized).length > 0 ? normalized : undefined
|
|
}
|
|
|
|
export function normalizeSourceControlAiActionDefaults(
|
|
value: unknown
|
|
): SourceControlAiActionDefaults | undefined {
|
|
if (!isRecord(value)) {
|
|
return undefined
|
|
}
|
|
|
|
const normalized: SourceControlAiActionDefaults = {}
|
|
for (const [key, item] of Object.entries(value)) {
|
|
if (!isSafeRecordKey(key) || !isSourceControlActionId(key)) {
|
|
continue
|
|
}
|
|
const defaultValue = normalizeSourceControlActionRecipe(item)
|
|
if (defaultValue) {
|
|
normalized[key] = defaultValue
|
|
}
|
|
}
|
|
return Object.keys(normalized).length > 0 ? normalized : undefined
|
|
}
|
|
|
|
export function readSourceControlActionDefault(
|
|
defaults: SourceControlAiActionDefaults | null | undefined,
|
|
actionId: SourceControlActionId
|
|
): SourceControlActionRecipe {
|
|
const value = defaults?.[actionId]
|
|
return {
|
|
...(value?.agentId !== undefined ? { agentId: value.agentId } : {}),
|
|
...(typeof value?.commandInputTemplate === 'string'
|
|
? { commandInputTemplate: value.commandInputTemplate.trim() }
|
|
: {}),
|
|
...(typeof value?.agentArgs === 'string' ? { agentArgs: value.agentArgs.trim() } : {})
|
|
}
|
|
}
|
|
|
|
export function resolveSourceControlActionCommandTemplate(
|
|
defaults: SourceControlAiActionDefaults | null | undefined,
|
|
actionId: SourceControlActionId
|
|
): string {
|
|
const template = readSourceControlActionDefault(defaults, actionId).commandInputTemplate
|
|
return template !== undefined
|
|
? template
|
|
: DEFAULT_SOURCE_CONTROL_ACTION_COMMAND_TEMPLATES[actionId]
|
|
}
|
|
|
|
export function setSourceControlActionDefault(
|
|
defaults: SourceControlAiActionDefaults | null | undefined,
|
|
actionId: SourceControlActionId,
|
|
value: SourceControlActionRecipe
|
|
): SourceControlAiActionDefaults {
|
|
return {
|
|
...defaults,
|
|
[actionId]: {
|
|
...defaults?.[actionId],
|
|
...value
|
|
}
|
|
}
|
|
}
|
|
|
|
export function setSourceControlActionAgentDefault(
|
|
defaults: SourceControlAiActionDefaults | null | undefined,
|
|
actionId: SourceControlActionId,
|
|
agentId: TuiAgent | CustomAgentId | null
|
|
): SourceControlAiActionDefaults {
|
|
return setSourceControlActionDefault(defaults, actionId, { agentId })
|
|
}
|
|
|
|
export function renderSourceControlActionCommandTemplate(
|
|
template: string,
|
|
variables: Record<string, string | null | undefined>
|
|
): string {
|
|
return template.replace(
|
|
/\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}|\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}/g,
|
|
(match, doubleName, singleName) => {
|
|
const name = (doubleName ?? singleName) as string
|
|
// Why: placeholder names may start with letters or underscores.
|
|
// Why: only own keys are real variables; inherited Object.prototype names
|
|
// (e.g. `constructor`) must stay visible instead of rendering their value.
|
|
if (!Object.hasOwn(variables, name)) {
|
|
return match
|
|
}
|
|
const value = variables[name]
|
|
return value === undefined || value === null ? match : value
|
|
}
|
|
)
|
|
}
|