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 +22 -15
- package/mcp-server/index.ts +60 -9
- package/package.json +1 -1
- package/prepare/index.ts +8 -8
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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
|
|
package/mcp-server/index.ts
CHANGED
|
@@ -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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
3.
|
|
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
|
|
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
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
|
|
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 {
|