diff --git a/examples/postgres-historic/README.md b/examples/postgres-historic/README.md index 3e27b462..1a97cba2 100644 --- a/examples/postgres-historic/README.md +++ b/examples/postgres-historic/README.md @@ -13,8 +13,8 @@ generates query workload under separate users, runs `ktx setup` with - Docker with Compose v2 - Node and pnpm matching the KTX workspace -- `python-service/.venv` already created, or `KTX_SQL_ANALYSIS_URL` pointing at - a running service that exposes `/api/sql/analyze-for-fingerprint` +- `KTX_SQL_ANALYSIS_URL` or `KTX_DAEMON_URL` pointing at a running SQL-analysis + service that exposes `/api/sql/analyze-for-fingerprint` ## Run @@ -111,5 +111,5 @@ The manifest should have `dialect: "postgres"`, `degraded: true`, - Missing grants: confirm `GRANT pg_read_all_stats TO ktx_reader;`. - Empty templates: rerun `scripts/generate-workload.sh base` and keep `--historic-sql-min-calls 2` for the smoke. -- SQL-analysis failures: set `KTX_SQL_ANALYSIS_URL` to the running service URL - or create `python-service/.venv` before running `scripts/smoke.sh`. +- SQL-analysis failures: set `KTX_SQL_ANALYSIS_URL` or `KTX_DAEMON_URL` to a + running service URL before running `scripts/smoke.sh`. diff --git a/examples/postgres-historic/scripts/smoke.sh b/examples/postgres-historic/scripts/smoke.sh index d948cf8e..5b1be929 100755 --- a/examples/postgres-historic/scripts/smoke.sh +++ b/examples/postgres-historic/scripts/smoke.sh @@ -4,46 +4,23 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" EXAMPLE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" KTX_ROOT="$(cd "$EXAMPLE_DIR/../.." && pwd)" -REPO_ROOT="$(cd "$KTX_ROOT/.." && pwd)" COMPOSE_FILE="$EXAMPLE_DIR/docker-compose.yml" PROJECT_PARENT="${KTX_POSTGRES_HISTORIC_PROJECT_PARENT:-$(mktemp -d)}" PROJECT_DIR="$PROJECT_PARENT/postgres-historic-ktx" KTX_BIN="$KTX_ROOT/packages/cli/dist/bin.js" -PYTHON_SERVICE_LOG="$PROJECT_PARENT/python-service.log" -PYTHON_SERVICE_PID="" cleanup() { - if [[ -n "$PYTHON_SERVICE_PID" ]]; then - kill "$PYTHON_SERVICE_PID" >/dev/null 2>&1 || true - fi if [[ "${KTX_POSTGRES_HISTORIC_KEEP_DOCKER:-0}" != "1" ]]; then docker compose -f "$COMPOSE_FILE" down -v >/dev/null 2>&1 || true fi } trap cleanup EXIT -start_sql_analysis_if_needed() { - if [[ -n "${KTX_SQL_ANALYSIS_URL:-}" ]]; then +require_sql_analysis_url() { + if [[ -n "${KTX_SQL_ANALYSIS_URL:-}" || -n "${KTX_DAEMON_URL:-}" ]]; then return fi - if [[ ! -d "$REPO_ROOT/python-service/.venv" ]]; then - echo "Set KTX_SQL_ANALYSIS_URL or create python-service/.venv before running this smoke." >&2 - exit 1 - fi - ( - cd "$REPO_ROOT/python-service" - source .venv/bin/activate - uvicorn app.main:app --host 127.0.0.1 --port 18081 >"$PYTHON_SERVICE_LOG" 2>&1 - ) & - PYTHON_SERVICE_PID="$!" - export KTX_SQL_ANALYSIS_URL="http://127.0.0.1:18081" - for _ in $(seq 1 60); do - if curl -fsS "$KTX_SQL_ANALYSIS_URL/health" >/dev/null 2>&1; then - return - fi - sleep 1 - done - echo "SQL analysis service did not become healthy. Log: $PYTHON_SERVICE_LOG" >&2 + echo "Set KTX_SQL_ANALYSIS_URL or KTX_DAEMON_URL before running this smoke." >&2 exit 1 } @@ -111,7 +88,7 @@ NODE cd "$KTX_ROOT" pnpm --filter @ktx/context run build pnpm --filter @ktx/cli run build -start_sql_analysis_if_needed +require_sql_analysis_url docker compose -f "$COMPOSE_FILE" up -d --wait "$EXAMPLE_DIR/scripts/generate-workload.sh" base diff --git a/packages/context/src/sl/schemas.ts b/packages/context/src/sl/schemas.ts index 55e07f22..ad7e5b01 100644 --- a/packages/context/src/sl/schemas.ts +++ b/packages/context/src/sl/schemas.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; // Literal vocabularies — kept in lockstep with the Python Pydantic model at -// python-service/ktx-sl/semantic_layer/models.py (SourceColumn / ColumnRole / +// python/ktx-sl/semantic_layer/models.py (SourceColumn / ColumnRole / // ColumnVisibility / JoinDeclaration). If these diverge, YAMLs can pass // TypeScript validation at ingest time but fail Python loading at query time. const columnTypeValues = ['string', 'number', 'time', 'boolean'] as const; diff --git a/packages/context/src/sql-analysis/http-sql-analysis-port.test.ts b/packages/context/src/sql-analysis/http-sql-analysis-port.test.ts index f6cdd3fe..f9bf513b 100644 --- a/packages/context/src/sql-analysis/http-sql-analysis-port.test.ts +++ b/packages/context/src/sql-analysis/http-sql-analysis-port.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest'; import { createHttpSqlAnalysisPort } from './http-sql-analysis-port.js'; describe('createHttpSqlAnalysisPort', () => { - it('calls the python-service fingerprint endpoint and maps snake_case response fields', async () => { + it('calls the SQL-analysis fingerprint endpoint and maps snake_case response fields', async () => { const requestJson = vi.fn(async () => ({ fingerprint: 'fingerprint-template', normalized_sql: 'SELECT * FROM analytics.orders WHERE status = ?', @@ -26,7 +26,7 @@ describe('createHttpSqlAnalysisPort', () => { }); }); - it('preserves python-service parse errors in the mapped result', async () => { + it('preserves SQL-analysis parse errors in the mapped result', async () => { const requestJson = vi.fn(async () => ({ fingerprint: '', normalized_sql: '', diff --git a/packages/context/src/tools/base-tool.ts b/packages/context/src/tools/base-tool.ts index 37da69a0..0566a0ca 100644 --- a/packages/context/src/tools/base-tool.ts +++ b/packages/context/src/tools/base-tool.ts @@ -151,7 +151,7 @@ export abstract class BaseTool { } } }, - // Send only markdown to LLM - frontend still receives full { markdown, structured } via stream + // Send only markdown to the LLM; tool callers still receive the structured output. toModelOutput: ({ output }) => { if (output && typeof output === 'object' && 'markdown' in output) { return { type: 'content', value: [{ type: 'text', text: output.markdown as string }] }; diff --git a/scripts/check-boundaries.test.mjs b/scripts/check-boundaries.test.mjs index fab2b87e..39946464 100644 --- a/scripts/check-boundaries.test.mjs +++ b/scripts/check-boundaries.test.mjs @@ -14,7 +14,7 @@ function lowerProductName() { describe('scanFileContent', () => { it('rejects source imports from application directories', () => { const serverAlias = '@' + 'server/contracts'; - const pythonAppPath = 'python-service/' + 'app/api/endpoints/semantic_layer.py'; + const pythonAppPath = `${['python', 'service'].join('-')}/app/api/endpoints/semantic_layer.py`; const violations = [ ...scanFileContent('packages/context/src/index.ts', `import { orpc } from '${serverAlias}';`), diff --git a/scripts/ci-artifact-upload.test.mjs b/scripts/ci-artifact-upload.test.mjs index d18db979..3fecdfbc 100644 --- a/scripts/ci-artifact-upload.test.mjs +++ b/scripts/ci-artifact-upload.test.mjs @@ -4,7 +4,7 @@ import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { describe, it } from 'node:test'; -const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..'); +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const ciWorkflowPath = resolve(repoRoot, '.github', 'workflows', 'ci.yml'); async function readCiWorkflowOrSkip(testContext) { @@ -21,7 +21,7 @@ async function readCiWorkflowOrSkip(testContext) { } describe('KTX CI artifact upload contract', () => { - it('uploads verified KTX package artifacts from check-ktx-subtree', async (testContext) => { + it('uploads verified KTX package artifacts from the standalone check job', async (testContext) => { const workflow = await readCiWorkflowOrSkip(testContext); if (workflow === null) { return; @@ -29,42 +29,35 @@ describe('KTX CI artifact upload contract', () => { assert.match( workflow, - /name: Build ktx package artifacts and verify public smoke\s+run: cd ktx && pnpm run artifacts:build && pnpm run artifacts:verify-manifest && pnpm run artifacts:verify-demo\s+- name: Upload ktx package artifacts/s, + /name: Build and verify package artifacts\s+run: pnpm run artifacts:check\s+- name: Upload package artifacts/s, ); assert.match(workflow, /uses: actions\/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f/); assert.match(workflow, /name: ktx-package-artifacts-\$\{\{ github\.sha \}\}/); - assert.match(workflow, /ktx\/dist\/artifacts\/manifest\.json/); - assert.match(workflow, /ktx\/dist\/artifacts\/npm\/\*\.tgz/); - assert.match(workflow, /ktx\/dist\/artifacts\/python\/\*\.whl/); - assert.match(workflow, /ktx\/dist\/artifacts\/python\/\*\.tar\.gz/); + assert.match(workflow, /dist\/artifacts\/manifest\.json/); + assert.match(workflow, /dist\/artifacts\/npm\/\*\.tgz/); + assert.match(workflow, /dist\/artifacts\/python\/\*\.whl/); + assert.match(workflow, /dist\/artifacts\/python\/\*\.tar\.gz/); assert.match(workflow, /if-no-files-found: error/); assert.match(workflow, /retention-days: 7/); }); - it('runs packed demo artifact smoke on Linux and macOS', async (testContext) => { + it('runs TypeScript and Python checks in the standalone workflow', async (testContext) => { const workflow = await readCiWorkflowOrSkip(testContext); if (workflow === null) { return; } - assert.match(workflow, /check-ktx-packed-demo:/); - assert.match(workflow, /matrix:\s+os: \[ubuntu-latest, macos-latest\]/s); - assert.match(workflow, /name: Download ktx package artifacts/); - assert.match(workflow, /path: ktx\/dist\/artifacts/); - assert.match(workflow, /run: cd ktx && pnpm run artifacts:verify-demo/); + assert.match(workflow, /run: pnpm run check/); + assert.match(workflow, /run: uv sync --all-packages/); + assert.match(workflow, /run: uv run pytest/); }); - it('includes packed demo artifact smoke in ci-success', async (testContext) => { + it('does not depend on host application CI jobs', async (testContext) => { const workflow = await readCiWorkflowOrSkip(testContext); if (workflow === null) { return; } - assert.match( - workflow, - /needs: \[check-ktx-subtree, check-ktx-packed-demo, build-python-service, test-server, build-frontend, run-pre-commit, build-docker-images\]/, - ); - assert.match(workflow, /needs\.check-ktx-packed-demo\.result.*== "failure"/); - assert.match(workflow, /needs\.check-ktx-packed-demo\.result.*== "cancelled"/); + assert.doesNotMatch(workflow, /build-python-service|test-server|build-frontend|build-docker-images/); }); }); diff --git a/scripts/conductor-scripts.test.mjs b/scripts/conductor-scripts.test.mjs index 610dacf1..38689def 100644 --- a/scripts/conductor-scripts.test.mjs +++ b/scripts/conductor-scripts.test.mjs @@ -31,11 +31,10 @@ describe('Conductor workspace scripts', () => { it('runs the KTX daemon on the documented fixed local port', async () => { const runScript = await readText('scripts/conductor-run.sh'); - const legacyServerPackagePattern = new RegExp(`@${['kae', 'lio'].join('')}/server|python-service|npx`); assert.match(runScript, /pnpm run build/); assert.match(runScript, /source \.venv\/bin\/activate/); assert.match(runScript, /uv run ktx-daemon serve-http --host 127\.0\.0\.1 --port 8765/); - assert.doesNotMatch(runScript, legacyServerPackagePattern); + assert.doesNotMatch(runScript, /\bnpx\b/); }); }); diff --git a/scripts/examples-docs.test.mjs b/scripts/examples-docs.test.mjs index 3d3aa168..50a9556d 100644 --- a/scripts/examples-docs.test.mjs +++ b/scripts/examples-docs.test.mjs @@ -66,6 +66,8 @@ describe('standalone example docs', () => { assert.match(smoke, /assert_manifest "\$FIRST_MANIFEST" true/); assert.match(smoke, /assert_manifest "\$SECOND_MANIFEST" false/); assert.match(smoke, /assert_manifest "\$RESET_MANIFEST" true/); + assert.doesNotMatch(readme, /python-service/); + assert.doesNotMatch(smoke, /python-service|PYTHON_SERVICE|REPO_ROOT/); }); it('lists every published TypeScript package in the package root README', async () => { diff --git a/scripts/precommit-check.test.mjs b/scripts/precommit-check.test.mjs index 94f71e09..55ef66bb 100644 --- a/scripts/precommit-check.test.mjs +++ b/scripts/precommit-check.test.mjs @@ -9,7 +9,7 @@ function commandKeys(files) { describe('precommit-check', () => { it('skips files outside ktx', () => { - assert.deepEqual(commandKeys(['server/src/app.ts']), []); + assert.deepEqual(commandKeys(['outside-workspace/src/app.ts']), []); }); it('runs only the touched package checks for package code', () => {