openuispec 0.1.29 → 0.1.30

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.
@@ -35,10 +35,10 @@
35
35
  "org.jetbrains.kotlin.plugin.compose"
36
36
  ],
37
37
  "libraries": [
38
- "com.arkivanov.decompose:decompose:{latest}",
39
- "com.arkivanov.decompose:extensions-compose-android:{latest}",
40
- "com.arkivanov.essenty:lifecycle-android:{latest}",
41
- "com.arkivanov.essenty:instance-keeper-android:{latest}"
38
+ "com.arkivanov.decompose:decompose:{latest stable}",
39
+ "com.arkivanov.decompose:extensions-compose-android:{latest stable}",
40
+ "com.arkivanov.essenty:lifecycle-android:{latest stable}",
41
+ "com.arkivanov.essenty:instance-keeper-android:{latest stable}"
42
42
  ],
43
43
  "docs": [
44
44
  "https://github.com/arkivanov/Decompose",
@@ -58,8 +58,8 @@
58
58
  "org.jetbrains.kotlin.plugin.compose"
59
59
  ],
60
60
  "libraries": [
61
- "androidx.navigation:navigation-compose:{latest}",
62
- "androidx.lifecycle:lifecycle-runtime-compose:{latest}"
61
+ "androidx.navigation:navigation-compose:{latest stable}",
62
+ "androidx.lifecycle:lifecycle-runtime-compose:{latest stable}"
63
63
  ],
64
64
  "docs": [
65
65
  "https://developer.android.com/guide/navigation",
@@ -85,9 +85,9 @@
85
85
  "refs": {
86
86
  "plugins": [],
87
87
  "libraries": [
88
- "com.arkivanov.mvikotlin:mvikotlin:{latest}",
89
- "com.arkivanov.mvikotlin:mvikotlin-main:{latest}",
90
- "com.arkivanov.mvikotlin:mvikotlin-logging:{latest}"
88
+ "com.arkivanov.mvikotlin:mvikotlin:{latest stable}",
89
+ "com.arkivanov.mvikotlin:mvikotlin-main:{latest stable}",
90
+ "com.arkivanov.mvikotlin:mvikotlin-logging:{latest stable}"
91
91
  ],
92
92
  "docs": [
93
93
  "https://github.com/arkivanov/MVIKotlin"
@@ -105,9 +105,9 @@
105
105
  "refs": {
106
106
  "plugins": [],
107
107
  "libraries": [
108
- "androidx.lifecycle:lifecycle-viewmodel-compose:{latest}",
109
- "androidx.lifecycle:lifecycle-runtime-compose:{latest}",
110
- "org.jetbrains.kotlinx:kotlinx-coroutines-core:{latest}"
108
+ "androidx.lifecycle:lifecycle-viewmodel-compose:{latest stable}",
109
+ "androidx.lifecycle:lifecycle-runtime-compose:{latest stable}",
110
+ "org.jetbrains.kotlinx:kotlinx-coroutines-core:{latest stable}"
111
111
  ],
112
112
  "docs": [
113
113
  "https://developer.android.com/jetpack/androidx/releases/lifecycle",
@@ -132,8 +132,8 @@
132
132
  "refs": {
133
133
  "plugins": [],
134
134
  "libraries": [
135
- "androidx.datastore:datastore-preferences:{latest}",
136
- "androidx.datastore:datastore-core:{latest}"
135
+ "androidx.datastore:datastore-preferences:{latest stable}",
136
+ "androidx.datastore:datastore-core:{latest stable}"
137
137
  ],
138
138
  "docs": [
139
139
  "https://developer.android.com/topic/libraries/architecture/datastore"
@@ -169,8 +169,8 @@
169
169
  "app.cash.sqldelight"
170
170
  ],
171
171
  "libraries": [
172
- "app.cash.sqldelight:android-driver:{latest}",
173
- "app.cash.sqldelight:coroutines-extensions:{latest}"
172
+ "app.cash.sqldelight:android-driver:{latest stable}",
173
+ "app.cash.sqldelight:coroutines-extensions:{latest stable}"
174
174
  ],
175
175
  "docs": [
176
176
  "https://github.com/sqldelight/sqldelight"
@@ -189,8 +189,8 @@
189
189
  "androidx.room"
190
190
  ],
191
191
  "libraries": [
192
- "androidx.room:room-runtime:{latest}",
193
- "androidx.room:room-ktx:{latest}"
192
+ "androidx.room:room-runtime:{latest stable}",
193
+ "androidx.room:room-ktx:{latest stable}"
194
194
  ],
195
195
  "docs": [
196
196
  "https://developer.android.com/training/data-storage/room"
@@ -224,8 +224,8 @@
224
224
  "refs": {
225
225
  "plugins": [],
226
226
  "libraries": [
227
- "dev.zacsweers.metro:metro:{latest}",
228
- "dev.zacsweers.metro:metro-compose:{latest}"
227
+ "dev.zacsweers.metro:metro:{latest stable}",
228
+ "dev.zacsweers.metro:metro-compose:{latest stable}"
229
229
  ],
230
230
  "docs": [
231
231
  "https://github.com/ZacSweers/metro"
@@ -242,8 +242,8 @@
242
242
  "refs": {
243
243
  "plugins": [],
244
244
  "libraries": [
245
- "io.insert-koin:koin-android:{latest}",
246
- "io.insert-koin:koin-compose:{latest}"
245
+ "io.insert-koin:koin-android:{latest stable}",
246
+ "io.insert-koin:koin-compose:{latest stable}"
247
247
  ],
248
248
  "docs": [
249
249
  "https://insert-koin.io/docs/quickstart/android/",
@@ -263,8 +263,8 @@
263
263
  "com.google.dagger.hilt.android"
264
264
  ],
265
265
  "libraries": [
266
- "com.google.dagger:hilt-android:{latest}",
267
- "androidx.hilt:hilt-navigation-compose:{latest}"
266
+ "com.google.dagger:hilt-android:{latest stable}",
267
+ "androidx.hilt:hilt-navigation-compose:{latest stable}"
268
268
  ],
269
269
  "docs": [
270
270
  "https://developer.android.com/training/dependency-injection/hilt-android",
@@ -330,7 +330,7 @@
330
330
  "dependencies": [],
331
331
  "refs": {
332
332
  "packages": [
333
- "next@{latest}"
333
+ "next@{latest stable}"
334
334
  ],
335
335
  "docs": [
336
336
  "https://nextjs.org/docs"
@@ -343,7 +343,7 @@
343
343
  "dependencies": [],
344
344
  "refs": {
345
345
  "packages": [
346
- "hono@{latest}"
346
+ "hono@{latest stable}"
347
347
  ],
348
348
  "docs": [
349
349
  "https://hono.dev/"
@@ -365,7 +365,7 @@
365
365
  ],
366
366
  "refs": {
367
367
  "packages": [
368
- "tailwindcss@{latest}"
368
+ "tailwindcss@{latest stable}"
369
369
  ],
370
370
  "docs": [
371
371
  "https://tailwindcss.com/docs"
@@ -406,7 +406,7 @@
406
406
  ],
407
407
  "refs": {
408
408
  "packages": [
409
- "react-router@{latest}"
409
+ "react-router@{latest stable}"
410
410
  ],
411
411
  "docs": [
412
412
  "https://reactrouter.com/"
@@ -422,7 +422,7 @@
422
422
  ],
423
423
  "refs": {
424
424
  "packages": [
425
- "@tanstack/react-router@{latest}"
425
+ "@tanstack/react-router@{latest stable}"
426
426
  ],
427
427
  "docs": [
428
428
  "https://tanstack.com/router/docs"
@@ -438,7 +438,7 @@
438
438
  ],
439
439
  "refs": {
440
440
  "packages": [
441
- "vue-router@{latest}"
441
+ "vue-router@{latest stable}"
442
442
  ],
443
443
  "docs": [
444
444
  "https://router.vuejs.org/"
@@ -473,7 +473,7 @@
473
473
  ],
474
474
  "refs": {
475
475
  "packages": [
476
- "zustand@{latest}"
476
+ "zustand@{latest stable}"
477
477
  ],
478
478
  "docs": [
479
479
  "https://zustand.docs.pmnd.rs/"
@@ -490,8 +490,8 @@
490
490
  ],
491
491
  "refs": {
492
492
  "packages": [
493
- "@reduxjs/toolkit@{latest}",
494
- "react-redux@{latest}"
493
+ "@reduxjs/toolkit@{latest stable}",
494
+ "react-redux@{latest stable}"
495
495
  ],
496
496
  "docs": [
497
497
  "https://redux-toolkit.js.org/",
@@ -508,7 +508,7 @@
508
508
  ],
509
509
  "refs": {
510
510
  "packages": [
511
- "@tanstack/react-query@{latest}"
511
+ "@tanstack/react-query@{latest stable}"
512
512
  ],
513
513
  "docs": [
514
514
  "https://tanstack.com/query/latest"
@@ -524,7 +524,7 @@
524
524
  ],
525
525
  "refs": {
526
526
  "packages": [
527
- "pinia@{latest}"
527
+ "pinia@{latest stable}"
528
528
  ],
529
529
  "docs": [
530
530
  "https://pinia.vuejs.org/"
@@ -567,7 +567,7 @@
567
567
  ],
568
568
  "refs": {
569
569
  "packages": [
570
- "@supabase/supabase-js@{latest}"
570
+ "@supabase/supabase-js@{latest stable}"
571
571
  ],
572
572
  "docs": [
573
573
  "https://supabase.com/docs"
@@ -582,7 +582,7 @@
582
582
  ],
583
583
  "refs": {
584
584
  "packages": [
585
- "firebase@{latest}"
585
+ "firebase@{latest stable}"
586
586
  ],
587
587
  "docs": [
588
588
  "https://firebase.google.com/docs/web/setup"
@@ -640,7 +640,7 @@
640
640
  },
641
641
  "refs": {
642
642
  "packages": [
643
- "pointfreeco/swift-composable-architecture@{latest}"
643
+ "pointfreeco/swift-composable-architecture@{latest stable}"
644
644
  ],
645
645
  "docs": [
646
646
  "https://github.com/pointfreeco/swift-composable-architecture"
@@ -677,7 +677,7 @@
677
677
  ],
678
678
  "refs": {
679
679
  "packages": [
680
- "stephencelis/SQLite.swift@{latest}"
680
+ "stephencelis/SQLite.swift@{latest stable}"
681
681
  ],
682
682
  "docs": [
683
683
  "https://www.sqlite.org/docs.html"
@@ -717,7 +717,7 @@
717
717
  ],
718
718
  "refs": {
719
719
  "packages": [
720
- "hmlongco/Factory@{latest}"
720
+ "hmlongco/Factory@{latest stable}"
721
721
  ],
722
722
  "docs": [
723
723
  "https://github.com/hmlongco/Factory"
@@ -41,6 +41,7 @@
41
41
  - it should offer preset defaults for known stacks
42
42
  - it must still allow custom values when the project uses frameworks or libraries outside the catalog
43
43
  - `openuispec init --no-configure-targets` should remain available for users who want to defer target stack decisions until later
44
+ - `--defaults` is an unattended fallback only; it must not count as user confirmation for implementation
44
45
  - It should support two modes:
45
46
  1. bootstrap mode when no target snapshot exists yet
46
47
  2. update mode when a target snapshot exists
@@ -69,6 +70,9 @@
69
70
  - AI should resolve exact versions and wiring from current platform docs instead of assuming the preset list is exhaustive
70
71
  - Bootstrap mode should surface soft warnings when configured framework/stack values are custom and therefore not covered by preset dependency refs.
71
72
  - these warnings should explain that dependency guidance is incomplete, not silently omit the missing refs
73
+ - Bootstrap mode should also surface pending stack confirmation when values were auto-applied from defaults.
74
+ - `generation_ready` must remain false in that state
75
+ - AI consumers should ask the user to confirm or change the stack before starting implementation
72
76
  - Bootstrap mode should also carry explicit generation constraints for the target:
73
77
  - localization rules
74
78
  - use target-native runtime localization resources
package/drift/index.ts CHANGED
@@ -22,6 +22,8 @@ import { execFileSync } from "node:child_process";
22
22
  import YAML from "yaml";
23
23
 
24
24
  const STATE_FILE = ".openuispec-state.json";
25
+ export const SUPPORTED_TARGETS = ["ios", "android", "web"] as const;
26
+ export type SupportedTarget = typeof SUPPORTED_TARGETS[number];
25
27
 
26
28
  // ── types ─────────────────────────────────────────────────────────────
27
29
 
@@ -84,6 +86,10 @@ export function listFiles(dir: string, ext: string): string[] {
84
86
  }
85
87
  }
86
88
 
89
+ export function isSupportedTarget(value: string): value is SupportedTarget {
90
+ return (SUPPORTED_TARGETS as readonly string[]).includes(value);
91
+ }
92
+
87
93
  function hashFile(filePath: string): string {
88
94
  const content = readFileSync(filePath);
89
95
  const hash = createHash("sha256").update(content).digest("hex");
@@ -680,7 +686,7 @@ function check(
680
686
 
681
687
  const d = result.drift;
682
688
  const hasDrift = d.changed.length > 0 || d.added.length > 0 || d.removed.length > 0;
683
- process.exit(hasDrift ? 1 : 0);
689
+ process.exit(hasDrift ? 2 : 0);
684
690
  }
685
691
 
686
692
  function checkAll(
@@ -724,7 +730,7 @@ function checkAll(
724
730
  }
725
731
  }
726
732
 
727
- process.exit(anyDrift ? 1 : 0);
733
+ process.exit(anyDrift ? 2 : 0);
728
734
  }
729
735
 
730
736
  // ── output ────────────────────────────────────────────────────────────
@@ -1,5 +1,5 @@
1
1
  <!-- openuispec-rules-start -->
2
- <!-- openuispec-rules-version: 0.1.28 -->
2
+ <!-- openuispec-rules-version: 0.1.29 -->
3
3
  # OpenUISpec — AI Assistant Rules
4
4
  # ================================
5
5
  # This project uses OpenUISpec to define UI as a semantic spec.
@@ -77,6 +77,7 @@ Spec-first workflow:
77
77
  5. Run `openuispec validate semantic`.
78
78
  6. Run `openuispec drift --target <target> --explain` to inspect semantic changes since that target's baseline.
79
79
  7. Run `openuispec prepare --target <target>` to build the target work bundle for that target. In `bootstrap` mode it provides first-generation constraints; in `update` mode it provides drift-based update scope.
80
+ If the target stack was filled from defaults, stop and ask the user to confirm or change it before implementation.
80
81
  8. Verify the affected UI targets build/run if possible.
81
82
  9. Only then run `openuispec drift --snapshot --target <target>` for affected targets, after that target output directory exists.
82
83
  10. Run `openuispec drift --target <target> --explain` again to confirm no spec changes remain for that target.
@@ -97,16 +98,16 @@ Platform-first workflow:
97
98
  - Do not treat `openuispec drift` as proof that generated UI matches the spec.
98
99
  - Do not skip `--explain` / `prepare` when another platform needs to catch up with shared spec changes.
99
100
  - Do not modify generated UI without checking whether the spec must change first.
101
+ - Do not use `configure-target --defaults` as silent approval for implementation. Ask the user to confirm the stack first.
100
102
 
101
103
  ## CLI commands
102
104
  - `openuispec init` — scaffold a new spec project
103
105
  - `openuispec validate [group...]` — validate spec files against schemas
104
106
  - `openuispec validate semantic` — run semantic cross-reference linting
105
- - `openuispec configure-target <t> [--defaults]` — configure target stack defaults
106
107
  - `openuispec drift --target <t>` — check for spec drift
107
108
  - `openuispec drift --target <t> --explain` — explain semantic spec drift since the target baseline
108
109
  - `openuispec drift --snapshot --target <t>` — snapshot current state after the target output exists
109
- - `openuispec prepare --target <t>` — build the target work bundle
110
+ - `openuispec prepare --target <t>` — build the target work bundle and check whether stack confirmation is still pending
110
111
  - `openuispec status` — show cross-target baseline/drift status
111
112
  - `openuispec update-rules` — update AI rules to match installed package version
112
113
  - `openuispec drift --all` — include stubs in drift check
@@ -1,5 +1,5 @@
1
1
  <!-- openuispec-rules-start -->
2
- <!-- openuispec-rules-version: 0.1.28 -->
2
+ <!-- openuispec-rules-version: 0.1.29 -->
3
3
  # OpenUISpec — AI Assistant Rules
4
4
  # ================================
5
5
  # This project uses OpenUISpec to define UI as a semantic spec.
@@ -77,6 +77,7 @@ Spec-first workflow:
77
77
  5. Run `openuispec validate semantic`.
78
78
  6. Run `openuispec drift --target <target> --explain` to inspect semantic changes since that target's baseline.
79
79
  7. Run `openuispec prepare --target <target>` to build the target work bundle for that target. In `bootstrap` mode it provides first-generation constraints; in `update` mode it provides drift-based update scope.
80
+ If the target stack was filled from defaults, stop and ask the user to confirm or change it before implementation.
80
81
  8. Verify the affected UI targets build/run if possible.
81
82
  9. Only then run `openuispec drift --snapshot --target <target>` for affected targets, after that target output directory exists.
82
83
  10. Run `openuispec drift --target <target> --explain` again to confirm no spec changes remain for that target.
@@ -97,16 +98,16 @@ Platform-first workflow:
97
98
  - Do not treat `openuispec drift` as proof that generated UI matches the spec.
98
99
  - Do not skip `--explain` / `prepare` when another platform needs to catch up with shared spec changes.
99
100
  - Do not modify generated UI without checking whether the spec must change first.
101
+ - Do not use `configure-target --defaults` as silent approval for implementation. Ask the user to confirm the stack first.
100
102
 
101
103
  ## CLI commands
102
104
  - `openuispec init` — scaffold a new spec project
103
105
  - `openuispec validate [group...]` — validate spec files against schemas
104
106
  - `openuispec validate semantic` — run semantic cross-reference linting
105
- - `openuispec configure-target <t> [--defaults]` — configure target stack defaults
106
107
  - `openuispec drift --target <t>` — check for spec drift
107
108
  - `openuispec drift --target <t> --explain` — explain semantic spec drift since the target baseline
108
109
  - `openuispec drift --snapshot --target <t>` — snapshot current state after the target output exists
109
- - `openuispec prepare --target <t>` — build the target work bundle
110
+ - `openuispec prepare --target <t>` — build the target work bundle and check whether stack confirmation is still pending
110
111
  - `openuispec status` — show cross-target baseline/drift status
111
112
  - `openuispec update-rules` — update AI rules to match installed package version
112
113
  - `openuispec drift --all` — include stubs in drift check
@@ -1,5 +1,5 @@
1
1
  <!-- openuispec-rules-start -->
2
- <!-- openuispec-rules-version: 0.1.28 -->
2
+ <!-- openuispec-rules-version: 0.1.29 -->
3
3
  # OpenUISpec — AI Assistant Rules
4
4
  # ================================
5
5
  # This project uses OpenUISpec to define UI as a semantic spec.
@@ -77,6 +77,7 @@ Spec-first workflow:
77
77
  5. Run `openuispec validate semantic`.
78
78
  6. Run `openuispec drift --target <target> --explain` to inspect semantic changes since that target's baseline.
79
79
  7. Run `openuispec prepare --target <target>` to build the target work bundle for that target. In `bootstrap` mode it provides first-generation constraints; in `update` mode it provides drift-based update scope.
80
+ If the target stack was filled from defaults, stop and ask the user to confirm or change it before implementation.
80
81
  8. Verify the affected UI targets build/run if possible.
81
82
  9. Only then run `openuispec drift --snapshot --target <target>` for affected targets, after that target output directory exists.
82
83
  10. Run `openuispec drift --target <target> --explain` again to confirm no spec changes remain for that target.
@@ -97,16 +98,16 @@ Platform-first workflow:
97
98
  - Do not treat `openuispec drift` as proof that generated UI matches the spec.
98
99
  - Do not skip `--explain` / `prepare` when another platform needs to catch up with shared spec changes.
99
100
  - Do not modify generated UI without checking whether the spec must change first.
101
+ - Do not use `configure-target --defaults` as silent approval for implementation. Ask the user to confirm the stack first.
100
102
 
101
103
  ## CLI commands
102
104
  - `openuispec init` — scaffold a new spec project
103
105
  - `openuispec validate [group...]` — validate spec files against schemas
104
106
  - `openuispec validate semantic` — run semantic cross-reference linting
105
- - `openuispec configure-target <t> [--defaults]` — configure target stack defaults
106
107
  - `openuispec drift --target <t>` — check for spec drift
107
108
  - `openuispec drift --target <t> --explain` — explain semantic spec drift since the target baseline
108
109
  - `openuispec drift --snapshot --target <t>` — snapshot current state after the target output exists
109
- - `openuispec prepare --target <t>` — build the target work bundle
110
+ - `openuispec prepare --target <t>` — build the target work bundle and check whether stack confirmation is still pending
110
111
  - `openuispec status` — show cross-target baseline/drift status
111
112
  - `openuispec update-rules` — update AI rules to match installed package version
112
113
  - `openuispec drift --all` — include stubs in drift check
@@ -1,5 +1,5 @@
1
1
  <!-- openuispec-rules-start -->
2
- <!-- openuispec-rules-version: 0.1.28 -->
2
+ <!-- openuispec-rules-version: 0.1.29 -->
3
3
  # OpenUISpec — AI Assistant Rules
4
4
  # ================================
5
5
  # This project uses OpenUISpec to define UI as a semantic spec.
@@ -77,6 +77,7 @@ Spec-first workflow:
77
77
  5. Run `openuispec validate semantic`.
78
78
  6. Run `openuispec drift --target <target> --explain` to inspect semantic changes since that target's baseline.
79
79
  7. Run `openuispec prepare --target <target>` to build the target work bundle for that target. In `bootstrap` mode it provides first-generation constraints; in `update` mode it provides drift-based update scope.
80
+ If the target stack was filled from defaults, stop and ask the user to confirm or change it before implementation.
80
81
  8. Verify the affected UI targets build/run if possible.
81
82
  9. Only then run `openuispec drift --snapshot --target <target>` for affected targets, after that target output directory exists.
82
83
  10. Run `openuispec drift --target <target> --explain` again to confirm no spec changes remain for that target.
@@ -97,16 +98,16 @@ Platform-first workflow:
97
98
  - Do not treat `openuispec drift` as proof that generated UI matches the spec.
98
99
  - Do not skip `--explain` / `prepare` when another platform needs to catch up with shared spec changes.
99
100
  - Do not modify generated UI without checking whether the spec must change first.
101
+ - Do not use `configure-target --defaults` as silent approval for implementation. Ask the user to confirm the stack first.
100
102
 
101
103
  ## CLI commands
102
104
  - `openuispec init` — scaffold a new spec project
103
105
  - `openuispec validate [group...]` — validate spec files against schemas
104
106
  - `openuispec validate semantic` — run semantic cross-reference linting
105
- - `openuispec configure-target <t> [--defaults]` — configure target stack defaults
106
107
  - `openuispec drift --target <t>` — check for spec drift
107
108
  - `openuispec drift --target <t> --explain` — explain semantic spec drift since the target baseline
108
109
  - `openuispec drift --snapshot --target <t>` — snapshot current state after the target output exists
109
- - `openuispec prepare --target <t>` — build the target work bundle
110
+ - `openuispec prepare --target <t>` — build the target work bundle and check whether stack confirmation is still pending
110
111
  - `openuispec status` — show cross-target baseline/drift status
111
112
  - `openuispec update-rules` — update AI rules to match installed package version
112
113
  - `openuispec drift --all` — include stubs in drift check
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openuispec",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A semantic UI specification format for AI-native, platform-native app development",
package/prepare/index.ts CHANGED
@@ -11,10 +11,12 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
11
11
  import { basename, dirname, extname, join, relative, resolve } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import YAML from "yaml";
14
+ import { listTargetWizardOptions, type TargetWizardOptionsResponse } from "../cli/configure-target.js";
14
15
  import {
15
16
  discoverSpecFiles,
16
17
  findProjectDir,
17
18
  hasStatusSemantics,
19
+ isSupportedTarget,
18
20
  loadTargetDrift,
19
21
  readManifest,
20
22
  readProjectName,
@@ -77,6 +79,10 @@ interface PreparePlatformConfig {
77
79
  target_sdk: number | null;
78
80
  generation: Record<string, any>;
79
81
  stack: Record<string, string>;
82
+ stack_confirmation: {
83
+ status: string | null;
84
+ requires_user_confirmation: boolean;
85
+ };
80
86
  dependency_guidance: {
81
87
  anchor_refs_only: boolean;
82
88
  notes: string[];
@@ -95,7 +101,9 @@ interface PrepareBootstrapBundle {
95
101
  output_exists: boolean;
96
102
  generation_ready: boolean;
97
103
  missing_platform_decisions: string[];
104
+ pending_user_confirmation: boolean;
98
105
  generation_warnings: string[];
106
+ target_stack_options: TargetWizardOptionsResponse | null;
99
107
  includes: Record<string, string>;
100
108
  output_format: Record<string, any>;
101
109
  i18n: {
@@ -261,6 +269,14 @@ function buildPlatformConfig(target: string, platformDef: Record<string, any>):
261
269
  target_sdk: typeof platformDef.target_sdk === "number" ? platformDef.target_sdk : null,
262
270
  generation,
263
271
  stack,
272
+ stack_confirmation: {
273
+ status:
274
+ typeof generation.stack_confirmation?.status === "string"
275
+ ? generation.stack_confirmation.status
276
+ : null,
277
+ requires_user_confirmation:
278
+ generation.stack_confirmation?.status === "pending_user_confirmation",
279
+ },
264
280
  dependency_guidance: {
265
281
  anchor_refs_only: true,
266
282
  notes: [
@@ -803,6 +819,12 @@ function referenceExamples(): string[] {
803
819
  function generationWarnings(target: string, platformConfig: PreparePlatformConfig): string[] {
804
820
  const warnings: string[] = [];
805
821
 
822
+ if (platformConfig.stack_confirmation.requires_user_confirmation) {
823
+ warnings.push(
824
+ `The configured ${target} stack was applied from defaults and still requires explicit user confirmation before implementation.`
825
+ );
826
+ }
827
+
806
828
  for (const [key, value] of Object.entries(platformConfig.stack)) {
807
829
  if (!PRESENTATION_ONLY_KEYS.has(key) && !platformConfig.selected_option_refs[key]) {
808
830
  warnings.push(
@@ -893,6 +915,9 @@ function printReport(result: PrepareResult): void {
893
915
  console.log(` - ${key}: ${refs.value}`);
894
916
  }
895
917
  }
918
+ if (result.platform_config.stack_confirmation.status) {
919
+ console.log(` stack confirmation: ${result.platform_config.stack_confirmation.status}`);
920
+ }
896
921
  if (result.platform_config.dependency_guidance.notes.length > 0) {
897
922
  console.log(" dependency guidance:");
898
923
  for (const note of result.platform_config.dependency_guidance.notes) {
@@ -911,6 +936,9 @@ function printReport(result: PrepareResult): void {
911
936
  console.log("\nBootstrap Bundle");
912
937
  console.log(` output exists: ${result.bootstrap.output_exists ? "yes" : "no"}`);
913
938
  console.log(` generation ready: ${result.bootstrap.generation_ready ? "yes" : "no"}`);
939
+ console.log(
940
+ ` pending user confirmation: ${result.bootstrap.pending_user_confirmation ? "yes" : "no"}`
941
+ );
914
942
 
915
943
  if (result.bootstrap.missing_platform_decisions.length > 0) {
916
944
  console.log(" missing platform decisions:");
@@ -919,6 +947,12 @@ function printReport(result: PrepareResult): void {
919
947
  }
920
948
  }
921
949
 
950
+ if (result.bootstrap.target_stack_options) {
951
+ console.log(" target stack options:");
952
+ console.log(` - interactive: ${result.bootstrap.target_stack_options.interactive_command}`);
953
+ console.log(` - non-interactive schema: openuispec configure-target ${result.target} --list-options`);
954
+ }
955
+
922
956
  if (result.bootstrap.generation_warnings.length > 0) {
923
957
  console.log(" generation warnings:");
924
958
  for (const warning of result.bootstrap.generation_warnings) {
@@ -1018,6 +1052,7 @@ function buildBootstrapPrepareResult(cwd: string, target: string): PrepareResult
1018
1052
  const backendRoot = resolveBackendRoot(projectDir, manifest);
1019
1053
  const backendContextRequired = hasApiEndpoints(manifest);
1020
1054
  const backendContextReady = !backendContextRequired || (backendRoot !== null && existsSync(backendRoot));
1055
+ const pendingUserConfirmation = platformConfig.stack_confirmation.requires_user_confirmation;
1021
1056
 
1022
1057
  const nextSteps = [
1023
1058
  ...(!backendContextReady
@@ -1025,6 +1060,11 @@ function buildBootstrapPrepareResult(cwd: string, target: string): PrepareResult
1025
1060
  "Set `generation.code_roots.backend` in openuispec.yaml to the backend folder used to implement the declared API endpoints.",
1026
1061
  ]
1027
1062
  : []),
1063
+ ...(pendingUserConfirmation
1064
+ ? [
1065
+ `Run \`openuispec configure-target ${target}\` without \`--defaults\` and confirm the stack choices before implementation.`,
1066
+ ]
1067
+ : []),
1028
1068
  ...(missingDecisions.length > 0
1029
1069
  ? [
1030
1070
  `Run \`openuispec configure-target ${target}\` to choose the missing ${target} stack defaults before generation.`,
@@ -1069,9 +1109,14 @@ function buildBootstrapPrepareResult(cwd: string, target: string): PrepareResult
1069
1109
  items: [],
1070
1110
  bootstrap: {
1071
1111
  output_exists: existsSync(outputDir),
1072
- generation_ready: missingDecisions.length === 0 && backendContextReady,
1112
+ generation_ready: missingDecisions.length === 0 && backendContextReady && !pendingUserConfirmation,
1073
1113
  missing_platform_decisions: missingDecisions,
1114
+ pending_user_confirmation: pendingUserConfirmation,
1074
1115
  includes: manifest.includes ?? {},
1116
+ target_stack_options:
1117
+ (missingDecisions.length > 0 || pendingUserConfirmation) && isSupportedTarget(target)
1118
+ ? listTargetWizardOptions(target)
1119
+ : null,
1075
1120
  output_format: outputFormat,
1076
1121
  i18n: {
1077
1122
  default_locale: manifest.i18n?.default_locale ?? null,
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { basename, join, resolve } from "node:path";
3
3
  import YAML from "yaml";
4
- import { listFiles } from "../drift/index.js";
4
+ import { listFiles, readManifest } from "../drift/index.js";
5
5
 
6
6
  type UnknownRecord = Record<string, unknown>;
7
7
 
@@ -204,9 +204,7 @@ function collectIconRefs(filePath: string): { refs: Set<string>; suffixes: strin
204
204
  return { refs, suffixes };
205
205
  }
206
206
 
207
- function buildContext(projectDir: string, includes: Includes): SemanticContext {
208
- const manifestPath = join(projectDir, "openuispec.yaml");
209
- const manifest = loadYaml(manifestPath) as UnknownRecord;
207
+ function buildContext(projectDir: string, includes: Includes, manifest: UnknownRecord): SemanticContext {
210
208
 
211
209
  const localeDir = resolve(projectDir, includes.locales);
212
210
  const localeFiles = new Map<string, Set<string>>();
@@ -589,8 +587,36 @@ function printSemanticErrors(label: string, errors: UsageLint[]): number {
589
587
  return errors.length;
590
588
  }
591
589
 
590
+ export function collectSemanticLint(projectDir: string, includes: Includes): UsageLint[] {
591
+ const manifest = readManifest(projectDir) as UnknownRecord;
592
+ const context = buildContext(projectDir, includes, manifest);
593
+ const contractsDir = resolve(projectDir, includes.contracts);
594
+
595
+ const allErrors: UsageLint[] = [
596
+ ...lintLocaleCoverage(context),
597
+ ...lintManifestGenerationContext(projectDir, context.manifest),
598
+ ];
599
+
600
+ const files = [
601
+ join(projectDir, "openuispec.yaml"),
602
+ ...listFiles(resolve(projectDir, includes.screens), ".yaml"),
603
+ ...listFiles(resolve(projectDir, includes.flows), ".yaml"),
604
+ ...listFiles(resolve(projectDir, includes.platform), ".yaml"),
605
+ ...listFiles(resolve(projectDir, includes.contracts), ".yaml"),
606
+ ];
607
+
608
+ for (const filePath of files) {
609
+ allErrors.push(
610
+ ...lintFile(filePath, context, { validateTokens: !filePath.startsWith(contractsDir) })
611
+ );
612
+ }
613
+
614
+ return allErrors;
615
+ }
616
+
592
617
  export function runSemanticLint(projectDir: string, includes: Includes): number {
593
- const context = buildContext(projectDir, includes);
618
+ const manifest = readManifest(projectDir) as UnknownRecord;
619
+ const context = buildContext(projectDir, includes, manifest);
594
620
  let total = 0;
595
621
  const contractsDir = resolve(projectDir, includes.contracts);
596
622