diff --git a/README.md b/README.md index a2bf31f..9544388 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,7 @@ the CLI fallback, then continue the same workflow check with `--ran-suggest`. ## Public truth -v0.4.30 · 36 MCP tools + 5 prompts · 214 diagnostic codes · 1350 tests · 58 live packages · 34 bundled templates +v0.4.30 · 36 MCP tools + 5 prompts · 214 diagnostic codes · 1362 tests · 58 live packages · 39 bundled templates Public proof is generated from `../public-truth/public-truth.json` via `npm --prefix .. run truth:sync`. diff --git a/ROADMAP.md b/ROADMAP.md index b2955cd..1a687e6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,11 +1,13 @@ # Axint Roadmap -_Last updated: May 2026 · Current release: [v0.4.28](https://github.com/agenticempire/axint/releases/tag/v0.4.28)_ +_Last updated: June 2026 · Current release: [v0.4.30](https://github.com/agenticempire/axint/releases/tag/v0.4.30)_ Axint is the Apple-native execution layer for AI coding agents. The open-source package gives agents a smaller contract for Apple surfaces, emits ordinary Swift, validates Apple-specific rules, writes Fix Packets, and coordinates proof loops across CLI, MCP, Xcode, Registry, and Cloud-facing workflows. The thesis is simple: agents can write code, but Apple-native software needs proof. Axint turns agent output into validated, repairable, inspectable Apple work. +Current compiler snapshot: v0.4.30 · 36 MCP tools + 5 prompts · 39 templates · 214 diagnostic codes · 1362 tests. + --- ## Shipped Now @@ -22,8 +24,8 @@ The thesis is simple: agents can write code, but Apple-native software needs pro - TypeScript, Python, JSON schema mode, and preview `.axint` inputs compile into Apple-native Swift. - Supported surfaces include App Intents, SwiftUI views, WidgetKit widgets, app scaffolds, plist fragments, entitlements, and Apple metadata. -- The validator covers 204 diagnostic codes across compiler, intent, view, widget, app, Swift build, SwiftUI, accessibility, concurrency, and Live Activity rules. -- The suite currently tracks 1319 tests across TypeScript and Python paths. +- The validator covers 214 diagnostic codes across compiler, intent, view, widget, app, Swift build, SwiftUI, accessibility, concurrency, and Live Activity rules. +- The suite currently tracks 1362 tests across TypeScript and Python paths. ### Agent distribution @@ -35,7 +37,7 @@ The thesis is simple: agents can write code, but Apple-native software needs pro ### First-use and templates - `create-axint-app` / `axint create` generates the Apple Day Agent starter: multiple App Intent contracts, generated Swift, plist and entitlement fragments, agent prompts, proof artifacts, and an interactive local proof preview. -- 26 bundled templates and 58 live Registry packages give agents reusable starting points instead of asking them to hallucinate every Apple surface from scratch. +- 39 bundled templates and 58 live Registry packages give agents reusable starting points instead of asking them to hallucinate every Apple surface from scratch. ### Privacy-safe learning and adoption proof diff --git a/extensions/README.md b/extensions/README.md index baa962b..18525f1 100644 --- a/extensions/README.md +++ b/extensions/README.md @@ -12,7 +12,7 @@ Pre-built integrations for every major AI coding tool. Each directory contains t | **Cursor** | Copy `cursor/mcp.json` → `.cursor/mcp.json` or search in Settings → Tools → MCP | | **Windsurf** | Copy `windsurf/mcp_config.json` → `~/.codeium/windsurf/mcp_config.json` | | **Codex** | Copy `codex/mcp.json` into your Codex MCP config | -| **Xcode** | Add SPM dependency (see `xcode/README.md`) | +| **Xcode** | Use the Xcode 27 agent plugin or add the SPM dependency (see `xcode/README.md`) | | **JetBrains** | Settings → Tools → AI Assistant → MCP Servers (see `jetbrains/README.md`) | | **Zed** | Add to `~/.config/zed/settings.json` (see `zed/README.md`) | | **Neovim** | Configure your MCP plugin (see `neovim/README.md`) | diff --git a/extensions/xcode/README.md b/extensions/xcode/README.md index 4d92ea4..fd2817f 100644 --- a/extensions/xcode/README.md +++ b/extensions/xcode/README.md @@ -1,6 +1,6 @@ # Axint for Xcode -Axint integrates with Xcode in four ways: as an MCP server for agentic coding, as an SPM build plugin for compile-time generation, as a [native Source Editor Extension](./source-editor-extension) for in-editor quickfixes, and via the `axint xcode setup` command for one-step configuration. +Axint integrates with Xcode in five ways: as an Xcode 27 agent plugin, as an MCP server for agentic coding, as an SPM build plugin for compile-time generation, as a [native Source Editor Extension](./source-editor-extension) for in-editor quickfixes, and via the `axint xcode setup` command for one-step configuration. ## Quick Setup (recommended) @@ -27,6 +27,26 @@ What MCP servers are available? You should see both `xcode-tools` and `axint`. +## Xcode 27 Agent Plugin + +Xcode 27 can load agent plugins that bundle skills, MCP servers, and tool metadata. Axint ships a public plugin package at [`agent-plugin/plugin.json`](./agent-plugin/plugin.json) so Xcode agents can discover the Axint MCP server with branded tool names and an Apple Intelligence proof skill. + +The plugin points Xcode at the npm package: + +```json +{ + "mcpServers": { + "axint": { + "type": "stdio", + "command": "npx", + "args": ["-y", "-p", "@axint/compiler", "axint-mcp"] + } + } +} +``` + +Use the bundled `apple-intelligence-proof` skill when building App Intents schemas, Foundation Models features, previews, localization flows, or any Xcode 27 Apple Intelligence surface. It requires `axint.swift.validate`, `axint.cloud.check`, AppIntentsTesting proof for schema-backed App Intents, and focused Xcode build/test evidence before the agent can call a feature demo-ready. + ## MCP for Xcode Agentic Coding Xcode 26.3+ supports agentic coding with external agents via MCP. Axint adds specialized Apple-native feature generation on top of Xcode's built-in workspace/build/test tools. diff --git a/extensions/xcode/agent-plugin/icon.svg b/extensions/xcode/agent-plugin/icon.svg new file mode 100644 index 0000000..70d3343 --- /dev/null +++ b/extensions/xcode/agent-plugin/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/extensions/xcode/agent-plugin/plugin.json b/extensions/xcode/agent-plugin/plugin.json new file mode 100644 index 0000000..18c8035 --- /dev/null +++ b/extensions/xcode/agent-plugin/plugin.json @@ -0,0 +1,37 @@ +{ + "name": "Axint", + "description": "Apple Intelligence proof layer for Xcode agents: compile, validate, repair, and prove App Intents, Foundation Models, previews, localization, and Xcode build loops.", + "version": "0.4.30", + "homepage": "https://axint.ai", + "repository": "https://github.com/agenticempire/axint", + "license": "Apache-2.0", + "mcpServers": { + "axint": { + "type": "stdio", + "command": "npx", + "args": ["-y", "-p", "@axint/compiler", "axint-mcp"], + "tools": ["*"], + "_meta": { + "ideToolIconPath": "./icon.svg", + "ideToolIconRendersAsTemplate": true, + "ideToolTitles": { + "axint.status": "Axint Status", + "axint.suggest": "Apple Feature Plan", + "axint.feature": "Generate Apple Feature", + "axint.swift.validate": "Swift Validator", + "axint.cloud.check": "Apple Readiness Check", + "axint.repair": "Apple Repair Plan", + "axint.run": "Build Proof Loop", + "axint.xcode.guard": "Axint Guard", + "axint.xcode.write": "Guarded Swift Write" + } + } + } + }, + "skills": [ + { + "name": "apple-intelligence-proof", + "path": "./skills/apple-intelligence-proof.md" + } + ] +} diff --git a/extensions/xcode/agent-plugin/skills/apple-intelligence-proof.md b/extensions/xcode/agent-plugin/skills/apple-intelligence-proof.md new file mode 100644 index 0000000..7bfa5ab --- /dev/null +++ b/extensions/xcode/agent-plugin/skills/apple-intelligence-proof.md @@ -0,0 +1,19 @@ +# Apple Intelligence Proof + +Use Axint before and after every Apple Intelligence or App Intents edit in Xcode. + +## Required Loop + +1. Start with `axint.status`, then `axint.session.start` for the project. +2. For new App Intents, entities, enums, Foundation Models, previews, or localization flows, call `axint.suggest` or `axint.feature` before writing code. +3. After edits, run `axint.swift.validate` on changed Swift files. +4. Run `axint.cloud.check` with Xcode 27 build/test evidence. +5. For schema-backed App Intents, attach AppIntentsTesting proof for Siri, Shortcuts, and Spotlight pathways. +6. Before claiming the task is fixed, run `axint.run` or focused `xcodebuild` proof and summarize the exact command output. + +## Guardrails + +- Do not call generated Apple Intelligence code demo-ready from static checks alone. +- Do not invent App Schema, Foundation Models, or AppIntentsTesting APIs. +- If Xcode 27 beta is installed outside the selected developer directory, set `DEVELOPER_DIR` before build/test proof. +- Keep source, prompts, and user content out of public reports unless the user explicitly opts in. diff --git a/metrics.json b/metrics.json index 710b031..6883d88 100644 --- a/metrics.json +++ b/metrics.json @@ -47,7 +47,7 @@ "axint.create-widget", "axint.create-intent" ], - "bundledTemplates": 34, + "bundledTemplates": 39, "diagnostics": 214, "xcodeFixRules": 33, "xcodeFixRuleCodes": [ @@ -86,7 +86,7 @@ "AX748" ], "tests": { - "typescript": 1236, + "typescript": 1248, "python": 114 }, "registryPackages": 58, diff --git a/src/cloud/check.ts b/src/cloud/check.ts index 984e762..4dec9fa 100644 --- a/src/cloud/check.ts +++ b/src/cloud/check.ts @@ -496,6 +496,18 @@ export function runCloudCheck(input: CloudCheckInput): CloudCheckReport { : []), ] ) satisfies CloudCheckReport["checks"]; + const wwdc26Diagnostics = diagnostics.filter((diagnostic) => + diagnostic.code.startsWith("AXCLOUD-WWDC26-") + ); + if (wwdc26Diagnostics.length > 0) { + checks.push({ + label: "Xcode 27 readiness", + state: wwdc26Diagnostics.some((diagnostic) => diagnostic.severity === "error") + ? "fail" + : "warn", + detail: `${wwdc26Diagnostics.length} WWDC26 proof requirement${wwdc26Diagnostics.length === 1 ? "" : "s"} need evidence before this is demo-ready.`, + }); + } if (projectContext) { checks.push({ label: "Project context pack", @@ -1204,6 +1216,29 @@ function diagnosticsFromWwdc26Readiness( ): Diagnostic[] { const diagnostics: Diagnostic[] = []; const lower = source.toLowerCase(); + const touchesEvaluationProof = + /\b[A-Za-z_][A-Za-z0-9_]*Evaluations\b/.test(source) || + /\b(static\s+let\s+scenarios|static\s+let\s+criteria|Evaluation\s+suite)\b/i.test( + source + ); + const touchesPreviewSnapshotProof = + /\bPreview Snapshot proof matrix\b/i.test(source) || + /\bpreviewProof\b/.test(source) || + /\bXcode Preview Snapshot proof\b/i.test(source); + const touchesVisualIntelligence = + /\b(VisualIntelligence|VisionKit|visual-intelligence|visual intelligence)\b/i.test( + source + ); + const touchesImagePlayground = + /\b(ImagePlayground|Image Playground|generated image)\b/i.test(source); + const touchesStringCatalog = + /\b(StringCatalog|String Catalog|Localizable\.xcstrings|\.xcstrings|generated translations?)\b/i.test( + source + ); + const touchesResizableIosLayout = + /\bSwiftUI\b/.test(source) && + /\bView\b/.test(source) && + /\.frame\s*\([^)]*\b(width|height)\s*:\s*\d{3,}/.test(source); const touchesAppleIntelligence = /@App(Intent|Entity|Enum)\(schema:/.test(source) || /\b(AppSchema|SyncableEntity|OwnershipProvidingEntity|IndexedEntityQuery|IntentValueQuery|FoundationModels|LanguageModelSession|SystemLanguageModel|PrivateCloudComputeLanguageModel|GenerationSchema|ToolCallingMode)\b/.test( @@ -1212,10 +1247,56 @@ function diagnosticsFromWwdc26Readiness( /@(?:Generable|UnionValue)\b/.test(source) || /\b(LongRunningIntent|ProgressReportingIntent|SnippetIntent|ShowsSnippetIntent|ShowsSnippetView|ResultsCollection|IntentItemCollection|AppUnionValue|AppUnionValueCasesProviding)\b/.test( source - ); + ) || + touchesEvaluationProof || + touchesPreviewSnapshotProof || + touchesVisualIntelligence || + touchesImagePlayground || + touchesStringCatalog || + touchesResizableIosLayout; if (!touchesAppleIntelligence) return diagnostics; + const touchesAppIntentSystemPath = + /@App(Intent|Entity|Enum)\(schema:/.test(source) || + /\b(AppSchema|AssistantSchemaIntent|AssistantIntent|IndexedEntity|IndexedEntityQuery|AppShortcutsProvider)\b/.test( + source + ); + + if ( + touchesAppIntentSystemPath && + !/\b(xcode\s*27|ios\s*27|ipados\s*27|macos\s*27|visionos\s*27|sdk\s*27)\b/.test( + evidenceText + ) + ) { + diagnostics.push({ + code: "AXCLOUD-WWDC26-XCODE27-PROOF", + severity: "warning", + file, + message: + "WWDC26 App Intents and Apple Intelligence surfaces need Xcode 27 / SDK 27 proof before they are demo-ready.", + suggestion: + "Attach Xcode 27 build/test evidence, or rerun the proof loop with DEVELOPER_DIR pointing at the Xcode 27 beta.", + }); + } + + if ( + /@App(Intent|Entity|Enum)\(schema:/.test(source) && + !/\b(appintentstesting|app\s*intents\s*testing|siri|shortcuts|spotlight)\b/.test( + evidenceText + ) + ) { + diagnostics.push({ + code: "AXCLOUD-WWDC26-APPINTENTS-TESTING", + severity: "warning", + file, + message: + "Schema-backed App Intents need AppIntentsTesting proof through the same Siri, Shortcuts, and Spotlight pathways people will use.", + suggestion: + "Add an AppIntentsTesting test for the intent/entity/enum adoption, then attach the passing Xcode 27 test log to Cloud Check.", + }); + } + if ( /@AppEntity\(schema:/.test(source) && !/\bSyncableEntity\b/.test(source) && @@ -1279,6 +1360,108 @@ function diagnosticsFromWwdc26Readiness( }); } + if ( + touchesEvaluationProof && + !/\b(evaluation|evaluations|scenario|criteria|pass|passed|xcode\s*27|test succeeded|0 failures)\b/.test( + evidenceText + ) + ) { + diagnostics.push({ + code: "AXCLOUD-WWDC26-EVALUATION-PROOF", + severity: "warning", + file, + message: + "Evaluation suites need attached scenario evidence before model-backed behavior is demo-ready.", + suggestion: + "Attach the passing evaluation run, Xcode 27 test log, or scenario/criteria proof that covers the generated suite.", + }); + } + + if ( + touchesPreviewSnapshotProof && + !/\b(preview snapshot|snapshot|baseline|variant|accessibility|xcode\s*27|test succeeded|0 failures)\b/.test( + evidenceText + ) + ) { + diagnostics.push({ + code: "AXCLOUD-WWDC26-PREVIEW-SNAPSHOT-PROOF", + severity: "warning", + file, + message: + "Preview Snapshot proof matrices need attached rendered snapshot evidence before release.", + suggestion: + "Attach the Xcode 27 Preview Snapshot run or baseline artifact for each declared variant, including accessibility-size variants when listed.", + }); + } + + if ( + touchesVisualIntelligence && + !/\b(visual intelligence|visionkit|screenshot|camera|object|image understanding|xcode\s*27|test succeeded|0 failures)\b/.test( + evidenceText + ) + ) { + diagnostics.push({ + code: "AXCLOUD-WWDC26-VISUAL-INTELLIGENCE-PROOF", + severity: "warning", + file, + message: + "Visual Intelligence routes need screenshot/object-understanding proof before they are demo-ready.", + suggestion: + "Attach the Xcode 27 run, screenshot fixture, or VisionKit/Visual Intelligence evidence showing the detected object maps to the intended app action.", + }); + } + + if ( + touchesImagePlayground && + !/\b(image playground|generated image|image generation|style|safety|private cloud compute|pcc|xcode\s*27|test succeeded|0 failures)\b/.test( + evidenceText + ) + ) { + diagnostics.push({ + code: "AXCLOUD-WWDC26-IMAGE-PLAYGROUND-PROOF", + severity: "warning", + file, + message: + "Image Playground flows need generated-image proof before they are safe to present as working.", + suggestion: + "Attach the generated image artifact or Xcode 27 run evidence, including prompt/style notes and any safety or Private Cloud Compute constraints.", + }); + } + + if ( + touchesStringCatalog && + !/\b(string catalog|xcstrings|localization|localized|locale|translation|xliff|xcode\s*27|test succeeded|0 failures)\b/.test( + evidenceText + ) + ) { + diagnostics.push({ + code: "AXCLOUD-WWDC26-STRING-CATALOG-PROOF", + severity: "warning", + file, + message: "String Catalog workflows need localization proof before release.", + suggestion: + "Attach the updated .xcstrings output, locale coverage, or Xcode 27 localization/export evidence for the generated strings.", + }); + } + + if ( + touchesResizableIosLayout && + !/\b(resizable|size class|compact|regular|dynamic type|accessibility|preview snapshot|baseline|xcode\s*27|test succeeded|0 failures)\b/.test( + evidenceText + ) + ) { + diagnostics.push({ + code: "AXCLOUD-WWDC26-RESIZABLE-IOS-LAYOUT", + severity: "warning", + file, + line: findLine(source, ".frame"), + message: + "SwiftUI layouts with fixed large frame dimensions need resizable iOS proof.", + suggestion: + "Replace fixed device-sized frames with adaptive layout, or attach Preview Snapshot evidence across compact/regular, landscape, and accessibility-size variants.", + }); + } + if ( /\bLongRunningIntent\b/.test(source) && !/\b(performBackgroundTask|LongRunningTaskOptions)\b/.test( diff --git a/src/core/generator.ts b/src/core/generator.ts index 4695f31..fd39c75 100644 --- a/src/core/generator.ts +++ b/src/core/generator.ts @@ -20,6 +20,8 @@ import type { IRType, IREntity, IRParameterSummary, + IREvaluationConfig, + IRPreviewProofConfig, } from "./types.js"; import { irTypeToSwift } from "./types.js"; @@ -38,6 +40,10 @@ export function escapeSwiftString(s: string): string { .replace(/\t/g, "\\t"); } +function quotedSwiftString(value: string): string { + return `"${escapeSwiftString(value)}"`; +} + /** * Escape a string for safe interpolation into XML (Info.plist or * .entitlements). Plist XML uses the same rules as general XML. @@ -75,6 +81,9 @@ export function generateSwift(intent: IRIntent): string { if (intentUsesCoreTransferable(safeIntent)) { lines.push(`import CoreTransferable`); } + if (safeIntent.model) { + lines.push(`import FoundationModels`); + } lines.push(`import Foundation`); lines.push(``); @@ -93,6 +102,11 @@ export function generateSwift(intent: IRIntent): string { lines.push(``); } + if (safeIntent.model) { + lines.push(generateFoundationModelSupport(safeIntent)); + lines.push(``); + } + // Struct declaration if (safeIntent.schema) { lines.push(`@AppIntent(schema: ${safeIntent.schema})`); @@ -166,6 +180,16 @@ export function generateSwift(intent: IRIntent): string { lines.push(`}`); lines.push(``); + if (safeIntent.evaluation) { + lines.push(generateEvaluationSupport(safeIntent.evaluation)); + lines.push(``); + } + + if (safeIntent.previewProof) { + lines.push(generatePreviewProofSupport(safeIntent.previewProof)); + lines.push(``); + } + return lines.join("\n"); } @@ -364,6 +388,121 @@ function generateDynamicOptionsProvider(providerName: string, valueType: IRType) return lines.join("\n"); } +function generateFoundationModelSupport(intent: IRIntent): string { + const model = intent.model!; + const sessionName = model.sessionName || `${intent.name}ModelSession`; + const lines: string[] = []; + + if (model.generable) { + lines.push(`@Generable`); + lines.push(`struct ${model.generable.name}: Generable {`); + for (const [name, type] of Object.entries(model.generable.fields)) { + lines.push(` var ${name}: ${type}`); + } + lines.push(`}`); + lines.push(``); + } + + for (const tool of model.tools ?? []) { + const argumentsType = tool.argumentsType || `${tool.name}Arguments`; + const outputType = tool.outputType || "String"; + lines.push(`struct ${tool.name}: Tool {`); + lines.push(` let name = "${escapeSwiftString(tool.name)}"`); + lines.push(` let description = "${escapeSwiftString(tool.description)}"`); + lines.push(``); + lines.push(` @Generable`); + lines.push(` struct Arguments: Generable {`); + lines.push( + ` // TODO: Replace with fields from ${escapeSwiftString(argumentsType)}.` + ); + lines.push(` var query: String`); + lines.push(` }`); + lines.push(``); + lines.push(` func call(arguments: Arguments) async throws -> ${outputType} {`); + lines.push(` // TODO: Implement ${escapeSwiftString(tool.name)} safely.`); + lines.push(` ${defaultFoundationModelToolReturn(outputType)}`); + lines.push(` }`); + lines.push(`}`); + lines.push(``); + } + + lines.push(`enum ${sessionName}Factory {`); + lines.push(` static func make() -> LanguageModelSession {`); + if (model.instructions) { + lines.push( + ` let instructions = Instructions("${escapeSwiftString(model.instructions)}")` + ); + } else { + lines.push(` let instructions = Instructions("")`); + } + if (model.dynamicProfile) { + lines.push(` // Dynamic Profile: ${escapeSwiftString(model.dynamicProfile)}`); + } + if (model.guardrails?.length) { + lines.push( + ` // Guardrails: ${model.guardrails.map(escapeSwiftString).join(", ")}` + ); + } + lines.push( + ` // Provider: ${escapeSwiftString(model.provider)}${model.useCase ? ` · Use case: ${escapeSwiftString(model.useCase)}` : ""}` + ); + if (model.prompt) { + lines.push(` // Prompt seed: ${escapeSwiftString(model.prompt)}`); + } + lines.push(` return LanguageModelSession(instructions: instructions)`); + lines.push(` }`); + lines.push(`}`); + + return lines.join("\n"); +} + +function defaultFoundationModelToolReturn(outputType: string): string { + if (outputType === "String") return `return ""`; + if (outputType === "Int") return `return 0`; + if (outputType === "Bool") return `return false`; + if (/^\[[A-Za-z_][A-Za-z0-9_]*\]$/.test(outputType)) return `return []`; + return `throw CancellationError()`; +} + +function generateEvaluationSupport(evaluation: IREvaluationConfig): string { + const lines: string[] = []; + lines.push(`enum ${evaluation.suite} {`); + lines.push(` // Evaluations framework proof contract.`); + lines.push( + ` static let scenarios: [String] = [${evaluation.scenarios.map(quotedSwiftString).join(", ")}]` + ); + lines.push( + ` static let criteria: [String] = [${evaluation.criteria.map(quotedSwiftString).join(", ")}]` + ); + lines.push( + ` // AppIntentsTesting: pair these scenarios with Siri, Shortcuts, and Spotlight pathway tests.` + ); + lines.push(`}`); + return lines.join("\n"); +} + +function generatePreviewProofSupport(previewProof: IRPreviewProofConfig): string { + const lines: string[] = []; + lines.push(`// Preview Snapshot proof matrix for ${previewProof.view}.`); + lines.push( + `// Variants: ${previewProof.variants.length ? previewProof.variants.map(escapeSwiftString).join(", ") : "default"}` + ); + if (previewProof.widgetTimeline) { + lines.push( + `// Widget timeline states must be rendered in Xcode Preview Snapshot proof.` + ); + } + if (previewProof.liveActivityStates?.length) { + lines.push( + `// Live Activity states: ${previewProof.liveActivityStates.map(escapeSwiftString).join(", ")}` + ); + } + lines.push( + `// AppIntentsTesting and preview evidence should be attached before release.` + ); + return lines.join("\n"); +} + // ─── Info.plist Fragment Generator ─────────────────────────────────── /** diff --git a/src/core/index.ts b/src/core/index.ts index 1836051..0fe2762 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -96,6 +96,12 @@ export type { AppSchemaDomain, AppIntentConformance, EntityOwnership, + FoundationModelProvider, + FoundationModelGenerableDefinition, + FoundationModelToolDefinition, + FoundationModelDefinition, + EvaluationDefinition, + PreviewProofDefinition, EntityDefinition, EntityDisplay, ViewDefinition, diff --git a/src/core/parser.ts b/src/core/parser.ts index a97bdc1..8a92a09 100644 --- a/src/core/parser.ts +++ b/src/core/parser.ts @@ -24,6 +24,12 @@ import type { IRAppSchemaDomain, IRIntentConformance, IREntityOwnership, + IRFoundationModelConfig, + IRFoundationModelProvider, + IRFoundationModelTool, + IRFoundationModelGenerable, + IREvaluationConfig, + IRPreviewProofConfig, } from "./types.js"; import { PARAM_TYPES, LEGACY_PARAM_ALIASES, isPrimitiveType } from "./types.js"; import { @@ -39,6 +45,12 @@ import { findAllCallExpressions, } from "./parser-utils.js"; +const FOUNDATION_MODEL_PROVIDERS = new Set([ + "apple-on-device", + "private-cloud-compute", + "custom-language-model", +]); + function resolveEntityProperties(type: IRType, entities: IREntity[]): void { if (type.kind === "entity") { const match = entities.find((e) => e.name === type.entityName); @@ -172,6 +184,13 @@ export function parseIntentSource( const conformsTo = readStringArray(props.get("conformsTo")); const supportedModes = readStringLiteral(props.get("supportedModes")); const allowedExecutionTargets = readStringLiteral(props.get("allowedExecutionTargets")); + const model = parseFoundationModelConfig(props.get("model"), filePath, sourceFile); + const evaluation = parseEvaluationConfig(props.get("evaluation"), filePath, sourceFile); + const previewProof = parsePreviewProofConfig( + props.get("previewProof"), + filePath, + sourceFile + ); return { name, @@ -194,6 +213,9 @@ export function parseIntentSource( conformsTo: conformsTo.length > 0 ? (conformsTo as IRIntentConformance[]) : undefined, supportedModes: supportedModes || undefined, allowedExecutionTargets: allowedExecutionTargets || undefined, + model, + evaluation, + previewProof, }; } @@ -472,6 +494,196 @@ function parseParameterSummaryDefinition( ); } +function parseFoundationModelConfig( + node: ts.Node | undefined, + filePath: string, + sourceFile: ts.SourceFile +): IRFoundationModelConfig | undefined { + if (!node) return undefined; + if (!ts.isObjectLiteralExpression(node)) { + throw new ParserError( + "AX031", + "model must be an object literal", + filePath, + posOf(sourceFile, node), + 'Use model: { provider: "apple-on-device", instructions: "..." }.' + ); + } + + const props = propertyMap(node); + const provider = readStringLiteral(props.get("provider")); + if (!provider) { + throw new ParserError( + "AX032", + "model.provider is required", + filePath, + posOf(sourceFile, node), + 'Add provider: "apple-on-device", "private-cloud-compute", or "custom-language-model".' + ); + } + if (!FOUNDATION_MODEL_PROVIDERS.has(provider)) { + throw new ParserError( + "AX042", + `Invalid model.provider: "${provider}"`, + filePath, + posOf(sourceFile, props.get("provider") ?? node), + 'Use provider: "apple-on-device", "private-cloud-compute", or "custom-language-model".' + ); + } + + return { + sessionName: readStringLiteral(props.get("sessionName")) || undefined, + provider: provider as IRFoundationModelProvider, + useCase: readStringLiteral(props.get("useCase")) || undefined, + instructions: readStringLiteral(props.get("instructions")) || undefined, + prompt: readStringLiteral(props.get("prompt")) || undefined, + dynamicProfile: readStringLiteral(props.get("dynamicProfile")) || undefined, + guardrails: readStringArray(props.get("guardrails")), + generable: parseFoundationModelGenerable( + props.get("generable"), + filePath, + sourceFile + ), + tools: parseFoundationModelTools(props.get("tools"), filePath, sourceFile), + }; +} + +function parseFoundationModelGenerable( + node: ts.Node | undefined, + filePath: string, + sourceFile: ts.SourceFile +): IRFoundationModelGenerable | undefined { + if (!node) return undefined; + if (!ts.isObjectLiteralExpression(node)) { + throw new ParserError( + "AX033", + "model.generable must be an object literal", + filePath, + posOf(sourceFile, node) + ); + } + const props = propertyMap(node); + const name = readStringLiteral(props.get("name")); + if (!name) { + throw new ParserError( + "AX034", + "model.generable.name is required", + filePath, + posOf(sourceFile, node) + ); + } + return { + name, + fields: readStringRecord(props.get("fields")), + }; +} + +function parseFoundationModelTools( + node: ts.Node | undefined, + filePath: string, + sourceFile: ts.SourceFile +): IRFoundationModelTool[] { + if (!node) return []; + if (!ts.isArrayLiteralExpression(node)) { + throw new ParserError( + "AX035", + "model.tools must be an array", + filePath, + posOf(sourceFile, node) + ); + } + return node.elements.map((element) => { + if (!ts.isObjectLiteralExpression(element)) { + throw new ParserError( + "AX036", + "model.tools entries must be object literals", + filePath, + posOf(sourceFile, element) + ); + } + const props = propertyMap(element); + const name = readStringLiteral(props.get("name")); + const description = readStringLiteral(props.get("description")); + if (!name || !description) { + throw new ParserError( + "AX037", + "model.tools entries require name and description", + filePath, + posOf(sourceFile, element) + ); + } + return { + name, + description, + argumentsType: readStringLiteral(props.get("argumentsType")) || undefined, + outputType: readStringLiteral(props.get("outputType")) || undefined, + }; + }); +} + +function parseEvaluationConfig( + node: ts.Node | undefined, + filePath: string, + sourceFile: ts.SourceFile +): IREvaluationConfig | undefined { + if (!node) return undefined; + if (!ts.isObjectLiteralExpression(node)) { + throw new ParserError( + "AX038", + "evaluation must be an object literal", + filePath, + posOf(sourceFile, node) + ); + } + const props = propertyMap(node); + const suite = readStringLiteral(props.get("suite")); + if (!suite) { + throw new ParserError( + "AX039", + "evaluation.suite is required", + filePath, + posOf(sourceFile, node) + ); + } + return { + suite, + scenarios: readStringArray(props.get("scenarios")), + criteria: readStringArray(props.get("criteria")), + }; +} + +function parsePreviewProofConfig( + node: ts.Node | undefined, + filePath: string, + sourceFile: ts.SourceFile +): IRPreviewProofConfig | undefined { + if (!node) return undefined; + if (!ts.isObjectLiteralExpression(node)) { + throw new ParserError( + "AX040", + "previewProof must be an object literal", + filePath, + posOf(sourceFile, node) + ); + } + const props = propertyMap(node); + const view = readStringLiteral(props.get("view")); + if (!view) { + throw new ParserError( + "AX041", + "previewProof.view is required", + filePath, + posOf(sourceFile, node) + ); + } + return { + view, + variants: readStringArray(props.get("variants")), + widgetTimeline: readBooleanLiteral(props.get("widgetTimeline")), + liveActivityStates: readStringArray(props.get("liveActivityStates")), + }; +} + // ─── Parameter Extraction ──────────────────────────────────────────── function extractParameters( diff --git a/src/core/types.ts b/src/core/types.ts index cf9f946..b7039c1 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -96,6 +96,48 @@ export type IRIntentConformance = export type IREntityOwnership = "unknown" | "shared" | "public"; +export type IRFoundationModelProvider = + | "apple-on-device" + | "private-cloud-compute" + | "custom-language-model"; + +export interface IRFoundationModelGenerable { + name: string; + fields: Record; +} + +export interface IRFoundationModelTool { + name: string; + description: string; + argumentsType?: string; + outputType?: string; +} + +export interface IRFoundationModelConfig { + sessionName?: string; + provider: IRFoundationModelProvider; + useCase?: string; + instructions?: string; + prompt?: string; + dynamicProfile?: string; + guardrails?: string[]; + generable?: IRFoundationModelGenerable; + tools?: IRFoundationModelTool[]; +} + +export interface IREvaluationConfig { + suite: string; + scenarios: string[]; + criteria: string[]; +} + +export interface IRPreviewProofConfig { + view: string; + variants: string[]; + widgetTimeline?: boolean; + liveActivityStates?: string[]; +} + /** * An App Entity definition for complex, domain-specific data types. * Entities can be queried and used as parameter types in intents. @@ -237,6 +279,12 @@ export interface IRIntent { supportedModes?: string; /** Swift expression for `allowedExecutionTargets`, e.g. `.main`. */ allowedExecutionTargets?: string; + /** Foundation Models generation contract for Apple Intelligence intents. */ + model?: IRFoundationModelConfig; + /** Evaluation suite metadata that should accompany model-backed output. */ + evaluation?: IREvaluationConfig; + /** Preview snapshot matrix required to prove generated UI variants. */ + previewProof?: IRPreviewProofConfig; } // ─── Widget IR Types ──────────────────────────────────────────────────────── diff --git a/src/mcp/manifest.ts b/src/mcp/manifest.ts index 777c140..588bcdd 100644 --- a/src/mcp/manifest.ts +++ b/src/mcp/manifest.ts @@ -2206,7 +2206,7 @@ export const TOOL_MANIFEST = [ { name: "axint.templates.list", description: - "List all 26 bundled reference templates in the Axint SDK. Returns " + + "List all 39 bundled reference templates in the Axint SDK. Returns " + "a JSON array of { id, name, description } objects — one per template. " + "Templates cover messaging, productivity, health, finance, commerce, " + "media, navigation, smart-home, and entity/query patterns. No input " + diff --git a/src/sdk/index.ts b/src/sdk/index.ts index d959d65..fdf96aa 100644 --- a/src/sdk/index.ts +++ b/src/sdk/index.ts @@ -83,6 +83,48 @@ export type AppIntentConformance = export type EntityOwnership = "unknown" | "shared" | "public"; +export type FoundationModelProvider = + | "apple-on-device" + | "private-cloud-compute" + | "custom-language-model"; + +export interface FoundationModelGenerableDefinition { + name: string; + fields: Record; +} + +export interface FoundationModelToolDefinition { + name: string; + description: string; + argumentsType?: string; + outputType?: string; +} + +export interface FoundationModelDefinition { + sessionName?: string; + provider: FoundationModelProvider; + useCase?: string; + instructions?: string; + prompt?: string; + dynamicProfile?: string; + guardrails?: string[]; + generable?: FoundationModelGenerableDefinition; + tools?: FoundationModelToolDefinition[]; +} + +export interface EvaluationDefinition { + suite: string; + scenarios: string[]; + criteria: string[]; +} + +export interface PreviewProofDefinition { + view: string; + variants: string[]; + widgetTimeline?: boolean; + liveActivityStates?: string[]; +} + /** Configuration for a single parameter. */ export interface ParamConfig { /** Display name for this parameter (auto-generated from field name if omitted). */ @@ -297,6 +339,12 @@ export interface IntentDefinition< supportedModes?: string; /** Swift expression emitted as `static var allowedExecutionTargets`. */ allowedExecutionTargets?: string; + /** Foundation Models session/tool/guided-generation contract. */ + model?: FoundationModelDefinition; + /** Evaluation suite that proves model behavior across scenarios. */ + evaluation?: EvaluationDefinition; + /** Preview Snapshot proof matrix for generated UI variants. */ + previewProof?: PreviewProofDefinition; } /** diff --git a/src/templates/index.ts b/src/templates/index.ts index a01ef3b..7070144 100644 --- a/src/templates/index.ts +++ b/src/templates/index.ts @@ -1086,6 +1086,174 @@ export default defineIntent({ `, }; +const appIntentsTestingHarness: IntentTemplate = { + id: "appintents-testing-harness", + name: "appintents-testing-harness", + title: "AppIntentsTesting Harness", + domain: "apple-intelligence", + category: "testing", + description: + "Scaffold a schema-backed intent with an evaluation contract for Siri, Shortcuts, and Spotlight proof.", + source: `import { defineIntent, param } from "@axint/compiler"; + +export default defineIntent({ + name: "VerifyReminderIntentPath", + title: "Verify Reminder Intent Path", + description: "Exercises a schema-backed App Intent through user-facing system paths.", + schemaDomain: "reminders", + schema: "AppSchema.RemindersIntent.createReminder", + params: { + title: param.string("Reminder title"), + dueDate: param.date("Due date", { required: false }), + }, + evaluation: { + suite: "ReminderIntentPathEvaluations", + scenarios: ["siri-request", "shortcuts-run", "spotlight-suggestion"], + criteria: ["intent resolves", "parameters bind", "result is visible"], + }, + perform: async ({ title }) => { + // Swift proof hint: + // Add AppIntentsTesting coverage for Siri, Shortcuts, and Spotlight before release. + return { title }; + }, +}); +`, +}; + +const visualIntelligenceRouter: IntentTemplate = { + id: "visual-intelligence-router", + name: "visual-intelligence-router", + title: "Visual Intelligence Router", + domain: "apple-intelligence", + category: "visual-intelligence", + description: + "Scaffold an intent that maps a visual result into app search or object-specific actions.", + source: `import { defineIntent, param } from "@axint/compiler"; + +export default defineIntent({ + name: "RouteVisualResult", + title: "Route Visual Result", + description: "Routes a Visual Intelligence result into the right app workflow.", + schemaDomain: "visual-intelligence", + params: { + objectLabel: param.string("Detected object label"), + sourceContext: param.string("Screenshot, camera, or scene context", { + required: false, + }), + }, + previewProof: { + view: "VisualResultRouteView", + variants: ["light", "dark", "landscape"], + }, + perform: async ({ objectLabel }) => { + // Swift proof hint: + // Attach screenshot fixtures and Xcode 27 evidence that detected objects map correctly. + return { routedObject: objectLabel }; + }, +}); +`, +}; + +const imagePlaygroundIntent: IntentTemplate = { + id: "image-playground-intent", + name: "image-playground-intent", + title: "Image Playground Intent", + domain: "apple-intelligence", + category: "image-playground", + description: + "Scaffold an intent for generating an image artifact with style, safety, and proof metadata.", + source: `import { defineIntent, param } from "@axint/compiler"; + +export default defineIntent({ + name: "GenerateCampaignImage", + title: "Generate Campaign Image", + description: "Creates a generated image with an Image Playground handoff.", + schemaDomain: "assistant", + params: { + prompt: param.string("Image prompt"), + style: param.string("Requested visual style", { required: false }), + audience: param.string("Audience or campaign context", { required: false }), + }, + evaluation: { + suite: "CampaignImageEvaluations", + scenarios: ["safe-prompt", "style-match", "empty-context"], + criteria: ["artifact returned", "style respected", "safety constraints held"], + }, + perform: async ({ prompt }) => { + // Swift proof hint: + // Attach generated-image evidence before calling this demo-ready. + return { prompt, artifact: "Replace with generated image handle" }; + }, +}); +`, +}; + +const stringCatalogLocalizer: IntentTemplate = { + id: "string-catalog-localizer", + name: "string-catalog-localizer", + title: "String Catalog Localizer", + domain: "productivity", + category: "localization", + description: + "Scaffold a workflow for generating and proving String Catalog localization updates.", + source: `import { defineIntent, param } from "@axint/compiler"; + +export default defineIntent({ + name: "LocalizeStringCatalog", + title: "Localize String Catalog", + description: "Updates Localizable.xcstrings with reviewed generated translations.", + domain: "localization", + conformsTo: ["LongRunningIntent", "ProgressReportingIntent"], + supportedModes: "[.foreground, .background]", + params: { + catalogPath: param.string("Path to Localizable.xcstrings"), + locale: param.string("Target locale identifier"), + reviewMode: param.string("Review mode, such as draft or approved", { + default: "draft", + }), + }, + perform: async ({ catalogPath, locale }) => { + // Swift proof hint: + // Attach the updated .xcstrings artifact and locale coverage evidence. + return { catalogPath, locale }; + }, +}); +`, +}; + +const resizableLayoutProof: IntentTemplate = { + id: "resizable-layout-proof", + name: "resizable-layout-proof", + title: "Resizable Layout Proof", + domain: "developer-tools", + category: "preview-proof", + description: + "Scaffold a proof intent that tracks adaptive SwiftUI layout snapshots across iOS sizes.", + source: `import { defineIntent, param } from "@axint/compiler"; + +export default defineIntent({ + name: "VerifyResizableLayout", + title: "Verify Resizable Layout", + description: "Records Preview Snapshot proof for adaptive iOS SwiftUI layouts.", + domain: "developer-tools", + params: { + viewName: param.string("SwiftUI view name"), + targetSize: param.string("Target size class or preview variant"), + dynamicType: param.string("Dynamic Type size", { required: false }), + }, + previewProof: { + view: "ResizableLayoutPreview", + variants: ["compact", "regular", "landscape", "accessibilityExtraLarge"], + }, + perform: async ({ viewName, targetSize }) => { + // Swift proof hint: + // Attach Preview Snapshot baselines instead of relying on fixed device-sized frames. + return { viewName, targetSize }; + }, +}); +`, +}; + // ─── Registry ──────────────────────────────────────────────────────── export const TEMPLATES: IntentTemplate[] = [ @@ -1123,6 +1291,11 @@ export const TEMPLATES: IntentTemplate[] = [ systemShortcutBridge, entityCollectionSearch, unionValueRouter, + appIntentsTestingHarness, + visualIntelligenceRouter, + imagePlaygroundIntent, + stringCatalogLocalizer, + resizableLayoutProof, ]; /** @deprecated Use TEMPLATES. Kept for v0.1.x import compatibility. */ diff --git a/tests/cloud/check.test.ts b/tests/cloud/check.test.ts index 3174c80..51114d1 100644 --- a/tests/cloud/check.test.ts +++ b/tests/cloud/check.test.ts @@ -122,6 +122,60 @@ struct SendMessageIntent: AppIntent { expect(report.status).toBe("needs_review"); }); + it("asks schema-backed App Intents for Xcode 27 and AppIntentsTesting proof", () => { + const report = runCloudCheck({ + fileName: "CreateReminderIntent.swift", + source: ` +import AppIntents + +@AppIntent(schema: AppSchema.RemindersIntent.createReminder) +struct CreateReminderIntent: AppIntent { + static var title: LocalizedStringResource = "Create Reminder" + static var description: IntentDescription = "Create a reminder" + func perform() async throws -> some IntentResult { .result() } +} +`, + }); + + const codes = report.diagnostics.map((d) => d.code); + expect(codes).toContain("AXCLOUD-WWDC26-XCODE27-PROOF"); + expect(codes).toContain("AXCLOUD-WWDC26-APPINTENTS-TESTING"); + expect(report.checks).toContainEqual( + expect.objectContaining({ + label: "Xcode 27 readiness", + state: "warn", + }) + ); + expect(report.status).toBe("needs_review"); + }); + + it("accepts Xcode 27 AppIntentsTesting evidence for schema-backed intents", () => { + const report = runCloudCheck({ + fileName: "CreateReminderIntent.swift", + source: ` +import AppIntents + +@AppIntent(schema: AppSchema.RemindersIntent.createReminder) +struct CreateReminderIntent: AppIntent { + static var title: LocalizedStringResource = "Create Reminder" + static var description: IntentDescription = "Create a reminder" + func perform() async throws -> some IntentResult { .result() } +} +`, + xcodeBuildLog: [ + "Xcode 27.0", + "AppIntentsTesting passed CreateReminderIntent through Siri, Shortcuts, and Spotlight pathways.", + "** TEST SUCCEEDED **", + "Executed 1 test, with 0 failures", + ].join("\n"), + }); + + const codes = report.diagnostics.map((d) => d.code); + expect(codes).not.toContain("AXCLOUD-WWDC26-XCODE27-PROOF"); + expect(codes).not.toContain("AXCLOUD-WWDC26-APPINTENTS-TESTING"); + expect(report.status).toBe("pass"); + }); + it("asks Foundation Models code for model-version proof", () => { const report = runCloudCheck({ fileName: "ModelSummary.swift", @@ -144,6 +198,128 @@ struct SummarizeIntent: AppIntent { expect(report.status).toBe("needs_review"); }); + it("asks evaluation suites for scenario proof", () => { + const report = runCloudCheck({ + fileName: "MessageSummaryEvaluations.swift", + source: ` +import AppIntents + +enum MessageSummaryEvaluations { + static let scenarios: [String] = ["short-thread", "long-thread"] + static let criteria: [String] = ["preserves sender intent"] +} +`, + }); + + expect(report.diagnostics.map((d) => d.code)).toContain( + "AXCLOUD-WWDC26-EVALUATION-PROOF" + ); + expect(report.status).toBe("needs_review"); + }); + + it("asks preview snapshot matrices for attached snapshot proof", () => { + const report = runCloudCheck({ + fileName: "MessageSummaryPreviewProof.swift", + source: ` +import SwiftUI + +// Preview Snapshot proof matrix for MessageSummaryView. +// Variants: light, dark, landscape, accessibilityExtraLarge +struct MessageSummaryView: View { + var body: some View { Text("Summary") } +} +`, + }); + + expect(report.diagnostics.map((d) => d.code)).toContain( + "AXCLOUD-WWDC26-PREVIEW-SNAPSHOT-PROOF" + ); + expect(report.status).toBe("needs_review"); + }); + + it("asks Visual Intelligence routers for screenshot/object proof", () => { + const report = runCloudCheck({ + fileName: "VisualIntelligenceRouter.swift", + source: ` +import AppIntents +import VisionKit + +struct VisualIntelligenceRouterIntent: AppIntent { + static var title: LocalizedStringResource = "Route Visual Result" + static var description: IntentDescription = "Routes a Visual Intelligence result into app search." + func perform() async throws -> some IntentResult { .result() } +} +`, + }); + + expect(report.diagnostics.map((d) => d.code)).toContain( + "AXCLOUD-WWDC26-VISUAL-INTELLIGENCE-PROOF" + ); + expect(report.status).toBe("needs_review"); + }); + + it("asks Image Playground flows for generated-image proof", () => { + const report = runCloudCheck({ + fileName: "ImagePlaygroundIntent.swift", + source: ` +import AppIntents +import ImagePlayground + +struct GenerateCampaignImageIntent: AppIntent { + static var title: LocalizedStringResource = "Generate Campaign Image" + static var description: IntentDescription = "Creates a generated image in Image Playground." + func perform() async throws -> some IntentResult { .result() } +} +`, + }); + + expect(report.diagnostics.map((d) => d.code)).toContain( + "AXCLOUD-WWDC26-IMAGE-PLAYGROUND-PROOF" + ); + expect(report.status).toBe("needs_review"); + }); + + it("asks String Catalog workflows for localization proof", () => { + const report = runCloudCheck({ + fileName: "StringCatalogLocalizer.swift", + source: ` +import AppIntents + +struct LocalizeStringCatalogIntent: AppIntent { + static var title: LocalizedStringResource = "Localize String Catalog" + static var description: IntentDescription = "Updates Localizable.xcstrings with generated translations." + func perform() async throws -> some IntentResult { .result() } +} +`, + }); + + expect(report.diagnostics.map((d) => d.code)).toContain( + "AXCLOUD-WWDC26-STRING-CATALOG-PROOF" + ); + expect(report.status).toBe("needs_review"); + }); + + it("asks fixed-size SwiftUI layouts for resizable iOS proof", () => { + const report = runCloudCheck({ + fileName: "CampaignDashboardView.swift", + source: ` +import SwiftUI + +struct CampaignDashboardView: View { + var body: some View { + Text("Campaign") + .frame(width: 393, height: 852) + } +} +`, + }); + + expect(report.diagnostics.map((d) => d.code)).toContain( + "AXCLOUD-WWDC26-RESIZABLE-IOS-LAYOUT" + ); + expect(report.status).toBe("needs_review"); + }); + it("asks long-running progress intents for background and progress proof", () => { const report = runCloudCheck({ fileName: "ResearchBriefIntent.swift", diff --git a/tests/core/compiler.test.ts b/tests/core/compiler.test.ts index 3daab3d..f78181e 100644 --- a/tests/core/compiler.test.ts +++ b/tests/core/compiler.test.ts @@ -248,4 +248,75 @@ export default defineIntent({ expect(result.output?.swiftCode).toContain("var messages: [Message]"); expect(result.output?.swiftCode).toContain("var tags: [String]?"); }); + + it("emits Foundation Models, Evaluations, and preview proof support", () => { + const source = ` +import { defineIntent, param } from "@axint/sdk"; + +export default defineIntent({ + name: "SummarizeWithModel", + title: "Summarize With Model", + description: "Summarizes selected text with Apple Intelligence proof.", + schemaDomain: "assistant", + schema: "AppSchema.AssistantIntent.summarize", + params: { + sourceText: param.string("Text to summarize"), + }, + model: { + sessionName: "MessageSummarySession", + provider: "apple-on-device", + useCase: "summarization", + instructions: "Summarize the input for a busy reader.", + prompt: "Summarize the selected text.", + dynamicProfile: "MessageSummaryProfile", + guardrails: ["sensitive-content", "locale-aware"], + generable: { + name: "MessageSummary", + fields: { + summary: "String", + actionItems: "[String]", + }, + }, + tools: [ + { + name: "MessageSearchTool", + description: "Searches local messages for cited context.", + argumentsType: "MessageSearchArguments", + outputType: "[Message]", + }, + ], + }, + evaluation: { + suite: "MessageSummaryEvaluations", + scenarios: ["short-thread", "long-thread"], + criteria: ["preserves sender intent", "returns action items"], + }, + previewProof: { + view: "MessageSummaryView", + variants: ["light", "dark", "landscape", "accessibilityExtraLarge"], + widgetTimeline: true, + liveActivityStates: ["queued", "complete"], + }, + perform: async ({ sourceText }) => { + return { summary: sourceText }; + }, +}); +`; + const result = compileSource(source, "model-proof.ts"); + + expect(result.success).toBe(true); + expect(result.output?.ir.model?.provider).toBe("apple-on-device"); + expect(result.output?.ir.evaluation?.suite).toBe("MessageSummaryEvaluations"); + expect(result.output?.ir.previewProof?.view).toBe("MessageSummaryView"); + expect(result.output?.swiftCode).toContain("import FoundationModels"); + expect(result.output?.swiftCode).toContain("@Generable"); + expect(result.output?.swiftCode).toContain("struct MessageSummary: Generable"); + expect(result.output?.swiftCode).toContain("struct MessageSearchTool: Tool"); + expect(result.output?.swiftCode).toContain("enum MessageSummarySessionFactory"); + expect(result.output?.swiftCode).toContain("LanguageModelSession"); + expect(result.output?.swiftCode).toContain("Dynamic Profile: MessageSummaryProfile"); + expect(result.output?.swiftCode).toContain("enum MessageSummaryEvaluations"); + expect(result.output?.swiftCode).toContain("AppIntentsTesting"); + expect(result.output?.swiftCode).toContain("Preview Snapshot proof matrix"); + }); }); diff --git a/tests/core/parser.test.ts b/tests/core/parser.test.ts index 2f041b2..f75bb06 100644 --- a/tests/core/parser.test.ts +++ b/tests/core/parser.test.ts @@ -107,6 +107,24 @@ defineIntent({ expect(() => parseIntentSource(source, "badtype.ts")).toThrow(ParserError); }); + it("throws ParserError for unsupported Foundation Model providers", () => { + const source = ` +defineIntent({ + name: "ModelIntent", + title: "Model Intent", + description: "Uses a model", + params: {}, + model: { + provider: "unsupported-provider", + }, + perform: async () => {}, +}); +`; + expect(() => parseIntentSource(source, "bad-model-provider.ts")).toThrow( + /model.provider/ + ); + }); + it("ParserError formats correctly", () => { const err = new ParserError( "AX001", diff --git a/tests/extensions/xcode-agent-plugin.test.ts b/tests/extensions/xcode-agent-plugin.test.ts new file mode 100644 index 0000000..5ea764a --- /dev/null +++ b/tests/extensions/xcode-agent-plugin.test.ts @@ -0,0 +1,43 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; + +const ROOT = process.cwd(); + +describe("Xcode agent plugin package", () => { + it("declares Axint as an Xcode 27 agent plugin with MCP tools and skills", () => { + const manifestPath = join(ROOT, "extensions", "xcode", "agent-plugin", "plugin.json"); + const manifest = JSON.parse(readFileSync(manifestPath, "utf-8")) as { + name: string; + version: string; + mcpServers?: Record< + string, + { + type: string; + command?: string; + args?: string[]; + _meta?: { + ideToolIconPath?: string; + ideToolTitles?: Record; + }; + } + >; + skills?: Array<{ name: string; path: string }>; + }; + + expect(manifest.name).toBe("Axint"); + expect(manifest.version).toBe("0.4.30"); + expect(manifest.mcpServers?.axint).toMatchObject({ + type: "stdio", + command: "npx", + args: ["-y", "-p", "@axint/compiler", "axint-mcp"], + }); + expect(manifest.mcpServers?.axint?._meta?.ideToolTitles).toMatchObject({ + "axint.cloud.check": "Apple Readiness Check", + "axint.xcode.guard": "Axint Guard", + }); + expect(manifest.skills?.map((skill) => skill.name)).toContain( + "apple-intelligence-proof" + ); + }); +}); diff --git a/tests/templates/index.test.ts b/tests/templates/index.test.ts index 1c17f56..3b21a4e 100644 --- a/tests/templates/index.test.ts +++ b/tests/templates/index.test.ts @@ -5,6 +5,7 @@ import { templates, TEMPLATES, } from "../../src/templates/index.js"; +import { compileSource } from "../../src/core/compiler.js"; describe("templates registry", () => { it("TEMPLATES contains the bundled reference set", () => { @@ -51,4 +52,23 @@ describe("templates registry", () => { expect(msgs.length).toBeGreaterThan(0); expect(msgs.every((t) => t.category === "messaging")).toBe(true); }); + + it("ships WWDC26 proof templates that compile", () => { + const ids = [ + "appintents-testing-harness", + "visual-intelligence-router", + "image-playground-intent", + "string-catalog-localizer", + "resizable-layout-proof", + ]; + + for (const id of ids) { + const template = getTemplate(id); + expect(template, id).toBeDefined(); + + const result = compileSource(template!.source, `${id}.ts`); + expect(result.success, id).toBe(true); + expect(result.output?.swiftCode, id).toContain("struct"); + } + }); });