openuispec 0.1.29 → 0.1.31
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 +3 -1
- package/cli/configure-target.ts +124 -17
- package/cli/index.ts +25 -6
- package/cli/init.ts +107 -37
- package/cli/target-presets.json +40 -40
- package/docs/implementation-notes.md +4 -0
- package/drift/index.ts +8 -2
- package/examples/taskflow/AGENTS.md +4 -3
- package/examples/taskflow/CLAUDE.md +4 -3
- package/examples/todo-orbit/AGENTS.md +4 -3
- package/examples/todo-orbit/CLAUDE.md +4 -3
- package/package.json +1 -1
- package/prepare/index.ts +46 -1
- package/schema/semantic-lint.ts +31 -5
- package/schema/validate.ts +192 -5
- package/status/index.ts +13 -7
package/cli/target-presets.json
CHANGED
|
@@ -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 ?
|
|
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 ?
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
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,
|
package/schema/semantic-lint.ts
CHANGED
|
@@ -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
|
|
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
|
|