Skip to content

Commit fc17ac4

Browse files
oblomov-devclaude
andauthored
Sync AGENTS.md with the reorganized package structure and guard it in CI (#682)
The July restructurings (category consolidation, sap.ui.unified subpackage, generated ports moved out) left AGENTS.md §1/§2/§4 describing subpackages that no longer exist (00/08-00/13) and stale CTEXTs (01/05, 01/08, 00/02, 00/04, 00/07). Update the tree, the src/00 categorization rules and the overview-generation examples to the actual layout, rewrite the retired z2ui5_cl_util_xml section (§10) around _generic( ), and point the checklists at the generator instead of hand-edited catalogs. Add scripts/check-agents-structure.js (npm run check:agents) plus a check_docs workflow that compares the documented tree against the actual package.devc.xml CTEXTs on every PR, so the doc cannot drift silently again. Also add a PR-title rule: squash-merge titles must describe the change and never stay auto-generated branch names. Claude-Session: https://claude.ai/code/session_01F6ZLY4zwg3tr9P7z4tiR9d Co-authored-by: Claude <noreply@anthropic.com>
1 parent 74d4ae4 commit fc17ac4

4 files changed

Lines changed: 182 additions & 103 deletions

File tree

.github/workflows/check_docs.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: check_docs
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches: [standard]
7+
8+
permissions:
9+
contents: read
10+
11+
concurrency:
12+
group: check_docs-${{ github.ref }}
13+
cancel-in-progress: true
14+
15+
jobs:
16+
agents_structure:
17+
runs-on: ubuntu-latest
18+
timeout-minutes: 5
19+
steps:
20+
- uses: actions/checkout@v7
21+
- uses: actions/setup-node@v6
22+
with:
23+
node-version: '22'
24+
- run: node scripts/check-agents-structure.js

AGENTS.md

Lines changed: 74 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ ABAP code style / app-structure conventions.
1212
**This entire project is in English.** All code, comments, commit messages, PR
1313
titles, PR descriptions, and any other text must be written in English.
1414

15+
## Pull requests
16+
17+
- **The PR title becomes the squash-merge commit subject — make it describe
18+
the change.** Before merging, replace any auto-generated title (e.g. a
19+
branch name like `Claude/...-abc123`) with a short descriptive English
20+
title that states what actually changed.
21+
- **One topic per PR.** A structural change (moving, adding, or renaming
22+
subpackages) must not ride along in a PR titled for an unrelated sample —
23+
split it into its own PR so the history stays searchable.
24+
1525
---
1626

1727
## 1. Repository layout
@@ -25,32 +35,33 @@ src/
2535
├── 01/ "basic" cloud-ready & downportable — survives every build
2636
│ ├── 01/ framework - basics
2737
│ ├── 02/ framework - action
28-
│ ├── 05/ controls - extended
29-
│ └── 08/ controls 1:1 rebuilds of UI5 demo kit samples, split by library
38+
│ ├── 05/ framework - extended Controls (CC and Action)
39+
│ ├── 06/ framework - use cases
40+
│ └── 08/ controls - UI5 Demo Kit 1:1 rebuilds of UI5 demo kit samples, split by library
3041
│ ├── 00/ controls - sap.m
3142
│ ├── 01/ controls - sap.uxap
3243
│ ├── 02/ controls - sap.f
3344
│ ├── 03/ controls - sap.ui.core
3445
│ ├── 04/ controls - sap.ui.layout
3546
│ ├── 05/ controls - sap.tnt
36-
│ └── 06/ controls - sap.ui.codeeditor
47+
│ ├── 06/ controls - sap.ui.codeeditor
48+
│ └── 07/ controls - sap.ui.unified
3749
└── 00/ "extended" restricted / special-purpose — STRIPPED from cloud & 702 builds
3850
├── 01/ only non-abap-cloud on-premise-only ABAP (not ABAP Cloud ready)
39-
├── 02/ only non-openui5 SAPUI5-only controls (sap.suite.*, sap.ui.comp.*, VizFrame, …)
51+
├── 02/ only non-openui5 or higher UI5 1.71 SAPUI5-only controls (sap.suite.*, sap.ui.comp.*, VizFrame, …) or a control/property introduced after UI5 1.71
4052
├── 03/ only with launchpad runs only inside the Fiori Launchpad
41-
├── 04/ only higher UI5 1.71 uses a control/property introduced after UI5 1.71
53+
├── 04/ use of z2ui5 needs the z2ui5/Util frontend helper module in the view
4254
├── 05/ only with javascript and css and html needs native JS / CSS / HTML
4355
├── 06/ only testing test / scaffolding apps, not demos
44-
├── 07/ experimental work-in-progress / not finished
45-
├── 08/ demos complete showcase apps (multi-feature)
46-
├── 09/ generic xml view built on z2ui5_cl_util_xml
47-
├── 10/ only non-openui5-with-cc SAPUI5-only control that also needs a custom control
48-
├── 11/ uncategorized not yet triaged into a category
49-
├── 12/ controls - custom own control demos without a UI5 demo kit original
50-
├── 13/ deprecated sample built on a deprecated UI5 control (ActionSheet, P13n*, UploadSet, …)
51-
└── 99/ obsolete superseded, or uses a deprecated control
56+
├── 07/ experimental, TODO work-in-progress / not finished
57+
└── 99/ obsolete superseded, or built on a deprecated UI5 control
5258
```
5359

60+
This tree is machine-checked: `node scripts/check-agents-structure.js` compares
61+
it against the actual `package.devc.xml` `<CTEXT>` values and fails on any
62+
drift (runs in CI). **Whenever a subpackage is added, removed, or renamed,
63+
update this tree in the same change.**
64+
5465
Each subpackage's `package.devc.xml` `<CTEXT>` is the human-readable name shown
5566
above (e.g. `only non-abap-cloud`). **That CTEXT string is also the overview
5667
group name — keep the two identical** (see §4).
@@ -71,14 +82,14 @@ the library's `demokit/docuindex.json` in openui5 (HTML markup stripped,
7182
truncated to the 60-character DESCRIPT limit). The **full, untruncated**
7283
description is kept as additional ABAP Doc lines below the URL line; the
7384
overview generator prefers those lines as the tile `sub` (§4).
74-
Demos that have no demo kit original belong in `00/12` (controls - custom)
75-
instead.
85+
Demos that have no demo kit original do not belong in `01/08` — file them in
86+
the framework packages (`01/05` for custom-control/action demos, `01/06` for
87+
use cases) or, when a restriction applies, in the matching `src/00` category.
7688

77-
Machine-generated ports that have not been manually reviewed yet live in
78-
`01/08/07` (controls - generated) and carry the marker line
79-
`"! Generated port of a UI5 demo kit sample - not yet manually reviewed`
80-
directly above the `"! Rebuild ...` line. After review, move a sample into
81-
the library subpackage it belongs to and drop the marker line.
89+
Machine-generated demo kit ports that have not been manually reviewed do not
90+
live in this repository — they are collected in the separate api repository
91+
of the abap2UI5 organization. Everything under `01/08` here is manually
92+
reviewed.
8293

8394
---
8495

@@ -103,22 +114,19 @@ The split is driven directly by the CI builds:
103114
applies:
104115

105116
1. Needs on-premise-only ABAP (not Cloud) → `00/01`
106-
2. Uses SAPUI5-only controls → `00/02` (and if it *also* needs a custom control `00/10`)
117+
2. Uses a SAPUI5-only control, **or** a control/property introduced after UI5 1.71 `00/02`
107118
3. Runs only inside the Launchpad → `00/03`
108-
4. Uses a control/property introduced after UI5 1.71`00/04`
119+
4. Needs the `z2ui5/Util` frontend helper module in the view (`core:require {Helper:'z2ui5/Util'}`)`00/04`
109120
5. Needs native JavaScript / CSS / HTML → `00/05`
110121
6. Test / scaffolding app → `00/06`
111122
7. Experimental / work-in-progress → `00/07`
112-
8. Complete multi-feature showcase demo → `00/08`
113-
9. Built on the generic XML view (`z2ui5_cl_util_xml`) → `00/09`
114-
10. Deprecated control/property, or superseded → `00/99`
115-
11. Not yet triaged → `00/11` (temporary parking; triage into 01–99 later)
123+
8. Deprecated control/property, or superseded → `00/99`
116124

117125
A sample qualifies for `src/01` **only if none** of the above restrictions
118126
apply: OpenUI5-compatible, ABAP-Cloud-ready, standalone, every control **and**
119127
property available since UI5 1.71 (16 Jan 2020) **and** not deprecated, no native
120128
JS, not a test, finished and clean. "Old" is not enough (deprecated → `00/99`);
121-
"non-deprecated" is not enough (post-1.71 → `00/04`).
129+
"non-deprecated" is not enough (post-1.71 → `00/02`).
122130

123131
---
124132

@@ -224,20 +232,21 @@ from the old catalog.
224232
every tile's `group` to match. A tile's group must equal the CTEXT of the
225233
folder the class physically lives in — never a neighbouring category.
226234
4. **Group blocks follow folder order.** Emit groups in ascending folder number
227-
(`00/01``00/12``00/99`; `01/01``01/08/00``01/08/06`) so the
235+
(`00/01``00/07``00/99`; `01/01``01/08/00``01/08/07`) so the
228236
on-screen order mirrors the tree; a nested subpackage forms its own group
229237
directly after its parent slot. When inserting a new group, place it at its
230-
numeric slot (e.g. `uncategorized` = `00/11` goes **after**
231-
`only non-openui5-with-cc` (`00/10`) and **before** `obsolete` (`00/99`)).
238+
numeric slot (e.g. `use of z2ui5` = `00/04` goes **after**
239+
`only with launchpad` (`00/03`) and **before**
240+
`only with javascript and css and html` (`00/05`)).
232241
5. **Within a group, sort tiles alphabetically (case-insensitive) by `header`,
233242
then by `sub`.** Sorting by `header` first keeps numbered series together and
234243
in order (`Binding I`, `Binding II`, `Binding III`, … underneath each other;
235244
likewise `Popover I…IV`, `Popup I…III`). The group order from rule 4 is
236245
untouched; only the tiles inside each group are ordered.
237246
6. **Moving a subpackage = moving its whole tile group** between the two
238-
catalogs, inserted at the correct numeric slot (e.g. the uncategorized move
239-
`src/01/07` `src/00/11` lifted the entire `uncategorized` group out of
240-
`sample_app_001` and into `sample_app_000`).
247+
catalogs, inserted at the correct numeric slot (moving a subpackage from
248+
`src/01` to `src/00` lifts its entire tile group out of `sample_app_001`
249+
and into `sample_app_000`).
241250
7. After every change, verify: `get_catalog( )` and the folder tree agree —
242251
same apps, same group names (== CTEXT), same grouping, no app in the wrong
243252
overview, none missing. The safest way to regenerate is to rebuild each
@@ -256,18 +265,20 @@ newline). **Run `abaplint` — 0 issues — before committing.**
256265

257266
**Adding a sample**
258267
1. Create the class; place it in the correct folder per §2.
259-
2. Add one tile to the matching overview catalog (§4), in the right group and
260-
numeric position, sorted by `sub`.
268+
2. Regenerate the overview catalogs: `npm run launchpad` (§4).
261269
3. `abaplint` → 0 issues → commit (English message).
262270

263271
**Moving a sample / subpackage**
264272
1. `git mv` the files (no rename needed — `FOLDER_LOGIC=PREFIX`).
265-
2. Update the catalog(s): change the tile's `group`, and if it crossed between
266-
`src/00` and `src/01`, move the tile to the other overview app.
267-
3. `abaplint` → 0 issues → commit.
273+
2. Regenerate the overview catalogs: `npm run launchpad` (§4).
274+
3. If a subpackage was added/removed/renamed: update the §1 tree and run
275+
`node scripts/check-agents-structure.js`.
276+
4. `abaplint` → 0 issues → commit.
268277

269278
**Before every commit**
270279
- `abaplint` reports 0 issues (config `abaplint.jsonc`).
280+
- `node scripts/check-agents-structure.js` reports no drift between the §1
281+
tree and the actual `package.devc.xml` CTEXTs.
271282
- abapGit file format for all file types: UTF-8, LF only, final newline,
272283
2-space indent (§6).
273284
- Overview catalogs still mirror the folder tree (§4).
@@ -481,7 +492,9 @@ ENDMETHOD.
481492

482493
## 10. Building Views
483494

484-
Views are XML strings passed to `client->view_display()`. There are two ways to build them:
495+
Views are XML strings passed to `client->view_display()`, built with the
496+
`z2ui5_cl_xml_view` fluent API — typed methods where they exist, `_generic( )`
497+
for the rest.
485498

486499
### 1. `z2ui5_cl_xml_view` — typed fluent API
487500
Pre-built methods for common UI5 controls (`shell`, `page`, `simple_form`, `input`, `button`, etc.). Use this for standard layouts.
@@ -542,75 +555,33 @@ ENDMETHOD.
542555
The hierarchy above is: `shell``page``simple_form``content` → (leaf elements).
543556
`label`, `input`, `button` are siblings inside `content`, so they stay at the same indent level as `)->content(`.
544557

545-
### 2. `z2ui5_cl_util_xml` — generic XML builder
546-
Builds any XML structure directly from element names, namespaces and attributes. **Look up the control in the [UI5 API Reference](https://ui5.sap.com/#/api) and translate 1:1 to ABAP** — no wrapper, no abstraction layer.
547-
548-
**UI5 XML:**
549-
```xml
550-
<form:SimpleForm title="T" editable="true">
551-
<form:content>
552-
<Label text="qty"/>
553-
<Input value="{...}"/>
554-
</form:content>
555-
</form:SimpleForm>
556-
```
557-
558-
**ABAP:**
559-
```abap
560-
->_( n = `SimpleForm` ns = `form` p = VALUE #(
561-
( n = `title` v = `T` )
562-
( n = `editable` v = abap_true ) )
563-
)->_( n = `content` ns = `form`
564-
)->__( n = `Label` a = `text` v = `qty`
565-
)->__( n = `Input` a = `value` v = client->_bind_edit( qty ) )
566-
```
567-
568-
Key rules for `z2ui5_cl_util_xml`:
569-
- `_( n, ns, p )` — add child element, navigate **into** it
570-
- `__( n, ns, a, v, p )` — add child element, **stay** at current level
571-
- `p( n, v )` — add a single attribute to current element
572-
- Single attribute: use `a = ... v = ...` parameters directly
573-
- Multiple attributes: use `p = VALUE #( ( n = ... v = ... ) ... )`
574-
- Namespace declarations go on the root `mvc:View` element as regular attributes (`xmlns:form`, etc.)
575-
- Only declare namespaces that are actually used in the view
576-
- `stringify()` on the factory root produces the complete XML string
577-
578-
#### Method chaining
579-
580-
Each call in a chain must start on its own line — never place two `->_()` / `->__()` calls on the same line:
558+
### 2. `_generic( )` — controls without a typed method
581559

582-
```abap
583-
" Wrong
584-
DATA(page) = root->__( `Shell` )->__( n = `Page`
585-
586-
" Correct
587-
DATA(page) = root->__( `Shell`
588-
)->__( n = `Page`
589-
```
590-
591-
Chain continuations (`)->`) are indented 3 spaces relative to the statement's base indentation.
592-
593-
#### Parameter alignment
594-
595-
When `p` appears on a continuation line, align it directly under `n` — one space after the opening `(`:
560+
When a control or aggregation has no typed wrapper method (yet), stay inside
561+
the `z2ui5_cl_xml_view` chain and add the element generically. **Look up the
562+
control in the [UI5 API Reference](https://ui5.sap.com/#/api) and translate
563+
1:1** — element name, namespace and properties exactly as documented:
596564

597565
```abap
598-
" Wrong — p is one space too far right
599-
)->_( n = `Input`
600-
p = VALUE #( ... ) ).
601-
602-
" Correct — p directly under n
603-
)->_( n = `Input`
604-
p = VALUE #( ... ) ).
566+
header_title->_generic(
567+
name = `breadcrumbs`
568+
ns = `f`
569+
)->breadcrumbs(
570+
)->link( text = `Home` ).
605571
```
606572

607-
Continuation lines inside `VALUE #( )` align with the first tuple `(`:
608-
609-
```abap
610-
)->_( n = `Input`
611-
p = VALUE #( ( n = `type` v = `Number` )
612-
( n = `value` v = client->_bind_edit( qty ) ) ) ).
613-
```
573+
Key rules for `_generic( )`:
574+
- `_generic( name = ... ns = ... t_prop = ... )` adds one element and
575+
navigates **into** it; typed methods continue the chain below it.
576+
- Attributes go into `t_prop = VALUE #( ( n = `...` v = `...` ) ... )`.
577+
- `ns` is registered on the view automatically — only pass namespaces that
578+
are actually used.
579+
- Raw embedded content (e.g. an inline `<style>` body) goes through
580+
`_cc_plain_xml( )`.
581+
582+
> The former standalone XML builder `z2ui5_cl_util_xml` is retired in the
583+
> framework (obsolete package, no new consumers) and is no longer used by any
584+
> sample — do not use it in new samples.
614585
615586
#### VALUE #( ) formatting
616587

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"description": "Learn how to code with abap2UI5.",
66
"scripts": {
77
"launchpad": "node scripts/generate-launchpad.js",
8+
"check:agents": "node scripts/check-agents-structure.js",
89
"syfixes": "find . -type f -name '*.abap' -exec sed -i -e 's/ RAISE EXCEPTION TYPE cx_sy_itab_line_not_found/ ASSERT 1 = 0/g' {} + ",
910
"abaplintpathfix": "sed -i 's|\"files\": \"/\\.\\.\\/\\.\\.\\/src\\/\\*\\*\\/\\*\\.\\*\"|\"files\": \"/src/**/*.*\"|g' abaplint.jsonc",
1011
"downport": "rm -rf src/00 && abaplint --fix .github/abaplint/abap_702.jsonc && npm run syfixes && cp -f .github/abaplint/abap_702.jsonc abaplint.jsonc && npm run abaplintpathfix"

scripts/check-agents-structure.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#!/usr/bin/env node
2+
// Verifies that the folder tree documented in AGENTS.md §1 matches the actual
3+
// package structure: every subpackage in the tree must exist with exactly the
4+
// documented CTEXT (package.devc.xml), and every actual subpackage must appear
5+
// in the tree. Exits non-zero on any drift.
6+
"use strict";
7+
8+
const fs = require("fs");
9+
const path = require("path");
10+
11+
const root = path.join(__dirname, "..");
12+
13+
function actualPackages() {
14+
const map = new Map(); // "src/00/01" -> CTEXT
15+
const walk = (dir) => {
16+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
17+
const p = path.join(dir, entry.name);
18+
if (entry.isDirectory()) {
19+
walk(p);
20+
} else if (entry.name === "package.devc.xml") {
21+
const rel = path.relative(root, dir).split(path.sep).join("/");
22+
if (rel === "src") continue; // root package is not part of the documented tree
23+
const match = fs.readFileSync(p, "utf8").match(/<CTEXT>([^<]*)<\/CTEXT>/);
24+
map.set(rel, match ? match[1].trim() : "");
25+
}
26+
}
27+
};
28+
walk(path.join(root, "src"));
29+
return map;
30+
}
31+
32+
function documentedPackages() {
33+
const md = fs.readFileSync(path.join(root, "AGENTS.md"), "utf8");
34+
const section = md.split(/^## 1\. Repository layout$/m)[1];
35+
if (!section) throw new Error("AGENTS.md: section '## 1. Repository layout' not found");
36+
const fence = section.match(/```\n([\s\S]*?)```/);
37+
if (!fence) throw new Error("AGENTS.md: no fenced tree block in section 1");
38+
39+
const map = new Map(); // "src/00/01" -> CTEXT
40+
const stack = [];
41+
for (const line of fence[1].split("\n")) {
42+
const m = line.match(/^([ ]*)(?:|) (\d+)\/ {2}(.*)$/);
43+
if (!m) continue;
44+
const depth = m[1].length / 4;
45+
if (!Number.isInteger(depth)) throw new Error(`AGENTS.md: unexpected tree indentation in line: ${line}`);
46+
stack[depth] = m[2];
47+
stack.length = depth + 1;
48+
49+
// CTEXT = quoted label ("basic") or the text before the first run of 2+ spaces
50+
let rest = m[3].trim();
51+
const quoted = rest.match(/^"([^"]*)"/);
52+
const ctext = quoted ? quoted[1] : rest.split(/ {2,}/)[0].trim();
53+
map.set("src/" + stack.join("/"), ctext);
54+
}
55+
if (map.size === 0) throw new Error("AGENTS.md: no package lines found in the tree block");
56+
return map;
57+
}
58+
59+
const actual = actualPackages();
60+
const documented = documentedPackages();
61+
const errors = [];
62+
63+
for (const [pkg, ctext] of documented) {
64+
if (!actual.has(pkg)) {
65+
errors.push(`documented in AGENTS.md but missing on disk: ${pkg} ("${ctext}")`);
66+
} else if (actual.get(pkg) !== ctext) {
67+
errors.push(`CTEXT mismatch for ${pkg}: AGENTS.md says "${ctext}", package.devc.xml says "${actual.get(pkg)}"`);
68+
}
69+
}
70+
for (const [pkg, ctext] of actual) {
71+
if (!documented.has(pkg)) {
72+
errors.push(`exists on disk but not documented in AGENTS.md §1: ${pkg} ("${ctext}")`);
73+
}
74+
}
75+
76+
if (errors.length > 0) {
77+
console.error("AGENTS.md §1 tree and src/ package structure have drifted apart:\n");
78+
for (const error of errors) console.error(" - " + error);
79+
console.error("\nUpdate the tree in AGENTS.md §1 (or fix the package) so both agree.");
80+
process.exit(1);
81+
}
82+
83+
console.log(`AGENTS.md §1 tree matches src/ package structure (${actual.size} subpackages).`);

0 commit comments

Comments
 (0)