diff --git a/openspec/changes/add-aclass-parser/specs/aclass/spec.md b/openspec/changes/archive/2026-09-29-add-aclass-parser/design.md similarity index 100% rename from openspec/changes/add-aclass-parser/specs/aclass/spec.md rename to openspec/changes/archive/2026-09-29-add-aclass-parser/design.md diff --git a/openspec/changes/add-aclass-parser/proposal.md b/openspec/changes/archive/2026-09-29-add-aclass-parser/proposal.md similarity index 100% rename from openspec/changes/add-aclass-parser/proposal.md rename to openspec/changes/archive/2026-09-29-add-aclass-parser/proposal.md diff --git a/openspec/changes/archive/2026-09-29-add-aclass-parser/specs/aclass/spec.md b/openspec/changes/archive/2026-09-29-add-aclass-parser/specs/aclass/spec.md new file mode 100644 index 000000000..d86ef7ccf --- /dev/null +++ b/openspec/changes/archive/2026-09-29-add-aclass-parser/specs/aclass/spec.md @@ -0,0 +1,63 @@ +# Delta — `aclass` capability + +## ADDED Requirements + +### Requirement: Structural ABAP OO parsing + +The parser SHALL recognise class and interface declarations — headers, +visibility sections, and member declarations (methods, attributes, types, +constants, events, aliases, interface statements) — including inheritance +and implements lists. Method implementation bodies SHALL be preserved as +opaque source slices with span information; statements inside a method +body SHALL NOT be parsed. + +#### Scenario: Class definition round-trips through the AST + +- **WHEN** a `.clas.abap` source containing sections and member + declarations is parsed +- **THEN** the AST contains a `ClassDef` with typed `Section` and + `ClassMember` nodes and each `MethodImpl` carries its raw body text + and span + +#### Scenario: Method body stays opaque + +- **WHEN** a `METHOD .` … `ENDMETHOD.` block is parsed +- **THEN** its statements are not interpreted and the raw slice is + preserved byte-for-byte + +### Requirement: Non-throwing parse contract + +`parse()` SHALL return `{ ast, errors }` and SHALL NOT throw on malformed +input. Unrecoverable errors SHALL yield a best-effort AST for the portion +understood before the break, with lex and parse diagnostics reported as +`ParseError` entries carrying severity, line, column, and message. + +#### Scenario: Malformed source returns diagnostics + +- **WHEN** source containing a syntax error is parsed +- **THEN** the result contains a non-empty `errors` array and a partial + AST, and no exception propagates + +### Requirement: No runtime dependency on `@abapify/abap-ast` + +The package SHALL NOT import `@abapify/abap-ast` in `src/**/*.ts`. +Shared shapes SHALL be re-declared locally; `abap-ast` is permitted only +as a devDependency for roundtrip tests. + +#### Scenario: Runtime boundary enforced + +- **WHEN** `packages/aclass/src/**/*.ts` is inspected +- **THEN** no import references `@abapify/abap-ast` + +### Requirement: Chevrotain-based lexer and statement parser + +Tokenisation SHALL use Chevrotain token definitions; no hand-rolled lexer +or regex-driven tokenizer is permitted. Keyword ordering SHALL place +compound keywords before their prefixes and `Identifier` last so keywords +win via `longer_alt`. + +#### Scenario: Compound keyword tokenises correctly + +- **WHEN** source contains `CLASS-DATA` +- **THEN** the lexer emits a single `ClassData` token rather than + splitting at the hyphen diff --git a/openspec/changes/add-aclass-parser/tasks.md b/openspec/changes/archive/2026-09-29-add-aclass-parser/tasks.md similarity index 100% rename from openspec/changes/add-aclass-parser/tasks.md rename to openspec/changes/archive/2026-09-29-add-aclass-parser/tasks.md diff --git a/openspec/changes/add-bounded-analysis-class/design.md b/openspec/changes/archive/2026-09-29-add-bounded-analysis-class/design.md similarity index 100% rename from openspec/changes/add-bounded-analysis-class/design.md rename to openspec/changes/archive/2026-09-29-add-bounded-analysis-class/design.md diff --git a/openspec/changes/add-bounded-analysis-class/proposal.md b/openspec/changes/archive/2026-09-29-add-bounded-analysis-class/proposal.md similarity index 100% rename from openspec/changes/add-bounded-analysis-class/proposal.md rename to openspec/changes/archive/2026-09-29-add-bounded-analysis-class/proposal.md diff --git a/openspec/changes/add-bounded-analysis-class/specs/adt-mcp/spec.md b/openspec/changes/archive/2026-09-29-add-bounded-analysis-class/specs/adt-mcp/spec.md similarity index 100% rename from openspec/changes/add-bounded-analysis-class/specs/adt-mcp/spec.md rename to openspec/changes/archive/2026-09-29-add-bounded-analysis-class/specs/adt-mcp/spec.md diff --git a/openspec/changes/add-bounded-analysis-class/tasks.md b/openspec/changes/archive/2026-09-29-add-bounded-analysis-class/tasks.md similarity index 63% rename from openspec/changes/add-bounded-analysis-class/tasks.md rename to openspec/changes/archive/2026-09-29-add-bounded-analysis-class/tasks.md index f74dcd0d7..be5f448ab 100644 --- a/openspec/changes/add-bounded-analysis-class/tasks.md +++ b/openspec/changes/archive/2026-09-29-add-bounded-analysis-class/tasks.md @@ -4,4 +4,5 @@ - [x] Reclassify `atc_run` and prove ordinary reads cannot dispatch it. - [x] Keep signed `safe_execute` claims fail-closed until exact scope enforcement exists. -- [ ] Run package build, typecheck, tests, lint, and full-tree formatting. +- [x] Run package build, typecheck, tests, lint, and full-tree formatting. + Verified in PR #223 (adt-mcp: 208 tests, lint, build, format all pass). diff --git a/openspec/changes/add-cts-transport-metadata-json/design.md b/openspec/changes/archive/2026-09-29-add-cts-transport-metadata-json/design.md similarity index 100% rename from openspec/changes/add-cts-transport-metadata-json/design.md rename to openspec/changes/archive/2026-09-29-add-cts-transport-metadata-json/design.md diff --git a/openspec/changes/add-cts-transport-metadata-json/proposal.md b/openspec/changes/archive/2026-09-29-add-cts-transport-metadata-json/proposal.md similarity index 100% rename from openspec/changes/add-cts-transport-metadata-json/proposal.md rename to openspec/changes/archive/2026-09-29-add-cts-transport-metadata-json/proposal.md diff --git a/openspec/changes/add-cts-transport-metadata-json/specs/cts-transport-metadata/spec.md b/openspec/changes/archive/2026-09-29-add-cts-transport-metadata-json/specs/cts-transport-metadata/spec.md similarity index 100% rename from openspec/changes/add-cts-transport-metadata-json/specs/cts-transport-metadata/spec.md rename to openspec/changes/archive/2026-09-29-add-cts-transport-metadata-json/specs/cts-transport-metadata/spec.md diff --git a/openspec/changes/add-cts-transport-metadata-json/tasks.md b/openspec/changes/archive/2026-09-29-add-cts-transport-metadata-json/tasks.md similarity index 51% rename from openspec/changes/add-cts-transport-metadata-json/tasks.md rename to openspec/changes/archive/2026-09-29-add-cts-transport-metadata-json/tasks.md index 96b594409..eb64c52c0 100644 --- a/openspec/changes/add-cts-transport-metadata-json/tasks.md +++ b/openspec/changes/archive/2026-09-29-add-cts-transport-metadata-json/tasks.md @@ -10,5 +10,10 @@ ## 3. Verification -- [ ] 3.1 Run focused package tests, typecheck, and strict OpenSpec validation. +- [x] 3.1 Run focused package tests and strict OpenSpec validation. + adt-mcp test suite (208 tests incl. revived security files) green on main; + `openspec validate --strict` passes. The adt-mcp `typecheck` Nx target is + intentionally disabled (MCP SDK + Zod type inference OOM — see + `packages/adt-mcp/AGENTS.md`), so no typecheck result is recorded. - [ ] 3.2 Prove the command against a disposable SAP transport before consumer promotion. + Deferred: requires a live SAP system; tracked outside this change. diff --git a/openspec/changes/add-delegated-assistant-read-scope/design.md b/openspec/changes/archive/2026-09-29-add-delegated-assistant-read-scope/design.md similarity index 100% rename from openspec/changes/add-delegated-assistant-read-scope/design.md rename to openspec/changes/archive/2026-09-29-add-delegated-assistant-read-scope/design.md diff --git a/openspec/changes/add-delegated-assistant-read-scope/proposal.md b/openspec/changes/archive/2026-09-29-add-delegated-assistant-read-scope/proposal.md similarity index 100% rename from openspec/changes/add-delegated-assistant-read-scope/proposal.md rename to openspec/changes/archive/2026-09-29-add-delegated-assistant-read-scope/proposal.md diff --git a/openspec/changes/add-delegated-assistant-read-scope/specs/adt-mcp/spec.md b/openspec/changes/archive/2026-09-29-add-delegated-assistant-read-scope/specs/adt-mcp/spec.md similarity index 100% rename from openspec/changes/add-delegated-assistant-read-scope/specs/adt-mcp/spec.md rename to openspec/changes/archive/2026-09-29-add-delegated-assistant-read-scope/specs/adt-mcp/spec.md diff --git a/openspec/changes/add-delegated-assistant-read-scope/tasks.md b/openspec/changes/archive/2026-09-29-add-delegated-assistant-read-scope/tasks.md similarity index 100% rename from openspec/changes/add-delegated-assistant-read-scope/tasks.md rename to openspec/changes/archive/2026-09-29-add-delegated-assistant-read-scope/tasks.md diff --git a/openspec/changes/add-flow-index-only/.openspec.yaml b/openspec/changes/archive/2026-09-29-add-flow-index-only/.openspec.yaml similarity index 100% rename from openspec/changes/add-flow-index-only/.openspec.yaml rename to openspec/changes/archive/2026-09-29-add-flow-index-only/.openspec.yaml diff --git a/openspec/changes/add-flow-index-only/design.md b/openspec/changes/archive/2026-09-29-add-flow-index-only/design.md similarity index 100% rename from openspec/changes/add-flow-index-only/design.md rename to openspec/changes/archive/2026-09-29-add-flow-index-only/design.md diff --git a/openspec/changes/add-flow-index-only/proposal.md b/openspec/changes/archive/2026-09-29-add-flow-index-only/proposal.md similarity index 100% rename from openspec/changes/add-flow-index-only/proposal.md rename to openspec/changes/archive/2026-09-29-add-flow-index-only/proposal.md diff --git a/openspec/changes/add-flow-index-only/specs/adt-flow-index-only/spec.md b/openspec/changes/archive/2026-09-29-add-flow-index-only/specs/adt-flow-index-only/spec.md similarity index 100% rename from openspec/changes/add-flow-index-only/specs/adt-flow-index-only/spec.md rename to openspec/changes/archive/2026-09-29-add-flow-index-only/specs/adt-flow-index-only/spec.md diff --git a/openspec/changes/add-flow-index-only/tasks.md b/openspec/changes/archive/2026-09-29-add-flow-index-only/tasks.md similarity index 100% rename from openspec/changes/add-flow-index-only/tasks.md rename to openspec/changes/archive/2026-09-29-add-flow-index-only/tasks.md diff --git a/openspec/changes/add-mcp-http-transport/design.md b/openspec/changes/archive/2026-09-29-add-mcp-http-transport/design.md similarity index 100% rename from openspec/changes/add-mcp-http-transport/design.md rename to openspec/changes/archive/2026-09-29-add-mcp-http-transport/design.md diff --git a/openspec/changes/add-mcp-http-transport/proposal.md b/openspec/changes/archive/2026-09-29-add-mcp-http-transport/proposal.md similarity index 100% rename from openspec/changes/add-mcp-http-transport/proposal.md rename to openspec/changes/archive/2026-09-29-add-mcp-http-transport/proposal.md diff --git a/openspec/changes/add-mcp-http-transport/specs/adt-mcp/spec.md b/openspec/changes/archive/2026-09-29-add-mcp-http-transport/specs/adt-mcp/spec.md similarity index 98% rename from openspec/changes/add-mcp-http-transport/specs/adt-mcp/spec.md rename to openspec/changes/archive/2026-09-29-add-mcp-http-transport/specs/adt-mcp/spec.md index 6d2c5e68c..bd42d2c6d 100644 --- a/openspec/changes/add-mcp-http-transport/specs/adt-mcp/spec.md +++ b/openspec/changes/archive/2026-09-29-add-mcp-http-transport/specs/adt-mcp/spec.md @@ -1,10 +1,10 @@ # Delta — `adt-mcp` capability -## MODIFIED Requirements +## ADDED Requirements -### Requirement: Stateless server — connection-per-call +### Requirement: Two transports with distinct state models -> Previous wording (invariant #4 in `packages/adt-mcp/AGENTS.md`): +> Supersedes invariant #4 in `packages/adt-mcp/AGENTS.md`: > "Each tool call creates its own AdtClient via ctx.getClient(args). The > server holds no session, no cached client, and no credentials between > calls." diff --git a/openspec/changes/add-mcp-http-transport/tasks.md b/openspec/changes/archive/2026-09-29-add-mcp-http-transport/tasks.md similarity index 63% rename from openspec/changes/add-mcp-http-transport/tasks.md rename to openspec/changes/archive/2026-09-29-add-mcp-http-transport/tasks.md index 2d5bd2851..5c9f61cbf 100644 --- a/openspec/changes/add-mcp-http-transport/tasks.md +++ b/openspec/changes/archive/2026-09-29-add-mcp-http-transport/tasks.md @@ -7,13 +7,13 @@ feature that ships a CLI command and an MCP tool. ## Wave 0 — proposal + scaffold (sequential, lead) -- [ ] Write this OpenSpec change (`proposal.md`, `design.md`, `tasks.md`, +- [x] Write this OpenSpec change (`proposal.md`, `design.md`, `tasks.md`, `specs/adt-mcp/spec.md`). -- [ ] Feature branch `feat/mcp-http-transport` off `main`. -- [ ] Confirm `@modelcontextprotocol/sdk` ^1.27 is installed in +- [x] Feature branch `feat/mcp-http-transport` off `main`. +- [x] Confirm `@modelcontextprotocol/sdk` ^1.27 is installed in `packages/adt-mcp/package.json`; add `zod` refinements util if missing. -- [ ] Add `MCP_HTTP_PORT`, `MCP_AUTH_TOKEN`, `MCP_ALLOWED_HOSTS`, +- [x] Add `MCP_HTTP_PORT`, `MCP_AUTH_TOKEN`, `MCP_ALLOWED_HOSTS`, `MCP_ALLOWED_ORIGINS`, `MCP_SESSION_IDLE_MS`, `SAP_DEFAULT_SYSTEM_ID`, `TRUST_FORWARDED_AUTH` to the `adt-mcp` README's configuration section as placeholders. @@ -23,100 +23,103 @@ feature that ships a CLI command and an MCP tool. Parallelisable subagents; all touch `packages/adt-mcp` and `packages/adt-config`. -- [ ] **adt-mcp #http-entry** — `src/bin/adt-mcp.ts` transport switch: +- [x] **adt-mcp #http-entry** — `src/bin/adt-mcp.ts` transport switch: if `--http` / `MCP_HTTP_PORT`, start HTTP server; otherwise keep existing stdio path untouched. -- [ ] **adt-mcp #http-server** — new `src/lib/http/server.ts` that +- [x] **adt-mcp #http-server** — new `src/lib/http/server.ts` that boots `node:http`, wires `hostHeaderValidation` + CORS, registers `POST /mcp`, `GET /mcp`, `DELETE /mcp` and delegates to `SessionRegistry`. -- [ ] **adt-mcp #session-registry** — new `src/lib/http/session-registry.ts` +- [x] **adt-mcp #session-registry** — new `src/lib/http/session-registry.ts` owning the `Map`, TTL sweeper, and `closeSession()` cleanup routine (release locks, DELETE SAP session, `transport.close()`). -- [ ] **adt-mcp #session-ctx** — new `ToolContext` variant that, when a +- [x] **adt-mcp #session-ctx** — new `ToolContext` variant that, when a session has an `AdtClient`, returns it from `getClient()` instead of constructing a fresh one per call. Stdio path keeps today's behaviour for backward compat. -- [ ] **adt-mcp #sap-connect-tool** — new `src/lib/tools/sap-connect.ts` +- [x] **adt-mcp #sap-connect-tool** — new `src/lib/tools/sap-connect.ts` implementing `sap_connect`, `sap_disconnect`, `sap_list_systems`. Input validated by Zod with a `systemId XOR inline creds` refinement. -- [ ] **adt-config #systems** — typed loader for `~/.adt/systems.yaml` + env `SAP_SYSTEMS` override; credentials always resolved from +- [x] **adt-config #systems** — shipped as `src/lib/http/multi-system.ts` + in `adt-mcp` (not `adt-config`). Typed loader for `~/.adt/systems.yaml` + env `SAP_SYSTEMS` override; credentials always resolved from `SAP__USERNAME` / `SAP__PASSWORD` env vars, never from YAML. -- [ ] **adt-mcp #routing** — resolution helper that walks +- [x] **adt-mcp #routing** — resolution helper that walks `arg → header x-sap-system-id → env → first configured` and returns a `ResolvedSystem` record. -- [ ] **adt-mcp #mock-http** — extend `src/lib/mock/server.ts` so the +- [x] **adt-mcp #mock-http** — extend `src/lib/mock/server.ts` so the integration mock can be driven over HTTP (used by the new tests). -- [ ] **adt-mcp #http-tests** — `tests/http.integration.test.ts` covering +- [x] **adt-mcp #http-tests** — `tests/http.integration.test.ts` covering session init, reuse, `sap_connect`, tool call, DELETE, and idle-TTL expiry. ## Wave 2 — MCP-level auth -- [ ] **adt-mcp #auth-bearer** — `src/lib/http/auth.ts` middleware with +- [x] **adt-mcp #auth-bearer** — `src/lib/http/auth.ts` middleware with `timingSafeEqual` compare against `MCP_AUTH_TOKEN`. -- [ ] **adt-mcp #auth-forwarded** — optional reverse-proxy mode under +- [x] **adt-mcp #auth-forwarded** — optional reverse-proxy mode under `TRUST_FORWARDED_AUTH=1` that trusts `x-forwarded-user`. -- [ ] **adt-mcp #auth-tests** — tests for missing token, wrong token, +- [x] **adt-mcp #auth-tests** — tests for missing token, wrong token, right token, disabled auth, and forwarded-auth paths. -- [ ] **adt-mcp #host-cors-tests** — host-header and CORS allow-list +- [x] **adt-mcp #host-cors-tests** — host-header and CORS allow-list tests against a malicious `Host` header and a disallowed `Origin`. ## Wave 3 — Transactional changesets (CLI + MCP + parity) -- [ ] **adt-cli #changeset-service** — new `ChangesetService` in +- [x] **adt-cli #changeset-service** — new `ChangesetService` in `packages/adt-cli/src/lib/services/changeset/` with `begin`, `add`, `commit`, `rollback`; exported from `packages/adt-cli/src/index.ts`. -- [ ] **adt-cli #changeset-commands** — `adt changeset begin|add|commit|rollback` +- [x] **adt-cli #changeset-commands** — `adt changeset begin|add|commit|rollback` subcommands (thin wrappers, service pattern per `packages/adt-cli/AGENTS.md`). -- [ ] **adt-mcp #changeset-tools** — new +- [x] **adt-mcp #changeset-tools** — new `src/lib/tools/sap-changeset.ts` exposing `changeset_begin`, `changeset_add`, `changeset_commit`, `changeset_rollback`. Tools delegate to the CLI service — no business logic in the MCP package. -- [ ] **adt-mcp #changeset-registry** — session-scoped changeset state +- [x] **adt-mcp #changeset-registry** — session-scoped changeset state stored on `McpSession.changeset`; rejects nested begins. -- [ ] **parity #changeset** — `packages/adt-cli/tests/e2e/parity.changeset.test.ts` +- [x] **parity #changeset** — `packages/adt-cli/tests/e2e/parity.changeset.test.ts` drives both the CLI subcommand and the MCP tool through the same mock server and asserts equivalent results for every begin/add/commit/rollback scenario. ## Wave 4 — Okta / OIDC bearer (deferred / optional) -- [ ] **adt-mcp #oidc-middleware** — JWT verify via `jose`; issuer and +- [x] **adt-mcp #oidc-middleware** — JWT verify via `jose`; issuer and audience configured by `MCP_OIDC_ISSUER` / `MCP_OIDC_AUDIENCE`. JWKs fetched and cached per SDK `server/auth/*` helpers. -- [ ] **adt-mcp #oidc-tests** — tests with a disposable issuer +- [x] **adt-mcp #oidc-tests** — tests with a disposable issuer (e.g. static JWK + signed JWT fixtures) — no live Okta. -- [ ] **adt-mcp #oidc-docs** — README section on Okta / Azure AD / +- [x] **adt-mcp #oidc-docs** — README section on Okta / Azure AD / Cognito setup, including scopes and audiences. ## Wave 5 — Deployment artefacts + docs - [ ] **adt-mcp #dockerfile** — `packages/adt-mcp/Dockerfile.mcp`, distroless Node, `BUN_CONFIG_REGISTRY` build arg for JFrog. + Deferred: no container artefact shipped; deployment docs cover + process-level install in `docs/deployment/mcp-http.md`. - [ ] **adt-mcp #compose** — `packages/adt-mcp/docker-compose.yml` - with reverse-proxy + MCP service. -- [ ] **adt-mcp #readme** — rewrite README intro to cover HTTP + + with reverse-proxy + MCP service. Deferred with #dockerfile. +- [x] **adt-mcp #readme** — rewrite README intro to cover HTTP + stdio, auth model, multi-system routing, Docker deploy, changeset workflow. -- [ ] **root AGENTS** — update the _MCP ↔ CLI Coupling_ section to +- [x] **root AGENTS** — update the _MCP ↔ CLI Coupling_ section to call out HTTP transport and the changeset parity expectation. -- [ ] **adt-mcp AGENTS** — update invariant #4 per +- [x] **adt-mcp AGENTS** — update invariant #4 per `specs/adt-mcp/spec.md` and add a new "Session model" section. ## Wave 6 — Verification + PR (sequential, lead) -- [ ] `bunx nx run-many -t build,test,typecheck,lint -p adt-mcp,adt-cli,adt-config` -- [ ] `bunx nx format:write` -- [ ] Manual smoke test: `MCP_HTTP_PORT=3333 MCP_AUTH_TOKEN=dev bun packages/adt-mcp/src/bin/adt-mcp.ts` +- [x] `bunx nx run-many -t build,test,typecheck,lint -p adt-mcp,adt-cli,adt-config` +- [x] `bunx nx format:write` +- [x] Manual smoke test: `MCP_HTTP_PORT=3333 MCP_AUTH_TOKEN=dev bun packages/adt-mcp/src/bin/adt-mcp.ts` then drive with a Streamable-HTTP MCP client. -- [ ] Docker smoke test: `docker compose -f packages/adt-mcp/docker-compose.yml up --build`. -- [ ] Commit waves as separate commits; push feature branch; open PR +- [ ] Docker smoke test — deferred with #dockerfile/#compose. +- [x] Commit waves as separate commits; push feature branch; open PR cross-linking [#110](https://github.com/abapify/adt-cli/pull/110). diff --git a/openspec/changes/arc-1-feature-parity/.openspec.yaml b/openspec/changes/archive/2026-09-29-arc-1-feature-parity/.openspec.yaml similarity index 100% rename from openspec/changes/arc-1-feature-parity/.openspec.yaml rename to openspec/changes/archive/2026-09-29-arc-1-feature-parity/.openspec.yaml diff --git a/openspec/changes/arc-1-feature-parity/design.md b/openspec/changes/archive/2026-09-29-arc-1-feature-parity/design.md similarity index 100% rename from openspec/changes/arc-1-feature-parity/design.md rename to openspec/changes/archive/2026-09-29-arc-1-feature-parity/design.md diff --git a/openspec/changes/arc-1-feature-parity/proposal.md b/openspec/changes/archive/2026-09-29-arc-1-feature-parity/proposal.md similarity index 100% rename from openspec/changes/arc-1-feature-parity/proposal.md rename to openspec/changes/archive/2026-09-29-arc-1-feature-parity/proposal.md diff --git a/openspec/changes/arc-1-feature-parity/specs/abap-lint/spec.md b/openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/abap-lint/spec.md similarity index 100% rename from openspec/changes/arc-1-feature-parity/specs/abap-lint/spec.md rename to openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/abap-lint/spec.md diff --git a/openspec/changes/arc-1-feature-parity/specs/adt-cli/spec.md b/openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/adt-cli/spec.md similarity index 100% rename from openspec/changes/arc-1-feature-parity/specs/adt-cli/spec.md rename to openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/adt-cli/spec.md diff --git a/openspec/changes/arc-1-feature-parity/specs/code-completion/spec.md b/openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/code-completion/spec.md similarity index 100% rename from openspec/changes/arc-1-feature-parity/specs/code-completion/spec.md rename to openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/code-completion/spec.md diff --git a/openspec/changes/arc-1-feature-parity/specs/context-compression/spec.md b/openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/context-compression/spec.md similarity index 100% rename from openspec/changes/arc-1-feature-parity/specs/context-compression/spec.md rename to openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/context-compression/spec.md diff --git a/openspec/changes/arc-1-feature-parity/specs/method-surgery/spec.md b/openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/method-surgery/spec.md similarity index 100% rename from openspec/changes/arc-1-feature-parity/specs/method-surgery/spec.md rename to openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/method-surgery/spec.md diff --git a/openspec/changes/arc-1-feature-parity/specs/short-dumps/spec.md b/openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/short-dumps/spec.md similarity index 100% rename from openspec/changes/arc-1-feature-parity/specs/short-dumps/spec.md rename to openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/short-dumps/spec.md diff --git a/openspec/changes/arc-1-feature-parity/specs/traces/spec.md b/openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/traces/spec.md similarity index 100% rename from openspec/changes/arc-1-feature-parity/specs/traces/spec.md rename to openspec/changes/archive/2026-09-29-arc-1-feature-parity/specs/traces/spec.md diff --git a/openspec/changes/arc-1-feature-parity/tasks.md b/openspec/changes/archive/2026-09-29-arc-1-feature-parity/tasks.md similarity index 100% rename from openspec/changes/arc-1-feature-parity/tasks.md rename to openspec/changes/archive/2026-09-29-arc-1-feature-parity/tasks.md diff --git a/openspec/changes/classify-atc-as-read-analysis/design.md b/openspec/changes/archive/2026-09-29-classify-atc-as-read-analysis/design.md similarity index 100% rename from openspec/changes/classify-atc-as-read-analysis/design.md rename to openspec/changes/archive/2026-09-29-classify-atc-as-read-analysis/design.md diff --git a/openspec/changes/classify-atc-as-read-analysis/proposal.md b/openspec/changes/archive/2026-09-29-classify-atc-as-read-analysis/proposal.md similarity index 100% rename from openspec/changes/classify-atc-as-read-analysis/proposal.md rename to openspec/changes/archive/2026-09-29-classify-atc-as-read-analysis/proposal.md diff --git a/openspec/changes/classify-atc-as-read-analysis/specs/adt-mcp/spec.md b/openspec/changes/archive/2026-09-29-classify-atc-as-read-analysis/specs/adt-mcp/spec.md similarity index 100% rename from openspec/changes/classify-atc-as-read-analysis/specs/adt-mcp/spec.md rename to openspec/changes/archive/2026-09-29-classify-atc-as-read-analysis/specs/adt-mcp/spec.md diff --git a/openspec/changes/classify-atc-as-read-analysis/tasks.md b/openspec/changes/archive/2026-09-29-classify-atc-as-read-analysis/tasks.md similarity index 100% rename from openspec/changes/classify-atc-as-read-analysis/tasks.md rename to openspec/changes/archive/2026-09-29-classify-atc-as-read-analysis/tasks.md diff --git a/openspec/specs/abap-lint/spec.md b/openspec/specs/abap-lint/spec.md new file mode 100644 index 000000000..890494c7d --- /dev/null +++ b/openspec/specs/abap-lint/spec.md @@ -0,0 +1,87 @@ +# abap-lint Specification + +## Purpose + +Offline ABAP linting via `@abaplint/core` for the `adt lint` CLI command and `lint_abap` MCP tool — presets, rule inspection, auto-fixes, and an opt-in pre-write gate. + +## Requirements + +### Requirement: Lint ABAP source locally + +The system SHALL lint ABAP source code offline using `@abaplint/core` without requiring a live SAP connection, returning structured diagnostics (line, column, severity, message, rule name). + +#### Scenario: Lint clean source returns no issues + +- **WHEN** the user provides syntactically valid ABAP source with no rule violations +- **THEN** the tool returns an empty issues list and a success status + +#### Scenario: Lint source with violations returns diagnostics + +- **WHEN** the user provides ABAP source that violates abaplint rules (e.g. mixed-case keywords) +- **THEN** the tool returns one diagnostic entry per violation with line, column, severity (`error` | `warning` | `info`), message text, and rule name + +#### Scenario: Lint and fix returns corrected source + +- **WHEN** the user calls lint with `action: "lint_and_fix"` on source with auto-fixable issues +- **THEN** the tool returns the corrected source code and any remaining non-fixable issues + +#### Scenario: List rules returns rule catalog + +- **WHEN** the user calls lint with `action: "list_rules"` +- **THEN** the tool returns a list of all available abaplint rule names with their enabled/disabled status and current configuration + +### Requirement: Explicit preset selection + +The system SHALL let the caller select the rule preset explicitly — CLI `--preset ` flag or MCP `systemType` parameter — defaulting to `onpremise`. Automatic detection from the SAP system info endpoint is not implemented; callers targeting a BTP ABAP Environment must pass the `btp` preset explicitly. + +#### Scenario: BTP preset uses cloud rules + +- **WHEN** the caller passes `--preset btp` (or `systemType: "btp"`) +- **THEN** the linter enables `cloud_types` and `strict_sql` at Error severity + +#### Scenario: Default preset is on-premise + +- **WHEN** no preset is provided +- **THEN** the `cloud_types` rule is disabled (on-premise ruleset) + +### Requirement: Custom abaplint config override + +The system SHALL accept an optional path to a custom `abaplint.jsonc` configuration file (CLI `--config` flag) or an inline rule-override object (MCP tool parameter) that takes precedence over the selected preset. + +#### Scenario: Custom config overrides preset + +- **WHEN** the user provides a custom abaplint.jsonc config +- **THEN** that config's rules are applied instead of the selected preset + +### Requirement: Pre-write lint gate in update_source + +The system SHALL provide an opt-in lint gate for `update_source` / `adt source write`. When enabled (`lintBeforeWrite: true` / `--lint-before-write`), diagnostics with keys `parser_error`, `cloud_types`, or `strict_sql` SHALL block the write and report the blocking diagnostics without modifying SAP (`strict_sql` violations block only when the BTP preset enables the rule). + +#### Scenario: Gate blocks write on parser error + +- **WHEN** `lintBeforeWrite` is enabled and the source has ABAP parser errors +- **THEN** the write is rejected with `isError: true` and the blocking diagnostics are reported (formatted as `rule: message` text; a structured diagnostics payload is tracked as follow-up work) + +#### Scenario: Gate allows write when source is clean + +- **WHEN** `lintBeforeWrite` is enabled and the source passes lint +- **THEN** the write proceeds normally + +#### Scenario: Gate is disabled by default + +- **WHEN** no `lintBeforeWrite` flag is set +- **THEN** `update_source` writes without running lint + +### Requirement: CLI lint command + +The system SHALL expose `adt lint ` (or `adt lint --source `) that reads source from a file path or an inline `--source` argument and prints diagnostics to stdout in human-readable or `--json` format. Reading from standard input is not supported. + +#### Scenario: Lint a file and print diagnostics + +- **WHEN** the user runs `adt lint path/to/myclass.abap` +- **THEN** diagnostics are printed to stdout with file, line, column, severity, and message + +#### Scenario: Lint exits non-zero on errors + +- **WHEN** linting finds at least one error-severity issue +- **THEN** the CLI exits with a non-zero exit code diff --git a/openspec/specs/aclass/spec.md b/openspec/specs/aclass/spec.md new file mode 100644 index 000000000..9e8a66330 --- /dev/null +++ b/openspec/specs/aclass/spec.md @@ -0,0 +1,67 @@ +# aclass Specification + +## Purpose + +ABAP OO source parser producing a typed AST for `.clas.abap` / `.intf.abap` files — structural declarations only, with method bodies preserved as opaque source slices. + +## Requirements + +### Requirement: Structural ABAP OO parsing + +The parser SHALL recognise class and interface declarations — headers, +visibility sections, and member declarations (methods, attributes, types, +constants, events, aliases, interface statements) — including inheritance +and implements lists. Method implementation bodies SHALL be preserved as +opaque source slices with span information; statements inside a method +body SHALL NOT be parsed. + +#### Scenario: Class definition round-trips through the AST + +- **WHEN** a `.clas.abap` source containing sections and member + declarations is parsed +- **THEN** the AST contains a `ClassDef` with typed `Section` and + `ClassMember` nodes and each `MethodImpl` carries its raw body text + and span + +#### Scenario: Method body stays opaque + +- **WHEN** a `METHOD .` … `ENDMETHOD.` block is parsed +- **THEN** its statements are not interpreted and the raw slice is + preserved byte-for-byte + +### Requirement: Non-throwing parse contract + +`parse()` SHALL return `{ ast, errors }` and SHALL NOT throw on malformed +input. Unrecoverable errors SHALL yield a best-effort AST for the portion +understood before the break, with lex and parse diagnostics reported as +`ParseError` entries carrying severity, line, column, and message. + +#### Scenario: Malformed source returns diagnostics + +- **WHEN** source containing a syntax error is parsed +- **THEN** the result contains a non-empty `errors` array and a partial + AST, and no exception propagates + +### Requirement: No runtime dependency on `@abapify/abap-ast` + +The package SHALL NOT import `@abapify/abap-ast` in `src/**/*.ts`. +Shared shapes SHALL be re-declared locally; `abap-ast` is permitted only +as a devDependency for roundtrip tests. + +#### Scenario: Runtime boundary enforced + +- **WHEN** `packages/aclass/src/**/*.ts` is inspected +- **THEN** no import references `@abapify/abap-ast` + +### Requirement: Chevrotain-based lexer and statement parser + +Tokenisation SHALL use Chevrotain token definitions; no hand-rolled lexer +or regex-driven tokenizer is permitted. Keyword ordering SHALL place +compound keywords before their prefixes and `Identifier` last so keywords +win via `longer_alt`. + +#### Scenario: Compound keyword tokenises correctly + +- **WHEN** source contains `CLASS-DATA` +- **THEN** the lexer emits a single `ClassData` token rather than + splitting at the hyphen diff --git a/openspec/specs/adt-cli/spec.md b/openspec/specs/adt-cli/spec.md index 9555392c0..2ebc3b7e9 100644 --- a/openspec/specs/adt-cli/spec.md +++ b/openspec/specs/adt-cli/spec.md @@ -33,6 +33,38 @@ The ADT CLI SHALL communicate directly with SAP ADT REST APIs, eliminating middl - **WHEN** a CLI command is executed (e.g., `adt get`) - **THEN** it communicates directly with the SAP system's ADT REST API +### Requirement: adt lint subcommand + +The system SHALL expose `adt lint ` as a top-level Commander.js subcommand registered in the `adt` CLI command tree. The command SHALL also accept `--source ` to lint inline source without a file. + +#### Scenario: adt lint is reachable from CLI root + +- **WHEN** the user runs `adt lint --help` +- **THEN** usage information for the lint command is displayed + +### Requirement: adt context subcommand + +The system SHALL expose `adt context ` as a top-level Commander.js subcommand with `--type`, `--depth`, `--max-deps`, and `--json` options. + +#### Scenario: adt context is reachable from CLI root + +- **WHEN** the user runs `adt context --help` +- **THEN** usage information for the context command is displayed + +### Requirement: adt diagnose subcommand group + +The system SHALL expose `adt diagnose` as a top-level Commander.js subcommand group with child commands `dumps` and `traces`. + +#### Scenario: adt diagnose dumps is reachable + +- **WHEN** the user runs `adt diagnose dumps --help` +- **THEN** usage information for the dumps command is displayed + +#### Scenario: adt diagnose traces is reachable + +- **WHEN** the user runs `adt diagnose traces --help` +- **THEN** usage information for the traces command is displayed + ## Background ### Problem Statement with Existing Solutions diff --git a/openspec/specs/adt-flow-index-only/spec.md b/openspec/specs/adt-flow-index-only/spec.md new file mode 100644 index 000000000..72acdf30f --- /dev/null +++ b/openspec/specs/adt-flow-index-only/spec.md @@ -0,0 +1,52 @@ +# adt-flow-index-only Specification + +## Purpose + +Provide a durable, source-free record of transport inventory and unresolved +source boundaries without weakening exact source checkout guarantees. + +## Requirements + +### Requirement: Flow can index a transport without materializing source + +`adt-flow` SHALL provide an explicit index-only operation for a transport scope +that persists deterministic `.adt` transport and object descriptors without +reading source bodies or changing format-owned source paths. + +#### Scenario: Inexact source boundary is indexed + +- **GIVEN** a transport contains a relevant object whose source boundary is + inexact +- **WHEN** an index-only operation is requested for that transport +- **THEN** the transport inventory and an omitted-object descriptor retain the + object identity, component, source transport, and bounded diagnostic +- **THEN** no source body is read and no format-owned source path is changed + +#### Scenario: Exact source remains unmaterialized during indexing + +- **GIVEN** a transport contains an exact source component +- **WHEN** an index-only operation is requested +- **THEN** the transport inventory is persisted without selecting or writing + the component's source files + +#### Scenario: Normal checkout remains strict + +- **GIVEN** a transport contains a relevant object whose source boundary is + inexact +- **WHEN** normal checkout is requested without the index-only operation +- **THEN** checkout fails with its typed bounded diagnostic before any + repository path is changed + +### Requirement: Index-only flow is available through equivalent adapters + +The CLI and MCP flow adapters SHALL expose the same explicit index-only +operation and SHALL return equivalent structured results without source bodies +or credentials. + +#### Scenario: CLI and MCP index the same fixture + +- **GIVEN** the CLI and MCP receive the same flow configuration, transport + manifest, and repository tree +- **WHEN** each requests index-only flow for the transport +- **THEN** both return equivalent inventory, descriptor, and omission results +- **THEN** neither changes format-owned source paths diff --git a/openspec/specs/adt-mcp/spec.md b/openspec/specs/adt-mcp/spec.md new file mode 100644 index 000000000..98663ff47 --- /dev/null +++ b/openspec/specs/adt-mcp/spec.md @@ -0,0 +1,354 @@ +# adt-mcp Specification + +## Purpose + +MCP server bridging AI assistants to SAP ADT — tool registration, delegated/ambient authorization, scoped dispatch policies, Streamable HTTP transport, sessions, and changesets. + +## Requirements + +### Requirement: Delegated assistants receive a server-owned read catalogue + +The server SHALL accept an exact signed delegated-assistant policy bound to +one principal, thread, execution, System, and Destination. The resulting MCP +catalogue SHALL contain every registered tool whose server-owned operation +class is `server` or `read`. + +#### Scenario: Delegated assistant lists tools + +- **GIVEN** a valid delegated-assistant credential requests the read envelope +- **WHEN** the client calls `tools/list` +- **THEN** the server advertises multiple permitted read tools without a + client-provided tool-name allowlist + +#### Scenario: A new read tool is registered + +- **GIVEN** a new tool has a complete `read` catalogue classification +- **WHEN** a delegated assistant refreshes `tools/list` +- **THEN** the new tool is admitted without changing the client credential + contract + +### Requirement: Delegated read authority cannot widen + +The server SHALL reject malformed delegated-assistant policies and SHALL deny +`safe_execute`, `write`, unknown, and out-of-Destination operations at both +catalogue and dispatch. + +#### Scenario: Delegated assistant attempts a write + +- **WHEN** the client requests or directly calls a write-class tool +- **THEN** the tool is absent from discovery and dispatch returns + `mcp_scope_denied` before a Destination lease or SAP operation + +#### Scenario: Delegated policy carries additional authority + +- **WHEN** the signed claim adds a tool list, resource override, non-empty + limits, another operation class, or an additional Destination +- **THEN** the invocation exposes no MCP tools + +### Requirement: Code review checks remain bounded analysis + +The server SHALL classify `atc_run` and `run_unit_tests`, including coverage, +as `safe_execute` operations. An authenticated credential containing only +`server` and `read` authority SHALL NOT see or dispatch them; SAP analysis +execution requires an explicit execution grant. + +> Note: this change originally reclassified these checks as `read`. During +> PR #173 review the exposure of SAP analysis execution to ordinary read +> credentials was flagged as a security finding and deliberately reverted — +> the `safe_execute` classification is the intended end state. + +#### Scenario: Delegated read assistant lists tools + +- **GIVEN** a delegated assistant has only `read` authority for one + Destination +- **WHEN** it lists tools or directly calls `atc_run` / `run_unit_tests` +- **THEN** the checks are absent from the catalogue and dispatch is denied + before a Destination lease or SAP operation + +#### Scenario: Read authority remains non-mutating and non-executing + +- **WHEN** the same assistant lists or calls a mutation or an analysis + execution +- **THEN** the operation is absent or denied before SAP state is created + +### Requirement: Stricter scoped ATC remains supported + +The server SHALL accept an exact object-bound `safe_execute` credential for +`atc_run` or `run_unit_tests` when a workflow chooses that narrower +execution policy. + +#### Scenario: Workflow supplies an exact ATC grant + +- **WHEN** a valid scoped `safe_execute` credential names `atc_run` and exact + object keys +- **THEN** catalogue and dispatch enforce the existing scoped policy + +### Requirement: Bounded analysis is a separate operation class + +The server SHALL classify every MCP tool that creates diagnostic analysis +state as `safe_execute`, independently of its HTTP method and independently +of repository mutation authority. + +#### Scenario: Read credential requests ATC + +- **WHEN** a credential contains only the `read` class +- **THEN** `atc_run` is absent from the destination-mode tool list and a direct + call is denied before a destination lease or tool handler + +#### Scenario: Explicit bounded-analysis credential requests ATC + +- **WHEN** a trusted request access snapshot contains `safe_execute` +- **THEN** the scope catalogue permits `atc_run` subject to all other + destination and resource checks + +### Requirement: Unsupported signed policies fail closed + +The server SHALL not dispatch a signed invocation that includes +`safe_execute` until it can enforce every policy field required for that +operation. + +#### Scenario: Future-form safe-execution credential arrives early + +- **WHEN** a valid signed credential contains `safe_execute` but no supported + exact execution policy exists +- **THEN** the server exposes no MCP tools through that invocation + +### Requirement: Streamable HTTP transport + +The server SHALL expose a Streamable HTTP transport using the +`StreamableHTTPServerTransport` primitive from +`@modelcontextprotocol/sdk`, selected when `MCP_HTTP_PORT` is set or +`--http` is passed. The transport SHALL handle `POST /mcp`, `GET /mcp`, +and `DELETE /mcp` and SHALL assign session IDs via `randomUUID`. + +#### Scenario: Initialize returns a session ID + +- **GIVEN** a running HTTP MCP server +- **WHEN** a client sends a JSON-RPC `initialize` to `POST /mcp` +- **THEN** the response includes an `Mcp-Session-Id` header whose value + is a UUID. + +#### Scenario: Legacy SSE is not supported + +- **WHEN** a client opens an SSE stream against the server +- **THEN** the server responds with HTTP 404 or 405, and documentation + directs the client to Streamable HTTP. + +### Requirement: Two transports, two client state models + +Both transports SHALL share every tool handler, but SHALL differ in +`AdtClient` state: over **stdio** the server creates a fresh `AdtClient` +per tool call from the call arguments and no state persists across +calls; over **Streamable HTTP** each MCP session (`Mcp-Session-Id`) +owns a cached `AdtClient`, a lock registry, and an optional active +changeset, established via `sap_connect`. Stateful tools such as +`changeset_*` therefore require an HTTP session. + +#### Scenario: stdio calls are stateless + +- **GIVEN** the server runs on the stdio transport +- **WHEN** two consecutive tool calls arrive +- **THEN** each constructs its own `AdtClient` from its arguments and no + client state carries over between the calls. + +#### Scenario: HTTP session reuses the connected client + +- **GIVEN** an HTTP session that called `sap_connect` +- **WHEN** subsequent tool calls arrive on the same `Mcp-Session-Id` +- **THEN** they reuse the session's cached `AdtClient` and lock + registry. + +### Requirement: Session lifecycle cleanup + +When an HTTP MCP session ends (explicit `DELETE /mcp`, `sap_disconnect`, +or idle TTL expiry), the server SHALL release every lock held by the +session, SHALL `DELETE` the SAP security session, and SHALL close the +transport. Partial failures SHALL be logged but SHALL NOT abort the +remaining cleanup steps. + +#### Scenario: DELETE releases locks and SAP session + +- **GIVEN** a session that holds two object locks and an established SAP + security session +- **WHEN** the client sends `DELETE /mcp` with the matching + `Mcp-Session-Id` +- **THEN** both locks are released against SAP, the SAP security session + is deleted, the transport is closed, and the session record is removed + from the registry. + +#### Scenario: Idle TTL expiry + +- **GIVEN** `MCP_SESSION_IDLE_MS=60000` and a session with no activity + for 70 seconds +- **WHEN** the idle sweeper runs +- **THEN** the same cleanup routine is executed as for an explicit + `DELETE`. + +### Requirement: MCP-layer bearer authentication + +When `MCP_AUTH_TOKEN` is set, the HTTP transport SHALL reject any request +whose `Authorization: Bearer ` header does not match the +configured token. The comparison SHALL be constant-time +(`crypto.timingSafeEqual`). When `TRUST_FORWARDED_AUTH=1`, the bearer +check SHALL be skipped and the server SHALL instead require a non-empty +`x-forwarded-user` header. + +#### Scenario: Wrong bearer is rejected + +- **GIVEN** `MCP_AUTH_TOKEN=expected` and `TRUST_FORWARDED_AUTH` unset +- **WHEN** a request arrives with `Authorization: Bearer wrong` +- **THEN** the server responds with HTTP 401 and does not invoke the MCP + transport. + +#### Scenario: Reverse-proxy mode trusts forwarded user + +- **GIVEN** `TRUST_FORWARDED_AUTH=1` +- **WHEN** a request arrives without `Authorization` but with + `x-forwarded-user: alice` +- **THEN** the request is accepted and the user identity is available to + tool handlers for logging. + +> Deployment note: proxy mode trusts whatever client sets +> `x-forwarded-user`; the listener currently only warns on non-loopback +> binds. Enforcing the trusted-proxy boundary (loopback-only or +> allowlist enforcement) is tracked as follow-up work. + +### Requirement: Host header and CORS protection + +The HTTP transport SHALL validate the `Host` header against +`MCP_ALLOWED_HOSTS` (default: `localhost`, `127.0.0.1`) and SHALL apply +CORS headers based on `MCP_ALLOWED_ORIGINS`. + +#### Scenario: Disallowed Host header + +- **GIVEN** `MCP_ALLOWED_HOSTS=localhost` +- **WHEN** a request arrives with `Host: attacker.example.com` +- **THEN** the server responds with HTTP 403. + +### Requirement: SAP-session handshake tool `sap_connect` + +The server SHALL expose a `sap_connect` tool that establishes a SAP +security session for the current MCP session. Input SHALL be either +`{ systemId }` (resolving a server-configured system) or a full +`{ baseUrl, username, password, client, auth }` bundle — exactly one +branch. On success the server SHALL cache the `AdtClient` on the session. + +#### Scenario: Connect by systemId + +- **GIVEN** `systems.yaml` contains a `DEV` entry and the env vars + `SAP_DEV_USERNAME` / `SAP_DEV_PASSWORD` are set +- **WHEN** the client calls `sap_connect` with `{ systemId: "DEV" }` +- **THEN** the server resolves credentials from env, performs the SAP + handshake, caches the client on the session, and returns + `{ connected: true, systemId: "DEV" }`. + +#### Scenario: Connect by local adt auth store + +- **GIVEN** `~/.adt/sessions/DEV.json` exists on the server host +- **WHEN** the client calls `sap_connect` with `{ systemId: "DEV" }` and + the multi-system registry has no `DEV` entry +- **THEN** the server resolves credentials/session via the local adt auth + store bridge, performs the verification call, caches the client, and + returns `{ connected: true, systemId: "DEV", source: "adt-cli-auth-store" }`. + +#### Scenario: Connect with inline credentials + +- **WHEN** the client calls `sap_connect` with `baseUrl`, `username`, + `password`, `client` +- **THEN** the server performs the SAP handshake and caches the client, + without persisting credentials anywhere. + +#### Scenario: Ambiguous input is rejected + +- **WHEN** the client calls `sap_connect` with both `systemId` and + `baseUrl` +- **THEN** the tool returns an error naming the two conflicting + branches. + +### Requirement: Multi-system routing resolution order + +The server SHALL resolve the target SAP system using the first match +from: (1) `sap_connect` argument `systemId`, (2) HTTP header +`x-sap-system-id`, (3) env `SAP_DEFAULT_SYSTEM_ID`, (4) first entry in +the systems registry. Credentials SHALL be sourced at runtime (tool +arguments or env-backed system resolution) and SHALL NOT be read from the +YAML/JSON systems registry file. A local `~/.adt/sessions/.json` +bridge MAY be used as a fallback resolution path for developer workflows. + +#### Scenario: Tool argument wins over header + +- **GIVEN** a request with header `x-sap-system-id: TEST` +- **WHEN** the client also passes `systemId: "DEV"` to `sap_connect` +- **THEN** the resolved system is `DEV`. + +### Requirement: Transactional changesets + +The server SHALL expose `changeset_begin`, +`changeset_add`, `changeset_commit`, +`changeset_rollback` tools, all of which SHALL delegate to the +`ChangesetService` in `@abapify/adt-cli`. At most one changeset MAY be +open per MCP session. + +#### Scenario: Commit applies all operations + +- **GIVEN** an open changeset with two `update` operations queued +- **WHEN** the client calls `changeset_commit` +- **THEN** the service applies both updates, activates the affected + objects, releases every lock, and the session's changeset state + returns to idle. + +#### Scenario: Rollback releases locks without reverting applied source + +- **GIVEN** an open changeset whose `changeset_add` calls already PUT + source to SAP under lock +- **WHEN** the client calls `changeset_rollback` +- **THEN** no activation occurs, every lock is released, and the session's + changeset state returns to idle. The already-written source PUTs are + NOT reverted — SAP has no transactional discard over ADT; the inactive + version stays on the system until the next edit/activate cycle + (matching Eclipse ADT editor-close behaviour). + +#### Scenario: Nested begin without force is rejected + +- **GIVEN** a session with an already open changeset +- **WHEN** the client calls `changeset_begin` again without `force` +- **THEN** the tool returns an error without modifying the existing + changeset. + +#### Scenario: Forced begin rolls back and restarts + +- **GIVEN** a session with an already open changeset +- **WHEN** the client calls `changeset_begin` with `force: true` +- **THEN** the server rolls back the existing changeset (releasing its + locks, without reverting applied source) and opens a new changeset. + +### Requirement: CLI ↔ MCP parity for changesets + +Every changeset operation SHALL be available as both an +`adt changeset …` CLI subcommand and a `changeset_*` MCP tool +(`changeset_begin`, `changeset_add`, `changeset_commit`, +`changeset_rollback`), and +both SHALL exercise the same `ChangesetService`. A parity test at +`packages/adt-cli/tests/e2e/parity.changeset.test.ts` SHALL drive the +CLI and MCP paths through the same mock server and assert equivalent +results. + +#### Scenario: Parity test covers commit and rollback + +- **WHEN** the parity suite runs +- **THEN** it asserts that `adt changeset commit` and + `changeset_commit` produce the same object-state diffs against + the mock, and likewise for `rollback`. + +### Requirement: CLI ↔ MCP parity scope for transport lifecycle tools + +Global CLI/MCP parity SHALL apply to domain/business operations. Transport +lifecycle tools that are HTTP-session specific (`sap_connect`, +`sap_disconnect`) MAY exist only on MCP when no meaningful CLI equivalent +exists. + +#### Scenario: sap lifecycle tools are MCP-only + +- **WHEN** parity checks evaluate the command/tool matrix +- **THEN** `sap_connect` and `sap_disconnect` are treated as transport + lifecycle exceptions and do not require `adt` CLI subcommands. diff --git a/openspec/specs/code-completion/spec.md b/openspec/specs/code-completion/spec.md new file mode 100644 index 000000000..8c62e3793 --- /dev/null +++ b/openspec/specs/code-completion/spec.md @@ -0,0 +1,35 @@ +# code-completion Specification + +## Purpose + +ABAP code-completion proposals from the ADT code-assistance endpoint, exposed as the `get_completions` MCP tool. + +## Requirements + +### Requirement: Request code completion proposals at cursor position + +The system SHALL provide a `get_completions` MCP tool that requests ABAP code completion proposals at a given line/column cursor position from the ADT code-assistance endpoint (`/sap/bc/adt/codeassistance/completion`), returning the endpoint's response payload serialized as JSON. Response normalization (guaranteeing a `proposals` list with `insertText`/`kind` on every item) is not implemented and is tracked as follow-up work. + +#### Scenario: Completions returned for partial symbol + +- **WHEN** the user provides source code with cursor positioned after a partial symbol (e.g. `ZCL_OR`) and specifies `line` and `column` +- **THEN** the tool returns the backend completion response for that position + +#### Scenario: Backend response is passed through unchanged + +- **WHEN** the ADT endpoint returns a response — with or without a `proposals` list +- **THEN** the tool serializes that response as-is, without adding or normalizing fields + +#### Scenario: Tool returns error on BTP where endpoint unavailable + +- **WHEN** the SAP system is a BTP ABAP Environment and the completion endpoint returns 404 +- **THEN** the tool returns `isError: true` with a message indicating the endpoint is not available on this system + +### Requirement: Completion requires cursor-position parameters + +The tool SHALL require `objectName`, `objectType`, `line` (1-based), `column` (1-based), and optionally the `sourceCode` of the object as parameters. + +#### Scenario: Missing line or column returns validation error + +- **WHEN** the user calls `get_completions` without `line` or `column` +- **THEN** the tool returns a schema validation error before making any ADT call diff --git a/openspec/specs/context-compression/spec.md b/openspec/specs/context-compression/spec.md new file mode 100644 index 000000000..59ec930ec --- /dev/null +++ b/openspec/specs/context-compression/spec.md @@ -0,0 +1,80 @@ +# context-compression Specification + +## Purpose + +Compressed dependency context for ABAP objects — `get_context` / `adt context` strip dependencies to their public API surface for token-efficient prompting. + +## Requirements + +### Requirement: Extract public API contracts from ABAP dependencies + +The system SHALL provide a `get_context` MCP tool and `adt context` CLI command that, given an ABAP object, fetches its source and all detected custom dependencies, then strips each dependency to its **public API surface** only, returning a compact JSON payload. + +Public API surface definition: + +- **CLAS**: `CLASS DEFINITION … PUBLIC SECTION … ENDCLASS.` — `PROTECTED`, `PRIVATE` sections and `CLASS IMPLEMENTATION` block are removed. +- **INTF**: Full interface source (interfaces are inherently public). +- **FUNC**: Function module signature only (`IMPORTING`, `EXPORTING`, `CHANGING`, `EXCEPTIONS` lines; function body removed). +- **DDLS (CDS view)**: Full DDL source of the view. Resolving referenced data sources and associations into a CDS dependency graph is not implemented (ABAP-only dependency patterns are used) and is tracked as follow-up work. + +#### Scenario: Get context for a class returns stripped dependencies + +- **WHEN** the user calls `get_context` with `objectName: "ZCL_ORDER"` and `objectType: "CLAS"` +- **THEN** the response contains the public section of all detected Z/Y class and interface dependencies, not their implementations + +#### Scenario: Implementation blocks are stripped + +- **WHEN** a dependency class has a long `CLASS IMPLEMENTATION` block +- **THEN** that block is absent from the returned context payload + +#### Scenario: SAP standard objects are excluded + +- **WHEN** a class uses `CL_ABAP_*`, `IF_ABAP_*`, or `CX_SY_*` dependencies +- **THEN** those standard objects are NOT included in the context (only Z/Y custom objects are returned) + +#### Scenario: Stripping failure falls back to full source + +- **WHEN** the line-based scanner cannot identify the expected structure of a dependency (e.g. unusual macros or non-standard formatting) +- **THEN** the full unstripped source for that dependency is returned with a `fallback: true` flag + +### Requirement: Dependency detection via token scan + +The system SHALL detect dependencies by tokenizing the source (after stripping ABAP comments) and identifying references from patterns including: `TYPE REF TO`, `NEW`, `CAST`, `INHERITING FROM`, `INTERFACES`, `CALL FUNCTION`, `RAISING`, `CATCH`, and static calls (`=>`). + +#### Scenario: Class reference in method is detected + +- **WHEN** a method body contains `lo_dep = NEW zcl_dep( )` +- **THEN** `ZCL_DEP` is included in the dependency set + +#### Scenario: Interface in class definition is detected + +- **WHEN** a class definition lists `INTERFACES: zif_something` +- **THEN** `ZIF_SOMETHING` is included in the dependency set + +### Requirement: Configurable depth and max-deps limits + +The system SHALL accept `maxDeps` (default 20) and `depth` (1 = direct only, max 3) parameters to prevent unbounded recursion on large dependency graphs. + +#### Scenario: Depth 1 returns only direct dependencies + +- **WHEN** `get_context` is called with `depth: 1` +- **THEN** only the immediate dependencies of the target object are included (not dependencies of dependencies) + +#### Scenario: maxDeps cap is respected + +- **WHEN** more dependencies are detected than `maxDeps` +- **THEN** the response is truncated at `maxDeps` entries and includes a `truncated: true` flag + +### Requirement: CLI context command + +The system SHALL expose `adt context [--type ] [--depth ] [--max-deps ] [--json]` that prints the compressed context to stdout. + +#### Scenario: CLI context outputs compressed JSON with --json + +- **WHEN** the user runs `adt context ZCL_ORDER --type CLAS --json` +- **THEN** the compressed dependency contracts are printed as JSON to stdout + +#### Scenario: CLI context lists dependency names by default + +- **WHEN** the user runs `adt context ZCL_ORDER --type CLAS` without `--json` +- **THEN** the detected dependency names are printed to stdout diff --git a/openspec/specs/cts-transport-metadata/spec.md b/openspec/specs/cts-transport-metadata/spec.md new file mode 100644 index 000000000..2bc3baffb --- /dev/null +++ b/openspec/specs/cts-transport-metadata/spec.md @@ -0,0 +1,41 @@ +# cts-transport-metadata Specification + +## Purpose + +Typed CTS transport metadata for automation — request/task hierarchy, status, owner, and timestamps via `adt cts tr metadata --json` and the `cts_transport_metadata` MCP tool. + +## Requirements + +### Requirement: Typed transport metadata is available to automation + +The system SHALL expose a read-only typed projection of a CTS request or task +that includes each returned unit's number, status, type, parent, owner, +description, and SAP last-change timestamp when SAP provides it. + +#### Scenario: Request metadata includes child tasks + +- **WHEN** a caller requests metadata for a CTS request that contains tasks +- **THEN** the result identifies the requested request and includes its request + unit and child task units + +### Requirement: CLI JSON stdout is machine-readable + +On success, the `adt cts tr metadata --json` command SHALL write +exactly one JSON document to stdout and SHALL write diagnostics only to +stderr. On failure, the command writes diagnostics to stderr, exits non-zero, +and stdout is left empty. + +#### Scenario: Successful JSON invocation + +- **WHEN** the command successfully reads a transport with `--json` +- **THEN** stdout can be parsed directly as the typed metadata result + +### Requirement: MCP and CLI share metadata semantics + +The system SHALL expose an MCP `cts_transport_metadata` tool that uses the +same metadata service as the CLI command. + +#### Scenario: Equivalent read through MCP + +- **WHEN** CLI and MCP query the same transport +- **THEN** they return equivalent requested transport and CTS unit metadata diff --git a/openspec/specs/method-surgery/spec.md b/openspec/specs/method-surgery/spec.md new file mode 100644 index 000000000..a39ecadfe --- /dev/null +++ b/openspec/specs/method-surgery/spec.md @@ -0,0 +1,45 @@ +# method-surgery Specification + +## Purpose + +Targeted method-body replacement in ABAP classes via `update_source action:"editMethod"` / `adt source write --method` without requiring the caller to supply the full class source. + +## Requirements + +### Requirement: Edit a single method without supplying the full class source + +The system SHALL extend `update_source` with an `action: "editMethod"` variant and extend `adt source write` with `--method ` that replaces only the body of the named method in an existing ABAP class, so the caller supplies only the method body — not the full class source. The implementation fetches the current full source, splices the method, and writes the reconstructed full source back to SAP via the standard source/main PUT (a partial-payload write is tracked as follow-up work). + +#### Scenario: Method body is replaced correctly + +- **WHEN** the user provides `objectName: "ZCL_ORDER"`, `objectType: "CLAS"`, `action: "editMethod"`, `methodName: "PROCESS"`, and the new method body +- **THEN** the system fetches the full class source, splices in the new body between the existing `METHOD PROCESS.` and `ENDMETHOD.` lines, and writes the modified full source back to SAP + +#### Scenario: Non-existent method returns an error + +- **WHEN** the specified `methodName` does not exist in the class source +- **THEN** the tool returns `isError: true` with message "Method not found in " + +#### Scenario: Method surgery reuses standard lock/PUT/unlock flow + +- **WHEN** method surgery writes to SAP +- **THEN** it uses the existing `LockService.lock`, PUT `/source/main`, `LockService.unlock` sequence — no new lock protocol + +#### Scenario: Edit method with transport request + +- **WHEN** the user provides a `transport` parameter alongside `editMethod` +- **THEN** the transport number is passed through to the lock and PUT calls + +### Requirement: Method boundary detection via line scan + +The system SHALL locate the method boundary using a case-insensitive line scan for `METHOD .` and `ENDMETHOD.` tokens, stripping inline ABAP comments before matching. The implementation MUST handle inline comments after the period on both `METHOD` and `ENDMETHOD` lines. + +#### Scenario: Simple scan finds method boundaries + +- **WHEN** the class source contains `METHOD process.` followed later by `ENDMETHOD.` (optionally indented) +- **THEN** the system correctly identifies the start and end lines for splicing + +#### Scenario: Ambiguous scan returns null + +- **WHEN** the simple line scan finds multiple potential `METHOD ` occurrences (e.g. local test classes) +- **THEN** the system returns `null` to avoid corrupting the wrong method body diff --git a/openspec/specs/short-dumps/spec.md b/openspec/specs/short-dumps/spec.md new file mode 100644 index 000000000..69db30532 --- /dev/null +++ b/openspec/specs/short-dumps/spec.md @@ -0,0 +1,50 @@ +# short-dumps Specification + +## Purpose + +Retrieval of ABAP runtime short dumps via `get_short_dumps` / `adt diagnose dumps`. + +## Requirements + +### Requirement: Retrieve ABAP runtime short dumps + +The system SHALL provide a `get_short_dumps` MCP tool and `adt diagnose dumps` CLI command that retrieves recent ABAP runtime error short dumps from the SAP system via the ADT dumps endpoint (`/sap/bc/adt/runtime/dumps`). + +#### Scenario: List recent short dumps + +- **WHEN** the user calls `get_short_dumps` with no filters +- **THEN** the tool returns a list of short dumps ordered by timestamp descending, each containing at minimum: dump ID, error type, program name, user, timestamp + +#### Scenario: Filter dumps by user + +- **WHEN** the user provides a `user` parameter +- **THEN** only dumps owned by that SAP user are returned + +#### Scenario: Limit results with maxResults + +- **WHEN** the user provides `maxResults: 10` +- **THEN** at most 10 dump entries are returned + +#### Scenario: Get dump detail by ID + +- **WHEN** the user provides a specific `id` parameter +- **THEN** the full dump text is returned for that dump ID + +#### Scenario: Endpoint unavailable on BTP returns informative error + +- **WHEN** the SAP system is a BTP ABAP Environment and the dumps endpoint is unavailable +- **THEN** the tool returns `isError: true` with a message explaining the BTP limitation + +### Requirement: CLI diagnose dumps command + +The system SHALL expose `adt diagnose dumps [--user ] [--max ] [--id ] [--json]` that lists or shows short dumps. + +#### Scenario: CLI dumps command outputs list + +- **WHEN** the user runs `adt diagnose dumps` +- **THEN** a table of recent dumps is printed to stdout + +#### Scenario: CLI dumps --id shows full text + +- **WHEN** the user runs `adt diagnose dumps --id ` +- **THEN** the full dump analysis text is printed to stdout diff --git a/openspec/specs/traces/spec.md b/openspec/specs/traces/spec.md new file mode 100644 index 000000000..84ad27fe1 --- /dev/null +++ b/openspec/specs/traces/spec.md @@ -0,0 +1,40 @@ +# traces Specification + +## Purpose + +Retrieval and management of SAT/ABAP runtime traces via `get_traces` / `adt diagnose traces`. + +## Requirements + +### Requirement: Retrieve ABAP performance traces + +The system SHALL provide a `get_traces` MCP tool and `adt diagnose traces` CLI command that retrieves ABAP performance trace data from the SAP system via the ADT traces endpoint (`/sap/bc/adt/runtime/traces`). + +#### Scenario: List available traces + +- **WHEN** the user calls `get_traces` with `action: "list"` +- **THEN** the tool returns available trace records with ID, user, program, creation timestamp + +#### Scenario: Get trace hitlist + +- **WHEN** the user calls `get_traces` with `action: "hitlist"` and a specific trace `id` +- **THEN** the tool returns the hitlist (hot-spot statements ranked by gross time) + +#### Scenario: Get trace DB accesses + +- **WHEN** the user calls `get_traces` with `action: "dbAccesses"` and a specific trace `id` +- **THEN** the tool returns the database access statistics for that trace + +#### Scenario: Endpoint unavailable on BTP returns informative error + +- **WHEN** the SAP system is a BTP ABAP Environment and the traces endpoint is unavailable +- **THEN** the tool returns `isError: true` with a message explaining the BTP limitation + +### Requirement: CLI diagnose traces command + +The system SHALL expose `adt diagnose traces [list|hitlist|db] [--id ] [--json]` that retrieves trace data from SAP. + +#### Scenario: CLI traces list outputs available traces + +- **WHEN** the user runs `adt diagnose traces list` +- **THEN** a list of available traces is printed to stdout diff --git a/packages/aclass/tests/parse-interface.test.ts b/packages/aclass/tests/parse-interface.test.ts index 4bbeea9e7..c79e898dc 100644 --- a/packages/aclass/tests/parse-interface.test.ts +++ b/packages/aclass/tests/parse-interface.test.ts @@ -349,6 +349,31 @@ describe('parse — MethodImpl.bodySpan', () => { expect(m.bodySpan.startLine).toBe(3); // body content line }); + it('body is the exact source slice — whitespace, comments, casing preserved byte-for-byte', () => { + const src = [ + 'CLASS zcl_x IMPLEMENTATION.', + ' METHOD foo.', + ' DATA lv_x TYPE i. " trailing comment kept', + ' lv_x = lv_x + 1 .', + ' " a full-line comment', + ' WRITE lv_x.', + ' ENDMETHOD.', + 'ENDCLASS.', + ].join('\n'); + const { ast, errors } = parse(src); + expect(errors).toEqual([]); + const impl = ast.definitions.find((d) => d.kind === 'ClassImpl'); + if (impl?.kind !== 'ClassImpl') throw new Error('expected ClassImpl'); + const m = impl.methods[0]; + const expected = src.slice( + m.bodySpan.startOffset, + m.bodySpan.endOffset + 1, + ); + expect(m.body).toBe(expected); + expect(m.body).toContain('" trailing comment kept'); + expect(m.body).toContain('lv_x = lv_x + 1 .'); + }); + it('empty method body still produces a valid bodySpan', () => { const src = [ 'CLASS zcl_x IMPLEMENTATION.',