From c58d88c0c4889c2eb101eb4f84e36a039e3019ba Mon Sep 17 00:00:00 2001 From: wiliyam Date: Wed, 1 Apr 2026 14:15:07 +0000 Subject: [PATCH] fix: create stub files on init and rename apiVersion to v1alpha1 (#20 #21) --- examples/gymcoach/agent.yaml | 2 +- .../src/__tests__/claude-adapter.test.ts | 4 +- packages/adapter-claude/src/index.ts | 2 +- packages/cli/src/__tests__/commands.test.ts | 22 +++++------ packages/cli/src/__tests__/deploy-k8s.test.ts | 4 +- packages/cli/src/__tests__/evaluate.test.ts | 2 +- .../cli/src/__tests__/generate-policy.test.ts | 4 +- .../cli/src/__tests__/scan-builder.test.ts | 4 +- packages/cli/src/__tests__/scan.test.ts | 2 +- packages/cli/src/commands/init.ts | 37 +++++++++++++++++-- packages/cli/src/commands/scan-builder.ts | 2 +- packages/cli/src/commands/validate.ts | 2 +- .../fitness-tracker/agentobservation.yaml | 2 +- .../demo/fitness-tracker/deployment.yaml | 2 +- .../demo/gymcoach/agentobservation.yaml | 2 +- .../operator/demo/gymcoach/deployment.yaml | 2 +- .../research-agent-patched-configmap.yaml | 2 +- .../voice-assistant-patched-configmap.yaml | 2 +- .../demo/research-agent/agentobservation.yaml | 2 +- .../demo/research-agent/deployment.yaml | 2 +- .../demo/trading-bot/agentobservation.yaml | 2 +- .../operator/demo/trading-bot/deployment.yaml | 2 +- .../voice-assistant/agentobservation.yaml | 2 +- .../demo/voice-assistant/deployment.yaml | 2 +- packages/sdk/src/__tests__/audit.test.ts | 2 +- .../src/__tests__/generate-registry.test.ts | 2 +- .../sdk/src/__tests__/health-index.test.ts | 4 +- packages/sdk/src/__tests__/health.test.ts | 2 +- packages/sdk/src/__tests__/loader.test.ts | 24 ++++++------ packages/sdk/src/__tests__/push.test.ts | 2 +- packages/sdk/src/__tests__/reporter.test.ts | 2 +- packages/sdk/src/__tests__/schema.test.ts | 2 +- .../sdk/src/__tests__/service-check.test.ts | 4 +- packages/sdk/src/loader/migrations/index.ts | 6 +-- .../src/loader/migrations/v1alpha1-to-v1.ts | 25 +++++++++++-- packages/sdk/src/schema/manifest.schema.ts | 2 +- packages/sidecar/src/__tests__/fixtures.ts | 2 +- .../sidecar/src/__tests__/opa-client.test.ts | 2 +- packages/sidecar/test/e2e/agent.yaml | 2 +- 39 files changed, 121 insertions(+), 73 deletions(-) diff --git a/examples/gymcoach/agent.yaml b/examples/gymcoach/agent.yaml index 93b1650..0258f78 100644 --- a/examples/gymcoach/agent.yaml +++ b/examples/gymcoach/agent.yaml @@ -1,4 +1,4 @@ -apiVersion: agentspec.io/v1 +apiVersion: agentspec.io/v1alpha1 kind: AgentSpec metadata: diff --git a/packages/adapter-claude/src/__tests__/claude-adapter.test.ts b/packages/adapter-claude/src/__tests__/claude-adapter.test.ts index 68dbc20..7824a49 100644 --- a/packages/adapter-claude/src/__tests__/claude-adapter.test.ts +++ b/packages/adapter-claude/src/__tests__/claude-adapter.test.ts @@ -7,7 +7,7 @@ import type { AgentSpecManifest } from '@agentspec/sdk' // ── Fixtures ────────────────────────────────────────────────────────────────── const baseManifest: AgentSpecManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'test-agent', @@ -90,7 +90,7 @@ describe('buildContext()', () => { it('serialises all manifest fields', () => { const ctx = buildContext({ manifest: baseManifest }) - expect(ctx).toContain('"apiVersion": "agentspec.io/v1"') + expect(ctx).toContain('"apiVersion": "agentspec.io/v1alpha1"') expect(ctx).toContain('"provider": "groq"') }) diff --git a/packages/adapter-claude/src/index.ts b/packages/adapter-claude/src/index.ts index 5ef7225..4a87f31 100644 --- a/packages/adapter-claude/src/index.ts +++ b/packages/adapter-claude/src/index.ts @@ -78,7 +78,7 @@ const REPAIR_SYSTEM_PROMPT = `Return ONLY a JSON object with this exact shape (no other text):\n` + `{"files":{"agent.yaml":""},"installCommands":[],"envVars":[]}\n\n` + `## AgentSpec v1 schema rules (enforce all of these):\n` + - `- Top-level keys: apiVersion: "agentspec.io/v1", kind: "AgentSpec"\n` + + `- Top-level keys: apiVersion: "agentspec.io/v1alpha1", kind: "AgentSpec"\n` + `- metadata: name (slug a-z0-9-), version (semver), description\n` + `- spec.model: provider, id (never "name"), apiKey: "$env:VAR"\n` + `- spec.model.fallback: provider, id, apiKey, triggerOn (array of strings)\n` + diff --git a/packages/cli/src/__tests__/commands.test.ts b/packages/cli/src/__tests__/commands.test.ts index ef34118..6b7c5c7 100644 --- a/packages/cli/src/__tests__/commands.test.ts +++ b/packages/cli/src/__tests__/commands.test.ts @@ -60,7 +60,7 @@ vi.mock('@agentspec/sdk', () => ({ migrateManifest: mockMigrateManifest, detectVersion: mockDetectVersion, isLatestVersion: mockIsLatestVersion, - LATEST_API_VERSION: 'agentspec.io/v1', + LATEST_API_VERSION: 'agentspec.io/v1alpha1', })) vi.mock('node:fs', () => ({ @@ -82,7 +82,7 @@ vi.mock('@clack/prompts', () => ({ // ── Test data ───────────────────────────────────────────────────────────────── const mockManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'test-agent', @@ -104,7 +104,7 @@ const mockManifest = { const mockLoadResult = { manifest: mockManifest, filePath: '/fake/agent.yaml', - raw: 'apiVersion: agentspec.io/v1', + raw: 'apiVersion: agentspec.io/v1alpha1', baseDir: '/fake', } @@ -179,7 +179,7 @@ describe('validate command', () => { expect(parsed.valid).toBe(true) expect(parsed.agentName).toBe('test-agent') expect(parsed.version).toBe('1.0.0') - expect(parsed.apiVersion).toBe('agentspec.io/v1') + expect(parsed.apiVersion).toBe('agentspec.io/v1alpha1') }) it('throws ExitError(1) when loadManifest throws (text mode)', async () => { @@ -735,14 +735,14 @@ describe('migrate command', () => { const validYaml = 'apiVersion: agentspec/v1alpha1\nkind: AgentSpec\nmetadata:\n name: old-agent\n' const migratedObj = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'old-agent' }, } it('prints "already at latest version" when already up to date', async () => { - mockReadFileSync.mockReturnValue('apiVersion: agentspec.io/v1\nkind: AgentSpec\n') - mockDetectVersion.mockReturnValue('agentspec.io/v1') + mockReadFileSync.mockReturnValue('apiVersion: agentspec.io/v1alpha1\nkind: AgentSpec\n') + mockDetectVersion.mockReturnValue('agentspec.io/v1alpha1') mockIsLatestVersion.mockReturnValue(true) await run(['/fake/agent.yaml']) expect(logOutput()).toContain('latest') @@ -755,7 +755,7 @@ describe('migrate command', () => { mockIsLatestVersion.mockReturnValue(false) mockMigrateManifest.mockReturnValue({ result: migratedObj, - migrationsApplied: ['agentspec/v1alpha1 → agentspec.io/v1'], + migrationsApplied: ['agentspec/v1alpha1 → agentspec.io/v1alpha1'], }) await run(['/fake/agent.yaml']) expect(mockWriteFileSync).toHaveBeenCalled() @@ -769,7 +769,7 @@ describe('migrate command', () => { mockIsLatestVersion.mockReturnValue(false) mockMigrateManifest.mockReturnValue({ result: migratedObj, - migrationsApplied: ['agentspec/v1alpha1 → agentspec.io/v1'], + migrationsApplied: ['agentspec/v1alpha1 → agentspec.io/v1alpha1'], }) await run(['/fake/agent.yaml']) expect(logOutput()).toContain('agentspec/v1alpha1') @@ -781,7 +781,7 @@ describe('migrate command', () => { mockIsLatestVersion.mockReturnValue(false) mockMigrateManifest.mockReturnValue({ result: migratedObj, - migrationsApplied: ['agentspec/v1alpha1 → agentspec.io/v1'], + migrationsApplied: ['agentspec/v1alpha1 → agentspec.io/v1alpha1'], }) await run(['/fake/agent.yaml', '--dry-run']) expect(mockWriteFileSync).not.toHaveBeenCalled() @@ -850,7 +850,7 @@ describe('init command', () => { await run(['--yes']) expect(mockWriteFileSync).toHaveBeenCalledOnce() const content = mockWriteFileSync.mock.calls[0][1] as string - expect(content).toContain('apiVersion: agentspec.io/v1') + expect(content).toContain('apiVersion: agentspec.io/v1alpha1') expect(content).toContain('my-agent') }) diff --git a/packages/cli/src/__tests__/deploy-k8s.test.ts b/packages/cli/src/__tests__/deploy-k8s.test.ts index 9ed6888..b82ad4e 100644 --- a/packages/cli/src/__tests__/deploy-k8s.test.ts +++ b/packages/cli/src/__tests__/deploy-k8s.test.ts @@ -13,7 +13,7 @@ import { generateK8sManifests } from '../deploy/k8s.js' // ── Fixtures ────────────────────────────────────────────────────────────────── const minimalManifest: AgentSpecManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'my-agent', @@ -34,7 +34,7 @@ const minimalManifest: AgentSpecManifest = { } const fullManifest: AgentSpecManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'budget-assistant', diff --git a/packages/cli/src/__tests__/evaluate.test.ts b/packages/cli/src/__tests__/evaluate.test.ts index 82e706c..1c70fdc 100644 --- a/packages/cli/src/__tests__/evaluate.test.ts +++ b/packages/cli/src/__tests__/evaluate.test.ts @@ -55,7 +55,7 @@ let logSpy: ReturnType let errorSpy: ReturnType const mockManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'test-agent', version: '1.0.0', description: 'A test agent' }, spec: { diff --git a/packages/cli/src/__tests__/generate-policy.test.ts b/packages/cli/src/__tests__/generate-policy.test.ts index 25aea13..fa4922b 100644 --- a/packages/cli/src/__tests__/generate-policy.test.ts +++ b/packages/cli/src/__tests__/generate-policy.test.ts @@ -44,7 +44,7 @@ import { // ── Test fixtures ────────────────────────────────────────────────────────────── const fullManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'gymcoach', version: '1.0.0', description: 'AI fitness coach' }, spec: { @@ -98,7 +98,7 @@ const fullManifest = { } as const const minimalManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'minimal-agent', version: '1.0.0', description: 'Minimal agent' }, spec: { diff --git a/packages/cli/src/__tests__/scan-builder.test.ts b/packages/cli/src/__tests__/scan-builder.test.ts index ff46ecc..00ae744 100644 --- a/packages/cli/src/__tests__/scan-builder.test.ts +++ b/packages/cli/src/__tests__/scan-builder.test.ts @@ -92,9 +92,9 @@ describe('slugify', () => { // ── buildManifestFromDetection() ───────────────────────────────────────────── describe('buildManifestFromDetection — top-level structure', () => { - it('sets apiVersion: "agentspec.io/v1"', () => { + it('sets apiVersion: "agentspec.io/v1alpha1"', () => { const result = buildManifestFromDetection(minimalDetection) - expect(result.apiVersion).toBe('agentspec.io/v1') + expect(result.apiVersion).toBe('agentspec.io/v1alpha1') }) it('sets kind: "AgentSpec"', () => { diff --git a/packages/cli/src/__tests__/scan.test.ts b/packages/cli/src/__tests__/scan.test.ts index 6651c03..7ce2c12 100644 --- a/packages/cli/src/__tests__/scan.test.ts +++ b/packages/cli/src/__tests__/scan.test.ts @@ -283,7 +283,7 @@ describe('scan — CLI integration', () => { writeFileSync(join(srcDir, 'agent.yaml'), 'name: old') await runScan(srcDir, ['--update']) const content = readFileSync(join(srcDir, 'agent.yaml'), 'utf-8') - expect(content).toContain('apiVersion: agentspec.io/v1') + expect(content).toContain('apiVersion: agentspec.io/v1alpha1') }) it('--dry-run prints to stdout and does not write a file', async () => { diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index f748d94..f99ed39 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1,6 +1,6 @@ import type { Command } from 'commander' -import { writeFileSync, existsSync } from 'node:fs' -import { resolve, join } from 'node:path' +import { writeFileSync, existsSync, mkdirSync } from 'node:fs' +import { resolve, join, dirname } from 'node:path' import chalk from 'chalk' import * as p from '@clack/prompts' import { printHeader } from '../utils/output.js' @@ -126,6 +126,37 @@ export function registerInitCommand(program: Command): void { writeFileSync(outFile, yaml, 'utf-8') + // Create stub files referenced by $file: in the manifest so the + // quick-start flow (init → validate → health → generate) works out of + // the box without confusing "file not found" errors. + const stubs: Array<{ path: string; content: string }> = [ + { + path: join(outDir, 'prompts', 'system.md'), + content: + `# System Prompt\n\n` + + `You are a helpful AI assistant named ${name}.\n\n` + + `## Guidelines\n\n` + + `- Be concise and accurate\n` + + `- Ask for clarification when unsure\n` + + `- Always respond in the user's language\n`, + }, + ] + + if (includeEval) { + stubs.push({ + path: join(outDir, 'eval', 'datasets', 'qa.jsonl'), + content: JSON.stringify({ input: 'Hello', expected_output: 'Hi! How can I help you?' }) + '\n', + }) + } + + for (const stub of stubs) { + if (!existsSync(stub.path)) { + mkdirSync(dirname(stub.path), { recursive: true }) + writeFileSync(stub.path, stub.content, 'utf-8') + console.log(chalk.gray(` ✓ Created ${stub.path}`)) + } + } + if (!opts.yes) p.outro(chalk.green(`✓ Created ${outFile}`)) else console.log(chalk.green(`\n ✓ Created ${outFile}\n`)) @@ -150,7 +181,7 @@ function generateManifest(opts: { includeEval: boolean }): string { const sections: string[] = [ - `apiVersion: agentspec.io/v1 + `apiVersion: agentspec.io/v1alpha1 kind: AgentSpec metadata: diff --git a/packages/cli/src/commands/scan-builder.ts b/packages/cli/src/commands/scan-builder.ts index dcf2844..8ec5f4d 100644 --- a/packages/cli/src/commands/scan-builder.ts +++ b/packages/cli/src/commands/scan-builder.ts @@ -270,7 +270,7 @@ export function slugify(s: string): string { */ export function buildManifestFromDetection(d: ScanDetection): AgentSpecManifest { return { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: buildMetadata(d), spec: { diff --git a/packages/cli/src/commands/validate.ts b/packages/cli/src/commands/validate.ts index ee488b2..9d261a1 100644 --- a/packages/cli/src/commands/validate.ts +++ b/packages/cli/src/commands/validate.ts @@ -30,7 +30,7 @@ export function registerValidateCommand(program: Command): void { printHeader('AgentSpec Validate') printSuccess( - `Manifest valid — ${chalk.cyan(manifest.metadata.name)} v${manifest.metadata.version} (agentspec.io/v1)`, + `Manifest valid — ${chalk.cyan(manifest.metadata.name)} v${manifest.metadata.version} (agentspec.io/v1alpha1)`, ) console.log(chalk.gray(` Provider : ${manifest.spec.model.provider}/${manifest.spec.model.id}`)) console.log(chalk.gray(` Tools : ${manifest.spec.tools?.length ?? 0}`)) diff --git a/packages/operator/demo/fitness-tracker/agentobservation.yaml b/packages/operator/demo/fitness-tracker/agentobservation.yaml index 7941bc9..6604b80 100644 --- a/packages/operator/demo/fitness-tracker/agentobservation.yaml +++ b/packages/operator/demo/fitness-tracker/agentobservation.yaml @@ -1,4 +1,4 @@ -apiVersion: agentspec.io/v1 +apiVersion: agentspec.io/v1alpha1 kind: AgentObservation metadata: name: fitness-tracker diff --git a/packages/operator/demo/fitness-tracker/deployment.yaml b/packages/operator/demo/fitness-tracker/deployment.yaml index aaeaeba..76b7bca 100644 --- a/packages/operator/demo/fitness-tracker/deployment.yaml +++ b/packages/operator/demo/fitness-tracker/deployment.yaml @@ -49,7 +49,7 @@ metadata: agentspec.io/agent: fitness-tracker data: agent.yaml: | - apiVersion: agentspec.io/v1 + apiVersion: agentspec.io/v1alpha1 kind: AgentSpec metadata: name: fitness-tracker diff --git a/packages/operator/demo/gymcoach/agentobservation.yaml b/packages/operator/demo/gymcoach/agentobservation.yaml index 7b340ba..fdfc940 100644 --- a/packages/operator/demo/gymcoach/agentobservation.yaml +++ b/packages/operator/demo/gymcoach/agentobservation.yaml @@ -1,4 +1,4 @@ -apiVersion: agentspec.io/v1 +apiVersion: agentspec.io/v1alpha1 kind: AgentObservation metadata: name: gymcoach diff --git a/packages/operator/demo/gymcoach/deployment.yaml b/packages/operator/demo/gymcoach/deployment.yaml index f224031..f224b46 100644 --- a/packages/operator/demo/gymcoach/deployment.yaml +++ b/packages/operator/demo/gymcoach/deployment.yaml @@ -58,7 +58,7 @@ metadata: agentspec.io/agent: gymcoach data: agent.yaml: | - apiVersion: agentspec.io/v1 + apiVersion: agentspec.io/v1alpha1 kind: AgentSpec metadata: name: gymcoach diff --git a/packages/operator/demo/patches/research-agent-patched-configmap.yaml b/packages/operator/demo/patches/research-agent-patched-configmap.yaml index f76a2c7..3c44d51 100644 --- a/packages/operator/demo/patches/research-agent-patched-configmap.yaml +++ b/packages/operator/demo/patches/research-agent-patched-configmap.yaml @@ -11,7 +11,7 @@ metadata: agentspec.io/agent: research-agent data: agent.yaml: | - apiVersion: agentspec.io/v1 + apiVersion: agentspec.io/v1alpha1 kind: AgentSpec metadata: name: research-agent diff --git a/packages/operator/demo/patches/voice-assistant-patched-configmap.yaml b/packages/operator/demo/patches/voice-assistant-patched-configmap.yaml index a5a5af3..4a0354d 100644 --- a/packages/operator/demo/patches/voice-assistant-patched-configmap.yaml +++ b/packages/operator/demo/patches/voice-assistant-patched-configmap.yaml @@ -11,7 +11,7 @@ metadata: agentspec.io/agent: voice-assistant data: agent.yaml: | - apiVersion: agentspec.io/v1 + apiVersion: agentspec.io/v1alpha1 kind: AgentSpec metadata: name: voice-assistant diff --git a/packages/operator/demo/research-agent/agentobservation.yaml b/packages/operator/demo/research-agent/agentobservation.yaml index 62b3071..00246e0 100644 --- a/packages/operator/demo/research-agent/agentobservation.yaml +++ b/packages/operator/demo/research-agent/agentobservation.yaml @@ -1,4 +1,4 @@ -apiVersion: agentspec.io/v1 +apiVersion: agentspec.io/v1alpha1 kind: AgentObservation metadata: name: research-agent diff --git a/packages/operator/demo/research-agent/deployment.yaml b/packages/operator/demo/research-agent/deployment.yaml index fbf5a14..5a580c5 100644 --- a/packages/operator/demo/research-agent/deployment.yaml +++ b/packages/operator/demo/research-agent/deployment.yaml @@ -9,7 +9,7 @@ metadata: agentspec.io/agent: research-agent data: agent.yaml: | - apiVersion: agentspec.io/v1 + apiVersion: agentspec.io/v1alpha1 kind: AgentSpec metadata: name: research-agent diff --git a/packages/operator/demo/trading-bot/agentobservation.yaml b/packages/operator/demo/trading-bot/agentobservation.yaml index 6c51738..6bde50e 100644 --- a/packages/operator/demo/trading-bot/agentobservation.yaml +++ b/packages/operator/demo/trading-bot/agentobservation.yaml @@ -1,4 +1,4 @@ -apiVersion: agentspec.io/v1 +apiVersion: agentspec.io/v1alpha1 kind: AgentObservation metadata: name: trading-bot diff --git a/packages/operator/demo/trading-bot/deployment.yaml b/packages/operator/demo/trading-bot/deployment.yaml index a0b945c..31d2be3 100644 --- a/packages/operator/demo/trading-bot/deployment.yaml +++ b/packages/operator/demo/trading-bot/deployment.yaml @@ -8,7 +8,7 @@ metadata: agentspec.io/agent: trading-bot data: agent.yaml: | - apiVersion: agentspec.io/v1 + apiVersion: agentspec.io/v1alpha1 kind: AgentSpec metadata: name: trading-bot diff --git a/packages/operator/demo/voice-assistant/agentobservation.yaml b/packages/operator/demo/voice-assistant/agentobservation.yaml index 6646ecd..626eb0b 100644 --- a/packages/operator/demo/voice-assistant/agentobservation.yaml +++ b/packages/operator/demo/voice-assistant/agentobservation.yaml @@ -1,4 +1,4 @@ -apiVersion: agentspec.io/v1 +apiVersion: agentspec.io/v1alpha1 kind: AgentObservation metadata: name: voice-assistant diff --git a/packages/operator/demo/voice-assistant/deployment.yaml b/packages/operator/demo/voice-assistant/deployment.yaml index 265465c..d80bc0d 100644 --- a/packages/operator/demo/voice-assistant/deployment.yaml +++ b/packages/operator/demo/voice-assistant/deployment.yaml @@ -8,7 +8,7 @@ metadata: agentspec.io/agent: voice-assistant data: agent.yaml: | - apiVersion: agentspec.io/v1 + apiVersion: agentspec.io/v1alpha1 kind: AgentSpec metadata: name: voice-assistant diff --git a/packages/sdk/src/__tests__/audit.test.ts b/packages/sdk/src/__tests__/audit.test.ts index bf5b38e..e56affc 100644 --- a/packages/sdk/src/__tests__/audit.test.ts +++ b/packages/sdk/src/__tests__/audit.test.ts @@ -19,7 +19,7 @@ const allRules = [ ] const minimalManifest: AgentSpecManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'test-agent', diff --git a/packages/sdk/src/__tests__/generate-registry.test.ts b/packages/sdk/src/__tests__/generate-registry.test.ts index 9d062d1..f4509c3 100644 --- a/packages/sdk/src/__tests__/generate-registry.test.ts +++ b/packages/sdk/src/__tests__/generate-registry.test.ts @@ -21,7 +21,7 @@ import type { AgentSpecManifest } from '../schema/manifest.schema.js' // ── Fixtures ────────────────────────────────────────────────────────────────── const testManifest: AgentSpecManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'test-agent', version: '1.0.0', description: 'test' }, spec: { diff --git a/packages/sdk/src/__tests__/health-index.test.ts b/packages/sdk/src/__tests__/health-index.test.ts index 2a1ad18..077eacf 100644 --- a/packages/sdk/src/__tests__/health-index.test.ts +++ b/packages/sdk/src/__tests__/health-index.test.ts @@ -41,7 +41,7 @@ afterEach(() => { // ── Fixtures ────────────────────────────────────────────────────────────────── const baseManifest: AgentSpecManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'health-idx-agent', version: '1.0.0', description: 'test' }, spec: { @@ -66,7 +66,7 @@ describe('runHealthCheck — subagent agentspec ref', () => { it('returns pass for subagent when manifest file exists', async () => { const subPath = 'sub-agent.yaml' - writeFileSync(join(tmpDir, subPath), 'apiVersion: agentspec.io/v1') + writeFileSync(join(tmpDir, subPath), 'apiVersion: agentspec.io/v1alpha1') const manifest: AgentSpecManifest = { ...baseManifest, diff --git a/packages/sdk/src/__tests__/health.test.ts b/packages/sdk/src/__tests__/health.test.ts index 0313b6b..9e2f8e7 100644 --- a/packages/sdk/src/__tests__/health.test.ts +++ b/packages/sdk/src/__tests__/health.test.ts @@ -8,7 +8,7 @@ import { runHealthCheck } from '../health/index.js' // ── Fixtures ────────────────────────────────────────────────────────────────── const baseManifest: AgentSpecManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'health-test-agent', diff --git a/packages/sdk/src/__tests__/loader.test.ts b/packages/sdk/src/__tests__/loader.test.ts index e9bd5a0..0de43a3 100644 --- a/packages/sdk/src/__tests__/loader.test.ts +++ b/packages/sdk/src/__tests__/loader.test.ts @@ -23,7 +23,7 @@ const exampleManifest = resolve(repoRoot, 'examples/gymcoach/agent.yaml') describe('loadManifest()', () => { it('loads and validates the gymcoach example manifest', () => { const result = loadManifest(exampleManifest) - expect(result.manifest.apiVersion).toBe('agentspec.io/v1') + expect(result.manifest.apiVersion).toBe('agentspec.io/v1alpha1') expect(result.manifest.kind).toBe('AgentSpec') expect(result.manifest.metadata.name).toBeTruthy() expect(result.filePath).toBe(exampleManifest) @@ -53,7 +53,7 @@ describe('loadManifest()', () => { mkdirSync(tmpDir, { recursive: true }) const badYaml = join(tmpDir, 'agent.yaml') // YAML with a tab character at start of line (invalid YAML indentation) - writeFileSync(badYaml, 'apiVersion: agentspec.io/v1\n\t: bad indentation') + writeFileSync(badYaml, 'apiVersion: agentspec.io/v1alpha1\n\t: bad indentation') try { expect(() => loadManifest(badYaml)).toThrow(/Invalid YAML/) } finally { @@ -69,7 +69,7 @@ describe('loadManifest()', () => { // A minimal manifest with NO $env: refs — all literal values writeFileSync(manifestPath, [ - 'apiVersion: agentspec.io/v1', + 'apiVersion: agentspec.io/v1alpha1', 'kind: AgentSpec', 'metadata:', ' name: literal-agent', @@ -100,7 +100,7 @@ describe('tryLoadManifest()', () => { const result = tryLoadManifest(exampleManifest) expect(result.ok).toBe(true) if (result.ok) { - expect(result.data.manifest.apiVersion).toBe('agentspec.io/v1') + expect(result.data.manifest.apiVersion).toBe('agentspec.io/v1alpha1') } }) @@ -117,7 +117,7 @@ describe('tryLoadManifest()', () => { describe('isLatestVersion()', () => { it('returns true for the current latest apiVersion', () => { - expect(isLatestVersion({ apiVersion: 'agentspec.io/v1' })).toBe(true) + expect(isLatestVersion({ apiVersion: 'agentspec.io/v1alpha1' })).toBe(true) }) it('returns false for a legacy apiVersion', () => { @@ -137,7 +137,7 @@ describe('isLatestVersion()', () => { describe('detectVersion()', () => { it('returns the apiVersion from the manifest', () => { - expect(detectVersion({ apiVersion: 'agentspec.io/v1' })).toBe('agentspec.io/v1') + expect(detectVersion({ apiVersion: 'agentspec.io/v1alpha1' })).toBe('agentspec.io/v1alpha1') }) it('returns the legacy version correctly', () => { @@ -152,13 +152,13 @@ describe('detectVersion()', () => { // ── migrateManifest ──────────────────────────────────────────────────────────── describe('migrateManifest()', () => { - it('migrates agentspec/v1alpha1 to agentspec.io/v1', () => { + it('migrates agentspec/v1alpha1 to agentspec.io/v1alpha1', () => { const input = { apiVersion: 'agentspec/v1alpha1', metadata: { name: 'test' }, } const { result, migrationsApplied } = migrateManifest(input) - expect(result.apiVersion).toBe('agentspec.io/v1') + expect(result.apiVersion).toBe('agentspec.io/v1alpha1') expect(migrationsApplied).toHaveLength(1) expect(migrationsApplied[0]).toContain('agentspec/v1alpha1') }) @@ -177,12 +177,12 @@ describe('migrateManifest()', () => { it('returns the manifest unchanged when already at latest version', () => { const input = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'test' }, } const { result, migrationsApplied } = migrateManifest(input) - expect(result.apiVersion).toBe('agentspec.io/v1') + expect(result.apiVersion).toBe('agentspec.io/v1alpha1') expect(migrationsApplied).toHaveLength(0) }) @@ -201,7 +201,7 @@ describe('migrateManifest()', () => { // ── LATEST_API_VERSION constant ──────────────────────────────────────────────── describe('LATEST_API_VERSION', () => { - it('is agentspec.io/v1', () => { - expect(LATEST_API_VERSION).toBe('agentspec.io/v1') + it('is agentspec.io/v1alpha1', () => { + expect(LATEST_API_VERSION).toBe('agentspec.io/v1alpha1') }) }) diff --git a/packages/sdk/src/__tests__/push.test.ts b/packages/sdk/src/__tests__/push.test.ts index 2c21d95..96bf461 100644 --- a/packages/sdk/src/__tests__/push.test.ts +++ b/packages/sdk/src/__tests__/push.test.ts @@ -31,7 +31,7 @@ vi.mock('../audit/index.js', async (importOriginal) => { // ── Test fixtures ───────────────────────────────────────────────────────────── const testManifest: AgentSpecManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'push-test-agent', version: '1.0.0', description: 'test' }, spec: { diff --git a/packages/sdk/src/__tests__/reporter.test.ts b/packages/sdk/src/__tests__/reporter.test.ts index 8b888b7..ef6e888 100644 --- a/packages/sdk/src/__tests__/reporter.test.ts +++ b/packages/sdk/src/__tests__/reporter.test.ts @@ -27,7 +27,7 @@ vi.mock('../health/index.js', async (importOriginal) => { // ── Test fixtures ───────────────────────────────────────────────────────────── const testManifest: AgentSpecManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'test-reporter-agent', version: '1.0.0', description: 'test' }, spec: { diff --git a/packages/sdk/src/__tests__/schema.test.ts b/packages/sdk/src/__tests__/schema.test.ts index 07e52c7..129ed8b 100644 --- a/packages/sdk/src/__tests__/schema.test.ts +++ b/packages/sdk/src/__tests__/schema.test.ts @@ -3,7 +3,7 @@ import { ManifestSchema } from '../schema/manifest.schema.js' import { ZodError } from 'zod' const minimalManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'test-agent', diff --git a/packages/sdk/src/__tests__/service-check.test.ts b/packages/sdk/src/__tests__/service-check.test.ts index 644083a..538acf3 100644 --- a/packages/sdk/src/__tests__/service-check.test.ts +++ b/packages/sdk/src/__tests__/service-check.test.ts @@ -328,7 +328,7 @@ describe('runHealthCheck integrates service checks', () => { it('service checks appear in HealthReport when services are declared', async () => { const { runHealthCheck } = await import('../health/index.js') const manifest = { - apiVersion: 'agentspec.io/v1' as const, + apiVersion: 'agentspec.io/v1alpha1' as const, kind: 'AgentSpec' as const, metadata: { name: 'test-agent', version: '1.0.0', description: 'test' }, spec: { @@ -355,7 +355,7 @@ describe('runHealthCheck integrates service checks', () => { it('service checks are skipped when checkServices: false', async () => { const { runHealthCheck } = await import('../health/index.js') const manifest = { - apiVersion: 'agentspec.io/v1' as const, + apiVersion: 'agentspec.io/v1alpha1' as const, kind: 'AgentSpec' as const, metadata: { name: 'test-agent', version: '1.0.0', description: 'test' }, spec: { diff --git a/packages/sdk/src/loader/migrations/index.ts b/packages/sdk/src/loader/migrations/index.ts index de9c50c..a74353f 100644 --- a/packages/sdk/src/loader/migrations/index.ts +++ b/packages/sdk/src/loader/migrations/index.ts @@ -1,4 +1,4 @@ -import { v1alpha1ToV1 } from './v1alpha1-to-v1.js' +import { v1alpha1ToV1, v1ToV1Alpha1 } from './v1alpha1-to-v1.js' export interface Migration { from: string @@ -6,9 +6,9 @@ export interface Migration { migrate(raw: Record): Record } -const migrations: Migration[] = [v1alpha1ToV1] +const migrations: Migration[] = [v1alpha1ToV1, v1ToV1Alpha1] -export const LATEST_API_VERSION = 'agentspec.io/v1' +export const LATEST_API_VERSION = 'agentspec.io/v1alpha1' export function migrateManifest(raw: Record): { result: Record diff --git a/packages/sdk/src/loader/migrations/v1alpha1-to-v1.ts b/packages/sdk/src/loader/migrations/v1alpha1-to-v1.ts index bf6a4d7..bf72488 100644 --- a/packages/sdk/src/loader/migrations/v1alpha1-to-v1.ts +++ b/packages/sdk/src/loader/migrations/v1alpha1-to-v1.ts @@ -1,21 +1,38 @@ import type { Migration } from './index.js' /** - * Migrates manifests from agentspec/v1alpha1 (pre-release) to agentspec.io/v1 (stable). + * Migrates manifests from agentspec/v1alpha1 (pre-release) to agentspec.io/v1alpha1 (stable). * * Changes: - * - apiVersion: agentspec/v1alpha1 → agentspec.io/v1 + * - apiVersion: agentspec/v1alpha1 → agentspec.io/v1alpha1 * - kind defaults to 'AgentSpec' if missing */ export const v1alpha1ToV1: Migration = { from: 'agentspec/v1alpha1', - to: 'agentspec.io/v1', + to: 'agentspec.io/v1alpha1', migrate(raw: Record): Record { return { ...raw, - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: raw.kind ?? 'AgentSpec', } }, } + +/** + * Backward-compat migration: manifests that used the old agentspec.io/v1 + * identifier (before the rename to v1alpha1) are silently upgraded. + * This prevents breakage for users who pinned to `agentspec.io/v1`. + */ +export const v1ToV1Alpha1: Migration = { + from: 'agentspec.io/v1', + to: 'agentspec.io/v1alpha1', + + migrate(raw: Record): Record { + return { + ...raw, + apiVersion: 'agentspec.io/v1alpha1', + } + }, +} diff --git a/packages/sdk/src/schema/manifest.schema.ts b/packages/sdk/src/schema/manifest.schema.ts index 4e26b96..02b47ef 100644 --- a/packages/sdk/src/schema/manifest.schema.ts +++ b/packages/sdk/src/schema/manifest.schema.ts @@ -564,7 +564,7 @@ const SpecSchema = z.object({ // ── Top-level Manifest ──────────────────────────────────────────────────────── export const ManifestSchema = z.object({ - apiVersion: z.literal('agentspec.io/v1'), + apiVersion: z.literal('agentspec.io/v1alpha1'), kind: z.literal('AgentSpec'), metadata: MetadataSchema, spec: SpecSchema, diff --git a/packages/sidecar/src/__tests__/fixtures.ts b/packages/sidecar/src/__tests__/fixtures.ts index dfdd180..ee1616e 100644 --- a/packages/sidecar/src/__tests__/fixtures.ts +++ b/packages/sidecar/src/__tests__/fixtures.ts @@ -5,7 +5,7 @@ import type { AgentSpecManifest } from '@agentspec/sdk' * Tool names use lowercase slugs as required by the schema. */ export const testManifest: AgentSpecManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'gymcoach', diff --git a/packages/sidecar/src/__tests__/opa-client.test.ts b/packages/sidecar/src/__tests__/opa-client.test.ts index 43d7fea..14df2c9 100644 --- a/packages/sidecar/src/__tests__/opa-client.test.ts +++ b/packages/sidecar/src/__tests__/opa-client.test.ts @@ -19,7 +19,7 @@ import type { AgentProbeResult } from '../control-plane/agent-probe.js' // ── Test fixtures ────────────────────────────────────────────────────────────── const manifest: AgentSpecManifest = { - apiVersion: 'agentspec.io/v1', + apiVersion: 'agentspec.io/v1alpha1', kind: 'AgentSpec', metadata: { name: 'gymcoach', version: '1.0.0', description: 'AI fitness coach' }, spec: { diff --git a/packages/sidecar/test/e2e/agent.yaml b/packages/sidecar/test/e2e/agent.yaml index 8d9959c..41e68f4 100644 --- a/packages/sidecar/test/e2e/agent.yaml +++ b/packages/sidecar/test/e2e/agent.yaml @@ -1,4 +1,4 @@ -apiVersion: agentspec.io/v1 +apiVersion: agentspec.io/v1alpha1 kind: AgentSpec metadata: name: test-agent