@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.
@@ -90,31 +90,29 @@ function isWrite(node) {
90
90
  || ts.isDeleteExpression(parent);
91
91
  }
92
92
 
93
- // Inspects style access itself, so aliases, computed keys, cssText and bulk writes
94
- // cannot silently turn a narrow property exception into a whole-file exception.
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
- if (isMember(node) && memberName(node) === "style") {
98
- const access = unwrapped(node);
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 scriptFiles = files.filter((file) => /\.[cm]?[jt]sx?$/.test(file));
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", "Scenarios and fixtures define no styles. Put the frame/layout in a public ADS pattern and product UI in ADS components or patterns.", offset.line);
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", "UI factories follow the same style ownership as JSX. Use semantic ADS props or a narrow renderer boundary.");
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", "Scenarios use public ADS props, not custom class names.");
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", "Scenario controls must render public ADS components.");
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", "Showcase and mock controls must use public ADS components. Implement missing reusable controls in the ADS, then import them here.");
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", "Showcase and mocks use known public ADS entities and props, not custom class names. Put styling in the owning ADS component or frame pattern.");
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", "Move visual policy into the local ADS, or document a narrow measured-geometry exception in inlineStyleFiles.");
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", "Do not expose CSSProperties-based visual plumbing. Use semantic props, or document a narrow JSX renderer boundary.");
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
 
@@ -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 };
@@ -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.
@@ -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. Token manifests list companionFiles for canonical foundation import changes required by token files created/deleted in the same patch. 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."}`,
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. Token manifests include companionFiles with the foundation entry path and allowed import changes. 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 }) => {
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 including export {};. 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. Set dryRun true to validate without writing. Fix rejected patches and inspect current previews after acceptance.",
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: "Move visual policy into the local ADS, or document a narrow exception in inlineStyleFiles." } },
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. Use `dryRun: true` to
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
- Put skin/state/animation in the Linaria owner, and scenario layout variations in
262
- the public review pattern's typed API. Browser review still verifies the effects.
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. Add generic architectural protections to
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. Use `dryRun: true` to
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
- Put skin/state/animation in the Linaria owner, and scenario layout variations in
214
- the public review pattern's typed API. Browser review still verifies the effects.
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. Add generic architectural protections to
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. Use `dryRun: true` to
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
- Put skin/state/animation in the Linaria owner, and scenario layout variations in
352
- the public review pattern's typed API. Browser review still verifies the effects.
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. Add generic architectural protections to
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. Use `dryRun: true` to
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
- Put skin/state/animation in the Linaria owner, and scenario layout variations in
319
- the public review pattern's typed API. Browser review still verifies the effects.
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. Add generic architectural protections to
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. Use `dryRun: true` to
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
- Put skin/state/animation in the Linaria owner, and scenario layout variations in
209
- the public review pattern's typed API. Browser review still verifies the effects.
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. Add generic architectural protections to
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
- const directoryUnits = files.filter(file => /(?:^|\/)Scenario\.[cm]?[jt]sx?$/.test(file)).map(file => slash(path.dirname(file))).sort((a, b) => b.length - a.length);
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 JSX styles and imperative style writes require narrow configured boundaries; public props omit inherited style and reject open-ended visual strings |
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,
@@ -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 and canonical foundation imports for token files created or deleted in
307
- the same patch. ADS restricts these companion edits and validates the complete
308
- candidate before writing, so coupled changes do not need a guard bypass.
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reqvire-org/ads",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "description": "A design system your agent can see and shape. Your components. Your look. Your code.",
6
6
  "repository": {