From 57bdc0a19ac52554663e908e6010f8f74cb07b78 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Sat, 16 May 2026 02:13:59 +0200 Subject: [PATCH] refactor(cli): rename text ingest memory port --- packages/cli/src/text-ingest.test.ts | 74 ++++++++++++++-------------- packages/cli/src/text-ingest.ts | 36 +++++++------- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/packages/cli/src/text-ingest.test.ts b/packages/cli/src/text-ingest.test.ts index 55dbe9e3..04ff2e40 100644 --- a/packages/cli/src/text-ingest.test.ts +++ b/packages/cli/src/text-ingest.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it, vi } from 'vitest'; -import type { MemoryCaptureStatus } from '@ktx/context/memory'; +import type { MemoryIngestStatus } from '@ktx/context/memory'; import type { KtxLocalProject } from '@ktx/context/project'; -import { runKtxTextIngest, type TextMemoryCapturePort } from './text-ingest.js'; +import { runKtxTextIngest, type TextMemoryIngestPort } from './text-ingest.js'; function makeIo(options: { isTTY?: boolean } = {}) { let stdout = ''; @@ -25,18 +25,18 @@ function makeIo(options: { isTTY?: boolean } = {}) { }; } -function fakeCapture( +function fakeIngest( options: { failRunIds?: Set; missingStatusRunIds?: Set; events?: string[]; } = {}, -): TextMemoryCapturePort { +): TextMemoryIngestPort { let next = 1; return { - capture: vi.fn(async () => { + ingest: vi.fn(async () => { const runId = `run-${next++}`; - options.events?.push(`capture:${runId}`); + options.events?.push(`ingest:${runId}`); return { runId }; }), waitForRun: vi.fn(async (runId: string) => { @@ -51,26 +51,26 @@ function fakeCapture( return { runId, status: 'error', - stage: 'capturing', + stage: 'ingesting', done: true, captured: { wiki: [], sl: [], xrefs: [] }, error: `${runId} failed`, commitHash: null, skillsLoaded: [], signalDetected: false, - } satisfies MemoryCaptureStatus; + } satisfies MemoryIngestStatus; } return { runId, status: 'done', - stage: 'capturing', + stage: 'ingesting', done: true, captured: { wiki: [`wiki-${runId}`], sl: [`sl-${runId}`], xrefs: [] }, error: null, commitHash: `commit-${runId}`, skillsLoaded: ['wiki_capture', 'sl'], signalDetected: true, - } satisfies MemoryCaptureStatus; + } satisfies MemoryIngestStatus; }), }; } @@ -80,11 +80,11 @@ function fakeProject(projectDir = '/tmp/project'): KtxLocalProject { } describe('runKtxTextIngest', () => { - it('captures repeated inline text sequentially with generated internal chat ids', async () => { + it('ingests repeated inline text sequentially with generated internal chat ids', async () => { const io = makeIo(); const events: string[] = []; - const capture = fakeCapture({ events }); - const createMemoryCapture = vi.fn(() => capture); + const ingest = fakeIngest({ events }); + const createMemoryIngest = vi.fn(() => ingest); await expect( runKtxTextIngest( @@ -99,14 +99,14 @@ describe('runKtxTextIngest', () => { io.io, { loadProject: vi.fn(async () => fakeProject()), - createMemoryCapture, + createMemoryIngest, now: () => 1_700_000_000_000, }, ), ).resolves.toBe(0); - expect(createMemoryCapture).toHaveBeenCalledWith({ projectDir: '/tmp/project' }); - expect(capture.capture).toHaveBeenNthCalledWith( + expect(createMemoryIngest).toHaveBeenCalledWith({ projectDir: '/tmp/project' }); + expect(ingest.ingest).toHaveBeenNthCalledWith( 1, expect.objectContaining({ userId: 'local-cli', @@ -116,7 +116,7 @@ describe('runKtxTextIngest', () => { sourceType: 'external_ingest', }), ); - expect(capture.capture).toHaveBeenNthCalledWith( + expect(ingest.ingest).toHaveBeenNthCalledWith( 2, expect.objectContaining({ chatId: 'cli-text-ingest-1700000000000-2', @@ -124,8 +124,8 @@ describe('runKtxTextIngest', () => { assistantMessage: 'Orders are completed purchases.', }), ); - expect(capture.capture).not.toHaveBeenCalledWith(expect.objectContaining({ connectionId: expect.anything() })); - expect(events).toEqual(['capture:run-1', 'wait:run-1', 'status:run-1', 'capture:run-2', 'wait:run-2', 'status:run-2']); + expect(ingest.ingest).not.toHaveBeenCalledWith(expect.objectContaining({ connectionId: expect.anything() })); + expect(events).toEqual(['ingest:run-1', 'wait:run-1', 'status:run-1', 'ingest:run-2', 'wait:run-2', 'status:run-2']); expect(JSON.parse(io.stdout())).toMatchObject({ status: 'done', results: [ @@ -147,7 +147,7 @@ describe('runKtxTextIngest', () => { it('loads files and stdin as batch items and passes a global connection id', async () => { const io = makeIo(); - const capture = fakeCapture(); + const ingest = fakeIngest(); await expect( runKtxTextIngest( @@ -163,7 +163,7 @@ describe('runKtxTextIngest', () => { io.io, { loadProject: vi.fn(async () => fakeProject()), - createMemoryCapture: vi.fn(() => capture), + createMemoryIngest: vi.fn(() => ingest), readFile: vi.fn(async (path) => `file:${path}`), readStdin: vi.fn(async () => 'stdin content'), now: () => 10, @@ -171,7 +171,7 @@ describe('runKtxTextIngest', () => { ), ).resolves.toBe(0); - expect(capture.capture).toHaveBeenNthCalledWith( + expect(ingest.ingest).toHaveBeenNthCalledWith( 1, expect.objectContaining({ connectionId: 'warehouse', @@ -180,7 +180,7 @@ describe('runKtxTextIngest', () => { assistantMessage: 'file:/tmp/docs/revenue.md', }), ); - expect(capture.capture).toHaveBeenNthCalledWith( + expect(ingest.ingest).toHaveBeenNthCalledWith( 2, expect.objectContaining({ connectionId: 'warehouse', @@ -194,9 +194,9 @@ describe('runKtxTextIngest', () => { expect(io.stdout()).toContain('stdin'); }); - it('uses bounded inline text previews as labels in plain output and capture metadata', async () => { + it('uses bounded inline text previews as labels in plain output and ingest metadata', async () => { const io = makeIo(); - const capture = fakeCapture(); + const ingest = fakeIngest(); const longText = `This inline note is intentionally long ${'x'.repeat(120)}`; await expect( @@ -212,7 +212,7 @@ describe('runKtxTextIngest', () => { io.io, { loadProject: vi.fn(async () => fakeProject()), - createMemoryCapture: vi.fn(() => capture), + createMemoryIngest: vi.fn(() => ingest), now: () => 10, }, ), @@ -225,19 +225,19 @@ describe('runKtxTextIngest', () => { expect(output).not.toContain('text-1'); expect(output).not.toContain(longText); - expect(capture.capture).toHaveBeenNthCalledWith( + expect(ingest.ingest).toHaveBeenNthCalledWith( 1, expect.objectContaining({ userMessage: 'Ingest external text artifact "remember to call me Andrey" into KTX memory.', }), ); - expect(capture.capture).toHaveBeenNthCalledWith( + expect(ingest.ingest).toHaveBeenNthCalledWith( 2, expect.objectContaining({ userMessage: 'Ingest external text artifact "first line second line" into KTX memory.', }), ); - expect(capture.capture).toHaveBeenNthCalledWith( + expect(ingest.ingest).toHaveBeenNthCalledWith( 3, expect.objectContaining({ userMessage: 'Ingest external text artifact "This inline note is intentionally long xxxxxxxx..." into KTX memory.', @@ -247,7 +247,7 @@ describe('runKtxTextIngest', () => { it('continues after an item failure by default and stops when failFast is set', async () => { const continueIo = makeIo(); - const continueCapture = fakeCapture({ failRunIds: new Set(['run-1']) }); + const continueIngest = fakeIngest({ failRunIds: new Set(['run-1']) }); await expect( runKtxTextIngest( @@ -262,12 +262,12 @@ describe('runKtxTextIngest', () => { continueIo.io, { loadProject: vi.fn(async () => fakeProject()), - createMemoryCapture: vi.fn(() => continueCapture), + createMemoryIngest: vi.fn(() => continueIngest), }, ), ).resolves.toBe(1); - expect(continueCapture.capture).toHaveBeenCalledTimes(2); + expect(continueIngest.ingest).toHaveBeenCalledTimes(2); expect(JSON.parse(continueIo.stdout())).toMatchObject({ status: 'failed', results: [ @@ -277,7 +277,7 @@ describe('runKtxTextIngest', () => { }); const failFastIo = makeIo(); - const failFastCapture = fakeCapture({ failRunIds: new Set(['run-1']) }); + const failFastIngest = fakeIngest({ failRunIds: new Set(['run-1']) }); await expect( runKtxTextIngest( @@ -292,12 +292,12 @@ describe('runKtxTextIngest', () => { failFastIo.io, { loadProject: vi.fn(async () => fakeProject()), - createMemoryCapture: vi.fn(() => failFastCapture), + createMemoryIngest: vi.fn(() => failFastIngest), }, ), ).resolves.toBe(1); - expect(failFastCapture.capture).toHaveBeenCalledTimes(1); + expect(failFastIngest.ingest).toHaveBeenCalledTimes(1); expect(JSON.parse(failFastIo.stdout()).results).toHaveLength(1); }); @@ -314,7 +314,7 @@ describe('runKtxTextIngest', () => { failFast: false, }, noInputIo.io, - { loadProject: vi.fn(), createMemoryCapture: vi.fn() }, + { loadProject: vi.fn(), createMemoryIngest: vi.fn() }, ), ).resolves.toBe(1); expect(noInputIo.stderr()).toContain('Provide at least one text item'); @@ -331,7 +331,7 @@ describe('runKtxTextIngest', () => { failFast: false, }, emptyIo.io, - { loadProject: vi.fn(), createMemoryCapture: vi.fn() }, + { loadProject: vi.fn(), createMemoryIngest: vi.fn() }, ), ).resolves.toBe(1); expect(emptyIo.stderr()).toContain('Text item "text-1" is empty'); diff --git a/packages/cli/src/text-ingest.ts b/packages/cli/src/text-ingest.ts index d48ee24b..fe15244e 100644 --- a/packages/cli/src/text-ingest.ts +++ b/packages/cli/src/text-ingest.ts @@ -1,6 +1,6 @@ import { readFile as fsReadFile } from 'node:fs/promises'; import { basename, resolve } from 'node:path'; -import { createLocalProjectMemoryCapture, type MemoryAgentInput, type MemoryCaptureStatus } from '@ktx/context/memory'; +import { createLocalProjectMemoryIngest, type MemoryAgentInput, type MemoryIngestStatus } from '@ktx/context/memory'; import { loadKtxProject, type KtxLocalProject } from '@ktx/context/project'; import type { KtxCliIo } from './cli-runtime.js'; import { createRepainter, initViewState, renderContextBuildView, type ContextBuildTargetState } from './context-build-view.js'; @@ -17,10 +17,10 @@ export interface KtxTextIngestArgs { failFast: boolean; } -export interface TextMemoryCapturePort { - capture(input: MemoryAgentInput): Promise<{ runId: string }>; +export interface TextMemoryIngestPort { + ingest(input: MemoryAgentInput): Promise<{ runId: string }>; waitForRun(runId: string): Promise; - status(runId: string): Promise; + status(runId: string): Promise; } interface TextIngestItem { @@ -32,14 +32,14 @@ interface TextIngestResult { label: string; runId: string | null; status: 'done' | 'error'; - captured: MemoryCaptureStatus['captured']; + captured: MemoryIngestStatus['captured']; commitHash: string | null; error: string | null; } export interface KtxTextIngestDeps { loadProject?: (options: { projectDir: string }) => Promise; - createMemoryCapture?: (project: KtxLocalProject) => TextMemoryCapturePort; + createMemoryIngest?: (project: KtxLocalProject) => TextMemoryIngestPort; readFile?: (path: string) => Promise; readStdin?: () => Promise; now?: () => number; @@ -48,8 +48,8 @@ export interface KtxTextIngestDeps { const INLINE_TEXT_LABEL_MAX_LENGTH = 50; const ANSI_ESCAPE_PATTERN = /\x1B\[[0-?]*[ -/]*[@-~]/g; -function defaultCreateMemoryCapture(project: KtxLocalProject): TextMemoryCapturePort { - return createLocalProjectMemoryCapture(project); +function defaultCreateMemoryIngest(project: KtxLocalProject): TextMemoryIngestPort { + return createLocalProjectMemoryIngest(project); } async function defaultReadStdin(): Promise { @@ -65,7 +65,7 @@ async function defaultReadFile(path: string): Promise { return await fsReadFile(path, 'utf-8'); } -function emptyCaptured(): MemoryCaptureStatus['captured'] { +function emptyCaptured(): MemoryIngestStatus['captured'] { return { wiki: [], sl: [], xrefs: [] }; } @@ -182,7 +182,7 @@ function renderTextIngestView(state: ReturnType, styled: b }); } -function summarizeCaptured(captured: MemoryCaptureStatus['captured']): string { +function summarizeCaptured(captured: MemoryIngestStatus['captured']): string { const parts = [ `wiki=${captured.wiki.length}`, `sl=${captured.sl.length}`, @@ -191,7 +191,7 @@ function summarizeCaptured(captured: MemoryCaptureStatus['captured']): string { return parts.join(', '); } -function resultFromStatus(label: string, status: MemoryCaptureStatus): TextIngestResult { +function resultFromStatus(label: string, status: MemoryIngestStatus): TextIngestResult { return { label, runId: status.runId, @@ -251,7 +251,7 @@ export async function runKtxTextIngest( } const project = await (deps.loadProject ?? loadKtxProject)({ projectDir: args.projectDir }); - const memoryCapture = (deps.createMemoryCapture ?? defaultCreateMemoryCapture)(project); + const memoryIngest = (deps.createMemoryIngest ?? defaultCreateMemoryIngest)(project); const now = deps.now ?? (() => Date.now()); const batchId = now(); const state = initViewState(items.map((item) => makeTarget(item.label))); @@ -292,7 +292,7 @@ export async function runKtxTextIngest( let runId: string | null = null; let result: TextIngestResult; try { - const captureInput: MemoryAgentInput = { + const ingestInput: MemoryAgentInput = { userId: args.userId, chatId: `cli-text-ingest-${batchId}-${index + 1}`, userMessage: `Ingest external text artifact ${artifactReference(item.label)} into KTX memory.`, @@ -300,12 +300,12 @@ export async function runKtxTextIngest( ...(args.connectionId ? { connectionId: args.connectionId } : {}), sourceType: 'external_ingest', }; - const capture = await memoryCapture.capture(captureInput); - runId = capture.runId; - await memoryCapture.waitForRun(runId); - const status = await memoryCapture.status(runId); + const ingest = await memoryIngest.ingest(ingestInput); + runId = ingest.runId; + await memoryIngest.waitForRun(runId); + const status = await memoryIngest.status(runId); if (!status) { - throw new Error(`Memory capture run "${runId}" was not found.`); + throw new Error(`Memory ingest run "${runId}" was not found.`); } result = resultFromStatus(item.label, status); } catch (error) {