From c2b24df2c8f3e40a32583c223be0284ae58105d3 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov <7889985+andreybavt@users.noreply.github.com> Date: Mon, 11 May 2026 16:39:00 +0200 Subject: [PATCH] feat: add historic sql skill schemas --- .../historic-sql/skill-schemas.test.ts | 74 +++++++++++++++++++ .../adapters/historic-sql/skill-schemas.ts | 31 ++++++++ packages/context/src/ingest/index.ts | 9 +++ 3 files changed, 114 insertions(+) create mode 100644 packages/context/src/ingest/adapters/historic-sql/skill-schemas.test.ts create mode 100644 packages/context/src/ingest/adapters/historic-sql/skill-schemas.ts diff --git a/packages/context/src/ingest/adapters/historic-sql/skill-schemas.test.ts b/packages/context/src/ingest/adapters/historic-sql/skill-schemas.test.ts new file mode 100644 index 00000000..b384c0c0 --- /dev/null +++ b/packages/context/src/ingest/adapters/historic-sql/skill-schemas.test.ts @@ -0,0 +1,74 @@ +import { describe, expect, it } from 'vitest'; +import { z } from 'zod'; +import { + patternOutputSchema, + patternsArraySchema, + tableUsageOutputSchema, +} from './skill-schemas.js'; + +describe('historic-sql skill schemas', () => { + it('accepts table usage output and preserves future keys', () => { + const parsed = tableUsageOutputSchema.parse({ + narrative: 'Orders are queried for paid/refunded lifecycle analysis.', + frequencyTier: 'high', + commonFilters: ['status', 'created_at'], + commonGroupBys: ['status'], + commonJoins: [{ table: 'public.customers', on: ['customer_id'] }], + staleSince: null, + analystNote: 'preserve me', + }); + + expect(parsed).toMatchObject({ + narrative: 'Orders are queried for paid/refunded lifecycle analysis.', + frequencyTier: 'high', + commonFilters: ['status', 'created_at'], + commonGroupBys: ['status'], + commonJoins: [{ table: 'public.customers', on: ['customer_id'] }], + staleSince: null, + analystNote: 'preserve me', + }); + }); + + it('rejects invalid frequency tiers', () => { + const result = tableUsageOutputSchema.safeParse({ + narrative: 'Orders are queried often.', + frequencyTier: 'sometimes', + commonFilters: [], + commonJoins: [], + }); + + expect(result.success).toBe(false); + }); + + it('accepts pattern outputs used for wiki projection', () => { + const parsed = patternsArraySchema.parse([ + { + slug: 'order-lifecycle-analysis', + title: 'Order Lifecycle Analysis', + narrative: 'Teams inspect order status by customer and month.', + definitionSql: 'select status, count(*) from public.orders group by status', + tablesInvolved: ['public.orders', 'public.customers'], + slRefs: ['orders', 'customers'], + constituentTemplateIds: ['template_1', 'template_2'], + }, + ]); + + expect(parsed[0]).toEqual({ + slug: 'order-lifecycle-analysis', + title: 'Order Lifecycle Analysis', + narrative: 'Teams inspect order status by customer and month.', + definitionSql: 'select status, count(*) from public.orders group by status', + tablesInvolved: ['public.orders', 'public.customers'], + slRefs: ['orders', 'customers'], + constituentTemplateIds: ['template_1', 'template_2'], + }); + }); + + it('exports zod schemas that can produce JSON schema for prompt prefixes', () => { + const tableUsageJsonSchema = z.toJSONSchema(tableUsageOutputSchema); + const patternJsonSchema = z.toJSONSchema(patternOutputSchema); + + expect(tableUsageJsonSchema).toMatchObject({ type: 'object' }); + expect(patternJsonSchema).toMatchObject({ type: 'object' }); + }); +}); diff --git a/packages/context/src/ingest/adapters/historic-sql/skill-schemas.ts b/packages/context/src/ingest/adapters/historic-sql/skill-schemas.ts new file mode 100644 index 00000000..340cd5b1 --- /dev/null +++ b/packages/context/src/ingest/adapters/historic-sql/skill-schemas.ts @@ -0,0 +1,31 @@ +import { z } from 'zod'; + +export const tableUsageOutputSchema = z + .object({ + narrative: z.string(), + frequencyTier: z.enum(['high', 'mid', 'low', 'unused']), + commonFilters: z.array(z.string()), + commonGroupBys: z.array(z.string()).optional(), + commonJoins: z.array( + z.object({ + table: z.string(), + on: z.array(z.string()), + }), + ), + staleSince: z.iso.datetime().nullable().optional(), + }) + .passthrough(); +export type TableUsageOutput = z.infer; + +export const patternOutputSchema = z.object({ + slug: z.string(), + title: z.string(), + narrative: z.string(), + definitionSql: z.string(), + tablesInvolved: z.array(z.string()), + slRefs: z.array(z.string()), + constituentTemplateIds: z.array(z.string()), +}); +export type PatternOutput = z.infer; + +export const patternsArraySchema = z.array(patternOutputSchema); diff --git a/packages/context/src/ingest/index.ts b/packages/context/src/ingest/index.ts index 9991391f..0db5c2eb 100644 --- a/packages/context/src/ingest/index.ts +++ b/packages/context/src/ingest/index.ts @@ -330,6 +330,15 @@ export type { BigQueryHistoricSqlQueryHistoryReaderOptions } from './adapters/hi export { PostgresPgssQueryHistoryReader } from './adapters/historic-sql/postgres-pgss-query-history-reader.js'; export { SnowflakeHistoricSqlQueryHistoryReader } from './adapters/historic-sql/snowflake-query-history-reader.js'; export { stageHistoricSqlTemplates } from './adapters/historic-sql/stage.js'; +export { + patternOutputSchema, + patternsArraySchema, + tableUsageOutputSchema, +} from './adapters/historic-sql/skill-schemas.js'; +export type { + PatternOutput, + TableUsageOutput, +} from './adapters/historic-sql/skill-schemas.js'; export { pgssBaselinePath, readPgssBaseline,