@master/css-mcp 2.0.0-rc.87 → 2.0.0

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.
Files changed (47) hide show
  1. package/README.md +47 -22
  2. package/dist/bin/index.js +4 -5
  3. package/dist/classes.js +153 -168
  4. package/dist/compare.js +99 -83
  5. package/dist/context.d.ts +5 -10
  6. package/dist/context.js +28 -10
  7. package/dist/design-system-schema.js +97 -0
  8. package/dist/directives.js +59 -109
  9. package/dist/document.js +2 -10
  10. package/dist/format.js +67 -56
  11. package/dist/index.d.ts +2 -3
  12. package/dist/index.js +1 -3
  13. package/dist/internal/src/workspace.js +70 -0
  14. package/dist/language.js +27 -23
  15. package/dist/lint.js +86 -96
  16. package/dist/manifest-query.js +40 -100
  17. package/dist/manifest-summary.js +15 -35
  18. package/dist/output-schema.js +207 -0
  19. package/dist/preview-write-lock.js +90 -0
  20. package/dist/project-comparison-schema.js +59 -0
  21. package/dist/project.js +77 -18
  22. package/dist/result.js +67 -8
  23. package/dist/scan.js +93 -73
  24. package/dist/semantic-schema.js +286 -0
  25. package/dist/server.d.ts +10 -9
  26. package/dist/server.js +123 -111
  27. package/dist/setup.js +28 -29
  28. package/dist/source-schema.js +141 -0
  29. package/dist/suggest.js +22 -0
  30. package/dist/tooling-session.js +27 -0
  31. package/package.json +63 -73
  32. package/LICENSE +0 -21
  33. package/dist/classes.d.ts +0 -236
  34. package/dist/compare.d.ts +0 -70
  35. package/dist/contributor.d.ts +0 -149
  36. package/dist/contributor.js +0 -499
  37. package/dist/directives.d.ts +0 -132
  38. package/dist/document.d.ts +0 -24
  39. package/dist/format.d.ts +0 -56
  40. package/dist/language.d.ts +0 -24
  41. package/dist/lint.d.ts +0 -55
  42. package/dist/manifest-query.d.ts +0 -148
  43. package/dist/manifest-summary.d.ts +0 -47
  44. package/dist/project.d.ts +0 -31
  45. package/dist/result.d.ts +0 -8
  46. package/dist/scan.d.ts +0 -156
  47. package/dist/setup.d.ts +0 -67
@@ -1,8 +1,9 @@
1
- import { loadWorkspaceManifest } from "./project.js";
2
- import { compactConditions, compactUtility, compactVariable, summarizeManifest } from "./manifest-summary.js";
3
- import { flattenMasterCSSManifestVariables } from "@master/css-engine";
4
- //#region src/manifest-query.ts
5
- const MANIFEST_QUERY_VERSION = 1;
1
+ import { loadWorkspaceManifest, manifestMetadata, requireWorkspaceManifest } from "./project.js";
2
+ import { createMCPToolingSession } from "./tooling-session.js";
3
+ import { compactMixin, compactVariable, summarizeManifest } from "./manifest-summary.js";
4
+ import { flattenMasterCSSManifestVariables } from "@master/css-schema/manifest";
5
+ // src/manifest-query.ts
6
+ const MANIFEST_QUERY_VERSION = 6;
6
7
  function includesQuery(value, query) {
7
8
  if (!query) return true;
8
9
  if (value === void 0 || value === null) return false;
@@ -14,116 +15,55 @@ function matchesAny(values, query) {
14
15
  function limitResults(items, limit) {
15
16
  return items.slice(0, limit);
16
17
  }
17
- function utilitySearchValues(utility) {
18
- return [
19
- utility.id,
20
- utility.name,
21
- utility.key,
22
- utility.subkey,
23
- ...utility.keys || [],
24
- ...utility.namespaces || [],
25
- ...utility.aliasGroups || [],
26
- ...utility.variableAliasRefs || [],
27
- ...utility.matchers.flatMap((matcher) => Object.values(matcher))
28
- ];
18
+ function mixinSearchValues(mixin) {
19
+ return [mixin.name, ...(mixin.parameters ?? []).map((parameter) => parameter.name)];
29
20
  }
30
21
  async function queryManifest(context, options = {}) {
31
- const manifest = await loadWorkspaceManifest(context);
22
+ const manifest = await loadWorkspaceManifest(context, options.context);
32
23
  const query = (options.query || "").toLowerCase();
33
24
  const kind = options.kind || "all";
34
25
  const namespace = options.namespace;
35
26
  const limit = options.limit ?? 50;
36
- if (manifest.status === "error") return {
37
- version: MANIFEST_QUERY_VERSION,
38
- root: context.root,
39
- manifest: {
40
- status: manifest.status,
41
- entries: manifest.entries,
42
- error: manifest.error
43
- },
44
- inputs: {
45
- query: options.query,
46
- kind,
47
- namespace,
48
- limit
49
- },
50
- results: {
51
- tokens: [],
52
- utilities: [],
53
- variants: [],
54
- modes: [],
55
- conditions: [],
56
- aliases: []
57
- },
58
- diagnostics: [{
59
- code: "manifest-load-error",
60
- severity: "error",
61
- message: manifest.error,
62
- source: "Master CSS",
63
- sourceKind: "manifest"
64
- }],
65
- summary: {
66
- total: 0,
67
- returned: 0,
68
- status: "error"
69
- }
70
- };
71
- const activeManifest = manifest.manifest;
27
+ const activeManifest = requireWorkspaceManifest(manifest);
72
28
  const variables = flattenMasterCSSManifestVariables(activeManifest.variables).filter((variable) => !namespace || variable.namespace === namespace).filter((variable) => matchesAny([
73
29
  variable.name,
74
30
  variable.key,
75
31
  variable.namespace,
76
- variable.value
32
+ ...variable.values.flatMap(({ path, value }) => [...path, value])
77
33
  ], query)).map(compactVariable);
78
- const utilities = (activeManifest.utilities || []).filter((utility) => !namespace || utility.namespaces?.includes(namespace)).filter((utility) => matchesAny(utilitySearchValues(utility), query)).map(compactUtility);
79
- const variants = (activeManifest.variants || []).filter((variant) => includesQuery(variant.token, query)).map((variant) => ({
80
- token: variant.token,
81
- branches: variant.branches.length,
82
- layers: [...new Set(variant.branches.map((branch) => branch.layer).filter(Boolean))]
34
+ const mixins = (activeManifest.mixins || []).filter((mixin) => !namespace || mixin.name === `--${namespace}`).filter((mixin) => matchesAny(mixinSearchValues(mixin), query)).map(compactMixin);
35
+ const utilities = (activeManifest.utilities || []).filter((utility) => !namespace || utility.parameters?.some((parameter) => parameter.name === `--${namespace}`)).filter((utility) => matchesAny(mixinSearchValues(utility), query)).map((utility) => ({
36
+ ...utility,
37
+ parameters: utility.parameters ?? []
38
+ }));
39
+ const customMedia = Object.entries(activeManifest.customMedia ?? {}).filter(([name]) => includesQuery(name, query)).map(([name, expression]) => ({
40
+ name,
41
+ expression
83
42
  }));
84
- const modes = [...activeManifest.settings?.defaultMode ? [activeManifest.settings.defaultMode] : [], ...activeManifest.settings?.modes || []].filter((mode, index, list) => list.indexOf(mode) === index).filter((mode) => includesQuery(mode, query)).map((mode) => ({
85
- name: mode,
86
- default: mode === activeManifest.settings?.defaultMode,
87
- trigger: activeManifest.settings?.modeTrigger
43
+ const customContainers = Object.entries(activeManifest.customContainers ?? {}).filter(([name]) => includesQuery(name, query)).map(([name, condition]) => ({
44
+ name,
45
+ condition
88
46
  }));
89
- const conditions = [
90
- ...compactConditions(activeManifest.conditions),
91
- ...compactConditions(activeManifest.breakpointConditions),
92
- ...compactConditions(activeManifest.containerConditions)
93
- ].filter((rule) => matchesAny([rule.name, rule.id], query));
94
- const aliases = (activeManifest.utilities || []).flatMap((utility) => [
95
- ...(utility.variableAliases || []).map(([key, name]) => ({
96
- utility: utility.id,
97
- key,
98
- name,
99
- type: "variable-alias"
100
- })),
101
- ...(utility.variableAliasRefs || []).map((ref) => ({
102
- utility: utility.id,
103
- ref,
104
- type: "variable-alias-ref"
105
- })),
106
- ...(utility.aliasGroups || []).map((group) => ({
107
- utility: utility.id,
108
- group,
109
- type: "alias-group"
110
- }))
111
- ]).filter((alias) => matchesAny(Object.values(alias), query));
47
+ using tooling = createMCPToolingSession(activeManifest);
48
+ const families = tooling.tokenFamilies().families.map((family) => ({
49
+ type: "token-family",
50
+ ...family
51
+ })).filter((family) => !namespace || family.namespace === namespace).filter((family) => matchesAny(Object.values(family), query));
112
52
  const allResults = {
113
53
  tokens: kind === "all" || kind === "token" ? variables : [],
114
54
  utilities: kind === "all" || kind === "utility" ? utilities : [],
115
- variants: kind === "all" || kind === "variant" ? variants : [],
116
- modes: kind === "all" || kind === "mode" ? modes : [],
117
- conditions: kind === "all" || kind === "condition" ? conditions : [],
118
- aliases: kind === "all" || kind === "alias" ? aliases : []
55
+ mixins: kind === "all" || kind === "mixin" ? mixins : [],
56
+ customMedia: kind === "all" || kind === "custom-media" ? customMedia : [],
57
+ customContainers: kind === "all" || kind === "custom-container" ? customContainers : [],
58
+ families: kind === "all" || kind === "family" ? families : []
119
59
  };
120
60
  const limitedResults = {
121
61
  tokens: limitResults(allResults.tokens, limit),
122
62
  utilities: limitResults(allResults.utilities, limit),
123
- variants: limitResults(allResults.variants, limit),
124
- modes: limitResults(allResults.modes, limit),
125
- conditions: limitResults(allResults.conditions, limit),
126
- aliases: limitResults(allResults.aliases, limit)
63
+ mixins: limitResults(allResults.mixins, limit),
64
+ customMedia: limitResults(allResults.customMedia, limit),
65
+ customContainers: limitResults(allResults.customContainers, limit),
66
+ families: limitResults(allResults.families, limit)
127
67
  };
128
68
  const total = Object.values(allResults).reduce((count, items) => count + items.length, 0);
129
69
  const returned = Object.values(limitedResults).reduce((count, items) => count + items.length, 0);
@@ -131,6 +71,7 @@ async function queryManifest(context, options = {}) {
131
71
  version: MANIFEST_QUERY_VERSION,
132
72
  root: context.root,
133
73
  manifest: {
74
+ ...manifestMetadata(manifest),
134
75
  status: manifest.status,
135
76
  entries: manifest.entries,
136
77
  summary: summarizeManifest(activeManifest)
@@ -146,14 +87,13 @@ async function queryManifest(context, options = {}) {
146
87
  total,
147
88
  returned,
148
89
  tokens: variables.length,
90
+ mixins: mixins.length,
149
91
  utilities: utilities.length,
150
- variants: variants.length,
151
- modes: modes.length,
152
- conditions: conditions.length,
153
- aliases: aliases.length,
92
+ customMedia: customMedia.length,
93
+ customContainers: customContainers.length,
94
+ families: families.length,
154
95
  status: "ok"
155
96
  }
156
97
  };
157
98
  }
158
- //#endregion
159
99
  export { queryManifest };
@@ -1,56 +1,36 @@
1
- import { flattenMasterCSSManifestVariables } from "@master/css-engine";
2
- //#region src/manifest-summary.ts
1
+ import { flattenMasterCSSManifestVariables } from "@master/css-schema/manifest";
2
+ // src/manifest-summary.ts
3
3
  function summarizeManifest(manifest) {
4
4
  const variables = flattenMasterCSSManifestVariables(manifest.variables);
5
5
  return {
6
- settings: manifest.settings ?? {},
6
+ languageVersion: manifest.languageVersion,
7
+ theme: manifest.theme ?? [],
7
8
  counts: {
8
9
  variables: variables.length,
9
10
  variableNamespaces: Object.keys(manifest.variables || {}).length,
11
+ mixins: manifest.mixins?.length ?? 0,
10
12
  utilities: manifest.utilities?.length ?? 0,
11
- variants: manifest.variants?.length ?? 0,
12
- conditions: Object.keys(manifest.conditions || {}).length,
13
- breakpointConditions: Object.keys(manifest.breakpointConditions || {}).length,
14
- containerConditions: Object.keys(manifest.containerConditions || {}).length,
15
- selectors: Object.keys(manifest.selectors || {}).length,
16
- animations: Object.keys(manifest.animations || {}).length
13
+ customMedia: Object.keys(manifest.customMedia || {}).length,
14
+ customContainers: Object.keys(manifest.customContainers || {}).length
17
15
  }
18
16
  };
19
17
  }
20
18
  function compactVariable(variable) {
21
19
  return {
20
+ ...variable,
21
+ dependencies: [...variable.dependencies ?? []],
22
22
  name: variable.name,
23
23
  key: variable.key,
24
24
  namespace: variable.namespace || "",
25
25
  type: variable.type,
26
- value: variable.value,
27
- ...variable.numeric ? { numeric: variable.numeric } : {},
28
- ...variable.modes ? { modes: Object.keys(variable.modes) } : {},
29
- ...variable.inline ? { inline: true } : {},
30
- ...variable.static ? { static: true } : {}
26
+ values: variable.values,
27
+ ...variable.numeric ? { numeric: variable.numeric } : {}
31
28
  };
32
29
  }
33
- function compactUtility(utility) {
30
+ function compactMixin(mixin) {
34
31
  return {
35
- id: utility.id,
36
- ...utility.name ? { name: utility.name } : {},
37
- ...utility.key ? { key: utility.key } : {},
38
- ...utility.subkey ? { subkey: utility.subkey } : {},
39
- ...utility.keys?.length ? { keys: utility.keys } : {},
40
- ...utility.namespaces?.length ? { namespaces: utility.namespaces } : {},
41
- layer: utility.layer,
42
- matcherTypes: utility.matchers.map((matcher) => matcher.type),
43
- emitType: utility.emit.type,
44
- ...utility.aliasGroups?.length ? { aliasGroups: utility.aliasGroups } : {},
45
- ...utility.variableAliasRefs?.length ? { variableAliasRefs: utility.variableAliasRefs } : {}
32
+ ...mixin,
33
+ parameters: mixin.parameters ?? []
46
34
  };
47
35
  }
48
- function compactConditions(conditions) {
49
- return Object.entries(conditions || {}).map(([name, rule]) => ({
50
- name,
51
- id: rule.id,
52
- nodes: rule.nodes.length
53
- }));
54
- }
55
- //#endregion
56
- export { compactConditions, compactUtility, compactVariable, summarizeManifest };
36
+ export { compactMixin, compactVariable, summarizeManifest };
@@ -0,0 +1,207 @@
1
+ import { change, classDiff, completion, diagnostic, envelopeMetadata, inspection, manifestResults, metadata, range, ruleDiff, strings } from "./semantic-schema.js";
2
+ import { extractSummary, extractedFile, formatFile, formatSummary, lintCoverage, lintFile, lintSummary, previewSummary, scanSummary, scannedFile, scanner, stylesheets } from "./source-schema.js";
3
+ import { projectComparison, projectSnapshot } from "./project-comparison-schema.js";
4
+ import { designSystemReport } from "./design-system-schema.js";
5
+ import * as z from "zod/v4";
6
+ // src/output-schema.ts
7
+ const object = z.record(z.string(), z.json());
8
+ const css = z.object({
9
+ bytes: z.number(),
10
+ text: z.string().optional()
11
+ }).passthrough();
12
+ const preview = z.object({
13
+ confirmToken: z.string().optional(),
14
+ expiresAt: z.number().optional(),
15
+ changes: z.array(change),
16
+ summary: previewSummary
17
+ }).passthrough();
18
+ const fields = {
19
+ mastercss_suggest_classes: {
20
+ version: z.literal(1),
21
+ manifest: metadata,
22
+ status: z.enum(["suggested", "preserved"]),
23
+ classes: strings,
24
+ css: z.string(),
25
+ remainingCss: z.string(),
26
+ notes: strings,
27
+ declarations: z.array(z.object({
28
+ property: z.string(),
29
+ value: z.string(),
30
+ important: z.boolean(),
31
+ className: z.string()
32
+ })),
33
+ verification: z.object({
34
+ declarations: z.boolean(),
35
+ cascade: z.literal(false),
36
+ browser: z.literal(false)
37
+ }),
38
+ validation: z.array(inspection.pick({
39
+ className: true,
40
+ matchStatus: true,
41
+ cssSyntaxStatus: true,
42
+ cssValueStatus: true,
43
+ browserSupport: true
44
+ }))
45
+ },
46
+ mastercss_workspace_info: {
47
+ root: z.string(),
48
+ roots: strings,
49
+ packages: object,
50
+ manifest: metadata
51
+ },
52
+ mastercss_setup_audit: {
53
+ root: z.string(),
54
+ status: z.string(),
55
+ packageJSON: object,
56
+ packages: object,
57
+ integrations: z.array(object),
58
+ manifest: metadata,
59
+ summary: object
60
+ },
61
+ mastercss_lint_project: {
62
+ root: z.string(),
63
+ manifest: metadata,
64
+ version: z.literal(5),
65
+ files: z.array(lintFile),
66
+ coverage: lintCoverage,
67
+ summary: lintSummary
68
+ },
69
+ mastercss_lint_content: {
70
+ root: z.string(),
71
+ manifest: metadata,
72
+ version: z.literal(5),
73
+ files: z.array(lintFile),
74
+ coverage: lintCoverage,
75
+ summary: lintSummary
76
+ },
77
+ mastercss_preview_fixes: {
78
+ manifest: metadata,
79
+ mode: z.enum(["lint-fixes", "generated-css"]),
80
+ preview
81
+ },
82
+ mastercss_preview_directive_format: {
83
+ root: z.string(),
84
+ manifest: metadata,
85
+ mode: z.enum(["content", "files"]),
86
+ files: z.array(formatFile),
87
+ summary: formatSummary
88
+ },
89
+ mastercss_apply_preview: {
90
+ applied: z.literal(true),
91
+ changes: z.array(z.object({
92
+ filePath: z.string(),
93
+ afterHash: z.string()
94
+ }))
95
+ },
96
+ mastercss_inspect_class: {
97
+ manifest: metadata,
98
+ ...inspection.shape,
99
+ css: z.string()
100
+ },
101
+ mastercss_render_css: {
102
+ manifest: metadata,
103
+ classes: strings,
104
+ inspections: z.array(inspection),
105
+ invalid: strings,
106
+ css
107
+ },
108
+ mastercss_trace_class: {
109
+ manifest: metadata,
110
+ inspection,
111
+ occurrences: z.array(z.object({
112
+ filePath: z.string(),
113
+ source: z.string(),
114
+ statuses: strings
115
+ })),
116
+ status: z.string(),
117
+ css
118
+ },
119
+ mastercss_extract_classes: {
120
+ manifest: metadata,
121
+ files: z.array(extractedFile),
122
+ summary: extractSummary
123
+ },
124
+ mastercss_scan_project: {
125
+ manifest: metadata,
126
+ files: z.array(scannedFile),
127
+ scanner,
128
+ stylesheets,
129
+ css,
130
+ summary: scanSummary,
131
+ snapshot: projectSnapshot.optional(),
132
+ designSystem: designSystemReport.optional()
133
+ },
134
+ mastercss_inspect_directives: {
135
+ context: metadata,
136
+ manifest: object,
137
+ directiveEntries: z.array(z.object({
138
+ name: z.string(),
139
+ range,
140
+ prelude: z.string(),
141
+ hasBlock: z.boolean(),
142
+ quotedStrings: z.number()
143
+ }).passthrough()),
144
+ dependencies: strings,
145
+ css,
146
+ summary: object
147
+ },
148
+ mastercss_manifest_query: {
149
+ version: z.literal(6),
150
+ manifest: metadata,
151
+ results: manifestResults,
152
+ summary: z.object({
153
+ total: z.number(),
154
+ returned: z.number(),
155
+ tokens: z.number(),
156
+ mixins: z.number(),
157
+ utilities: z.number(),
158
+ customMedia: z.number(),
159
+ customContainers: z.number(),
160
+ families: z.number(),
161
+ status: z.literal("ok")
162
+ })
163
+ },
164
+ mastercss_css_compare: {
165
+ manifest: metadata,
166
+ classes: classDiff,
167
+ css: z.object({
168
+ changed: z.boolean(),
169
+ before: css,
170
+ after: css,
171
+ bytesDelta: z.number(),
172
+ diff: z.string()
173
+ }),
174
+ rules: ruleDiff,
175
+ summary: object
176
+ },
177
+ mastercss_suggest_syntax: {
178
+ manifest: metadata,
179
+ completions: z.array(completion),
180
+ total: z.number()
181
+ }
182
+ };
183
+ function toolOutputSchema(name) {
184
+ if (!fields[name]) throw new Error(`Missing output schema for ${name}`);
185
+ const standard = z.object(fields[name]).passthrough();
186
+ const payload = name === "mastercss_css_compare" ? z.union([standard, z.object({
187
+ version: z.literal(3),
188
+ mode: z.literal("project"),
189
+ comparison: projectComparison
190
+ })]) : standard;
191
+ return z.object({
192
+ version: z.literal(3),
193
+ metadata: envelopeMetadata,
194
+ diagnostics: z.array(diagnostic),
195
+ result: z.discriminatedUnion("status", [z.object({
196
+ status: z.literal("success"),
197
+ data: payload
198
+ }), z.object({
199
+ status: z.literal("error"),
200
+ error: z.object({
201
+ code: z.string(),
202
+ message: z.string()
203
+ })
204
+ })])
205
+ });
206
+ }
207
+ export { toolOutputSchema };
@@ -0,0 +1,90 @@
1
+ import { join } from "node:path";
2
+ import { randomUUID } from "node:crypto";
3
+ import { mkdir, readFile, readdir, rename, unlink, writeFile } from "node:fs/promises";
4
+ import { userInfo } from "node:os";
5
+ import { setTimeout } from "node:timers/promises";
6
+ // src/preview-write-lock.ts
7
+ const NAME = /^(\d+)-[a-f0-9-]+\.json$/;
8
+ function missing(error) {
9
+ return error?.code === "ENOENT";
10
+ }
11
+ async function remove(file) {
12
+ try {
13
+ await unlink(file);
14
+ } catch (error) {
15
+ if (!missing(error)) throw error;
16
+ }
17
+ }
18
+ function alive(pid) {
19
+ try {
20
+ process.kill(pid, 0);
21
+ return true;
22
+ } catch (error) {
23
+ return error.code !== "ESRCH";
24
+ }
25
+ }
26
+ async function tickets(directory) {
27
+ const entries = [];
28
+ for (const id of await readdir(directory)) {
29
+ const register = id.endsWith(".next") ? id.slice(0, -5) : id;
30
+ const match = NAME.exec(register);
31
+ if (!match) continue;
32
+ const file = join(directory, register);
33
+ if (!alive(Number(match[1]))) {
34
+ await remove(file);
35
+ await remove(file + ".next");
36
+ continue;
37
+ }
38
+ if (id !== register) continue;
39
+ let text;
40
+ try {
41
+ text = await readFile(file, "utf8");
42
+ } catch (error) {
43
+ if (missing(error)) continue;
44
+ throw error;
45
+ }
46
+ const value = JSON.parse(text);
47
+ if (typeof value.choosing !== "boolean" || typeof value.number !== "string" || !/^\d+$/.test(value.number)) throw new Error(`Invalid MCP preview lock register: ${file}`);
48
+ entries.push({
49
+ id,
50
+ choosing: value.choosing,
51
+ number: BigInt(value.number)
52
+ });
53
+ }
54
+ return entries;
55
+ }
56
+ /** Serialize hash validation and writes, including across independently loaded servers. */
57
+ async function withPreviewWriteLock(work, options = {}) {
58
+ const directory = options.directory ?? join(userInfo().homedir, ".cache", "mastercss", "mcp-preview-lock-v1");
59
+ const deadline = performance.now() + (options.timeout ?? 3e4);
60
+ const id = `${process.pid}-${randomUUID()}.json`;
61
+ const file = join(directory, id);
62
+ await mkdir(directory, {
63
+ recursive: true,
64
+ mode: 448
65
+ });
66
+ const publish = async (value) => {
67
+ await writeFile(file + ".next", JSON.stringify(value), { mode: 384 });
68
+ await rename(file + ".next", file);
69
+ };
70
+ try {
71
+ await publish({
72
+ choosing: true,
73
+ number: "0"
74
+ });
75
+ const number = (await tickets(directory)).reduce((max, peer) => peer.number > max ? peer.number : max, 0n) + 1n;
76
+ await publish({
77
+ choosing: false,
78
+ number: String(number)
79
+ });
80
+ while (true) {
81
+ if (performance.now() >= deadline) throw new Error("Timed out waiting for another MCP preview apply.");
82
+ if (!(await tickets(directory)).some((peer) => peer.id !== id && (peer.choosing || peer.number < number || peer.number === number && peer.id < id))) return await work();
83
+ await setTimeout(10);
84
+ }
85
+ } finally {
86
+ await remove(file);
87
+ await remove(file + ".next");
88
+ }
89
+ }
90
+ export { withPreviewWriteLock };
@@ -0,0 +1,59 @@
1
+ import { matchStatus, rule, strings } from "./semantic-schema.js";
2
+ import * as z from "zod/v4";
3
+ // src/project-comparison-schema.ts
4
+ const asset = z.object({
5
+ path: z.string().min(1),
6
+ css: z.string()
7
+ }).strict();
8
+ const projectSnapshot = z.object({
9
+ version: z.literal(1),
10
+ manifest: z.object({
11
+ version: z.literal(6),
12
+ languageVersion: z.literal(18)
13
+ }).catchall(z.json()),
14
+ sources: z.array(z.object({
15
+ path: z.string().min(1),
16
+ classes: strings
17
+ }).strict()),
18
+ stylesheets: z.array(asset),
19
+ outputs: z.array(asset),
20
+ excluded: strings,
21
+ unresolved: strings
22
+ }).strict();
23
+ const assetChange = z.object({
24
+ path: z.string(),
25
+ before: z.string().nullable(),
26
+ after: z.string().nullable(),
27
+ changedDependencies: strings
28
+ });
29
+ const projectComparison = z.object({
30
+ version: z.literal(1),
31
+ definitions: z.array(z.object({
32
+ id: z.string(),
33
+ before: z.json().nullable(),
34
+ after: z.json().nullable()
35
+ })),
36
+ classes: z.array(z.object({
37
+ className: z.string(),
38
+ files: strings,
39
+ reasons: strings,
40
+ changedDependencies: strings,
41
+ beforeMatchStatus: matchStatus.nullable(),
42
+ afterMatchStatus: matchStatus.nullable(),
43
+ beforeRules: z.array(rule),
44
+ afterRules: z.array(rule)
45
+ })),
46
+ stylesheets: z.array(assetChange),
47
+ outputs: z.array(assetChange),
48
+ stylesheetOrderChanged: z.boolean(),
49
+ outputOrderChanged: z.boolean(),
50
+ files: strings,
51
+ coverage: z.object({
52
+ scope: z.literal("known-sources"),
53
+ browser: z.literal("not-checked"),
54
+ excluded: strings,
55
+ unresolved: strings,
56
+ notes: strings
57
+ })
58
+ });
59
+ export { projectComparison, projectSnapshot };