cbrowser 20.2.2 → 21.0.0

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 (302) hide show
  1. package/README.md +2 -2
  2. package/dist/analysis/accessibility-empathy.d.ts +29 -1
  3. package/dist/analysis/accessibility-empathy.d.ts.map +1 -1
  4. package/dist/analysis/accessibility-empathy.js +69 -35
  5. package/dist/analysis/accessibility-empathy.js.map +1 -1
  6. package/dist/browser.d.ts.map +1 -1
  7. package/dist/browser.js +11 -7
  8. package/dist/browser.js.map +1 -1
  9. package/dist/cif-score.d.ts +56 -5
  10. package/dist/cif-score.d.ts.map +1 -1
  11. package/dist/cif-score.js +595 -346
  12. package/dist/cif-score.js.map +1 -1
  13. package/dist/cli.js +10 -8
  14. package/dist/cli.js.map +1 -1
  15. package/dist/cognitive/default-traits.d.ts +14 -0
  16. package/dist/cognitive/default-traits.d.ts.map +1 -0
  17. package/dist/cognitive/default-traits.js +36 -0
  18. package/dist/cognitive/default-traits.js.map +1 -0
  19. package/dist/cognitive/emotions.d.ts.map +1 -1
  20. package/dist/cognitive/emotions.js +104 -174
  21. package/dist/cognitive/emotions.js.map +1 -1
  22. package/dist/cognitive/index.d.ts +28 -0
  23. package/dist/cognitive/index.d.ts.map +1 -1
  24. package/dist/cognitive/index.js +353 -287
  25. package/dist/cognitive/index.js.map +1 -1
  26. package/dist/cognitive/temporal.d.ts +0 -24
  27. package/dist/cognitive/temporal.d.ts.map +1 -1
  28. package/dist/cognitive/temporal.js +45 -35
  29. package/dist/cognitive/temporal.js.map +1 -1
  30. package/dist/data/ctc-reference-v2-2026-10-24.d.ts +16 -0
  31. package/dist/data/ctc-reference-v2-2026-10-24.d.ts.map +1 -0
  32. package/dist/data/ctc-reference-v2-2026-10-24.js +1004 -0
  33. package/dist/data/ctc-reference-v2-2026-10-24.js.map +1 -0
  34. package/dist/ideal-profiles-gap.d.ts +1 -0
  35. package/dist/ideal-profiles-gap.d.ts.map +1 -1
  36. package/dist/ideal-profiles-gap.js +9 -1
  37. package/dist/ideal-profiles-gap.js.map +1 -1
  38. package/dist/ideal-profiles.d.ts +8 -0
  39. package/dist/ideal-profiles.d.ts.map +1 -1
  40. package/dist/ideal-profiles.js +28 -24
  41. package/dist/ideal-profiles.js.map +1 -1
  42. package/dist/mcp-server-remote.d.ts.map +1 -1
  43. package/dist/mcp-server-remote.js +7 -8
  44. package/dist/mcp-server-remote.js.map +1 -1
  45. package/dist/mcp-tools/account-personas.d.ts.map +1 -1
  46. package/dist/mcp-tools/account-personas.js +3 -1
  47. package/dist/mcp-tools/account-personas.js.map +1 -1
  48. package/dist/mcp-tools/base/answer-provenance.d.ts +28 -0
  49. package/dist/mcp-tools/base/answer-provenance.d.ts.map +1 -0
  50. package/dist/mcp-tools/base/answer-provenance.js +32 -0
  51. package/dist/mcp-tools/base/answer-provenance.js.map +1 -0
  52. package/dist/mcp-tools/base/audit-tools.d.ts +0 -3
  53. package/dist/mcp-tools/base/audit-tools.d.ts.map +1 -1
  54. package/dist/mcp-tools/base/audit-tools.js +152 -84
  55. package/dist/mcp-tools/base/audit-tools.js.map +1 -1
  56. package/dist/mcp-tools/base/capture-tools.js +1 -1
  57. package/dist/mcp-tools/base/capture-tools.js.map +1 -1
  58. package/dist/mcp-tools/base/chain-values.d.ts +38 -0
  59. package/dist/mcp-tools/base/chain-values.d.ts.map +1 -0
  60. package/dist/mcp-tools/base/chain-values.js +85 -0
  61. package/dist/mcp-tools/base/chain-values.js.map +1 -0
  62. package/dist/mcp-tools/base/cognitive-tools.d.ts.map +1 -1
  63. package/dist/mcp-tools/base/cognitive-tools.js +126 -120
  64. package/dist/mcp-tools/base/cognitive-tools.js.map +1 -1
  65. package/dist/mcp-tools/base/gif-tools.d.ts.map +1 -1
  66. package/dist/mcp-tools/base/gif-tools.js +23 -5
  67. package/dist/mcp-tools/base/gif-tools.js.map +1 -1
  68. package/dist/mcp-tools/base/persona-comparison-tools.d.ts.map +1 -1
  69. package/dist/mcp-tools/base/persona-comparison-tools.js +229 -172
  70. package/dist/mcp-tools/base/persona-comparison-tools.js.map +1 -1
  71. package/dist/mcp-tools/base/site-familiarity.d.ts +31 -0
  72. package/dist/mcp-tools/base/site-familiarity.d.ts.map +1 -0
  73. package/dist/mcp-tools/base/site-familiarity.js +30 -0
  74. package/dist/mcp-tools/base/site-familiarity.js.map +1 -0
  75. package/dist/mcp-tools/base/site-knowledge-tools.js +1 -1
  76. package/dist/mcp-tools/base/site-knowledge-tools.js.map +1 -1
  77. package/dist/mcp-tools/base/story-narrative.d.ts +50 -0
  78. package/dist/mcp-tools/base/story-narrative.d.ts.map +1 -0
  79. package/dist/mcp-tools/base/story-narrative.js +88 -0
  80. package/dist/mcp-tools/base/story-narrative.js.map +1 -0
  81. package/dist/mcp-tools/base/values-tools.d.ts.map +1 -1
  82. package/dist/mcp-tools/base/values-tools.js +19 -5
  83. package/dist/mcp-tools/base/values-tools.js.map +1 -1
  84. package/dist/mcp-tools/base/visual-testing-tools.d.ts +5 -5
  85. package/dist/mcp-tools/base/visual-testing-tools.d.ts.map +1 -1
  86. package/dist/mcp-tools/base/visual-testing-tools.js +38 -11
  87. package/dist/mcp-tools/base/visual-testing-tools.js.map +1 -1
  88. package/dist/mcp-tools/persona-creation-tools.js +6 -6
  89. package/dist/mcp-tools/persona-creation-tools.js.map +1 -1
  90. package/dist/mcp-tools/persona-lifecycle-tools.d.ts.map +1 -1
  91. package/dist/mcp-tools/persona-lifecycle-tools.js +5 -1
  92. package/dist/mcp-tools/persona-lifecycle-tools.js.map +1 -1
  93. package/dist/mcp-tools/tier-gate.js +1 -1
  94. package/dist/mcp-tools/tool-result-saver.d.ts +7 -0
  95. package/dist/mcp-tools/tool-result-saver.d.ts.map +1 -1
  96. package/dist/mcp-tools/tool-result-saver.js +11 -0
  97. package/dist/mcp-tools/tool-result-saver.js.map +1 -1
  98. package/dist/mcp-tools/trait-provenance.d.ts +109 -0
  99. package/dist/mcp-tools/trait-provenance.d.ts.map +1 -0
  100. package/dist/mcp-tools/trait-provenance.js +187 -0
  101. package/dist/mcp-tools/trait-provenance.js.map +1 -0
  102. package/dist/mcp-tools/ui-resources.d.ts +6 -7
  103. package/dist/mcp-tools/ui-resources.d.ts.map +1 -1
  104. package/dist/mcp-tools/ui-resources.js +162 -12
  105. package/dist/mcp-tools/ui-resources.js.map +1 -1
  106. package/dist/mcp-tools/widget-kit.d.ts +129 -3
  107. package/dist/mcp-tools/widget-kit.d.ts.map +1 -1
  108. package/dist/mcp-tools/widget-kit.js +507 -28
  109. package/dist/mcp-tools/widget-kit.js.map +1 -1
  110. package/dist/performance/metrics.d.ts.map +1 -1
  111. package/dist/performance/metrics.js +4 -1
  112. package/dist/performance/metrics.js.map +1 -1
  113. package/dist/persona-questionnaire.js +1 -1
  114. package/dist/persona-questionnaire.js.map +1 -1
  115. package/dist/personas.d.ts.map +1 -1
  116. package/dist/personas.js +133 -82
  117. package/dist/personas.js.map +1 -1
  118. package/dist/recording/engine.d.ts.map +1 -1
  119. package/dist/recording/engine.js +5 -2
  120. package/dist/recording/engine.js.map +1 -1
  121. package/dist/recording/saliency-overlay.d.ts +8 -3
  122. package/dist/recording/saliency-overlay.d.ts.map +1 -1
  123. package/dist/recording/saliency-overlay.js +14 -5
  124. package/dist/recording/saliency-overlay.js.map +1 -1
  125. package/dist/recording/types.js +1 -1
  126. package/dist/testing/nl-test-suite.d.ts.map +1 -1
  127. package/dist/testing/nl-test-suite.js +2 -1
  128. package/dist/testing/nl-test-suite.js.map +1 -1
  129. package/dist/trait-reference.d.ts +19 -2
  130. package/dist/trait-reference.d.ts.map +1 -1
  131. package/dist/trait-reference.js +19 -2
  132. package/dist/trait-reference.js.map +1 -1
  133. package/dist/types.d.ts +32 -21
  134. package/dist/types.d.ts.map +1 -1
  135. package/dist/types.js +62 -52
  136. package/dist/types.js.map +1 -1
  137. package/dist/values/persona-completeness.js +1 -1
  138. package/dist/values/persona-completeness.js.map +1 -1
  139. package/dist/visual/ab-comparison.d.ts.map +1 -1
  140. package/dist/visual/ab-comparison.js +4 -1
  141. package/dist/visual/ab-comparison.js.map +1 -1
  142. package/dist/visual/attention-quality.d.ts +1 -1
  143. package/dist/visual/attention-quality.js +1 -1
  144. package/dist/visual/attention-transport.d.ts +74 -10
  145. package/dist/visual/attention-transport.d.ts.map +1 -1
  146. package/dist/visual/attention-transport.js +353 -193
  147. package/dist/visual/attention-transport.js.map +1 -1
  148. package/dist/visual/cognitive-models.d.ts +77 -55
  149. package/dist/visual/cognitive-models.d.ts.map +1 -1
  150. package/dist/visual/cognitive-models.js +231 -104
  151. package/dist/visual/cognitive-models.js.map +1 -1
  152. package/dist/visual/cognitive-transport-chain.d.ts +328 -30
  153. package/dist/visual/cognitive-transport-chain.d.ts.map +1 -1
  154. package/dist/visual/cognitive-transport-chain.js +576 -353
  155. package/dist/visual/cognitive-transport-chain.js.map +1 -1
  156. package/dist/visual/cognitive-transport.d.ts +446 -13
  157. package/dist/visual/cognitive-transport.d.ts.map +1 -1
  158. package/dist/visual/cognitive-transport.js +1085 -245
  159. package/dist/visual/cognitive-transport.js.map +1 -1
  160. package/dist/visual/cross-browser.d.ts.map +1 -1
  161. package/dist/visual/cross-browser.js +8 -6
  162. package/dist/visual/cross-browser.js.map +1 -1
  163. package/dist/visual/ctc-bands.d.ts +91 -0
  164. package/dist/visual/ctc-bands.d.ts.map +1 -0
  165. package/dist/visual/ctc-bands.js +117 -0
  166. package/dist/visual/ctc-bands.js.map +1 -0
  167. package/dist/visual/ctc-reference.d.ts +287 -0
  168. package/dist/visual/ctc-reference.d.ts.map +1 -0
  169. package/dist/visual/ctc-reference.js +376 -0
  170. package/dist/visual/ctc-reference.js.map +1 -0
  171. package/dist/visual/demand-laws.d.ts +145 -0
  172. package/dist/visual/demand-laws.d.ts.map +1 -0
  173. package/dist/visual/demand-laws.js +220 -0
  174. package/dist/visual/demand-laws.js.map +1 -0
  175. package/dist/visual/distance-metrics.d.ts +23 -1
  176. package/dist/visual/distance-metrics.d.ts.map +1 -1
  177. package/dist/visual/distance-metrics.js +49 -18
  178. package/dist/visual/distance-metrics.js.map +1 -1
  179. package/dist/visual/dro-bound.d.ts +220 -0
  180. package/dist/visual/dro-bound.d.ts.map +1 -0
  181. package/dist/visual/dro-bound.js +341 -0
  182. package/dist/visual/dro-bound.js.map +1 -0
  183. package/dist/visual/exact-w1.d.ts +60 -0
  184. package/dist/visual/exact-w1.d.ts.map +1 -0
  185. package/dist/visual/exact-w1.js +247 -0
  186. package/dist/visual/exact-w1.js.map +1 -0
  187. package/dist/visual/human-input.d.ts.map +1 -1
  188. package/dist/visual/human-input.js +45 -41
  189. package/dist/visual/human-input.js.map +1 -1
  190. package/dist/visual/index.d.ts +2 -0
  191. package/dist/visual/index.d.ts.map +1 -1
  192. package/dist/visual/index.js +2 -0
  193. package/dist/visual/index.js.map +1 -1
  194. package/dist/visual/law-times-report.d.ts +49 -0
  195. package/dist/visual/law-times-report.d.ts.map +1 -0
  196. package/dist/visual/law-times-report.js +38 -0
  197. package/dist/visual/law-times-report.js.map +1 -0
  198. package/dist/visual/model-provenance.d.ts +151 -0
  199. package/dist/visual/model-provenance.d.ts.map +1 -0
  200. package/dist/visual/model-provenance.js +334 -0
  201. package/dist/visual/model-provenance.js.map +1 -0
  202. package/dist/visual/normal-cdf.d.ts +14 -0
  203. package/dist/visual/normal-cdf.d.ts.map +1 -0
  204. package/dist/visual/normal-cdf.js +26 -0
  205. package/dist/visual/normal-cdf.js.map +1 -0
  206. package/dist/visual/perceptual-transport.d.ts +28 -4
  207. package/dist/visual/perceptual-transport.d.ts.map +1 -1
  208. package/dist/visual/perceptual-transport.js +66 -32
  209. package/dist/visual/perceptual-transport.js.map +1 -1
  210. package/dist/visual/provenance/attention.d.ts +55 -0
  211. package/dist/visual/provenance/attention.d.ts.map +1 -0
  212. package/dist/visual/provenance/attention.js +188 -0
  213. package/dist/visual/provenance/attention.js.map +1 -0
  214. package/dist/visual/provenance/chain.d.ts +41 -0
  215. package/dist/visual/provenance/chain.d.ts.map +1 -0
  216. package/dist/visual/provenance/chain.js +66 -0
  217. package/dist/visual/provenance/chain.js.map +1 -0
  218. package/dist/visual/provenance/cif.d.ts +42 -0
  219. package/dist/visual/provenance/cif.d.ts.map +1 -0
  220. package/dist/visual/provenance/cif.js +116 -0
  221. package/dist/visual/provenance/cif.js.map +1 -0
  222. package/dist/visual/provenance/ctc-reference.d.ts +36 -0
  223. package/dist/visual/provenance/ctc-reference.d.ts.map +1 -0
  224. package/dist/visual/provenance/ctc-reference.js +96 -0
  225. package/dist/visual/provenance/ctc-reference.js.map +1 -0
  226. package/dist/visual/provenance/distance.d.ts +10 -0
  227. package/dist/visual/provenance/distance.d.ts.map +1 -0
  228. package/dist/visual/provenance/distance.js +72 -0
  229. package/dist/visual/provenance/distance.js.map +1 -0
  230. package/dist/visual/provenance/dro.d.ts +10 -0
  231. package/dist/visual/provenance/dro.d.ts.map +1 -0
  232. package/dist/visual/provenance/dro.js +2 -0
  233. package/dist/visual/provenance/dro.js.map +1 -0
  234. package/dist/visual/provenance/emotion.d.ts +26 -0
  235. package/dist/visual/provenance/emotion.d.ts.map +1 -0
  236. package/dist/visual/provenance/emotion.js +99 -0
  237. package/dist/visual/provenance/emotion.js.map +1 -0
  238. package/dist/visual/provenance/human-input.d.ts +19 -0
  239. package/dist/visual/provenance/human-input.d.ts.map +1 -0
  240. package/dist/visual/provenance/human-input.js +84 -0
  241. package/dist/visual/provenance/human-input.js.map +1 -0
  242. package/dist/visual/provenance/journey-group.d.ts +15 -0
  243. package/dist/visual/provenance/journey-group.d.ts.map +1 -0
  244. package/dist/visual/provenance/journey-group.js +15 -0
  245. package/dist/visual/provenance/journey-group.js.map +1 -0
  246. package/dist/visual/provenance/journey-mcp.d.ts +24 -0
  247. package/dist/visual/provenance/journey-mcp.d.ts.map +1 -0
  248. package/dist/visual/provenance/journey-mcp.js +54 -0
  249. package/dist/visual/provenance/journey-mcp.js.map +1 -0
  250. package/dist/visual/provenance/journey.d.ts +34 -0
  251. package/dist/visual/provenance/journey.d.ts.map +1 -0
  252. package/dist/visual/provenance/journey.js +184 -0
  253. package/dist/visual/provenance/journey.js.map +1 -0
  254. package/dist/visual/provenance/laws.d.ts +38 -0
  255. package/dist/visual/provenance/laws.d.ts.map +1 -0
  256. package/dist/visual/provenance/laws.js +40 -0
  257. package/dist/visual/provenance/laws.js.map +1 -0
  258. package/dist/visual/provenance/page-metrics.d.ts +42 -0
  259. package/dist/visual/provenance/page-metrics.d.ts.map +1 -0
  260. package/dist/visual/provenance/page-metrics.js +79 -0
  261. package/dist/visual/provenance/page-metrics.js.map +1 -0
  262. package/dist/visual/provenance/perceptual.d.ts +63 -0
  263. package/dist/visual/provenance/perceptual.d.ts.map +1 -0
  264. package/dist/visual/provenance/perceptual.js +115 -0
  265. package/dist/visual/provenance/perceptual.js.map +1 -0
  266. package/dist/visual/provenance/persona-rates.d.ts +84 -0
  267. package/dist/visual/provenance/persona-rates.d.ts.map +1 -0
  268. package/dist/visual/provenance/persona-rates.js +112 -0
  269. package/dist/visual/provenance/persona-rates.js.map +1 -0
  270. package/dist/visual/provenance/personas.d.ts +122 -0
  271. package/dist/visual/provenance/personas.d.ts.map +1 -0
  272. package/dist/visual/provenance/personas.js +298 -0
  273. package/dist/visual/provenance/personas.js.map +1 -0
  274. package/dist/visual/provenance/profiles.d.ts +39 -0
  275. package/dist/visual/provenance/profiles.d.ts.map +1 -0
  276. package/dist/visual/provenance/profiles.js +37 -0
  277. package/dist/visual/provenance/profiles.js.map +1 -0
  278. package/dist/visual/provenance/temporal.d.ts +21 -0
  279. package/dist/visual/provenance/temporal.d.ts.map +1 -0
  280. package/dist/visual/provenance/temporal.js +56 -0
  281. package/dist/visual/provenance/temporal.js.map +1 -0
  282. package/dist/visual/provenance/visual-distance.d.ts +22 -0
  283. package/dist/visual/provenance/visual-distance.d.ts.map +1 -0
  284. package/dist/visual/provenance/visual-distance.js +30 -0
  285. package/dist/visual/provenance/visual-distance.js.map +1 -0
  286. package/dist/visual/regression.d.ts.map +1 -1
  287. package/dist/visual/regression.js +14 -9
  288. package/dist/visual/regression.js.map +1 -1
  289. package/dist/visual/responsive.d.ts.map +1 -1
  290. package/dist/visual/responsive.js +4 -1
  291. package/dist/visual/responsive.js.map +1 -1
  292. package/dist/visual/scope-prose.js +2 -2
  293. package/dist/visual/scope-prose.js.map +1 -1
  294. package/dist/visual/seeded-random.d.ts +16 -0
  295. package/dist/visual/seeded-random.d.ts.map +1 -0
  296. package/dist/visual/seeded-random.js +25 -0
  297. package/dist/visual/seeded-random.js.map +1 -0
  298. package/dist/visual/stored-accessibility-traits.d.ts +27 -0
  299. package/dist/visual/stored-accessibility-traits.d.ts.map +1 -0
  300. package/dist/visual/stored-accessibility-traits.js +35 -0
  301. package/dist/visual/stored-accessibility-traits.js.map +1 -0
  302. package/package.json +1 -1
@@ -8,8 +8,15 @@ import { z } from "zod";
8
8
  import { barrierWeightFor, weightedSeverity, weightKeyFor } from "../../visual/perceptual-transport.js";
9
9
  import { writeArtifact } from "../../artifact-store.js";
10
10
  import { buildContentWithPreviews } from "../inline-image.js";
11
- import { bridgeCapacityTraits } from "../../visual/cognitive-models.js";
11
+ import { bridgeCapacityTraits, personaRateContext } from "../../visual/cognitive-models.js";
12
+ import { lawTimesReport } from "../../visual/law-times-report.js";
13
+ import { resolveSiteFamiliarity as resolveRunFamiliarity } from "./site-familiarity.js";
12
14
  import { refuseHostedUrl } from "../hosted-inputs.js";
15
+ import { activeProvenance, collectPersonaReads, isolatedProvenanceAsync, personaRateProvenance, ATTENTION_PROVENANCE_TEXT } from "../../visual/model-provenance.js";
16
+ import { ctcRank } from "../../visual/ctc-bands.js";
17
+ import { recordedAnswer } from "./answer-provenance.js";
18
+ import { resolveChainValues, valueModulationReport } from "./chain-values.js";
19
+ import { storyEffortSection, storyVerdict } from "./story-narrative.js";
13
20
  import { runAgentReadyAudit, pageAgentReadyFindings, runCompetitiveBenchmark, runEmpathyAudit, runWebMCPReadyAudit, } from "../../analysis/index.js";
14
21
  import { listAccessibilityPersonas } from "../../personas.js";
15
22
  import { barrierElementCount, countAffectedElements } from "../../analysis/accessibility-empathy.js";
@@ -17,7 +24,7 @@ import { getDefaultConfig, effectiveDataDir } from "../../config.js";
17
24
  import { describeRenderTarget } from "../../devices.js";
18
25
  import { writeFileSync, mkdirSync, existsSync } from "fs";
19
26
  import { join } from "path";
20
- import { makeTempDir } from "../../temp-files.js";
27
+ import { makeTempDir, uniqueStamp } from "../../temp-files.js";
21
28
  /**
22
29
  * Persist an audit screenshot and return its path.
23
30
  *
@@ -39,7 +46,9 @@ export function writeAuditScreenshot(base64, persona) {
39
46
  const dir = join(effectiveDataDir(), "audit-screenshots");
40
47
  if (!existsSync(dir))
41
48
  mkdirSync(dir, { recursive: true });
42
- const path = join(dir, `empathy-${safe}-${Date.now()}.png`);
49
+ // Time plus random bits: two audits of one persona in the same
50
+ // millisecond wrote one file and both returned its path.
51
+ const path = join(dir, `empathy-${safe}-${uniqueStamp()}.png`);
43
52
  writeFileSync(path, Buffer.from(base64, "base64"));
44
53
  out.path = path;
45
54
  }
@@ -66,6 +75,10 @@ export function writeAuditScreenshot(base64, persona) {
66
75
  /**
67
76
  * Register audit tools (4 tools + site_cognitive_assessment + visual_cognitive_story)
68
77
  */
78
+ // One shared helper for every attention caller (ISC-48, ISC-48.1): the persona
79
+ // record's own accessibilityTraits, only the fields it sets. It used to be
80
+ // copied here and in the other tool file.
81
+ import { personaStoredAccessibilityTraits as personaAccessibilityTraits } from "../../visual/stored-accessibility-traits.js";
69
82
  export function registerAuditTools(server, context) {
70
83
  const getBrowserByToken = context?.getBrowserByToken;
71
84
  server.registerTool("agent_ready_audit", {
@@ -204,17 +217,16 @@ export function registerAuditTools(server, context) {
204
217
  // ── Site Cognitive Assessment (Composite Pipeline) ──
205
218
  server.registerTool("site_cognitive_assessment", {
206
219
  title: "Site Cognitive Assessment",
207
- description: "Three-gate pipeline: (1) bot-detection probe, (2) agent-ready qualification, (3) per-persona cognitive effort analysis. IMPORTANT: If the user requests high-familiarity personas (power-user, confident-user), check site_model_status first. Without site knowledge, these personas are tested as first-time visitors and a familiarityWarning is returned. Ask the user if they want to build site knowledge first.",
220
+ description: "Three-gate pipeline: (1) bot-detection probe, (2) agent-ready qualification, (3) per-persona cognitive effort analysis. Site familiarity is per run: pass siteFamiliarity (0 first visit, 1 regular user) or every persona is scored as a first visit. No persona carries a familiarity of its own, and site knowledge from crawling is reported but does not change the score.",
208
221
  inputSchema: {
209
222
  url: z.string().url().describe("URL to assess"),
210
223
  personas: z.string().optional().default("first-timer,cognitive-adhd").describe("Comma-separated persona names (default: first-timer,cognitive-adhd)"),
211
224
  threshold: z.number().optional().default(60).describe("Minimum agent-ready score to proceed to persona analysis (default: 60)"),
212
225
  // Per RUN, not per persona. The same person is familiar with one site and new
213
- // to another, so this has no value until a site is named -- a stored value only
214
- // ever answered "familiar with which site?" by accident. The persona's value is
215
- // the fallback when a caller does not pass one. (2026-08-01)
226
+ // to another, so this has no value until a site is named. Omitted, the run
227
+ // scores a first visit (0.0); see site-familiarity.ts. (2026-08-01, 21.0)
216
228
  siteFamiliarity: z.number().min(0).max(1).optional()
217
- .describe("How familiar these personas are with THIS site: 0 first visit, 1 regular user. Overrides any value stored on the persona. Familiarity is a persona-site pair, not a disposition, so set it per run."),
229
+ .describe("How familiar these personas are with THIS site: 0 first visit, 1 regular user. Omitted, every persona is scored as a first visit (0). Familiarity is a persona-site pair, not a disposition, so set it per run; site knowledge does not supply it."),
218
230
  userLocation: z.string().optional().describe("User's approximate location (e.g., 'Denver, Colorado, US') — for geo-aware content expectations"),
219
231
  userTimezone: z.string().optional().describe("User's timezone (e.g., 'America/Denver') — for time-sensitive content evaluation"),
220
232
  userLanguage: z.string().optional().describe("User's expected language (e.g., 'en-US') — for readability calibration"),
@@ -233,7 +245,9 @@ export function registerAuditTools(server, context) {
233
245
  idempotentHint: false,
234
246
  openWorldHint: true,
235
247
  },
236
- }, async ({ url, personas, threshold, siteFamiliarity, userLocation, userTimezone, userLanguage, proxy, geoRegion, device, waitAfterLoad, waitForSelector, scope, _browserToken }) => {
248
+ }, recordedAnswer(async ({ url, personas, threshold, siteFamiliarity, userLocation, userTimezone, userLanguage, proxy, geoRegion, device, waitAfterLoad, waitForSelector, scope, _browserToken }) => {
249
+ // recordedAnswer: one provenance recording per call, across every await,
250
+ // reported as modelProvenance once gate 3 has scored (answer-provenance.ts).
237
251
  // Hosted: http(s) only (sec-paths, 2026-10-10; see hosted-inputs.ts).
238
252
  const hostedRefusal = refuseHostedUrl("site_cognitive_assessment", { url });
239
253
  if (hostedRefusal)
@@ -492,6 +506,10 @@ export function registerAuditTools(server, context) {
492
506
  }
493
507
  // ── Gate 3: Per-Persona Cognitive Effort ──
494
508
  const personaResults = {};
509
+ // Each scored persona's rank sentence, for the recommendations below.
510
+ const rankSentences = {};
511
+ // Each scored persona's motor and reading rates (personaRateProvenance).
512
+ const personaRates = {};
495
513
  const { buildOTCognitiveProfile } = await import("../../visual/cognitive-transport.js");
496
514
  const { computeDemandDistribution, computeSequentialCTC } = await import("../../visual/cognitive-transport-chain.js");
497
515
  const { extractPageMetrics } = await import("../../visual/cognitive-transport.js");
@@ -518,7 +536,6 @@ export function registerAuditTools(server, context) {
518
536
  // extractPageMetrics runs its own lazy-load scroll walk at full_page, so
519
537
  // forwarding the scope is the entire fix.
520
538
  const pageMetrics = await extractPageMetrics(metricsPage, auditScope);
521
- const demand = computeDemandDistribution(pageMetrics);
522
539
  // Page understanding
523
540
  let pageUnderstanding = null;
524
541
  try {
@@ -536,8 +553,8 @@ export function registerAuditTools(server, context) {
536
553
  };
537
554
  }
538
555
  catch { }
539
- // Check site knowledge — binary gate for siteFamiliarity
540
- // Has data = persona keeps its familiarity. No data = forced to 0.0 (first visit).
556
+ // Site knowledge is looked up only to report it. siteFamiliarity is the
557
+ // caller's value or 0.0 (first visit), whatever this install has crawled.
541
558
  let hasSiteKnowledge = false;
542
559
  try {
543
560
  const domain = new URL(url).hostname;
@@ -554,26 +571,13 @@ export function registerAuditTools(server, context) {
554
571
  resolvePersonaOrThrow(personaName);
555
572
  const personaObj = existingPersona || createCognitivePersona(personaName, personaName, {});
556
573
  const traits = { ...(personaObj.cognitiveTraits || {}) };
557
- // Familiarity is a persona-SITE pair, so the caller's value for THIS
558
- // run wins over anything stored on the persona. A stored value only
559
- // ever answered "familiar with which site?" by accident.
560
- // (2026-08-01)
561
- const reqFam = traits.siteFamiliarity ?? 0.5;
562
- const familiarityFromCaller = typeof siteFamiliarity === "number";
563
- if (familiarityFromCaller) {
564
- traits.siteFamiliarity = siteFamiliarity;
565
- }
566
- else {
567
- // v18.61.0: without an explicit value it stays a binary gate on
568
- // whether this install has crawled the site at all.
569
- if (hasSiteKnowledge) {
570
- if (reqFam > 0.5)
571
- traits.siteFamiliarity = 1.0;
572
- }
573
- else {
574
- traits.siteFamiliarity = 0.0;
575
- }
576
- }
574
+ // Familiarity is the visit's: the caller's value for THIS run, else
575
+ // 0.0, the same for every persona. Since 21.0 (Stage C) it is
576
+ // Cockburn et al.'s expertise e on the choosing time, handed to the
577
+ // demand, not a persona trait. Through 20.x a site-knowledge branch
578
+ // left the trait unset, which the chain read as 0.5 (site-familiarity.ts).
579
+ const { resolveSiteFamiliarity } = await import("./site-familiarity.js");
580
+ const familiarity = resolveSiteFamiliarity(siteFamiliarity, hasSiteKnowledge);
577
581
  // Bridge the derived capacities before building the profile. This was
578
582
  // MISSING here and present in cognitive_effort, so the motor layer --
579
583
  // whose sole trait is motorCapacity -- scored against traitValue's
@@ -589,11 +593,22 @@ export function registerAuditTools(server, context) {
589
593
  // This moves more than motor. readability, readingAttention, totalCTC,
590
594
  // bottleneck and abandonmentRisk all shift for every persona here, all
591
595
  // converging on what cognitive_effort already reported.
592
- bridgeCapacityTraits(personaName, traits, personaObj.accessibilityTraits);
596
+ personaRates[personaName] = personaRateProvenance(collectPersonaReads(() => bridgeCapacityTraits(personaName, traits, personaObj.accessibilityTraits, personaRateContext(personaObj))).ids);
593
597
  const otProfile = buildOTCognitiveProfile(personaName, traits);
594
- const result = computeSequentialCTC(otProfile, demand, { asymmetric: true, interactions: true });
598
+ // Rebuilt for each persona from the same metrics, so every persona is
599
+ // scored against the same demand; this tool has no values option. The
600
+ // whole handler is one provenance recording, so the demand curves and
601
+ // the chain are counted with everything else the answer read.
602
+ const result = computeSequentialCTC(otProfile, computeDemandDistribution(pageMetrics, { siteFamiliarity: familiarity.value }), { asymmetric: true, interactions: true });
603
+ const rank = ctcRank(result);
604
+ rankSentences[personaName] = rank.sentence;
595
605
  personaResults[personaName] = {
606
+ // A percentile of the real reference pages (21.x); `band` names its band.
596
607
  totalCTC: Math.round(result.totalCTC * 1000) / 1000,
608
+ // Its 95% interval across bootstrap resamples of the reference pages.
609
+ totalCTCInterval: result.totalCTCInterval.map((x) => Math.round(x * 1000) / 1000),
610
+ band: rank.band,
611
+ referenceScope: result.referenceScope,
597
612
  bottleneck: result.bottleneckLayer,
598
613
  abandonmentRisk: Math.round(result.abandonmentRisk * 100) + "%",
599
614
  deficit: Math.round(result.deficitCost * 1000) / 1000,
@@ -602,13 +617,9 @@ export function registerAuditTools(server, context) {
602
617
  name: l.name,
603
618
  cost: Math.round(l.transportCost * 1000) / 1000,
604
619
  })),
605
- // Only warn about the automatic downgrade. A caller who SET the
606
- // value for this run has already answered the question the
607
- // downgrade exists to answer.
608
- ...(!familiarityFromCaller && reqFam > 0.5 && !hasSiteKnowledge ? {
609
- familiarityWarning: `siteFamiliarity downgraded from ${reqFam} to 0.0 — no site knowledge exists for this domain. Pass siteFamiliarity on this call to state it directly, or run page_understand first.`,
610
- } : {}),
611
- ...(familiarityFromCaller ? { siteFamiliaritySource: "supplied for this run" } : {}),
620
+ siteFamiliarity: familiarity.value,
621
+ siteFamiliaritySource: familiarity.source,
622
+ siteKnowledgeAvailable: familiarity.siteKnowledgeAvailable,
612
623
  };
613
624
  }
614
625
  catch (personaErr) {
@@ -616,6 +627,9 @@ export function registerAuditTools(server, context) {
616
627
  }
617
628
  }
618
629
  response.gate3 = { status: "complete", personas: personaResults };
630
+ // Holds its place in the JSON; filled after the recommendations, which
631
+ // read the band constants, so the block covers the whole answer.
632
+ response.modelProvenance = undefined;
619
633
  response.pageMetrics = {
620
634
  informationDensity: Math.round(pageMetrics.informationDensity * 1000) / 1000,
621
635
  visualComplexity: Math.round(pageMetrics.visualComplexity * 1000) / 1000,
@@ -628,6 +642,9 @@ export function registerAuditTools(server, context) {
628
642
  longWordRatio: Math.round(pageMetrics.longWordRatio * 1000) / 1000,
629
643
  technicalDensity: Math.round(pageMetrics.technicalDensity * 1000) / 1000,
630
644
  scriptFamily: pageMetrics.scriptFamily || "alphabetic",
645
+ // Stage B: the four law times behind the motor, decision, procedure and reading
646
+ // demands; choosing at the run's siteFamiliarity (Stage C).
647
+ lawTimes: lawTimesReport(pageMetrics, resolveRunFamiliarity(siteFamiliarity, false).value),
631
648
  // Every ratio above is 0-1 NORMALIZED, not a raw measurement.
632
649
  // cognitive_effort's readability penalties report avg word length in
633
650
  // raw CHARACTERS (e.g. 11.4), so the same name carried two scales
@@ -639,14 +656,24 @@ export function registerAuditTools(server, context) {
639
656
  if (pageUnderstanding)
640
657
  response.pageUnderstanding = pageUnderstanding;
641
658
  // Generate recommendations
659
+ // The rank, not a bare totalCTC: "CTC=0.41" read as a cost on a scale
660
+ // it is no longer on (a percentile since 21.x), with no reference named.
642
661
  const recommendations = [];
643
662
  for (const [name, data] of Object.entries(personaResults)) {
644
663
  const d = data;
645
664
  if (d.bottleneck) {
646
- recommendations.push(`${name}: Reduce ${d.bottleneck} complexity (primary bottleneck, CTC=${d.totalCTC})`);
665
+ recommendations.push(`${name}: reduce ${d.bottleneck} complexity first, the costliest layer. ${rankSentences[name]}`);
647
666
  }
648
667
  }
649
668
  response.recommendations = recommendations.slice(0, 5);
669
+ // Reported when at least one persona was scored, as before.
670
+ if (Object.keys(rankSentences).length > 0) {
671
+ response.modelProvenance = activeProvenance();
672
+ // Per scored persona, counted apart from the score's constants.
673
+ response.personaRateProvenance = Object.fromEntries(Object.keys(rankSentences).map((n) => [n, personaRates[n]]));
674
+ }
675
+ else
676
+ delete response.modelProvenance;
650
677
  response.result = "COMPLETE";
651
678
  response.duration = `${Date.now() - startTime}ms`;
652
679
  // Auto-save to site dashboard
@@ -683,7 +710,7 @@ export function registerAuditTools(server, context) {
683
710
  response.duration = `${Date.now() - startTime}ms`;
684
711
  }
685
712
  return { content: [{ type: "text", text: JSON.stringify(response, null, 2) }] };
686
- });
713
+ }));
687
714
  // ── Visual Cognitive Story ──
688
715
  server.registerTool("visual_cognitive_story", {
689
716
  title: "Visual Cognitive Story",
@@ -692,7 +719,7 @@ export function registerAuditTools(server, context) {
692
719
  url: z.string().url().describe("URL to analyze"),
693
720
  persona: z.string().optional().default("cognitive-adhd").describe("Persona name"),
694
721
  device: z.string().optional().describe("Device to emulate: 'mobile', 'tablet', 'desktop' (1920x1080), or a specific device like 'iPhone 15', 'Pixel 7', 'iPad Pro 11'. Default: no emulation, at the configured viewport (1920x1080 unless CBROWSER_VIEWPORT_WIDTH/HEIGHT or the config file say otherwise); the response reports the device and viewport used. Ignored with _browserToken (the session keeps its own)."),
695
- useValues: z.boolean().optional().default(false).describe("Enable motivational value influence on saliency map generation, attention scoring, and narrative. Default: false."),
722
+ useValues: z.boolean().optional().default(false).describe("Apply the persona's Schwartz values (opt-in). They change two things: the attention quality scoring (which CTAs and distractors the persona weights), and the effort summary, where they modulate the page's demand inside the chain through its registered coefficients (chain.valueModulation.*, hand-set hypotheses, counted in modelProvenance), exactly as cognitive_effort's useValues does. They do not change the attention heatmap. `valueModulation` in the response says whether values were found for the chain and which scores it used. Default: false."),
696
723
  // Goal-directed attention is the largest single lever in the semantic
697
724
  // layer: computeGoalRelevance boosts any element whose text matches the
698
725
  // goal's keywords, which is what separates "where the eye lands on this
@@ -710,12 +737,15 @@ export function registerAuditTools(server, context) {
710
737
  idempotentHint: false,
711
738
  openWorldHint: true,
712
739
  },
713
- }, async ({ url, persona, device, useValues, goal, aiLayer, _browserToken }) => {
740
+ }, recordedAnswer(async ({ url, persona, device, useValues, goal, aiLayer, _browserToken }) => {
741
+ // recordedAnswer: one provenance recording per call, across every await,
742
+ // reported as modelProvenance (answer-provenance.ts).
714
743
  // Hosted: http(s) only (sec-paths, 2026-10-10; see hosted-inputs.ts).
715
744
  const hostedRefusal = refuseHostedUrl("visual_cognitive_story", { url });
716
745
  if (hostedRefusal)
717
746
  return hostedRefusal;
718
747
  const startTime = Date.now();
748
+ const { writeFileSync, mkdirSync, existsSync } = await import("fs");
719
749
  let ownsVcsBrowser = true;
720
750
  // The screenshot's own temp dir, removed in the finally below: a
721
751
  // clock-named file in the shared temp dir was read by concurrent calls.
@@ -772,6 +802,7 @@ export function registerAuditTools(server, context) {
772
802
  ? `the PNG at images.${name}`
773
803
  : "not available (the artifact store was unavailable, so the full-resolution PNG was not saved)";
774
804
  // ── 1. Attention Heatmap ──
805
+ let attentionProvenance;
775
806
  let attentionData = null;
776
807
  try {
777
808
  const { analyzeAttention } = await import("../../visual/attention-transport.js");
@@ -782,12 +813,19 @@ export function registerAuditTools(server, context) {
782
813
  // different model wearing the same name. (2026-07-29)
783
814
  const { collectDomAttentionElements } = await import("../../visual/attention-quality.js");
784
815
  const domAttentionElements = await collectDomAttentionElements(page).catch(() => []);
785
- const attnResult = await analyzeAttention(ssPath, persona, 4, undefined, domAttentionElements, goal);
816
+ const a11y = await personaAccessibilityTraits(persona);
817
+ // Recorded apart from the score (attentionProvenance): the attention
818
+ // model reads fitted gaze calibration and published colour science,
819
+ // and counted in modelProvenance they made the effort score look
820
+ // partly fitted while the map does not feed it.
821
+ const { result: attnResult, provenance } = await isolatedProvenanceAsync(() => analyzeAttention(ssPath, persona, 4, undefined, domAttentionElements, goal, undefined, undefined, a11y), ATTENTION_PROVENANCE_TEXT);
822
+ attentionProvenance = provenance;
786
823
  attentionData = {
787
824
  entropy: attnResult.entropy,
788
825
  concentration: attnResult.concentration,
789
826
  hotspots: attnResult.saliencyMap?.hotspots || [],
790
827
  saliencyMap: attnResult.saliencyMap,
828
+ perceptualProfile: attnResult.perceptualProfile,
791
829
  };
792
830
  if (attnResult.saliencyMap) {
793
831
  const { generateHeatmapOverlay } = await import("../../visual/heatmap-overlay.js");
@@ -943,6 +981,14 @@ export function registerAuditTools(server, context) {
943
981
  }
944
982
  // ── 4. Cognitive Effort Summary ──
945
983
  let effortData = null;
984
+ // Unrounded, for the narrative: a band edge must not move with display rounding.
985
+ let storyEffort = null;
986
+ // The ids the bridge read for this persona: personaRateProvenance below.
987
+ let storyPersonaRateIds = new Set();
988
+ // useValues reaches the chain here exactly as in cognitive_effort: the
989
+ // persona's Schwartz scores, through the chain's registered modulation.
990
+ // Through 21.0 the flag never reached this summary at all.
991
+ const chainValues = useValues ? await resolveChainValues(persona) : undefined;
946
992
  try {
947
993
  const { buildOTCognitiveProfile } = await import("../../visual/cognitive-transport.js");
948
994
  const { computeDemandDistribution, computeSequentialCTC } = await import("../../visual/cognitive-transport-chain.js");
@@ -950,11 +996,24 @@ export function registerAuditTools(server, context) {
950
996
  const { getAnyPersona, createCognitivePersona, resolvePersonaOrThrow } = await import("../../personas.js");
951
997
  // A miss throws instead of yielding an all-midpoint stand-in.
952
998
  const personaObj = getAnyPersona(persona) || resolvePersonaOrThrow(persona);
953
- const traits = (personaObj.cognitiveTraits || {});
999
+ // A copy, because the bridge writes into what it is given. This was the
1000
+ // registry's own object, so one story run left the persona carrying
1001
+ // three derived capacities for the rest of the process, which
1002
+ // cognitive_distance then read as stored traits (first-timer to
1003
+ // power-user moved 0.1512 -> 0.1550). (2026-10-21)
1004
+ const traits = { ...(personaObj.cognitiveTraits || {}) };
1005
+ // A first visit, as cognitive_effort and site_cognitive_assessment
1006
+ // score it when no familiarity is supplied (this tool takes none).
1007
+ // Left unset, the chain read the missing-trait capacity (0.5) for
1008
+ // siteFamiliarity, the 20.x defect site-familiarity.ts describes, and
1009
+ // this summary disagreed with cognitive_effort on the same page and
1010
+ // persona (first-timer, prose fixture: 0.669 against 0.700).
1011
+ const { resolveSiteFamiliarity } = await import("./site-familiarity.js");
1012
+ const familiarity = resolveSiteFamiliarity(undefined, false);
954
1013
  // Same bridge as Gate 3 and cognitive_effort. Without it this tool has
955
1014
  // the identical missing-capacity defect and would reproduce the motor
956
1015
  // disagreement against cognitive_effort the moment anyone compared them.
957
- bridgeCapacityTraits(persona, traits, personaObj.accessibilityTraits);
1016
+ storyPersonaRateIds = collectPersonaReads(() => bridgeCapacityTraits(persona, traits, personaObj.accessibilityTraits, personaRateContext(personaObj))).ids;
958
1017
  const otProfile = buildOTCognitiveProfile(persona, traits);
959
1018
  // viewport-by-design: visual_cognitive_story declares no `scope` parameter,
960
1019
  // so it promises the caller nothing about how much of the page it read, and
@@ -963,13 +1022,33 @@ export function registerAuditTools(server, context) {
963
1022
  // a scope WAS offered, echoed, and then not applied. If this tool ever gains
964
1023
  // a scope, this marker must go and the scope must be threaded.
965
1024
  const pageMetrics = await extractPageMetrics(page);
966
- const demand = computeDemandDistribution(pageMetrics);
967
- const ctcResult = computeSequentialCTC(otProfile, demand, { asymmetric: true, interactions: true });
1025
+ const demand = computeDemandDistribution(pageMetrics, { siteFamiliarity: familiarity.value });
1026
+ const ctcResult = computeSequentialCTC(otProfile, demand, {
1027
+ asymmetric: true, interactions: true, ...(chainValues ? { schwartzValues: chainValues } : {}),
1028
+ });
1029
+ const rank = ctcRank(ctcResult);
968
1030
  effortData = {
1031
+ // A percentile of the real reference pages (21.x); `rank` says it in words.
969
1032
  totalCTC: Math.round(ctcResult.totalCTC * 1000) / 1000,
1033
+ // Its 95% interval across bootstrap resamples of the reference pages.
1034
+ totalCTCInterval: ctcResult.totalCTCInterval.map((x) => Math.round(x * 1000) / 1000),
1035
+ band: rank.band,
1036
+ referenceScope: ctcResult.referenceScope,
1037
+ rank: rank.sentence,
1038
+ raw: Math.round(ctcResult.rawCTC * 1000) / 1000,
970
1039
  bottleneck: ctcResult.bottleneckLayer,
971
1040
  abandonmentRisk: Math.round(ctcResult.abandonmentRisk * 100),
972
1041
  layers: ctcResult.layers.map(l => ({ name: l.name, cost: Math.round(l.transportCost * 1000) / 1000 })),
1042
+ siteFamiliarity: familiarity.value,
1043
+ siteFamiliaritySource: familiarity.source,
1044
+ };
1045
+ storyEffort = {
1046
+ totalCTC: ctcResult.totalCTC,
1047
+ totalCTCInterval: ctcResult.totalCTCInterval,
1048
+ rawCTC: ctcResult.rawCTC,
1049
+ referenceScope: ctcResult.referenceScope,
1050
+ bottleneck: ctcResult.bottleneckLayer,
1051
+ abandonmentRiskPercent: Math.round(ctcResult.abandonmentRisk * 100),
973
1052
  };
974
1053
  }
975
1054
  catch (e) {
@@ -1025,9 +1104,6 @@ export function registerAuditTools(server, context) {
1025
1104
  // ── Build narrative ──
1026
1105
  const concentration = attentionData?.concentration ?? 0;
1027
1106
  const entropy = attentionData?.entropy ?? 0;
1028
- const ctc = effortData?.totalCTC ?? 0;
1029
- const bottleneck = effortData?.bottleneck ?? "unknown";
1030
- const risk = effortData?.abandonmentRisk ?? 0;
1031
1107
  const qualityData = urls.qualityData;
1032
1108
  const _nu = new URL(url);
1033
1109
  let narrative = `## ${persona} on ${_nu.hostname.replace(/^www\d*\./, '')}${_nu.pathname !== '/' ? _nu.pathname.replace(/\/$/, '') : ''}\n\n`;
@@ -1054,11 +1130,10 @@ export function registerAuditTools(server, context) {
1054
1130
  else {
1055
1131
  narrative += `Attention is spread evenly (concentration: ${pct}%) without a dominant anchor, though the page is not visually noisy. Nothing is fighting for attention; nothing is claiming it either.\n\n`;
1056
1132
  }
1057
- narrative += `### The Effort: How Hard It Is\n`;
1058
- narrative += `Total cognitive transport cost: ${ctc.toFixed(2)}. `;
1059
- narrative += ctc < 0.5 ? `This page is comfortable for ${persona}. ` : ctc < 1.0 ? `Moderate effort required. ` : `Significant cognitive burden. `;
1060
- narrative += `The bottleneck is **${bottleneck}** — this is where the design creates the most friction.\n`;
1061
- narrative += `Abandonment risk: **${risk}%**.\n\n`;
1133
+ // The rank, from the registered bands (story-narrative.ts). This read
1134
+ // totalCTC on the logistic: "comfortable" below 0.5, "Significant
1135
+ // cognitive burden" from 1.0, and 0 (comfortable) when effort failed.
1136
+ narrative += storyEffortSection(persona, storyEffort);
1062
1137
  narrative += `### The Question: Do They See What Matters?\n`;
1063
1138
  if (qualityData) {
1064
1139
  const cta = qualityData.ctaCaptureRate ?? 0;
@@ -1075,31 +1150,12 @@ export function registerAuditTools(server, context) {
1075
1150
  narrative += `Attention quality data unavailable.\n`;
1076
1151
  }
1077
1152
  narrative += `\n### The Verdict\n`;
1078
- // The verdict branched on ctc and ctaCaptureRate ONLY, ignoring the
1079
- // distractor ratio and quality score printed directly above it. So a
1080
- // page scoring 31/100 with 70% of attention on non-actionable elements
1081
- // was told "**Strong.** Easy to use AND attention lands on CTAs" — the
1082
- // verdict contradicting its own evidence one line later. A summary that
1083
- // reads less of the data than the section above it is worse than no
1084
- // summary, because it is the line people quote. (2026-07-29)
1085
- const distractRatio = qualityData?.distractorRatio ?? 0;
1086
- const qScore = qualityData?.qualityScore ?? 0;
1087
- const leaking = distractRatio > 0.4 || (qualityData !== undefined && qScore < 50);
1088
- if (ctc < 0.5 && (qualityData?.ctaCaptureRate ?? 0) > 0.2 && !leaking) {
1089
- narrative += `**Strong.** Easy to use AND attention lands on CTAs. This page works for ${persona}.`;
1090
- }
1091
- else if (ctc < 0.5 && (qualityData?.ctaCaptureRate ?? 0) > 0.2 && leaking) {
1092
- narrative += `**Mixed.** Low cognitive effort and the CTAs do get seen, but attention quality is ${qScore}/100 with ${(distractRatio * 100).toFixed(0)}% going to non-actionable elements. ${persona} can use this page; the design is spending most of their attention on things that do not convert.`;
1093
- }
1094
- else if (ctc < 0.5 && (qualityData?.ctaCaptureRate ?? 0) < 0.1) {
1095
- narrative += `**Easy but unfocused.** Low cognitive effort, but attention misses the CTAs. The page needs stronger visual hierarchy to guide ${persona} toward conversion.`;
1096
- }
1097
- else if (ctc > 1.0 && (qualityData?.ctaCaptureRate ?? 0) > 0.2) {
1098
- narrative += `**Hard but focused.** High cognitive effort, but when ${persona} pushes through, they see the CTAs. Reduce complexity to lower the abandonment risk without losing the visual focus.`;
1099
- }
1100
- else {
1101
- narrative += `**Needs work.** High effort AND low CTA visibility. ${persona} is both struggling to process the page AND not seeing the conversion path. Prioritize: simplify the ${bottleneck} layer, then make CTAs more prominent.`;
1102
- }
1153
+ // Reads the distractor ratio and quality score as well as the CTA rate:
1154
+ // a verdict that reads less of the data than the section above it is
1155
+ // the line people quote and the one that contradicts it (2026-07-29).
1156
+ // Cost is the registered band; every branch is reachable and tested
1157
+ // (story-narrative.ts, tests/story-verdict.test.ts).
1158
+ narrative += storyVerdict(persona, storyEffort, qualityData).text;
1103
1159
  // ── Assemble response ──
1104
1160
  // Third layer, opt-in. Runs AFTER the deterministic layers and is
1105
1161
  // reported beside them rather than folded in: layers 1 and 2 are
@@ -1133,9 +1189,13 @@ export function registerAuditTools(server, context) {
1133
1189
  ...(aiAttention ? { aiAttention } : {}),
1134
1190
  images: urls,
1135
1191
  effort: effortData,
1192
+ // Only when useValues asked for it, as in cognitive_effort.
1193
+ ...(useValues ? { valueModulation: valueModulationReport(chainValues) } : {}),
1136
1194
  attention: {
1137
1195
  entropy: Math.round(entropy * 1000) / 1000,
1138
1196
  concentration: Math.round(concentration * 1000) / 1000,
1197
+ // Which perceptual filter drew the map, as attention_analysis reports it.
1198
+ perceptualProfile: attentionData?.perceptualProfile ?? null,
1139
1199
  },
1140
1200
  quality: qualityData ? {
1141
1201
  ctaCaptureRate: qualityData.ctaCaptureRate,
@@ -1143,6 +1203,14 @@ export function registerAuditTools(server, context) {
1143
1203
  score: qualityData.qualityScore,
1144
1204
  } : null,
1145
1205
  duration: `${Date.now() - startTime}ms`,
1206
+ // Every registered constant the effort score read, the same block
1207
+ // cognitive_effort reports. Taken here, after every step that reads one;
1208
+ // fitting the previews below reads none.
1209
+ modelProvenance: activeProvenance(),
1210
+ // The scored persona's motor and reading rates, counted apart from the score's constants.
1211
+ ...(storyEffort ? { personaRateProvenance: personaRateProvenance(storyPersonaRateIds) } : {}),
1212
+ // The attention analysis's own block, kept out of modelProvenance.
1213
+ ...(attentionProvenance ? { attentionProvenance } : {}),
1146
1214
  };
1147
1215
  // Every overlay rides along as a preview, the budget split between them
1148
1216
  // so text plus all images stays under the cap; imagePreviews says what
@@ -1165,7 +1233,7 @@ export function registerAuditTools(server, context) {
1165
1233
  }],
1166
1234
  };
1167
1235
  }
1168
- });
1236
+ }));
1169
1237
  }
1170
1238
  /**
1171
1239
  * The canonical empathy_audit. Exported on its own so the stdio server can