openuispec 0.1.40 → 0.1.42

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
 
@@ -16,12 +16,14 @@ import { z } from "zod";
16
16
  import { readFileSync } from "node:fs";
17
17
  import { join, dirname } from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
- import { SUPPORTED_TARGETS } from "../drift/index.js";
19
+ import { SUPPORTED_TARGETS, findProjectDir, discoverSpecFiles } from "../drift/index.js";
20
20
  import { buildPrepareResult } from "../prepare/index.js";
21
21
  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
27
 
26
28
  // ── resolve project cwd ──────────────────────────────────────────────
27
29
 
@@ -67,13 +69,26 @@ const server = new McpServer(
67
69
  Spec files (YAML) are the single source of truth for all UI across platforms.
68
70
 
69
71
  MANDATORY WORKFLOW for any UI-related request (screens, navigation, layout, tokens, flows, localization):
70
- 1. BEFORE writing or modifying any UI code, call openuispec_prepare with the target platform.
71
- This returns the spec context, platform config, generation constraints, AND the full contents
72
- of every spec file (tokens, screens, flows, contracts, locales, platform overrides).
73
- Use the spec_contents field as the authoritative source do NOT paraphrase from memory.
74
- Cross-reference exact token values, contract must_handle lists, and locale keys from the inline content.
75
- 2. If the request requires spec changes, update the spec files FIRST, then call openuispec_check to validate.
76
- 3. Only then generate or update the platform UI code based on the prepare output.
72
+
73
+ PRE-GENERATION:
74
+ 1. Call openuispec_prepare with the target platform.
75
+ 2. Call openuispec_read_specs to load the spec file contents you need.
76
+ Use the returned contents as the AUTHORITATIVE source do NOT paraphrase from memory.
77
+ 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.
79
+ 4. Generate or update the platform UI code based on the spec contents.
80
+
81
+ POST-GENERATION (do this EVERY TIME after writing UI code):
82
+ 5. Call openuispec_check to validate spec integrity.
83
+ 6. Call openuispec_read_specs for the screens/contracts you just generated code for.
84
+ 7. Audit your generated code against the spec contents. For each screen, verify:
85
+ - Every field/action in the spec has a corresponding UI element
86
+ - Token values (colors, spacing, radii) match the spec exactly, not approximations
87
+ - Contract must_handle states are all implemented (loading, error, empty, etc.)
88
+ - Adaptive breakpoints match the layout size_classes in the spec
89
+ - Locale keys match $t: references in the spec
90
+ - Navigation targets match flow definitions
91
+ Report any real gaps found and fix them before finishing.
77
92
 
78
93
  Skip these tools ONLY when the request is purely non-UI (API logic, database, infrastructure, etc.)
79
94
  or explicitly platform-specific polish that doesn't affect shared UI semantics.`,
@@ -85,7 +100,7 @@ or explicitly platform-specific polish that doesn't affect shared UI semantics.`
85
100
  server.registerTool(
86
101
  "openuispec_prepare",
87
102
  {
88
- description: "Build AI-ready work bundle for a target platform. REQUIRED before any UI code generation. Returns spec context, platform config, semantic changes, generation constraints, AND the full contents of every spec file inline. Use spec_contents as the authoritative source for tokens, screens, contracts, and locales — do not paraphrase from memory.",
103
+ description: "Build AI-ready work bundle for a target platform. REQUIRED before any UI code generation. Returns spec context, platform config, semantic changes, and generation constraints. Call openuispec_read_specs afterward to load the actual spec file contents you need for generation.",
89
104
  inputSchema: { target: targetSchema },
90
105
  },
91
106
  async ({ target }) => {
@@ -152,6 +167,42 @@ server.registerTool(
152
167
  }
153
168
  );
154
169
 
170
+ // ── tool: openuispec_read_specs ───────────────────────────────────────
171
+
172
+ server.registerTool(
173
+ "openuispec_read_specs",
174
+ {
175
+ description: "Read the full contents of spec files. Call after openuispec_prepare to load the actual YAML/JSON content. Pass specific file paths from the prepare output, or omit to read all spec files. Use these contents as the authoritative source — do NOT paraphrase from memory.",
176
+ inputSchema: {
177
+ paths: z
178
+ .array(z.string())
179
+ .optional()
180
+ .describe("Specific spec file paths to read (relative to spec root, e.g. 'screens/home.yaml'). If omitted, reads all spec files."),
181
+ },
182
+ },
183
+ async ({ paths }) => {
184
+ try {
185
+ const projectDir = findProjectDir(projectCwd);
186
+ const allFiles = discoverSpecFiles(projectDir);
187
+ const filesToRead = paths && paths.length > 0
188
+ ? allFiles.filter((f) => {
189
+ const rel = relative(projectDir, f);
190
+ return paths.some((p) => rel === p || rel.endsWith(p));
191
+ })
192
+ : allFiles;
193
+
194
+ const contents = filesToRead.map((f) => ({
195
+ path: relative(projectDir, f),
196
+ content: fsReadFileSync(f, "utf-8"),
197
+ }));
198
+
199
+ return toolResult(contents);
200
+ } catch (err) {
201
+ return toolError(err);
202
+ }
203
+ }
204
+ );
205
+
155
206
  // ── tool: openuispec_drift ───────────────────────────────────────────
156
207
 
157
208
  server.registerTool(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openuispec",
3
- "version": "0.1.40",
3
+ "version": "0.1.42",
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
@@ -144,7 +144,7 @@ export interface PrepareResult {
144
144
  explanation_note?: string;
145
145
  items: PrepareItem[];
146
146
  bootstrap?: PrepareBootstrapBundle;
147
- spec_contents: SpecFileContent[];
147
+ spec_contents?: SpecFileContent[];
148
148
  next_steps: string[];
149
149
  }
150
150
 
@@ -1057,7 +1057,7 @@ function printReport(result: PrepareResult): void {
1057
1057
  }
1058
1058
  }
1059
1059
 
1060
- function buildBootstrapPrepareResult(cwd: string, target: string): PrepareResult {
1060
+ function buildBootstrapPrepareResult(cwd: string, target: string, includeContents: boolean = false): PrepareResult {
1061
1061
  const projectDir = findProjectDir(cwd);
1062
1062
  const projectName = readProjectName(projectDir);
1063
1063
  const outputDir = resolveOutputDir(projectDir, projectName, target);
@@ -1124,7 +1124,7 @@ function buildBootstrapPrepareResult(cwd: string, target: string): PrepareResult
1124
1124
  changes_available: false,
1125
1125
  explanation_note: "No snapshot exists yet. This is a first-time generation bundle.",
1126
1126
  items: [],
1127
- spec_contents: readAllSpecContents(projectDir),
1127
+ ...(includeContents ? { spec_contents: readAllSpecContents(projectDir) } : {}),
1128
1128
  bootstrap: {
1129
1129
  output_exists: existsSync(outputDir),
1130
1130
  generation_ready: missingDecisions.length === 0 && backendContextReady && !pendingUserConfirmation,
@@ -1150,7 +1150,7 @@ function buildBootstrapPrepareResult(cwd: string, target: string): PrepareResult
1150
1150
  };
1151
1151
  }
1152
1152
 
1153
- function buildUpdatePrepareResult(cwd: string, target: string): PrepareResult {
1153
+ function buildUpdatePrepareResult(cwd: string, target: string, includeContents: boolean = false): PrepareResult {
1154
1154
  const { projectDir, projectName, result } = loadTargetDrift(cwd, target, false, true);
1155
1155
  const outputDir = resolveOutputDir(projectDir, projectName, target);
1156
1156
  const codeRoots = suggestCodeRoots(target, outputDir);
@@ -1195,20 +1195,20 @@ function buildUpdatePrepareResult(cwd: string, target: string): PrepareResult {
1195
1195
  changes_available: result.explanation?.available ?? false,
1196
1196
  explanation_note: result.explanation?.note,
1197
1197
  items,
1198
- spec_contents: readAllSpecContents(projectDir),
1198
+ ...(includeContents ? { spec_contents: readAllSpecContents(projectDir) } : {}),
1199
1199
  next_steps: nextSteps,
1200
1200
  };
1201
1201
  }
1202
1202
 
1203
- export function buildPrepareResult(target: string, cwd: string = process.cwd()): PrepareResult {
1203
+ export function buildPrepareResult(target: string, cwd: string = process.cwd(), includeContents: boolean = false): PrepareResult {
1204
1204
  const projectDir = findProjectDir(cwd);
1205
1205
  const projectName = readProjectName(projectDir);
1206
1206
  const outputDir = resolveOutputDir(projectDir, projectName, target);
1207
1207
  const statePath = join(outputDir, ".openuispec-state.json");
1208
1208
  if (!existsSync(statePath)) {
1209
- return buildBootstrapPrepareResult(cwd, target);
1209
+ return buildBootstrapPrepareResult(cwd, target, includeContents);
1210
1210
  }
1211
- return buildUpdatePrepareResult(cwd, target);
1211
+ return buildUpdatePrepareResult(cwd, target, includeContents);
1212
1212
  }
1213
1213
 
1214
1214
  export function runPrepare(argv: string[]): void {