chore(workspace): gate dead-code with knip production mode

Turn on production-mode knip plus an autofix run in pre-commit and the
`pnpm dead-code` script, document the `/** @internal */` convention for
test-only exports in AGENTS.md, annotate test-only exports across the
CLI with that JSDoc, and drop dead exports/wrappers the new gate
surfaced (e.g. `cli-project.ts`, `lookerRuntimeSourceToFileAdapterSource`,
`createLocalScanEnrichmentProvidersFromConfig`,
`PGLITE_OWNER_PROCESS_BACKEND_CAPABILITIES`, stale type re-exports).
Replace the loose `ignoreIssues` allowlist in `knip.json` with explicit
production entries so cross-package barrel leaks are caught.
This commit is contained in:
Andrey Avtomonov 2026-05-21 11:35:41 +02:00
parent ac3885b652
commit b690e6988b
71 changed files with 211 additions and 279 deletions

View file

@ -3,6 +3,7 @@ import { join, relative } from 'node:path';
const YAML_EXT_RE = /\.(ya?ml)$/i;
/** @internal */
export function normalizeDbtPath(path: string): string {
return path.replaceAll('\\', '/');
}

View file

@ -1,13 +1,14 @@
import { Buffer } from 'node:buffer';
import type { StagedPatternsInput } from './types.js';
export const HISTORIC_SQL_PATTERN_WORKUNIT_DIR = 'patterns-input';
const HISTORIC_SQL_PATTERN_WORKUNIT_DIR = 'patterns-input';
/** @internal */
export const HISTORIC_SQL_PATTERN_WORKUNIT_MAX_BYTES = 110_000;
export const HISTORIC_SQL_PATTERN_WORKUNIT_PATH_RE = /^patterns-input\/part-\d{4}\.json$/;
const HISTORIC_SQL_PATTERN_WORKUNIT_PATH_RE = /^patterns-input\/part-\d{4}\.json$/;
type PatternTemplate = StagedPatternsInput['templates'][number];
export interface HistoricSqlPatternInputShard {
interface HistoricSqlPatternInputShard {
path: string;
input: StagedPatternsInput;
byteLength: number;
@ -27,10 +28,11 @@ export function isHistoricSqlPatternInputShardPath(path: string): boolean {
return HISTORIC_SQL_PATTERN_WORKUNIT_PATH_RE.test(path);
}
export function serializeStagedPatternsInput(input: StagedPatternsInput): string {
function serializeStagedPatternsInput(input: StagedPatternsInput): string {
return `${JSON.stringify(input, null, 2)}\n`;
}
/** @internal */
export function serializedStagedPatternsInputByteLength(input: StagedPatternsInput): number {
return Buffer.byteLength(serializeStagedPatternsInput(input), 'utf-8');
}

View file

@ -33,6 +33,7 @@ function tableSortKey(table: KtxTableRef): string {
return `${table.catalog ?? ''}\u0000${table.db ?? ''}\u0000${table.name}`;
}
/** @internal */
export function liveDatabaseTablePath(table: KtxTableRef): string {
return `${LIVE_DATABASE_TABLES_DIR}/${encodePathPart(table.catalog)}.${encodePathPart(table.db)}.${encodePathPart(
table.name,

View file

@ -1,11 +1,11 @@
import { mkdir, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';
import type { ParsedTargetTable } from '../../parsed-target-table.js';
import type { FetchContext } from '../../types.js';
import { writeLookerEvidenceDocuments } from './evidence-documents.js';
import { writeLookerFetchReport } from './fetch-report.js';
import {
type LookerPullConfig,
type ParsedTargetTable,
parseLookerPullConfig,
STAGED_FILES,
type StagedDashboardFile,

View file

@ -1,11 +1,6 @@
import type { ParsedTargetTable } from '../../parsed-target-table.js';
import type { LookerWarehouseConnectionInfo } from './client.js';
import type {
LookerPullConfig,
LookerRuntimeCursors,
ParsedTargetTable,
StagedExploreFile,
StagedLookmlModelsFile,
} from './types.js';
import type { LookerPullConfig, LookerRuntimeCursors, StagedExploreFile, StagedLookmlModelsFile } from './types.js';
export const LOOKER_DIALECT_TO_CONNECTION_TYPE = {
bigquery: 'BIGQUERY',

View file

@ -1,17 +1,5 @@
import { describe, expect, it } from 'vitest';
import { buildLookerReconcileNotes, lookerRuntimeSourceToFileAdapterSource } from './reconcile.js';
describe('lookerRuntimeSourceToFileAdapterSource', () => {
it('maps API-derived Looker source names to file-adapter source names', () => {
expect(lookerRuntimeSourceToFileAdapterSource('looker__b2b__sales_pipeline')).toBe('b2b__sales_pipeline');
expect(lookerRuntimeSourceToFileAdapterSource('looker__finance__orders')).toBe('finance__orders');
});
it('ignores non-Looker and malformed source names', () => {
expect(lookerRuntimeSourceToFileAdapterSource('b2b__sales_pipeline')).toBeNull();
expect(lookerRuntimeSourceToFileAdapterSource('looker__missing_explore')).toBeNull();
});
});
import { buildLookerReconcileNotes } from './reconcile.js';
describe('buildLookerReconcileNotes', () => {
it('instructs reconciliation to record subsumed provenance', () => {

View file

@ -1,16 +1,3 @@
export function lookerRuntimeSourceToFileAdapterSource(sourceName: string): string | null {
if (!sourceName.startsWith('looker__')) {
return null;
}
const stripped = sourceName.slice('looker__'.length);
const parts = stripped.split('__');
if (parts.length < 2 || parts.some((part) => part.length === 0)) {
return null;
}
const [model, ...exploreParts] = parts;
return `${model}__${exploreParts.join('__')}`;
}
export function buildLookerReconcileNotes(): string[] {
return [
[

View file

@ -1,7 +1,8 @@
import { tool } from 'ai';
import { z } from 'zod';
import type { ToolOutput } from '../../../../tools/index.js';
import { type ParsedTargetTable, stagedLookerQuerySchema } from '../types.js';
import type { ParsedTargetTable } from '../../../parsed-target-table.js';
import { stagedLookerQuerySchema } from '../types.js';
const lookerUsageInputSchema = z.object({
queryCount30d: z.number().int().nonnegative().default(0),

View file

@ -1,8 +1,8 @@
import { describe, expect, it } from 'vitest';
import { parsedTargetTableSchema } from '../../parsed-target-table.js';
import {
lookerPullConfigSchema,
parseLookerPullConfig,
parsedTargetTableSchema,
stagedDashboardFileSchema,
stagedExploreFileSchema,
stagedLookerFetchIssueSchema,

View file

@ -7,8 +7,6 @@ const nullableLookerIdSchema = z.union([lookerIdSchema, z.null()]).default(null)
export const lookerConnectionIdSchema = z.string().min(1).regex(/^[A-Za-z0-9_-]+$/);
export { parsedTargetTableSchema, type ParsedTargetTable } from '../../parsed-target-table.js';
export const lookerRuntimeCursorsSchema = z.object({
dashboardsLastSyncedAt: z.iso.datetime().nullable().default(null),
looksLastSyncedAt: z.iso.datetime().nullable().default(null),
@ -16,6 +14,7 @@ export const lookerRuntimeCursorsSchema = z.object({
export type LookerRuntimeCursors = z.infer<typeof lookerRuntimeCursorsSchema>;
/** @internal */
export const lookerPullConfigSchema = z.object({
lookerConnectionId: lookerConnectionIdSchema.optional(),
instanceBaseUrl: z.url().optional(),

View file

@ -4,7 +4,9 @@ import * as z from 'zod';
import type { SourceFetchReport } from '../../types.js';
import type { ParsedLookmlProject } from './parse.js';
/** @internal */
export const LOOKML_FETCH_REPORT_FILE = 'lookml-fetch-report.json';
/** @internal */
export const LOOKML_MISMATCHED_MODELS_FILE = 'lookml-mismatched-models.json';
const fetchIssueKindSchema = z.enum([

View file

@ -1,7 +1,7 @@
import * as z from 'zod';
import { parsedTargetTableSchema } from '../../parsed-target-table.js';
export const lookmlPullConfigSchema = z.object({
const lookmlPullConfigSchema = z.object({
repoUrl: z.string().url(),
branch: z.string().default('main'),
path: z.string().nullable().default(null),

View file

@ -6,6 +6,7 @@ const CARD_REF_RE = /\{\{#(\d+)\}\}/g;
* Input TemplateTag shape mirrors `MetabaseClient.getTemplateTags` output. We keep the
* shape loose only `name`, `type`, and optional `cardReference`/`default` are needed here.
*/
/** @internal */
export interface InputTemplateTag {
name: string;
type: string;
@ -13,6 +14,7 @@ export interface InputTemplateTag {
defaultValue?: string | null;
}
/** @internal */
export function extractReferencedCardIds(templateTags: InputTemplateTag[], sql: string): number[] {
const ids = new Set<number>();
for (const tag of templateTags) {
@ -34,7 +36,7 @@ export function extractReferencedCardIds(templateTags: InputTemplateTag[], sql:
* care about. The adapter reads whatever the client returns; this helper stays
* duck-typed so the client's type can evolve without churn here.
*/
export interface InputCard {
interface InputCard {
id: number;
name: string;
description?: string | null;

View file

@ -1,6 +1,6 @@
import { z } from 'zod';
export const metabaseSyncModeSchema = z.enum(['ALL', 'ONLY', 'EXCEPT']);
const metabaseSyncModeSchema = z.enum(['ALL', 'ONLY', 'EXCEPT']);
export type MetabaseSyncMode = z.infer<typeof metabaseSyncModeSchema>;
export const metabaseLocalConnectionIdSchema = z.string().regex(/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/);
@ -25,7 +25,7 @@ export function parseMetabasePullConfig(raw: unknown): MetabasePullConfig {
}
/** A Metabase column from `card.result_metadata`. Mirrors what the LLM consumes today. */
export const stagedResultColumnSchema = z.object({
const stagedResultColumnSchema = z.object({
name: z.string(),
display_name: z.string().optional().nullable(),
base_type: z.string(),
@ -37,7 +37,7 @@ export const stagedResultColumnSchema = z.object({
export type StagedResultColumn = z.infer<typeof stagedResultColumnSchema>;
export const stagedParameterSchema = z.object({
const stagedParameterSchema = z.object({
id: z.string(),
name: z.string(),
type: z.string(),
@ -49,7 +49,7 @@ export const stagedParameterSchema = z.object({
export type StagedParameter = z.infer<typeof stagedParameterSchema>;
/** A template tag pulled from an MBQL card's `dataset_query.stages[0].template-tags`. */
export const stagedTemplateTagSchema = z.object({
const stagedTemplateTagSchema = z.object({
name: z.string(),
type: z.string(),
defaultValue: z.string().optional().nullable(),
@ -87,7 +87,7 @@ export const stagedCardFileSchema = z.object({
export type StagedCardFile = z.infer<typeof stagedCardFileSchema>;
/** A serialized collection file, `collections/<id>.json`. Minimal — path lives on the card. */
export const stagedCollectionFileSchema = z.object({
const stagedCollectionFileSchema = z.object({
metabaseId: z.union([z.number().int(), z.literal('root')]),
name: z.string(),
parentId: z.union([z.number().int(), z.literal('root')]).nullable(),
@ -96,7 +96,7 @@ export const stagedCollectionFileSchema = z.object({
export type StagedCollectionFile = z.infer<typeof stagedCollectionFileSchema>;
/** A serialized database-mapping snapshot, `databases/<id>.json`. */
export const stagedDatabaseFileSchema = z.object({
const stagedDatabaseFileSchema = z.object({
metabaseDatabaseId: z.number().int().positive(),
metabaseDatabaseName: z.string(),
metabaseEngine: z.string().nullable(),

View file

@ -2,7 +2,7 @@ import { readdir, readFile } from 'node:fs/promises';
import { join, relative } from 'node:path';
import { parse as parseYaml } from 'yaml';
export interface ParsedMetricFlowSemanticModel {
interface ParsedMetricFlowSemanticModel {
/** Path relative to stagedDir, e.g. "models/orders.yml". */
path: string;
/** `name:` on the semantic_model. */
@ -24,9 +24,9 @@ export interface ParsedMetricFlowSemanticModel {
defaultTimeDimension: string | null;
}
export type MetricFlowMetricType = 'simple' | 'derived' | 'cumulative' | 'ratio' | 'conversion';
type MetricFlowMetricType = 'simple' | 'derived' | 'cumulative' | 'ratio' | 'conversion';
export interface ParsedMetricFlowMetric {
interface ParsedMetricFlowMetric {
path: string;
name: string;
type: MetricFlowMetricType;

View file

@ -5,6 +5,7 @@ import { kmeans, pickK } from '../../clustering/kmeans.js';
import type { WorkUnit } from '../../types.js';
import { notionMetadataSchema } from './types.js';
/** @internal */
export const MIN_PAGES_TO_CLUSTER = 5;
const CLUSTER_TEXT_BODY_CHARS = 1024;
const CLUSTER_SEED = 42;

View file

@ -14,6 +14,7 @@ function richTextToMarkdown(value: unknown): string {
.trim();
}
/** @internal */
export function propertyValueToText(value: unknown): string {
if (!value || typeof value !== 'object' || !('type' in value)) {
return '';

View file

@ -89,6 +89,7 @@ function shouldRetryNotionError(error: unknown): boolean {
return code === 'rate_limited' || transientErrorCodes.has(code ?? '') || transientStatusCodes.has(status ?? 0);
}
/** @internal */
export async function retryNotionRequest<T>(operation: () => Promise<T>, options: RetryOptions = {}): Promise<T> {
const maxAttempts = options.maxAttempts ?? 4;
const sleep = options.sleep ?? defaultSleep;