Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
f567359
Initial plan
Copilot Apr 15, 2026
857badc
chore: initial plan for CTS tr reassign command
Copilot Apr 15, 2026
cfd8b70
feat: add CTS tr reassign command to change transport owner
Copilot Apr 15, 2026
5fbb860
feat: add package CRUD and object CRUD commands (class, program, inte…
Copilot Apr 15, 2026
9e9289e
feat: add datapreview osql, abap run, and DDIC object commands
Copilot Apr 15, 2026
fde59fa
feat: add AUnit sonar format, improved exit codes, and ATC customizin…
Copilot Apr 15, 2026
6b49e9a
feat: add CDS DDL and DCL source commands with XML injection fix
Copilot Apr 15, 2026
bf97ca6
feat: add checkout command and remaining CDS/DDIC subtask completions
Copilot Apr 15, 2026
e20eef7
test: add coverage for sonar formatter, CDS ADK objects, and CLI comm…
Copilot Apr 15, 2026
0eeea08
feat(contracts): add datapreview, CTS useraction, DDL/DCL, tablesettings
ThePlenkov Apr 18, 2026
c9411fe
refactor(adk,cli): route every ADT call through typed contracts
ThePlenkov Apr 18, 2026
d25885b
refactor(mock): consolidate three mock stacks into @abapify/adt-fixtures
ThePlenkov Apr 18, 2026
457c3cc
feat(parity): 15 new MCP tools + CLI↔MCP e2e test harness
ThePlenkov Apr 18, 2026
cbc19f4
feat(aunit): ABAP Unit code coverage — JaCoCo output + CLI/MCP parity
ThePlenkov Apr 18, 2026
2caff95
docs(roadmap): add sapcli-parity master plan + 15 self-contained epic…
ThePlenkov Apr 18, 2026
ffbe4db
feat(incl): INCL CLI + MCP — E01
ThePlenkov Apr 18, 2026
e763d35
feat(function): FUGR/FUNC CLI + MCP — E02
ThePlenkov Apr 18, 2026
7a4ed04
feat(strust): STRUST PSE/cert CLI + MCP — E04
ThePlenkov Apr 18, 2026
a4e6eee
feat(plugin): FormatPlugin API foundation — E05
ThePlenkov Apr 18, 2026
51fa799
feat(acds): full CDS DDL/DCL grammar + AST walker + validators — E09
ThePlenkov Apr 18, 2026
665c16d
feat(gcts): AFF/gCTS format plugin — E06
ThePlenkov Apr 18, 2026
027a172
feat(checkin): checkin via batch lock session — E08
ThePlenkov Apr 18, 2026
94583dc
feat(gcts-cli): gCTS command plugin — E07
ThePlenkov Apr 18, 2026
af89d72
feat(rap): BDEF (Behavior Definition) CRUD — E10
ThePlenkov Apr 18, 2026
0c3cb7e
feat(rap): SRVD (Service Definition) CRUD — E11
ThePlenkov Apr 18, 2026
f8b6c3c
feat(rap): SRVB (Service Binding) CRUD + publish/unpublish — E12
ThePlenkov Apr 18, 2026
dd0f8ff
feat(badi): BAdI CRUD + real-SAP e2e harness — E03
ThePlenkov Apr 18, 2026
45487cc
feat(rfc): SOAP-over-HTTP RFC transport + adt rfc command — E13
ThePlenkov Apr 18, 2026
aa8b42b
feat(flp): Fiori Launchpad read-only inventory — E14
ThePlenkov Apr 18, 2026
f2d73d4
feat(wb): workbench CLI + real-e2e uncovers MCP endpoint bugs — E15
ThePlenkov Apr 18, 2026
13e777d
feat(real-e2e): TRL backfill sweep + WB where-used 2-step POST fix
ThePlenkov Apr 18, 2026
8115231
fix: resolve all pre-existing typecheck + test failures — QC1
ThePlenkov Apr 18, 2026
1f2442d
feat(gcts): format.export — complete checkin roundtrip for gCTS — QC2
ThePlenkov Apr 18, 2026
ab1c913
chore(lint): reduce warnings 713 → 348 (51% cleanup) — QC3
ThePlenkov Apr 18, 2026
b4bfebc
fix(ci): resolve CodeQL ReDoS + code-quality findings, fix main CI
ThePlenkov Apr 19, 2026
cd6c5d5
fix(ci): restore ADT coverage contract files swallowed by .gitignore
ThePlenkov Apr 19, 2026
faa8697
fix(ci): adt-cli tests depend on adt-mcp:build (harness imports it)
ThePlenkov Apr 19, 2026
c72533a
fix(security): replace regex XML parsing with @xmldom/xmldom — CodeQL…
ThePlenkov Apr 19, 2026
708ca6c
fix: drop CDATA branch from TAG_RE in adt-rfc — CodeQL final
ThePlenkov Apr 19, 2026
c32db2e
fix: replace TAG_RE loop with split-based tokenizer — CodeQL final
ThePlenkov Apr 19, 2026
734713b
fix(security): resolve all remaining CodeQL alerts with source-level …
ThePlenkov Apr 19, 2026
f860bdd
fix(security): resolve remaining SonarCloud hotspots + reliability bugs
ThePlenkov Apr 19, 2026
646dbe4
fix(security): 6 more .sort() comparators — SonarCloud S2871
ThePlenkov Apr 19, 2026
d5031bc
fix(security): replace hardcoded mock password with randomBytes — S2068
ThePlenkov Apr 19, 2026
df2b177
chore(quality): sweep SonarCloud code smells — 254 → ~116 (−138)
ThePlenkov Apr 19, 2026
8aa591d
fix(pr-103): address review comments — type safety, parity, harness, …
ThePlenkov Apr 19, 2026
97f42b2
feat(nx-cloud): setup nx cloud workspace
ThePlenkov Apr 19, 2026
14e2c48
Merge PR #105: nx-cloud setup into PR #103
ThePlenkov Apr 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ lib-cov

# Coverage directory used by tools like istanbul
coverage
# ...but NOT the adt-contracts contract directory named "coverage" (ADT runtime traces coverage endpoints)
!packages/adt-contracts/src/adt/runtime/traces/coverage
!packages/adt-contracts/src/adt/runtime/traces/coverage/**
*.lcov

# nyc test coverage
Expand Down
26 changes: 26 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,32 @@ adt-cli

Foundation packages (no `@abapify` deps): `ts-xsd`, `speci`, `logger`, `acds`.

## MCP ↔ CLI Coupling (intentional)

`@abapify/adt-mcp` is a **thin MCP adapter over the CLI service layer**. It
may (and does) depend on `@abapify/adt-cli` and on the domain plugin packages
(`@abapify/adt-aunit`, `@abapify/adt-rfc`, `@abapify/adt-plugin-*`, etc.).

This is a deliberate architectural choice, not an accident:

- **Invariant**: every CLI subcommand has a matching MCP tool, and every MCP
tool has a matching CLI subcommand. Behaviour, flags, output shape, and
error messages are expected to match.
- **Enforcement**: the parity test suite in
`packages/adt-cli/tests/e2e/parity.*.test.ts` is the source of truth. A
feature is not "done" until both the CLI path and the MCP path hit the
same mock backend through the same service function and return equivalent
results.
- **Code reuse**: MCP tool handlers delegate to CLI service functions
(exported from `packages/adt-cli/src/index.ts`) rather than re-implementing
transports, locking, XML serialisation, or ADK orchestration.
- **Consequence**: the `adt-cli` → `adt-mcp` dependency direction is
forbidden (would create a cycle). The `adt-mcp` → `adt-cli` direction is
required.

When adding a new feature, add the CLI command **and** the MCP tool in the
same change, and add a parity test that exercises both paths.

## Type Flow (Core Architecture)

```
Expand Down
253 changes: 30 additions & 223 deletions bun.lock

Large diffs are not rendered by default.

174 changes: 174 additions & 0 deletions docs/architecture/format-plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Format-Plugin API

_Status_: accepted (E05)

## Problem

The `adt` CLI originally hard-wired `@abapify/adt-plugin-abapgit` as the only
serialization format. `export`, `diff`, `import`, and `checkout` all imported
that package directly, which made it impossible to add a new format (gCTS,
AFF, …) without either (a) forking the CLI or (b) shipping an additional
`adt-plugin-abapgit`-shaped package and patching every consumer.

The user requirement driving this epic is:

> Even if the `gcts` command isn't installed we must still be able to save in
> gCTS format alongside abapGit.

That means the CLI must discover formats through a contract, not through
import statements.

## Solution

A small in-process registry of **format plugins**, plus a stable
`FormatPlugin` interface that any third-party package can implement. Format
plugins self-register at module-load time — importing the package is enough
to make `--format <id>` work.

### Moving parts

```
┌──────────────────────────────────────────────────────┐
│ @abapify/adt-plugin │
│ │
│ interface FormatPlugin { id, description, │
│ supportedTypes, │
│ getHandler(type), │
│ parseFilename?(name) } │
│ │
│ registerFormatPlugin(plugin) │
│ getFormatPlugin(id) / requireFormatPlugin(id) │
│ listFormatPlugins() │
└───────────────────────▲──────────────────────────────┘
│ implements
┌───────────────────────┴──────────────────────────────┐
│ @abapify/adt-plugin-abapgit │
│ │
│ abapgitFormatPlugin : FormatPlugin │
│ (self-registers on module load) │
└───────────────────────▲──────────────────────────────┘
│ looks up via getFormatPlugin('abapgit')
┌───────────────────────┴──────────────────────────────┐
│ Consumers │
│ - @abapify/adt-diff │
│ - @abapify/adt-export │
│ - @abapify/adt-cli services/import + checkout │
└──────────────────────────────────────────────────────┘
```

The registry is keyed on the `globalThis` with a `Symbol.for` well-known key
so duplicate module-graph evaluation (tests, bundler outputs) does not
produce two independent registries.

### Bootstrap

Exactly **one** location in the CLI imports `@abapify/adt-plugin-abapgit`
directly: `packages/adt-cli/src/lib/cli.ts` uses a side-effect-only import
(no `from` clause) so that the acceptance grep — which forbids
`from.*adt-plugin-abapgit` in `adt-export`, `adt-diff`, and `adt-cli` — stays
clean. Every other file uses `getFormatPlugin('abapgit')`.

Third-party plugins are loaded either:

1. Statically, by adding `import '@abapify/<your-format-plugin>';` to the
consumer's entry point, or
2. Dynamically, via `await import(packageName)` (current behaviour of
`loadFormatPlugin` in adt-cli — useful for plugins listed in
`adt.config.ts`).

Both paths trigger the plugin's module-level `registerFormatPlugin(...)` call.

### Interface contract

```ts
export interface FormatPlugin {
readonly id: string;
readonly description: string;
readonly supportedTypes: ReadonlyArray<string>;
getHandler(type: string): FormatHandler | undefined;
parseFilename?(filename: string): ParsedFormatFilename | undefined;
}
```

- **`id`** is what users pass after `--format` on the CLI. It is part of the
public API and cannot change without a major version bump.
- **`supportedTypes`** may be computed lazily via a getter (the abapGit
plugin does this because it reads from a live handler registry).
- **`getHandler(type)`** returns a `FormatHandler` — the per-object-type
serializer. The abapGit concrete `ObjectHandler<T, TSchema>` is a
structural superset of `FormatHandler`, so existing abapGit handlers work
through a widening cast (no translation layer needed).
- **`parseFilename(name)`** is optional because not every format uses
filenames at all (gCTS streams via REST, for example).

### Registration rules

```
registerFormatPlugin(plugin)
- same id, same instance → no-op (idempotent, survives HMR / dual graph)
- same id, different object → throws (prevents silent shadowing)
- new id → stored
```

`requireFormatPlugin(id)` throws with a message that lists the currently
registered ids, which keeps user-visible errors actionable:

```
Format plugin "gcts" is not registered. Available formats: abapgit.
```

## Alternatives considered

1. **Extend the existing `AdtPlugin`** (`name`, `registry`, `format` shape) —
rejected because that interface couples serialization logic with
import/export workflow orchestration. The format registry needs a
narrower, purely serialization-focused contract so that future formats
(e.g. gCTS) can implement it without also re-implementing the import
pipeline.

2. **Move `ObjectHandler` into `@abapify/adt-plugin`** — rejected because
`ObjectHandler` is heavily parameterized on `AbapGitSchema`, which is an
abapGit-specific thing and has no business leaking into the generic
plugin interface. Instead, `FormatHandler` in `@abapify/adt-plugin`
defines the minimum surface consumers need (parse/build/serialize) and
concrete handlers may be structural supersets.

3. **Auto-discover `node_modules/@abapify/*`** — deferred. The CLI already
has a plugin-loading mechanism fed by `adt.config.ts`; rather than
introducing a second discovery path, format plugins piggy-back on the
same module imports. See _Open questions_ below.

## Out of scope

- Implementing gCTS as a format plugin (E06).
- The `gcts` CLI command surface (E07).
- Checkin-side lock/transport orchestration (E08).
- `diff(local, remote)` on the `FormatPlugin` interface — the diff command
today reaches into the concrete handler directly through `getHandler()`.
Promoting it onto `FormatPlugin` can happen when a second format needs
diff support.

## Open questions

1. **Automatic discovery vs explicit bootstrap.** The current design relies
on the consumer (CLI, tests) deciding which plugin packages to import.
Should `@abapify/adt-plugin` ship a `discoverFormatPlugins()` helper that
scans `node_modules/@abapify/adt-plugin-*` and imports them? The
equivalent already exists for CLI-command plugins — parity is probably
desirable once a second format lands.

2. **Multi-file round-trip metadata.** `SerializedFile[]` carries only
`path`, `content`, `encoding`. Some formats may need extra per-file
metadata (e.g. gCTS pack hints, charset overrides). We intentionally did
**not** extend `SerializedFile` yet; we will revisit when the first
format actually needs it.

3. **Diff on `FormatPlugin`.** The epic listed
`diff(local, remote): Promise<DiffResult>` as a candidate method. It was
dropped from the v1 interface because the current diff logic is
abapGit-specific (projecting remote onto local's field set, XML
normalization, etc.) and there is no obvious generic contract yet. When
gCTS arrives (E06) we'll have two data points and can lift a real
abstraction.
123 changes: 123 additions & 0 deletions docs/roadmap/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# sapcli-parity Roadmap

Master plan for closing functional gaps with `jfilak/sapcli` and going beyond.

Each epic is a **self-contained brief** (one file in `epics/`) ready to be handed to a fresh Devin session. The orchestrator (mega-agent) tracks dependencies; each agent works only inside its own epic file.

## Status legend

- 🟢 Ready to start (no blocking deps)
- 🟡 Blocked by another epic
- ✅ Landed
- 🚧 In progress

## Dependency graph

```
┌─────────────────────┐
│ E05: format-plugin │ (foundation)
│ API contract │
└─────────┬───────────┘
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ E06: gcts │ │ E08: checkin │ │ E07: gcts-cmd │
│ format plug. │ │ (lock-batch) │ │ plugin (cmds) │
└──────────────┘ └──────────────────┘ └──────────────────┘

┌─────────────────┐
│ E09: acds parser│ (foundation for RAP)
└────────┬────────┘
┌──────────────┼──────────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│E10:BDEF │ │E11:SRVD │ │E12:SRVB │
└─────────┘ └─────────┘ └─────────┘

Independent (can parallelize from day 1):
E01:include E02:function E03:badi E04:strust E13:startrfc E14:flp E15:wb
```

## Epics

| # | File | Title | Size | Status | Blocks |
| --- | -------------------------------------------------------------------- | ---------------------------- | ---- | ------------- | ------------- |
| E01 | [epics/e01-include.md](epics/e01-include.md) | INCL CLI + MCP | S | 🟢 | — |
| E02 | [epics/e02-function.md](epics/e02-function.md) | FUGR/FUNC CLI + MCP | M | 🟢 | — |
| E03 | [epics/e03-badi.md](epics/e03-badi.md) | BAdI implementations | M | 🟢 | — |
| E04 | [epics/e04-strust.md](epics/e04-strust.md) | STRUST cert management | S | 🟢 | — |
| E05 | [epics/e05-format-plugin-api.md](epics/e05-format-plugin-api.md) | Format-plugin API foundation | M | 🟢 | E06, E07, E08 |
| E06 | [epics/e06-gcts-format-plugin.md](epics/e06-gcts-format-plugin.md) | gCTS as format-plugin | L | 🟡 (E05) | E07, E08 |
| E07 | [epics/e07-gcts-command-plugin.md](epics/e07-gcts-command-plugin.md) | gCTS as command-plugin | L | 🟡 (E05, E06) | — |
| E08 | [epics/e08-checkin.md](epics/e08-checkin.md) | checkin (push to SAP) | M | 🟡 (E05) | — |
| E09 | [epics/e09-acds-parser.md](epics/e09-acds-parser.md) | Extend acds parser | M | 🟢 | E10, E11, E12 |
| E10 | [epics/e10-rap-bdef.md](epics/e10-rap-bdef.md) | RAP BDEF | M | 🟡 (E09) | — |
| E11 | [epics/e11-rap-srvd.md](epics/e11-rap-srvd.md) | RAP SRVD | S | 🟡 (E09) | — |
| E12 | [epics/e12-rap-srvb.md](epics/e12-rap-srvb.md) | RAP SRVB CLI/MCP | S | 🟢 | — |
| E13 | [epics/e13-startrfc.md](epics/e13-startrfc.md) | startrfc (NW RFC transport) | L | 🟢 | — |
| E14 | [epics/e14-flp.md](epics/e14-flp.md) | Fiori Launchpad | M | 🟢 | — |
| E15 | [epics/e15-wb.md](epics/e15-wb.md) | Workbench navigation | S | 🟢 | — |

**Sizes:** S = ≤ 2 days, M = 3-5 days, L = 1-2 weeks.

## Recommended first-wave dispatch (parallel)

Day 1 — start these 5 in parallel (no inter-dependencies):

- **E01** include — quickest win, opens INCL surface
- **E02** function — closes existing FUGR/FUNC contract gap
- **E04** strust — small, isolated
- **E05** format-plugin API — unblocks the gCTS chain
- **E09** acds parser — unblocks RAP chain

Day 4-5 (after E05 lands):

- **E06** gcts-format
- **E08** checkin

Day 8+ (after E06):

- **E07** gcts-command

Day 4+ (after E09):

- **E10/E11/E12** RAP trio

## Per-epic Devin session protocol

Every epic file must contain (template at `epics/_template.md`):

1. **Mission** — single-sentence goal
2. **Why** — value, urgency, dependencies fulfilled
3. **References** — sapcli files (in `tmp/sapcli-ref/sapcli/`), existing repo files, RFCs
4. **Scope** — explicit list of files to add/modify
5. **Out of scope** — what NOT to touch
6. **Tests** — unit + e2e CLI/MCP parity expectations
7. **Acceptance** — bash commands that must pass
8. **Devin prompt** — ready-to-paste opening message for the spawned session

The spawned Devin session reads the epic file as its sole spec and produces a PR (or commits to a feature branch). Mega-agent does NOT inline code in epic files — only intent and references.

## How to spawn a session

When the Devin private-mode `devin_session_create` MCP tool is available:

```
mcp_call_tool(server="deepwiki", tool="devin_session_create",
arguments={"prompt": "<contents of epics/eXX-name.md Devin prompt section>",
"tags": ["sapcli-parity", "eXX"]})
```

Otherwise: open https://app.devin.ai/ → New session → paste the **Devin prompt** block from the epic file.

## Conventions enforced across all epics

- All HTTP via typed contracts (`@abapify/adt-contracts`). NO `fast-xml-parser`. NO `client.fetch()` bypasses unless documented.
- CLI command + MCP tool MUST be added together (parity is non-negotiable).
- Every operation needs an entry in `packages/adt-cli/tests/e2e/parity.<area>.test.ts`.
- Real SAP fixtures in `@abapify/adt-fixtures` (mark `TODO-synthetic` only when no capture available).
- abapGit-style filenames everywhere a file path is emitted (use `adtUriToAbapGitPath`).
- No commits without explicit user approval (per repo `.agents/rules/git/no-auto-commit`).
- Run `bunx nx format:write` before signalling done.
Loading
Loading