diff --git a/docs/release.md b/docs/release.md index 00362c99..3a72f54e 100644 --- a/docs/release.md +++ b/docs/release.md @@ -105,27 +105,36 @@ prior rc lineage is consumed cleanly. ## Release metadata semantic-release calls `scripts/update-public-release-version.mjs` during the -prepare step before the exec publish command runs. That script updates the -following files **inside the CI runner only**: +prepare step before the exec publish command runs. That script rewrites the +following files in lockstep: -- `package.json` with the semantic-release version. -- `release-policy.json` with `publicNpmPackageVersion`, npm publish settings, - and the published package smoke-test version. +- `package.json` and `packages/cli/package.json` with the semantic-release + version. +- `python/ktx-daemon/pyproject.toml` and `python/ktx-sl/pyproject.toml` with + the same version, normalized for PEP 440 (e.g. `0.1.0-rc.2` → + `0.1.0rc2`). Branch-prefixed npm releases skip this step because they are + not published to PyPI. +- `release-policy.json` with the npm publish settings, release mode, and + published package smoke-test version. The package version itself is not + stored here; it is derived from `packages/cli/package.json.version` by + `scripts/public-npm-release-metadata.mjs`, so there is only one place that + owns the version. -The artifact packaging, readiness, and smoke-test scripts read -`publicNpmPackageVersion` from `release-policy.json` within the same CI run. -Nothing reads these files at runtime — the daemon and CLI rely on the -published `package.json` (for the installed `@kaelio/ktx` package) or -`packages/cli/package.json` (for dev-tree runs from this repo, which always -report `0.0.0-private`). Because the metadata mutation never has to survive -the run, no commit is pushed back to `main`. The git tag plus the published -npm artifact carry the version forward. +The `@semantic-release/git` plugin then commits these files back to `main` +with the release tag (see `scripts/semantic-release-config.cjs`). As a +result, the dev tree always reflects the most recently published version — +there is no sentinel pin. `ktx --version` and the bundled Python wheel both +report whatever was last released. -The bundled Python runtime wheel also derives its version from -`publicNpmPackageVersion`. Stable npm versions are reused as-is, and rc -versions are normalized to Python's version format. For example, -`0.1.0-rc.2` becomes `0.1.0rc2` in the `kaelio-ktx` wheel filename and wheel -metadata. +At runtime the CLI reads its version from its own `package.json` via +`getKtxCliPackageInfo()` (`packages/cli/src/cli-runtime.ts`); the Python +daemon reads its version from installed-package metadata via +`importlib.metadata.version()` (`python/ktx-daemon/src/ktx_daemon/__init__.py`). + +The bundled Python runtime wheel also derives its version from the same +single source. Stable npm versions are reused as-is, and rc versions are +normalized to Python's version format. For example, `0.1.0-rc.2` becomes +`0.1.0rc2` in the `kaelio-ktx` wheel filename and wheel metadata. ## npm authentication diff --git a/packages/cli/package.json b/packages/cli/package.json index 84acf6f2..296276c0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@kaelio/ktx", "version": "0.4.1", - "description": "Standalone ktx context layer for database agents", + "description": "Standalone ktx context layer for data agents", "type": "module", "engines": { "node": ">=22.0.0" @@ -30,7 +30,7 @@ "scripts": { "assets:demo": "node scripts/build-demo-assets.mjs", "build": "tsc -p tsconfig.json && node scripts/copy-runtime-assets.mjs && node ../../scripts/prepare-cli-bin.mjs", - "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true }); fs.rmSync('node_modules/.cache/tsc.tsbuildinfo', { force: true })\"", + "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"", "docs:commands": "pnpm run build && node dist/print-command-tree.js", "smoke": "vitest run src/standalone-smoke.test.ts src/example-smoke.test.ts --testTimeout 30000", "test": "vitest run --exclude src/standalone-smoke.test.ts --exclude src/example-smoke.test.ts --exclude src/setup-databases.test.ts --exclude src/scan.test.ts --exclude src/commands/connection-metabase-setup.test.ts --exclude src/setup-models.test.ts --exclude src/setup-sources.test.ts --exclude src/setup.test.ts --exclude src/connection.test.ts --exclude src/setup-embeddings.test.ts --exclude src/ingest.test.ts --exclude src/commands/connection-mapping.test.ts --exclude src/ingest-viz.test.ts --exclude src/demo.test.ts --exclude src/setup-project.test.ts --exclude src/sl.test.ts --exclude src/local-scan-connectors.test.ts --exclude src/commands/connection-notion.test.ts --exclude src/context/scan/local-scan.test.ts --exclude src/context/mcp/local-project-ports.test.ts --exclude src/context/ingest/local-stage-ingest.test.ts --exclude src/context/sl/pglite-sl-search-prototype.test.ts --exclude src/context/core/git.service.test.ts --exclude src/context/ingest/local-adapters.test.ts --exclude src/context/ingest/local-bundle-ingest.test.ts --exclude src/context/ingest/local-metabase-ingest.test.ts --exclude src/context/sl/local-sl.test.ts --exclude src/context/search/pglite-owner-process.test.ts --exclude src/context/scan/local-enrichment-artifacts.test.ts --exclude src/context/search/pglite-spike.test.ts --exclude src/context/wiki/local-knowledge.test.ts --exclude src/context/sl/local-query.test.ts --exclude src/context/scan/relationship-review-decisions.test.ts --exclude src/context/scan/relationship-profiling.test.ts", diff --git a/packages/cli/src/context/mcp/server.ts b/packages/cli/src/context/mcp/server.ts index e1236e9c..73f970d6 100644 --- a/packages/cli/src/context/mcp/server.ts +++ b/packages/cli/src/context/mcp/server.ts @@ -16,11 +16,11 @@ export function createKtxMcpServer(deps: KtxMcpServerDeps): KtxMcpServerDeps['se } export function createDefaultKtxMcpServer( - deps: Omit & { name?: string; version?: string }, + deps: Omit & { name?: string; version: string }, ): McpServer { const server = new McpServer({ name: deps.name ?? 'ktx', - version: deps.version ?? '0.0.0-private', + version: deps.version, }); createKtxMcpServer({ server: server as KtxMcpServerLike, diff --git a/packages/cli/src/ingest.ts b/packages/cli/src/ingest.ts index c821bf45..0c84be72 100644 --- a/packages/cli/src/ingest.ts +++ b/packages/cli/src/ingest.ts @@ -8,6 +8,7 @@ import type { MemoryFlowEvent, MemoryFlowReplayInput } from './context/ingest/me import { renderMemoryFlowReplay } from './context/ingest/memory-flow/render.js'; import type { KtxSqlQueryExecutorPort } from './context/connections/query-executor.js'; import { loadKtxProject, type KtxLocalProject } from './context/project/project.js'; +import { getKtxCliPackageInfo } from './cli-runtime.js'; import { resolveProjectEmbeddingProvider } from './embedding-resolution.js'; import { createKtxCliIngestQueryExecutor } from './ingest-query-executor.js'; import { readIngestReportSnapshotFile } from './ingest-report-file.js'; @@ -677,7 +678,7 @@ export async function runKtxIngest( const resolution = await resolveProjectEmbeddingProvider(project, { mode: 'ensure', installPolicy: args.runtimeInstallPolicy ?? 'never', - cliVersion: args.cliVersion ?? '0.0.0-private', + cliVersion: args.cliVersion ?? getKtxCliPackageInfo().version, io, }); const embeddingProvider = diff --git a/packages/cli/src/mcp-http-server.ts b/packages/cli/src/mcp-http-server.ts index 0eac2a71..a95dcbbe 100644 --- a/packages/cli/src/mcp-http-server.ts +++ b/packages/cli/src/mcp-http-server.ts @@ -4,7 +4,7 @@ import { loadKtxProject } from './context/project/project.js'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js'; -import type { KtxCliIo } from './cli-runtime.js'; +import { getKtxCliPackageInfo, type KtxCliIo } from './cli-runtime.js'; import { createKtxMcpServerFactory } from './mcp-server-factory.js'; const DEFAULT_ALLOWED_HOSTS = ['localhost', '127.0.0.1', '::1'] as const; @@ -178,7 +178,7 @@ export async function runKtxMcpHttpServer(options: RunKtxMcpHttpServerOptions): (await createKtxMcpServerFactory({ project: project!, projectDir: options.projectDir, - cliVersion: options.cliVersion ?? '0.0.0-private', + cliVersion: options.cliVersion ?? getKtxCliPackageInfo().version, io: options.io, })); const sessions = new Map(); diff --git a/packages/cli/src/mcp-stdio-server.ts b/packages/cli/src/mcp-stdio-server.ts index d4e21eec..93b5a155 100644 --- a/packages/cli/src/mcp-stdio-server.ts +++ b/packages/cli/src/mcp-stdio-server.ts @@ -3,7 +3,7 @@ import type { Readable, Writable } from 'node:stream'; import { loadKtxProject } from './context/project/project.js'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; -import type { KtxCliIo } from './cli-runtime.js'; +import { getKtxCliPackageInfo, type KtxCliIo } from './cli-runtime.js'; import { createKtxMcpServerFactory } from './mcp-server-factory.js'; export interface RunKtxMcpStdioServerOptions { @@ -30,7 +30,7 @@ export async function runKtxMcpStdioServer(options: RunKtxMcpStdioServerOptions) (await createKtxMcpServerFactory({ project: project!, projectDir: options.projectDir, - cliVersion: options.cliVersion ?? '0.0.0-private', + cliVersion: options.cliVersion ?? getKtxCliPackageInfo().version, io: protocolIo, })); const stdin = options.stdin ?? process.stdin; diff --git a/packages/cli/src/public-ingest.ts b/packages/cli/src/public-ingest.ts index a88a342d..1a9c6674 100644 --- a/packages/cli/src/public-ingest.ts +++ b/packages/cli/src/public-ingest.ts @@ -1,3 +1,4 @@ +import { getKtxCliPackageInfo } from './cli-runtime.js'; import { loadKtxProject, type KtxLocalProject } from './context/project/project.js'; import type { KtxProjectConnectionConfig } from './context/project/config.js'; import type { KtxProgressPort } from './context/scan/types.js'; @@ -878,7 +879,7 @@ export async function runKtxPublicIngest( for (const feature of requirements.features) { try { await ensureRuntime({ - cliVersion: args.cliVersion ?? '0.0.0-private', + cliVersion: args.cliVersion ?? getKtxCliPackageInfo().version, installPolicy: args.runtimeInstallPolicy ?? 'prompt', io, feature, diff --git a/packages/cli/src/scan.ts b/packages/cli/src/scan.ts index 393111b6..d5c4ea7c 100644 --- a/packages/cli/src/scan.ts +++ b/packages/cli/src/scan.ts @@ -1,6 +1,7 @@ import type { KtxProgressPort, KtxScanMode, KtxScanReport, KtxScanWarning } from './context/scan/types.js'; import { runLocalScan } from './context/scan/local-scan.js'; import { loadKtxProject } from './context/project/project.js'; +import { getKtxCliPackageInfo } from './cli-runtime.js'; import { resolveProjectEmbeddingProvider } from './embedding-resolution.js'; import type { KtxCliIo } from './index.js'; import { createKtxCliLocalIngestAdapters } from './local-adapters.js'; @@ -314,7 +315,7 @@ export async function runKtxScan(args: KtxScanArgs, io: KtxCliIo = process, deps const resolution = await resolveProjectEmbeddingProvider(project, { mode: 'ensure', installPolicy: args.runtimeInstallPolicy ?? 'never', - cliVersion: args.cliVersion ?? '0.0.0-private', + cliVersion: args.cliVersion ?? getKtxCliPackageInfo().version, io, }); const embeddingProvider = diff --git a/python/ktx-daemon/pyproject.toml b/python/ktx-daemon/pyproject.toml index 4f21f9de..8cb78f36 100644 --- a/python/ktx-daemon/pyproject.toml +++ b/python/ktx-daemon/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ktx-daemon" -version = "0.0.0+private" +version = "0.4.1" description = "Portable compute package for KTX semantic-layer operations" readme = "README.md" requires-python = ">=3.13" diff --git a/python/ktx-sl/pyproject.toml b/python/ktx-sl/pyproject.toml index 51e97a83..cfa7a014 100644 --- a/python/ktx-sl/pyproject.toml +++ b/python/ktx-sl/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "ktx-sl" -version = "0.0.0+private" +version = "0.4.1" description = "Agent-first semantic layer engine with aggregate locality" readme = "README.md" requires-python = ">=3.13" diff --git a/release-policy.json b/release-policy.json index 813e09d3..534f5ad9 100644 --- a/release-policy.json +++ b/release-policy.json @@ -1,6 +1,5 @@ { "schemaVersion": 1, - "publicNpmPackageVersion": "0.4.1", "releaseMode": "npm-public-release-ready", "npm": { "publish": true, diff --git a/scripts/build-python-runtime-wheel.test.mjs b/scripts/build-python-runtime-wheel.test.mjs index bf25af9a..67b85175 100644 --- a/scripts/build-python-runtime-wheel.test.mjs +++ b/scripts/build-python-runtime-wheel.test.mjs @@ -52,7 +52,10 @@ describe('runtimeWheelPyproject', () => { const pyproject = runtimeWheelPyproject(); assert.match(pyproject, /name = "kaelio-ktx"/); - assert.match(pyproject, new RegExp(`version = "${RUNTIME_WHEEL_PACKAGE_VERSION.replace(/\./g, '\\.')}"`)); + assert.match( + pyproject, + new RegExp(`version = "${RUNTIME_WHEEL_PACKAGE_VERSION.replace(/[.+]/g, (char) => `\\${char}`)}"`), + ); assert.match(pyproject, /ktx-daemon = "ktx_daemon\.__main__:main"/); assert.match(pyproject, /packages = \["semantic_layer", "ktx_daemon"\]/); assert.match(pyproject, /\[project\.optional-dependencies\]/); diff --git a/scripts/local-embeddings-runtime-smoke.test.mjs b/scripts/local-embeddings-runtime-smoke.test.mjs index 5fcd75c6..92df9dae 100644 --- a/scripts/local-embeddings-runtime-smoke.test.mjs +++ b/scripts/local-embeddings-runtime-smoke.test.mjs @@ -57,11 +57,11 @@ describe('publicKtxTarballName', () => { }); describe('expectedPublicKtxVersionPattern', () => { - it('matches the public package version and rejects the private workspace version', () => { + it('matches the public package version and rejects other versions', () => { const pattern = expectedPublicKtxVersionPattern(); assert.match(`@kaelio/ktx ${PUBLIC_NPM_PACKAGE_VERSION}\n`, pattern); - assert.doesNotMatch('@kaelio/ktx 0.0.0-private\n', pattern); + assert.doesNotMatch('@kaelio/ktx 9.9.9-other\n', pattern); }); }); diff --git a/scripts/package-artifacts.test.mjs b/scripts/package-artifacts.test.mjs index 6a3b5d72..7ea9339b 100644 --- a/scripts/package-artifacts.test.mjs +++ b/scripts/package-artifacts.test.mjs @@ -34,7 +34,6 @@ async function writeJson(path, value) { async function writeReleaseMetadataInputs(root) { await writeJson(join(root, 'release-policy.json'), { schemaVersion: 1, - publicNpmPackageVersion: PUBLIC_NPM_PACKAGE_VERSION, releaseMode: 'ci-artifact-only', npm: { publish: false, diff --git a/scripts/public-npm-release-metadata.mjs b/scripts/public-npm-release-metadata.mjs index ec671b82..a295c427 100644 --- a/scripts/public-npm-release-metadata.mjs +++ b/scripts/public-npm-release-metadata.mjs @@ -25,6 +25,10 @@ export function releasePolicyPath(rootDir = scriptRootDir()) { return join(rootDir, 'release-policy.json'); } +export function cliPackageJsonPath(rootDir = scriptRootDir()) { + return join(rootDir, 'packages', 'cli', 'package.json'); +} + function readJsonSync(path) { return JSON.parse(readFileSync(path, 'utf8')); } @@ -70,20 +74,24 @@ export function assertPublicNpmReleaseTag(tag) { throw new Error(`Invalid public npm release tag: ${tag}`); } +function readCliPackageVersion(rootDir = scriptRootDir()) { + const packageJson = readJsonSync(cliPackageJsonPath(rootDir)); + return assertPublicNpmPackageVersion(packageJson.version); +} + export function readPublicNpmReleaseMetadata(rootDir = scriptRootDir()) { const policy = readJsonSync(releasePolicyPath(rootDir)); - const version = assertPublicNpmPackageVersion(policy.publicNpmPackageVersion); const tag = assertPublicNpmReleaseTag(policy.npm?.tag); return { packageName: PUBLIC_NPM_PACKAGE_NAME, - version, + version: readCliPackageVersion(rootDir), tag, }; } export function publicNpmPackageVersion(rootDir = scriptRootDir()) { - return readPublicNpmReleaseMetadata(rootDir).version; + return readCliPackageVersion(rootDir); } export const PUBLIC_NPM_PACKAGE_VERSION = publicNpmPackageVersion(); diff --git a/scripts/release-readiness.mjs b/scripts/release-readiness.mjs index ffd22ab5..ae3aa825 100644 --- a/scripts/release-readiness.mjs +++ b/scripts/release-readiness.mjs @@ -5,7 +5,7 @@ import { dirname, join, resolve } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { packageArtifactLayout, packageReleaseMetadata, verifyArtifactManifest } from './package-artifacts.mjs'; -import { assertPublicNpmPackageVersion, publicNpmPackageVersion } from './public-npm-release-metadata.mjs'; +import { publicNpmPackageVersion } from './public-npm-release-metadata.mjs'; import { readPublishedPackageSmokeConfig } from './published-package-smoke-config.mjs'; function scriptRootDir() { @@ -138,8 +138,6 @@ export function validateReleasePolicy(policy) { throw new Error(`Unsupported release policy schemaVersion: ${policy.schemaVersion}`); } assertSupportedReleaseMode(policy.releaseMode); - assertString(policy.publicNpmPackageVersion, 'Release policy publicNpmPackageVersion'); - assertPublicNpmPackageVersion(policy.publicNpmPackageVersion); assertPlainObject(policy.npm, 'Release policy npm'); assertPlainObject(policy.python, 'Release policy python'); assertPlainObject(policy.publishedPackageSmoke, 'Release policy publishedPackageSmoke'); diff --git a/scripts/release-readiness.test.mjs b/scripts/release-readiness.test.mjs index 7cf5d498..e01337b9 100644 --- a/scripts/release-readiness.test.mjs +++ b/scripts/release-readiness.test.mjs @@ -52,7 +52,6 @@ function releasePolicy(overrides = {}) { return { schemaVersion: 1, - publicNpmPackageVersion: PUBLIC_NPM_PACKAGE_VERSION, releaseMode: 'ci-artifact-only', npm: { publish: false, diff --git a/scripts/semantic-release-config.cjs b/scripts/semantic-release-config.cjs index 2f7c3426..2dee466a 100644 --- a/scripts/semantic-release-config.cjs +++ b/scripts/semantic-release-config.cjs @@ -166,7 +166,13 @@ function createReleaseConfig(env = process.env) { [ '@semantic-release/git', { - assets: ['package.json', 'release-policy.json', 'packages/cli/package.json'], + assets: [ + 'package.json', + 'release-policy.json', + 'packages/cli/package.json', + 'python/ktx-daemon/pyproject.toml', + 'python/ktx-sl/pyproject.toml', + ], message: 'chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}', }, ], diff --git a/scripts/semantic-release-config.test.mjs b/scripts/semantic-release-config.test.mjs index 56c9e88f..24289896 100644 --- a/scripts/semantic-release-config.test.mjs +++ b/scripts/semantic-release-config.test.mjs @@ -88,7 +88,13 @@ describe('semantic-release config', () => { const config = createReleaseConfig({ KTX_RELEASE_KIND: kind, GITHUB_REF_NAME: 'main' }); const git = gitPluginOptions(config); assert.ok(git, `${kind}: @semantic-release/git plugin must be configured`); - assert.deepEqual(git.assets, ['package.json', 'release-policy.json', 'packages/cli/package.json']); + assert.deepEqual(git.assets, [ + 'package.json', + 'release-policy.json', + 'packages/cli/package.json', + 'python/ktx-daemon/pyproject.toml', + 'python/ktx-sl/pyproject.toml', + ]); assert.match(git.message, /^chore\(release\): \$\{nextRelease\.version\} \[skip ci\]/); } }); diff --git a/scripts/update-public-release-version.mjs b/scripts/update-public-release-version.mjs index 0df2d234..8ab01849 100644 --- a/scripts/update-public-release-version.mjs +++ b/scripts/update-public-release-version.mjs @@ -8,6 +8,7 @@ import { PUBLIC_NPM_PACKAGE_NAME, assertPublicNpmPackageVersion, assertPublicNpmReleaseTag, + publicNpmPackageVersionToPythonVersion, releasePolicyPath, } from './public-npm-release-metadata.mjs'; @@ -23,6 +24,42 @@ async function writeJson(path, value) { await writeFile(path, `${JSON.stringify(value, null, 2)}\n`); } +function pyprojectWithProjectVersion(source, version) { + const lines = source.split('\n'); + let inProject = false; + let replaced = false; + for (let index = 0; index < lines.length; index += 1) { + const line = lines[index]; + const sectionMatch = /^\s*\[([^\]]+)\]\s*$/.exec(line); + if (sectionMatch) { + inProject = sectionMatch[1] === 'project'; + continue; + } + if (inProject && /^\s*version\s*=\s*"[^"]*"\s*$/.test(line)) { + lines[index] = `version = "${version}"`; + replaced = true; + break; + } + } + if (!replaced) { + throw new Error('No [project].version assignment found in pyproject.toml'); + } + return lines.join('\n'); +} + +async function rewritePyprojectVersion(path, version) { + const source = await readFile(path, 'utf8'); + await writeFile(path, pyprojectWithProjectVersion(source, version)); +} + +function safePythonVersionFor(version) { + try { + return publicNpmPackageVersionToPythonVersion(version); + } catch { + return null; + } +} + export async function updatePublicReleaseVersion(rootDir, version, tag) { const safeVersion = assertPublicNpmPackageVersion(version); const safeTag = assertPublicNpmReleaseTag(tag); @@ -37,9 +74,15 @@ export async function updatePublicReleaseVersion(rootDir, version, tag) { cliPackageJson.version = safeVersion; await writeJson(cliPackageJsonPath, cliPackageJson); + const pythonVersion = safePythonVersionFor(safeVersion); + if (pythonVersion !== null) { + await rewritePyprojectVersion(join(rootDir, 'python', 'ktx-daemon', 'pyproject.toml'), pythonVersion); + await rewritePyprojectVersion(join(rootDir, 'python', 'ktx-sl', 'pyproject.toml'), pythonVersion); + } + const policyPath = releasePolicyPath(rootDir); const policy = await readJson(policyPath); - policy.publicNpmPackageVersion = safeVersion; + delete policy.publicNpmPackageVersion; policy.releaseMode = 'npm-public-release-ready'; policy.requiredBeforePublishing = []; policy.npm = { @@ -60,6 +103,7 @@ export async function updatePublicReleaseVersion(rootDir, version, tag) { return { version: safeVersion, tag: safeTag, + pythonVersion, }; } diff --git a/scripts/update-public-release-version.test.mjs b/scripts/update-public-release-version.test.mjs index cfe8b67b..63391819 100644 --- a/scripts/update-public-release-version.test.mjs +++ b/scripts/update-public-release-version.test.mjs @@ -11,23 +11,51 @@ async function writeJson(path, value) { await writeFile(path, `${JSON.stringify(value, null, 2)}\n`); } +async function writeText(path, value) { + await mkdir(join(path, '..'), { recursive: true }); + await writeFile(path, value); +} + async function readJson(path) { return JSON.parse(await readFile(path, 'utf8')); } +async function readText(path) { + return readFile(path, 'utf8'); +} + +const DAEMON_PYPROJECT = `[project] +name = "ktx-daemon" +version = "0.4.0" +description = "Portable compute package for KTX semantic-layer operations" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" +`; + +const SL_PYPROJECT = `[project] +name = "ktx-sl" +version = "0.4.0" +description = "Agent-first semantic layer engine with aggregate locality" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" +`; + async function writeReleaseFixture(root) { await writeJson(join(root, 'package.json'), { name: 'ktx-workspace', - version: '0.0.0-private', + version: '0.4.0', private: true, }); await writeJson(join(root, 'packages', 'cli', 'package.json'), { name: '@kaelio/ktx', - version: '0.0.0-private', + version: '0.4.0', }); await writeJson(join(root, 'release-policy.json'), { schemaVersion: 1, - publicNpmPackageVersion: '0.1.0-rc.1', releaseMode: 'ci-artifact-only', npm: { publish: false, @@ -43,7 +71,7 @@ async function writeReleaseFixture(root) { }, publishedPackageSmoke: { packageName: '@kaelio/ktx', - version: '0.1.0-rc.1', + version: 'latest', registry: null, }, runtimeInstaller: { @@ -53,6 +81,8 @@ async function writeReleaseFixture(root) { }, requiredBeforePublishing: ['Choose public release version.'], }); + await writeText(join(root, 'python', 'ktx-daemon', 'pyproject.toml'), DAEMON_PYPROJECT); + await writeText(join(root, 'python', 'ktx-sl', 'pyproject.toml'), SL_PYPROJECT); } describe('updatePublicReleaseVersion', () => { @@ -65,9 +95,10 @@ describe('updatePublicReleaseVersion', () => { assert.equal((await readJson(join(root, 'package.json'))).version, '0.1.0-rc.2'); assert.equal((await readJson(join(root, 'packages', 'cli', 'package.json'))).version, '0.1.0-rc.2'); + assert.match(await readText(join(root, 'python', 'ktx-daemon', 'pyproject.toml')), /^version = "0\.1\.0rc2"$/m); + assert.match(await readText(join(root, 'python', 'ktx-sl', 'pyproject.toml')), /^version = "0\.1\.0rc2"$/m); assert.deepEqual(await readJson(join(root, 'release-policy.json')), { schemaVersion: 1, - publicNpmPackageVersion: '0.1.0-rc.2', releaseMode: 'npm-public-release-ready', npm: { publish: true, @@ -110,8 +141,17 @@ describe('updatePublicReleaseVersion', () => { (await readJson(join(root, 'packages', 'cli', 'package.json'))).version, '0.1.0-feature-foo.0', ); + assert.match( + await readText(join(root, 'python', 'ktx-daemon', 'pyproject.toml')), + /^version = "0\.4\.0"$/m, + ); + assert.match( + await readText(join(root, 'python', 'ktx-sl', 'pyproject.toml')), + /^version = "0\.4\.0"$/m, + ); const policy = await readJson(join(root, 'release-policy.json')); - assert.equal(policy.publicNpmPackageVersion, '0.1.0-feature-foo.0'); + assert.equal(policy.publicNpmPackageVersion, undefined); + assert.equal(policy.publishedPackageSmoke.version, '0.1.0-feature-foo.0'); assert.equal(policy.npm.tag, 'branch-feature-foo'); } finally { await rm(root, { recursive: true, force: true }); diff --git a/uv.lock b/uv.lock index 6c5afa29..29ce5981 100644 --- a/uv.lock +++ b/uv.lock @@ -440,7 +440,7 @@ wheels = [ [[package]] name = "ktx-daemon" -version = "0.0.0+private" +version = "0.4.1" source = { editable = "python/ktx-daemon" } dependencies = [ { name = "fastapi" }, @@ -495,7 +495,7 @@ dev = [ [[package]] name = "ktx-sl" -version = "0.0.0+private" +version = "0.4.1" source = { editable = "python/ktx-sl" } dependencies = [ { name = "pydantic" },