@reqvire-org/ads 0.3.16 → 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.
Files changed (59) hide show
  1. package/README.md +3 -0
  2. package/dist/atomic-composition.mjs +1 -1
  3. package/dist/check.mjs +43 -11
  4. package/dist/evidence.mjs +18 -0
  5. package/dist/investigations.mjs +101 -0
  6. package/dist/linaria/preserve-values.cjs +21 -2
  7. package/dist/mcp/behavior-pages.mjs +36 -0
  8. package/dist/mcp/context.mjs +3 -1
  9. package/dist/mcp/execution-results.mjs +146 -0
  10. package/dist/mcp/mutations.mjs +34 -17
  11. package/dist/mcp/preview-widget.bundle.js +12 -12
  12. package/dist/mcp/resource-loader.js +3 -3
  13. package/dist/mcp/result-pages.mjs +87 -0
  14. package/dist/mcp/server.mjs +67 -30
  15. package/dist/property-registrations.mjs +39 -0
  16. package/dist/scenario-selectors.mjs +140 -0
  17. package/dist/scenario-trace.mjs +1 -1
  18. package/dist/scoped-tokens.mjs +6 -0
  19. package/dist/selector-match.mjs +94 -0
  20. package/dist/showcase/client.bundle.js +2 -2
  21. package/dist/skills/ads-break-ui/SKILL.md +8 -3
  22. package/dist/skills/ads-break-ui/references/atomic-styles.md +48 -0
  23. package/dist/skills/ads-break-ui/references/captures.md +6 -1
  24. package/dist/skills/ads-break-ui/references/evidence.md +120 -0
  25. package/dist/skills/ads-break-ui/references/guarded-editing.md +8 -5
  26. package/dist/skills/ads-break-ui/references/scenarios.md +32 -0
  27. package/dist/skills/ads-designer/SKILL.md +8 -3
  28. package/dist/skills/ads-designer/references/atomic-styles.md +48 -0
  29. package/dist/skills/ads-designer/references/captures.md +6 -1
  30. package/dist/skills/ads-designer/references/evidence.md +120 -0
  31. package/dist/skills/ads-designer/references/guarded-editing.md +8 -5
  32. package/dist/skills/ads-designer/references/scenarios.md +32 -0
  33. package/dist/skills/ads-import/SKILL.md +8 -3
  34. package/dist/skills/ads-import/references/atomic-styles.md +48 -0
  35. package/dist/skills/ads-import/references/captures.md +6 -1
  36. package/dist/skills/ads-import/references/evidence.md +120 -0
  37. package/dist/skills/ads-import/references/guarded-editing.md +8 -5
  38. package/dist/skills/ads-import/references/scenarios.md +32 -0
  39. package/dist/skills/ads-migrate/SKILL.md +8 -3
  40. package/dist/skills/ads-migrate/references/atomic-styles.md +48 -0
  41. package/dist/skills/ads-migrate/references/captures.md +6 -1
  42. package/dist/skills/ads-migrate/references/evidence.md +120 -0
  43. package/dist/skills/ads-migrate/references/guarded-editing.md +8 -5
  44. package/dist/skills/ads-migrate/references/scenarios.md +32 -0
  45. package/dist/skills/ads-motion/SKILL.md +8 -3
  46. package/dist/skills/ads-motion/references/atomic-styles.md +48 -0
  47. package/dist/skills/ads-motion/references/captures.md +6 -1
  48. package/dist/skills/ads-motion/references/evidence.md +120 -0
  49. package/dist/skills/ads-motion/references/guarded-editing.md +8 -5
  50. package/dist/skills/ads-motion/references/scenarios.md +32 -0
  51. package/dist/verification/browser.mjs +22 -7
  52. package/dist/verification/playwright-reporter.cjs +23 -5
  53. package/dist/verification.mjs +31 -9
  54. package/docs/ADS.md +10 -0
  55. package/docs/ARCHITECTURE.md +47 -3
  56. package/docs/BEHAVIORS.md +30 -0
  57. package/docs/CHANGELOG.md +30 -0
  58. package/docs/MCP.md +66 -7
  59. package/package.json +2 -1
@@ -12,6 +12,8 @@ import { inspectUnits, configurationFiles } from "./units.mjs";
12
12
  import { inspectBehaviors } from "./behaviors.mjs";
13
13
  import { inspectScenarios } from "./scenarios.mjs";
14
14
  import { readPreviewManifest } from "./preview-resources.mjs";
15
+ import { acceptance, failure, failureCategories } from "./evidence.mjs";
16
+ import { investigationArtifacts, investigationUri, saveInvestigations } from "./investigations.mjs";
15
17
 
16
18
  export const BEHAVIOR_URI = "ads://project/behaviors";
17
19
  const hash = bytes => createHash("sha256").update(bytes).digest("hex");
@@ -123,7 +125,7 @@ export function behaviorInputs(options, contract, check, context = inputContext(
123
125
  const rows = [...files].sort().map(file => [slash(path.relative(options.root, file)), context.digest(file)]);
124
126
  for (const file of ["package-lock.json", "bun.lock", "bun.lockb", "pnpm-lock.yaml", "yarn.lock", "node_modules/.package-lock.json"]) rows.push([file, existsSync(path.join(options.root, file)) ? context.digest(path.join(options.root, file)) : null]);
125
127
  rows.push(["profile", JSON.stringify(options.config.mcp.behaviorProfiles[check.profile])], ["node", process.version]);
126
- for (const file of ["./verification.mjs", "./verification/browser.mjs", "./verification/playwright-reporter.cjs"]) rows.push([`tool:${file}`, hash(readFileSync(new URL(file, import.meta.url)))]);
128
+ for (const file of ["./verification.mjs", "./verification/browser.mjs", "./verification/playwright-reporter.cjs", "./evidence.mjs", "./investigations.mjs"]) rows.push([`tool:${file}`, hash(readFileSync(new URL(file, import.meta.url)))]);
127
129
  return { revision: hash(JSON.stringify(rows)), files: rows.map(([file]) => file) };
128
130
  }
129
131
 
@@ -136,6 +138,11 @@ function saved(root) {
136
138
  } catch { return {}; }
137
139
  }
138
140
  const identity = (unit, check) => `${unit}#${check}`;
141
+ function verificationResult(diagnostics, results) {
142
+ const ok=diagnostics.length===0&&results.length>0&&results.every(result=>result.status==="passed"&&result.current);
143
+ return {ok,diagnostics:diagnostics.map(diagnostic=>({...diagnostic,failure:diagnostic.failure??failure("runner","verification",diagnostic.rule)})),results,
144
+ acceptance:acceptance("selected-behavior-checks",ok,results.filter(result=>result.status==="passed"&&result.current).map(result=>`${result.unit}#${result.check}`),["unselected-checks","unasserted-behavior","visual-parity","accessibility-audit"])};
145
+ }
139
146
 
140
147
  export function behaviorStatus(options) {
141
148
  const inventory = inspectBehaviors(options), evidence = saved(options.root);
@@ -223,7 +230,7 @@ export function createVerificationController(root, { reportRoot = root, onProgre
223
230
  status: () => ({ running: !!active, ...(active && progress ? { progress: { ...progress, elapsedMs: Date.now() - progress.startedAt } } : {}) }),
224
231
  async close() { active?.abort(); await active?.completion; },
225
232
  async run({ unit, units, checks, persist = true } = {}, signal, notifyProgress) {
226
- if (active) return { ok: false, diagnostics: [{ rule: "behavior-busy", message: "A behavior verification is already running." }], results: [] };
233
+ if (active) return verificationResult([{ rule: "behavior-busy", message: "A behavior verification is already running." }],[]);
227
234
  active = new AbortController();
228
235
  const controller = active;
229
236
  signal = signal ? AbortSignal.any([signal, active.signal]) : active.signal;
@@ -232,13 +239,13 @@ export function createVerificationController(root, { reportRoot = root, onProgre
232
239
  const results = [], diagnostics = [];
233
240
  try {
234
241
  const options = loadConfig(root), inventory = inspectBehaviors(options);
235
- if (inventory.diagnostics.length) return { ok: false, diagnostics: inventory.diagnostics, results };
242
+ if (inventory.diagnostics.length) return verificationResult(inventory.diagnostics.map(diagnostic=>({...diagnostic,failure:failure("architecture","behavior-contract",diagnostic.rule)})),results);
236
243
  const selectedUnits = new Set(units ?? (unit ? [unit] : inventory.contracts.map(item => item.unit)));
237
244
  const contracts = inventory.contracts.filter(item => selectedUnits.has(item.unit));
238
- if (!contracts.length || [...selectedUnits].some(id => !contracts.some(contract => contract.unit === id))) return { ok: false, diagnostics: [{ rule: "behavior-selection", message: "No behavior contract matches the complete selection. Discover exact owner/check IDs with ads_behaviors." }], results };
245
+ if (!contracts.length || [...selectedUnits].some(id => !contracts.some(contract => contract.unit === id))) return verificationResult([{ rule: "behavior-selection", message: "No behavior contract matches the complete selection. Discover exact owner/check IDs with ads_behaviors." }],results);
239
246
  const context = inputContext(options);
240
247
  const selection = contracts.flatMap(contract => Object.entries(contract.checks).filter(([id]) => !checks || checks.includes(id)).map(([id, check]) => ({ unit: contract.unit, id, ...check, inputRevision: behaviorInputs(options, contract, check, context).revision, contract })));
241
- if (!selection.length || checks?.some(id => !selection.some(check => check.id === id))) return { ok: false, diagnostics: [{ rule: "behavior-selection", message: "Unknown check selection. Use check IDs returned by ads_behaviors." }], results };
248
+ if (!selection.length || checks?.some(id => !selection.some(check => check.id === id))) return verificationResult([{ rule: "behavior-selection", message: "Unknown check selection. Use check IDs returned by ads_behaviors." }],results);
242
249
  for (const profileName of new Set(selection.map(check => check.profile))) {
243
250
  const profile = options.config.mcp.behaviorProfiles[profileName];
244
251
  const group = selection.filter(check => check.profile === profileName);
@@ -295,7 +302,11 @@ export function createVerificationController(root, { reportRoot = root, onProgre
295
302
  // Unexplained exits, suite errors and interrupted reports grant no pass.
296
303
  const caseFailureExit = run?.code === 1 && !runnerError && !suiteErrors.length && !incompleteReport && reportValue?.cases.some(item => item?.status === "failed");
297
304
  const runnerFailure = run && (run.code !== 0 || suiteErrors.length || incompleteReport) ? `Registered runner ${profileName} ${run.code !== 0 ? `exited with code ${run.code ?? "unknown"}` : incompleteReport ? "left an incomplete report" : "reported a suite error"}. ${suiteErrors.join("; ") || reportValue?.cases.filter(item => item?.status === "failed").slice(0, 3).map(item => `${item.id}: ${item.message || "failed without a case diagnostic"}`).join("; ") || "Inspect the runner output and verification report."}`.slice(0, 3000) : undefined;
298
- if (runnerFailure) diagnostics.push({ rule: "behavior-runner", message: runnerFailure, output: run.output?.slice(-4000), profile: profileName });
305
+ if (runnerFailure) {
306
+ const caseFailures=reportValue?.cases.filter(item=>item.status==="failed"&&failureCategories.has(item.failure?.category)).map(item=>item.failure)??[];
307
+ diagnostics.push({ rule: "behavior-runner", message: runnerFailure, output: run.output?.slice(-4000), profile: profileName,
308
+ failure:caseFailureExit&&caseFailures.length?{...caseFailures[0],phase:"case-results",relatedCategories:[...new Set(caseFailures.map(item=>item.category))]}:failure("runner","profile-execution","ADS_RUNNER_EXIT",{certainty:caseFailureExit?"unclassified":"observed-boundary"}) });
309
+ }
299
310
  const afterOptions = loadConfig(root), afterContext = inputContext(afterOptions);
300
311
  for (const check of group) {
301
312
  const file = slash(path.relative(root, path.resolve(root, path.dirname(check.contract.file), check.file)));
@@ -307,7 +318,17 @@ export function createVerificationController(root, { reportRoot = root, onProgre
307
318
  const current = behaviorInputs(afterOptions, check.contract, check, afterContext).revision === check.inputRevision;
308
319
  if (!current) status = "stale";
309
320
  if (signal.aborted) status = "cancelled";
310
- results.push({ unit: check.unit, check: check.id, file: check.file, case: check.case, profile: profileName, status, current, revision: check.inputRevision, runId, completedAt: new Date().toISOString(),
321
+ const caseDetail=matches.find(item=>item.status!=="passed")??matches.at(-1);
322
+ const reportedFailure=caseDetail?.failure;
323
+ const checkFailure=status==="passed"?undefined:status==="stale"||status==="cancelled"?failure("runner","verification",`ADS_${status.toUpperCase()}`):caseDetail?.status==="failed"&&failureCategories.has(reportedFailure?.category)?reportedFailure:blockedRun?failure("runner","profile-execution",runnerError?.reason??"ADS_RUNNER_INCOMPLETE",{certainty:"observed-boundary"}):failure("runner","test-execution","ADS_CASE_UNCLASSIFIED",{certainty:"unclassified"});
324
+ const accepted=acceptance("registered-case",status==="passed"&&current,status==="passed"&&current?["registered-case-completed"]:[],["unasserted-behavior","visual-parity","accessibility-audit"]);
325
+ const investigation={unit:check.unit,check:check.id,status,runId,resourceUri:investigationUri({unit:check.unit,check:check.id}),target:root===reportRoot?"project":"candidate",framework:options.config.framework,profile:profileName,mode:profile.mode??"unit",scenario:check.scenario??null,inputRevision:check.inputRevision,inputsCurrentAtCompletion:current,
326
+ source:caseDetail?.source??{file},failingStep:caseDetail?.failingStep??null,viewport:caseDetail?.viewport??null,viewportSource:caseDetail?.viewportSource??null,
327
+ observedPreviewRevision:matches.flatMap(item=>item.diagnostics??[]).findLast(item=>item.kind==="preview-startup")?.previewRevision??null,
328
+ requests:matches.flatMap(item=>item.diagnostics??[]).flatMap(item=>item.requests??[]).slice(0,8),browserErrors:matches.flatMap(item=>item.diagnostics??[]).flatMap(item=>item.errors??[]).slice(0,3),
329
+ artifacts:investigationArtifacts(reportRoot,report,matches.flatMap(item=>item.artifacts??[])),acceptance:accepted,...(checkFailure?{failure:checkFailure}:{}),completedAt:new Date().toISOString()};
330
+ results.push({ unit: check.unit, check: check.id, file: check.file, case: check.case, profile: profileName, status, current, revision: check.inputRevision, runId, completedAt: investigation.completedAt,
331
+ ...(checkFailure?{failure:checkFailure}:{}),acceptance:accepted,investigation,
311
332
  previewRevision: profile.mode === "browser" ? host?.previewRevision() ?? readPreviewManifest(options)?.revision ?? null : null,
312
333
  ...(matches.length ? { reportedStatus, durationMs: matches.reduce((sum, item) => sum + (Number.isFinite(item.durationMs) ? Math.max(0, item.durationMs) : 0), 0) } : {}),
313
334
  ...(matches.some(item => item.diagnostics?.length) ? { startupDiagnostics: matches.flatMap(item => Array.isArray(item.diagnostics) ? item.diagnostics.slice(0, 5) : []).slice(0, 10) } : {}),
@@ -317,6 +338,7 @@ export function createVerificationController(root, { reportRoot = root, onProgre
317
338
  }
318
339
  if (signal.aborted) break;
319
340
  }
341
+ saveInvestigations(reportRoot,results.map(result=>result.investigation),inventory.contracts.flatMap(contract=>Object.keys(contract.checks).map(id=>identity(contract.unit,id))));
320
342
  if (persist) {
321
343
  const evidence = saved(root);
322
344
  for (const result of results) evidence[identity(result.unit, result.check)] = result;
@@ -325,8 +347,8 @@ export function createVerificationController(root, { reportRoot = root, onProgre
325
347
  if (Buffer.byteLength(content) > 2 * 1024 * 1024) throw new Error("Verification evidence exceeds 2 MiB. Remove obsolete generated evidence and retry.");
326
348
  try { writeFileSync(temporary, content); renameSync(temporary, file); } finally { rmSync(temporary, { force: true }); }
327
349
  }
328
- return { ok: diagnostics.length === 0 && results.length > 0 && results.every(result => result.status === "passed" && result.current), diagnostics, results };
329
- } catch (error) { return { ok: false, diagnostics: [{ rule: "behavior-verification", message: error.message }], results }; }
350
+ return verificationResult(diagnostics,results);
351
+ } catch (error) { return verificationResult([{ rule: "behavior-verification", message: error.message }],results); }
330
352
  finally { try { await host?.close(); } finally { if (active === controller) { active = undefined; progress = undefined; } finish(); } }
331
353
  },
332
354
  };
package/docs/ADS.md CHANGED
@@ -259,6 +259,16 @@ custom styling in fixture files. The ADS frame pattern uses `--ux-*`. Foreign br
259
259
  CSS imports the approved token files in order; shared assignments belong in
260
260
  those token files, never in consumer styles or component state overrides.
261
261
 
262
+ Typed custom-property registrations follow the same ownership. Runtime-variable
263
+ `@property` metadata lives in component/pattern Linaria; shared and owner-local
264
+ token registrations live beside real token definitions. Names are document-global:
265
+ unrelated owners and conflicting descriptor sets cannot claim the same name.
266
+ Descriptors have their own CSS grammar and independent literal defaults; normal
267
+ visual styling still uses tokens. Registration does not create token/sample
268
+ coverage, and base resets/font metadata cannot host it. Preserve whole rules with
269
+ the ADS Linaria resolver in product and MCP preview builds. See the portable
270
+ [atomic styling guide](../skills/ads-designer/references/atomic-styles.md).
271
+
262
272
  Foundation roles are enforced by `foundation-role`, `foundation-selector` and
263
273
  `foundation-import`, alongside the regular token checks. Shared definitions
264
274
  belong on the document root or explicit root themes; ordinary declarations belong
@@ -155,6 +155,19 @@ values; source guards alone cannot establish extraction correctness. The
155
155
  skill-local [atomic styling guide](../skills/ads-designer/references/atomic-styles.md#preserve-custom-property-arithmetic-during-extraction)
156
156
  documents integration and source-workaround semantics.
157
157
 
158
+ The resolver also preserves complete `@property` registrations and their
159
+ conditional at-rule context for atomic `css`/`styled`. Their descriptors are schema
160
+ metadata, not separate atomic declarations. CSS-tree validates CSS value grammar;
161
+ PostCSS supplies source/at-rule structure. Registered names have one owner and a
162
+ stable schema across its modules, because registration is document-global. Runtime
163
+ variables are registered in owned Linaria; shared or namespaced design tokens are
164
+ registered beside their real value definitions. Base resets and fonts cannot own
165
+ registrations. Descriptor literals are narrowly permitted where CSS requires an
166
+ independent initial value; ordinary visual assignments remain token-governed.
167
+ MCP serves these styles to the sandboxed App document. Registration metadata
168
+ neither creates new token definitions nor replaces token sample coverage.
169
+ Compiled-CSS and browser tests verify interpolation in React and Preact Apps.
170
+
158
171
  ## Public APIs preserve the boundary
159
172
 
160
173
  The application and project-owned preview scenarios consume a project's public ADS entry. Components
@@ -322,7 +335,12 @@ styles. This preserves ownership rather than granting scenario styling freedom.
322
335
  A native element may provide semantics as the single direct child of a public
323
336
  runtime slot when source tracing proves it is cloned with that owner's props.
324
337
  This preserves polymorphic composition without allowing standalone scenario
325
- controls, scenario styling or name-based exceptions.
338
+ controls, scenario styling or name-based exceptions. Selector-based composition
339
+ can establish the same ownership through actual returned DOM and attached runtime
340
+ Linaria: a positive native-tag relationship, or a `:has` relationship to a marker
341
+ declared by a real runtime-owned child. Broad selectors and scenario-authored
342
+ lookalikes grant no permission. This source proof does not establish computed
343
+ appearance, keyboard behavior or visual parity; browser checks remain necessary.
326
344
  The skill-local [scenario guide](../skills/ads-designer/references/scenarios.md)
327
345
  explains evidence, limits and diagnostics.
328
346
 
@@ -427,8 +445,34 @@ ADS owns affected-check selection, candidate hosting, progress, evidence freshne
427
445
  and process cleanup. One profile timeout bounds its entire runner process. Parallel
428
446
  workers can share that candidate host with independent App state. Partial outcomes
429
447
  remain diagnostic when execution is interrupted; they cannot approve a mutation.
430
- Passing checks describe their registered assertions, not every natural-language claim. This keeps edits
431
- focused without requiring entire file bodies in model context. Peer units and
448
+ Passing checks describe their registered assertions, not every natural-language claim.
449
+ Results distinguish architecture, project-build, transport, runner and assertion
450
+ boundaries with structured phase/code evidence; an unclassified test exception
451
+ does not become an assertion or a component defect. Acceptance explicitly lists
452
+ established and unverified scope. Verification retains one latest investigation
453
+ per registered check, including isolated candidates: exact input revision,
454
+ observed preview revision, failing step/source, observed startup viewport,
455
+ failed requests and available project-recorded artifacts. MCP exposes bounded
456
+ bundle/index resources and only registered, digest-checked artifact files.
457
+ Another run replaces that check's resource access; candidate evidence never
458
+ certifies live source. Detailed usage lives in the portable skill-local
459
+ [evidence guide](../skills/ads-designer/references/evidence.md).
460
+
461
+ MCP delivery is separate from validation: verification and mutation controllers
462
+ use complete results to decide acceptance. Their MCP replies contain counts,
463
+ bounded acceptance, at most three failure previews and a result ID in at most
464
+ 4 KiB of UTF-8 JSON. `ads_results` reads saved outcomes, diagnostics and other
465
+ sections without rerunning checks. Pages have at most 20 rows and 8 KiB of JSON;
466
+ truncated records support bounded text ranges. Discovery and investigation
467
+ sections use the same paging budget. Cursors bind the query and result/revision;
468
+ superseded IDs fail explicitly instead of silently returning another run.
469
+ Only the latest verification and mutation snapshots are retained, each at most
470
+ 8 MiB under `.ds/tool-results/`. Oversized reports retain a summary with an
471
+ explicit unavailable-details warning. Storage/notification failure cannot change
472
+ an accepted edit's applied state. These are payload byte budgets, not tokenizer
473
+ estimates; MCP compatibility text may duplicate structured JSON on the wire.
474
+
475
+ Edits stay focused without requiring entire file bodies in model context. Peer units and
432
476
  unrelated owners remain outside the selected unit's edit boundary. Shared foundation,
433
477
  branding and guides have their own units. Shared credits and text license notices
434
478
  belong to the documentation unit, including conventional root notices and shared
package/docs/BEHAVIORS.md CHANGED
@@ -272,6 +272,13 @@ publish `complete:true` at completion. Older final reports without `complete`
272
272
  remain supported after normal process completion. An explicitly incomplete report
273
273
  cannot pass even with exit code zero. Partial reports retain the same schema/run
274
274
  identity and size limits as final reports.
275
+ Cases may optionally include `failure: {category,phase,code,certainty?}`,
276
+ `source: {file,line,column}`, `failingStep`, `viewport`/`viewportSource`, and
277
+ `artifacts: [{name,mimeType,path}]`. Artifact paths are relative to the report
278
+ directory and must remain under that run's `artifacts/` directory. The built-in
279
+ Playwright reporter supplies these from actual step/startup/attachment evidence.
280
+ Older protocol runners can omit them; ADS preserves that lack of classification
281
+ instead of treating an arbitrary thrown message as an assertion.
275
282
  `file` is project-relative. Nonzero process exit, missing cases, skipped cases,
276
283
  wrong run IDs and unavailable reports do not pass. Test commands are trusted
277
284
  project code; an adapter cannot prove the quality or honesty of their assertions.
@@ -300,6 +307,29 @@ changes mark results stale; changes during execution prevent a current pass.
300
307
  Guarantees report passing only when all their checks have current passing evidence.
301
308
  Passing applies to those cases and profiles, not every application state.
302
309
 
310
+ Results expose structured `failure` boundaries and explicit `acceptance` scope.
311
+ MCP execution replies contain counts, bounded acceptance, at most three failure
312
+ previews and a `resultId` (4 KiB UTF-8 JSON maximum). Use `ads_results` to read
313
+ selected `outcomes`, `checks`, `diagnostics` or other sections without execution.
314
+ Pages default to 10 rows, allow at most 20 and stay within 8 KiB; follow
315
+ `nextCursor` with identical selection/status. Truncated records provide
316
+ `recordIndex` for exact `offset`/`chars` ranges. Only the latest verification and
317
+ mutation snapshots are retained, independently, at most 8 MiB each. Replaced IDs
318
+ fail explicitly. Validation still uses complete internal outcomes.
319
+
320
+ Use `ads_investigate({unit,check,runId})` with an outcome's exact IDs for its compact
321
+ summary (4 KiB maximum); details, requests, browser errors and artifact metadata are separate
322
+ paged sections (8 KiB maximum). Truncated records support exact text ranges with
323
+ the recorded run ID. Saved outcomes describe completion time; compare current
324
+ `ads_behaviors` revisions for freshness. It includes isolated dry runs; `ads://project/investigations` lists available entries. Exact
325
+ input and observed preview revisions, failing step/source, startup viewport,
326
+ failed startup requests and available project-recorded artifacts keep diagnosis
327
+ separate from an inferred component defect. No artifact is invented or test
328
+ retried. Each check retains one latest bundle; old artifact resource access is
329
+ superseded. Candidate bundles remain distinct from passing live-source evidence.
330
+ Read the portable [evidence reference](../skills/ads-designer/references/evidence.md)
331
+ for category meanings, collection limits and artifact policy.
332
+
303
333
  Revisions include owned files, literal import dependencies, local CSS imports and
304
334
  asset URLs, token foundation, project/runner configuration, lockfiles and tooling.
305
335
  Computed module selection conservatively includes all ADS owners. Environment,
package/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.18
4
+
5
+ - Preserve complete typed `@property` registrations through atomic css/styled
6
+ extraction, conditional rules and CSS minification. Validate descriptors with
7
+ CSS-tree, enforce runtime/token ownership and naming conflicts, and retain
8
+ ordinary style-token gates. Verify dummy interpolation in React/Preact MCP Apps.
9
+ - Sync all skill references for registrations and selector-owned native
10
+ composition; keep implementation independent of any imported component library.
11
+
12
+ - Deliver verification and guarded-edit replies as 4 KiB JSON summaries with
13
+ counts, acceptance and result IDs. Page outcomes, diagnostics, logs and captures
14
+ through `ads_results`; retain only the latest verification and mutation snapshots.
15
+ Keep complete internal validation independent of delivery.
16
+ - Page behavior discovery and investigation details; bind cursors to their query
17
+ and recorded run, reject superseded evidence, and document selective retrieval
18
+ in every portable skill. Keep artifacts separate from tool JSON.
19
+
20
+ ## 0.3.17
21
+
22
+ - Recognize native semantic composition through source-verified runtime Linaria
23
+ selectors, including runtime-owned part bindings. Keep unrelated controls,
24
+ broad selectors, fabricated markers and custom scenario styling rejected.
25
+ - Distinguish architecture, build, transport, runner and assertion boundaries;
26
+ expose explicit acceptance scope and latest-per-check investigation bundles
27
+ with revisions, source/step, startup viewport, requests and available artifacts.
28
+ Preserve candidate/live-source evidence and independent case outcomes.
29
+ - Add generic React/Preact conformance checks for selector composition, extracted
30
+ CSS, MCP resource recovery and native iframe interaction. Document diagnosis
31
+ through portable skill references without project-specific implementation rules.
32
+
3
33
  ## 0.3.16
4
34
 
5
35
  - Recover transient showcase MCP resource fetch failures with bounded read-only
package/docs/MCP.md CHANGED
@@ -175,8 +175,10 @@ The server supplies agent guidance through its instructions, tool descriptions,
175
175
  | `ads_show_tokens` | Render a discovered token scenario; legacy compilers retain the definition inspector |
176
176
  | `ads_scenarios` | Bounded token/component/pattern/composition scenario discovery |
177
177
  | `ads_show_scenario` | Render an exact discovered scenario with its own fixtures |
178
- | `ads_behaviors` | Bounded guarantee/evidence discovery; pass an exact unit for contract details |
178
+ | `ads_behaviors` | Paged owner discovery; select a unit and detail section |
179
179
  | `ads_verify` | Execute an owner's registered checks and record revision-bound evidence |
180
+ | `ads_results` | Page saved execution outcomes, diagnostics, logs and captures without rerunning |
181
+ | `ads_investigate` | Read one check's summary, then paged requests/errors/artifacts |
180
182
  | `ads_capture` | On-demand PNG of a discovered scenario or named element; replace current and clear candidate |
181
183
  | `ads_capture_clear` | Remove generated captures or discard only the candidate |
182
184
  | `ads_show_<id>` | Optional explicitly configured grouped App |
@@ -192,8 +194,9 @@ public exports from `components/`, `patterns/` and `controllers/`; it does not e
192
194
  guess component props. Read the exported API and owning guide. An export name
193
195
  alone does not prove that a preview renders that particular element.
194
196
 
195
- `ads://project/status`, `/build`, `/tokens`, `/catalog`, `/scenarios`, `/behaviors`, and `/checks` carry complete JSON
196
- detail. Allowed project documentation/source and skill files are separate
197
+ `ads://project/status`, `/build`, `/tokens`, `/catalog`, `/scenarios` and `/checks`
198
+ carry project JSON detail. Behavior discovery, execution outcomes and investigation
199
+ details are paged; their resource reads use the same bounds as their tools. Allowed project documentation/source and skill files are separate
197
200
  resources. No arbitrary filesystem reader is exposed. Prefer bounded tools for
198
201
  large catalogues; explicit resource reads can enter model context and a client
199
202
  may truncate large payloads.
@@ -214,6 +217,14 @@ informational limit nor a traced target proves a rendered failure or success.
214
217
  Bounded check results put warnings before informational limits; the checks
215
218
  resource retains complete results.
216
219
 
220
+ `property-registration` is a blocking architecture diagnostic for invalid or
221
+ conflicting typed CSS registrations. Keep registrations in their runtime owner
222
+ or beside their real token definitions; `base.css` remains a closed reset contract.
223
+ Use `@reqvire-org/ads/linaria` in both product and preview extraction so each
224
+ registration survives intact in the App's CSS resources. Descriptor literals are
225
+ metadata; normal styling still uses tokens. Read `ads://guides/atomic-styles` for
226
+ ownership, valid initial values, browser checks and examples.
227
+
217
228
  ### Screenshots and candidate comparison
218
229
 
219
230
  `ads_capture({scenario:"<discovered ID>",target?,theme?,viewport?})` renders current
@@ -248,8 +259,10 @@ limits, target declarations and the guarded review workflow.
248
259
 
249
260
  Read `ads://guides/behaviors` or the [behavior guide](BEHAVIORS.md) for the contract
250
261
  and runner schema. `ads_behaviors({ "unit": "components/MessageScroller" })`
251
- returns subjects, guarantees, demonstration links and check IDs for a discovered
252
- owner. `ads_verify({ "unit": "components/MessageScroller", "checks": ["prepend-anchor"] })`
262
+ pages an owner's checks by default; select `section: "subjects"`, `"guarantees"`,
263
+ `"demonstrations"` or `"profiles"` for other details. Without a unit it lists owner
264
+ summaries. Follow `nextCursor` with identical filters. Changed registrations or
265
+ evidence revisions invalidate discovery cursors. `ads_verify({ "unit": "components/MessageScroller", "checks": ["prepend-anchor"] })`
253
266
  runs those registered cases. The names here are examples, never built-in cases.
254
267
 
255
268
  `covers` describes rendered UI; optional `exercises` describes public behavioral
@@ -275,6 +288,51 @@ single failed assertion does not turn the other checks into failures. Overall
275
288
  verification and guarded application still fail. Suite errors, unexplained exits,
276
289
  missing results and interrupted reports cannot certify passing checks.
277
290
 
291
+ Verification and guarded-edit replies are summaries: counts, acceptance, at most
292
+ three failure previews and `resultId`, within 4096 UTF-8 JSON bytes. Use:
293
+
294
+ ```js
295
+ ads_results({resultId: "<returned ID>", section: "outcomes", status: "not_passed"})
296
+ ads_results({resultId: "<returned ID>", section: "outcomes", status: "not_passed", cursor: "<nextCursor>"})
297
+ ```
298
+
299
+ Each listing defaults to 10 rows, allows at most 20 and stays within 8192 JSON
300
+ bytes. A byte-limited page can contain fewer than `limit` rows; follow
301
+ `nextCursor` with the same result ID, section and status. Other sections are
302
+ `checks`, `advisories`, `warnings`, `changes`, `captures`, `acceptance`,
303
+ `investigation` and `selection`. No page reruns validation. Rows marked
304
+ `detailTruncated` can be read exactly with `recordIndex`, `offset` and `chars`
305
+ (maximum 2000 UTF-16 characters); follow `nextOffset`. Read full logs only for the
306
+ relevant record. JSON also remains available through `ads://results/<resultId>`;
307
+ use matching query parameters for pages or ranges. `ads://project/results` lists
308
+ the latest IDs. Status embeds summaries, not complete runner reports. Saved
309
+ outcomes are completion-time evidence; compare revisions with `ads_behaviors`
310
+ before claiming current source passed.
311
+
312
+ Only the latest verification snapshot and latest mutation snapshot are retained,
313
+ independently, each up to 8 MiB in `.ds/tool-results/`. Replacing a snapshot makes
314
+ its old ID unavailable; do not mix pages across runs. An oversized report retains
315
+ only its summary and an explicit unavailable-details warning. A failed disk write
316
+ keeps details in server memory until shutdown. Neither condition changes guard
317
+ acceptance or whether source was applied. These are logical payload byte limits;
318
+ compatibility text can duplicate structured JSON in the MCP response.
319
+
320
+ For diagnosis, take one outcome's exact `unit`, `check` and `runId` and call
321
+ `ads_investigate`. The run ID rejects superseded evidence. Without a check, it
322
+ lists available investigations with paging. An exact check returns a summary
323
+ within 4096 JSON bytes; `section: "details"`, `"requests"`, `"browserErrors"` or `"artifacts"`
324
+ returns detail pages with the same 10/20-row and 8192-byte limits. Truncated records support
325
+ `recordIndex`/`offset`/`chars` with the exact `runId`; artifact URIs are read
326
+ separately. It never reruns checks, including rejected candidates.
327
+ `ads://project/investigations` lists available bundles; returned resource URIs expose the bundle and available registered
328
+ artifacts. Exact input/observed preview revisions, source/failing step, startup
329
+ viewport and failed requests separate evidence from a suspected cause.
330
+ The summary's `details.failure.category` distinguishes architecture, build,
331
+ transport, runner and assertion boundaries; `acceptance` names established and unverified scope.
332
+ Only the latest bundle per check remains resource-accessible. Read the portable
333
+ [evidence guide](../skills/ads-designer/references/evidence.md) for collection
334
+ limits and project-controlled trace/screenshot recording.
335
+
278
336
  Browser profiles start an isolated showcase and verify its sandboxed App using
279
337
  the selected candidate's sources. StrictMode profiles need a project React
280
338
  development harness. Profiles describe the execution environment, not a proof
@@ -411,8 +469,9 @@ Pass that `patchFile` to `ads_apply_patch` with `dryRun:true` to validate before
411
469
  applying. This runs the normal candidate gates, including affected registered
412
470
  behavior checks against an isolated candidate and its configured browser preview.
413
471
  It writes no live source and does not persist passing evidence for live inputs.
414
- Read returned `behavior` failures or behavior-check results in `checks`; update the
415
- draft, diff again and repeat as needed. Apply with `dryRun:false` after validation;
472
+ Read the summary, then use its `resultId` with `ads_results` for failing
473
+ `outcomes`, `diagnostics` or named pipeline `checks`; update the draft, diff again
474
+ and repeat as needed. Apply with `dryRun:false` after validation;
416
475
  the gates run again. `ads_verify({unit,checks?})` checks current source and accepts
417
476
  no draft argument; a selected-check draft runner is not currently exposed.
418
477
  Use `action:"discard"` to remove the draft. Remote clients can continue sending
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reqvire-org/ads",
3
- "version": "0.3.16",
3
+ "version": "0.3.18",
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": {
@@ -52,6 +52,7 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@modelcontextprotocol/server": "2.3.1",
55
+ "css-tree": "3.2.1",
55
56
  "postcss": "^8.5.30",
56
57
  "postcss-selector-parser": "^7.1.6",
57
58
  "postcss-value-parser": "^4.2.0",