@agentskit/doc-bridge 1.6.4 → 1.7.45

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 (64) hide show
  1. package/CHANGELOG.md +249 -0
  2. package/CONTRIBUTING.md +6 -4
  3. package/action.yml +1 -1
  4. package/dist/cli/program.js +1139 -294
  5. package/dist/cli/program.js.map +1 -1
  6. package/dist/config/index.d.ts +1 -1
  7. package/dist/config/index.js +43 -5
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/index-BUL0q7s8.d.ts +660 -0
  10. package/dist/index.d.ts +817 -2134
  11. package/dist/index.js +1154 -244
  12. package/dist/index.js.map +1 -1
  13. package/docs/PRD-enterprise-hardening.md +288 -0
  14. package/docs/RELEASE.md +22 -8
  15. package/docs/adr/0001-enterprise-verification-contract.md +35 -0
  16. package/docs/agent-corpus/INDEX.md +2 -2
  17. package/docs/agent-corpus/chat.md +2 -2
  18. package/docs/agent-corpus/cli.md +2 -2
  19. package/docs/agent-corpus/conformance.md +2 -2
  20. package/docs/agent-corpus/doc-bridge.md +1 -1
  21. package/docs/agent-corpus/doctor.md +2 -2
  22. package/docs/agent-corpus/gates.md +2 -2
  23. package/docs/agent-corpus/mcp.md +2 -2
  24. package/docs/agent-corpus/memory.md +2 -2
  25. package/docs/agent-corpus/query.md +2 -2
  26. package/docs/knowledge-engine-runbook.md +30 -2
  27. package/docs/spec/analyzer-plugin-v1.md +24 -0
  28. package/docs/spec/benchmark-v1.md +36 -0
  29. package/docs/spec/config-v1.md +156 -0
  30. package/docs/validation-cycle-plan.md +255 -0
  31. package/docs/verification-harness.md +37 -4
  32. package/mcpb/manifest.json +1 -1
  33. package/package.json +68 -70
  34. package/scripts/check-ecosystem-upstream.mjs +3 -2
  35. package/scripts/report-visual-check.mjs +64 -12
  36. package/scripts/verification-harness.mjs +216 -14
  37. package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
  38. package/src/agents/registry-adapter.ts +31 -7
  39. package/src/cli/demo.ts +2 -2
  40. package/src/cli/program.ts +59 -16
  41. package/src/config/index.ts +2 -0
  42. package/src/config/load-config.ts +7 -1
  43. package/src/config/schema.ts +60 -2
  44. package/src/conformance/documentation-standard-v1.ts +14 -8
  45. package/src/discovery/documentation.ts +90 -23
  46. package/src/discovery/repository.ts +147 -19
  47. package/src/doctor/run-doctor.ts +2 -15
  48. package/src/federation/llms.ts +72 -20
  49. package/src/fixes/proposals.ts +4 -3
  50. package/src/index-builder/human-adapters/fumadocs.ts +1 -1
  51. package/src/index-builder/watch-index.ts +1 -1
  52. package/src/index.ts +29 -0
  53. package/src/lib/bounded-text.ts +15 -10
  54. package/src/metrics/benchmark.ts +176 -0
  55. package/src/plugins/contract.ts +89 -0
  56. package/src/reconciliation/reconcile.ts +181 -5
  57. package/src/report/html.ts +318 -88
  58. package/src/rules/engine.ts +15 -2
  59. package/src/safety/repository.ts +1 -1
  60. package/src/schemas/knowledge.ts +21 -3
  61. package/src/validate.ts +7 -1
  62. package/src/version.ts +1 -1
  63. package/src/workflow/engine.ts +65 -9
  64. package/dist/index-DudNuwI5.d.ts +0 -2060
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentskit/doc-bridge",
3
- "version": "1.6.4",
3
+ "version": "1.7.45",
4
4
  "mcpName": "io.github.AgentsKit-io/doc-bridge",
5
5
  "description": "Human↔agent documentation bridge — deterministic handoffs, doc-site links, memory→docs, optional AgentsKit RAG/chat.",
6
6
  "type": "module",
@@ -46,51 +46,6 @@
46
46
  "src",
47
47
  "skills"
48
48
  ],
49
- "scripts": {
50
- "prebuild": "node scripts/sync-version.mjs",
51
- "build": "tsup",
52
- "test": "vitest run && pnpm test:cursor-plugin && pnpm test:claude-plugin && pnpm test:copilot-plugin && pnpm test:portable-skill",
53
- "test:watch": "vitest",
54
- "coverage": "vitest run --coverage",
55
- "check:ecosystem-upstream": "node scripts/check-ecosystem-upstream.mjs",
56
- "check:no-legacy-chat-imports": "node scripts/check-no-legacy-chat-imports.mjs",
57
- "typecheck": "tsc --noEmit",
58
- "smoke:packaged": "node scripts/smoke-packaged.mjs",
59
- "smoke:docsites": "node scripts/smoke-docsites.mjs",
60
- "smoke:real-docsites": "node scripts/smoke-real-docsites.mjs",
61
- "smoke:ollama": "node scripts/smoke-ollama.mjs",
62
- "mcpb:stage": "npm run build && node scripts/build-mcpb.mjs stage",
63
- "mcpb:validate": "node scripts/build-mcpb.mjs validate",
64
- "mcpb:smoke": "node scripts/smoke-mcpb.mjs",
65
- "mcpb:pack": "npm run mcpb:stage && npm run mcpb:smoke && node scripts/build-mcpb.mjs pack",
66
- "test:mcpb": "node --test scripts/mcpb-contract.test.mjs",
67
- "test:cursor-plugin": "node --test scripts/cursor-plugin-contract.test.mjs",
68
- "test:claude-plugin": "node --test scripts/claude-plugin-contract.test.mjs",
69
- "test:copilot-plugin": "node --test scripts/copilot-plugin-contract.test.mjs",
70
- "test:portable-skill": "node --test scripts/portable-skill-contract.test.mjs",
71
- "coverage:badge": "node scripts/update-coverage-badge.mjs",
72
- "changeset": "changeset",
73
- "version-packages": "changeset version && node scripts/sync-version.mjs",
74
- "release": "npm run build && npm test && changeset publish",
75
- "version": "node scripts/sync-version.mjs && git add package.json src/version.ts action.yml",
76
- "prepublishOnly": "npm run build && npm test",
77
- "prepare": "node scripts/prepare.mjs",
78
- "prepack": "npm run build",
79
- "docs:dev": "cd apps/docs && next dev",
80
- "docs:artifacts": "node scripts/build-docs-artifacts.mjs",
81
- "docs:build": "pnpm build && node bin/ak-docs.js index && pnpm docs:artifacts && cd apps/docs && next build",
82
- "docs:typecheck": "cd apps/docs && fumadocs-mdx && tsc --noEmit",
83
- "docs:e2e": "playwright test",
84
- "report:visual": "node scripts/report-visual-check.mjs",
85
- "test:verification-harness": "node --test scripts/verification-harness.test.mjs",
86
- "docs:lighthouse": "lhci autorun",
87
- "test:readme-standard": "node --test scripts/readme-standard.test.mjs",
88
- "test:marketplace": "node --test scripts/marketplace-contract.test.mjs",
89
- "check:marketplace": "node scripts/check-marketplace-contract.mjs",
90
- "test:docs-artifacts": "node --test scripts/docs-artifacts-contract.test.mjs",
91
- "check:readme-standard": "node scripts/check-readme-standard.mjs",
92
- "check:docs": "pnpm check:readme-standard && node bin/ak-docs.js gate run && node bin/ak-docs.js conformance run documentation-standard-v1 --text && pnpm docs:typecheck && pnpm docs:build && pnpm test:docs-artifacts"
93
- },
94
49
  "keywords": [
95
50
  "agentskit",
96
51
  "documentation",
@@ -120,10 +75,10 @@
120
75
  },
121
76
  "dependencies": {
122
77
  "github-slugger": "^2.0.0",
123
- "mermaid": "^11.16.1",
78
+ "mermaid": "^11.17.2",
124
79
  "minimatch": "^10.2.6",
125
80
  "typescript": "^6.0.3",
126
- "zod": "^3.24.2"
81
+ "zod": "^4.4.3"
127
82
  },
128
83
  "peerDependencies": {
129
84
  "@agentskit/adapters": ">=0.12.0 <1",
@@ -154,30 +109,31 @@
154
109
  }
155
110
  },
156
111
  "devDependencies": {
157
- "@agentskit/chat": "0.4.0",
158
- "@agentskit/core": "1.12.3",
159
- "@agentskit/ink": "0.10.4",
112
+ "@agentskit/chat": "0.4.1",
113
+ "@agentskit/core": "1.12.8",
114
+ "@agentskit/ink": "0.10.9",
160
115
  "@agentskit/react": "0.7.4",
161
116
  "@anthropic-ai/mcpb": "2.1.2",
162
- "@changesets/cli": "^2.31.0",
117
+ "@changesets/cli": "^3.0.1",
163
118
  "@lhci/cli": "^0.15.1",
164
- "@playwright/test": "1.61.1",
165
- "@tailwindcss/postcss": "4.3.2",
166
- "@types/node": "^22.15.3",
167
- "@types/react": "19.2.17",
168
- "@types/react-dom": "19.2.3",
169
- "@vitest/coverage-v8": "4.1.10",
170
- "esbuild": "^0.28.1",
171
- "fumadocs-core": "15.6.5",
172
- "fumadocs-mdx": "11.6.4",
173
- "fumadocs-ui": "15.6.5",
174
- "lucide-react": "0.460.0",
175
- "next": "15.5.21",
176
- "react": "19.2.0",
177
- "react-dom": "19.2.0",
178
- "tailwindcss": "4.3.2",
119
+ "@playwright/test": "1.62.1",
120
+ "@tailwindcss/postcss": "4.3.3",
121
+ "@types/node": "^26.4.0",
122
+ "@types/react": "19.2.18",
123
+ "@types/react-dom": "19.2.5",
124
+ "@vitest/coverage-v8": "4.1.11",
125
+ "esbuild": "^0.28.2",
126
+ "fumadocs-core": "16.15.4",
127
+ "fumadocs-mdx": "15.4.0",
128
+ "fumadocs-ui": "16.15.4",
129
+ "lucide-react": "1.34.0",
130
+ "next": "16.3.3",
131
+ "proxy-agent": "8.0.2",
132
+ "react": "19.2.8",
133
+ "react-dom": "19.2.8",
134
+ "tailwindcss": "4.3.3",
179
135
  "tsup": "^8.5.0",
180
- "vitest": "^4.1.9"
136
+ "vitest": "^4.1.11"
181
137
  },
182
138
  "directories": {
183
139
  "doc": "docs",
@@ -188,5 +144,47 @@
188
144
  "bugs": {
189
145
  "url": "https://github.com/AgentsKit-io/doc-bridge/issues"
190
146
  },
191
- "homepage": "https://doc-bridge.agentskit.io/"
192
- }
147
+ "homepage": "https://doc-bridge.agentskit.io/",
148
+ "scripts": {
149
+ "prebuild": "node scripts/sync-version.mjs",
150
+ "build": "tsup",
151
+ "test": "vitest run && pnpm test:cursor-plugin && pnpm test:claude-plugin && pnpm test:copilot-plugin && pnpm test:portable-skill",
152
+ "test:watch": "vitest",
153
+ "coverage": "vitest run --coverage",
154
+ "check:ecosystem-upstream": "node scripts/check-ecosystem-upstream.mjs",
155
+ "check:no-legacy-chat-imports": "node scripts/check-no-legacy-chat-imports.mjs",
156
+ "typecheck": "tsc --noEmit",
157
+ "smoke:packaged": "node scripts/smoke-packaged.mjs",
158
+ "smoke:docsites": "node scripts/smoke-docsites.mjs",
159
+ "smoke:real-docsites": "node scripts/smoke-real-docsites.mjs",
160
+ "smoke:ollama": "node scripts/smoke-ollama.mjs",
161
+ "mcpb:stage": "npm run build && node scripts/build-mcpb.mjs stage",
162
+ "mcpb:validate": "node scripts/build-mcpb.mjs validate",
163
+ "mcpb:smoke": "node scripts/smoke-mcpb.mjs",
164
+ "mcpb:pack": "npm run mcpb:stage && npm run mcpb:smoke && node scripts/build-mcpb.mjs pack",
165
+ "test:mcpb": "node --test scripts/mcpb-contract.test.mjs",
166
+ "test:cursor-plugin": "node --test scripts/cursor-plugin-contract.test.mjs",
167
+ "test:claude-plugin": "node --test scripts/claude-plugin-contract.test.mjs",
168
+ "test:copilot-plugin": "node --test scripts/copilot-plugin-contract.test.mjs",
169
+ "test:portable-skill": "node --test scripts/portable-skill-contract.test.mjs",
170
+ "coverage:badge": "node scripts/update-coverage-badge.mjs",
171
+ "changeset": "changeset",
172
+ "version-packages": "changeset version && node scripts/sync-version.mjs",
173
+ "release": "npm run build && npm test && changeset publish",
174
+ "version": "node scripts/sync-version.mjs && git add package.json src/version.ts action.yml",
175
+ "docs:dev": "cd apps/docs && next dev",
176
+ "docs:artifacts": "node scripts/build-docs-artifacts.mjs",
177
+ "docs:build": "pnpm build && node bin/ak-docs.js index && pnpm docs:artifacts && cd apps/docs && next build",
178
+ "docs:typecheck": "cd apps/docs && fumadocs-mdx && tsc --noEmit",
179
+ "docs:e2e": "playwright test",
180
+ "report:visual": "node scripts/report-visual-check.mjs",
181
+ "test:verification-harness": "node --test scripts/verification-harness.test.mjs",
182
+ "docs:lighthouse": "lhci autorun",
183
+ "test:readme-standard": "node --test scripts/readme-standard.test.mjs",
184
+ "test:marketplace": "node --test scripts/marketplace-contract.test.mjs",
185
+ "check:marketplace": "node scripts/check-marketplace-contract.mjs",
186
+ "test:docs-artifacts": "node --test scripts/docs-artifacts-contract.test.mjs",
187
+ "check:readme-standard": "node scripts/check-readme-standard.mjs",
188
+ "check:docs": "pnpm check:readme-standard && node bin/ak-docs.js gate run && node bin/ak-docs.js conformance run documentation-standard-v1 --text && pnpm docs:typecheck && pnpm docs:build && pnpm test:docs-artifacts"
189
+ }
190
+ }
@@ -41,8 +41,9 @@ for (const file of requiredFiles) {
41
41
  if (localDigest !== expectedDigest) {
42
42
  throw new Error(`${file} differs from its recorded upstream SHA-256 digest.`)
43
43
  }
44
- const url = `https://raw.githubusercontent.com/${metadata.repository}/${metadata.ref}/${file}`
45
- const upstream = await fetchText(url)
44
+ const url = new URL(`https://raw.githubusercontent.com/${metadata.repository}/${metadata.ref}/${file}`)
45
+ if (url.hostname !== 'raw.githubusercontent.com') throw new Error('Unexpected upstream host.')
46
+ const upstream = await fetchText(url.href)
46
47
  if (sha256(upstream) !== expectedDigest || upstream !== local) {
47
48
  throw new Error(`${file} is stale against ${metadata.repository}@${metadata.ref}. Sync the canonical snapshot and digest.`)
48
49
  }
@@ -1,5 +1,6 @@
1
- import { mkdirSync, writeFileSync } from 'node:fs'
2
- import { join, resolve } from 'node:path'
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
2
+ import { createHash } from 'node:crypto'
3
+ import { join, relative, resolve } from 'node:path'
3
4
  import { pathToFileURL } from 'node:url'
4
5
 
5
6
  import { chromium } from '@playwright/test'
@@ -15,6 +16,7 @@ const themes = ['light', 'dark']
15
16
  const failures = []
16
17
  const checks = []
17
18
  const networkRequests = []
19
+ const clock = () => performance.now()
18
20
 
19
21
  mkdirSync(outputDir, { recursive: true })
20
22
 
@@ -92,6 +94,12 @@ const inspect = async (frame, width, height) => frame.evaluate(({ width: viewpor
92
94
  diagnosticCount: typeof globalThis.__DOC_BRIDGE_DATA__?.diagnosticCount === 'number'
93
95
  ? globalThis.__DOC_BRIDGE_DATA__.diagnosticCount
94
96
  : Number.parseInt(document.querySelector('#finding-count')?.textContent ?? '', 10) || document.querySelectorAll('.finding').length,
97
+ actionableCount: typeof globalThis.__DOC_BRIDGE_DATA__?.actionableCount === 'number'
98
+ ? globalThis.__DOC_BRIDGE_DATA__.actionableCount
99
+ : Number.parseInt((document.querySelector('#finding-count')?.textContent ?? '').match(/(\d+) actionable findings/)?.[1] ?? '0', 10),
100
+ confirmedCount: typeof globalThis.__DOC_BRIDGE_DATA__?.confirmedCount === 'number'
101
+ ? globalThis.__DOC_BRIDGE_DATA__.confirmedCount
102
+ : Number.parseInt((document.querySelector('#finding-count')?.textContent ?? '').match(/(\d+) confirmed checks/)?.[1] ?? '0', 10),
95
103
  unnamedButtonCount: unnamedButtons.length,
96
104
  visibleOverflowCount: visibleOverflow.length,
97
105
  contrastFailures,
@@ -99,25 +107,46 @@ const inspect = async (frame, width, height) => frame.evaluate(({ width: viewpor
99
107
  }, { width, height })
100
108
 
101
109
  const poll = async (read, predicate, label, timeoutMs = interactionTimeoutMs) => {
102
- const started = Date.now()
103
- while (Date.now() - started < timeoutMs) {
104
- if (predicate(await read())) return Date.now() - started
110
+ const started = clock()
111
+ while (clock() - started < timeoutMs) {
112
+ if (predicate(await read())) return Math.round(clock() - started)
105
113
  await new Promise((resolve) => setTimeout(resolve, 50))
106
114
  }
107
115
  throw new Error(`${label} did not reach the expected state within ${timeoutMs}ms`)
108
116
  }
109
117
 
110
118
  const exercise = async (frame, result) => {
111
- const run = async (label, action, verify) => {
112
- const started = Date.now()
119
+ result.interactionDurationsMs ??= []
120
+ const run = async (label, action, verify, browserEvent) => {
121
+ const started = clock()
113
122
  try {
123
+ if (browserEvent) await frame.evaluate((type) => {
124
+ document.documentElement.dataset.docBridgeInteractionStartedAt = ''
125
+ window.addEventListener(type, () => {
126
+ document.documentElement.dataset.docBridgeInteractionStartedAt = String(performance.now())
127
+ }, { capture: true, once: true })
128
+ }, browserEvent)
114
129
  await Promise.race([
115
130
  (async () => { await action(); if (verify) await verify() })(),
116
131
  new Promise((_, reject) => setTimeout(() => reject(new Error('interaction timeout')), interactionTimeoutMs)),
117
132
  ])
118
- const durationMs = Date.now() - started
133
+ const gestureDurationMs = Math.round(clock() - started)
134
+ const durationMs = browserEvent
135
+ ? await frame.evaluate(() => {
136
+ const startedAt = Number(document.documentElement.dataset.docBridgeInteractionStartedAt)
137
+ return Number.isFinite(startedAt) ? Math.round(performance.now() - startedAt) : null
138
+ })
139
+ : gestureDurationMs
140
+ if (durationMs == null) throw new Error(`${browserEvent} event was not observed`)
141
+ result.interactionDurationsMs.push({ label, durationMs, ...(browserEvent ? { gestureDurationMs } : {}) })
142
+ if (browserEvent) {
143
+ const renderTimings = await frame.evaluate(() => JSON.parse(document.documentElement.dataset.docBridgeRenderTimings || '{}'))
144
+ result.renderTimings ??= []
145
+ result.renderTimings.push({ label, ...renderTimings })
146
+ }
119
147
  if (durationMs > 2000) result.failures.push(`${label} took ${durationMs}ms`)
120
148
  } catch (error) {
149
+ result.interactionDurationsMs.push({ label, durationMs: Math.round(clock() - started), failed: true })
121
150
  result.failures.push(`${label}: ${error instanceof Error ? error.message : String(error)}`)
122
151
  }
123
152
  }
@@ -134,7 +163,8 @@ const exercise = async (frame, result) => {
134
163
  if (await load.count()) await load.click()
135
164
  else if (result.diagnosticCount > 0 && await frame.locator('.finding-group').count() === 0) throw new Error('finding loader is missing while findings are present')
136
165
  }, async () => {
137
- if (result.diagnosticCount > 0) {
166
+ const evidenceView = await frame.locator('body').getAttribute('data-report-view') === 'coverage'
167
+ if (result.actionableCount > 0 || (evidenceView && result.confirmedCount > 0)) {
138
168
  await poll(() => frame.locator('.finding-group').count(), (count) => count > 0, 'finding groups')
139
169
  } else if (await frame.locator('.finding-group').count() !== 0) {
140
170
  throw new Error('finding groups rendered despite zero findings')
@@ -165,7 +195,7 @@ const exercise = async (frame, result) => {
165
195
  await poll(() => frame.locator('[data-level][aria-pressed="true"]').getAttribute('data-level'), (actual) => actual === expectedLevel, `drill-down ${expectedLevel}`)
166
196
  const details = await frame.locator('#details').innerText()
167
197
  if (/Select a node in the map/i.test(details)) throw new Error('details panel did not update')
168
- })
198
+ }, 'dblclick')
169
199
  }
170
200
  if (await frame.locator('#breadcrumbs [data-breadcrumb-level]').count() < 1) result.failures.push('drill-down did not create usable breadcrumbs')
171
201
  await run('breadcrumb back to repository', () => frame.locator('#breadcrumbs [data-breadcrumb-level="overview"]').click(), async () => {
@@ -189,6 +219,9 @@ const exercise = async (frame, result) => {
189
219
  let browser
190
220
  try {
191
221
  browser = await chromium.launch()
222
+ const warmup = await browser.newPage()
223
+ await warmup.goto('about:blank')
224
+ await warmup.close()
192
225
  for (const theme of themes) {
193
226
  for (const viewport of viewports) {
194
227
  const page = await browser.newPage()
@@ -203,10 +236,12 @@ try {
203
236
  try {
204
237
  await page.setViewportSize({ width: viewport[0], height: viewport[1] })
205
238
  await page.emulateMedia({ colorScheme: theme })
239
+ const renderStarted = clock()
206
240
  await page.goto(pathToFileURL(reportPath).href, { waitUntil: 'load' })
207
241
  const frame = page.frames().find((candidate) => candidate !== page.mainFrame() && candidate.url().endsWith('/report/index.html')) ?? page.mainFrame()
208
242
  await frame.locator('#graph').waitFor({ state: 'visible', timeout: interactionTimeoutMs })
209
243
  result = await inspect(frame, viewport[0], viewport[1])
244
+ result.firstRenderMs = Math.round(clock() - renderStarted)
210
245
  await exercise(frame, result)
211
246
  result.pageErrors = pageErrors
212
247
  result.consoleErrors = consoleErrors
@@ -229,8 +264,15 @@ try {
229
264
  }
230
265
 
231
266
  const status = failures.length || checks.some((check) => check.failures.length) ? 'failed' : humanApproved ? 'passed' : 'pending-human-review'
267
+ const artifacts = checks.map((check) => {
268
+ const path = join(outputDir, `${check.viewport}-${check.theme}.png`)
269
+ return existsSync(path) ? { type: 'screenshot', path: relative(process.cwd(), path), sha256: createHash('sha256').update(readFileSync(path)).digest('hex'), viewport: check.viewport, theme: check.theme } : null
270
+ }).filter(Boolean)
232
271
  const result = {
233
272
  status,
273
+ capability: 'real-browser',
274
+ artifacts,
275
+ criteria: { 'report-ui': { status: status === 'failed' ? 'failed' : 'passed' } },
234
276
  reportPath,
235
277
  outputDir,
236
278
  viewports,
@@ -245,6 +287,16 @@ const result = {
245
287
  : 'Automated checks passed; screenshots require human visual review. Re-run with --human-approved only after review.',
246
288
  }
247
289
  writeFileSync(join(outputDir, 'result.json'), `${JSON.stringify(result, null, 2)}\n`, 'utf8')
248
- console.log(JSON.stringify(result, null, 2))
249
- console.log(JSON.stringify({ status }))
290
+ console.log(JSON.stringify({
291
+ status,
292
+ capability: result.capability,
293
+ artifacts: result.artifacts,
294
+ criteria: result.criteria,
295
+ reportPath: result.reportPath,
296
+ outputDir: result.outputDir,
297
+ viewports: result.viewports,
298
+ themes: result.themes,
299
+ failures: result.failures,
300
+ note: result.note,
301
+ }))
250
302
  if (status === 'failed') process.exitCode = 1