mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-28 08:49:38 +02:00
rename klo to ktx
This commit is contained in:
parent
1a42152e6f
commit
3ce510b55b
704 changed files with 10205 additions and 10255 deletions
|
|
@ -1,9 +1,9 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { buildDefaultKloProjectConfig, parseKloProjectConfig, serializeKloProjectConfig } from './config.js';
|
||||
import { buildDefaultKtxProjectConfig, parseKtxProjectConfig, serializeKtxProjectConfig } from './config.js';
|
||||
|
||||
describe('KLO project config', () => {
|
||||
describe('KTX project config', () => {
|
||||
it('builds the default standalone project config', () => {
|
||||
expect(buildDefaultKloProjectConfig('warehouse')).toEqual({
|
||||
expect(buildDefaultKtxProjectConfig('warehouse')).toEqual({
|
||||
project: 'warehouse',
|
||||
connections: {},
|
||||
storage: {
|
||||
|
|
@ -11,7 +11,7 @@ describe('KLO project config', () => {
|
|||
search: 'sqlite-fts5',
|
||||
git: {
|
||||
auto_commit: true,
|
||||
author: 'klo <klo@example.com>',
|
||||
author: 'ktx <ktx@example.com>',
|
||||
},
|
||||
},
|
||||
llm: {
|
||||
|
|
@ -63,8 +63,8 @@ describe('KLO project config', () => {
|
|||
});
|
||||
|
||||
it('round-trips through YAML with stable defaults', () => {
|
||||
const serialized = serializeKloProjectConfig(buildDefaultKloProjectConfig('warehouse'));
|
||||
const parsed = parseKloProjectConfig(serialized);
|
||||
const serialized = serializeKtxProjectConfig(buildDefaultKtxProjectConfig('warehouse'));
|
||||
const parsed = parseKtxProjectConfig(serialized);
|
||||
|
||||
expect(serialized).toContain('project: warehouse');
|
||||
expect(serialized).toContain('live-database');
|
||||
|
|
@ -82,7 +82,7 @@ describe('KLO project config', () => {
|
|||
});
|
||||
|
||||
it('parses and serializes setup wizard metadata', () => {
|
||||
const config = parseKloProjectConfig(`
|
||||
const config = parseKtxProjectConfig(`
|
||||
project: revenue
|
||||
setup:
|
||||
database_connection_ids:
|
||||
|
|
@ -102,14 +102,14 @@ connections:
|
|||
completed_steps: ['project', 'llm'],
|
||||
});
|
||||
|
||||
const serialized = serializeKloProjectConfig(config);
|
||||
const serialized = serializeKtxProjectConfig(config);
|
||||
expect(serialized).toContain('setup:');
|
||||
expect(serialized).toContain('database_connection_ids:');
|
||||
expect(serialized).toContain('completed_steps:');
|
||||
});
|
||||
|
||||
it('parses global direct Anthropic LLM config', () => {
|
||||
const config = parseKloProjectConfig(`
|
||||
const config = parseKtxProjectConfig(`
|
||||
project: demo
|
||||
llm:
|
||||
provider:
|
||||
|
|
@ -149,7 +149,7 @@ ingest:
|
|||
});
|
||||
|
||||
it('parses global Vertex LLM config', () => {
|
||||
const config = parseKloProjectConfig(`
|
||||
const config = parseKtxProjectConfig(`
|
||||
project: demo
|
||||
llm:
|
||||
provider:
|
||||
|
|
@ -171,7 +171,7 @@ llm:
|
|||
});
|
||||
|
||||
it('parses gateway LLM, OpenAI scan embeddings, and sentence-transformers ingest embeddings', () => {
|
||||
const config = parseKloProjectConfig(`
|
||||
const config = parseKtxProjectConfig(`
|
||||
project: demo
|
||||
llm:
|
||||
provider:
|
||||
|
|
@ -215,7 +215,7 @@ scan:
|
|||
});
|
||||
|
||||
it('parses scan relationship settings', () => {
|
||||
const config = parseKloProjectConfig(`
|
||||
const config = parseKtxProjectConfig(`
|
||||
project: demo
|
||||
scan:
|
||||
relationships:
|
||||
|
|
@ -243,20 +243,20 @@ scan:
|
|||
validationConcurrency: 2,
|
||||
validationBudget: 0,
|
||||
});
|
||||
expect(serializeKloProjectConfig(config)).toContain('enabled: false');
|
||||
expect(serializeKloProjectConfig(config)).toContain('llmProposals: false');
|
||||
expect(serializeKloProjectConfig(config)).toContain('validationRequiredForManifest: true');
|
||||
expect(serializeKloProjectConfig(config)).toContain('acceptThreshold: 0.91');
|
||||
expect(serializeKloProjectConfig(config)).toContain('reviewThreshold: 0.61');
|
||||
expect(serializeKloProjectConfig(config)).toContain('maxLlmTablesPerBatch: 12');
|
||||
expect(serializeKloProjectConfig(config)).toContain('maxCandidatesPerColumn: 7');
|
||||
expect(serializeKloProjectConfig(config)).toContain('profileSampleRows: 500');
|
||||
expect(serializeKloProjectConfig(config)).toContain('validationConcurrency: 2');
|
||||
expect(serializeKloProjectConfig(config)).toContain('validationBudget: 0');
|
||||
expect(serializeKtxProjectConfig(config)).toContain('enabled: false');
|
||||
expect(serializeKtxProjectConfig(config)).toContain('llmProposals: false');
|
||||
expect(serializeKtxProjectConfig(config)).toContain('validationRequiredForManifest: true');
|
||||
expect(serializeKtxProjectConfig(config)).toContain('acceptThreshold: 0.91');
|
||||
expect(serializeKtxProjectConfig(config)).toContain('reviewThreshold: 0.61');
|
||||
expect(serializeKtxProjectConfig(config)).toContain('maxLlmTablesPerBatch: 12');
|
||||
expect(serializeKtxProjectConfig(config)).toContain('maxCandidatesPerColumn: 7');
|
||||
expect(serializeKtxProjectConfig(config)).toContain('profileSampleRows: 500');
|
||||
expect(serializeKtxProjectConfig(config)).toContain('validationConcurrency: 2');
|
||||
expect(serializeKtxProjectConfig(config)).toContain('validationBudget: 0');
|
||||
});
|
||||
|
||||
it('parses the scan relationship validation budget sentinel', () => {
|
||||
const config = parseKloProjectConfig(`
|
||||
const config = parseKtxProjectConfig(`
|
||||
project: demo
|
||||
scan:
|
||||
relationships:
|
||||
|
|
@ -264,11 +264,11 @@ scan:
|
|||
`);
|
||||
|
||||
expect(config.scan.relationships.validationBudget).toBe('all');
|
||||
expect(serializeKloProjectConfig(config)).toContain('validationBudget: all');
|
||||
expect(serializeKtxProjectConfig(config)).toContain('validationBudget: all');
|
||||
});
|
||||
|
||||
it('falls back to safe scan relationship defaults for invalid numeric settings', () => {
|
||||
const config = parseKloProjectConfig(`
|
||||
const config = parseKtxProjectConfig(`
|
||||
project: demo
|
||||
scan:
|
||||
relationships:
|
||||
|
|
@ -293,7 +293,7 @@ scan:
|
|||
});
|
||||
|
||||
it('falls back for invalid scan relationship validation budget strings', () => {
|
||||
const config = parseKloProjectConfig(`
|
||||
const config = parseKtxProjectConfig(`
|
||||
project: demo
|
||||
scan:
|
||||
relationships:
|
||||
|
|
@ -305,7 +305,7 @@ scan:
|
|||
|
||||
it('rejects legacy local LLM and embedding fields', () => {
|
||||
expect(() =>
|
||||
parseKloProjectConfig(`
|
||||
parseKtxProjectConfig(`
|
||||
project: demo
|
||||
ingest:
|
||||
llm:
|
||||
|
|
@ -314,7 +314,7 @@ ingest:
|
|||
).toThrow('Unsupported ingest.llm: use top-level llm.provider, llm.models, and ingest.workUnits');
|
||||
|
||||
expect(() =>
|
||||
parseKloProjectConfig(`
|
||||
parseKtxProjectConfig(`
|
||||
project: demo
|
||||
scan:
|
||||
enrichment:
|
||||
|
|
@ -323,7 +323,7 @@ scan:
|
|||
).toThrow('Unsupported scan.enrichment.backend: use scan.enrichment.mode');
|
||||
|
||||
expect(() =>
|
||||
parseKloProjectConfig(`
|
||||
parseKtxProjectConfig(`
|
||||
project: demo
|
||||
scan:
|
||||
enrichment:
|
||||
|
|
@ -334,7 +334,7 @@ scan:
|
|||
).toThrow('Unsupported scan.enrichment.llm: use top-level llm.provider and llm.models');
|
||||
|
||||
expect(() =>
|
||||
parseKloProjectConfig(`
|
||||
parseKtxProjectConfig(`
|
||||
project: demo
|
||||
ingest:
|
||||
embeddings:
|
||||
|
|
@ -346,7 +346,7 @@ ingest:
|
|||
|
||||
it('rejects gateway embedding configs', () => {
|
||||
expect(() =>
|
||||
parseKloProjectConfig(`
|
||||
parseKtxProjectConfig(`
|
||||
project: demo
|
||||
ingest:
|
||||
embeddings:
|
||||
|
|
@ -357,7 +357,7 @@ ingest:
|
|||
).toThrow('Unsupported ingest.embeddings.backend: gateway');
|
||||
|
||||
expect(() =>
|
||||
parseKloProjectConfig(`
|
||||
parseKtxProjectConfig(`
|
||||
project: demo
|
||||
scan:
|
||||
enrichment:
|
||||
|
|
@ -371,9 +371,9 @@ scan:
|
|||
});
|
||||
|
||||
it('fills optional sections when a minimal config is loaded', () => {
|
||||
const config = parseKloProjectConfig('project: local\n');
|
||||
const config = parseKtxProjectConfig('project: local\n');
|
||||
|
||||
expect(config).toEqual(buildDefaultKloProjectConfig('local'));
|
||||
expect(config).toEqual(buildDefaultKtxProjectConfig('local'));
|
||||
expect(config.ingest.embeddings).toEqual({
|
||||
backend: 'deterministic',
|
||||
model: 'deterministic',
|
||||
|
|
@ -382,10 +382,10 @@ scan:
|
|||
});
|
||||
|
||||
it('rejects configs without an object root', () => {
|
||||
expect(() => parseKloProjectConfig('- nope\n')).toThrow('klo.yaml must contain a YAML object');
|
||||
expect(() => parseKtxProjectConfig('- nope\n')).toThrow('ktx.yaml must contain a YAML object');
|
||||
});
|
||||
|
||||
it('rejects configs with a missing project name', () => {
|
||||
expect(() => parseKloProjectConfig('connections: {}\n')).toThrow('klo.yaml field "project" is required');
|
||||
expect(() => parseKtxProjectConfig('connections: {}\n')).toThrow('ktx.yaml field "project" is required');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,35 +1,35 @@
|
|||
import type { KloEmbeddingBackend, KloLlmBackend, KloModelRole, KloPromptCacheTtl } from '@klo/llm';
|
||||
import type { KtxEmbeddingBackend, KtxLlmBackend, KtxModelRole, KtxPromptCacheTtl } from '@ktx/llm';
|
||||
import YAML from 'yaml';
|
||||
|
||||
export type KloStorageState = 'postgres' | 'sqlite';
|
||||
export type KloSearchBackend = 'postgres-hybrid' | 'sqlite-fts5';
|
||||
type KloLocalLlmBackend = KloLlmBackend | 'none';
|
||||
type KloLocalEmbeddingBackend = KloEmbeddingBackend | 'none';
|
||||
type KloScanEnrichmentMode = 'none' | 'deterministic' | 'llm';
|
||||
export type KtxStorageState = 'postgres' | 'sqlite';
|
||||
export type KtxSearchBackend = 'postgres-hybrid' | 'sqlite-fts5';
|
||||
type KtxLocalLlmBackend = KtxLlmBackend | 'none';
|
||||
type KtxLocalEmbeddingBackend = KtxEmbeddingBackend | 'none';
|
||||
type KtxScanEnrichmentMode = 'none' | 'deterministic' | 'llm';
|
||||
|
||||
interface KloProjectPromptCachingConfig {
|
||||
interface KtxProjectPromptCachingConfig {
|
||||
enabled?: boolean;
|
||||
systemTtl?: KloPromptCacheTtl;
|
||||
toolsTtl?: KloPromptCacheTtl;
|
||||
historyTtl?: KloPromptCacheTtl;
|
||||
systemTtl?: KtxPromptCacheTtl;
|
||||
toolsTtl?: KtxPromptCacheTtl;
|
||||
historyTtl?: KtxPromptCacheTtl;
|
||||
vertexFallbackTo5m?: boolean;
|
||||
}
|
||||
|
||||
export interface KloProjectLlmProviderConfig {
|
||||
backend: KloLocalLlmBackend;
|
||||
export interface KtxProjectLlmProviderConfig {
|
||||
backend: KtxLocalLlmBackend;
|
||||
vertex?: { project?: string; location: string };
|
||||
anthropic?: { api_key?: string; base_url?: string };
|
||||
gateway?: { api_key?: string; base_url?: string };
|
||||
}
|
||||
|
||||
export interface KloProjectLlmConfig {
|
||||
provider: KloProjectLlmProviderConfig;
|
||||
models: Partial<Record<KloModelRole, string>> & { default?: string };
|
||||
promptCaching?: KloProjectPromptCachingConfig;
|
||||
export interface KtxProjectLlmConfig {
|
||||
provider: KtxProjectLlmProviderConfig;
|
||||
models: Partial<Record<KtxModelRole, string>> & { default?: string };
|
||||
promptCaching?: KtxProjectPromptCachingConfig;
|
||||
}
|
||||
|
||||
export interface KloProjectEmbeddingConfig {
|
||||
backend: KloLocalEmbeddingBackend;
|
||||
export interface KtxProjectEmbeddingConfig {
|
||||
backend: KtxLocalEmbeddingBackend;
|
||||
model?: string;
|
||||
dimensions: number;
|
||||
openai?: { api_key?: string; base_url?: string };
|
||||
|
|
@ -37,18 +37,18 @@ export interface KloProjectEmbeddingConfig {
|
|||
batchSize?: number;
|
||||
}
|
||||
|
||||
export interface KloScanEnrichmentConfig {
|
||||
mode: KloScanEnrichmentMode;
|
||||
embeddings?: KloProjectEmbeddingConfig;
|
||||
export interface KtxScanEnrichmentConfig {
|
||||
mode: KtxScanEnrichmentMode;
|
||||
embeddings?: KtxProjectEmbeddingConfig;
|
||||
}
|
||||
|
||||
export interface KloIngestWorkUnitsConfig {
|
||||
export interface KtxIngestWorkUnitsConfig {
|
||||
stepBudget: number;
|
||||
maxConcurrency: number;
|
||||
failureMode: 'abort' | 'continue';
|
||||
}
|
||||
|
||||
export interface KloScanRelationshipConfig {
|
||||
export interface KtxScanRelationshipConfig {
|
||||
enabled: boolean;
|
||||
llmProposals: boolean;
|
||||
validationRequiredForManifest: boolean;
|
||||
|
|
@ -61,40 +61,40 @@ export interface KloScanRelationshipConfig {
|
|||
validationBudget?: number | 'all';
|
||||
}
|
||||
|
||||
export interface KloProjectScanConfig {
|
||||
enrichment: KloScanEnrichmentConfig;
|
||||
relationships: KloScanRelationshipConfig;
|
||||
export interface KtxProjectScanConfig {
|
||||
enrichment: KtxScanEnrichmentConfig;
|
||||
relationships: KtxScanRelationshipConfig;
|
||||
}
|
||||
|
||||
export interface KloProjectConnectionConfig {
|
||||
export interface KtxProjectConnectionConfig {
|
||||
driver: string;
|
||||
url?: string;
|
||||
readonly?: boolean;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface KloProjectSetupConfig {
|
||||
export interface KtxProjectSetupConfig {
|
||||
database_connection_ids: string[];
|
||||
completed_steps: string[];
|
||||
}
|
||||
|
||||
export interface KloProjectConfig {
|
||||
export interface KtxProjectConfig {
|
||||
project: string;
|
||||
setup?: KloProjectSetupConfig;
|
||||
connections: Record<string, KloProjectConnectionConfig>;
|
||||
setup?: KtxProjectSetupConfig;
|
||||
connections: Record<string, KtxProjectConnectionConfig>;
|
||||
storage: {
|
||||
state: KloStorageState;
|
||||
search: KloSearchBackend;
|
||||
state: KtxStorageState;
|
||||
search: KtxSearchBackend;
|
||||
git: {
|
||||
auto_commit: boolean;
|
||||
author: string;
|
||||
};
|
||||
};
|
||||
llm: KloProjectLlmConfig;
|
||||
llm: KtxProjectLlmConfig;
|
||||
ingest: {
|
||||
adapters: string[];
|
||||
embeddings: KloProjectEmbeddingConfig;
|
||||
workUnits: KloIngestWorkUnitsConfig;
|
||||
embeddings: KtxProjectEmbeddingConfig;
|
||||
workUnits: KtxIngestWorkUnitsConfig;
|
||||
};
|
||||
agent: {
|
||||
run_research: {
|
||||
|
|
@ -106,7 +106,7 @@ export interface KloProjectConfig {
|
|||
memory: {
|
||||
auto_commit: boolean;
|
||||
};
|
||||
scan: KloProjectScanConfig;
|
||||
scan: KtxProjectScanConfig;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
|
|
@ -167,7 +167,7 @@ function ratioConfigValue(value: unknown, fallback: number): number {
|
|||
return value;
|
||||
}
|
||||
|
||||
function localLlmBackend(value: unknown, fallback: KloLocalLlmBackend, section = 'llm.provider'): KloLocalLlmBackend {
|
||||
function localLlmBackend(value: unknown, fallback: KtxLocalLlmBackend, section = 'llm.provider'): KtxLocalLlmBackend {
|
||||
if (value == null) {
|
||||
return fallback;
|
||||
}
|
||||
|
|
@ -181,9 +181,9 @@ function localLlmBackend(value: unknown, fallback: KloLocalLlmBackend, section =
|
|||
|
||||
function localEmbeddingBackend(
|
||||
value: unknown,
|
||||
fallback: KloLocalEmbeddingBackend,
|
||||
fallback: KtxLocalEmbeddingBackend,
|
||||
section = 'ingest.embeddings',
|
||||
): KloLocalEmbeddingBackend {
|
||||
): KtxLocalEmbeddingBackend {
|
||||
if (value == null) {
|
||||
return fallback;
|
||||
}
|
||||
|
|
@ -200,7 +200,7 @@ function localEmbeddingBackend(
|
|||
throw new Error(`Unsupported ${section}.backend: ${String(value)}`);
|
||||
}
|
||||
|
||||
function scanEnrichmentMode(value: unknown, fallback: KloScanEnrichmentMode): KloScanEnrichmentMode {
|
||||
function scanEnrichmentMode(value: unknown, fallback: KtxScanEnrichmentMode): KtxScanEnrichmentMode {
|
||||
if (value == null) {
|
||||
return fallback;
|
||||
}
|
||||
|
|
@ -239,26 +239,26 @@ function optionalProviderConfig(value: unknown): { api_key?: string; base_url?:
|
|||
};
|
||||
}
|
||||
|
||||
function parseModels(value: unknown): KloProjectLlmConfig['models'] {
|
||||
function parseModels(value: unknown): KtxProjectLlmConfig['models'] {
|
||||
if (!isRecord(value)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const models: KloProjectLlmConfig['models'] = {};
|
||||
const models: KtxProjectLlmConfig['models'] = {};
|
||||
for (const [role, model] of Object.entries(value)) {
|
||||
const modelName = optionalNonEmptyString(model);
|
||||
if (modelName) {
|
||||
models[role as KloModelRole] = modelName;
|
||||
models[role as KtxModelRole] = modelName;
|
||||
}
|
||||
}
|
||||
return models;
|
||||
}
|
||||
|
||||
function promptCacheTtl(value: unknown): KloPromptCacheTtl | undefined {
|
||||
function promptCacheTtl(value: unknown): KtxPromptCacheTtl | undefined {
|
||||
return value === '5m' || value === '1h' ? value : undefined;
|
||||
}
|
||||
|
||||
function parsePromptCaching(value: unknown): KloProjectPromptCachingConfig | undefined {
|
||||
function parsePromptCaching(value: unknown): KtxProjectPromptCachingConfig | undefined {
|
||||
if (!isRecord(value)) {
|
||||
return undefined;
|
||||
}
|
||||
|
|
@ -274,9 +274,9 @@ function parsePromptCaching(value: unknown): KloProjectPromptCachingConfig | und
|
|||
|
||||
function parseProjectLlmProviderConfig(
|
||||
raw: Record<string, unknown>,
|
||||
defaults: KloProjectLlmProviderConfig,
|
||||
defaults: KtxProjectLlmProviderConfig,
|
||||
section: string,
|
||||
): KloProjectLlmProviderConfig {
|
||||
): KtxProjectLlmProviderConfig {
|
||||
rejectLegacyProvider(section, raw.provider);
|
||||
|
||||
const vertex = isRecord(raw.vertex)
|
||||
|
|
@ -296,7 +296,7 @@ function parseProjectLlmProviderConfig(
|
|||
};
|
||||
}
|
||||
|
||||
function parseProjectLlmConfig(raw: Record<string, unknown>, defaults: KloProjectLlmConfig): KloProjectLlmConfig {
|
||||
function parseProjectLlmConfig(raw: Record<string, unknown>, defaults: KtxProjectLlmConfig): KtxProjectLlmConfig {
|
||||
const provider = isRecord(raw.provider) ? raw.provider : {};
|
||||
return {
|
||||
provider: parseProjectLlmProviderConfig(provider, defaults.provider, 'llm.provider'),
|
||||
|
|
@ -307,9 +307,9 @@ function parseProjectLlmConfig(raw: Record<string, unknown>, defaults: KloProjec
|
|||
|
||||
function parseProjectEmbeddingConfig(
|
||||
raw: Record<string, unknown>,
|
||||
defaults: KloProjectEmbeddingConfig,
|
||||
defaults: KtxProjectEmbeddingConfig,
|
||||
section: string,
|
||||
): KloProjectEmbeddingConfig {
|
||||
): KtxProjectEmbeddingConfig {
|
||||
rejectLegacyProvider(section, raw.provider);
|
||||
|
||||
const openai = optionalProviderConfig(raw.openai);
|
||||
|
|
@ -338,8 +338,8 @@ function parseProjectEmbeddingConfig(
|
|||
|
||||
function parseScanRelationshipConfig(
|
||||
raw: Record<string, unknown>,
|
||||
defaults: KloScanRelationshipConfig,
|
||||
): KloScanRelationshipConfig {
|
||||
defaults: KtxScanRelationshipConfig,
|
||||
): KtxScanRelationshipConfig {
|
||||
const validationBudget = validationBudgetConfigValue(
|
||||
raw.validation_budget ?? raw.validationBudget,
|
||||
defaults.validationBudget,
|
||||
|
|
@ -380,8 +380,8 @@ function workUnitFailureMode(value: unknown, fallback: 'abort' | 'continue'): 'a
|
|||
|
||||
function parseIngestWorkUnitsConfig(
|
||||
raw: Record<string, unknown>,
|
||||
defaults: KloIngestWorkUnitsConfig,
|
||||
): KloIngestWorkUnitsConfig {
|
||||
defaults: KtxIngestWorkUnitsConfig,
|
||||
): KtxIngestWorkUnitsConfig {
|
||||
return {
|
||||
stepBudget: positiveIntegerConfigValue(raw.stepBudget, defaults.stepBudget),
|
||||
maxConcurrency: positiveIntegerConfigValue(raw.maxConcurrency, defaults.maxConcurrency),
|
||||
|
|
@ -389,7 +389,7 @@ function parseIngestWorkUnitsConfig(
|
|||
};
|
||||
}
|
||||
|
||||
export function buildDefaultKloProjectConfig(projectName = 'klo-project'): KloProjectConfig {
|
||||
export function buildDefaultKtxProjectConfig(projectName = 'ktx-project'): KtxProjectConfig {
|
||||
return {
|
||||
project: projectName,
|
||||
connections: {},
|
||||
|
|
@ -398,7 +398,7 @@ export function buildDefaultKloProjectConfig(projectName = 'klo-project'): KloPr
|
|||
search: 'sqlite-fts5',
|
||||
git: {
|
||||
auto_commit: true,
|
||||
author: 'klo <klo@example.com>',
|
||||
author: 'ktx <ktx@example.com>',
|
||||
},
|
||||
},
|
||||
llm: {
|
||||
|
|
@ -449,18 +449,18 @@ export function buildDefaultKloProjectConfig(projectName = 'klo-project'): KloPr
|
|||
};
|
||||
}
|
||||
|
||||
export function parseKloProjectConfig(raw: string): KloProjectConfig {
|
||||
export function parseKtxProjectConfig(raw: string): KtxProjectConfig {
|
||||
const parsed = YAML.parse(raw) as unknown;
|
||||
if (!isRecord(parsed)) {
|
||||
throw new Error('klo.yaml must contain a YAML object');
|
||||
throw new Error('ktx.yaml must contain a YAML object');
|
||||
}
|
||||
|
||||
const project = parsed.project;
|
||||
if (typeof project !== 'string' || project.trim().length === 0) {
|
||||
throw new Error('klo.yaml field "project" is required');
|
||||
throw new Error('ktx.yaml field "project" is required');
|
||||
}
|
||||
|
||||
const defaults = buildDefaultKloProjectConfig(project.trim());
|
||||
const defaults = buildDefaultKtxProjectConfig(project.trim());
|
||||
const llm = isRecord(parsed.llm) ? parsed.llm : {};
|
||||
const storage = isRecord(parsed.storage) ? parsed.storage : {};
|
||||
const storageGit = isRecord(storage.git) ? storage.git : {};
|
||||
|
|
@ -496,7 +496,7 @@ export function parseKloProjectConfig(raw: string): KloProjectConfig {
|
|||
defaults.ingest.embeddings,
|
||||
'scan.enrichment.embeddings',
|
||||
);
|
||||
const parsedScanEnrichment: KloScanEnrichmentConfig = {
|
||||
const parsedScanEnrichment: KtxScanEnrichmentConfig = {
|
||||
mode: scanEnrichmentMode(scanEnrichment.mode, defaults.scan.enrichment.mode),
|
||||
...(isRecord(scanEnrichment.embeddings) ? { embeddings: scanEmbeddings } : {}),
|
||||
};
|
||||
|
|
@ -513,7 +513,7 @@ export function parseKloProjectConfig(raw: string): KloProjectConfig {
|
|||
}
|
||||
: {}),
|
||||
connections: isRecord(parsed.connections)
|
||||
? (parsed.connections as Record<string, KloProjectConnectionConfig>)
|
||||
? (parsed.connections as Record<string, KtxProjectConnectionConfig>)
|
||||
: defaults.connections,
|
||||
storage: {
|
||||
state: storage.state === 'sqlite' ? 'sqlite' : defaults.storage.state,
|
||||
|
|
@ -546,6 +546,6 @@ export function parseKloProjectConfig(raw: string): KloProjectConfig {
|
|||
};
|
||||
}
|
||||
|
||||
export function serializeKloProjectConfig(config: KloProjectConfig): string {
|
||||
export function serializeKtxProjectConfig(config: KtxProjectConfig): string {
|
||||
return `${YAML.stringify(config, { indent: 2, lineWidth: 0 }).trimEnd()}\n`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
export type {
|
||||
KloProjectConfig,
|
||||
KloProjectConnectionConfig,
|
||||
KloProjectEmbeddingConfig,
|
||||
KloProjectLlmConfig,
|
||||
KloSearchBackend,
|
||||
KloStorageState,
|
||||
KtxProjectConfig,
|
||||
KtxProjectConnectionConfig,
|
||||
KtxProjectEmbeddingConfig,
|
||||
KtxProjectLlmConfig,
|
||||
KtxSearchBackend,
|
||||
KtxStorageState,
|
||||
} from './config.js';
|
||||
export { buildDefaultKloProjectConfig, parseKloProjectConfig, serializeKloProjectConfig } from './config.js';
|
||||
export { buildDefaultKtxProjectConfig, parseKtxProjectConfig, serializeKtxProjectConfig } from './config.js';
|
||||
export type { LocalGitFileStoreDeps } from './local-git-file-store.js';
|
||||
export { LocalGitFileStore } from './local-git-file-store.js';
|
||||
export { kloLocalStateDbPath } from './local-state-db.js';
|
||||
export { ktxLocalStateDbPath } from './local-state-db.js';
|
||||
export type {
|
||||
ConnectionMappingBootstrap,
|
||||
LookerMappingBootstrap,
|
||||
|
|
@ -22,12 +22,12 @@ export {
|
|||
parseLookmlMappingBootstrap,
|
||||
parseMetabaseMappingBootstrap,
|
||||
} from './mappings-yaml-schema.js';
|
||||
export type { InitKloProjectOptions, InitKloProjectResult, KloLocalProject, LoadKloProjectOptions } from './project.js';
|
||||
export { initKloProject, loadKloProject } from './project.js';
|
||||
export type { KloSetupStep } from './setup-config.js';
|
||||
export type { InitKtxProjectOptions, InitKtxProjectResult, KtxLocalProject, LoadKtxProjectOptions } from './project.js';
|
||||
export { initKtxProject, loadKtxProject } from './project.js';
|
||||
export type { KtxSetupStep } from './setup-config.js';
|
||||
export {
|
||||
KLO_SETUP_STEPS,
|
||||
markKloSetupStepComplete,
|
||||
mergeKloSetupGitignoreEntries,
|
||||
setKloSetupDatabaseConnectionIds,
|
||||
KTX_SETUP_STEPS,
|
||||
markKtxSetupStepComplete,
|
||||
mergeKtxSetupGitignoreEntries,
|
||||
setKtxSetupDatabaseConnectionIds,
|
||||
} from './setup-config.js';
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
|
|||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { GitService, type KloCoreConfig } from '../core/index.js';
|
||||
import { GitService, type KtxCoreConfig } from '../core/index.js';
|
||||
import { LocalGitFileStore } from './local-git-file-store.js';
|
||||
|
||||
describe('LocalGitFileStore', () => {
|
||||
|
|
@ -10,15 +10,15 @@ describe('LocalGitFileStore', () => {
|
|||
let store: LocalGitFileStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await mkdtemp(join(tmpdir(), 'klo-local-store-'));
|
||||
const coreConfig: KloCoreConfig = {
|
||||
tempDir = await mkdtemp(join(tmpdir(), 'ktx-local-store-'));
|
||||
const coreConfig: KtxCoreConfig = {
|
||||
storage: { configDir: tempDir, homeDir: tempDir },
|
||||
git: {
|
||||
userName: 'klo',
|
||||
userEmail: 'klo@example.com',
|
||||
userName: 'ktx',
|
||||
userEmail: 'ktx@example.com',
|
||||
bootstrapMessage: 'Initialize test project',
|
||||
bootstrapAuthor: 'klo',
|
||||
bootstrapAuthorEmail: 'klo@example.com',
|
||||
bootstrapAuthor: 'ktx',
|
||||
bootstrapAuthorEmail: 'ktx@example.com',
|
||||
},
|
||||
};
|
||||
const git = new GitService(coreConfig);
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|||
import type {
|
||||
GitCommitInfo,
|
||||
GitService,
|
||||
KloFileHistoryEntry,
|
||||
KloFileListResult,
|
||||
KloFileReadResult,
|
||||
KloFileStorePort,
|
||||
KloFileWriteResult,
|
||||
KtxFileHistoryEntry,
|
||||
KtxFileListResult,
|
||||
KtxFileReadResult,
|
||||
KtxFileStorePort,
|
||||
KtxFileWriteResult,
|
||||
} from '../core/index.js';
|
||||
|
||||
export interface LocalGitFileStoreDeps {
|
||||
|
|
@ -19,7 +19,7 @@ function normalizeRelativePath(filePath: string): string {
|
|||
return filePath.replaceAll('\\', '/').replace(/^\.\/+/, '');
|
||||
}
|
||||
|
||||
function gitInfoToWriteResult(info: GitCommitInfo): KloFileWriteResult {
|
||||
function gitInfoToWriteResult(info: GitCommitInfo): KtxFileWriteResult {
|
||||
return {
|
||||
success: true,
|
||||
commitHash: info.commitHash,
|
||||
|
|
@ -31,7 +31,7 @@ function gitInfoToWriteResult(info: GitCommitInfo): KloFileWriteResult {
|
|||
};
|
||||
}
|
||||
|
||||
export class LocalGitFileStore implements KloFileStorePort<LocalGitFileStore> {
|
||||
export class LocalGitFileStore implements KtxFileStorePort<LocalGitFileStore> {
|
||||
private readonly rootDir: string;
|
||||
private readonly git: GitService;
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ export class LocalGitFileStore implements KloFileStorePort<LocalGitFileStore> {
|
|||
authorEmail: string,
|
||||
commitMessage: string,
|
||||
options?: { skipLock?: boolean },
|
||||
): Promise<KloFileWriteResult> {
|
||||
): Promise<KtxFileWriteResult> {
|
||||
const relativePath = this.safeRelativePath(path);
|
||||
const absolutePath = this.absolutePath(relativePath);
|
||||
await fs.mkdir(dirname(absolutePath), { recursive: true });
|
||||
|
|
@ -65,7 +65,7 @@ export class LocalGitFileStore implements KloFileStorePort<LocalGitFileStore> {
|
|||
return { ...gitInfoToWriteResult(info), path: relativePath, operation: 'write' };
|
||||
}
|
||||
|
||||
async readFile(path: string): Promise<KloFileReadResult> {
|
||||
async readFile(path: string): Promise<KtxFileReadResult> {
|
||||
const relativePath = this.safeRelativePath(path);
|
||||
const absolutePath = this.absolutePath(relativePath);
|
||||
const content = await fs.readFile(absolutePath, 'utf-8');
|
||||
|
|
@ -84,7 +84,7 @@ export class LocalGitFileStore implements KloFileStorePort<LocalGitFileStore> {
|
|||
authorEmail: string,
|
||||
commitMessage: string,
|
||||
options?: { skipLock?: boolean },
|
||||
): Promise<KloFileWriteResult | null> {
|
||||
): Promise<KtxFileWriteResult | null> {
|
||||
const relativePath = this.safeRelativePath(path);
|
||||
const absolutePath = this.absolutePath(relativePath);
|
||||
try {
|
||||
|
|
@ -103,7 +103,7 @@ export class LocalGitFileStore implements KloFileStorePort<LocalGitFileStore> {
|
|||
return { ...gitInfoToWriteResult(info), path: relativePath, operation: 'delete' };
|
||||
}
|
||||
|
||||
async listFiles(path = '', stripPrefix = false): Promise<KloFileListResult> {
|
||||
async listFiles(path = '', stripPrefix = false): Promise<KtxFileListResult> {
|
||||
const relativePath = path ? this.safeRelativePath(path) : '';
|
||||
const searchRoot = relativePath ? this.absolutePath(relativePath) : this.rootDir;
|
||||
let files: string[];
|
||||
|
|
@ -121,14 +121,14 @@ export class LocalGitFileStore implements KloFileStorePort<LocalGitFileStore> {
|
|||
const relativeFiles = files
|
||||
.map((file) => normalizeRelativePath(relative(this.rootDir, file)))
|
||||
.filter((file) => !file.startsWith('.git/') && !file.includes('/.git/'))
|
||||
.filter((file) => !file.startsWith('.klo/cache/'))
|
||||
.filter((file) => !file.startsWith('.ktx/cache/'))
|
||||
.map((file) => (stripPrefix && prefix && file.startsWith(prefix) ? file.slice(prefix.length) : file))
|
||||
.sort();
|
||||
|
||||
return { files: relativeFiles };
|
||||
}
|
||||
|
||||
async getFileHistory(path: string): Promise<KloFileHistoryEntry[]> {
|
||||
async getFileHistory(path: string): Promise<KtxFileHistoryEntry[]> {
|
||||
const relativePath = this.safeRelativePath(path);
|
||||
const history = await this.git.getFileHistory(relativePath);
|
||||
return history.map((entry) => ({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { join } from 'node:path';
|
||||
import type { KloLocalProject } from './project.js';
|
||||
import type { KtxLocalProject } from './project.js';
|
||||
|
||||
export function kloLocalStateDbPath(project: Pick<KloLocalProject, 'projectDir'>): string {
|
||||
return join(project.projectDir, '.klo', 'db.sqlite');
|
||||
export function ktxLocalStateDbPath(project: Pick<KtxLocalProject, 'projectDir'>): string {
|
||||
return join(project.projectDir, '.ktx', 'db.sqlite');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {
|
|||
parseMetabaseMappingBootstrap,
|
||||
} from './mappings-yaml-schema.js';
|
||||
|
||||
describe('klo.yaml mapping bootstrap schema', () => {
|
||||
describe('ktx.yaml mapping bootstrap schema', () => {
|
||||
it('parses Metabase mapping intent with CLI syncMode default ALL', () => {
|
||||
const bootstrap = parseMetabaseMappingBootstrap('prod-metabase', {
|
||||
driver: 'metabase',
|
||||
|
|
@ -14,7 +14,7 @@ describe('klo.yaml mapping bootstrap schema', () => {
|
|||
databaseMappings: { '1': 'prod-warehouse', '2': null },
|
||||
syncEnabled: { '1': true, '2': false },
|
||||
selections: { collections: [12], items: [345] },
|
||||
defaultTagNames: ['klo', 'prod'],
|
||||
defaultTagNames: ['ktx', 'prod'],
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -25,7 +25,7 @@ describe('klo.yaml mapping bootstrap schema', () => {
|
|||
syncEnabled: { '1': true, '2': false },
|
||||
syncMode: 'ALL',
|
||||
selections: { collections: [12], items: [345] },
|
||||
defaultTagNames: ['klo', 'prod'],
|
||||
defaultTagNames: ['ktx', 'prod'],
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import * as z from 'zod';
|
||||
import type { KloProjectConnectionConfig } from './config.js';
|
||||
import type { KtxProjectConnectionConfig } from './config.js';
|
||||
|
||||
const metabaseSyncModeSchema = z.enum(['ALL', 'ONLY', 'EXCEPT']);
|
||||
const positiveIntegerValueSchema = z.number().int().positive();
|
||||
|
|
@ -66,13 +66,13 @@ function assertPositiveIntegerKeys(field: string, record: Record<string, unknown
|
|||
}
|
||||
}
|
||||
|
||||
function driverOf(connection: KloProjectConnectionConfig): string {
|
||||
function driverOf(connection: KtxProjectConnectionConfig): string {
|
||||
return String(connection.driver ?? '').toLowerCase();
|
||||
}
|
||||
|
||||
export function parseMetabaseMappingBootstrap(
|
||||
connectionId: string,
|
||||
connection: KloProjectConnectionConfig,
|
||||
connection: KtxProjectConnectionConfig,
|
||||
): MetabaseMappingBootstrap {
|
||||
const rawMappings = recordValue(connection.mappings);
|
||||
assertPositiveIntegerKeys('databaseMappings', recordValue(rawMappings.databaseMappings));
|
||||
|
|
@ -91,7 +91,7 @@ export function parseMetabaseMappingBootstrap(
|
|||
|
||||
export function parseLookerMappingBootstrap(
|
||||
connectionId: string,
|
||||
connection: KloProjectConnectionConfig,
|
||||
connection: KtxProjectConnectionConfig,
|
||||
): LookerMappingBootstrap {
|
||||
const parsed = lookerMappingsSchema.parse(recordValue(connection.mappings));
|
||||
return {
|
||||
|
|
@ -103,7 +103,7 @@ export function parseLookerMappingBootstrap(
|
|||
|
||||
export function parseLookmlMappingBootstrap(
|
||||
connectionId: string,
|
||||
connection: KloProjectConnectionConfig,
|
||||
connection: KtxProjectConnectionConfig,
|
||||
): LookmlMappingBootstrap {
|
||||
const parsed = lookmlMappingsSchema.parse(recordValue(connection.mappings));
|
||||
return {
|
||||
|
|
@ -115,7 +115,7 @@ export function parseLookmlMappingBootstrap(
|
|||
|
||||
export function parseConnectionMappingBootstrap(
|
||||
connectionId: string,
|
||||
connection: KloProjectConnectionConfig,
|
||||
connection: KtxProjectConnectionConfig,
|
||||
): ConnectionMappingBootstrap | null {
|
||||
if (!connection.mappings || typeof connection.mappings !== 'object' || Array.isArray(connection.mappings)) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ import { mkdtemp, readFile, rm, stat } from 'node:fs/promises';
|
|||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { initKloProject, loadKloProject } from './project.js';
|
||||
import { initKtxProject, loadKtxProject } from './project.js';
|
||||
|
||||
describe('KLO local project runtime', () => {
|
||||
describe('KTX local project runtime', () => {
|
||||
let tempDir: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await mkdtemp(join(tmpdir(), 'klo-project-runtime-'));
|
||||
tempDir = await mkdtemp(join(tmpdir(), 'ktx-project-runtime-'));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
|
@ -18,7 +18,7 @@ describe('KLO local project runtime', () => {
|
|||
it('initializes the standalone project layout and commits it', async () => {
|
||||
const projectDir = join(tempDir, 'warehouse');
|
||||
|
||||
const result = await initKloProject({
|
||||
const result = await initKtxProject({
|
||||
projectDir,
|
||||
projectName: 'warehouse',
|
||||
authorName: 'Agent',
|
||||
|
|
@ -28,8 +28,8 @@ describe('KLO local project runtime', () => {
|
|||
expect(result.projectDir).toBe(projectDir);
|
||||
expect(result.config.project).toBe('warehouse');
|
||||
expect(result.commitHash).toMatch(/^[0-9a-f]{40}$/);
|
||||
await expect(readFile(join(projectDir, 'klo.yaml'), 'utf-8')).resolves.toContain('project: warehouse');
|
||||
const gitignore = await readFile(join(projectDir, '.klo/.gitignore'), 'utf-8');
|
||||
await expect(readFile(join(projectDir, 'ktx.yaml'), 'utf-8')).resolves.toContain('project: warehouse');
|
||||
const gitignore = await readFile(join(projectDir, '.ktx/.gitignore'), 'utf-8');
|
||||
expect(gitignore).toContain('cache/');
|
||||
expect(gitignore).toContain('db.sqlite');
|
||||
expect(gitignore).toContain('secrets/');
|
||||
|
|
@ -44,9 +44,9 @@ describe('KLO local project runtime', () => {
|
|||
|
||||
it('loads an initialized project with a working file store', async () => {
|
||||
const projectDir = join(tempDir, 'warehouse');
|
||||
await initKloProject({ projectDir, projectName: 'warehouse' });
|
||||
await initKtxProject({ projectDir, projectName: 'warehouse' });
|
||||
|
||||
const loaded = await loadKloProject({ projectDir });
|
||||
const loaded = await loadKtxProject({ projectDir });
|
||||
await loaded.fileStore.writeFile(
|
||||
'knowledge/global/revenue.md',
|
||||
'# Revenue\n',
|
||||
|
|
@ -63,13 +63,13 @@ describe('KLO local project runtime', () => {
|
|||
|
||||
it('rejects reinitializing an existing project unless force is set', async () => {
|
||||
const projectDir = join(tempDir, 'warehouse');
|
||||
await initKloProject({ projectDir, projectName: 'warehouse' });
|
||||
await initKtxProject({ projectDir, projectName: 'warehouse' });
|
||||
|
||||
await expect(initKloProject({ projectDir, projectName: 'warehouse' })).rejects.toThrow(
|
||||
'Project already contains klo.yaml',
|
||||
await expect(initKtxProject({ projectDir, projectName: 'warehouse' })).rejects.toThrow(
|
||||
'Project already contains ktx.yaml',
|
||||
);
|
||||
|
||||
await expect(initKloProject({ projectDir, projectName: 'warehouse-v2', force: true })).resolves.toMatchObject({
|
||||
await expect(initKtxProject({ projectDir, projectName: 'warehouse-v2', force: true })).resolves.toMatchObject({
|
||||
config: {
|
||||
project: 'warehouse-v2',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,59 +1,59 @@
|
|||
import { promises as fs } from 'node:fs';
|
||||
import { basename, dirname, join, resolve } from 'node:path';
|
||||
import { GitService, type KloCoreConfig, type KloLogger, noopLogger } from '../core/index.js';
|
||||
import type { KloProjectConfig } from './config.js';
|
||||
import { buildDefaultKloProjectConfig, parseKloProjectConfig, serializeKloProjectConfig } from './config.js';
|
||||
import { GitService, type KtxCoreConfig, type KtxLogger, noopLogger } from '../core/index.js';
|
||||
import type { KtxProjectConfig } from './config.js';
|
||||
import { buildDefaultKtxProjectConfig, parseKtxProjectConfig, serializeKtxProjectConfig } from './config.js';
|
||||
import { LocalGitFileStore } from './local-git-file-store.js';
|
||||
|
||||
export interface InitKloProjectOptions {
|
||||
export interface InitKtxProjectOptions {
|
||||
projectDir: string;
|
||||
projectName?: string;
|
||||
force?: boolean;
|
||||
authorName?: string;
|
||||
authorEmail?: string;
|
||||
logger?: KloLogger;
|
||||
logger?: KtxLogger;
|
||||
}
|
||||
|
||||
export interface LoadKloProjectOptions {
|
||||
export interface LoadKtxProjectOptions {
|
||||
projectDir: string;
|
||||
authorName?: string;
|
||||
authorEmail?: string;
|
||||
logger?: KloLogger;
|
||||
logger?: KtxLogger;
|
||||
}
|
||||
|
||||
export interface KloLocalProject {
|
||||
export interface KtxLocalProject {
|
||||
projectDir: string;
|
||||
configPath: string;
|
||||
config: KloProjectConfig;
|
||||
coreConfig: KloCoreConfig;
|
||||
config: KtxProjectConfig;
|
||||
coreConfig: KtxCoreConfig;
|
||||
git: GitService;
|
||||
fileStore: LocalGitFileStore;
|
||||
}
|
||||
|
||||
export interface InitKloProjectResult extends KloLocalProject {
|
||||
export interface InitKtxProjectResult extends KtxLocalProject {
|
||||
commitHash: string | null;
|
||||
}
|
||||
|
||||
const TRACKED_SCAFFOLD_FILES: Array<{ path: string; content: string }> = [
|
||||
{ path: '.klo/.gitignore', content: 'cache/\ndb.sqlite\nsecrets/\nsetup/\nagents/\n' },
|
||||
{ path: '.klo/prompts/.gitkeep', content: '' },
|
||||
{ path: '.klo/skills/.gitkeep', content: '' },
|
||||
{ path: '.ktx/.gitignore', content: 'cache/\ndb.sqlite\nsecrets/\nsetup/\nagents/\n' },
|
||||
{ path: '.ktx/prompts/.gitkeep', content: '' },
|
||||
{ path: '.ktx/skills/.gitkeep', content: '' },
|
||||
{ path: 'knowledge/global/.gitkeep', content: '' },
|
||||
{ path: 'semantic-layer/.gitkeep', content: '' },
|
||||
{ path: 'raw-sources/.gitkeep', content: '' },
|
||||
];
|
||||
|
||||
function createCoreConfig(projectDir: string, authorName: string, authorEmail: string): KloCoreConfig {
|
||||
function createCoreConfig(projectDir: string, authorName: string, authorEmail: string): KtxCoreConfig {
|
||||
return {
|
||||
storage: {
|
||||
configDir: projectDir,
|
||||
homeDir: dirname(projectDir),
|
||||
worktreesDir: join(projectDir, '.klo/worktrees'),
|
||||
worktreesDir: join(projectDir, '.ktx/worktrees'),
|
||||
},
|
||||
git: {
|
||||
userName: authorName,
|
||||
userEmail: authorEmail,
|
||||
bootstrapMessage: 'Initialize klo project repository',
|
||||
bootstrapMessage: 'Initialize ktx project repository',
|
||||
bootstrapAuthor: authorName,
|
||||
bootstrapAuthorEmail: authorEmail,
|
||||
},
|
||||
|
|
@ -77,18 +77,18 @@ async function writeProjectFile(projectDir: string, relativePath: string, conten
|
|||
|
||||
async function createRuntime(
|
||||
projectDir: string,
|
||||
config: KloProjectConfig,
|
||||
config: KtxProjectConfig,
|
||||
authorName: string,
|
||||
authorEmail: string,
|
||||
logger: KloLogger,
|
||||
): Promise<KloLocalProject> {
|
||||
logger: KtxLogger,
|
||||
): Promise<KtxLocalProject> {
|
||||
const coreConfig = createCoreConfig(projectDir, authorName, authorEmail);
|
||||
const git = new GitService(coreConfig, logger);
|
||||
await git.onModuleInit();
|
||||
|
||||
return {
|
||||
projectDir,
|
||||
configPath: join(projectDir, 'klo.yaml'),
|
||||
configPath: join(projectDir, 'ktx.yaml'),
|
||||
config,
|
||||
coreConfig,
|
||||
git,
|
||||
|
|
@ -96,31 +96,31 @@ async function createRuntime(
|
|||
};
|
||||
}
|
||||
|
||||
export async function initKloProject(options: InitKloProjectOptions): Promise<InitKloProjectResult> {
|
||||
export async function initKtxProject(options: InitKtxProjectOptions): Promise<InitKtxProjectResult> {
|
||||
const projectDir = resolve(options.projectDir);
|
||||
const projectName = options.projectName?.trim() || basename(projectDir) || 'klo-project';
|
||||
const authorName = options.authorName ?? 'klo';
|
||||
const authorEmail = options.authorEmail ?? 'klo@example.com';
|
||||
const projectName = options.projectName?.trim() || basename(projectDir) || 'ktx-project';
|
||||
const authorName = options.authorName ?? 'ktx';
|
||||
const authorEmail = options.authorEmail ?? 'ktx@example.com';
|
||||
const logger = options.logger ?? noopLogger;
|
||||
const configPath = join(projectDir, 'klo.yaml');
|
||||
const configPath = join(projectDir, 'ktx.yaml');
|
||||
|
||||
await fs.mkdir(projectDir, { recursive: true });
|
||||
if (!options.force && (await fileExists(configPath))) {
|
||||
throw new Error(`Project already contains klo.yaml: ${configPath}`);
|
||||
throw new Error(`Project already contains ktx.yaml: ${configPath}`);
|
||||
}
|
||||
|
||||
const config = buildDefaultKloProjectConfig(projectName);
|
||||
const config = buildDefaultKtxProjectConfig(projectName);
|
||||
const runtime = await createRuntime(projectDir, config, authorName, authorEmail, logger);
|
||||
|
||||
await writeProjectFile(projectDir, 'klo.yaml', serializeKloProjectConfig(config));
|
||||
await fs.mkdir(join(projectDir, '.klo/cache'), { recursive: true });
|
||||
await writeProjectFile(projectDir, 'ktx.yaml', serializeKtxProjectConfig(config));
|
||||
await fs.mkdir(join(projectDir, '.ktx/cache'), { recursive: true });
|
||||
for (const file of TRACKED_SCAFFOLD_FILES) {
|
||||
await writeProjectFile(projectDir, file.path, file.content);
|
||||
}
|
||||
|
||||
const commit = await runtime.git.commitFiles(
|
||||
['klo.yaml', ...TRACKED_SCAFFOLD_FILES.map((file) => file.path)],
|
||||
`Initialize KLO project: ${projectName}`,
|
||||
['ktx.yaml', ...TRACKED_SCAFFOLD_FILES.map((file) => file.path)],
|
||||
`Initialize KTX project: ${projectName}`,
|
||||
authorName,
|
||||
authorEmail,
|
||||
);
|
||||
|
|
@ -131,13 +131,13 @@ export async function initKloProject(options: InitKloProjectOptions): Promise<In
|
|||
};
|
||||
}
|
||||
|
||||
export async function loadKloProject(options: LoadKloProjectOptions): Promise<KloLocalProject> {
|
||||
export async function loadKtxProject(options: LoadKtxProjectOptions): Promise<KtxLocalProject> {
|
||||
const projectDir = resolve(options.projectDir);
|
||||
const authorName = options.authorName ?? 'klo';
|
||||
const authorEmail = options.authorEmail ?? 'klo@example.com';
|
||||
const authorName = options.authorName ?? 'ktx';
|
||||
const authorEmail = options.authorEmail ?? 'ktx@example.com';
|
||||
const logger = options.logger ?? noopLogger;
|
||||
const configPath = join(projectDir, 'klo.yaml');
|
||||
const configPath = join(projectDir, 'ktx.yaml');
|
||||
const raw = await fs.readFile(configPath, 'utf-8');
|
||||
const config = parseKloProjectConfig(raw);
|
||||
const config = parseKtxProjectConfig(raw);
|
||||
return createRuntime(projectDir, config, authorName, authorEmail, logger);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { buildDefaultKloProjectConfig } from './config.js';
|
||||
import { buildDefaultKtxProjectConfig } from './config.js';
|
||||
import {
|
||||
markKloSetupStepComplete,
|
||||
mergeKloSetupGitignoreEntries,
|
||||
setKloSetupDatabaseConnectionIds,
|
||||
markKtxSetupStepComplete,
|
||||
mergeKtxSetupGitignoreEntries,
|
||||
setKtxSetupDatabaseConnectionIds,
|
||||
} from './setup-config.js';
|
||||
|
||||
describe('KLO setup config helpers', () => {
|
||||
describe('KTX setup config helpers', () => {
|
||||
it('marks setup steps complete without duplicating existing state', () => {
|
||||
const config = buildDefaultKloProjectConfig('warehouse');
|
||||
const config = buildDefaultKtxProjectConfig('warehouse');
|
||||
|
||||
const withProject = markKloSetupStepComplete(config, 'project');
|
||||
const withProjectAgain = markKloSetupStepComplete(withProject, 'project');
|
||||
const withLlm = markKloSetupStepComplete(withProjectAgain, 'llm');
|
||||
const withContext = markKloSetupStepComplete(withLlm, 'context');
|
||||
const withProject = markKtxSetupStepComplete(config, 'project');
|
||||
const withProjectAgain = markKtxSetupStepComplete(withProject, 'project');
|
||||
const withLlm = markKtxSetupStepComplete(withProjectAgain, 'llm');
|
||||
const withContext = markKtxSetupStepComplete(withLlm, 'context');
|
||||
|
||||
expect(withProject.setup).toEqual({
|
||||
database_connection_ids: [],
|
||||
|
|
@ -27,23 +27,23 @@ describe('KLO setup config helpers', () => {
|
|||
|
||||
it('preserves database connection ids while marking a step complete', () => {
|
||||
const config = {
|
||||
...buildDefaultKloProjectConfig('warehouse'),
|
||||
...buildDefaultKtxProjectConfig('warehouse'),
|
||||
setup: {
|
||||
database_connection_ids: ['warehouse'],
|
||||
completed_steps: ['databases'],
|
||||
},
|
||||
};
|
||||
|
||||
expect(markKloSetupStepComplete(config, 'project').setup).toEqual({
|
||||
expect(markKtxSetupStepComplete(config, 'project').setup).toEqual({
|
||||
database_connection_ids: ['warehouse'],
|
||||
completed_steps: ['databases', 'project'],
|
||||
});
|
||||
});
|
||||
|
||||
it('sets setup database connection ids without duplicates', () => {
|
||||
const config = buildDefaultKloProjectConfig('warehouse');
|
||||
const config = buildDefaultKtxProjectConfig('warehouse');
|
||||
|
||||
const withDatabases = setKloSetupDatabaseConnectionIds(config, ['warehouse', 'analytics', 'warehouse']);
|
||||
const withDatabases = setKtxSetupDatabaseConnectionIds(config, ['warehouse', 'analytics', 'warehouse']);
|
||||
|
||||
expect(withDatabases.setup).toEqual({
|
||||
database_connection_ids: ['warehouse', 'analytics'],
|
||||
|
|
@ -53,10 +53,10 @@ describe('KLO setup config helpers', () => {
|
|||
});
|
||||
|
||||
it('marks databases complete only when requested', () => {
|
||||
const config = markKloSetupStepComplete(buildDefaultKloProjectConfig('warehouse'), 'project');
|
||||
const config = markKtxSetupStepComplete(buildDefaultKtxProjectConfig('warehouse'), 'project');
|
||||
|
||||
const withDatabases = setKloSetupDatabaseConnectionIds(config, ['warehouse'], { complete: true });
|
||||
const withDatabasesAgain = setKloSetupDatabaseConnectionIds(withDatabases, ['warehouse'], { complete: true });
|
||||
const withDatabases = setKtxSetupDatabaseConnectionIds(config, ['warehouse'], { complete: true });
|
||||
const withDatabasesAgain = setKtxSetupDatabaseConnectionIds(withDatabases, ['warehouse'], { complete: true });
|
||||
|
||||
expect(withDatabases.setup).toEqual({
|
||||
database_connection_ids: ['warehouse'],
|
||||
|
|
@ -66,10 +66,10 @@ describe('KLO setup config helpers', () => {
|
|||
});
|
||||
|
||||
it('merges setup-local gitignore entries without removing existing lines', () => {
|
||||
expect(mergeKloSetupGitignoreEntries('cache/\ndb.sqlite\n')).toBe(
|
||||
expect(mergeKtxSetupGitignoreEntries('cache/\ndb.sqlite\n')).toBe(
|
||||
['cache/', 'db.sqlite', 'secrets/', 'setup/', 'agents/', ''].join('\n'),
|
||||
);
|
||||
expect(mergeKloSetupGitignoreEntries('cache/\nsecrets/\n')).toBe(
|
||||
expect(mergeKtxSetupGitignoreEntries('cache/\nsecrets/\n')).toBe(
|
||||
['cache/', 'secrets/', 'setup/', 'agents/', ''].join('\n'),
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import type { KloProjectConfig } from './config.js';
|
||||
import type { KtxProjectConfig } from './config.js';
|
||||
|
||||
export const KLO_SETUP_STEPS = ['project', 'llm', 'embeddings', 'databases', 'sources', 'context', 'agents'] as const;
|
||||
export const KTX_SETUP_STEPS = ['project', 'llm', 'embeddings', 'databases', 'sources', 'context', 'agents'] as const;
|
||||
|
||||
export type KloSetupStep = (typeof KLO_SETUP_STEPS)[number];
|
||||
export type KtxSetupStep = (typeof KTX_SETUP_STEPS)[number];
|
||||
|
||||
const SETUP_GITIGNORE_ENTRIES = ['secrets/', 'setup/', 'agents/'] as const;
|
||||
|
||||
export function markKloSetupStepComplete(config: KloProjectConfig, step: KloSetupStep): KloProjectConfig {
|
||||
export function markKtxSetupStepComplete(config: KtxProjectConfig, step: KtxSetupStep): KtxProjectConfig {
|
||||
const databaseConnectionIds = config.setup?.database_connection_ids ?? [];
|
||||
const completedSteps = config.setup?.completed_steps ?? [];
|
||||
return {
|
||||
|
|
@ -18,11 +18,11 @@ export function markKloSetupStepComplete(config: KloProjectConfig, step: KloSetu
|
|||
};
|
||||
}
|
||||
|
||||
export function setKloSetupDatabaseConnectionIds(
|
||||
config: KloProjectConfig,
|
||||
export function setKtxSetupDatabaseConnectionIds(
|
||||
config: KtxProjectConfig,
|
||||
connectionIds: string[],
|
||||
options: { complete?: boolean } = {},
|
||||
): KloProjectConfig {
|
||||
): KtxProjectConfig {
|
||||
const uniqueConnectionIds = [...new Set(connectionIds.filter((connectionId) => connectionId.trim().length > 0))];
|
||||
const completedSteps = config.setup?.completed_steps ?? [];
|
||||
const nextCompletedSteps =
|
||||
|
|
@ -39,7 +39,7 @@ export function setKloSetupDatabaseConnectionIds(
|
|||
};
|
||||
}
|
||||
|
||||
export function mergeKloSetupGitignoreEntries(content: string): string {
|
||||
export function mergeKtxSetupGitignoreEntries(content: string): string {
|
||||
const lines = content
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trimEnd())
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue