mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-10 08:05:14 +02:00
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:
parent
721f1a998f
commit
bcb0d2f8f7
29 changed files with 818 additions and 220 deletions
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import {
|
|||
startLiveMemoryFlowTui,
|
||||
type KtxMemoryFlowTuiIo,
|
||||
type MemoryFlowInkInstance,
|
||||
type MemoryFlowInkRenderOptions,
|
||||
} from './memory-flow-tui.js';
|
||||
|
||||
function replayInput(): MemoryFlowReplayInput {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
import { resolve } from 'node:path';
|
||||
|
||||
export function resolveProjectDir(projectDir?: string, fallback = '.'): string {
|
||||
return resolve(projectDir ?? fallback);
|
||||
}
|
||||
|
|
@ -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 =
|
||||
| {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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` });
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue