@kaelio/ktx 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/python/{kaelio_ktx-0.3.0-py3-none-any.whl → kaelio_ktx-0.4.1-py3-none-any.whl} +0 -0
- package/assets/python/manifest.json +4 -4
- package/dist/admin-reindex.js +9 -14
- package/dist/admin-reindex.test.js +4 -1
- package/dist/cli-project.d.ts +4 -10
- package/dist/cli-project.js +5 -49
- package/dist/cli-project.test.js +4 -131
- package/dist/commands/knowledge-commands.js +2 -0
- package/dist/commands/sl-commands.js +2 -0
- package/dist/embedding-resolution.d.ts +36 -0
- package/dist/embedding-resolution.js +63 -0
- package/dist/embedding-resolution.test.d.ts +1 -0
- package/dist/embedding-resolution.test.js +132 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.test.js +36 -33
- package/dist/ingest.js +12 -9
- package/dist/knowledge.d.ts +7 -2
- package/dist/knowledge.js +21 -7
- package/dist/knowledge.test.js +53 -9
- package/dist/managed-local-embeddings.d.ts +7 -6
- package/dist/managed-local-embeddings.js +19 -13
- package/dist/managed-local-embeddings.test.js +87 -18
- package/dist/mcp-server-factory.js +11 -0
- package/dist/public-ingest.js +2 -8
- package/dist/runtime-requirements.js +1 -2
- package/dist/runtime-requirements.test.js +1 -2
- package/dist/scan.js +8 -4
- package/dist/setup-embeddings.js +6 -2
- package/dist/setup-embeddings.test.js +2 -5
- package/dist/setup-runtime.test.js +1 -3
- package/dist/sl.d.ts +6 -4
- package/dist/sl.js +23 -9
- package/dist/sl.test.js +77 -6
- package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.d.ts +2 -0
- package/node_modules/@ktx/context/dist/ingest/local-bundle-runtime.js +2 -2
- package/node_modules/@ktx/context/dist/ingest/local-ingest.d.ts +1 -0
- package/node_modules/@ktx/context/dist/ingest/local-ingest.js +2 -0
- package/node_modules/@ktx/context/dist/llm/index.d.ts +1 -1
- package/node_modules/@ktx/context/dist/llm/index.js +1 -1
- package/node_modules/@ktx/context/dist/llm/local-config.d.ts +0 -1
- package/node_modules/@ktx/context/dist/llm/local-config.js +1 -2
- package/node_modules/@ktx/context/dist/llm/local-config.test.js +3 -3
- package/node_modules/@ktx/context/dist/mcp/local-project-ports.d.ts +2 -2
- package/node_modules/@ktx/context/dist/mcp/local-project-ports.js +2 -5
- package/node_modules/@ktx/context/dist/mcp/local-project-ports.test.js +14 -10
- package/node_modules/@ktx/context/dist/package-exports.test.js +0 -1
- package/node_modules/@ktx/context/dist/project/config.js +1 -1
- package/node_modules/@ktx/context/dist/scan/local-enrichment.test.js +4 -5
- package/node_modules/@ktx/context/dist/scan/local-scan.d.ts +3 -1
- package/node_modules/@ktx/context/dist/scan/local-scan.js +3 -2
- package/package.json +1 -1
package/dist/index.test.js
CHANGED
|
@@ -6,6 +6,8 @@ import { initKtxProject } from '@ktx/context/project';
|
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
7
|
import { getKtxCliPackageInfo, packageInfoFromJson, rendererUnavailableVizFallback, renderMemoryFlowTui, resolveVizFallback, runKtxCli, sanitizeMemoryFlowTuiError, startLiveMemoryFlowTui, warnVizFallbackOnce, } from './index.js';
|
|
8
8
|
const require = createRequire(import.meta.url);
|
|
9
|
+
const cliPackageJson = require('@ktx/cli/package.json');
|
|
10
|
+
const cliVersion = cliPackageJson.version;
|
|
9
11
|
function makeIo(options = {}) {
|
|
10
12
|
let stdout = '';
|
|
11
13
|
let stderr = '';
|
|
@@ -31,7 +33,7 @@ describe('getKtxCliPackageInfo', () => {
|
|
|
31
33
|
it('identifies the CLI package and its context dependency', () => {
|
|
32
34
|
expect(getKtxCliPackageInfo()).toEqual({
|
|
33
35
|
name: '@ktx/cli',
|
|
34
|
-
version:
|
|
36
|
+
version: cliVersion,
|
|
35
37
|
contextPackageName: '@ktx/context',
|
|
36
38
|
});
|
|
37
39
|
});
|
|
@@ -39,8 +41,9 @@ describe('getKtxCliPackageInfo', () => {
|
|
|
39
41
|
const packageJson = require('@ktx/cli/package.json');
|
|
40
42
|
expect(packageJson).toMatchObject({
|
|
41
43
|
name: '@ktx/cli',
|
|
42
|
-
version:
|
|
44
|
+
version: cliVersion,
|
|
43
45
|
});
|
|
46
|
+
expect(cliVersion).toMatch(/^\d+\.\d+\.\d+/);
|
|
44
47
|
});
|
|
45
48
|
it('normalizes public package metadata from package.json contents', () => {
|
|
46
49
|
expect(packageInfoFromJson({
|
|
@@ -86,7 +89,7 @@ describe('runKtxCli', () => {
|
|
|
86
89
|
it('prints version information', async () => {
|
|
87
90
|
const testIo = makeIo();
|
|
88
91
|
await expect(runKtxCli(['--version'], testIo.io)).resolves.toBe(0);
|
|
89
|
-
expect(testIo.stdout()).toBe(
|
|
92
|
+
expect(testIo.stdout()).toBe(`@ktx/cli ${cliVersion}\n`);
|
|
90
93
|
expect(testIo.stderr()).toBe('');
|
|
91
94
|
});
|
|
92
95
|
it('prints the public command surface in root help', async () => {
|
|
@@ -114,41 +117,41 @@ describe('runKtxCli', () => {
|
|
|
114
117
|
const listIo = makeIo();
|
|
115
118
|
await expect(runKtxCli(['--project-dir', tempDir, 'wiki', '--json'], listIo.io, { knowledge }))
|
|
116
119
|
.resolves.toBe(0);
|
|
117
|
-
expect(knowledge).toHaveBeenCalledWith({
|
|
120
|
+
expect(knowledge).toHaveBeenCalledWith(expect.objectContaining({
|
|
118
121
|
command: 'list',
|
|
119
122
|
projectDir: tempDir,
|
|
120
123
|
userId: 'local',
|
|
121
124
|
json: true,
|
|
122
|
-
}, listIo.io);
|
|
125
|
+
}), listIo.io);
|
|
123
126
|
const searchIo = makeIo();
|
|
124
127
|
await expect(runKtxCli(['--project-dir', tempDir, 'wiki', 'revenue', '--limit', '5'], searchIo.io, { knowledge })).resolves.toBe(0);
|
|
125
|
-
expect(knowledge).toHaveBeenLastCalledWith({
|
|
128
|
+
expect(knowledge).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
126
129
|
command: 'search',
|
|
127
130
|
projectDir: tempDir,
|
|
128
131
|
query: 'revenue',
|
|
129
132
|
userId: 'local',
|
|
130
133
|
json: false,
|
|
131
134
|
limit: 5,
|
|
132
|
-
}, searchIo.io);
|
|
135
|
+
}), searchIo.io);
|
|
133
136
|
const debugSearchIo = makeIo();
|
|
134
137
|
await expect(runKtxCli(['--project-dir', tempDir, '--debug', 'wiki', 'revenue'], debugSearchIo.io, { knowledge })).resolves.toBe(0);
|
|
135
|
-
expect(knowledge).toHaveBeenLastCalledWith({
|
|
138
|
+
expect(knowledge).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
136
139
|
command: 'search',
|
|
137
140
|
projectDir: tempDir,
|
|
138
141
|
query: 'revenue',
|
|
139
142
|
userId: 'local',
|
|
140
143
|
json: false,
|
|
141
144
|
debug: true,
|
|
142
|
-
}, debugSearchIo.io);
|
|
145
|
+
}), debugSearchIo.io);
|
|
143
146
|
const multiWordIo = makeIo();
|
|
144
147
|
await expect(runKtxCli(['--project-dir', tempDir, 'wiki', 'revenue', 'policy'], multiWordIo.io, { knowledge })).resolves.toBe(0);
|
|
145
|
-
expect(knowledge).toHaveBeenLastCalledWith({
|
|
148
|
+
expect(knowledge).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
146
149
|
command: 'search',
|
|
147
150
|
projectDir: tempDir,
|
|
148
151
|
query: 'revenue policy',
|
|
149
152
|
userId: 'local',
|
|
150
153
|
json: false,
|
|
151
|
-
}, multiWordIo.io);
|
|
154
|
+
}), multiWordIo.io);
|
|
152
155
|
});
|
|
153
156
|
it('rejects unknown write-style flags on the flattened wiki and sl commands', async () => {
|
|
154
157
|
const knowledge = vi.fn(async () => 0);
|
|
@@ -166,7 +169,7 @@ describe('runKtxCli', () => {
|
|
|
166
169
|
const sl = vi.fn(async () => 0);
|
|
167
170
|
const searchIo = makeIo();
|
|
168
171
|
await expect(runKtxCli(['--project-dir', tempDir, 'sl', 'revenue', '--connection-id', 'warehouse', '--limit', '5', '--json'], searchIo.io, { sl })).resolves.toBe(0);
|
|
169
|
-
expect(sl).toHaveBeenCalledWith({
|
|
172
|
+
expect(sl).toHaveBeenCalledWith(expect.objectContaining({
|
|
170
173
|
command: 'search',
|
|
171
174
|
projectDir: tempDir,
|
|
172
175
|
connectionId: 'warehouse',
|
|
@@ -174,16 +177,16 @@ describe('runKtxCli', () => {
|
|
|
174
177
|
limit: 5,
|
|
175
178
|
json: true,
|
|
176
179
|
output: undefined,
|
|
177
|
-
}, searchIo.io);
|
|
180
|
+
}), searchIo.io);
|
|
178
181
|
const bareIo = makeIo();
|
|
179
182
|
await expect(runKtxCli(['--project-dir', tempDir, 'sl', '--connection-id', 'warehouse', '--json'], bareIo.io, { sl })).resolves.toBe(0);
|
|
180
|
-
expect(sl).toHaveBeenLastCalledWith({
|
|
183
|
+
expect(sl).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
181
184
|
command: 'list',
|
|
182
185
|
projectDir: tempDir,
|
|
183
186
|
connectionId: 'warehouse',
|
|
184
187
|
json: true,
|
|
185
188
|
output: undefined,
|
|
186
|
-
}, bareIo.io);
|
|
189
|
+
}), bareIo.io);
|
|
187
190
|
const unknownIo = makeIo();
|
|
188
191
|
await expect(runKtxCli(['--project-dir', tempDir, 'sl', '--query', 'revenue'], unknownIo.io, { sl })).resolves.toBe(1);
|
|
189
192
|
expect(unknownIo.stderr()).toContain("unknown option '--query'");
|
|
@@ -206,32 +209,32 @@ describe('runKtxCli', () => {
|
|
|
206
209
|
await expect(runKtxCli(['admin', 'runtime', 'prune', '--dry-run'], pruneIo.io, { runtime })).resolves.toBe(1);
|
|
207
210
|
expect(runtime).toHaveBeenNthCalledWith(1, {
|
|
208
211
|
command: 'install',
|
|
209
|
-
cliVersion
|
|
212
|
+
cliVersion,
|
|
210
213
|
feature: 'local-embeddings',
|
|
211
214
|
force: true,
|
|
212
215
|
}, installIo.io);
|
|
213
216
|
expect(runtime).toHaveBeenNthCalledWith(2, {
|
|
214
217
|
command: 'start',
|
|
215
|
-
cliVersion
|
|
218
|
+
cliVersion,
|
|
216
219
|
projectDir: expect.any(String),
|
|
217
220
|
feature: 'local-embeddings',
|
|
218
221
|
force: true,
|
|
219
222
|
}, startIo.io);
|
|
220
223
|
expect(runtime).toHaveBeenNthCalledWith(3, {
|
|
221
224
|
command: 'stop',
|
|
222
|
-
cliVersion
|
|
225
|
+
cliVersion,
|
|
223
226
|
projectDir: expect.any(String),
|
|
224
227
|
all: false,
|
|
225
228
|
}, stopIo.io);
|
|
226
229
|
expect(runtime).toHaveBeenNthCalledWith(4, {
|
|
227
230
|
command: 'stop',
|
|
228
|
-
cliVersion
|
|
231
|
+
cliVersion,
|
|
229
232
|
projectDir: expect.any(String),
|
|
230
233
|
all: true,
|
|
231
234
|
}, stopAllIo.io);
|
|
232
235
|
expect(runtime).toHaveBeenNthCalledWith(5, {
|
|
233
236
|
command: 'status',
|
|
234
|
-
cliVersion
|
|
237
|
+
cliVersion,
|
|
235
238
|
json: true,
|
|
236
239
|
}, statusIo.io);
|
|
237
240
|
expect(runtime).toHaveBeenCalledTimes(5);
|
|
@@ -278,7 +281,7 @@ describe('runKtxCli', () => {
|
|
|
278
281
|
expect(sl).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
279
282
|
command: 'query',
|
|
280
283
|
projectDir: tempDir,
|
|
281
|
-
cliVersion
|
|
284
|
+
cliVersion,
|
|
282
285
|
runtimeInstallPolicy: 'prompt',
|
|
283
286
|
query: expect.objectContaining({ measures: ['orders.order_count'], dimensions: [] }),
|
|
284
287
|
}), promptIo.io);
|
|
@@ -287,13 +290,13 @@ describe('runKtxCli', () => {
|
|
|
287
290
|
sl,
|
|
288
291
|
})).resolves.toBe(0);
|
|
289
292
|
expect(sl).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
290
|
-
cliVersion
|
|
293
|
+
cliVersion,
|
|
291
294
|
runtimeInstallPolicy: 'auto',
|
|
292
295
|
}), autoIo.io);
|
|
293
296
|
const noInputIo = makeIo();
|
|
294
297
|
await expect(runKtxCli(['--project-dir', tempDir, 'sl', 'query', '--measure', 'orders.order_count', '--no-input'], noInputIo.io, { sl })).resolves.toBe(0);
|
|
295
298
|
expect(sl).toHaveBeenLastCalledWith(expect.objectContaining({
|
|
296
|
-
cliVersion
|
|
299
|
+
cliVersion,
|
|
297
300
|
runtimeInstallPolicy: 'never',
|
|
298
301
|
}), noInputIo.io);
|
|
299
302
|
});
|
|
@@ -403,7 +406,7 @@ describe('runKtxCli', () => {
|
|
|
403
406
|
skipAgents: false,
|
|
404
407
|
inputMode: 'auto',
|
|
405
408
|
yes: false,
|
|
406
|
-
cliVersion
|
|
409
|
+
cliVersion,
|
|
407
410
|
skipLlm: false,
|
|
408
411
|
skipEmbeddings: false,
|
|
409
412
|
databaseSchemas: [],
|
|
@@ -512,7 +515,7 @@ describe('runKtxCli', () => {
|
|
|
512
515
|
inputMode: 'disabled',
|
|
513
516
|
depth: 'fast',
|
|
514
517
|
queryHistory: 'default',
|
|
515
|
-
cliVersion
|
|
518
|
+
cliVersion,
|
|
516
519
|
runtimeInstallPolicy: 'never',
|
|
517
520
|
}, testIo.io);
|
|
518
521
|
expect(testIo.stderr()).toBe(`Project: ${tempDir}\n`);
|
|
@@ -531,7 +534,7 @@ describe('runKtxCli', () => {
|
|
|
531
534
|
inputMode: 'auto',
|
|
532
535
|
depth: 'deep',
|
|
533
536
|
queryHistory: 'default',
|
|
534
|
-
cliVersion
|
|
537
|
+
cliVersion,
|
|
535
538
|
runtimeInstallPolicy: 'prompt',
|
|
536
539
|
}, testIo.io);
|
|
537
540
|
expect(testIo.stderr()).toBe('');
|
|
@@ -580,7 +583,7 @@ describe('runKtxCli', () => {
|
|
|
580
583
|
json: false,
|
|
581
584
|
inputMode: 'disabled',
|
|
582
585
|
queryHistory: 'default',
|
|
583
|
-
cliVersion
|
|
586
|
+
cliVersion,
|
|
584
587
|
runtimeInstallPolicy: 'never',
|
|
585
588
|
}, testIo.io);
|
|
586
589
|
});
|
|
@@ -798,7 +801,7 @@ describe('runKtxCli', () => {
|
|
|
798
801
|
command: 'run',
|
|
799
802
|
projectDir: tempDir,
|
|
800
803
|
inputMode: 'disabled',
|
|
801
|
-
cliVersion
|
|
804
|
+
cliVersion,
|
|
802
805
|
anthropicApiKeyEnv: 'ANTHROPIC_API_KEY', // pragma: allowlist secret
|
|
803
806
|
llmModel: 'claude-sonnet-4-6',
|
|
804
807
|
skipLlm: false,
|
|
@@ -825,7 +828,7 @@ describe('runKtxCli', () => {
|
|
|
825
828
|
command: 'run',
|
|
826
829
|
projectDir: tempDir,
|
|
827
830
|
inputMode: 'disabled',
|
|
828
|
-
cliVersion
|
|
831
|
+
cliVersion,
|
|
829
832
|
llmBackend: 'vertex',
|
|
830
833
|
vertexProject: 'local-gcp-project',
|
|
831
834
|
vertexLocation: 'us-east5',
|
|
@@ -850,7 +853,7 @@ describe('runKtxCli', () => {
|
|
|
850
853
|
command: 'run',
|
|
851
854
|
projectDir: tempDir,
|
|
852
855
|
inputMode: 'disabled',
|
|
853
|
-
cliVersion
|
|
856
|
+
cliVersion,
|
|
854
857
|
llmBackend: 'claude-code',
|
|
855
858
|
llmModel: 'opus',
|
|
856
859
|
skipLlm: false,
|
|
@@ -925,7 +928,7 @@ describe('runKtxCli', () => {
|
|
|
925
928
|
projectDir: '/tmp/project',
|
|
926
929
|
inputMode: 'disabled',
|
|
927
930
|
yes: true,
|
|
928
|
-
cliVersion
|
|
931
|
+
cliVersion,
|
|
929
932
|
skipLlm: true,
|
|
930
933
|
skipEmbeddings: true,
|
|
931
934
|
databaseDrivers: ['postgres'],
|
|
@@ -1143,7 +1146,7 @@ describe('runKtxCli', () => {
|
|
|
1143
1146
|
queryFile: '/tmp/query.json',
|
|
1144
1147
|
execute: false,
|
|
1145
1148
|
format: 'json',
|
|
1146
|
-
cliVersion
|
|
1149
|
+
cliVersion,
|
|
1147
1150
|
runtimeInstallPolicy: 'auto',
|
|
1148
1151
|
}, autoIo.io);
|
|
1149
1152
|
expect(sl).toHaveBeenNthCalledWith(2, {
|
|
@@ -1153,7 +1156,7 @@ describe('runKtxCli', () => {
|
|
|
1153
1156
|
queryFile: '/tmp/query.json',
|
|
1154
1157
|
execute: false,
|
|
1155
1158
|
format: 'json',
|
|
1156
|
-
cliVersion
|
|
1159
|
+
cliVersion,
|
|
1157
1160
|
runtimeInstallPolicy: 'never',
|
|
1158
1161
|
}, neverIo.io);
|
|
1159
1162
|
expect(conflictIo.stderr()).toContain('Choose only one runtime install mode: --yes or --no-input');
|
package/dist/ingest.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildMemoryFlowViewModel, createMemoryFlowLiveBuffer, formatMemoryFlowFinalSummary, getLatestLocalIngestStatus, getLocalIngestStatus, ingestReportToMemoryFlowReplay, renderMemoryFlowReplay, runLocalIngest, runLocalMetabaseIngest, savedMemoryCountsForReport, sanitizeMemoryFlowError, } from '@ktx/context/ingest';
|
|
2
|
-
import {
|
|
2
|
+
import { loadKtxProject } from '@ktx/context/project';
|
|
3
|
+
import { resolveProjectEmbeddingProvider } from './embedding-resolution.js';
|
|
3
4
|
import { createKtxCliIngestQueryExecutor } from './ingest-query-executor.js';
|
|
4
5
|
import { readIngestReportSnapshotFile } from './ingest-report-file.js';
|
|
5
6
|
import { createCliOperationalLogger } from './io/logger.js';
|
|
@@ -470,16 +471,16 @@ async function writeReportRecord(report, outputMode, io, options = {}) {
|
|
|
470
471
|
}
|
|
471
472
|
export async function runKtxIngest(args, io = process, deps = {}) {
|
|
472
473
|
try {
|
|
473
|
-
const
|
|
474
|
-
const runtimeInstallPolicy = args.command === 'run' ? args.runtimeInstallPolicy : undefined;
|
|
475
|
-
const project = await loadKtxCliProject({
|
|
476
|
-
projectDir: args.projectDir,
|
|
477
|
-
cliVersion: cliVersion ?? '0.0.0-private',
|
|
478
|
-
installPolicy: runtimeInstallPolicy ?? 'never',
|
|
479
|
-
io,
|
|
480
|
-
});
|
|
474
|
+
const project = await loadKtxProject({ projectDir: args.projectDir });
|
|
481
475
|
const env = deps.env ?? process.env;
|
|
482
476
|
if (args.command === 'run') {
|
|
477
|
+
const resolution = await resolveProjectEmbeddingProvider(project, {
|
|
478
|
+
mode: 'ensure',
|
|
479
|
+
installPolicy: args.runtimeInstallPolicy ?? 'never',
|
|
480
|
+
cliVersion: args.cliVersion ?? '0.0.0-private',
|
|
481
|
+
io,
|
|
482
|
+
});
|
|
483
|
+
const embeddingProvider = resolution.kind === 'disabled' || resolution.kind === 'managed-unavailable' ? null : resolution.provider;
|
|
483
484
|
const ingestProject = args.allowImplicitAdapter && !project.config.ingest.adapters.includes(args.adapter)
|
|
484
485
|
? {
|
|
485
486
|
...project,
|
|
@@ -550,6 +551,7 @@ export async function runKtxIngest(args, io = process, deps = {}) {
|
|
|
550
551
|
queryExecutor,
|
|
551
552
|
trigger: 'manual_resync',
|
|
552
553
|
jobIdFactory: deps.jobIdFactory,
|
|
554
|
+
embeddingProvider,
|
|
553
555
|
...(memoryFlow ? { memoryFlow } : {}),
|
|
554
556
|
...(progress ? { progress } : {}),
|
|
555
557
|
});
|
|
@@ -617,6 +619,7 @@ export async function runKtxIngest(args, io = process, deps = {}) {
|
|
|
617
619
|
...localIngestOptions,
|
|
618
620
|
queryExecutor,
|
|
619
621
|
pullConfigOptions: adapterOptions,
|
|
622
|
+
embeddingProvider,
|
|
620
623
|
...(args.debugLlmRequestFile ? { llmDebugRequestFile: args.debugLlmRequestFile } : {}),
|
|
621
624
|
...(memoryFlow ? { memoryFlow } : {}),
|
|
622
625
|
});
|
package/dist/knowledge.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type KtxEmbeddingPort } from '@ktx/context';
|
|
2
|
+
import { searchLocalKnowledgePages as defaultSearchLocalKnowledgePages } from '@ktx/context/wiki';
|
|
3
|
+
import { resolveProjectEmbeddingProvider } from './embedding-resolution.js';
|
|
2
4
|
export type KtxKnowledgeArgs = {
|
|
3
5
|
command: 'list';
|
|
4
6
|
projectDir: string;
|
|
5
7
|
userId: string;
|
|
6
8
|
output?: string;
|
|
7
9
|
json?: boolean;
|
|
10
|
+
cliVersion: string;
|
|
8
11
|
} | {
|
|
9
12
|
command: 'search';
|
|
10
13
|
projectDir: string;
|
|
@@ -14,11 +17,13 @@ export type KtxKnowledgeArgs = {
|
|
|
14
17
|
json?: boolean;
|
|
15
18
|
limit?: number;
|
|
16
19
|
debug?: boolean;
|
|
20
|
+
cliVersion: string;
|
|
17
21
|
};
|
|
18
22
|
type KtxKnowledgeIo = import('./cli-runtime.js').KtxCliIo;
|
|
19
23
|
interface KtxKnowledgeDeps {
|
|
20
24
|
embeddingService?: KtxEmbeddingPort | null;
|
|
21
|
-
|
|
25
|
+
resolveEmbeddingProvider?: typeof resolveProjectEmbeddingProvider;
|
|
26
|
+
searchLocalKnowledgePages?: typeof defaultSearchLocalKnowledgePages;
|
|
22
27
|
}
|
|
23
28
|
export declare function runKtxKnowledge(args: KtxKnowledgeArgs, io?: KtxKnowledgeIo, deps?: KtxKnowledgeDeps): Promise<number>;
|
|
24
29
|
export {};
|
package/dist/knowledge.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { KtxIngestEmbeddingPortAdapter } from '@ktx/context';
|
|
2
2
|
import { loadKtxProject } from '@ktx/context/project';
|
|
3
|
-
import { listLocalKnowledgePages, searchLocalKnowledgePages, } from '@ktx/context/wiki';
|
|
3
|
+
import { listLocalKnowledgePages, searchLocalKnowledgePages as defaultSearchLocalKnowledgePages, } from '@ktx/context/wiki';
|
|
4
|
+
import { resolveProjectEmbeddingProvider, } from './embedding-resolution.js';
|
|
4
5
|
import { resolveOutputMode } from './io/mode.js';
|
|
5
6
|
import { createRankBadgeFormatter, printList } from './io/print-list.js';
|
|
6
7
|
const WIKI_LIST_COLUMNS = [
|
|
@@ -23,12 +24,24 @@ function wikiSearchColumns(rows) {
|
|
|
23
24
|
{ key: 'summary', label: 'SUMMARY', plain: '', optional: true, dim: true },
|
|
24
25
|
];
|
|
25
26
|
}
|
|
26
|
-
function
|
|
27
|
+
function resolutionToEmbeddingPort(resolution) {
|
|
28
|
+
if (resolution.kind === 'configured' ||
|
|
29
|
+
resolution.kind === 'managed-running' ||
|
|
30
|
+
resolution.kind === 'managed-started') {
|
|
31
|
+
return new KtxIngestEmbeddingPortAdapter(resolution.provider);
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
async function wikiSearchEmbeddingService(project, deps, args, io) {
|
|
27
36
|
if ('embeddingService' in deps) {
|
|
28
37
|
return deps.embeddingService ?? null;
|
|
29
38
|
}
|
|
30
|
-
const
|
|
31
|
-
|
|
39
|
+
const resolution = await (deps.resolveEmbeddingProvider ?? resolveProjectEmbeddingProvider)(project, {
|
|
40
|
+
mode: 'use-if-running',
|
|
41
|
+
cliVersion: args.cliVersion,
|
|
42
|
+
io,
|
|
43
|
+
});
|
|
44
|
+
return resolutionToEmbeddingPort(resolution);
|
|
32
45
|
}
|
|
33
46
|
function writeWikiSearchDebug(io, input) {
|
|
34
47
|
io.stderr.write(`[debug] wiki search mode=${input.mode} embedding=${input.embeddingConfigured ? 'configured' : 'unconfigured'} results=${input.results.length}\n`);
|
|
@@ -58,8 +71,9 @@ export async function runKtxKnowledge(args, io = process, deps = {}) {
|
|
|
58
71
|
return 0;
|
|
59
72
|
}
|
|
60
73
|
if (args.command === 'search') {
|
|
61
|
-
const embeddingService = wikiSearchEmbeddingService(project, deps);
|
|
62
|
-
const
|
|
74
|
+
const embeddingService = await wikiSearchEmbeddingService(project, deps, { cliVersion: args.cliVersion }, io);
|
|
75
|
+
const search = deps.searchLocalKnowledgePages ?? defaultSearchLocalKnowledgePages;
|
|
76
|
+
const results = await search(project, {
|
|
63
77
|
query: args.query,
|
|
64
78
|
userId: args.userId,
|
|
65
79
|
embeddingService,
|
package/dist/knowledge.test.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from 'node:path';
|
|
|
4
4
|
import { stripVTControlCharacters } from 'node:util';
|
|
5
5
|
import { initKtxProject, loadKtxProject } from '@ktx/context/project';
|
|
6
6
|
import { writeLocalKnowledgePage } from '@ktx/context/wiki';
|
|
7
|
-
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
7
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
8
8
|
import { runKtxKnowledge } from './knowledge.js';
|
|
9
9
|
function makeIo() {
|
|
10
10
|
let stdout = '';
|
|
@@ -62,10 +62,10 @@ describe('runKtxKnowledge', () => {
|
|
|
62
62
|
await initKtxProject({ projectDir });
|
|
63
63
|
await seedWikiPage(projectDir);
|
|
64
64
|
const listIo = makeIo();
|
|
65
|
-
await expect(runKtxKnowledge({ command: 'list', projectDir, userId: 'local' }, listIo.io)).resolves.toBe(0);
|
|
65
|
+
await expect(runKtxKnowledge({ command: 'list', projectDir, userId: 'local', cliVersion: '0.0.0-test' }, listIo.io)).resolves.toBe(0);
|
|
66
66
|
expect(listIo.stdout()).toContain('GLOBAL\tmetrics-revenue\tRevenue');
|
|
67
67
|
const searchIo = makeIo();
|
|
68
|
-
await expect(runKtxKnowledge({ command: 'search', projectDir, query: 'paid order', userId: 'local' }, searchIo.io)).resolves.toBe(0);
|
|
68
|
+
await expect(runKtxKnowledge({ command: 'search', projectDir, query: 'paid order', userId: 'local', cliVersion: '0.0.0-test' }, searchIo.io)).resolves.toBe(0);
|
|
69
69
|
expect(searchIo.stdout()).toContain('metrics-revenue');
|
|
70
70
|
});
|
|
71
71
|
it('prints wiki search rank badges in pretty output', async () => {
|
|
@@ -73,7 +73,14 @@ describe('runKtxKnowledge', () => {
|
|
|
73
73
|
await initKtxProject({ projectDir });
|
|
74
74
|
await seedWikiPage(projectDir);
|
|
75
75
|
const searchIo = makeIo();
|
|
76
|
-
await expect(runKtxKnowledge({
|
|
76
|
+
await expect(runKtxKnowledge({
|
|
77
|
+
command: 'search',
|
|
78
|
+
projectDir,
|
|
79
|
+
query: 'paid order',
|
|
80
|
+
userId: 'local',
|
|
81
|
+
output: 'pretty',
|
|
82
|
+
cliVersion: '0.0.0-test',
|
|
83
|
+
}, searchIo.io)).resolves.toBe(0);
|
|
77
84
|
const stdout = stripVTControlCharacters(searchIo.stdout());
|
|
78
85
|
expect(stdout).toMatch(/#1\s+metrics-revenue/);
|
|
79
86
|
expect(stdout).not.toContain('%');
|
|
@@ -83,14 +90,22 @@ describe('runKtxKnowledge', () => {
|
|
|
83
90
|
await initKtxProject({ projectDir });
|
|
84
91
|
await seedWikiPage(projectDir);
|
|
85
92
|
const listIo = makeIo();
|
|
86
|
-
await expect(runKtxKnowledge({ command: 'list', projectDir, userId: 'local', json: true }, listIo.io)).resolves.toBe(0);
|
|
93
|
+
await expect(runKtxKnowledge({ command: 'list', projectDir, userId: 'local', json: true, cliVersion: '0.0.0-test' }, listIo.io)).resolves.toBe(0);
|
|
87
94
|
expect(JSON.parse(listIo.stdout())).toMatchObject({
|
|
88
95
|
kind: 'list',
|
|
89
96
|
data: { items: [expect.objectContaining({ key: 'metrics-revenue', summary: 'Revenue' })] },
|
|
90
97
|
meta: { command: 'wiki list' },
|
|
91
98
|
});
|
|
92
99
|
const searchIo = makeIo();
|
|
93
|
-
await expect(runKtxKnowledge({
|
|
100
|
+
await expect(runKtxKnowledge({
|
|
101
|
+
command: 'search',
|
|
102
|
+
projectDir,
|
|
103
|
+
query: 'paid order',
|
|
104
|
+
userId: 'local',
|
|
105
|
+
json: true,
|
|
106
|
+
limit: 5,
|
|
107
|
+
cliVersion: '0.0.0-test',
|
|
108
|
+
}, searchIo.io)).resolves.toBe(0);
|
|
94
109
|
expect(JSON.parse(searchIo.stdout())).toMatchObject({
|
|
95
110
|
kind: 'list',
|
|
96
111
|
data: { items: [expect.objectContaining({ key: 'metrics-revenue', summary: 'Revenue' })] },
|
|
@@ -101,7 +116,7 @@ describe('runKtxKnowledge', () => {
|
|
|
101
116
|
const projectDir = join(tempDir, 'empty-project');
|
|
102
117
|
await initKtxProject({ projectDir });
|
|
103
118
|
const searchIo = makeIo();
|
|
104
|
-
await expect(runKtxKnowledge({ command: 'search', projectDir, query: 'revenue', userId: 'local' }, searchIo.io)).resolves.toBe(0);
|
|
119
|
+
await expect(runKtxKnowledge({ command: 'search', projectDir, query: 'revenue', userId: 'local', cliVersion: '0.0.0-test' }, searchIo.io)).resolves.toBe(0);
|
|
105
120
|
expect(searchIo.stdout()).toBe('');
|
|
106
121
|
expect(searchIo.stderr()).toContain('No local wiki pages found');
|
|
107
122
|
expect(searchIo.stderr()).toContain('ktx ingest <connectionId>');
|
|
@@ -117,16 +132,45 @@ describe('runKtxKnowledge', () => {
|
|
|
117
132
|
slRefs: [],
|
|
118
133
|
});
|
|
119
134
|
const searchIo = makeIo();
|
|
120
|
-
await expect(runKtxKnowledge({ command: 'search', projectDir, query: 'revenue', userId: 'local' }, searchIo.io, { embeddingService: new FakeEmbeddingPort() })).resolves.toBe(0);
|
|
135
|
+
await expect(runKtxKnowledge({ command: 'search', projectDir, query: 'revenue', userId: 'local', cliVersion: '0.0.0-test' }, searchIo.io, { embeddingService: new FakeEmbeddingPort() })).resolves.toBe(0);
|
|
121
136
|
expect(searchIo.stdout()).toContain('active-contract-arr-open-tickets');
|
|
122
137
|
expect(searchIo.stderr()).toBe('');
|
|
123
138
|
});
|
|
139
|
+
it('routes wiki search through resolveEmbeddingProvider when no embeddingService is injected', async () => {
|
|
140
|
+
const projectDir = join(tempDir, 'resolver-project');
|
|
141
|
+
await initKtxProject({ projectDir });
|
|
142
|
+
const search = vi.fn(async () => []);
|
|
143
|
+
const searchIo = makeIo();
|
|
144
|
+
await expect(runKtxKnowledge({
|
|
145
|
+
command: 'search',
|
|
146
|
+
projectDir,
|
|
147
|
+
query: 'income',
|
|
148
|
+
userId: 'local',
|
|
149
|
+
cliVersion: '0.5.0',
|
|
150
|
+
}, searchIo.io, {
|
|
151
|
+
resolveEmbeddingProvider: async () => ({
|
|
152
|
+
kind: 'managed-running',
|
|
153
|
+
provider: { id: 'fake' },
|
|
154
|
+
baseUrl: 'http://127.0.0.1:51234',
|
|
155
|
+
}),
|
|
156
|
+
searchLocalKnowledgePages: search,
|
|
157
|
+
})).resolves.toBe(0);
|
|
158
|
+
expect(search).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ embeddingService: expect.any(Object) }));
|
|
159
|
+
});
|
|
124
160
|
it('writes wiki search lane diagnostics to stderr when debug is enabled', async () => {
|
|
125
161
|
const projectDir = join(tempDir, 'debug-project');
|
|
126
162
|
await initKtxProject({ projectDir });
|
|
127
163
|
await seedWikiPage(projectDir);
|
|
128
164
|
const searchIo = makeIo();
|
|
129
|
-
await expect(runKtxKnowledge({
|
|
165
|
+
await expect(runKtxKnowledge({
|
|
166
|
+
command: 'search',
|
|
167
|
+
projectDir,
|
|
168
|
+
query: 'paid order',
|
|
169
|
+
userId: 'local',
|
|
170
|
+
json: true,
|
|
171
|
+
debug: true,
|
|
172
|
+
cliVersion: '0.0.0-test',
|
|
173
|
+
}, searchIo.io, { embeddingService: new FakeEmbeddingPort() })).resolves.toBe(0);
|
|
130
174
|
expect(JSON.parse(searchIo.stdout())).toMatchObject({
|
|
131
175
|
kind: 'list',
|
|
132
176
|
data: { items: [expect.objectContaining({ key: 'metrics-revenue' })] },
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import type { KtxProjectEmbeddingConfig } from '@ktx/context/project';
|
|
2
1
|
import type { KtxEmbeddingConfig } from '@ktx/llm';
|
|
3
2
|
import type { KtxCliIo } from './cli-runtime.js';
|
|
4
3
|
import { type KtxManagedPythonInstallPolicy, type ManagedPythonCommandRuntime } from './managed-python-command.js';
|
|
5
|
-
import { type ManagedPythonDaemonStartResult } from './managed-python-daemon.js';
|
|
4
|
+
import { readManagedPythonDaemonStatus, type ManagedPythonDaemonStartResult } from './managed-python-daemon.js';
|
|
6
5
|
export interface ManagedLocalEmbeddingsDaemon {
|
|
7
6
|
baseUrl: string;
|
|
8
7
|
stdoutLog: string;
|
|
@@ -26,13 +25,15 @@ export interface ManagedLocalEmbeddingsOptions {
|
|
|
26
25
|
force: boolean;
|
|
27
26
|
}) => Promise<ManagedPythonDaemonStartResult>;
|
|
28
27
|
}
|
|
29
|
-
export declare function managedLocalEmbeddingProjectConfig(input: {
|
|
30
|
-
model: string;
|
|
31
|
-
dimensions: number;
|
|
32
|
-
}): KtxProjectEmbeddingConfig;
|
|
33
28
|
export declare function managedLocalEmbeddingHealthConfig(input: {
|
|
34
29
|
baseUrl: string;
|
|
35
30
|
model: string;
|
|
36
31
|
dimensions: number;
|
|
37
32
|
}): KtxEmbeddingConfig;
|
|
38
33
|
export declare function ensureManagedLocalEmbeddingsDaemon(options: ManagedLocalEmbeddingsOptions): Promise<ManagedLocalEmbeddingsDaemon>;
|
|
34
|
+
export interface TryUseManagedLocalEmbeddingsOptions {
|
|
35
|
+
cliVersion: string;
|
|
36
|
+
projectDir: string;
|
|
37
|
+
readStatus?: typeof readManagedPythonDaemonStatus;
|
|
38
|
+
}
|
|
39
|
+
export declare function tryUseManagedLocalEmbeddingsDaemon(options: TryUseManagedLocalEmbeddingsOptions): Promise<ManagedLocalEmbeddingsDaemon | null>;
|
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
import { MANAGED_SENTENCE_TRANSFORMERS_BASE_URL } from '@ktx/context';
|
|
2
1
|
import { ensureManagedPythonCommandRuntime, } from './managed-python-command.js';
|
|
3
|
-
import { startManagedPythonDaemon } from './managed-python-daemon.js';
|
|
4
|
-
export function managedLocalEmbeddingProjectConfig(input) {
|
|
5
|
-
return {
|
|
6
|
-
backend: 'sentence-transformers',
|
|
7
|
-
model: input.model,
|
|
8
|
-
dimensions: input.dimensions,
|
|
9
|
-
sentenceTransformers: {
|
|
10
|
-
base_url: MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
|
11
|
-
pathPrefix: '',
|
|
12
|
-
},
|
|
13
|
-
};
|
|
14
|
-
}
|
|
2
|
+
import { readManagedPythonDaemonStatus, startManagedPythonDaemon, } from './managed-python-daemon.js';
|
|
15
3
|
export function managedLocalEmbeddingHealthConfig(input) {
|
|
16
4
|
return {
|
|
17
5
|
backend: 'sentence-transformers',
|
|
@@ -46,3 +34,21 @@ export async function ensureManagedLocalEmbeddingsDaemon(options) {
|
|
|
46
34
|
stderrLog: daemon.state.stderrLog,
|
|
47
35
|
};
|
|
48
36
|
}
|
|
37
|
+
export async function tryUseManagedLocalEmbeddingsDaemon(options) {
|
|
38
|
+
const readStatus = options.readStatus ?? readManagedPythonDaemonStatus;
|
|
39
|
+
const status = await readStatus({
|
|
40
|
+
cliVersion: options.cliVersion,
|
|
41
|
+
projectDir: options.projectDir,
|
|
42
|
+
});
|
|
43
|
+
if (status.kind !== 'running') {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (!status.state.features.includes('local-embeddings')) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
baseUrl: status.baseUrl,
|
|
51
|
+
stdoutLog: status.state.stdoutLog,
|
|
52
|
+
stderrLog: status.state.stderrLog,
|
|
53
|
+
};
|
|
54
|
+
}
|