mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 11:22:15 +03:00
Merge remote-tracking branch 'origin/release/v3.8.51' into fix/trivy-cve-2025-68121-tls-client
This commit is contained in:
@@ -289,8 +289,7 @@ When creating _any_ validation tests or one-off logic scripts, default to `scrip
|
||||
### Database
|
||||
|
||||
- **Always** go through `src/lib/db/` domain modules — **never** write raw SQL in routes or handlers
|
||||
- **Never** add logic to `src/lib/localDb.ts` (re-export layer only)
|
||||
- **Never** barrel-import from `localDb.ts` — import specific `db/` modules instead
|
||||
- **Never** barrel-import from `localDb.ts` — import specific `src/lib/db/*` modules
|
||||
- DB singleton: `getDbInstance()` from `src/lib/db/core.ts` (WAL journaling)
|
||||
- Migrations: `src/lib/db/migrations/` — versioned SQL files, idempotent, run in transactions
|
||||
|
||||
@@ -355,8 +354,7 @@ Documentation must describe verified behavior, not plausible behavior.
|
||||
1. Create `src/lib/db/yourModule.ts` — import `getDbInstance` from `./core.ts`
|
||||
2. Export CRUD functions for your domain table(s)
|
||||
3. Add migration in `src/lib/db/migrations/` if new tables needed
|
||||
4. Re-export from `src/lib/localDb.ts` (add to the re-export list only)
|
||||
5. Write tests
|
||||
4. Write tests
|
||||
|
||||
### Adding a New MCP Tool
|
||||
|
||||
@@ -668,7 +666,7 @@ the stale-enforcement added in Fase 6A.3.
|
||||
## Hard Rules
|
||||
|
||||
1. Never commit secrets or credentials
|
||||
2. Never add logic to `localDb.ts`
|
||||
2. Never barrel-import from `localDb.ts` — import specific `src/lib/db/*` modules
|
||||
3. Never use `eval()` / `new Function()` / implied eval
|
||||
4. Never commit directly to `main`
|
||||
5. Never write raw SQL in routes — use `src/lib/db/` modules
|
||||
|
||||
1
changelog.d/fixes/11704-prepublish-npx-cmd-win32.md
Normal file
1
changelog.d/fixes/11704-prepublish-npx-cmd-win32.md
Normal file
@@ -0,0 +1 @@
|
||||
- **fix(build):** `prepublish.ts` bundles the ChatGPT Web (Codex) MCP bridge through `runBuildTool()` instead of spawning `npx.cmd` raw, fixing the build crash on Node ≥ 20/Windows where `.cmd` shims cannot be spawned without a shell (EINVAL) ([#11704](https://github.com/diegosouzapw/OmniRoute/issues/11704))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -514,7 +514,7 @@ For the full stealth playbook and operational guidance, see
|
||||
Primary state DB (SQLite):
|
||||
|
||||
- Core infra: `src/lib/db/core.ts` (better-sqlite3, migrations, WAL)
|
||||
- Re-export facade: `src/lib/localDb.ts` (thin compatibility layer for callers)
|
||||
- DB access: import specific `src/lib/db/*` modules directly (the old `localDb.ts` barrel was removed)
|
||||
- file: `${DATA_DIR}/storage.sqlite` (or `$XDG_CONFIG_HOME/omniroute/storage.sqlite` when set, else `~/.omniroute/storage.sqlite`)
|
||||
- entities (tables + KV namespaces): providerConnections, providerNodes, modelAliases, combos, apiKeys, settings, pricing, **customModels**, **proxyConfig**, **ipFilter**, **thinkingBudget**, **systemPrompt**
|
||||
|
||||
@@ -888,7 +888,7 @@ flowchart LR
|
||||
### Persistence
|
||||
|
||||
- `src/lib/db/*`: persistent config/state and domain persistence on SQLite
|
||||
- `src/lib/localDb.ts`: compatibility re-export for DB modules
|
||||
- `src/lib/db/*`: import specific modules directly — no barrel (the old `localDb.ts` re-export layer was removed)
|
||||
- `src/lib/usageDb.ts`: usage history/call logs facade on top of SQLite tables
|
||||
|
||||
## Provider Executor Coverage (Strategy Pattern)
|
||||
|
||||
@@ -313,7 +313,7 @@ table groups the actual directories and notable top-level files.
|
||||
|
||||
Top-level files in `src/lib/`:
|
||||
|
||||
- `localDb.ts` — re-export layer only. **Never** add logic here.
|
||||
- The old `localDb.ts` barrel was removed — consumers import specific `src/lib/db/*` modules directly.
|
||||
- `proxyHealth.ts`, `proxyLogger.ts`, `tokenHealthCheck.ts`, `localHealthCheck.ts`
|
||||
- `oneproxyRotator.ts`, `oneproxySync.ts`
|
||||
- `apiBridgeServer.ts`, `cacheLayer.ts`, `semanticCache.ts`, `settingsCache.ts`
|
||||
@@ -759,7 +759,7 @@ See [RESILIENCE_GUIDE.md](./RESILIENCE_GUIDE.md) and the dedicated section in
|
||||
2. Export CRUD functions for your domain.
|
||||
3. If new tables: add a migration under `src/lib/db/migrations/`, numbered
|
||||
sequentially, idempotent, transactional.
|
||||
4. Re-export from `src/lib/localDb.ts` (re-export only — **no logic**).
|
||||
4. Importers use direct imports from `@/lib/db/yourModule` (no barrel — the old `localDb.ts` re-export layer was removed).
|
||||
5. Add tests under `tests/unit/`.
|
||||
|
||||
### Add a new MCP tool
|
||||
@@ -790,7 +790,7 @@ See [A2A-SERVER.md § Adding a New Skill](../frameworks/A2A-SERVER.md). Skills l
|
||||
- **TypeScript**: `strict: false` (legacy posture). Prefer explicit types over
|
||||
inference for cross-module boundaries.
|
||||
- **Database**: never write raw SQL in routes or handlers — always go through
|
||||
`src/lib/db/` modules. Never add logic to `src/lib/localDb.ts`.
|
||||
`src/lib/db/` modules. Never barrel-import — use specific `src/lib/db/*` modules directly.
|
||||
- **DB-entity typing (#3512)**: a function that writes or reads a DB table's
|
||||
row shape should take/return a named TS interface mirroring that table's
|
||||
columns 1:1, not `any` or an inline anonymous type at the call site. Land
|
||||
@@ -824,7 +824,7 @@ See [A2A-SERVER.md § Adding a New Skill](../frameworks/A2A-SERVER.md). Skills l
|
||||
## 12. Hard Rules (from CLAUDE.md)
|
||||
|
||||
1. Never commit secrets or credentials.
|
||||
2. Never add logic to `src/lib/localDb.ts`.
|
||||
2. Never barrel-import — use specific `src/lib/db/*` modules directly.
|
||||
3. Never use `eval()` / `new Function()` / implied eval.
|
||||
4. Never commit directly to `main`.
|
||||
5. Never write raw SQL in routes — always go through `src/lib/db/` modules.
|
||||
|
||||
@@ -158,7 +158,7 @@ status. CI performs the broader package artifact and ecosystem checks.
|
||||
|
||||
**Contracts**
|
||||
|
||||
- Domain modules under `src/lib/db/`; `src/lib/localDb.ts` remains a re-export layer only.
|
||||
- Domain modules under `src/lib/db/`; import specific modules directly (the old `localDb.ts` re-export layer was removed).
|
||||
- Numbered, idempotent SQL migrations under `src/lib/db/migrations/`, transaction safety, upgrade
|
||||
behavior, indexes, and every caller affected by the schema.
|
||||
- Routes and handlers never issue raw SQL directly.
|
||||
|
||||
@@ -329,11 +329,12 @@ const nextConfig = {
|
||||
// TODO: Re-enable after fixing all sub-component useTranslations scope issues
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
webpack(config, { webpack }) {
|
||||
webpack(config, { dev, webpack }) {
|
||||
config.ignoreWarnings = [
|
||||
...(config.ignoreWarnings || []),
|
||||
isNextIntlExtractorDynamicImportWarning,
|
||||
];
|
||||
const nextDefaultSplitChunks = config.optimization?.splitChunks;
|
||||
config.optimization = config.optimization || {};
|
||||
config.optimization.splitChunks = {
|
||||
...config.optimization.splitChunks,
|
||||
@@ -392,6 +393,9 @@ const nextConfig = {
|
||||
},
|
||||
},
|
||||
};
|
||||
// Next's development defaults are tuned for incremental route compilation.
|
||||
// Retain the custom vendor topology for production without imposing it on dev.
|
||||
if (dev) config.optimization.splitChunks = nextDefaultSplitChunks;
|
||||
|
||||
if (isMinimalBuild) {
|
||||
// Mirror the turbopack.resolveAlias entries for webpack-built artifacts.
|
||||
|
||||
@@ -1427,7 +1427,7 @@ export async function handleChatCore({
|
||||
};
|
||||
if ((isCombo && comboName) || routingComboId) {
|
||||
try {
|
||||
const { getComboByName } = await import("../../src/lib/localDb");
|
||||
const { getComboByName } = await import("@/lib/db/combos");
|
||||
let comboConfig = await getComboByName(comboName);
|
||||
if (!comboConfig && comboName?.startsWith("combo/")) {
|
||||
comboConfig = await getComboByName(comboName.substring(6));
|
||||
@@ -1918,7 +1918,7 @@ export async function handleChatCore({
|
||||
if (isCombo && comboName) {
|
||||
log?.info?.("CONTEXT", `Attempting to resolve combo limits for comboName=${comboName}`);
|
||||
try {
|
||||
const { getComboByName } = await import("../../src/lib/localDb");
|
||||
const { getComboByName } = await import("@/lib/db/combos");
|
||||
const { resolveComboTargets } = await import("../services/combo.ts");
|
||||
let comboConfig = await getComboByName(comboName);
|
||||
if (!comboConfig && comboName.startsWith("combo/")) {
|
||||
@@ -4292,13 +4292,14 @@ export async function handleChatCore({
|
||||
let quotaCooldownMs = kimiRateLimitResetAt
|
||||
? Math.max(new Date(kimiRateLimitResetAt).getTime() - Date.now(), 0)
|
||||
: retryAfterMs || COOLDOWN_MS.rateLimit;
|
||||
const deferAntigravityQuotaStateToCaller =
|
||||
shouldDeferAntigravityQuotaStateToCaller(
|
||||
provider,
|
||||
typeof onStreamFailure === "function"
|
||||
);
|
||||
const isAntigravityQuotaFamily =
|
||||
shouldDeferAntigravityQuotaStateToCaller(provider, true);
|
||||
const deferAntigravityQuotaStateToCaller = shouldDeferAntigravityQuotaStateToCaller(
|
||||
provider,
|
||||
typeof onStreamFailure === "function"
|
||||
);
|
||||
const isAntigravityQuotaFamily = shouldDeferAntigravityQuotaStateToCaller(
|
||||
provider,
|
||||
true
|
||||
);
|
||||
let coreOwnedAntigravityLockout: {
|
||||
cooldownMs: number;
|
||||
failureCount: number;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { getUpstreamProxyConfig } from "@/lib/localDb";
|
||||
import type { FallbackBackend } from "@/lib/db/upstreamProxy";
|
||||
import { FallbackBackend, getUpstreamProxyConfig } from "@/lib/db/upstreamProxy";
|
||||
|
||||
/**
|
||||
* Module-level cache for upstream proxy config (shared across all requests).
|
||||
@@ -29,7 +28,8 @@ const COMBOS_CACHE_TTL = 10_000;
|
||||
|
||||
export async function getCombosCached(): Promise<unknown[]> {
|
||||
const now = Date.now();
|
||||
const { getCombos, getCombosCacheVersion } = await import("@/lib/localDb");
|
||||
const { getCombos } = await import("@/lib/db/combos");
|
||||
const { getCombosCacheVersion } = await import("@/lib/db/readCache");
|
||||
const version = getCombosCacheVersion();
|
||||
// A combo write (create/update/delete/reorder) bumps the shared version via
|
||||
// invalidateDbCache("combos"); when it no longer matches our snapshot we drop
|
||||
|
||||
@@ -265,7 +265,15 @@ export function translateNonStreamingResponse(
|
||||
if (toolCalls.length > 0) {
|
||||
message.tool_calls = toolCalls;
|
||||
}
|
||||
if (message.content === undefined) {
|
||||
if (
|
||||
(!message.content ||
|
||||
(typeof message.content === "string" && message.content.trim().length === 0)) &&
|
||||
toolCalls.length === 0 &&
|
||||
replayableReasoningContent &&
|
||||
replayableReasoningContent.trim().length > 0
|
||||
) {
|
||||
message.content = replayableReasoningContent;
|
||||
} else if (message.content === undefined) {
|
||||
message.content = "";
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ import {
|
||||
clampMcpAccessibilityConfig,
|
||||
type McpAccessibilityConfig,
|
||||
} from "../services/compression/engines/mcpAccessibility/constants.ts";
|
||||
import { getDbInstance } from "../../src/lib/db/core.ts";
|
||||
import { getDbInstance, ensureDbInitialized } from "../../src/lib/db/core.ts";
|
||||
import { normalizeQuotaResponse } from "../../src/shared/contracts/quota.ts";
|
||||
import { resolveOmniRouteBaseUrl } from "../../src/shared/utils/resolveOmniRouteBaseUrl.ts";
|
||||
import { sanitizeErrorMessage } from "../utils/error.ts";
|
||||
@@ -1527,10 +1527,10 @@ export function createMcpServer(options?: CreateMcpServerOptions): McpServer {
|
||||
* Called when `omniroute --mcp` is used.
|
||||
*/
|
||||
export async function startMcpStdio(): Promise<void> {
|
||||
await ensureDbInitialized();
|
||||
// Stdout is reserved for JSON-RPC — bin/mcpStdioConsoleGuard.mjs is preloaded via
|
||||
// `node --import` (see bin/mcp-server.mjs) so console.log/warn already redirect to
|
||||
// stderr before this module's own imports evaluate (DB init happens as a side effect of
|
||||
// createMcpServer()'s tool registration, earlier than any code placed here could catch).
|
||||
// stderr before this module's own imports evaluate.
|
||||
const server = createMcpServer();
|
||||
const transport = new StdioServerTransport();
|
||||
const version = process.env.npm_package_version || "1.8.1";
|
||||
|
||||
@@ -3,7 +3,7 @@ import { describe, it, expect, vi } from "vitest";
|
||||
// Mock the DB so recommendStrategyOverride sees adaptiveVolumeRouting = true.
|
||||
// Without this the real getSettings() throws (no SQLite in test env), the
|
||||
// catch block fires, and the function returns noOverride before any rule runs.
|
||||
vi.mock("@/lib/localDb", () => ({
|
||||
vi.mock("@/lib/db/settings", () => ({
|
||||
getSettings: vi.fn().mockResolvedValue({ adaptiveVolumeRouting: true }),
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import type { BatchItemCheckpoint, BatchRecord } from "@/lib/localDb";
|
||||
import {
|
||||
type BatchItemCheckpoint,
|
||||
type BatchRecord,
|
||||
countBatchItemCheckpoints,
|
||||
createFile,
|
||||
deleteFile,
|
||||
ensureBatchItemCheckpoints,
|
||||
getApiKeyById,
|
||||
getBatch,
|
||||
getFileContent,
|
||||
getPendingBatches,
|
||||
getTerminalBatches,
|
||||
listBatchItemCheckpoints,
|
||||
listFiles,
|
||||
markBatchItemError,
|
||||
markBatchItemProcessing,
|
||||
markBatchItemResult,
|
||||
updateBatch,
|
||||
} from "@/lib/localDb";
|
||||
} from "@/lib/db/batches";
|
||||
import { createFile, deleteFile, getFileContent, listFiles } from "@/lib/db/files";
|
||||
import { getApiKeyById } from "@/lib/db/apiKeys";
|
||||
import { dispatch } from "@/lib/batches/dispatch";
|
||||
import type { SupportedBatchEndpoint } from "@/shared/constants/batchEndpoints";
|
||||
import { DEFAULT_BATCH_EXPIRATION_SECONDS } from "@/shared/constants/batch";
|
||||
|
||||
@@ -370,7 +370,7 @@ export function clearStaleLKGP(
|
||||
): void {
|
||||
void (async () => {
|
||||
try {
|
||||
const { clearLKGP } = await import("@/lib/localDb");
|
||||
const { clearLKGP } = await import("@/lib/db/settings");
|
||||
const promises: Promise<void>[] = [clearLKGP(comboName, comboId || comboName)];
|
||||
if (executionKey) {
|
||||
promises.push(clearLKGP(comboName, executionKey));
|
||||
@@ -427,7 +427,7 @@ export async function buildAutoCandidates(
|
||||
// apply, so auto-routing behavior is unchanged.
|
||||
const quotaCutoffEnabled =
|
||||
(resilienceSettings ?? resolveResilienceSettings(null))?.quotaPreflight?.enabled === true;
|
||||
const { getPricingForModel } = await import("../../src/lib/localDb");
|
||||
const { getPricingForModel } = await import("@/lib/db/settings");
|
||||
const quotaPromises = new Map<string, Promise<unknown>>();
|
||||
let historicalLatencyStats: Record<string, HistoricalLatencyStatsEntry> = {};
|
||||
try {
|
||||
@@ -1640,7 +1640,8 @@ async function handleComboChatInner({
|
||||
lastModel,
|
||||
modelStr,
|
||||
`Model routing: ${lastModel} → ${modelStr}`,
|
||||
existingHandoff
|
||||
existingHandoff,
|
||||
universalHandoffConfig.relayMode
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1969,7 +1970,7 @@ async function handleComboChatInner({
|
||||
const connId = effectiveConnectionId || undefined;
|
||||
void (async () => {
|
||||
try {
|
||||
const { setLKGP } = await import("../../src/lib/localDb");
|
||||
const { setLKGP } = await import("@/lib/db/settings");
|
||||
await Promise.all([
|
||||
setLKGP(combo.name, target.executionKey, provider, connId),
|
||||
setLKGP(combo.name, combo.id || combo.name, provider, connId),
|
||||
@@ -3566,7 +3567,7 @@ async function handleRoundRobinCombo({
|
||||
const connId = effectiveConnectionId || undefined;
|
||||
void (async () => {
|
||||
try {
|
||||
const { setLKGP } = await import("../../src/lib/localDb");
|
||||
const { setLKGP } = await import("@/lib/db/settings");
|
||||
await Promise.all([
|
||||
setLKGP(combo.name, target.executionKey, provider, connId),
|
||||
setLKGP(combo.name, combo.id || combo.name, provider, connId),
|
||||
|
||||
@@ -67,7 +67,7 @@ export async function applyStrategyOrdering(
|
||||
|
||||
if (strategy === "lkgp") {
|
||||
try {
|
||||
const { getLKGP } = await import("../../../src/lib/localDb");
|
||||
const { getLKGP } = await import("@/lib/db/settings");
|
||||
const lkgpProvider = await getLKGP(combo.name, combo.id || combo.name);
|
||||
|
||||
if (lkgpProvider) {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* shrinking (Quality Gate / #3501).
|
||||
*/
|
||||
|
||||
import { getCachedProviderConnectionById } from "@/lib/localDb";
|
||||
import { getCachedProviderConnectionById } from "@/lib/db/readCache";
|
||||
import { effectiveMaxConcurrency } from "./comboPredicates.ts";
|
||||
import type { ResolvedComboTarget } from "./types.ts";
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
type PreflightQuotaThresholds,
|
||||
type QuotaInfo,
|
||||
} from "../quotaPreflight.ts";
|
||||
import { getCachedProviderConnectionById } from "@/lib/localDb";
|
||||
import { getCachedProviderConnectionById } from "@/lib/db/readCache";
|
||||
import {
|
||||
resolveResilienceSettings,
|
||||
type ResilienceSettings,
|
||||
@@ -109,8 +109,7 @@ export async function resolveQuotaExhaustionCutoffForTarget(
|
||||
let connection: Record<string, unknown> | undefined;
|
||||
try {
|
||||
connection = (await getCachedProviderConnectionById(connectionId)) as
|
||||
| Record<string, unknown>
|
||||
| undefined;
|
||||
Record<string, unknown> | undefined;
|
||||
} catch {
|
||||
connection = undefined;
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ export async function resolveAutoStrategyOrder(
|
||||
|
||||
let lastKnownGoodProvider: string | undefined;
|
||||
try {
|
||||
const { getLKGP } = await import("../../../src/lib/localDb");
|
||||
const { getLKGP } = await import("@/lib/db/settings");
|
||||
const lkgp = await getLKGP(combo.name, combo.id || combo.name);
|
||||
if (lkgp) lastKnownGoodProvider = lkgp.provider;
|
||||
} catch (err) {
|
||||
|
||||
@@ -65,7 +65,7 @@ export function orderTargetsForWeightedFallback<T extends { executionKey: string
|
||||
*/
|
||||
export async function sortModelsByCost(models: string[]): Promise<string[]> {
|
||||
try {
|
||||
const { getPricingForModel } = await import("../../../src/lib/localDb");
|
||||
const { getPricingForModel } = await import("@/lib/db/settings");
|
||||
const withCost = await Promise.all(
|
||||
models.map(async (modelStr) => {
|
||||
const parsed = parseModel(modelStr);
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface ContextRelayConfig {
|
||||
handoffThreshold?: number;
|
||||
handoffProviders?: string[];
|
||||
maxMessagesForSummary?: number;
|
||||
relayMode?: "schema-locked" | "standard";
|
||||
}
|
||||
|
||||
export interface UniversalHandoffConfig {
|
||||
@@ -65,6 +66,7 @@ export interface UniversalHandoffConfig {
|
||||
ttlMinutes: number;
|
||||
/** Preserve existing system prompt when injecting handoff */
|
||||
preserveSystemPrompt: boolean;
|
||||
relayMode?: "schema-locked" | "standard";
|
||||
}
|
||||
|
||||
export const DEFAULT_UNIVERSAL_HANDOFF_CONFIG: UniversalHandoffConfig = {
|
||||
@@ -155,6 +157,8 @@ export function resolveUniversalHandoffConfig(
|
||||
"preserveSystemPrompt",
|
||||
DEFAULT_UNIVERSAL_HANDOFF_CONFIG.preserveSystemPrompt
|
||||
),
|
||||
relayMode:
|
||||
getString("relayMode", "standard") === "schema-locked" ? "schema-locked" : "standard",
|
||||
};
|
||||
}
|
||||
export interface ParsedHandoffContent {
|
||||
@@ -192,6 +196,7 @@ export function resolveContextRelayConfig(
|
||||
Number.isFinite(rawMaxMessages) && rawMaxMessages >= 5 && rawMaxMessages <= 100
|
||||
? Math.round(rawMaxMessages)
|
||||
: DEFAULT_MAX_MESSAGES_FOR_SUMMARY,
|
||||
relayMode: config?.relayMode === "schema-locked" ? "schema-locked" : "standard",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -232,7 +237,8 @@ function formatMessagesForPrompt(messages: MessageLike[]): string {
|
||||
|
||||
export function selectMessagesForSummary(
|
||||
messages: MessageLike[],
|
||||
maxMessages: number
|
||||
maxMessages: number,
|
||||
relayMode?: "schema-locked" | "standard"
|
||||
): MessageLike[] {
|
||||
const validMessages = messages.filter((m) => m && typeof m === "object");
|
||||
const system = validMessages.filter(
|
||||
@@ -242,15 +248,23 @@ export function selectMessagesForSummary(
|
||||
(m) => typeof m.role !== "string" || (m.role !== "system" && m.role !== "developer")
|
||||
);
|
||||
|
||||
const recentMessages = [...system, ...nonSystem.slice(-maxMessages)];
|
||||
const recentMessages =
|
||||
relayMode === "schema-locked"
|
||||
? [...nonSystem.slice(-maxMessages)]
|
||||
: [...system, ...nonSystem.slice(-maxMessages)];
|
||||
let working = [...recentMessages];
|
||||
|
||||
while (working.length > system.length + 1) {
|
||||
const minWorkingLength = relayMode === "schema-locked" ? 1 : system.length + 1;
|
||||
|
||||
while (working.length > minWorkingLength) {
|
||||
const history = formatMessagesForPrompt(working);
|
||||
if (estimateTokens(history) <= MAX_HISTORY_TOKENS_FOR_SUMMARY) {
|
||||
return working;
|
||||
}
|
||||
working = [...system, ...working.slice(system.length + 1)];
|
||||
working =
|
||||
relayMode === "schema-locked"
|
||||
? working.slice(1)
|
||||
: [...system, ...working.slice(system.length + 1)];
|
||||
}
|
||||
|
||||
const fallbackHistory = formatMessagesForPrompt(working);
|
||||
@@ -258,7 +272,7 @@ export function selectMessagesForSummary(
|
||||
// If there are system messages, return them so the caller can still produce context.
|
||||
// If there are no system messages (system=[]), fall back to the single most-recent
|
||||
// non-system message rather than returning [] which would silently drop the handoff.
|
||||
if (system.length > 0) {
|
||||
if (relayMode !== "schema-locked" && system.length > 0) {
|
||||
return system;
|
||||
}
|
||||
const lastNonSystem = nonSystem[nonSystem.length - 1];
|
||||
@@ -386,7 +400,8 @@ async function generateHandoffAsync(options: {
|
||||
const summaryModel = relayConfig.handoffModel || options.model;
|
||||
const selectedMessages = selectMessagesForSummary(
|
||||
Array.isArray(options.messages) ? options.messages : [],
|
||||
relayConfig.maxMessagesForSummary
|
||||
relayConfig.maxMessagesForSummary,
|
||||
relayConfig.relayMode
|
||||
);
|
||||
const historyText = formatMessagesForPrompt(selectedMessages);
|
||||
if (!historyText) return;
|
||||
@@ -499,7 +514,8 @@ The context above contains a concise summary of the prior work. Continue seamles
|
||||
|
||||
export function injectHandoffIntoBody(
|
||||
body: Record<string, unknown>,
|
||||
payload: HandoffPayload
|
||||
payload: HandoffPayload,
|
||||
_relayMode?: "schema-locked" | "standard"
|
||||
): Record<string, unknown> {
|
||||
const handoffContent = buildHandoffSystemMessage(payload);
|
||||
const isResponsesRequest =
|
||||
@@ -680,12 +696,14 @@ async function generateUniversalHandoffAsync(options: {
|
||||
handoffModel: string;
|
||||
ttlMs: number;
|
||||
maxMessages: number;
|
||||
relayMode?: "schema-locked" | "standard";
|
||||
providerAllowlist: string[];
|
||||
handleSingleModel: (body: Record<string, unknown>, modelStr: string) => Promise<Response>;
|
||||
}): Promise<UniversalHandoffOutcome> {
|
||||
const selectedMessages = selectMessagesForSummary(
|
||||
Array.isArray(options.messages) ? options.messages : [],
|
||||
options.maxMessages
|
||||
options.maxMessages,
|
||||
options.relayMode
|
||||
);
|
||||
const historyText = formatMessagesForPrompt(selectedMessages);
|
||||
if (!historyText) return "unavailable";
|
||||
@@ -789,6 +807,7 @@ export function maybeGenerateUniversalHandoff(options: {
|
||||
handoffModel: options.universalConfig.handoffModel || options.currModel,
|
||||
ttlMs,
|
||||
maxMessages: options.universalConfig.maxMessagesForSummary,
|
||||
relayMode: options.universalConfig.relayMode,
|
||||
providerAllowlist: options.universalConfig.providerAllowlist,
|
||||
handleSingleModel: options.handleSingleModel,
|
||||
})
|
||||
@@ -812,7 +831,8 @@ export function injectUniversalHandoffBody(
|
||||
prevModel: string,
|
||||
currModel: string,
|
||||
reason: string,
|
||||
existingPayload?: HandoffPayload | null
|
||||
existingPayload?: HandoffPayload | null,
|
||||
_relayMode?: "schema-locked" | "standard"
|
||||
): Record<string, unknown> {
|
||||
const handoffContent = buildUniversalHandoffSystemMessage(
|
||||
prevModel,
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* - Provider-specific rules: "openai/gpt-4"
|
||||
*/
|
||||
|
||||
import { checkKeyModelAccess, getKeyGroupsForApiKey } from "@/lib/localDb";
|
||||
import { checkKeyModelAccess, getKeyGroupsForApiKey } from "@/lib/db/apiKeyGroups";
|
||||
|
||||
export interface KeyGroupAuthResult {
|
||||
/** Whether the request is authorized */
|
||||
|
||||
@@ -332,7 +332,7 @@ function getProviderIdFromConnection(connection: unknown) {
|
||||
|
||||
async function getActiveProviderSet() {
|
||||
try {
|
||||
const { getCachedProviderConnections } = await import("@/lib/localDb");
|
||||
const { getCachedProviderConnections } = await import("@/lib/db/readCache");
|
||||
const conns = (await getCachedProviderConnections()) as unknown[];
|
||||
const providers = conns
|
||||
.map(getProviderIdFromConnection)
|
||||
@@ -345,7 +345,7 @@ async function getActiveProviderSet() {
|
||||
|
||||
async function getActiveSyncedProvidersForModel(modelId: string) {
|
||||
try {
|
||||
const { getActiveProvidersWithSyncedModel } = await import("@/lib/localDb");
|
||||
const { getActiveProvidersWithSyncedModel } = await import("@/lib/db/models");
|
||||
const providers = await getActiveProvidersWithSyncedModel(modelId);
|
||||
return providers
|
||||
.map(resolveProviderAlias)
|
||||
@@ -403,7 +403,7 @@ function isTruthyEnv(value: string | undefined) {
|
||||
|
||||
async function getPreferClaudeCodeForUnprefixedClaudeModels() {
|
||||
try {
|
||||
const { getCachedSettings } = await import("@/lib/localDb");
|
||||
const { getCachedSettings } = await import("@/lib/db/readCache");
|
||||
const settings = (await getCachedSettings()) as Record<string, unknown>;
|
||||
if (typeof settings.preferClaudeCodeForUnprefixedClaudeModels === "boolean") {
|
||||
return settings.preferClaudeCodeForUnprefixedClaudeModels;
|
||||
|
||||
@@ -223,7 +223,7 @@ export function clearPayloadRulesConfigOverride() {
|
||||
// silently reverting to the (usually empty) file config.
|
||||
async function loadPayloadRulesFromSettings(): Promise<PayloadRulesConfig | null> {
|
||||
try {
|
||||
const { getCachedSettings } = await import("@/lib/localDb");
|
||||
const { getCachedSettings } = await import("@/lib/db/readCache");
|
||||
const settings = (await getCachedSettings()) as { payloadRules?: unknown };
|
||||
const raw = settings?.payloadRules;
|
||||
if (raw === null || raw === undefined) return null;
|
||||
|
||||
@@ -338,7 +338,8 @@ export async function initializeRateLimits() {
|
||||
applyBottleneckHeartbeatPatch();
|
||||
|
||||
try {
|
||||
const { getCachedProviderConnections, getSettings } = await import("@/lib/localDb");
|
||||
const { getCachedProviderConnections } = await import("@/lib/db/readCache");
|
||||
const { getSettings } = await import("@/lib/db/settings");
|
||||
const [connections, settings] = await Promise.all([
|
||||
getCachedProviderConnections(),
|
||||
getSettings(),
|
||||
@@ -385,7 +386,7 @@ export async function applyRequestQueueSettings(nextSettings: RequestQueueSettin
|
||||
currentRequestQueueSettings = { ...nextSettings };
|
||||
// Global policy changes invalidate snapshots from the previous generation.
|
||||
preservedReplacementSettings.clear();
|
||||
const { getCachedProviderConnections } = await import("@/lib/localDb");
|
||||
const { getCachedProviderConnections } = await import("@/lib/db/readCache");
|
||||
const connections = await getCachedProviderConnections();
|
||||
// Also discard any snapshot created while the asynchronous DB read yielded.
|
||||
preservedReplacementSettings.clear();
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
getTokenLimitsForRequest,
|
||||
logTokenLimitReset,
|
||||
type TokenLimit,
|
||||
} from "@/lib/localDb";
|
||||
} from "@/lib/db/tokenLimits";
|
||||
|
||||
interface CacheEntry {
|
||||
windowStart: string;
|
||||
@@ -295,8 +295,7 @@ export function recordTokenUsage(
|
||||
ORDER BY window_start DESC LIMIT 1`
|
||||
)
|
||||
.get(limit.id, windowStart) as
|
||||
| { window_start?: string; tokens_used?: number }
|
||||
| undefined;
|
||||
{ window_start?: string; tokens_used?: number } | undefined;
|
||||
const prevTokens =
|
||||
priorRow && typeof priorRow.tokens_used === "number" ? priorRow.tokens_used : 0;
|
||||
if (prevTokens > 0) {
|
||||
|
||||
@@ -155,7 +155,7 @@ export async function recommendStrategyOverride(
|
||||
|
||||
// Check if adaptive routing is enabled globally
|
||||
try {
|
||||
const { getSettings } = await import("@/lib/localDb");
|
||||
const { getSettings } = await import("@/lib/db/settings");
|
||||
const settings = await getSettings();
|
||||
if (!settings.adaptiveVolumeRouting) {
|
||||
return noOverride;
|
||||
|
||||
@@ -20,7 +20,7 @@ async function getConfig() {
|
||||
if (_cachedConfig && now < _cacheExpiry) return _cachedConfig;
|
||||
|
||||
try {
|
||||
const { getProxyConfig } = await import("../../src/lib/localDb");
|
||||
const { getProxyConfig } = await import("@/lib/db/settings");
|
||||
_cachedConfig = await getProxyConfig();
|
||||
_cacheExpiry = now + 30_000; // Cache for 30s
|
||||
return _cachedConfig;
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
|
||||
import { fetch as undiciFetch } from "undici";
|
||||
import { createProxyDispatcher, normalizeProxyUrl } from "./proxyDispatcher.ts";
|
||||
import { resolveProxyForScopeFromRegistry, listProxies, listOneproxyProxies } from "@/lib/localDb";
|
||||
import { resolveProxyForScopeFromRegistry, listProxies } from "@/lib/db/proxies";
|
||||
import { listOneproxyProxies } from "@/lib/db/oneproxy";
|
||||
import { isFeatureFlagEnabled } from "@/shared/utils/featureFlags";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -328,10 +328,10 @@ const chatGptWebCodexMcpDestFile = join(
|
||||
if (existsSync(chatGptWebCodexMcpSrcFile)) {
|
||||
console.log(" 🔨 Bundling ChatGPT Web (Codex) MCP bridge...");
|
||||
mkdirSync(dirname(chatGptWebCodexMcpDestFile), { recursive: true });
|
||||
execFileSync(
|
||||
NPX_BIN,
|
||||
runBuildTool(
|
||||
"esbuild",
|
||||
"esbuild",
|
||||
[
|
||||
"esbuild",
|
||||
"open-sse/vendor/codex-chatgpt-web/adapters/chatgpt-web/mcp-server.ts",
|
||||
"--bundle",
|
||||
"--platform=node",
|
||||
|
||||
@@ -1,89 +1,24 @@
|
||||
#!/usr/bin/env node
|
||||
// scripts/check/check-db-rules.mjs
|
||||
// Gate de convenções de banco (CLAUDE.md Hard Rules #2 e #5). Três verificações:
|
||||
// (a) Todo módulo de domínio em src/lib/db/*.ts deve ser re-exportado por
|
||||
// src/lib/localDb.ts (camada de compat). Um módulo db NOVO que não é
|
||||
// re-exportado (e não está congelado) falha — força a decisão consciente
|
||||
// de expor ou justificar (Hard Rule #2).
|
||||
// (b) src/lib/localDb.ts é APENAS camada de re-export: nada de lógica
|
||||
// (function/class/arrow de negócio). Mata o anti-padrão de "só uma
|
||||
// funçãozinha aqui" que vira regra de negócio fora dos módulos db/.
|
||||
// Gate de convenções de banco (CLAUDE.md Hard Rules #2 e #5). Uma verificação:
|
||||
// (c) Nenhum SQL cru em src/app/api/**/route.ts ou open-sse/handlers/*.ts.
|
||||
// SQL deve viver em src/lib/db/ (Hard Rule #5). Ofensores pré-existentes
|
||||
// são congelados; QUALQUER novo SQL cru em rota/handler falha.
|
||||
// Stale-enforcement (6A.3): entradas em INTENTIONALLY_INTERNAL / EXTERNAL_DB_ALLOWED
|
||||
// que não suprimem nenhuma violação real → gate falha com instrução de remoção.
|
||||
// As antigas verificações (a) re-export completo via src/lib/localDb.ts e
|
||||
// (b) localDb.ts sem lógica foram REMOVIDAS: o barrel src/lib/localDb.ts foi
|
||||
// deletado (#11795) — consumidores importam módulos src/lib/db/* diretamente
|
||||
// (regra "never barrel-import", aplicada por eslint no-restricted-imports).
|
||||
// Stale-enforcement (6A.3): entradas em EXTERNAL_DB_ALLOWED que não suprimem
|
||||
// nenhuma violação real → gate falha com instrução de remoção.
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { assertNoStale } from "./lib/allowlist.mjs";
|
||||
|
||||
const cwd = process.cwd();
|
||||
const DB_DIR = path.join(cwd, "src/lib/db");
|
||||
const LOCAL_DB = path.join(cwd, "src/lib/localDb.ts");
|
||||
const API_DIR = path.join(cwd, "src/app/api");
|
||||
const HANDLERS_DIR = path.join(cwd, "open-sse/handlers");
|
||||
|
||||
// (a) Módulos db/ que NÃO são re-exportados por localDb.ts por DESIGN (Hard Rule #2:
|
||||
// "Never barrel-import from localDb.ts — import specific db/ modules instead").
|
||||
// Cada entrada aqui foi auditada e é consumida via import direto de "@/lib/db/X"
|
||||
// (estático ou dinâmico) pelos seus consumidores — exatamente o padrão correto.
|
||||
// Re-exportar esses módulos via localDb.ts INCENTIVARIA o anti-padrão proibido.
|
||||
// O gate ainda bloqueia QUALQUER módulo db/ NOVO que não seja re-exportado E não
|
||||
// esteja nessa lista — mantendo a decisão consciente obrigatória (Hard Rule #2).
|
||||
// Legenda de classificação:
|
||||
// type-only = exporta apenas tipos (sem runtime API), não há o que re-exportar
|
||||
// db-internal = importado apenas dentro de src/lib/db/ (coordenação interna)
|
||||
// intentionally-internal = consumido por import direto fora de db/ (correto per Rule #2)
|
||||
// DEAD? = zero importers encontrados na auditoria de 2026-06-11; não deletar
|
||||
// sem investigação — pode ser reserva de schema ou F2 pendente
|
||||
export const INTENTIONALLY_INTERNAL = new Set([
|
||||
"_rowTypes", // type-only: 5 importers internos em db/ (AgentBridge/Inspector row types)
|
||||
"accessTokens", // intentionally-internal: 4 rotas /api/cli/* (connect, whoami, tokens, tokens/[id]) + server/authz/accessTokenAuth.ts via import direto "@/lib/db/accessTokens" (Rule #2)
|
||||
"apiKeyColumnFallbacks", // db-internal: importado só por db/apiKeys.ts (API_KEY_COLUMN_FALLBACKS — fallbacks de coluna split do apiKeys.ts)
|
||||
"apiKeyUsageLimitFields", // db-internal: importado só por db/apiKeys.ts (helpers de campo de limite de uso split do apiKeys.ts; mig 101)
|
||||
"backupRetention", // db-internal: importado só por db/backup.ts e db/migrationRunner.ts (política de retenção compartilhada; mora fora de backup.ts porque core.ts importa migrationRunner.ts — importar backup.ts de lá fecharia um ciclo, #10421)
|
||||
"caseMapping", // db-internal: importado só por db/core.ts (toSnakeCase/toCamelCase/objToSnake — column-mapping snake↔camel split do core.ts, #4947)
|
||||
"cleanup", // intentionally-internal: 3 API routes (purge-quota-snapshots, purge-call-logs, purge-detailed-logs)
|
||||
"cliToolState", // intentionally-internal: 14+ API routes em /api/cli-tools/*-settings
|
||||
"comboForecast", // intentionally-internal: src/lib/usage/comboForecast.ts
|
||||
"commandCodeAuth", // intentionally-internal: 5 API routes em /api/providers/command-code/auth/*
|
||||
"compression", // intentionally-internal: 2 API routes (settings/compression, context/rtk/config)
|
||||
"compressionDetailNormalizers", // db-internal: importado só por db/compression.ts (normalizeSessionDedupConfig/normalizeCcrConfig/buildDetailConfigDefaults/applyDetailConfigUpdate — normalizadores do detail-config split do compression.ts, #8404)
|
||||
"connectionRuntimeState", // intentionally-internal: warmupScheduler sqlite/redis stores importam diretamente de @/lib/db/connectionRuntimeState (Rule #2)
|
||||
"vacuumScheduler", // intentionally-internal: src/instrumentation-node.ts (dynamic import, lifecycle wiring per Rule #2)
|
||||
"detailedLogs", // intentionally-internal: 3 callers (callLogs.ts, logs/detail route, embeddings handler)
|
||||
"discovery", // DEAD?: 0 importers na auditoria de 2026-06-11; lib/discovery/index.ts não usa db/discovery
|
||||
"domainState", // intentionally-internal: 5 callers (batchWriter, circuitBreaker, costRules, fallbackPolicy, lockoutPolicy)
|
||||
"encryption", // intentionally-internal: 8+ callers (container, webhookDispatcher, cloudAgent/credentials, services/apiKey, 4+ routes, open-sse)
|
||||
"healthCheck", // db-internal: importado por db/core.ts (runDbHealthCheck)
|
||||
"jsonMigration", // intentionally-internal: src/app/api/settings/import-json/route.ts
|
||||
"migrationRunner", // db-internal: importado por db/core.ts (runMigrations ao inicializar o DB)
|
||||
"modelCapabilityOverrides", // intentionally-internal: src/app/api/model-capability-overrides/route.ts via import direto "@/lib/db/modelCapabilityOverrides" (#6727 — evita empurrar localDb.ts para o cap de 800 linhas)
|
||||
"notion", // intentionally-internal: settings/notion API route + open-sse/mcp-server/tools/notionTools.ts
|
||||
"obsidian", // intentionally-internal: src/lib/obsidianSync.ts + settings/obsidian route + MCP obsidianTools.ts
|
||||
"optimizationSettings", // db-internal: imported by db/core.ts for SQLite PRAGMA application helpers that require the live adapter
|
||||
"pluginMetrics", // DEAD? (production): write path não foi conectado ainda (documentado no cabeçalho do módulo); testado por tests/unit/plugins-metrics.test.ts
|
||||
"prompts", // DEAD? (production): zero callers de produção encontrados; domínio domain/prompts.ts é independente; testado por tests/integration/proxy-pipeline.test.ts
|
||||
"probeUtils", // db-internal: importado so por db/core.ts (retryProbeIfTransient no caminho da corruption-probe, #9541); testado por tests/unit/probe-9541-repro.test.ts
|
||||
"providerNodeSelect", // db-internal: importado só por db/providers.ts (selectProviderNodeForConnection — lógica pura de seleção de provider node split do providers.ts, #4421)
|
||||
"providerStats", // intentionally-internal: src/app/api/provider-stats/route.ts
|
||||
"proxyLatency", // intentionally-internal: imported directly by src/lib/db/proxies.ts (anti-barrel, #6798)
|
||||
"proxySubscriptions", // db-internal: importado só por db/proxies.ts (addProxiesToScopePool — split do proxies.ts para ficar sob o cap de tamanho congelado, #7299); a função já é re-exportada por proxies.ts (que localDb.ts re-exporta)
|
||||
"recovery", // intentionally-internal: bin/cli/runtime.mjs (import() dinâmico) + tests
|
||||
"schemaColumns", // db-internal: importado só por db/core.ts (ensureProviderConnections/UsageHistory/CallLogsColumns + hasColumn/hasTable/getTableColumns — schema-column reconciliation split do core.ts, #4948)
|
||||
"secrets", // intentionally-internal: src/instrumentation-node.ts (import() dinâmico na inicialização)
|
||||
"serviceModels", // intentionally-internal: 3 callers (services/modelSync, services/bootstrap, /api/services/9router/models)
|
||||
"stateReset", // db-internal: 3 callers dentro de src/lib/db/ (core, backup, apiKeys) para coordenação de reset
|
||||
"stats", // intentionally-internal: src/app/api/settings/database/refresh-stats/route.ts
|
||||
"tierConfig", // intentionally-internal: open-sse/services/tierResolver.ts (require() dinâmico)
|
||||
"webSessionDedup", // db-internal: importado só por db/providers.ts (webSessionCredentialKey/parseProviderSpecificData — helpers puros de dedup de credencial web-session split do providers.ts, #3368 PR6)
|
||||
]);
|
||||
|
||||
// Alias para retrocompatibilidade com os testes existentes que importam KNOWN_UNEXPORTED.
|
||||
// O comportamento do gate é idêntico — só o nome e os comentários mudaram (#3499).
|
||||
export const KNOWN_UNEXPORTED = INTENTIONALLY_INTERNAL;
|
||||
|
||||
// (c) Leituras de SQL contra bancos EXTERNOS, permitidas por design (#3500).
|
||||
// Esta rota NÃO consulta o DB do OmniRoute (getDbInstance) — ela abre o
|
||||
// SQLite de OUTRO aplicativo (Kiro) para auto-importar credenciais.
|
||||
@@ -97,16 +32,13 @@ export const KNOWN_UNEXPORTED = INTENTIONALLY_INTERNAL;
|
||||
// precisa de entrada aqui: o SQL contra o state.vscdb externo do Cursor vive
|
||||
// em src/lib/cursor/tokenExtractor.ts, fora do escopo desta checagem (que só
|
||||
// varre src/app/api/**/route.ts e open-sse/handlers/*.ts).
|
||||
const EXTERNAL_DB_ALLOWED = new Set([
|
||||
export const EXTERNAL_DB_ALLOWED = new Set([
|
||||
"src/app/api/oauth/kiro/auto-import/route.ts", // read-only no SQLite do Kiro (DB externo)
|
||||
]);
|
||||
|
||||
// Alias de retrocompatibilidade (testes/consumidores que importam KNOWN_RAW_SQL).
|
||||
// Comportamento do gate idêntico — só o nome e o enquadramento mudaram (#3500).
|
||||
const KNOWN_RAW_SQL = EXTERNAL_DB_ALLOWED;
|
||||
|
||||
// Módulos sempre excluídos da checagem (a): não são domínio re-exportável.
|
||||
const DB_MODULE_EXCLUDE = new Set(["core", "localDb", "index"]);
|
||||
export const KNOWN_RAW_SQL = EXTERNAL_DB_ALLOWED;
|
||||
|
||||
function walk(dir, acc = []) {
|
||||
if (!fs.existsSync(dir)) return acc;
|
||||
@@ -118,59 +50,6 @@ function walk(dir, acc = []) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
// Lista os módulos de domínio em src/lib/db (top-level *.ts), excluindo
|
||||
// core/localDb/index, *.d.ts e qualquer subdiretório (migrations/, adapters/, __tests__/).
|
||||
export function collectDbModules(dbDir = DB_DIR) {
|
||||
if (!fs.existsSync(dbDir)) return [];
|
||||
return fs
|
||||
.readdirSync(dbDir, { withFileTypes: true })
|
||||
.filter((e) => e.isFile() && /\.ts$/.test(e.name) && !/\.d\.ts$/.test(e.name))
|
||||
.map((e) => e.name.replace(/\.ts$/, ""))
|
||||
.filter((name) => !DB_MODULE_EXCLUDE.has(name))
|
||||
.sort();
|
||||
}
|
||||
|
||||
// Extrai os nomes de módulo re-exportados de localDb.ts a partir de
|
||||
// `... from "./db/X"` (cobre export {…}, export * e export type {…}).
|
||||
export function extractReexportedModules(localDbSource) {
|
||||
const re = /from\s+["']\.\/db\/([A-Za-z0-9_]+)["']/g;
|
||||
const out = new Set();
|
||||
let m;
|
||||
while ((m = re.exec(localDbSource))) out.add(m[1]);
|
||||
return out;
|
||||
}
|
||||
|
||||
// (a) Módulos db/ que não são re-exportados e não estão na lista de
|
||||
// intencionalmente-internos (INTENTIONALLY_INTERNAL). O gate falha para
|
||||
// qualquer módulo NOVO que não seja re-exportado nem justificado.
|
||||
export function findMissingReexports(dbModules, reexported, allowlist = INTENTIONALLY_INTERNAL) {
|
||||
return dbModules.filter((mod) => !reexported.has(mod) && !allowlist.has(mod));
|
||||
}
|
||||
|
||||
// (b) localDb.ts deve conter SOMENTE import/export + comentários (sem lógica).
|
||||
// Remove comentários e strings, depois procura declarações de runtime.
|
||||
export function hasLogic(localDbSource) {
|
||||
const stripped = localDbSource
|
||||
// comentários de bloco
|
||||
.replace(/\/\*[\s\S]*?\*\//g, "")
|
||||
// comentários de linha
|
||||
.replace(/\/\/[^\n]*/g, "")
|
||||
// template strings
|
||||
.replace(/`(?:\\[\s\S]|[^\\`])*`/g, '""')
|
||||
// strings simples/duplas (paths de import etc.)
|
||||
.replace(/"(?:\\.|[^"\\])*"/g, '""')
|
||||
.replace(/'(?:\\.|[^'\\])*'/g, '""');
|
||||
|
||||
// function/class declaradas, ou atribuição a função (const X = (…) =>, const X = function).
|
||||
const logicPatterns = [
|
||||
/(^|[^.\w])function\s+[A-Za-z_$]/, // function decl (não method .foo())
|
||||
/(^|[^.\w])class\s+[A-Za-z_$]/, // class decl
|
||||
/(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*(?:async\s*)?\(/, // const X = (…) ... (arrow/call)
|
||||
/(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*=\s*(?:async\s+)?function\b/, // const X = function
|
||||
];
|
||||
return logicPatterns.some((rx) => rx.test(stripped));
|
||||
}
|
||||
|
||||
// SQL cru é sempre uma STRING passada a db.prepare()/exec(): casamos os padrões
|
||||
// SÓ dentro de literais de string (não em código JS — `import … from`, `.set(`,
|
||||
// `new Set(`, `delete x` etc. são falsos positivos se varrermos o código todo).
|
||||
@@ -198,7 +77,7 @@ export function extractStringLiterals(code) {
|
||||
// tira as aspas/crases delimitadoras
|
||||
out.push(m[0].slice(1, -1));
|
||||
}
|
||||
return out.join("\n | ||||