mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +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.
171 lines
5.6 KiB
TypeScript
171 lines
5.6 KiB
TypeScript
import type { MarkdownDocument } from './filesystem-entry-types'
|
|
import { measureUtf8ByteLength } from './utf8-byte-limits'
|
|
|
|
export const MARKDOWN_DOCUMENT_LISTING_MAX_DOCUMENTS = 20_000
|
|
export const MARKDOWN_DOCUMENT_LISTING_MAX_METADATA_BYTES = 8 * 1024 * 1024
|
|
export const MARKDOWN_DOCUMENT_LISTING_MAX_PATH_BYTES = 64 * 1024
|
|
export const MARKDOWN_DOCUMENT_LISTING_MAX_VISITED_ENTRIES = 100_000
|
|
export const MARKDOWN_DOCUMENT_LISTING_MAX_DEPTH = 256
|
|
export const MARKDOWN_DOCUMENT_LISTING_ERROR_CODE = 'markdown_document_listing_capacity'
|
|
export const MARKDOWN_DOCUMENT_LISTING_ERROR_MESSAGE =
|
|
'Workspace is too large for Markdown link completion.'
|
|
|
|
const MARKDOWN_DOCUMENT_RETAINED_OVERHEAD_BYTES = 256
|
|
|
|
export type MarkdownDocumentListingLimits = {
|
|
maxDocuments: number
|
|
maxMetadataBytes: number
|
|
maxPathBytes: number
|
|
maxVisitedEntries: number
|
|
maxDepth: number
|
|
}
|
|
|
|
export type MarkdownDocumentListingBudget = {
|
|
documents: number
|
|
metadataBytes: number
|
|
visitedEntries: number
|
|
limits: MarkdownDocumentListingLimits
|
|
}
|
|
|
|
export class MarkdownDocumentListingCapacityError extends Error {
|
|
readonly code = MARKDOWN_DOCUMENT_LISTING_ERROR_CODE
|
|
|
|
constructor() {
|
|
super(MARKDOWN_DOCUMENT_LISTING_ERROR_MESSAGE)
|
|
this.name = 'MarkdownDocumentListingCapacityError'
|
|
}
|
|
}
|
|
|
|
export function isMarkdownDocumentListingCapacityError(error: unknown): boolean {
|
|
if (!(error instanceof Error)) {
|
|
return false
|
|
}
|
|
return (
|
|
('code' in error &&
|
|
(error as { code?: unknown }).code === MARKDOWN_DOCUMENT_LISTING_ERROR_CODE) ||
|
|
error.message.includes(MARKDOWN_DOCUMENT_LISTING_ERROR_MESSAGE)
|
|
)
|
|
}
|
|
|
|
export function createMarkdownDocumentListingBudget(
|
|
requested: Partial<MarkdownDocumentListingLimits> = {}
|
|
): MarkdownDocumentListingBudget {
|
|
return {
|
|
documents: 0,
|
|
metadataBytes: 0,
|
|
visitedEntries: 0,
|
|
limits: {
|
|
maxDocuments: clampLimit(requested.maxDocuments, MARKDOWN_DOCUMENT_LISTING_MAX_DOCUMENTS),
|
|
maxMetadataBytes: clampLimit(
|
|
requested.maxMetadataBytes,
|
|
MARKDOWN_DOCUMENT_LISTING_MAX_METADATA_BYTES
|
|
),
|
|
maxPathBytes: clampLimit(requested.maxPathBytes, MARKDOWN_DOCUMENT_LISTING_MAX_PATH_BYTES),
|
|
maxVisitedEntries: clampLimit(
|
|
requested.maxVisitedEntries,
|
|
MARKDOWN_DOCUMENT_LISTING_MAX_VISITED_ENTRIES
|
|
),
|
|
maxDepth: clampLimit(requested.maxDepth, MARKDOWN_DOCUMENT_LISTING_MAX_DEPTH)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function assertMarkdownDocumentPathWithinLimit(
|
|
path: string,
|
|
maxPathBytes = MARKDOWN_DOCUMENT_LISTING_MAX_PATH_BYTES
|
|
): void {
|
|
if (measureUtf8ByteLength(path, { stopAfterBytes: maxPathBytes }).exceededLimit) {
|
|
throw new MarkdownDocumentListingCapacityError()
|
|
}
|
|
}
|
|
|
|
export function visitMarkdownDocumentListingEntry(
|
|
budget: MarkdownDocumentListingBudget,
|
|
path: string,
|
|
depth: number
|
|
): void {
|
|
assertMarkdownDocumentPathWithinLimit(path, budget.limits.maxPathBytes)
|
|
if (budget.visitedEntries >= budget.limits.maxVisitedEntries || depth > budget.limits.maxDepth) {
|
|
throw new MarkdownDocumentListingCapacityError()
|
|
}
|
|
budget.visitedEntries += 1
|
|
}
|
|
|
|
export function estimateMarkdownDocumentRetainedBytes(document: MarkdownDocument): number {
|
|
return (
|
|
(document.filePath.length +
|
|
document.relativePath.length +
|
|
document.basename.length +
|
|
document.name.length) *
|
|
2 +
|
|
MARKDOWN_DOCUMENT_RETAINED_OVERHEAD_BYTES
|
|
)
|
|
}
|
|
|
|
export function retainMarkdownDocument(
|
|
budget: MarkdownDocumentListingBudget,
|
|
document: MarkdownDocument
|
|
): void {
|
|
if (
|
|
!document ||
|
|
typeof document.filePath !== 'string' ||
|
|
typeof document.relativePath !== 'string' ||
|
|
typeof document.basename !== 'string' ||
|
|
typeof document.name !== 'string'
|
|
) {
|
|
throw new MarkdownDocumentListingCapacityError()
|
|
}
|
|
assertMarkdownDocumentPathWithinLimit(document.filePath, budget.limits.maxPathBytes)
|
|
assertMarkdownDocumentPathWithinLimit(document.relativePath, budget.limits.maxPathBytes)
|
|
const retainedBytes = estimateMarkdownDocumentRetainedBytes(document)
|
|
if (
|
|
budget.documents >= budget.limits.maxDocuments ||
|
|
budget.metadataBytes + retainedBytes > budget.limits.maxMetadataBytes
|
|
) {
|
|
throw new MarkdownDocumentListingCapacityError()
|
|
}
|
|
budget.documents += 1
|
|
budget.metadataBytes += retainedBytes
|
|
}
|
|
|
|
export function retainMarkdownRelativePath(
|
|
budget: MarkdownDocumentListingBudget,
|
|
rootPath: string,
|
|
relativePath: string
|
|
): void {
|
|
const normalizedRoot = rootPath.replace(/[\\/]+$/, '')
|
|
const normalizedRelativePath = relativePath.replaceAll('\\', '/')
|
|
const basename = normalizedRelativePath.slice(normalizedRelativePath.lastIndexOf('/') + 1)
|
|
const extensionIndex = basename.lastIndexOf('.')
|
|
retainMarkdownDocument(budget, {
|
|
filePath: `${normalizedRoot}/${normalizedRelativePath}`,
|
|
relativePath: normalizedRelativePath,
|
|
basename,
|
|
name: extensionIndex > 0 ? basename.slice(0, extensionIndex) : basename
|
|
})
|
|
}
|
|
|
|
export function assertMarkdownDocumentsWithinLimit(
|
|
documents: unknown,
|
|
requested: Partial<MarkdownDocumentListingLimits> = {}
|
|
): number {
|
|
const budget = createMarkdownDocumentListingBudget(requested)
|
|
if (!Array.isArray(documents)) {
|
|
throw new MarkdownDocumentListingCapacityError()
|
|
}
|
|
if (documents.length > budget.limits.maxDocuments) {
|
|
throw new MarkdownDocumentListingCapacityError()
|
|
}
|
|
for (const document of documents) {
|
|
retainMarkdownDocument(budget, document as MarkdownDocument)
|
|
}
|
|
return budget.metadataBytes
|
|
}
|
|
|
|
function clampLimit(value: number | undefined, maximum: number): number {
|
|
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value <= 0) {
|
|
return maximum
|
|
}
|
|
return Math.min(value, maximum)
|
|
}
|