@master/css-mcp 2.0.0-rc.77 → 2.0.0-rc.78
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/README.md +34 -4
- package/dist/bin/index.js +1 -1
- package/dist/classes.d.ts +237 -0
- package/dist/classes.js +224 -0
- package/dist/compare.d.ts +71 -0
- package/dist/compare.js +131 -0
- package/dist/context.js +2 -2
- package/dist/contributor.d.ts +150 -0
- package/dist/contributor.js +497 -0
- package/dist/directives.d.ts +133 -0
- package/dist/directives.js +158 -0
- package/dist/document.d.ts +25 -0
- package/dist/document.js +51 -0
- package/dist/format.d.ts +57 -0
- package/dist/format.js +81 -0
- package/dist/index.d.ts +1 -1
- package/dist/language.js +2 -20
- package/dist/manifest-query.d.ts +149 -0
- package/dist/manifest-query.js +159 -0
- package/dist/manifest-summary.d.ts +48 -0
- package/dist/manifest-summary.js +56 -0
- package/dist/scan.d.ts +1 -1
- package/dist/scan.js +2 -2
- package/dist/server.js +202 -3
- package/dist/setup.d.ts +63 -0
- package/dist/setup.js +168 -0
- package/package.json +11 -9
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
import { getErrorMessage } from "./result.js";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
4
|
+
import fg from "fast-glob";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
//#region src/contributor.ts
|
|
7
|
+
const REPORT_VERSION = 1;
|
|
8
|
+
const AUDIENCE = "master-css-repository-contributors";
|
|
9
|
+
const MASTER_REPO_MARKERS = [
|
|
10
|
+
"AGENTS.md",
|
|
11
|
+
".ai/context/index.md",
|
|
12
|
+
".ai/context/package-routing.md",
|
|
13
|
+
"packages/mcp/package.json"
|
|
14
|
+
];
|
|
15
|
+
const PACKAGE_JSON_PATTERNS = [
|
|
16
|
+
"package.json",
|
|
17
|
+
"packages/*/package.json",
|
|
18
|
+
"site/package.json",
|
|
19
|
+
"shared/package.json",
|
|
20
|
+
"benchmarks/package.json",
|
|
21
|
+
"examples/*/package.json"
|
|
22
|
+
];
|
|
23
|
+
const PACKAGE_JSON_IGNORE = [
|
|
24
|
+
"**/node_modules/**",
|
|
25
|
+
"**/dist/**",
|
|
26
|
+
"**/.next/**",
|
|
27
|
+
"**/.svelte-kit/**",
|
|
28
|
+
"**/.vercel/**",
|
|
29
|
+
"**/.turbo/**",
|
|
30
|
+
"**/.tsbuild/**",
|
|
31
|
+
"**/playwright/.cache/**"
|
|
32
|
+
];
|
|
33
|
+
const HIGH_RISK_PACKAGES = {
|
|
34
|
+
engine: "CSS output and class generation",
|
|
35
|
+
preset: "default preset and generated manifest output",
|
|
36
|
+
compiler: "CSS-first manifest lowering",
|
|
37
|
+
runtime: "runtime hydration, CSSOM insertion, and DOM observation",
|
|
38
|
+
scanner: "static extraction and generated CSS scanner state",
|
|
39
|
+
source: "source class candidate extraction",
|
|
40
|
+
language: "language tokenization and source positions",
|
|
41
|
+
"language-service": "completion, hover, colors, and semantic token features",
|
|
42
|
+
"language-server": "LSP behavior",
|
|
43
|
+
lint: "framework-neutral class lint policy",
|
|
44
|
+
"eslint-plugin": "ESLint parser support, diagnostics, and autofix ranges"
|
|
45
|
+
};
|
|
46
|
+
const PACKAGE_COMMON_PACKS = {
|
|
47
|
+
schema: [".ai/context/package-boundaries.md"],
|
|
48
|
+
lexer: [".ai/context/package-boundaries.md"],
|
|
49
|
+
source: [".ai/context/package-boundaries.md", ".ai/context/testing.md"],
|
|
50
|
+
engine: [".ai/context/css-output.md", ".ai/context/performance.md"],
|
|
51
|
+
preset: [".ai/context/css-output.md"],
|
|
52
|
+
facade: [".ai/context/package-boundaries.md", ".ai/context/css-output.md"],
|
|
53
|
+
compiler: [".ai/context/css-output.md", ".ai/context/package-boundaries.md"],
|
|
54
|
+
project: [".ai/context/package-boundaries.md"],
|
|
55
|
+
integration: [".ai/context/package-boundaries.md"],
|
|
56
|
+
stylesheet: [".ai/context/css-output.md", ".ai/context/package-boundaries.md"],
|
|
57
|
+
runtime: [".ai/context/css-output.md", ".ai/context/performance.md"],
|
|
58
|
+
server: [".ai/context/css-output.md"],
|
|
59
|
+
scanner: [".ai/context/testing.md", ".ai/context/css-output.md"],
|
|
60
|
+
validator: [".ai/context/testing.md", ".ai/context/css-output.md"],
|
|
61
|
+
diagnostics: [".ai/context/testing.md", ".ai/context/package-boundaries.md"],
|
|
62
|
+
lint: [".ai/context/testing.md", ".ai/context/package-boundaries.md"],
|
|
63
|
+
vite: [".ai/context/package-boundaries.md", ".ai/context/css-output.md"],
|
|
64
|
+
webpack: [".ai/context/package-boundaries.md", ".ai/context/css-output.md"],
|
|
65
|
+
next: [".ai/context/package-boundaries.md", ".ai/context/css-output.md"],
|
|
66
|
+
cli: [".ai/context/testing.md", ".ai/context/package-boundaries.md"]
|
|
67
|
+
};
|
|
68
|
+
function unique(values) {
|
|
69
|
+
return [...new Set(values)];
|
|
70
|
+
}
|
|
71
|
+
function sortStrings(values) {
|
|
72
|
+
return unique(values.filter(Boolean)).sort((a, b) => a.localeCompare(b));
|
|
73
|
+
}
|
|
74
|
+
function isMasterCSSRepository(context) {
|
|
75
|
+
return MASTER_REPO_MARKERS.every((marker) => existsSync(resolve(context.root, marker)));
|
|
76
|
+
}
|
|
77
|
+
function normalizeRepoPath(context, filePath) {
|
|
78
|
+
const withoutPrefix = filePath.replace(/^[ab]\//, "");
|
|
79
|
+
const resolved = isAbsolute(withoutPrefix) ? resolve(withoutPrefix) : resolve(context.root, withoutPrefix);
|
|
80
|
+
context.assertContained(resolved);
|
|
81
|
+
const repoPath = relative(context.root, resolved).split(/[\\/]+/).join("/");
|
|
82
|
+
if (!repoPath || repoPath.startsWith("..")) throw new Error(`Path is outside the repository root: ${filePath}`);
|
|
83
|
+
return repoPath;
|
|
84
|
+
}
|
|
85
|
+
function extractDiffPaths(diff = "") {
|
|
86
|
+
const paths = [];
|
|
87
|
+
const push = (value) => {
|
|
88
|
+
if (!value || value === "/dev/null") return;
|
|
89
|
+
paths.push(value);
|
|
90
|
+
};
|
|
91
|
+
for (const line of diff.split("\n")) {
|
|
92
|
+
const gitMatch = line.match(/^diff --git a\/(.+?) b\/(.+)$/);
|
|
93
|
+
if (gitMatch) {
|
|
94
|
+
push(gitMatch[1]);
|
|
95
|
+
push(gitMatch[2]);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const fileMatch = line.match(/^(?:---|\+\+\+) (?:a\/|b\/)?(.+)$/);
|
|
99
|
+
if (fileMatch) {
|
|
100
|
+
push(fileMatch[1]);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const renameMatch = line.match(/^rename (?:from|to) (.+)$/);
|
|
104
|
+
if (renameMatch) push(renameMatch[1]);
|
|
105
|
+
}
|
|
106
|
+
return paths;
|
|
107
|
+
}
|
|
108
|
+
function normalizeInputPaths(context, input = {}) {
|
|
109
|
+
return sortStrings([...input.paths ?? [], ...extractDiffPaths(input.diff)].map((path) => normalizeRepoPath(context, path)));
|
|
110
|
+
}
|
|
111
|
+
async function readJSON(filePath) {
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
114
|
+
} catch {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function workspaceKind(path) {
|
|
119
|
+
if (path === ".") return "root";
|
|
120
|
+
if (path === "site") return "site";
|
|
121
|
+
if (path === "shared") return "shared";
|
|
122
|
+
if (path === "benchmarks") return "benchmarks";
|
|
123
|
+
if (path.startsWith("examples/")) return "example";
|
|
124
|
+
return "package";
|
|
125
|
+
}
|
|
126
|
+
function normalizeExports(exportsField) {
|
|
127
|
+
if (!exportsField) return [];
|
|
128
|
+
if (typeof exportsField === "string") return ["."];
|
|
129
|
+
if (Array.isArray(exportsField)) return ["."];
|
|
130
|
+
if (typeof exportsField === "object") return Object.keys(exportsField).sort();
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
function collectDependencies(pkg) {
|
|
134
|
+
return sortStrings([
|
|
135
|
+
...Object.keys(pkg.dependencies ?? {}),
|
|
136
|
+
...Object.keys(pkg.devDependencies ?? {}),
|
|
137
|
+
...Object.keys(pkg.peerDependencies ?? {}),
|
|
138
|
+
...Object.keys(pkg.optionalDependencies ?? {})
|
|
139
|
+
]);
|
|
140
|
+
}
|
|
141
|
+
async function loadWorkspacePackages(context, includeExamples = true) {
|
|
142
|
+
const packageFiles = await fg(PACKAGE_JSON_PATTERNS, {
|
|
143
|
+
cwd: context.root,
|
|
144
|
+
ignore: includeExamples ? PACKAGE_JSON_IGNORE : [...PACKAGE_JSON_IGNORE, "examples/**"],
|
|
145
|
+
onlyFiles: true
|
|
146
|
+
});
|
|
147
|
+
const packages = [];
|
|
148
|
+
for (const filePath of packageFiles.sort((a, b) => a.localeCompare(b))) {
|
|
149
|
+
const pkg = await readJSON(resolve(context.root, filePath));
|
|
150
|
+
if (!pkg) continue;
|
|
151
|
+
const path = filePath === "package.json" ? "." : filePath.replace(/\/package\.json$/, "");
|
|
152
|
+
const aiPath = path === "." ? "AGENTS.md" : `${path}/AI.md`;
|
|
153
|
+
packages.push({
|
|
154
|
+
name: pkg.name || path,
|
|
155
|
+
path,
|
|
156
|
+
private: Boolean(pkg.private),
|
|
157
|
+
kind: workspaceKind(path),
|
|
158
|
+
scripts: Object.keys(pkg.scripts ?? {}).sort(),
|
|
159
|
+
exports: normalizeExports(pkg.exports),
|
|
160
|
+
dependencies: collectDependencies(pkg),
|
|
161
|
+
dependents: [],
|
|
162
|
+
aiNotes: existsSync(resolve(context.root, aiPath)) ? aiPath : null
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const packageNames = new Set(packages.map((pkg) => pkg.name));
|
|
166
|
+
for (const pkg of packages) pkg.dependencies = pkg.dependencies.filter((dependency) => packageNames.has(dependency));
|
|
167
|
+
for (const pkg of packages) for (const dependency of pkg.dependencies) {
|
|
168
|
+
const dependencyPackage = packages.find((candidate) => candidate.name === dependency);
|
|
169
|
+
if (dependencyPackage) dependencyPackage.dependents.push(pkg.name);
|
|
170
|
+
}
|
|
171
|
+
for (const pkg of packages) pkg.dependents = sortStrings(pkg.dependents);
|
|
172
|
+
return packages;
|
|
173
|
+
}
|
|
174
|
+
function routePath(packages, path) {
|
|
175
|
+
const packageMatch = path.match(/^packages\/([^/]+)(?:\/|$)/);
|
|
176
|
+
if (packageMatch) {
|
|
177
|
+
const packagePath = `packages/${packageMatch[1]}`;
|
|
178
|
+
const pkg = packages.find((candidate) => candidate.path === packagePath);
|
|
179
|
+
return {
|
|
180
|
+
name: pkg?.name ?? packagePath,
|
|
181
|
+
path: packagePath,
|
|
182
|
+
kind: pkg?.kind ?? "package",
|
|
183
|
+
packageJSON: `${packagePath}/package.json`,
|
|
184
|
+
aiNotes: pkg?.aiNotes ?? null
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
if (path.startsWith("site/")) {
|
|
188
|
+
const pkg = packages.find((candidate) => candidate.path === "site");
|
|
189
|
+
return {
|
|
190
|
+
name: pkg?.name ?? "site",
|
|
191
|
+
path: "site",
|
|
192
|
+
kind: "site",
|
|
193
|
+
packageJSON: "site/package.json",
|
|
194
|
+
aiNotes: pkg?.aiNotes ?? null
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (path.startsWith("shared/")) return {
|
|
198
|
+
name: packages.find((candidate) => candidate.path === "shared")?.name ?? "shared",
|
|
199
|
+
path: "shared",
|
|
200
|
+
kind: "shared",
|
|
201
|
+
packageJSON: "shared/package.json",
|
|
202
|
+
aiNotes: null
|
|
203
|
+
};
|
|
204
|
+
if (path.startsWith("benchmarks/")) return {
|
|
205
|
+
name: packages.find((candidate) => candidate.path === "benchmarks")?.name ?? "benchmarks",
|
|
206
|
+
path: "benchmarks",
|
|
207
|
+
kind: "benchmarks",
|
|
208
|
+
packageJSON: "benchmarks/package.json",
|
|
209
|
+
aiNotes: null
|
|
210
|
+
};
|
|
211
|
+
const exampleMatch = path.match(/^examples\/([^/]+)(?:\/|$)/);
|
|
212
|
+
if (exampleMatch) {
|
|
213
|
+
const packagePath = `examples/${exampleMatch[1]}`;
|
|
214
|
+
return {
|
|
215
|
+
name: packages.find((candidate) => candidate.path === packagePath)?.name ?? packagePath,
|
|
216
|
+
path: packagePath,
|
|
217
|
+
kind: "example",
|
|
218
|
+
packageJSON: `${packagePath}/package.json`,
|
|
219
|
+
aiNotes: null
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (path === "AGENTS.md" || path.startsWith(".ai/") || path.startsWith(".github/prompts/")) return {
|
|
223
|
+
name: "agent-docs",
|
|
224
|
+
path: ".ai",
|
|
225
|
+
kind: "agent-docs",
|
|
226
|
+
aiNotes: null
|
|
227
|
+
};
|
|
228
|
+
const rootPackage = packages.find((candidate) => candidate.path === ".");
|
|
229
|
+
return {
|
|
230
|
+
name: rootPackage?.name ?? "repo-root",
|
|
231
|
+
path: ".",
|
|
232
|
+
kind: "repo-root",
|
|
233
|
+
packageJSON: rootPackage ? "package.json" : void 0,
|
|
234
|
+
aiNotes: rootPackage?.aiNotes ?? null
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function routePaths(packages, paths) {
|
|
238
|
+
const routed = /* @__PURE__ */ new Map();
|
|
239
|
+
for (const path of paths) {
|
|
240
|
+
const route = routePath(packages, path);
|
|
241
|
+
const key = route.path;
|
|
242
|
+
const existing = routed.get(key);
|
|
243
|
+
if (existing) existing.matchedPaths.push(path);
|
|
244
|
+
else routed.set(key, {
|
|
245
|
+
...route,
|
|
246
|
+
matchedPaths: [path]
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
return [...routed.values()].map((route) => ({
|
|
250
|
+
...route,
|
|
251
|
+
matchedPaths: sortStrings(route.matchedPaths)
|
|
252
|
+
}));
|
|
253
|
+
}
|
|
254
|
+
function taskPacks(task = "") {
|
|
255
|
+
const normalized = task.toLowerCase();
|
|
256
|
+
const packs = [];
|
|
257
|
+
if (/\b(review|pr)\b/.test(normalized)) packs.push(".ai/context/review.md");
|
|
258
|
+
if (/\b(test|coverage|fixture|snapshot)\b/.test(normalized)) packs.push(".ai/context/testing.md");
|
|
259
|
+
if (/\b(refactor|rewrite|cleanup|migration|re-architecture)\b/.test(normalized)) packs.push(".ai/context/refactor.md");
|
|
260
|
+
if (/\b(css output|generated css|cascade|layer|selector|at-rule|variable|mode|priority|class)\b/.test(normalized)) packs.push(".ai/context/css-output.md");
|
|
261
|
+
if (/\b(performance|benchmark|slow|hot path|memory|cpu)\b/.test(normalized)) packs.push(".ai/context/performance.md");
|
|
262
|
+
if (/\b(doc|docs|readme|site|guide)\b/.test(normalized)) packs.push(".ai/context/docs.md");
|
|
263
|
+
if (/\b(bug|fix|regression|error|failing|failure)\b/.test(normalized)) packs.push(".ai/context/bugfix.md");
|
|
264
|
+
if (/\b(package|boundary|dependency|export|public api|cycle)\b/.test(normalized)) packs.push(".ai/context/package-boundaries.md");
|
|
265
|
+
return packs;
|
|
266
|
+
}
|
|
267
|
+
function pathPacks(paths) {
|
|
268
|
+
const packs = [];
|
|
269
|
+
if (paths.length) packs.push(".ai/context/package-routing.md");
|
|
270
|
+
if (paths.some((path) => path.includes("/tests/") || path.endsWith(".test.ts") || path.endsWith(".test.js"))) packs.push(".ai/context/testing.md");
|
|
271
|
+
if (paths.some((path) => path.startsWith("site/") || path.endsWith("README.md") || path.endsWith(".mdx"))) packs.push(".ai/context/docs.md");
|
|
272
|
+
if (paths.some((path) => path.endsWith("package.json") || path === "pnpm-workspace.yaml" || path.includes("tsconfig"))) packs.push(".ai/context/package-boundaries.md");
|
|
273
|
+
return packs;
|
|
274
|
+
}
|
|
275
|
+
function packagePacks(routes) {
|
|
276
|
+
const packs = [];
|
|
277
|
+
for (const route of routes) {
|
|
278
|
+
const packageName = route.path.match(/^packages\/([^/]+)$/)?.[1];
|
|
279
|
+
if (packageName) {
|
|
280
|
+
packs.push(...PACKAGE_COMMON_PACKS[packageName] ?? []);
|
|
281
|
+
if (packageName.startsWith("language")) packs.push(".ai/context/testing.md");
|
|
282
|
+
if (packageName.startsWith("eslint-")) packs.push(".ai/context/testing.md");
|
|
283
|
+
}
|
|
284
|
+
if (route.kind === "site") packs.push(".ai/context/docs.md");
|
|
285
|
+
if (route.kind === "shared") packs.push(".ai/context/package-boundaries.md");
|
|
286
|
+
}
|
|
287
|
+
return packs;
|
|
288
|
+
}
|
|
289
|
+
function pushRisk(risks, risk) {
|
|
290
|
+
const existing = risks.find((candidate) => candidate.id === risk.id);
|
|
291
|
+
if (existing) {
|
|
292
|
+
existing.paths = sortStrings([...existing.paths, ...risk.paths]);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
risks.push({
|
|
296
|
+
...risk,
|
|
297
|
+
paths: sortStrings(risk.paths)
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function detectRisks(paths, routes) {
|
|
301
|
+
const risks = [];
|
|
302
|
+
for (const route of routes) {
|
|
303
|
+
const packageName = route.path.match(/^packages\/([^/]+)$/)?.[1];
|
|
304
|
+
if (packageName && HIGH_RISK_PACKAGES[packageName]) pushRisk(risks, {
|
|
305
|
+
id: packageName === "engine" || packageName === "preset" || packageName === "compiler" ? "css-output" : packageName,
|
|
306
|
+
severity: packageName === "engine" || packageName === "runtime" || packageName === "compiler" ? "high" : "warning",
|
|
307
|
+
reason: HIGH_RISK_PACKAGES[packageName],
|
|
308
|
+
paths: route.matchedPaths
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
if (paths.some((path) => path.endsWith("generated.css") || path.endsWith(".snap") || path.includes("/fixtures/"))) pushRisk(risks, {
|
|
312
|
+
id: "fixtures-or-snapshots",
|
|
313
|
+
severity: "high",
|
|
314
|
+
reason: "Fixture, snapshot, or generated CSS changes must be intentional and covered by tests.",
|
|
315
|
+
paths: paths.filter((path) => path.endsWith("generated.css") || path.endsWith(".snap") || path.includes("/fixtures/"))
|
|
316
|
+
});
|
|
317
|
+
if (paths.some((path) => path.endsWith("package.json") || path === "pnpm-workspace.yaml" || path.includes("/src/index.ts"))) pushRisk(risks, {
|
|
318
|
+
id: "public-api-or-package-boundary",
|
|
319
|
+
severity: "warning",
|
|
320
|
+
reason: "Package exports, scripts, dependencies, or public entrypoints may affect downstream consumers.",
|
|
321
|
+
paths: paths.filter((path) => path.endsWith("package.json") || path === "pnpm-workspace.yaml" || path.includes("/src/index.ts"))
|
|
322
|
+
});
|
|
323
|
+
if (paths.some((path) => path.startsWith("site/") || path.endsWith("README.md") || path.endsWith(".mdx"))) pushRisk(risks, {
|
|
324
|
+
id: "docs",
|
|
325
|
+
severity: "info",
|
|
326
|
+
reason: "Public documentation should be verified against source behavior.",
|
|
327
|
+
paths: paths.filter((path) => path.startsWith("site/") || path.endsWith("README.md") || path.endsWith(".mdx"))
|
|
328
|
+
});
|
|
329
|
+
if (paths.some((path) => path.startsWith(".ai/") || path === "AGENTS.md" || path.startsWith(".github/prompts/"))) pushRisk(risks, {
|
|
330
|
+
id: "agent-instructions",
|
|
331
|
+
severity: "info",
|
|
332
|
+
reason: "AI-facing routing changes should stay consistent with context pack rules.",
|
|
333
|
+
paths: paths.filter((path) => path.startsWith(".ai/") || path === "AGENTS.md" || path.startsWith(".github/prompts/"))
|
|
334
|
+
});
|
|
335
|
+
if (routes.length > 1) pushRisk(risks, {
|
|
336
|
+
id: "multi-package",
|
|
337
|
+
severity: "warning",
|
|
338
|
+
reason: "Multiple ownership areas changed; package-boundary context and broader validation may be needed.",
|
|
339
|
+
paths
|
|
340
|
+
});
|
|
341
|
+
return risks.sort((a, b) => {
|
|
342
|
+
const order = {
|
|
343
|
+
high: 0,
|
|
344
|
+
warning: 1,
|
|
345
|
+
info: 2
|
|
346
|
+
};
|
|
347
|
+
return order[a.severity] - order[b.severity] || a.id.localeCompare(b.id);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
function contextFiles(paths, routes, risks, task) {
|
|
351
|
+
const files = [
|
|
352
|
+
"AGENTS.md",
|
|
353
|
+
".ai/context/index.md",
|
|
354
|
+
...pathPacks(paths),
|
|
355
|
+
...packagePacks(routes),
|
|
356
|
+
...taskPacks(task),
|
|
357
|
+
...routes.flatMap((route) => [route.packageJSON, route.aiNotes].filter(Boolean))
|
|
358
|
+
];
|
|
359
|
+
if (risks.some((risk) => risk.severity === "high" || [
|
|
360
|
+
"public-api-or-package-boundary",
|
|
361
|
+
"multi-package",
|
|
362
|
+
"runtime",
|
|
363
|
+
"scanner",
|
|
364
|
+
"source",
|
|
365
|
+
"language",
|
|
366
|
+
"language-service",
|
|
367
|
+
"language-server",
|
|
368
|
+
"lint",
|
|
369
|
+
"eslint-plugin"
|
|
370
|
+
].includes(risk.id))) files.push(".ai/context/accuracy-guardrails.md");
|
|
371
|
+
return sortStrings(files);
|
|
372
|
+
}
|
|
373
|
+
function commandReason(command, reason) {
|
|
374
|
+
return {
|
|
375
|
+
command,
|
|
376
|
+
reason
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
function validationCommands(packages, routes, risks) {
|
|
380
|
+
const commands = [];
|
|
381
|
+
const packageByPath = new Map(packages.map((pkg) => [pkg.path, pkg]));
|
|
382
|
+
for (const route of routes) {
|
|
383
|
+
const pkg = packageByPath.get(route.path);
|
|
384
|
+
if (!pkg || pkg.kind === "root" || pkg.kind === "example") continue;
|
|
385
|
+
if (pkg.scripts.includes("test")) commands.push(commandReason(`pnpm --filter ${pkg.name} test`, `Run ${pkg.name} focused tests.`));
|
|
386
|
+
if (pkg.scripts.includes("e2e") && risks.some((risk) => risk.id === "runtime" || risk.id === "css-output")) commands.push(commandReason(`pnpm --filter ${pkg.name} e2e`, `Run ${pkg.name} browser/e2e coverage for high-risk behavior.`));
|
|
387
|
+
if (pkg.scripts.includes("lint")) commands.push(commandReason(`pnpm --filter ${pkg.name} lint`, `Package-local lint is required when ${pkg.name} changes.`));
|
|
388
|
+
if (pkg.scripts.includes("type-check") && risks.some((risk) => risk.id === "public-api-or-package-boundary")) commands.push(commandReason(`pnpm --filter ${pkg.name} type-check`, `Check public types and package boundary changes in ${pkg.name}.`));
|
|
389
|
+
if (pkg.scripts.includes("build") && risks.some((risk) => risk.id === "public-api-or-package-boundary")) commands.push(commandReason(`pnpm --filter ${pkg.name} build`, `Build ${pkg.name} after public API or export changes.`));
|
|
390
|
+
if (pkg.kind === "site" && pkg.scripts.includes("prepare-app")) commands.push(commandReason("pnpm --filter site prepare-app", "Refresh generated site metadata for docs changes."));
|
|
391
|
+
}
|
|
392
|
+
if (routes.some((route) => route.kind === "agent-docs")) commands.push(commandReason("pnpm run test:docs-consistency", "Validate AI-facing context pack routing consistency."));
|
|
393
|
+
return unique(commands.map((entry) => JSON.stringify(entry))).map((entry) => JSON.parse(entry));
|
|
394
|
+
}
|
|
395
|
+
function routeStatus(context) {
|
|
396
|
+
const supported = isMasterCSSRepository(context);
|
|
397
|
+
return {
|
|
398
|
+
status: supported ? "loaded" : "limited",
|
|
399
|
+
reason: supported ? "Master CSS repository markers were found." : "Contributor routing is optimized for the Master CSS monorepo; generic workspaces return limited package data."
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
async function getPackageGraph(context, input = {}) {
|
|
403
|
+
const support = routeStatus(context);
|
|
404
|
+
const packages = await loadWorkspacePackages(context, input.includeExamples ?? true);
|
|
405
|
+
const filtered = input.packageName ? packages.filter((pkg) => pkg.name === input.packageName || pkg.path === input.packageName) : packages;
|
|
406
|
+
return {
|
|
407
|
+
version: REPORT_VERSION,
|
|
408
|
+
audience: AUDIENCE,
|
|
409
|
+
root: context.root,
|
|
410
|
+
...support,
|
|
411
|
+
packages: filtered,
|
|
412
|
+
summary: {
|
|
413
|
+
packages: filtered.length,
|
|
414
|
+
workspacePackages: filtered.filter((pkg) => pkg.kind === "package").length,
|
|
415
|
+
examples: filtered.filter((pkg) => pkg.kind === "example").length
|
|
416
|
+
}
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
async function buildContributorResult(context, input = {}) {
|
|
420
|
+
const support = routeStatus(context);
|
|
421
|
+
const packages = await loadWorkspacePackages(context);
|
|
422
|
+
const paths = normalizeInputPaths(context, input);
|
|
423
|
+
const routes = routePaths(packages, paths);
|
|
424
|
+
const isLoaded = support.status === "loaded";
|
|
425
|
+
const risks = isLoaded ? detectRisks(paths, routes) : [];
|
|
426
|
+
return {
|
|
427
|
+
version: REPORT_VERSION,
|
|
428
|
+
audience: AUDIENCE,
|
|
429
|
+
root: context.root,
|
|
430
|
+
...support,
|
|
431
|
+
inputs: {
|
|
432
|
+
task: input.task ?? null,
|
|
433
|
+
paths,
|
|
434
|
+
hasDiff: Boolean(input.diff)
|
|
435
|
+
},
|
|
436
|
+
affectedPackages: routes,
|
|
437
|
+
context: {
|
|
438
|
+
files: isLoaded ? contextFiles(paths, routes, risks, input.task) : [],
|
|
439
|
+
note: isLoaded ? "Read these files as routing inputs; source and nearby tests remain the final authority." : "Contributor context routing is limited outside the Master CSS repository; no Master CSS repo-specific context files are recommended."
|
|
440
|
+
},
|
|
441
|
+
risks,
|
|
442
|
+
validation: { commands: isLoaded ? validationCommands(packages, routes, risks) : [] }
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
async function getRepoContext(context, input = {}) {
|
|
446
|
+
try {
|
|
447
|
+
return await buildContributorResult(context, input);
|
|
448
|
+
} catch (error) {
|
|
449
|
+
return {
|
|
450
|
+
version: REPORT_VERSION,
|
|
451
|
+
audience: AUDIENCE,
|
|
452
|
+
root: context.root,
|
|
453
|
+
status: "error",
|
|
454
|
+
error: getErrorMessage(error)
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
async function getChangeImpact(context, input = {}) {
|
|
459
|
+
const result = await getRepoContext(context, input);
|
|
460
|
+
if (result.status === "error") return result;
|
|
461
|
+
return {
|
|
462
|
+
version: REPORT_VERSION,
|
|
463
|
+
audience: AUDIENCE,
|
|
464
|
+
root: context.root,
|
|
465
|
+
status: result.status,
|
|
466
|
+
reason: result.reason,
|
|
467
|
+
inputs: result.inputs,
|
|
468
|
+
affectedPackages: result.affectedPackages,
|
|
469
|
+
risks: result.risks,
|
|
470
|
+
summary: {
|
|
471
|
+
riskCount: result.risks.length,
|
|
472
|
+
highRisk: result.risks.filter((risk) => risk.severity === "high").length,
|
|
473
|
+
packageCount: result.affectedPackages.length
|
|
474
|
+
}
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
async function getTestRouter(context, input = {}) {
|
|
478
|
+
const result = await getRepoContext(context, input);
|
|
479
|
+
if (result.status === "error") return result;
|
|
480
|
+
return {
|
|
481
|
+
version: REPORT_VERSION,
|
|
482
|
+
audience: AUDIENCE,
|
|
483
|
+
root: context.root,
|
|
484
|
+
status: result.status,
|
|
485
|
+
reason: result.reason,
|
|
486
|
+
inputs: result.inputs,
|
|
487
|
+
affectedPackages: result.affectedPackages,
|
|
488
|
+
validation: result.validation,
|
|
489
|
+
risks: result.risks.map((risk) => ({
|
|
490
|
+
id: risk.id,
|
|
491
|
+
severity: risk.severity,
|
|
492
|
+
reason: risk.reason
|
|
493
|
+
}))
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
//#endregion
|
|
497
|
+
export { getChangeImpact, getPackageGraph, getRepoContext, getTestRouter };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import MasterCSSMCPContext from "./context.js";
|
|
2
|
+
|
|
3
|
+
//#region src/directives.d.ts
|
|
4
|
+
interface InspectDirectivesOptions {
|
|
5
|
+
content?: string;
|
|
6
|
+
filePath?: string;
|
|
7
|
+
entryPath?: string;
|
|
8
|
+
preserveNativeCSS?: boolean;
|
|
9
|
+
}
|
|
10
|
+
declare function inspectDirectives(context: MasterCSSMCPContext, options: InspectDirectivesOptions): Promise<{
|
|
11
|
+
diagnostics: {
|
|
12
|
+
code: string;
|
|
13
|
+
severity: "warning";
|
|
14
|
+
message: string;
|
|
15
|
+
source: string;
|
|
16
|
+
sourceKind: string;
|
|
17
|
+
filePath: string;
|
|
18
|
+
}[];
|
|
19
|
+
summary: {
|
|
20
|
+
status: string;
|
|
21
|
+
directives: number;
|
|
22
|
+
classNames: number;
|
|
23
|
+
nativeClassNames: number;
|
|
24
|
+
dependencies: number;
|
|
25
|
+
warnings: number;
|
|
26
|
+
cssBytes: number;
|
|
27
|
+
errors?: undefined;
|
|
28
|
+
};
|
|
29
|
+
manifest: {
|
|
30
|
+
settings: import("@master/css-engine").MasterCSSManifestSettings;
|
|
31
|
+
counts: {
|
|
32
|
+
variables: number;
|
|
33
|
+
variableNamespaces: number;
|
|
34
|
+
utilities: number;
|
|
35
|
+
variants: number;
|
|
36
|
+
atRules: number;
|
|
37
|
+
breakpointAtRules: number;
|
|
38
|
+
containerAtRules: number;
|
|
39
|
+
selectors: number;
|
|
40
|
+
animations: number;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
directives: {
|
|
44
|
+
manifestInput: {
|
|
45
|
+
keys: string[];
|
|
46
|
+
counts: {
|
|
47
|
+
settings: number;
|
|
48
|
+
variables: number;
|
|
49
|
+
utilities: number;
|
|
50
|
+
variants: number;
|
|
51
|
+
atRules: number;
|
|
52
|
+
selectors: number;
|
|
53
|
+
animations: number;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
classNames: string[];
|
|
57
|
+
nativeClassNames: string[];
|
|
58
|
+
styleDefinitions: number;
|
|
59
|
+
extractionPolicy: import("@master/css-schema/css-directives").CSSDirectiveExtractionPolicy;
|
|
60
|
+
};
|
|
61
|
+
css: {
|
|
62
|
+
bytes: number;
|
|
63
|
+
nativeBytes: number;
|
|
64
|
+
generatedBytes: number;
|
|
65
|
+
};
|
|
66
|
+
dependencies: string[];
|
|
67
|
+
warnings: string[];
|
|
68
|
+
version: number;
|
|
69
|
+
root: string;
|
|
70
|
+
status: string;
|
|
71
|
+
input: {
|
|
72
|
+
filePath: string;
|
|
73
|
+
name: string;
|
|
74
|
+
mode: string;
|
|
75
|
+
};
|
|
76
|
+
directiveEntries: {
|
|
77
|
+
name: import("@master/css-lexer").CSSDirectiveRangeName;
|
|
78
|
+
range: {
|
|
79
|
+
start: number;
|
|
80
|
+
end: number;
|
|
81
|
+
};
|
|
82
|
+
loc: {
|
|
83
|
+
start: import("vscode-languageserver-textdocument").Position;
|
|
84
|
+
end: import("vscode-languageserver-textdocument").Position;
|
|
85
|
+
};
|
|
86
|
+
prelude: string;
|
|
87
|
+
hasBlock: boolean;
|
|
88
|
+
quotedStrings: number;
|
|
89
|
+
}[];
|
|
90
|
+
} | {
|
|
91
|
+
version: number;
|
|
92
|
+
root: string;
|
|
93
|
+
status: string;
|
|
94
|
+
input: {
|
|
95
|
+
filePath: string;
|
|
96
|
+
mode: string;
|
|
97
|
+
name?: undefined;
|
|
98
|
+
};
|
|
99
|
+
directiveEntries: {
|
|
100
|
+
name: import("@master/css-lexer").CSSDirectiveRangeName;
|
|
101
|
+
range: {
|
|
102
|
+
start: number;
|
|
103
|
+
end: number;
|
|
104
|
+
};
|
|
105
|
+
loc: {
|
|
106
|
+
start: import("vscode-languageserver-textdocument").Position;
|
|
107
|
+
end: import("vscode-languageserver-textdocument").Position;
|
|
108
|
+
};
|
|
109
|
+
prelude: string;
|
|
110
|
+
hasBlock: boolean;
|
|
111
|
+
quotedStrings: number;
|
|
112
|
+
}[];
|
|
113
|
+
diagnostics: {
|
|
114
|
+
code: string;
|
|
115
|
+
severity: "error";
|
|
116
|
+
message: string;
|
|
117
|
+
source: string;
|
|
118
|
+
sourceKind: string;
|
|
119
|
+
filePath: string;
|
|
120
|
+
}[];
|
|
121
|
+
summary: {
|
|
122
|
+
status: string;
|
|
123
|
+
directives: number;
|
|
124
|
+
classNames: number;
|
|
125
|
+
nativeClassNames: number;
|
|
126
|
+
dependencies: number;
|
|
127
|
+
warnings: number;
|
|
128
|
+
errors: number;
|
|
129
|
+
cssBytes: number;
|
|
130
|
+
};
|
|
131
|
+
}>;
|
|
132
|
+
//#endregion
|
|
133
|
+
export { InspectDirectivesOptions, inspectDirectives };
|