capy-mcp 1.0.8 → 1.0.10
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/dist/brief.js +3 -1
- package/dist/component-discovery.js +7 -3
- package/dist/design-system.js +1 -153
- package/dist/server.js +8 -7
- package/dist/types.d.ts +1 -17
- package/dist/update.js +1 -1
- package/package.json +1 -1
package/dist/brief.js
CHANGED
|
@@ -13,6 +13,7 @@ const SECTION_ORDER = [
|
|
|
13
13
|
"Data Display",
|
|
14
14
|
"Feedback",
|
|
15
15
|
"Overlays",
|
|
16
|
+
"Components",
|
|
16
17
|
"Feature or Page Sections",
|
|
17
18
|
];
|
|
18
19
|
export async function buildPreviewBrief(projectRoot, input) {
|
|
@@ -143,6 +144,7 @@ function buildWarnings(framework, changedFiles, discoveryGaps = []) {
|
|
|
143
144
|
warnings.push("No changedFiles were provided. The agent should inspect git diff or recent edits when performing update_preview.");
|
|
144
145
|
}
|
|
145
146
|
warnings.push(...discoveryGaps);
|
|
147
|
+
warnings.push("IMPORTANT: Add .capy/ and the created preview route/page files (e.g. app/preview/, pages/preview.tsx) to .gitignore so they are not committed to the repository.");
|
|
146
148
|
return warnings;
|
|
147
149
|
}
|
|
148
150
|
function buildInstructions(projectFacts, input, discoveryInstruction) {
|
|
@@ -150,5 +152,5 @@ function buildInstructions(projectFacts, input, discoveryInstruction) {
|
|
|
150
152
|
? "Update the existing /preview route incrementally."
|
|
151
153
|
: "Create the /preview route from scratch.";
|
|
152
154
|
const userGoal = input.userGoal ? ` User goal: ${input.userGoal}.` : "";
|
|
153
|
-
return `${lead}${userGoal} Read the app shell first, then global styles, then traverse all discovered component directories. After that, implement ${projectFacts.previewEntryFile} as a clean preview surface that supports both vertical and horizontal scanning when useful, includes a dedicated icon section when icons can be discovered, and renders colors as consistent swatches with 6-character hex labels plus click-to-copy behavior using a pointer cursor. ${discoveryInstruction}
|
|
155
|
+
return `${lead}${userGoal} Read the app shell first, then global styles, then traverse all discovered component directories. After that, implement ${projectFacts.previewEntryFile} as a clean preview surface that supports both vertical and horizontal scanning when useful, includes a dedicated icon section when icons can be discovered, and renders colors as consistent swatches with 6-character hex labels plus click-to-copy behavior using a pointer cursor. ${discoveryInstruction} IMPORTANT: After creating files, add the .capy/ folder and the created preview page files (e.g. ${projectFacts.previewEntryFile}) to .gitignore so they are not committed to the repository.`;
|
|
154
156
|
}
|
|
@@ -87,9 +87,13 @@ function buildDiscoveryFacts(components) {
|
|
|
87
87
|
for (const comp of components) {
|
|
88
88
|
const slashIndex = comp.path.lastIndexOf("/");
|
|
89
89
|
const dir = slashIndex === -1 ? "." : comp.path.slice(0, slashIndex);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
const existing = byDir.get(dir);
|
|
91
|
+
if (existing) {
|
|
92
|
+
existing.push(comp.exports[0] ?? comp.basename);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
byDir.set(dir, [comp.exports[0] ?? comp.basename]);
|
|
96
|
+
}
|
|
93
97
|
}
|
|
94
98
|
const facts = [];
|
|
95
99
|
for (const [dir, names] of byDir) {
|
package/dist/design-system.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { glob } from "glob";
|
|
2
2
|
import { basename, join } from "path";
|
|
3
|
-
import { buildPreviewBrief } from "./brief.js";
|
|
4
3
|
import { buildComponentDiscoveryPlan } from "./component-discovery.js";
|
|
5
4
|
import { readText, toPosixPath, writeText } from "./files.js";
|
|
6
5
|
import { detectFramework } from "./framework.js";
|
|
@@ -10,28 +9,10 @@ export async function buildDesignSystemArtifact(projectRoot, input = {}) {
|
|
|
10
9
|
const framework = await detectFramework(projectRoot);
|
|
11
10
|
const projectFacts = await buildProjectFacts(projectRoot, framework);
|
|
12
11
|
const discoveryPlan = await buildComponentDiscoveryPlan(projectRoot, projectFacts);
|
|
13
|
-
const previewBrief = await buildPreviewBrief(projectRoot, {
|
|
14
|
-
task: input.mode === "update" ? "update_preview" : "build_preview",
|
|
15
|
-
changedFiles: input.changedFiles,
|
|
16
|
-
userGoal: input.userGoal,
|
|
17
|
-
});
|
|
18
12
|
const cssVariables = await collectCssVariables(projectRoot, projectFacts.likelyStyleFiles);
|
|
19
13
|
const components = await collectComponents(projectRoot, projectFacts);
|
|
20
|
-
const readFirst = buildReadFirst(previewBrief, components, discoveryPlan.prioritizedFiles);
|
|
21
|
-
const bridges = await buildUsageBridges(projectRoot, projectFacts.likelyStyleFiles, components);
|
|
22
|
-
const gaps = buildGapNotes(projectFacts, components, cssVariables, discoveryPlan.missingFamilyGaps);
|
|
23
14
|
const artifactPath = input.artifactPath ?? DEFAULT_ARTIFACT_PATH;
|
|
24
15
|
return {
|
|
25
|
-
forAgent: {
|
|
26
|
-
intent: input.mode === "update"
|
|
27
|
-
? "Update /preview incrementally. Read the files below first, then touch only the sections affected by the changed files unless shared foundations changed."
|
|
28
|
-
: "Build or refine /preview. Read the files below first, then mirror the repo's existing UI language instead of inventing a new one.",
|
|
29
|
-
readFirst,
|
|
30
|
-
facts: buildAgentFacts(projectFacts, components, cssVariables, discoveryPlan.discoveredFamilyFacts),
|
|
31
|
-
bridges,
|
|
32
|
-
gaps,
|
|
33
|
-
updateHints: previewBrief.updateStrategy,
|
|
34
|
-
},
|
|
35
16
|
artifact: {
|
|
36
17
|
generatedAt: new Date().toISOString(),
|
|
37
18
|
mode: input.mode ?? "build",
|
|
@@ -50,6 +31,7 @@ export async function buildDesignSystemArtifact(projectRoot, input = {}) {
|
|
|
50
31
|
pageDirs: projectFacts.likelyPageDirs,
|
|
51
32
|
styleFiles: projectFacts.likelyStyleFiles,
|
|
52
33
|
uiDirs: projectFacts.likelyUiDirs,
|
|
34
|
+
discoveredFamilies: discoveryPlan.discoveredFamilyFacts,
|
|
53
35
|
},
|
|
54
36
|
tokens: {
|
|
55
37
|
cssVariables,
|
|
@@ -59,12 +41,6 @@ export async function buildDesignSystemArtifact(projectRoot, input = {}) {
|
|
|
59
41
|
count: components.length,
|
|
60
42
|
items: components,
|
|
61
43
|
},
|
|
62
|
-
preview: {
|
|
63
|
-
route: projectFacts.previewRoute,
|
|
64
|
-
entryFile: projectFacts.previewEntryFile,
|
|
65
|
-
sections: previewBrief.deliverableSpec.sections,
|
|
66
|
-
updateStrategy: previewBrief.updateStrategy,
|
|
67
|
-
},
|
|
68
44
|
};
|
|
69
45
|
}
|
|
70
46
|
export async function writeDesignSystemArtifact(projectRoot, input = {}) {
|
|
@@ -170,131 +146,3 @@ function classifyCssVariable(name) {
|
|
|
170
146
|
function lineNumberAt(contents, index) {
|
|
171
147
|
return contents.slice(0, index).split("\n").length;
|
|
172
148
|
}
|
|
173
|
-
function buildReadFirst(previewBrief, components, prioritizedFiles) {
|
|
174
|
-
const seen = new Set();
|
|
175
|
-
const readFirst = [];
|
|
176
|
-
const firstComponentByDir = new Map();
|
|
177
|
-
for (const component of components) {
|
|
178
|
-
const slashIndex = component.path.lastIndexOf("/");
|
|
179
|
-
if (slashIndex === -1)
|
|
180
|
-
continue;
|
|
181
|
-
const directory = component.path.slice(0, slashIndex);
|
|
182
|
-
if (!firstComponentByDir.has(directory)) {
|
|
183
|
-
firstComponentByDir.set(directory, component.path);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
for (const step of previewBrief.inspectionPlan.slice(0, 2)) {
|
|
187
|
-
for (const target of step.targets) {
|
|
188
|
-
if (!looksLikeProjectPath(target))
|
|
189
|
-
continue;
|
|
190
|
-
const normalizedTarget = firstComponentByDir.get(target) ?? target;
|
|
191
|
-
if (seen.has(normalizedTarget))
|
|
192
|
-
continue;
|
|
193
|
-
seen.add(normalizedTarget);
|
|
194
|
-
readFirst.push({
|
|
195
|
-
path: normalizedTarget,
|
|
196
|
-
reason: step.reason,
|
|
197
|
-
});
|
|
198
|
-
if (readFirst.length >= 8) {
|
|
199
|
-
return readFirst;
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
for (const prioritizedFile of prioritizedFiles) {
|
|
204
|
-
if (seen.has(prioritizedFile))
|
|
205
|
-
continue;
|
|
206
|
-
seen.add(prioritizedFile);
|
|
207
|
-
readFirst.push({
|
|
208
|
-
path: prioritizedFile,
|
|
209
|
-
reason: "Likely reusable primitive or usage example. Read this before inventing preview-only specimens.",
|
|
210
|
-
});
|
|
211
|
-
if (readFirst.length >= 8) {
|
|
212
|
-
return readFirst;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
for (const step of previewBrief.inspectionPlan.slice(2)) {
|
|
216
|
-
for (const target of step.targets) {
|
|
217
|
-
if (!looksLikeProjectPath(target))
|
|
218
|
-
continue;
|
|
219
|
-
const normalizedTarget = firstComponentByDir.get(target) ?? target;
|
|
220
|
-
if (seen.has(normalizedTarget))
|
|
221
|
-
continue;
|
|
222
|
-
seen.add(normalizedTarget);
|
|
223
|
-
readFirst.push({
|
|
224
|
-
path: normalizedTarget,
|
|
225
|
-
reason: step.reason,
|
|
226
|
-
});
|
|
227
|
-
if (readFirst.length >= 8) {
|
|
228
|
-
return readFirst;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
return readFirst;
|
|
233
|
-
}
|
|
234
|
-
function buildAgentFacts(projectFacts, components, cssVariables, discoveredFamilyFacts) {
|
|
235
|
-
return [
|
|
236
|
-
`Use ${projectFacts.framework} conventions.`,
|
|
237
|
-
`Implement /preview at ${projectFacts.previewEntryFile}.`,
|
|
238
|
-
"Use repo-relative paths only.",
|
|
239
|
-
`Found ${components.length} component candidates and ${cssVariables.length} CSS variables.`,
|
|
240
|
-
...discoveredFamilyFacts,
|
|
241
|
-
];
|
|
242
|
-
}
|
|
243
|
-
function buildGapNotes(projectFacts, components, cssVariables, discoveryGaps) {
|
|
244
|
-
const gaps = [...discoveryGaps];
|
|
245
|
-
if (components.length === 0) {
|
|
246
|
-
gaps.push("No components were detected. Grep src/components, src/ui, src/features, components, ui, or features before assuming the app has no reusable UI.");
|
|
247
|
-
}
|
|
248
|
-
if (cssVariables.length === 0 && projectFacts.likelyStyleFiles.length > 0) {
|
|
249
|
-
gaps.push(`No CSS variables were detected. Read ${projectFacts.likelyStyleFiles[0]} directly and look for literal classes, theme config, or inline color tokens.`);
|
|
250
|
-
}
|
|
251
|
-
if (projectFacts.likelyStyleFiles.length === 0) {
|
|
252
|
-
gaps.push("No global style files were detected. Inspect app shell files and component code directly for layout and color usage.");
|
|
253
|
-
}
|
|
254
|
-
return gaps;
|
|
255
|
-
}
|
|
256
|
-
async function buildUsageBridges(projectRoot, styleFiles, components) {
|
|
257
|
-
const bridges = [];
|
|
258
|
-
for (const relativePath of styleFiles) {
|
|
259
|
-
const fileContents = await readText(join(projectRoot, relativePath));
|
|
260
|
-
if (!fileContents)
|
|
261
|
-
continue;
|
|
262
|
-
if (fileContents.includes("hsl(var(--")) {
|
|
263
|
-
bridges.push(`Theme tokens are bridged through hsl(var(--...)) in ${relativePath}.`);
|
|
264
|
-
break;
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
const hexByFile = await collectHexLiterals(projectRoot, components);
|
|
268
|
-
for (const [path, hexValues] of hexByFile.slice(0, 3)) {
|
|
269
|
-
bridges.push(`Literal accents also appear in ${path}: ${hexValues.join(", ")}.`);
|
|
270
|
-
}
|
|
271
|
-
return bridges;
|
|
272
|
-
}
|
|
273
|
-
async function collectHexLiterals(projectRoot, components) {
|
|
274
|
-
const matches = [];
|
|
275
|
-
for (const component of components) {
|
|
276
|
-
const contents = await readText(join(projectRoot, component.path));
|
|
277
|
-
if (!contents)
|
|
278
|
-
continue;
|
|
279
|
-
const hexMatches = Array.from(contents.matchAll(/#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})\b/g))
|
|
280
|
-
.map((match) => normalizeHex(match[0]))
|
|
281
|
-
.filter((value, index, values) => values.indexOf(value) === index);
|
|
282
|
-
if (hexMatches.length > 0) {
|
|
283
|
-
matches.push([component.path, hexMatches.slice(0, 4)]);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
return matches;
|
|
287
|
-
}
|
|
288
|
-
function normalizeHex(hex) {
|
|
289
|
-
const normalized = hex.replace("#", "").toUpperCase();
|
|
290
|
-
if (normalized.length === 3) {
|
|
291
|
-
return `#${normalized
|
|
292
|
-
.split("")
|
|
293
|
-
.map((char) => `${char}${char}`)
|
|
294
|
-
.join("")}`;
|
|
295
|
-
}
|
|
296
|
-
return `#${normalized}`;
|
|
297
|
-
}
|
|
298
|
-
function looksLikeProjectPath(target) {
|
|
299
|
-
return target.includes("/") || /^[A-Za-z0-9._-]+\.(?:tsx|ts|jsx|js|css|scss|sass|less)$/.test(target);
|
|
300
|
-
}
|
package/dist/server.js
CHANGED
|
@@ -224,9 +224,9 @@ export function createServer(projectRoot = process.cwd()) {
|
|
|
224
224
|
preview_entry_file: z.string(),
|
|
225
225
|
component_count: z.number(),
|
|
226
226
|
css_variable_count: z.number(),
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
227
|
+
component_dirs: z.array(z.string()),
|
|
228
|
+
style_files: z.array(z.string()),
|
|
229
|
+
discovered_families: z.array(z.string()),
|
|
230
230
|
},
|
|
231
231
|
}, async ({ artifactPath = ".capy/design-system.json", mode = "build", changedFiles, userGoal }) => {
|
|
232
232
|
const artifact = await writeDesignSystemArtifact(projectRoot, {
|
|
@@ -243,9 +243,9 @@ export function createServer(projectRoot = process.cwd()) {
|
|
|
243
243
|
preview_entry_file: artifact.repo.previewEntryFile,
|
|
244
244
|
component_count: artifact.components.count,
|
|
245
245
|
css_variable_count: artifact.tokens.cssVariables.length,
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
246
|
+
component_dirs: artifact.scan.componentDirs,
|
|
247
|
+
style_files: artifact.scan.styleFiles,
|
|
248
|
+
discovered_families: artifact.scan.discoveredFamilies,
|
|
249
249
|
};
|
|
250
250
|
return {
|
|
251
251
|
content: [
|
|
@@ -263,7 +263,8 @@ export function createServer(projectRoot = process.cwd()) {
|
|
|
263
263
|
return server;
|
|
264
264
|
}
|
|
265
265
|
export async function main() {
|
|
266
|
-
const
|
|
266
|
+
const projectRoot = process.argv[2] || process.cwd();
|
|
267
|
+
const server = createServer(projectRoot);
|
|
267
268
|
const transport = new StdioServerTransport();
|
|
268
269
|
await server.connect(transport);
|
|
269
270
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -66,17 +66,6 @@ export interface ComponentRecord {
|
|
|
66
66
|
kind: "primitive" | "component" | "feature" | "unknown";
|
|
67
67
|
}
|
|
68
68
|
export interface DesignSystemArtifact {
|
|
69
|
-
forAgent: {
|
|
70
|
-
intent: string;
|
|
71
|
-
readFirst: Array<{
|
|
72
|
-
path: string;
|
|
73
|
-
reason: string;
|
|
74
|
-
}>;
|
|
75
|
-
facts: string[];
|
|
76
|
-
bridges: string[];
|
|
77
|
-
gaps: string[];
|
|
78
|
-
updateHints: string[];
|
|
79
|
-
};
|
|
80
69
|
artifact: {
|
|
81
70
|
generatedAt: string;
|
|
82
71
|
mode: "build" | "update";
|
|
@@ -95,6 +84,7 @@ export interface DesignSystemArtifact {
|
|
|
95
84
|
pageDirs: string[];
|
|
96
85
|
styleFiles: string[];
|
|
97
86
|
uiDirs: string[];
|
|
87
|
+
discoveredFamilies: string[];
|
|
98
88
|
};
|
|
99
89
|
tokens: {
|
|
100
90
|
cssVariables: CssVariableRecord[];
|
|
@@ -104,12 +94,6 @@ export interface DesignSystemArtifact {
|
|
|
104
94
|
count: number;
|
|
105
95
|
items: ComponentRecord[];
|
|
106
96
|
};
|
|
107
|
-
preview: {
|
|
108
|
-
route: string;
|
|
109
|
-
entryFile: string;
|
|
110
|
-
sections: string[];
|
|
111
|
-
updateStrategy: string[];
|
|
112
|
-
};
|
|
113
97
|
}
|
|
114
98
|
export interface PreviewStateSnapshot {
|
|
115
99
|
generatedAt: string;
|
package/dist/update.js
CHANGED
|
@@ -29,7 +29,7 @@ export async function runPreviewUpdate(projectRoot, input = {}) {
|
|
|
29
29
|
userGoal: input.userGoal,
|
|
30
30
|
});
|
|
31
31
|
await writePreviewStateSnapshot(projectRoot, snapshotPath, diffResult.snapshot);
|
|
32
|
-
const warnings = [
|
|
32
|
+
const warnings = [];
|
|
33
33
|
if (diffResult.baselineCreated) {
|
|
34
34
|
warnings.unshift("No prior preview-state snapshot was found. Capy created a baseline snapshot, so this run cannot infer incremental changes automatically.");
|
|
35
35
|
}
|
package/package.json
CHANGED