@reqvire-org/ads 0.3.3 → 0.3.5
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 +12 -4
- package/dist/behaviors.mjs +157 -0
- package/dist/bootstrap-ds/ADS.md +7 -0
- package/dist/bootstrap-ds/design-system/controllers/.gitkeep +0 -0
- package/dist/check.mjs +7 -2
- package/dist/cli.mjs +17 -0
- package/dist/config.mjs +19 -3
- package/dist/css-values.mjs +26 -0
- package/dist/index.mjs +2 -0
- package/dist/init.mjs +1 -1
- package/dist/mcp/build-status.mjs +10 -3
- package/dist/mcp/catalog.mjs +11 -8
- package/dist/mcp/context.mjs +2 -2
- package/dist/mcp/mutations.mjs +10 -0
- package/dist/mcp/preview-owner-guard.mjs +29 -0
- package/dist/mcp/preview-watch.mjs +28 -4
- package/dist/mcp/preview-widget.bundle.js +2 -2
- package/dist/mcp/prompts.mjs +2 -2
- package/dist/mcp/server.mjs +56 -19
- package/dist/mcp/showcase-host.mjs +9 -5
- package/dist/public-api.mjs +39 -0
- package/dist/scenarios-compiler.mjs +17 -2
- package/dist/scenarios.mjs +27 -39
- package/dist/skills/README.md +3 -3
- package/dist/skills/ads-break-ui/SKILL.md +22 -0
- package/dist/skills/ads-designer/SKILL.md +23 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +7 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/controllers/.gitkeep +0 -0
- package/dist/skills/ads-designer/references/cli.md +1 -1
- package/dist/skills/ads-designer/references/design-craft.md +1 -1
- package/dist/skills/ads-import/SKILL.md +22 -0
- package/dist/skills/ads-import/references/cli.md +1 -1
- package/dist/skills/{ads-assimilate → ads-migrate}/SKILL.md +28 -6
- package/dist/skills/ads-migrate/agents/openai.yaml +4 -0
- package/dist/skills/{ads-assimilate → ads-migrate}/references/cli.md +1 -1
- package/dist/skills/{ads-assimilate → ads-migrate}/references/native-implementation.md +11 -0
- package/dist/skills/ads-motion/SKILL.md +22 -0
- package/dist/tokens.mjs +3 -2
- package/dist/units.mjs +20 -9
- package/dist/verification/browser.mjs +27 -0
- package/dist/verification/playwright-reporter.cjs +20 -0
- package/dist/verification.mjs +214 -0
- package/docs/ADS.md +27 -2
- package/docs/ARCHITECTURE.md +39 -1
- package/docs/BEHAVIORS.md +230 -0
- package/docs/CHANGELOG.md +36 -0
- package/docs/GUIDE.md +16 -1
- package/docs/MCP.md +46 -7
- package/package.json +4 -2
- package/dist/skills/ads-assimilate/agents/openai.yaml +0 -4
- /package/dist/skills/{ads-assimilate → ads-migrate}/references/lint-and-docs.md +0 -0
- /package/dist/skills/{ads-assimilate → ads-migrate}/references/testing.md +0 -0
package/dist/scenarios.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import ts from "typescript";
|
|
4
|
+
import { publicApi } from "./public-api.mjs";
|
|
4
5
|
import { uiOperation } from "./ui-framework.mjs";
|
|
5
6
|
import { isDevelopmentFile, isInside, isScenarioFile, sourceFiles } from "./config.mjs";
|
|
6
7
|
import { projectCompilerOptions } from "./boundaries.mjs";
|
|
@@ -27,46 +28,17 @@ function literal(node, checker, seen = new Set()) {
|
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
export function publicUi(program, options) {
|
|
30
|
-
|
|
31
|
-
const entry = program.getSourceFile(path.join(options.dsRoot, options.config.publicEntry));
|
|
32
|
-
const module = entry && checker.getSymbolAtLocation(entry);
|
|
33
|
-
const items = [];
|
|
34
|
-
for (const exported of module ? checker.getExportsOfModule(module) : []) {
|
|
35
|
-
let symbol = exported;
|
|
36
|
-
if (symbol.flags & ts.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
|
|
37
|
-
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
38
|
-
if (!declaration || !(symbol.flags & ts.SymbolFlags.Value)) continue;
|
|
39
|
-
const file = declaration.getSourceFile().fileName;
|
|
40
|
-
if (isDevelopmentFile(file, options)) continue;
|
|
41
|
-
const kind = isInside(path.join(options.dsRoot, "components"), file) ? "components" : isInside(path.join(options.dsRoot, "patterns"), file) ? "patterns" : null;
|
|
42
|
-
if (!kind) continue;
|
|
43
|
-
let renderable = false;
|
|
44
|
-
function visit(node) {
|
|
45
|
-
if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node) || ts.isJsxFragment(node)) renderable = true;
|
|
46
|
-
if (ts.isCallExpression(node) && ["createElement", "forwardRef", "memo"].includes(uiOperation(node.expression, checker))) renderable = true;
|
|
47
|
-
if (ts.isTaggedTemplateExpression(node)) {
|
|
48
|
-
let tag = node.tag;
|
|
49
|
-
if (ts.isCallExpression(tag)) tag = tag.expression;
|
|
50
|
-
let base = tag;
|
|
51
|
-
while (ts.isPropertyAccessExpression(base)) base = base.expression;
|
|
52
|
-
const symbol = checker.getSymbolAtLocation(base);
|
|
53
|
-
if (symbol?.declarations?.some(item => ts.isImportSpecifier(item) && (item.propertyName ?? item.name).text === "styled" && item.parent.parent.parent.moduleSpecifier.text.startsWith("@linaria/") || ts.isNamespaceImport(item) && item.parent.parent.moduleSpecifier.text.startsWith("@linaria/") && /^\.styled(?:\.|$)/.test(tag.getText().slice(base.getText().length)))) renderable = true;
|
|
54
|
-
}
|
|
55
|
-
ts.forEachChild(node, visit);
|
|
56
|
-
}
|
|
57
|
-
visit(declaration);
|
|
58
|
-
items.push({ name: exported.name, kind, file, declaration, renderable });
|
|
59
|
-
}
|
|
60
|
-
return items;
|
|
31
|
+
return publicApi(program, options).filter(item => item.kind !== "controllers");
|
|
61
32
|
}
|
|
62
33
|
|
|
63
34
|
/** Discover metadata statically. The MCP process never imports scenario modules. */
|
|
64
35
|
export function inspectScenarios(options, suppliedProgram) {
|
|
65
36
|
const { root, dsRoot } = options;
|
|
66
37
|
const files = sourceFiles(dsRoot).filter(isScenarioFile);
|
|
67
|
-
const program = suppliedProgram ?? ts.createProgram([path.join(dsRoot, options.config.publicEntry), ...files], projectCompilerOptions(options));
|
|
38
|
+
const program = suppliedProgram ?? ts.createProgram([path.join(dsRoot, options.config.publicEntry), ...files], { ...projectCompilerOptions(options), allowJs: true });
|
|
68
39
|
const known = new Map(publicUi(program, options).map((item) => [item.name, item]));
|
|
69
40
|
const checker = program.getTypeChecker();
|
|
41
|
+
const behaviorApis = new Map(publicApi(program, options).filter(item => !item.renderable && item.behavioral).map(item => [item.name, item]));
|
|
70
42
|
const tokenNames = new Set(inspectTokens(root).tokens.map(token => token.name));
|
|
71
43
|
const scenarios = [];
|
|
72
44
|
const diagnostics = [];
|
|
@@ -105,21 +77,22 @@ export function inspectScenarios(options, suppliedProgram) {
|
|
|
105
77
|
}
|
|
106
78
|
const fixtureBase = path.basename(file).startsWith("Scenario.") ? path.join(path.dirname(file), "fixture") : file.replace(/\.scenario\.[^.]+$/, ".fixture");
|
|
107
79
|
if (![".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs", ".json"].some(extension => existsSync(fixtureBase + extension))) report(file, "Keep this scenario's inputs beside it: fixture.ts (directory entry) or <Owner>.fixture.ts (flat entry). An empty fixture is valid for a component without inputs.");
|
|
108
|
-
if (!metadata || Object.keys(metadata).some((key) => !["title", "covers", "tokens", "samples"].includes(key)) || typeof metadata.title !== "string" || !metadata.title.trim() || !Array.isArray(metadata.covers) || !metadata.covers.length || metadata.covers.some((name) => typeof name !== "string" || !known.get(name)?.renderable)
|
|
80
|
+
if (!metadata || Object.keys(metadata).some((key) => !["title", "covers", "exercises", "tokens", "samples"].includes(key)) || typeof metadata.title !== "string" || !metadata.title.trim() || !Array.isArray(metadata.covers) || !metadata.covers.length || metadata.covers.some((name) => typeof name !== "string" || !known.get(name)?.renderable)
|
|
81
|
+
|| Object.hasOwn(metadata, "exercises") && (!Array.isArray(metadata.exercises) || metadata.exercises.some(name => typeof name !== "string" || !behaviorApis.has(name)))
|
|
109
82
|
|| Object.hasOwn(metadata, "tokens") && (!Array.isArray(metadata.tokens) || metadata.tokens.some(name => typeof name !== "string" || !name.startsWith("--")))
|
|
110
83
|
|| kind === "tokens" && (Object.hasOwn(metadata, "tokens") || !validTokenSamples(metadata.samples) || !metadata.covers.some(name => known.get(name)?.kind === "patterns"))
|
|
111
84
|
|| Object.hasOwn(metadata, "samples") && !validTokenSamples(metadata.samples)) {
|
|
112
|
-
report(file, "Export static metadata with nonempty covers. Token scenarios must render a project-owned public review pattern and declare samples: fixture.samples, a literal list of { token, property, probe } CSS samples. A plain value list or covers: [] is incomplete."); continue;
|
|
85
|
+
report(file, "Export static metadata with nonempty covers naming rendered public UI. Optional exercises names public behavioral APIs, not rendered UI. Token scenarios must render a project-owned public review pattern and declare samples: fixture.samples, a literal list of { token, property, probe } CSS samples. A plain value list or covers: [] is incomplete."); continue;
|
|
113
86
|
}
|
|
114
87
|
if (!["composition", "tokens"].includes(kind) && metadata.covers.some((name) => known.get(name).kind !== kind)) report(file, "Component scenarios cover primitives; pattern scenarios cover patterns. Use composition/ for larger interfaces.");
|
|
115
88
|
const tokens = [...new Set([...(metadata.tokens ?? []), ...(metadata.samples ?? []).map(sample => sample.token)])];
|
|
116
89
|
for (const name of tokens) if (!tokenNames.has(name)) diagnostics.push({ file: path.relative(root, file).replaceAll("\\", "/"), rule: "token-scenario-reference", line: 1, column: 1, message: `Scenario references undeclared token ${name}. Use a real foundation token.` });
|
|
117
|
-
scenarios.push({ id, kind, title: metadata.title, covers: [...new Set(metadata.covers)], tokens, ...(metadata.samples ? { samples: metadata.samples } : {}), file: path.relative(root, file).replaceAll("\\", "/"), artifact: path.relative(root, scenarioArtifact(root, id)).replaceAll("\\", "/") });
|
|
90
|
+
scenarios.push({ id, kind, title: metadata.title, covers: [...new Set(metadata.covers)], ...(metadata.exercises ? { exercises: [...new Set(metadata.exercises)] } : {}), tokens, ...(metadata.samples ? { samples: metadata.samples } : {}), file: path.relative(root, file).replaceAll("\\", "/"), artifact: path.relative(root, scenarioArtifact(root, id)).replaceAll("\\", "/") });
|
|
118
91
|
}
|
|
119
92
|
return { scenarios: scenarios.sort((a, b) => a.id.localeCompare(b.id)), diagnostics };
|
|
120
93
|
}
|
|
121
94
|
|
|
122
|
-
export function inspectScenarioCoverage(options, program, report) {
|
|
95
|
+
export function inspectScenarioCoverage(options, program, report, advise = () => {}) {
|
|
123
96
|
const registry = inspectScenarios(options, program);
|
|
124
97
|
for (const item of registry.diagnostics) report(path.join(options.root, item.file), item.rule, item.message, item.line, item.column);
|
|
125
98
|
const checker = program.getTypeChecker();
|
|
@@ -153,7 +126,7 @@ export function inspectScenarioCoverage(options, program, report) {
|
|
|
153
126
|
return symbol.declarations?.length ? symbol : null;
|
|
154
127
|
}
|
|
155
128
|
const identities = new Map();
|
|
156
|
-
for (const item of
|
|
129
|
+
for (const item of publicApi(program, options)) {
|
|
157
130
|
if (!identities.has(item.declaration)) identities.set(item.declaration, new Set());
|
|
158
131
|
identities.get(item.declaration).add(item.name);
|
|
159
132
|
}
|
|
@@ -165,12 +138,16 @@ export function inspectScenarioCoverage(options, program, report) {
|
|
|
165
138
|
for (const scenario of registry.scenarios) {
|
|
166
139
|
const source = program.getSourceFile(path.join(options.root, scenario.file));
|
|
167
140
|
const rendered = new Set();
|
|
141
|
+
const exercised = new Set(), referencedApis = new Set();
|
|
168
142
|
const referencedTokens = new Set();
|
|
169
143
|
let samplesPassed = false;
|
|
170
144
|
const visited = new Set();
|
|
171
145
|
function visit(node, reached = false) {
|
|
172
146
|
if (!node || visited.has(node)) return;
|
|
173
|
-
if (!reached &&
|
|
147
|
+
if (!reached && ts.isFunctionDeclaration(node)) return;
|
|
148
|
+
// Calls/construction in a mounted adapter execute even when only a result
|
|
149
|
+
// property is read later. Unused literal fixture fields still do not count.
|
|
150
|
+
if (!reached && ts.isVariableDeclaration(node) && !(node.initializer && (ts.isCallExpression(node.initializer) || ts.isNewExpression(node.initializer)))) return;
|
|
174
151
|
if (!reached && (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) && !ts.isCallExpression(node.parent) && !ts.isJsxExpression(node.parent)) return;
|
|
175
152
|
visited.add(node);
|
|
176
153
|
if (ts.isStringLiteralLike(node) && (isSharedToken(node.text) || scenario.tokens.includes(node.text))) referencedTokens.add(node.text);
|
|
@@ -180,18 +157,25 @@ export function inspectScenarioCoverage(options, program, report) {
|
|
|
180
157
|
if (expression) {
|
|
181
158
|
const symbol = canonical(symbolOf(expression));
|
|
182
159
|
const names = identities.get(symbol?.valueDeclaration ?? symbol?.declarations?.[0]);
|
|
183
|
-
for (const name of names ?? []) rendered.add(name);
|
|
160
|
+
for (const name of names ?? []) { rendered.add(name); exercised.add(name); }
|
|
184
161
|
if (scenario.kind === "tokens" && [...names ?? []].some(name => scenario.covers.includes(name))) {
|
|
185
162
|
const attributes = ts.isCallExpression(node) ? literal(node.arguments[1], checker) : Object.assign({}, ...node.attributes.properties.map(attribute => ts.isJsxSpreadAttribute(attribute) ? literal(attribute.expression, checker) : ts.isJsxAttribute(attribute) && ts.isIdentifier(attribute.name) && attribute.initializer && ts.isJsxExpression(attribute.initializer) ? { [attribute.name.text]: literal(attribute.initializer.expression, checker) } : {}));
|
|
186
163
|
if (JSON.stringify(attributes?.samples) === JSON.stringify(scenario.samples)) samplesPassed = true;
|
|
187
164
|
}
|
|
188
165
|
}
|
|
166
|
+
if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
|
|
167
|
+
let expression = node.expression;
|
|
168
|
+
if (ts.isPropertyAccessExpression(expression) && ["call", "apply"].includes(expression.name.text)) expression = expression.expression;
|
|
169
|
+
const symbol = canonical(symbolOf(expression));
|
|
170
|
+
for (const name of identities.get(symbol?.valueDeclaration ?? symbol?.declarations?.[0]) ?? []) exercised.add(name);
|
|
171
|
+
}
|
|
189
172
|
// Follow local adapters reachable from the default entry. Unused example
|
|
190
173
|
// functions do not satisfy coverage; production implementations are never
|
|
191
174
|
// traversed to manufacture coverage for their internal dependencies.
|
|
192
175
|
if (ts.isPropertyAccessExpression(node) || ts.isElementAccessExpression(node) || ts.isIdentifier(node) && !(ts.isPropertyAccessExpression(node.parent) || ts.isElementAccessExpression(node.parent))) {
|
|
193
176
|
const symbol = canonical(symbolOf(node));
|
|
194
177
|
const declaration = symbol?.valueDeclaration;
|
|
178
|
+
for (const name of identities.get(declaration ?? symbol?.declarations?.[0]) ?? []) referencedApis.add(name);
|
|
195
179
|
if (declaration && isDevelopmentFile(declaration.getSourceFile().fileName, options)) visit(declaration, true);
|
|
196
180
|
}
|
|
197
181
|
ts.forEachChild(node, child => visit(child, reached && (ts.isExportAssignment(node) || ts.isVariableDeclaration(node))));
|
|
@@ -201,6 +185,10 @@ export function inspectScenarioCoverage(options, program, report) {
|
|
|
201
185
|
if (!rendered.has(name)) report(source.fileName, "scenario-render", `${name} is declared in covers but has no public render reference in this scenario. Render the real API; an import alone does not count.`);
|
|
202
186
|
else covered.add(name);
|
|
203
187
|
}
|
|
188
|
+
for (const name of scenario.exercises ?? []) {
|
|
189
|
+
if (!exercised.has(name) && !referencedApis.has(name)) report(source.fileName, "scenario-exercise", `${name} is declared in exercises but has no reachable behavioral use. Imports and unused adapters do not count.`);
|
|
190
|
+
else if (!exercised.has(name)) advise(source.fileName, "scenario-exercise-review", `${name} is referenced dynamically. Verify its invocation in the registered browser/behavior checks; a reference alone does not prove execution.`);
|
|
191
|
+
}
|
|
204
192
|
for (const name of referencedTokens) if (!declaredTokens.has(name)) report(source.fileName, "token-scenario-reference", `Reachable scenario input references undeclared token ${name}.`);
|
|
205
193
|
if (scenario.kind === "tokens" && !samplesPassed) report(source.fileName, "token-scenario-render", "Pass the declared fixture.samples into a rendered public review pattern's samples prop. Printing token names or reading unused fixtures is not sample coverage.");
|
|
206
194
|
for (const name of scenario.tokens) {
|
package/dist/skills/README.md
CHANGED
|
@@ -3,13 +3,13 @@
|
|
|
3
3
|
Install the skills you need with `npx skills`:
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npx skills add reqvire-org/ads --skill ads-designer --skill ads-import --skill ads-
|
|
6
|
+
npx skills add reqvire-org/ads --skill ads-designer --skill ads-import --skill ads-migrate --skill ads-break-ui --skill ads-motion
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
For local development, install from this checkout:
|
|
10
10
|
|
|
11
11
|
```sh
|
|
12
|
-
npx skills add ./skills --skill ads-designer --skill ads-import --skill ads-
|
|
12
|
+
npx skills add ./skills --skill ads-designer --skill ads-import --skill ads-migrate --skill ads-break-ui --skill ads-motion
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Each skill includes its own instructions and references. `ads-designer` also
|
|
@@ -36,7 +36,7 @@ the browser, and provides a running URL or exact command for review. See the
|
|
|
36
36
|
|-------|-----|---------|
|
|
37
37
|
| [ads-designer](ads-designer/SKILL.md) | Bootstrap or build UI using the local ADS, with craft/mobile guidance | `$ads-designer build a settings page using this project's ADS` |
|
|
38
38
|
| [ads-import](ads-import/SKILL.md) | Import selected external ADS source, brand artwork, or compatible source token values | `$ads-import /path/to/source-repo component Toast` |
|
|
39
|
-
| [ads-
|
|
39
|
+
| [ads-migrate](ads-migrate/SKILL.md) | Study an external component library and implement a native counterpart | `$ads-migrate https://github.com/emilkowalski/sonner` |
|
|
40
40
|
| [ads-break-ui](ads-break-ui/SKILL.md) | Stress-test real components/patterns; optionally fix demonstrated failures | `$ads-break-ui AppShell +fix` |
|
|
41
41
|
| [ads-motion](ads-motion/SKILL.md) | Implement, review, audit, or profile motion | `$ads-motion review Toast` |
|
|
42
42
|
|
|
@@ -46,6 +46,23 @@ ADS gates. For binary/large assets, generate `git diff --binary --full-index` in
|
|
|
46
46
|
stay out of model context. Binary changes require Git. When editing is disabled,
|
|
47
47
|
use the client's normal source-editing tools.
|
|
48
48
|
|
|
49
|
+
|
|
50
|
+
Controllers are UI behavior owners: private helpers stay with their component or
|
|
51
|
+
pattern; reusable controllers use discovered `controllers/<Name>` units. Use the
|
|
52
|
+
project's configured framework and declared dependencies, or plain TypeScript,
|
|
53
|
+
browser APIs and local helpers. MCP hosts supply no ad hoc runtime APIs. The
|
|
54
|
+
framework setting selects compilation/mounting; controller checks also validate
|
|
55
|
+
imports. For controller/behavior work, read `ads://guides/behaviors` (offline:
|
|
56
|
+
the installed ADS package's `docs/BEHAVIORS.md`). Discover registered guarantees
|
|
57
|
+
with `ads_behaviors`; use `ads_verify` for selected real checks. Optional
|
|
58
|
+
`exercises` links public behavioral APIs to scenarios; keep `covers` for rendered
|
|
59
|
+
UI. Registration and demonstration are not passing evidence. `behavior.json`,
|
|
60
|
+
scenarios, fixtures and checks stay out of production exports/imports. Verify
|
|
61
|
+
current results, including lifecycle setup/cleanup/setup and the actual browser
|
|
62
|
+
profile when promised. React StrictMode needs a development harness; wrapping a
|
|
63
|
+
production widget cannot replay development effects. Keep dynamic behavior and
|
|
64
|
+
capabilities intact rather than rewriting them only for static coverage.
|
|
65
|
+
|
|
49
66
|
Before trusting a preview after edits, check `ads_status.build` or
|
|
50
67
|
`ads://project/build`. Fix build diagnostics in owned source; an older view or
|
|
51
68
|
unreported freshness is not evidence of current code. Notifications may not
|
|
@@ -226,6 +243,11 @@ Do not copy an unchecked upstream preflight or turn a component repair into a re
|
|
|
226
243
|
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
227
244
|
in a public review/layout pattern; add component props for a deliberate runtime
|
|
228
245
|
API need. Preserve required capabilities while following ownership rules.
|
|
246
|
+
Use tokens for absolute/font-relative lengths, including `ch`, `ex`, `cap`, `ic`
|
|
247
|
+
and `lh`; calculations and fallback values follow the same rule. Structural
|
|
248
|
+
angle units remain available for orientation, skew and spin. Put tunable visual
|
|
249
|
+
angles in project tokens; static checks cannot infer that intent. Percentages,
|
|
250
|
+
grid fractions and viewport/container relationships retain structural use.
|
|
229
251
|
Browser review still verifies the effects.
|
|
230
252
|
|
|
231
253
|
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
@@ -78,6 +78,23 @@ ADS gates. For binary/large assets, generate `git diff --binary --full-index` in
|
|
|
78
78
|
stay out of model context. Binary changes require Git. When editing is disabled,
|
|
79
79
|
use the client's normal source-editing tools.
|
|
80
80
|
|
|
81
|
+
Controllers are UI behavior owners: private helpers stay with their component or
|
|
82
|
+
pattern; reusable controllers use discovered `controllers/<Name>` units. Use the
|
|
83
|
+
project's configured framework and declared dependencies, or plain TypeScript,
|
|
84
|
+
browser APIs and local helpers. MCP hosts supply no ad hoc runtime APIs. The
|
|
85
|
+
framework setting selects compilation/mounting; controller checks also validate
|
|
86
|
+
imports. For controller/behavior work, read `ads://guides/behaviors` (offline:
|
|
87
|
+
the installed ADS package's `docs/BEHAVIORS.md`). Discover registered guarantees
|
|
88
|
+
with `ads_behaviors`; use `ads_verify` for selected real checks. Optional
|
|
89
|
+
`exercises` links public behavioral APIs to scenarios; keep `covers` for rendered
|
|
90
|
+
UI. Registration and demonstration are not passing evidence. `behavior.json`,
|
|
91
|
+
scenarios, fixtures and checks stay out of production exports/imports. Verify
|
|
92
|
+
current results, including lifecycle setup/cleanup/setup and the actual browser
|
|
93
|
+
profile when promised. React StrictMode needs a development harness; wrapping a
|
|
94
|
+
production widget cannot replay development effects. Keep dynamic behavior and
|
|
95
|
+
capabilities intact rather than rewriting them only for static coverage.
|
|
96
|
+
|
|
97
|
+
|
|
81
98
|
Check `ads_status.previewWatch` before launching another preview builder. With a
|
|
82
99
|
managed watcher, edit owned sources, wait for the rebuild and invoke the preview
|
|
83
100
|
again; the client already starts and stops the builder through `ads mcp`. Check
|
|
@@ -153,7 +170,7 @@ Do not assume it has a particular component, hook, font, or token helper.
|
|
|
153
170
|
Record the source revision and required dependencies.
|
|
154
171
|
|
|
155
172
|
Use the ADS import workflow to bring selected existing ADS items into the target,
|
|
156
|
-
or
|
|
173
|
+
or migration to create a native component from an upstream library. Follow
|
|
157
174
|
those workflows directly if the corresponding skills are unavailable: copy
|
|
158
175
|
selected source and its dependency closure, or study/reproduce the upstream
|
|
159
176
|
contract; integrate local ownership, exports, styles, assets, credits, applicable
|
|
@@ -364,6 +381,11 @@ Do not copy an unchecked upstream preflight or turn a component repair into a re
|
|
|
364
381
|
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
365
382
|
in a public review/layout pattern; add component props for a deliberate runtime
|
|
366
383
|
API need. Preserve required capabilities while following ownership rules.
|
|
384
|
+
Use tokens for absolute/font-relative lengths, including `ch`, `ex`, `cap`, `ic`
|
|
385
|
+
and `lh`; calculations and fallback values follow the same rule. Structural
|
|
386
|
+
angle units remain available for orientation, skew and spin. Put tunable visual
|
|
387
|
+
angles in project tokens; static checks cannot infer that intent. Percentages,
|
|
388
|
+
grid fractions and viewport/container relationships retain structural use.
|
|
367
389
|
Browser review still verifies the effects.
|
|
368
390
|
|
|
369
391
|
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
@@ -241,3 +241,10 @@ Run `npm run build` after adding or changing components. Add meaningful tests
|
|
|
241
241
|
for stateful behavior, accessibility, gestures, or async lifecycle where needed.
|
|
242
242
|
Run `npm run dev` to inspect the real showcase. A screenshot alone does not
|
|
243
243
|
verify interaction or animation. Generated output and dependencies are not source.
|
|
244
|
+
|
|
245
|
+
Shared behavior may live in `controllers/<Name>/`; private controllers stay with
|
|
246
|
+
their component/pattern. Use the configured framework and declared dependencies.
|
|
247
|
+
Behavior contracts/checks are development-only, and scenario `exercises` links
|
|
248
|
+
public hooks to demonstrations separately from rendered `covers`. Read the
|
|
249
|
+
installed ADS package's `docs/BEHAVIORS.md` or MCP `ads://guides/behaviors` before
|
|
250
|
+
registering guarantees, profiles and verification.
|
|
File without changes
|
|
@@ -102,7 +102,7 @@ script but does not install the project preview compiler. Integrate its graph
|
|
|
102
102
|
compiler/mount adapter and configure `mcp.previewManifest` plus `mcp.previewWatch`;
|
|
103
103
|
then use the same foundation and Linaria extraction as the application. Source discovery,
|
|
104
104
|
selected component/pattern/style/brand imports, explicit theme-value imports, and
|
|
105
|
-
|
|
105
|
+
migration are agent workflows. There is no CLI `import` command. Core checks
|
|
106
106
|
do not copy source, import values, or establish behavior/motion parity; continue
|
|
107
107
|
project type checking, meaningful tests, and browser comparisons.
|
|
108
108
|
|
|
@@ -56,7 +56,7 @@ changing them to a generic default.
|
|
|
56
56
|
|
|
57
57
|
Keep typed props, examples, state behavior, token hooks, and important tradeoffs
|
|
58
58
|
in the item README. Record concrete upstream influences, authors, revision, and
|
|
59
|
-
applicable notices when importing or
|
|
59
|
+
applicable notices when importing or migrating source. Do not describe an
|
|
60
60
|
adapted implementation as independent solely because it uses native primitives.
|
|
61
61
|
|
|
62
62
|
Run the actual project's checks after changes and inspect the real consumer
|
|
@@ -42,6 +42,23 @@ ADS gates. For binary/large assets, generate `git diff --binary --full-index` in
|
|
|
42
42
|
stay out of model context. Binary changes require Git. When editing is disabled,
|
|
43
43
|
use the client's normal source-editing tools.
|
|
44
44
|
|
|
45
|
+
Controllers are UI behavior owners: private helpers stay with their component or
|
|
46
|
+
pattern; reusable controllers use discovered `controllers/<Name>` units. Use the
|
|
47
|
+
project's configured framework and declared dependencies, or plain TypeScript,
|
|
48
|
+
browser APIs and local helpers. MCP hosts supply no ad hoc runtime APIs. The
|
|
49
|
+
framework setting selects compilation/mounting; controller checks also validate
|
|
50
|
+
imports. For controller/behavior work, read `ads://guides/behaviors` (offline:
|
|
51
|
+
the installed ADS package's `docs/BEHAVIORS.md`). Discover registered guarantees
|
|
52
|
+
with `ads_behaviors`; use `ads_verify` for selected real checks. Optional
|
|
53
|
+
`exercises` links public behavioral APIs to scenarios; keep `covers` for rendered
|
|
54
|
+
UI. Registration and demonstration are not passing evidence. `behavior.json`,
|
|
55
|
+
scenarios, fixtures and checks stay out of production exports/imports. Verify
|
|
56
|
+
current results, including lifecycle setup/cleanup/setup and the actual browser
|
|
57
|
+
profile when promised. React StrictMode needs a development harness; wrapping a
|
|
58
|
+
production widget cannot replay development effects. Keep dynamic behavior and
|
|
59
|
+
capabilities intact rather than rewriting them only for static coverage.
|
|
60
|
+
|
|
61
|
+
|
|
45
62
|
Before trusting a preview after edits, check `ads_status.build` or
|
|
46
63
|
`ads://project/build`. Fix build diagnostics in owned source; an older view or
|
|
47
64
|
unreported freshness is not evidence of current code. Notifications may not
|
|
@@ -331,6 +348,11 @@ Do not copy an unchecked upstream preflight or turn a component repair into a re
|
|
|
331
348
|
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
332
349
|
in a public review/layout pattern; add component props for a deliberate runtime
|
|
333
350
|
API need. Preserve required capabilities while following ownership rules.
|
|
351
|
+
Use tokens for absolute/font-relative lengths, including `ch`, `ex`, `cap`, `ic`
|
|
352
|
+
and `lh`; calculations and fallback values follow the same rule. Structural
|
|
353
|
+
angle units remain available for orientation, skew and spin. Put tunable visual
|
|
354
|
+
angles in project tokens; static checks cannot infer that intent. Percentages,
|
|
355
|
+
grid fractions and viewport/container relationships retain structural use.
|
|
334
356
|
Browser review still verifies the effects.
|
|
335
357
|
|
|
336
358
|
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
@@ -102,7 +102,7 @@ script but does not install the project preview compiler. Integrate its graph
|
|
|
102
102
|
compiler/mount adapter and configure `mcp.previewManifest` plus `mcp.previewWatch`;
|
|
103
103
|
then use the same foundation and Linaria extraction as the application. Source discovery,
|
|
104
104
|
selected component/pattern/style/brand imports, explicit theme-value imports, and
|
|
105
|
-
|
|
105
|
+
migration are agent workflows. There is no CLI `import` command. Core checks
|
|
106
106
|
do not copy source, import values, or establish behavior/motion parity; continue
|
|
107
107
|
project type checking, meaningful tests, and browser comparisons.
|
|
108
108
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: ads-
|
|
3
|
-
description: "Implement or refine a native design-system component from a component-library URL, studying the original implementation and verifying behavior, visuals, and motion against the real reference. Use for component
|
|
2
|
+
name: ads-migrate
|
|
3
|
+
description: "Implement or refine a native design-system component from a component-library URL, studying the original implementation and verifying behavior, visuals, and motion against the real reference. Use for component migration, including local documentation and justified usage lint rules; not for simply installing or wrapping a dependency."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# ADS
|
|
6
|
+
# ADS Migrate
|
|
7
7
|
|
|
8
8
|
When ADS MCP is connected, discover public UI and addressable scenarios with
|
|
9
9
|
`ads_status`/`ads_catalog`; use `ads_tokens` for definitions and values, and
|
|
@@ -43,6 +43,23 @@ ADS gates. For binary/large assets, generate `git diff --binary --full-index` in
|
|
|
43
43
|
stay out of model context. Binary changes require Git. When editing is disabled,
|
|
44
44
|
use the client's normal source-editing tools.
|
|
45
45
|
|
|
46
|
+
|
|
47
|
+
Controllers are UI behavior owners: private helpers stay with their component or
|
|
48
|
+
pattern; reusable controllers use discovered `controllers/<Name>` units. Use the
|
|
49
|
+
project's configured framework and declared dependencies, or plain TypeScript,
|
|
50
|
+
browser APIs and local helpers. MCP hosts supply no ad hoc runtime APIs. The
|
|
51
|
+
framework setting selects compilation/mounting; controller checks also validate
|
|
52
|
+
imports. For controller/behavior work, read `ads://guides/behaviors` (offline:
|
|
53
|
+
the installed ADS package's `docs/BEHAVIORS.md`). Discover registered guarantees
|
|
54
|
+
with `ads_behaviors`; use `ads_verify` for selected real checks. Optional
|
|
55
|
+
`exercises` links public behavioral APIs to scenarios; keep `covers` for rendered
|
|
56
|
+
UI. Registration and demonstration are not passing evidence. `behavior.json`,
|
|
57
|
+
scenarios, fixtures and checks stay out of production exports/imports. Verify
|
|
58
|
+
current results, including lifecycle setup/cleanup/setup and the actual browser
|
|
59
|
+
profile when promised. React StrictMode needs a development harness; wrapping a
|
|
60
|
+
production widget cannot replay development effects. Keep dynamic behavior and
|
|
61
|
+
capabilities intact rather than rewriting them only for static coverage.
|
|
62
|
+
|
|
46
63
|
Before trusting a preview after edits, check `ads_status.build` or
|
|
47
64
|
`ads://project/build`. Fix build diagnostics in owned source; an older view or
|
|
48
65
|
unreported freshness is not evidence of current code. Notifications may not
|
|
@@ -75,8 +92,8 @@ workflows. Do not look for a built-in or optional UI catalogue in the tool.
|
|
|
75
92
|
Invocation accepts a library/repository/documentation URL as its first argument:
|
|
76
93
|
|
|
77
94
|
```text
|
|
78
|
-
$ads-
|
|
79
|
-
$ads-
|
|
95
|
+
$ads-migrate <library-url> [component-name] [requirements]
|
|
96
|
+
$ads-migrate https://github.com/emilkowalski/sonner toast
|
|
80
97
|
```
|
|
81
98
|
|
|
82
99
|
The URL is the required reference, not a request to install the library into the
|
|
@@ -147,7 +164,7 @@ the scenario; larger entries belong under `composition/`. Browsing pages only
|
|
|
147
164
|
select these entries. The scenario labels, controls and review layouts are public ADS entities with defined
|
|
148
165
|
APIs. The outer shell belongs to the tool client. Showcase/scenario files define no CSS, custom classes, or widget markup.
|
|
149
166
|
Change the owning ADS pattern when a frame layout is missing; do not recreate
|
|
150
|
-
the
|
|
167
|
+
the migrated UI there or override its internals.
|
|
151
168
|
|
|
152
169
|
The showcase is a minimal shell plus an MCP client and Apps host, supplied by ADS
|
|
153
170
|
tooling. It discovers Tokens/Components/Patterns/Composition entries through MCP
|
|
@@ -274,6 +291,11 @@ Do not copy an unchecked upstream preflight or turn a component repair into a re
|
|
|
274
291
|
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
275
292
|
in a public review/layout pattern; add component props for a deliberate runtime
|
|
276
293
|
API need. Preserve required capabilities while following ownership rules.
|
|
294
|
+
Use tokens for absolute/font-relative lengths, including `ch`, `ex`, `cap`, `ic`
|
|
295
|
+
and `lh`; calculations and fallback values follow the same rule. Structural
|
|
296
|
+
angle units remain available for orientation, skew and spin. Put tunable visual
|
|
297
|
+
angles in project tokens; static checks cannot infer that intent. Percentages,
|
|
298
|
+
grid fractions and viewport/container relationships retain structural use.
|
|
277
299
|
Browser review still verifies the effects.
|
|
278
300
|
|
|
279
301
|
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
@@ -102,7 +102,7 @@ script but does not install the project preview compiler. Integrate its graph
|
|
|
102
102
|
compiler/mount adapter and configure `mcp.previewManifest` plus `mcp.previewWatch`;
|
|
103
103
|
then use the same foundation and Linaria extraction as the application. Source discovery,
|
|
104
104
|
selected component/pattern/style/brand imports, explicit theme-value imports, and
|
|
105
|
-
|
|
105
|
+
migration are agent workflows. There is no CLI `import` command. Core checks
|
|
106
106
|
do not copy source, import values, or establish behavior/motion parity; continue
|
|
107
107
|
project type checking, meaningful tests, and browser comparisons.
|
|
108
108
|
|
|
@@ -112,3 +112,14 @@ README and in a References and credits section in the component-local README.
|
|
|
112
112
|
Link the library and the actual source/docs/articles studied, identify the
|
|
113
113
|
version or commit, name the authors, and explain the concrete influences.
|
|
114
114
|
Document material adaptations and agreed deviations beside the public contract.
|
|
115
|
+
|
|
116
|
+
## Behavioral evidence
|
|
117
|
+
|
|
118
|
+
Keep private controllers in their owner; extract a shared controller only for a
|
|
119
|
+
real reuse need. Register optional behavior guarantees and real colocated checks;
|
|
120
|
+
see `ads://guides/behaviors` or the installed package's `docs/BEHAVIORS.md`. Link
|
|
121
|
+
public hooks with scenario `exercises`, while `covers` still requires rendered UI.
|
|
122
|
+
Move reviewed scratchpad investigations into maintained owner checks. Disabling
|
|
123
|
+
StrictMode can isolate an upstream lifecycle defect; keep a development StrictMode
|
|
124
|
+
check and verify the native port's setup/cleanup/setup independently. Use first-
|
|
125
|
+
render browser cases for lazy intrinsic sizing, retaining offscreen rendering.
|
|
@@ -63,6 +63,23 @@ ADS gates. For binary/large assets, generate `git diff --binary --full-index` in
|
|
|
63
63
|
stay out of model context. Binary changes require Git. When editing is disabled,
|
|
64
64
|
use the client's normal source-editing tools.
|
|
65
65
|
|
|
66
|
+
|
|
67
|
+
Controllers are UI behavior owners: private helpers stay with their component or
|
|
68
|
+
pattern; reusable controllers use discovered `controllers/<Name>` units. Use the
|
|
69
|
+
project's configured framework and declared dependencies, or plain TypeScript,
|
|
70
|
+
browser APIs and local helpers. MCP hosts supply no ad hoc runtime APIs. The
|
|
71
|
+
framework setting selects compilation/mounting; controller checks also validate
|
|
72
|
+
imports. For controller/behavior work, read `ads://guides/behaviors` (offline:
|
|
73
|
+
the installed ADS package's `docs/BEHAVIORS.md`). Discover registered guarantees
|
|
74
|
+
with `ads_behaviors`; use `ads_verify` for selected real checks. Optional
|
|
75
|
+
`exercises` links public behavioral APIs to scenarios; keep `covers` for rendered
|
|
76
|
+
UI. Registration and demonstration are not passing evidence. `behavior.json`,
|
|
77
|
+
scenarios, fixtures and checks stay out of production exports/imports. Verify
|
|
78
|
+
current results, including lifecycle setup/cleanup/setup and the actual browser
|
|
79
|
+
profile when promised. React StrictMode needs a development harness; wrapping a
|
|
80
|
+
production widget cannot replay development effects. Keep dynamic behavior and
|
|
81
|
+
capabilities intact rather than rewriting them only for static coverage.
|
|
82
|
+
|
|
66
83
|
Before trusting a preview after edits, check `ads_status.build` or
|
|
67
84
|
`ads://project/build`. Fix build diagnostics in owned source; an older view or
|
|
68
85
|
unreported freshness is not evidence of current code. Notifications may not
|
|
@@ -221,6 +238,11 @@ Do not copy an unchecked upstream preflight or turn a component repair into a re
|
|
|
221
238
|
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
222
239
|
in a public review/layout pattern; add component props for a deliberate runtime
|
|
223
240
|
API need. Preserve required capabilities while following ownership rules.
|
|
241
|
+
Use tokens for absolute/font-relative lengths, including `ch`, `ex`, `cap`, `ic`
|
|
242
|
+
and `lh`; calculations and fallback values follow the same rule. Structural
|
|
243
|
+
angle units remain available for orientation, skew and spin. Put tunable visual
|
|
244
|
+
angles in project tokens; static checks cannot infer that intent. Percentages,
|
|
245
|
+
grid fractions and viewport/container relationships retain structural use.
|
|
224
246
|
Browser review still verifies the effects.
|
|
225
247
|
|
|
226
248
|
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
package/dist/tokens.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import postcss from "postcss";
|
|
|
4
4
|
import valueParser from "postcss-value-parser";
|
|
5
5
|
import { loadConfig, sourceFiles } from "./config.mjs";
|
|
6
6
|
import { foundationFiles, isSharedToken, tokenContract } from "./token-policy.mjs";
|
|
7
|
+
import { dimensionValue, numericCssUnit } from "./css-values.mjs";
|
|
7
8
|
import { checkComponentVariable } from "./boundaries.mjs";
|
|
8
9
|
|
|
9
10
|
export { tokenContract };
|
|
@@ -33,8 +34,8 @@ function matchesType(value, type) {
|
|
|
33
34
|
if (/^(?:inherit|initial|unset|revert(?:-layer)?)$/.test(value)) return true;
|
|
34
35
|
switch (type) {
|
|
35
36
|
case "color": return /^(?:#[\da-f]{3,8}|[a-z][\w-]*|(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklab|oklch|color|color-mix|light-dark)\([\s\S]+\))$/i.test(value);
|
|
36
|
-
case "dimension": return
|
|
37
|
-
case "duration": return
|
|
37
|
+
case "dimension": return dimensionValue(value);
|
|
38
|
+
case "duration": return ["s", "ms"].includes(numericCssUnit(value));
|
|
38
39
|
case "number": return /^[-+]?(?:\d*\.)?\d+$/.test(value);
|
|
39
40
|
case "easing": return /^(?:linear|ease(?:-in|-out|-in-out)?|step-start|step-end|(?:cubic-bezier|steps|linear)\([\s\S]+\))$/.test(value);
|
|
40
41
|
case "shadow": return value === "none" || /(?:\d(?:px|rem|em)|var\()/.test(value);
|
package/dist/units.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import ts from "typescript";
|
|
4
4
|
import postcss from "postcss";
|
|
5
|
-
import { isInside } from "./config.mjs";
|
|
5
|
+
import { isDevelopmentFile, isInside } from "./config.mjs";
|
|
6
6
|
import { importResolver } from "./boundaries.mjs";
|
|
7
7
|
import { isProjectDocument, projectGuideFiles } from "./project-documents.mjs";
|
|
8
8
|
import { foundationFiles, foundationImportTarget } from "./token-policy.mjs";
|
|
@@ -16,14 +16,21 @@ export function configurationFiles(options) {
|
|
|
16
16
|
const { root, config, sourceRoot, dsRoot } = options;
|
|
17
17
|
const files = new Set(["ds.config.json", "package.json"]);
|
|
18
18
|
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
19
|
-
if (entry.isFile() && /^(?:(?:ts|js)config[^/]*\.jsonc?|(?:vite|vitest|linaria|babel|postcss|biome|oxlint)[^/]*\.(?:jsonc?|[cm]?[jt]s)|\.(?:oxlintrc|linariarc|babelrc)(?:\.json)?)$/.test(entry.name)) files.add(entry.name);
|
|
19
|
+
if (entry.isFile() && /^(?:(?:ts|js)config[^/]*\.jsonc?|(?:vite|vitest|playwright|linaria|babel|postcss|biome|oxlint)[^/]*\.(?:jsonc?|[cm]?[jt]s)|\.(?:oxlintrc|linariarc|babelrc)(?:\.json)?)$/.test(entry.name)) files.add(entry.name);
|
|
20
20
|
}
|
|
21
21
|
const resolve = importResolver(options), visited = new Set();
|
|
22
22
|
function visit(file, entrypoint = false, typeConfig = false) {
|
|
23
23
|
if (!isInside(root, file) || file === root || /(?:^|[\\/])(?:node_modules|\.git|\.ds|dist)(?:[\\/]|$)/.test(path.relative(root, file)) || visited.has(file) || !existsSync(file)) return;
|
|
24
|
+
// Registering a test command must not reassign its colocated checks or
|
|
25
|
+
// fixtures to configuration. Those remain in the behavior owner's patch.
|
|
26
|
+
if (isInside(dsRoot, file) && isDevelopmentFile(file, options)) return;
|
|
24
27
|
if (!entrypoint && isInside(sourceRoot, file) && !isInside(dsRoot, file)) return;
|
|
25
|
-
if (!entrypoint && isInside(dsRoot, file) && /^(?:components|patterns|composition|tokens)[\\/]/.test(path.relative(dsRoot, file))) {
|
|
26
|
-
if (
|
|
28
|
+
if (!entrypoint && isInside(dsRoot, file) && /^(?:components|patterns|controllers|composition|tokens)[\\/]/.test(path.relative(dsRoot, file))) {
|
|
29
|
+
if (isInside(path.join(dsRoot, "controllers"), file) || isDevelopmentFile(file, options)) return;
|
|
30
|
+
const folder = path.dirname(file), stem = path.basename(file).split(".")[0];
|
|
31
|
+
const anchored = ["index", path.basename(folder)].some(name => [".ts", ".tsx", ".js", ".jsx", ".mjs", ".mts"].some(extension => existsSync(path.join(folder, name + extension))));
|
|
32
|
+
const sidecar = path.basename(file).split(".").length > 2 && [".ts", ".tsx", ".js", ".jsx", ".mjs", ".mts"].some(extension => existsSync(path.join(folder, stem + extension)));
|
|
33
|
+
if (anchored || sidecar || /\.[jt]sx$/.test(file)) return;
|
|
27
34
|
if (source.test(file) && /(?:from\s*["'](?:react|preact|@linaria\/)|\b(?:require|import)\s*\(\s*["'](?:react|preact|@linaria\/))/.test(readFileSync(file,"utf8"))) return;
|
|
28
35
|
}
|
|
29
36
|
visited.add(file);
|
|
@@ -65,6 +72,10 @@ export function configurationFiles(options) {
|
|
|
65
72
|
if (["npm", "bun"].includes(watch.command) && watch.args[0] === "run") script(watch.args[1]);
|
|
66
73
|
else for (const argument of [watch.command, ...watch.args]) if (/\.[cm]?[jt]sx?$/.test(argument)) visit(path.resolve(root, argument), true);
|
|
67
74
|
}
|
|
75
|
+
for (const profile of Object.values(config.mcp.behaviorProfiles)) {
|
|
76
|
+
if (["npm", "bun"].includes(profile.command) && profile.args[0] === "run") script(profile.args[1]);
|
|
77
|
+
else for (const argument of [profile.command, ...profile.args]) if (/\.[cm]?[jt]sx?$/.test(argument)) visit(path.resolve(root, argument), true);
|
|
78
|
+
}
|
|
68
79
|
return files;
|
|
69
80
|
}
|
|
70
81
|
|
|
@@ -90,14 +101,14 @@ export function inspectUnits(options) {
|
|
|
90
101
|
const flatFamilies = new Set(files.filter(file => /\.scenario\.[cm]?[jt]sx?$/.test(file)).map(file => path.posix.dirname(file)));
|
|
91
102
|
const directories = new Set(files.filter(file => /(?:^|\/)Scenario\.[cm]?[jt]sx?$/.test(file)).map(file => slash(path.dirname(file))));
|
|
92
103
|
for (const file of files) {
|
|
93
|
-
if (!/^(?:components|patterns|composition)\//.test(file) || !source.test(file)) continue;
|
|
104
|
+
if (!/^(?:components|patterns|controllers|composition)\//.test(file) || !source.test(file)) continue;
|
|
94
105
|
const directory = slash(path.dirname(file)), name = path.basename(file).replace(source, "");
|
|
95
106
|
if (!directory.includes("/") || /\.(?:scenario|fixture|scenario-adapter)$/.test(name)) continue;
|
|
96
107
|
if (flatFamilies.has(directory)) continue;
|
|
97
108
|
if (name === "index" || sameName(name) === sameName(path.posix.basename(directory)) || name === "fixture") directories.add(directory);
|
|
98
109
|
}
|
|
99
110
|
const directoryUnits = [...directories].sort((a, b) => b.length - a.length);
|
|
100
|
-
const flatUnits = [...new Set(files.filter(file => /^(?:components|patterns|composition)\//.test(file) && source.test(file) && !directoryUnits.some(directory => file.startsWith(directory + "/"))).map(file => {
|
|
111
|
+
const flatUnits = [...new Set(files.filter(file => /^(?:components|patterns|controllers|composition)\//.test(file) && source.test(file) && !directoryUnits.some(directory => file.startsWith(directory + "/"))).map(file => {
|
|
101
112
|
const base = path.basename(file).split(".")[0];
|
|
102
113
|
return slash(path.join(path.dirname(file), base));
|
|
103
114
|
}))].sort((a, b) => b.length - a.length);
|
|
@@ -113,7 +124,7 @@ export function inspectUnits(options) {
|
|
|
113
124
|
if (relative.startsWith(tokenDir + "/")) return "tokens";
|
|
114
125
|
if (relative.startsWith("assets/")) return relative.split("/").length > 2 ? relative.split("/").slice(0, 2).join("/") : "assets";
|
|
115
126
|
if (!relative.includes("/")) return /(?:\.(?:md|txt)$|^LICENSE$)/i.test(relative) ? "documentation" : "foundation";
|
|
116
|
-
if (!/^(?:components|patterns|composition)\//.test(relative)) return relative.split("/")[0];
|
|
127
|
+
if (!/^(?:components|patterns|controllers|composition)\//.test(relative)) return relative.split("/")[0];
|
|
117
128
|
for (const directory of directoryUnits) if (relative.startsWith(directory + "/")) return directory;
|
|
118
129
|
for (const unit of flatUnits) if (relative.startsWith(unit + "/") || relative.startsWith(unit + ".")) return unit;
|
|
119
130
|
const base = path.basename(relative).split(".")[0];
|
|
@@ -142,7 +153,7 @@ export function inspectUnits(options) {
|
|
|
142
153
|
function boundaryCompanion(unit) { return !["configuration", "documentation", "assets"].includes(unit.split("/")[0]); }
|
|
143
154
|
|
|
144
155
|
export function validateUnitId(unit) {
|
|
145
|
-
if (typeof unit !== "string" || unit.length > 256 || !/^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*$/.test(unit) || ["components", "patterns", "composition"].includes(unit)) throw fail("Select one ADS unit ID from ads_units or an unoccupied owner path following its colocation convention.");
|
|
156
|
+
if (typeof unit !== "string" || unit.length > 256 || !/^[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*$/.test(unit) || ["components", "patterns", "controllers", "composition"].includes(unit)) throw fail("Select one ADS unit ID from ads_units or an unoccupied owner path following its colocation convention.");
|
|
146
157
|
}
|
|
147
158
|
|
|
148
159
|
/** Reject arbitrary file batches, even when every path is inside the ADS. */
|
|
@@ -207,7 +218,7 @@ export function checkUnitCandidate(afterOptions, unit, changes, before) {
|
|
|
207
218
|
}
|
|
208
219
|
const exportChange = changes.find(change => before.relative(change.path) === before.publicEntry);
|
|
209
220
|
if (exportChange) {
|
|
210
|
-
if (!["components", "patterns"].includes(unit.split("/")[0])) throw fail("Only a component/pattern unit may update its public re-exports.", exportChange.path);
|
|
221
|
+
if (!["components", "patterns", "controllers"].includes(unit.split("/")[0])) throw fail("Only a component/pattern/controller unit may update its public re-exports.", exportChange.path);
|
|
211
222
|
const current = exportChange.originalContent ?? "", proposed = exportChange.content;
|
|
212
223
|
if (proposed === null) throw fail("The shared public entry cannot be deleted.", exportChange.path);
|
|
213
224
|
const resolve = importResolver(afterOptions);
|