<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title></title>
    <description>Infrequent musings</description>
    <link>plo.ug/</link>
    <atom:link href="plo.ug/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Thu, 26 Jun 2025 14:55:00 +0000</pubDate>
    <lastBuildDate>Thu, 26 Jun 2025 14:55:00 +0000</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Experimenting with LLMs for semantic testing</title>
        <description>&lt;p&gt;A common problem when building modern websites with searches, content recommendations and personalisation is that standard unit and integration testing can fairly easily show if the site is working, functionally, but its much harder to show if these complex content structures return content as expected by the user, as that require an understanding of what the user is expecting and a semantic understanding of the content they get back.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Semantic testing is a test design technique that focuses on evaluating the meaning and logical correctness of inputs and outputs based on the intended behavior of the system. It aims to detect errors related to misunderstandings of requirements, domain rules, or data interpretations rather than just syntactic mistakes.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;some-examples&quot;&gt;Some examples:&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;If I search for “black dress shoes” does my store return fancy shoes, or black dresses?&lt;/li&gt;
  &lt;li&gt;If the site does content recommendations, does these work well with the primary content and do they match what a given user is expecting?&lt;/li&gt;
  &lt;li&gt;Does the content in our articles and product pages follow the specified tone of voice&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Using standard testing tools, you can get an indication, but would require html parsing, knowledge of the html element structure etc, to get a red/green judgement, hence we have historically used QA teams of humans to catch these kind of issues.&lt;/p&gt;

&lt;p&gt;Instead we can introduce an LLM into our testing setup, as I’m deploying my website in a container, it’s a low barrier to just use Jest, &lt;a href=&quot;https://testcontainers.com/&quot;&gt;Testcontainers&lt;/a&gt; and &lt;a href=&quot;https://docs.docker.com/ai/model-runner/&quot;&gt;Docker Model Runner&lt;/a&gt; which both run on my mac, and in CI/CD (with gpu available)&lt;/p&gt;

&lt;p&gt;All snippets in this post are shortened down for brewity, but can be found in &lt;a href=&quot;https://github.com/perploug/LLMs-for-semantic-testing&quot;&gt;this github repo&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;setup&quot;&gt;Setup&lt;/h2&gt;
&lt;p&gt;Get a local model running, and ensure it follows a specific output schema, for jest to be able to assert success/fail on.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker model pull ai/qwen3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and then access it using the normal openai js library:&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ai_client&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;OpenAI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;baseURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;http://localhost:12434/engines/v1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;apiKey&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;NOTNEEDED&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ai_client&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;chat&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;completions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;model&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;ai/qwen3&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;messages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;temperature&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Importantly, for all test responses I instructured the model to always respond in json using
a simple format that allows me to grade quality and get a reason for failing tests, this is
appended to all test prompts:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  Only respond in json, with the following format: 
  {&quot;rating&quot;: &quot;good|medium|bad|&quot;, &quot;reason&quot;: string }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For the testcontainers part, we simply reuse the existing compose file and get the hostname
and port of the site&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// load Umbraco into testcontainers&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;DockerComposeEnvironment&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;`/path`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;`docker-compose.yml`&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;umbraco&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getContainer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;umbracodocker-1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;siteurl&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;umbraco&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getHost&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;umbraco&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getFirstMappedPort&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;order-of-blog-articles&quot;&gt;Order of blog articles&lt;/h2&gt;
&lt;p&gt;Determine if all the articles linked on the page have fitting headlines and they are sorted in a way that makes logical sense either by date or alphabetically.&lt;/p&gt;

&lt;p&gt;For this I created a small agent (narrator: this is not an agent)&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Blog posts should be listed with punchy headlines and sorted by date&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;baseURL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`/blog`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;persona&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`You are an umbraco community member, with a developer
                    background, highly interested in community events
                    and eager to find out more about umbraco
                    `&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;persona_agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;persona&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// fetches the url, prompts the LLM with the person and the intent, and has a html selector&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// to narrow down the html to examine (to reduce context side and hallucinations)&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// in this case the &amp;lt;article&amp;gt; element&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;validateContentWithIntent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;`I am browsing this page to find news about umbraco and its community
    I am only interested in click-baity and exciting news, if I see anything
    boring I will rage and quit the site - I expect blog posts to have interesting
    headlines and be sorted by date with the latest posts first`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;article&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;good&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What I personally find lovely here, is the sheer readability of test, I can see
what is happening, what the test is supposed to do, just by reading the prompt.&lt;/p&gt;

&lt;p&gt;Jest runs, spins up Umbraco, performs the test and throws away the environment:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  console.log
      &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      rating: &lt;span class=&quot;s1&quot;&gt;&apos;good&apos;&lt;/span&gt;,
      reason: &lt;span class=&quot;s2&quot;&gt;&quot;Posts have engaging headlines, sorted by date with latest first, aligning with the user&apos;s interest in community news.&quot;&lt;/span&gt;
      &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  PASS  src/tests/umbraco.content.quality.test.ts &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;31.092 s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  Content Quality
      ✓ umbraco should run &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;2 ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
      ✓ Blog posts should be listed with punchy headlines and sorted by &lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;24018 ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

  Test Suites: 1 passed, 1 total
  Tests:       2 passed, 2 total
  Snapshots:   0 total
  Time:        31.147 s, estimated 36 s
  Ran all &lt;span class=&quot;nb&quot;&gt;test &lt;/span&gt;suites.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;tone-of-voice&quot;&gt;Tone of voice&lt;/h3&gt;
&lt;p&gt;Okey, lets try something else, namely determining whether content on a page matches
the corporate tone of voice guidelines. To do this, we will provide the LLM with a styleguide from &lt;a href=&quot;&quot;&gt;mailchimp&lt;/a&gt; and then asking the test to validate html on a given url.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;it&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Tone of voice should be followed&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;baseURL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`/blog/join-the-umbraco-community-on-mastodon/`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;toneOfVoice_agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;styleGuide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;validateUrl&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nx&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;rating&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reason&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;toBe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;medium&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This also passes, but if I add the requirement in the  styleguide that content should
always be written in danish, the test breaks:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  Language
      Mailchimp only publishes content in the danish language, 
      this is a critical requirement
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Jest failure:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
 FAIL  src/tests/umbraco.content.quality.test.ts &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;25.976 s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    Content Quality
        ✕ Tone of voice should be followed &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;19516 ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

    ● Content Quality › Tone of voice should be followed
        Content is &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;English instead of Danish, which violates the critical language requirement. While the tone is clear and informative, it lacks the subtle humor and warmth specified &lt;span class=&quot;k&quot;&gt;in &lt;/span&gt;the styleguide.

        Expected: &lt;span class=&quot;s2&quot;&gt;&quot;medium&quot;&lt;/span&gt;
        Received: &lt;span class=&quot;s2&quot;&gt;&quot;bad&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This took a couple of prompt tweaks, some times it started returning the reasoning in danish, some times it just ignored it, so
the quality of your test very much comes down to your prompt engineering ability.&lt;/p&gt;

&lt;h2 id=&quot;search-results&quot;&gt;Search Results&lt;/h2&gt;
&lt;p&gt;Next, lets try to validate the search results we get back, against a repository of matching and not-matching content,
For your specific use case, you can add in common edge cases, misspellings etc, the Umbraco out-of-the-box search isvery basic, so we will keep it basic as well:&lt;/p&gt;

&lt;p&gt;For this we need the LLM for 2 things, one to generate content on flying cats, and one on flying cars, I want to ensure 
our search page correctly ranks pages, for both “flying”, “cars” and “flying cars”&lt;/p&gt;

&lt;p&gt;I’ve created an editor agent, which I can give a writer persona and then instructions on what to write, reusing the styleguide 
alongside the expected output schema format that I want the content in.&lt;/p&gt;

&lt;div class=&quot;language-typescript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;editor_agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;editor_persona&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;styleGuide&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;baseURL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;authenticate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;a&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createContent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;Articles about the latest and greatest flying car and how it will change the world, title should be punchy and clickbaity, subtitle should be 100 words or less and repeat the main topic to optimise for search results, description should be 300 words and full of cool, innovative made up words, only use clear text without any formatting&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;`{&quot;title&quot;: string, &quot;subtitle&quot;: string &quot;description&quot;: string}`&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;p&quot;&gt;...&lt;/span&gt; 

  &lt;span class=&quot;kd&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;baseURL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;`/search/?q=flying-car`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;persona_agent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;persona&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;validateContentWithIntent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;`I am searching for pages on &apos;flying cars&apos; and expect to only see atleast 3 search results about flying cars.
    Given the user query and the list of search results (title + snippet), assess the relevance and rank appropriateness of each result.
    `&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;#search&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Validation is a bit crude here, and the prompts could likely be built out further, or give the LLM more context
for simplicity I used the same persona/intent validation, but search results validation could benefit from more specific
input on the expected behavior of the system.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
 FAIL  src/tests/umbraco.content.quality.test.ts &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;17.41 s&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    Content Quality
        ✕ Search returns correctly ranked results &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;11229 ms&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

    ● Content Quality › Search returns correctly ranked results

        Custom message:
        Only 1 result found, and it&lt;span class=&quot;s1&quot;&gt;&apos;s about a flying car, not flying cats.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a very basic setup, to validate the ideas, but I believe this could be a useful tool, with a number of other test scenarios:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Clarity of CTAs&lt;/li&gt;
  &lt;li&gt;Bias checks&lt;/li&gt;
  &lt;li&gt;Validate metadata with page content, title etc&lt;/li&gt;
  &lt;li&gt;Compare content variants or translations&lt;/li&gt;
  &lt;li&gt;Determine helpfullness of error messages/labels/etc&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Again, source code is &lt;a href=&quot;https://github.com/perploug/LLMs-for-semantic-testing&quot;&gt;here&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 26 Jun 2025 13:00:00 +0000</pubDate>
        <link>plo.ug/llms,/typescript,/testing/2025/06/26/LLMs-for-testing.html</link>
        <guid isPermaLink="true">plo.ug/llms,/typescript,/testing/2025/06/26/LLMs-for-testing.html</guid>
        
        
        <category>LLMs,</category>
        
        <category>typescript,</category>
        
        <category>testing</category>
        
      </item>
    
      <item>
        <title>Powerdynamics and urban biking</title>
        <description>&lt;p&gt;I see that a lot of my fellow white dudes in tech enjoy biking - so let’s use that analogy to talk about diversity, power dynamics and 
understanding why marginalized groups are wary of us as a group.&lt;/p&gt;

&lt;p&gt;As a biker in Berlin I am constantly wary of the cars around me - they take up the majority of space on the road, they can hurt me easily 
and are completely oblivious to that fact.&lt;/p&gt;

&lt;p&gt;I’ve had many terrible experiences with cars, not respecting my space, driving in the bike lane, not stopping, yelling at me - even after hitting me…
And most of them think they are amazing drivers - so amazing that they drive while looking at a phone, speeding for fun, honk their horn for fun etc&lt;/p&gt;

&lt;p&gt;For bikers - car owners fun is a threat and that they are oblivious to their power just scares us even further
So I put all cars in the same bucket - I generally fear them - yes, not all cars are assholes - but the few are a hazard to me - so I keep my distance.&lt;/p&gt;

&lt;p&gt;But here’s the thing: my relative power as a biker in traffic is the direct opposite of my relative power in my work life, being a white dude tech leader&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;I have all the power and privilege, compared to my teams consisting of 13 nationalities from mostly outside Europe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comparing the 2 gives me perspective on how other people can see me in my professional context- through my gender, ethnicity and background I 
am potentially the office version of a truck at full speed without visibility. 
So per my relative power - I need to be aware of the damage I can cause, I need to be extra mindful, and need to give room rather than speed ahead- 
and understand why some are wary of me.
Especially the last part, don’t expect people to be friendly or “close” with you - the relative power difference is a risk, and it is safer to 
keep a distance - as a privileged individual - don’t take it personal, and also, be aware that your attempts at being friendly can be 
perceived as something entirely different, and its your responsibility as the most powerful party to be mindful of when that line is crossed.&lt;/p&gt;

&lt;p&gt;So fellow white dude, you are a truck in traffic, give room to the bikers, be mindful of your actions and accept that it’s on you to ensure 
there’s room for everyone.&lt;/p&gt;

&lt;p&gt;Being the most privileged and fortunate is also an obligation to support other people.&lt;/p&gt;
</description>
        <pubDate>Wed, 15 Sep 2021 13:00:00 +0000</pubDate>
        <link>plo.ug/diversity/2021/09/15/Bikes-and-powerdynamics.html</link>
        <guid isPermaLink="true">plo.ug/diversity/2021/09/15/Bikes-and-powerdynamics.html</guid>
        
        
        <category>diversity</category>
        
      </item>
    
      <item>
        <title>Closing thoughts on Umbraco</title>
        <description>&lt;p&gt;Last week it finally became public knowledge that the Swedish equity fund Monterro is buying a majority stake in Umbraco, for me it marks an end of a 10 year journey with Umbraco, which I left 3 years ago, but didn’t really process before the sale negotiations started earlier this year.&lt;/p&gt;

&lt;p&gt;On a rainy, grey day, back in 2007 I travelled to a dull office building outside Nyborg, the former site of the Great Belt Bridge building company. I was there to discuss with Niels on how we could make Umbraco a “real and sustainable business”, we spend hours in front of whiteboards and in spreadsheets, trying to figure out how many courses, add-ons and support contracts we would need to sell, to make 2 fulltime wages. We even dared to dream big and estimated that with a distributed team of 5-7 people, we would have an amazing operational setup. We threw around statements such as “stay small and nimble”, “let’s rewrite the rules”, “bootstrapped”. As I left the office late in the evening we had a plan.&lt;/p&gt;

&lt;p&gt;10 years later, Umbraco and I had outgrown each other and I decided to pursue other things. With a staff of ~40, a physical office, a partner program and a full fledged cloud offering, nothing had of course gone according to our initial naive plan, it became something else and greater and it was truly an exceptional time for me.&lt;/p&gt;

&lt;p&gt;This was not a VC-fueled, 80 hour workweek, soul destroying growth hell. It is an organically grown business with solid principles and heart and soul. It was a rare opportunity to build a bootstrapped company, being involved in strategy, engineering, community building and product. 
Staying true to the dream of finding an open source business model, which respected the needs of the community, the paying customers and the business itself. Meaning delivering an amazing content management system to the community for free and at the same time monetizing it through additional value targeted at commercial and enterprise customers with an ecosystem and cloud offerings, without diluting the free offering - it took many attempts to get it balanced just right.&lt;/p&gt;

&lt;p&gt;I think we were able to pull it off, and with the announcement last week, Umbraco is continuing in that direction, but now with a substantial financial backing to continue scaling.&lt;/p&gt;

&lt;p&gt;While this marks the definitive end for me, with a 3 year delay, I continue to be very excited for Umbraco and what they will be able to do in the future.&lt;/p&gt;

&lt;p&gt;Thank you Niels for being such an inspiring collaborator through the years, to Kim for giving me my first real leadership learnings and to all the other product, engineering and partner people who I worked with through the years and who patiently taught me so much.&lt;/p&gt;
</description>
        <pubDate>Fri, 20 Aug 2021 13:00:00 +0000</pubDate>
        <link>plo.ug/umbraco/personal/closure/2021/08/20/Closure-on-umbraco.html</link>
        <guid isPermaLink="true">plo.ug/umbraco/personal/closure/2021/08/20/Closure-on-umbraco.html</guid>
        
        
        <category>umbraco</category>
        
        <category>personal</category>
        
        <category>closure</category>
        
      </item>
    
      <item>
        <title>Debuggning Probot with Visual Studio Code</title>
        <description>&lt;p&gt;One of the &lt;a href=&quot;https://opensource.zalando.com/&quot;&gt;Zalando Open Source Teams&lt;/a&gt; responsibilities is to ensure that our use of Github is Compliant and accesible to our engineering teams.
One way to ensure this is to build tooling and automation which supports our compliance rules.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://probot.github.io/&quot;&gt;Probot&lt;/a&gt; is an excellent framework for building Github bots, but it’s been challenging to figure our how to monitor activities in the bot and debug it with VS codes breakpoints. Out of the box Probot uses nodemon, which gives me a decent output to the terminal, but it feels like going back in time to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;console.log&lt;/code&gt; based development&lt;/p&gt;

&lt;h2 id=&quot;configuring-vscode-for-debugging&quot;&gt;Configuring VSCode for debugging&lt;/h2&gt;
&lt;p&gt;As I’m writing &lt;a href=&quot;https://github.com/perploug/4eyesbot/&quot;&gt;a new bot in typescript&lt;/a&gt; I want to have 2 things automated, compiling the Typescript to javascript, and then attaching the debugger to my project so I can add breakpoints and see what is going on when a Probot webhook is triggered.&lt;/p&gt;

&lt;p&gt;Luckily the probot &lt;a href=&quot;https://github.com/probot/create-probot-app&quot;&gt;typescript template&lt;/a&gt; comes with a good starting point, after going through the wizard I configured a new Typescript compile task in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vscode/tasks.json&lt;/code&gt; :&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tasks&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; 
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;identifier&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;typescript&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;typescript&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;tsconfig&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;tsconfig.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;problemMatcher&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;$tsc&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I then used this task with an entry in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vscode/launch.json&lt;/code&gt; config to ensure Typescript is compiled before the debugger is attached:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;span class=&quot;nl&quot;&gt;&quot;configurations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;node&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;launch&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;preLaunchTask&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;typescript&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Launch Probot&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;program&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;${workspaceFolder}/node_modules/probot/bin/probot-run.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;./lib/index.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;console&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;integratedTerminal&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;cwd&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;${workspaceRoot}/&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;outFiles&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;sourceMaps&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;env&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Notice the prelaunch task is my Typescript compiler and that I use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;probot-run.js&lt;/code&gt; from the Probot module to run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./lib/index.js&lt;/code&gt; the index.js is the output of my typescript compile, based on the include .tsconfig file.&lt;/p&gt;

&lt;h2 id=&quot;debugging-with-breakpoints&quot;&gt;Debugging with breakpoints&lt;/h2&gt;

&lt;p&gt;With the combination of a compile task and a launch setting I can now simply hit F5 and the probot will start up locally, receive webhooks from Github through smee.io and any breakpoints set in VS Code will be hit with access to variables and all the other debugging goodness.&lt;/p&gt;

</description>
        <pubDate>Fri, 28 Sep 2018 13:00:00 +0000</pubDate>
        <link>plo.ug/probot/github/dev/2018/09/28/Debugging-Probot-With-Visual-Studio-Code.html</link>
        <guid isPermaLink="true">plo.ug/probot/github/dev/2018/09/28/Debugging-Probot-With-Visual-Studio-Code.html</guid>
        
        
        <category>probot</category>
        
        <category>github</category>
        
        <category>dev</category>
        
      </item>
    
  </channel>
</rss>
