openuispec 0.1.41 → 0.1.43

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/cli/init.ts CHANGED
@@ -319,21 +319,28 @@ or any visual/structural change — you MUST use the OpenUISpec tools before wri
319
319
 
320
320
  Call these MCP tools directly. They return structured JSON with everything you need.
321
321
 
322
- 1. **Before ANY UI code generation or modification:**
323
- Call \`openuispec_prepare\` with the target platform. This returns the spec context,
324
- platform config, generation constraints, and semantic changes. Do not skip this step.
325
-
326
- 2. **After editing spec files:**
327
- Call \`openuispec_check\` to validate schema + semantic lint + prepare readiness.
328
-
329
- 3. **To understand project state:**
330
- Call \`openuispec_status\` for cross-target summary.
331
-
332
- 4. **To detect what changed:**
333
- Call \`openuispec_drift\` with \`explain: true\` to see property-level spec changes.
334
-
335
- 5. **For schema validation only:**
336
- Call \`openuispec_validate\` optionally with specific groups.
322
+ **Pre-generation:**
323
+ 1. Call \`openuispec_prepare\` with the target platform returns spec context, platform config, constraints.
324
+ 2. Call \`openuispec_read_specs\` to load spec file contents. Use these as the AUTHORITATIVE source.
325
+ 3. If spec changes are needed, update spec files FIRST, then call \`openuispec_check\`.
326
+ 4. Generate or update the platform UI code based on the spec contents.
327
+
328
+ **Post-generation (EVERY TIME after writing UI code):**
329
+ 5. Call \`openuispec_check\` to validate spec integrity.
330
+ 6. Call \`openuispec_read_specs\` for the screens/contracts you just generated code for.
331
+ 7. Audit your generated code against the spec. For each screen, verify:
332
+ - Every field/action in the spec has a corresponding UI element
333
+ - Token values (colors, spacing, radii) match exactly no approximations
334
+ - Contract \`must_handle\` states are all implemented (loading, error, empty, etc.)
335
+ - Adaptive breakpoints match the spec's \`size_classes\`
336
+ - Locale keys match \`$t:\` references
337
+ - Navigation targets match flow definitions
338
+ 8. Report any real gaps found and fix them before finishing.
339
+
340
+ **Other tools:**
341
+ - \`openuispec_status\` — cross-target summary, good starting point
342
+ - \`openuispec_drift\` with \`explain: true\` — property-level spec changes
343
+ - \`openuispec_validate\` — schema-only validation by group
337
344
 
338
345
  ### CLI fallback (when MCP is not available)
339
346
 
@@ -22,8 +22,9 @@ import { buildCheckResult } from "../check/index.js";
22
22
  import { buildStatusResult } from "../status/index.js";
23
23
  import { buildValidateResult } from "../schema/validate.js";
24
24
  import { loadTargetDrift } from "../drift/index.js";
25
- import { readFileSync as fsReadFileSync } from "node:fs";
26
- import { relative } from "node:path";
25
+ import { readFileSync as fsReadFileSync, existsSync, readdirSync } from "node:fs";
26
+ import { relative, resolve } from "node:path";
27
+ import YAML from "yaml";
27
28
 
28
29
  // ── resolve project cwd ──────────────────────────────────────────────
29
30
 
@@ -69,14 +70,26 @@ const server = new McpServer(
69
70
  Spec files (YAML) are the single source of truth for all UI across platforms.
70
71
 
71
72
  MANDATORY WORKFLOW for any UI-related request (screens, navigation, layout, tokens, flows, localization):
72
- 1. BEFORE writing or modifying any UI code, call openuispec_prepare with the target platform.
73
- This returns the spec context, platform config, generation constraints, and the list of spec files.
74
- 2. Then call openuispec_read_specs to load the spec file contents you need for the task.
75
- Pass specific paths for targeted work, or omit paths to load everything.
73
+
74
+ PRE-GENERATION:
75
+ 1. Call openuispec_prepare with the target platform.
76
+ 2. Call openuispec_read_specs to load the spec file contents you need.
76
77
  Use the returned contents as the AUTHORITATIVE source — do NOT paraphrase from memory.
77
78
  Cross-reference exact token values, contract must_handle lists, and locale keys from the content.
78
- 3. If the request requires spec changes, update the spec files FIRST, then call openuispec_check to validate.
79
- 4. Only then generate or update the platform UI code based on the spec contents.
79
+ 3. If the request requires spec changes, update the spec files FIRST, then call openuispec_check.
80
+ 4. Generate or update the platform UI code based on the spec contents.
81
+
82
+ POST-GENERATION (do this EVERY TIME after writing UI code):
83
+ 5. Call openuispec_check to validate spec integrity.
84
+ 6. Call openuispec_read_specs for the screens/contracts you just generated code for.
85
+ 7. Audit your generated code against the spec contents. For each screen, verify:
86
+ - Every field/action in the spec has a corresponding UI element
87
+ - Token values (colors, spacing, radii) match the spec exactly, not approximations
88
+ - Contract must_handle states are all implemented (loading, error, empty, etc.)
89
+ - Adaptive breakpoints match the layout size_classes in the spec
90
+ - Locale keys match $t: references in the spec
91
+ - Navigation targets match flow definitions
92
+ Report any real gaps found and fix them before finishing.
80
93
 
81
94
  Skip these tools ONLY when the request is purely non-UI (API logic, database, infrastructure, etc.)
82
95
  or explicitly platform-specific polish that doesn't affect shared UI semantics.`,
@@ -102,15 +115,153 @@ server.registerTool(
102
115
 
103
116
  // ── tool: openuispec_check ───────────────────────────────────────────
104
117
 
118
+ function buildAuditChecklist(projectDir: string, target: string): string {
119
+ const lines: string[] = [
120
+ "POST-GENERATION AUDIT — verify your code against these concrete spec requirements:",
121
+ "",
122
+ "HOW TO AUDIT: For each item below, READ the generated component/screen file,",
123
+ "find the code that implements it, and confirm the values match exactly.",
124
+ "If you cannot find the implementation, it is a REAL GAP — fix it.",
125
+ "",
126
+ ];
127
+
128
+ // Extract must_handle from contracts
129
+ const manifest = YAML.parse(fsReadFileSync(join(projectDir, "openuispec.yaml"), "utf-8"));
130
+ const contractsDir = resolve(projectDir, manifest.includes?.contracts ?? "./contracts/");
131
+
132
+ if (existsSync(contractsDir)) {
133
+ lines.push("## Contract must_handle requirements");
134
+ for (const file of readdirSync(contractsDir).filter(f => f.endsWith(".yaml")).sort()) {
135
+ try {
136
+ const content = YAML.parse(fsReadFileSync(join(contractsDir, file), "utf-8"));
137
+ const contractName = Object.keys(content)[0];
138
+ const contract = content[contractName];
139
+ if (!contract?.variants) continue;
140
+
141
+ for (const [variantName, variant] of Object.entries(contract.variants as Record<string, any>)) {
142
+ const mustHandle = variant?.generation?.must_handle;
143
+ if (mustHandle?.length) {
144
+ lines.push(`\n### ${contractName}.${variantName}`);
145
+ for (const item of mustHandle) {
146
+ lines.push(`- [ ] ${item}`);
147
+ }
148
+ }
149
+ }
150
+
151
+ // Top-level generation.must_handle
152
+ const topMustHandle = contract?.generation?.must_handle;
153
+ if (topMustHandle?.length) {
154
+ lines.push(`\n### ${contractName} (global)`);
155
+ for (const item of topMustHandle) {
156
+ lines.push(`- [ ] ${item}`);
157
+ }
158
+ }
159
+ } catch { /* skip unparseable files */ }
160
+ }
161
+ lines.push("");
162
+ }
163
+
164
+ // Extract screens and their sections
165
+ const screensDir = resolve(projectDir, manifest.includes?.screens ?? "./screens/");
166
+ if (existsSync(screensDir)) {
167
+ lines.push("## Screens — verify all sections exist in generated code");
168
+ for (const file of readdirSync(screensDir).filter(f => f.endsWith(".yaml")).sort()) {
169
+ try {
170
+ const content = YAML.parse(fsReadFileSync(join(screensDir, file), "utf-8"));
171
+ const screenName = Object.keys(content)[0];
172
+ const screen = content[screenName];
173
+ if (screen?.status === "stub") continue;
174
+
175
+ const sections: string[] = [];
176
+ const collectSections = (node: any, prefix = "") => {
177
+ if (!node || typeof node !== "object") return;
178
+ if (node.contract) sections.push(`${prefix}${node.contract}${node.variant ? `.${node.variant}` : ""}`);
179
+ if (node.sections) {
180
+ for (const [key, child] of Object.entries(node.sections)) {
181
+ collectSections(child, `${prefix}${key}/`);
182
+ }
183
+ }
184
+ if (node.children && Array.isArray(node.children)) {
185
+ for (const child of node.children) {
186
+ collectSections(child, prefix);
187
+ }
188
+ }
189
+ };
190
+ collectSections(screen?.layout);
191
+
192
+ if (sections.length > 0) {
193
+ lines.push(`\n### ${screenName} (${file})`);
194
+ for (const section of sections) {
195
+ lines.push(`- [ ] ${section}`);
196
+ }
197
+ }
198
+
199
+ // Adaptive layout
200
+ if (screen?.layout?.adaptive || screen?.adaptive) {
201
+ lines.push(`- [ ] Adaptive breakpoints implemented`);
202
+ }
203
+ } catch { /* skip */ }
204
+ }
205
+ lines.push("");
206
+ }
207
+
208
+ // Locale keys count
209
+ const localesDir = resolve(projectDir, manifest.includes?.locales ?? "./locales/");
210
+ if (existsSync(localesDir)) {
211
+ const localeFiles = readdirSync(localesDir).filter(f => f.endsWith(".json"));
212
+ if (localeFiles.length > 0) {
213
+ lines.push("## Locales — verify all locale files are wired");
214
+ for (const file of localeFiles) {
215
+ try {
216
+ const keys = Object.keys(JSON.parse(fsReadFileSync(join(localesDir, file), "utf-8")));
217
+ lines.push(`- [ ] ${file}: ${keys.length} keys loaded at runtime`);
218
+ } catch { /* skip */ }
219
+ }
220
+ lines.push("");
221
+ }
222
+ }
223
+
224
+ // Platform-specific checks
225
+ const platformDir = resolve(projectDir, manifest.includes?.platform ?? "./platform/");
226
+ const platformPath = join(platformDir, `${target}.yaml`);
227
+ if (existsSync(platformPath)) {
228
+ try {
229
+ const platformDoc = YAML.parse(fsReadFileSync(platformPath, "utf-8"));
230
+ const platformDef = platformDoc?.[target];
231
+ if (platformDef?.generation) {
232
+ lines.push("## Platform generation requirements");
233
+ const gen = platformDef.generation;
234
+ if (gen.architecture) lines.push(`- [ ] Architecture: ${gen.architecture}`);
235
+ if (gen.naming) lines.push(`- [ ] Naming convention: ${gen.naming}`);
236
+ if (gen.css) lines.push(`- [ ] CSS framework: ${gen.css}`);
237
+ }
238
+ } catch { /* skip */ }
239
+ }
240
+
241
+ lines.push("FOR EACH UNCHECKED ITEM: Read the generated file, search for the implementation,");
242
+ lines.push("and either confirm it matches or fix it. Do not mark items as 'intentionally skipped'");
243
+ lines.push("unless the user explicitly requested to skip them.");
244
+
245
+ return lines.join("\n");
246
+ }
247
+
105
248
  server.registerTool(
106
249
  "openuispec_check",
107
250
  {
108
- description: "Run composite validation: schema validation, semantic linting, and prepare readiness check. Call after spec edits to verify correctness.",
251
+ description: "Run composite validation + post-generation audit. Returns schema validation results AND a concrete audit checklist derived from your spec files — listing every contract must_handle item, every screen section, and every locale file that must exist in your generated code. Verify each item.",
109
252
  inputSchema: { target: targetSchema },
110
253
  },
111
254
  async ({ target }) => {
112
255
  try {
113
- return toolResult(buildCheckResult(target, projectCwd));
256
+ const result = buildCheckResult(target, projectCwd);
257
+ const projectDir = findProjectDir(projectCwd);
258
+ const audit = buildAuditChecklist(projectDir, target);
259
+ return {
260
+ content: [
261
+ { type: "text" as const, text: JSON.stringify(result, null, 2) },
262
+ { type: "text" as const, text: audit },
263
+ ],
264
+ };
114
265
  } catch (err) {
115
266
  return toolError(err);
116
267
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openuispec",
3
- "version": "0.1.41",
3
+ "version": "0.1.43",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "A semantic UI specification format for AI-native, platform-native app development",