@reqvire-org/ads 0.3.17 → 0.3.18
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/atomic-composition.mjs +1 -1
- package/dist/check.mjs +30 -3
- package/dist/investigations.mjs +34 -3
- package/dist/linaria/preserve-values.cjs +21 -2
- package/dist/mcp/behavior-pages.mjs +36 -0
- package/dist/mcp/context.mjs +3 -1
- package/dist/mcp/execution-results.mjs +146 -0
- package/dist/mcp/mutations.mjs +3 -3
- package/dist/mcp/result-pages.mjs +87 -0
- package/dist/mcp/server.mjs +43 -28
- package/dist/property-registrations.mjs +39 -0
- package/dist/scenario-trace.mjs +1 -1
- package/dist/scoped-tokens.mjs +6 -0
- package/dist/skills/ads-break-ui/SKILL.md +6 -4
- package/dist/skills/ads-break-ui/references/atomic-styles.md +48 -0
- package/dist/skills/ads-break-ui/references/captures.md +6 -1
- package/dist/skills/ads-break-ui/references/evidence.md +52 -5
- package/dist/skills/ads-break-ui/references/guarded-editing.md +8 -5
- package/dist/skills/ads-designer/SKILL.md +6 -4
- package/dist/skills/ads-designer/references/atomic-styles.md +48 -0
- package/dist/skills/ads-designer/references/captures.md +6 -1
- package/dist/skills/ads-designer/references/evidence.md +52 -5
- package/dist/skills/ads-designer/references/guarded-editing.md +8 -5
- package/dist/skills/ads-import/SKILL.md +6 -4
- package/dist/skills/ads-import/references/atomic-styles.md +48 -0
- package/dist/skills/ads-import/references/captures.md +6 -1
- package/dist/skills/ads-import/references/evidence.md +52 -5
- package/dist/skills/ads-import/references/guarded-editing.md +8 -5
- package/dist/skills/ads-migrate/SKILL.md +6 -4
- package/dist/skills/ads-migrate/references/atomic-styles.md +48 -0
- package/dist/skills/ads-migrate/references/captures.md +6 -1
- package/dist/skills/ads-migrate/references/evidence.md +52 -5
- package/dist/skills/ads-migrate/references/guarded-editing.md +8 -5
- package/dist/skills/ads-motion/SKILL.md +6 -4
- package/dist/skills/ads-motion/references/atomic-styles.md +48 -0
- package/dist/skills/ads-motion/references/captures.md +6 -1
- package/dist/skills/ads-motion/references/evidence.md +52 -5
- package/dist/skills/ads-motion/references/guarded-editing.md +8 -5
- package/docs/ADS.md +10 -0
- package/docs/ARCHITECTURE.md +27 -0
- package/docs/BEHAVIORS.md +14 -2
- package/docs/CHANGELOG.md +17 -0
- package/docs/MCP.md +60 -14
- package/package.json +2 -1
package/dist/mcp/server.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/s
|
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { checkProject } from "../check.mjs";
|
|
7
7
|
import { loadConfig, sourceFiles } from "../config.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import { INVESTIGATIONS_URI, investigationIndex,
|
|
8
|
+
import { createVerificationController, BEHAVIOR_URI } from "../verification.mjs";
|
|
9
|
+
import { INVESTIGATIONS_URI, investigationIndex, investigationPage, investigationSections, readInvestigationResource } from "../investigations.mjs";
|
|
10
10
|
import { failure } from "../evidence.mjs";
|
|
11
11
|
import { inspectTokens } from "../tokens.mjs";
|
|
12
12
|
import { inspectScenarios } from "../scenarios.mjs";
|
|
@@ -26,6 +26,9 @@ import { reconcileTokenSamples } from "../token-fixtures.mjs";
|
|
|
26
26
|
import { createCaptureController } from "../capture.mjs";
|
|
27
27
|
import { CAPTURES_URI, captureSpec, screenshotFields } from "../capture-store.mjs";
|
|
28
28
|
import { previewSourceRevision } from "../preview-inputs.mjs";
|
|
29
|
+
import { createExecutionResults, RESULTS_URI, resultSections } from "./execution-results.mjs";
|
|
30
|
+
import { behaviorPage, behaviorSections } from "./behavior-pages.mjs";
|
|
31
|
+
import { compactProgress, resultBudgets } from "./result-pages.mjs";
|
|
29
32
|
|
|
30
33
|
const pkg = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
31
34
|
class InvalidParamsError extends ProtocolError {
|
|
@@ -40,10 +43,18 @@ function captureResponse(value, resourceLinks = false) {
|
|
|
40
43
|
}
|
|
41
44
|
const empty = z.object({}).strict();
|
|
42
45
|
const query = z.object({ query: z.string().max(200).optional(), limit: z.number().int().min(1).max(50).default(20) }).strict();
|
|
46
|
+
const recordRange = { recordIndex: z.number().int().min(0).optional(), offset: z.number().int().min(0).optional(), chars: z.number().int().min(1).max(2000).optional() };
|
|
47
|
+
const paged = { cursor: z.string().max(300).optional(), limit: z.number().int().min(1).max(resultBudgets.maxLimit).optional() };
|
|
43
48
|
const annotations = { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false };
|
|
44
49
|
const schemas = { params: z.object({ cursor: z.string().optional() }).passthrough(), result: z.object({ resultType: z.literal("complete") }).passthrough() };
|
|
45
50
|
const ownerPatch = z.object({ unit: z.string().min(1).max(256), 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.");
|
|
46
51
|
|
|
52
|
+
function resourceQuery(uri, allowed) {
|
|
53
|
+
const url=new URL(uri);
|
|
54
|
+
if([...url.searchParams.keys()].some(key=>!allowed.includes(key)))throw new Error("Unknown paged resource query.");
|
|
55
|
+
return Object.fromEntries([...url.searchParams].map(([key,value])=>[key,["limit","recordIndex","offset","chars"].includes(key)?Number(value):value]));
|
|
56
|
+
}
|
|
57
|
+
|
|
47
58
|
export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollInterval = 1000, previewWatch, showcaseUrl, notifications, inspection, mutations = false } = {}) {
|
|
48
59
|
const root = path.resolve(directory);
|
|
49
60
|
const cache = inspection ?? {};
|
|
@@ -51,6 +62,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
51
62
|
const captures = cache.captures ??= createCaptureController(root);
|
|
52
63
|
const edits = cache.edits ??= createMutationController(root, { captures });
|
|
53
64
|
const verification = cache.verification ??= createVerificationController(root);
|
|
65
|
+
const executionResults = cache.executionResults ??= createExecutionResults(root);
|
|
54
66
|
let options = loadConfig(root);
|
|
55
67
|
const signature = `${root}:${JSON.stringify(options.config)}`;
|
|
56
68
|
if (cache.signature !== signature) {
|
|
@@ -120,6 +132,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
120
132
|
{ uri: `${PROJECT_URI}/catalog`, name: "Public components and patterns", mimeType: "application/json" },
|
|
121
133
|
{ uri: `${PROJECT_URI}/scenarios`, name: "Independently mountable scenarios", mimeType: "application/json" },
|
|
122
134
|
{ uri: BEHAVIOR_URI, name: "Behavior contracts, demonstrations and current verification evidence", mimeType: "application/json" },
|
|
135
|
+
{ uri: RESULTS_URI, name: "Latest execution summaries and paged outcomes", mimeType: "application/json" },
|
|
123
136
|
{ uri: INVESTIGATIONS_URI, name: "Latest per-check investigation evidence", mimeType: "application/json" },
|
|
124
137
|
{ uri: CAPTURES_URI, name: "On-demand current and candidate screenshots", mimeType: "application/json" },
|
|
125
138
|
{ uri: `${PROJECT_URI}/checks`, name: "Current ADS architecture diagnostics", mimeType: "application/json" },
|
|
@@ -136,18 +149,18 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
136
149
|
];
|
|
137
150
|
}
|
|
138
151
|
function status() {
|
|
139
|
-
const context = project();
|
|
152
|
+
const context = project(), mutationState = mutations ? edits.status() : null, behaviorState = verification.status();
|
|
140
153
|
return { projectRoot: root, workflow: workflow([...previews(), ...scenarioApps()]), project: context,
|
|
141
154
|
package: pkg.name, version: pkg.version, dsDir: options.config.dsDir, publicAlias: options.config.publicAlias,
|
|
142
155
|
components: getCatalog().filter((item) => item.kind === "components" && item.apiKind === "ui").length,
|
|
143
156
|
patterns: getCatalog().filter((item) => item.kind === "patterns" && item.apiKind === "ui").length,
|
|
144
157
|
tokenCount: inspectTokens(root).tokens.length, showcaseUrl: getShowcaseUrl(),
|
|
145
158
|
previewWatch: previewWatch?.status() ?? { state: "disabled" },
|
|
146
|
-
mutations: mutations ?
|
|
159
|
+
mutations: mutations ? { ...mutationState, progress: compactProgress(mutationState.progress), lastResult: executionResults.latest("mutation") } : { enabled: false },
|
|
147
160
|
build: summarizeBuild(builds()),
|
|
148
161
|
previews: previews(), skills: skills.skills.map(({ uri, frontmatter }) => ({ uri, name: frontmatter.name, description: frontmatter.description })),
|
|
149
162
|
scenarios: { count: getRegistry().scenarios.length, diagnostics: getRegistry().diagnostics, resourceUri: `${PROJECT_URI}/scenarios`, tool: "ads_show_scenario" },
|
|
150
|
-
behaviors: { resourceUri: BEHAVIOR_URI, discoveryTool: "ads_behaviors", verificationTool: "ads_verify", investigationTool: "ads_investigate", investigationResourceUri: INVESTIGATIONS_URI, ...
|
|
163
|
+
behaviors: { resourceUri: BEHAVIOR_URI, discoveryTool: "ads_behaviors", verificationTool: "ads_verify", investigationTool: "ads_investigate", investigationResourceUri: INVESTIGATIONS_URI, ...behaviorState, progress: compactProgress(behaviorState.progress), lastResult: executionResults.latest("verification"), resultsTool: "ads_results", resultsResourceUri: RESULTS_URI },
|
|
151
164
|
captures: { resourceUri: CAPTURES_URI, tool: "ads_capture", clearTool: "ads_capture_clear", ...captures.status() },
|
|
152
165
|
experimental: { apps: true, nativeSkills: era === "modern", nativeSkillActivation: "host-controlled" } };
|
|
153
166
|
}
|
|
@@ -166,8 +179,9 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
166
179
|
else if (uri === `${PROJECT_URI}/catalog`) value = { items: getCatalog() };
|
|
167
180
|
else if (uri === `${PROJECT_URI}/scenarios`) value = getRegistry();
|
|
168
181
|
else if (uri === `${PROJECT_URI}/checks`) value = checkProject(root);
|
|
169
|
-
else if (uri === BEHAVIOR_URI) value =
|
|
170
|
-
else if (uri === INVESTIGATIONS_URI) value =
|
|
182
|
+
else if (uri === BEHAVIOR_URI || uri.startsWith(`${BEHAVIOR_URI}?`)) value = behaviorPage(options, resourceQuery(uri, ["unit", "api", "query", "section", "cursor", "limit"]));
|
|
183
|
+
else if (uri === INVESTIGATIONS_URI || uri.startsWith(`${INVESTIGATIONS_URI}?`)) value = investigationPage(root, resourceQuery(uri, ["unit", "check", "runId", "section", "cursor", "limit", "recordIndex", "offset", "chars"]));
|
|
184
|
+
else if (uri === RESULTS_URI || uri.startsWith("ads://results/")) return { contents: [executionResults.readResource(uri)] };
|
|
171
185
|
else if (uri === CAPTURES_URI) value = captureIndex();
|
|
172
186
|
else if (uri === PREVIEW_MANIFEST_URI && options.config.mcp.previewManifest) {
|
|
173
187
|
value = readPreviewManifest(previewOptions());
|
|
@@ -209,6 +223,11 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
209
223
|
return build;
|
|
210
224
|
}
|
|
211
225
|
|
|
226
|
+
async function finishExecution(kind, result) {
|
|
227
|
+
const summary = executionResults.save(kind, result);
|
|
228
|
+
try { await refresh(); } catch (error) { process.stderr.write(`ads mcp: result saved; notification failed: ${error.message}\n`); }
|
|
229
|
+
return { ...json(summary), ...(!result.ok ? { isError: true } : {}) };
|
|
230
|
+
}
|
|
212
231
|
async function applyEdits(args,signal,progress) {
|
|
213
232
|
const result = await edits.apply(args,signal,progress);
|
|
214
233
|
if (result.screenshots) await refresh();
|
|
@@ -219,7 +238,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
219
238
|
await refresh(true);
|
|
220
239
|
} 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.`}];}
|
|
221
240
|
}
|
|
222
|
-
return
|
|
241
|
+
return finishExecution("mutation", { ...(args.patches ? {units:args.patches.map(item=>item.unit),transaction:true} : {unit:args.unit}), ...result });
|
|
223
242
|
}
|
|
224
243
|
function tools() {
|
|
225
244
|
return [
|
|
@@ -283,23 +302,12 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
283
302
|
}
|
|
284
303
|
return json({ widget: { resourceUri: TOKENS_APP_URI }, category: null, tokenCount: inspectTokens(root).tokens.length, dataUri: `${PROJECT_URI}/tokens`, showcaseUrl: getShowcaseUrl() });
|
|
285
304
|
} },
|
|
286
|
-
{ name: "ads_behaviors", description: "Discover
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
if (unit) return item;
|
|
291
|
-
const apis = Object.values(item.subjects).filter(subject => subject.api).map(subject => subject.api);
|
|
292
|
-
const demonstrations = [...new Set(Object.values(item.demonstrations).flat())];
|
|
293
|
-
return { unit: item.unit, file: item.file, apis: apis.slice(0, 20), apiCount: apis.length,
|
|
294
|
-
guarantees: item.guarantees.slice(0, 5).map(guarantee => ({ id: guarantee.id, result: guarantee.result })), guaranteeCount: item.guarantees.length,
|
|
295
|
-
demonstrations: demonstrations.slice(0, 10), demonstrationCount: demonstrations.length, checkCount: Object.keys(item.checks).length };
|
|
296
|
-
}), diagnostics: inventory.diagnostics.slice(0, 50), resourceUri: BEHAVIOR_URI });
|
|
297
|
-
} },
|
|
298
|
-
{ name: "ads_investigate", title: "Inspect check evidence", description: "Read the latest compact investigation for an exact registered unit/check, including candidate runs. Returns failure boundary, exact input/observed preview revisions, source/failing step, observed viewport, failed startup requests and available artifact resource URIs. Does not rerun tests or infer root cause. Bundles replace the previous entry per check; compare revisions with ads_behaviors before claiming freshness.", schema: z.object({unit:z.string().min(1).max(256),check:z.string().min(1).max(100)}).strict(), call: args => json(readInvestigation(root,args)) },
|
|
299
|
-
{ name: "ads_verify", description: "Run one owner's registered development behavior checks using its configured runner/profile. Executes trusted project commands with bounded output, cancellation and process-tree cleanup; installs nothing. Progress and per-check timings are available through ads_status; a profile timeout bounds the whole run. Browser profiles use an isolated managed showcase/MCP App. React StrictMode requires a project development harness, not a production widget. Missing/skipped cases, stale inputs and process exit alone cannot produce passing evidence. Results separate failure boundaries and explicit acceptance scope; inspect retained source/request/artifact evidence with ads_investigate. Discover exact unit/check IDs with ads_behaviors first.", schema: z.object({ unit: z.string().min(1).max(256), checks: z.array(z.string().min(1).max(100)).min(1).max(100).optional() }).strict(), annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, call: async (args, signal, progress) => {
|
|
305
|
+
{ name: "ads_behaviors", description: "Discover behavior owners, then page an exact unit's checks, guarantees, subjects, demonstrations or profiles. Summary listings never embed runner logs. Default 10/max 20 entries and 8 KiB JSON per page; continue with nextCursor and identical filters. Cursors expire when discovery/evidence changes. Read sourceFile with ads_read_file for a complete contract. Metadata and reachable calls do not prove execution; use ads_verify for registered checks.", schema: z.object({ query: z.string().max(200).optional(), unit: z.string().min(1).max(256).optional(), api: z.string().max(200).optional(), section: z.enum(behaviorSections).optional(), ...paged }).strict(), call: args => json(behaviorPage(options,args)) },
|
|
306
|
+
{ name: "ads_results", title: "Read execution outcomes", description: "Read saved ads_verify/ads_apply_patch/ads_apply_transaction outcomes without rerunning anything. Pass the returned resultId; select outcomes, checks, diagnostics, advisories, warnings, changes, captures, acceptance, investigation or selection. Default 10/max 20 rows, at most 8 KiB JSON per page. Follow nextCursor with identical section/status. Rows marked detailTruncated can be read in full using recordIndex and offset/chars (max 2000); follow nextOffset. Only the latest verification and mutation snapshots are retained; replaced IDs fail explicitly. For one outcome's browser evidence use ads_investigate with its exact unit/check/runId.", schema: z.object({ resultId: z.string().uuid(), section: z.enum(resultSections).optional(), status: z.enum(["all","passed","not_passed"]).optional(), ...paged, ...recordRange }).strict(), call: args => json(executionResults.page(args)) },
|
|
307
|
+
{ name: "ads_investigate", title: "Inspect check evidence", description: "Read a summary for an exact unit/check, or list latest investigations when no check is selected. Pass the outcome's runId to reject superseded evidence. Summary is at most 4 KiB JSON; details, requests, browserErrors and artifacts are separate paged sections (default 10/max 20 entries, 8 KiB). Read truncated rows with recordIndex/offset/chars and the exact runId. Artifact URIs are read separately. Does not rerun checks or infer root cause. Only the latest bundle per check is retained, including candidate runs.", schema: z.object({unit:z.string().min(1).max(256).optional(),check:z.string().min(1).max(100).optional(),runId:z.string().min(1).max(100).optional(),section:z.enum(investigationSections).optional(),...paged,...recordRange}).strict(), call: args => json(investigationPage(root,args)) },
|
|
308
|
+
{ name: "ads_verify", description: "Run one owner's registered development behavior checks using its configured runner/profile. Executes trusted project commands with bounded output, cancellation and process-tree cleanup; installs nothing. Progress and per-check timings are available through ads_status; a profile timeout bounds the whole run. Browser profiles use an isolated managed showcase/MCP App. React StrictMode requires a project development harness, not a production widget. Missing/skipped cases, stale inputs and process exit alone cannot produce passing evidence. Replies contain counts, acceptance, at most three failure summaries and a resultId (4 KiB JSON maximum). Page outcomes with ads_results; inspect one exact unit/check/runId with ads_investigate. Discover exact unit/check IDs with ads_behaviors first.", schema: z.object({ unit: z.string().min(1).max(256), checks: z.array(z.string().min(1).max(100)).min(1).max(100).optional() }).strict(), annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }, call: async (args, signal, progress) => {
|
|
300
309
|
const result = await verification.run(args, signal, progress);
|
|
301
|
-
|
|
302
|
-
return { ...json(result), ...(!result.ok ? { isError: true } : {}) };
|
|
310
|
+
return finishExecution("verification", {unit:args.unit,...result});
|
|
303
311
|
} },
|
|
304
312
|
{ name: "ads_units", description: "Discover editable ADS units and their files: components/patterns with colocated previews; shared controllers; composition; tokens; foundation shared files; documentation including shared credits/license notices; 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 filePolicy for supported new documentation paths and 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 }) => {
|
|
305
313
|
const matches = edits.units().filter(item => unit ? item.id === unit : item.id.toLowerCase().includes(search.toLowerCase()));
|
|
@@ -311,11 +319,11 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
311
319
|
const {patch,...result}=reconcileTokenSamples(options,args);
|
|
312
320
|
return json({...result,removed:result.removed.slice(0,50),unresolved:result.unresolved.slice(0,50),unresolvedCount:result.unresolved.length,...storePreparedPatch(options,patch)});
|
|
313
321
|
}},
|
|
314
|
-
{ 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. Optional screenshot:true plus captures:[{scenario,target?,theme?,viewport?}] captures current/candidate only after gates pass. For genuinely coupled owner changes use ads_apply_transaction with one patch per owner. Fix rejected patches and inspect current previews after acceptance.",
|
|
322
|
+
{ 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. Optional screenshot:true plus captures:[{scenario,target?,theme?,viewport?}] captures current/candidate only after gates pass. For genuinely coupled owner changes use ads_apply_transaction with one patch per owner. Fix rejected patches and inspect current previews after acceptance. Reply is a 4 KiB summary; read diagnostics, outcomes and requested captures through ads_results using its resultId.",
|
|
315
323
|
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(), ...screenshotFields }).strict().refine(args => (args.patch !== undefined) !== (args.patchFile !== undefined), "Provide exactly one of patch or patchFile."),
|
|
316
324
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, _meta: { ui: { visibility: ["model"] } },
|
|
317
325
|
call: applyEdits,
|
|
318
|
-
}, {name:"ads_apply_transaction",description:"Apply genuinely coupled ADS changes as 2–8 distinct owner Git patches. Each patch must obey its unit and narrow companion rules. Patches run in order; later patches use the earlier candidate's shared-entry content. Core guards, registered Oxlint/project checks and affected behavior checks validate the complete final candidate before any live source is written. Maximum 32 distinct files/16 MiB combined. Dry runs, failures and cancellation publish nothing; publication rolls back on write failure. Optional screenshot:true with dryRun:true and explicit captures selects current/candidate images. Prefer ads_apply_patch for independent edits.",schema:z.object({patches:z.array(ownerPatch).min(2).max(mutationLimits.units),dryRun:z.boolean(),...screenshotFields}).strict(),annotations:{readOnlyHint:false,destructiveHint:true,idempotentHint:false,openWorldHint:false},_meta:{ui:{visibility:["model"]}},call:applyEdits}] : []),
|
|
326
|
+
}, {name:"ads_apply_transaction",description:"Apply genuinely coupled ADS changes as 2–8 distinct owner Git patches. Each patch must obey its unit and narrow companion rules. Patches run in order; later patches use the earlier candidate's shared-entry content. Core guards, registered Oxlint/project checks and affected behavior checks validate the complete final candidate before any live source is written. Maximum 32 distinct files/16 MiB combined. Dry runs, failures and cancellation publish nothing; publication rolls back on write failure. Optional screenshot:true with dryRun:true and explicit captures selects current/candidate images. Prefer ads_apply_patch for independent edits. Reply is a bounded summary; page diagnostics, outcomes and captures with ads_results using the returned resultId.",schema:z.object({patches:z.array(ownerPatch).min(2).max(mutationLimits.units),dryRun:z.boolean(),...screenshotFields}).strict(),annotations:{readOnlyHint:false,destructiveHint:true,idempotentHint:false,openWorldHint:false},_meta:{ui:{visibility:["model"]}},call:applyEdits}] : []),
|
|
319
327
|
...options.config.mcp.apps.map((app) => ({ name: `ads_show_${app.id.replaceAll("-", "_")}`, title: app.title, description: `${app.description ?? app.title}. Renders the project's built ${app.kind} examples through MCP Apps; text fallback provides the showcase URL.`, schema: empty, _meta: { ui: { resourceUri: appUri(app.id) } }, call: async (_args, signal) => {
|
|
320
328
|
// Validate readiness without embedding executable HTML in model results.
|
|
321
329
|
const build = await previewBuild(app, signal);
|
|
@@ -339,7 +347,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
339
347
|
let progress = 0;
|
|
340
348
|
const token = context.mcpReq._meta?.progressToken;
|
|
341
349
|
const notify = token === undefined ? undefined : value => {
|
|
342
|
-
void context.mcpReq.notify({ method: "notifications/progress", params: { progressToken: token, progress: ++progress, message: value.message ?? `${value.profile}: ${value.completed}/${value.total} completed; ${value.active.map(item => `${item.unit}#${item.check}`).join(", ") || "no active check reported"}; ${value.elapsedMs} ms elapsed` } }).catch(() => {});
|
|
350
|
+
void context.mcpReq.notify({ method: "notifications/progress", params: { progressToken: token, progress: ++progress, message: value.message ?? `${value.profile}: ${value.completed}/${value.total} completed; ${(value.active ?? []).slice(0,3).map(item => `${item.unit}#${item.check}`).join(", ") || "no active check reported"}; ${value.elapsedMs} ms elapsed` } }).catch(() => {});
|
|
343
351
|
};
|
|
344
352
|
try { return await tool.call(input.data, context.mcpReq.signal, notify); }
|
|
345
353
|
catch (error) { return { isError: true, content: [{ type: "text", text: error.message }], structuredContent: {ok:false,message:error.message,failure:failure(error.build?"build":"runner",error.build?"preview-build":"tool-execution",error.build?.code??"ADS_TOOL_ERROR",{certainty:"observed-boundary"}),...(error.build?{build:error.build}:{})} }; }
|
|
@@ -352,11 +360,15 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
352
360
|
});
|
|
353
361
|
server.setRequestHandler("resources/templates/list", () => ({ resourceTemplates: [
|
|
354
362
|
{ uriTemplate: "ads://captures/{key}/{slot}", name: "Current/candidate screenshot", description: "Exact keys and available slots come from ads://project/captures. Slots are replaced in place, not revision history.", mimeType: "image/png" },
|
|
363
|
+
{ uriTemplate: "ads://results/{resultId}", name: "Saved execution summary", description: "Use resultId from execution or ads://project/results. Query section/status/cursor/limit for pages, or section/recordIndex/offset/chars for exact record ranges. Only the latest result of each kind remains available.", mimeType: "application/json" },
|
|
355
364
|
{ uriTemplate: "ads://investigations/{unit}/{check}", name: "Latest check investigation", description: "Use encoded unit/check segments from ads://project/investigations. One latest bundle per check, including candidate verification.", mimeType: "application/json" },
|
|
356
365
|
] }));
|
|
357
366
|
server.setRequestHandler("resources/read", ({ params: { uri } }, context) => read(uri, context.mcpReq.signal));
|
|
358
367
|
server.setRequestHandler("resources/subscribe", ({ params: { uri } }) => {
|
|
359
368
|
if (!resources().some((entry) => entry.uri === uri)) {
|
|
369
|
+
if(uri.startsWith("ads://results/")) {
|
|
370
|
+
try {executionResults.readResource(uri);subscribed.add(uri);return {};}catch{throw new ResourceNotFoundError(uri,"Unknown or superseded execution result.");}
|
|
371
|
+
}
|
|
360
372
|
if(uri.startsWith("ads://investigations/")) {
|
|
361
373
|
try {readInvestigationResource(root,uri);subscribed.add(uri);return {};}catch{throw new ResourceNotFoundError(uri,"Unknown check investigation.");}
|
|
362
374
|
}
|
|
@@ -402,6 +414,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
402
414
|
let previousCaptureUris = captures.store.resourceUris();
|
|
403
415
|
const investigationFingerprint=()=>fileFingerprint([path.join(root,".ds/investigations.json")]);
|
|
404
416
|
let previousInvestigations=investigationFingerprint();
|
|
417
|
+
let previousResults=executionResults.fingerprint();
|
|
405
418
|
async function refresh(force = false) {
|
|
406
419
|
// Polling covers replaced files and new directories consistently across OSes.
|
|
407
420
|
cache.documents = undefined;
|
|
@@ -415,7 +428,8 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
415
428
|
const assetsChanged = assets !== previousAssets;
|
|
416
429
|
const capturesChanged = captures.store.fingerprint() !== previousCaptures;
|
|
417
430
|
const investigationsChanged=investigationFingerprint()!==previousInvestigations;
|
|
418
|
-
|
|
431
|
+
const resultsChanged=executionResults.fingerprint()!==previousResults;
|
|
432
|
+
if (!filesChanged && !buildChanged && !artifactsChanged && !assetsChanged && !capturesChanged && !investigationsChanged && !resultsChanged) return false;
|
|
419
433
|
if (filesChanged) {
|
|
420
434
|
const updated = loadConfig(root);
|
|
421
435
|
await previewWatch?.reconfigure?.(updated.config.mcp);
|
|
@@ -433,6 +447,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
433
447
|
previousBuild = JSON.stringify(builds());
|
|
434
448
|
const targets = filesChanged ? era === "modern" ? resources().map((entry) => entry.uri).filter((uri) => !skills.files.has(uri)) : [...subscribed]
|
|
435
449
|
: [BUILD_URI, BEHAVIOR_URI, INVESTIGATIONS_URI, `${PROJECT_URI}/status`, ...(options.config.mcp.previewManifest ? [PREVIEW_MANIFEST_URI] : []), ...(artifactsChanged ? allApps().map(app => appUri(app.id)) : [])].filter((uri) => era === "modern" || subscribed.has(uri));
|
|
450
|
+
if(resultsChanged){targets.push(...[RESULTS_URI,...executionResults.index().results.map(entry=>entry.resourceUri),...[...subscribed].filter(uri=>uri.startsWith("ads://results/"))].filter(uri=>era==="modern"||subscribed.has(uri)));previousResults=executionResults.fingerprint();}
|
|
436
451
|
if(investigationsChanged){targets.push(...[INVESTIGATIONS_URI,...investigationIndex(root).entries.map(entry=>entry.resourceUri),...[...subscribed].filter(uri=>uri.startsWith("ads://investigations/"))].filter(uri=>era==="modern"||subscribed.has(uri)));previousInvestigations=investigationFingerprint();}
|
|
437
452
|
if (capturesChanged || filesChanged) {
|
|
438
453
|
const current = (captures.store.fingerprint() ? captureIndex().captures : []).flatMap(record => [record.current?.resourceUri, record.candidate?.resourceUri].filter(Boolean));
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ident, lexer, parse, string, walk, generate } from "css-tree";
|
|
2
|
+
|
|
3
|
+
// CSS Properties and Values API Level 1 syntax types, not project token roles.
|
|
4
|
+
const types=new Set(["angle","color","custom-ident","image","integer","length","length-percentage","number","percentage","resolution","string","time","transform-function","transform-list","url"]);
|
|
5
|
+
const relative=/^(?:r?em|r?ex|r?cap|r?ch|r?ic|r?lh|[sld]?v(?:w|h|i|b|min|max)|cq(?:w|h|i|b|min|max))$/i;
|
|
6
|
+
const descriptors=new Set(["syntax","inherits","initial-value"]);
|
|
7
|
+
|
|
8
|
+
/** Registration metadata has its own CSS grammar; it is not an element style. */
|
|
9
|
+
export function propertyRegistration(rule) {
|
|
10
|
+
const name=ident.decode(rule.params.trim());
|
|
11
|
+
if(!name.startsWith("--")||name==="--"||lexer.matchType("custom-property-name",rule.params).error)throw new Error("@property needs one literal custom-property name.");
|
|
12
|
+
for(let parent=rule.parent;parent&&parent.type!=="root";parent=parent.parent)if(parent.type!=="atrule"||["property","font-face"].includes(parent.name.toLowerCase())||/keyframes$/i.test(parent.name))throw new Error("@property is document-global. Place it at template/sheet level or inside conditional at-rules, not element selectors or another descriptor block.");
|
|
13
|
+
const values={};
|
|
14
|
+
for(const node of rule.nodes??[]){
|
|
15
|
+
if(node.type==="comment")continue;
|
|
16
|
+
if(node.type!=="decl"||!descriptors.has(node.prop.toLowerCase())||node.important||Object.hasOwn(values,node.prop.toLowerCase()))throw new Error("@property accepts only one syntax, inherits and initial-value descriptor; no styles, nested rules or !important.");
|
|
17
|
+
values[node.prop.toLowerCase()]=node.value;
|
|
18
|
+
}
|
|
19
|
+
const syntaxAst=parse(values.syntax??"",{context:"value"}),syntaxNode=syntaxAst.children.first;
|
|
20
|
+
if(syntaxAst.children.size!==1||syntaxNode?.type!=="String")throw new Error("@property syntax must be one CSS string.");
|
|
21
|
+
const syntax=string.decode(generate(syntaxNode)).trim();
|
|
22
|
+
if(syntax!=="*"&&(!syntax||!syntax.split("|").every(part=>{
|
|
23
|
+
part=part.trim();const multiplier=/[+#]$/.test(part)?part.at(-1):"",base=multiplier?part.slice(0,-1).trim():part,match=/^<([a-z-]+)>$/.exec(base);
|
|
24
|
+
return match?types.has(match[1])&&!(match[1]==="transform-list"&&multiplier):!!base&&!lexer.matchType("custom-ident",base).error;
|
|
25
|
+
})))throw new Error("Invalid @property syntax. Use the CSS Properties and Values API grammar, not a project-specific type.");
|
|
26
|
+
if(!/^(?:true|false)$/i.test(values.inherits??""))throw new Error("@property requires inherits: true or false.");
|
|
27
|
+
if(values["initial-value"]===undefined&&syntax!=="*")throw new Error("Typed @property registrations require initial-value.");
|
|
28
|
+
if(values["initial-value"]!==undefined){
|
|
29
|
+
const initial=parse(values["initial-value"],{context:"value"});
|
|
30
|
+
if(syntax!=="*"){
|
|
31
|
+
walk(initial,node=>{
|
|
32
|
+
if(node.type==="Function"&&/^(?:var|env|attr)$/i.test(node.name)||node.type==="Dimension"&&relative.test(node.unit)||node.type==="Identifier"&&/^(?:currentcolor|inherit|initial|unset|revert(?:-layer)?)$/i.test(node.name))throw new Error("Typed @property initial-value must be computationally independent: no var(), relative units or currentColor. Keep design assignments token-backed outside this descriptor.");
|
|
33
|
+
});
|
|
34
|
+
const match=lexer.match(syntax,initial);
|
|
35
|
+
if(match.error)throw new Error("@property initial-value must match its declared syntax.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {name,signature:JSON.stringify([syntax,values.inherits.toLowerCase(),values["initial-value"]===undefined?null:generate(parse(values["initial-value"],{context:"value"}))])};
|
|
39
|
+
}
|
package/dist/scenario-trace.mjs
CHANGED
|
@@ -120,7 +120,7 @@ export function createScenarioTrace(program, options) {
|
|
|
120
120
|
let declarations = 0;
|
|
121
121
|
const template = node.template;
|
|
122
122
|
const text = ts.isNoSubstitutionTemplateLiteral(template) ? template.text : template.head.text + template.templateSpans.map(span => `__ads_expression__${span.literal.text}`).join("");
|
|
123
|
-
try { postcss.parse(text).walkDecls(()
|
|
123
|
+
try { postcss.parse(text).walkDecls(declaration => { for(let parent=declaration.parent;parent;parent=parent.parent)if(parent.type==="atrule"&&/^(?:property|font-face|(?:-\w+-)?keyframes)$/i.test(parent.name))return; ++declarations; }); } catch { /* Source CSS guards own malformed templates. */ }
|
|
124
124
|
if (owned(source, via) && declarations) return result(true, true);
|
|
125
125
|
reasons.add("The returned style must originate in runtime component/pattern Linaria through a public callable helper.");
|
|
126
126
|
return result(false);
|
package/dist/scoped-tokens.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { inspectUnits } from "./units.mjs";
|
|
|
6
6
|
import { isDevelopmentFile } from "./config.mjs";
|
|
7
7
|
import { foundationSelector } from "./foundation.mjs";
|
|
8
8
|
import { isSharedToken, tokenContract } from "./token-policy.mjs";
|
|
9
|
+
import { propertyRegistration } from "./property-registrations.mjs";
|
|
9
10
|
|
|
10
11
|
/** An explicit owner contract, never a heuristic based on a token's name. */
|
|
11
12
|
export function scopedTokens(options) {
|
|
@@ -30,8 +31,13 @@ export function scopedTokens(options) {
|
|
|
30
31
|
if (realpathSync(file) !== file) throw new Error("Scoped token stylesheets cannot resolve through symlinks.");
|
|
31
32
|
const css = postcss.parse(readFileSync(file, "utf8"), {from:file});
|
|
32
33
|
let count = 0;
|
|
34
|
+
css.walkAtRules(/^property$/i,rule=>{
|
|
35
|
+
try {const {name}=propertyRegistration(rule);let defined=false;css.walkDecls(name,()=>{defined=true;});if(!name.startsWith(data.namespace)||!defined)throw new Error("Register only a real token defined in this owner's namespace and sheet.");}
|
|
36
|
+
catch(error){report(stylesheet,error.message,rule.source?.start.line);}
|
|
37
|
+
});
|
|
33
38
|
css.walk(node => {
|
|
34
39
|
if (node.type === "comment" || node.type === "root") return;
|
|
40
|
+
if(node.type==="atrule"&&node.name.toLowerCase()==="property"||node.type==="decl"&&node.parent.type==="atrule"&&node.parent.name.toLowerCase()==="property")return; // Separately validated registration metadata.
|
|
35
41
|
if (node.type === "atrule" && ["media", "supports", "layer"].includes(node.name) && node.nodes) return;
|
|
36
42
|
if (node.type === "rule" && node.parent.type !== "rule" && !foundationSelector(node.selector, {definitions:true})) return;
|
|
37
43
|
if (node.type === "decl" && node.parent.type === "rule" && node.prop.startsWith(data.namespace) && node.prop.length > data.namespace.length && !node.important) { count++; return; }
|
|
@@ -54,18 +54,20 @@ For on-demand current/candidate screenshots and visual dry-run review, read
|
|
|
54
54
|
[captures.md](references/captures.md).
|
|
55
55
|
For a rejected patch, failed check or loading error, read
|
|
56
56
|
[evidence and diagnosis](references/evidence.md); inspect the failing boundary
|
|
57
|
-
|
|
57
|
+
then page needed `ads_results` sections using the returned result ID. Inspect
|
|
58
|
+
one `ads_investigate({unit,check,runId})` summary before changing APIs or policy;
|
|
59
|
+
load request/error/artifact details only when relevant.
|
|
58
60
|
|
|
59
61
|
|
|
60
62
|
For parent/child `covers`, conditional-control review, reference comparisons,
|
|
61
|
-
public style-helper use, fragment
|
|
62
|
-
`showcase-class` findings, read [scenario contracts](references/scenarios.md). Its verification
|
|
63
|
+
public style-helper use, native slot/selector composition, fragment navigation,
|
|
64
|
+
or `scenario-render` / `showcase-control` / `showcase-class` findings, read [scenario contracts](references/scenarios.md). Its verification
|
|
63
65
|
examples explain API visibility, reference baselines and state-transition checks.
|
|
64
66
|
Traced parents can cover declared public children; verified public Linaria
|
|
65
67
|
helpers do not grant custom scenario CSS.
|
|
66
68
|
|
|
67
69
|
For atomic base/variant styling, helper/wrapper class composition, extracted
|
|
68
|
-
custom-property arithmetic, or an
|
|
70
|
+
custom-property arithmetic, typed `@property` registrations, or an
|
|
69
71
|
`atomic-composition-review` / `atomic-composition-limit` advisory, read
|
|
70
72
|
[atomic style composition](references/atomic-styles.md). The same guide is served
|
|
71
73
|
as `ads://guides/atomic-styles`. It explains shorthand/logical conflicts and
|
|
@@ -81,6 +81,54 @@ const treatment = css`
|
|
|
81
81
|
This is a distinct state, not two competing base atoms. Whether changing padding
|
|
82
82
|
on hover is desirable remains a product decision.
|
|
83
83
|
|
|
84
|
+
## Register typed custom properties without atomizing their descriptors
|
|
85
|
+
|
|
86
|
+
Use a literal `@property` in its component/pattern's Linaria template when the
|
|
87
|
+
browser needs a typed, interpolated runtime variable:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
const treatment = css`
|
|
91
|
+
@property --ds-example-progress {
|
|
92
|
+
syntax: "<number>";
|
|
93
|
+
inherits: false;
|
|
94
|
+
initial-value: 0;
|
|
95
|
+
}
|
|
96
|
+
opacity: var(--ds-example-progress);
|
|
97
|
+
`;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The identifier is illustrative; use a unique name owned by the real unit.
|
|
101
|
+
Registrations are document-global, even when authored beside one component.
|
|
102
|
+
Put them at template level or inside conditional at-rules, never element selectors.
|
|
103
|
+
Use the same ADS tag resolver described below in both product and preview builds;
|
|
104
|
+
it preserves each complete registration and its conditional context instead of
|
|
105
|
+
splitting syntax/inherits/initial-value into invalid atomic rules. This works for
|
|
106
|
+
atomic `css` and `styled`; the browser receives CSS without a registration runtime.
|
|
107
|
+
MCP delivers it inside the App document, not the outer showcase host.
|
|
108
|
+
|
|
109
|
+
`property-registration` validates descriptor shape, CSS syntax/initial-value and
|
|
110
|
+
ownership, and rejects conflicting schemas or registrations shared by unrelated
|
|
111
|
+
owners. Identical registrations from the same owner are permitted. Runtime
|
|
112
|
+
`--ds-*` / `--ux-*` variables stay with their runtime owner. A shared token's
|
|
113
|
+
registration belongs beside its actual definition in a foundation value sheet;
|
|
114
|
+
a namespaced owner token's registration belongs in its registered `tokens.css`.
|
|
115
|
+
Neither `base.css` nor `fonts.css` becomes a registration escape hatch. Registration
|
|
116
|
+
alone does not define a token or satisfy its sample/fixture coverage.
|
|
117
|
+
|
|
118
|
+
Typed initial values must match their syntax and be computationally independent:
|
|
119
|
+
`0px` is valid metadata; `var(--space-4)`, `1rem` and `currentColor` are not.
|
|
120
|
+
The literal exemption applies only to registration descriptors. Keep design
|
|
121
|
+
assignments outside them token-backed; a technical fallback is not a place to
|
|
122
|
+
hide a component skin. Use literal static descriptors so guards can inspect them.
|
|
123
|
+
See the [W3C registration contract](https://www.w3.org/TR/css-properties-values-api-1/#at-property-rule).
|
|
124
|
+
|
|
125
|
+
Verify extracted CSS and actual interpolation, including after minification and
|
|
126
|
+
inside the MCP App. A declaration can parse while a browser ignores an invalid
|
|
127
|
+
registration. Keep required browser fallbacks and reduced-motion behavior; do not
|
|
128
|
+
replace a valid CSS implementation with script merely to evade extraction or a
|
|
129
|
+
guard limitation. These checks cover declarative registrations; arbitrary dynamic
|
|
130
|
+
`CSS.registerProperty()` calls are not an ownership proof.
|
|
131
|
+
|
|
84
132
|
## Preserve custom-property arithmetic during extraction
|
|
85
133
|
|
|
86
134
|
Custom properties can hold an unevaluated token stream that becomes arithmetic
|
|
@@ -101,7 +101,12 @@ failed/cancelled capture does not replace existing image slots or apply source.
|
|
|
101
101
|
If a scenario/target is newly introduced, `current` is null with an explicit
|
|
102
102
|
`currentAbsent` explanation; only the real candidate image exists.
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
The edit reply is a summary. Read `ads_results({resultId,section:"captures"})` for
|
|
105
|
+
the requested image resource URIs and `section:"diagnostics"` for relevant
|
|
106
|
+
messages; follow `nextCursor` when needed. Read image resources explicitly.
|
|
107
|
+
Standalone `ads_capture` continues returning its selected capture directly.
|
|
108
|
+
|
|
109
|
+
Review those images and diagnostics, fix the draft if necessary, then apply
|
|
105
110
|
with `dryRun:false`. Apply reruns all gates. An image is promoted only when the
|
|
106
111
|
accepted source revision matches the captured candidate; other proposals remain
|
|
107
112
|
stale. Images do not approve a patch or certify interaction/accessibility/parity.
|
|
@@ -20,21 +20,67 @@ A complete failed run can contain independent passing cases. Suite interruption,
|
|
|
20
20
|
missing/skipped cases, stale inputs and an unexplained process exit cannot certify
|
|
21
21
|
a pass. Keep the reported case outcome separate from whole-run acceptance.
|
|
22
22
|
|
|
23
|
+
## Retrieve only the evidence needed
|
|
24
|
+
|
|
25
|
+
`ads_verify`, `ads_apply_patch` and `ads_apply_transaction` reply with counts,
|
|
26
|
+
bounded acceptance, at most three failure previews and `resultId`, within 4096
|
|
27
|
+
UTF-8 JSON bytes. Full validation still decides acceptance. Do not repeat a suite
|
|
28
|
+
merely to recover its output, or restate the complete report in conversation.
|
|
29
|
+
|
|
30
|
+
Read a relevant saved section:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
ads_results({resultId: "<returned ID>", section: "outcomes", status: "not_passed"})
|
|
34
|
+
// Continue only when needed, with the same section and status:
|
|
35
|
+
ads_results({resultId: "<returned ID>", section: "outcomes", status: "not_passed", cursor: "<nextCursor>"})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Pages default to 10 rows, allow at most 20 and stay within 8192 JSON bytes. The
|
|
39
|
+
byte cap can return fewer rows; `nextCursor` is the continuation. Other sections
|
|
40
|
+
are `checks` (pipeline steps), `diagnostics`, `advisories`, `warnings`, `changes`,
|
|
41
|
+
`captures`, `acceptance`, `investigation` (mutation boundary) and `selection`.
|
|
42
|
+
A row marked `detailTruncated` is a preview. To read that exact record, pass its
|
|
43
|
+
`recordIndex`, the same result/section, and `offset`/`chars` (maximum 2000 UTF-16
|
|
44
|
+
characters); follow `nextOffset`. Do not combine ranges with a cursor/status filter.
|
|
45
|
+
Read only the relevant log range. Reading pages or ranges never executes checks.
|
|
46
|
+
|
|
47
|
+
`ads://project/results` lists the latest IDs. `ads://results/<resultId>` reads a
|
|
48
|
+
summary; matching query parameters read pages/ranges. Only the latest verification
|
|
49
|
+
and latest mutation snapshots are retained, independently, up to 8 MiB each.
|
|
50
|
+
A superseded ID fails explicitly. An oversized report retains only its summary
|
|
51
|
+
with a warning; disk failure retains details in server memory until shutdown.
|
|
52
|
+
Neither changes actual acceptance or applied state. These are logical JSON byte
|
|
53
|
+
budgets, not token estimates; compatibility text can duplicate structured JSON.
|
|
54
|
+
Saved outcomes describe completion-time evidence, not current source freshness;
|
|
55
|
+
compare their revisions with `ads_behaviors` before claiming a current pass.
|
|
56
|
+
|
|
57
|
+
`ads_behaviors` pages owner summaries, then an exact unit's `checks`, `guarantees`,
|
|
58
|
+
`subjects`, `demonstrations` or `profiles`. Use `nextCursor` and identical filters;
|
|
59
|
+
changes invalidate a discovery cursor. Read a truncated contract via its
|
|
60
|
+
`sourceFile` and bounded `ads_read_file`, rather than requesting everything.
|
|
61
|
+
|
|
23
62
|
## Get a compact investigation
|
|
24
63
|
|
|
25
|
-
|
|
64
|
+
Take an outcome's exact owner/check/run IDs from `ads_results`, then:
|
|
26
65
|
|
|
27
66
|
```json
|
|
28
|
-
{ "unit": "components/Example", "check": "keyboard-navigation" }
|
|
67
|
+
{ "unit": "components/Example", "check": "keyboard-navigation", "runId": "<outcome runId>" }
|
|
29
68
|
```
|
|
30
69
|
|
|
31
|
-
Send this to `ads_investigate`. It reads
|
|
70
|
+
Send this to `ads_investigate`. It reads a summary within 4096 JSON bytes, without
|
|
71
|
+
running code. The run ID rejects evidence superseded by another execution.
|
|
72
|
+
`section: "details"`, `"requests"`, `"browserErrors"` or `"artifacts"` reads separate pages
|
|
73
|
+
(default 10/max 20 rows, at most 8192 JSON bytes); artifacts themselves require
|
|
74
|
+
explicit resource reads. Truncated investigation records also support
|
|
75
|
+
`recordIndex`/`offset`/`chars`; keep the exact `runId` to avoid mixing evidence.
|
|
76
|
+
Without a check, the tool pages available investigations.
|
|
32
77
|
`ads://project/investigations` lists available entries; the returned `resourceUri`
|
|
33
78
|
reads the same bundle. It records:
|
|
34
79
|
|
|
35
80
|
- Project versus isolated candidate, configured framework/profile and scenario.
|
|
36
81
|
- Exact input revision and the preview revision actually observed by the helper.
|
|
37
|
-
-
|
|
82
|
+
- `details.failingStep`, `details.source`, startup viewport and failure boundary.
|
|
83
|
+
- Counts for separately paged relevant requests, browser errors and artifacts.
|
|
38
84
|
- Available registered trace, screenshot or context artifact resource URIs.
|
|
39
85
|
- Explicit `acceptance.established` and `acceptance.unverified` scope.
|
|
40
86
|
|
|
@@ -58,7 +104,8 @@ Candidate evidence never becomes passing live-source evidence. Compare the
|
|
|
58
104
|
recorded input revision with current `ads_behaviors` evidence; a bundle describes
|
|
59
105
|
its run, and does not claim current source merely because it remains available.
|
|
60
106
|
For a build/architecture rejection before behavior runs, inspect the mutation
|
|
61
|
-
|
|
107
|
+
saved `investigation` section via `ads_results`. Status retains the compact
|
|
108
|
+
execution summary and result ID, rather than embedding complete reports.
|
|
62
109
|
|
|
63
110
|
## Preserve the design while repairing the failure
|
|
64
111
|
|
|
@@ -15,8 +15,10 @@ ads_apply_patch({"unit":"components/Example","patchFile":"<returned patchFile>",
|
|
|
15
15
|
|
|
16
16
|
The dry run validates the full candidate, including affected registered behavior
|
|
17
17
|
checks in an isolated project copy. Configured browser checks use that candidate's
|
|
18
|
-
managed preview, not the live showcase.
|
|
19
|
-
|
|
18
|
+
managed preview, not the live showcase. Read the summary, then page failing
|
|
19
|
+
`outcomes`, `diagnostics` or pipeline `checks` through `ads_results` with its
|
|
20
|
+
`resultId`. Inspect one exact outcome with `ads_investigate({unit,check,runId})`;
|
|
21
|
+
fix the draft and diff again. See [evidence.md](evidence.md) for page/range limits.
|
|
20
22
|
Keep behavior.json/specs with their owner and use registered checks; a scratchpad
|
|
21
23
|
run against live source cannot verify unapplied changes. After validation, apply
|
|
22
24
|
with `dryRun:false`; ADS revalidates before writing. Dry-run evidence is not saved
|
|
@@ -120,9 +122,10 @@ them through their actual public composition/customization path and list their r
|
|
|
120
122
|
render references in covers. Do not count internal production renders as public API
|
|
121
123
|
coverage. Behavioral APIs use exercises and registered guarantees instead.
|
|
122
124
|
Scenario controls still use public UI. A native link/button can supply semantics
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
through a public runtime slot that demonstrably clones its child with owned
|
|
126
|
+
props, or through matching runtime-owned selectors attached to the actual rendered
|
|
127
|
+
DOM. Source ownership must be proven; an `asChild` flag or a lookalike marker is
|
|
128
|
+
insufficient. Standalone controls and custom scenario styling remain rejected. See
|
|
126
129
|
[scenarios.md](scenarios.md) for evidence and limits. Preserve the intended
|
|
127
130
|
composition instead of substituting an unrelated styled control or adding a
|
|
128
131
|
demo-only component prop. Review framing belongs in public layout patterns.
|
|
@@ -86,17 +86,19 @@ For on-demand current/candidate screenshots and visual dry-run review, read
|
|
|
86
86
|
[captures.md](references/captures.md).
|
|
87
87
|
For a rejected patch, failed check or loading error, read
|
|
88
88
|
[evidence and diagnosis](references/evidence.md); inspect the failing boundary
|
|
89
|
-
|
|
89
|
+
then page needed `ads_results` sections using the returned result ID. Inspect
|
|
90
|
+
one `ads_investigate({unit,check,runId})` summary before changing APIs or policy;
|
|
91
|
+
load request/error/artifact details only when relevant.
|
|
90
92
|
|
|
91
93
|
For parent/child `covers`, conditional-control review, reference comparisons,
|
|
92
|
-
public style-helper use, fragment
|
|
93
|
-
`showcase-class` findings, read [scenario contracts](references/scenarios.md). Its verification
|
|
94
|
+
public style-helper use, native slot/selector composition, fragment navigation,
|
|
95
|
+
or `scenario-render` / `showcase-control` / `showcase-class` findings, read [scenario contracts](references/scenarios.md). Its verification
|
|
94
96
|
examples explain API visibility, reference baselines and state-transition checks.
|
|
95
97
|
Traced parents can cover declared public children; verified public Linaria
|
|
96
98
|
helpers do not grant custom scenario CSS.
|
|
97
99
|
|
|
98
100
|
For atomic base/variant styling, helper/wrapper class composition, extracted
|
|
99
|
-
custom-property arithmetic, or an
|
|
101
|
+
custom-property arithmetic, typed `@property` registrations, or an
|
|
100
102
|
`atomic-composition-review` / `atomic-composition-limit` advisory, read
|
|
101
103
|
[atomic style composition](references/atomic-styles.md). The same guide is served
|
|
102
104
|
as `ads://guides/atomic-styles`. It explains shorthand/logical conflicts and
|
|
@@ -81,6 +81,54 @@ const treatment = css`
|
|
|
81
81
|
This is a distinct state, not two competing base atoms. Whether changing padding
|
|
82
82
|
on hover is desirable remains a product decision.
|
|
83
83
|
|
|
84
|
+
## Register typed custom properties without atomizing their descriptors
|
|
85
|
+
|
|
86
|
+
Use a literal `@property` in its component/pattern's Linaria template when the
|
|
87
|
+
browser needs a typed, interpolated runtime variable:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
const treatment = css`
|
|
91
|
+
@property --ds-example-progress {
|
|
92
|
+
syntax: "<number>";
|
|
93
|
+
inherits: false;
|
|
94
|
+
initial-value: 0;
|
|
95
|
+
}
|
|
96
|
+
opacity: var(--ds-example-progress);
|
|
97
|
+
`;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The identifier is illustrative; use a unique name owned by the real unit.
|
|
101
|
+
Registrations are document-global, even when authored beside one component.
|
|
102
|
+
Put them at template level or inside conditional at-rules, never element selectors.
|
|
103
|
+
Use the same ADS tag resolver described below in both product and preview builds;
|
|
104
|
+
it preserves each complete registration and its conditional context instead of
|
|
105
|
+
splitting syntax/inherits/initial-value into invalid atomic rules. This works for
|
|
106
|
+
atomic `css` and `styled`; the browser receives CSS without a registration runtime.
|
|
107
|
+
MCP delivers it inside the App document, not the outer showcase host.
|
|
108
|
+
|
|
109
|
+
`property-registration` validates descriptor shape, CSS syntax/initial-value and
|
|
110
|
+
ownership, and rejects conflicting schemas or registrations shared by unrelated
|
|
111
|
+
owners. Identical registrations from the same owner are permitted. Runtime
|
|
112
|
+
`--ds-*` / `--ux-*` variables stay with their runtime owner. A shared token's
|
|
113
|
+
registration belongs beside its actual definition in a foundation value sheet;
|
|
114
|
+
a namespaced owner token's registration belongs in its registered `tokens.css`.
|
|
115
|
+
Neither `base.css` nor `fonts.css` becomes a registration escape hatch. Registration
|
|
116
|
+
alone does not define a token or satisfy its sample/fixture coverage.
|
|
117
|
+
|
|
118
|
+
Typed initial values must match their syntax and be computationally independent:
|
|
119
|
+
`0px` is valid metadata; `var(--space-4)`, `1rem` and `currentColor` are not.
|
|
120
|
+
The literal exemption applies only to registration descriptors. Keep design
|
|
121
|
+
assignments outside them token-backed; a technical fallback is not a place to
|
|
122
|
+
hide a component skin. Use literal static descriptors so guards can inspect them.
|
|
123
|
+
See the [W3C registration contract](https://www.w3.org/TR/css-properties-values-api-1/#at-property-rule).
|
|
124
|
+
|
|
125
|
+
Verify extracted CSS and actual interpolation, including after minification and
|
|
126
|
+
inside the MCP App. A declaration can parse while a browser ignores an invalid
|
|
127
|
+
registration. Keep required browser fallbacks and reduced-motion behavior; do not
|
|
128
|
+
replace a valid CSS implementation with script merely to evade extraction or a
|
|
129
|
+
guard limitation. These checks cover declarative registrations; arbitrary dynamic
|
|
130
|
+
`CSS.registerProperty()` calls are not an ownership proof.
|
|
131
|
+
|
|
84
132
|
## Preserve custom-property arithmetic during extraction
|
|
85
133
|
|
|
86
134
|
Custom properties can hold an unevaluated token stream that becomes arithmetic
|