@msn-control/liftoff 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Status: implemented.
4
4
 
5
- Liftoff is the Mission Control Launchpad Engine package under `tools/liftoff-cli`. It exposes the `liftoff` command and scaffolds enterprise GenAI applications with FastAPI, PydanticAI, Docker Compose, OpenTofu, and spec-driven governance assets.
5
+ Liftoff is the Mission Control Launchpad Engine package under `tools/liftoff-cli`. It exposes the `liftoff` command and scaffolds governed GenAI applications or standard APIs with Docker Compose, OpenTofu, and spec-driven governance assets.
6
6
 
7
7
  ## Installation
8
8
 
@@ -12,20 +12,144 @@ Install the latest stable CLI globally:
12
12
  npm install -g @msn-control/liftoff@latest
13
13
  ```
14
14
 
15
- After installation, run:
15
+ After installation, confirm the command is available:
16
16
 
17
17
  ```bash
18
18
  liftoff help
19
- liftoff create
20
19
  ```
21
20
 
22
- ## Contract conventions
21
+ ## Quick Start
22
+
23
+ Preview a generated project before writing files:
24
+
25
+ ```bash
26
+ liftoff plan --pattern rag --cloud azure --region eastus --frontend
27
+ ```
28
+
29
+ Create a project non-interactively:
30
+
31
+ ```bash
32
+ liftoff create claims-copilot --pattern rag --cloud azure --region eastus --spec openspec --frontend --yes
33
+ cd claims-copilot
34
+ cp .env.example .env
35
+ liftoff validate
36
+ liftoff doctor
37
+ ```
38
+
39
+ Create a standard Node.js API without GenAI components:
40
+
41
+ ```bash
42
+ liftoff plan --no-genai --api node --cloud azure --region eastus
43
+ liftoff create claims-api --no-genai --api node --cloud azure --region eastus --spec openspec --no-frontend --yes
44
+ ```
45
+
46
+ Standard API choices are `python` (FastAPI), `node` (Fastify with TypeScript), and `go` (Huma v2 with Chi). Existing GenAI commands that provide `--pattern` continue to infer the GenAI project type and Python/FastAPI stack.
47
+
48
+ Use the local development helper to print the Docker Compose command, then run the printed command:
49
+
50
+ ```bash
51
+ liftoff dev up
52
+ docker compose up --build
53
+ ```
54
+
55
+ For an existing project, create a Liftoff-shaped sibling scaffold and staged migration plan without modifying the source project:
56
+
57
+ ```bash
58
+ liftoff migrate ../legacy-app --region eastus --yes
59
+ ```
60
+
61
+ ## How Liftoff Works
62
+
63
+ Liftoff turns a small set of project decisions into a deterministic scaffold and records what it wrote so future CLI versions can reconcile safely.
64
+
65
+ ```text
66
+ plan -> create or migrate -> validate and doctor -> update -> dev and infra helpers
67
+ ```
68
+
69
+ - `liftoff plan` resolves flags or config into a project plan, renders the artifact list, and writes nothing.
70
+ - `liftoff create` first determines whether the project is GenAI or standard. GenAI projects select a pattern and use Python/FastAPI/PydanticAI; standard projects select an approved Python, Node.js, or Go API stack. Both flows collect cloud, region, frontend, environments, and spec workflow.
71
+ - `liftoff migrate <path>` scans a legacy project, generates a fresh sibling Liftoff scaffold, stages a filtered legacy copy under `migration/legacy`, and emits an OpenSpec migration change or `MIGRATION.md`.
72
+ - `liftoff validate` checks that `liftoff.manifest.json` can be loaded and every durable manifest artifact exists on disk.
73
+ - `liftoff doctor` runs read-only readiness checks. Outside a generated project it checks the local environment; inside a generated project it also checks manifest health, scaffold drift, runtime configuration, cloud auth, and Azure Functions tooling for worker-enabled Azure projects.
74
+ - `liftoff update` checks scaffold drift by default and writes nothing. It exits 0 when clean and 2 when drift exists, which makes it usable as a CI drift gate.
75
+ - `liftoff update --apply` writes safe changes such as new, missing, moved, or untouched upgraded artifacts. It skips conflicts unless `--force` is also provided, and it reports orphaned files without deleting them automatically.
76
+ - `liftoff dev` prints Docker Compose helper commands such as `docker compose up --build`, `docker compose logs -f`, and `docker compose down --volumes`.
77
+ - `liftoff infra` prints OpenTofu helper commands such as `tofu init`, `tofu plan -var-file=environments/dev.tfvars`, and `tofu apply -var-file=environments/dev.tfvars`.
78
+
79
+ Discovery commands are available for the catalogs that drive generation:
80
+
81
+ ```bash
82
+ liftoff patterns
83
+ liftoff providers
84
+ liftoff regions
85
+ liftoff regions search korea --cloud azure
86
+ ```
87
+
88
+ Azure is the available V1 provider. AWS and GCP are listed as planned provider adapters and are rejected before generation.
89
+
90
+ ## Generated Project Structure
91
+
92
+ Generated paths below are logical project structure examples. The CLI writes them using platform-correct filesystem handling on macOS, Linux, and Windows, and machine-readable manifests store path parts rather than joined path strings.
93
+
94
+ ```text
95
+ claims-copilot/
96
+ |-- README.md
97
+ |-- liftoff.config.json
98
+ |-- liftoff.manifest.json
99
+ |-- .env.example
100
+ |-- Dockerfile
101
+ |-- docker-compose.yml
102
+ |-- backend/ # internals depend on the selected API stack
103
+ |-- database/
104
+ | |-- alembic.ini
105
+ | |-- migrations/
106
+ | `-- models/
107
+ |-- environments/
108
+ | |-- dev/
109
+ | |-- test/
110
+ | `-- prod/
111
+ |-- infrastructure/
112
+ | `-- opentofu/
113
+ | `-- azure/
114
+ |-- openspec/ or .specify/
115
+ |-- frontend/ # only when frontend generation is selected
116
+ |-- functions/<worker-name>/ # only for worker-enabled Azure patterns
117
+ `-- migration/legacy/ # only for projects created by liftoff migrate
118
+ ```
119
+
120
+ Core generated areas:
121
+
122
+ - `backend` contains the selected API stack and Scalar/OpenAPI wiring. Python uses `backend/apis`, Node.js uses `backend/src`, and Go uses `backend/cmd/api` plus `backend/internal`.
123
+ - `backend/orchestration` appears only in GenAI projects and contains PydanticAI agents, prompts, model configuration, and integration boundaries.
124
+ - `database` contains stack-native migrations and SQL schema assets: SQLAlchemy/Alembic for Python, Drizzle for Node.js, or pgx/Goose for Go.
125
+ - `environments/<env>` contains environment-specific backend settings, and Functions settings when a worker is generated.
126
+ - `docker-compose.yml` starts the selected backend runtime, PostgreSQL, Redis, Azurite, and Mailpit. GenAI projects use pgvector when required and include the optional Langfuse observability profile.
127
+ - `infrastructure/opentofu/azure` contains Azure OpenTofu modules, environment tfvars, local state configuration, and a remote state example.
128
+ - `openspec` is generated for the OpenSpec workflow; `.specify` and `specs` are generated for the Spec Kit workflow.
129
+
130
+ Conditional areas:
131
+
132
+ - `frontend` is generated only when `--frontend` is selected. It uses Vue 3 and Tailwind with a generic API starter for standard projects or an experience matched to the selected GenAI pattern.
133
+ - `functions/<worker-name>` is generated only for worker-enabled Azure GenAI patterns such as RAG, agent, multi-agent, and workflow. Azure Functions trigger adapters live there; reusable GenAI orchestration stays under `backend/orchestration`.
134
+ - `backend/workers` appears for worker-backed patterns as backend-adjacent or containerized worker code, separate from Azure Functions runtime files.
135
+ - `migration/legacy` appears only after `liftoff migrate` and contains a filtered copy of the source project for guided migration work.
136
+
137
+ ## Configuration And Manifest
138
+
139
+ Generated projects contain two root files with different ownership models:
140
+
141
+ - `liftoff.config.json` is user-owned desired state after creation. Liftoff writes it once during generation and does not machine-rewrite it afterwards. Supported changes, such as adding an environment or enabling frontend output, are reconciled by `liftoff update`. Project type, API stack, and GenAI pattern changes are migrations and should use `liftoff migrate`.
142
+ - `liftoff.manifest.json` is the CLI-owned compatibility record. Manifest schema v2 records `artifactVersion`, the generating `liftoffVersion`, project type, API stack, applicable GenAI pattern, durable generated artifact `logicalName`s, categories, OS-neutral path parts, and `sha256:` content hashes.
143
+
144
+ The manifest lets `liftoff validate`, `liftoff doctor`, and `liftoff update` distinguish clean generated files from local edits. Seed content, such as the initial OpenSpec bootstrap change, is written once and intentionally omitted from the durable manifest so it can follow its own lifecycle.
145
+
146
+ ## Contract Conventions
23
147
 
24
148
  Generated projects contain persistent files that outlive any CLI release. The following rules are the compatibility contract, enforced by `tests/contract.test.ts` where possible:
25
149
 
26
- - **Manifest schema**: `liftoff.manifest.json` uses `artifactVersion` 2 the first supported schema version recording the generating CLI version (`liftoffVersion`) and a `sha256:`-prefixed `contentHash` per artifact. Readers accept every supported version and reject others with a remedy; writers always write the latest version.
27
- - **Append-only identifiers**: artifact `logicalName`s and catalog ids (patterns, providers, environments, spec workflows) are never renamed or removed, only added. The contract test snapshots the logical-name sets.
28
- - **Deterministic rendering**: artifact content depends only on the project plan and the template code no timestamps, randomness, or environment leakage. Verified by a double-render byte-equality test.
150
+ - **Manifest schema**: `liftoff.manifest.json` uses `artifactVersion` 2, the first supported schema version, recording the generating CLI version (`liftoffVersion`) and a `sha256:`-prefixed `contentHash` per artifact. Readers accept every supported version and reject others with a remedy; writers always write the latest version.
151
+ - **Append-only identifiers**: artifact `logicalName`s and catalog ids (project types, API stacks, patterns, providers, environments, spec workflows) are never renamed or removed, only added. The contract test snapshots the logical-name sets.
152
+ - **Deterministic rendering**: artifact content depends only on the project plan and the template code, with no timestamps, randomness, or environment leakage. Verified by a double-render byte-equality test.
29
153
  - **Reserved namespaces**: `.liftoff/` in generated projects is reserved for future CLI-managed state; no new CLI-managed root-level files beyond `liftoff.config.json` and `liftoff.manifest.json`. `liftoff.config.json` is written once at generation and never machine-written afterwards.
30
154
  - **Portable paths**: machine-readable files store OS-neutral path-part arrays, never joined path strings.
31
155
  - **Exit codes**: 0 = success or clean check, 1 = failure, 2 = a check mode found drift.
package/dist/args.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const commandsWithSubcommands = new Set(['regions', 'dev', 'infra']);
2
- const booleanFlags = new Set(['yes', 'frontend', 'help', 'json', 'print']);
3
- const valueFlags = new Set(['pattern', 'cloud', 'region', 'spec', 'environments', 'config', 'project', 'env', 'profile']);
2
+ const booleanFlags = new Set(['yes', 'frontend', 'genai', 'help', 'json', 'print']);
3
+ const valueFlags = new Set(['api', 'pattern', 'cloud', 'region', 'spec', 'environments', 'config', 'project', 'env', 'profile']);
4
4
  export function parseArgs(argv) {
5
5
  const [command, maybeSubcommand, ...rest] = argv;
6
6
  const positional = [];
package/dist/args.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"args.js","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"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"}
@@ -1,11 +1,15 @@
1
- import type { EnvironmentDefinition, EnvironmentId, PatternDefinition, PatternId, ProviderDefinition, ProviderId, RegionDefinition, SpecWorkflowDefinition, SpecWorkflowId } from './types.js';
1
+ import type { ApiStackDefinition, ApiStackId, EnvironmentDefinition, EnvironmentId, PatternDefinition, PatternId, ProviderDefinition, ProviderId, ProjectTypeDefinition, ProjectTypeId, RegionDefinition, SpecWorkflowDefinition, SpecWorkflowId } from './types.js';
2
2
  export declare const approvedStack: string[];
3
+ export declare const projectTypes: ProjectTypeDefinition[];
4
+ export declare const apiStacks: ApiStackDefinition[];
3
5
  export declare const patterns: PatternDefinition[];
4
6
  export declare const providers: ProviderDefinition[];
5
7
  export declare const azureRegions: RegionDefinition[];
6
8
  export declare const environments: EnvironmentDefinition[];
7
9
  export declare const specWorkflows: SpecWorkflowDefinition[];
8
10
  export declare function getPattern(value: string): PatternDefinition | undefined;
11
+ export declare function getProjectType(value: string): ProjectTypeDefinition | undefined;
12
+ export declare function getApiStack(value: string): ApiStackDefinition | undefined;
9
13
  export declare function getProvider(value: string): ProviderDefinition | undefined;
10
14
  export declare function getSpecWorkflow(value: string): SpecWorkflowDefinition | undefined;
11
15
  export declare function getEnvironment(value: string): EnvironmentDefinition | undefined;
@@ -24,6 +28,8 @@ export type RegionResolution = {
24
28
  };
25
29
  export declare function resolveRegion(provider: ProviderId, input?: string): RegionResolution;
26
30
  export declare function isPatternId(value: string): value is PatternId;
31
+ export declare function isProjectTypeId(value: string): value is ProjectTypeId;
32
+ export declare function isApiStackId(value: string): value is ApiStackId;
27
33
  export declare function isProviderId(value: string): value is ProviderId;
28
34
  export declare function isEnvironmentId(value: string): value is EnvironmentId;
29
35
  export declare function isSpecWorkflowId(value: string): value is SpecWorkflowId;
package/dist/catalogs.js CHANGED
@@ -14,6 +14,47 @@ export const approvedStack = [
14
14
  'Docker Compose',
15
15
  'OpenTofu'
16
16
  ];
17
+ export const projectTypes = [
18
+ {
19
+ id: 'genai',
20
+ label: 'GenAI application',
21
+ description: 'Python/FastAPI application with PydanticAI and a selected GenAI pattern.'
22
+ },
23
+ {
24
+ id: 'standard',
25
+ label: 'Standard application',
26
+ description: 'Non-GenAI API application using an approved Python, Node.js, or Go stack.'
27
+ }
28
+ ];
29
+ export const apiStacks = [
30
+ {
31
+ id: 'python-fastapi',
32
+ label: 'Python / FastAPI',
33
+ aliases: ['python', 'py', 'fastapi', 'python-fastapi'],
34
+ language: 'Python',
35
+ framework: 'FastAPI',
36
+ databaseTooling: 'SQLAlchemy + Alembic',
37
+ testFramework: 'pytest'
38
+ },
39
+ {
40
+ id: 'node-fastify',
41
+ label: 'Node.js / Fastify / TypeScript',
42
+ aliases: ['node', 'nodejs', 'node.js', 'fastify', 'typescript', 'node-fastify'],
43
+ language: 'Node.js',
44
+ framework: 'Fastify with TypeScript',
45
+ databaseTooling: 'Drizzle',
46
+ testFramework: 'Vitest'
47
+ },
48
+ {
49
+ id: 'go-huma',
50
+ label: 'Go / Huma / Chi',
51
+ aliases: ['go', 'golang', 'huma', 'chi', 'go-huma'],
52
+ language: 'Go',
53
+ framework: 'Huma v2 with Chi',
54
+ databaseTooling: 'pgx + Goose',
55
+ testFramework: 'go test'
56
+ }
57
+ ];
17
58
  export const patterns = [
18
59
  {
19
60
  id: 'rag',
@@ -191,6 +232,14 @@ export function getPattern(value) {
191
232
  const normalized = normalize(value);
192
233
  return patterns.find((pattern) => normalize(pattern.id) === normalized || pattern.aliases.some((alias) => normalize(alias) === normalized));
193
234
  }
235
+ export function getProjectType(value) {
236
+ const normalized = normalize(value);
237
+ return projectTypes.find((projectType) => normalize(projectType.id) === normalized || normalize(projectType.label) === normalized);
238
+ }
239
+ export function getApiStack(value) {
240
+ const normalized = normalize(value);
241
+ return apiStacks.find((stack) => normalize(stack.id) === normalized || stack.aliases.some((alias) => normalize(alias) === normalized));
242
+ }
194
243
  export function getProvider(value) {
195
244
  const normalized = normalize(value);
196
245
  return providers.find((provider) => normalize(provider.id) === normalized || normalize(provider.label) === normalized);
@@ -250,6 +299,12 @@ export function resolveRegion(provider, input) {
250
299
  export function isPatternId(value) {
251
300
  return patterns.some((pattern) => pattern.id === value);
252
301
  }
302
+ export function isProjectTypeId(value) {
303
+ return projectTypes.some((projectType) => projectType.id === value);
304
+ }
305
+ export function isApiStackId(value) {
306
+ return apiStacks.some((stack) => stack.id === value);
307
+ }
253
308
  export function isProviderId(value) {
254
309
  return providers.some((provider) => provider.id === value);
255
310
  }
@@ -1 +1 @@
1
- {"version":3,"file":"catalogs.js","sourceRoot":"","sources":["../src/catalogs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"catalogs.js","sourceRoot":"","sources":["../src/catalogs.ts"],"names":[],"mappings":"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"}
package/dist/commands.js CHANGED
@@ -4,7 +4,7 @@ import { cp, mkdir, mkdtemp, rm, stat, writeFile } from 'node:fs/promises';
4
4
  import os from 'node:os';
5
5
  import path from 'node:path';
6
6
  import { readBooleanFlag, readListFlag, readStringFlag } from './args.js';
7
- import { approvedStack, listRegions, patterns, providers, searchRegions } from './catalogs.js';
7
+ import { apiStacks, listRegions, patterns, providers, searchRegions } from './catalogs.js';
8
8
  import { artifactPath, assertNewOrEmptyDirectory, deleteProjectFile, findProjectRoot, loadManifest, manifestDisplayPath, resolveTargetRoot, validateGeneratedProject, writeArtifacts, writeProjectFile } from './file-system.js';
9
9
  import { confirmPlan, promptForCreateOptions } from './interactive.js';
10
10
  import { renderMigrationChecklist, renderMigrationProposal, renderMigrationTasks, seedMigrationGroups } from './migrate-plan.js';
@@ -130,7 +130,7 @@ async function validateCommand(parsed, context) {
130
130
  context.stdout.write('Generated project manifest is valid.\n');
131
131
  return 0;
132
132
  }
133
- const STAGING_EXCLUDES = new Set(['.git', 'node_modules', '.venv', 'venv', '__pycache__', 'dist', 'build', '.next']);
133
+ const STAGING_EXCLUDES = new Set(['.git', 'node_modules', 'vendor', '.venv', 'venv', '__pycache__', 'dist', 'build', '.next']);
134
134
  async function migrateCommand(parsed, context) {
135
135
  const sourceArg = parsed.positional[0];
136
136
  if (!sourceArg) {
@@ -163,6 +163,22 @@ async function migrateCommand(parsed, context) {
163
163
  context.stdout.write('\n');
164
164
  const flagOptions = await optionsFromParsedArgs(parsed, context.cwd, false);
165
165
  const initial = mergeOptions(defaults, flagOptions);
166
+ if (flagOptions.pattern && flagOptions.projectType === undefined) {
167
+ initial.projectType = 'genai';
168
+ if (flagOptions.apiStack === undefined) {
169
+ initial.apiStack = undefined;
170
+ }
171
+ }
172
+ else if (flagOptions.apiStack && flagOptions.projectType === undefined) {
173
+ initial.projectType = 'standard';
174
+ initial.pattern = undefined;
175
+ }
176
+ if (flagOptions.projectType === 'standard' && flagOptions.pattern === undefined) {
177
+ initial.pattern = undefined;
178
+ }
179
+ if (flagOptions.projectType === 'genai' && flagOptions.apiStack === undefined) {
180
+ initial.apiStack = undefined;
181
+ }
166
182
  const needsPrompts = !initial.yes && hasMissingCreateInputs(initial);
167
183
  const options = needsPrompts ? await promptForCreateOptions(initial) : initial;
168
184
  const plan = buildProjectPlan(options, { requireProjectName: true });
@@ -194,7 +210,7 @@ async function migrateCommand(parsed, context) {
194
210
  return !relative.split(path.sep).some((part) => STAGING_EXCLUDES.has(part));
195
211
  }
196
212
  });
197
- const groups = seedMigrationGroups(inventory);
213
+ const groups = seedMigrationGroups(inventory, plan);
198
214
  let planLocation;
199
215
  if (plan.specWorkflow.id === 'openspec') {
200
216
  const changeDir = path.join(targetRoot, 'openspec', 'changes', 'migrate-to-liftoff');
@@ -285,11 +301,19 @@ async function updateCommand(parsed, context) {
285
301
  return 1;
286
302
  }
287
303
  const config = await loadConfigOptions('liftoff.config.json', projectRoot);
288
- if (config.pattern && config.pattern !== manifest.project.pattern) {
289
- context.stderr.write(`Pattern changes (${manifest.project.pattern} -> ${config.pattern}) are a migration, not an update. Run liftoff migrate instead.\n`);
304
+ const plan = buildProjectPlan(config, { requireProjectName: true });
305
+ if (plan.projectType.id !== manifest.project.projectType) {
306
+ context.stderr.write(`Project type changes (${manifest.project.projectType} -> ${plan.projectType.id}) are a migration, not an update. Run liftoff migrate instead.\n`);
307
+ return 1;
308
+ }
309
+ if (plan.apiStack.id !== manifest.project.apiStack) {
310
+ context.stderr.write(`API stack changes (${manifest.project.apiStack} -> ${plan.apiStack.id}) are a migration, not an update. Run liftoff migrate instead.\n`);
311
+ return 1;
312
+ }
313
+ if (plan.pattern?.id !== manifest.project.pattern) {
314
+ context.stderr.write(`Pattern changes (${manifest.project.pattern ?? 'none'} -> ${plan.pattern?.id ?? 'none'}) are a migration, not an update. Run liftoff migrate instead.\n`);
290
315
  return 1;
291
316
  }
292
- const plan = buildProjectPlan(config, { requireProjectName: true });
293
317
  const render = buildArtifacts(plan);
294
318
  const entries = await reconcileProject(manifest, render, projectRoot);
295
319
  const summary = summarizeEntries(entries);
@@ -408,24 +432,97 @@ async function updateCommand(parsed, context) {
408
432
  context.stdout.write(`Updated: ${written.length} written, ${skipped.length} skipped, ${summary.orphan} orphan(s). Manifest recorded at ${liftoffVersion}.\n`);
409
433
  return 0;
410
434
  }
411
- function binaryCheck(command, args, remedy) {
412
- const result = spawnSync(command, args, { encoding: 'utf8' });
435
+ function binaryCheck(command, args, remedy, cwd) {
436
+ const result = spawnSync(command, args, { cwd, encoding: 'utf8' });
413
437
  if (result.status === 0) {
414
438
  return { label: command, severity: 'ok', detail: (result.stdout || result.stderr).split('\n')[0].trim() };
415
439
  }
416
440
  return { label: command, severity: 'fail', detail: 'not found', remedy };
417
441
  }
418
- function environmentLayer() {
442
+ function versionedBinaryCheck(label, command, args, minimum, remedy) {
443
+ const result = spawnSync(command, args, { encoding: 'utf8' });
444
+ if (result.status !== 0) {
445
+ return { label, severity: 'fail', detail: 'not found', remedy };
446
+ }
447
+ const output = (result.stdout || result.stderr).split('\n')[0].trim();
448
+ const match = output.match(/(\d+)\.(\d+)/);
449
+ if (!match) {
450
+ return { label, severity: 'fail', detail: `unable to determine version from "${output}"`, remedy };
451
+ }
452
+ const found = [Number(match[1]), Number(match[2])];
453
+ if (found[0] < minimum[0] || found[0] === minimum[0] && found[1] < minimum[1]) {
454
+ return { label, severity: 'fail', detail: `${output} is below ${minimum.join('.')}`, remedy };
455
+ }
456
+ return { label, severity: 'ok', detail: output };
457
+ }
458
+ function pythonRuntime() {
459
+ const candidates = process.platform === 'win32'
460
+ ? [
461
+ { command: 'py', versionArgs: ['-3', '--version'], commandArgs: ['-3'] },
462
+ { command: 'python', versionArgs: ['--version'], commandArgs: [] },
463
+ { command: 'python3', versionArgs: ['--version'], commandArgs: [] }
464
+ ]
465
+ : [
466
+ { command: 'python3', versionArgs: ['--version'], commandArgs: [] },
467
+ { command: 'python', versionArgs: ['--version'], commandArgs: [] }
468
+ ];
469
+ return candidates.find((candidate) => versionedBinaryCheck('python', candidate.command, candidate.versionArgs, [3, 12], 'install Python 3.12 or newer').severity === 'ok') ?? candidates.find((candidate) => binaryPresent(candidate.command));
470
+ }
471
+ function environmentLayer(apiStack) {
472
+ const python = pythonRuntime();
473
+ const runtimeChecks = apiStack === 'go-huma'
474
+ ? [versionedBinaryCheck('go', 'go', ['version'], [1, 23], 'install Go 1.23 or newer')]
475
+ : apiStack === 'node-fastify'
476
+ ? []
477
+ : [python
478
+ ? versionedBinaryCheck('python', python.command, python.versionArgs, [3, 12], 'install Python 3.12 or newer')
479
+ : { label: 'python', severity: 'fail', detail: 'not found', remedy: 'install Python 3.12 or newer' }];
419
480
  return {
420
481
  title: 'Environment',
421
482
  checks: [
422
- binaryCheck('node', ['--version'], 'install Node.js 20 or newer'),
423
- binaryCheck('python3', ['--version'], 'install Python 3'),
483
+ versionedBinaryCheck('node', 'node', ['--version'], [20, 0], 'install Node.js 20 or newer'),
484
+ ...runtimeChecks,
424
485
  binaryCheck('docker', ['--version'], 'install Docker'),
425
486
  binaryCheck('tofu', ['--version'], 'install OpenTofu')
426
487
  ]
427
488
  };
428
489
  }
490
+ function stackProjectCheck(projectRoot, apiStack) {
491
+ let result;
492
+ switch (apiStack) {
493
+ case 'python-fastapi':
494
+ {
495
+ const python = pythonRuntime();
496
+ if (!python) {
497
+ return { label: 'python project', severity: 'skipped', detail: 'python is unavailable' };
498
+ }
499
+ result = runReadOnly(python.command, [...python.commandArgs, '-c', 'from pathlib import Path; p=Path("backend/apis/main.py"); compile(p.read_text(), str(p), "exec")'], projectRoot);
500
+ }
501
+ break;
502
+ case 'node-fastify':
503
+ result = runReadOnly('node', ['-e', 'const f=require("fs"); JSON.parse(f.readFileSync("backend/package.json")); JSON.parse(f.readFileSync("backend/tsconfig.json"));'], projectRoot);
504
+ break;
505
+ case 'go-huma':
506
+ if (!binaryPresent('go')) {
507
+ return { label: 'go project', severity: 'skipped', detail: 'go is unavailable' };
508
+ }
509
+ result = runReadOnly('go', ['mod', 'edit', '-json'], path.join(projectRoot, 'backend'));
510
+ break;
511
+ }
512
+ if (result.status === 0) {
513
+ return { label: `${apiStack} project`, severity: 'ok', detail: 'stack configuration is valid' };
514
+ }
515
+ return {
516
+ label: `${apiStack} project`,
517
+ severity: 'fail',
518
+ detail: (result.stderr || result.stdout || 'stack validation failed').split('\n')[0],
519
+ remedy: `repair the generated ${apiStack} backend configuration`
520
+ };
521
+ }
522
+ function runReadOnly(command, args, cwd) {
523
+ const result = spawnSync(command, args, { cwd, encoding: 'utf8' });
524
+ return { status: result.status, stdout: result.stdout, stderr: result.stderr };
525
+ }
429
526
  function binaryPresent(command) {
430
527
  const probe = process.platform === 'win32' ? 'where' : 'which';
431
528
  return spawnSync(probe, [command], { encoding: 'utf8' }).status === 0;
@@ -492,6 +589,7 @@ async function projectLayer(projectRoot, manifest) {
492
589
  else {
493
590
  checks.push({ label: 'version', severity: 'ok', detail: `generated by ${manifest.liftoffVersion}, CLI ${liftoffVersion}` });
494
591
  }
592
+ checks.push(stackProjectCheck(projectRoot, manifest.project.apiStack));
495
593
  const latest = await lookupLatestPublishedVersion();
496
594
  if (latest && compareSemver(latest, liftoffVersion) > 0) {
497
595
  checks.push({
@@ -586,19 +684,25 @@ async function doctorCommand(parsed, context) {
586
684
  const jsonMode = readBooleanFlag(parsed.flags, 'json') ?? false;
587
685
  const cloudOverride = readStringFlag(parsed.flags, 'cloud');
588
686
  const layers = [];
589
- const environment = environmentLayer();
590
- layers.push(environment);
591
- const dockerAvailable = environment.checks.some((check) => check.label === 'docker' && check.severity === 'ok');
592
687
  const projectRoot = await findProjectRoot(context.cwd);
688
+ let manifest;
689
+ let manifestError;
593
690
  if (projectRoot) {
594
- let manifest;
595
691
  try {
596
692
  manifest = await loadManifest(projectRoot);
597
693
  }
598
694
  catch (error) {
695
+ manifestError = error;
696
+ }
697
+ }
698
+ const environment = environmentLayer(manifest?.project.apiStack);
699
+ layers.push(environment);
700
+ const dockerAvailable = environment.checks.some((check) => check.label === 'docker' && check.severity === 'ok');
701
+ if (projectRoot) {
702
+ if (manifestError) {
599
703
  layers.push({
600
704
  title: 'Project',
601
- checks: [{ label: 'manifest', severity: 'fail', detail: error.message, remedy: 'regenerate the project or use a matching CLI version' }]
705
+ checks: [{ label: 'manifest', severity: 'fail', detail: manifestError.message, remedy: 'regenerate the project or use a matching CLI version' }]
602
706
  });
603
707
  }
604
708
  if (manifest) {
@@ -668,6 +772,10 @@ async function optionsFromParsedArgs(parsed, cwd, includeProjectName) {
668
772
  const configOptions = configPath ? await loadConfigOptions(configPath, cwd) : {};
669
773
  const flagOptions = {
670
774
  projectName: includeProjectName ? parsed.positional[0] ?? readStringFlag(parsed.flags, 'project') : readStringFlag(parsed.flags, 'project'),
775
+ projectType: readBooleanFlag(parsed.flags, 'genai') === undefined
776
+ ? undefined
777
+ : readBooleanFlag(parsed.flags, 'genai') ? 'genai' : 'standard',
778
+ apiStack: readStringFlag(parsed.flags, 'api'),
671
779
  pattern: readStringFlag(parsed.flags, 'pattern'),
672
780
  cloud: readStringFlag(parsed.flags, 'cloud'),
673
781
  region: readStringFlag(parsed.flags, 'region'),
@@ -680,7 +788,9 @@ async function optionsFromParsedArgs(parsed, cwd, includeProjectName) {
680
788
  return mergeOptions(configOptions, flagOptions);
681
789
  }
682
790
  function hasMissingCreateInputs(options) {
683
- return !options.projectName || !options.pattern || !options.cloud || options.includeFrontend === undefined || !options.specWorkflow || !options.environments;
791
+ const projectType = options.projectType ?? (options.pattern ? 'genai' : options.apiStack ? 'standard' : undefined);
792
+ const missingTypeSpecific = projectType === 'genai' ? !options.pattern : projectType === 'standard' ? !options.apiStack : true;
793
+ return !options.projectName || missingTypeSpecific || !options.cloud || options.includeFrontend === undefined || !options.specWorkflow || !options.environments;
684
794
  }
685
795
  function printHelp(stream) {
686
796
  stream.write(`Mission Control Liftoff\n\n`);
@@ -696,7 +806,8 @@ function printHelp(stream) {
696
806
  stream.write(` doctor Check local readiness\n`);
697
807
  stream.write(` dev Print Docker Compose helper commands\n`);
698
808
  stream.write(` infra Print OpenTofu helper commands\n`);
699
- stream.write(`\nApproved stack: ${approvedStack.join(', ')}\n`);
809
+ stream.write(`\nProject types: GenAI (Python/FastAPI/PydanticAI) or standard API\n`);
810
+ stream.write(`Standard API stacks: ${apiStacks.map((stack) => stack.label).join(', ')}\n`);
700
811
  }
701
812
  export async function createFixtureProject(options) {
702
813
  const tempRoot = await mkdtemp(path.join(os.tmpdir(), 'liftoff-'));