@reqvire-org/ads 0.3.2 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/boundaries.mjs +21 -23
- package/dist/check.mjs +18 -11
- package/dist/cli.mjs +1 -0
- package/dist/mcp/mutations.mjs +1 -1
- package/dist/mcp/prompts.mjs +1 -1
- package/dist/mcp/server.mjs +6 -6
- package/dist/oxlint.mjs +1 -1
- package/dist/skills/ads-assimilate/SKILL.md +26 -4
- package/dist/skills/ads-assimilate/references/native-implementation.md +16 -0
- package/dist/skills/ads-break-ui/SKILL.md +26 -4
- package/dist/skills/ads-designer/SKILL.md +26 -4
- package/dist/skills/ads-import/SKILL.md +26 -4
- package/dist/skills/ads-motion/SKILL.md +26 -4
- package/dist/style-boundaries.mjs +59 -0
- package/dist/units.mjs +37 -1
- package/docs/ADS.md +5 -1
- package/docs/ARCHITECTURE.md +6 -3
- package/docs/CHANGELOG.md +12 -0
- package/docs/GUIDE.md +9 -0
- package/docs/MCP.md +30 -0
- package/package.json +1 -1
package/dist/boundaries.mjs
CHANGED
|
@@ -90,31 +90,29 @@ function isWrite(node) {
|
|
|
90
90
|
|| ts.isDeleteExpression(parent);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
/** Exact writes and known reads share one interpretation with exception audits.
|
|
94
|
+
* undefined = unrelated, false = read, null = unknown/bulk, string = exact write. */
|
|
95
|
+
export function styleAccessProperty(node) {
|
|
96
|
+
if (!isMember(node) || memberName(node) !== "style") return undefined;
|
|
97
|
+
const access = unwrapped(node), parent = access.parent;
|
|
98
|
+
if (!isMember(parent) || parent.expression !== access) return null;
|
|
99
|
+
const property = cssName(memberName(parent)), target = unwrapped(parent);
|
|
100
|
+
if (ts.isCallExpression(target.parent) && target.parent.expression === target) {
|
|
101
|
+
if (["set-property", "remove-property"].includes(property)) {
|
|
102
|
+
const name = target.parent.arguments[0];
|
|
103
|
+
return name && ts.isStringLiteralLike(name) ? name.text : null;
|
|
104
|
+
}
|
|
105
|
+
return ["get-property-value", "get-property-priority", "item"].includes(property) ? false : null;
|
|
106
|
+
}
|
|
107
|
+
if (isWrite(target)) return property ?? null;
|
|
108
|
+
return property && !["set-property", "remove-property"].includes(property) ? false : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Aliases, computed keys, cssText and bulk writes cannot widen an exception.
|
|
95
112
|
export function styleWriteViolation(node, filename, { root, config }) {
|
|
96
113
|
const allowed = config.boundaries.styleWrites.find((boundary) => path.resolve(root, boundary.file) === filename)?.properties ?? [];
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const parent = access.parent;
|
|
100
|
-
let property;
|
|
101
|
-
if (isMember(parent) && parent.expression === access) {
|
|
102
|
-
property = cssName(memberName(parent));
|
|
103
|
-
const target = unwrapped(parent);
|
|
104
|
-
if (ts.isCallExpression(target.parent) && target.parent.expression === target) {
|
|
105
|
-
if (["set-property", "remove-property"].includes(property)) {
|
|
106
|
-
const name = target.parent.arguments[0];
|
|
107
|
-
property = name && ts.isStringLiteralLike(name) ? name.text : null;
|
|
108
|
-
} else if (["get-property-value", "get-property-priority", "item"].includes(property)) return null;
|
|
109
|
-
else property = null;
|
|
110
|
-
} else if (!isWrite(target)) {
|
|
111
|
-
// Plain property reads are allowed. Passing a method reference or a
|
|
112
|
-
// computed member elsewhere is not a known property read.
|
|
113
|
-
if (property && !["set-property", "remove-property"].includes(property)) return null;
|
|
114
|
-
property = null;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (property && property !== "css-text" && allowed.includes(property)) return null;
|
|
114
|
+
const property = styleAccessProperty(node);
|
|
115
|
+
if (property !== undefined && property !== false && !(property && property !== "css-text" && allowed.includes(property))) {
|
|
118
116
|
return "Imperative styles require a file boundary and an explicit property. Bulk writes, computed property names, and style-object escapes are forbidden.";
|
|
119
117
|
}
|
|
120
118
|
if (ts.isCallExpression(node) && isMember(node.expression) && ["setAttribute", "setAttributeNS"].includes(memberName(node.expression))) {
|
package/dist/check.mjs
CHANGED
|
@@ -14,8 +14,12 @@ import { inspectShowcasePages } from "./showcase.mjs";
|
|
|
14
14
|
import { inspectShowcaseCoverage } from "./showcase-coverage.mjs";
|
|
15
15
|
import { inspectScenarioCoverage } from "./scenarios.mjs";
|
|
16
16
|
import { uiOperation, importedUiApi } from "./ui-framework.mjs";
|
|
17
|
+
import { inspectStyleBoundaries, pointerCaptureAdvisories } from "./style-boundaries.mjs";
|
|
17
18
|
import { foundationDeclaration, foundationSelector } from "./foundation.mjs";
|
|
18
19
|
|
|
20
|
+
const scenarioLayoutGuidance = "Put demo-only sizing and framing in a public review/layout pattern. Keep required behavior in the runtime owner; add component props for deliberate product API needs.";
|
|
21
|
+
const inlineStyleGuidance = `Own visual styles in component/pattern Linaria. ${scenarioLayoutGuidance} Reserve inlineStyleFiles for documented measured geometry, not demo layout.`;
|
|
22
|
+
|
|
19
23
|
const showcaseControls = new Set(["a", "button", "input", "select", "textarea", "option", "optgroup", "table", "thead", "tbody", "tfoot", "tr", "td", "th", "dialog", "details", "summary", "progress", "meter"]);
|
|
20
24
|
const svgTags = new Set(["svg", "g", "path", "circle", "rect", "ellipse", "line", "polyline", "polygon", "text", "tspan", "use", "defs", "clipPath", "mask", "filter", "linearGradient", "radialGradient", "stop", "marker", "pattern"]);
|
|
21
25
|
const svgPresentation = new Set(["strokeWidth", "strokeLinecap", "strokeLinejoin", "strokeDasharray", "strokeDashoffset", "strokeOpacity", "strokeMiterlimit", "fillRule", "fillOpacity", "clipRule", "clipPath", "colorInterpolation", "colorInterpolationFilters", "floodColor", "floodOpacity", "stopColor", "stopOpacity", "fontFamily", "fontSize", "fontStyle", "fontWeight", "letterSpacing", "textAnchor", "dominantBaseline", "markerStart", "markerMid", "markerEnd", "vectorEffect", "shapeRendering", "textRendering", "paintOrder", "alignmentBaseline", "baselineShift", "transformOrigin"]);
|
|
@@ -24,7 +28,7 @@ const widgetRoles = new Set(["button", "link", "checkbox", "radio", "switch", "c
|
|
|
24
28
|
export function checkProject(directory, { trackingRoot } = {}) {
|
|
25
29
|
const options = { ...loadConfig(directory), ...(trackingRoot ? { trackingRoot: path.resolve(trackingRoot) } : {}) };
|
|
26
30
|
const { root, config, dsRoot, sourceRoot } = options;
|
|
27
|
-
const diagnostics = [];
|
|
31
|
+
const diagnostics = [], advisories = [];
|
|
28
32
|
for (const [field, file] of [["dsDir", dsRoot], ["publicEntry", path.join(dsRoot, config.publicEntry)], ["stylesEntry", path.join(dsRoot, config.stylesEntry)]]) {
|
|
29
33
|
if (!existsSync(file) || (field === "dsDir" ? !statSync(file).isDirectory() : !statSync(file).isFile())) diagnostics.push({ rule: "project-structure", file: path.relative(root, file), line: 1, column: 1, message: `Missing or invalid ${field}. Bootstrap an empty ADS or configure its actual path.` });
|
|
30
34
|
}
|
|
@@ -36,13 +40,15 @@ export function checkProject(directory, { trackingRoot } = {}) {
|
|
|
36
40
|
const resolveImport = importResolver(options);
|
|
37
41
|
const rootHtml = path.join(root, "index.html");
|
|
38
42
|
const files = [...new Set([...sourceFiles(sourceRoot), ...sourceFiles(dsRoot), ...(existsSync(rootHtml) ? [rootHtml] : [])])];
|
|
39
|
-
const
|
|
43
|
+
const boundaryFiles = [...config.inlineStyleFiles, ...config.boundaries.styleWrites.map(entry => entry.file)].map(file => path.resolve(root, file)).filter(file => existsSync(file) && statSync(file).isFile());
|
|
44
|
+
const scriptFiles = [...new Set([...files, ...boundaryFiles])].filter((file) => /\.[cm]?[jt]sx?$/.test(file));
|
|
40
45
|
const compiler = projectCompilerOptions(options);
|
|
41
46
|
const program = ts.createProgram(scriptFiles, { ...compiler, paths: { ...compiler.paths, [config.publicAlias]: [path.join(dsRoot, config.publicEntry)] }, allowJs: true, noResolve: true, noLib: true, types: [], target: ts.ScriptTarget.Latest, module: ts.ModuleKind.ESNext, jsx: ts.JsxEmit.Preserve });
|
|
42
47
|
const checker = program.getTypeChecker();
|
|
43
48
|
function report(file, rule, message, line = 1, column = 1) {
|
|
44
49
|
diagnostics.push({ file: path.relative(root, file).replaceAll("\\", "/"), rule, message, line, column });
|
|
45
50
|
}
|
|
51
|
+
inspectStyleBoundaries(options, program, report);
|
|
46
52
|
inspectShowcasePages(options, program, resolveImport, report);
|
|
47
53
|
inspectShowcaseCoverage(options, program, report);
|
|
48
54
|
inspectScenarioCoverage(options, program, report);
|
|
@@ -60,7 +66,7 @@ export function checkProject(directory, { trackingRoot } = {}) {
|
|
|
60
66
|
try { css = postcss.parse(text, { from: file }); }
|
|
61
67
|
catch (error) { report(file, "css-syntax", error.reason ?? error.message, offset.line + (error.line ?? 1) - 1); return; }
|
|
62
68
|
if (!foundation && (isDevelopmentFile(file, options) || !isInside(path.join(dsRoot, "components"), file) && !isInside(path.join(dsRoot, "patterns"), file) && file !== foundationEntry)) report(file, "style-ownership", "Visual CSS belongs to the local ADS's runtime components or patterns.");
|
|
63
|
-
if (isDevelopmentFile(file, options) && css.nodes.some((node) => node.type !== "comment" && !(node.type === "atrule" && node.name === "import"))) report(file, "showcase-style",
|
|
69
|
+
if (isDevelopmentFile(file, options) && css.nodes.some((node) => node.type !== "comment" && !(node.type === "atrule" && node.name === "import"))) report(file, "showcase-style", `Scenarios and fixtures define no styles. ${scenarioLayoutGuidance}`, offset.line);
|
|
64
70
|
css.walkAtRules("import", (rule) => {
|
|
65
71
|
if (foundation) report(file, "foundation-import", "Import foundation sheets through the configured styles entry. Token sheets cannot import an unchecked styling layer.", rule.source.start.line);
|
|
66
72
|
const first = valueParser(rule.params).nodes.find((node) => node.type !== "space" && node.type !== "comment");
|
|
@@ -128,6 +134,7 @@ export function checkProject(directory, { trackingRoot } = {}) {
|
|
|
128
134
|
}
|
|
129
135
|
const source = program.getSourceFile(file);
|
|
130
136
|
if (!source) continue;
|
|
137
|
+
advisories.push(...pointerCaptureAdvisories(source).map(entry => ({ file: path.relative(root, file).replaceAll("\\", "/"), ...entry })));
|
|
131
138
|
for (const diagnostic of program.getSyntacticDiagnostics(source)) {
|
|
132
139
|
const position = source.getLineAndCharacterOfPosition(diagnostic.start ?? 0);
|
|
133
140
|
report(file, "source-syntax", ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"), position.line + 1, position.character + 1);
|
|
@@ -186,14 +193,14 @@ export function checkProject(directory, { trackingRoot } = {}) {
|
|
|
186
193
|
for (const property of properties) {
|
|
187
194
|
if (!ts.isPropertyAssignment(property) || !(ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name))) continue;
|
|
188
195
|
if (tag && ts.isStringLiteralLike(tag) && svgTags.has(tag.text) && importedUiApi(node.expression, checker)?.module === "preact") checkSvgAttribute(property, property.name.text);
|
|
189
|
-
if (property.name.text === "style" && (!inlineAllowed || showcase)) nodeReport(property, "no-inline-style",
|
|
196
|
+
if (property.name.text === "style" && (!inlineAllowed || showcase)) nodeReport(property, "no-inline-style", showcase ? `Scenarios define no inline styles. ${scenarioLayoutGuidance}` : inlineStyleGuidance);
|
|
190
197
|
if (property.name.text === "style") inspectProperty(property.initializer);
|
|
191
198
|
if (property.name.text === "className") inspectClass(property.initializer);
|
|
192
|
-
if (showcase && property.name.text === "className") nodeReport(property, "showcase-class",
|
|
199
|
+
if (showcase && property.name.text === "className") nodeReport(property, "showcase-class", `Scenarios define no custom class names. ${scenarioLayoutGuidance}`);
|
|
193
200
|
}
|
|
194
201
|
if (showcase && tag && ts.isStringLiteralLike(tag)) {
|
|
195
202
|
const role = properties.find(property => ts.isPropertyAssignment(property) && (ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)) && property.name.text === "role")?.initializer;
|
|
196
|
-
if (showcaseControls.has(tag.text) || role && ts.isStringLiteralLike(role) && widgetRoles.has(role.text)) nodeReport(node, "showcase-control",
|
|
203
|
+
if (showcaseControls.has(tag.text) || role && ts.isStringLiteralLike(role) && widgetRoles.has(role.text)) nodeReport(node, "showcase-control", `Scenario controls must render public ADS components for actual interaction. ${scenarioLayoutGuidance}`);
|
|
197
204
|
}
|
|
198
205
|
}
|
|
199
206
|
if (isInside(dsRoot, file) && (ts.isIdentifier(node) && ["fetch", "XMLHttpRequest", "WebSocket", "EventSource"].includes(node.text) && !(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node))) {
|
|
@@ -204,9 +211,9 @@ export function checkProject(directory, { trackingRoot } = {}) {
|
|
|
204
211
|
if (showcase && (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) && ts.isIdentifier(node.tagName) && /^[a-z]/.test(node.tagName.text)) {
|
|
205
212
|
const role = node.attributes.properties.find((attr) => ts.isJsxAttribute(attr) && attr.name.text === "role")?.initializer;
|
|
206
213
|
const roleValue = role && ts.isStringLiteralLike(role) ? role.text : role && ts.isJsxExpression(role) && role.expression && ts.isStringLiteralLike(role.expression) ? role.expression.text : null;
|
|
207
|
-
if (showcaseControls.has(node.tagName.text) || widgetRoles.has(roleValue)) nodeReport(node, "showcase-control",
|
|
214
|
+
if (showcaseControls.has(node.tagName.text) || widgetRoles.has(roleValue)) nodeReport(node, "showcase-control", `Scenario controls must render public ADS components for actual interaction. ${scenarioLayoutGuidance}`);
|
|
208
215
|
}
|
|
209
|
-
if (showcase && ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && node.name.text === "className") nodeReport(node, "showcase-class",
|
|
216
|
+
if (showcase && ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && node.name.text === "className") nodeReport(node, "showcase-class", `Scenarios define no custom class names. ${scenarioLayoutGuidance}`);
|
|
210
217
|
let specifier;
|
|
211
218
|
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) specifier = node.moduleSpecifier.text;
|
|
212
219
|
if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments[0] && ts.isStringLiteral(node.arguments[0])) specifier = node.arguments[0].text;
|
|
@@ -214,13 +221,13 @@ export function checkProject(directory, { trackingRoot } = {}) {
|
|
|
214
221
|
if (specifier && resolveImport(specifier, file).some((target) => (specifier.startsWith(".") || existsSync(target)) && target.endsWith(".css") && isInside(root, target) && !isInside(dsRoot, target) && target !== path.join(sourceRoot, "app-mount.css") && !isInside(path.join(root, "node_modules"), target))) nodeReport(node, "style-ownership", "Imported project CSS belongs to ADS components, patterns, or the foundation. Only structural app-mount.css is allowed outside the ADS.");
|
|
215
222
|
if (specifier && appImportViolation(specifier, file, options, resolveImport)) nodeReport(node, "no-app-import", "ADS implementations and showcase fixtures must not import app code. Use public ADS APIs with local fixture data; keep application integration harnesses in application tests.");
|
|
216
223
|
if (specifier && showcaseImportViolation(specifier, file, options, resolveImport)) nodeReport(node, "showcase-import", "Showcase code and styles are development fixtures; runtime code must not import them.");
|
|
217
|
-
if (ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && node.name.text === "style" && (!inlineAllowed || showcase)) nodeReport(node, "no-inline-style",
|
|
224
|
+
if (ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && node.name.text === "style" && (!inlineAllowed || showcase)) nodeReport(node, "no-inline-style", showcase ? `Scenarios define no inline styles. ${scenarioLayoutGuidance}` : inlineStyleGuidance);
|
|
218
225
|
if (ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && node.name.text === "style" && node.initializer) {
|
|
219
226
|
inspectProperty(node.initializer);
|
|
220
227
|
}
|
|
221
228
|
const mutation = styleWriteViolation(node, file, options);
|
|
222
229
|
if (mutation) nodeReport(node, "style-mutation", mutation);
|
|
223
|
-
if (!inlineAllowed && isUiCssProperties(node, checker)) nodeReport(node, "css-properties", "
|
|
230
|
+
if (!inlineAllowed && isUiCssProperties(node, checker)) nodeReport(node, "css-properties", "Keep CSSProperties out of the public API. Use deliberate component contracts or a documented measured-geometry renderer boundary; demo layout belongs in a public review pattern.");
|
|
224
231
|
if (ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && node.name.text === "className" && node.initializer) {
|
|
225
232
|
inspectClass(node.initializer);
|
|
226
233
|
}
|
|
@@ -234,5 +241,5 @@ export function checkProject(directory, { trackingRoot } = {}) {
|
|
|
234
241
|
}
|
|
235
242
|
visit(source);
|
|
236
243
|
}
|
|
237
|
-
return { ok: diagnostics.length === 0, diagnostics };
|
|
244
|
+
return { ok: diagnostics.length === 0, diagnostics, ...(advisories.length ? { advisories } : {}) };
|
|
238
245
|
}
|
package/dist/cli.mjs
CHANGED
|
@@ -96,6 +96,7 @@ async function main(args) {
|
|
|
96
96
|
if (!json && result.diagnostics?.length) {
|
|
97
97
|
for (const diagnostic of result.diagnostics) console.error(`${diagnostic.file}:${diagnostic.line}:${diagnostic.column} [${diagnostic.rule}] ${diagnostic.message}`);
|
|
98
98
|
}
|
|
99
|
+
if (!json) for (const advisory of result.advisories ?? []) console.error(`${advisory.file}:${advisory.line}:${advisory.column} [advisory:${advisory.rule}] ${advisory.message}`);
|
|
99
100
|
if (result.ok === false || result.diagnostics?.length) process.exitCode = 1;
|
|
100
101
|
}
|
|
101
102
|
|
package/dist/mcp/mutations.mjs
CHANGED
|
@@ -291,7 +291,7 @@ export function createMutationController(directory) {
|
|
|
291
291
|
}
|
|
292
292
|
const warnings = [];
|
|
293
293
|
if (!dryRun && (unit.startsWith("assets") || unit === "configuration")) { try { await generateBrandAssets(root); } catch (error) { warnings.push({ rule: "mutation-assets", message: `Source was applied; generated brand output needs recovery: ${error.message}` }); } }
|
|
294
|
-
return lastResult = { ok: true, unit, applied: !dryRun, changes: changes.map(change => ({ path: change.path, operation: change.content === null ? "delete" : change.expectedDigest === null ? "create" : "update", digest: change.content === null ? null : digest(Buffer.from(change.content)) })), checks: verified, ...(warnings.length ? { warnings } : {}),
|
|
294
|
+
return lastResult = { ok: true, unit, applied: !dryRun, changes: changes.map(change => ({ path: change.path, operation: change.content === null ? "delete" : change.expectedDigest === null ? "create" : "update", digest: change.content === null ? null : digest(Buffer.from(change.content)) })), checks: verified, ...(result.advisories?.length ? { advisories: result.advisories.slice(0, 50), advisoryCount: result.advisories.length } : {}), ...(warnings.length ? { warnings } : {}),
|
|
295
295
|
preview: "Source validation passed. Inspect the managed preview build and rendered scenario after applying; validation is not visual verification." };
|
|
296
296
|
} catch (error) {
|
|
297
297
|
return lastResult = { ok: false, applied: false, diagnostics: [error.diagnostic ?? { rule: "mutation-error", message: error.message }], checks: verified };
|
package/dist/mcp/prompts.mjs
CHANGED
|
@@ -2,7 +2,7 @@ export const reasoning = `ADS is development tooling. The project owns its compo
|
|
|
2
2
|
Start with ads_status and use its projectRoot, workflow and project commands; the MCP workspace may be nested inside the client repository. If ADS tools are not exposed, read ads://project/guide then ads://project/status. Find existing public UI with ads_catalog and load one relevant ADS workflow via ads_skills/ads_read_skill. UI implementation/refinement uses ads-designer; library adaptation ads-assimilate; ADS transfer ads-import; motion ads-motion; stress testing ads-break-ui. Domain-model workflows are separate. Inspect the relevant public API, local guide, token definitions and real showcase fixture before proposing changes. Keep source/instruction discovery scoped to the target project, ancestry and selected external sources; broad scans of /mnt or home are not needed to discover this ADS.
|
|
3
3
|
When ads_status.mutations.enabled, discover one owner with ads_units, read relevant ranges with ads_read_file and send only a Git unified diff through ads_apply_patch({unit,patch,dryRun}). A unit includes its own scenarios/fixtures; imported peers remain separate. tokens is the token foundation unit. Only that owner's public re-exports may change in the shared entry; preserve unrelated statements including export {};. Token manifests list companionFiles: include required foundation import changes only for canonical token files created/deleted in the same patch, preserving other imports/comments and canonical order. Use dryRun to review validation. Rejected patches leave source unchanged; fix diagnostics rather than bypassing checks with direct writes. Shared globals use foundation; guides use documentation; assets use discovered assets/* units. Intentional configuration, registered project guards/builders and rule exceptions use configuration, which validates the proposed settings and refreshes its compiler. Keep mandatory ADS gates. For large/binary diffs, pass patchFile pointing to .ds/patches/<name>.patch rather than loading bytes into context; Git binary patches require installed Git. When editing is disabled, use local editing tools.
|
|
4
4
|
Reason through Data (design-token values, semantic roles and relationships), UX (structure, behavior, accessibility, states and layout), and Skin (token-backed identity and visual states). Business data is a separate application-owned input, supplied through typed props or injected callbacks/services. The project uses Linaria extraction for owned styles; MCP adds no production runtime.
|
|
5
|
-
Choose an existing public component or pattern first. Keep application data/routing/effects in the application and fixture data/state beside its scenario. Change presentation in its owning ADS implementation, then demonstrate the same public API in the showcase. Do not create a widget-only replica or override component internals.
|
|
5
|
+
Choose an existing public component or pattern first. Keep application data/routing/effects in the application and fixture data/state beside its scenario. Change presentation in its owning ADS implementation, then demonstrate the same public API in the showcase. Put demo-only sizing and framing in a public review/layout pattern; add component props for deliberate runtime API needs and preserve required behavior. Do not create a widget-only replica or override component internals.
|
|
6
6
|
For token source inspection, call ads_tokens with a focused query. For token samples, components, patterns and composition, use ads_scenarios to select a path-derived id and invoke ads_show_scenario. Showcase and MCP mount that exact entry and its colocated fixture; no page initialization is required. The showcase is a minimal tool-owned shell plus an MCP client and Apps host. Its menus come from tools; it invokes the same entry tools and refreshes the active scenario through notifications. The reusable App loads framework runtime, foundation CSS, fonts and selected modules via MCP resources inside its sandbox. Keep project execution and styles out of the outer host; preserve mount/unmount cleanup and lazy dependencies. Use the existing ads_status showcase URL, or ads showcase when no MCP runtime is connected. Projects supply scenarios/fixtures and the compiler, not browser page files or another shell. Tool metadata selects an MCP App; a resources/read of HTML alone is not proof of rendered UI. If the host has no Apps view, give the configured showcase URL and use text/resources.
|
|
7
7
|
Check ads_status.previewWatch before launching a separate preview builder. A configured watcher is managed by ads mcp. Check ads_status.build or ads://project/build after changes: running does not mean ready, and unreported freshness is not evidence of current source. Reported preview failures include structured diagnostics; fix the owning source and wait for a successful rebuild, then invoke the preview tool again. Notifications may not reach the agent, so read status explicitly. Build revisions identify reported compilation attempts, not proof of runtime/visual correctness. Do not invent replacement UI to bypass a failed preview.
|
|
8
8
|
Skills: use ads_skills to select a workflow and read its SKILL.md with ads_read_skill. Resolve relative supporting files from that skill URI. Reading instructions does not prove native skill activation or authorize unrelated actions.
|
package/dist/mcp/server.mjs
CHANGED
|
@@ -43,7 +43,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
43
43
|
const project = () => { const context = projectContext(options); context.showcase.url = getShowcaseUrl(); return context; };
|
|
44
44
|
const subscribed = new Set();
|
|
45
45
|
const server = new Server({ name: "reqvire-ads", version: pkg.version }, {
|
|
46
|
-
instructions: `${serverInstructions} ${mutations ? "Guarded editing is enabled: discover one owner with ads_units, read relevant ranges with ads_read_file and send only a Git unified diff to ads_apply_patch with that unit ID. Include its own scenarios/fixtures and required re-exports together, never unrelated units.
|
|
46
|
+
instructions: `${serverInstructions} ${mutations ? "Guarded editing is enabled: discover one owner with ads_units, read relevant ranges with ads_read_file and send only a Git unified diff to ads_apply_patch with that unit ID. Include its own scenarios/fixtures and required re-exports together, never unrelated units. Manifests list companionFiles: required canonical token imports, or only the selected runtime owner's style-boundary entries in ds.config.json alongside its source. Unused style permissions fail validation. The server validates architecture, registered Oxlint and project checks before writing. Fix rejected patches in their owner. Intentional settings, project guards and builders use the configuration unit; mandatory ADS gates still run." : "Source mutation tools are disabled; source edits use the client's editor/filesystem tools."}`,
|
|
47
47
|
capabilities: { tools: { listChanged: true }, resources: { listChanged: true, subscribe: true }, prompts: {},
|
|
48
48
|
extensions: { "io.modelcontextprotocol/ui": { mimeTypes: [APP_MIME] }, ...(era === "modern" ? { [SKILLS_EXTENSION]: { directoryRead: true } } : {}) } },
|
|
49
49
|
});
|
|
@@ -181,9 +181,9 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
181
181
|
const matches = inventory.tokens.filter((token) => token.name.toLowerCase().includes(search.toLowerCase()));
|
|
182
182
|
return json({ total: matches.length, tokens: matches.slice(0, limit), categories: tokenScenarios(), diagnosticCount: inventory.diagnostics.length, resourceUri: `${PROJECT_URI}/tokens` });
|
|
183
183
|
} },
|
|
184
|
-
{ name: "ads_check", description: "Run read-only core ADS architecture checks. Project Oxlint, typecheck, build and browser tests remain separate.", schema: z.object({ limit: z.number().int().min(1).max(50).default(20) }).strict(), call: ({ limit }) => {
|
|
184
|
+
{ name: "ads_check", description: "Run read-only core ADS architecture checks; returns blocking diagnostics separately from nonblocking review advisories. Project Oxlint, typecheck, build and browser tests remain separate.", schema: z.object({ limit: z.number().int().min(1).max(50).default(20) }).strict(), call: ({ limit }) => {
|
|
185
185
|
const result = checkProject(root);
|
|
186
|
-
return json({ ok: result.ok, total: result.diagnostics.length, diagnostics: result.diagnostics.slice(0, limit), resourceUri: `${PROJECT_URI}/checks` });
|
|
186
|
+
return json({ ok: result.ok, total: result.diagnostics.length, diagnostics: result.diagnostics.slice(0, limit), advisoryCount: result.advisories?.length ?? 0, advisories: (result.advisories ?? []).slice(0, limit), resourceUri: `${PROJECT_URI}/checks` });
|
|
187
187
|
} },
|
|
188
188
|
{ name: "ads_skills", description: "Select one ADS workflow after ads_status: ads-designer for building/refining/debugging UI; ads-assimilate for a library reference; ads-import for another project's ADS; ads-motion for animation; ads-break-ui for stress testing. Then read that workflow with ads_read_skill. Listing does not activate a native skill.", schema: empty, call: () => json({ skills: skills.skills.map(({ uri, frontmatter }) => ({ uri, ...frontmatter })) }) },
|
|
189
189
|
{ name: "ads_read_skill", description: "Read a selected skill or relative supporting text file in bounded chunks. This is explicit instruction access, not automatic native skill activation/install.", schema: z.object({ name: z.string().max(64), file: z.string().max(300).default("SKILL.md"), offset: z.number().int().min(0).default(0), limit: z.number().int().min(1).max(8000).default(6000) }).strict(), call: ({ name, file, offset, limit }) => {
|
|
@@ -214,19 +214,19 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
214
214
|
}
|
|
215
215
|
return json({ widget: { resourceUri: TOKENS_APP_URI }, category: null, tokenCount: inspectTokens(root).tokens.length, dataUri: `${PROJECT_URI}/tokens`, showcaseUrl: getShowcaseUrl() });
|
|
216
216
|
} },
|
|
217
|
-
{ name: "ads_units", description: "Discover editable ADS units and their files: components/patterns with colocated previews; composition; tokens; foundation shared files; documentation; assets/brand, assets/fonts and asset groups; configuration with ds.config.json, package/build/lint configuration and registered project guard/compiler sources. List summaries with query, or pass an exact unit ID for its manifest.
|
|
217
|
+
{ name: "ads_units", description: "Discover editable ADS units and their files: components/patterns with colocated previews; composition; tokens; foundation shared files; documentation; assets/brand, assets/fonts and asset groups; configuration with ds.config.json, package/build/lint configuration and registered project guard/compiler sources. List summaries with query, or pass an exact unit ID for its manifest. Manifests include companionFiles with permitted shared-entry or owner-only style-boundary bookkeeping; preserve peer permissions and unrelated configuration. Imported peers stay separate. New owners follow the existing colocation convention.", schema: query.extend({ unit: z.string().min(1).max(256).optional() }), call: ({ unit, query: search = "", limit }) => {
|
|
218
218
|
const matches = edits.units().filter(item => unit ? item.id === unit : item.id.toLowerCase().includes(search.toLowerCase()));
|
|
219
219
|
return json({ total: matches.length, units: matches.slice(0, limit).map(item => ({ id: item.id, kind: item.kind, fileCount: item.files.length, ...(unit ? { files: item.files.slice(0, 200), filesTruncated: item.files.length > 200, ...(item.companionFiles ? { companionFiles: item.companionFiles } : {}) } : {}) })) });
|
|
220
220
|
} },
|
|
221
221
|
{ name: "ads_read_file", description: "Read a bounded range of ADS or registered configuration/extension source. Returns unit, full-file digest, startLine and nextOffset; default/max is 12000 UTF-16 characters. Binary assets return byte count/digest and no encoded content. Paths come from ads_units or discovery. Missing new paths return digest null. Read only relevant context before preparing a Git patch.", schema: z.object({ path: z.string().min(1).max(500), offset: z.number().int().min(0).default(0), limit: z.number().int().min(1).max(mutationLimits.readChars).default(mutationLimits.readChars) }).strict(), call: ({ path: file, ...range }) => json(edits.read(file, range)) },
|
|
222
|
-
...(mutations ? [{ name: "ads_apply_patch", description: "Apply one Git diff to one ADS unit from ads_units. Pass exactly one of patch (inline) or patchFile (a regular .patch/.diff under .ds/patches/, for large/binary diffs without model-context bytes). Text uses --- a/path, +++ b/path and exact @@ hunks; /dev/null creates/deletes. Git binary patches require git diff --binary --full-index and installed Git. A unit owns its implementation, previews and fixtures; imported peers are separate. Foundation, documentation, assets and configuration are editable too. Configuration changes may register/edit project guards and builders; the proposed schema, mandatory core/Oxlint and candidate project checks must pass. Only selected-unit public re-exports may change; preserve unrelated statements
|
|
222
|
+
...(mutations ? [{ name: "ads_apply_patch", description: "Apply one Git diff to one ADS unit from ads_units. Pass exactly one of patch (inline) or patchFile (a regular .patch/.diff under .ds/patches/, for large/binary diffs without model-context bytes). Text uses --- a/path, +++ b/path and exact @@ hunks; /dev/null creates/deletes. Git binary patches require git diff --binary --full-index and installed Git. A unit owns its implementation, previews and fixtures; imported peers are separate. Foundation, documentation, assets and configuration are editable too. Configuration changes may register/edit project guards and builders; the proposed schema, mandatory core/Oxlint and candidate project checks must pass. Only selected-unit public re-exports may change; preserve unrelated statements. A real selected-unit re-export may remove a redundant export {}; placeholder. A tokens patch may include its companionFiles foundation entry only to add/remove canonical imports required by token files created/deleted in this patch, preserving other imports/comments and canonical order. A runtime owner patch may also include its ds.config.json companion solely for that owner's inlineStyleFiles/styleWrites entries alongside its source. Unused permissions fail validation. Set dryRun true to validate without writing. Fix rejected patches and inspect current previews after acceptance.",
|
|
223
223
|
schema: z.object({ unit: z.string().min(1).max(256), dryRun: z.boolean(), patch: z.string().min(1).max(mutationLimits.patchBytes).optional(), patchFile: z.string().min(1).max(500).optional() }).strict().refine(args => (args.patch !== undefined) !== (args.patchFile !== undefined), "Provide exactly one of patch or patchFile."),
|
|
224
224
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, _meta: { ui: { visibility: ["model"] } },
|
|
225
225
|
call: async (args, signal) => {
|
|
226
226
|
const result = await edits.apply(args, signal);
|
|
227
227
|
if (result.applied) {
|
|
228
228
|
try {
|
|
229
|
-
if (result.unit === "configuration") await previewWatch?.reconfigure?.(loadConfig(root).config.mcp, true);
|
|
229
|
+
if (result.changes.some(change => change.path === "ds.config.json") || result.unit === "configuration") await previewWatch?.reconfigure?.(loadConfig(root).config.mcp, true);
|
|
230
230
|
await refresh(true);
|
|
231
231
|
}
|
|
232
232
|
catch (error) { result.warnings = [...(result.warnings ?? []), { rule: "mutation-notification", message: `Changes were applied, but preview refresh failed: ${error.message.slice(0, 2000)}. Recheck ads_status and reopen the scenario.` }]; }
|
package/dist/oxlint.mjs
CHANGED
|
@@ -66,7 +66,7 @@ export default {
|
|
|
66
66
|
},
|
|
67
67
|
},
|
|
68
68
|
"no-inline-style": {
|
|
69
|
-
meta: { type: "problem", schema, messages: { inline: "
|
|
69
|
+
meta: { type: "problem", schema, messages: { inline: "Own visual styles in component/pattern Linaria. Put demo-only sizing and framing in a public review/layout pattern. Reserve inlineStyleFiles for documented measured geometry; add component props for deliberate product API needs." } },
|
|
70
70
|
create(context) {
|
|
71
71
|
const { root, config } = options(context);
|
|
72
72
|
const allowed = config.inlineStyleFiles.some((file) => path.resolve(root, file) === path.resolve(context.getFilename()));
|
|
@@ -27,7 +27,11 @@ unrelated statements. Its real re-export may accompany removal of a redundant
|
|
|
27
27
|
`export {};` placeholder; do not add empty exports. A `tokens` manifest lists the foundation
|
|
28
28
|
entry in `companionFiles`: when creating/deleting a canonical token file such as
|
|
29
29
|
`fonts.css`, include only its required import change there in the same patch.
|
|
30
|
-
Preserve other imports/comments and the canonical order.
|
|
30
|
+
Preserve other imports/comments and the canonical order. Runtime owner manifests
|
|
31
|
+
also list a `ds.config.json` companion: change only that owner's `inlineStyleFiles`
|
|
32
|
+
and `boundaries.styleWrites` entries together with its source. Unused permissions
|
|
33
|
+
fail the core checks; do not pre-register a boundary without its implementation.
|
|
34
|
+
Use `dryRun: true` to
|
|
31
35
|
inspect validation; applying re-runs the gates. Fix rejected patches in their
|
|
32
36
|
owner. Report unsupported coupled changes instead of bypassing a rejection with
|
|
33
37
|
direct source writes. Shared styles/helpers use `foundation`, root guides use
|
|
@@ -241,6 +245,15 @@ source definitions and values; inspecting definitions does not prove UI parity.
|
|
|
241
245
|
The tool keeps the shared naming contract; sample templates and their token
|
|
242
246
|
lists belong to the project.
|
|
243
247
|
|
|
248
|
+
A public review pattern may be covered by real token or composition scenarios;
|
|
249
|
+
it needs no redundant standalone entry. Remove an optional scenario only while
|
|
250
|
+
retaining reachable render coverage. Keep directory ownership anchored by its
|
|
251
|
+
runtime entry (`index` or a file matching the directory name) or colocated fixture.
|
|
252
|
+
Import colocated image/font fixtures so MCP serves them through asset resources.
|
|
253
|
+
For a broken-image case, use a local failing fixture, rather than an external
|
|
254
|
+
network failure or a domain-specific CSP exception. Production asset APIs remain
|
|
255
|
+
unrestricted by this fixture guidance.
|
|
256
|
+
|
|
244
257
|
Scenarios, colocated fixtures and helpers are development-only. Never export them
|
|
245
258
|
from the runtime barrel or import them in production, including through aliases,
|
|
246
259
|
re-exports or dynamic imports. They may not import application stores, routers,
|
|
@@ -258,8 +271,10 @@ normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
|
258
271
|
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
259
272
|
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
260
273
|
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
261
|
-
|
|
262
|
-
|
|
274
|
+
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
275
|
+
in a public review/layout pattern; add component props for a deliberate runtime
|
|
276
|
+
API need. Preserve required capabilities while following ownership rules.
|
|
277
|
+
Browser review still verifies the effects.
|
|
263
278
|
|
|
264
279
|
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
265
280
|
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
@@ -277,7 +292,14 @@ scoped `oxlint.overrides` for justified file-specific settings; never edit
|
|
|
277
292
|
`.ds/oxlintrc.json`. These overrides configure Oxlint, not the independent
|
|
278
293
|
architecture checks in `ads check`. Measured renderer properties and documented
|
|
279
294
|
non-state hooks use explicit `boundaries`; shared token writes and primitive
|
|
280
|
-
state overrides cannot be allowlisted.
|
|
295
|
+
state overrides cannot be allowlisted. When migrating JSX inline geometry to
|
|
296
|
+
imperative writes, port the source and replace its own boundary entries together
|
|
297
|
+
in the owner's patch. Remove obsolete `inlineStyleFiles` entries and unused
|
|
298
|
+
`styleWrites` properties; do not broaden permissions to keep a migration passing.
|
|
299
|
+
Pointer-capture advisories call for review of cancellation, capture loss and
|
|
300
|
+
teardown, including cleanup delegated to hooks. Verify actual cleanup in a browser;
|
|
301
|
+
a locally present handler alone does not prove correctness.
|
|
302
|
+
Add generic architectural protections to
|
|
281
303
|
ADS core and keep domain/component-specific rules registered in project setup.
|
|
282
304
|
|
|
283
305
|
Update meaningful variants, states, content, themes, and container sizes in the
|
|
@@ -55,6 +55,22 @@ target framework. With the default root ADS layout:
|
|
|
55
55
|
dimensions, positions, or transforms. Document and constrain the exception in
|
|
56
56
|
the shared architecture guard; never broaden it to arbitrary visual styles.
|
|
57
57
|
|
|
58
|
+
Preserve the reference's required capabilities while assigning each concern to
|
|
59
|
+
its owner. Demo utilities for container size, border or framing become a public
|
|
60
|
+
review/layout pattern around the real component. Add component props for a
|
|
61
|
+
deliberate runtime API requirement; missing demo layout alone is not justification.
|
|
62
|
+
For imperative geometry, register exact `styleWrites` properties. After migrating
|
|
63
|
+
from JSX inline geometry, change source and its own config companion together:
|
|
64
|
+
remove the obsolete `inlineStyleFiles` exception and register only properties the
|
|
65
|
+
implementation actually writes. Verify the final component with that boundary.
|
|
66
|
+
|
|
67
|
+
Keep presentational building blocks independent of an unnecessary form controller
|
|
68
|
+
or other state engine. Scenario adapters may own demo state and validation. When
|
|
69
|
+
a reusable controller is part of the intended product API, define and test its
|
|
70
|
+
actual contract; a similar method shape alone does not establish compatibility
|
|
71
|
+
with the reference library. Keep state updaters pure and verify submit, focus,
|
|
72
|
+
validation, reset and asynchronous behavior that the API promises.
|
|
73
|
+
|
|
58
74
|
Use current repository conventions in another target rather than assuming the
|
|
59
75
|
same framework, directory structure, or lint tooling.
|
|
60
76
|
|
|
@@ -30,7 +30,11 @@ unrelated statements. Its real re-export may accompany removal of a redundant
|
|
|
30
30
|
`export {};` placeholder; do not add empty exports. A `tokens` manifest lists the foundation
|
|
31
31
|
entry in `companionFiles`: when creating/deleting a canonical token file such as
|
|
32
32
|
`fonts.css`, include only its required import change there in the same patch.
|
|
33
|
-
Preserve other imports/comments and the canonical order.
|
|
33
|
+
Preserve other imports/comments and the canonical order. Runtime owner manifests
|
|
34
|
+
also list a `ds.config.json` companion: change only that owner's `inlineStyleFiles`
|
|
35
|
+
and `boundaries.styleWrites` entries together with its source. Unused permissions
|
|
36
|
+
fail the core checks; do not pre-register a boundary without its implementation.
|
|
37
|
+
Use `dryRun: true` to
|
|
34
38
|
inspect validation; applying re-runs the gates. Fix rejected patches in their
|
|
35
39
|
owner. Report unsupported coupled changes instead of bypassing a rejection with
|
|
36
40
|
direct source writes. Shared styles/helpers use `foundation`, root guides use
|
|
@@ -193,6 +197,15 @@ source definitions and values; inspecting definitions does not prove UI parity.
|
|
|
193
197
|
The tool keeps the shared naming contract; sample templates and their token
|
|
194
198
|
lists belong to the project.
|
|
195
199
|
|
|
200
|
+
A public review pattern may be covered by real token or composition scenarios;
|
|
201
|
+
it needs no redundant standalone entry. Remove an optional scenario only while
|
|
202
|
+
retaining reachable render coverage. Keep directory ownership anchored by its
|
|
203
|
+
runtime entry (`index` or a file matching the directory name) or colocated fixture.
|
|
204
|
+
Import colocated image/font fixtures so MCP serves them through asset resources.
|
|
205
|
+
For a broken-image case, use a local failing fixture, rather than an external
|
|
206
|
+
network failure or a domain-specific CSP exception. Production asset APIs remain
|
|
207
|
+
unrestricted by this fixture guidance.
|
|
208
|
+
|
|
196
209
|
Scenarios, colocated fixtures and helpers are development-only. Never export them
|
|
197
210
|
from the runtime barrel or import them in production, including through aliases,
|
|
198
211
|
re-exports or dynamic imports. They may not import application stores, routers,
|
|
@@ -210,8 +223,10 @@ normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
|
210
223
|
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
211
224
|
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
212
225
|
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
213
|
-
|
|
214
|
-
|
|
226
|
+
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
227
|
+
in a public review/layout pattern; add component props for a deliberate runtime
|
|
228
|
+
API need. Preserve required capabilities while following ownership rules.
|
|
229
|
+
Browser review still verifies the effects.
|
|
215
230
|
|
|
216
231
|
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
217
232
|
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
@@ -229,7 +244,14 @@ scoped `oxlint.overrides` for justified file-specific settings; never edit
|
|
|
229
244
|
`.ds/oxlintrc.json`. These overrides configure Oxlint, not the independent
|
|
230
245
|
architecture checks in `ads check`. Measured renderer properties and documented
|
|
231
246
|
non-state hooks use explicit `boundaries`; shared token writes and primitive
|
|
232
|
-
state overrides cannot be allowlisted.
|
|
247
|
+
state overrides cannot be allowlisted. When migrating JSX inline geometry to
|
|
248
|
+
imperative writes, port the source and replace its own boundary entries together
|
|
249
|
+
in the owner's patch. Remove obsolete `inlineStyleFiles` entries and unused
|
|
250
|
+
`styleWrites` properties; do not broaden permissions to keep a migration passing.
|
|
251
|
+
Pointer-capture advisories call for review of cancellation, capture loss and
|
|
252
|
+
teardown, including cleanup delegated to hooks. Verify actual cleanup in a browser;
|
|
253
|
+
a locally present handler alone does not prove correctness.
|
|
254
|
+
Add generic architectural protections to
|
|
233
255
|
ADS core and keep domain/component-specific rules registered in project setup.
|
|
234
256
|
|
|
235
257
|
Read [references/verification.md](references/verification.md) for viewport,
|
|
@@ -62,7 +62,11 @@ unrelated statements. Its real re-export may accompany removal of a redundant
|
|
|
62
62
|
`export {};` placeholder; do not add empty exports. A `tokens` manifest lists the foundation
|
|
63
63
|
entry in `companionFiles`: when creating/deleting a canonical token file such as
|
|
64
64
|
`fonts.css`, include only its required import change there in the same patch.
|
|
65
|
-
Preserve other imports/comments and the canonical order.
|
|
65
|
+
Preserve other imports/comments and the canonical order. Runtime owner manifests
|
|
66
|
+
also list a `ds.config.json` companion: change only that owner's `inlineStyleFiles`
|
|
67
|
+
and `boundaries.styleWrites` entries together with its source. Unused permissions
|
|
68
|
+
fail the core checks; do not pre-register a boundary without its implementation.
|
|
69
|
+
Use `dryRun: true` to
|
|
66
70
|
inspect validation; applying re-runs the gates. Fix rejected patches in their
|
|
67
71
|
owner. Report unsupported coupled changes instead of bypassing a rejection with
|
|
68
72
|
direct source writes. Shared styles/helpers use `foundation`, root guides use
|
|
@@ -331,6 +335,15 @@ source definitions and values; inspecting definitions does not prove UI parity.
|
|
|
331
335
|
The tool keeps the shared naming contract; sample templates and their token
|
|
332
336
|
lists belong to the project.
|
|
333
337
|
|
|
338
|
+
A public review pattern may be covered by real token or composition scenarios;
|
|
339
|
+
it needs no redundant standalone entry. Remove an optional scenario only while
|
|
340
|
+
retaining reachable render coverage. Keep directory ownership anchored by its
|
|
341
|
+
runtime entry (`index` or a file matching the directory name) or colocated fixture.
|
|
342
|
+
Import colocated image/font fixtures so MCP serves them through asset resources.
|
|
343
|
+
For a broken-image case, use a local failing fixture, rather than an external
|
|
344
|
+
network failure or a domain-specific CSP exception. Production asset APIs remain
|
|
345
|
+
unrestricted by this fixture guidance.
|
|
346
|
+
|
|
334
347
|
Scenarios, colocated fixtures and helpers are development-only. Never export them
|
|
335
348
|
from the runtime barrel or import them in production, including through aliases,
|
|
336
349
|
re-exports or dynamic imports. They may not import application stores, routers,
|
|
@@ -348,8 +361,10 @@ normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
|
348
361
|
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
349
362
|
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
350
363
|
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
351
|
-
|
|
352
|
-
|
|
364
|
+
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
365
|
+
in a public review/layout pattern; add component props for a deliberate runtime
|
|
366
|
+
API need. Preserve required capabilities while following ownership rules.
|
|
367
|
+
Browser review still verifies the effects.
|
|
353
368
|
|
|
354
369
|
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
355
370
|
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
@@ -367,7 +382,14 @@ scoped `oxlint.overrides` for justified file-specific settings; never edit
|
|
|
367
382
|
`.ds/oxlintrc.json`. These overrides configure Oxlint, not the independent
|
|
368
383
|
architecture checks in `ads check`. Measured renderer properties and documented
|
|
369
384
|
non-state hooks use explicit `boundaries`; shared token writes and primitive
|
|
370
|
-
state overrides cannot be allowlisted.
|
|
385
|
+
state overrides cannot be allowlisted. When migrating JSX inline geometry to
|
|
386
|
+
imperative writes, port the source and replace its own boundary entries together
|
|
387
|
+
in the owner's patch. Remove obsolete `inlineStyleFiles` entries and unused
|
|
388
|
+
`styleWrites` properties; do not broaden permissions to keep a migration passing.
|
|
389
|
+
Pointer-capture advisories call for review of cancellation, capture loss and
|
|
390
|
+
teardown, including cleanup delegated to hooks. Verify actual cleanup in a browser;
|
|
391
|
+
a locally present handler alone does not prove correctness.
|
|
392
|
+
Add generic architectural protections to
|
|
371
393
|
ADS core and keep domain/component-specific rules registered in project setup.
|
|
372
394
|
|
|
373
395
|
Examples and composition scenarios must consume the same public components, patterns,
|
|
@@ -26,7 +26,11 @@ unrelated statements. Its real re-export may accompany removal of a redundant
|
|
|
26
26
|
`export {};` placeholder; do not add empty exports. A `tokens` manifest lists the foundation
|
|
27
27
|
entry in `companionFiles`: when creating/deleting a canonical token file such as
|
|
28
28
|
`fonts.css`, include only its required import change there in the same patch.
|
|
29
|
-
Preserve other imports/comments and the canonical order.
|
|
29
|
+
Preserve other imports/comments and the canonical order. Runtime owner manifests
|
|
30
|
+
also list a `ds.config.json` companion: change only that owner's `inlineStyleFiles`
|
|
31
|
+
and `boundaries.styleWrites` entries together with its source. Unused permissions
|
|
32
|
+
fail the core checks; do not pre-register a boundary without its implementation.
|
|
33
|
+
Use `dryRun: true` to
|
|
30
34
|
inspect validation; applying re-runs the gates. Fix rejected patches in their
|
|
31
35
|
owner. Report unsupported coupled changes instead of bypassing a rejection with
|
|
32
36
|
direct source writes. Shared styles/helpers use `foundation`, root guides use
|
|
@@ -298,6 +302,15 @@ source definitions and values; inspecting definitions does not prove UI parity.
|
|
|
298
302
|
The tool keeps the shared naming contract; sample templates and their token
|
|
299
303
|
lists belong to the project.
|
|
300
304
|
|
|
305
|
+
A public review pattern may be covered by real token or composition scenarios;
|
|
306
|
+
it needs no redundant standalone entry. Remove an optional scenario only while
|
|
307
|
+
retaining reachable render coverage. Keep directory ownership anchored by its
|
|
308
|
+
runtime entry (`index` or a file matching the directory name) or colocated fixture.
|
|
309
|
+
Import colocated image/font fixtures so MCP serves them through asset resources.
|
|
310
|
+
For a broken-image case, use a local failing fixture, rather than an external
|
|
311
|
+
network failure or a domain-specific CSP exception. Production asset APIs remain
|
|
312
|
+
unrestricted by this fixture guidance.
|
|
313
|
+
|
|
301
314
|
Scenarios, colocated fixtures and helpers are development-only. Never export them
|
|
302
315
|
from the runtime barrel or import them in production, including through aliases,
|
|
303
316
|
re-exports or dynamic imports. They may not import application stores, routers,
|
|
@@ -315,8 +328,10 @@ normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
|
315
328
|
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
316
329
|
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
317
330
|
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
318
|
-
|
|
319
|
-
|
|
331
|
+
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
332
|
+
in a public review/layout pattern; add component props for a deliberate runtime
|
|
333
|
+
API need. Preserve required capabilities while following ownership rules.
|
|
334
|
+
Browser review still verifies the effects.
|
|
320
335
|
|
|
321
336
|
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
322
337
|
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
@@ -334,7 +349,14 @@ scoped `oxlint.overrides` for justified file-specific settings; never edit
|
|
|
334
349
|
`.ds/oxlintrc.json`. These overrides configure Oxlint, not the independent
|
|
335
350
|
architecture checks in `ads check`. Measured renderer properties and documented
|
|
336
351
|
non-state hooks use explicit `boundaries`; shared token writes and primitive
|
|
337
|
-
state overrides cannot be allowlisted.
|
|
352
|
+
state overrides cannot be allowlisted. When migrating JSX inline geometry to
|
|
353
|
+
imperative writes, port the source and replace its own boundary entries together
|
|
354
|
+
in the owner's patch. Remove obsolete `inlineStyleFiles` entries and unused
|
|
355
|
+
`styleWrites` properties; do not broaden permissions to keep a migration passing.
|
|
356
|
+
Pointer-capture advisories call for review of cancellation, capture loss and
|
|
357
|
+
teardown, including cleanup delegated to hooks. Verify actual cleanup in a browser;
|
|
358
|
+
a locally present handler alone does not prove correctness.
|
|
359
|
+
Add generic architectural protections to
|
|
338
360
|
ADS core and keep domain/component-specific rules registered in project setup.
|
|
339
361
|
|
|
340
362
|
Write or update the imported item's local README with public usage, required
|
|
@@ -47,7 +47,11 @@ unrelated statements. Its real re-export may accompany removal of a redundant
|
|
|
47
47
|
`export {};` placeholder; do not add empty exports. A `tokens` manifest lists the foundation
|
|
48
48
|
entry in `companionFiles`: when creating/deleting a canonical token file such as
|
|
49
49
|
`fonts.css`, include only its required import change there in the same patch.
|
|
50
|
-
Preserve other imports/comments and the canonical order.
|
|
50
|
+
Preserve other imports/comments and the canonical order. Runtime owner manifests
|
|
51
|
+
also list a `ds.config.json` companion: change only that owner's `inlineStyleFiles`
|
|
52
|
+
and `boundaries.styleWrites` entries together with its source. Unused permissions
|
|
53
|
+
fail the core checks; do not pre-register a boundary without its implementation.
|
|
54
|
+
Use `dryRun: true` to
|
|
51
55
|
inspect validation; applying re-runs the gates. Fix rejected patches in their
|
|
52
56
|
owner. Report unsupported coupled changes instead of bypassing a rejection with
|
|
53
57
|
direct source writes. Shared styles/helpers use `foundation`, root guides use
|
|
@@ -188,6 +192,15 @@ source definitions and values; inspecting definitions does not prove UI parity.
|
|
|
188
192
|
The tool keeps the shared naming contract; sample templates and their token
|
|
189
193
|
lists belong to the project.
|
|
190
194
|
|
|
195
|
+
A public review pattern may be covered by real token or composition scenarios;
|
|
196
|
+
it needs no redundant standalone entry. Remove an optional scenario only while
|
|
197
|
+
retaining reachable render coverage. Keep directory ownership anchored by its
|
|
198
|
+
runtime entry (`index` or a file matching the directory name) or colocated fixture.
|
|
199
|
+
Import colocated image/font fixtures so MCP serves them through asset resources.
|
|
200
|
+
For a broken-image case, use a local failing fixture, rather than an external
|
|
201
|
+
network failure or a domain-specific CSP exception. Production asset APIs remain
|
|
202
|
+
unrestricted by this fixture guidance.
|
|
203
|
+
|
|
191
204
|
Scenarios, colocated fixtures and helpers are development-only. Never export them
|
|
192
205
|
from the runtime barrel or import them in production, including through aliases,
|
|
193
206
|
re-exports or dynamic imports. They may not import application stores, routers,
|
|
@@ -205,8 +218,10 @@ normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
|
205
218
|
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
206
219
|
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
207
220
|
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
208
|
-
|
|
209
|
-
|
|
221
|
+
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
222
|
+
in a public review/layout pattern; add component props for a deliberate runtime
|
|
223
|
+
API need. Preserve required capabilities while following ownership rules.
|
|
224
|
+
Browser review still verifies the effects.
|
|
210
225
|
|
|
211
226
|
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
212
227
|
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
@@ -224,7 +239,14 @@ scoped `oxlint.overrides` for justified file-specific settings; never edit
|
|
|
224
239
|
`.ds/oxlintrc.json`. These overrides configure Oxlint, not the independent
|
|
225
240
|
architecture checks in `ads check`. Measured renderer properties and documented
|
|
226
241
|
non-state hooks use explicit `boundaries`; shared token writes and primitive
|
|
227
|
-
state overrides cannot be allowlisted.
|
|
242
|
+
state overrides cannot be allowlisted. When migrating JSX inline geometry to
|
|
243
|
+
imperative writes, port the source and replace its own boundary entries together
|
|
244
|
+
in the owner's patch. Remove obsolete `inlineStyleFiles` entries and unused
|
|
245
|
+
`styleWrites` properties; do not broaden permissions to keep a migration passing.
|
|
246
|
+
Pointer-capture advisories call for review of cancellation, capture loss and
|
|
247
|
+
teardown, including cleanup delegated to hooks. Verify actual cleanup in a browser;
|
|
248
|
+
a locally present handler alone does not prove correctness.
|
|
249
|
+
Add generic architectural protections to
|
|
228
250
|
ADS core and keep domain/component-specific rules registered in project setup.
|
|
229
251
|
|
|
230
252
|
- Motion constants belong in the project's token layer. Shared token meanings
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import { isUiCssProperties, styleAccessProperty } from "./boundaries.mjs";
|
|
4
|
+
import { uiOperation } from "./ui-framework.mjs";
|
|
5
|
+
|
|
6
|
+
const nameOf = node => node && (ts.isIdentifier(node) || ts.isStringLiteralLike(node)) ? node.text : null;
|
|
7
|
+
|
|
8
|
+
/** Audit explicit permissions without inferring project-specific style APIs. */
|
|
9
|
+
export function inspectStyleBoundaries({ root, config }, program, report) {
|
|
10
|
+
const checker = program.getTypeChecker(), usage = new Map();
|
|
11
|
+
function inspect(file) {
|
|
12
|
+
if (usage.has(file)) return usage.get(file);
|
|
13
|
+
const result = { inline: false, writes: new Set() }, source = program.getSourceFile(file);
|
|
14
|
+
function visit(node) {
|
|
15
|
+
if ((ts.isJsxAttribute(node) && nameOf(node.name) === "style") || isUiCssProperties(node, checker)) result.inline = true;
|
|
16
|
+
if (ts.isCallExpression(node) && uiOperation(node.expression, checker) === "createElement") {
|
|
17
|
+
const props = node.arguments[1];
|
|
18
|
+
if (props && ts.isObjectLiteralExpression(props) && props.properties.some(property => nameOf(property.name) === "style")) result.inline = true;
|
|
19
|
+
}
|
|
20
|
+
const property = styleAccessProperty(node);
|
|
21
|
+
if (typeof property === "string") result.writes.add(property);
|
|
22
|
+
ts.forEachChild(node, visit);
|
|
23
|
+
}
|
|
24
|
+
if (source) visit(source);
|
|
25
|
+
usage.set(file, result);
|
|
26
|
+
return result;
|
|
27
|
+
}
|
|
28
|
+
const configFile = path.join(root, "ds.config.json");
|
|
29
|
+
for (const file of config.inlineStyleFiles) {
|
|
30
|
+
if (!inspect(path.resolve(root, file)).inline) report(configFile, "stale-style-boundary", `inlineStyleFiles entry ${file} has no JSX/factory style or UI CSSProperties use. Remove the unused permission; migrate source and its own boundary entries together in the owner's patch.`);
|
|
31
|
+
}
|
|
32
|
+
for (const { file, properties } of config.boundaries.styleWrites) {
|
|
33
|
+
const used = inspect(path.resolve(root, file)).writes;
|
|
34
|
+
for (const property of properties) if (!used.has(property)) report(configFile, "stale-style-boundary", `styleWrites entry ${file} never writes ${property}. Remove the unused property in the owner's source/boundary patch.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** A local review signal, never proof that delegated cleanup is absent/correct. */
|
|
39
|
+
export function pointerCaptureAdvisories(source) {
|
|
40
|
+
const captures = [], cleanup = [];
|
|
41
|
+
function hasHandler(node) {
|
|
42
|
+
while (node && (ts.isJsxExpression(node) || ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isNonNullExpression(node))) node = node.expression;
|
|
43
|
+
return !!node && ![ts.SyntaxKind.NullKeyword, ts.SyntaxKind.FalseKeyword].includes(node.kind) && !(ts.isIdentifier(node) && node.text === "undefined");
|
|
44
|
+
}
|
|
45
|
+
function visit(node) {
|
|
46
|
+
if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) {
|
|
47
|
+
if (node.expression.name.text === "setPointerCapture") captures.push(node);
|
|
48
|
+
if (node.expression.name.text === "addEventListener" && node.arguments[0] && ts.isStringLiteralLike(node.arguments[0]) && node.arguments[0].text === "lostpointercapture" && hasHandler(node.arguments[1])) cleanup.push(node);
|
|
49
|
+
}
|
|
50
|
+
if ((ts.isJsxAttribute(node) || ts.isPropertyAssignment(node) || ts.isMethodDeclaration(node)) && /^(?:onLostPointerCapture(?:Capture)?|onlostpointercapture)$/.test(nameOf(node.name) ?? "") && (ts.isMethodDeclaration(node) || hasHandler(node.initializer))) cleanup.push(node);
|
|
51
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isPropertyAccessExpression(node.left) && node.left.name.text === "onlostpointercapture" && hasHandler(node.right)) cleanup.push(node);
|
|
52
|
+
ts.forEachChild(node, visit);
|
|
53
|
+
}
|
|
54
|
+
visit(source);
|
|
55
|
+
return cleanup.length ? [] : captures.map(node => {
|
|
56
|
+
const position = source.getLineAndCharacterOfPosition(node.getStart(source));
|
|
57
|
+
return { rule: "pointer-capture-review", line: position.line + 1, column: position.character + 1, message: "Pointer capture has no locally visible lostpointercapture cleanup handler. Verify cancellation and capture-loss cleanup, including any delegated hook/helper. This advisory does not prove cleanup is missing or correct." };
|
|
58
|
+
});
|
|
59
|
+
}
|
package/dist/units.mjs
CHANGED
|
@@ -84,7 +84,19 @@ export function inspectUnits(options) {
|
|
|
84
84
|
}
|
|
85
85
|
walk(dsRoot);
|
|
86
86
|
files.sort();
|
|
87
|
-
|
|
87
|
+
// Runtime entry conventions keep directory ownership stable when an optional
|
|
88
|
+
// preview is removed. A flat scenario family still denotes separate owners.
|
|
89
|
+
const sameName = name => name.replace(/[-_]/g, "").toLowerCase();
|
|
90
|
+
const flatFamilies = new Set(files.filter(file => /\.scenario\.[cm]?[jt]sx?$/.test(file)).map(file => path.posix.dirname(file)));
|
|
91
|
+
const directories = new Set(files.filter(file => /(?:^|\/)Scenario\.[cm]?[jt]sx?$/.test(file)).map(file => slash(path.dirname(file))));
|
|
92
|
+
for (const file of files) {
|
|
93
|
+
if (!/^(?:components|patterns|composition)\//.test(file) || !source.test(file)) continue;
|
|
94
|
+
const directory = slash(path.dirname(file)), name = path.basename(file).replace(source, "");
|
|
95
|
+
if (!directory.includes("/") || /\.(?:scenario|fixture|scenario-adapter)$/.test(name)) continue;
|
|
96
|
+
if (flatFamilies.has(directory)) continue;
|
|
97
|
+
if (name === "index" || sameName(name) === sameName(path.posix.basename(directory)) || name === "fixture") directories.add(directory);
|
|
98
|
+
}
|
|
99
|
+
const directoryUnits = [...directories].sort((a, b) => b.length - a.length);
|
|
88
100
|
const flatUnits = [...new Set(files.filter(file => /^(?:components|patterns|composition)\//.test(file) && source.test(file) && !directoryUnits.some(directory => file.startsWith(directory + "/"))).map(file => {
|
|
89
101
|
const base = path.basename(file).split(".")[0];
|
|
90
102
|
return slash(path.join(path.dirname(file), base));
|
|
@@ -120,10 +132,15 @@ export function inspectUnits(options) {
|
|
|
120
132
|
const file = slash(path.relative(root, path.join(dsRoot, relative)));
|
|
121
133
|
if (!units.get(id).files.includes(file)) units.get(id).files.push(file);
|
|
122
134
|
}
|
|
135
|
+
for (const unit of units.values()) if (boundaryCompanion(unit.id)) {
|
|
136
|
+
(unit.companionFiles ??= []).push({ path: "ds.config.json", allowedChanges: "Only this owner's inlineStyleFiles and boundaries.styleWrites entries, together with its source change; preserve all other configuration and peer permissions." });
|
|
137
|
+
}
|
|
123
138
|
return { units: [...units.values()].sort((a, b) => a.id.localeCompare(b.id)), owner, publicEntry, stylesEntry, tokenDir,
|
|
124
139
|
relative: file => slash(path.relative(dsRoot, path.resolve(root, file))) };
|
|
125
140
|
}
|
|
126
141
|
|
|
142
|
+
function boundaryCompanion(unit) { return !["configuration", "documentation", "assets"].includes(unit.split("/")[0]); }
|
|
143
|
+
|
|
127
144
|
export function validateUnitId(unit) {
|
|
128
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.");
|
|
129
146
|
}
|
|
@@ -134,6 +151,7 @@ export function checkUnitFiles(options, unit, changes, inventory = inspectUnits(
|
|
|
134
151
|
for (const change of changes) {
|
|
135
152
|
const relative = inventory.relative(change.path);
|
|
136
153
|
if (allowNew && unit === "configuration") continue; // The candidate must register every setup/extension file.
|
|
154
|
+
if (change.path === "ds.config.json" && boundaryCompanion(unit)) continue; // Validated against only this owner below.
|
|
137
155
|
if (relative === inventory.publicEntry) continue;
|
|
138
156
|
if (unit === "tokens" && relative === inventory.stylesEntry) continue; // Controlled foundation import bookkeeping.
|
|
139
157
|
if (allowNew && change.originalContent === null && (relative.startsWith(unit + "/") || relative.startsWith(unit + "."))) continue;
|
|
@@ -153,6 +171,24 @@ export function checkUnitCandidate(afterOptions, unit, changes, before) {
|
|
|
153
171
|
if (unit === "configuration" && (entry.id === "configuration" || after.owner(relative) === "configuration")) continue;
|
|
154
172
|
if (after.owner(relative) !== entry.id) throw fail("A patch cannot change ownership of existing peer units. Choose an unoccupied owner path.", file);
|
|
155
173
|
}
|
|
174
|
+
const boundaryChange = changes.find(change => change.path === "ds.config.json");
|
|
175
|
+
if (boundaryChange && unit !== "configuration") {
|
|
176
|
+
if (!boundaryCompanion(unit) || typeof boundaryChange.originalContent !== "string" || typeof boundaryChange.content !== "string") throw fail("This unit cannot create/delete configuration.", boundaryChange.path);
|
|
177
|
+
if (!changes.some(change => change.path !== boundaryChange.path && (before.owner(before.relative(change.path)) === unit || after.owner(after.relative(change.path)) === unit))) throw fail("Boundary companions require a source change in the selected owner.", boundaryChange.path);
|
|
178
|
+
function peerPolicy(content, inventory) {
|
|
179
|
+
const config = JSON.parse(content);
|
|
180
|
+
const owns = file => typeof file === "string" && inventory.owner(inventory.relative(file)) === unit;
|
|
181
|
+
config.inlineStyleFiles = (config.inlineStyleFiles ?? []).filter(file => !owns(file));
|
|
182
|
+
config.boundaries = { ...config.boundaries, styleWrites: (config.boundaries?.styleWrites ?? []).filter(entry => !owns(entry.file)) };
|
|
183
|
+
return config;
|
|
184
|
+
}
|
|
185
|
+
function canonical(value) {
|
|
186
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
187
|
+
if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map(key => [key, canonical(value[key])]));
|
|
188
|
+
return value;
|
|
189
|
+
}
|
|
190
|
+
if (JSON.stringify(canonical(peerPolicy(boundaryChange.originalContent, before))) !== JSON.stringify(canonical(peerPolicy(boundaryChange.content, after)))) throw fail("Boundary companion patches may change only inlineStyleFiles and styleWrites entries owned by the selected unit; preserve all other configuration and peer permissions.", boundaryChange.path);
|
|
191
|
+
}
|
|
156
192
|
const foundationChange = changes.find(change => before.relative(change.path) === before.stylesEntry);
|
|
157
193
|
if (unit === "tokens" && foundationChange) {
|
|
158
194
|
const { originalContent: current, content: proposed } = foundationChange;
|
package/docs/ADS.md
CHANGED
|
@@ -291,10 +291,14 @@ The checks enforce the architecture's ownership boundaries:
|
|
|
291
291
|
| Tokens and styles | Fixed token vocabulary, required definitions, declared value types and references, raw visual values, shared-token assignments, foundation import order, and component/pattern namespaces |
|
|
292
292
|
| Public API and dependencies | Consumers use public ADS imports; ADS implementations and showcase do not import application source; runtime code does not import showcase fixtures |
|
|
293
293
|
| Interaction policy | Primitive state variables stay inside components; non-state customization hooks require explicit registration |
|
|
294
|
-
| Renderer exceptions | Inline
|
|
294
|
+
| Renderer exceptions | Inline styles and imperative writes require used, narrow configured boundaries; stale entries fail; public props omit inherited style and reject open-ended visual strings |
|
|
295
295
|
| Preview ownership | Scenarios define no CSS, custom classes, or native controls; all declared tokens and public renderable UI have reachable registered scenarios; token references must exist; legacy catalogue pages are checked when present |
|
|
296
296
|
| Assets | Source/public collisions and configured generated artifacts in source are rejected |
|
|
297
297
|
|
|
298
|
+
Pointer capture without locally visible capture-loss cleanup produces a
|
|
299
|
+
nonblocking advisory. Review delegated cleanup and browser behavior; neither a
|
|
300
|
+
warning nor a handler's presence establishes lifecycle correctness.
|
|
301
|
+
|
|
298
302
|
Document a new ownership exception with its guard/configuration change and
|
|
299
303
|
tests. Passing static checks establishes only the policies and source formats
|
|
300
304
|
they cover. Browser review and behavior tests still verify accessibility,
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -222,6 +222,8 @@ real public implementation. It mounts independently, without opening a showcase
|
|
|
222
222
|
page or initializing the application's router, store or services first.
|
|
223
223
|
|
|
224
224
|
Scenarios and their fixtures live beside the tokens, component or pattern they exercise.
|
|
225
|
+
Demo framing belongs to public review/layout patterns; component props describe
|
|
226
|
+
intentional product behavior and visual variants.
|
|
225
227
|
Token scenarios choose real token names and sample inputs; a project-owned
|
|
226
228
|
review pattern applies the foundation variables. The tool contains no project
|
|
227
229
|
token list, sample templates or assumed visual identity. Even an initial token
|
|
@@ -303,9 +305,10 @@ remain in place for source changes made through other editors; preview review re
|
|
|
303
305
|
necessary after either editing path.
|
|
304
306
|
|
|
305
307
|
Required shared-entry bookkeeping can accompany its owner: component/pattern
|
|
306
|
-
re-exports
|
|
307
|
-
|
|
308
|
-
|
|
308
|
+
re-exports, canonical foundation imports for token files created or deleted, and
|
|
309
|
+
only that owner’s style-boundary entries in project configuration in the same
|
|
310
|
+
patch. ADS restricts these companion edits and validates the complete candidate
|
|
311
|
+
before writing, so coupled changes do not need a guard bypass.
|
|
309
312
|
|
|
310
313
|
The architecture makes that inspection practical. A public export has an owning
|
|
311
314
|
source and addressable preview coverage. Token definitions expose its visual
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.3
|
|
4
|
+
|
|
5
|
+
- Reject unused inline-style permissions and imperative property permissions.
|
|
6
|
+
Allow only the edited owner's boundary entries to accompany its source patch
|
|
7
|
+
atomically; preserve other settings and peer permissions.
|
|
8
|
+
- Keep directory unit ownership stable when removing an optional standalone
|
|
9
|
+
scenario; require actual coverage from remaining entries.
|
|
10
|
+
- Expose nonblocking pointer-capture cleanup advisories in CLI, MCP checks and
|
|
11
|
+
accepted mutations. Keep delegated cleanup and runtime correctness under review.
|
|
12
|
+
- Clarify demo framing, intentional product APIs and local asset fixtures in
|
|
13
|
+
diagnostics and skills; preserve component capabilities without demo-only props.
|
|
14
|
+
|
|
3
15
|
## 0.3.2
|
|
4
16
|
|
|
5
17
|
- Enforce foundation CSS roles, parsed selector boundaries, root-only shared token
|
package/docs/GUIDE.md
CHANGED
|
@@ -689,6 +689,15 @@ Registered project configuration and build helpers may import one another;
|
|
|
689
689
|
this boundary is derived when lint configuration is generated. It does not
|
|
690
690
|
allow application code to import private ADS modules or build helpers to import
|
|
691
691
|
private UI implementation.
|
|
692
|
+
|
|
693
|
+
Unused `inlineStyleFiles` entries and `styleWrites` properties fail the core
|
|
694
|
+
`stale-style-boundary` check. Inline permissions recognize JSX, imported UI
|
|
695
|
+
factories and UI `CSSProperties` use; property permissions require an exact write
|
|
696
|
+
or removal, not a read. For MCP migrations, include the owner's source and only
|
|
697
|
+
its own exception entries through its `ds.config.json` companion. Preserve peer
|
|
698
|
+
permissions and other settings. A future filename can be valid configuration,
|
|
699
|
+
but the complete checked candidate must contain the matching use.
|
|
700
|
+
|
|
692
701
|
`inlineStyleFiles` should name only documented geometry/renderer boundaries,
|
|
693
702
|
not broad styling exceptions. Ignore `.ds/`; generated paths refer to the project
|
|
694
703
|
and its locally installed tool.
|
package/docs/MCP.md
CHANGED
|
@@ -209,6 +209,22 @@ imports for canonical files created/deleted in that patch. Preserve other import
|
|
|
209
209
|
and comments, retain the foundation entry, and follow the required import order.
|
|
210
210
|
Both files pass the same candidate checks before either is written.
|
|
211
211
|
|
|
212
|
+
Runtime owner manifests also list `ds.config.json` in `companionFiles`. An owner's
|
|
213
|
+
patch may update only its own `inlineStyleFiles` and `boundaries.styleWrites`
|
|
214
|
+
entries together with its source. All other settings and peer permissions must
|
|
215
|
+
remain unchanged. This supports atomic JSX-to-imperative geometry migrations,
|
|
216
|
+
new boundaries and removal of obsolete permissions without bypassing the gate.
|
|
217
|
+
Unused inline exceptions and properties never written fail `stale-style-boundary`;
|
|
218
|
+
configuration-first registration of an unused boundary therefore fails validation.
|
|
219
|
+
General policy, component customization hooks and compiler changes still use the
|
|
220
|
+
`configuration` unit.
|
|
221
|
+
|
|
222
|
+
A directory owner's runtime entry (`index` or a source file matching its directory
|
|
223
|
+
name) or colocated fixture keeps ownership stable without `Scenario.tsx`.
|
|
224
|
+
A redundant standalone scenario can be removed when other registered scenarios
|
|
225
|
+
still declare and actually render its public exports. Coverage requirements remain
|
|
226
|
+
in force; there is no metadata waiver for uncovered UI.
|
|
227
|
+
|
|
212
228
|
The same discovery covers the rest of the project ADS:
|
|
213
229
|
|
|
214
230
|
| Unit | Scope |
|
|
@@ -262,6 +278,13 @@ not an edit to live ADS source. Binary `ads_read_file` results return the digest
|
|
|
262
278
|
byte count without encoded content. Git verifies the original binary blob and
|
|
263
279
|
reconstructs the result inside the validation copy.
|
|
264
280
|
|
|
281
|
+
Resolve ownership diagnostics by preserving the required capability in its proper
|
|
282
|
+
layer. Demo-only sizing, borders and framing belong in a public review/layout
|
|
283
|
+
pattern; a component API addition needs a runtime use case. Measured imperative
|
|
284
|
+
geometry uses exact `styleWrites` properties. After migrating JSX inline geometry,
|
|
285
|
+
replace its own boundary entries in the same owner patch and check the final state.
|
|
286
|
+
After the gates pass, verify the behavior in a browser.
|
|
287
|
+
|
|
265
288
|
A small change needs only its hunk. A complete new component may include its
|
|
266
289
|
implementation, public re-export, scenario and fixture in the same unit patch,
|
|
267
290
|
so coverage is checked against a complete candidate. New units use a new
|
|
@@ -378,6 +401,13 @@ and unchanged styles/fonts are reused. External hosts may create a fresh App for
|
|
|
378
401
|
each tool call. Each fresh App loads independently through the same contract.
|
|
379
402
|
There is no special local direct-artifact rendering path.
|
|
380
403
|
|
|
404
|
+
`ads_check` returns blocking `diagnostics` separately from bounded nonblocking
|
|
405
|
+
`advisories`, with `advisoryCount`. The checks resource contains the full result.
|
|
406
|
+
For example, `pointer-capture-review` reports capture without a locally visible
|
|
407
|
+
capture-loss handler. Cleanup can be delegated; inspect it and test cancellation,
|
|
408
|
+
capture loss and teardown. A local handler suppresses that advisory but does not
|
|
409
|
+
prove correct behavior. Accepted mutations also return bounded advisories.
|
|
410
|
+
|
|
381
411
|
### Host boundary and compatibility
|
|
382
412
|
|
|
383
413
|
The showcase's packaged shell retains its own minimal CSS and navigation. It
|