<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Marian Zeis Blog</title>
    <link>https://blog.zeis.de/</link>
    <description>Recent content on Marian Zeis Blog</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Tue, 12 May 2026 09:00:00 +0200</lastBuildDate>
    <atom:link href="https://blog.zeis.de/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>ABAP Development Automation with ARC-1 and GitHub Workflows</title>
      <link>https://blog.zeis.de/posts/2026-05-12-arc-1-abap-cicd-review/</link>
      <pubDate>Tue, 12 May 2026 09:00:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2026-05-12-arc-1-abap-cicd-review/</guid>
      <description>&lt;p&gt;Series note: This post is part of my &lt;a href=&#34;https://blog.zeis.de/tags/ai-abap-development-series/&#34;&gt;AI ABAP development series&lt;/a&gt;. In the previous posts I introduced &lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1&lt;/a&gt;, then used it from BTP, Copilot Studio, Joule Studio, and a SEGW to RAP migration.&lt;/p&gt;&#xA;&lt;p&gt;This post is about GitHub workflows as an ARC-1 development automation surface.&lt;/p&gt;&#xA;&lt;p&gt;ABAP teams already have strong SAP-side tooling: ATC, Code Inspector, ST22, ADT, and the activated source in the SAP system. GitHub has a different strength: pull requests, review comments, checks, labels, issues, and automation.&lt;/p&gt;&#xA;&lt;p&gt;The problem is that those worlds usually do not meet cleanly. &lt;a href=&#34;https://abapgit.org/&#34;&gt;abapGit&lt;/a&gt; can push ABAP source to GitHub, &lt;a href=&#34;https://abaplint.org/&#34;&gt;abaplint&lt;/a&gt; can run static checks, and Copilot or Claude can review a diff. But without access to the SAP system, the automation only sees text. It cannot run ABAP Unit, call ATC, compare against the activated object, inspect ST22, or verify what the SAP system actually knows.&lt;/p&gt;&#xA;&lt;p&gt;The underlying idea comes from the &lt;a href=&#34;https://docs.heliconialabs.com/patterns-for-using-llms-in-abap-development.pdf&#34;&gt;Patterns for using LLMs in ABAP development&lt;/a&gt; document by Lars Hvam from Heliconia Labs. The relevant pattern is section 2.7, Git / Off-Stack with read-only MCP. ABAP source lives in Git. Pull requests, CI/CD, static analysis, and human review stay in the Git workflow. The MCP server gives the LLM read-only context from the SAP system, but does not let it write directly into the system.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Heliconia Labs Git / Off-Stack with read-only MCP pattern showing Git, CI/CD, SAP, and a read-only ADT MCP server.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-12-arc-1-abap-cicd-review/patterns-git-offstack-mcp.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;So I created a small sample repository for this pattern: &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review&#34;&gt;github.com/marianfoo/arc-1-abap-cicd-review&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This is my ARC-1 implementation of the idea. GitHub is the workflow surface. abapGit connects the repository and the SAP system. ARC-1 is the SAP automation gateway that lets GitHub workflows execute SAP-side checks and gives AI reviewers read-only SAP context through MCP.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-the-sample-shows&#34;&gt;What The Sample Shows&lt;/h2&gt;&#xA;&lt;p&gt;The sample repository contains a small ABAP package &lt;code&gt;ZARC1_DEMO&lt;/code&gt; in abapGit format. The package is deliberately small. The interesting part is what can be automated around it when GitHub can reach SAP through ARC-1.&lt;/p&gt;&#xA;&lt;p&gt;It covers three development automation scenarios:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;PR checks for ABAP code: abaplint, ABAP Unit, and ATC.&lt;/li&gt;&#xA;&lt;li&gt;PR review with SAP context: Copilot and Claude can review with live SAP reads instead of only the diff.&lt;/li&gt;&#xA;&lt;li&gt;Operational follow-up: ARC-1 can check ST22 dumps on a schedule, create GitHub issues for new dumps, and trigger a deeper investigation from a label.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;The repo has six small workflow files: &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/blob/main/.github/workflows/pr.yml&#34;&gt;&lt;code&gt;pr.yml&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/blob/main/.github/workflows/sap-tests.yml&#34;&gt;&lt;code&gt;sap-tests.yml&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/blob/main/.github/workflows/copilot-review-trigger.yml&#34;&gt;&lt;code&gt;copilot-review-trigger.yml&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/blob/main/.github/workflows/claude-review-trigger.yml&#34;&gt;&lt;code&gt;claude-review-trigger.yml&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/blob/main/.github/workflows/sap-dump-triage.yml&#34;&gt;&lt;code&gt;sap-dump-triage.yml&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/blob/main/.github/workflows/sap-dump-deep-dive.yml&#34;&gt;&lt;code&gt;sap-dump-deep-dive.yml&lt;/code&gt;&lt;/a&gt;. ARC-1 is the common backend. GitHub Actions is only the automation surface.&lt;/p&gt;&#xA;&lt;h2 id=&#34;architecture-and-setup&#34;&gt;Architecture And Setup&lt;/h2&gt;&#xA;&lt;p&gt;The architecture follows the Git / Off-Stack with read-only MCP pattern:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Developer / IDE / LLM&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; local ABAP files&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; GitHub repository&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; pull requests, code reviews, CI/CD&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; abapGit&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; SAP system&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;GitHub Actions / review automation&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; ARC-1 MCP endpoint on SAP BTP&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; read-only ADT context from the SAP system&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The Git repository is the collaboration surface. It contains source files, pull requests, review comments, labels, and CI/CD checks. That is where the AI can propose changes and where a human keeps the gate.&lt;/p&gt;&#xA;&lt;p&gt;The SAP system remains the source of truth for activated ABAP source, syntax checks, dumps, package content, and system-specific context. GitHub has the PR diff, but GitHub is not the runtime.&lt;/p&gt;&#xA;&lt;p&gt;ARC-1 sits between GitHub and SAP. In my setup it runs on SAP BTP Cloud Foundry. GitHub Actions calls the ARC-1 &lt;code&gt;/mcp&lt;/code&gt; endpoint over HTTPS with an API key. ARC-1 validates the key profile and then connects to the SAP system through the BTP destination. The technical SAP user is configured in that destination, not in GitHub.&lt;/p&gt;&#xA;&lt;p&gt;GitHub runners and AI tools do not get SAP credentials. They only get the ARC-1 URL and a scoped API key. The SAP system itself is not exposed to GitHub runners.&lt;/p&gt;&#xA;&lt;p&gt;The API key used in the demo has a &lt;code&gt;viewer-sql&lt;/code&gt; profile. That means read access, SQL/table preview where needed, diagnostics, navigation, and linting. It does not expose write tools like &lt;code&gt;SAPWrite&lt;/code&gt;, &lt;code&gt;SAPActivate&lt;/code&gt;, &lt;code&gt;SAPManage&lt;/code&gt;, &lt;code&gt;SAPTransport&lt;/code&gt;, or &lt;code&gt;SAPGit&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;That last point matters. I do not want the first version of this pattern to be &amp;ldquo;AI writes ABAP from a PR comment&amp;rdquo;. The safer and more useful first version is:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AI can read the real SAP system.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AI can explain what it found.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AI can propose changes in a PR review.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Humans still decide what gets applied.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The setup has five parts.&lt;/p&gt;&#xA;&lt;p&gt;First, get the ABAP source into GitHub with abapGit. The exact setup depends on the system and repository authentication. The important point is that ABAP objects are available as normal files in GitHub, and approved changes can later be pulled back into SAP through the normal abapGit process.&lt;/p&gt;&#xA;&lt;p&gt;Second, deploy ARC-1. For this sample I use the BTP Cloud Foundry deployment because it gives GitHub Actions a public HTTPS endpoint while the SAP backend remains behind BTP connectivity. ARC-1 needs a destination to the ABAP system. The technical user is configured there and should only have the permissions needed for the checks you want to run. ARC-1 then adds the API key profile on top.&lt;/p&gt;&#xA;&lt;p&gt;Third, configure GitHub Actions secrets and variables:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ARC1_URL&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ARC1_API_KEY&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ANTHROPIC_API_KEY&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;COPILOT_TRIGGER_PAT&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;ARC1_URL&lt;/code&gt; points to the ARC-1 endpoint. &lt;code&gt;ARC1_API_KEY&lt;/code&gt; is used by the SAP test gate and the Claude workflows. &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; is needed for Claude Code Action. &lt;code&gt;COPILOT_TRIGGER_PAT&lt;/code&gt; is needed for the Copilot trigger workflow because comments posted by the default &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; do not trigger &lt;code&gt;@copilot&lt;/code&gt; mentions.&lt;/p&gt;&#xA;&lt;p&gt;Fourth, configure Copilot Coding Agent. In the repository settings, the Copilot Cloud Agent gets an MCP configuration for ARC-1. The important detail is the tool allowlist: Copilot should see the read-side tools it needs for review, not the write-side tools.&lt;/p&gt;&#xA;&lt;p&gt;Fifth, decide which workflows you actually want. For a first rollout I would start with abaplint, the SAP test gate, and one AI review path. The scheduled dump check is useful because it helps you see new runtime problems before someone reports them manually, but it is a separate operational pattern. You do not need to enable everything on day one.&lt;/p&gt;&#xA;&lt;h2 id=&#34;pr-automation-layers&#34;&gt;PR Automation Layers&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;Layer 1: abaplint&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The static layer is intentionally boring.&lt;/p&gt;&#xA;&lt;p&gt;Every pull request runs &lt;a href=&#34;https://abaplint.org/&#34;&gt;abaplint&lt;/a&gt; through &lt;a href=&#34;https://github.com/reviewdog/reviewdog&#34;&gt;reviewdog&lt;/a&gt;. Findings appear as check annotations, inline comments where GitHub allows it, and a sticky summary comment. The workflow gates only on findings introduced by the PR diff. Existing findings stay visible, but they do not block a PR that did not create them.&lt;/p&gt;&#xA;&lt;p&gt;If abaplint can catch it, abaplint should catch it. It is fast, deterministic, and basically free apart from GitHub Actions minutes.&lt;/p&gt;&#xA;&lt;p&gt;In the sample PR, abaplint found existing issues in the class and test class. That is useful, but it did not catch the main semantic issue in the PR: a direct &lt;code&gt;SELECT&lt;/code&gt; from &lt;code&gt;MARA&lt;/code&gt; added inside a task service method. The code is syntactically valid. The problem is architectural and Clean Core related.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Layer 2: ABAP Unit and ATC&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The next layer is still not AI. Here ARC-1 is simply the gateway connector from GitHub Actions into SAP.&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/blob/main/.github/workflows/sap-tests.yml&#34;&gt;&lt;code&gt;sap-tests.yml&lt;/code&gt;&lt;/a&gt; runs on every PR that touches &lt;code&gt;src/&lt;/code&gt;. It parses the abapGit filenames, maps them back to ABAP object types, and then calls ARC-1 directly from Bash. There is no model involved in this part.&lt;/p&gt;&#xA;&lt;p&gt;For changed classes it runs ABAP Unit through &lt;a href=&#34;https://marianfoo.github.io/arc-1/tools/#sapdiagnose&#34;&gt;&lt;code&gt;SAPDiagnose&lt;/code&gt;&lt;/a&gt; with &lt;code&gt;action=&amp;quot;unittest&amp;quot;&lt;/code&gt;. For changed classes, interfaces, programs, and function groups it runs ATC through &lt;a href=&#34;https://marianfoo.github.io/arc-1/tools/#sapdiagnose&#34;&gt;&lt;code&gt;SAPDiagnose&lt;/code&gt;&lt;/a&gt; with &lt;code&gt;action=&amp;quot;atc&amp;quot;&lt;/code&gt;. The result is posted as a sticky PR comment, and line-specific ATC findings are posted inline on the Files Changed tab when GitHub can attach them to the diff.&lt;/p&gt;&#xA;&lt;p&gt;The gate is simple: failing or erroring unit tests fail the job, and P1/P2 ATC findings fail the job. P3 findings are visible but do not block.&lt;/p&gt;&#xA;&lt;p&gt;The clean demo is &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/20&#34;&gt;PR #20&lt;/a&gt;. It adds one more unit test to &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/blob/main/src/zcl_arc1_test_helpers.clas.abap&#34;&gt;&lt;code&gt;ZCL_ARC1_TEST_HELPERS&lt;/code&gt;&lt;/a&gt;. The workflow &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/20#issuecomment-4432679538&#34;&gt;reports 7 of 7 ABAP Unit tests passing&lt;/a&gt; and one non-blocking P3 ATC warning.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;GitHub PR comment showing the SAP test gate with 7 of 7 ABAP Unit tests passing and one non-blocking ATC P3 finding.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-12-arc-1-abap-cicd-review/untitest-atc-report.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;There is one important assumption here. The test workflow runs against activated objects in SAP. That matches the normal abapGit loop I use: edit, activate, push. If your team pushes before activation, you need a PR sandbox system or a pre-step that pulls and activates before this workflow runs.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Layer 3: AI review without MCP&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;GitHub&amp;rsquo;s built-in Copilot Code Review can be assigned from the PR sidebar. That is already useful. It reads the diff and can catch common problems from the text alone.&lt;/p&gt;&#xA;&lt;p&gt;In the sample &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/14&#34;&gt;PR #14&lt;/a&gt;, Copilot Code Review did catch the &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/14#discussion_r3226642123&#34;&gt;direct &lt;code&gt;SELECT&lt;/code&gt; from &lt;code&gt;MARA&lt;/code&gt;&lt;/a&gt; as a Clean Core hotspot. That is a good result.&lt;/p&gt;&#xA;&lt;p&gt;But this review has an important limitation: it is still a diff-only review. It cannot know whether the object is already activated in SAP. It cannot ask the system for callers. It cannot inspect ST22. It cannot prove that a claim is backed by the current SAP system.&lt;/p&gt;&#xA;&lt;p&gt;That does not make it useless. It just means it is one layer.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Layer 4: AI review with ARC-1&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The MCP-backed review is the more interesting part.&lt;/p&gt;&#xA;&lt;p&gt;In the repo, applying the &lt;code&gt;copilot:review&lt;/code&gt; label posts a canonical &lt;code&gt;@copilot review ...&lt;/code&gt; prompt to the PR. Copilot Coding Agent then uses the ARC-1 MCP server configured in the repository settings.&lt;/p&gt;&#xA;&lt;p&gt;Applying the &lt;code&gt;claude:review&lt;/code&gt; label starts &lt;a href=&#34;https://github.com/anthropics/claude-code-action&#34;&gt;&lt;code&gt;anthropics/claude-code-action&lt;/code&gt;&lt;/a&gt; in GitHub Actions. The workflow writes an MCP config file, allows only selected ARC-1 tools, loads the prompt from &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/blob/main/.github/claude-review-prompt.md&#34;&gt;&lt;code&gt;claude-review-prompt.md&lt;/code&gt;&lt;/a&gt;, and asks Claude to post one pull request review.&lt;/p&gt;&#xA;&lt;p&gt;The prompt is deliberately concrete:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Use 2 to 5 ARC-1 tool calls.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Read the changed ABAP object from SAP.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Check for GitHub to SAP drift.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Only navigate references when the public contract changed.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Do not repeat abaplint or SAP test gate findings.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Post inline comments on changed lines.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Use GitHub suggestion blocks when the fix is concrete.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This makes the review auditable. The model still reasons, but important claims can point back to tool results.&lt;/p&gt;&#xA;&lt;p&gt;In PR #14, the decisive ARC-1 call was a &lt;a href=&#34;https://marianfoo.github.io/arc-1/tools/#sapread&#34;&gt;&lt;code&gt;SAPRead&lt;/code&gt;&lt;/a&gt; of the activated &lt;code&gt;ZCL_ARC1_TASK_SERVICE~LIST_TASKS&lt;/code&gt; method from SAP. Claude then posted the &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/14#discussion_r3228270334&#34;&gt;key finding&lt;/a&gt;: the &lt;code&gt;MARA&lt;/code&gt; block is not only a direct SAP table access, it also changes the meaning of &lt;code&gt;list_tasks&lt;/code&gt;. A task service should not silently return no tasks because the material master has no finished product.&lt;/p&gt;&#xA;&lt;p&gt;It also did the useful drift check:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;The activated ZCL_ARC1_TASK_SERVICE~LIST_TASKS in SAP matches the pre-PR source.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;The MARA block is not yet activated, as expected for an open PR.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;No unexpected drift.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is the small sentence that justifies the whole architecture. A diff-only reviewer cannot know that. ARC-1 can.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Claude review on PR #14 showing SAPRead-backed drift information and the MARA finding on the changed ABAP method.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-12-arc-1-abap-cicd-review/claude-review-pr14.png&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;demo-prs&#34;&gt;Demo PRs&lt;/h2&gt;&#xA;&lt;p&gt;The main demo is &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/14&#34;&gt;PR #14&lt;/a&gt;. It contains one harmless refactoring and one deliberately bad preflight inside &lt;code&gt;ZCL_ARC1_TASK_SERVICE~LIST_TASKS&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-abap&#34; data-lang=&#34;abap&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;select&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;single&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;matnr&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;from&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;mara&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;into&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;@&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;lv_dummy_matnr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;where&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;mtart&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;FERT&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;sy&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;subrc&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;endif&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is deliberately wrong for the demo. It creates a hard dependency from a task service to the MM material master and reads a SAP-standard table directly. If product validation is needed, it should go through a released API or CDS view, and the service should not hide the reason by returning an empty list.&lt;/p&gt;&#xA;&lt;p&gt;That PR is useful because it shows several review surfaces on the same diff:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;abaplint finds static issues, but not the &lt;code&gt;MARA&lt;/code&gt; problem.&lt;/li&gt;&#xA;&lt;li&gt;the SAP test gate adds live ABAP Unit and ATC results for changed objects.&lt;/li&gt;&#xA;&lt;li&gt;Copilot Code Review sees the diff and flags the &lt;code&gt;MARA&lt;/code&gt; access, but cannot verify SAP state.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/14#issuecomment-4430841521&#34;&gt;Copilot Coding Agent with ARC-1&lt;/a&gt; can combine diff review with live SAP reads, syntax, navigation, and release checks.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/14#discussion_r3228270334&#34;&gt;Claude with ARC-1&lt;/a&gt; posts a focused review with inline comments, suggestion blocks, and the SAP drift check.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The separate SAP test gate example is &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/20&#34;&gt;PR #20&lt;/a&gt;: one extra test method, 7 of 7 ABAP Unit tests green, and a single non-blocking P3 ATC finding.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;One-click suggestions&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Both Copilot and Claude can post GitHub review comments with suggestion blocks. That means the PR shows the same &amp;ldquo;Apply suggestion&amp;rdquo; button a human reviewer would get.&lt;/p&gt;&#xA;&lt;p&gt;This is now verified in PR #14. &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/14#discussion_r3226642123&#34;&gt;Copilot Code Review&lt;/a&gt; and &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/14#discussion_r3228270334&#34;&gt;Claude&lt;/a&gt; both suggested deleting the &lt;code&gt;MARA&lt;/code&gt; preflight block. Claude&amp;rsquo;s version adds the SAP-side evidence on top: &lt;a href=&#34;https://marianfoo.github.io/arc-1/tools/#sapread&#34;&gt;&lt;code&gt;SAPRead&lt;/code&gt;&lt;/a&gt; confirmed that this block is not part of the currently activated method, so the review can separate &amp;ldquo;new in this PR&amp;rdquo; from &amp;ldquo;already active in SAP&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;The two screenshots below are from the same lines in PR #14. Copilot shows the diff-only version of the review. Claude shows the same GitHub suggestion UX, but with the SAP-side drift evidence from ARC-1.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Claude review comment on PR #14 showing the MARA preflight deletion suggestion with SAPRead drift evidence and an Apply suggestion button.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-12-arc-1-abap-cicd-review/pr14-mara-claude.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Copilot Code Review comment on PR #14 showing the MARA preflight deletion suggestion with an Apply suggestion button.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-12-arc-1-abap-cicd-review/pr14-mara-copilot.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;The prompt asks for suggestion blocks only when the fix is concrete. A wrong suggestion block is worse than a normal comment because it invites the reviewer to apply it without thinking.&lt;/p&gt;&#xA;&lt;p&gt;This is not a replacement for a proper feature branch. It is just a good review UX for small, local fixes.&lt;/p&gt;&#xA;&lt;h2 id=&#34;operations-automation&#34;&gt;Operations Automation&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;Autonomous dump triage&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The repo also has an operations pattern for staying ahead of runtime issues.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/blob/main/.github/workflows/sap-dump-triage.yml&#34;&gt;&lt;code&gt;sap-dump-triage.yml&lt;/code&gt;&lt;/a&gt; workflow asks ARC-1 for recent ST22 dumps through &lt;a href=&#34;https://marianfoo.github.io/arc-1/tools/#sapdiagnose&#34;&gt;&lt;code&gt;SAPDiagnose&lt;/code&gt;&lt;/a&gt;. It can run on a schedule, deduplicates dumps against existing GitHub issues by storing the dump ID in an HTML comment marker, and creates one issue for each new dump with metadata, a short Claude triage, and an urgency label.&lt;/p&gt;&#xA;&lt;p&gt;For the sample repo, the cron is disabled so the issue list stays stable. In a real setup, I would turn it on so GitHub becomes a lightweight watch list for new dumps instead of waiting until someone reports the same runtime error again. The current examples are visible through the &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/issues?q=is%3Aissue+label%3Asap%3Adump&#34;&gt;&lt;code&gt;sap:dump&lt;/code&gt; label&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;This is the part I like most conceptually:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cheap shallow triage for everything&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;expensive deep investigation only when needed&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is a much better cost model than asking the LLM to deeply inspect every dump forever.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Deep dive on demand&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;The second stage is the &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/blob/main/.github/workflows/sap-dump-deep-dive.yml&#34;&gt;&lt;code&gt;sap-dump-deep-dive.yml&lt;/code&gt;&lt;/a&gt; workflow. A human applies the &lt;code&gt;dump:investigate&lt;/code&gt; label to a dump issue. The workflow extracts the dump ID, reads the full dump through ARC-1, reads the failing ABAP source, and posts one investigation comment.&lt;/p&gt;&#xA;&lt;p&gt;The best demo is &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/issues/15&#34;&gt;issue #15&lt;/a&gt;, an &lt;code&gt;OBJECTS_OBJREF_NOT_ASSIGNED_NO&lt;/code&gt; dump in &lt;code&gt;CL_ENH_ADT_ENHO_OBJ_PERSIST&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Claude found a very concrete root cause. In &lt;code&gt;GET_DATA_FROM_TOOL&lt;/code&gt;, the first &lt;code&gt;CASE l_enhtooltype&lt;/code&gt; block creates &lt;code&gt;r_object_data&lt;/code&gt;, but it handles only BAdI and hook enhancement tool types. The dump variables showed &lt;code&gt;I_ENHO_TOOL&lt;/code&gt; as &lt;code&gt;CL_ENH_TOOL_WDY&lt;/code&gt;, a Web Dynpro enhancement tool. Because that tool type is not handled, &lt;code&gt;r_object_data&lt;/code&gt; stays initial. The next dereference at line 30 raises &lt;code&gt;CX_SY_REF_IS_INITIAL&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The interesting detail is that there is a second &lt;code&gt;CASE&lt;/code&gt; block later in the same method with a &lt;code&gt;WHEN OTHERS&lt;/code&gt; guard, but execution never reaches it because the null dereference happens earlier.&lt;/p&gt;&#xA;&lt;p&gt;That is the kind of analysis I want from this setup. It used the dump, selected variables, call stack, and source code. It did not just say &amp;ldquo;probably a null reference&amp;rdquo;. It explained why the reference was null and where the missing guard belongs.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/issues/15#issuecomment-4431477985&#34;&gt;deep-dive comment&lt;/a&gt; then updates the labels from &lt;code&gt;needs-triage&lt;/code&gt; to &lt;code&gt;dump:investigated&lt;/code&gt;. So GitHub becomes the lightweight operations board, while SAP remains the source of truth.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;GitHub issue #15 showing a Claude deep-dive analysis for an SAP short dump with root cause and labels.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-12-arc-1-abap-cicd-review/issue-15-claude.png&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-i-would-use-this&#34;&gt;How I Would Use This&lt;/h2&gt;&#xA;&lt;p&gt;For a small ABAP team, I would start with this sequence:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Push ABAP source to GitHub with abapGit.&lt;/li&gt;&#xA;&lt;li&gt;Run abaplint on every PR.&lt;/li&gt;&#xA;&lt;li&gt;Run ABAP Unit and ATC through ARC-1 on every PR.&lt;/li&gt;&#xA;&lt;li&gt;Use Copilot Code Review as a cheap, generic first AI review if you already have Copilot.&lt;/li&gt;&#xA;&lt;li&gt;Add &lt;code&gt;claude:review&lt;/code&gt; or &lt;code&gt;copilot:review&lt;/code&gt; only when the PR needs deeper SAP context.&lt;/li&gt;&#xA;&lt;li&gt;Enable dump triage manually first.&lt;/li&gt;&#xA;&lt;li&gt;Turn on the scheduled dump check once the issue noise level is understood.&lt;/li&gt;&#xA;&lt;li&gt;Use &lt;code&gt;dump:investigate&lt;/code&gt; only for dumps that are recurring, high urgency, or unclear.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;This keeps the workflow understandable. Not every PR needs a deep AI review, and not every dump needs full investigation. Static and deterministic checks should do the boring work first.&lt;/p&gt;&#xA;&lt;p&gt;The label interface is intentionally simple:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;code&gt;copilot:review&lt;/code&gt; triggers Copilot Coding Agent with ARC-1.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;claude:review&lt;/code&gt; triggers Claude Code Action with ARC-1.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;sap:dump&lt;/code&gt; marks a dump tracker issue.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;needs-triage&lt;/code&gt; means the shallow workflow created the issue.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;dump:investigate&lt;/code&gt; triggers the deep-dive workflow.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;dump:investigated&lt;/code&gt; means the deep dive has already posted its analysis.&lt;/li&gt;&#xA;&lt;li&gt;&lt;code&gt;urgency:high&lt;/code&gt;, &lt;code&gt;urgency:medium&lt;/code&gt;, and &lt;code&gt;urgency:low&lt;/code&gt; make the issue list sortable.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The labels are the UI. That keeps the pattern easy to teach and easy to extend.&lt;/p&gt;&#xA;&lt;h2 id=&#34;boundaries-and-next-steps&#34;&gt;Boundaries And Next Steps&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;Limitations&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Copilot Code Review and Copilot Coding Agent are not the same thing. The sidebar review is useful, but it does not use the MCP server. For MCP context, this repo uses label-triggered workflows.&lt;/p&gt;&#xA;&lt;p&gt;GitHub-hosted runners must reach the ARC-1 endpoint. That is why BTP Cloud Foundry is a good demo topology. If ARC-1 is only available inside a private network, use self-hosted runners or another controlled network path.&lt;/p&gt;&#xA;&lt;p&gt;The SAP user behind the BTP destination still matters. Even with a read-only ARC-1 key, the workflow can read what that technical user can read. In a real setup, scope both the SAP user and the ARC-1 API key deliberately.&lt;/p&gt;&#xA;&lt;p&gt;The SAP test gate runs against activated objects in SAP. That fits the edit, activate, push flow. If your team pushes before activation, use a PR sandbox system or add a controlled pull and activate step before the checks run.&lt;/p&gt;&#xA;&lt;p&gt;And AI review is still review. The goal is not to replace an ABAP developer. The goal is to catch useful things earlier, with enough evidence that a developer can decide quickly.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;What else this pattern can do&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;p&gt;Once the plumbing exists, the same pattern can be used for more than PR review.&lt;/p&gt;&#xA;&lt;p&gt;A scheduled Clean Core audit could read a package through ARC-1, check released API usage, and open GitHub issues. A multi-system setup could expose &lt;code&gt;arc-1-dev&lt;/code&gt;, &lt;code&gt;arc-1-qa&lt;/code&gt;, and &lt;code&gt;arc-1-prod&lt;/code&gt; as separate MCP servers and compare what is active in each system.&lt;/p&gt;&#xA;&lt;p&gt;The SAP test gate can also grow beyond this small demo. Full ATC variants, Code Inspector checks, transport checks, and package-level baselines can all surface in the same PR interface developers already use.&lt;/p&gt;&#xA;&lt;p&gt;The next obvious step is the post-merge path. After a PR is merged to &lt;code&gt;main&lt;/code&gt;, a GitHub workflow could call ARC-1 with a separate git-scoped key and run an abapGit pull through &lt;a href=&#34;https://marianfoo.github.io/arc-1/tools/#sapgit&#34;&gt;&lt;code&gt;SAPGit&lt;/code&gt;&lt;/a&gt; for the package in the SAP system.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SAP -&amp;gt; abapGit -&amp;gt; GitHub -&amp;gt; AI review -&amp;gt; merge -&amp;gt; ARC-1 pull -&amp;gt; SAP&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I would keep that write path separate from the review key, gated by branch, labels, or environment approvals. If the pull has conflicts or activation problems, the workflow should report that back and stop.&lt;/p&gt;&#xA;&lt;p&gt;That is also where the operations loop becomes interesting: detect a dump, investigate it, propose a fix PR, review it, merge it, and pull it back into SAP. The client can change. The governed SAP access layer stays the same.&lt;/p&gt;&#xA;&lt;h2 id=&#34;takeaway&#34;&gt;Takeaway&lt;/h2&gt;&#xA;&lt;p&gt;This is the direction I find practical for ABAP development automation.&lt;/p&gt;&#xA;&lt;p&gt;Do not start with &amp;ldquo;AI should write all ABAP&amp;rdquo;. Start with a safer and more useful question:&lt;/p&gt;&#xA;&lt;p&gt;Can the development workflow become better if GitHub automation can safely use the actual SAP system?&lt;/p&gt;&#xA;&lt;p&gt;For me, the answer is clearly yes. Not because the model magically understands ABAP, but because ARC-1 brings missing SAP context into the workflow: activated source, ABAP Unit, ATC, references, dumps, syntax checks, and system-specific evidence.&lt;/p&gt;&#xA;&lt;p&gt;abapGit gets the code into GitHub. abaplint covers the static layer. ARC-1 runs ABAP Unit and ATC from GitHub Actions, and gives AI a read-only window into SAP. Copilot and Claude become more useful reviewers because they can stop guessing and start checking.&lt;/p&gt;&#xA;&lt;p&gt;That is the main thread of this sample: GitHub is the automation surface, ARC-1 is the SAP gateway, and humans still decide what gets merged.&lt;/p&gt;&#xA;&lt;h2 id=&#34;references-and-links&#34;&gt;References And Links&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review&#34;&gt;Sample repository: arc-1-abap-cicd-review&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/14&#34;&gt;Demo PR #14: multi-engine review with suggestion blocks&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/pull/20&#34;&gt;Demo PR #20: SAP Unit and ATC gate&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/issues/15&#34;&gt;Dump issue #15: deep-dive investigation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1-abap-cicd-review/issues?q=is%3Aissue+label%3Asap%3Adump&#34;&gt;Dump tracker issues&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1 on GitHub&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/&#34;&gt;ARC-1 Documentation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.heliconialabs.com/patterns-for-using-llms-in-abap-development.pdf&#34;&gt;Patterns for using LLMs in ABAP development&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://abapgit.org/&#34;&gt;abapGit&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://abaplint.org/&#34;&gt;abaplint&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/reviewdog/reviewdog&#34;&gt;reviewdog&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/anthropics/claude-code-action&#34;&gt;Claude Code Action&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://docs.github.com/en/copilot/concepts/agents/coding-agent/about-coding-agent&#34;&gt;GitHub Copilot cloud agent&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://modelcontextprotocol.io/&#34;&gt;Model Context Protocol&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.zeis.de/posts/2026-05-08-arc-1-joule-studio-clean-core/&#34;&gt;Joule Studio and ARC-1 Clean Core post&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>From SEGW and Legacy UI5 to RAP with ARC-1</title>
      <link>https://blog.zeis.de/posts/2026-05-11-segw-to-rap/</link>
      <pubDate>Mon, 11 May 2026 09:00:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2026-05-11-segw-to-rap/</guid>
      <description>&lt;p&gt;Series note: This post is part of my &lt;a href=&#34;https://blog.zeis.de/tags/ai-abap-development-series/&#34;&gt;AI ABAP development series&lt;/a&gt;, where I go from AI development in general, to ABAP-specific problems, and then to ARC-1.&lt;/p&gt;&#xA;&lt;p&gt;In the last posts I introduced &lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1&lt;/a&gt;, explained why I built it, showed the BTP architecture, and then used it from Copilot Studio and Joule Studio. Those posts were mostly about architecture and possible use cases.&lt;/p&gt;&#xA;&lt;p&gt;This one is different. This is the use case ARC-1 was originally built for: making real SAP development easier.&lt;/p&gt;&#xA;&lt;p&gt;A lot of SAP customers are still somewhere between old ECC-style custom development and the S/4HANA world they actually want to reach. The uncomfortable part is that &amp;ldquo;moving to S/4&amp;rdquo; can easily become a technical lift and shift. The old custom code moves, the old OData services move, the old UI patterns move, and after the migration the landscape is still not really modern. It just runs somewhere newer.&lt;/p&gt;&#xA;&lt;p&gt;That is not the direction I find interesting. If we already touch those applications, then we should also ask where it makes sense to move them toward RAP, OData V4, Fiori elements, cleaner APIs, and a development model that fits the Clean Core direction much better.&lt;/p&gt;&#xA;&lt;p&gt;So I built a small but complete demo for that:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;a legacy SEGW OData V2 service&lt;/li&gt;&#xA;&lt;li&gt;a legacy freestyle UI5 JavaScript app&lt;/li&gt;&#xA;&lt;li&gt;a new RAP OData V4 service&lt;/li&gt;&#xA;&lt;li&gt;a modern UI5 TypeScript app&lt;/li&gt;&#xA;&lt;li&gt;a Fiori elements app on top of the RAP service&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The full workspace, generated code, ABAP sources, screenshots, and chat logs are published here:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap&#34;&gt;github.com/marianfoo/arc-1-segw-to-rap&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-starting-point&#34;&gt;The Starting Point&lt;/h2&gt;&#xA;&lt;p&gt;The demo starts with a small project management application on an S/4HANA 2023 trial system, ABAP Platform 7.58. The legacy backend sits in package &lt;code&gt;ZDEMO_MIG&lt;/code&gt; and uses three custom tables: &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/zdm_project.tabl.xml&#34;&gt;&lt;code&gt;ZDM_PROJECT&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/zdm_task.tabl.xml&#34;&gt;&lt;code&gt;ZDM_TASK&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/zdm_timeentry.tabl.xml&#34;&gt;&lt;code&gt;ZDM_TIMEENTRY&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;On top of that sits the SEGW service &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/zdemo_mig_projects_srv_0001.iwsg.xml&#34;&gt;&lt;code&gt;ZDEMO_MIG_PROJECTS_SRV&lt;/code&gt;&lt;/a&gt;. It exposes &lt;code&gt;ProjectSet&lt;/code&gt;, &lt;code&gt;TaskSet&lt;/code&gt;, and &lt;code&gt;TimeEntrySet&lt;/code&gt;, with navigation from projects to tasks and from tasks to time entries. The one bit of behavior is the &lt;code&gt;ApproveProject&lt;/code&gt; function import, which changes the project status.&lt;/p&gt;&#xA;&lt;p&gt;This is the old service in SAP Gateway Service Builder. You can see the classic SEGW shape directly: entity types, properties, navigation associations, entity sets, and the function import sitting next to the data model.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;SAP Gateway Service Builder showing the legacy SEGW project service with Project properties, entity sets, associations, and the ApproveProject function import.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-11-segw-to-rap/images/segw-screenshot.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;The old UI is a classic master/detail app. The left side shows projects, the right side shows the selected project with tasks and time entries. It works, but it also contains the kind of patterns many SAP teams know too well: manual OData V2 model creation, global formatters, synchronous bootstrap, path string parsing, &lt;code&gt;jQuery.sap.require&lt;/code&gt;, manual function import calls, and UI state that is partly stitched together in controller code.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Legacy UI5 freestyle app showing the old project master/detail application.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-11-segw-to-rap/images/ui5-legacy-app-screenshot.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;The backend is similar. The generated &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/zcl_zdemo_mig_projects_mpc.clas.abap&#34;&gt;MPC class&lt;/a&gt; describes the model, and the &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/zcl_zdemo_mig_projects_dpc_ext.clas.abap&#34;&gt;DPC_EXT class&lt;/a&gt; contains the custom logic. For the demo I made the legacy code deliberately rough: manual reads, ignored filters in some places, no authority checks, manual navigation handling, and an &lt;code&gt;ApproveProject&lt;/code&gt; function import that updates the project status directly.&lt;/p&gt;&#xA;&lt;p&gt;That is a good migration target because the interesting question is not &amp;ldquo;can an AI write a RAP sample from scratch?&amp;rdquo;. The more useful question is:&lt;/p&gt;&#xA;&lt;p&gt;Can it inspect the old service, understand the contract, and create a modern replacement that keeps the functional shape?&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-skills&#34;&gt;The Skills&lt;/h2&gt;&#xA;&lt;p&gt;For this run I did not just write one big prompt. I used three ARC-1 skills from the maintained &lt;a href=&#34;https://github.com/marianfoo/arc-1/tree/main/skills&#34;&gt;&lt;code&gt;arc-1/skills&lt;/code&gt;&lt;/a&gt; folder: &lt;a href=&#34;https://github.com/marianfoo/arc-1/tree/main/skills/migrate-segw-to-rap&#34;&gt;&lt;code&gt;migrate-segw-to-rap&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/marianfoo/arc-1/tree/main/skills/modernize-ui5-app&#34;&gt;&lt;code&gt;modernize-ui5-app&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&#34;https://github.com/marianfoo/arc-1/tree/main/skills/convert-ui5-to-fiori-elements&#34;&gt;&lt;code&gt;convert-ui5-to-fiori-elements&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The folder is worth looking at beyond this demo. It also contains skills for RAP service generation, ABAP test generation, code explanation, custom-code migration, Clean Core checks, documentation, and system-context setup. The idea is that ARC-1 does not only provide tools, but repeatable workflows around those tools.&lt;/p&gt;&#xA;&lt;p&gt;The point of these skills is not that they are perfect generic migration products. They are templates for a process.&lt;/p&gt;&#xA;&lt;p&gt;That distinction matters. The real value is that the skill forces the agent into a structured workflow:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Read the instructions first.&lt;/li&gt;&#xA;&lt;li&gt;Inspect the old system and source code before designing anything.&lt;/li&gt;&#xA;&lt;li&gt;Print the extracted model and plan.&lt;/li&gt;&#xA;&lt;li&gt;Stop for confirmation before writes.&lt;/li&gt;&#xA;&lt;li&gt;Create the new artifacts.&lt;/li&gt;&#xA;&lt;li&gt;Activate, publish, lint, typecheck, and smoke test.&lt;/li&gt;&#xA;&lt;li&gt;Use the feedback from those gates before calling the run done.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;This is much closer to how I want agentic SAP development to work. Not &amp;ldquo;please convert my system&amp;rdquo; and hope for the best, but a guided workflow with discovery, constraints, acceptance gates, and real SAP system access.&lt;/p&gt;&#xA;&lt;p&gt;For the run, I used Cursor with Composer-2. I did that intentionally. This was not meant to be a best-case frontier-model demo. It should be closer to what people might actually try in an IDE with a good but not magical model.&lt;/p&gt;&#xA;&lt;p&gt;That is also why the skills matter so much. A detailed skill moves part of the intelligence out of the model and into the workflow: what to read first, when to stop, which tools to use, which checks must pass, and what the target shape should look like. The model still has to reason, but it does not have to invent the whole process from scratch.&lt;/p&gt;&#xA;&lt;p&gt;The MCP setup was split by responsibility. &lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1&lt;/a&gt; handled the SAP system side: object reads, writes, activation, transports, and service binding work. &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;SAP Docs MCP Server&lt;/a&gt; gave RAP and CDS documentation context. &lt;a href=&#34;https://github.com/UI5/mcp-server&#34;&gt;UI5 MCP Server&lt;/a&gt; and &lt;a href=&#34;https://github.com/SAP/open-ux-tools/tree/main/packages/fiori-mcp-server&#34;&gt;SAP Fiori MCP Server&lt;/a&gt; were used for the frontend conversion and validation steps.&lt;/p&gt;&#xA;&lt;p&gt;The net execution time for the three guided runs was below 20 minutes. That does not include the time I spent before the final run building the demo, improving the skills, and learning from earlier failed attempts. But for the final guided process itself, this was comfortably under 20 minutes.&lt;/p&gt;&#xA;&lt;h2 id=&#34;prompt-1-segw-to-rap&#34;&gt;Prompt 1: SEGW to RAP&lt;/h2&gt;&#xA;&lt;p&gt;The first prompt asked the agent to run the &lt;code&gt;migrate-segw-to-rap&lt;/code&gt; skill against &lt;code&gt;ZDEMO_MIG_PROJECTS_SRV&lt;/code&gt;. The full transcript is in &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/tree/main/llm-chat-history/prompt-1-segw-to-rap&#34;&gt;&lt;code&gt;llm-chat-history/prompt-1-segw-to-rap&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Cursor prompt for the SEGW to RAP run showing ARC-1 reading the legacy service.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-11-segw-to-rap/images/starting-prompt-1-segw-to-rap.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;The important part is what happened before the agent wrote anything. ARC-1 first probed the system and confirmed the real constraints: ABAP 7.58, RAP availability, transport access, search access, and write access. The agent then tried the obvious generated MPC naming, discovered that this SEGW service used the generated &lt;code&gt;ZCL_ZDEMO_MIG_PROJECTS_*&lt;/code&gt; class names, and read the &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/zcl_zdemo_mig_projects_mpc.clas.abap&#34;&gt;MPC class&lt;/a&gt; as the source of the OData model. After that it read the &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/zcl_zdemo_mig_projects_dpc_ext.clas.abap&#34;&gt;DPC_EXT class&lt;/a&gt;, because the interesting behavior was not in the metadata. It was in the custom code, especially the &lt;code&gt;ApproveProject&lt;/code&gt; function import.&lt;/p&gt;&#xA;&lt;p&gt;Only after that discovery step did it print the extracted model and stop for &lt;code&gt;ok&lt;/code&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Project&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  key ProjectId&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  navigation Tasks&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Task&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  key TaskId&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  foreign key ProjectId&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  navigation TimeEntries&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;TimeEntry&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  key EntryId&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  foreign keys TaskId, ProjectId&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Function import&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  ApproveProject(ProjectId) -&amp;gt; Project&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After confirmation, ARC-1 created the &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/tree/main/ABAP_SRC/src/dm&#34;&gt;RAP stack under &lt;code&gt;ABAP_SRC/src/dm&lt;/code&gt;&lt;/a&gt;. This is where the migration becomes interesting on the ABAP side.&lt;/p&gt;&#xA;&lt;p&gt;The root interface view &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zi_dm_project.ddls.asddls&#34;&gt;&lt;code&gt;ZI_DM_PROJECT&lt;/code&gt;&lt;/a&gt; selects from &lt;code&gt;ZDM_PROJECT&lt;/code&gt; and models tasks as a RAP composition child. The same pattern continues with &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zi_dm_task.ddls.asddls&#34;&gt;&lt;code&gt;ZI_DM_TASK&lt;/code&gt;&lt;/a&gt; and &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zi_dm_timeentry.ddls.asddls&#34;&gt;&lt;code&gt;ZI_DM_TIMEENTRY&lt;/code&gt;&lt;/a&gt;. So the old OData navigation model becomes a RAP business object structure instead of staying as manual navigation handling in DPC_EXT.&lt;/p&gt;&#xA;&lt;p&gt;The interface behavior definition &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zi_dm_project.bdef.asbdef&#34;&gt;&lt;code&gt;ZI_DM_PROJECT&lt;/code&gt;&lt;/a&gt; is managed, uses &lt;code&gt;strict ( 2 )&lt;/code&gt;, and enables draft. It maps the nicer RAP field names like &lt;code&gt;ProjectId&lt;/code&gt;, &lt;code&gt;StartDate&lt;/code&gt;, and &lt;code&gt;EstimatedHours&lt;/code&gt; back to the old table fields like &lt;code&gt;project_id&lt;/code&gt;, &lt;code&gt;start_date&lt;/code&gt;, and &lt;code&gt;estimated_hours&lt;/code&gt;. It also defines the project as the lock master and the child entities as dependent by project. The draft tables &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/rap/zdm_project_d.tabl.xml&#34;&gt;&lt;code&gt;ZDM_PROJECT_D&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/rap/zdm_task_d.tabl.xml&#34;&gt;&lt;code&gt;ZDM_TASK_D&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/rap/zdm_timeentry_d.tabl.xml&#34;&gt;&lt;code&gt;ZDM_TIMEENTRY_D&lt;/code&gt;&lt;/a&gt; are part of that generated target shape.&lt;/p&gt;&#xA;&lt;p&gt;The projection layer then exposes the service-facing model. &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zc_dm_project.ddls.asddls&#34;&gt;&lt;code&gt;ZC_DM_PROJECT&lt;/code&gt;&lt;/a&gt; is a transactional query projection on the interface view, redirects &lt;code&gt;_Tasks&lt;/code&gt; to &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zc_dm_task.ddls.asddls&#34;&gt;&lt;code&gt;ZC_DM_TASK&lt;/code&gt;&lt;/a&gt;, and carries metadata extension support. The projection behavior &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zc_dm_project.bdef.asbdef&#34;&gt;&lt;code&gt;ZC_DM_PROJECT&lt;/code&gt;&lt;/a&gt; reuses create, update, delete, draft actions, and the new approve action from the interface behavior.&lt;/p&gt;&#xA;&lt;p&gt;Finally, the service definition &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zui_dm_projects.srvd.srvdsrv&#34;&gt;&lt;code&gt;ZUI_DM_PROJECTS&lt;/code&gt;&lt;/a&gt; exposes the projection entities as &lt;code&gt;Project&lt;/code&gt;, &lt;code&gt;Task&lt;/code&gt;, and &lt;code&gt;TimeEntry&lt;/code&gt;, and the service binding &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zui_dm_projects_o4.srvb.xml&#34;&gt;&lt;code&gt;ZUI_DM_PROJECTS_O4&lt;/code&gt;&lt;/a&gt; publishes them as OData V4.&lt;/p&gt;&#xA;&lt;p&gt;The new service is OData V4 and published through the service binding:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/sap/opu/odata4/sap/zui_dm_projects_o4/srvd/sap/zui_dm_projects_o4/0001/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The old &lt;code&gt;ApproveProject&lt;/code&gt; function import became a RAP action:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-abap&#34; data-lang=&#34;abap&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;action&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;approve_project&lt;/span&gt; &lt;span class=&#34;nv&#34;&gt;result&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;err&#34;&gt;$&lt;/span&gt;&lt;span class=&#34;nv&#34;&gt;self&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zbp_dm_project.clas.locals_imp.abap&#34;&gt;behavior implementation&lt;/a&gt; no longer does a direct &lt;code&gt;UPDATE ... COMMIT WORK&lt;/code&gt; in the old DPC_EXT style. It uses RAP EML in local mode, updates the project status, and lets the RAP runtime handle the transactional context.&lt;/p&gt;&#xA;&lt;p&gt;The logs also show why this is not just text generation. The agent had to deal with real ABAP Platform 7.58 details: where the provider contract belongs, how draft table fields need to be named, which timestamp annotations are available, and how the projection behavior should reuse the interface behavior. These are exactly the details that make a generated RAP example look easy and a real migration annoying.&lt;/p&gt;&#xA;&lt;p&gt;That is the first Clean Core angle of the demo. It does not magically make every old custom object clean. But it moves the service shape away from a SEGW/DPC_EXT implementation and toward a RAP business object, CDS projections, behavior definitions, OData V4, draft support, and metadata-driven UI consumption.&lt;/p&gt;&#xA;&lt;p&gt;For an ECC to S/4HANA journey, this is the kind of direction I would rather see than only lifting old custom services into the new system.&lt;/p&gt;&#xA;&lt;h2 id=&#34;prompt-2-ui5-javascript-to-ui5-typescript&#34;&gt;Prompt 2: UI5 JavaScript to UI5 TypeScript&lt;/h2&gt;&#xA;&lt;p&gt;The second prompt converted the &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/tree/main/legacy-ui5-app&#34;&gt;legacy freestyle UI5 JavaScript app&lt;/a&gt; into a &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/tree/main/modern-ui5-ts-app&#34;&gt;modern UI5 TypeScript app&lt;/a&gt;. The transcript is in &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/tree/main/llm-chat-history/prompt-2-js-to-ts&#34;&gt;&lt;code&gt;llm-chat-history/prompt-2-js-to-ts&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Cursor prompt for converting the legacy UI5 JavaScript app into UI5 TypeScript.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-11-segw-to-rap/images/starting-prompt-2-ui5-ts.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;Here the agent did not just rewrite files. The skill forced it to understand the old component setup, manifest, controllers, XML views, formatter logic, model setup, bootstrap, package scripts, and UI5 tooling config before writing the new app.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/UI5/mcp-server&#34;&gt;UI5 MCP Server&lt;/a&gt; was the guardrail around that work. Before writing code, the agent used it for TypeScript conversion guidance and general UI5 guidelines. That shaped the target app: a real TypeScript scaffold, &lt;code&gt;@sapui5/types&lt;/code&gt;, typed controller events, async loading, manifest-driven models, and no old &lt;code&gt;jQuery.sap.*&lt;/code&gt; patterns. It also created the modern UI5 app scaffold and later ran the UI5 linter and manifest validation. Those checks are important because plain TypeScript compilation does not understand UI5-specific conventions, XML event-handler rules, or manifest schema details.&lt;/p&gt;&#xA;&lt;p&gt;The UI result is mostly relevant here because it proves that the RAP service was usable from a real consumer. The old app talked to &lt;code&gt;/ProjectSet&lt;/code&gt;, followed &lt;code&gt;Tasks&lt;/code&gt;, and called &lt;code&gt;ApproveProject&lt;/code&gt; as an OData V2 function import. The new freestyle app talks to the RAP V4 entity set &lt;code&gt;/Project&lt;/code&gt;, follows &lt;code&gt;_Tasks&lt;/code&gt;, and calls &lt;code&gt;approve_project&lt;/code&gt; as a bound OData V4 operation. The app itself also moved from JavaScript controllers and a manual OData V2 model to TypeScript controllers, manifest-driven OData V4, FlexibleColumnLayout routing, imported formatter modules, and real lint/typecheck gates.&lt;/p&gt;&#xA;&lt;p&gt;The new app consumes the RAP V4 service directly from its &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/modern-ui5-ts-app/webapp/manifest.json&#34;&gt;manifest&lt;/a&gt;:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;dataSources&amp;#34;&lt;/span&gt;&lt;span class=&#34;err&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;mainService&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;uri&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;/sap/opu/odata4/sap/zui_dm_projects_o4/srvd/sap/zui_dm_projects_o4/0001/&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;OData&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;odataVersion&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;4.0&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The action call in the &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/modern-ui5-ts-app/webapp/controller/Detail.controller.ts&#34;&gt;detail controller&lt;/a&gt; also changed from the old OData V2 function import style to a bound OData V4 operation:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-ts&#34; data-lang=&#34;ts&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;operation&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;model&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bindContext&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;s2&#34;&gt;&amp;#34;com.sap.gateway.srvd.zui_dm_projects.v0001.approve_project()&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;ctx&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;k&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;operation&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;invoke&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s2&#34;&gt;&amp;#34;$auto&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The UI is still recognizable, but the app is now using a modern runtime and tooling setup.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Modern UI5 TypeScript app consuming the new RAP OData V4 service.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-11-segw-to-rap/images/ui5-modern-ts-app-screenshot.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;This part is important because many real SAP teams cannot immediately replace every freestyle UI5 app with Fiori elements. Sometimes a freestyle app is still the right target. But even then, there is a big difference between old UI5 JavaScript glued to a SEGW V2 service and a modern TypeScript app consuming a RAP V4 service.&lt;/p&gt;&#xA;&lt;p&gt;The run ended with the important gates green: ESLint, UI5 linter, manifest validation, TypeScript typecheck, and a browser render check.&lt;/p&gt;&#xA;&lt;p&gt;That is the second lesson from the logs: the model needed tooling feedback. It was not enough to let it generate code once. The loop with UI5 MCP, linting, manifest validation, and browser verification made the result useful.&lt;/p&gt;&#xA;&lt;h2 id=&#34;prompt-3-legacy-ui5-to-fiori-elements&#34;&gt;Prompt 3: Legacy UI5 to Fiori Elements&lt;/h2&gt;&#xA;&lt;p&gt;The third prompt took a different path. Instead of converting the old freestyle app to another freestyle app, it rebuilt the user-facing contract as a &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/tree/main/modern-fe-app/dm-projects-fe&#34;&gt;Fiori elements V4 list report and object page&lt;/a&gt;. The transcript is in &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/tree/main/llm-chat-history/prompt-3-js-to-fiori-elements&#34;&gt;&lt;code&gt;llm-chat-history/prompt-3-js-to-fiori-elements&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Cursor prompt for converting the legacy UI5 app into a Fiori elements V4 app.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-11-segw-to-rap/images/starting-prompt-3-fiori-elements.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;This is the more interesting Clean Core direction for many S/4HANA projects. A lot of old UI5 code exists because the backend metadata did not carry enough UI intent, or because the old stack made custom frontend code feel like the default. With RAP and Fiori elements, more of the application can move into CDS annotations and behavior.&lt;/p&gt;&#xA;&lt;p&gt;The agent mined the legacy UI for the actual user contract: the columns from the old master list, search behavior, project header fields from the detail view, task table columns, time entry navigation from a selected task, &lt;code&gt;Approve&lt;/code&gt; as a project action, and the status, priority, and date semantics that used to live in the formatter.&lt;/p&gt;&#xA;&lt;p&gt;Then it moved that contract into RAP UI metadata, mainly through the &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zme_dm_project.ddlx.asddlxs&#34;&gt;&lt;code&gt;ZME_DM_PROJECT&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zme_dm_task.ddlx.asddlxs&#34;&gt;&lt;code&gt;ZME_DM_TASK&lt;/code&gt;&lt;/a&gt;, and &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/ABAP_SRC/src/dm/zme_dm_timeentry.ddlx.asddlxs&#34;&gt;&lt;code&gt;ZME_DM_TIMEENTRY&lt;/code&gt;&lt;/a&gt; metadata extensions. That is where the old UI intent becomes &lt;code&gt;@UI.headerInfo&lt;/code&gt;, line items, selection fields, facets, field groups, presentation variants, search metadata, and semantic keys.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/SAP/open-ux-tools/tree/main/packages/fiori-mcp-server&#34;&gt;SAP Fiori MCP Server&lt;/a&gt; had a different role than the UI5 MCP Server. It did not translate freestyle controller code into another set of controllers. It exposed the Fiori tools workflow for an external OData V4 list report and object page: discover the available generation capabilities, inspect the required generator configuration, and shape the app around a project name, target folder, UI5 version, service URL or metadata file, and main entity &lt;code&gt;Project&lt;/code&gt;. In other words, it kept the frontend target inside the standard Fiori elements model instead of letting the conversion drift back into custom page wiring.&lt;/p&gt;&#xA;&lt;p&gt;That also makes the split of responsibility clearer. ARC-1 and SAP Docs MCP helped create and correct the RAP annotations in the ABAP backend. Fiori MCP helped define and generate the Fiori elements application that consumes those annotations through &lt;code&gt;$metadata&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The result is a Fiori elements list report on the RAP &lt;code&gt;Project&lt;/code&gt; entity. The generated app manifest is also in the repo, under &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/blob/main/modern-fe-app/dm-projects-fe/webapp/manifest.json&#34;&gt;&lt;code&gt;modern-fe-app/dm-projects-fe/webapp/manifest.json&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Fiori elements list report generated from the RAP OData V4 service.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-11-segw-to-rap/images/fe-screenshot-1.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;And the object page uses the RAP annotations and navigation model, including general data, audit data, and a tasks table.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Fiori elements object page generated from the RAP OData V4 service.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-11-segw-to-rap/images/fe-screenshot-2.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;This is the third lesson from the logs: Fiori elements migration is not mainly a JavaScript conversion. It is a relocation of intent.&lt;/p&gt;&#xA;&lt;p&gt;Old freestyle UI5 had the intent spread across controllers, XML views, formatters, routing, and manual binding code. The Fiori elements version moves much of that intent into the RAP service metadata. That is not always the right answer for every app, but when it fits, it is exactly the direction I would prefer for S/4HANA custom apps.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-the-logs-show&#34;&gt;What The Logs Show&lt;/h2&gt;&#xA;&lt;p&gt;I included the &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/tree/main/llm-chat-history&#34;&gt;Cursor chat exports&lt;/a&gt; in the repository because I think the transcript is more useful than a polished final result alone.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/tree/main/llm-chat-history/prompt-1-segw-to-rap&#34;&gt;SEGW to RAP run&lt;/a&gt; has 140 tool records. That is the most important transcript for this post because it shows ARC-1 reading the legacy service, extracting the model from the generated classes, creating the RAP artifacts, activating them, and publishing the service binding. The &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/tree/main/llm-chat-history/prompt-2-js-to-ts&#34;&gt;UI5 TypeScript run&lt;/a&gt; has 142 tool records and is mostly about making the new V4 service usable from a freestyle app. The &lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap/tree/main/llm-chat-history/prompt-3-js-to-fiori-elements&#34;&gt;Fiori elements run&lt;/a&gt; has 230 tool records because it had to mine the old UI and move more intent into annotations.&lt;/p&gt;&#xA;&lt;p&gt;There are a few patterns that stand out.&lt;/p&gt;&#xA;&lt;p&gt;First, the model needed real SAP context. It did not know the generated class names until ARC-1 searched the system. It did not know the exact service binding shape until ARC-1 read it. It did not know the old &lt;code&gt;ApproveProject&lt;/code&gt; behavior until it inspected the DPC_EXT source.&lt;/p&gt;&#xA;&lt;p&gt;Second, the skills mattered. Without the skills, the model would probably start writing too early. With the skills, it had to read, extract, summarize, stop, then write.&lt;/p&gt;&#xA;&lt;p&gt;Third, the MCP servers had different jobs. &lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1&lt;/a&gt; was the SAP system bridge. &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;SAP Docs MCP Server&lt;/a&gt; gave release and documentation context. &lt;a href=&#34;https://github.com/UI5/mcp-server&#34;&gt;UI5 MCP Server&lt;/a&gt; and &lt;a href=&#34;https://github.com/SAP/open-ux-tools/tree/main/packages/fiori-mcp-server&#34;&gt;SAP Fiori MCP Server&lt;/a&gt; grounded the frontend work in current UI5 and Fiori conventions.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-this-is-more-than-a-conversion-demo&#34;&gt;Why This Is More Than A Conversion Demo&lt;/h2&gt;&#xA;&lt;p&gt;The obvious reading of this demo is &amp;ldquo;AI converted old code to new code&amp;rdquo;. That is true, but it is not the main point for me.&lt;/p&gt;&#xA;&lt;p&gt;There are now companies like &lt;a href=&#34;https://www.novaintelligence.com/&#34;&gt;Nova Intelligence&lt;/a&gt; and &lt;a href=&#34;https://www.conduct.ai/&#34;&gt;Conduct&lt;/a&gt; working on SAP custom-code understanding and modernization. I think that is a good signal for the space. My angle here is different: how far can you get with open-source tools, your own AI setup, and MCP servers whose code you can actually inspect?&lt;/p&gt;&#xA;&lt;p&gt;This kind of setup is not as polished as a full commercial SAP modernization platform. But the tradeoff is interesting. ARC-1 is open source, the SAP Docs MCP server is open source, and the UI5 and Fiori MCP servers are open-source SAP tooling. You can see what the tools do, adapt the skills, run the workflow against your own system, and understand why the agent made a decision. For many teams, that transparency matters as much as the raw automation.&lt;/p&gt;&#xA;&lt;p&gt;The more important point is that this is the kind of workflow SAP teams need when they want to move away from lift and shift.&lt;/p&gt;&#xA;&lt;p&gt;In a pure lift-and-shift migration, the old application survives almost unchanged:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SEGW service stays SEGW&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;DPC_EXT logic stays DPC_EXT logic&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;UI5 JavaScript stays UI5 JavaScript&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;OData V2 stays OData V2&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;custom UI logic stays custom UI logic&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That may be necessary in some places. But if you do that everywhere, the new S/4HANA system inherits most of the old custom development model.&lt;/p&gt;&#xA;&lt;p&gt;In this demo, the target shape is different:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SEGW contract -&amp;gt; RAP business object&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;OData V2 -&amp;gt; OData V4&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;DPC_EXT function import -&amp;gt; RAP action&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;manual UI wiring -&amp;gt; typed UI5 or Fiori elements metadata&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;frontend-only intent -&amp;gt; CDS annotations where possible&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is much closer to the Clean Core direction. Not because custom code disappears. It does not. But because the custom code is expressed in newer, more structured extension technologies that fit S/4HANA better.&lt;/p&gt;&#xA;&lt;p&gt;For real projects I would still be careful. A large productive SEGW service can contain much more complex behavior than this demo. It may call BAPIs, use conversion exits, have custom authorization logic, support deep inserts, or depend on UI assumptions that are not visible in the service metadata. You do not solve that with one prompt.&lt;/p&gt;&#xA;&lt;p&gt;But that is also why ARC-1 matters. It gives the agent a way to inspect the real objects, not only files copied into a prompt. And with good skills, the agent can be forced to build a migration plan before touching the system.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-i-would-take-from-this&#34;&gt;What I Would Take From This&lt;/h2&gt;&#xA;&lt;p&gt;For me the practical takeaway is:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;ARC-1 is strongest when the task needs real ABAP system context.&lt;/li&gt;&#xA;&lt;li&gt;Skills are the right abstraction for repeatable SAP modernization workflows.&lt;/li&gt;&#xA;&lt;li&gt;MCP servers should be combined by responsibility, not treated as one magic tool.&lt;/li&gt;&#xA;&lt;li&gt;RAP and Fiori elements are not just &amp;ldquo;newer tech&amp;rdquo;, they give the AI a more structured target model.&lt;/li&gt;&#xA;&lt;li&gt;Clean Core migration is not only about deleting custom code. It is also about moving useful custom functionality into cleaner extension patterns.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;The demo is intentionally small, but the pattern is the part I care about. If an agent can read an old SEGW service, understand a legacy UI5 app, create a RAP V4 service, modernize the freestyle UI, and generate a Fiori elements app in one guided run, then this is a useful direction for real SAP modernization work.&lt;/p&gt;&#xA;&lt;p&gt;Not as an autopilot that blindly rewrites landscapes, but as a controlled development workflow with real SAP context, clear boundaries, and human review.&lt;/p&gt;&#xA;&lt;p&gt;That is exactly where I want ARC-1 to go.&lt;/p&gt;&#xA;&lt;h2 id=&#34;references--links&#34;&gt;References &amp;amp; Links&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1-segw-to-rap&#34;&gt;Demo repository: arc-1-segw-to-rap&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1 on GitHub&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/&#34;&gt;ARC-1 Documentation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1/tree/main/skills&#34;&gt;ARC-1 Skills&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;SAP Docs MCP Server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://pages.community.sap.com/topics/abap/rap&#34;&gt;ABAP RESTful Application Programming Model&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://ui5.github.io/typescript/&#34;&gt;UI5 TypeScript&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/UI5/mcp-server&#34;&gt;UI5 MCP Server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/SAP/open-ux-tools/tree/main/packages/fiori-mcp-server&#34;&gt;SAP Fiori MCP Server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/SAP-samples/abap-platform-fiori-feature-showcase&#34;&gt;ABAP Platform Fiori Feature Showcase&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.novaintelligence.com/&#34;&gt;Nova Intelligence&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.conduct.ai/&#34;&gt;Conduct&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;discuss-this-post&#34;&gt;Discuss this post&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.linkedin.com/posts/marianzeis_i-converted-a-legacy-segw-odata-service-to-activity-7459822169758326786-LagF&#34;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://saptodon.org/@Mianbsp/116558132953167166&#34;&gt;Saptodon&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bsky.app/profile/marian.zeis.de/post/3mlmapv4vu22d&#34;&gt;Bluesky&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>ARC-1 with Joule Studio: Bringing Real ABAP System Context into Joule</title>
      <link>https://blog.zeis.de/posts/2026-05-08-arc-1-joule-studio-clean-core/</link>
      <pubDate>Wed, 06 May 2026 09:00:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2026-05-08-arc-1-joule-studio-clean-core/</guid>
      <description>&lt;p&gt;Series note: This post is part of my &lt;a href=&#34;https://blog.zeis.de/tags/ai-abap-development-series/&#34;&gt;AI ABAP development series&lt;/a&gt;, where I go from AI development in general, to ABAP-specific problems, and then to ARC-1.&lt;/p&gt;&#xA;&lt;p&gt;In the &lt;a href=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/&#34;&gt;previous post&lt;/a&gt;, I showed &lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1&lt;/a&gt; with Microsoft Copilot Studio. The idea was that SAP system context should not be locked inside a local IDE. If ARC-1 runs centrally on SAP BTP, it can also be used from Teams, Microsoft 365 Copilot, SharePoint, Jira, and other places where functional consultants, architects, support teams, and project leads already work.&lt;/p&gt;&#xA;&lt;p&gt;This post is the SAP-native version of that idea. If Microsoft Copilot Studio is close to the Microsoft workday, then &lt;a href=&#34;https://help.sap.com/docs/JOULE/3fdd7b321eb24d1b9d40605dce822e84&#34;&gt;Joule&lt;/a&gt; is close to the SAP workday. SAP describes Joule as the unified assistant experience across SAP&amp;rsquo;s solution portfolio, and &lt;a href=&#34;https://help.sap.com/docs/Joule_Studio/45f9d2b8914b4f0ba731570ff9a85313/4444cd1ce4cd471bbe127ea2e4735b40.html&#34;&gt;Joule Studio&lt;/a&gt; as the place to build custom Joule agents with SAP and non-SAP integrations.&lt;/p&gt;&#xA;&lt;p&gt;So the question is not only: can Joule call another MCP server? The better question is: can the ARC-1 capabilities be brought to the place where SAP users already ask questions?&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-carries-over-from-earlier-posts&#34;&gt;What Carries Over From Earlier Posts&lt;/h2&gt;&#xA;&lt;p&gt;ARC-1 is a secure ADT MCP server for ABAP systems. It connects AI clients to SAP systems through &lt;a href=&#34;https://help.sap.com/docs/btp/sap-business-technology-platform/abap-development-user-guides&#34;&gt;ABAP Development Tools&lt;/a&gt; APIs and exposes this through tools like &lt;a href=&#34;https://marianfoo.github.io/arc-1/tools/&#34;&gt;SAPRead, SAPSearch, SAPWrite, SAPActivate, SAPContext, SAPDiagnose, SAPTransport, and SAPManage&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;For this post, the important part is not the full tool list. It is the operating model from the earlier BTP post:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;central server instead of every user laptop&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;safe by default instead of allow all by default&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;server ceiling + user permission + SAP authorization&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;audit logging instead of invisible local tool calls&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the &lt;a href=&#34;https://blog.zeis.de/posts/2026-04-29-arc-1-btp/&#34;&gt;BTP post&lt;/a&gt;, I described how ARC-1 can run as a central Cloud Foundry application with &lt;a href=&#34;https://marianfoo.github.io/arc-1/xsuaa-setup/&#34;&gt;XSUAA&lt;/a&gt;, &lt;a href=&#34;https://marianfoo.github.io/arc-1/btp-destination-setup/&#34;&gt;BTP destinations&lt;/a&gt;, &lt;a href=&#34;https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/cloud-connector&#34;&gt;Cloud Connector&lt;/a&gt;, &lt;a href=&#34;https://marianfoo.github.io/arc-1/principal-propagation-setup/&#34;&gt;Principal Propagation&lt;/a&gt;, roles, and BTP audit logging. For Joule Studio, I would not create a second SAP access architecture. I would reuse that controlled endpoint.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-arc-1-adds-to-joule&#34;&gt;What ARC-1 Adds To Joule&lt;/h2&gt;&#xA;&lt;p&gt;Joule already has useful SAP context. SAP has &lt;a href=&#34;https://help.sap.com/docs/btp/btp-developers-guide/use-joule-for-developers-generative-ai-in-abap-cloud&#34;&gt;Joule for Developers&lt;/a&gt;, &lt;a href=&#34;https://help.sap.com/docs/abap-ai/generative-ai-in-abap-cloud/joule-for-developers-abap-ai-capabilities-5f175c94900741d6af3dbb23ce37e81a&#34;&gt;Joule for Developers, ABAP AI capabilities&lt;/a&gt;, and &lt;a href=&#34;https://help.sap.com/docs/joule/serviceguide/sap-consulting-capability-for-joule&#34;&gt;Joule for Consultants&lt;/a&gt;. So this is not about pretending Joule cannot help with ABAP at all.&lt;/p&gt;&#xA;&lt;p&gt;But there is a difference between a useful standard assistant and a tool that can query my actual ABAP system through ADT, read my custom code, inspect dependencies, run diagnostics, check release state, read ATC findings, and create a system-specific proposal.&lt;/p&gt;&#xA;&lt;p&gt;That workflow is not something I get just by opening standard Joule. If SAP does not expose this level of ABAP system access in Joule today, a custom MCP server can still bring it there. With ARC-1, Joule is no longer limited to general SAP knowledge or predefined product capabilities. It can ask the real ABAP system.&lt;/p&gt;&#xA;&lt;p&gt;Not because the model became smarter. Because the tool access became better.&lt;/p&gt;&#xA;&lt;p&gt;That matters beyond pure development. The same ADT-backed context can help architects with Clean Core planning, functional consultants with impact analysis, support teams with dumps and bug analysis, quality leads with ATC reporting, and developers with implementation proposals.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-architecture-i-would-show&#34;&gt;The Architecture I Would Show&lt;/h2&gt;&#xA;&lt;p&gt;SAP&amp;rsquo;s Architecture Center describes &lt;a href=&#34;https://architecture.learning.sap.com/docs/ref-arch/ca1d2a3e/1&#34;&gt;A2A and MCP for interoperability&lt;/a&gt;. The important part for this post is the direction: Joule as the user-facing assistant, A2A for agent-to-agent collaboration, and MCP for tool access. SAP also describes an &lt;a href=&#34;https://architecture.learning.sap.com/docs/ref-arch/ca1d2a3e/1#mcp-gateway-in-integration-suite&#34;&gt;MCP Gateway in SAP Integration Suite&lt;/a&gt; as a governed way to expose SAP and non-SAP APIs, integrations, data sources, and external MCP servers as tools for AI agents.&lt;/p&gt;&#xA;&lt;p&gt;ARC-1 fits that pattern as the ADT development-tooling adapter:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;User in Joule&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; Joule Studio Agent&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; SAP Integration Suite API Management / future MCP Gateway&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; ARC-1 on SAP BTP Cloud Foundry&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; Destination Service + Connectivity Service&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; SAP Cloud Connector&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; SAP S/4HANA or ABAP system&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; ADT development APIs&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ARC-1 can fit the architecture pattern SAP describes. Joule is the user-facing AI entry point, Integration Suite is the governed access layer, ARC-1 is the ADT adapter, and the SAP system stays protected behind BTP connectivity and SAP authorizations.&lt;/p&gt;&#xA;&lt;p&gt;There are still open points. &lt;a href=&#34;https://help.sap.com/docs/Joule_Studio/45f9d2b8914b4f0ba731570ff9a85313/3d9dfad0bc39468292d508f0808a12fe.html&#34;&gt;Joule Studio already supports adding MCP servers&lt;/a&gt; through BTP destinations, but the documentation also lists restrictions: the destination must be streamable HTTP and MCP servers requiring interactive OAuth user authorization are not supported. SAP also shows MCP hub capabilities on the roadmap, but from what I can see this is still a future SAP Build capability and not the fully available enterprise MCP hub today.&lt;/p&gt;&#xA;&lt;p&gt;So I would present this as a target architecture and showcase, not as a final productive setup guide yet.&lt;/p&gt;&#xA;&lt;p&gt;For the showcase, I configured a Joule Studio agent with a small instruction set, Anthropic Claude Sonnet 4 as model, and two MCP servers: &lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1&lt;/a&gt; for the ABAP system and the &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;SAP Docs MCP Server&lt;/a&gt; for official SAP documentation context. I condensed the screenshots here to show only the relevant configuration parts:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Condensed Joule Studio agent configuration showing the agent basics, instructions, model settings, execution steps, and MCP servers for ARC-1 and SAP_DOCS.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-08-arc-1-joule-studio-clean-core/joule-studio-agent-configuration.png&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;use-cases-i-would-show-in-this-post&#34;&gt;Use Cases I Would Show In This Post&lt;/h2&gt;&#xA;&lt;p&gt;I would not repeat all Copilot Studio examples. The Joule post should show scenarios that make sense because the user starts inside the SAP world, not in Teams or SharePoint.&lt;/p&gt;&#xA;&lt;p&gt;One note before the screenshots: I was not able to change the language in this Joule setup, so the screenshots are in German. I describe the important parts in English below each screenshot.&lt;/p&gt;&#xA;&lt;p&gt;The first task for the agent is a Clean Core readiness check for one object. It is narrow enough to be understandable and safe enough because it can be read-only:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Check whether ZCL_ARC1_DEMO_CCORE is clean-core ready.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Use the real SAP system, ATC where possible, API release state, and SAP documentation.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Do not change anything. Return risks, evidence, and suggested next steps.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is a good first agent task because it shows the difference between &amp;ldquo;Joule explains Clean Core&amp;rdquo; and &amp;ldquo;Joule analyzes this class in this SAP system for Clean Core risk&amp;rdquo;. ARC-1 reads the ABAP source, dependencies, ATC findings, and &lt;code&gt;API_STATE&lt;/code&gt;. &lt;a href=&#34;https://mcp-sap-docs.marianzeis.de/&#34;&gt;mcp-sap-docs&lt;/a&gt; adds SAP guidance and released API context. Joule returns a short assessment with evidence.&lt;/p&gt;&#xA;&lt;p&gt;This is the kind of result I mean:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Joule showing a Clean Core readiness analysis for ZCL_ARC1_DEMO_CCORE with API release state, used SAP tables, and ATC result.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-08-arc-1-joule-studio-clean-core/joule-clean-core-object-analysis.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;The screenshot is still a rough showcase, but it already shows the value. Joule is not only giving generic Clean Core advice. It can show that the class is not released for the relevant contracts, that it uses &lt;code&gt;USR02&lt;/code&gt; and &lt;code&gt;BUT000&lt;/code&gt;, and that there are no ATC findings in this small example. That is a much better starting point for an architect or developer than a general explanation of what Clean Core means.&lt;/p&gt;&#xA;&lt;p&gt;I also asked a follow-up prompt for a dependency diagram. The result was almost the answer I wanted: not perfect, but useful because it returned a Mermaid diagram and an evidence table instead of only prose. You can open the &lt;a href=&#34;https://blog.zeis.de/posts/2026-05-08-arc-1-joule-studio-clean-core/full-reply/&#34;&gt;translated and rendered full Joule reply here&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;The second use case is package-level modernization planning. This is where an agent is useful because it needs multiple tool calls, grouping, prioritization, and a bit more reasoning:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Analyze package ZSD_LEGACY for Clean Core and ABAP Cloud risks.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Group the findings and create a modernization backlog.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Do not change code.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The same agent can combine ARC-1 for package content, dependencies, ATC, release state, and where-used information with SAP documentation for official guidance. The output should be a backlog, not a &amp;ldquo;fixed everything&amp;rdquo; claim:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Priority 1: direct SAP table usage with released successors&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Priority 2: ATC findings with clear fix direction&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Priority 3: objects that need architecture discussion&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Priority 4: probably dead or low-value code to review later&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This screenshot shows another interesting part of Joule. The response is not only a long text answer. Joule created a left-side list with grouped findings and backlog items, and the selected item is shown with details on the right side:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Joule showing an interactive package modernization backlog with grouped Clean Core and ABAP Cloud risk categories on the left and details on the right.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-08-arc-1-joule-studio-clean-core/joule-package-modernization-backlog.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;In this run, the package &lt;code&gt;ZSD_LEGACY&lt;/code&gt; did not exist. Joule still searched the system and found 28 &lt;code&gt;ZSD*&lt;/code&gt; objects that may represent legacy risks. It grouped them into high, medium, and low risk buckets, and then created a modernization backlog. The selected high-risk card shows temporary &lt;code&gt;$TMP&lt;/code&gt; package objects, split into service definitions, tables, and message classes. That is not a final assessment, but it is a useful interactive starting point.&lt;/p&gt;&#xA;&lt;p&gt;These examples have a better through line for this post than repeating the Copilot Studio demos. Copilot Studio is good when the work starts in Microsoft 365. Joule Studio is good when the work starts in SAP, but still needs real ABAP system context.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-i-use-an-agent-here&#34;&gt;Why I Use An Agent Here&lt;/h2&gt;&#xA;&lt;p&gt;For this showcase an agent is perfectly fine. The interesting part is not the packaging. The interesting part is that Joule can call &lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1&lt;/a&gt; and the &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;SAP Docs MCP Server&lt;/a&gt;, collect system evidence, and turn that into something useful for the user.&lt;/p&gt;&#xA;&lt;p&gt;For ARC-1 I would still keep the first Joule Studio agent conservative: one object or one package, read-only by default, no automatic rewrite. I would not start with a big automation story. I would start with analysis, evidence, and a proposal.&lt;/p&gt;&#xA;&lt;p&gt;And yes, you can also vibe code with this. If write access is enabled for a development or sandbox system, a Joule Studio agent can propose changes and call ARC-1 to apply them. I just do not want this to be the main story here, because the more important point is the architecture: first give Joule controlled access to real ABAP system context, then decide which write actions are safe enough.&lt;/p&gt;&#xA;&lt;h2 id=&#34;open-points&#34;&gt;Open Points&lt;/h2&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://help.sap.com/doc/sap-api-policy/latest/en-US/API_Policy_latest.pdf&#34;&gt;SAP API Policy&lt;/a&gt; also matters here, but it should not become the whole post. ARC-1 should be positioned as ADT development tooling, not as a generic business-data extraction layer. Clean Core checks, ATC, code inspection, syntax checks, ABAP Unit, activation, and development support are the story. Data extraction is not the story.&lt;/p&gt;&#xA;&lt;p&gt;This is also why the Joule Studio architecture is interesting. SAP&amp;rsquo;s architecture guidance describes SAP Integration Suite and API Management as part of a governed pathway for AI agents, including a governance layer like an MCP gateway in front of APIs. That is much closer to the setup in this post than a local MCP server on a developer laptop. It gives the agent a controlled entry point, central configuration, identity, monitoring, and auditability. It does not mean every possible ADT usage is automatically fine, but the architecture shape is much closer to what SAP is describing for API agents.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-this-matters&#34;&gt;Why This Matters&lt;/h2&gt;&#xA;&lt;p&gt;Copilot Studio and Joule Studio are different surfaces:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Copilot Studio = close to Teams, Microsoft 365, SharePoint, Excel, Word, Jira&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Joule Studio = close to SAP products, SAP Build, Joule agents, SAP users&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ARC-1 = governed ABAP system access layer that can serve both&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The client can change, but the security model should not. For me that is the main point. ARC-1 should not be locked to Claude, Cursor, Copilot Studio, or Joule Studio. If the company allows it, the same governed ARC-1 endpoint should be usable from different AI clients.&lt;/p&gt;&#xA;&lt;p&gt;And ARC-1 is open source, which is important to me. If this kind of access layer becomes part of SAP AI architectures, then people should be able to inspect how it works, challenge the security model, test it against real systems, and decide for themselves if it fits their landscape.&lt;/p&gt;&#xA;&lt;p&gt;Also, this does not have to start with write access. Even read-only mode is already very powerful. Reading code, dependencies, ATC findings, release state, dumps, messages, packages, and where-used information can help architects, consultants, support teams, and developers without the assistant writing a single line of ABAP.&lt;/p&gt;&#xA;&lt;p&gt;The target picture is not &amp;ldquo;AI writes ABAP from a chat prompt&amp;rdquo;. The better target is:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;the user asks inside Joule&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Joule has access to the right tools&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ARC-1 provides real ABAP system context&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SAP documentation provides the official direction&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Integration Suite provides governance&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SAP authorizations still protect the backend&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;humans approve the risky parts&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That is much more interesting than a local MCP demo. It is also much closer to what I think enterprise ABAP agentic development needs.&lt;/p&gt;&#xA;&lt;h2 id=&#34;discuss-this-post&#34;&gt;Discuss this post&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://saptodon.org/@Mianbsp/116529573994656695&#34;&gt;Saptodon&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bsky.app/profile/marian.zeis.de/post/3ml7kulu55s2v&#34;&gt;Bluesky&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.linkedin.com/posts/marianzeis_what-if-joule-could-answer-abap-and-clean-activity-7458010353239691264-gbF0&#34;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;references--links&#34;&gt;References &amp;amp; links&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1 on GitHub&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/&#34;&gt;ARC-1 Documentation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/tools/&#34;&gt;ARC-1 Tools&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/authorization/&#34;&gt;ARC-1 Authorization and Roles&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/phase4-btp-deployment/&#34;&gt;ARC-1 BTP Cloud Foundry Deployment&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/principal-propagation-setup/&#34;&gt;ARC-1 Principal Propagation Setup&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://mcp-sap-docs.marianzeis.de/&#34;&gt;mcp-sap-docs&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/docs/JOULE/3fdd7b321eb24d1b9d40605dce822e84&#34;&gt;SAP Help: What is Joule?&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/docs/Joule_Studio/45f9d2b8914b4f0ba731570ff9a85313/4444cd1ce4cd471bbe127ea2e4735b40.html&#34;&gt;SAP Help: What is Joule Studio?&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/docs/Joule_Studio/45f9d2b8914b4f0ba731570ff9a85313/3d9dfad0bc39468292d508f0808a12fe.html&#34;&gt;SAP Help: Add MCP Servers to Your Joule Agent&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://architecture.learning.sap.com/docs/ref-arch/ca1d2a3e/1&#34;&gt;SAP Architecture Center: A2A and MCP for Interoperability&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/docs/integration-suite/sap-integration-suite/api-management-capability&#34;&gt;SAP Integration Suite API Management&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/docs/btp/btp-developers-guide/use-joule-for-developers-generative-ai-in-abap-cloud&#34;&gt;SAP Joule for Developers&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/docs/abap-ai/generative-ai-in-abap-cloud/joule-for-developers-abap-ai-capabilities-5f175c94900741d6af3dbb23ce37e81a&#34;&gt;SAP Joule for Developers, ABAP AI capabilities&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/docs/joule/serviceguide/sap-consulting-capability-for-joule&#34;&gt;SAP Joule for Consultants&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/doc/sap-api-policy/latest/en-US/API_Policy_latest.pdf&#34;&gt;SAP API Policy&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>ARC-1 with Copilot Studio: SAP System Context Beyond Developers</title>
      <link>https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/</link>
      <pubDate>Mon, 04 May 2026 09:00:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/</guid>
      <description>&lt;p&gt;Series note: This post is part of my &lt;a href=&#34;https://blog.zeis.de/tags/ai-abap-development-series/&#34;&gt;AI ABAP development series&lt;/a&gt;, where I go from AI development in general, to ABAP-specific problems, and then to ARC-1.&lt;/p&gt;&#xA;&lt;p&gt;In the &lt;a href=&#34;https://blog.zeis.de/posts/2026-04-29-arc-1-btp/&#34;&gt;previous post&lt;/a&gt;, I wrote about running &lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1&lt;/a&gt; on SAP BTP. That was the architecture part: central deployment, XSUAA, destinations, Cloud Connector, Principal Propagation, roles, and auditability.&lt;/p&gt;&#xA;&lt;p&gt;This post is the next step. If ARC-1 is already deployed centrally on BTP, then it does not have to be used only from developer tools like VS Code, Claude, Cursor, or Eclipse. It can also be used from &lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/&#34;&gt;Microsoft Copilot Studio&lt;/a&gt; and then published into &lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/publication-add-bot-to-microsoft-teams&#34;&gt;Teams or Microsoft 365 Copilot&lt;/a&gt;. That changes the audience quite a bit.&lt;/p&gt;&#xA;&lt;p&gt;For developers, ARC-1 is mainly an ADT MCP gateway for code, packages, activation, transports, diagnostics, and system context. MCP is the protocol that lets an AI client call external tools, and ADT is the API layer behind &lt;a href=&#34;https://help.sap.com/docs/btp/sap-business-technology-platform/abap-development-user-guides&#34;&gt;ABAP Development Tools&lt;/a&gt;. But this SAP system access is not only useful for developers. &lt;a href=&#34;https://marianfoo.github.io/arc-1/tools/&#34;&gt;ARC-1 tools&lt;/a&gt; can also expose table structures, selected data, messages, transaction metadata, API release state, feature toggles, FLP content, dumps, transport information, and more. Depending on the enabled ARC-1 tools and SAP authorizations, that can become useful for functional consultants, solution architects, testers, security people, and support teams.&lt;/p&gt;&#xA;&lt;p&gt;The question for this post is: what happens if the SAP system context is not only available inside an IDE?&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-copilot-studio-fits-here&#34;&gt;Why Copilot Studio fits here&lt;/h2&gt;&#xA;&lt;p&gt;Functional consultants usually do not live in a local IDE. They work in Teams, Excel, Word, SharePoint, Jira, SAP GUI, Fiori, and many other tools around the actual system. So if the only AI access pattern is &amp;ldquo;install an MCP server locally and configure your IDE&amp;rdquo;, then we miss a large part of the SAP project team.&lt;/p&gt;&#xA;&lt;p&gt;Copilot Studio fits here because it is already close to where many business users work. An agent can be &lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/publication-add-bot-to-microsoft-teams&#34;&gt;published into Teams and Microsoft 365 Copilot&lt;/a&gt;. It can use &lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-add-sharepoint&#34;&gt;SharePoint as a knowledge source&lt;/a&gt;. It can use connectors for systems like &lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-365/copilot/connectors/jira-cloud-deployment&#34;&gt;Jira Cloud&lt;/a&gt;, and the same idea also applies to other enterprise sources like Confluence if there is a connector or MCP server for it. I did not use Confluence in this test, but the architecture would be the same. And since Copilot Studio can &lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/mcp-add-existing-server-to-agent&#34;&gt;connect to existing MCP servers&lt;/a&gt;, it can also call ARC-1 when the server is deployed to BTP.&lt;/p&gt;&#xA;&lt;p&gt;That does not mean everyone should get write access to SAP from Teams. Quite the opposite. For this kind of usage I would start read-only and very controlled. But even read-only is already powerful if the agent can combine:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;A business specification from SharePoint.&lt;/li&gt;&#xA;&lt;li&gt;SAP documentation from the separately deployed &lt;a href=&#34;https://mcp-sap-docs.marianzeis.de/&#34;&gt;mcp-sap-docs&lt;/a&gt; MCP server.&lt;/li&gt;&#xA;&lt;li&gt;Real system context from ARC-1.&lt;/li&gt;&#xA;&lt;li&gt;Tickets or tasks from Jira.&lt;/li&gt;&#xA;&lt;li&gt;The conversation interface in Teams.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h2 id=&#34;the-architecture-stays-the-same&#34;&gt;The Architecture Stays The Same&lt;/h2&gt;&#xA;&lt;p&gt;Copilot Studio should not create a second SAP access architecture. It should use the same &lt;a href=&#34;https://marianfoo.github.io/arc-1/phase4-btp-deployment/&#34;&gt;BTP-deployed ARC-1 endpoint&lt;/a&gt; from the previous post.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Copilot Studio / Teams&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; ARC-1 MCP endpoint on SAP BTP&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; XSUAA / OAuth&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; Destination Service&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; Cloud Connector&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; SAP ABAP system&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This means the security story does not change only because the client changes. ARC-1 still has the &lt;a href=&#34;https://marianfoo.github.io/arc-1/authorization/#the-model-in-one-picture&#34;&gt;server ceiling&lt;/a&gt;. &lt;a href=&#34;https://marianfoo.github.io/arc-1/xsuaa-setup/&#34;&gt;XSUAA roles&lt;/a&gt; still control what the user can do in ARC-1. If &lt;a href=&#34;https://marianfoo.github.io/arc-1/principal-propagation-setup/&#34;&gt;Principal Propagation&lt;/a&gt; is active, ARC-1 does not call SAP as one shared technical user. The signed-in user&amp;rsquo;s identity is propagated to the ABAP backend, so SAP checks the request with that user&amp;rsquo;s normal SAP authorizations, for example object, package, transport, table, or &lt;code&gt;S_DEVELOP&lt;/code&gt; permissions.&lt;/p&gt;&#xA;&lt;p&gt;In simpler words: Copilot calls ARC-1 over HTTPS, BTP authenticates the user and resolves the SAP destination, and the Cloud Connector forwards the request to the ABAP system. The server ceiling is the maximum ARC-1 capability the admin enabled for this instance, so a user role can never enable more than the server allows.&lt;/p&gt;&#xA;&lt;p&gt;For Copilot Studio I would be extra conservative with the first setup:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;read/search/diagnose only&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;data preview only for selected users&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;free SQL only for selected users&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;writes disabled by default&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;transport writes disabled&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A Copilot Studio agent can be much more accessible to non-developers than an IDE tool, so the safety model matters even more.&lt;/p&gt;&#xA;&lt;h2 id=&#34;setup&#34;&gt;Setup&lt;/h2&gt;&#xA;&lt;p&gt;I would keep the setup simple and reuse the BTP deployment from the previous post:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;Deploy ARC-1 on BTP with &lt;a href=&#34;https://marianfoo.github.io/arc-1/xsuaa-setup/&#34;&gt;XSUAA&lt;/a&gt;, &lt;a href=&#34;https://marianfoo.github.io/arc-1/btp-destination-setup/&#34;&gt;BTP destinations&lt;/a&gt;, and ideally &lt;a href=&#34;https://marianfoo.github.io/arc-1/principal-propagation-setup/&#34;&gt;Principal Propagation&lt;/a&gt;. The detailed steps are in the &lt;a href=&#34;https://marianfoo.github.io/arc-1/phase4-btp-deployment/&#34;&gt;BTP deployment guide&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Keep ARC-1 read-only first, or at least very restricted with &lt;a href=&#34;https://marianfoo.github.io/arc-1/authorization/&#34;&gt;ARC-1 authorization and roles&lt;/a&gt;.&lt;/li&gt;&#xA;&lt;li&gt;In Copilot Studio, add ARC-1 as an &lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/mcp-add-existing-server-to-agent&#34;&gt;existing MCP server&lt;/a&gt; with the public HTTPS endpoint from BTP, for example &lt;code&gt;https://arc1-ecc-dev.cfapps.eu10.hana.ondemand.com/mcp&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;Use OAuth 2.0 for the MCP server authentication. Copilot Studio supports manual OAuth configuration for MCP servers, and ARC-1 on BTP can use XSUAA for that.&lt;/li&gt;&#xA;&lt;li&gt;Add &lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-add-sharepoint&#34;&gt;SharePoint as a knowledge source&lt;/a&gt;, add the &lt;a href=&#34;https://mcp-sap-docs.marianzeis.de/&#34;&gt;SAP documentation MCP server&lt;/a&gt; with its public MCP endpoint &lt;code&gt;https://mcp-sap-docs.marianzeis.de/mcp&lt;/code&gt;, and then add other enterprise sources only where they make sense.&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/publication-add-bot-to-microsoft-teams&#34;&gt;Publish the agent to Teams or Microsoft 365 Copilot&lt;/a&gt; after testing.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/mcp-add-existing-server-to-agent&#34;&gt;existing MCP server page&lt;/a&gt; also documents Streamable transport and the API key or OAuth 2.0 authentication options. For SAP system access I would not start with API keys unless it is a very controlled automation scenario, because OAuth gives a better identity story.&lt;/p&gt;&#xA;&lt;p&gt;In my test agent I used ARC-1 on BTP, a Jira MCP server, and SAP Docs as tools. For knowledge, I added SharePoint locations for specifications and IT documents, plus a Jira connector. For this showcase I enabled the available read and write tools.&lt;/p&gt;&#xA;&lt;p&gt;Microsoft&amp;rsquo;s own &lt;a href=&#34;https://learn.microsoft.com/en-us/graph/mcp-server/use-enterprise-mcp-server-copilot-studio&#34;&gt;MCP Server for Enterprise&lt;/a&gt; points in a similar direction for Microsoft Graph: enterprise context should come through governed endpoints, not random local scripts.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Copilot Studio SAP Agent tools screen showing ARC-1 BTP, Jira MCP Server, and SAP Docs as MCP tools.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/copilotstudio-tools.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Copilot Studio SAP Agent knowledge screen showing SharePoint knowledge sources and a Jira connector.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/sapcopilot-knowledge.png&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;sharepoint-and-sap-documentation&#34;&gt;SharePoint And SAP Documentation&lt;/h2&gt;&#xA;&lt;p&gt;The most natural non-developer setup is probably SharePoint plus SAP docs plus SAP system context. SharePoint is where many specifications and project documents already live, and Copilot Studio can use &lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-add-sharepoint&#34;&gt;SharePoint as a knowledge source&lt;/a&gt; while Microsoft authentication keeps the normal document permissions in place.&lt;/p&gt;&#xA;&lt;p&gt;But SharePoint alone is not enough. A specification can say what should happen, but it often does not know how the SAP system actually works today. This is where ARC-1 comes in. The agent can ask ARC-1 for current ABAP objects, CDS views, table structures, message texts, service bindings, transaction metadata, or selected table data, depending on what is enabled and authorized in the &lt;a href=&#34;https://marianfoo.github.io/arc-1/authorization/&#34;&gt;ARC-1 tool and role model&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Then I would add the separately deployed &lt;a href=&#34;https://mcp-sap-docs.marianzeis.de/&#34;&gt;mcp-sap-docs&lt;/a&gt; server as the documentation layer, using the public MCP endpoint &lt;code&gt;https://mcp-sap-docs.marianzeis.de/mcp&lt;/code&gt;. It can search SAP documentation, ABAP keyword docs, RAP samples, style guides, DSAG guidelines, SAP Community, and also released object information. That gives the agent a much better chance to not only say &amp;ldquo;this is possible&amp;rdquo;, but also explain what SAP recommends and where the current system differs.&lt;/p&gt;&#xA;&lt;p&gt;This combination is more useful than any single source alone:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SharePoint = what the project wants&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Jira = what needs to be solved&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;mcp-sap-docs = what SAP recommends&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;ARC-1 = what the SAP system actually does&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Of these four, ARC-1 is the only one that can read or write actual ABAP in actual SAP. Everything else is orchestration around it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-real-use-cases-i-tested&#34;&gt;The Real Use Cases I Tested&lt;/h2&gt;&#xA;&lt;p&gt;I created a few small demo scenarios and tested them in Copilot Studio with ARC-1. The screenshots below are from those conversations.&lt;/p&gt;&#xA;&lt;h2 id=&#34;use-case-1-bug-fix-from-a-jira-ticket&#34;&gt;Use Case 1: Bug Fix From A Jira Ticket&lt;/h2&gt;&#xA;&lt;p&gt;The first scenario is still a developer scenario, but it shows the complete loop very well. The Jira ticket &lt;code&gt;KAN-2&lt;/code&gt; only says that a customer with exactly EUR 1,000,000.00 revenue gets the wrong discount tier. Copilot reads the ticket, searches the SAP system, reads &lt;code&gt;ZARC1_DEMO_DISCOUNT&lt;/code&gt;, compares the implementation with the header comment specification, and proposes the one character patch.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Look at KAN-2, propose a patch, but don&amp;#39;t apply it yet.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The important part for me is the confirmation step. The agent does not immediately write to SAP. It first shows the root cause and the diff. Only after the follow-up prompt it updates and activates the report.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Jira ticket KAN-2 describing the wrong discount tier for exactly EUR 1,000,000.00 revenue.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/conversations/01/jira-ticket.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Copilot Studio proposing a patch for Jira ticket KAN-2 after reading the ABAP report ZARC1_DEMO_DISCOUNT.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/conversations/01/patch-proposal.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Copilot Studio confirming that the patch for ZARC1_DEMO_DISCOUNT was applied and activated.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/conversations/01/patch-applied.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;full-answer-viewer.html?case=Use%20Case%201&amp;amp;title=Bug%20fix%20from%20a%20Jira%20ticket&amp;amp;answer=conversations/01/full-answer.txt&#34;&gt;Open the formatted full answer&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;This captured run shows both steps: first the patch proposal, then the explicit follow-up to apply and activate it. In a safer setup, the first step alone can already be enough. The developer can copy the suggested diff, review it, test it manually, and keep ARC-1 read-only for that scenario.&lt;/p&gt;&#xA;&lt;h2 id=&#34;use-case-2-sharepoint-change-request-to-impact-analysis&#34;&gt;Use Case 2: SharePoint Change Request To Impact Analysis&lt;/h2&gt;&#xA;&lt;p&gt;The second use case is more interesting for architects. A short SharePoint memo asks to widen &lt;code&gt;ZARC1_DEMO_AMOUNT_DOM&lt;/code&gt; from &lt;code&gt;CURR 13,2&lt;/code&gt; to &lt;code&gt;CURR 23,2&lt;/code&gt;. Copilot reads the memo and then uses ARC-1 to walk the dependency chain in SAP.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Read the change request in IT/Clean Core/ and tell me what would break.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The answer is not just &amp;ldquo;yes, change the domain&amp;rdquo;. It lists the dependency chain from domain to data element to table to report, then explains the risks: activation order, possible table lock, output length, and the hardcoded report layout.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Copilot Studio showing an impact analysis for a SharePoint change request to widen an ABAP amount domain.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/conversations/02/impact-analysis.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;full-answer-viewer.html?case=Use%20Case%202&amp;amp;title=SharePoint%20change%20request%20impact%20analysis&amp;amp;answer=conversations/02/full-answer.txt&#34;&gt;Open the formatted full answer&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;This is where a solution architect or functional consultant benefits from SAP system context without needing an IDE. It turns a business change request into a better technical discussion.&lt;/p&gt;&#xA;&lt;h2 id=&#34;use-case-3-build-a-cds-view-from-a-sharepoint-specification&#34;&gt;Use Case 3: Build A CDS View From A SharePoint Specification&lt;/h2&gt;&#xA;&lt;p&gt;The third use case combines SharePoint, SAP documentation, and ARC-1. The input is a small SharePoint specification for a sales order KPI CDS view. Copilot reads the spec, uses &lt;a href=&#34;https://mcp-sap-docs.marianzeis.de/&#34;&gt;mcp-sap-docs&lt;/a&gt; for SAP documentation and CDS conventions, reads the source table from SAP, and then creates and activates the views.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Build the CDS view from IT/Specifications/spec-orders-kpi.md.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What I liked in this example is that the agent had to adapt to the system. The first simple idea did not work because the system did not allow arithmetic expressions directly inside aggregate functions. The final result was a two-layer CDS design: a base view for line revenue and a KPI view for aggregation.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Copilot Studio explaining that it created and activated a two-layer CDS view design from a SharePoint specification.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/conversations/03/cds-view-built.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;full-answer-viewer.html?case=Use%20Case%203&amp;amp;title=CDS%20view%20from%20a%20SharePoint%20specification&amp;amp;answer=conversations/03/full-answer.txt&#34;&gt;Open the formatted full answer&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;This is where Copilot Studio becomes more than a chatbot over documents: SharePoint for the requirement, mcp-sap-docs for the SAP pattern, and ARC-1 for the real table and activation.&lt;/p&gt;&#xA;&lt;h2 id=&#34;use-case-4-code-quality-report&#34;&gt;Use Case 4: Code Quality Report&lt;/h2&gt;&#xA;&lt;p&gt;The fourth scenario is reporting only. The prompt asks Copilot to audit the &lt;code&gt;$TMP&lt;/code&gt; &lt;code&gt;ZARC1_DEMO_*&lt;/code&gt; ABAP programs and post a quality report. The captured answer shows the generated Markdown report, not the SharePoint write-back step.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Audit our $TMP ZARC1_DEMO_* ABAP programs and post a quality report.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Copilot searches the SAP system, reads the programs, runs both SAP ATC checks and abaplint, and produces a per-program report with the two findings axes scored against an overall Risk column. It also catches things pure linting would not flag on its own: SQL injection in the dump-trigger report, full-table scans in the invoice list, and table-buffer bypass on &lt;code&gt;USR02&lt;/code&gt;. The same output could then be written to SharePoint, Word, or another document store depending on the connectors and permissions.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Copilot Studio showing an ABAP code quality audit report for ZARC1_DEMO programs.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/conversations/04/code-quality-report.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;full-answer-viewer.html?case=Use%20Case%204&amp;amp;title=ABAP%20code%20quality%20report&amp;amp;answer=conversations/04/full-answer.txt&#34;&gt;Open the formatted full answer&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;This is not mainly a coding example. It gives an architect, technical lead, or quality manager a faster first view: which programs are risky, which findings are only style, and which ones would block a promotion.&lt;/p&gt;&#xA;&lt;h2 id=&#34;use-case-5-short-dump-diagnosis-from-a-jira-ticket&#34;&gt;Use Case 5: Short Dump Diagnosis From A Jira Ticket&lt;/h2&gt;&#xA;&lt;p&gt;The fifth example starts from an intentionally weak Jira ticket. It only says that &lt;code&gt;ZARC1_DEMO_DUMP_RPT&lt;/code&gt; short dumped overnight and that ST22 has the dump. This is close to how many real support tickets start.&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Investigate KAN-3 and apply a defensive fix.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Copilot reads the Jira ticket, uses ARC-1 to read short dumps, identifies &lt;code&gt;DBSQL_SQL_ERROR&lt;/code&gt;, reads the report, explains the root cause, and applies a defensive fix. In this demo it adds validation before a dynamic &lt;code&gt;SELECT FROM (p_table)&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Jira ticket KAN-3 with a short description that the ABAP report ZARC1_DEMO_DUMP_RPT short dumped overnight.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/conversations/05/jira-dump-ticket.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Copilot Studio showing ST22 short dump analysis and the defensive fix for ZARC1_DEMO_DUMP_RPT.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/conversations/05/short-dump-fix.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;full-answer-viewer.html?case=Use%20Case%205&amp;amp;title=Short%20dump%20diagnosis%20from%20Jira&amp;amp;answer=conversations/05/full-answer.txt&#34;&gt;Open the formatted full answer&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;In bug fixing, the analysis is often the biggest part. If the agent can collect the ticket, dump, source code, and related objects, then the developer starts much further ahead.&lt;/p&gt;&#xA;&lt;h2 id=&#34;use-case-6-clean-core-readiness-check&#34;&gt;Use Case 6: Clean Core Readiness Check&lt;/h2&gt;&#xA;&lt;p&gt;The last scenario is about clean core readiness. The prompt is simple:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Is ZCL_ARC1_DEMO_CCORE clean-core ready?&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Copilot reads the class through ARC-1 and finds direct selects on &lt;code&gt;USR02&lt;/code&gt; and &lt;code&gt;BUT000&lt;/code&gt;. Then it uses two different context sources for the assessment. ARC-1&amp;rsquo;s &lt;code&gt;API_STATE&lt;/code&gt; read capability asks the SAP system for release state and successor information. The SAP documentation source adds the official guidance around clean core, released APIs, ABAP Cloud readiness, and why direct table access to internal SAP tables is problematic. When SAP documentation returned no entry at all for &lt;code&gt;USR02&lt;/code&gt;, the agent treated that as &amp;ldquo;strictly internal, find a released alternative&amp;rdquo; rather than &amp;ldquo;unknown, assume fine&amp;rdquo;, and inferred the right successor anyway. Based on that combined context, Copilot proposes released successor APIs like &lt;code&gt;I_BUSINESSUSERBASIC&lt;/code&gt; and &lt;code&gt;I_BUSINESSPARTNER&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Copilot Studio showing a clean core readiness analysis for class ZCL_ARC1_DEMO_CCORE.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-05-05-arc-1-copilot-studio/conversations/06/clean-core-readiness.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;full-answer-viewer.html?case=Use%20Case%206&amp;amp;title=Clean%20core%20readiness%20check&amp;amp;answer=conversations/06/full-answer.txt&#34;&gt;Open the formatted full answer&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;ARC-1 provides the real custom code and the system-specific release state, while SAP documentation provides the official target direction. That makes the result useful for architecture and modernization planning, not only for a developer sitting in an IDE.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-this-matters&#34;&gt;Why This Matters&lt;/h2&gt;&#xA;&lt;p&gt;The important change is not that Copilot Studio can call one more tool. The important change is that a centrally deployed ARC-1 endpoint can make SAP system context available in places where more project roles can use it.&lt;/p&gt;&#xA;&lt;p&gt;Developers still need IDEs, and Copilot Studio does not replace ABAP development tools. But it can become a useful layer for analysis, specification, support, documentation, and architecture work, especially when it combines SAP system context, SAP documentation, SharePoint, Jira, and Microsoft 365 through governed tools and connectors. Without that central architecture, this would again become a local tool story. With BTP, it becomes an enterprise agent story.&lt;/p&gt;&#xA;&lt;h2 id=&#34;discuss-this-post&#34;&gt;Discuss this post&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://saptodon.org/@Mianbsp/116518419049843109&#34;&gt;Saptodon&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bsky.app/profile/marian.zeis.de/post/3ml2me6teds2q&#34;&gt;Bluesky&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.linkedin.com/posts/marianzeis_what-if-copilot-could-start-from-a-support-activity-7457285481433559041-tvAA&#34;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;references--links&#34;&gt;References &amp;amp; links&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1 on GitHub&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/&#34;&gt;ARC-1 Documentation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/tools/&#34;&gt;ARC-1 Tools&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/enterprise-auth/&#34;&gt;ARC-1 Authentication Overview&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/phase4-btp-deployment/&#34;&gt;ARC-1 BTP Cloud Foundry Deployment&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/xsuaa-setup/&#34;&gt;ARC-1 XSUAA Setup&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/btp-destination-setup/&#34;&gt;ARC-1 BTP Destination Setup&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/principal-propagation-setup/&#34;&gt;ARC-1 Principal Propagation Setup&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/authorization/&#34;&gt;ARC-1 Authorization and Roles&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;mcp-sap-docs&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/docs/btp/sap-business-technology-platform/abap-development-user-guides&#34;&gt;SAP Help: ABAP Development Tools for Eclipse: User Guides&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/&#34;&gt;Microsoft Learn: Copilot Studio documentation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/mcp-add-existing-server-to-agent&#34;&gt;Microsoft Learn: Connect your agent to an existing MCP server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/mcp-create-new-server&#34;&gt;Microsoft Learn: Create a new MCP server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/knowledge-add-sharepoint&#34;&gt;Microsoft Learn: Add SharePoint as a knowledge source&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-copilot-studio/publication-add-bot-to-microsoft-teams&#34;&gt;Microsoft Learn: Connect and configure an agent for Teams and Microsoft 365 Copilot&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/en-us/microsoft-365/copilot/connectors/jira-cloud-deployment&#34;&gt;Microsoft Learn: Jira Cloud connector for Microsoft 365 Copilot&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/en-us/graph/mcp-server/use-enterprise-mcp-server-copilot-studio&#34;&gt;Microsoft Learn: Use Microsoft MCP Server for Enterprise from Copilot Studio&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>ARC-1 on SAP BTP: Secure ABAP Agentic Development Beyond the Laptop</title>
      <link>https://blog.zeis.de/posts/2026-04-29-arc-1-btp/</link>
      <pubDate>Wed, 29 Apr 2026 09:00:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2026-04-29-arc-1-btp/</guid>
      <description>&lt;p&gt;Series note: This post is part of my &lt;a href=&#34;https://blog.zeis.de/tags/ai-abap-development-series/&#34;&gt;AI ABAP development series&lt;/a&gt;, where I go from AI development in general, to ABAP-specific problems, and then to ARC-1.&lt;/p&gt;&#xA;&lt;p&gt;In the &lt;a href=&#34;https://blog.zeis.de/posts/2026-04-27-arc-1/&#34;&gt;previous post&lt;/a&gt;, I introduced &lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1&lt;/a&gt; as a secure ADT MCP gateway for ABAP systems. The main point was not only that ARC-1 can expose ABAP development functionality to AI clients. The main point was that this access needs a place in the architecture.&lt;/p&gt;&#xA;&lt;p&gt;This post is about that place. If ARC-1 should not run uncontrolled on every developer laptop, then SAP BTP is the most natural enterprise option for me. Not because BTP makes the problem disappear, but because it already has the pieces you need for this kind of setup: XSUAA, destinations, Cloud Connector, role collections, audit logging, and the normal BTP operating model.&lt;/p&gt;&#xA;&lt;p&gt;This is not a full setup guide. The exact commands are in the &lt;a href=&#34;https://marianfoo.github.io/arc-1/deployment/&#34;&gt;ARC-1 deployment docs&lt;/a&gt;, the &lt;a href=&#34;https://marianfoo.github.io/arc-1/phase4-btp-deployment/&#34;&gt;BTP deployment guide&lt;/a&gt;, and the pages for &lt;a href=&#34;https://marianfoo.github.io/arc-1/xsuaa-setup/&#34;&gt;XSUAA&lt;/a&gt;, &lt;a href=&#34;https://marianfoo.github.io/arc-1/btp-destination-setup/&#34;&gt;destinations&lt;/a&gt;, and &lt;a href=&#34;https://marianfoo.github.io/arc-1/principal-propagation-setup/&#34;&gt;Principal Propagation&lt;/a&gt;. Here I want to explain the architecture options and what I would look at first.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-btp&#34;&gt;Why BTP&lt;/h2&gt;&#xA;&lt;p&gt;The problem with agentic ABAP development is not only context. It is controlled context. The AI needs to read real SAP objects, but the company also needs to know who is authenticated, where credentials live, what the effective permission is, and who can later audit what happened.&lt;/p&gt;&#xA;&lt;p&gt;That is where BTP helps. ARC-1 can run as one central Cloud Foundry application instead of many local MCP servers. The SAP connection can use BTP destinations. On-premise and private cloud systems can be reached through Cloud Connector. Users can authenticate through XSUAA. Role collections can decide who gets read, write, data preview, SQL, transport, git, or admin scopes. If the BTP Audit Log Service is bound, ARC-1 can write audit events into the platform instead of only to a local laptop.&lt;/p&gt;&#xA;&lt;p&gt;For me this is the real difference. A local setup is good for testing and for special cases, but it is not the architecture I would want for a real ABAP team. A central BTP deployment gives you one managed MCP endpoint per SAP system, one place for policy, and no need to store SAP passwords in every developer client.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-basic-shape&#34;&gt;The Basic Shape&lt;/h2&gt;&#xA;&lt;p&gt;The architecture still has the same simple shape:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;MCP client -&amp;gt; ARC-1 -&amp;gt; SAP system&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But on BTP this becomes two authentication hops:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;AI client&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; XSUAA OAuth&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; ARC-1 on SAP BTP Cloud Foundry&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; Destination Service&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; Connectivity Service&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; Cloud Connector&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  -&amp;gt; SAP ABAP system&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first hop is the MCP client talking to ARC-1. A client like Claude, Cursor, VS Code, GitHub Copilot for Eclipse, MCP Inspector, or Copilot Studio calls the ARC-1 &lt;code&gt;/mcp&lt;/code&gt; endpoint and authenticates through XSUAA. The second hop is ARC-1 talking to SAP. For that hop, ARC-1 can use a BTP destination with a technical user, a BTP destination with Principal Propagation, or a BTP ABAP Environment service key.&lt;/p&gt;&#xA;&lt;p&gt;For human developer usage, Principal Propagation is the most interesting option. ARC-1 receives the user token, passes it to the Destination Service, and BTP plus Cloud Connector can propagate the user identity to the backend. Then SAP sees the real user instead of one shared technical account. That is important for authorization and for audit.&lt;/p&gt;&#xA;&lt;p&gt;ARC-1 uses a dual-destination pattern for this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SAP_BTP_DESTINATION      = shared BasicAuth destination&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SAP_BTP_PP_DESTINATION   = per-user PrincipalPropagation destination&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SAP_PP_ENABLED           = true&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;SAP_XSUAA_AUTH           = true&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The BasicAuth destination is used for startup work like feature probing and cache warmup, because there is no user JWT at startup. The PrincipalPropagation destination is used for authenticated per-user requests. In production I would also look at &lt;code&gt;SAP_PP_STRICT=true&lt;/code&gt;, because then a Principal Propagation problem fails clearly instead of silently falling back to a shared user.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-three-gates&#34;&gt;The Three Gates&lt;/h2&gt;&#xA;&lt;p&gt;The important part is that ARC-1 does not rely on one big switch. A request has to pass three gates:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;The server ceiling: what this ARC-1 instance can ever do, configured with environment variables like &lt;code&gt;SAP_ALLOW_WRITES&lt;/code&gt;, &lt;code&gt;SAP_ALLOW_FREE_SQL&lt;/code&gt;, or &lt;code&gt;SAP_ALLOWED_PACKAGES&lt;/code&gt;.&lt;/li&gt;&#xA;&lt;li&gt;The user permission: what this user can do inside ARC-1, coming from XSUAA role collections, OIDC scopes, or API-key profiles.&lt;/li&gt;&#xA;&lt;li&gt;The SAP authorization: what the SAP backend user can do, for example through &lt;code&gt;S_DEVELOP&lt;/code&gt;, package authorizations, transport authorizations, or ABAP Cloud restrictions.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;That means the effective permission is an AND model:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Effective permission = server ceiling AND user permission AND SAP authorization&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is why BTP role collections and ARC-1 safety flags are not the same thing. If the server has &lt;code&gt;SAP_ALLOW_WRITES=false&lt;/code&gt;, a user with &lt;code&gt;ARC-1 Developer&lt;/code&gt; still cannot write. If &lt;code&gt;SAP_ALLOW_FREE_SQL=false&lt;/code&gt;, a user with SQL scope still cannot run freestyle SQL. And even if both ARC-1 layers allow the action, SAP can still reject it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;deployment-options&#34;&gt;Deployment Options&lt;/h2&gt;&#xA;&lt;p&gt;The recommended option is the MTA deployment. ARC-1 includes an &lt;code&gt;mta.yaml&lt;/code&gt; with the Cloud Foundry app and the required services: XSUAA, Destination Service, and Connectivity Service. This is the most reproducible setup because the application and service bindings are described together, similar to how SAP describes multitarget applications for Cloud Foundry.&lt;/p&gt;&#xA;&lt;p&gt;The rough commands are:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm run btp:build&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm run btp:deploy&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;or combined:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;npm run btp:build-deploy&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Docker on Cloud Foundry is the second option. That can make sense if a company wants to deploy a pinned image from GHCR or an internal registry. You still need the same BTP services, but you manage more yourself with &lt;code&gt;manifest.yml&lt;/code&gt;, &lt;code&gt;cf create-service&lt;/code&gt;, &lt;code&gt;cf bind-service&lt;/code&gt;, and &lt;code&gt;cf set-env&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;A direct Node.js buildpack deployment is also possible, especially if you patch or customize the source and push it directly with &lt;code&gt;cf push&lt;/code&gt;. For a stable team setup, I would start with MTA unless there is a concrete reason not to.&lt;/p&gt;&#xA;&lt;p&gt;BTP ABAP Environment is a separate scenario. There is no Cloud Connector involved. ARC-1 can use a service key and OAuth flow to connect to the ABAP environment. In this case &lt;code&gt;SAP_SYSTEM_TYPE=btp&lt;/code&gt; matters, because ARC-1 adapts tool definitions and avoids on-premise-only object types and assumptions.&lt;/p&gt;&#xA;&lt;h2 id=&#34;configuration-that-matters&#34;&gt;Configuration That Matters&lt;/h2&gt;&#xA;&lt;p&gt;This is not the full reference, but these are the variables I would explain first:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_TRANSPORT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;http-streamable&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_XSUAA_AUTH&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_BTP_DESTINATION&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;SAP_ECC_DEV&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_BTP_PP_DESTINATION&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;SAP_ECC_DEV_PP&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_PP_ENABLED&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_PP_STRICT&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_ALLOW_WRITES&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_ALLOW_DATA_PREVIEW&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_ALLOW_FREE_SQL&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_ALLOW_TRANSPORT_WRITES&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_ALLOW_GIT_WRITES&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;SAP_ALLOWED_PACKAGES&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;$TMP&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first group makes ARC-1 a remote MCP server with XSUAA authentication. The second group controls the BTP destination setup and Principal Propagation. The third group is the server ceiling, and I would keep it conservative by default.&lt;/p&gt;&#xA;&lt;p&gt;For a development system, you may open selected writes:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cf set-env arc1-ecc-dev SAP_ALLOW_WRITES &lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cf set-env arc1-ecc-dev SAP_ALLOW_TRANSPORT_WRITES &lt;span class=&#34;nb&#34;&gt;true&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cf set-env arc1-ecc-dev SAP_ALLOWED_PACKAGES &lt;span class=&#34;s1&#34;&gt;&amp;#39;Z*,$TMP&amp;#39;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cf restage arc1-ecc-dev&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For production, I would usually keep ARC-1 read-only:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cf set-env arc1-ecc-prod SAP_ALLOW_WRITES &lt;span class=&#34;nb&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cf set-env arc1-ecc-prod SAP_ALLOW_FREE_SQL &lt;span class=&#34;nb&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cf set-env arc1-ecc-prod SAP_ALLOW_DATA_PREVIEW &lt;span class=&#34;nb&#34;&gt;false&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cf restage arc1-ecc-prod&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The recommended architecture is like the one-instance-per-SAP-system model. A DEV system can allow selected writes, a PROD system can be read-only, and a BTP ABAP system can have its own endpoint and tool behavior. This keeps policies easier to understand than one large multi-backend gateway.&lt;/p&gt;&#xA;&lt;h2 id=&#34;roles-and-technical-users&#34;&gt;Roles And Technical Users&lt;/h2&gt;&#xA;&lt;p&gt;ARC-1 ships XSUAA scopes like &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt;, &lt;code&gt;data&lt;/code&gt;, &lt;code&gt;sql&lt;/code&gt;, &lt;code&gt;transports&lt;/code&gt;, &lt;code&gt;git&lt;/code&gt;, and &lt;code&gt;admin&lt;/code&gt;. These are grouped into role collections such as &lt;code&gt;ARC-1 Viewer&lt;/code&gt;, &lt;code&gt;ARC-1 Developer&lt;/code&gt;, &lt;code&gt;ARC-1 Developer + Data&lt;/code&gt;, &lt;code&gt;ARC-1 Developer + SQL&lt;/code&gt;, and &lt;code&gt;ARC-1 Admin&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;For human developer usage, I would prefer XSUAA plus Principal Propagation. Then ARC-1 knows the MCP user, and SAP can also see the real SAP user. That gives a much better audit story.&lt;/p&gt;&#xA;&lt;p&gt;But not every use case needs Principal Propagation. For automation, scheduled checks, process agents, or a very controlled BTP Process Automation scenario, a technical user can be fine. The tradeoff just has to be clear: ARC-1 may know which token or client called it, but SAP will see the technical user. With Principal Propagation, SAP sees the real user.&lt;/p&gt;&#xA;&lt;h2 id=&#34;connecting-clients&#34;&gt;Connecting Clients&lt;/h2&gt;&#xA;&lt;p&gt;Once ARC-1 runs centrally, client configuration should become small. Ideally the developer configures only the MCP server URL, not SAP credentials:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-json&#34; data-lang=&#34;json&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nt&#34;&gt;&amp;#34;mcpServers&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nt&#34;&gt;&amp;#34;arc1-ecc-dev&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nt&#34;&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s2&#34;&gt;&amp;#34;https://arc1-ecc-dev.cfapps.eu10.hana.ondemand.com/mcp&amp;#34;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Clients with remote MCP and OAuth discovery can follow the XSUAA flow. ARC-1 exposes OAuth metadata and proxies the flow to XSUAA, so this can work for Claude, Cursor, VS Code-style MCP clients, and MCP Inspector.&lt;/p&gt;&#xA;&lt;p&gt;For Eclipse this becomes more concrete with &lt;a href=&#34;https://marketplace.eclipse.org/content/github-copilot&#34;&gt;GitHub Copilot for Eclipse&lt;/a&gt;. Eclipse ADT can stay the normal ABAP development environment, while Copilot can use MCP to call the same central ARC-1 endpoint. GitHub also added MCP OAuth support for Copilot in Eclipse, JetBrains, and Xcode, so this fits the BTP/XSUAA endpoint model much better than a local server on every laptop. ARC-1 still does not replace Eclipse. It gives the AI part a controlled SAP access path.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-i-would-verify-first&#34;&gt;What I Would Verify First&lt;/h2&gt;&#xA;&lt;p&gt;Before I would enable any write access, I would verify the boring things:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl https://arc1-ecc-dev.cfapps.eu10.hana.ondemand.com/health&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;curl https://arc1-ecc-dev.cfapps.eu10.hana.ondemand.com/.well-known/oauth-authorization-server&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then I would test a read-only MCP call and check the ARC-1 logs. The logs should show which authentication modes are active, for example XSUAA on the MCP side and Principal Propagation on the SAP side.&lt;/p&gt;&#xA;&lt;p&gt;Only after that I would enable writes, and only in steps:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;read-only -&amp;gt; writes to $TMP -&amp;gt; writes to selected packages -&amp;gt; transport writes&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is less exciting than a demo where the AI writes everything immediately, but it is much closer to how I think enterprise ABAP AI development should be introduced.&lt;/p&gt;&#xA;&lt;h2 id=&#34;where-to-go-deeper&#34;&gt;Where To Go Deeper&lt;/h2&gt;&#xA;&lt;p&gt;The architecture is the important part first, but the actual setup has many landscape-specific details.&lt;/p&gt;&#xA;&lt;p&gt;For the concrete steps, I would start with the &lt;a href=&#34;https://marianfoo.github.io/arc-1/phase4-btp-deployment/&#34;&gt;BTP Cloud Foundry deployment guide&lt;/a&gt;, then go through &lt;a href=&#34;https://marianfoo.github.io/arc-1/xsuaa-setup/&#34;&gt;XSUAA setup&lt;/a&gt;, &lt;a href=&#34;https://marianfoo.github.io/arc-1/btp-destination-setup/&#34;&gt;BTP destination setup&lt;/a&gt;, and &lt;a href=&#34;https://marianfoo.github.io/arc-1/principal-propagation-setup/&#34;&gt;Principal Propagation setup&lt;/a&gt; depending on the target landscape.&lt;/p&gt;&#xA;&lt;p&gt;For me the main point stays the same: ARC-1 on BTP is not just a nicer place to host a Node.js app. It is where the MCP server can become part of an enterprise SAP development architecture, with central access, controlled permissions, and a real identity story.&lt;/p&gt;&#xA;&lt;h2 id=&#34;discuss-this-post&#34;&gt;Discuss this post&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://saptodon.org/@Mianbsp/116490533476845801&#34;&gt;Saptodon&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bsky.app/profile/marian.zeis.de/post/3mkoa3dkw5c27&#34;&gt;Bluesky&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.linkedin.com/posts/marianzeis_arc-1-on-btp-is-about-one-simple-shift-agentic-activity-7455473576280817664-6oWh&#34;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;references--links&#34;&gt;References &amp;amp; links&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1 on GitHub&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/&#34;&gt;ARC-1 Documentation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/deployment/&#34;&gt;ARC-1 Deployment&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/phase4-btp-deployment/&#34;&gt;ARC-1 BTP Cloud Foundry Deployment&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/enterprise-auth/&#34;&gt;ARC-1 Enterprise Authentication&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/xsuaa-setup/&#34;&gt;ARC-1 XSUAA Setup&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/btp-destination-setup/&#34;&gt;ARC-1 BTP Destination Setup&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/principal-propagation-setup/&#34;&gt;ARC-1 Principal Propagation Setup&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/authorization/&#34;&gt;ARC-1 Authorization and Roles&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/btp-abap-environment/&#34;&gt;ARC-1 BTP ABAP Environment Setup&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/docs/btp/sap-business-technology-platform/multitarget-applications-in-cloud-foundry-environment&#34;&gt;SAP Help: Multitarget Applications in Cloud Foundry&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/docs/btp/sap-business-technology-platform/what-is-sap-authorization-and-trust-management-service&#34;&gt;SAP Help: Authorization and Trust Management Service&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://help.sap.com/docs/connectivity/sap-btp-connectivity-cf/authenticating-users-against-on-premise-systems&#34;&gt;SAP Help: Authenticating Users Against On-Premise Systems&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://sap.github.io/cloud-sdk/docs/js/features/connectivity/destinations&#34;&gt;SAP Cloud SDK: Destinations&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marketplace.eclipse.org/content/github-copilot&#34;&gt;GitHub Copilot for Eclipse&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.blog/changelog/2025-11-18-enhanced-mcp-oauth-support-for-github-copilot-in-jetbrains-eclipse-and-xcode&#34;&gt;GitHub Changelog: Enhanced MCP OAuth support for Copilot in Eclipse, JetBrains, and Xcode&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>Introducing ARC-1: A Secure ADT MCP Server for Enterprise SAP Development</title>
      <link>https://blog.zeis.de/posts/2026-04-27-arc-1/</link>
      <pubDate>Mon, 27 Apr 2026 09:00:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2026-04-27-arc-1/</guid>
      <description>&lt;p&gt;Series note: This post is part of my &lt;a href=&#34;https://blog.zeis.de/tags/ai-abap-development-series/&#34;&gt;AI ABAP development series&lt;/a&gt;, where I go from AI development in general, to ABAP-specific problems, and then to ARC-1.&lt;/p&gt;&#xA;&lt;p&gt;In the &lt;a href=&#34;https://blog.zeis.de/posts/2026-04-20-how-i-use-ai/&#34;&gt;first post of this series&lt;/a&gt;, I wrote about context and how I use AI in development. In the &lt;a href=&#34;https://blog.zeis.de/posts/2026-04-22-ai-abap-development/&#34;&gt;previous post&lt;/a&gt;, I then moved that discussion into ABAP and ended more or less with one question: what would an ADT MCP setup need to look like if you take control, identity, and security seriously from the beginning?&lt;/p&gt;&#xA;&lt;p&gt;This post is my current answer to that question. It is called &lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1&lt;/a&gt; (ABAP Relay Connector, pronounced arc one [ɑːrk wʌn]), and yes, it is another ADT MCP server. But the important difference is not that it can talk to ADT at all. Other projects already showed very well that this is possible and useful. The difference is the architecture and the focus, which is also why the &lt;a href=&#34;https://marianfoo.github.io/arc-1/&#34;&gt;ARC-1 documentation&lt;/a&gt; spends a lot of space on security, authentication, deployment, and operations, not only on the tool list.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-arc-1-had-to-exist&#34;&gt;Why ARC-1 had to exist&lt;/h2&gt;&#xA;&lt;p&gt;Luckily, the community already did a lot of the hard work before ARC-1. &lt;a href=&#34;https://github.com/marcellourbani/abap-adt-api&#34;&gt;Marcello Urbani&amp;rsquo;s abap-adt-api&lt;/a&gt; gave many TypeScript projects the foundation to work with ADT APIs at all, which are still not really documented in a useful way. Projects like &lt;a href=&#34;https://github.com/oisee/vibing-steampunk&#34;&gt;oisee/vibing-steampunk&lt;/a&gt;, &lt;a href=&#34;https://github.com/fr0ster/mcp-abap-adt&#34;&gt;fr0ster/mcp-abap-adt&lt;/a&gt;, and &lt;a href=&#34;https://github.com/DassianInc/dassian-adt&#34;&gt;DassianInc/dassian-adt&lt;/a&gt; already proved that MCP and ABAP development can work in practice and can be very useful. And &lt;a href=&#34;https://github.com/lemaiwo/btp-sap-odata-to-mcp-server&#34;&gt;Wouter Lemaire&amp;rsquo;s btp-sap-odata-to-mcp-server&lt;/a&gt; was also important for me, because it already showed a BTP-deployed and centrally managed direction much earlier, and Wouter also helped me during the ARC-1 implementation.&lt;/p&gt;&#xA;&lt;p&gt;So ARC-1 is not me claiming that nobody solved this before. For me it is more this: the others already proved the technical possibility, and ARC-1 is my attempt to close the enterprise gap. That gap is the same one I described in the last post. In ABAP, the hard part is not only getting context into the model. It is that context access, write access, and control are tied very closely together in a real SAP landscape.&lt;/p&gt;&#xA;&lt;p&gt;That is why I did not want to build just another local developer tool that happens to speak MCP. I wanted to build a secure ADT MCP server with a clearer security concept, more central control, and an architecture that fits better into enterprise SAP environments.&lt;/p&gt;&#xA;&lt;h2 id=&#34;central-instead-of-local&#34;&gt;Central instead of local&lt;/h2&gt;&#xA;&lt;p&gt;For me this is probably the biggest architectural difference. Most current MCP ADT setups are still mainly local. The server runs on the developer laptop, gets configured there, stores credentials there, and then talks directly to the SAP system from there.&lt;/p&gt;&#xA;&lt;p&gt;That is fine for trying things out. It is also fine for fast experimentation. And to be clear, ARC-1 can also run locally. That is useful for tests, local development, and simply trying the server out. But local developer setup is not the main story I care about here. The main story is central deployment.&lt;/p&gt;&#xA;&lt;p&gt;That means one managed ARC-1 instance per SAP system, with central configuration, central security settings, central logging, and a clear operational owner. That can run on a company server, in Docker, or ideally on SAP BTP Cloud Foundry. The &lt;a href=&#34;https://marianfoo.github.io/arc-1/architecture/&#34;&gt;architecture documentation&lt;/a&gt; goes into the technical flow in more detail, but I will go much deeper into the BTP side in the next post, because that deserves its own post.&lt;/p&gt;&#xA;&lt;p&gt;For me BTP is especially interesting here because it lets you reuse things that already exist in many companies anyway: XSUAA, destinations, Cloud Connector, audit services, role assignment, and the usual BTP login flow. Then the developer mostly just needs the MCP URL and the standard login, while admins and authorization teams keep control in the place where they already work.&lt;/p&gt;&#xA;&lt;p&gt;I also do not think this is only an SAP-specific thought. Salesforce is already moving in a similar direction with &lt;a href=&#34;https://developer.salesforce.com/docs/platform/hosted-mcp-servers/guide/hosted-mcp-servers-overview.html&#34;&gt;hosted MCP servers&lt;/a&gt; and &lt;a href=&#34;https://www.salesforce.com/news/stories/salesforce-headless-360-announcement/&#34;&gt;Headless 360&lt;/a&gt;, where more of the platform is exposed as APIs, MCP tools, or CLI commands. To me that confirms that enterprise MCP is a platform and governance topic, not only a developer convenience topic.&lt;/p&gt;&#xA;&lt;p&gt;The reason this matters is simple. If the MCP server sits only on the laptop of an individual developer, then the architecture is also controlled there. That is exactly the part I do not find convincing for enterprise usage.&lt;/p&gt;&#xA;&lt;h2 id=&#34;two-auth-hops-matter&#34;&gt;Two auth hops matter&lt;/h2&gt;&#xA;&lt;p&gt;One thing that became very clear while building ARC-1 is that there are really two completely different authentication questions. The first one is who is allowed to talk to the MCP server at all. The second one is as which user the MCP server talks to the SAP system.&lt;/p&gt;&#xA;&lt;p&gt;That sounds obvious, but many discussions around MCP servers blur those two things together. In ARC-1, these are treated as two separate hops: &lt;code&gt;MCP client -&amp;gt; ARC-1&lt;/code&gt; and &lt;code&gt;ARC-1 -&amp;gt; SAP&lt;/code&gt;. This matters because the architecture becomes much clearer after that.&lt;/p&gt;&#xA;&lt;p&gt;For the first hop, ARC-1 can use things like API keys, OIDC/JWT, or &lt;a href=&#34;https://marianfoo.github.io/arc-1/enterprise-auth/&#34;&gt;XSUAA OAuth&lt;/a&gt;, depending on how and where it is deployed. For the second hop, ARC-1 can talk to SAP with Basic Auth, BTP service keys, or with &lt;a href=&#34;https://marianfoo.github.io/arc-1/principal-propagation-setup/&#34;&gt;Principal Propagation&lt;/a&gt;, depending on the target landscape.&lt;/p&gt;&#xA;&lt;p&gt;That split is very important for enterprise usage, because it lets you separate client access control from SAP identity handling instead of hiding both behind one local config file. It also makes it easier to support different use cases. If ARC-1 is used more like a backend connector for an automation, an API key may be totally fine. If real per-user identity matters, then OAuth or XSUAA plus Principal Propagation is the much more interesting setup.&lt;/p&gt;&#xA;&lt;h2 id=&#34;safe-by-default-not-allow-all-by-default&#34;&gt;Safe by default, not allow all by default&lt;/h2&gt;&#xA;&lt;p&gt;The next big difference is that ARC-1 starts from the assumption that the default should be restrictive. Out of the box ARC-1 is read-only. It blocks free SQL. It blocks named table preview. It does not expose transport actions unless those are enabled. And even if writing is enabled, writes are still restricted to &lt;code&gt;$TMP&lt;/code&gt; unless packages are explicitly allowed.&lt;/p&gt;&#xA;&lt;p&gt;That last point is important to me. Yes, it makes the first setup a bit less magical. Sometimes you need to configure one more thing. But I prefer that over accidentally giving an AI assistant full write access to transportable packages because one flag was too broad.&lt;/p&gt;&#xA;&lt;p&gt;ARC-1 also has operation allowlists and denylists, package restrictions, and safety profiles like &lt;code&gt;viewer&lt;/code&gt;, &lt;code&gt;viewer-sql&lt;/code&gt;, or &lt;code&gt;developer&lt;/code&gt;. So there is not only one on or off switch. For SAP development that feels much more realistic to me than a server that starts by allowing everything and then hopes people will restrict it later.&lt;/p&gt;&#xA;&lt;h2 id=&#34;layered-security-not-just-one-switch&#34;&gt;Layered security, not just one switch&lt;/h2&gt;&#xA;&lt;p&gt;This also leads to the next point. For me, security here is not one boolean setting. It is several layers on top of each other.&lt;/p&gt;&#xA;&lt;p&gt;At the ARC-1 level there is the server safety configuration itself: read-only mode, blocked SQL, package restrictions, operation filters, transport gates. Then there is the identity and role layer on top, for example via OIDC or XSUAA scopes and roles. And then there is still the SAP system itself with its own authorization objects and checks.&lt;/p&gt;&#xA;&lt;p&gt;That means all of these layers have to allow something before it actually happens. If ARC-1 is configured read-only, then even a user with a more powerful role still cannot write. If a user has write scope in ARC-1, but the SAP backend authorization is missing, the write still fails.&lt;/p&gt;&#xA;&lt;p&gt;That is exactly how I want it. ARC-1 should not replace SAP authorization. It should add another control layer in front of it.&lt;/p&gt;&#xA;&lt;h2 id=&#34;per-user-identity-matters&#34;&gt;Per-user identity matters&lt;/h2&gt;&#xA;&lt;p&gt;This becomes even more interesting once you look at per-user identity. One of the things I wanted very early is that ARC-1 should not force everything through one shared technical SAP user if that can be avoided.&lt;/p&gt;&#xA;&lt;p&gt;That is why &lt;a href=&#34;https://marianfoo.github.io/arc-1/principal-propagation-setup/&#34;&gt;Principal Propagation&lt;/a&gt; is such an important part of the architecture for me. If Principal Propagation is active, the MCP user identity can flow through ARC-1 and into SAP, so that the action runs with the real SAP user and the real SAP authorization of that person. I already tested this and it works.&lt;/p&gt;&#xA;&lt;p&gt;That is important not only because it is cleaner technically. It is important because it fits much better into audit, compliance, and real enterprise authorization models. I do not want the whole architecture to end in &amp;ldquo;trust this one service account and hope nobody asks too many questions later&amp;rdquo;. I want the system to be able to say who actually did what.&lt;/p&gt;&#xA;&lt;h2 id=&#34;audit-logging-is-part-of-the-feature-not-an-afterthought&#34;&gt;Audit logging is part of the feature, not an afterthought&lt;/h2&gt;&#xA;&lt;p&gt;The same applies to audit logging. If AI is allowed to read from or write to a real SAP system, then logging is not some optional extra feature. It is part of the basic architecture.&lt;/p&gt;&#xA;&lt;p&gt;ARC-1 emits structured audit events and can integrate with the &lt;a href=&#34;https://marianfoo.github.io/arc-1/security-guide/&#34;&gt;BTP Audit Log Service&lt;/a&gt;. That means you can log who called which tool, what happened, and in which request context it happened. Especially on BTP, that gives you a much better compliance story than a local MCP server that mostly lives on one laptop.&lt;/p&gt;&#xA;&lt;p&gt;For normal hobby tooling this might feel like overkill. For SAP it does not. Especially if the recommended target architecture is BTP, it makes sense to also use the established platform services for logging and identity instead of building a parallel side world around them.&lt;/p&gt;&#xA;&lt;h2 id=&#34;not-locked-into-one-client&#34;&gt;Not locked into one client&lt;/h2&gt;&#xA;&lt;p&gt;This point is also important to me personally. There are already many useful prompt files, local plugins, slash commands, and client-specific workflows around SAP and ABAP. I do not think that is wrong. A lot of that work is useful and practical.&lt;/p&gt;&#xA;&lt;p&gt;But that is still a different architecture. Very often the logic, the access, and the credentials live mainly on one laptop and inside one AI client. For enterprise usage I want the opposite.&lt;/p&gt;&#xA;&lt;p&gt;I want one secure ADT MCP server that can be used from many clients. That can be Claude, &lt;a href=&#34;https://github.com/features/copilot&#34;&gt;GitHub Copilot&lt;/a&gt;, Copilot Studio, IDEs, or later other MCP clients that also support OAuth. In the best case, that can then be combined with Principal Propagation instead of local SAP credentials.&lt;/p&gt;&#xA;&lt;p&gt;That flexibility matters, because in many companies the default AI developer tooling will not be Claude Code. It will more likely be GitHub Copilot or other centrally managed tools. And I do not want the SAP side of the architecture to depend on one specific AI client. MCP is useful exactly because it decouples those things.&lt;/p&gt;&#xA;&lt;h2 id=&#34;testing-matters-more-here-than-for-normal-dev-tooling&#34;&gt;Testing matters more here than for normal dev tooling&lt;/h2&gt;&#xA;&lt;p&gt;I also want to stress the testing part, because for a project like this it matters a lot.&lt;/p&gt;&#xA;&lt;p&gt;If an MCP server only works in one perfect demo system, then that is not enough. SAP systems are messy. Different releases behave differently. Different endpoints exist or do not exist. BTP ABAP is different again. And error handling around ADT is not always beautiful.&lt;/p&gt;&#xA;&lt;p&gt;That is why ARC-1 puts a lot of effort into testing. Right now the project has more than 1,300 unit tests, plus integration tests and E2E tests that execute real MCP tool calls against live SAP systems. As a freelancer I obviously do not have access to every possible enterprise landscape, but I do test against real systems. I test against BTP ABAP, the ABAP trial system in Docker, and the older 7.50 trial system.&lt;/p&gt;&#xA;&lt;p&gt;I also do not test only with one ideal model and one toy example. I try it myself with stronger and weaker models and with workflows that are much closer to reality. That includes things like clean core analysis or creating a full RAP project from nothing, not even a table, up to an activated RAP project you can actually use. A lot of the workflows and feature ideas behind that are also visible in the &lt;a href=&#34;https://github.com/marianfoo/arc-1/blob/main/skills/README.md&#34;&gt;ARC-1 skills catalog&lt;/a&gt;, because those are more or less the kinds of use cases I want to make work reliably.&lt;/p&gt;&#xA;&lt;p&gt;For me that is also one of the big lessons from working with SAP tooling in general: if you do not test against real systems, you often only test your assumptions.&lt;/p&gt;&#xA;&lt;h2 id=&#34;so-yes-this-is-another-adt-mcp-server&#34;&gt;So yes, this is another ADT MCP server&lt;/h2&gt;&#xA;&lt;p&gt;And that is fine. I do not think ARC-1 needs to exist because every other project is wrong. Quite the opposite. The other projects created the category, pushed the ecosystem forward, and showed there is real interest.&lt;/p&gt;&#xA;&lt;p&gt;At the same time I think ARC-1 is different enough in architecture and focus that it makes sense right now. It is especially different from the angle of secure defaults, central deployment, layered security, per-user identity, and auditability.&lt;/p&gt;&#xA;&lt;p&gt;And yes, it may also differentiate itself from the future official SAP approach depending on how that evolves. As of SAP&amp;rsquo;s &lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/introducing-the-next-era-of-abap-development/ba-p/14260522&#34;&gt;November 4, 2025 announcement about the next era of ABAP development&lt;/a&gt;, SAP plans an official ABAP MCP server for Q2 2026 together with the ABAP language server and related SDK pieces in VS Code. I think that is good news.&lt;/p&gt;&#xA;&lt;p&gt;Actually, that is also the point where building yet another ADT MCP server becomes harder to justify long term. If SAP publishes the language server and SDK in a reusable way, then the right move would be to reuse the same foundation that the official SAP MCP server and Eclipse tooling use instead of reimplementing more and more HTTP details on the community side.&lt;/p&gt;&#xA;&lt;p&gt;If SAP ends up shipping a server with a similar architectural direction, broad system coverage, and the same kind of enterprise features, then there may eventually be no strong reason for ARC-1 to continue. For me that would still be a win.&lt;/p&gt;&#xA;&lt;p&gt;The point is not that my server must win. The point is that there is clearly a gap today, the community is filling it, and competition is usually good. It helps people choose, it helps ideas spread, and ideally it also helps SAP learn from what the community already built. Until then, people can decide on their own if ARC-1 is useful for them.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-note-on-adt-apis-and-saps-new-api-policy&#34;&gt;A note on ADT APIs and SAP&amp;rsquo;s new API policy&lt;/h2&gt;&#xA;&lt;p&gt;There is one more point worth separating from the architecture discussion. SAP published a new API policy in April 2026, and some practical implications still need clarification, especially for SAP customers, partners, and community tools that build on top of SAP development APIs.&lt;/p&gt;&#xA;&lt;p&gt;For ADT, the publicly available material points to a more nuanced situation. SAP provides an official ADT SDK with JavaDoc, and the ADT download page describes it as a public API to implement or integrate your own tools with SAP&amp;rsquo;s ABAP IDE. There is also an SAP document about creating and consuming RESTful APIs in ADT. This does not imply that every internal &lt;code&gt;/sap/bc/adt&lt;/code&gt; endpoint is covered by the same support boundary, but it does show that parts of the ADT tooling and communication model are documented and intended for integration scenarios.&lt;/p&gt;&#xA;&lt;p&gt;This is also relevant to what SAP wrote in the discussion around the new ABAP Development Tools for VS Code. SAP described the ABAP language server as something like an &amp;ldquo;ADT SDK 2.0&amp;rdquo; and mentioned plans to release it as a standalone component after the first VS Code extension release. A standalone release could make the support boundaries clearer for community tools, CLIs, MCP servers, and other integrations. Until more detailed guidance is available, the main point is to track SAP&amp;rsquo;s policy and documentation closely and adjust implementations when specific activities are clarified as unsupported or not permitted.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-this-matters-for-the-next-post&#34;&gt;Why this matters for the next post&lt;/h2&gt;&#xA;&lt;p&gt;In this post I mainly wanted to explain why ARC-1 exists at all and why it looks the way it does.&lt;/p&gt;&#xA;&lt;p&gt;The next post is where I want to go deeper into the BTP side, because that is where the architecture becomes much more interesting in practice: XSUAA, Destination Service, Cloud Connector, Principal Propagation, and how this can fit into a real enterprise setup instead of just a developer laptop.&lt;/p&gt;&#xA;&lt;h2 id=&#34;discuss-this-post&#34;&gt;Discuss this post&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://saptodon.org/@Mianbsp/116477784495254338&#34;&gt;Saptodon&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bsky.app/profile/marian.zeis.de/post/3mkikvj4aos2k&#34;&gt;Bluesky&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.linkedin.com/posts/marianzeis_introducing-arc-1-an-enterprise-adt-mcp-activity-7454748910893457408-M8BU&#34;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;references--links&#34;&gt;References &amp;amp; links&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1&#34;&gt;ARC-1 on GitHub&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/&#34;&gt;ARC-1 Documentation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/architecture/&#34;&gt;ARC-1 Architecture&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/enterprise-auth/&#34;&gt;ARC-1 Authentication Overview&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://marianfoo.github.io/arc-1/security-guide/&#34;&gt;ARC-1 Security Guide&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.zeis.de/posts/2026-04-20-how-i-use-ai/&#34;&gt;How I Use AI for Development and Why Context Matters&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.zeis.de/posts/2026-04-22-ai-abap-development/&#34;&gt;ABAP and Agentic AI: The Hidden Problem in Real Projects&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marcellourbani/abap-adt-api&#34;&gt;Marcello Urbani: abap-adt-api&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/lemaiwo/btp-sap-odata-to-mcp-server&#34;&gt;Wouter Lemaire: btp-sap-odata-to-mcp-server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/oisee/vibing-steampunk&#34;&gt;oisee/vibing-steampunk&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/fr0ster/mcp-abap-adt&#34;&gt;fr0ster/mcp-abap-adt&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/DassianInc/dassian-adt&#34;&gt;DassianInc/dassian-adt&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://github.com/marianfoo/arc-1/blob/main/skills/README.md&#34;&gt;ARC-1 Skills Catalog&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/introducing-the-next-era-of-abap-development/ba-p/14260522&#34;&gt;Introducing the Next Era of ABAP Development&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.sap.com/documents/2026/04/dce9aee4-497f-0010-bca6-c68f7e60039b.html&#34;&gt;SAP API Policy&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://tools.hana.ondemand.com/#abap&#34;&gt;ABAP Development Tools SDK&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.sap.com/documents/2013/04/12289ce1-527c-0010-82c7-eda71af511fa.html&#34;&gt;Create and Consume RESTful APIs in ADT&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/abap-development-tools-for-vs-code-everything-you-need-to-know/bc-p/14263439/highlight/true#M186133&#34;&gt;ABAP Development Tools for VS Code discussion&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>ABAP and Agentic AI: The Hidden Problem in Real Projects</title>
      <link>https://blog.zeis.de/posts/2026-04-22-ai-abap-development/</link>
      <pubDate>Wed, 22 Apr 2026 09:00:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2026-04-22-ai-abap-development/</guid>
      <description>&lt;p&gt;Series note: This post is part of my &lt;a href=&#34;https://blog.zeis.de/tags/ai-abap-development-series/&#34;&gt;AI ABAP development series&lt;/a&gt;, where I go from AI development in general, to ABAP-specific problems, and then to ARC-1.&lt;/p&gt;&#xA;&lt;p&gt;In the &lt;a href=&#34;https://blog.zeis.de/posts/2026-04-20-how-i-use-ai/&#34;&gt;first post of this series&lt;/a&gt;, I wrote more generally about how I use AI today, why context matters so much, and why I am still not fully convinced by the more maximalist agentic hype.&lt;/p&gt;&#xA;&lt;p&gt;Here I want to get more concrete.&#xA;Because SAP development is not one thing, and that matters a lot for AI.&lt;/p&gt;&#xA;&lt;p&gt;There is a big difference between UI5 or CAP development with AI and ABAP development with AI. If I mix those together, then the whole discussion becomes too vague very quickly.&lt;/p&gt;&#xA;&lt;p&gt;For me this comes down to three questions:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;How does the model get the needed context?&lt;/li&gt;&#xA;&lt;li&gt;Where does the writing happen?&lt;/li&gt;&#xA;&lt;li&gt;Who is actually in control of that setup?&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;I want to answer those questions in this post.&lt;/p&gt;&#xA;&lt;h2 id=&#34;sap-development-is-not-one-thing&#34;&gt;SAP development is not one thing&lt;/h2&gt;&#xA;&lt;p&gt;When people talk about &amp;ldquo;AI in SAP development&amp;rdquo;, a lot of very different things get mixed together.&lt;/p&gt;&#xA;&lt;p&gt;UI5, Fiori, CAP, BTP extensions, ABAP Cloud, ABAP on a shared dev system, Joule in Eclipse, MCP servers, local git workflows, sandbox systems, custom code migration. All of that gets thrown into one big AI bucket.&lt;/p&gt;&#xA;&lt;p&gt;But these are not the same problem.&#xA;The reason I want to split this early is simple: some SAP development areas already fit quite naturally into AI assisted workflows, and some really do not.&lt;/p&gt;&#xA;&lt;p&gt;If I do not separate that clearly, then the discussion becomes too vague very quickly.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-ui5-and-cap-already-feel-much-more-natural&#34;&gt;Why UI5 and CAP already feel much more natural&lt;/h2&gt;&#xA;&lt;p&gt;For frontend work like UI5, Fiori, CAP, or generally JS and TS based development, AI usage already feels much more normal. The development is usually off stack. You work in local files, use git, open pull requests, run CI/CD pipelines, let tests run, get peer review, and only then move the change further. If AI does something weird, it is annoying, but usually not catastrophic, because there are already good gates in this ideal process.&lt;/p&gt;&#xA;&lt;p&gt;That is also why GitHub Copilot in VS Code became pretty normal in a lot of companies. If you combine that with good context, for example docs from SAP Help or SAP Docs through MCP, then you can already move quite fast today. I use my own &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;sap-mcp-docs server&lt;/a&gt; a lot for exactly that. SAP also moved in that direction with MCP servers for &lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/give-your-ai-agent-some-tools-introducing-the-ui5-mcp-server/bc-p/14206959&#34;&gt;UI5&lt;/a&gt;, &lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/boost-your-cap-development-with-ai-introducing-the-mcp-server-for-cap/ba-p/14202849&#34;&gt;CAP&lt;/a&gt;, and &lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/sap-fiori-tools-update-first-release-of-the-sap-fiori-mcp-server-for/ba-p/14204694&#34;&gt;Fiori&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;In that environment the three questions above are much easier to answer:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Context comes from local files, documentation, and existing dev tooling.&lt;/li&gt;&#xA;&lt;li&gt;Writing happens locally, not directly in a shared SAP system.&lt;/li&gt;&#xA;&lt;li&gt;Control already exists through git, reviews, tests, and pipelines.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;So the main question there is often more about workflow quality than about technical possibility.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-abap-is-structurally-harder&#34;&gt;Why ABAP is structurally harder&lt;/h2&gt;&#xA;&lt;p&gt;In the ABAP world things look different. The two main problems are the architecture and the tooling.&lt;/p&gt;&#xA;&lt;p&gt;ABAP development is usually on stack. That means if you develop on the dev system, every change is directly applied to that shared system and can affect other developers as well. Even without AI that is already something you have to be careful with. With AI it becomes more sensitive, because a wrong write action is not just a wrong local file edit.&lt;/p&gt;&#xA;&lt;p&gt;The second problem is tooling. A lot of ABAP developers still use SE80. Eclipse has been there for a long time, but if you were never forced into newer development models like RAP, a lot of people just stayed where they were. And if your main world is still SE80, then there is not really much serious AI support there.&lt;/p&gt;&#xA;&lt;p&gt;So if I compare UI5 and ABAP, the problem is not only &amp;ldquo;does the model know ABAP&amp;rdquo;. The bigger question is in which environment this happens at all, and what happens if the model is wrong.&lt;/p&gt;&#xA;&lt;p&gt;That is also why I think the SAP Community post &lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/the-agentic-revolution-is-here-and-your-abap-code-is-the-foundation/ba-p/14358726&#34;&gt;The agentic revolution is here, and your ABAP code is the foundation&lt;/a&gt; fits quite well here.&lt;/p&gt;&#xA;&lt;p&gt;The point there is not really that agents replace ABAP. It is more that if agents are going to call into SAP systems, then your ABAP foundation matters even more. Interfaces, CDS views, checks, naming, error handling, package structure, all of that becomes important.&lt;/p&gt;&#xA;&lt;p&gt;The better and clearer your ABAP foundation is, the more useful and safer the agent layer on top can become, because  they use your ABAP code as the foundation for their work.&lt;/p&gt;&#xA;&lt;p&gt;Model choice still matters, of course. I already wrote two separate posts around &lt;a href=&#34;https://blog.zeis.de/posts/2026-02-09-abap-llm-benchmark/&#34;&gt;ABAP code generation&lt;/a&gt; and &lt;a href=&#34;https://blog.zeis.de/posts/2026-03-05-abap-llm-benchmark-understanding/&#34;&gt;ABAP understanding&lt;/a&gt;, and my practical takeaway is still that strong general models together with better tooling and grounding are currently the better path than hoping one SAP specific model alone solves the problem. But even with a better model, the structural ABAP problem stays the same: on stack writes, shared systems, and weak control do not disappear. Frontier models are just better to handle more complex problems as with complexity comes usually a larger context to handle, and frontier models are usually better at that.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-three-questions-matter-more-than-the-hype&#34;&gt;The three questions matter more than the hype&lt;/h2&gt;&#xA;&lt;p&gt;I think this is also why &lt;a href=&#34;https://simonwillison.net/guides/agentic-engineering-patterns/&#34;&gt;Agentic Engineering Patterns&lt;/a&gt; from Simon Willison fits so well here.&lt;/p&gt;&#xA;&lt;p&gt;What I like there is that &amp;ldquo;agentic&amp;rdquo; is not treated like some magical category. It is more a set of working patterns: tools in a loop, tests, boundaries, anti-patterns, habits, and when not to let the loop run too far. For ABAP this matters even more, because the question is not only if an agent can do something, but what kind of loop you actually allow it to run against a real SAP system.&lt;/p&gt;&#xA;&lt;p&gt;So for every pattern below I want to keep asking the same three questions:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;How does the model get context?&lt;/li&gt;&#xA;&lt;li&gt;Where does writing happen?&lt;/li&gt;&#xA;&lt;li&gt;Who controls that setup?&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;current-patterns-for-ai-assisted-abap-development&#34;&gt;Current patterns for AI assisted ABAP development&lt;/h2&gt;&#xA;&lt;p&gt;Lars Hvam wrote a very useful paper on the &lt;a href=&#34;https://github.com/heliconialabs/patterns-for-using-llms-in-abap-development&#34;&gt;patterns for using LLMs in ABAP development&lt;/a&gt;. You can read more details and see diagrams there.&lt;/p&gt;&#xA;&lt;p&gt;I like that framing because it avoids the fake discussion of &amp;ldquo;one right way&amp;rdquo;. There is not one right way. There are different patterns with different tradeoffs depending on your landsacpe, knowledge, and setup.&lt;/p&gt;&#xA;&lt;h3 id=&#34;1-ide-integrated-ai-in-eclipse&#34;&gt;1. IDE integrated AI in Eclipse&lt;/h3&gt;&#xA;&lt;p&gt;This is the most obvious option. You stay where you already work and let the built in AI features help with explanations, code suggestions, tests, and smaller implementation tasks. Depending on landscape and setup, that could mean &lt;a href=&#34;https://www.sap.com/products/artificial-intelligence/joule-for-developers.html&#34;&gt;Joule for Developers&lt;/a&gt;, &lt;a href=&#34;https://marketplace.eclipse.org/content/github-copilot&#34;&gt;GitHub Copilot in Eclipse&lt;/a&gt;, or &lt;a href=&#34;https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/q-in-IDE-setup.html&#34;&gt;Amazon Q Developer for Eclipse&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Context:&lt;/strong&gt; Mostly the currently selected SAP object plus what the IDE integration can access or what you add manually.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Writing:&lt;/strong&gt; Directly to the SAP system.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Control:&lt;/strong&gt; Mostly on the developer side, not much central boundary around the agent loop itself.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This is easy to adopt as &amp;ldquo;quick win&amp;rdquo;, but it does not solve the core architecture problem.&lt;/p&gt;&#xA;&lt;h3 id=&#34;2-vs-code-today-mostly-with-abap-remote-fs-and-soon-also-with-saps-own-extension&#34;&gt;2. VS Code, today mostly with ABAP Remote FS and soon also with SAP&amp;rsquo;s own extension&lt;/h3&gt;&#xA;&lt;p&gt;This gives a much better AI developer experience because VS Code is simply ahead in that area compared to Eclipse. Today that usually means community tooling like ABAP Remote FS. But SAP&amp;rsquo;s own &lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/introducing-the-next-era-of-abap-development/ba-p/14260522&#34;&gt;ABAP Cloud Extension for VS Code&lt;/a&gt; is planned for Q2 2026, so likely around May if that timeline holds. That means ABAP Remote FS is probably more of a bridge than the long term answer, at least for the scenarios SAP will cover first.&lt;/p&gt;&#xA;&lt;p&gt;The problem stays similar though. If the setup writes back directly through ADT APIs, then the nicer editor experience still does not remove the main risk.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Context:&lt;/strong&gt; Better local editor experience, but still tied to what the tool can read from the system.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Writing:&lt;/strong&gt; Still directly to the SAP system.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Control:&lt;/strong&gt; Still relatively weak if the whole setup sits with the individual developer.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;So the nicer developer experience does not remove the main risk.&lt;/p&gt;&#xA;&lt;h3 id=&#34;3-adt-mcp-server&#34;&gt;3. ADT MCP Server&lt;/h3&gt;&#xA;&lt;p&gt;This is where things get really interesting.&lt;/p&gt;&#xA;&lt;p&gt;With an ADT MCP server the AI does not just see files. It gets structured tools. It can read objects, search code, inspect context, run checks, navigate references, write changes, etc., depending on what the server allows.&lt;/p&gt;&#xA;&lt;p&gt;This is also currently one of the best ways to solve the context problem.&lt;/p&gt;&#xA;&lt;p&gt;I already wrote earlier about this direction in &lt;a href=&#34;https://blog.zeis.de/posts/2026-02-04-abap-mcp-server/&#34;&gt;Finally: An MCP Server for ABAP&lt;/a&gt;, and since then the space has grown a lot. That is also why I keep &lt;a href=&#34;https://blog.zeis.de/posts/2026-03-05-sap-ai-mcp-servers-overview/&#34;&gt;SAP MCP Servers: The Missing Overview&lt;/a&gt; around, because the number of SAP MCP servers and adjacent tools is growing quickly enough that it is easy to lose track.&lt;/p&gt;&#xA;&lt;p&gt;The AI can say: I have a problem in this class, now get me the class, then maybe read the related table, check another class, search similar code, maybe combine that with SAP documentation from another MCP server like &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;sap-mcp-docs&lt;/a&gt;, and build up the context automatically. That is really useful.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Context:&lt;/strong&gt; Very strong, because tools can gather much richer SAP context such as objects, data, and logs.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Writing:&lt;/strong&gt; Potentially directly to the SAP system.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Control:&lt;/strong&gt; Depends entirely on how the MCP server is designed, configured, and deployed.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This is exactly why the MCP path is so interesting and so dangerous at the same time.&lt;/p&gt;&#xA;&lt;p&gt;It solves a real problem, but it also raises the control question much more sharply. It is also why SAP&amp;rsquo;s post on &lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/standardizing-ai-tool-integration-with-mcp-part-2-implementing-mcp-with-sap/ba-p/14359181&#34;&gt;Standardizing AI Tool Integration with MCP&lt;/a&gt; matters. MCP in SAP is not just some hobby side path anymore. It is becoming part of the real architectural conversation.&lt;/p&gt;&#xA;&lt;h3 id=&#34;4-sandbox-systems&#34;&gt;4. Sandbox systems&lt;/h3&gt;&#xA;&lt;p&gt;In my opinion this is one of the safest options.&lt;/p&gt;&#xA;&lt;p&gt;Give the AI a sandbox system that can be isolated, reset, or recreated, and suddenly much more becomes possible without damaging the shared dev system.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Context:&lt;/strong&gt; Very strong, because the AI can still inspect and work against a real SAP system.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Writing:&lt;/strong&gt; Direct, but only into an isolated system.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Control:&lt;/strong&gt; Much better than shared-system direct access, but with infrastructure cost and operational overhead.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This is a very good pattern if you can afford it. The problem is that most teams do not have this luxury.&lt;/p&gt;&#xA;&lt;h3 id=&#34;5-git-or-off-stack-development-with-abapgit-and-cicd&#34;&gt;5. Git or off stack development with abapGit and CI/CD&lt;/h3&gt;&#xA;&lt;p&gt;In many ways this is the cleanest approach.&lt;/p&gt;&#xA;&lt;p&gt;The AI never writes to the SAP system directly. It writes local files, then git, review, checks, and pipeline steps become the gate.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Context:&lt;/strong&gt; Depends on what additional read access you give the AI.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Writing:&lt;/strong&gt; Local first, not directly on the SAP system.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Control:&lt;/strong&gt; Much stronger, because review and automation become the boundary.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;This is how a lot of frontend development already works, and it is much easier to trust.&lt;/p&gt;&#xA;&lt;p&gt;The problem is that many ABAP teams are simply not there yet. If there is no clear package separation, no git workflow, and no pipeline culture, then introducing this only for AI becomes a bigger transformation project.&lt;/p&gt;&#xA;&lt;h3 id=&#34;6-hybrid-approach&#34;&gt;6. Hybrid approach&lt;/h3&gt;&#xA;&lt;p&gt;This is the pattern I find currently the most practical long term.&lt;/p&gt;&#xA;&lt;p&gt;The AI works locally or off stack, but still gets read access to the SAP system through ADT or MCP for context.&lt;/p&gt;&#xA;&lt;p&gt;That means it can understand the actual system better, but the writing still happens in a gated git flow.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Context:&lt;/strong&gt; Strong.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Writing:&lt;/strong&gt; Gated.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Control:&lt;/strong&gt; Much better than direct write access, while still preserving real system context.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;For me this is one of the most promising directions.&lt;/p&gt;&#xA;&lt;h2 id=&#34;read-access-already-gives-a-lot-of-value&#34;&gt;Read access already gives a lot of value&lt;/h2&gt;&#xA;&lt;p&gt;One thing I find important here is that AI does not need direct write access to be useful. For many real problems, reading and analysis can already take away maybe 80 percent of the work. If an issue comes in, the first question is usually not &amp;ldquo;who writes the line of code&amp;rdquo;, but &amp;ldquo;where is the actual problem&amp;rdquo;. If the AI can analyse the system, narrow it down, read code, data and logs, suggest where the bug is, and maybe propose the one line change, then the developer can still do that actual line manually. Or the AI writes the proposed fix and the developer still copy pastes it, reviews it properly, and tests it. That alone is already a lot of support.&lt;/p&gt;&#xA;&lt;p&gt;And this read access is not only useful for developers who want to code faster. It can also help people working more on architecture, specifications, analysis, testing, and migration. If they can read how the system works, where logic is implemented, how much something is used, or what the dependencies are, then that already helps a lot, for changes, fixes and the transformation from ECC to S/4HANA.&lt;/p&gt;&#xA;&lt;p&gt;That is also why I do not really buy the simple management idea that you just give everyone Copilot or an Agentic AI coding tool and suddenly everyone codes much faster. That is not the real point.&lt;/p&gt;&#xA;&lt;p&gt;The bigger question for me is how to help ABAP developers and SAP teams in their daily work with things like:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;faster problem analysis&lt;/li&gt;&#xA;&lt;li&gt;less wasted time searching for the right place&lt;/li&gt;&#xA;&lt;li&gt;easier access to relevant documentation, logs, and data&lt;/li&gt;&#xA;&lt;li&gt;faster understanding of old logic&lt;/li&gt;&#xA;&lt;li&gt;better support while writing specs and tests&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;An MCP server for documentation, an MCP server for SAP access, and access to more internal documentation can already help a lot here without giving full write access to everything.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-current-setups-still-do-not-solve&#34;&gt;What current setups still do not solve&lt;/h2&gt;&#xA;&lt;p&gt;So where is the actual gap today?&lt;/p&gt;&#xA;&lt;p&gt;For me it is not that AI cannot write ABAP. It can. It is also not that AI has no SAP context at all anymore. That situation is improving a lot through ADT tooling, MCP servers, documentation access, and better models.&lt;/p&gt;&#xA;&lt;p&gt;The real gap is that context access, write access, and control are tied much more closely together in ABAP than in off stack development, and that becomes an enterprise problem very quickly.&lt;/p&gt;&#xA;&lt;p&gt;What is often still missing is some combination of proper read-only defaults, operation boundaries, package restrictions, blocking dangerous features like free SQL, per-user identity instead of shared technical access, auditing, and a setup that security, basis, and development can all still trust.&lt;/p&gt;&#xA;&lt;p&gt;That is also why I am skeptical of setups where powerful ADT MCP servers simply run locally on the laptop of an individual user. If someone can set it up, and usually that is not even that hard, then suddenly you have a very powerful bridge from the model into the SAP system, but without much admin oversight around it. In an enterprise landscape that is not ideal at all.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-an-ideal-setup-would-look-like&#34;&gt;What an ideal setup would look like&lt;/h2&gt;&#xA;&lt;p&gt;So for me the question is not if agentic ABAP development is possible. It already is.&lt;/p&gt;&#xA;&lt;p&gt;The more important question is what a setup would need to look like if it should actually work in enterprise reality.&lt;/p&gt;&#xA;&lt;p&gt;For me that ideal direction is something like this: strong read access to real SAP context, useful structured tools instead of blind file edits, write access that is either gated or at least strongly restricted, central deployment instead of every user doing their own thing locally, clear identity handling, auditability, and enough safety boundaries that basis, security, and development can all live with it.&lt;/p&gt;&#xA;&lt;p&gt;That does not mean every team needs the exact same architecture. But it does mean the next step cannot just be &amp;ldquo;give the model more power and hope for the best&amp;rdquo;.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-this-matters-for-the-next-post&#34;&gt;Why this matters for the next post&lt;/h2&gt;&#xA;&lt;p&gt;The next post is where I want to look at what a more enterprise-ready ADT MCP setup should actually provide if you take those problems seriously from the beginning.&lt;/p&gt;&#xA;&lt;h2 id=&#34;discuss-this-post&#34;&gt;Discuss this post&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://saptodon.org/@Mianbsp/116450582013903540&#34;&gt;Saptodon&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bsky.app/profile/marian.zeis.de/post/3mk4idkeoi224&#34;&gt;Bluesky&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.linkedin.com/posts/marianzeis_abap-and-agentic-ai-the-hidden-problem-in-activity-7452940568571232256-GF_S&#34;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;related-posts&#34;&gt;Related posts&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.zeis.de/posts/2026-04-20-how-i-use-ai/&#34;&gt;How I Use AI for Development and Why Context Matters&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.zeis.de/posts/2026-02-04-abap-mcp-server/&#34;&gt;Finally: An MCP Server for ABAP&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.zeis.de/posts/2026-03-05-sap-ai-mcp-servers-overview/&#34;&gt;SAP MCP Servers: The Missing Overview&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>How I Use AI for Development and Why Context Matters</title>
      <link>https://blog.zeis.de/posts/2026-04-20-how-i-use-ai/</link>
      <pubDate>Mon, 20 Apr 2026 20:00:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2026-04-20-how-i-use-ai/</guid>
      <description>&lt;p&gt;Series note: This post is part of my &lt;a href=&#34;https://blog.zeis.de/tags/ai-abap-development-series/&#34;&gt;AI ABAP development series&lt;/a&gt;, where I go from AI development in general, to ABAP-specific problems, and then to ARC-1.&lt;/p&gt;&#xA;&lt;p&gt;When it comes to AI, I do not really think about magic.&lt;/p&gt;&#xA;&lt;p&gt;I think more about another colleague helping me out.&lt;/p&gt;&#xA;&lt;p&gt;Sometimes that means architecture. Sometimes implementation. Sometimes testing, review, CI/CD, or deployment.&lt;/p&gt;&#xA;&lt;p&gt;But this only works with trust, and trust needs communication.&lt;/p&gt;&#xA;&lt;p&gt;If I do not know something, I ask. If code looks strange, I ask. If tests fail, I ask.&lt;/p&gt;&#xA;&lt;p&gt;The less I need to ask, the more likely it is because I already gave enough context up front: architecture, documentation, constraints, examples, and what I actually want. Even then I still review the result.&lt;/p&gt;&#xA;&lt;p&gt;That is why using AI well is a skill you build over time.&lt;/p&gt;&#xA;&lt;p&gt;My first larger projects with AI were much less smooth than newer ones. Over time I learned that better context, better tooling, and better guardrails lead to much better results. AI can speed things up a lot, but it does not remove engineering judgement.&lt;/p&gt;&#xA;&lt;h2 id=&#34;my-workflow-changed-over-the-last-year&#34;&gt;My workflow changed over the last year&lt;/h2&gt;&#xA;&lt;p&gt;Last year I was mostly just using ChatGPT directly, before tools like Claude Code became more normal. I asked something, copied the generated code into my project, checked if it runs, copied the error back if it failed, and repeated that loop.&lt;/p&gt;&#xA;&lt;p&gt;The error message was context. The result of trying it out was context. That manual loop slowly improved the output.&lt;/p&gt;&#xA;&lt;p&gt;What changed over time was not only the model. More and more the AI can get context itself.&lt;/p&gt;&#xA;&lt;p&gt;Before I had to spoon feed much more manually. Now tools can search, read docs, inspect files, and in some cases get the needed context on their own. For me that is one of the biggest improvements, because the AI does not only answer, it can first understand more.&lt;/p&gt;&#xA;&lt;p&gt;That is also why I liked &lt;a href=&#34;https://www.stavros.io/posts/how-i-write-software-with-llms/&#34;&gt;How I write software with LLMs&lt;/a&gt; by Stavros. Not because I use exactly the same workflow, but because it shows quite well that this is not only about the model. The workflow around the model matters a lot as well.&lt;/p&gt;&#xA;&lt;h2 id=&#34;context-matters-more-than-people-first-think&#34;&gt;Context matters more than people first think&lt;/h2&gt;&#xA;&lt;p&gt;A lot of the time the biggest value is not even that the model writes code for me.&lt;/p&gt;&#xA;&lt;p&gt;Very often it helps with understanding. It can explain a codebase, compare options, write down a plan, summarize documentation, help with tests and refactoring, or point me to the next thing I should check. For a lot of developers that alone is already useful, without going fully agentic.&lt;/p&gt;&#xA;&lt;p&gt;In many projects the important information is not only in the source code. It is in documentation, issues, diagrams, product docs, logs, dumps, table data, screenshots, pipeline output, and things people in the team just know. If the model only sees one file, it often gives you an answer that sounds nice but misses the actual problem.&lt;/p&gt;&#xA;&lt;p&gt;That is why I really liked the post &lt;a href=&#34;https://secondphase.com.au/context-slop-vs-shipping/&#34;&gt;Context is the Difference Between Slop and Shipping&lt;/a&gt;. I think the core point is right.&lt;/p&gt;&#xA;&lt;p&gt;If you do not understand the system first, or if the model does not get the right grounding, then you just get plausible looking nonsense faster. It looks like momentum, but often it is just wrong output with confidence.&lt;/p&gt;&#xA;&lt;p&gt;One of the first places where I really noticed this was UI5, especially with controls like the UI5 Wizard. I had to explain the API again and again, and without that extra context the output was often just wrong. That was one of the first moments where I noticed more clearly that in SAP development good context is not optional.&lt;/p&gt;&#xA;&lt;p&gt;Especially in SAP I noticed pretty quickly that you need some kind of grounding. You want to be able to say: use this documentation, use this guideline, use this code reference, trust this as the source of truth and do not just invent something.&lt;/p&gt;&#xA;&lt;p&gt;It still hallucinates sometimes, that does not fully go away, but it becomes less. Especially weaker models benefit a lot from having the right information at the right time.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-i-care-so-much-about-tooling-around-the-model&#34;&gt;Why I care so much about tooling around the model&lt;/h2&gt;&#xA;&lt;p&gt;That is also one reason why last year I started building my own MCP server for context. It can search GitHub repositories, SAP Help, and related sources, so I can bring better grounding into the workflow.&lt;/p&gt;&#xA;&lt;p&gt;For SAP development especially this helped, because so much is not directly visible in one code file. If the model can pull in SAP documentation, guidelines, and related references, it gets pushed much more into the right direction.&lt;/p&gt;&#xA;&lt;p&gt;I now almost always have that SAP docs setup with me. At the beginning it felt even more important, because the models were weaker and hallucinations were stronger. Today it is a bit better with the stronger frontier models.&lt;/p&gt;&#xA;&lt;p&gt;I also noticed this with BTP and Cloud Foundry work. There the models can often already use the CLI and public documentation quite well. So the models are improving.&lt;/p&gt;&#xA;&lt;p&gt;But the more special something gets, the more hidden it is, or the more SAP specific it is, the more important that extra context becomes again.&lt;/p&gt;&#xA;&lt;p&gt;That is also why I do not find the current AI discussion fully convincing sometimes. People talk a lot about models, but much less about the system around the model. To me, context and tooling matter more in real projects than benchmark discussions alone.&lt;/p&gt;&#xA;&lt;h2 id=&#34;model-quality-still-matters-but-it-is-not-the-whole-story&#34;&gt;Model quality still matters, but it is not the whole story&lt;/h2&gt;&#xA;&lt;p&gt;I already wrote two separate blog posts about ABAP LLM benchmark results, one more around &lt;a href=&#34;https://blog.zeis.de/posts/2026-02-09-abap-llm-benchmark/&#34;&gt;code generation&lt;/a&gt; and one around &lt;a href=&#34;https://blog.zeis.de/posts/2026-03-05-abap-llm-benchmark-understanding/&#34;&gt;understanding&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;My takeaway there was that strong general models are currently still the more practical choice for me, and that tooling and grounding are often the bigger lever than hoping for one SAP specific model to solve everything by itself.&lt;/p&gt;&#xA;&lt;p&gt;What also helps a lot is that my MCP SAP docs setup is not only pure API documentation. There are also style guides, DSAG guidelines, and other recommendations in there. These are often not hard rules, but they still push you in a certain direction. So even if I do not know the exact recommendation by heart, I can ask the model to look for guidelines and it gets nudged more automatically towards a better direction.&lt;/p&gt;&#xA;&lt;p&gt;And context is not only documentation. If I say change this class, then the context is not just that class. It is also the tables that are used, the other classes that are called, similar implementations, internal guidelines, what this system can even do, and what is already there in working code. That broader context improves quality a lot more than just throwing one file at the model.&lt;/p&gt;&#xA;&lt;h2 id=&#34;i-am-still-skeptical-of-full-agentic-hype&#34;&gt;I am still skeptical of full agentic hype&lt;/h2&gt;&#xA;&lt;p&gt;There is also a very practical spectrum in how much AI help you even want. You can just ask questions about context. You can ask it to find docs and let you read them. You can use it for analysis and planning. Or you can let it implement things more autonomously. That is not one binary choice. It is more a slider, and depending on the project, the team, and the risk, you move that slider differently.&lt;/p&gt;&#xA;&lt;p&gt;At the same time I am still not sure that going more agentic automatically makes you faster. Pure AI assistance already helps me a lot. But once it becomes more agentic, other things get harder again. The coding itself is very fast, but then you spend more time on plans, instructions, guardrails, review, and correction.&lt;/p&gt;&#xA;&lt;p&gt;This also matches quite well with the &lt;a href=&#34;https://arxiv.org/abs/2507.09089&#34;&gt;METR study on experienced open-source developers&lt;/a&gt;. What I found interesting there is not some simple &amp;ldquo;AI bad&amp;rdquo; takeaway, but more the gap between feeling faster and actually being faster.&lt;/p&gt;&#xA;&lt;p&gt;If less time goes into typing code, but more time goes into prompting, reviewing, waiting, and correcting, then it can still feel fast while the total time says something else. That matches quite well with what I notice myself.&lt;/p&gt;&#xA;&lt;p&gt;At the same time I also do not want to overcorrect too much into pure skepticism. The post &lt;a href=&#34;https://lalitm.com/post/building-syntaqlite-ai/&#34;&gt;Eight years of wanting, three months of building with AI&lt;/a&gt; is a good counterexample. AI helped a lot there, but the first more maximalist agentic approach also created a messy codebase, and things only got better again with tighter ownership and stronger review. That feels much closer to reality than either extreme.&lt;/p&gt;&#xA;&lt;p&gt;For me there is still a big difference between strong AI assistance and just letting the agent fully take over. Writing the code is now often the easiest part. I spend more time before coding on research, testing, planning, and architecture. And I spend more time after coding on review, rework, manual testing, and documentation.&lt;/p&gt;&#xA;&lt;p&gt;It is still faster in my opinion, but the time moved.&lt;/p&gt;&#xA;&lt;h2 id=&#34;process-helps-but-process-is-also-overhead&#34;&gt;Process helps, but process is also overhead&lt;/h2&gt;&#xA;&lt;p&gt;This also fits well to &lt;a href=&#34;https://community.sap.com/t5/artificial-intelligence-blogs-posts/how-i-teach-claude-code-to-work-my-way/ba-p/14349299&#34;&gt;How I teach Claude Code to work my way&lt;/a&gt;. What I like there is that the focus is not on one magical prompt, but on process. Explore first, think first, plan first, then execute.&lt;/p&gt;&#xA;&lt;p&gt;I do not use exactly the same process everywhere, but the general idea fits very well to what I learned myself. If you give the AI a process, it usually follows one. If you do not, it drifts more easily. At the same time, process also creates overhead, so it is only worth it when the task is big enough.&lt;/p&gt;&#xA;&lt;p&gt;That is also why I often want the model to first find a reference before it gives me a strong answer. Search for the SAP documentation. Find the guideline. Show me a similar implementation. Do not just make something up because it sounds right.&lt;/p&gt;&#xA;&lt;h2 id=&#34;why-this-gets-harder-in-sap&#34;&gt;Why this gets harder in SAP&lt;/h2&gt;&#xA;&lt;p&gt;Security is also part of that trust question. How much trust do you put in the llm itself, how much in the model provider, and how much in the tools around it? How much security are you willing to give up to move faster? For toy projects people often accept a lot of risk. In enterprise development that looks very different.&lt;/p&gt;&#xA;&lt;p&gt;In SAP this matters even more, because so much important context is outside the code itself. The actual code change can be very small, but understanding why you need that change can require docs, config, logs, dumps, table entries, business process context, authorizations, and knowledge about how the landscape is set up.&lt;/p&gt;&#xA;&lt;p&gt;At the same time SAP is also moving. I was pleasantly surprised when SAP released MCP servers for &lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/give-your-ai-agent-some-tools-introducing-the-ui5-mcp-server/bc-p/14206959&#34;&gt;UI5&lt;/a&gt;, &lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/boost-your-cap-development-with-ai-introducing-the-mcp-server-for-cap/ba-p/14202849&#34;&gt;CAP&lt;/a&gt;, and &lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/sap-fiori-tools-update-first-release-of-the-sap-fiori-mcp-server-for/ba-p/14204694&#34;&gt;Fiori&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;In that world it already feels much more natural, because a lot of that development is off stack anyway. You work locally, use git, run pipelines, get peer review, and if something goes wrong you can usually revert it. That is much easier territory for AI assisted development.&lt;/p&gt;&#xA;&lt;p&gt;So the interesting question for me is not if AI can help in SAP development. It already can. The more interesting question is where it helps today, where it becomes risky, and why especially ABAP development is still much harder than frontend or CAP development.&lt;/p&gt;&#xA;&lt;p&gt;In frontend and CAP development, AI can already fit quite naturally into workflows many teams already have. In ABAP, things look very different.&lt;/p&gt;&#xA;&lt;p&gt;The next post is where I want to look more concrete at that gap: what is already possible today, which patterns exist, and why enterprise guardrails matter much more there.&lt;/p&gt;&#xA;&lt;h2 id=&#34;discuss-this-post&#34;&gt;Discuss this post&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://saptodon.org/@Mianbsp/116439309593074502&#34;&gt;Saptodon&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://bsky.app/profile/marian.zeis.de/post/3mjxi4yqxtk2k&#34;&gt;Bluesky&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.linkedin.com/posts/marianzeis_how-i-use-ai-for-development-and-why-context-activity-7452215820195184641-r-uK&#34;&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;references--links&#34;&gt;References &amp;amp; links&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://www.stavros.io/posts/how-i-write-software-with-llms/&#34;&gt;How I write software with LLMs&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://secondphase.com.au/context-slop-vs-shipping/&#34;&gt;Context is the Difference Between Slop and Shipping&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://lalitm.com/post/building-syntaqlite-ai/&#34;&gt;Eight years of wanting, three months of building with AI&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://arxiv.org/abs/2507.09089&#34;&gt;Measuring the Impact of Early-2025 AI on Experienced Open-Source Developer Productivity&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://community.sap.com/t5/artificial-intelligence-blogs-posts/how-i-teach-claude-code-to-work-my-way/ba-p/14349299&#34;&gt;How I teach Claude Code to work my way&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.zeis.de/posts/2026-02-04-abap-mcp-server/&#34;&gt;Finally: An MCP Server for ABAP&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.zeis.de/posts/2026-03-05-sap-ai-mcp-servers-overview/&#34;&gt;SAP MCP Servers: The Missing Overview&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.zeis.de/posts/2026-02-09-abap-llm-benchmark/&#34;&gt;Benchmarking LLMs for ABAP: Why ABAP-1 Isn&amp;rsquo;t a Code Generator (Yet)&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://blog.zeis.de/posts/2026-03-05-abap-llm-benchmark-understanding/&#34;&gt;SAP’s ABAP-1 Loses Every ABAP Benchmark, Even “Explaining”&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/give-your-ai-agent-some-tools-introducing-the-ui5-mcp-server/bc-p/14206959&#34;&gt;Give Your AI Agent Some Tools: Introducing the UI5 MCP Server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/boost-your-cap-development-with-ai-introducing-the-mcp-server-for-cap/ba-p/14202849&#34;&gt;Boost your CAP Development with AI: Introducing the MCP Server for CAP&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/sap-fiori-tools-update-first-release-of-the-sap-fiori-mcp-server-for/ba-p/14204694&#34;&gt;SAP Fiori Tools Update - First Release of the SAP Fiori MCP Server for Agentic AI Workflows&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>SAP Fiori Elements Feature Map: What Is Available in Each SAPUI5 Version</title>
      <link>https://blog.zeis.de/posts/2026-03-12-fiori-elements-feature-map/</link>
      <pubDate>Wed, 11 Mar 2026 20:55:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2026-03-12-fiori-elements-feature-map/</guid>
      <description>&lt;p&gt;If you work with SAP Fiori Elements, you have probably run into this problem before:&lt;br&gt;&#xA;it is hard to see in which UI5 version a specific feature was introduced.&lt;/p&gt;&#xA;&lt;p&gt;Bjorn Schulz already built a strong matrix for ABAP: &lt;a href=&#34;https://software-heroes.com/abap-feature-matrix&#34;&gt;ABAP Feature Matrix&lt;/a&gt;.&lt;br&gt;&#xA;For Fiori Elements and RAP/CAP, there was no comparable matrix yet.&lt;/p&gt;&#xA;&lt;p&gt;So I created an Influence request to close this gap:&lt;br&gt;&#xA;&lt;a href=&#34;https://influence.sap.com/sap/ino/#idea/351729&#34;&gt;influence.sap.com/sap/ino/#idea/351729&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;The new Developer Portal would be a great home for a matrix like this but so far, there still is no official version.&lt;br&gt;&#xA;The closest alternative is the &lt;a href=&#34;https://ui5.sap.com/#/topic/62d3f7c2a9424864921184fd6c7002eb&#34;&gt;SAP Fiori Elements Feature Map&lt;/a&gt; in the SAPUI5 documentation.&lt;/p&gt;&#xA;&lt;p&gt;The challenge is that SAPUI5 docs are published per version, so you mostly see a point-in-time view.&lt;br&gt;&#xA;You do not get a clear cross-version overview showing when a feature first appeared.&lt;/p&gt;&#xA;&lt;p&gt;That is why I decided to fetch the feature map for each version and compare when a feature first appears.&lt;br&gt;&#xA;The result is the feature map below, updated automatically whenever new versions are released.&lt;/p&gt;&#xA;&lt;p&gt;You can filter by features, floorplans, and versions, and sort by the key columns.&lt;br&gt;&#xA;The data is taken directly from the official feature list.&lt;/p&gt;&#xA;&lt;p&gt;Since the feature map in the SAPUI5 documentation has not been clearly managed, there are sometimes discrepancies.&lt;br&gt;&#xA;You can see this more clearly in the &lt;a href=&#34;https://blog.zeis.de/feature-map/fe-feature-matrix.md&#34;&gt;feature matrix markdown&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Download the data:&lt;/strong&gt; &lt;a href=&#34;https://blog.zeis.de/feature-map/fe-feature-adoption.json&#34;&gt;JSON&lt;/a&gt; · &lt;a href=&#34;https://blog.zeis.de/feature-map/fe-feature-adoption.csv&#34;&gt;CSV&lt;/a&gt; · &lt;a href=&#34;https://blog.zeis.de/feature-map/fe-feature-adoption.md&#34;&gt;Adoption (Markdown)&lt;/a&gt; · &lt;a href=&#34;https://blog.zeis.de/feature-map/fe-feature-matrix.md&#34;&gt;Matrix (Markdown)&lt;/a&gt;&lt;/p&gt;&#xA;&lt;link rel=&#34;stylesheet&#34; href=&#34;https://blog.zeis.de/feature-map/fe-feature-adoption.css&#34;&gt;&#xA;&lt;script defer src=&#34;https://blog.zeis.de/feature-map/fe-feature-adoption.js&#34;&gt;&lt;/script&gt;&lt;section class=&#34;fe-feature-map&#34; data-fe-feature-map data-json-url=&#34;https://blog.zeis.de/feature-map/fe-feature-adoption.json&#34;&gt;&#xA;  &lt;h2 class=&#34;fe-feature-map__title&#34;&gt;SAP Fiori Elements Feature Map&lt;/h2&gt;&#xA;  &lt;p class=&#34;fe-feature-map__meta&#34;&gt;Data updated: &lt;span data-role=&#34;generated-at&#34;&gt;-&lt;/span&gt;&lt;/p&gt;&#xA;&#xA;  &lt;div class=&#34;fe-feature-map__toolbar&#34; role=&#34;group&#34; aria-label=&#34;Feature map filters&#34;&gt;&#xA;    &lt;label class=&#34;fe-feature-map__toolbar-label&#34;&gt;&#xA;      Full text search&#xA;      &lt;input type=&#34;search&#34; data-role=&#34;global-search&#34; placeholder=&#34;Search all columns&#34;&gt;&#xA;    &lt;/label&gt;&#xA;&#xA;    &lt;div class=&#34;fe-feature-map__floorplan-filter&#34; data-role=&#34;floorplan-filter&#34;&gt;&#xA;      &lt;button type=&#34;button&#34; class=&#34;fe-feature-map__button&#34; data-role=&#34;floorplan-toggle&#34; aria-expanded=&#34;false&#34; aria-haspopup=&#34;true&#34;&gt;&#xA;        Floorplans: All&#xA;      &lt;/button&gt;&#xA;      &lt;div class=&#34;fe-feature-map__floorplan-menu&#34; data-role=&#34;floorplan-menu&#34; hidden&gt;&#xA;        &lt;div class=&#34;fe-feature-map__menu-actions&#34;&gt;&#xA;          &lt;button type=&#34;button&#34; class=&#34;fe-feature-map__button&#34; data-role=&#34;floorplan-select-all&#34;&gt;Select all&lt;/button&gt;&#xA;          &lt;button type=&#34;button&#34; class=&#34;fe-feature-map__button&#34; data-role=&#34;floorplan-clear&#34;&gt;Clear&lt;/button&gt;&#xA;        &lt;/div&gt;&#xA;        &lt;div class=&#34;fe-feature-map__floorplan-options&#34; data-role=&#34;floorplan-options&#34;&gt;&lt;/div&gt;&#xA;      &lt;/div&gt;&#xA;    &lt;/div&gt;&#xA;&#xA;    &lt;button type=&#34;button&#34; class=&#34;fe-feature-map__button&#34; data-role=&#34;clear-filters&#34;&gt;Clear Filters&lt;/button&gt;&#xA;    &lt;span class=&#34;fe-feature-map__count&#34; data-role=&#34;result-count&#34;&gt;0 / 0 rows&lt;/span&gt;&#xA;  &lt;/div&gt;&#xA;&#xA;  &lt;div class=&#34;fe-feature-map__table-wrap&#34;&gt;&#xA;    &lt;table class=&#34;fe-feature-map__table&#34; data-role=&#34;table&#34;&gt;&#xA;      &lt;caption&gt;Feature availability and references across SAPUI5 versions&lt;/caption&gt;&#xA;      &lt;thead&gt;&#xA;        &lt;tr data-role=&#34;header-row&#34;&gt;&lt;/tr&gt;&#xA;        &lt;tr class=&#34;fe-feature-map__filter-row&#34; data-role=&#34;filter-row&#34;&gt;&lt;/tr&gt;&#xA;      &lt;/thead&gt;&#xA;      &lt;tbody data-role=&#34;table-body&#34;&gt;&lt;/tbody&gt;&#xA;    &lt;/table&gt;&#xA;  &lt;/div&gt;&#xA;&#xA;  &lt;p class=&#34;fe-feature-map__status&#34; data-role=&#34;status&#34; aria-live=&#34;polite&#34;&gt;&lt;/p&gt;&#xA;&#xA;  &lt;noscript&gt;&#xA;    &lt;p&gt;This table needs JavaScript for search, filters, and sorting.&lt;/p&gt;&#xA;  &lt;/noscript&gt;&#xA;&lt;/section&gt;&#xA;&#xA;</description>
    </item>
    <item>
      <title>SAP MCP Servers: The Missing Overview</title>
      <link>https://blog.zeis.de/posts/2026-03-05-sap-ai-mcp-servers-overview/</link>
      <pubDate>Thu, 05 Mar 2026 09:00:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2026-03-05-sap-ai-mcp-servers-overview/</guid>
      <description>&lt;p&gt;If you are searching for &lt;strong&gt;SAP MCP Servers&lt;/strong&gt; (Model Context Protocol), you quickly run into the same problem:&lt;/p&gt;&#xA;&lt;p&gt;Which SAP MCP servers exist (official and community), and what other SAP AI tooling should you know about?&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Screenshot of the sap-ai-mcp-servers README showing the overview tables and categories.&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-03-05-sap-ai-mcp-servers-overview/readme.jpg&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;That is why I like this new repo:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;code&gt;marianfoo/sap-ai-mcp-servers&lt;/code&gt;&lt;/strong&gt;: &lt;a href=&#34;https://github.com/marianfoo/sap-ai-mcp-servers&#34;&gt;SAP MCP Servers and SAP AI Skills list&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;It is a curated list of &lt;strong&gt;SAP MCP servers&lt;/strong&gt;, &lt;strong&gt;SAP AI skills&lt;/strong&gt;, and &lt;strong&gt;adjacent developer tools&lt;/strong&gt;. The README is structured as tables so you can scan by category, compare licenses, and see what is actively maintained.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-you-will-find-inside&#34;&gt;What you will find inside&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Official SAP MCP servers&lt;/strong&gt;: for example &lt;strong&gt;SAP Fiori&lt;/strong&gt;, &lt;strong&gt;CAP&lt;/strong&gt;, &lt;strong&gt;UI5&lt;/strong&gt;, and &lt;strong&gt;MDK&lt;/strong&gt;.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Community SAP MCP servers&lt;/strong&gt;: docs, &lt;strong&gt;ABAP and ADT&lt;/strong&gt;, integration, Datasphere, &lt;strong&gt;OData&lt;/strong&gt;, SAP GUI, HANA.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Skills and adjacent tools&lt;/strong&gt;: skill repos plus “helper” tooling (plugins, samples, IDE integrations) that often matters just as much as the server itself.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h2 id=&#34;why-this-matters-for-sap-ai&#34;&gt;Why this matters for SAP AI&lt;/h2&gt;&#xA;&lt;p&gt;If you are building an AI assistant for SAP development, MCP servers are the “connectors” that let your agent reliably pull context from documentation and systems. Having a single overview makes it much easier to choose the right building blocks.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-to-contribute&#34;&gt;How to contribute&lt;/h2&gt;&#xA;&lt;p&gt;If you know a project that belongs there, you can add it by opening an issue using the &lt;a href=&#34;https://github.com/marianfoo/sap-ai-mcp-servers/issues/new?template=add-entry.yml&#34;&gt;“Add new entry”&lt;/a&gt; template in the repo.&lt;/p&gt;&#xA;&lt;p&gt;If you build anything SAP plus MCP related, this is the list I would want to keep open in a browser tab.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>SAP’s ABAP-1 Loses Every ABAP Benchmark, Even “Explaining”</title>
      <link>https://blog.zeis.de/posts/2026-03-05-abap-llm-benchmark-understanding/</link>
      <pubDate>Tue, 03 Mar 2026 22:00:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2026-03-05-abap-llm-benchmark-understanding/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Previous post (code generation benchmark):&lt;/strong&gt; &lt;a href=&#34;https://blog.zeis.de/posts/2026-02-09-abap-llm-benchmark/&#34;&gt;Benchmarking LLMs for ABAP&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Live benchmark results (old + new):&lt;/strong&gt; &lt;a href=&#34;https://abap-llm-benchmark.marianzeis.de/&#34;&gt;abap-llm-benchmark.marianzeis.de&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;In my first evaluation (based on the TH Köln benchmark paper), I extended the original setup with additional models and focused on a very concrete question: how well can LLMs generate ABAP code that actually compiles and passes ABAP Unit tests?&lt;/p&gt;&#xA;&lt;p&gt;I also tested SAP’s model &lt;strong&gt;ABAP-1&lt;/strong&gt;, and it performed very poorly for code generation. To be fair: SAP also states this in the documentation. ABAP-1 is primarily meant &lt;a href=&#34;https://help.sap.com/docs/sap-ai-core/generative-ai/sap-abap-1?locale=en-US&#34;&gt;for explaining ABAP code&lt;/a&gt; not for reliably generating full working implementations.&lt;/p&gt;&#xA;&lt;p&gt;So I built a second test that targets exactly that: understanding and explaining ABAP.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-understanding-test&#34;&gt;The “Understanding” test&lt;/h2&gt;&#xA;&lt;p&gt;In the “Understanding” benchmark, the model gets existing ABAP code plus the corresponding ABAP Unit tests (so this is not a generation task). From that, it must extract concrete facts as structured JSON, for example:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;which classes and methods are relevant&lt;/li&gt;&#xA;&lt;li&gt;expected behavior and validations&lt;/li&gt;&#xA;&lt;li&gt;inputs and outputs&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The JSON output is then scored automatically against a reference, so we can measure in a reproducible way how well a model understands ABAP code and describes it correctly. This works without running SAP or ADT.&lt;/p&gt;&#xA;&lt;p&gt;Here is the key result as a chart (cumulative success across feedback rounds):&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;ABAP understanding benchmark: cumulative success rates by model and feedback round&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-03-05-abap-llm-benchmark-understanding/images/understanding_success_by_model_by_feedbackround_in_percent.png&#34;&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-this-says-about-abap-1&#34;&gt;What this says about ABAP-1&lt;/h2&gt;&#xA;&lt;p&gt;As expected, ABAP-1 is clearly better in this “Understanding” setup than in code generation. But it still does not keep up with current general-purpose models.&lt;/p&gt;&#xA;&lt;p&gt;Even the current Anthropic Haiku model (optimized for speed and cost) beats ABAP-1 on this benchmark.&lt;/p&gt;&#xA;&lt;p&gt;That makes the practical conclusion pretty simple: &lt;strong&gt;for almost any ABAP use case, ABAP-1 is not a good default choice.&lt;/strong&gt; It is priced closer to premium models, but does not deliver premium results. So the “maybe it is cheaper” argument also does not hold.&lt;/p&gt;&#xA;&lt;h2 id=&#34;new-models&#34;&gt;New models&lt;/h2&gt;&#xA;&lt;p&gt;After the first post, people asked for more models to be added. I’m tracking those requests as GitHub issues here: &lt;a href=&#34;https://github.com/marianfoo/LLM-Benchmark-ABAP-Code-Generation/issues?q=sort%3Aupdated-desc%20is%3Aissue&#34;&gt;LLM-Benchmark-ABAP-Code-Generation issues&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;I added the following models:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;DeepSeek Reasoner&lt;/li&gt;&#xA;&lt;li&gt;Mistral Large 2512&lt;/li&gt;&#xA;&lt;li&gt;GPT-5.3 Codex&lt;/li&gt;&#xA;&lt;li&gt;Claude Haiku 4.5&lt;/li&gt;&#xA;&lt;li&gt;Gemini 3.1 Flash Preview&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;To put the “new models” into context, here is the updated &lt;strong&gt;code generation&lt;/strong&gt; overview (this is the original benchmark: generate ABAP, then iterate with compiler/test feedback up to 5 rounds). The plot already includes the new lineup:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;ABAP code generation benchmark: cumulative successful code generations by model and feedback round&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-03-05-abap-llm-benchmark-understanding/images/success_by_model_by_feedbackround_in_percent.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;GPT-5.3 Codex produced very strong results and even beat Opus 4.5 on some metrics. My guess is: if we compare against Opus 4.6, the gap will likely be small (or they might end up roughly equal).&lt;/p&gt;&#xA;&lt;p&gt;Depending on the metric, Opus 4.5 is still excellent: it has the highest first-try success rate (Round 0) and the best AUC across all feedback rounds.&lt;/p&gt;&#xA;&lt;p&gt;Unfortunately, Mistral’s current “best” model performed worse than I expected and does not look like a great choice for ABAP.&lt;/p&gt;&#xA;&lt;p&gt;For price/performance, DeepSeek Reasoner is the clear winner in this lineup.&lt;/p&gt;&#xA;&lt;p&gt;Using the public list prices (at the time of writing), Codex is at around &lt;strong&gt;$1.75 per 1M input tokens&lt;/strong&gt;, while DeepSeek Reasoner is at &lt;strong&gt;$0.28 per 1M input tokens&lt;/strong&gt; (more than 6x cheaper). The bigger difference is output: DeepSeek output is &lt;strong&gt;$0.42 per 1M tokens&lt;/strong&gt;, while Codex output is around &lt;strong&gt;$14.00 per 1M tokens&lt;/strong&gt; (about 33x).&lt;/p&gt;&#xA;&lt;h3 id=&#34;gemini-31-flash-preview&#34;&gt;Gemini 3.1 Flash Preview&lt;/h3&gt;&#xA;&lt;p&gt;Gemini 3.1 Flash Preview also surprised me with strong results. It is cheap and in my runs it was even slightly ahead of GPT-5.3 Codex, while being significantly cheaper.&lt;/p&gt;&#xA;&lt;p&gt;At the time of writing, Gemini Flash was roughly &lt;strong&gt;$0.50 per 1M input tokens&lt;/strong&gt; and &lt;strong&gt;$3.00 per 1M output tokens&lt;/strong&gt;, which is a big difference compared to Codex pricing.&lt;/p&gt;&#xA;&lt;p&gt;Unfortunately, I could not test Gemini 3.1 Pro: the rate limit was effectively capped at ~25 requests per minute and I repeatedly hit 503 errors (“system overloaded”). With several thousand requests, that made a full benchmark run impractical. My guess is that Gemini 3.1 Pro would be similar to, or better than, GPT-5.3 Codex, but Flash already delivered very good results.&lt;/p&gt;&#xA;&lt;h3 id=&#34;why-haiku-45-did-so-badly-here&#34;&gt;Why Haiku 4.5 did so badly here&lt;/h3&gt;&#xA;&lt;p&gt;Haiku 4.5 failed systematically because it kept using CDS-style type notation like &lt;code&gt;abap.char(20)&lt;/code&gt; in classic ABAP implementations (syntax errors). Worse, misleading parser error messages made it hard for the model to identify the real root cause, so it did not correct itself even across multiple feedback rounds.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-practical-takeaway&#34;&gt;A practical takeaway&lt;/h2&gt;&#xA;&lt;p&gt;For me, this is now a pretty solid baseline to decide which models are worth considering for ABAP, and which ones are not, depending on whether you care more about API calls (cost) or daily development (quality + speed).&lt;/p&gt;&#xA;&lt;p&gt;One important reminder: these models acted only on what they already “know”. Results can be improved a lot with tooling and better context, for example:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;ABAP MCP Server&lt;/strong&gt; for ABAP best practices and keyword documentation: &lt;a href=&#34;https://github.com/marianfoo/abap-mcp-server&#34;&gt;marianfoo/abap-mcp-server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;abaplint&lt;/strong&gt; for static checks and fast feedback loops: &lt;a href=&#34;https://abaplint.org/&#34;&gt;abaplint.org&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;With that kind of tooling and feedback loop, you can generate very good ABAP code even if you “only” use a strong general model.&lt;/p&gt;&#xA;&lt;p&gt;And yes, my conclusion from the first post still stands: I would not recommend ABAP-1. If SAP wants better outcomes, I think the bigger lever is not “yet another model”, but better tools and better integration so frontier models (OpenAI, Anthropic, Google) can reliably produce correct ABAP in real-world projects.&lt;/p&gt;&#xA;&lt;p&gt;You can still suggest models I have not tested yet, but benchmarks like this are not cheap. For now I will pause adding more models unless there is a very strong reason to do another run.&lt;/p&gt;&#xA;&lt;h2 id=&#34;references--links&#34;&gt;References &amp;amp; links&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;TH Köln paper: &lt;a href=&#34;https://arxiv.org/html/2601.15188v1&#34;&gt;Benchmarking Large Language Models for ABAP Code Generation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Previous post (code generation benchmark): &lt;a href=&#34;https://blog.zeis.de/posts/2026-02-09-abap-llm-benchmark/&#34;&gt;Benchmarking LLMs for ABAP&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Live results website: &lt;a href=&#34;https://abap-llm-benchmark.marianzeis.de/&#34;&gt;abap-llm-benchmark.marianzeis.de&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Original benchmark repo (TH Köln): &lt;a href=&#34;https://github.com/timkoehne/LLM-Benchmark-ABAP-Code-Generation&#34;&gt;timkoehne/LLM-Benchmark-ABAP-Code-Generation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Dataset (prompts + ABAP Unit tests): &lt;a href=&#34;https://github.com/timkoehne/LLM-Benchmark-ABAP-Code-Generation/tree/main/dataset&#34;&gt;dataset folder&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Benchmark repo (my fork with updates): &lt;a href=&#34;https://github.com/marianfoo/LLM-Benchmark-ABAP-Code-Generation&#34;&gt;marianfoo/LLM-Benchmark-ABAP-Code-Generation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Model requests / discussion: &lt;a href=&#34;https://github.com/marianfoo/LLM-Benchmark-ABAP-Code-Generation/issues?q=sort%3Aupdated-desc%20is%3Aissue&#34;&gt;GitHub issues&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;SAP ABAP-1 documentation: &lt;a href=&#34;https://help.sap.com/docs/sap-ai-core/generative-ai/sap-abap-1?locale=en-US&#34;&gt;SAP ABAP-1&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;ABAP Cloud Developer Trial (Docker image used by the benchmark): &lt;a href=&#34;https://github.com/SAP-docs/abap-platform-trial-image&#34;&gt;SAP-docs/abap-platform-trial-image&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Batch APIs (used for running large benchmarks cheaper): &lt;a href=&#34;https://platform.openai.com/docs/guides/batch&#34;&gt;OpenAI Batch API&lt;/a&gt;, &lt;a href=&#34;https://docs.anthropic.com/en/docs/build-with-claude/batch-processing&#34;&gt;Anthropic batch processing&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Pricing references:&#xA;&lt;ul&gt;&#xA;&lt;li&gt;OpenAI: &lt;a href=&#34;https://openai.com/api/pricing&#34;&gt;API pricing&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;DeepSeek: &lt;a href=&#34;https://api-docs.deepseek.com/quick_start/pricing/&#34;&gt;Pricing (official docs)&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Google Gemini: &lt;a href=&#34;https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing&#34;&gt;generative AI pricing&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Anthropic: &lt;a href=&#34;https://docs.anthropic.com/en/docs/about-claude/pricing&#34;&gt;Claude API pricing&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>Building an Enterprise-Ready SAP AI Agent with Open Source</title>
      <link>https://blog.zeis.de/posts/2026-02-16-librechat-enterprise-gpt/</link>
      <pubDate>Tue, 03 Mar 2026 09:00:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2026-02-16-librechat-enterprise-gpt/</guid>
      <description>&lt;p&gt;There is currently a huge hype around the new &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt; servers. I&amp;rsquo;ve built one for &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;SAP Docs&lt;/a&gt;, and there are excellent community projects like the &lt;a href=&#34;https://github.com/oisee/vibing-steampunk&#34;&gt;ADT API MCP Server&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;These tools are fantastic for individual developers. You install them, connect them to your IDE or a local LLM, and suddenly your AI assistant knows about SAP.&lt;/p&gt;&#xA;&lt;h3 id=&#34;architecture-overview-in-one-picture&#34;&gt;Architecture overview (in one picture)&lt;/h3&gt;&#xA;&lt;p&gt;Before diving into the details, here is the high-level architecture I’m aiming for:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Enterprise SAP AI agent architecture overview: user, LibreChat, LLM (local or cloud), and MCP-based knowledge sources&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-02-16-librechat-enterprise-gpt/images/architecture.png&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;The idea is simple: users talk to &lt;strong&gt;LibreChat&lt;/strong&gt; (the UI + orchestrator). LibreChat calls an &lt;strong&gt;LLM&lt;/strong&gt; (local inside the company network, or a trusted cloud model) and enriches answers via &lt;strong&gt;MCP servers&lt;/strong&gt;. Those MCP servers connect to your knowledge sources (SAP docs, your SAP system via a read-only ADT connection, and internal SharePoint) without turning this into an “AI tool with write access to prod”.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;But here is the problem:&lt;/strong&gt; For a common SAP consultant, architect, or developer in a corporate environment, this setup is often a nightmare.&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Complexity:&lt;/strong&gt; Setting up a local LLM, configuring MCP, and dealing maybe with Docker isn&amp;rsquo;t everyone&amp;rsquo;s cup of tea.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Security:&lt;/strong&gt; This is the big one. The default ADT MCP server, for example, is powerful, it allows reading and &lt;em&gt;writing&lt;/em&gt; to the SAP system. No security department in their right mind would allow a &amp;ldquo;random&amp;rdquo; AI tool to have write access to the entire development system without strict governance.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Governance:&lt;/strong&gt; Companies need to know where the data is going. Is it staying in Europe? Is it training the model?&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;This is not mainly a &amp;ldquo;developer toy&amp;rdquo;. The biggest value is for everyone who needs to understand what is going on in an SAP system: consultants, architects, project leads, support, and developers who are new to a system.&lt;/p&gt;&#xA;&lt;p&gt;For ABAP developers specifically, the best enterprise-ready setup today is often &lt;strong&gt;GitHub Copilot in ADT&lt;/strong&gt; because Agent Mode is available and you can add ABAP-specific knowledge through my &lt;a href=&#34;https://github.com/marianfoo/abap-mcp-server&#34;&gt;ABAP MCP Server&lt;/a&gt;. But this IDE setup still has a gap: it does not give a complete overview of the system and it cannot reliably retrieve additional classes, programs, and related objects on demand to build the full context for an answer.&#xA;Neverthless, it is a great tool for many ABAP developers to query the system and get a complete context for an answer. As you can see in the sample workflow below, it is possible to use the MCP servers to build the full context for an answer and just copy-paste the code into Eclipse.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-enterprise-struggle&#34;&gt;The Enterprise Struggle&lt;/h3&gt;&#xA;&lt;p&gt;I recently saw a discussion that perfectly illustrates this struggle. Companies are desperately searching for &amp;ldquo;Enterprise Ready&amp;rdquo; AI usage, but they are hitting walls.&lt;/p&gt;&#xA;&lt;p&gt;Some are trying manual workarounds, such as exporting their entire codebase to SharePoint for Microsoft Copilot to index. This often involves flattening package hierarchies to bypass navigation limits and manually creating &lt;code&gt;README.md&lt;/code&gt; or cross-reference files to provide context, all while battling hallucinations where the AI fails to find existing transactions due to indexing errors.&lt;/p&gt;&#xA;&lt;p&gt;Then there is &lt;strong&gt;SAP Joule&lt;/strong&gt;. SAP&amp;rsquo;s own AI assistant is available for consultants, but it is quite limited in&#xA;what it can do. It doesn&amp;rsquo;t connect to your custom codebase, can&amp;rsquo;t search your internal SharePoint documentation,&#xA;and has no way to follow references across ABAP objects the way an MCP-based agent can. It is a nice chatbot for&#xA;general SAP questions, but it is far from a tool that understands &lt;em&gt;your&lt;/em&gt; system.&lt;/p&gt;&#xA;&lt;p&gt;This also matches the broader picture. In the &lt;a href=&#34;https://dsag.de/presse/dsag-investment-report-2026-companies-are-investing-more-selectively-ai-is-becoming-established-cloud-computing-is-being-put-to-the-test/&#34;&gt;DSAG Investment Report 2026&lt;/a&gt;, most respondents who already run AI use cases do so with &lt;strong&gt;non-SAP solutions&lt;/strong&gt;, not SAP&amp;rsquo;s AI offerings.&lt;/p&gt;&#xA;&lt;p&gt;On the other end of the spectrum, you have high-end commercial solutions like &lt;strong&gt;Nova Intelligence&lt;/strong&gt;. They do exactly what you want: they read the SAP system directly, understand the context, and are enterprise-ready. But they are also &lt;strong&gt;really expensive&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-middle-ground-a-secure-open-source-alternative&#34;&gt;The &amp;ldquo;Middle Ground&amp;rdquo;: A Secure, Open Source Alternative&lt;/h3&gt;&#xA;&lt;p&gt;So, is there nothing in between? Do you have to choose between a hacking-together-files-on-SharePoint workaround and a six-figure enterprise contract?&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;No.&lt;/strong&gt; I want to show you that there is a &amp;ldquo;Middle Ground.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;You can build a secure, enterprise-grade setup using Open Source tools. Think of it as &lt;strong&gt;building your own internal &amp;ldquo;SAP Copilot&amp;rdquo;&lt;/strong&gt; that runs entirely under your control.&lt;/p&gt;&#xA;&lt;p&gt;This setup allows you to:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Search your SAP System&lt;/strong&gt; (Reports, Classes, Tables)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Search SAP Documentation&lt;/strong&gt; (Help, GitHub, SAP Community)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Use your internal SharePoint&lt;/strong&gt; (Specs, Docs)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Keep it Secure&lt;/strong&gt; (Read-Only access, Authentication via Entra ID)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;the-stack&#34;&gt;The Stack&lt;/h3&gt;&#xA;&lt;p&gt;I used &lt;strong&gt;LibreChat&lt;/strong&gt; as the UI and Orchestrator to showcase what is possible. It&amp;rsquo;s an open-source web interface that looks and feels like ChatGPT but gives full control.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Client:&lt;/strong&gt; &lt;a href=&#34;https://www.librechat.ai/&#34;&gt;LibreChat&lt;/a&gt; (supports Entra ID, SharePoint, and MCP out of the box).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Knowledge Source 1:&lt;/strong&gt; &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;SAP Docs MCP Server&lt;/a&gt; (for public documentation).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Knowledge Source 2:&lt;/strong&gt; &lt;a href=&#34;https://github.com/oisee/vibing-steampunk&#34;&gt;ADT MCP Server&lt;/a&gt; (for your SAP System).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;LLM:&lt;/strong&gt; A local LLM via Ollama.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;step-1-security-first-the-adt-connection&#34;&gt;Step 1: Security First (The ADT Connection)&lt;/h3&gt;&#xA;&lt;p&gt;The biggest concern is the SAP connection. I did &lt;strong&gt;not&lt;/strong&gt; want the AI to write code or change things autonomously.&lt;/p&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/oisee/vibing-steampunk&#34;&gt;ADT MCP Server&lt;/a&gt; has a specific mode for this. I configured it to be &lt;strong&gt;Read-Only&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Furthermore, I connected it using a &lt;strong&gt;Technical User&lt;/strong&gt; in SAP that has &lt;em&gt;only&lt;/em&gt; display authorizations. This ensures that even if the AI wanted to (or was tricked to), it effectively cannot change anything in the system. It can only &amp;ldquo;look&amp;rdquo;, just like a developer using SE80 in display mode.&lt;/p&gt;&#xA;&lt;h3 id=&#34;step-2-the-setup&#34;&gt;Step 2: The Setup&lt;/h3&gt;&#xA;&lt;p&gt;The entire setup can be containerized with Docker. This means you can run it on a secure server within your company network. No requests need to leave your intranet if you use a local model, or they only go to your trusted Azure OpenAI instance.&lt;/p&gt;&#xA;&lt;p&gt;Here is the high-level concept:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Deploy LibreChat&lt;/strong&gt;: Configure it with your company&amp;rsquo;s Entra ID (Azure AD). This means users log in with their standard company Microsoft accounts. No separate user management needed.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Connect SharePoint&lt;/strong&gt;: LibreChat has native SharePoint support. Connect it to your project&amp;rsquo;s documentation libraries.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Connect MCP Servers&lt;/strong&gt;: Add the SAP Docs and ADT MCP servers to the configuration.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;why-this-works-better&#34;&gt;Why this works better&lt;/h3&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Context is Key&lt;/strong&gt;: Unlike the SharePoint export method, the ADT MCP server understands the structure of ABAP. If you ask about a class, it knows to look for the definition and implementation. It doesn&amp;rsquo;t rely on a flat list of text files.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Data Privacy&lt;/strong&gt;: If you use a local model (like Llama 3) or a private Azure instance, your code never trains a public model.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;User Experience&lt;/strong&gt;: Developers get a chat interface they are used to (like ChatGPT), but it has &amp;ldquo;superpowers&amp;rdquo; to look up SAP notes and internal code.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;h3 id=&#34;sample-workflow-understanding-what-is-going-on-in-your-system&#34;&gt;Sample Workflow: Understanding What Is Going On in Your System&lt;/h3&gt;&#xA;&lt;p&gt;This is the day-to-day use case for consultants, architects, support, and anyone onboarding onto an SAP landscape.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The Request:&lt;/strong&gt;&#xA;Someone asks: &amp;ldquo;Tell me what &lt;code&gt;BAPI_DISPUTE_CREATE&lt;/code&gt; is doing in my SAP system. Use SAP docs and my system source code.&amp;rdquo;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The Process:&lt;/strong&gt;&#xA;The agent uses SAP Docs MCP to understand the standard context (what the BAPI is meant to do), then uses the ADT MCP server to find the function module in the actual system, pull the source and function group, and summarize what happens step by step.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Researching SAP System&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-02-16-librechat-enterprise-gpt/images/researching-sap-system.jpg&#34;&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;sample-workflow-implementing-a-rap-action&#34;&gt;Sample Workflow: Implementing a RAP Action&lt;/h3&gt;&#xA;&lt;p&gt;Instead of just answering simple questions, the agent can plan and implement features.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The Request:&lt;/strong&gt;&#xA;A developer uploads a PDF specification and asks: &lt;em&gt;&amp;ldquo;Please suggest what to change in my SAP System as specified in the Specification&amp;hellip;&amp;rdquo;&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;The Process:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;System Research (ADT MCP):&lt;/strong&gt; The Agent autonomously explores the system. It executes &lt;code&gt;SearchObject&lt;/code&gt; and &lt;code&gt;GetSource&lt;/code&gt; to find the relevant Business Object (&lt;code&gt;ZI_FOOTBALL_CLUBS&lt;/code&gt;), the Behavior Definition, and the Projection view.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Impact Analysis:&lt;/strong&gt; It identifies that a new action &lt;code&gt;UpdateFoundedYear&lt;/code&gt; is needed and lists the exact objects that need to be created or changed (BDEF, Class, Projection, Metadata).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Best Practice Lookup (SAP Docs MCP):&lt;/strong&gt; When asked for the implementation code, the agent searches SAP Help Portal for &amp;ldquo;Non-factory action&amp;rdquo; and &amp;ldquo;Action input parameter&amp;rdquo; to ensure the code follows best practices.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Code Generation:&lt;/strong&gt; It generates the complete ABAP implementation for the &lt;code&gt;ZCL_FOOTBALL_CLUBS&lt;/code&gt; class, including error handling (&lt;code&gt;FAILED&lt;/code&gt;/&lt;code&gt;REPORTED&lt;/code&gt;) and EML statements.&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;RAP Integration Step 1&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-02-16-librechat-enterprise-gpt/images/integration-rap-action-1.jpg&#34;&gt;&#xA;&lt;img alt=&#34;RAP Integration Step 2&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-02-16-librechat-enterprise-gpt/images/integration-rap-action-2.jpg&#34;&gt;&#xA;&lt;img alt=&#34;RAP Integration Step 3&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-02-16-librechat-enterprise-gpt/images/integration-rap-action-3.jpg&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;This turns the agent into a true pair programmer that understands &lt;em&gt;your&lt;/em&gt; specific system architecture.&lt;/p&gt;&#xA;&lt;h3 id=&#34;not-just-for-librechat-and-local-models&#34;&gt;Not Just for LibreChat and Local Models&lt;/h3&gt;&#xA;&lt;p&gt;I used LibreChat and Ollama because they are free and open source, which makes them perfect for a showcase. But the beauty of this architecture is its flexibility.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Want better models?&lt;/strong&gt;&#xA;You can swap Ollama for Azure OpenAI, Anthropic, or Mistral in seconds. LibreChat supports them out of the box. I even benchmarked different models for ABAP recently: &lt;a href=&#34;https://blog.zeis.de/posts/2026-02-09-abap-llm-benchmark/&#34;&gt;Benchmarking LLMs for ABAP&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Want to use Microsoft Copilot?&lt;/strong&gt;&#xA;Since MCP is an open standard, you aren&amp;rsquo;t tied to LibreChat. You can host these MCP servers as simple HTTP services and connect them to other clients, including Microsoft Copilot or other agent platforms that support the protocol. The MCP servers are just &amp;ldquo;infrastructure&amp;rdquo;, the client is interchangeable.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Want more data sources?&lt;/strong&gt;&#xA;There are MCP servers for GitHub, Jira, Confluence, and many more. You can &amp;ldquo;dock&amp;rdquo; them onto your agent just like we did with the SAP Docs server.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Want to extend it?&lt;/strong&gt;&#xA;You can add more MCP servers, create custom tools, or even build your own agents with specialized knowledge. The possibilities are endless.&lt;/p&gt;&#xA;&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;&#xA;&lt;p&gt;You don&amp;rsquo;t need to wait for a perfect, expensive enterprise product to start using AI securely with SAP. With Open Source tools like LibreChat and the ecosystem of MCP servers, you can build a solution that is secure, compliant, and incredibly powerful.&lt;/p&gt;&#xA;&lt;p&gt;It requires a bit of setup (Docker, Entra ID), but the payoff is a tool that actually understands your SAP system, without compromising on security.&lt;/p&gt;&#xA;&lt;p&gt;If you are interested in the technical details, check out my &lt;strong&gt;&lt;a href=&#34;https://github.com/marianfoo/llm-client-sap-system-integration-sharepoint/blob/main/docs/GUIDE.md&#34;&gt;Setup Guide&lt;/a&gt;&lt;/strong&gt; in the GitHub repository &lt;a href=&#34;https://github.com/marianfoo/llm-client-sap-system-integration-sharepoint&#34;&gt;marianfoo/llm-client-sap-system-integration-sharepoint&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h3 id=&#34;references--resources&#34;&gt;References &amp;amp; Resources&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Setup Guide &amp;amp; Repository:&lt;/strong&gt; &lt;a href=&#34;https://github.com/marianfoo/llm-client-sap-system-integration-sharepoint&#34;&gt;marianfoo/llm-client-sap-system-integration-sharepoint&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;SAP Docs MCP Server:&lt;/strong&gt; &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;marianfoo/mcp-sap-docs&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;ABAP MCP Server (for Copilot in ADT):&lt;/strong&gt; &lt;a href=&#34;https://github.com/marianfoo/abap-mcp-server&#34;&gt;marianfoo/abap-mcp-server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;ADT MCP Server (vibing-steampunk):&lt;/strong&gt; &lt;a href=&#34;https://github.com/oisee/vibing-steampunk&#34;&gt;oisee/vibing-steampunk&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;LibreChat:&lt;/strong&gt; &lt;a href=&#34;https://www.librechat.ai/&#34;&gt;librechat.ai&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;DSAG Investment Report 2026 (AI usage):&lt;/strong&gt; &lt;a href=&#34;https://dsag.de/presse/dsag-investment-report-2026-companies-are-investing-more-selectively-ai-is-becoming-established-cloud-computing-is-being-put-to-the-test/&#34;&gt;Companies are investing more selectively, AI is becoming established&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Benchmarking LLMs for ABAP:&lt;/strong&gt; &lt;a href=&#34;https://blog.zeis.de/posts/2026-02-09-abap-llm-benchmark/&#34;&gt;My Benchmark Post&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;MCP Protocol:&lt;/strong&gt; &lt;a href=&#34;https://modelcontextprotocol.io/&#34;&gt;modelcontextprotocol.io&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>Benchmarking LLMs for ABAP: Why ABAP-1 Isn&#39;t a Code Generator (Yet)</title>
      <link>https://blog.zeis.de/posts/2026-02-09-abap-llm-benchmark/</link>
      <pubDate>Mon, 09 Feb 2026 09:30:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2026-02-09-abap-llm-benchmark/</guid>
      <description>&lt;p&gt;&lt;strong&gt;Live benchmark results:&lt;/strong&gt; &lt;a href=&#34;http://abap-llm-benchmark.marianzeis.de/&#34;&gt;abap-llm-benchmark.marianzeis.de&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;In a lot of SAP webcasts and webinars, especially around AI, the question comes up very early: which model are you using, and which one do you recommend?&lt;/p&gt;&#xA;&lt;p&gt;For &lt;a href=&#34;https://cap.cloud.sap/docs/&#34;&gt;CAP&lt;/a&gt; and &lt;a href=&#34;https://ui5.sap.com/&#34;&gt;UI5&lt;/a&gt; the answer is usually pretty simple: use the current best model from Anthropic. If you add good context via &lt;a href=&#34;https://community.sap.com/t5/technology-blog-posts-by-sap/sap-build-introduces-new-mcp-servers-to-enable-agentic-development-for/ba-p/14205602&#34;&gt;MCP servers&lt;/a&gt; from the community or SAP, you are basically fine. There is just a lot of public knowledge available, and most of it is in JavaScript/TypeScript, which LLMs handle extremely well.&lt;/p&gt;&#xA;&lt;p&gt;With ABAP it&amp;rsquo;s different.&lt;/p&gt;&#xA;&lt;p&gt;ABAP is not the framework, it&amp;rsquo;s the language. And compared to JavaScript/TypeScript, far less ABAP knowledge and code is publicly accessible, so models simply know much less about it.&lt;/p&gt;&#xA;&lt;p&gt;Sure, you can help with extra context and tooling, but if you want this to work well, the model should already have a solid ABAP base. A good foundation makes it much easier to build on top.&lt;/p&gt;&#xA;&lt;p&gt;So how do you figure out which models are actually best for ABAP? Anthropic, Google, OpenAI, or maybe &lt;a href=&#34;https://help.sap.com/docs/sap-ai-core/generative-ai/sap-abap-1?locale=en-US&#34;&gt;ABAP-1&lt;/a&gt; from SAP?&lt;/p&gt;&#xA;&lt;p&gt;Luckily, TH Köln recently published a paper that answers exactly this question: &lt;a href=&#34;https://arxiv.org/html/2601.15188v1&#34;&gt;Benchmarking Large Language Models for ABAP Code Generation&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;the-benchmark&#34;&gt;The benchmark&lt;/h2&gt;&#xA;&lt;p&gt;The paper builds a benchmark so different LLMs can be compared fairly for ABAP code generation.&lt;/p&gt;&#xA;&lt;p&gt;It includes 180 tasks (&lt;a href=&#34;https://github.com/marianfoo/LLM-Benchmark-ABAP-Code-Generation/tree/main/dataset/abap_canonical_solution&#34;&gt;HumanEval&lt;/a&gt; adapted to ABAP plus a set of ABAP/SAP practical scenarios). The flow is fully automated: the model generates ABAP code, the code is created and activated in a fixed SAP environment (&lt;a href=&#34;https://github.com/SAP-docs/abap-platform-trial-image&#34;&gt;ABAP Cloud Developer Trial image documentation&lt;/a&gt;), and then ABAP Unit tests run against it.&lt;/p&gt;&#xA;&lt;p&gt;If syntax checks or tests fail, the model gets the error messages as feedback and can try again for up to 5 rounds. In the end, it&amp;rsquo;s not about whether the code &amp;ldquo;looks plausible&amp;rdquo;, but whether it actually passes the tests (and how quickly it gets there).&lt;/p&gt;&#xA;&lt;p&gt;The nice part: the authors published everything.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Original code&lt;/strong&gt;: &lt;a href=&#34;https://github.com/timkoehne/LLM-Benchmark-ABAP-Code-Generation&#34;&gt;LLM-Benchmark-ABAP-Code-Generation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Dataset (prompts, unit tests, expected classes)&lt;/strong&gt;: &lt;a href=&#34;https://github.com/timkoehne/LLM-Benchmark-ABAP-Code-Generation/tree/main/dataset&#34;&gt;dataset folder&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;My updated benchmark repo (additional models, speed-ups, plots, data, webpage)&lt;/strong&gt;: &lt;a href=&#34;https://github.com/marianfoo/LLM-Benchmark-ABAP-Code-Generation&#34;&gt;marianfoo/LLM-Benchmark-ABAP-Code-Generation&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;updating-the-benchmark&#34;&gt;Updating the benchmark&lt;/h2&gt;&#xA;&lt;p&gt;One small problem: the evaluation in the paper is from mid-2025 (for example using GPT-5 and Claude Sonnet 4). The model landscape moves fast, and now we already have things like GPT-5.3 and &lt;a href=&#34;https://www.anthropic.com/news/claude-opus-4-6&#34;&gt;Claude Opus 4.6&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;So I wanted a way to update the results and include additional models that are relevant for ABAP.&lt;/p&gt;&#xA;&lt;p&gt;I built on the original benchmark and added runs for additional models, so I can compare them against the original GPT-5 and Sonnet numbers:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Anthropic Claude Opus 4.5 (4.6 will be next)&lt;/li&gt;&#xA;&lt;li&gt;OpenAI GPT-5.2&lt;/li&gt;&#xA;&lt;li&gt;SAP ABAP-1&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I also made a few changes to speed up execution:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;For OpenAI and Anthropic I used the vendor batch APIs like in the original code (&lt;a href=&#34;https://platform.openai.com/docs/guides/batch&#34;&gt;OpenAI Batch API&lt;/a&gt;, &lt;a href=&#34;https://docs.anthropic.com/en/docs/build-with-claude/batch-processing&#34;&gt;Anthropic batch processing&lt;/a&gt;). GPT-5.2 Codex was not available via batch for me yet, but that model will be next.&lt;/li&gt;&#xA;&lt;li&gt;ABAP-1 has no batch API, so I implemented a parallel execution with multiple threads.&lt;/li&gt;&#xA;&lt;li&gt;I also parallelized the execution on the ABAP Trial system itself to reduce overall runtime. I&amp;rsquo;m happy I have a well-equipped MacBook, but during the runs RAM usage and fan speed were definitely not idle.&lt;/li&gt;&#xA;&lt;li&gt;Since there is a lot of Python code and I don&amp;rsquo;t know Python well enough, all the coding was done with AI help.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The next step is a small website with an overview of the current results and an easy model comparison.&lt;/p&gt;&#xA;&lt;p&gt;You can find the current results here: &lt;a href=&#34;http://abap-llm-benchmark.marianzeis.de/&#34;&gt;abap-llm-benchmark.marianzeis.de&lt;/a&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;so-what-is-the-best-model-for-abap&#34;&gt;So what is the best model for ABAP?&lt;/h2&gt;&#xA;&lt;p&gt;Not too surprising: the best results came from GPT-5 and Claude Opus 4.5, with a small edge for Opus.&lt;/p&gt;&#xA;&lt;p&gt;To make it more concrete, here are the cumulative success rates from my current comparison (Round 0 = first attempt, Round 5 = after up to 5 feedback iterations with compiler/test errors):&lt;/p&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;th&gt;Model&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;Round 0&lt;/th&gt;&#xA;          &lt;th style=&#34;text-align: right&#34;&gt;Round 5&lt;/th&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;Claude Opus 4.5&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;31.61%&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;78.72%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;GPT-5&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;19.28%&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;77.11%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;GPT-5.2&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;16.33%&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;64.00%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;      &lt;tr&gt;&#xA;          &lt;td&gt;SAP ABAP-1&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;10.67%&lt;/td&gt;&#xA;          &lt;td style=&#34;text-align: right&#34;&gt;19.89%&lt;/td&gt;&#xA;      &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;Here is the same as a chart:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;ABAP LLM benchmark: cumulative success rates by model (GPT-5, Claude Opus 4.5, ABAP-1) and feedback round&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-02-09-abap-llm-benchmark/success_by_model_by_feedbackround_in_percent.png&#34;&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;cost-rough-numbers&#34;&gt;Cost (rough numbers)&lt;/h3&gt;&#xA;&lt;p&gt;One more thing people ask quickly: cost.&lt;/p&gt;&#xA;&lt;p&gt;For my run, I ended up at about $20.26 for GPT-5.2, $39.76 for Claude Opus 4.5 (8,973,159 tokens), and EUR 98.80 for ABAP-1 (17,983,990 tokens).&lt;/p&gt;&#xA;&lt;p&gt;To be fair: for OpenAI and Anthropic I used batch processing, so these numbers are already discounted (batch is usually cheaper, sometimes roughly half compared to the regular API). ABAP-1 has no batch API, so there is no discount like that.&lt;/p&gt;&#xA;&lt;p&gt;Why was ABAP-1 so much more expensive? The benchmark is iterative: when activation or ABAP Unit tests fail, you send the error back to the model and try again. Since ABAP-1 produced errors much more often, many prompts did not get &amp;ldquo;finished&amp;rdquo; early and had to be sent again and again, which drives up tokens and cost.&lt;/p&gt;&#xA;&lt;p&gt;What I find interesting here is not only the final number, but also how the models behave across the feedback loops.&lt;/p&gt;&#xA;&lt;h3 id=&#34;why-is-gpt-52-worse-than-gpt-5-in-this-benchmark&#34;&gt;Why is GPT-5.2 worse than GPT-5 (in this benchmark)?&lt;/h3&gt;&#xA;&lt;p&gt;I can&amp;rsquo;t prove the exact reason (this is a black box), but based on the patterns in the benchmark it looks like GPT-5 is simply better at using compiler feedback to dig itself out of ABAP syntax and declaration issues.&lt;/p&gt;&#xA;&lt;p&gt;GPT-5.2 still does fine, but compared to GPT-5 it is:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;less consistent across reruns (about 41% of prompts solved 10/10 vs about 59% for GPT-5)&lt;/li&gt;&#xA;&lt;li&gt;less likely to recover from certain ABAP-specific errors within a few feedback rounds&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;My best guess is that GPT-5.2 is tuned differently (for example more general, faster, cheaper, or optimized for other coding workflows), and that does not translate 1:1 to &amp;ldquo;generate a full ABAP class that compiles and passes ABAP Unit tests&amp;rdquo;. Maybe when using the codex model, the results are better.&lt;/p&gt;&#xA;&lt;h3 id=&#34;claude-opus-45&#34;&gt;Claude Opus 4.5&lt;/h3&gt;&#xA;&lt;p&gt;Opus starts very strong in Round 0. That usually means less back and forth for basic ABAP class structure and syntax, and more time spent on the actual logic.&lt;/p&gt;&#xA;&lt;p&gt;In practice, this is what you want: you want to reach the unit test stage quickly, because that feedback is much more actionable than &amp;ldquo;your code does not activate&amp;rdquo;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;sap-abap-1&#34;&gt;SAP ABAP-1&lt;/h3&gt;&#xA;&lt;p&gt;ABAP-1 is clearly not competitive for code generation in this benchmark, and it also benefits much less from the feedback loops.&lt;/p&gt;&#xA;&lt;p&gt;This matches what SAP writes in the docs: ABAP-1 is mainly meant to explain ABAP code, while generation is experimental. For me, ABAP-1 is still useful as an &amp;ldquo;ABAP explainer&amp;rdquo;, but if you want to generate correct ABAP code fast, you still want a strong general model plus good tooling and feedback loops.&lt;/p&gt;&#xA;&lt;h3 id=&#34;what-this-means-in-practice&#34;&gt;What this means in practice&lt;/h3&gt;&#xA;&lt;p&gt;This matters most when you work with APIs and actually build things. If you only call APIs, the model choice matters more. If you actively develop, it matters a bit less because we can add a lot of context and feedback loops.&lt;/p&gt;&#xA;&lt;p&gt;When developing ABAP, we can provide much more context (for example via my &lt;a href=&#34;https://github.com/marianfoo/abap-mcp-server&#34;&gt;ABAP MCP server&lt;/a&gt;), catch syntax errors early, run tools like &lt;a href=&#34;https://abaplint.org/&#34;&gt;abaplint&lt;/a&gt;, and then feed that back into the LLM to improve the code, basically like in TypeScript. That usually gets you to pretty good ABAP code that also follows your rules.&lt;/p&gt;&#xA;&lt;p&gt;Depending on cost, I&amp;rsquo;ll try to keep this up to date with newer models. If you have a model you want to see in the comparison, just open an issue in the benchmark repo: &lt;a href=&#34;https://github.com/marianfoo/LLM-Benchmark-ABAP-Code-Generation/issues&#34;&gt;GitHub issues&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Big thanks to Stephan Wallraven, Tim Köhne, Hartmut Westenberger, and Andreas Moser for creating the foundation with their benchmark and paper.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Network Request &amp; Response Copier: An OData-Friendly DevTools Request Viewer</title>
      <link>https://blog.zeis.de/posts/2026-02-05-networkcopier-chrome/</link>
      <pubDate>Thu, 05 Feb 2026 09:30:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2026-02-05-networkcopier-chrome/</guid>
      <description>&lt;p&gt;AI assistants already help a lot, but they work best when you can give them good context. In web apps, the most important context is usually the real traffic between frontend and backend: &lt;strong&gt;request URLs, payloads, and responses&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;When debugging, I want to copy that information quickly and completely. And since I mostly work with &lt;strong&gt;UI5 and OData&lt;/strong&gt;, I usually need the full URL plus request and response body. In Chrome DevTools that often means a lot of manuall copy pasting, and if you need more than one request, it gets annoying fast.&lt;/p&gt;&#xA;&lt;p&gt;So I did the obvious thing: I built a small Chrome DevTools extension with AI help that solves this problem for me. Yes, I could also use MCP tooling or an automated browser to extract network data, but for day-to-day debugging I care about speed and I usually know exactly which requests matter.&lt;/p&gt;&#xA;&lt;p&gt;After using it locally for a while, I decided to publish my first extension in the Chrome Web Store.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Network Request &amp;amp; Response Copier inside Chrome DevTools&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-02-05-networkcopier-chrome/screenshot.jpg&#34;&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;&lt;strong&gt;Install it here:&lt;/strong&gt; &lt;a href=&#34;https://chromewebstore.google.com/detail/network-request-response/mphiaidjajmllkfkjlkfgmfkccpnomgc&#34;&gt;Chrome Web Store&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Update (2026-02-10):&lt;/strong&gt; Also available in the &lt;a href=&#34;https://microsoftedge.microsoft.com/addons/detail/network-request-respons/mmfhobojdlgibnffjhkidhdcjfbfhgpo&#34;&gt;Microsoft Edge Add-ons store&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Source code:&lt;/strong&gt; &lt;a href=&#34;https://github.com/marianfoo/chrome-extension-copynetworkrequests&#34;&gt;GitHub&lt;/a&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;the-solution&#34;&gt;The solution&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;Network Request &amp;amp; Response Copier&lt;/strong&gt; adds a dedicated panel to DevTools that shows network entries in a clean, sortable list.&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Left panel&lt;/strong&gt;: sortable request list with filters, a payload preview, and pinning&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Right panel&lt;/strong&gt;: request payload (top) and response body (bottom)&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Copy buttons&lt;/strong&gt;: copy the selected entry, all filtered entries, or all pinned entries&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;And the part that matters most for my work: it makes OData traffic much easier to work with, especially &lt;code&gt;$batch&lt;/code&gt; requests.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;key-features&#34;&gt;Key features&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Copy in one click&lt;/strong&gt;: Copy the selected entry with &lt;code&gt;Ctrl/Cmd+C&lt;/code&gt;, or export all filtered or pinned entries. Output includes method, URL, payload, and response in one readable block.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;OData &lt;code&gt;$batch&lt;/code&gt; parsing&lt;/strong&gt;: Splits multipart &lt;code&gt;$batch&lt;/code&gt; into per-operation blocks and shows a quick “what’s inside” preview in the list (for example: &lt;code&gt;PATCH MockConfig | GET Users&lt;/code&gt;).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Better OData overview&lt;/strong&gt;: Keeps long &lt;code&gt;$select&lt;/code&gt; and &lt;code&gt;$filter&lt;/code&gt; URLs readable and shows request and response side-by-side, which makes sharing context (and debugging) much faster.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;WebSocket included&lt;/strong&gt;: Captures sent and received messages and shows them alongside HTTP entries.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;DevTools-like workflow&lt;/strong&gt;: Sort, search, filter by method, resize panes, pin important calls.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Stays fast&lt;/strong&gt;: 500-entry cap, throttled UI updates, cached parsing.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;how-it-works-short-version&#34;&gt;How it works (short version)&lt;/h2&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Runs as a DevTools panel, so it only exists while DevTools is open.&lt;/li&gt;&#xA;&lt;li&gt;Collects HTTP traffic via &lt;code&gt;chrome.devtools.network.onRequestFinished&lt;/code&gt; (HAR entries).&lt;/li&gt;&#xA;&lt;li&gt;Detects and parses &lt;code&gt;$batch&lt;/code&gt; payloads to show a per-operation overview.&lt;/li&gt;&#xA;&lt;li&gt;Captures WebSocket messages by instrumenting &lt;code&gt;WebSocket&lt;/code&gt; in the inspected page.&lt;/li&gt;&#xA;&lt;li&gt;Copies to clipboard with fallbacks that work in the DevTools environment.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;You&amp;rsquo;re welcome to use it and let me know what you think, try it out suggest changes!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Finally: An MCP Server for ABAP</title>
      <link>https://blog.zeis.de/posts/2026-02-04-abap-mcp-server/</link>
      <pubDate>Wed, 04 Feb 2026 09:30:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2026-02-04-abap-mcp-server/</guid>
      <description>&lt;p&gt;Finally, there is an MCP server for ABAP.&lt;/p&gt;&#xA;&lt;p&gt;You can use it directly in Eclipse via &lt;code&gt;https://mcp-abap.marianzeis.de/mcp&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Setup instructions are here: &lt;a href=&#34;https://github.com/marianfoo/abap-mcp-server/blob/main/README.md#eclipse-configuration-github-copilot&#34;&gt;Eclipse configuration (GitHub Copilot)&lt;/a&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;why-a-community-abap-mcp-server&#34;&gt;Why a community ABAP MCP server?&lt;/h2&gt;&#xA;&lt;p&gt;SAP has announced an MCP server for ABAP, so why create a community one?&lt;/p&gt;&#xA;&lt;p&gt;The trigger for me was the &lt;a href=&#34;https://github.com/orgs/community/discussions/151288&#34;&gt;release of Agent Mode in GitHub Copilot for ADT&lt;/a&gt;, which finally makes it possible to edit ABAP code by Copilot.&lt;/p&gt;&#xA;&lt;p&gt;LLM models are already really good, but ABAP knowledge is still a recurring problem. So I wanted an MCP server that is actually tailored for ABAP.&lt;/p&gt;&#xA;&lt;p&gt;For that, I took my existing MCP server for SAP docs, removed documentation that is irrelevant for ABAP, and added ABAP-specific sources like Official ABAP Keyword Documentation, DSAG ABAP Development Guidelines and ABAP Style Guide.&lt;/p&gt;&#xA;&lt;p&gt;I also tweaked the MCP tools so that search automatically looks beyond local docs and includes SAP Community posts and SAP Help, then returns the best results. And since GitHub Copilot in Eclipse is not exactly the fastest, getting to relevant context quicker makes a real difference.&lt;/p&gt;&#xA;&lt;p&gt;Here is what it looks like in Eclipse:&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Eclipse with MCP server&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-02-04-abap-mcp-server/eclipse-mcp-server.jpg&#34;&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;why-eclipse-matters-right-now&#34;&gt;Why Eclipse matters (right now)&lt;/h2&gt;&#xA;&lt;p&gt;GitHub Copilot for Eclipse is especially important right now because (currently) Joule for Developers in Eclipse is not available for S/4HANA on-premise systems. For many ABAP developers in Eclipse, Copilot is the only practical way to use LLMs today.&lt;/p&gt;&#xA;&lt;p&gt;Sure, there is also VS Code, and SAP will support it in the near future. But let&amp;rsquo;s be honest: not every ABAP developer is even on Eclipse yet. And the &amp;ldquo;average developer&amp;rdquo; is not going to switch to VS Code now, and probably not anytime soon either.&lt;/p&gt;&#xA;&lt;p&gt;So this means: &lt;strong&gt;right now&lt;/strong&gt;, every ABAP developer, no matter which system, can use LLMs in Eclipse and also access ABAP-specific knowledge.&lt;/p&gt;&#xA;&lt;p&gt;Competition is good, so I published the ABAP MCP server as open source. That way, we have something to compare once SAP releases their own ABAP MCP server. And it also gives us a useful alternative to Joule for Developers.&lt;/p&gt;&#xA;&lt;p&gt;Uwe Fetzer already tested the new ABAP MCP server and confirmed it works better than the SAP MCP Docs server:&#xA;&lt;a href=&#34;https://saptodon.org/@se38@nrw.social/116011617758947257&#34;&gt;saptodon.org post&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Try it out and let me know what you think! You can use either the deployed version on my server or run it locally with Node or Docker.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;update-2026-02-05&#34;&gt;Update (2026-02-05)&lt;/h2&gt;&#xA;&lt;p&gt;Since publishing, I added two new capabilities:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Software Heroes as an online search source&lt;/strong&gt;: The &lt;code&gt;search&lt;/code&gt; tool can now also pull relevant content from &lt;a href=&#34;https://software-heroes.com/&#34;&gt;Software Heroes&lt;/a&gt; (including German and English results).&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;New &lt;code&gt;abap_feature_matrix&lt;/code&gt; tool&lt;/strong&gt;: You can query the &lt;a href=&#34;https://software-heroes.com/abap-feature-matrix&#34;&gt;ABAP Feature Matrix&lt;/a&gt; to quickly check feature availability across releases and ABAP Cloud, also provided by Björn Schulz.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Huge thanks to &lt;strong&gt;Björn Schulz&lt;/strong&gt; for offering his APIs and making these integrations possible.&lt;/p&gt;&#xA;&lt;p&gt;More info about the tools and sources is in the GitHub Repo README: &lt;a href=&#34;https://github.com/marianfoo/abap-mcp-server#available-tools&#34;&gt;https://github.com/marianfoo/abap-mcp-server#available-tools&lt;/a&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Animalist in German: I Had to Build This</title>
      <link>https://blog.zeis.de/posts/2026-02-02-animalist/</link>
      <pubDate>Mon, 02 Feb 2026 09:30:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2026-02-02-animalist/</guid>
      <description>&lt;p&gt;I found Vivian Rose’s little web game &lt;strong&gt;“List Animals Until Failure”&lt;/strong&gt; through a Hacker News thread (&lt;a href=&#34;https://news.ycombinator.com/item?id=46842603&#34;&gt;HN discussion&lt;/a&gt;) and immediately got hooked.&lt;/p&gt;&#xA;&lt;p&gt;Then I typed my first few animals… and realized my English animal vocabulary is not exactly perfect. So I did the only sensible thing and made a German version.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;Screenshot of the German Animalist game interface&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-02-02-animalist/animalist.jpg&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;The UI text is mostly translated with AI help (as all the other code). The part that actually took time was getting the animal names.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;what-the-game-is&#34;&gt;What the game is&lt;/h2&gt;&#xA;&lt;p&gt;It’s simple: you type animal names until you fail. If you repeat something, or you try to sneak in a too-broad term after a more specific one (and vice versa), the game calls you out.&lt;/p&gt;&#xA;&lt;p&gt;The magic is that it feels &lt;strong&gt;instant&lt;/strong&gt; because when you load the app you also load the complete animal list.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;where-the-animal-list-comes-from&#34;&gt;Where the animal list comes from&lt;/h2&gt;&#xA;&lt;p&gt;Vivian explains the original approach in her write-up (&lt;a href=&#34;https://rose.systems/blog/list-animals-until-failure&#34;&gt;blog post&lt;/a&gt;): the game is powered by &lt;strong&gt;Wikidata + Wikipedia&lt;/strong&gt;, and it ships a big offline lookup table so gameplay needs &lt;strong&gt;no network calls&lt;/strong&gt;.&lt;/p&gt;&#xA;&lt;p&gt;At a high level, the original project:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;mines Wikidata entities representing taxons and builds a parent/child tree&lt;/li&gt;&#xA;&lt;li&gt;uses Wikipedia article titles + redirects (and some extra heuristics like disambiguation pages) to decide what inputs should count as “the same animal term”&lt;/li&gt;&#xA;&lt;li&gt;has a bunch of hand-tuning for edge cases&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;For the German version, I took a pragmatic route:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;keep the &lt;strong&gt;same Q-IDs and taxonomy structure&lt;/strong&gt; from the original dataset&lt;/li&gt;&#xA;&lt;li&gt;fetch &lt;strong&gt;German names&lt;/strong&gt; for those Q-IDs from Wikidata in batches&lt;/li&gt;&#xA;&lt;li&gt;write out the German lookup tables that the game uses at runtime&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;I ended up with a few small Python helpers: one to fetch the German names from Wikidata, one to re-try missing entries, and one to add common singular forms (so “hai” works even if the canonical title is “haie”).&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;credits&#34;&gt;Credits&lt;/h2&gt;&#xA;&lt;p&gt;This exists because Vivian Rose built something genuinely fun and also wrote about it:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Original game: &lt;a href=&#34;https://rose.systems/animalist/&#34;&gt;List Animals Until Failure&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Original repo: &lt;a href=&#34;https://github.com/Roachbones/animalist&#34;&gt;Roachbones/animalist&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Original write-up: &lt;a href=&#34;https://rose.systems/blog/list-animals-until-failure&#34;&gt;blog post&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;And here’s my German adaptation again:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;German version: &lt;a href=&#34;https://animalist-de.marianzeis.de/&#34;&gt;animalist-de.marianzeis.de&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Repo: &lt;a href=&#34;https://github.com/marianfoo/animalist_de&#34;&gt;marianfoo/animalist_de&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>Extract your SAP Community blog posts with a simple Node.js script</title>
      <link>https://blog.zeis.de/posts/2026-01-20-download-scn-posts/</link>
      <pubDate>Tue, 20 Jan 2026 06:00:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2026-01-20-download-scn-posts/</guid>
      <description>&lt;p&gt;I wanted to have a local archive of my SAP Community blog posts for my own reference and to have a better overview of my content.&lt;br&gt;&#xA;Also I tried writing posts locally in Markdown first and then converting them to HTML. Mixed experience so far.&lt;br&gt;&#xA;Currently this is in a private repository and it fetches the latest posts from SAP Community daily if there are any new ones.&lt;/p&gt;&#xA;&lt;p&gt;I at least wanted to share my script so others can use it too.&lt;/p&gt;&#xA;&lt;p&gt;As I&amp;rsquo;m currently just too lazy to create a proper repository for this, I will just share the script here.&lt;/p&gt;&#xA;&lt;h2 id=&#34;what-you-need&#34;&gt;What you need&lt;/h2&gt;&#xA;&lt;p&gt;You need Node.js 18+ (Node 22 works as well). The script uses the built-in &lt;code&gt;fetch()&lt;/code&gt; API, so you do not need extra dependencies.&lt;/p&gt;&#xA;&lt;h2 id=&#34;the-idea&#34;&gt;The idea&lt;/h2&gt;&#xA;&lt;p&gt;The workflow is intentionally simple and mirrors a common “ETL” pattern.&lt;/p&gt;&#xA;&lt;p&gt;First, fetch the posts and store the raw API response as JSON.&lt;/p&gt;&#xA;&lt;p&gt;Second, generate local HTML files (one file per post). The SAP Community API already returns the post body as HTML, so the script wraps that into a minimal HTML document and adds a few metadata fields into the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Third, scan those HTML files for &lt;code&gt;&amp;lt;img src=&amp;quot;...&amp;quot;&amp;gt;&lt;/code&gt; references and download the images. This makes the archive usable offline and helps when you want to copy content into another system later.&lt;/p&gt;&#xA;&lt;p&gt;This also makes it possible to just open the HTML files in a browser and read the posts offline, including the pictures.&lt;/p&gt;&#xA;&lt;h2 id=&#34;a-minimal-script-test-mode&#34;&gt;A minimal script (test mode)&lt;/h2&gt;&#xA;&lt;p&gt;Save the following file as &lt;code&gt;blogpost/extract-posts-generate-html-download-images.js&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;p&gt;If you prefer, you can also download the script directly from this post: &lt;a href=&#34;https://blog.zeis.de/downloads/extract-posts-generate-html-download-images.js&#34;&gt;&lt;code&gt;/downloads/extract-posts-generate-html-download-images.js&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Before you run it, change &lt;code&gt;USER_ID&lt;/code&gt; to your own SAP Community numeric user ID. For testing, the script uses &lt;code&gt;LIMIT = 10&lt;/code&gt;, so you don’t accidentally pull thousands of posts while you are still experimenting.&lt;/p&gt;&#xA;&lt;p&gt;Please be API-friendly: keep &lt;code&gt;LIMIT&lt;/code&gt; small while testing, and don’t run the script in a tight loop.&lt;/p&gt;&#xA;&lt;p&gt;Expand the section below to see the full script.&lt;/p&gt;&#xA;&#xA;&#xA;&lt;p&gt;&lt;details &gt;&#xA;  &lt;summary markdown=&#34;span&#34;&gt;extract-posts-generate-html-download-images.js&lt;/summary&gt;&#xA;  &lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-javascript&#34; data-lang=&#34;javascript&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/**&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * SAP Community: extract your posts (test mode) and archive locally.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * This single script intentionally combines three steps into one file:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *   1) fetch posts from the SAP Community Search API&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *   2) generate local HTML files from the response (one file per post)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *   3) download images referenced by those HTML files&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Why keep it in one file?&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - It’s easier to explain in a short blog post.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - Readers can copy/paste one script, change USER_ID, run it, and inspect output.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * IMPORTANT:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - This is a &amp;#34;demo/testing&amp;#34; version. The query uses LIMIT 10 on purpose.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - The SAP Community API can be rate-limited. Keep limits small while experimenting.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Requirements:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - Node.js 18+ (Node 22 also works). This script relies on the built-in global fetch().&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Usage:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *   node blogpost/extract-posts-generate-html-download-images.js&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Output folders created inside blogpost/:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - blogpost/output/user_&amp;lt;USER_ID&amp;gt;_messages_limit10.json   (raw API response)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - blogpost/posts/                                       (generated HTML posts)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - blogpost/images/&amp;lt;postId&amp;gt;/                             (downloaded images)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; */&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fs&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fsp&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;promises&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;path&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;https&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;https&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;http&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;http&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;crypto&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;require&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;crypto&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// --- Configuration (change these for your own account) -----------------------&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Your SAP Community numeric user id (not your username).&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// In Marian’s repo this is 61; replace with your own.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;USER_ID&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;61&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Keep this small while testing. For a real archive you’d increase it.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// If you hit server-side errors (e.g., HTTP 413), you’ll need batching.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;LIMIT&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// SAP Community Search API endpoint used by this repo.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;API_BASE_URL&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;https://community.sap.com/api/2.0/search&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// Small delay between image downloads (be respectful).&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;IMAGE_DOWNLOAD_DELAY_MS&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;200&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// --- Paths (everything is local to this folder to keep it self-contained) ----&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ROOT_DIR&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;__dirname&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// blogpost/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;OUTPUT_DIR&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ROOT_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;output&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;POSTS_DIR&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ROOT_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;posts&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;IMAGES_DIR&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ROOT_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;images&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// --- Step 1: Fetch posts -----------------------------------------------------&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/**&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Fetch top-level posts for a user and store the raw response as JSON.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Why store the raw response?&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - It’s your &amp;#34;source of truth&amp;#34; archive.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - It makes debugging easier: you can inspect what the API returned.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - It decouples fetching from the downstream steps (HTML generation, downloads).&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; */&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fetchPostsAsJson&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;userId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;limit&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fsp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mkdir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;OUTPUT_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;recursive&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// depth=0 = top-level posts (not replies/comments)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// Keep selected fields small to reduce payload; body is still the largest part.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// For a larger export, consider fetching IDs first, then bodies in batches (IN (...)).&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;query&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`select id,subject,post_time,depth,body from messages where author.id=&amp;#39;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;userId&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&amp;#39; and depth=0 order by post_time desc limit &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;limit&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;API_BASE_URL&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;?q=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`\n[1/3] Fetching posts from SAP Community API...`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      USER_ID=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;userId&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;, LIMIT=&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;limit&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      Query: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;query&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;response&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fetch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ok&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;body&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;text&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;throw&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`API request failed: HTTP &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;status&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;\n&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;json&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;response&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;items&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;data&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;items&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;??&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[];&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;outFile&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;OUTPUT_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`user_&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;userId&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;_messages_limit&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;limit&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;.json`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fsp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;writeFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;outFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;JSON&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;stringify&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;json&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;2&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;utf8&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      Saved raw response to: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;outFile&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      Posts returned: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;items&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;length&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;outFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;items&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;};&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// --- Step 2: Generate HTML files --------------------------------------------&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/**&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Generate one HTML file per post.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Why generate HTML at all?&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - It’s the easiest format to open in any browser.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - It enables simple image extraction by scanning &amp;lt;img src=&amp;#34;...&amp;#34;&amp;gt; tags.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Note:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - SAP Community post bodies already contain HTML. We keep it as-is and wrap it&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *   into a minimal HTML document with metadata in &amp;lt;head&amp;gt;.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; */&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;generateHtmlFiles&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;posts&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fsp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mkdir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;POSTS_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;recursive&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`\n[2/3] Generating local HTML files...`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;written&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;of&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;posts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;id&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;postDate&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;safeDate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;post_time&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;dateStr&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;postDate&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;postDate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;toISOString&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;slice&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;10&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;unknown-date&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;cleanSubject&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;safeSlug&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;subject&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;sap-community-post&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;slice&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;40&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;filename&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;dateStr&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;-&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;cleanSubject&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;.html`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;filepath&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;POSTS_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;buildHtmlDocument&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;subject&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;SAP Community Post&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s1&#34;&gt;&amp;#39;post-id&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;id&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s1&#34;&gt;&amp;#39;author-id&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;USER_ID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;s1&#34;&gt;&amp;#39;post-time&amp;#39;&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;post_time&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;depth&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;depth&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;??&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;),&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;subject&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;subject&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;},&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;bodyHtml&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;post&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;body&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fsp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;writeFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;filepath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;utf8&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;written&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      Wrote: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      HTML files written: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;written&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      Output folder: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;POSTS_DIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;buildHtmlDocument&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;meta&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;bodyHtml&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;})&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;escapeAttr&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;s&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;replace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;/&amp;#34;/g&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;amp;quot;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;metaTags&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Object&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;entries&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;meta&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{})&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(([&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`    &amp;lt;meta name=&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;escapeAttr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;k&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&amp;#34; content=&amp;#34;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;escapeAttr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;v&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&amp;#34;&amp;gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`&amp;lt;!DOCTYPE html&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;&amp;lt;html lang=&amp;#34;en&amp;#34;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;&amp;lt;head&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    &amp;lt;meta charset=&amp;#34;UTF-8&amp;#34;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    &amp;lt;meta name=&amp;#34;viewport&amp;#34; content=&amp;#34;width=device-width, initial-scale=1.0&amp;#34;&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;    &amp;lt;title&amp;gt;&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;escapeAttr&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&amp;lt;/title&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;metaTags&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;&amp;lt;/head&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;&amp;lt;body&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;bodyHtml&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;&amp;lt;/body&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;&amp;lt;/html&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;safeDate&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;d&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Date&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// NaN check: invalid date produces NaN time&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Number&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;isFinite&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;d&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;getTime&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;())&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;d&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;null&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;safeSlug&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;value&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;replace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;/^Re:\s*/i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;replace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;/[&amp;lt;&amp;gt;:&amp;#34;/\\|?*]/g&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;c1&#34;&gt;// invalid filename characters&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;trim&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;replace&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;/\s+/g&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;toLowerCase&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// --- Step 3: Download images -------------------------------------------------&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt;/**&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Scan each generated HTML file for &amp;lt;img src=&amp;#34;...&amp;#34;&amp;gt; and download the images.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Why download images?&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - When you archive HTML locally, images often point to remote URLs.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - A local copy is useful for offline reading and for further processing.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * Notes:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - This is a lightweight HTML scan using regex (good enough for a blog post).&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; * - Some posts may use SAP Community-specific tags or lazy-loading patterns.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; *   If an image is missed, you can refine the regex or parse with a DOM library.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;cm&#34;&gt; */&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;downloadImagesFromGeneratedHtml&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fsp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mkdir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;IMAGES_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;recursive&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`\n[3/3] Downloading images referenced by the generated HTML...`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;files&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fsp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;readdir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;POSTS_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[]);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;htmlFiles&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;files&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;filter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;f&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;endsWith&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;.html&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;htmlFiles&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      No HTML files found in &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;POSTS_DIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt; (skipping image download).`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;totalFound&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;downloaded&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kd&#34;&gt;let&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;skipped&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;htmlFile&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;of&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;htmlFiles&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Filename format: YYYY-MM-DD-ID-subject.html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;match&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;htmlFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;match&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;/^\d{4}-\d{2}-\d{2}-(\d+)-/&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;postId&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;match&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;unknown-post&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;htmlPath&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;POSTS_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;htmlFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fsp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;readFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;htmlPath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;s1&#34;&gt;&amp;#39;utf8&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;imageUrls&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;extractImgSrcUrls&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;imageUrls&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;length&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;totalFound&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;+=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;imageUrls&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;length&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;postDir&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;IMAGES_DIR&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;postId&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fsp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;mkdir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;postDir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;recursive&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;htmlFile&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;: found &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;imageUrls&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;length&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt; &amp;lt;img&amp;gt; URLs`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;for&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;imageUrl&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;of&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;imageUrls&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;filename&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;chooseImageFilename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;imageUrl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fullPath&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;join&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;postDir&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;filename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;exists&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fullPath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;nx&#34;&gt;skipped&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;          &lt;span class=&#34;k&#34;&gt;continue&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;downloadToFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;imageUrl&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fullPath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;downloaded&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;++&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// Be polite to the server.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sleep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;IMAGE_DOWNLOAD_DELAY_MS&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;c1&#34;&gt;// For a blog post, it’s fine to keep going on individual failures.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`        Failed: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;imageUrl&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt; (&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;)`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`\n      Image download summary:`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      - &amp;lt;img&amp;gt; URLs found: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;totalFound&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      - downloaded: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;downloaded&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      - skipped (already present): &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;skipped&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`      Images folder: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;IMAGES_DIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;extractImgSrcUrls&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// Matches: &amp;lt;img ... src=&amp;#34;...&amp;#34; ...&amp;gt; and &amp;lt;IMG ... SRC=&amp;#39;...&amp;#39; ...&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;imageRegex&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;sr&#34;&gt;/&amp;lt;img[^&amp;gt;]*\ssrc\s*=\s*[&amp;#34;&amp;#39;]([^&amp;#34;&amp;#39;]+)[&amp;#34;&amp;#39;][^&amp;gt;]*&amp;gt;/gi&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;matches&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;[...&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;html&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;matchAll&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;imageRegex&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)];&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;matches&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;map&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;m&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;filter&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;Boolean&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;chooseImageFilename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// Try to keep recognizable filenames for SAP Community images, else create a stable hash.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// SAP Community often uses URLs like: .../image-id/&amp;lt;id&amp;gt;/...&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;idMatch&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;match&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sr&#34;&gt;/image-id\/([^\/?#]+)/i&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;idMatch&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;?&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;])&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;idMatch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;]&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;.jpg`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;u&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;URL&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;path&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;basename&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;u&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pathname&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;split&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;?&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)[&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;];&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;base&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;base&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;includes&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;))&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;base&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c1&#34;&gt;// Not a valid absolute URL, fall through to hash.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;hash&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;crypto&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createHash&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;sha1&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;update&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;digest&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;hex&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;slice&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;0&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;12&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;sb&#34;&gt;`image_&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;hash&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;.jpg`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;downloadToFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;filepath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// Use the URL protocol to decide between https/http.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;protocol&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;String&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;startsWith&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;https:&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;?&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;https&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;http&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Promise&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;resolve&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;reject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;protocol&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;get&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;c1&#34;&gt;// Follow basic redirects.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;statusCode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;301&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;||&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;statusCode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;===&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;302&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;next&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;headers&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;location&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;reject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`Redirect without location header: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;url&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;resume&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;downloadToFile&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;next&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;filepath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;then&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;resolve&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;).&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;reject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;k&#34;&gt;if&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;statusCode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;!==&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;200&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;resume&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;reject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`HTTP &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;statusCode&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;out&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fs&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;createWriteStream&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;filepath&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;res&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;pipe&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;finish&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;close&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;resolve&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;out&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;reject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;on&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;reject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;setTimeout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;mi&#34;&gt;30000&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;req&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;destroy&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;      &lt;span class=&#34;nx&#34;&gt;reject&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;s1&#34;&gt;&amp;#39;Download timeout&amp;#39;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;exists&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;try&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fsp&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;access&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;p&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;true&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;kc&#34;&gt;false&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;sleep&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;ms&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;k&#34;&gt;return&lt;/span&gt; &lt;span class=&#34;k&#34;&gt;new&lt;/span&gt; &lt;span class=&#34;nb&#34;&gt;Promise&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;setTimeout&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;r&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;ms&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;));&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c1&#34;&gt;// --- Main pipeline -----------------------------------------------------------&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kr&#34;&gt;async&lt;/span&gt; &lt;span class=&#34;kd&#34;&gt;function&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;()&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// Step 1: Fetch&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;const&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;items&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;}&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;fetchPostsAsJson&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;userId&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;USER_ID&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;limit&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;LIMIT&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// Step 2: Generate HTML&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;generateHtmlFiles&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;({&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;posts&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;items&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;c1&#34;&gt;// Step 3: Download images&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;kr&#34;&gt;await&lt;/span&gt; &lt;span class=&#34;nx&#34;&gt;downloadImagesFromGeneratedHtml&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;();&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`\nDone.`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`You can now inspect:`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`- Raw JSON: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;OUTPUT_DIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`- HTML posts: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;POSTS_DIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`- Images: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;IMAGES_DIR&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nx&#34;&gt;main&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;().&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;catch&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;((&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;console&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;error&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`\nScript failed: &lt;/span&gt;&lt;span class=&#34;si&#34;&gt;${&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;e&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;message&lt;/span&gt;&lt;span class=&#34;si&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;sb&#34;&gt;`&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;);&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;nx&#34;&gt;process&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;exitCode&lt;/span&gt; &lt;span class=&#34;o&#34;&gt;=&lt;/span&gt; &lt;span class=&#34;mi&#34;&gt;1&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;});&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&#xA;&lt;/details&gt;&lt;/p&gt;&#xA;&#xA;&lt;p&gt;Run it with:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;node blogpost/extract-posts-generate-html-download-images.js&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After it finishes, you’ll find three folders under &lt;code&gt;blogpost/&lt;/code&gt;: &lt;code&gt;output/&lt;/code&gt; for the raw JSON, &lt;code&gt;posts/&lt;/code&gt; for the generated HTML files, and &lt;code&gt;images/&lt;/code&gt; for downloaded images grouped by post ID.&lt;/p&gt;&#xA;&lt;h2 id=&#34;example-resulting-folder-structure&#34;&gt;Example resulting folder structure&lt;/h2&gt;&#xA;&lt;p&gt;If you have a repo like &lt;code&gt;sap-community-blog-posts&lt;/code&gt; and put the script into a &lt;code&gt;blogpost/&lt;/code&gt; folder, it can look like this afterwards:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;/sap-community-blog-posts/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;└── blogpost/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ├── extract-posts-generate-html-download-images.js&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ├── output/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │   └── user_61_messages_limit10.json&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    ├── posts/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │   ├── 2026-02-19-1234567-my-first-post.html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │   ├── 2026-02-10-1234500-another-post.html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    │   └── 2026-01-05-1234000-some-topic.html&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    └── images/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        ├── 1234567/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        │   ├── 4c2f1f6e-aaaa-bbbb-cccc-1234567890ab.jpg&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        │   └── image_6f2c0a1b3d4e.jpg&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;        └── 1234500/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;            └── image_1a2b3c4d5e6f.jpg&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;notes-and-limitations&#34;&gt;Notes and limitations&lt;/h2&gt;&#xA;&lt;p&gt;The script queries &lt;code&gt;depth = 0&lt;/code&gt; to fetch only top-level posts, not comments or replies. It also does not limit to blog posts, so you will get other types of posts as well (Q&amp;amp;A, etc.).&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Distributed Microservices with GitHub Pages and RSS Feeds</title>
      <link>https://blog.zeis.de/posts/2026-01-19-blueskybots/</link>
      <pubDate>Mon, 19 Jan 2026 10:00:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2026-01-19-blueskybots/</guid>
      <description>&lt;p&gt;I was recently asked how my UI5 Versions Bot on Bluesky works. So I want to simply explain how it works and how I use an RSS feed as a small “microservice” so I can base other services on this data.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-the-ui5-lib-diff-project-works&#34;&gt;How the ui5-lib-diff project works&lt;/h2&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/marianfoo/ui5-lib-diff&#34;&gt;&lt;code&gt;ui5-lib-diff&lt;/code&gt;&lt;/a&gt; project pulls data from official UI5 sources (SAPUI5/OpenUI5) and turns it into structured change data. That data is then used to generate an RSS feed entry for each patch update in both SAPUI5 and OpenUI5.&lt;/p&gt;&#xA;&lt;p&gt;It runs a small GitHub workflow that runs every two hours, fetches library data and change logs, parses and consolidates the results, and then creates the RSS XML files (&lt;a href=&#34;https://ui5-lib-diff.marianzeis.de/rss_feed_SAPUI5.xml&#34;&gt;&lt;code&gt;rss_feed_SAPUI5.xml&lt;/code&gt;&lt;/a&gt;, &lt;a href=&#34;https://ui5-lib-diff.marianzeis.de/rss_feed_OpenUI5.xml&#34;&gt;&lt;code&gt;rss_feed_OpenUI5.xml&lt;/code&gt;&lt;/a&gt;). A scheduled GitHub Actions workflow builds the UI5 app and publishes everything to the &lt;code&gt;docs&lt;/code&gt; branch so it can be served via GitHub Pages. There’s also a static page at &lt;code&gt;https://ui5-lib-diff.marianzeis.de/&lt;/code&gt; for browsing without using the RSS feeds and for creating diffs between versions.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;UI5 Lib Diff - Compare UI5 Versions&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-01-19-blueskybots/ui5-lib-diff-screenshot.jpg&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;So basically this is a small “microservice” that fetches data from the official UI5 sources and publishes it to an RSS feed. Now I’m able to use this data in other services — for example, in a Bluesky bot.&lt;/p&gt;&#xA;&lt;p&gt;I also created a GitHub repo for multiple Bluesky bots (like a UI5 Version Bot and an SCN Bot): &lt;code&gt;https://github.com/marianfoo/bluesky-bots/tree/main/packages/blueskybotui5version&lt;/code&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-the-bluesky-ui5-version-bot-works&#34;&gt;How the Bluesky UI5 version bot works&lt;/h2&gt;&#xA;&lt;p&gt;The Bluesky bot (see &lt;a href=&#34;https://github.com/marianfoo/bluesky-bots/tree/main/packages/blueskybotui5version&#34;&gt;&lt;code&gt;packages/blueskybotui5version&lt;/code&gt;&lt;/a&gt;) is an example consumer of the RSS “microservice”.&lt;/p&gt;&#xA;&lt;p&gt;Instead of scraping webpages, it periodically fetches the two RSS feeds from the UI5 microservice:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;SAPUI5: &lt;a href=&#34;https://ui5-lib-diff.marianzeis.de/rss_feed_SAPUI5.xml&#34;&gt;https://ui5-lib-diff.marianzeis.de/rss_feed_SAPUI5.xml&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;OpenUI5: &lt;a href=&#34;https://ui5-lib-diff.marianzeis.de/rss_feed_OpenUI5.xml&#34;&gt;https://ui5-lib-diff.marianzeis.de/rss_feed_OpenUI5.xml&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;For each RSS item (for example “Version 1.143.0 Changes”), it extracts the version number and checks if it already posted about it. The bot persists that state in a local &lt;code&gt;posted_versions.json&lt;/code&gt; file so it won’t repost the same version after a restart.&lt;/p&gt;&#xA;&lt;p&gt;When it finds a new version, it creates a Bluesky post that links back to the “What’s new” comparison on &lt;code&gt;https://ui5-lib-diff.marianzeis.de/&lt;/code&gt; and also adds the corresponding documentation link (SAPUI5: &lt;code&gt;https://ui5.sap.com/&amp;lt;version&amp;gt;/#/&lt;/code&gt;, OpenUI5: &lt;code&gt;https://sdk.openui5.org/&amp;lt;version&amp;gt;/#/&lt;/code&gt;). The bot uses the official &lt;code&gt;version.json&lt;/code&gt; endpoints to determine the previous available version for comparisons, and it rate-limits posting to avoid spamming.&lt;/p&gt;&#xA;&lt;p&gt;The bot itself does not run on GitHub but on my own private Hetzner server.&lt;/p&gt;&#xA;&lt;p&gt;&lt;img alt=&#34;UI5 Versions Bot on Bluesky&#34; loading=&#34;lazy&#34; src=&#34;https://blog.zeis.de/posts/2026-01-19-blueskybots/bluesky-bot-screenshot.jpg&#34;&gt;&lt;/p&gt;&#xA;&lt;p&gt;So RSS feeds are a great way to create a microservice that can be used by other services. To be fair, the kind of data you can put in an RSS feed is pretty limited but it’s perfectly valid for my use case.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Supercharged Local Open Source Joule for Consultants: Search Your Data &amp; Code</title>
      <link>https://blog.zeis.de/posts/2025-10-29-librechat/</link>
      <pubDate>Wed, 29 Oct 2025 09:30:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2025-10-29-librechat/</guid>
      <description>&lt;p&gt;This post is a mirrored copy of my LinkedIn article, kept here so it remains searchable and independent from external platforms.&#xA;You can still find the original on LinkedIn: &lt;a href=&#34;https://www.linkedin.com/pulse/supercharged-local-open-source-joule-consultants-search-marian-zeis-gepbf/&#34;&gt;LinkedIn Pulse article&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://www.sap.com/germany/products/artificial-intelligence/ai-assistant/sap-consulting-capability.html&#34;&gt;Joule for Consultants&lt;/a&gt; costs 250 € and mainly searches SAP Help, SAP Community and SAP for Me (Notes). That is useful, but it stops where real projects start: your own systems, codebase and sensitive data. Even though public assistants like ChatGPT and Claude can already reach SAP Help and SAP Community, the real gap is internal/authenticated data and code.&lt;/p&gt;&#xA;&lt;p&gt;For this case I created a &lt;a href=&#34;https://github.com/marianfoo/local-llm-client-for-sap-consultants-librechat&#34;&gt;LibreChat fork&lt;/a&gt; that includes and auto-starts SAP MCP servers (Docs, Notes, S/4 OData and ABAP ADT) via Docker. So the MCP Server are local inside your Docker and no external MCP Server is used. &lt;a href=&#34;https://www.librechat.ai/&#34;&gt;LibreChat&lt;/a&gt; is an open‑source local chat UI with MCP support and built‑in Ollama integration, and it also supports many other model providers. You get a private assistant that can read official docs, inspect code and data from your systems without sending customer context to third‑party clouds. The fork’s documentation is here: &lt;a href=&#34;https://github.com/marianfoo/local-llm-client-for-sap-consultants-librechat/tree/main/sap-mcp-docs&#34;&gt;sap‑mcp‑docs&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Running the assistant locally means the client, tools and, if you choose, the model stay on your machine (LibreChat supports Ollama), so you avoid pasting sensitive context into public chats. By adding S/4 OData and ABAP ADT, you bring real metadata, code and data into the loop.&lt;/p&gt;&#xA;&lt;h3 id=&#34;which-mcp-servers-are-used-here&#34;&gt;Which MCP servers are used here?&lt;/h3&gt;&#xA;&lt;p&gt;The &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;SAP Docs MCP&lt;/a&gt; provides unified search and fetch across SAPUI5/OpenUI5, CAP, ABAP keyword docs, SAP Help and SAP Community. The &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-notes&#34;&gt;SAP Notes MCP&lt;/a&gt; is for searching and fetching SAP Notes when you have an error, OSS number or concrete symptom; it requires an S‑User certificate (Passport). The &lt;a href=&#34;https://github.com/mario-andreschak/mcp-abap-adt&#34;&gt;ABAP ADT MCP&lt;/a&gt; reads ABAP sources, DDIC structures and tables content via ADT APIs so you never paste code into chat and still get precise context. The &lt;a href=&#34;https://github.com/lemaiwo/btp-sap-odata-to-mcp-server&#34;&gt;S/4 OData MCP&lt;/a&gt; discovers services and exposes safe CRUD tools so your assistant can read and write via approved APIs.&lt;/p&gt;&#xA;&lt;h2 id=&#34;how-this-works-in-practice&#34;&gt;How this works in practice&lt;/h2&gt;&#xA;&lt;p&gt;In my example I used o4‑mini from OpenAI, but LibreChat supports many &lt;a href=&#34;https://www.librechat.ai/docs/configuration/pre_configured_ai&#34;&gt;model providers&lt;/a&gt; including OpenAI, Anthropic, Google, Azure AI Foundry and even SAP AI Core (not tested by me). The key point: you can also use a local &lt;a href=&#34;https://www.librechat.ai/blog/2024-03-02_ollama#getting-started-with-ollama&#34;&gt;model via Ollama&lt;/a&gt;. Depending on your use case, a local model is smart enough to call the right tools and MCP servers. I also use an ABAP Cloud Developer Trial instance connected via the S/4 OData and ADT MCP servers.&lt;/p&gt;&#xA;&lt;h3 id=&#34;sap-notes-lookup--abap-table-verification&#34;&gt;SAP Notes lookup + ABAP table verification&lt;/h3&gt;&#xA;&lt;p&gt;This first example flow combines the SAP Notes MCP and ABAP ADT MCP. First the assistant searches and fetches a specific SAP Note (&lt;code&gt;sap_note_search&lt;/code&gt;, &lt;code&gt;sap_note_get&lt;/code&gt;), then verifies the relevant DDIC table and fields via ADT. This confirms the symptom and the fix steps before proposing a safe, auditable corrective action (for example, an OData action). This is the pattern for many “error → note → verify → fix” tasks.&lt;/p&gt;&#xA;&lt;img src=&#34;example-sap-notes-lookup.jpg&#34; alt=&#34;LibreChat example flow: SAP Notes lookup plus ABAP table verification via ADT MCP tools&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;h3 id=&#34;service-metadata--valuelist-guidance&#34;&gt;Service metadata + ValueList guidance&lt;/h3&gt;&#xA;&lt;p&gt;The assistant uses the S/4 MCP to discover the Football service and inspect entity schema (&lt;code&gt;search-sap-services&lt;/code&gt;, &lt;code&gt;discover-service-entities&lt;/code&gt;, &lt;code&gt;get-entity-schema&lt;/code&gt;). It then pivots to the SAP Docs MCP to &lt;code&gt;sap_help_search&lt;/code&gt; and &lt;code&gt;sap_help_get&lt;/code&gt; the ValueList documentation and shows exactly where to add annotations. This marries live service metadata with the official docs so consultants can implement confidently, then validate in a Fiori app.&lt;/p&gt;&#xA;&lt;img src=&#34;example-service-metadata-valuelist.jpg&#34; alt=&#34;LibreChat example flow: S/4 service metadata plus ValueList guidance via SAP Docs MCP&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;h3 id=&#34;cds-view-value-help-research&#34;&gt;CDS view Value Help research&lt;/h3&gt;&#xA;&lt;p&gt;For CDS‑centric cases, the assistant focuses on SAP Docs MCP to collect the canonical examples for &lt;code&gt;Common.ValueList&lt;/code&gt; and relevant ABAP/CDS annotations. If needed, ABAP ADT MCP can retrieve the current CDS definition (&lt;code&gt;GetStructure&lt;/code&gt;/&lt;code&gt;GetInclude&lt;/code&gt;) to compare the as‑is state and generate the minimal edit plan.&lt;/p&gt;&#xA;&lt;img src=&#34;example-cds-value-help.jpg&#34; alt=&#34;LibreChat example flow: CDS value help research with ABAP ADT MCP context&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;h3 id=&#34;other-options-and-clients&#34;&gt;Other options and clients&lt;/h3&gt;&#xA;&lt;p&gt;There are other options and clients to use these MCP servers. If you prefer the Microsoft ecosystem, Visual Studio Code’s GitHub Copilot Chat supports MCP servers so you can connect the same SAP MCP endpoints there, as described in the &lt;a href=&#34;https://code.visualstudio.com/docs/copilot/chat/mcp-servers&#34;&gt;VS Code Copilot Chat MCP guide&lt;/a&gt;. This is a simpler setup compared to the LibreChat fork. For a more governed setup you can use &lt;a href=&#34;https://adoption.microsoft.com/ai-agents/copilot-studio/&#34;&gt;Copilot Studio&lt;/a&gt; together with the hosted S/4 OData MCP server from &lt;a href=&#34;https://www.sap.com/products/artificial-intelligence/partners/nessi-nv-ai-data-enabler.html?countryCode=US&#34;&gt;Nessi NV&lt;/a&gt; and the SAP Docs MCP Server.&lt;/p&gt;&#xA;&lt;p&gt;I talked about these MCP servers and showed a live example of Copilot Studio and other LLM clients at SAP Inside Track Munich 2025. The recording is here: &lt;a href=&#34;https://www.youtube.com/watch?v=vTZCeaxsOwU&#34;&gt;SAP Inside Track Munich 2025&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;You can also build your own LLM client and use the MCP servers directly. I tried a quick PoC with the current libraries and tools. It worked, but turning it into a production‑ready assistant with robust MCP support is non‑trivial; I would not recommend rolling your own for production. The PoC is here: &lt;a href=&#34;https://github.com/marianfoo/custom-llm-client&#34;&gt;Custom LLM Client&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;h3 id=&#34;getting-started&#34;&gt;Getting started&lt;/h3&gt;&#xA;&lt;p&gt;To run this on your own machine, clone the repository, configure the &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;librechat.yaml&lt;/code&gt; files and start the Docker container. The docs are in this &lt;a href=&#34;https://github.com/marianfoo/local-llm-client-for-sap-consultants-librechat/tree/main/sap-mcp-docs&#34;&gt;folder&lt;/a&gt; and the setup guide is here: &lt;a href=&#34;https://github.com/marianfoo/local-llm-client-for-sap-consultants-librechat/blob/main/sap-mcp-docs/MCP_SETUP_GUIDE.md&#34;&gt;MCP_SETUP_GUIDE.md&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Minimal path (see guides for details):&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# Clone and configure&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git clone https://github.com/marianfoo/local-llm-client-for-sap-consultants-librechat&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cd local-llm-client-for-sap-consultants-librechat&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cp .env.example .env&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;cp librechat.example.yaml librechat.yaml&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# Configure the .env and librechat.yaml files&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;# Start in Docker&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;docker compose up -d&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;security-running-ai-and-mcp-locally&#34;&gt;Security: running AI and MCP locally&lt;/h3&gt;&#xA;&lt;p&gt;Because the client, MCP server and (optionally) the model run on your machine, prompts, retrieved docs and tool results stay inside your environment. When you do connect to external sources, do it intentionally and stick to trusted endpoints. Operationally keep it simple and start read-only by default and enable write tools only when you require it. The short version: a local, open-source stack is the safer option here; unlike cloud assistants, your information never leaves your machine unless you choose to send it.&lt;/p&gt;&#xA;&lt;h3 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h3&gt;&#xA;&lt;p&gt;With MCP servers, LLM clients become smarter and more useful in real projects. LLMs simply cannot know everything, especially internal context that lives on enterprise networks and changes quickly. Because the Model Context Protocol is still relatively new in the enterprise, treat adoption with care and integrate it securely. Keeping information on your own machine and using open-source libraries lets you see exactly what runs and how.&lt;/p&gt;&#xA;&lt;p&gt;SAP clearly wants to position Joule for Consultants as a unique product; today it still lags behind community projects for these use cases. At TechEd next week we’ll see whether community MCP servers are embraced and whether SAP’s own products improve enough to justify the price.&lt;/p&gt;&#xA;&lt;h3 id=&#34;references&#34;&gt;References&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Custom LLM Client PoC: &lt;a href=&#34;https://github.com/marianfoo/custom-llm-client&#34;&gt;https://github.com/marianfoo/custom-llm-client&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;SAP Inside Track Munich 2025 session: &lt;a href=&#34;https://www.youtube.com/watch?v=vTZCeaxsOwU&#34;&gt;https://www.youtube.com/watch?v=vTZCeaxsOwU&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;SAP partner listing (S/4 integration via OAuth): &lt;a href=&#34;https://www.sap.com/products/artificial-intelligence/partners/nessi-nv-ai-data-enabler.html?countryCode=US&#34;&gt;https://www.sap.com/products/artificial-intelligence/partners/nessi-nv-ai-data-enabler.html?countryCode=US&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;MCP clients overview (Claude Desktop etc.): &lt;a href=&#34;https://modelcontextprotocol.io/clients/&#34;&gt;https://modelcontextprotocol.io/clients/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;Copilot Studio: &lt;a href=&#34;https://adoption.microsoft.com/ai-agents/copilot-studio/&#34;&gt;https://adoption.microsoft.com/ai-agents/copilot-studio/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;VS Code Copilot Chat MCP docs: &lt;a href=&#34;https://code.visualstudio.com/docs/copilot/chat/mcp-servers&#34;&gt;https://code.visualstudio.com/docs/copilot/chat/mcp-servers&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;CAP MCP server (official): &lt;a href=&#34;https://github.com/cap-js/mcp-server&#34;&gt;https://github.com/cap-js/mcp-server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;CAP MCP plugin (community): &lt;a href=&#34;https://github.com/gavdilabs/cap-mcp-plugin&#34;&gt;https://github.com/gavdilabs/cap-mcp-plugin&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;S/4 OData MCP: &lt;a href=&#34;https://github.com/lemaiwo/btp-sap-odata-to-mcp-server&#34;&gt;https://github.com/lemaiwo/btp-sap-odata-to-mcp-server&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;ABAP ADT MCP: &lt;a href=&#34;https://github.com/mario-andreschak/mcp-abap-adt&#34;&gt;https://github.com/mario-andreschak/mcp-abap-adt&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;SAP Notes MCP: &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-notes&#34;&gt;https://github.com/marianfoo/mcp-sap-notes&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;SAP Docs MCP: &lt;a href=&#34;https://github.com/marianfoo/mcp-sap-docs&#34;&gt;https://github.com/marianfoo/mcp-sap-docs&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;LibreChat Ollama guide: &lt;a href=&#34;https://www.librechat.ai/blog/2024-03-02_ollama#getting-started-with-ollama&#34;&gt;https://www.librechat.ai/blog/2024-03-02_ollama#getting-started-with-ollama&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;LibreChat model providers: &lt;a href=&#34;https://www.librechat.ai/docs/configuration/pre_configured_ai&#34;&gt;https://www.librechat.ai/docs/configuration/pre_configured_ai&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;LibreChat website: &lt;a href=&#34;https://www.librechat.ai/&#34;&gt;https://www.librechat.ai/&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;LibreChat fork setup guide: &lt;a href=&#34;https://github.com/marianfoo/local-llm-client-for-sap-consultants-librechat/blob/main/sap-mcp-docs/MCP_SETUP_GUIDE.md&#34;&gt;https://github.com/marianfoo/local-llm-client-for-sap-consultants-librechat/blob/main/sap-mcp-docs/MCP_SETUP_GUIDE.md&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;LibreChat fork docs folder: &lt;a href=&#34;https://github.com/marianfoo/local-llm-client-for-sap-consultants-librechat/tree/main/sap-mcp-docs&#34;&gt;https://github.com/marianfoo/local-llm-client-for-sap-consultants-librechat/tree/main/sap-mcp-docs&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;LibreChat fork (SAP consultants, with MCP servers): &lt;a href=&#34;https://github.com/marianfoo/local-llm-client-for-sap-consultants-librechat&#34;&gt;https://github.com/marianfoo/local-llm-client-for-sap-consultants-librechat&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>Engagement in Free-Fall: Why SAP Community Blogs Struggle to Be Seen</title>
      <link>https://blog.zeis.de/posts/2025-05-13-scn-freefall/</link>
      <pubDate>Tue, 13 May 2025 09:30:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2025-05-13-scn-freefall/</guid>
      <description>&lt;p&gt;This post is a mirrored copy of my LinkedIn article, kept here so it remains searchable and independent from external platforms.&#xA;You can still find the original on LinkedIn: &lt;a href=&#34;https://www.linkedin.com/pulse/engagement-free-fall-why-sap-community-blogs-struggle-marian-zeis-z4otf/&#34;&gt;LinkedIn Pulse article&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Back in April 2024 I opened this little series with &lt;a href=&#34;https://www.linkedin.com/pulse/from-open-exchange-corporate-monologue-transformation-marian-zeis-zu6xf/&#34;&gt;&lt;em&gt;From Open Exchange to Corporate Monologue&lt;/em&gt;&lt;/a&gt;, followed in August by &lt;a href=&#34;https://www.linkedin.com/pulse/sap-community-decline-engagement-continues-fall-little-marian-zeis-1wvif/&#34;&gt;&lt;em&gt;SAP Community in Decline&lt;/em&gt;&lt;/a&gt; and, just before Christmas, the GPT-assisted look at &lt;a href=&#34;https://www.linkedin.com/pulse/has-quality-sap-community-questions-gotten-worse-data-driven-zeis-bdyjf/&#34;&gt;question quality&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;Twelve months, two platform releases and several batch-downloads later it is time for round four - this time with a split focus:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Who is writing the blog posts today?&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Does anybody still read them?&lt;/strong&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;1--share-of-sap-vs-non-sap-blog-posts---little-news-on-the-macro-level&#34;&gt;1 | Share of SAP vs. Non-SAP Blog Posts - Little News on the Macro Level&lt;/h2&gt;&#xA;&lt;p&gt;&amp;ldquo;Nothing has changed&amp;rdquo; was my gut feeling - and the long-range chart confirms it. Since the second half of 2023 the SAP share hovers stubbornly between &lt;strong&gt;60% and 70%&lt;/strong&gt;; no new record highs, but also no real retreat.&lt;/p&gt;&#xA;&lt;img src=&#34;chart-share-and-absolute-blog-posts-2003-2025.png&#34; alt=&#34;Share and absolute numbers of all blog posts, 2003-05 to 2025-05&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Share and absolute numbers of all blog posts, 2003-05 to 2025-05&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h3 id=&#34;does-the-plateau-invalidate-my-earlier-alarm&#34;&gt;Does the plateau invalidate my earlier alarm?&lt;/h3&gt;&#xA;&lt;p&gt;Not really. The community never bounced back to the sub-40% employee share we enjoyed a decade ago - the &lt;em&gt;status-quo&lt;/em&gt; is simply frozen at a two-thirds &amp;ldquo;corporate voice&amp;rdquo;.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;Assumption check:&lt;/em&gt; My earlier claim of &lt;strong&gt;&amp;ldquo;no further change&amp;rdquo; holds for percentages&lt;/strong&gt;, but the green bars (absolute SAP posts) are still inching up. The non-SAP bars are mostly flat. So the balance only stays constant because both lines move in parallel.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;2--tech-blog-boards---a-very-different-story&#34;&gt;2 | Tech-Blog Boards - A Very Different Story&lt;/h2&gt;&#xA;&lt;p&gt;Drill down to the two technology boards (&lt;code&gt;technology-blog-sap&lt;/code&gt;, &lt;code&gt;technology-blog-members&lt;/code&gt;) and you get a much livelier picture.&lt;/p&gt;&#xA;&lt;img src=&#34;chart-share-and-absolute-tech-blogs-all-time.png&#34; alt=&#34;Figure 2 - Share and absolute numbers in the tech blogs, entire history&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Figure 2 - Share and absolute numbers in the tech blogs, entire history&lt;/em&gt;&lt;/p&gt;&#xA;&lt;img src=&#34;chart-share-and-absolute-tech-blogs-last-12-months.png&#34; alt=&#34;Figure 3 - Share and absolute numbers in the tech blogs, last 12 months&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Figure 3 - Same data, last 12 months&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;Key observations:&lt;/p&gt;&#xA;&lt;ol&gt;&#xA;&lt;li&gt;&lt;strong&gt;Member posts are growing&lt;/strong&gt; - up by ~25% year-on-year.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;SAP posts are shrinking&lt;/strong&gt; - down by ~15% in the same period (especially 2025-01 to 2025-03).&lt;/li&gt;&#xA;&lt;/ol&gt;&#xA;&lt;p&gt;&lt;em&gt;Assumption:&lt;/em&gt; The main reason for this change is the lower number of SAP blog posts. One possible explanation is that the personal target number of blog posts required to achieve a possible bonus has been reached. This also explains the high number of SAP blog posts in December.&lt;/p&gt;&#xA;&lt;p&gt;There was also an increase in blog posts from non-SAP members and new members of the SAP community (see next point).&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;3--who-are-those-members---rank-distribution&#34;&gt;3 | Who Are Those Members? - Rank Distribution&lt;/h2&gt;&#xA;&lt;img src=&#34;chart-tech-blogs-experienced-vs-inexperienced-last-12-months.png&#34; alt=&#34;Figure 4 - Experienced vs. inexperienced share in the tech blogs, last 12 months&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Figure 4 - Experienced vs. inexperienced share in the tech blogs, last 12 months&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;The green bars (Participant+) show a &lt;strong&gt;drop to barely 40%&lt;/strong&gt; in February before recovering to the mid-40s. Net-net the fresh content boom is fuelled primarily by &lt;em&gt;new&lt;/em&gt; contributors.&lt;/p&gt;&#xA;&lt;p&gt;&lt;em&gt;Additional assumption:&lt;/em&gt; A steady inflow of first-time authors is great - &lt;strong&gt;if&lt;/strong&gt; their pieces are curated and converted into repeat contributions. Otherwise we risk a &amp;ldquo;drive-by blogging&amp;rdquo; pattern that never matures (see next point about views).&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;4--views---the-elephant-in-the-room&#34;&gt;4 | Views - The Elephant in the Room&lt;/h2&gt;&#xA;&lt;p&gt;When SAP switched the platform to Khoros I planned to ignore the view counter - until the time-series smacked me in the face:&lt;/p&gt;&#xA;&lt;img src=&#34;chart-views-mean-median-engagement-quality.png&#34; alt=&#34;Figure 5 - Mean and median views and engagement quality&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Figure 5 - Mean and median views and engagement quality&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;The trajectory is crystal-clear:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;Sharp decline since 2020&lt;/strong&gt;, long before the migration.&lt;/li&gt;&#xA;&lt;li&gt;Mean &amp;gt; median: a few viral posts keep the average alive; the &lt;em&gt;typical&lt;/em&gt; post is doing much worse.&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;are-the-counters-reliable&#34;&gt;Are the counters reliable?&lt;/h3&gt;&#xA;&lt;p&gt;I have already written that I do not consider the view parameters to be reliable. However, I cannot ignore this chart because the trend is quite clear. I trust the trend because it reflects my personal feeling.&lt;/p&gt;&#xA;&lt;p&gt;I strongly suspect that the curve is not so extreme in real numbers, but only SAP itself can answer this question with its own analytics data.&lt;/p&gt;&#xA;&lt;img src=&#34;chart-number-of-posts-no-downtrend.png&#34; alt=&#34;Chart showing the number of posts and no downward trend&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;But if you look at the number of posts here, there is no downward trend. This confirms my impression that a lot of blog posts are written by non-SAP members and that SAP employees are encouraged to write blog posts.&lt;/p&gt;&#xA;&lt;p&gt;This combination automatically leads to a decline in quality and therefore in engagement, which confirms my impression that blog posts that are really worth reading are becoming rarer.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;h2 id=&#34;6--what-could-sap-and-we-do&#34;&gt;6 | What Could SAP (and We) Do?&lt;/h2&gt;&#xA;&lt;p&gt;This is a difficult question to answer because there is no simple answer. Platform migration is only part of the answer, because engagement was already declining before that, but it definitely plays a big role.&lt;/p&gt;&#xA;&lt;p&gt;If you look at the latest &lt;a href=&#34;https://community.sap.com/t5/what-s-new/enhancements-for-sap-community-march-2025/ba-p/14037208&#34;&gt;&amp;lsquo;What&amp;rsquo;s New&amp;rsquo;&lt;/a&gt; post from the SAP community, the bug fixes are ones that shouldn&amp;rsquo;t have been there in the first place and are just minor cosmetic changes. It doesn&amp;rsquo;t change the big picture. And now, years after the migration, I don&amp;rsquo;t see any quick changes and we have to deal with the current situation.&lt;/p&gt;&#xA;&lt;p&gt;As a community, there&amp;rsquo;s nothing we can do about it, because you can&amp;rsquo;t build a community of this size overnight. There are legal requirements that can&amp;rsquo;t be met with a small team.&lt;/p&gt;&#xA;&lt;p&gt;Björn Schulz is doing a great job with his website and excellent content: &lt;a href=&#34;https://software-heroes.com/&#34;&gt;https://software-heroes.com/&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;As Twitter is dying and &lt;a href=&#34;https://go.bsky.app/M3Q7Nng&#34;&gt;Bluesky&lt;/a&gt; can&amp;rsquo;t take its place, the only alternative so far is (unfortunately) LinkedIn. There&amp;rsquo;s a lot of (at best) bad content there, but also a lot of untruths, and it&amp;rsquo;s also flooded with AI content.&lt;/p&gt;&#xA;&lt;p&gt;At the moment, my optimism about the SAP community has faded a bit. If anyone has any suggestions on how to reunite the community, I&amp;rsquo;m all ears!&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Has the Quality of SAP Community Questions Gotten Worse? A Data-Driven Perspective</title>
      <link>https://blog.zeis.de/posts/2024-12-18-scn-quality/</link>
      <pubDate>Wed, 18 Dec 2024 09:30:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/2024-12-18-scn-quality/</guid>
      <description>&lt;p&gt;This post is a mirrored copy of my LinkedIn article, kept here so it remains searchable and independent from external platforms.&#xA;You can still find the original on LinkedIn: &lt;a href=&#34;https://www.linkedin.com/pulse/has-quality-sap-community-questions-gotten-worse-data-driven-zeis-bdyjf/&#34;&gt;LinkedIn Pulse article&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;The SAP Community is and will remain the central point of entry for all issues and discussions relating to SAP.&lt;/p&gt;&#xA;&lt;p&gt;Over the past few years (and since the migration at the beginning of the year) I have had a feeling that the quality of questions in the technology area (the area I follow most closely) has gradually declined.&lt;/p&gt;&#xA;&lt;p&gt;I wanted to see if I could somehow verify or disprove this feeling.&lt;/p&gt;&#xA;&lt;p&gt;This blog post describes my approach and the results.&lt;/p&gt;&#xA;&lt;h2 id=&#34;methodology&#34;&gt;Methodology&lt;/h2&gt;&#xA;&lt;p&gt;The analysis was based on historical question data, including:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Question metadata (timestamps, authors, conversation details)&lt;/li&gt;&#xA;&lt;li&gt;Quality scores (rated by GPT-4o, ranging from 1 to 10)&lt;/li&gt;&#xA;&lt;li&gt;Additional metrics, such as the number of messages per conversation and whether the question was solved&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;Since the prices for tokens at OpenAI have fallen, I have simply decided to analyse the questions from the last six years in the technology section with an LLM.&lt;/p&gt;&#xA;&lt;p&gt;To do this, I downloaded all the questions and packed them into a prompt that looks like this:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;You are an expert who will rate the quality of a question from an online forum. The quality of a question depends on:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- How well the user describes the problem&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- How much contextual detail is given&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- How well-formatted the question is (images, code formatting, clarity)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- Length (but not too long just for fluff)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;- Overall clarity&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Please return a single integer between 0 and 10 (no text, no explanation), where 10 means it&amp;#39;s an extremely well-asked, clear question with all necessary details, and 0 means it&amp;#39;s very poor, unclear, and low quality.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Thanks to the &lt;a href=&#34;https://help.openai.com/en/articles/9197833-batch-api-faq&#34;&gt;Batch API&lt;/a&gt; from OpenAI, we got another 50% discount for the prompt and paid $6.82 for the analysis of approximately 200,000 questions.&lt;/p&gt;&#xA;&lt;p&gt;Data was processed using Python, and visualizations were created using Matplotlib and Seaborn.&lt;/p&gt;&#xA;&lt;h2 id=&#34;key-findings&#34;&gt;Key Findings&lt;/h2&gt;&#xA;&lt;h3 id=&#34;1-monthly-quality-scores-trends-over-time&#34;&gt;1. Monthly Quality Scores: Trends Over Time&lt;/h3&gt;&#xA;&lt;img src=&#34;chart-monthly-question-quality-scores.png&#34; alt=&#34;Chart showing average monthly question quality scores (LLM-rated) over time.&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;The chart shows a notable dip in average quality around 2018-2019, followed by a gradual recovery. Interestingly, the quality scores have stabilized since 2020, with improvements starting end of 2023.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Insight:&lt;/strong&gt; The dip could coincide with community policy changes, increased user activity, or other external factors. A deeper look into this period might reveal actionable insights.&lt;/p&gt;&#xA;&lt;h3 id=&#34;2-monthly-question-volume&#34;&gt;2. Monthly Question Volume&lt;/h3&gt;&#xA;&lt;img src=&#34;chart-monthly-question-volume.png&#34; alt=&#34;Chart showing the number of questions per month over time.&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;The number of questions peaked between 2019 and 2021, followed by a steady decline. The recent decline might reflect a shift in community activity or a move to alternative platforms for technical discussions.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Insight:&lt;/strong&gt; High activity in earlier years might have diluted quality, as seen in the overlapping periods of low scores and high question counts.&lt;/p&gt;&#xA;&lt;h3 id=&#34;3-quality-score-heatmap&#34;&gt;3. Quality Score Heatmap&lt;/h3&gt;&#xA;&lt;img src=&#34;chart-quality-score-heatmap.png&#34; alt=&#34;Heatmap of average quality scores by year and month.&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;The heatmap visualizes average quality scores by year and month. It confirms the dip in 2018 and highlights gradual improvements over time. Notably, 2024 have seen some of the highest average scores.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Insight:&lt;/strong&gt; Certain months, such as mid-2018, had consistently lower scores. Investigating these periods could help address specific quality issues.&lt;/p&gt;&#xA;&lt;h3 id=&#34;4-messages-in-conversations-vs-quality&#34;&gt;4. Messages in Conversations vs. Quality&lt;/h3&gt;&#xA;&lt;img src=&#34;chart-messages-vs-quality-scatter.png&#34; alt=&#34;Scatterplot of messages in conversations versus quality score.&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;The scatterplot shows no strong correlation between the number of messages in a conversation and the question’s quality score. However, higher-quality questions tend to lead to slightly longer discussions.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Insight:&lt;/strong&gt; High-quality questions often attract deeper engagement, which benefits the community overall.&lt;/p&gt;&#xA;&lt;h3 id=&#34;5-combined-trends-questions-and-quality&#34;&gt;5. Combined Trends: Questions and Quality&lt;/h3&gt;&#xA;&lt;img src=&#34;chart-question-volume-vs-quality-overlay.png&#34; alt=&#34;Overlay chart comparing question volume and average quality scores over time.&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;Overlaying question volume and quality scores highlights the inverse relationship: as the number of questions surged, quality declined. This pattern reversed as activity decreased.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Insight:&lt;/strong&gt; Moderating activity levels and encouraging thoughtful participation could help sustain or improve quality.&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusions&#34;&gt;Conclusions&lt;/h2&gt;&#xA;&lt;h3 id=&#34;what-the-data-tells-us&#34;&gt;What the Data Tells Us&lt;/h3&gt;&#xA;&lt;p&gt;I can&amp;rsquo;t explain the dip in 2018 to myself, and I don&amp;rsquo;t have the time to investigate it further.&lt;/p&gt;&#xA;&lt;p&gt;But at least the LLM rates that the quality has not dropped. That means there have always been very good and very bad questions. The average of the questions was always the same.&lt;/p&gt;&#xA;&lt;p&gt;According to the LLM, the quality in 2024 even increased while the number of questions decreased, which did not feel like my experience.&lt;/p&gt;&#xA;&lt;p&gt;What do you think about these insights? Are there specific periods or trends that resonate with your experience in the SAP Community?&lt;/p&gt;&#xA;&lt;h2 id=&#34;more-stats&#34;&gt;More stats&lt;/h2&gt;&#xA;&lt;p&gt;Here is a snippet of the data results:&lt;/p&gt;&#xA;&lt;p&gt;&lt;a href=&#34;https://gist.githubusercontent.com/marianfoo/9512f43ec4c536189906d35e6e023605/raw/c1a4ed715f7c56b348f0915820b7e5a4cffecebd/gistfile1.txt&#34;&gt;https://gist.githubusercontent.com/marianfoo/9512f43ec4c536189906d35e6e023605/raw/c1a4ed715f7c56b348f0915820b7e5a4cffecebd/gistfile1.txt&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Quality Score Distribution:&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;quality_score&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;1.0        42&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2.0      2661&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;3.0     11843&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;4.0     37171&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;5.0     34140&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;6.0     54559&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;7.0     45242&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;8.0     30511&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;9.0      1278&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;10.0        2&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here are the two 10 rated questions:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://community.sap.com/t5/technology-q-a/optimizing-multithreaded-data-insertion-into-sap-data-lake-seeking-guidance/qaq-p/13809681&#34;&gt;Optimizing Multithreaded Data Insertion into SAP Data Lake: Seeking Guidance and Best Practices&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;&lt;a href=&#34;https://community.sap.com/t5/technology-q-a/how-to-install-sap-cloud-connector-on-a-mac-m2-processor/qaq-p/13697727&#34;&gt;How to Install SAP Cloud Connector on a Mac M2 Processor&lt;/a&gt; (was a blog post)&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;&lt;strong&gt;Monthly Statistics (all months):&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt; mean    std  min  max  count&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;year_month                               &#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-01     5.758  1.468  1.0  9.0   1430&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-02     5.789  1.513  1.0  8.0    696&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-03     5.866  1.431  2.0  9.0    666&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-04     5.231  1.549  2.0  9.0    571&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-05     5.287  1.478  2.0  8.0    550&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-06     5.293  1.423  2.0  9.0    752&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-07     5.221  1.441  2.0  8.0   1948&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-08     5.325  1.512  2.0  9.0   1802&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-09     5.301  1.507  2.0  9.0   1738&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-10     5.262  1.483  2.0  9.0   1971&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-11     5.659  1.495  2.0  9.0   1884&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2018-12     5.761  1.489  2.0  9.0   1437&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-01     5.731  1.465  2.0  9.0   1888&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-02     5.821  1.473  2.0  9.0   1773&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-03     5.754  1.433  2.0  9.0   3024&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-04     5.794  1.475  2.0  9.0   3599&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-05     5.781  1.492  2.0  9.0   3558&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-06     5.746  1.453  2.0  9.0   3451&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-07     5.860  1.462  2.0  9.0   3579&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-08     5.803  1.468  1.0  9.0   3460&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-09     5.760  1.500  2.0  9.0   3423&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-10     5.725  1.481  2.0  9.0   3660&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-11     5.777  1.492  1.0  9.0   3461&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2019-12     5.755  1.523  1.0  9.0   2946&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-01     5.773  1.497  2.0  9.0   3814&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-02     5.669  1.514  2.0  9.0   3732&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-03     5.830  1.495  2.0  9.0   3579&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-04     5.823  1.470  2.0  9.0   3972&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-05     5.822  1.474  2.0  9.0   3855&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-06     5.770  1.430  2.0  9.0   4121&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-07     5.816  1.459  2.0  9.0   4208&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-08     5.761  1.494  2.0  9.0   3540&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-09     5.775  1.479  2.0  9.0   3894&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-10     5.843  1.489  2.0  9.0   3519&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-11     5.858  1.495  2.0  9.0   3286&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2020-12     5.746  1.513  1.0  9.0   3097&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-01     5.866  1.515  1.0  9.0   3429&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-02     5.872  1.514  2.0  9.0   3657&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-03     5.849  1.492  2.0  9.0   3934&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-04     5.953  1.513  1.0  9.0   3422&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-05     5.798  1.544  2.0  9.0   3338&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-06     5.801  1.535  2.0  9.0   3695&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-07     5.855  1.544  1.0  9.0         3320&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-08     5.838  1.514  2.0  9.0         3215&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-09     5.879  1.518  2.0  9.0         3082&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-10     5.805  1.492  2.0  9.0         3004&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-11     5.802  1.510  2.0  9.0         2908&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2021-12     5.800  1.536  2.0  9.0         2737&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-01     5.827  1.479  1.0  9.0         3170&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-02     5.913  1.485  2.0  9.0         3063&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-03     5.875  1.494  2.0  9.0         3373&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-04     5.766  1.534  2.0  9.0         3022&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-05     5.757  1.511  2.0  9.0         3094&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-06     5.816  1.503  2.0  9.0         3284&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-07     5.715  1.560  2.0  9.0         2842&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-08     5.800  1.510  2.0  9.0         3013&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-09     5.859  1.507  2.0  9.0         3124&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-10     5.765  1.566  1.0  9.0         3096&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-11     5.937  1.498  1.0  9.0         3320&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2022-12     5.748  1.571  1.0  9.0         2484&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-01     5.851  1.543  2.0  9.0         3080&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-02     5.728  1.579  1.0  9.0         3447&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-03     5.707  1.558  1.0  9.0         3546&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-04     5.761  1.594  2.0  9.0         2874&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-05     5.719  1.530  2.0  9.0         2985&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-06     5.772  1.575  1.0  9.0         1642&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-07     5.846  1.585  2.0  9.0         1711&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-08     5.763  1.566  1.0  9.0         1805&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-09     5.813  1.560  1.0  9.0         1529&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-10     5.894  1.564  2.0  9.0         1523&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-11     5.871  1.576  1.0  9.0         1624&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2023-12     5.871  1.534  2.0  9.0         1237&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-01     5.910  1.502  1.0   9.0    964&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-02     5.877  1.627  2.0   9.0   1448&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-03     5.887  1.644  1.0   9.0   2409&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-04     5.986  1.589  2.0   9.0   2035&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-05     6.088  1.595  1.0  10.0   1144&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-06     6.097  1.572  1.0   9.0   1096&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-07     5.961  1.693  2.0   9.0   1241&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-08     6.137  1.595  1.0  10.0   1227&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-09     6.090  1.639  2.0   9.0   1305&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-10     6.077  1.587  1.0   9.0   1283&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-11     6.092  1.624  2.0   9.0   1148&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;2024-12     6.097  1.719  2.0   9.0    636&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    <item>
      <title>SAP Community in Decline: Engagement Continues to Fall with Little Investment</title>
      <link>https://blog.zeis.de/posts/2024-08-05-scn-in-decline/</link>
      <pubDate>Mon, 05 Aug 2024 09:30:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2024-08-05-scn-in-decline/</guid>
      <description>&lt;p&gt;This post is a mirrored copy of my LinkedIn article, kept here so it remains searchable and independent from external platforms.&#xA;You can still find the original on LinkedIn: &lt;a href=&#34;https://www.linkedin.com/pulse/sap-community-decline-engagement-continues-fall-little-marian-zeis-1wvif/&#34;&gt;LinkedIn Pulse article&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;Half a year after the major migration, it&amp;rsquo;s time to publish the latest figures and uncover any hidden insights in the data.&lt;/p&gt;&#xA;&lt;p&gt;So, what&amp;rsquo;s new? The trend from the past few months (or even years) has continued.&#xA;More significant members of the community are moving away from Twitter, which was once a vital medium of exchange (and where I got my start).&lt;/p&gt;&#xA;&lt;p&gt;Even on the SAP Community site, good posts from non-SAP employees are rare.&#xA;I wanted to link to a good blog post, but since they are not sorted by publication date, I couldn&amp;rsquo;t find it.&#xA;However, there are quite a few LLM-generated posts:&#xA;&lt;a href=&#34;https://community.sap.com/t5/technology-blogs-by-members/the-future-of-application-lifecycle-management-in-sap-after-2027/ba-p/13742196&#34;&gt;The Future of Application Lifecycle Management in SAP After 2027&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Meanwhile, some community members are trying to bridge the gap by offering their own search:&#xA;&lt;a href=&#34;https://software-heroes.com/en/sap-technology-blog&#34;&gt;Software Heroes - SAP Technology Blog&lt;/a&gt;&#xA;It&amp;rsquo;s disappointing that SAP hasn&amp;rsquo;t addressed this issue after six months.&lt;/p&gt;&#xA;&lt;p&gt;After six months, there&amp;rsquo;s an update with some fixes, but the results are underwhelming:&#xA;&lt;a href=&#34;https://community.sap.com/t5/what-s-new/update-about-enhancements-to-sap-community/ba-p/13755862&#34;&gt;Update about Enhancements to SAP Community&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Views are displayed again for blog posts (though their accuracy is questionable), replies are automatically expanded, notifications have slightly more information, you can see if you&amp;rsquo;ve created a post or question, and a rotating carousel has been added to the homepage.&#xA;These are the highlights of the update after six months.&lt;/p&gt;&#xA;&lt;p&gt;SAP has clearly invested neither time nor money into the main page of the SAP Community to retain and attract users.&#xA;This will have consequences that will be evident in the following evaluations.&lt;/p&gt;&#xA;&lt;p&gt;Here is the update of the latest major graphic.&#xA;The absolute number of blog posts in green (SAP) and red (non-SAP) and the percentage in blue (SAP) and yellow (non-SAP).&lt;/p&gt;&#xA;&lt;h2 id=&#34;blog-posts&#34;&gt;Blog Posts&lt;/h2&gt;&#xA;&lt;img src=&#34;chart-blog-posts-share-and-absolute-over-time.png&#34; alt=&#34;Share in percent and absolute of SAP and Non-SAP Blog Post over time&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Share in percent and absolute of SAP and Non-SAP Blog Post over time&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;For better representation, here is the development over the last twelve months.&#xA;Both metrics are moving in the wrong direction.&#xA;The number of blog posts is falling, mainly due to the decline in non-SAP blog posts, which automatically increases the share of SAP blog posts to 70%.&lt;/p&gt;&#xA;&lt;img src=&#34;chart-blog-posts-share-and-absolute-last-12-months.png&#34; alt=&#34;Share in percent and absolute of SAP and Non-SAP Blog Post over the last 12 months&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Share in percent and absolute of SAP and Non-SAP Blog Post over the last 12 months&lt;/em&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;question-and-answers&#34;&gt;Question and Answers&lt;/h2&gt;&#xA;&lt;p&gt;In addition to blog posts, the exchange of questions and answers is crucial.&#xA;This is where community members help each other, and unfortunately, SAP has been too inactive here in recent years.&#xA;On the other hand, many individuals have contributed significantly and well.&#xA;It is not surprising that the same trend observed with blog posts is occurring here.&lt;/p&gt;&#xA;&lt;p&gt;Here is a look at the entire timeline of the last few years in the Q&amp;amp;A section.&#xA;Overall, there is an increase that has remained steady, with SAP making a very small contribution.&#xA;As above, the chart shows the absolute figures in green (SAP) and red (non-SAP) and the relative figures as a percentage in blue (SAP) and yellow (non-SAP).&lt;/p&gt;&#xA;&lt;img src=&#34;chart-qa-posts-share-and-absolute-over-time.png&#34; alt=&#34;Share in percent and absolute of SAP and Non-SAP Q&amp;A Post over time&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Share in percent and absolute of SAP and Non-SAP Q&amp;amp;A Post over time&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;It gets more interesting when you look at the last seven years.&#xA;The greater participation of SAP employees in the discourse is encouraging, as seen from the large absolute figures and the increasing relative share.&#xA;However, it is clear that there has been a drop from around 4,000 posts per month to around 2,500 posts per month in 2024.&lt;/p&gt;&#xA;&lt;img src=&#34;chart-qa-posts-share-and-absolute-last-12-months.png&#34; alt=&#34;Share in percent and absolute of SAP and Non-SAP Q&amp;A Post over the last 12 months&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Share in percent and absolute of SAP and Non-SAP Q&amp;amp;A Post over the last 12 months&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;I suspected such a decline, but I didn&amp;rsquo;t expect it to be so sharp.&#xA;Personally, I haven&amp;rsquo;t answered any questions since the migration because I haven&amp;rsquo;t found a good way to filter and sort as I could with the old platform.&lt;/p&gt;&#xA;&lt;h2 id=&#34;user-activity&#34;&gt;User Activity&lt;/h2&gt;&#xA;&lt;p&gt;Fortunately, there is also a way to read user activity.&#xA;While this is just a snapshot and not a development, it still provides good insight.&#xA;As always, the figures should be taken with a pinch of salt, but they still provide an insight into user activity.&lt;/p&gt;&#xA;&lt;img src=&#34;chart-user-participation-metrics.png&#34; alt=&#34;Graphic Metric for User Participation&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Graphic Metric for User Participation&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Logins&lt;/strong&gt;: Two SAP employees have never logged in. 60% of users have never logged in.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Minutes Online&lt;/strong&gt;: 75% of users have no minutes online. This metric likely came later, hence the discrepancy.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Posts&lt;/strong&gt;: 70% of users have never made a post (including Q&amp;amp;A, blog posts, etc.). It&amp;rsquo;s amazing that 30% of users have written a post, totaling over 236,000.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Private Messages&lt;/strong&gt;: Interestingly, this statistic still exists. It&amp;rsquo;s not surprising that only 2,723 users (0.32% of total users) use this feature.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;Full data is at the end of the post&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;views&#34;&gt;Views&lt;/h2&gt;&#xA;&lt;p&gt;Since the last update, views on blog posts are displayed again.&#xA;I did a small sample to see how reliable these views are.&#xA;Here is a small overview of the results: Total number of views:&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;10.04.2024:&lt;/strong&gt; 2,615,955,557&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;03.06.2024&lt;/strong&gt;: 3,318,366,233&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;02.08.2024&lt;/strong&gt;: 4,062,134,925&lt;/p&gt;&#xA;&lt;p&gt;That&amp;rsquo;s a sharp increase in just four months.&#xA;Let&amp;rsquo;s see how the views of blog posts behave.&#xA;One post that gained the most views is this one:&#xA;&lt;a href=&#34;https://community.sap.com/t5/enterprise-resource-planning-blogs-by-members/list-of-object-types-sap-business-one/ba-p/13336072&#34;&gt;List of Object Types SAP Business One.&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;It received 480,408 views within two months.&#xA;While it&amp;rsquo;s an important post with a good overview, that&amp;rsquo;s much more than the second post with the most views gained:&#xA;&lt;a href=&#34;https://community.sap.com/t5/technology-blogs-by-members/abap-7-40-quick-reference/ba-p/13287523&#34;&gt;ABAP 7.40 Quick Reference&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;This one gained 47,892 views in the same time.&#xA;How can this be explained?&#xA;I suspect an automatic fetch.&#xA;But why repeatedly, when the post does not change as seen in the history:&#xA;&lt;a href=&#34;https://community.sap.com/t5/forums/messagehistorypage/board-id/erp-blog-members/message-id/49443&#34;&gt;Post History&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;Or why does this question have more than 5 million views?&#xA;&lt;a href=&#34;https://community.sap.com/t5/technology-blogs-by-members/sap-crystal-reports-developer-version-for-microsoft-visual-studio-updates/ba-p/13232432&#34;&gt;SAP Crystal Reports Developer Version for Microsoft Visual Studio Updates&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;I wouldn&amp;rsquo;t put much faith in these view numbers, even though most of them are probably correct, use them as a reference point.&lt;/p&gt;&#xA;&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;&#xA;&lt;p&gt;Now what?&#xA;What I observe is the widespread use of LinkedIn articles (which I use a lot too).&#xA;I often shared SAP Community blog posts on LinkedIn and received more feedback there.&#xA;Why not write directly on LinkedIn, also because the UX is very good?&#xA;Social media diversification is definitely progressing, and the SAP Community page should actually be the bedrock.&lt;/p&gt;&#xA;&lt;p&gt;For me personally, the exchange on Twitter is still the most important, as there are still many active people, including SAP employees.&#xA;LinkedIn is more important for a broader exchange, even if gems are rarer, but the audience is definitely larger.&#xA;After that, there&amp;rsquo;s a long gap, and then Mastodon, unfortunately.&lt;/p&gt;&#xA;&lt;p&gt;I don&amp;rsquo;t see any increased investment from SAP for the SAP Community page.&#xA;This is why it is increasingly becoming a marketing tool for SAP in terms of blog posts and is being used less and less.&lt;/p&gt;&#xA;&lt;p&gt;In which direction do you see the SAP community heading?&#xA;Where will the discourse take place?&#xA;Could something be done for the community?&lt;/p&gt;&#xA;&lt;p&gt;You can comment your reason why are you not using the SAP Community site anymore?&#xA;Is it the lack of content?&#xA;The missing functions?&#xA;Universal ID?&lt;/p&gt;&#xA;&lt;p&gt;Here is the full data of user metrics:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Statistics for metrics_logins:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Overall:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 6.35&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 5,156,904.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 21.25&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 678,326.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Non_sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 5.74&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 4,478,578.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Statistics for metrics_minutes_online:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Overall:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 124.68&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 101,224,309.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 875.17&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 27,930,276.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Non_sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 93.97&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 73,294,033.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Statistics for metrics_posts:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Overall:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 7.07&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 5,743,538.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 22.22&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 709,070.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Non_sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 6.45&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 5,034,468.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Statistics for metrics_private_messages_received:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Overall:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 0.03&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 22,019.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 0.15&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 4,685.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Non_sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 0.02&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 17,334.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Statistics for metrics_private_messages_sent:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Overall:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 0.01&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 6,533.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 0.06&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 1,785.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Non_sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 0.01&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 4,748.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Statistics for metrics_page_views:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Overall:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 14.54&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 11,801,736.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 81.73&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 2,608,425.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Non_sap_employees:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Average: 11.79&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Total: 9,193,311.00&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Additional statistics for metrics_logins:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Total users: 811,878&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_logins == 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 486,940&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 2 (0.00%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Non-SAP Employees: 486,938 (59.98%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_logins &amp;gt; 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 324,938&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 31,912 (3.93%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Non-SAP Employees: 293,026 (36.09%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Additional statistics for metrics_minutes_online:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Total users: 811,878&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_minutes_online == 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 619,061&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 7,700 (0.95%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Non-SAP Employees: 611,361 (75.30%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_minutes_online &amp;gt; 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 192,817&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 24,214 (2.98%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Non-SAP Employees: 168,603 (20.77%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Additional statistics for metrics_posts:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Total users: 811,878&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_posts == 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 574,782&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 17,342 (2.14%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Non-SAP Employees: 557,440 (68.66%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_posts &amp;gt; 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 237,096&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 14,572 (1.79%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Non-SAP Employees: 222,524 (27.41%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Additional statistics for metrics_private_messages_received:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Total users: 811,878&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_private_messages_received == 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 810,309&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 31,608 (3.89%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Non-SAP Employees: 778,701 (95.91%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_private_messages_received &amp;gt; 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 1,569&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 306 (0.04%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Non-SAP Employees: 1,263 (0.16%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Additional statistics for metrics_private_messages_sent:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Total users: 811,878&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_private_messages_sent == 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 809,155&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 31,481 (3.88%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Non-SAP Employees: 777,674 (95.79%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_private_messages_sent &amp;gt; 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 2,723&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 433 (0.05%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Non-SAP Employees: 2,290 (0.28%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Additional statistics for metrics_page_views:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Total users: 811,878&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_page_views == 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 550,345&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 40 (0.00%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Non-SAP Employees: 550,305 (67.78%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  Users with metrics_page_views &amp;gt; 0:&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    Overall: 261,533&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    SAP Employees: 31,874 (3.93%)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description>
    </item>
    <item>
      <title>From Open Exchange to Corporate Monologue: The Transformation of SAP’s Community</title>
      <link>https://blog.zeis.de/posts/2024-04-07-open-exchange-scn/</link>
      <pubDate>Sun, 07 Apr 2024 09:30:00 +0200</pubDate>
      <guid>https://blog.zeis.de/posts/2024-04-07-open-exchange-scn/</guid>
      <description>&lt;p&gt;This post is a mirrored copy of my LinkedIn article, kept here so it remains searchable and independent from external platforms.&#xA;You can still find the original on LinkedIn: &lt;a href=&#34;https://www.linkedin.com/pulse/from-open-exchange-corporate-monologue-transformation-marian-zeis-zu6xf/&#34;&gt;LinkedIn Pulse article&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;The SAP community has had to overcome many challenges recently. The central exchange, especially for developers, Twitter has become an increasingly toxic platform and many long-time members have stopped using it or have switched to alternatives. Twitter was also one of my first ports of call and helped me a lot. There are still some, but it&amp;rsquo;s getting noticeably less.&lt;/p&gt;&#xA;&lt;p&gt;Most important was the change of platform for the SAP Community Network for Q&amp;amp;A and blog posts. They now use Khoros, which has fundamentally changed a lot of things. There are still a lot of criticisms of the platform. However, as with so many changes, this one is particularly difficult.&lt;/p&gt;&#xA;&lt;p&gt;If the company switches from SAP GUI to Fiori, then ideally this is accompanied by support and training and you have time to get used to it. In the SAP Community Network, non-SAP users are usually active in their free time to answer questions or write helpful blog posts. If fundamental things change here, you have to invest your valuable time to adapt your own workflow. If the change is too big, these valuable members may no longer be active.&lt;/p&gt;&#xA;&lt;p&gt;The valuable blog posts from the community become rarer and marketing posts from SAP become more frequent.&lt;/p&gt;&#xA;&lt;p&gt;Thanks to Khoros&amp;rsquo; API, I can now support this feeling with data.&lt;/p&gt;&#xA;&lt;p&gt;I analyzed 150,000 blog posts dating back to 2005 and the status of over 720,000 users.&lt;/p&gt;&#xA;&lt;h2 id=&#34;share-of-sap-vs-non-sap-blog-posts-over-time-in-the-scn&#34;&gt;Share of SAP vs. Non-SAP blog posts over time in the SCN&lt;/h2&gt;&#xA;&lt;p&gt;First, the percentage of blog posts written by SAP employees.&lt;/p&gt;&#xA;&lt;p&gt;The graph clearly shows that the proportion has risen sharply, especially in recent years towards SAP blog posts.&lt;/p&gt;&#xA;&lt;img src=&#34;chart-sap-vs-non-sap-blog-posts-over-time.png&#34; alt=&#34;Share of SAP vs. non-SAP blog posts over time (share and absolute numbers)&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Share of SAP vs. non-SAP blog posts over time (share and absolute numbers)&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;About 10 years ago, less than a third of blog posts were written by SAP employees. Today, two-thirds of blog posts are written by SAP employees.&lt;/p&gt;&#xA;&lt;p&gt;Especially in the last year, this distribution has shifted strongly towards SAP.&lt;/p&gt;&#xA;&lt;p&gt;Here is a detailed overview of the last 12 months.&lt;/p&gt;&#xA;&lt;img src=&#34;chart-sap-vs-non-sap-blog-posts-last-12-months.png&#34; alt=&#34;Share of SAP vs. non-SAP blog posts over the last 12 months&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Share of SAP vs. non-SAP blog posts over the last 12 months&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;The sad peak was reached when over 70 percent of all blog posts in February 2024 were written by SAP employees.&lt;/p&gt;&#xA;&lt;h2 id=&#34;proportion-of-blog-posts-from-non-sap-by-experience&#34;&gt;Proportion of blog posts from non-SAP by experience&lt;/h2&gt;&#xA;&lt;p&gt;Another point I have noticed is that it feels like fewer and fewer veteran non-SAP employees are publishing blog posts.&lt;/p&gt;&#xA;&lt;p&gt;Thanks to the rankings that have been introduced, it is also possible to investigate this.&lt;/p&gt;&#xA;&lt;p&gt;The more active a community member was, the higher the rank. Jerry has explained this in this blog post: &lt;a href=&#34;https://community.sap.com/t5/what-s-new/let-the-gamification-begin/ba-p/311892&#34;&gt;Let the gamification begin&lt;/a&gt;&lt;/p&gt;&#xA;&lt;p&gt;There are the following rankings for non-SAP employees:&lt;/p&gt;&#xA;&lt;p&gt;Member, Newcomer, Discoverer, Explorer, Participant, Active Participant, Contributor, Active Contributor, Collaborator, Active Collaborator, Advocate&lt;/p&gt;&#xA;&lt;p&gt;I have classified Member, Newcomer, Discoverer, Explorer as inexperienced and all others as experienced.&lt;/p&gt;&#xA;&lt;p&gt;Even if the rank does not necessarily say anything about the quality of the blog post, there is a strong causality according to my observations.&lt;/p&gt;&#xA;&lt;img src=&#34;chart-blog-posts-by-experience-over-time.png&#34; alt=&#34;Share of blog posts by experience over time&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Share of blog posts by experience over time&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;Here, too, we have seen a significant decline in &amp;ldquo;Experienced&amp;rdquo; blog posts over the years since 2014.&lt;/p&gt;&#xA;&lt;img src=&#34;chart-blog-posts-by-experience-last-12-months.png&#34; alt=&#34;Share of blog posts by experience in the last 12 months&#34; loading=&#34;lazy&#34;&gt;&#xA;&lt;p&gt;&lt;em&gt;Share of blog posts by experience in the last 12 months&lt;/em&gt;&lt;/p&gt;&#xA;&lt;p&gt;Also in the last 12 months, less than a third of blog posts came from community members with the rank Participant or higher.&lt;/p&gt;&#xA;&lt;p&gt;This analysis confirms the feeling in the SAP community as a whole. Even if SAP cannot be blamed for everything, the large proportion of support must come from SAP. A strong community benefits all sides and has a very good ROI.&lt;/p&gt;&#xA;&lt;p&gt;The community is more important than ever, especially in today&amp;rsquo;s world where you have to be happy for every person who works with SAP after their studies.&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;SAP can only be successful with a strong community!&lt;/strong&gt;&lt;/p&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: I removed the blog posts written by &lt;code&gt;former_member&lt;/code&gt; and updated the graphics. In total 153.655 posts. There are 421.213 members where the login name starts with &lt;code&gt;former_member&lt;/code&gt;.&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Welcome to My Blog</title>
      <link>https://blog.zeis.de/posts/welcome-to-my-blog/</link>
      <pubDate>Mon, 27 Dec 1999 10:00:00 +0100</pubDate>
      <guid>https://blog.zeis.de/posts/welcome-to-my-blog/</guid>
      <description>&lt;h2 id=&#34;hello-world-&#34;&gt;Hello World! 🚀&lt;/h2&gt;&#xA;&lt;p&gt;Welcome to my new blog! I&amp;rsquo;m excited to finally have a space where I can share my thoughts, experiences, and learnings from my journey as an Independent UI5/ABAP Developer.&lt;/p&gt;&#xA;&lt;h3 id=&#34;what-to-expect&#34;&gt;What to Expect&lt;/h3&gt;&#xA;&lt;p&gt;On this blog, I&amp;rsquo;ll be writing about:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;UI5 Development&lt;/strong&gt; - Tips, tricks, and best practices for building modern SAP Fiori applications&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;ABAP Programming&lt;/strong&gt; - Backend development insights and patterns&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Open Source&lt;/strong&gt; - Updates on my open source projects and contributions&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;SAP Ecosystem&lt;/strong&gt; - General thoughts about the SAP development landscape&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Developer Tools&lt;/strong&gt; - Useful tools and workflows that make development easier&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;why-a-blog&#34;&gt;Why a Blog?&lt;/h3&gt;&#xA;&lt;p&gt;As developers, we learn so much every day - from solving tricky bugs to discovering new patterns and approaches. I&amp;rsquo;ve always found that writing things down helps me understand them better, and sharing knowledge with the community is something I&amp;rsquo;m passionate about.&lt;/p&gt;&#xA;&lt;h3 id=&#34;the-tech-stack&#34;&gt;The Tech Stack&lt;/h3&gt;&#xA;&lt;p&gt;This blog is built with:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://gohugo.io/&#34;&gt;Hugo&lt;/a&gt;&lt;/strong&gt; - A fast and flexible static site generator&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;&lt;a href=&#34;https://github.com/adityatelange/hugo-PaperMod&#34;&gt;PaperMod&lt;/a&gt;&lt;/strong&gt; - A clean, fast, and feature-rich Hugo theme&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;GitHub Pages&lt;/strong&gt; - Free, reliable hosting&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;GitHub Actions&lt;/strong&gt; - Automated deployment on every push&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;The entire blog is &lt;a href=&#34;https://github.com/marianfoo&#34;&gt;open source on GitHub&lt;/a&gt;, so feel free to check out how it&amp;rsquo;s built!&lt;/p&gt;&#xA;&lt;h3 id=&#34;stay-tuned&#34;&gt;Stay Tuned&lt;/h3&gt;&#xA;&lt;p&gt;I&amp;rsquo;m looking forward to sharing more content with you. If you have any topics you&amp;rsquo;d like me to write about, feel free to reach out on &lt;a href=&#34;https://github.com/marianfoo&#34;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;&#xA;&lt;p&gt;See you in the next post! 👋&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title></title>
      <link>https://blog.zeis.de/posts/2026-02-xx-huehner-part1/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://blog.zeis.de/posts/2026-02-xx-huehner-part1/</guid>
      <description>&lt;p&gt;Ich habe mal ein Bild von unseren Hühnern auf Bluesky geteilt und seit dem bin ich schon ein paar mal darauf angesprochen worden.&lt;br&gt;&#xA;Deswegen wollte ich hier aufschreiben wir wir zu den Hühner gekommen sind und welche Probleme man damit haben kann.&lt;/p&gt;&#xA;&lt;p&gt;Ich bin in einem kleinen Dorf aufgewachsen und war in den letzen 15 jahren viel unterwegs in der Welt. Es war immer schon der Plan wieder zurückzukommen, am liebsten mit einem Haus mit viel Grund und Platz.&lt;br&gt;&#xA;Im Jahr 2024 hatten wir ein Haus gefunden, haben es gekauft und sind eingezogen.&lt;br&gt;&#xA;Der große Plan ist &amp;ldquo;viele Tiere&amp;rdquo; aber natürlich müssen wir auch Zeit und Platz dafür haben. Dafür sind Hühner eigentlich am besten geeignet den diese sind pflegeleicht. Außerdem haben wir ein kleines Nebenhaus das von früheren Besitzern für Papagieen genutzt wurde. Also perfekt geeignet mit nur minmalen Umbauten.&lt;/p&gt;&#xA;&lt;p&gt;Deswegen haben wir im Haus ein kleinen Stall gebaut und draußen mit einer großen (eher etwas zu großen) Volaire für den Auslauf mussten wir nichts ändern.&lt;br&gt;&#xA;Innerhalb nur zwei Tagen war der Stall dann auch schon feretig und einzugsbereit.  Nester fürs Eier legen, Stangen für Schlafen, einen Wasserspender für innen und Leitern um rauszukommen (Papageiegen können gut fliegen deswegen ist der Ausgang so hcoh.)&#xA;Für etwas abwechslung haben wir vier verschieden Hühnerrassen gekauft und alles haben dann auch gleich einen Namen bekommen:&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;Hedwig (weiß, Sussex)&lt;/li&gt;&#xA;&lt;li&gt;Bella (schwarz, Maran)&lt;/li&gt;&#xA;&lt;li&gt;Hermine (braun, Legehybrid)&lt;/li&gt;&#xA;&lt;li&gt;Sperber (weiß grau gesperbert, Amrock) es ist ein Sperber also heißt sie Sperber&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;</description>
    </item>
    <item>
      <title>About Me</title>
      <link>https://blog.zeis.de/about/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://blog.zeis.de/about/</guid>
      <description>&lt;h2 id=&#34;hi-im-marian-zeis-&#34;&gt;Hi, I&amp;rsquo;m Marian Zeis 👋&lt;/h2&gt;&#xA;&lt;p&gt;I&amp;rsquo;m an &lt;strong&gt;Independent UI5/ABAP Developer&lt;/strong&gt; and SAP Consultant based in &lt;strong&gt;Bavaria, Germany&lt;/strong&gt;. I run my own consulting business, &lt;strong&gt;IT Consulting Marian Zeis&lt;/strong&gt;, helping companies build modern SAP solutions.&lt;/p&gt;&#xA;&lt;h3 id=&#34;what-i-do&#34;&gt;What I Do&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;🔧 &lt;strong&gt;UI5 Development&lt;/strong&gt; - Building modern, responsive SAP Fiori applications&lt;/li&gt;&#xA;&lt;li&gt;💻 &lt;strong&gt;ABAP Development&lt;/strong&gt; - Backend development and system integration&lt;/li&gt;&#xA;&lt;li&gt;🚀 &lt;strong&gt;SAP Consulting&lt;/strong&gt; - Helping businesses leverage SAP technologies effectively&lt;/li&gt;&#xA;&lt;li&gt;🌐 &lt;strong&gt;Open Source&lt;/strong&gt; - Contributing to the SAP developer community&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;h3 id=&#34;open-source--community&#34;&gt;Open Source &amp;amp; Community&lt;/h3&gt;&#xA;&lt;p&gt;I&amp;rsquo;m passionate about open source and actively contribute to projects that help the SAP developer community. You can find my work on &lt;a href=&#34;https://github.com/marianfoo&#34;&gt;GitHub&lt;/a&gt;, where I maintain various UI5-related tools and libraries.&lt;/p&gt;&#xA;&lt;h3 id=&#34;get-in-touch&#34;&gt;Get in Touch&lt;/h3&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;🌐 Website: &lt;a href=&#34;https://zeis.de&#34;&gt;zeis.de&lt;/a&gt;&lt;/li&gt;&#xA;&lt;li&gt;💻 GitHub: &lt;a href=&#34;https://github.com/marianfoo&#34;&gt;@marianfoo&lt;/a&gt;&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;hr&gt;&#xA;&lt;p&gt;&lt;em&gt;This blog is where I share my experiences, learnings, and insights about UI5, ABAP, SAP development, and technology in general.&lt;/em&gt;&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Impressum</title>
      <link>https://blog.zeis.de/impressum/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://blog.zeis.de/impressum/</guid>
      <description>&lt;h2 id=&#34;impressum&#34;&gt;Impressum&lt;/h2&gt;&#xA;&lt;h2 id=&#34;angaben-gemäß--5-tmg&#34;&gt;Angaben gemäß § 5 TMG&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;IT Consulting Marian Zeis&lt;/strong&gt;&lt;br&gt;&#xA;&lt;strong&gt;Inhaber: Marian Zeis&lt;/strong&gt;&lt;br&gt;&#xA;Hünleinshaus 1&lt;br&gt;&#xA;95336 Mainleus&lt;br&gt;&#xA;Deutschland&lt;/p&gt;&#xA;&lt;h2 id=&#34;kontakt&#34;&gt;Kontakt&lt;/h2&gt;&#xA;&lt;p&gt;Telefon: +49 152 07 57 44 95&lt;br&gt;&#xA;E-Mail: &lt;a href=&#34;mailto:marian@zeis.de&#34;&gt;marian@zeis.de&lt;/a&gt;&lt;/p&gt;&#xA;&lt;h2 id=&#34;umsatzsteuer-id&#34;&gt;Umsatzsteuer-ID&lt;/h2&gt;&#xA;&lt;p&gt;Umsatzsteuer-Identifikationsnummer gemäß § 27 a UStG:&lt;br&gt;&#xA;DE 353 926 927&lt;/p&gt;&#xA;&lt;h2 id=&#34;verantwortlich-für-den-inhalt-nach--18-abs-2-mstv&#34;&gt;Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV&lt;/h2&gt;&#xA;&lt;p&gt;Marian Zeis, Anschrift wie oben&lt;/p&gt;&#xA;&lt;p&gt;Quelle für die Impressums-Vorlage: e-recht24.de (angepasst)&lt;/p&gt;&#xA;</description>
    </item>
    <item>
      <title>Search</title>
      <link>https://blog.zeis.de/search/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://blog.zeis.de/search/</guid>
      <description></description>
    </item>
  </channel>
</rss>
