openuispec 0.1.39 → 0.1.41
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/mcp-server/index.ts +47 -5
- package/package.json +1 -1
- package/prepare/index.ts +25 -5
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
|
|
|
@@ -68,9 +70,13 @@ 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
72
|
1. BEFORE writing or modifying any UI code, call openuispec_prepare with the target platform.
|
|
71
|
-
This returns the spec context, platform config,
|
|
72
|
-
2.
|
|
73
|
-
|
|
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.
|
|
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 to validate.
|
|
79
|
+
4. Only then generate or update the platform UI code based on the spec contents.
|
|
74
80
|
|
|
75
81
|
Skip these tools ONLY when the request is purely non-UI (API logic, database, infrastructure, etc.)
|
|
76
82
|
or explicitly platform-specific polish that doesn't affect shared UI semantics.`,
|
|
@@ -82,7 +88,7 @@ or explicitly platform-specific polish that doesn't affect shared UI semantics.`
|
|
|
82
88
|
server.registerTool(
|
|
83
89
|
"openuispec_prepare",
|
|
84
90
|
{
|
|
85
|
-
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.",
|
|
91
|
+
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.",
|
|
86
92
|
inputSchema: { target: targetSchema },
|
|
87
93
|
},
|
|
88
94
|
async ({ target }) => {
|
|
@@ -149,6 +155,42 @@ server.registerTool(
|
|
|
149
155
|
}
|
|
150
156
|
);
|
|
151
157
|
|
|
158
|
+
// ── tool: openuispec_read_specs ───────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
server.registerTool(
|
|
161
|
+
"openuispec_read_specs",
|
|
162
|
+
{
|
|
163
|
+
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.",
|
|
164
|
+
inputSchema: {
|
|
165
|
+
paths: z
|
|
166
|
+
.array(z.string())
|
|
167
|
+
.optional()
|
|
168
|
+
.describe("Specific spec file paths to read (relative to spec root, e.g. 'screens/home.yaml'). If omitted, reads all spec files."),
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
async ({ paths }) => {
|
|
172
|
+
try {
|
|
173
|
+
const projectDir = findProjectDir(projectCwd);
|
|
174
|
+
const allFiles = discoverSpecFiles(projectDir);
|
|
175
|
+
const filesToRead = paths && paths.length > 0
|
|
176
|
+
? allFiles.filter((f) => {
|
|
177
|
+
const rel = relative(projectDir, f);
|
|
178
|
+
return paths.some((p) => rel === p || rel.endsWith(p));
|
|
179
|
+
})
|
|
180
|
+
: allFiles;
|
|
181
|
+
|
|
182
|
+
const contents = filesToRead.map((f) => ({
|
|
183
|
+
path: relative(projectDir, f),
|
|
184
|
+
content: fsReadFileSync(f, "utf-8"),
|
|
185
|
+
}));
|
|
186
|
+
|
|
187
|
+
return toolResult(contents);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
return toolError(err);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
|
|
152
194
|
// ── tool: openuispec_drift ───────────────────────────────────────────
|
|
153
195
|
|
|
154
196
|
server.registerTool(
|
package/package.json
CHANGED
package/prepare/index.ts
CHANGED
|
@@ -116,6 +116,12 @@ interface PrepareBootstrapBundle {
|
|
|
116
116
|
reference_examples: string[];
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
+
interface SpecFileContent {
|
|
120
|
+
path: string;
|
|
121
|
+
category: string;
|
|
122
|
+
content: string;
|
|
123
|
+
}
|
|
124
|
+
|
|
119
125
|
export interface PrepareResult {
|
|
120
126
|
mode: "bootstrap" | "update";
|
|
121
127
|
project: string;
|
|
@@ -138,6 +144,7 @@ export interface PrepareResult {
|
|
|
138
144
|
explanation_note?: string;
|
|
139
145
|
items: PrepareItem[];
|
|
140
146
|
bootstrap?: PrepareBootstrapBundle;
|
|
147
|
+
spec_contents?: SpecFileContent[];
|
|
141
148
|
next_steps: string[];
|
|
142
149
|
}
|
|
143
150
|
|
|
@@ -842,6 +849,17 @@ function generationWarnings(target: string, platformConfig: PreparePlatformConfi
|
|
|
842
849
|
return warnings;
|
|
843
850
|
}
|
|
844
851
|
|
|
852
|
+
function readAllSpecContents(projectDir: string): SpecFileContent[] {
|
|
853
|
+
return discoverSpecFiles(projectDir).map((filePath) => {
|
|
854
|
+
const relPath = relative(projectDir, filePath);
|
|
855
|
+
return {
|
|
856
|
+
path: relPath,
|
|
857
|
+
category: categorizeSpecFile(relPath),
|
|
858
|
+
content: readFileSync(filePath, "utf-8"),
|
|
859
|
+
};
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
|
|
845
863
|
function bootstrapSpecFiles(projectDir: string, target: string): BootstrapSpecFile[] {
|
|
846
864
|
return discoverSpecFiles(projectDir)
|
|
847
865
|
.map((filePath) => {
|
|
@@ -1039,7 +1057,7 @@ function printReport(result: PrepareResult): void {
|
|
|
1039
1057
|
}
|
|
1040
1058
|
}
|
|
1041
1059
|
|
|
1042
|
-
function buildBootstrapPrepareResult(cwd: string, target: string): PrepareResult {
|
|
1060
|
+
function buildBootstrapPrepareResult(cwd: string, target: string, includeContents: boolean = false): PrepareResult {
|
|
1043
1061
|
const projectDir = findProjectDir(cwd);
|
|
1044
1062
|
const projectName = readProjectName(projectDir);
|
|
1045
1063
|
const outputDir = resolveOutputDir(projectDir, projectName, target);
|
|
@@ -1106,6 +1124,7 @@ function buildBootstrapPrepareResult(cwd: string, target: string): PrepareResult
|
|
|
1106
1124
|
changes_available: false,
|
|
1107
1125
|
explanation_note: "No snapshot exists yet. This is a first-time generation bundle.",
|
|
1108
1126
|
items: [],
|
|
1127
|
+
...(includeContents ? { spec_contents: readAllSpecContents(projectDir) } : {}),
|
|
1109
1128
|
bootstrap: {
|
|
1110
1129
|
output_exists: existsSync(outputDir),
|
|
1111
1130
|
generation_ready: missingDecisions.length === 0 && backendContextReady && !pendingUserConfirmation,
|
|
@@ -1131,7 +1150,7 @@ function buildBootstrapPrepareResult(cwd: string, target: string): PrepareResult
|
|
|
1131
1150
|
};
|
|
1132
1151
|
}
|
|
1133
1152
|
|
|
1134
|
-
function buildUpdatePrepareResult(cwd: string, target: string): PrepareResult {
|
|
1153
|
+
function buildUpdatePrepareResult(cwd: string, target: string, includeContents: boolean = false): PrepareResult {
|
|
1135
1154
|
const { projectDir, projectName, result } = loadTargetDrift(cwd, target, false, true);
|
|
1136
1155
|
const outputDir = resolveOutputDir(projectDir, projectName, target);
|
|
1137
1156
|
const codeRoots = suggestCodeRoots(target, outputDir);
|
|
@@ -1176,19 +1195,20 @@ function buildUpdatePrepareResult(cwd: string, target: string): PrepareResult {
|
|
|
1176
1195
|
changes_available: result.explanation?.available ?? false,
|
|
1177
1196
|
explanation_note: result.explanation?.note,
|
|
1178
1197
|
items,
|
|
1198
|
+
...(includeContents ? { spec_contents: readAllSpecContents(projectDir) } : {}),
|
|
1179
1199
|
next_steps: nextSteps,
|
|
1180
1200
|
};
|
|
1181
1201
|
}
|
|
1182
1202
|
|
|
1183
|
-
export function buildPrepareResult(target: string, cwd: string = process.cwd()): PrepareResult {
|
|
1203
|
+
export function buildPrepareResult(target: string, cwd: string = process.cwd(), includeContents: boolean = false): PrepareResult {
|
|
1184
1204
|
const projectDir = findProjectDir(cwd);
|
|
1185
1205
|
const projectName = readProjectName(projectDir);
|
|
1186
1206
|
const outputDir = resolveOutputDir(projectDir, projectName, target);
|
|
1187
1207
|
const statePath = join(outputDir, ".openuispec-state.json");
|
|
1188
1208
|
if (!existsSync(statePath)) {
|
|
1189
|
-
return buildBootstrapPrepareResult(cwd, target);
|
|
1209
|
+
return buildBootstrapPrepareResult(cwd, target, includeContents);
|
|
1190
1210
|
}
|
|
1191
|
-
return buildUpdatePrepareResult(cwd, target);
|
|
1211
|
+
return buildUpdatePrepareResult(cwd, target, includeContents);
|
|
1192
1212
|
}
|
|
1193
1213
|
|
|
1194
1214
|
export function runPrepare(argv: string[]): void {
|