chore: add TypeScript dead-code checks (#60)

* chore: add TypeScript dead-code checks

* chore: trim stale Knip ignores

* Fix CI smoke and artifact checks
This commit is contained in:
Andrey Avtomonov 2026-05-13 13:33:28 +02:00 committed by GitHub
parent 721f1a998f
commit bcb0d2f8f7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 818 additions and 220 deletions

View file

@ -188,7 +188,7 @@ export function registerConnectionCommands(program: Command, context: KtxCliComm
registerConnectionNotionCommands(connection, context);
}
export function registerConnectionMappingCommands(connection: Command, context: KtxCliCommandContext): void {
function registerConnectionMappingCommands(connection: Command, context: KtxCliCommandContext): void {
const mapping = connection
.command('mapping')
.description('Manage Metabase warehouse mappings')

View file

@ -369,14 +369,6 @@ function setExpanded(state: PickerState, nodeId: string, value: boolean | 'toggl
return cloneState(state, { expanded });
}
function expandPath(state: PickerState, nodeId: string): PickerState {
const expanded = new Set(state.expanded);
for (const ancestorId of ancestorsOf(nodeId, state.byId)) {
expanded.add(ancestorId);
}
return cloneState(state, { expanded });
}
export function moveCursor(state: PickerState, dir: 'up' | 'down' | 'left' | 'right'): PickerState {
const node = state.byId.get(state.cursorId);
if (!node) {

View file

@ -1,6 +1,6 @@
/* @jsxImportSource react */
import { render as renderInkTest } from 'ink-testing-library';
import React, { act, type ReactNode } from 'react';
import { act, type ReactNode } from 'react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { buildInitialState, buildPickerTree, type NotionPickerPageInput } from './connection-notion-tree.js';
import {

View file

@ -1,6 +1,6 @@
/* @jsxImportSource react */
import { Box, Text, render as renderInkRuntime, useApp, useInput } from 'ink';
import React, { type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import {
filterTree,
flattenSelection,

View file

@ -1,4 +1,4 @@
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { mkdtemp, rm } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

View file

@ -1,10 +1,8 @@
import { EventEmitter } from 'node:events';
import { access, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { AgentRunnerService, type RunLoopParams } from '@ktx/context/agent';
import {
LocalLookerRuntimeStore,
LocalMetabaseSourceStateReader,
MetabaseSourceAdapter,
getLocalIngestStatus,
@ -12,12 +10,10 @@ import {
type FetchContext,
type IngestReportSnapshot,
type LocalIngestResult,
type LocalMetabaseFanoutProgress,
type LookerMappingClient,
type LookerRuntimeClient,
type LookerTableIdentifierParser,
type MemoryFlowEventSink,
type MemoryFlowReplayInput,
type MetabaseCard,
type MetabaseCardSummary,
type MetabaseClientFactory,
@ -28,7 +24,7 @@ import {
} from '@ktx/context/ingest';
import { ktxLocalStateDbPath, loadKtxProject } from '@ktx/context/project';
import { expect, vi } from 'vitest';
import { type KtxIngestArgs, runKtxIngest } from './ingest.js';
import { runKtxIngest } from './ingest.js';
export function makeIo(
options: {

View file

@ -4,10 +4,8 @@ import { join } from 'node:path';
import {
LocalLookerRuntimeStore,
LocalMetabaseSourceStateReader,
getLocalIngestStatus,
type LocalIngestResult,
type LocalMetabaseFanoutProgress,
type MemoryFlowReplayInput,
type RunLocalIngestOptions,
type SourceAdapter,
} from '@ktx/context/ingest';
@ -20,7 +18,6 @@ import {
CliMetabaseAgentRunner,
CliMetabaseSourceAdapter,
completedLocalBundleRun,
emitLiveLocalMemoryFlow,
failedLocalBundleRun,
localFakeBundleReport,
makeCliLookerParser,
@ -28,7 +25,6 @@ import {
makeIo,
persistLocalBundleReport,
runPublicMetabaseSyncModeCase,
writeBundleReportFile,
writeMetabaseConfig,
writeWarehouseConfig,
} from './ingest.test-utils.js';

View file

@ -1,7 +1,7 @@
/* @jsxImportSource react */
import type { MemoryFlowEvent, MemoryFlowReplayInput } from '@ktx/context/ingest/memory-flow';
import { Box, Text } from 'ink';
import React, { type ReactNode } from 'react';
import { type ReactNode } from 'react';
import { buildDemoMetrics, formatCost, formatDuration } from './demo-metrics.js';
import { formatNextStepLines } from './next-steps.js';
import { profileMark } from './startup-profile.js';
@ -38,45 +38,6 @@ function isPrepopulatedDemoReplay(input: MemoryFlowReplayInput): boolean {
return input.metadata?.origin === 'packaged' || input.metadata?.timing === 'prebuilt';
}
function flowLine(width: number, frame: number, active: boolean): string {
if (!active) return '━'.repeat(width);
const pulse = ['░', '▒', '▓', '█', '█', '█', '▓', '▒', '░'];
const pw = pulse.length;
const chars: string[] = [];
const offset = (frame * 2) % (width + pw);
for (let i = 0; i < width; i += 1) {
const p = i - offset + pw;
chars.push(p >= 0 && p < pw ? (pulse[p] ?? '━') : '━');
}
return chars.join('');
}
function brailleFlow(width: number, frame: number): string {
// Braille unicode: U+2800 + dot bitmask
// Dots: 1=0x01 2=0x02 3=0x04 4=0x08 5=0x10 6=0x20 7=0x40 8=0x80
// Layout: col0=[1,2,3,7] col1=[4,5,6,8]
const chars: string[] = [];
for (let i = 0; i < width; i += 1) {
const density = (i + 1) / width;
const phase = (i * 3 + frame * 2) % 12;
let dots = 0;
// Sparse diagonal streams on the left, dense on the right
// Each "stream" is a diagonal line of dots moving rightward
if ((phase + 0) % 4 < density * 4) dots |= 0x01; // dot 1
if ((phase + 1) % 5 < density * 4) dots |= 0x08; // dot 4
if ((phase + 2) % 4 < density * 3) dots |= 0x02; // dot 2
if ((phase + 3) % 5 < density * 3) dots |= 0x10; // dot 5
if ((phase + 4) % 4 < density * 2.5) dots |= 0x04; // dot 3
if ((phase + 5) % 5 < density * 2.5) dots |= 0x20; // dot 6
if ((phase + 1) % 6 < density * 2) dots |= 0x40; // dot 7
if ((phase + 3) % 6 < density * 2) dots |= 0x80; // dot 8
chars.push(String.fromCharCode(0x2800 + dots));
}
return chars.join('');
}
function progressBarOverall(
finishedCount: number,
activeCount: number,
@ -104,43 +65,6 @@ function progressBarOverall(
return finished + activeChars.join('') + '░'.repeat(queuedWidth);
}
function sparkleWipe(width: number, frame: number, row: number): string {
const chars: string[] = [];
const sweepPos = (frame * 2 + row * 6) % (width + 8);
const sparkles = ['✨', '✦', '✧', '·'];
for (let i = 0; i < width; i += 1) {
const dist = i - sweepPos;
if (dist < -6) {
const t = (i * 11 + row * 5 + frame * 3) % 10;
chars.push(t === 0 ? sparkles[0]! : t === 3 ? sparkles[1]! : t === 7 ? sparkles[2]! : ' ');
} else if (dist < -3) {
const t = (i + frame) % 3;
chars.push(t === 0 ? sparkles[1]! : t === 1 ? sparkles[2]! : sparkles[3]!);
} else if (dist <= 0) {
const gradient = ['░', '▒', '▓', '█'];
chars.push(gradient[Math.min(3, dist + 3)] ?? '█');
} else if (dist <= 2) {
chars.push(dist === 1 ? '▓' : '▒');
} else {
const noise = (i * 31 + row * 17 + frame * 3) % 5;
const messy = ['░', '▒', '▓', '▒', '░'];
chars.push(messy[noise] ?? '▒');
}
}
return chars.join('');
}
function activityWave(width: number, frame: number, offset: number): string {
const heights = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
const chars: string[] = [];
for (let i = 0; i < width; i += 1) {
const wave = Math.sin(((i * 2 + frame + offset * 5) * Math.PI) / 6);
const idx = Math.round(((wave + 1) / 2) * (heights.length - 1));
chars.push(heights[idx] ?? '▁');
}
return chars.join('');
}
function topicName(key: string): string {
return (key.split('/').pop()?.replace(/\.md$/, '') ?? key).replace(/[_-]/g, ' ');
}
@ -155,18 +79,9 @@ function humanizeInsight(key: string, target: 'sl' | 'wiki', summary: string | u
return target === 'sl' ? `Query definition: ${name}` : `Knowledge page: ${name}`;
}
const ADAPTER_PREFIXES = ['live_database_', 'metabase_', 'looker_', 'lookml_', 'metricflow_', 'notion_', 'historic_sql_', 'dbt_descriptions_'];
const INTERNAL_DEMO_CONNECTION_ID = 'orbit_demo';
const PUBLIC_DEMO_SOURCE_LABEL = 'Orbit Demo';
function humanizeUnitKey(unitKey: string): string {
let key = unitKey.replace(/-/g, '_');
for (const prefix of ADAPTER_PREFIXES) {
if (key.startsWith(prefix)) { key = key.slice(prefix.length); break; }
}
return key.replace(/_/g, ' ');
}
interface SourceInfo {
type: string;
name: string;
@ -224,13 +139,6 @@ function sourceDescription(input: MemoryFlowReplayInput): SourceInfo {
return { type: info.type, name: conn, sourceCount: count, itemNounPlural: info.plural, readingVerb: info.verb, ingestDescription: info.description };
}
function activeWorkUnit(
input: MemoryFlowReplayInput,
): { unitKey: string; stepIndex: number; stepBudget: number } | null {
const units = activeWorkUnits(input);
return units.at(-1) ?? null;
}
function activeWorkUnits(
input: MemoryFlowReplayInput,
): Array<{ unitKey: string; stepIndex: number; stepBudget: number }> {
@ -299,22 +207,6 @@ function finishedUnits(input: MemoryFlowReplayInput): Array<{ unitKey: string; a
return units;
}
function artifactCounts(input: MemoryFlowReplayInput): { sl: number; wiki: number } {
let sl = 0;
let wiki = 0;
for (const e of input.events) {
if (e.type === 'candidate_action') {
if (e.target === 'sl') sl++;
else wiki++;
}
}
return { sl, wiki };
}
function pad(str: string, width: number): string {
return str.length >= width ? str : str + ' '.repeat(width - str.length);
}
const KTX_LOGO_SMALL = [
'██╗ ██╗████████╗██╗ ██╗',
'██║ ██╔╝╚══██╔══╝╚██╗██╔╝',
@ -344,12 +236,7 @@ export function Hud(props: {
width: number;
now?: () => number;
}): ReactNode {
const isRunning = props.input.status === 'running';
const isDone = props.input.status === 'done';
const isFlowing = isRunning && hasWorkStarted(props.input);
const src = sourceDescription(props.input);
const counts = artifactCounts(props.input);
const metrics = buildDemoMetrics(props.input, props.now ? { now: props.now } : {});
const workStarted = hasWorkStarted(props.input);
@ -358,11 +245,6 @@ export function Hud(props: {
const innerWidth = Math.max(60, props.width - 6);
const actives = activeWorkUnits(props.input);
const reconEvent = props.input.events.find((e) => e.type === 'reconciliation_finished');
const allAnalyzed = isFlowing && actives.length === 0;
const isReconciling = allAnalyzed && !reconEvent && !isDone;
const hLine = '─'.repeat(innerWidth);
const elapsed = formatDuration(metrics.elapsedMs);
@ -429,7 +311,6 @@ export function ActivityFeed(props: {
const workStarted = hasWorkStarted(props.input);
const totalChunks = planEvent?.chunkCount ?? 0;
const finishedWithArtifacts = finished.filter((u) => u.artifactCount > 0);
const finishedAreas = totalChunks > 0 ? Math.min(finished.length, totalChunks) : finished.length;
const allWorkDone = workStarted && actives.length === 0 && queued.length === 0;
const isReconciling = allWorkDone && !reconEvent && !isDone && !isError;

View file

@ -11,7 +11,6 @@ import {
startLiveMemoryFlowTui,
type KtxMemoryFlowTuiIo,
type MemoryFlowInkInstance,
type MemoryFlowInkRenderOptions,
} from './memory-flow-tui.js';
function replayInput(): MemoryFlowReplayInput {

View file

@ -1,7 +1,6 @@
/* @jsxImportSource react */
import {
buildMemoryFlowViewModel,
buildMemoryFlowVisualModel,
createInitialMemoryFlowInteractionState,
findMemoryFlowSearchMatches,
type MemoryFlowColumnId,
@ -14,8 +13,7 @@ import {
selectedMemoryFlowDetails,
} from '@ktx/context/ingest';
import { Box, Text, render as renderInkRuntime, useApp, useInput } from 'ink';
import React, { type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import { buildDemoMetrics } from './demo-metrics.js';
import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react';
import {
ActivityFeed,
Hud,
@ -201,14 +199,6 @@ function stageLabel(columnId: MemoryFlowColumnId): string {
return STAGE_LABELS[columnId];
}
function statusLabel(status: string): 'OK' | 'RUN' | 'WARN' | 'FAIL' | 'WAIT' {
if (status === 'complete') return 'OK';
if (status === 'active') return 'RUN';
if (status === 'warning') return 'WARN';
if (status === 'failed') return 'FAIL';
return 'WAIT';
}
function filterLabel(filter: MemoryFlowInteractionState['filter']): string {
return filter === 'failed_or_flagged' ? 'issues' : 'all';
}
@ -325,7 +315,6 @@ export function MemoryFlowTuiApp(props: MemoryFlowTuiAppProps): ReactNode {
const view = useMemo(() => buildMemoryFlowViewModel(pacedInput), [pacedInput]);
const [state, setState] = useState<MemoryFlowInteractionState>(() => createInitialMemoryFlowInteractionState(view));
const [frame, setFrame] = useState(0);
const [thoughtFrame, setThoughtFrame] = useState(0);
const [completionFrame, setCompletionFrame] = useState(0);
const [holdComplete, setHoldComplete] = useState(false);
const [userHasNavigated, setUserHasNavigated] = useState(false);
@ -346,7 +335,6 @@ export function MemoryFlowTuiApp(props: MemoryFlowTuiAppProps): ReactNode {
useEffect(() => {
const timer = setInterval(() => {
setFrame((current) => current + 1);
setThoughtFrame((current) => current + 1);
}, props.frameMs ?? DEFAULT_TUI_TIMING.frameMs);
return () => clearInterval(timer);
}, [props.frameMs]);
@ -354,7 +342,6 @@ export function MemoryFlowTuiApp(props: MemoryFlowTuiAppProps): ReactNode {
useEffect(() => {
if (lastEventCountRef.current !== pacedInput.events.length) {
lastEventCountRef.current = pacedInput.events.length;
setThoughtFrame(0);
}
}, [pacedInput.events.length]);
@ -409,10 +396,6 @@ export function MemoryFlowTuiApp(props: MemoryFlowTuiAppProps): ReactNode {
});
const isComplete = pacedInput.status === 'done' || pacedInput.status === 'error';
const completionMetrics = useMemo(
() => buildDemoMetrics(pacedInput, pacedNow ? { now: pacedNow } : {}),
[pacedInput, pacedNow],
);
const termWidth = props.terminalWidth ?? 80;

View file

@ -1,5 +0,0 @@
import { resolve } from 'node:path';
export function resolveProjectDir(projectDir?: string, fallback = '.'): string {
return resolve(projectDir ?? fallback);
}

View file

@ -6,9 +6,9 @@ import { profileMark } from './startup-profile.js';
profileMark('module:public-ingest');
export type KtxPublicIngestStepName = 'scan' | 'source-ingest' | 'enrich' | 'memory-update';
export type KtxPublicIngestStepStatus = 'done' | 'skipped' | 'failed' | 'not-run';
export type KtxPublicIngestInputMode = 'auto' | 'disabled';
type KtxPublicIngestStepName = 'scan' | 'source-ingest' | 'enrich' | 'memory-update';
type KtxPublicIngestStepStatus = 'done' | 'skipped' | 'failed' | 'not-run';
type KtxPublicIngestInputMode = 'auto' | 'disabled';
export type KtxPublicIngestArgs =
| {

View file

@ -474,16 +474,6 @@ async function markContextComplete(projectDir: string): Promise<void> {
await markKtxSetupStateStepComplete(projectDir, 'context');
}
function writeBuildHeader(projectDir: string, runId: string, io: KtxCliIo): void {
const commands = contextBuildCommands(projectDir, runId);
io.stdout.write('\nKTX context build\n');
io.stdout.write(`Run: ${runId}\n`);
io.stdout.write(`Project: ${resolve(projectDir)}\n\n`);
io.stdout.write('Detach: press d to leave this running.\n');
io.stdout.write(`Resume: ${commands.watch}\n`);
io.stdout.write(`Status: ${commands.status}\n\n`);
}
function writeMissingCapabilities(missing: string[], io: KtxCliIo): void {
io.stderr.write('KTX cannot build agent-ready context yet.\n\n');
io.stderr.write('Missing:\n');

View file

@ -126,10 +126,6 @@ async function writeSqliteScanConfig(projectDir: string, dbPath: string, enrich
);
}
function parseJsonOutput<T>(stdout: string): T {
return JSON.parse(stdout) as T;
}
function expectProjectStderr(result: CliResult, projectDir: string): void {
expect(result).toMatchObject({ code: 0, stderr: `Project: ${projectDir}\n` });
}