Close the agent's loop: see the view in seconds, read the right rulebook, start a project - #19
Merged
Merged
Conversation
Two things the SAMPLES.md parser dropped on the floor.
The TITLE of every port in samples-controls. Its rows carry the header
entirely in bold with nothing after it
| **sap.m.Bar**<br>Each screen of a mobile application...<br><sub>...</sub> |
and the row pattern required a dash after that bold half. So 430 of the 614
apps parsed as rows with NO header of their own: `title` fell back to the
section, and every port in the file announced itself as the LIBRARY it belongs
to, while the control an agent asked this tool for survived only inside the
keyword blob and inside `sub` with its asterisks still attached. The dash is
optional now; the dashed shape the other two catalogues use is untouched, and
all three still parse to exactly 152 / 430 / 32 rows.
(What remains is upstream's: 241 of those 430 rows carry only `**sap.m**` in
the header rather than the control. Noted in AGENTS.md - the fix belongs in
samples-controls' generator, not in a parser guessing at a control name.)
And the `docs:` links. Each row names the cookbook chapters somebody decided
that app is the worked example of, and this parser knew the block existed only
well enough to SKIP it while looking for the keywords. The agent got a class
to read and no way to reach the prose explaining what it demonstrates - the
half a human reviewer opens first. They travel as `{ topic, url }`, an ANSWER
rather than a search term: almost every row in `samples` carries one starting
`cookbook/`, so putting them in the haystack would make one query match the
entire catalogue.
deploy_app enforced ^z2ui5_cl_[a-z0-9_]+$ - the naming convention of the demo-kit PORTS - and this server exists for an agent building its OWN app. abap2UI5/app-template, the starting point the ecosystem recommends, ships src/zcl_app_001.clas.abap. So an agent that followed the recommended path could not deploy it, could not build it and could not look at it: every tool here refused the name, and the name was the one the template gave it. Widened to the customer namespace, ^[zy][a-z0-9_]*$ and <= 30 chars, which is the real rule - SAP reserves everything else. The dev lint config had the same mistake one layer down (it relaxed the corpus' Z2UI5_CL_SMPC_ prefix only as far as ^Z2UI5_CL_, so it would have failed the very class this had just accepted); it is now the same ^[ZY] boundary, so the two cannot disagree about what a legal app is. The safety property is untouched and is why this stays a whitelist rather than becoming a blacklist: every caller-supplied name becomes a PATH under src/zz_dev and is validated before it is joined, so no separator, dot or space can appear in one. A new test walks the shapes whose only purpose is to leave the sandbox - `../../src/01/...`, `/etc/passwd`, `zcl_app/../../x` - through both the write path and the delete path.
Three tools' worth of change, all one problem - this server was scoped to PORTING demo-kit samples while its stated purpose is helping an agent build a user's app, and the two need different things. screenshot_view. The linter can photograph a view in seconds without a system: it reconstructs the view from the builder calls, seeds it from the class's own TYPES/DATA and renders it in the same headless harness its render gate already runs. This server could not reach any of it. The only way for an agent to SEE anything was run_app, which boots the REAL app and therefore needs the whole framework transpiled first - tens of minutes once, minutes per rebuild. So the loop was "write ABAP, get a verdict in seconds, then pay a build to look at it, or never look at it at all". A separate tool rather than a flag on validate_view, because it answers a different question (is this legal / what does it look like), takes different arguments (viewports, theme, preview data) and needs the render runtime and a browser that the property gate does not. Several viewports come out of ONE browser session - the launch and the UI5 boot cost more than every render put together - each as an MCP image block, the way run_app already returns its screenshot. app_guide. generation_rules described itself as "the canonical rulebook for writing an abap2UI5 app" and served a document whose first line is "You are porting one official UI5 demo kit sample to abap2UI5". An agent building someone's app was handed the porting brief: an input sample it does not have, a z2ui5_cl_smpc_app_<n> convention that is not its app's, 1:1 fidelity to something that does not exist. The right document already existed beside the framework sources - abap2UI5's docs/agents/building-apps.md, written to be self-contained precisely so an agent with no web access has the whole picture - and nothing served it. Sliced by chapter the way pitfalls slices the skills, selectable by number or by a word in the heading. The porting brief stays; both descriptions now say which job they are for, because a tool description is the only documentation the agent ever reads. Rule explanations. A finding came back as an id and one terminal-width line, with the paragraph behind it reachable only on the published rules page - a web fetch mid-task. The linter exports that prose now (./rule-docs), so each rule that fired comes back under `rules`, keyed by id: twelve findings of one type cost one explanation, not twelve. The summary line always, the paragraph on explain:true - a first run on an unfamiliar class can hit a dozen distinct rules, and a dozen paragraphs would crowd out the findings they are about. The linter is an UNPINNED sibling, so both new reads degrade: a checkout without screenshotFiles says so, one without ./rule-docs costs the agent the explanations and nothing else. Neither may cost it the findings. Verified end to end against the real siblings: app-template's own zcl_app_001.clas.abap comes back as two styled PNGs (390x844 and 1280x900) in one call, which is the whole point - the app the ecosystem tells you to start from could not be deployed, built or seen through this server at all. Also here: the counts every document repeated by hand (10 tools -> 13, "~615 apps" -> the 614 the parser actually reads, samples-controls 431 -> 430), and the AGENTS.md claim that source files are 7-bit ASCII, which no file in the repo has ever satisfied - the rule that is real, and enforced, is about literals.
CHANGELOG says @abap2ui5/mcp has never been on npm, and consumers - the VS Code extension among them - run it as `npx --yes github:abap2UI5/ai-mcp`, so an agent's setup changes under it day to day. The release plumbing was already there; what it lacked was the one check that can only be made from the outside. `npm test` runs against the WORKING TREE, where every file exists whether or not `files` lists it. So the single defect this package can ship - a lib/ module left out of the allowlist, making the server import-fail on first use - is invisible to the entire suite, and the release job only PRINTED the tarball contents for a human to read. It now packs the tarball, installs it into a scratch project and drives the installed bin over stdio: initialize, tools/list, and one tool call with every checkout absent, which has to come back as the actionable message rather than as a crash. That is the level-1 surface, and it is the only part a CI job here can exercise - everything else needs sibling checkouts and a build measured in tens of minutes. Checked by hand for the first release, and written into RELEASING.md so the next person does not have to rediscover it: the manifest carries every field a publish needs; the tarball is 12 files (server.mjs, lib/, README, AGENTS.md, LICENSE, package.json - no tests, no workflows); the level-1 tools work from the tarball with only AI_VIEW_CHECK_HOME set, validate_view returning ok:true and screenshot_view returning a PNG. One finding recorded rather than fixed: the install is ~45 MB and 19 MB of it is playwright, which only run_app uses. Marking the dependency optional would not help - npm installs optionalDependencies by default - so the fix is the shape @abap2ui5/linter arrived at, a separate package declared as an optional peer. That is a design change, not a release step.
The server could tell an agent how to WRITE an app class (app_guide) and where
to put one so it could be run (deploy_app, into the corpus' scratch package).
It could not answer the question somebody building an app of their own
actually arrives with: what does a repository for this look like.
That gap mattered because the class is the half an agent can do. The half it
cannot invent is everything around it - the abaplint config with the framework
pinned at a release under the `branch` key (`tag` silently does not work), the
abap2ui5lint.jsonc the render gate needs in order to run rather than skip, the
CI workflow, the .abapgit.xml that decides where abapGit puts any of it, and
the .clas.xml sidecar whose CLSNAME must match the class or the object does not
activate. An agent asked for "an abap2UI5 app" wrote a class into a directory
with none of that.
So scaffold_app, served live from abap2UI5/app-template - the repository this
ecosystem already points people at to begin. Not embedded here: a copy would
be a second answer to "what does a new project look like", and the template
moves. AGENTS.md ships with it, because a project whose agent has no briefing
is the same gap one level down.
`class` renames it throughout: the ABAP, the sidecar's CLSNAME - upper case
there, lower in the source, and that asymmetry is precisely why the template
ships scripts/rename.mjs instead of an instruction - and the file names. The
name is validated before it is substituted, since it reaches file paths; the
refusals are tested with the same escape shapes deploy_app is.
A missing template file is reported rather than skipped. This list is a claim
about another repository, and a project quietly missing its CI workflow is not
noticed until somebody wonders why nothing is checked.
npm test 53/53 (4 new unit tests + the degradation contract)
live call 12 files, renamed, through the real stdio server
scaffolded project npm install && npm run check -> abaplint 0 issues,
linter 0 findings with the render gate on
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XY3AoWMiCC52cuQjbce4SU
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of an ecosystem-wide pass over the two goals: make it maximally easy for AI agents, and for people, to build abap2UI5 apps. This server is the half that serves goal A, and four things were missing from it.
screenshot_view— an agent can SEE the view in secondsThe linter gained
--screenshot: it reconstructs the view from the builder calls, seeds it from the class's ownTYPES/DATAand photographs it in the same headless harness its render gate runs. This server had no way to reach it, so the only way to look at anything wasrun_app— which boots the REAL app and therefore needs the whole framework transpiled first, tens of minutes on a cold checkout. A dedicated tool rather than a flag onvalidate_view: different question, different arguments (viewports, theme, preview model) and a different failure mode.deploy_appaccepts the app the agent was told to writeThe regex was
^z2ui5_cl_[a-z0-9_]+$— the corpus' own porting convention. An agent following the ecosystem's recommended path builds something likeapp-template'szcl_app_001and could not deploy, build or screenshot it at all. Widened to the customer namespace^[zy]c[lx]_, with the safety property kept as a whitelist and tested with escape shapes through both the write and the delete path.app_guideandscaffold_app— the right rulebook, and a project to put it ingeneration_rulesserves a prompt whose first line is "You are porting one official UI5 demo kit sample to abap2UI5" — the wrong instructions for someone building an app.app_guideserves the real thing (abap2UI5 docs/agents/building-apps.md, chapter by chapter); both tool descriptions now say which job they are for.scaffold_appanswers what neither could: what a repository for this looks like. The class is the half an agent can write; what it cannot invent is the abaplint config with the framework pinned under thebranchkey, theabap2ui5lint.jsoncthe render gate needs to run rather than skip, the CI workflow, and the.clas.xmlsidecar whoseCLSNAMEmust match the class or the object does not activate. Served live fromabap2UI5/app-template, renaming throughout — ABAP, sidecar and file names. Proven end to end: a scaffolded project installs and passesnpm run checkwith abaplint at 0 issues and the render gate on.Smaller, and found by using it
title: "sap.m"instead of the sample name.docs:links samples' catalogue carries were parsed and then discarded../rule-docsexport.AGENTS.mdsaid "10 tools" (there were 11, now 14), claimed~615 appswhere the parser reads 614, and stated a 7-bit-ASCII rule no file in the repository satisfies.Release readiness
npm packverified from a scratch install: 12 files, 44.3 kB packed, and the level-1 tools drive correctly from the tarball.release.ymlnow proves the tarball before the version becomes immutable. Not published —npm login,npm publish --access publicand the Trusted Publisher setup need maintainer credentials; the steps are inRELEASING.md.How to test
Generated by Claude Code