@dozimple/abap-adt 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -0
- package/README.es.md +10 -7
- package/README.md +10 -7
- package/dist/core/catalog.en.js +3 -0
- package/dist/core/catalog.js +3 -0
- package/dist/core/concurrency.js +13 -0
- package/dist/core/packages.js +26 -0
- package/dist/core/transport.js +7 -0
- package/dist/tools/core/dumps.js +133 -10
- package/dist/tools/core/enhancements.js +135 -0
- package/dist/tools/core/package_contents.js +3 -14
- package/dist/tools/core/sap_notes.js +148 -0
- package/dist/tools/core/source_search.js +188 -0
- package/dist/tools/core/transport_diff.js +1 -12
- package/docs/TOOLS.md +112 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,42 @@ Formato basado en [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/). V
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.2.0] - 2026-09-23
|
|
8
|
+
|
|
9
|
+
Cuatro tools para diagnóstico y soporte: dumps agrupados por periodo, búsqueda de texto en el código, estado de notas SAP y ampliaciones/BAdI. Todas verificadas en vivo en un NW 7.50.
|
|
10
|
+
|
|
11
|
+
### Añadido
|
|
12
|
+
- **`enhancements`: ampliaciones y BAdI.** Tres preguntas en una tool, por SQL de diccionario (7.50 y S/4):
|
|
13
|
+
implementaciones de una BAdI, nuevas y clásicas, activas o no, con las propias (Z/Y) primero; ampliaciones de código
|
|
14
|
+
implementadas dentro de un programa, clase o grupo de funciones, avisando de las que **sustituyen** código estándar
|
|
15
|
+
(overwrite); y todas las implementaciones de un espacio de nombres por tipo, con el total real. Lo que cambia el
|
|
16
|
+
comportamiento de un estándar sin tocar su código, que es lo primero a mirar en un incidente. Verificado en vivo en
|
|
17
|
+
un 7.50: 1.070 implementaciones Z (566 de código, 501 de BAdI).
|
|
18
|
+
- **`sap_notes`: estado de notas SAP en el sistema, como en SNOTE**, sin portal ni S-user: si la nota está descargada,
|
|
19
|
+
estado de implementación y de tratamiento, versión, componente y título (español si existe). Por notas concretas
|
|
20
|
+
(«¿está la 2198647 en PRD?») o filtrando por estado y/o prefijo de componente, con el total real. Distingue «no
|
|
21
|
+
descargada» de «no implementada». Los códigos de estado no son valores fijos del diccionario: su significado sale de
|
|
22
|
+
las constantes de SAP (`IF_SCWN_NA_CONSTANTS`), verificadas en un 7.50. En sistemas con datos productivos no se
|
|
23
|
+
muestra quién trató la nota. Sustituto seguro del MCP de notas de SAP, que usa APIs privadas y el S-user.
|
|
24
|
+
- **`source_search`: buscar texto en el código de un paquete, una orden o una lista de objetos.** Texto literal o
|
|
25
|
+
expresión regular (máx. 200 caracteres), sin distinguir mayúsculas; devuelve objeto, include, línea y la línea
|
|
26
|
+
encontrada, con salida estructurada. Lee cada fuente (programas, clases con sus includes locales, interfaces,
|
|
27
|
+
módulos e includes propios de los grupos de funciones, CDS) con 4 lecturas en paralelo, progreso y cancelación;
|
|
28
|
+
exige un alcance, tiene tope de objetos (150 por defecto, ~0,8 s por objeto medido en 7.50) y lista aparte los
|
|
29
|
+
objetos que no pudo leer. Existe porque ADT no tiene búsqueda de texto en NW 7.50 (`textsearch` da 404 y no está
|
|
30
|
+
en el discovery), y llega donde `where_used` no: verificado en vivo, `where_used` no encontró ningún uso de una
|
|
31
|
+
clase de excepción y `source_search` encontró 49 en 6 objetos del mismo paquete.
|
|
32
|
+
- **`dumps(group_by, days)`: los errores más frecuentes de un periodo.** Cuenta los dumps de los últimos N días (hasta
|
|
33
|
+
90) agrupados por error, programa, error+programa, usuario o día, con primer y último caso, usuarios distintos
|
|
34
|
+
afectados y dónde terminó el más reciente (include y línea); salida estructurada. Los grupos salen de la cabecera
|
|
35
|
+
de ST22 (`SNAP_BEG.FLIST`, solo metadatos: `SNAP`, que guarda valores de variables, sigue vetada), porque el feed
|
|
36
|
+
ADT solo trae los dumps más recientes: en un 7.50 real devolvió 5 con miles en ST22, y su parámetro `from` no
|
|
37
|
+
amplía la ventana. En sistemas con datos productivos no se agrupa por usuario (columna personal); el número de
|
|
38
|
+
usuarios afectados sí se da. Cierra el hueco anotado el 11-09.
|
|
39
|
+
|
|
40
|
+
### Corregido
|
|
41
|
+
- `package_contents` listaba objetos **borrados** que TADIR conserva con `DELFLAG = 'X'`; ahora no aparecen.
|
|
42
|
+
|
|
7
43
|
## [1.1.0] - 2026-09-22
|
|
8
44
|
|
|
9
45
|
Tool nueva y contrato MCP ampliado (salida estructurada, progreso, cancelación) más resiliencia de conexión: el sprint 1 y parte del 2 del plan de mejoras.
|
package/README.es.md
CHANGED
|
@@ -56,8 +56,8 @@ El agente elige las tools, las encadena y responde con evidencia. Cada respuesta
|
|
|
56
56
|
| Grupo | Para qué | Tools |
|
|
57
57
|
|---|---|---|
|
|
58
58
|
| [Revisión de código y pases](#g-revision) | Saber qué cambia de verdad una orden y qué puede romper, antes de liberarla. | 5 |
|
|
59
|
-
| [Calidad, ATC y remediación](#g-calidad) | Encontrar, entender y corregir hallazgos con la sintaxis y las correcciones reales de SAP. |
|
|
60
|
-
| [Exploración del repositorio](#g-exploracion) | Leer y entender cualquier objeto ABAP y sus relaciones, en ECC y en S/4HANA. |
|
|
59
|
+
| [Calidad, ATC y remediación](#g-calidad) | Encontrar, entender y corregir hallazgos con la sintaxis y las correcciones reales de SAP. | 6 |
|
|
60
|
+
| [Exploración del repositorio](#g-exploracion) | Leer y entender cualquier objeto ABAP y sus relaciones, en ECC y en S/4HANA. | 11 |
|
|
61
61
|
| [Consulta de datos](#g-datos) | Preguntar a las tablas con ABAP SQL, de solo lectura y sin tocar material de credenciales. | 2 |
|
|
62
62
|
| [Diagnóstico de incidentes](#g-diagnostico) | Reunir en una conversación lo que antes exigía ST22, SM37, SLG1 y /IWFND/ERROR_LOG. | 4 |
|
|
63
63
|
| [Documentación SAP](#g-documentacion) | Responder con la documentación oficial y comprobar qué sintaxis existe en cada release. | 7 |
|
|
@@ -122,6 +122,7 @@ la **[referencia completa](docs/TOOLS.md)**.
|
|
|
122
122
|
| [`run_atc`](docs/TOOLS.md#calidad) | **Ejecutar ATC.** Ejecuta el ATC sobre un objeto o una orden de transporte y lista los hallazgos numerados (prioridad, línea, check, mensaje), con los totales P1/P2/P3 que da SAP. | lectura |
|
|
123
123
|
| [`atc_quickfix`](docs/TOOLS.md#calidad) | **Correcciones propuestas por SAP.** Correcciones que SAP ofrece (las mismas de Ctrl+1 en Eclipse) para un hallazgo ATC o una línea: crear símbolo de texto, extraer constante, etc. | lectura |
|
|
124
124
|
| [`api_release_state`](docs/TOOLS.md#calidad) | **¿Está liberada esta API? ¿Cuál es su sucesor?** Estado de liberación de un objeto SAP (clase, FM/BAPI, tabla, CDS…) por contrato C0–C4 y su sucesor liberado, leído del propio sistema. | lectura |
|
|
125
|
+
| [`sap_notes`](docs/TOOLS.md#calidad) | **Notas SAP en el sistema (SNOTE).** Estado de notas SAP en este sistema, como en SNOTE: si está descargada, estado de implementación (completa, incompleta, versión anterior, se puede implementar, obsoleta, no se puede implementar), estado de tratamiento, versión, componente y título. | lectura |
|
|
125
126
|
| [`syntax_check`](docs/TOOLS.md#calidad) | **Chequeo de sintaxis SAP.** Chequeo de sintaxis real de SAP (no abaplint). | lectura |
|
|
126
127
|
| [`run_unit_tests`](docs/TOOLS.md#calidad) | **Ejecutar ABAP Unit.** Ejecuta los tests ABAP Unit de una clase o programa y devuelve el resultado por método, con el detalle de cada fallo. | ejecuta (DEV) |
|
|
127
128
|
|
|
@@ -135,12 +136,14 @@ la **[referencia completa](docs/TOOLS.md)**.
|
|
|
135
136
|
| [`search_objects`](docs/TOOLS.md#exploracion) | **Buscar objetos ABAP.** Busca objetos del repositorio por nombre (admite * como comodín). | lectura |
|
|
136
137
|
| [`get_source`](docs/TOOLS.md#exploracion) | **Leer fuente ABAP.** Lee la fuente de cualquier objeto: programa, include, clase (y sus includes), interfaz, módulo de función (sin necesidad de saber el grupo), CDS, tabla/estructura, etc. | lectura |
|
|
137
138
|
| [`where_used`](docs/TOOLS.md#exploracion) | **Dónde se usa.** Lista de uso (where-used) de un objeto: quién lo referencia, con paquete y responsable. | lectura |
|
|
139
|
+
| [`source_search`](docs/TOOLS.md#exploracion) | **Buscar texto en el código.** Busca un texto (o una expresión regular) en el código fuente de un paquete (con subpaquetes), de una orden de transporte o de una lista de objetos, y devuelve objeto, include, línea y la línea encontrada. | lectura |
|
|
138
140
|
| [`object_versions`](docs/TOOLS.md#exploracion) | **Versiones de un objeto.** Historial de versiones de un objeto (fecha, autor, orden). | lectura |
|
|
139
141
|
| [`package_contents`](docs/TOOLS.md#exploracion) | **Contenido de un paquete.** Objetos de un paquete de desarrollo agrupados por tipo, con sus subpaquetes (TADIR/TDEVC, cualquier release). | lectura |
|
|
140
142
|
| [`ddic_type_info`](docs/TOOLS.md#exploracion) | **Elemento de datos, dominio o tipo tabla.** Definición de un tipo DDIC: elemento de datos (dominio, tipo, longitud, textos), dominio (tipo, longitud, valores fijos, tabla de valores) o tipo tabla (tipo de línea, clave). | lectura |
|
|
141
143
|
| [`transaction_info`](docs/TOOLS.md#exploracion) | **Qué ejecuta una transacción.** Programa, dynpro y parámetros de una transacción (TSTC/TSTCP), con su texto. | lectura |
|
|
142
144
|
| [`function_modules`](docs/TOOLS.md#exploracion) | **Módulos de función de un grupo.** Lista los módulos de función de un grupo de funciones, con su texto y si son RFC o de actualización. | lectura |
|
|
143
145
|
| [`text_elements`](docs/TOOLS.md#exploracion) | **Símbolos de texto y textos de selección.** Lee los símbolos de texto (TEXT-001…), textos de selección o encabezados de un programa, clase o grupo de funciones. | lectura |
|
|
146
|
+
| [`enhancements`](docs/TOOLS.md#exploracion) | **Ampliaciones y BAdI.** Qué ampliaciones hay y dónde. badi=NOMBRE: implementaciones de esa BAdI, nuevas y clásicas, activas o no. program + program_type (PROG, CLAS, FUGR): ampliaciones de código (puntos y secciones implícitos o explícitos) implementadas dentro de ese programa, clase o grupo — lo que cambia el comportamiento de un estándar sin tocar su código. prefix (p. ej. Z): implementaciones de ampliación de ese espacio de nombres, por tipo. | lectura |
|
|
144
147
|
|
|
145
148
|
<a id="g-datos"></a>
|
|
146
149
|
### Consulta de datos
|
|
@@ -292,7 +295,7 @@ Modelo de amenazas con STRIDE y OWASP Top 10 para aplicaciones LLM: **[docs/THRE
|
|
|
292
295
|
Con la versión exacta y sin scripts de instalación, igual que las dependencias del propio proyecto:
|
|
293
296
|
|
|
294
297
|
```sh
|
|
295
|
-
npm install -g --ignore-scripts @dozimple/abap-adt@1.
|
|
298
|
+
npm install -g --ignore-scripts @dozimple/abap-adt@1.2.0
|
|
296
299
|
PKG="$(npm root -g)/@dozimple/abap-adt"
|
|
297
300
|
mkdir -p ~/.config/abap-adt-dozimple && chmod 700 ~/.config/abap-adt-dozimple
|
|
298
301
|
cp "$PKG/config/systems.example.json" ~/.config/abap-adt-dozimple/systems.json # sistemas, roles y permisos
|
|
@@ -308,9 +311,9 @@ Registro en el cliente MCP:
|
|
|
308
311
|
```
|
|
309
312
|
|
|
310
313
|
Cada versión se publica desde el CI con [procedencia de npm](https://docs.npmjs.com/generating-provenance-statements):
|
|
311
|
-
`npm view @dozimple/abap-adt@1.
|
|
314
|
+
`npm view @dozimple/abap-adt@1.2.0 dist.attestations` muestra la atestación, y la release de GitHub incluye el
|
|
312
315
|
paquete, su bundle de Sigstore (`.sigstore.json`), el mismo bundle como procedencia in-toto (`.intoto.jsonl`)
|
|
313
|
-
y el SBOM. Para comprobarlo: `gh attestation verify dozimple-abap-adt-1.
|
|
316
|
+
y el SBOM. Para comprobarlo: `gh attestation verify dozimple-abap-adt-1.2.0.tgz --repo <owner>/abap-adt-dozimple`.
|
|
314
317
|
|
|
315
318
|
### Desde el código fuente
|
|
316
319
|
|
|
@@ -344,7 +347,7 @@ abap-adt-doZimple se construye sobre el trabajo de otros, y lo reconoce: cada to
|
|
|
344
347
|
<!-- credits:start -->
|
|
345
348
|
| Proyecto | Autor / titular | Licencia | Tipo | Usado en |
|
|
346
349
|
|---|---|---|---|---|
|
|
347
|
-
| [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) | Marcello Urbani | MIT | dependencia | todas (núcleo), `transport_diff`, `transport_contents`, `inactive_objects`, `edit_preflight`, `run_atc` y
|
|
350
|
+
| [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) | Marcello Urbani | MIT | dependencia | todas (núcleo), `transport_diff`, `transport_contents`, `inactive_objects`, `edit_preflight`, `run_atc` y 26 más |
|
|
348
351
|
| [Model Context Protocol TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) | Model Context Protocol | MIT | dependencia | todas (núcleo) |
|
|
349
352
|
| [mcp-sap-docs](https://github.com/marianfoo/mcp-sap-docs) | Marian Zeis (marianfoo) | Apache-2.0 | dependencia | `abap_feature_matrix`, `docs_search`, `docs_fetch`, `clean_core_objects`, `clean_core_object`, `abap_lint` y 1 más |
|
|
350
353
|
| [abaplint](https://github.com/abaplint/abaplint) | Lars Hvam y contribuidores | MIT | dependencia | `abap_lint` |
|
|
@@ -359,7 +362,7 @@ abap-adt-doZimple se construye sobre el trabajo de otros, y lo reconoce: cada to
|
|
|
359
362
|
| [ABAP Remote FS (vscode_abap_remote_fs)](https://github.com/marcellourbani/vscode_abap_remote_fs) | Marcello Urbani | MIT | idea | `syntax_check` |
|
|
360
363
|
| [mcp-abap-adt](https://github.com/mario-andreschak/mcp-abap-adt) | mario-andreschak | MIT | idea | `search_objects`, `get_source`, `package_contents`, `ddic_type_info`, `transaction_info`, `table_contents` |
|
|
361
364
|
| [ARC-1](https://github.com/arc-mcp/arc-1) | arc-mcp (Marian Zeis y contribuidores) | MIT | idea | `transport_diff`, `atc_quickfix`, `gateway_errors` |
|
|
362
|
-
| [vibing-steampunk](https://github.com/oisee/vibing-steampunk) | oisee y contribuidores | MIT | idea | `co_change`, `api_release_state`, `jobs`, `application_log` |
|
|
365
|
+
| [vibing-steampunk](https://github.com/oisee/vibing-steampunk) | oisee y contribuidores | MIT | idea | `co_change`, `api_release_state`, `source_search`, `enhancements`, `jobs`, `application_log` |
|
|
363
366
|
| [ABAP Accelerator for Amazon Q Developer](https://github.com/aws-solutions-library-samples/guidance-for-deploying-sap-abap-accelerator-for-amazon-q-developer) | AWS Solutions Library Samples | MIT-0 | idea | todas (núcleo), `usage_stats` |
|
|
364
367
|
| [An O(ND) Difference Algorithm and Its Variations (1986)](https://doi.org/10.1007/BF01840446) | Eugene W. Myers | algoritmo publicado | algoritmo | `transport_diff`, `atc_quickfix` |
|
|
365
368
|
<!-- credits:end -->
|
package/README.md
CHANGED
|
@@ -57,8 +57,8 @@ and **a failure is never presented as an empty result or as success**.
|
|
|
57
57
|
| Group | What for | Tools |
|
|
58
58
|
|---|---|---|
|
|
59
59
|
| [Code review and transports](#g-revision) | Know what a transport really changes and what it may break, before releasing it. | 5 |
|
|
60
|
-
| [Quality, ATC and remediation](#g-calidad) | Find, understand and fix findings with SAP's real syntax check and quick fixes. |
|
|
61
|
-
| [Repository exploration](#g-exploracion) | Read and understand any ABAP object and its relations, on ECC and S/4HANA. |
|
|
60
|
+
| [Quality, ATC and remediation](#g-calidad) | Find, understand and fix findings with SAP's real syntax check and quick fixes. | 6 |
|
|
61
|
+
| [Repository exploration](#g-exploracion) | Read and understand any ABAP object and its relations, on ECC and S/4HANA. | 11 |
|
|
62
62
|
| [Data queries](#g-datos) | Query tables with ABAP SQL, read-only, with sensitive and personal data protected. | 2 |
|
|
63
63
|
| [Incident diagnosis](#g-diagnostico) | One conversation for what used to take ST22, SM37, SLG1 and /IWFND/ERROR_LOG. | 4 |
|
|
64
64
|
| [SAP documentation](#g-documentacion) | Answer from official documentation and check which syntax exists in each release. | 7 |
|
|
@@ -123,6 +123,7 @@ Summary per group; each tool's details — parameters, types, defaults, requirem
|
|
|
123
123
|
| [`run_atc`](docs/TOOLS.md#calidad) | **Run ATC.** Runs the ABAP Test Cockpit on an object or a transport and lists numbered findings (priority, line, check, message) with SAP's P1/P2/P3 totals. | read |
|
|
124
124
|
| [`atc_quickfix`](docs/TOOLS.md#calidad) | **SAP-proposed fixes.** The fixes SAP offers (the same as Ctrl+1 in Eclipse) for an ATC finding or a line: create text symbol, extract constant, etc. | read |
|
|
125
125
|
| [`api_release_state`](docs/TOOLS.md#calidad) | **Is this API released? What is its successor?** Release state of an SAP object (class, function module/BAPI, table, CDS…) by contract C0–C4 and its released successor, read from the system itself. | read |
|
|
126
|
+
| [`sap_notes`](docs/TOOLS.md#calidad) | **SAP notes in the system (SNOTE).** Whether a note is downloaded in this system, its implementation and processing status, version, component and title, per note or filtered by status/component; no SAP portal or S-user needed. | read |
|
|
126
127
|
| [`syntax_check`](docs/TOOLS.md#calidad) | **SAP syntax check.** SAP's real syntax check (not abaplint), also on code not saved yet. | read |
|
|
127
128
|
| [`run_unit_tests`](docs/TOOLS.md#calidad) | **Run ABAP Unit.** Runs the ABAP Unit tests of a class or program (harmless and short only) and returns the result per method, with each failure in detail. | executes (DEV) |
|
|
128
129
|
|
|
@@ -136,12 +137,14 @@ Summary per group; each tool's details — parameters, types, defaults, requirem
|
|
|
136
137
|
| [`search_objects`](docs/TOOLS.md#exploracion) | **Search ABAP objects.** Searches repository objects by name (supports * wildcards). | read |
|
|
137
138
|
| [`get_source`](docs/TOOLS.md#exploracion) | **Read ABAP source.** Reads the source of any object: program, include, class (and its includes), interface, function module (without knowing its group), CDS, table/structure, etc. | read |
|
|
138
139
|
| [`where_used`](docs/TOOLS.md#exploracion) | **Where used.** Where-used list of an object: who references it, with package and owner. | read |
|
|
140
|
+
| [`source_search`](docs/TOOLS.md#exploracion) | **Search text in source code.** Searches a text or regex in the source of a package (with subpackages), a transport or a list of objects, and returns object, include, line and the matching line; reaches what where-used misses (dynamic calls, literals, Z code outside the reference index). | read |
|
|
139
141
|
| [`object_versions`](docs/TOOLS.md#exploracion) | **Object versions.** Version history of an object (date, author, transport). | read |
|
|
140
142
|
| [`package_contents`](docs/TOOLS.md#exploracion) | **Package contents.** Objects of a development package grouped by type, with subpackages (TADIR/TDEVC, any release). | read |
|
|
141
143
|
| [`ddic_type_info`](docs/TOOLS.md#exploracion) | **Data element, domain or table type.** Definition of a DDIC type: data element (domain, type, length, texts), domain (type, length, fixed values, value table) or table type (line type, key). | read |
|
|
142
144
|
| [`transaction_info`](docs/TOOLS.md#exploracion) | **What a transaction runs.** Program, screen and parameters of a transaction (TSTC/TSTCP), with its text. | read |
|
|
143
145
|
| [`function_modules`](docs/TOOLS.md#exploracion) | **Function modules of a group.** Lists the function modules of a function group with their text and whether they are RFC or update modules; given a module, finds its group and siblings. Works with /XXX/ namespaces. | read |
|
|
144
146
|
| [`text_elements`](docs/TOOLS.md#exploracion) | **Text symbols and selection texts.** Reads the text symbols (TEXT-001…), selection texts or headings of a program, class or function group. | read |
|
|
147
|
+
| [`enhancements`](docs/TOOLS.md#exploracion) | **Enhancements and BAdIs.** Implementations of a BAdI (new and classic, active or not), source code enhancements implemented inside a program, class or function group (flagging those that overwrite standard code), or all enhancement implementations of a namespace by type. | read |
|
|
145
148
|
|
|
146
149
|
<a id="g-datos"></a>
|
|
147
150
|
### Data queries
|
|
@@ -292,7 +295,7 @@ Designed to pass a Security and Basis review without exceptions. Details: **[SEC
|
|
|
292
295
|
Pinned to an exact version, without install scripts, like the project's own dependencies:
|
|
293
296
|
|
|
294
297
|
```sh
|
|
295
|
-
npm install -g --ignore-scripts @dozimple/abap-adt@1.
|
|
298
|
+
npm install -g --ignore-scripts @dozimple/abap-adt@1.2.0
|
|
296
299
|
PKG="$(npm root -g)/@dozimple/abap-adt"
|
|
297
300
|
mkdir -p ~/.config/abap-adt-dozimple && chmod 700 ~/.config/abap-adt-dozimple
|
|
298
301
|
cp "$PKG/config/systems.example.json" ~/.config/abap-adt-dozimple/systems.json # systems, roles and permissions
|
|
@@ -308,9 +311,9 @@ MCP client registration:
|
|
|
308
311
|
```
|
|
309
312
|
|
|
310
313
|
Every release is published from CI with [npm provenance](https://docs.npmjs.com/generating-provenance-statements):
|
|
311
|
-
`npm view @dozimple/abap-adt@1.
|
|
314
|
+
`npm view @dozimple/abap-adt@1.2.0 dist.attestations` shows the attestation, and the GitHub release carries the
|
|
312
315
|
tarball, its Sigstore bundle (`.sigstore.json`), the same bundle as in-toto provenance (`.intoto.jsonl`) and the
|
|
313
|
-
SBOM. To check it: `gh attestation verify dozimple-abap-adt-1.
|
|
316
|
+
SBOM. To check it: `gh attestation verify dozimple-abap-adt-1.2.0.tgz --repo <owner>/abap-adt-dozimple`.
|
|
314
317
|
|
|
315
318
|
### From source
|
|
316
319
|
|
|
@@ -345,7 +348,7 @@ abap-adt-doZimple is built on other people's work, and says so: each tool lists
|
|
|
345
348
|
<!-- credits:start -->
|
|
346
349
|
| Project | Author / holder | License | Type | Used in |
|
|
347
350
|
|---|---|---|---|---|
|
|
348
|
-
| [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) | Marcello Urbani | MIT | dependency | all (core), `transport_diff`, `transport_contents`, `inactive_objects`, `edit_preflight`, `run_atc` and
|
|
351
|
+
| [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) | Marcello Urbani | MIT | dependency | all (core), `transport_diff`, `transport_contents`, `inactive_objects`, `edit_preflight`, `run_atc` and 26 more |
|
|
349
352
|
| [Model Context Protocol TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) | Model Context Protocol | MIT | dependency | all (core) |
|
|
350
353
|
| [mcp-sap-docs](https://github.com/marianfoo/mcp-sap-docs) | Marian Zeis (marianfoo) | Apache-2.0 | dependency | `abap_feature_matrix`, `docs_search`, `docs_fetch`, `clean_core_objects`, `clean_core_object`, `abap_lint` and 1 more |
|
|
351
354
|
| [abaplint](https://github.com/abaplint/abaplint) | Lars Hvam and contributors | MIT | dependency | `abap_lint` |
|
|
@@ -360,7 +363,7 @@ abap-adt-doZimple is built on other people's work, and says so: each tool lists
|
|
|
360
363
|
| [ABAP Remote FS (vscode_abap_remote_fs)](https://github.com/marcellourbani/vscode_abap_remote_fs) | Marcello Urbani | MIT | idea | `syntax_check` |
|
|
361
364
|
| [mcp-abap-adt](https://github.com/mario-andreschak/mcp-abap-adt) | mario-andreschak | MIT | idea | `search_objects`, `get_source`, `package_contents`, `ddic_type_info`, `transaction_info`, `table_contents` |
|
|
362
365
|
| [ARC-1](https://github.com/arc-mcp/arc-1) | arc-mcp (Marian Zeis and contributors) | MIT | idea | `transport_diff`, `atc_quickfix`, `gateway_errors` |
|
|
363
|
-
| [vibing-steampunk](https://github.com/oisee/vibing-steampunk) | oisee and contributors | MIT | idea | `co_change`, `api_release_state`, `jobs`, `application_log` |
|
|
366
|
+
| [vibing-steampunk](https://github.com/oisee/vibing-steampunk) | oisee and contributors | MIT | idea | `co_change`, `api_release_state`, `source_search`, `enhancements`, `jobs`, `application_log` |
|
|
364
367
|
| [ABAP Accelerator for Amazon Q Developer](https://github.com/aws-solutions-library-samples/guidance-for-deploying-sap-abap-accelerator-for-amazon-q-developer) | AWS Solutions Library Samples | MIT-0 | idea | all (core), `usage_stats` |
|
|
365
368
|
| [An O(ND) Difference Algorithm and Its Variations (1986)](https://doi.org/10.1007/BF01840446) | Eugene W. Myers | published algorithm | algorithm | `transport_diff`, `atc_quickfix` |
|
|
366
369
|
<!-- credits:end -->
|
package/dist/core/catalog.en.js
CHANGED
|
@@ -23,11 +23,14 @@ export const TOOLS_EN = {
|
|
|
23
23
|
run_atc: "**Run ATC.** Runs the ABAP Test Cockpit on an object or a transport and lists numbered findings (priority, line, check, message) with SAP's P1/P2/P3 totals.",
|
|
24
24
|
atc_quickfix: "**SAP-proposed fixes.** The fixes SAP offers (the same as Ctrl+1 in Eclipse) for an ATC finding or a line: create text symbol, extract constant, etc.",
|
|
25
25
|
api_release_state: "**Is this API released? What is its successor?** Release state of an SAP object (class, function module/BAPI, table, CDS…) by contract C0–C4 and its released successor, read from the system itself.",
|
|
26
|
+
sap_notes: "**SAP notes in the system (SNOTE).** Whether a note is downloaded in this system, its implementation and processing status, version, component and title, per note or filtered by status/component; no SAP portal or S-user needed.",
|
|
26
27
|
syntax_check: "**SAP syntax check.** SAP's real syntax check (not abaplint), also on code not saved yet.",
|
|
27
28
|
run_unit_tests: "**Run ABAP Unit.** Runs the ABAP Unit tests of a class or program (harmless and short only) and returns the result per method, with each failure in detail.",
|
|
28
29
|
search_objects: "**Search ABAP objects.** Searches repository objects by name (supports * wildcards).",
|
|
29
30
|
get_source: "**Read ABAP source.** Reads the source of any object: program, include, class (and its includes), interface, function module (without knowing its group), CDS, table/structure, etc.",
|
|
30
31
|
where_used: "**Where used.** Where-used list of an object: who references it, with package and owner.",
|
|
32
|
+
source_search: "**Search text in source code.** Searches a text or regex in the source of a package (with subpackages), a transport or a list of objects, and returns object, include, line and the matching line; reaches what where-used misses (dynamic calls, literals, Z code outside the reference index).",
|
|
33
|
+
enhancements: "**Enhancements and BAdIs.** Implementations of a BAdI (new and classic, active or not), source code enhancements implemented inside a program, class or function group (flagging those that overwrite standard code), or all enhancement implementations of a namespace by type.",
|
|
31
34
|
object_versions: "**Object versions.** Version history of an object (date, author, transport).",
|
|
32
35
|
package_contents: "**Package contents.** Objects of a development package grouped by type, with subpackages (TADIR/TDEVC, any release).",
|
|
33
36
|
ddic_type_info: "**Data element, domain or table type.** Definition of a DDIC type: data element (domain, type, length, texts), domain (type, length, fixed values, value table) or table type (line type, key).",
|
package/dist/core/catalog.js
CHANGED
|
@@ -40,6 +40,7 @@ export const GROUPS = [
|
|
|
40
40
|
{ name: "run_atc", credits: ADT },
|
|
41
41
|
{ name: "atc_quickfix", credits: ["abapAdtApi", "arc1", "myers"] },
|
|
42
42
|
{ name: "api_release_state", credits: ["abapAdtApi", "vsp"] },
|
|
43
|
+
{ name: "sap_notes", credits: ADT },
|
|
43
44
|
{ name: "syntax_check", credits: ["abapAdtApi", "abapFs"] },
|
|
44
45
|
{ name: "run_unit_tests", credits: ADT },
|
|
45
46
|
],
|
|
@@ -52,12 +53,14 @@ export const GROUPS = [
|
|
|
52
53
|
{ name: "search_objects", credits: ["abapAdtApi", "marioAdt"] },
|
|
53
54
|
{ name: "get_source", credits: ["abapAdtApi", "marioAdt"] },
|
|
54
55
|
{ name: "where_used", credits: ADT },
|
|
56
|
+
{ name: "source_search", credits: ["abapAdtApi", "vsp"] },
|
|
55
57
|
{ name: "object_versions", credits: ADT },
|
|
56
58
|
{ name: "package_contents", credits: ["abapAdtApi", "marioAdt"] },
|
|
57
59
|
{ name: "ddic_type_info", credits: ["abapAdtApi", "marioAdt"] },
|
|
58
60
|
{ name: "transaction_info", credits: ["abapAdtApi", "marioAdt"] },
|
|
59
61
|
{ name: "function_modules", credits: ADT },
|
|
60
62
|
{ name: "text_elements", credits: ADT },
|
|
63
|
+
{ name: "enhancements", credits: ["abapAdtApi", "vsp"] },
|
|
61
64
|
],
|
|
62
65
|
},
|
|
63
66
|
{
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Limita cuántas promesas corren a la vez (no saturar SAP). Conserva el orden de los resultados. */
|
|
2
|
+
export async function pool(items, n, fn) {
|
|
3
|
+
const out = new Array(items.length);
|
|
4
|
+
let next = 0;
|
|
5
|
+
await Promise.all(Array.from({ length: Math.min(n, items.length) }, async () => {
|
|
6
|
+
while (next < items.length) {
|
|
7
|
+
const i = next++;
|
|
8
|
+
out[i] = await fn(items[i]);
|
|
9
|
+
}
|
|
10
|
+
}));
|
|
11
|
+
return out;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=concurrency.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { sqlLiteral } from "./objects.js";
|
|
2
|
+
/** Paquete y, si se pide, sus subpaquetes (hasta 5 niveles). `subs` son los hijos encontrados (directos si no es recursivo). */
|
|
3
|
+
export async function packageTree(sap, root, recursive) {
|
|
4
|
+
const packages = [root];
|
|
5
|
+
const subs = [];
|
|
6
|
+
let frontier = [root];
|
|
7
|
+
for (let level = 0; level < (recursive ? 5 : 1) && frontier.length; level++) {
|
|
8
|
+
const r = await sap.query(`SELECT devclass FROM tdevc WHERE parentcl IN ( ${frontier.map(sqlLiteral).join(", ")} )`, 2000);
|
|
9
|
+
frontier = r.values.map((v) => v.DEVCLASS);
|
|
10
|
+
subs.push(...frontier);
|
|
11
|
+
if (recursive)
|
|
12
|
+
packages.push(...frontier);
|
|
13
|
+
}
|
|
14
|
+
return { packages, subs };
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Objetos R3TR de esos paquetes (sin DEVC), ordenados por tipo y nombre. Sin los marcados como borrados
|
|
18
|
+
* (DELFLAG = 'X'): TADIR los conserva, pero ya no existen en el repositorio.
|
|
19
|
+
*/
|
|
20
|
+
export async function packageObjects(sap, packages, max, objectType) {
|
|
21
|
+
const typeFilter = objectType ? ` AND object = ${sqlLiteral(objectType.toUpperCase())}` : "";
|
|
22
|
+
const r = await sap.query(`SELECT devclass, pgmid, object, obj_name FROM tadir WHERE pgmid = 'R3TR' AND devclass IN ( ${packages.map(sqlLiteral).join(", ")} )` +
|
|
23
|
+
`${typeFilter} AND object <> 'DEVC' AND delflag = ' ' ORDER BY object, obj_name`, max);
|
|
24
|
+
return r.values;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=packages.js.map
|
package/dist/core/transport.js
CHANGED
|
@@ -45,6 +45,13 @@ export async function orderHeaders(sap, trkorrs) {
|
|
|
45
45
|
}
|
|
46
46
|
return out;
|
|
47
47
|
}
|
|
48
|
+
/** Entradas E071 de una orden y de todas sus tareas. */
|
|
49
|
+
export async function orderEntries(sap, root) {
|
|
50
|
+
const tasks = await sap.query(`SELECT trkorr FROM e070 WHERE strkorr = ${sqlLiteral(root)}`, 500);
|
|
51
|
+
const ids = [root, ...tasks.values.map((v) => v.TRKORR)];
|
|
52
|
+
const rows = await sap.query(`SELECT pgmid, object, obj_name FROM e071 WHERE trkorr IN ( ${ids.map(sqlLiteral).join(", ")} )`, 5000);
|
|
53
|
+
return rows.values;
|
|
54
|
+
}
|
|
48
55
|
export function describeOrder(h) {
|
|
49
56
|
return (`${h.trkorr} «${h.text}» · ${TRFUNCTION[h.trfunction] ?? h.trfunction} · ${TRSTATUS[h.trstatus] ?? h.trstatus} · ` +
|
|
50
57
|
`${h.owner}` + (h.tarsystem ? ` · destino ${h.tarsystem}` : ""));
|
package/dist/tools/core/dumps.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { dataClassOf } from "../../core/datapolicy.js";
|
|
2
3
|
import { ToolError } from "../../core/errors.js";
|
|
3
4
|
import { decodeEntities, htmlToText } from "../../core/feeds.js";
|
|
4
5
|
import { budget } from "../../core/output.js";
|
|
@@ -11,16 +12,87 @@ function headerField(d, label) {
|
|
|
11
12
|
const m = d.text.match(new RegExp(`<b>${label} </b></td><td[^>]*>([\\s\\S]*?)</td>`));
|
|
12
13
|
return m ? decode(m[1]).trim() : undefined;
|
|
13
14
|
}
|
|
15
|
+
const cat = (d, label) => d.categories.find((c) => c.label === label)?.term;
|
|
14
16
|
function summary(d, i) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
return (`${i}. ${headerField(d, "Date/Time")?.replace(" (System)", "") ?? "?"} · ${cat(d, "ABAP runtime error") ?? "?"} · ` +
|
|
18
|
+
`${cat(d, "Terminated ABAP program") ?? "?"} · ${d.author ?? "?"}\n ${headerField(d, "Short Text") ?? ""}`);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Cabecera de un dump tal como la guarda ST22 en SNAP_BEG.FLIST: secuencia de «id (2) + longitud (3) + valor».
|
|
22
|
+
* Solo trae metadatos (error, programa, include, línea, clase de excepción, ids), nunca valores de variables: esos
|
|
23
|
+
* están en SNAP, que sigue vetada. Ids verificados en un NW 7.50 real.
|
|
24
|
+
*/
|
|
25
|
+
export function parseFlist(flist) {
|
|
26
|
+
const out = {};
|
|
27
|
+
for (let i = 0; i + 5 <= flist.length;) {
|
|
28
|
+
const id = flist.slice(i, i + 2);
|
|
29
|
+
const len = Number(flist.slice(i + 2, i + 5));
|
|
30
|
+
if (!/^[A-Z0-9]{2}$/.test(id) || !Number.isInteger(len) || len < 0)
|
|
31
|
+
break;
|
|
32
|
+
out[id] = flist.slice(i + 5, i + 5 + len).trim();
|
|
33
|
+
i += 5 + len;
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
const GROUP_BY = ["error", "program", "error_program", "user", "day"];
|
|
38
|
+
/** El tope de filas que se piden a SNAP_BEG: por encima, la respuesta dice que puede haber más. */
|
|
39
|
+
export const GROUP_ROWS = 5000;
|
|
40
|
+
function groupKey(by, r, h) {
|
|
41
|
+
const error = h.FC || "?";
|
|
42
|
+
const program = h.AP || "?";
|
|
43
|
+
switch (by) {
|
|
44
|
+
case "error": return error;
|
|
45
|
+
case "program": return program;
|
|
46
|
+
case "error_program": return `${error} · ${program}`;
|
|
47
|
+
case "user": return String(r.UNAME ?? "?");
|
|
48
|
+
case "day": return String(r.DATUM ?? "?");
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function groupDumps(rows, by) {
|
|
52
|
+
const groups = new Map();
|
|
53
|
+
for (const r of rows) {
|
|
54
|
+
const h = parseFlist(String(r.FLIST ?? ""));
|
|
55
|
+
const key = groupKey(by, r, h);
|
|
56
|
+
const at = `${r.DATUM} ${String(r.UZEIT ?? "").slice(0, 6)}`;
|
|
57
|
+
let g = groups.get(key);
|
|
58
|
+
if (!g) {
|
|
59
|
+
g = { key, count: 0, first: at, last: at, users: 0, userSet: new Set() };
|
|
60
|
+
groups.set(key, g);
|
|
61
|
+
}
|
|
62
|
+
g.count++;
|
|
63
|
+
if (at < g.first)
|
|
64
|
+
g.first = at;
|
|
65
|
+
if (at >= g.last) {
|
|
66
|
+
g.last = at;
|
|
67
|
+
if (h.AI)
|
|
68
|
+
g.where = `${h.AI}${h.AL ? ` L${h.AL}` : ""}`;
|
|
69
|
+
}
|
|
70
|
+
g.userSet.add(String(r.UNAME ?? ""));
|
|
71
|
+
}
|
|
72
|
+
return [...groups.values()]
|
|
73
|
+
.map(({ userSet, ...g }) => ({ ...g, users: userSet.size }))
|
|
74
|
+
.sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
|
|
75
|
+
}
|
|
76
|
+
const GROUP = z.object({
|
|
77
|
+
key: z.string(),
|
|
78
|
+
count: z.number().int(),
|
|
79
|
+
first: z.string().describe("AAAAMMDD hhmmss del primer dump del grupo en el periodo"),
|
|
80
|
+
last: z.string(),
|
|
81
|
+
users: z.number().int().describe("Usuarios distintos afectados"),
|
|
82
|
+
where: z.string().optional().describe("Include y línea del dump más reciente del grupo"),
|
|
83
|
+
});
|
|
84
|
+
function dayStamp(daysAgo, now = new Date()) {
|
|
85
|
+
const d = new Date(now.getTime() - daysAgo * 86_400_000);
|
|
86
|
+
return d.toISOString().slice(0, 10).replace(/-/g, "");
|
|
18
87
|
}
|
|
19
88
|
export default defineTool({
|
|
20
89
|
name: "dumps",
|
|
21
90
|
title: "Dumps (ST22)",
|
|
22
91
|
description: "Lista los dumps de ejecución (ST22): fecha, error, programa, usuario y texto corto. Filtra por usuario y por " +
|
|
23
|
-
"texto (error o programa). Con detail=N devuelve el dump N completo (qué pasó, análisis, dónde terminó, fuente, pila)."
|
|
92
|
+
"texto (error o programa). Con detail=N devuelve el dump N completo (qué pasó, análisis, dónde terminó, fuente, pila). " +
|
|
93
|
+
"Con group_by (error, program, error_program, user, day) y days=N responde «los errores más frecuentes del periodo»: " +
|
|
94
|
+
"cuenta los dumps de los últimos N días agrupados, con primer y último caso, usuarios afectados y dónde terminó. " +
|
|
95
|
+
"La lista y el detalle salen del feed ADT (solo los más recientes); los grupos, de la cabecera de ST22 (SNAP_BEG).",
|
|
24
96
|
access: "read",
|
|
25
97
|
requires: { adt: ["/sap/bc/adt/runtime/dumps"] },
|
|
26
98
|
input: {
|
|
@@ -28,29 +100,80 @@ export default defineTool({
|
|
|
28
100
|
contains: z.string().optional().describe("Filtra por error o programa, p. ej. CALL_FUNCTION_NOT_FOUND o ZFI_REPORTE"),
|
|
29
101
|
max: z.number().int().min(1).max(200).default(20),
|
|
30
102
|
detail: z.number().int().min(1).optional().describe("Número de la lista para ver el dump completo"),
|
|
103
|
+
group_by: z.enum(GROUP_BY).optional().describe("Agrupar los dumps del periodo: por error, programa, error+programa, usuario o día"),
|
|
104
|
+
days: z.number().int().min(1).max(90).default(7).describe("Periodo hacia atrás para group_by (días)"),
|
|
31
105
|
},
|
|
32
|
-
|
|
106
|
+
output: {
|
|
107
|
+
mode: z.enum(["list", "detail", "groups"]),
|
|
108
|
+
total: z.number().int().describe("Dumps considerados (feed leído o filas del periodo)"),
|
|
109
|
+
period_days: z.number().int().optional(),
|
|
110
|
+
since: z.string().optional().describe("AAAAMMDD desde el que se contó"),
|
|
111
|
+
truncated: z.boolean().optional().describe("true si el periodo tiene más dumps de los que se pudieron leer"),
|
|
112
|
+
group_by: z.enum(GROUP_BY).optional(),
|
|
113
|
+
groups: z.array(GROUP).optional(),
|
|
114
|
+
dumps: z.array(z.object({ n: z.number().int(), error: z.string(), program: z.string(), user: z.string() })).optional(),
|
|
115
|
+
},
|
|
116
|
+
async run({ user, contains, max, detail, group_by, days }, { sap, system }) {
|
|
117
|
+
if (group_by) {
|
|
118
|
+
if (group_by === "user" && dataClassOf(system) !== "test") {
|
|
119
|
+
throw new ToolError("POLICY", `En ${system.id} (datos ${dataClassOf(system)}) no se agrupa por usuario: es una columna personal. Agrupa por error o programa; el número de usuarios afectados sale igualmente.`);
|
|
120
|
+
}
|
|
121
|
+
const since = dayStamp(days);
|
|
122
|
+
const where = [`seqno = '000'`, `datum >= '${since}'`, ...(user ? [`uname = '${user.toUpperCase()}'`] : [])].join(" AND ");
|
|
123
|
+
const r = await sap.query(`SELECT datum, uzeit, uname, flist FROM snap_beg WHERE ${where} ORDER BY datum DESCENDING, uzeit DESCENDING`, GROUP_ROWS);
|
|
124
|
+
const needle = contains?.toUpperCase();
|
|
125
|
+
const rows = r.values.filter((row) => {
|
|
126
|
+
if (!needle)
|
|
127
|
+
return true;
|
|
128
|
+
const h = parseFlist(String(row.FLIST ?? ""));
|
|
129
|
+
return (h.FC ?? "").toUpperCase().includes(needle) || (h.AP ?? "").toUpperCase().includes(needle);
|
|
130
|
+
});
|
|
131
|
+
const truncated = r.values.length >= GROUP_ROWS;
|
|
132
|
+
const groups = groupDumps(rows, group_by);
|
|
133
|
+
const shown = groups.slice(0, max);
|
|
134
|
+
const structured = { mode: "groups", total: rows.length, period_days: days, since, truncated, group_by, groups: shown };
|
|
135
|
+
if (!rows.length) {
|
|
136
|
+
return { text: `Sin dumps desde ${since} (${days} días)${user ? ` del usuario ${user}` : ""}${contains ? ` que casen con «${contains}»` : ""}: se consultó ST22 (SNAP_BEG) y no hay filas.`, structured };
|
|
137
|
+
}
|
|
138
|
+
const label = { error: "error", program: "programa", error_program: "error · programa", user: "usuario", day: "día" };
|
|
139
|
+
const lines = shown.map((g, i) => `${String(i + 1).padStart(3)}. ${String(g.count).padStart(5)} ${g.key} · ${g.users} usuario${g.users === 1 ? "" : "s"} · ${g.first.slice(0, 8)} → ${g.last.slice(0, 8)}${g.where ? ` · ${g.where}` : ""}`);
|
|
140
|
+
return {
|
|
141
|
+
text: `${rows.length} dumps desde ${since} (${days} días)${truncated ? ` — TOPE ALCANZADO (${GROUP_ROWS} filas): puede haber más; acota con days o contains` : ""}` +
|
|
142
|
+
`, ${groups.length} grupos por ${label[group_by]} (se muestran ${shown.length})\n\n` +
|
|
143
|
+
` # dumps ${label[group_by]}\n${lines.join("\n")}\n\n` +
|
|
144
|
+
`Siguiente: dumps(contains="<error o programa>") para ver los casos recientes y dumps(detail=N) para uno completo.`,
|
|
145
|
+
structured,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
33
148
|
const c = await sap.adt();
|
|
34
149
|
const feed = await c.dumps(user ? `and( equals( user, ${user.toUpperCase()} ) )` : "");
|
|
35
150
|
const needle = contains?.toUpperCase();
|
|
36
151
|
const list = feed.dumps.filter((d) => !needle || d.categories.some((x) => x.term.toUpperCase().includes(needle)));
|
|
152
|
+
const asRow = (d, i) => ({ n: i + 1, error: cat(d, "ABAP runtime error") ?? "?", program: cat(d, "Terminated ABAP program") ?? "?", user: d.author ?? "?" });
|
|
37
153
|
if (!list.length) {
|
|
38
|
-
return
|
|
154
|
+
return {
|
|
155
|
+
text: `El feed de dumps se leyó (${feed.dumps.length} en total) y ninguno casa${user ? ` con usuario ${user}` : ""}${contains ? ` y «${contains}»` : ""}. Para un periodo largo usa group_by.`,
|
|
156
|
+
structured: { mode: "list", total: feed.dumps.length, dumps: [] },
|
|
157
|
+
};
|
|
39
158
|
}
|
|
40
159
|
if (detail) {
|
|
41
160
|
const d = list[detail - 1];
|
|
42
161
|
if (!d)
|
|
43
162
|
throw new ToolError("INPUT", `Solo hay ${list.length} dumps en la lista.`);
|
|
163
|
+
const structured = { mode: "detail", total: list.length, dumps: [asRow(d, detail - 1)] };
|
|
44
164
|
const self = d.links.find((l) => l.rel === "self")?.href.replace(/^adt:\/\/[^/]+/, "");
|
|
45
165
|
if (!self)
|
|
46
|
-
return budget(stripHtml(d.text));
|
|
166
|
+
return { text: budget(stripHtml(d.text)), structured };
|
|
47
167
|
// El recurso ofrece HTML (dump completo) o un XML índice; text/plain da 406.
|
|
48
168
|
const r = await c.httpClient.request(self, { headers: { Accept: "text/html" } });
|
|
49
|
-
return budget(`${summary(d, detail)}\n\n${stripHtml(String(r.body))}`, undefined, "El dump completo está en ST22.");
|
|
169
|
+
return { text: budget(`${summary(d, detail)}\n\n${stripHtml(String(r.body))}`, undefined, "El dump completo está en ST22."), structured };
|
|
50
170
|
}
|
|
51
171
|
const shown = list.slice(0, max);
|
|
52
|
-
return
|
|
53
|
-
|
|
172
|
+
return {
|
|
173
|
+
text: `${list.length} dumps${needle || user ? " que casan" : ""} (se muestran ${shown.length}; el feed trae solo los más recientes: para un periodo usa group_by)\n\n` +
|
|
174
|
+
shown.map((d, i) => summary(d, i + 1)).join("\n"),
|
|
175
|
+
structured: { mode: "list", total: list.length, dumps: shown.map(asRow) },
|
|
176
|
+
};
|
|
54
177
|
},
|
|
55
178
|
});
|
|
56
179
|
//# sourceMappingURL=dumps.js.map
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { rowCap } from "../../core/datapolicy.js";
|
|
3
|
+
import { ToolError } from "../../core/errors.js";
|
|
4
|
+
import { sqlLiteral } from "../../core/objects.js";
|
|
5
|
+
import { tsv } from "../../core/output.js";
|
|
6
|
+
import { defineTool } from "../../core/tool.js";
|
|
7
|
+
/**
|
|
8
|
+
* Ampliaciones del sistema por SQL de diccionario (valen en 7.50 y S/4):
|
|
9
|
+
* - BAdI nuevas (framework de ampliaciones): BADIIMPL_ENH; clásicas: SXC_EXIT + SXC_ATTR.
|
|
10
|
+
* - Ampliaciones en código (puntos y secciones implícitos o explícitos): ENHINCINX, por programa principal.
|
|
11
|
+
* - Implementaciones de ampliación: ENHHEADER.
|
|
12
|
+
* Los significados vienen del diccionario (ENHMODE, ENHBOOLEAN, ACTIVEFLAG), verificados en un 7.50.
|
|
13
|
+
*/
|
|
14
|
+
const ENHMODE = { S: "estática", D: "dinámica" };
|
|
15
|
+
/** ENHTOOLTYPE no tiene valores fijos en el diccionario: se muestra el código y, si se conoce, qué es. */
|
|
16
|
+
const TOOL = {
|
|
17
|
+
HOOK_IMPL: "ampliación de código (punto o sección)",
|
|
18
|
+
BADI_IMPL: "implementación de BAdI",
|
|
19
|
+
CLASENH: "ampliación de clase",
|
|
20
|
+
INTFENH: "ampliación de interfaz",
|
|
21
|
+
FUGRENH: "ampliación de grupo de funciones",
|
|
22
|
+
WDYENH: "ampliación Web Dynpro",
|
|
23
|
+
};
|
|
24
|
+
const tool = (t) => (TOOL[t] ? `${t} (${TOOL[t]})` : t);
|
|
25
|
+
/** Nombre del programa principal que usa ENHINCINX: clase → NOMBRE====…CP (32), grupo → SAPL<grupo>. */
|
|
26
|
+
export function mainProgram(name, type) {
|
|
27
|
+
const n = name.trim().toUpperCase();
|
|
28
|
+
if (type === "PROG")
|
|
29
|
+
return n;
|
|
30
|
+
if (type === "CLAS")
|
|
31
|
+
return `${n.padEnd(30, "=")}CP`;
|
|
32
|
+
const m = /^(\/[^/]+\/)(.+)$/.exec(n);
|
|
33
|
+
return m ? `${m[1]}SAPL${m[2]}` : `SAPL${n}`;
|
|
34
|
+
}
|
|
35
|
+
const NAME = /^[A-Z0-9_/]{1,40}$/i;
|
|
36
|
+
/** Propias del cliente primero: Z, Y y espacios de nombres que no son de SAP no se distinguen aquí; Z/Y sí. */
|
|
37
|
+
const isCustom = (n) => /^[ZY]/i.test(n);
|
|
38
|
+
export default defineTool({
|
|
39
|
+
name: "enhancements",
|
|
40
|
+
title: "Ampliaciones y BAdI",
|
|
41
|
+
description: "Qué ampliaciones hay y dónde. badi=NOMBRE: implementaciones de esa BAdI, nuevas y clásicas, activas o no. " +
|
|
42
|
+
"program + program_type (PROG, CLAS, FUGR): ampliaciones de código (puntos y secciones implícitos o explícitos) " +
|
|
43
|
+
"implementadas dentro de ese programa, clase o grupo — lo que cambia el comportamiento de un estándar sin tocar " +
|
|
44
|
+
"su código. prefix (p. ej. Z): implementaciones de ampliación de ese espacio de nombres, por tipo. Solo lectura.",
|
|
45
|
+
access: "read",
|
|
46
|
+
input: {
|
|
47
|
+
badi: z.string().regex(NAME).optional().describe("Definición de BAdI, p. ej. MB_MIGO_BADI"),
|
|
48
|
+
program: z.string().regex(NAME).optional().describe("Programa, clase o grupo de funciones ampliado, p. ej. SAPMV45A"),
|
|
49
|
+
program_type: z.enum(["PROG", "CLAS", "FUGR"]).default("PROG"),
|
|
50
|
+
prefix: z.string().regex(/^[A-Z0-9_/]{1,20}$/i).optional().describe("Implementaciones cuyo nombre empieza así, p. ej. Z o ZSD"),
|
|
51
|
+
tool_type: z.string().regex(/^[A-Z_]{3,20}$/i).optional().describe("Con prefix: filtrar por tipo, p. ej. BADI_IMPL o HOOK_IMPL"),
|
|
52
|
+
max: z.number().int().min(1).max(2000).default(200),
|
|
53
|
+
},
|
|
54
|
+
output: {
|
|
55
|
+
mode: z.enum(["badi", "program", "prefix"]),
|
|
56
|
+
target: z.string(),
|
|
57
|
+
rows: z.array(z.record(z.union([z.string(), z.number(), z.boolean()]))),
|
|
58
|
+
total: z.number().int(),
|
|
59
|
+
truncated: z.boolean(),
|
|
60
|
+
},
|
|
61
|
+
async run({ badi, program, program_type, prefix, tool_type, max }, { sap, system }) {
|
|
62
|
+
if ([badi, program, prefix].filter(Boolean).length !== 1)
|
|
63
|
+
throw new ToolError("INPUT", "Indica exactamente uno: badi, program o prefix.");
|
|
64
|
+
const cap = Math.min(max, rowCap(system));
|
|
65
|
+
if (badi) {
|
|
66
|
+
const b = badi.toUpperCase();
|
|
67
|
+
const kernel = await sap.query(`SELECT enhname, badi_impl, active, spotname FROM badiimpl_enh WHERE badi_name = ${sqlLiteral(b)}`, 2000);
|
|
68
|
+
const classic = await sap.query(`SELECT imp_name FROM sxc_exit WHERE exit_name = ${sqlLiteral(b)}`, 2000);
|
|
69
|
+
const names = classic.values.map((v) => String(v.IMP_NAME).trim());
|
|
70
|
+
const attr = names.length ? await sap.query(`SELECT imp_name, active FROM sxc_attr WHERE imp_name IN ( ${names.map(sqlLiteral).join(", ")} )`, 2000) : { values: [] };
|
|
71
|
+
const activeClassic = new Map(attr.values.map((v) => [String(v.IMP_NAME).trim(), v.ACTIVE === "X"]));
|
|
72
|
+
// Una BAdI clásica migrada aparece en las dos tablas: se muestra una vez, como nueva.
|
|
73
|
+
const kernelImpls = new Set(kernel.values.map((v) => String(v.BADI_IMPL).trim()));
|
|
74
|
+
const rows = [
|
|
75
|
+
...kernel.values.map((v) => ({ implementation: String(v.BADI_IMPL).trim(), kind: "nueva", enhancement: String(v.ENHNAME).trim(), spot: String(v.SPOTNAME ?? "").trim(), active: v.ACTIVE === "X" })),
|
|
76
|
+
...names.filter((n) => !kernelImpls.has(n)).map((n) => ({ implementation: n, kind: "clásica", enhancement: "", spot: "", active: activeClassic.get(n) ?? false })),
|
|
77
|
+
].sort((a, b2) => Number(isCustom(b2.implementation)) - Number(isCustom(a.implementation)) || Number(b2.active) - Number(a.active) || a.implementation.localeCompare(b2.implementation));
|
|
78
|
+
const shown = rows.slice(0, cap);
|
|
79
|
+
const structured = { mode: "badi", target: b, rows: shown, total: rows.length, truncated: rows.length > cap };
|
|
80
|
+
if (!rows.length)
|
|
81
|
+
return { text: `La BAdI ${b} no tiene implementaciones en este sistema (se consultaron BADIIMPL_ENH y SXC_EXIT).`, structured };
|
|
82
|
+
const act = rows.filter((r) => r.active).length;
|
|
83
|
+
return {
|
|
84
|
+
text: `BAdI ${b}: ${rows.length} implementaciones, ${act} activas, ${rows.filter((r) => isCustom(r.implementation)).length} propias (Z/Y, primero)` +
|
|
85
|
+
`${rows.length > cap ? ` (se muestran ${cap})` : ""}\n\n` +
|
|
86
|
+
tsv(["implementación", "tipo", "activa", "ampliación", "spot"], shown.map((r) => [r.implementation, r.kind, r.active ? "sí" : "NO", r.enhancement, r.spot])),
|
|
87
|
+
structured,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
if (program) {
|
|
91
|
+
const p = mainProgram(program, program_type);
|
|
92
|
+
const r = await sap.query(`SELECT enhname, version, enhmode, overwrite FROM enhincinx WHERE programname = ${sqlLiteral(p)} ORDER BY enhname`, 5000);
|
|
93
|
+
const by = new Map();
|
|
94
|
+
for (const v of r.values) {
|
|
95
|
+
const k = String(v.ENHNAME).trim();
|
|
96
|
+
const cur = by.get(k) ?? { enhancement: k, mode: ENHMODE[String(v.ENHMODE)] ?? String(v.ENHMODE), active: false, overwrite: false, places: 0 };
|
|
97
|
+
cur.places++;
|
|
98
|
+
if (v.VERSION === "A")
|
|
99
|
+
cur.active = true;
|
|
100
|
+
if (v.OVERWRITE === "X")
|
|
101
|
+
cur.overwrite = true;
|
|
102
|
+
by.set(k, cur);
|
|
103
|
+
}
|
|
104
|
+
const rows = [...by.values()].sort((a, b2) => a.enhancement.localeCompare(b2.enhancement));
|
|
105
|
+
const shown = rows.slice(0, cap);
|
|
106
|
+
const structured = { mode: "program", target: p, rows: shown, total: rows.length, truncated: rows.length > cap };
|
|
107
|
+
if (!rows.length)
|
|
108
|
+
return { text: `${program.toUpperCase()} (${p}) no tiene ampliaciones de código implementadas (se consultó ENHINCINX).`, structured };
|
|
109
|
+
const over = rows.filter((x) => x.overwrite).length;
|
|
110
|
+
return {
|
|
111
|
+
text: `${program.toUpperCase()} (${p}): ${rows.length} implementaciones de ampliación en su código` +
|
|
112
|
+
`${over ? ` — ${over} SUSTITUYEN código estándar (overwrite)` : ""}${rows.length > cap ? ` (se muestran ${cap})` : ""}\n\n` +
|
|
113
|
+
tsv(["ampliación", "modo", "activa", "sustituye", "lugares"], shown.map((x) => [x.enhancement, x.mode, x.active ? "sí" : "solo inactiva", x.overwrite ? "SÍ" : "", x.places])),
|
|
114
|
+
structured,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const pre = prefix.toUpperCase();
|
|
118
|
+
const typeFilter = tool_type ? ` AND enhtooltype = ${sqlLiteral(tool_type.toUpperCase())}` : "";
|
|
119
|
+
const where = `WHERE enhname LIKE ${sqlLiteral(`${pre}%`)} AND version = 'A'${typeFilter}`;
|
|
120
|
+
const counts = await sap.query(`SELECT enhtooltype, COUNT(*) AS n FROM enhheader ${where} GROUP BY enhtooltype`, 50);
|
|
121
|
+
const total = counts.values.reduce((s, v) => s + Number(v.N), 0);
|
|
122
|
+
const r = await sap.query(`SELECT enhname, enhtooltype FROM enhheader ${where} ORDER BY enhname`, cap);
|
|
123
|
+
const rows = r.values.map((v) => ({ enhancement: String(v.ENHNAME).trim(), type: String(v.ENHTOOLTYPE).trim() }));
|
|
124
|
+
const structured = { mode: "prefix", target: pre, rows, total, truncated: total > rows.length };
|
|
125
|
+
if (!total)
|
|
126
|
+
return { text: `No hay implementaciones de ampliación activas que empiecen por ${pre}${tool_type ? ` de tipo ${tool_type}` : ""}.`, structured };
|
|
127
|
+
const summary = counts.values.map((v) => `${tool(String(v.ENHTOOLTYPE).trim())}: ${Number(v.N)}`).join(" · ");
|
|
128
|
+
return {
|
|
129
|
+
text: `${total} implementaciones de ampliación activas que empiezan por ${pre}${total > rows.length ? ` (se muestran ${rows.length})` : ""}\n${summary}\n\n` +
|
|
130
|
+
tsv(["ampliación", "tipo"], rows.map((x) => [x.enhancement, x.type])),
|
|
131
|
+
structured,
|
|
132
|
+
};
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
//# sourceMappingURL=enhancements.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ToolError } from "../../core/errors.js";
|
|
3
3
|
import { sqlLiteral } from "../../core/objects.js";
|
|
4
|
+
import { packageObjects, packageTree } from "../../core/packages.js";
|
|
4
5
|
import { defineTool } from "../../core/tool.js";
|
|
5
6
|
export default defineTool({
|
|
6
7
|
name: "package_contents",
|
|
@@ -21,20 +22,8 @@ export default defineTool({
|
|
|
21
22
|
if (!h)
|
|
22
23
|
throw new ToolError("NOT_FOUND", `El paquete ${root} no existe en este sistema.`);
|
|
23
24
|
const txt = await sap.query(`SELECT ctext FROM tdevct WHERE devclass = ${sqlLiteral(root)}`, 1);
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const subs = [];
|
|
27
|
-
let frontier = [root];
|
|
28
|
-
for (let level = 0; level < (recursive ? 5 : 1) && frontier.length; level++) {
|
|
29
|
-
const r = await sap.query(`SELECT devclass FROM tdevc WHERE parentcl IN ( ${frontier.map(sqlLiteral).join(", ")} )`, 2000);
|
|
30
|
-
frontier = r.values.map((v) => v.DEVCLASS);
|
|
31
|
-
subs.push(...frontier);
|
|
32
|
-
if (recursive)
|
|
33
|
-
packages.push(...frontier);
|
|
34
|
-
}
|
|
35
|
-
const typeFilter = object_type ? ` AND object = ${sqlLiteral(object_type.toUpperCase())}` : "";
|
|
36
|
-
const objs = await sap.query(`SELECT devclass, object, obj_name FROM tadir WHERE pgmid = 'R3TR' AND devclass IN ( ${packages.map(sqlLiteral).join(", ")} )` +
|
|
37
|
-
`${typeFilter} AND object <> 'DEVC' ORDER BY object, obj_name`, max_objects);
|
|
25
|
+
const { packages, subs } = await packageTree(sap, root, recursive);
|
|
26
|
+
const objs = { values: await packageObjects(sap, packages, max_objects, object_type) };
|
|
38
27
|
const byType = new Map();
|
|
39
28
|
for (const o of objs.values) {
|
|
40
29
|
const label = recursive && o.DEVCLASS !== root ? `${o.OBJ_NAME} [${o.DEVCLASS}]` : o.OBJ_NAME;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { dataClassOf, rowCap } from "../../core/datapolicy.js";
|
|
3
|
+
import { ToolError } from "../../core/errors.js";
|
|
4
|
+
import { sqlLiteral } from "../../core/objects.js";
|
|
5
|
+
import { tsv } from "../../core/output.js";
|
|
6
|
+
import { defineTool } from "../../core/tool.js";
|
|
7
|
+
/**
|
|
8
|
+
* Estado de las notas SAP en ESTE sistema (lo que muestra SNOTE), leído de las tablas de la herramienta de notas:
|
|
9
|
+
* CWBNTCUST (estado por nota), CWBNTHEAD (versión y componente) y CWBNTSTXT (título). No consulta el portal de SAP
|
|
10
|
+
* ni necesita S-user: solo sabe de las notas descargadas en el sistema. Los códigos de estado no son valores fijos
|
|
11
|
+
* del diccionario; su significado sale de las constantes de SAP (IF_SCWN_NA_CONSTANTS), verificadas en un 7.50.
|
|
12
|
+
*/
|
|
13
|
+
/** CWBNTCUST-PRSTATUS, «Implementation State». */
|
|
14
|
+
export const IMPL_STATE = {
|
|
15
|
+
E: "implementada completamente",
|
|
16
|
+
U: "implementada de forma incompleta",
|
|
17
|
+
V: "implementada una versión anterior",
|
|
18
|
+
N: "se puede implementar (no implementada)",
|
|
19
|
+
O: "obsoleta",
|
|
20
|
+
"-": "no se puede implementar (sin instrucción de corrección válida)",
|
|
21
|
+
"": "sin determinar",
|
|
22
|
+
};
|
|
23
|
+
/** CWBNTCUST-NTSTATUS, «Processing Status». */
|
|
24
|
+
export const PROC_STATE = { N: "nueva", I: "en tratamiento", A: "terminada", R: "no relevante" };
|
|
25
|
+
const describe = (map, code) => map[code.trim()] ?? `código «${code}» desconocido`;
|
|
26
|
+
const noteKey = (n) => String(Number(n));
|
|
27
|
+
/** Una consulta por tramo: un IN con miles de literales supera lo que acepta la vista previa de datos ADT. */
|
|
28
|
+
export async function inChunks(sap, sql, keys, size = 200) {
|
|
29
|
+
const out = [];
|
|
30
|
+
for (let i = 0; i < keys.length; i += size)
|
|
31
|
+
out.push(...(await sap.query(sql(keys.slice(i, i + size).map(sqlLiteral).join(", ")), 5000)).values);
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
export async function noteDetails(sap, notes) {
|
|
35
|
+
const heads = { values: await inChunks(sap, (l) => `SELECT numm, versno, themk FROM cwbnthead WHERE numm IN ( ${l} )`, notes) };
|
|
36
|
+
const latest = new Map();
|
|
37
|
+
for (const h of heads.values) {
|
|
38
|
+
const k = noteKey(h.NUMM);
|
|
39
|
+
const v = Number(h.VERSNO);
|
|
40
|
+
if (!latest.has(k) || v > latest.get(k).version)
|
|
41
|
+
latest.set(k, { version: v, component: String(h.THEMK ?? "").trim() });
|
|
42
|
+
}
|
|
43
|
+
const texts = { values: await inChunks(sap, (l) => `SELECT numm, versno, langu, stext FROM cwbntstxt WHERE numm IN ( ${l} ) AND ( langu = 'S' OR langu = 'E' )`, notes) };
|
|
44
|
+
const title = new Map();
|
|
45
|
+
for (const t of texts.values) {
|
|
46
|
+
const k = noteKey(t.NUMM);
|
|
47
|
+
const cand = { v: Number(t.VERSNO), lang: String(t.LANGU), text: String(t.STEXT ?? "").trim() };
|
|
48
|
+
const cur = title.get(k);
|
|
49
|
+
// Versión más alta; a igual versión, español antes que inglés.
|
|
50
|
+
if (!cur || cand.v > cur.v || (cand.v === cur.v && cand.lang === "S" && cur.lang !== "S"))
|
|
51
|
+
title.set(k, cand);
|
|
52
|
+
}
|
|
53
|
+
return { latest, title };
|
|
54
|
+
}
|
|
55
|
+
const NOTE = z.object({
|
|
56
|
+
note: z.string(),
|
|
57
|
+
downloaded: z.boolean().describe("false = la nota no está en este sistema (nunca se descargó con SNOTE)"),
|
|
58
|
+
implementation: z.string().optional(),
|
|
59
|
+
implementation_code: z.string().optional(),
|
|
60
|
+
processing: z.string().optional(),
|
|
61
|
+
version: z.number().int().optional(),
|
|
62
|
+
component: z.string().optional(),
|
|
63
|
+
title: z.string().optional(),
|
|
64
|
+
processor: z.string().optional().describe("Usuario que la trató (solo en sistemas con datos de prueba)"),
|
|
65
|
+
});
|
|
66
|
+
export default defineTool({
|
|
67
|
+
name: "sap_notes",
|
|
68
|
+
title: "Notas SAP en el sistema (SNOTE)",
|
|
69
|
+
description: "Estado de notas SAP en este sistema, como en SNOTE: si está descargada, estado de implementación (completa, " +
|
|
70
|
+
"incompleta, versión anterior, se puede implementar, obsoleta, no se puede implementar), estado de tratamiento, " +
|
|
71
|
+
"versión, componente y título. Con notes=[…] responde por notas concretas (p. ej. «¿está la 2198647 en PRD?»); " +
|
|
72
|
+
"sin notas, lista las del estado pedido. Solo conoce las notas descargadas en el sistema: no consulta el portal.",
|
|
73
|
+
access: "read",
|
|
74
|
+
input: {
|
|
75
|
+
notes: z.array(z.string().regex(/^\d{1,10}$/, "número de nota: solo dígitos")).max(200).optional(),
|
|
76
|
+
implementation: z
|
|
77
|
+
.array(z.enum(["E", "U", "V", "N", "O", "-"]))
|
|
78
|
+
.optional()
|
|
79
|
+
.describe("Sin notes: filtrar por estado de implementación (E completa, U incompleta, V versión anterior, N se puede implementar, O obsoleta, - no se puede)"),
|
|
80
|
+
component: z.string().regex(/^[A-Z0-9-]{2,24}$/i).optional().describe("Sin notes: prefijo de componente, p. ej. SD-BF o MM-PUR"),
|
|
81
|
+
max: z.number().int().min(1).max(1000).default(100),
|
|
82
|
+
},
|
|
83
|
+
output: { notes: z.array(NOTE), total: z.number().int(), truncated: z.boolean() },
|
|
84
|
+
async run({ notes, implementation, component, max }, { sap, system }) {
|
|
85
|
+
const showUser = dataClassOf(system) === "test";
|
|
86
|
+
const cap = Math.min(max, rowCap(system));
|
|
87
|
+
let custRows;
|
|
88
|
+
let counted;
|
|
89
|
+
if (notes?.length) {
|
|
90
|
+
const r = await sap.query(`SELECT numm, ntstatus, prstatus, cwbuser FROM cwbntcust WHERE numm IN ( ${notes.map(sqlLiteral).join(", ")} )`, 1000);
|
|
91
|
+
custRows = r.values;
|
|
92
|
+
}
|
|
93
|
+
else if (component) {
|
|
94
|
+
// El componente está en la cabecera: se filtra ahí y luego se trae el estado de esas notas, por tramos.
|
|
95
|
+
const h = await sap.query(`SELECT DISTINCT numm FROM cwbnthead WHERE themk LIKE ${sqlLiteral(`${component.toUpperCase()}%`)}`, 5000);
|
|
96
|
+
const ids = h.values.map((v) => noteKey(v.NUMM));
|
|
97
|
+
const impl = implementation?.length ? ` AND prstatus IN ( ${implementation.map(sqlLiteral).join(", ")} )` : "";
|
|
98
|
+
custRows = await inChunks(sap, (l) => `SELECT numm, ntstatus, prstatus, cwbuser FROM cwbntcust WHERE numm IN ( ${l} )${impl}`, ids);
|
|
99
|
+
custRows.sort((a, b) => Number(b.NUMM) - Number(a.NUMM));
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
if (!implementation?.length)
|
|
103
|
+
throw new ToolError("INPUT", "Indica notes, o un filtro: implementation y/o component.");
|
|
104
|
+
const where = `WHERE prstatus IN ( ${implementation.map(sqlLiteral).join(", ")} )`;
|
|
105
|
+
const r = await sap.query(`SELECT numm, ntstatus, prstatus, cwbuser FROM cwbntcust ${where} ORDER BY numm DESCENDING`, cap + 1);
|
|
106
|
+
custRows = r.values;
|
|
107
|
+
counted = Number((await sap.query(`SELECT COUNT(*) AS n FROM cwbntcust ${where}`, 1)).values[0]?.N ?? custRows.length);
|
|
108
|
+
}
|
|
109
|
+
// Sin notas concretas solo hace falta el detalle de las que se van a mostrar.
|
|
110
|
+
const shownRows = notes?.length ? custRows : custRows.slice(0, cap);
|
|
111
|
+
const keys = [...new Set([...(notes ?? []).map(noteKey), ...shownRows.map((c) => noteKey(c.NUMM))])];
|
|
112
|
+
const { latest, title } = keys.length ? await noteDetails(sap, keys) : { latest: new Map(), title: new Map() };
|
|
113
|
+
const byNote = new Map(custRows.map((c) => [noteKey(c.NUMM), c]));
|
|
114
|
+
let rows = (notes?.length ? notes.map(noteKey) : [...byNote.keys()]).map((k) => {
|
|
115
|
+
const c = byNote.get(k);
|
|
116
|
+
const h = latest.get(k);
|
|
117
|
+
if (!c && !h)
|
|
118
|
+
return { note: k, downloaded: false };
|
|
119
|
+
const impl = String(c?.PRSTATUS ?? "").trim();
|
|
120
|
+
return {
|
|
121
|
+
note: k,
|
|
122
|
+
downloaded: true,
|
|
123
|
+
implementation: c ? describe(IMPL_STATE, impl) : "sin estado en SNOTE",
|
|
124
|
+
implementation_code: c ? impl || " " : undefined,
|
|
125
|
+
processing: c ? describe(PROC_STATE, String(c.NTSTATUS ?? "")) : undefined,
|
|
126
|
+
version: h?.version,
|
|
127
|
+
component: h?.component,
|
|
128
|
+
title: title.get(k)?.text,
|
|
129
|
+
...(showUser && c?.CWBUSER ? { processor: String(c.CWBUSER).trim() } : {}),
|
|
130
|
+
};
|
|
131
|
+
});
|
|
132
|
+
const total = notes?.length ? rows.length : (counted ?? custRows.length);
|
|
133
|
+
const truncated = !notes?.length && total > cap;
|
|
134
|
+
rows = rows.slice(0, notes?.length ? rows.length : cap);
|
|
135
|
+
const structured = { notes: rows, total, truncated };
|
|
136
|
+
if (!rows.length)
|
|
137
|
+
return { text: "La consulta se hizo y ninguna nota de este sistema cumple el filtro.", structured };
|
|
138
|
+
const header = ["nota", "descargada", "implementación", "tratamiento", "versión", "componente", "título", ...(showUser ? ["tratada por"] : [])];
|
|
139
|
+
const table = tsv(header, rows.map((r) => [r.note, r.downloaded ? "sí" : "NO", r.implementation ?? "—", r.processing ?? "—", r.version ?? "", r.component ?? "", r.title ?? "", ...(showUser ? [r.processor ?? ""] : [])]));
|
|
140
|
+
const missing = rows.filter((r) => !r.downloaded).map((r) => r.note);
|
|
141
|
+
return {
|
|
142
|
+
text: `${rows.length} notas${truncated ? ` (TOPE: hay ${total}; sube max o afina el filtro)` : ""}\n\n${table}` +
|
|
143
|
+
(missing.length ? `\n\nNo descargadas en este sistema: ${missing.join(", ")}. Que no esté no significa que no aplique: SNOTE solo conoce lo descargado.` : ""),
|
|
144
|
+
structured,
|
|
145
|
+
};
|
|
146
|
+
},
|
|
147
|
+
});
|
|
148
|
+
//# sourceMappingURL=sap_notes.js.map
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { ADTClient } from "abap-adt-api";
|
|
3
|
+
import { pool } from "../../core/concurrency.js";
|
|
4
|
+
import { normalizeError, ToolError } from "../../core/errors.js";
|
|
5
|
+
import { resolveByTypePrefix, sqlLiteral } from "../../core/objects.js";
|
|
6
|
+
import { packageObjects, packageTree } from "../../core/packages.js";
|
|
7
|
+
import { assertTrkorr } from "../../core/policy.js";
|
|
8
|
+
import { sourceObjectsOf } from "../../core/revisions.js";
|
|
9
|
+
import { defineTool } from "../../core/tool.js";
|
|
10
|
+
import { orderEntries, orderHeaders } from "../../core/transport.js";
|
|
11
|
+
/**
|
|
12
|
+
* Búsqueda de texto en el código fuente de un alcance acotado (paquete, orden u objetos). ADT tiene un endpoint de
|
|
13
|
+
* búsqueda de texto (`informationsystem/textsearch`), pero en NW 7.50 no existe: responde 404 y no figura en el
|
|
14
|
+
* discovery. Así que se leen las fuentes del alcance y se busca aquí. Sirve donde where_used no llega: el índice de
|
|
15
|
+
* referencias de un sistema convertido puede no cubrir el código Z.
|
|
16
|
+
*/
|
|
17
|
+
const MAX_PATTERN = 200;
|
|
18
|
+
const MAX_LINE = 200;
|
|
19
|
+
/** Programa principal e includes de un grupo de funciones, con namespace: /DZ/GRP → /DZ/SAPLGRP y /DZ/LGRP. */
|
|
20
|
+
export function groupNames(group) {
|
|
21
|
+
const g = group.trim().toUpperCase();
|
|
22
|
+
const m = /^(\/[^/]+\/)(.+)$/.exec(g);
|
|
23
|
+
return m ? { main: `${m[1]}SAPL${m[2]}`, includePrefix: `${m[1]}L${m[2]}` } : { main: `SAPL${g}`, includePrefix: `L${g}` };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Un grupo de funciones se busca en sus módulos (TFDIR) y en sus includes propios (TOP, F, O, I…), no en los U (son
|
|
27
|
+
* los módulos) ni en los generados con «$». ADT tipa esos includes como FUGR/I, no PROG/I (verificado en 7.50).
|
|
28
|
+
*/
|
|
29
|
+
async function expandGroup(sap, group) {
|
|
30
|
+
const { main, includePrefix } = groupNames(group);
|
|
31
|
+
const fms = await sap.query(`SELECT funcname FROM tfdir WHERE pname = ${sqlLiteral(main)}`, 500);
|
|
32
|
+
const incs = await sap.query(`SELECT name FROM trdir WHERE name LIKE ${sqlLiteral(`${includePrefix}%`)} AND name NOT LIKE ${sqlLiteral(`${includePrefix}U%`)}`, 500);
|
|
33
|
+
return [
|
|
34
|
+
...fms.values.map((v) => ({ name: String(v.FUNCNAME).trim(), types: ["FUGR/FF"], from: [`R3TR FUGR ${group}`] })),
|
|
35
|
+
...incs.values
|
|
36
|
+
.map((v) => String(v.NAME).trim())
|
|
37
|
+
.filter((n) => !n.includes("$"))
|
|
38
|
+
.map((name) => ({ name, types: ["FUGR/I", "PROG/"], from: [`R3TR FUGR ${group}`] })),
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
async function scopeRefs(sap, a, max) {
|
|
42
|
+
let rows;
|
|
43
|
+
let label;
|
|
44
|
+
if (a.package) {
|
|
45
|
+
const root = a.package.trim().toUpperCase();
|
|
46
|
+
const { packages } = await packageTree(sap, root, a.include_subpackages);
|
|
47
|
+
rows = await packageObjects(sap, packages, 5000);
|
|
48
|
+
label = `paquete ${root}${a.include_subpackages && packages.length > 1 ? ` y ${packages.length - 1} subpaquetes` : ""}`;
|
|
49
|
+
}
|
|
50
|
+
else if (a.transport) {
|
|
51
|
+
const tr = assertTrkorr(a.transport);
|
|
52
|
+
const head = (await orderHeaders(sap, [tr])).get(tr);
|
|
53
|
+
if (!head)
|
|
54
|
+
throw new ToolError("NOT_FOUND", `La orden ${tr} no existe en este sistema.`);
|
|
55
|
+
const root = head.parent || tr;
|
|
56
|
+
rows = await orderEntries(sap, root);
|
|
57
|
+
label = `orden ${root}${root !== tr ? ` (pediste la tarea ${tr})` : ""}`;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Lista explícita: cada nombre se resuelve con cualquier tipo con fuente.
|
|
61
|
+
const refs = a.objects.map((n) => ({ name: n.trim().toUpperCase(), types: ["PROG/", "CLAS/", "INTF/", "FUGR/FF", "DDLS/"], from: ["objects"] }));
|
|
62
|
+
return { refs: refs.slice(0, max), total: refs.length, label: `${refs.length} objetos indicados` };
|
|
63
|
+
}
|
|
64
|
+
const { source, other } = sourceObjectsOf(rows);
|
|
65
|
+
const groups = other.filter((o) => o.startsWith("R3TR FUGR ")).map((o) => o.slice("R3TR FUGR ".length));
|
|
66
|
+
const refs = [...source];
|
|
67
|
+
for (const g of groups)
|
|
68
|
+
refs.push(...(await expandGroup(sap, g)));
|
|
69
|
+
return { refs: refs.slice(0, max), total: refs.length, label };
|
|
70
|
+
}
|
|
71
|
+
/** Fuentes de un objeto: una sola, salvo clases (main y sus includes locales, los que existan). */
|
|
72
|
+
async function sourcesOf(c, uri, type) {
|
|
73
|
+
if (!type.startsWith("CLAS")) {
|
|
74
|
+
const url = ADTClient.isMainInclude(uri) ? uri : `${uri}/source/main`;
|
|
75
|
+
return [{ include: "main", text: await c.getObjectSource(url) }];
|
|
76
|
+
}
|
|
77
|
+
const includes = ADTClient.classIncludes((await c.objectStructure(uri)));
|
|
78
|
+
const out = [];
|
|
79
|
+
for (const inc of ["main", "definitions", "implementations", "testclasses"]) {
|
|
80
|
+
const url = includes.get(inc);
|
|
81
|
+
if (url)
|
|
82
|
+
out.push({ include: inc, text: await c.getObjectSource(url) });
|
|
83
|
+
}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
function matcher(text, regex) {
|
|
87
|
+
if (text.length > MAX_PATTERN)
|
|
88
|
+
throw new ToolError("INPUT", `El patrón tiene ${text.length} caracteres; el máximo es ${MAX_PATTERN}.`);
|
|
89
|
+
if (!regex) {
|
|
90
|
+
const needle = text.toUpperCase();
|
|
91
|
+
return (line) => line.toUpperCase().includes(needle);
|
|
92
|
+
}
|
|
93
|
+
let re;
|
|
94
|
+
try {
|
|
95
|
+
re = new RegExp(text, "i");
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
throw new ToolError("INPUT", `Expresión regular inválida: ${e.message}`);
|
|
99
|
+
}
|
|
100
|
+
return (line) => re.test(line);
|
|
101
|
+
}
|
|
102
|
+
const isComment = (line) => line.startsWith("*") || line.trimStart().startsWith('"');
|
|
103
|
+
const HIT = z.object({ object: z.string(), type: z.string(), include: z.string(), line: z.number().int(), text: z.string() });
|
|
104
|
+
export default defineTool({
|
|
105
|
+
name: "source_search",
|
|
106
|
+
title: "Buscar texto en el código",
|
|
107
|
+
description: "Busca un texto (o una expresión regular) en el código fuente de un paquete (con subpaquetes), de una orden de " +
|
|
108
|
+
"transporte o de una lista de objetos, y devuelve objeto, include, línea y la línea encontrada. Sirve donde " +
|
|
109
|
+
"where_used no llega: llamadas dinámicas, literales, código Z que el índice de referencias no cubre. Lee cada " +
|
|
110
|
+
"fuente, así que exige un alcance y tiene tope de objetos; los objetos que no se pudieron leer se listan aparte.",
|
|
111
|
+
access: "read",
|
|
112
|
+
timeoutMs: 180_000,
|
|
113
|
+
input: {
|
|
114
|
+
text: z.string().min(2).describe("Texto a buscar (sin distinguir mayúsculas), p. ej. RV_FLOW o 'CALL FUNCTION'"),
|
|
115
|
+
regex: z.boolean().default(false).describe("Tratar text como expresión regular (máx. 200 caracteres)"),
|
|
116
|
+
package: z.string().optional().describe("Alcance: paquete de desarrollo"),
|
|
117
|
+
include_subpackages: z.boolean().default(true),
|
|
118
|
+
transport: z.string().optional().describe("Alcance: orden (o tarea) de transporte"),
|
|
119
|
+
objects: z.array(z.string().min(1)).max(200).optional().describe("Alcance: lista de objetos por nombre"),
|
|
120
|
+
ignore_comments: z.boolean().default(false).describe("No contar líneas de comentario"),
|
|
121
|
+
// ~0,8 s por objeto medido en un 7.50 con 4 lecturas en paralelo: 150 objetos caben en el tiempo máximo de la tool.
|
|
122
|
+
max_objects: z.number().int().min(1).max(1000).default(150),
|
|
123
|
+
max_hits: z.number().int().min(1).max(2000).default(300),
|
|
124
|
+
},
|
|
125
|
+
output: {
|
|
126
|
+
scope: z.string(),
|
|
127
|
+
objects_in_scope: z.number().int(),
|
|
128
|
+
scanned: z.number().int().describe("Objetos leídos"),
|
|
129
|
+
truncated: z.boolean().describe("true si el alcance tenía más objetos que max_objects o más coincidencias que max_hits"),
|
|
130
|
+
hits: z.array(HIT),
|
|
131
|
+
skipped: z.array(z.object({ object: z.string(), reason: z.string() })).describe("Objetos que no se pudieron leer"),
|
|
132
|
+
},
|
|
133
|
+
async run(a, ctx) {
|
|
134
|
+
const scopes = [a.package, a.transport, a.objects?.length ? "x" : undefined].filter(Boolean).length;
|
|
135
|
+
if (scopes !== 1) {
|
|
136
|
+
throw new ToolError("INPUT", "Indica exactamente un alcance: package, transport u objects. Buscar en todo el repositorio no es viable leyendo fuentes.");
|
|
137
|
+
}
|
|
138
|
+
const match = matcher(a.text, a.regex);
|
|
139
|
+
const { sap } = ctx;
|
|
140
|
+
const { refs, total, label } = await scopeRefs(sap, a, a.max_objects);
|
|
141
|
+
const c = await sap.adt();
|
|
142
|
+
const hits = [];
|
|
143
|
+
const skipped = [];
|
|
144
|
+
let done = 0;
|
|
145
|
+
let scanned = 0;
|
|
146
|
+
ctx.progress?.(`Leyendo ${refs.length} objetos de ${label}…`, 0, refs.length);
|
|
147
|
+
await pool(refs, 4, async (ref) => {
|
|
148
|
+
ctx.signal?.throwIfAborted();
|
|
149
|
+
try {
|
|
150
|
+
const res = await resolveByTypePrefix(c, ref.name, ref.types);
|
|
151
|
+
if (!res) {
|
|
152
|
+
skipped.push({ object: ref.name, reason: "no se encontró con un tipo con fuente" });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
for (const src of await sourcesOf(c, res.uri, res.type)) {
|
|
156
|
+
src.text.split(/\r?\n/).forEach((line, i) => {
|
|
157
|
+
if (hits.length >= a.max_hits || (a.ignore_comments && isComment(line)) || !match(line))
|
|
158
|
+
return;
|
|
159
|
+
hits.push({ object: ref.name, type: res.type, include: src.include, line: i + 1, text: line.trim().slice(0, MAX_LINE) });
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
scanned++;
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
const te = normalizeError(e);
|
|
166
|
+
if (te.kind === "NETWORK" || te.kind === "AUTH" || te.kind === "CANCELLED")
|
|
167
|
+
throw te;
|
|
168
|
+
skipped.push({ object: ref.name, reason: `${te.kind}: ${te.message.slice(0, 120)}` });
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
ctx.progress?.(`${++done} de ${refs.length} objetos leídos`, done, refs.length);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
hits.sort((x, y) => x.object.localeCompare(y.object) || x.include.localeCompare(y.include) || x.line - y.line);
|
|
175
|
+
const truncated = total > refs.length || hits.length >= a.max_hits;
|
|
176
|
+
const structured = { scope: label, objects_in_scope: total, scanned, truncated, hits, skipped };
|
|
177
|
+
const head = `«${a.text}»${a.regex ? " (regex)" : ""} en ${label}: ${hits.length} coincidencias en ${new Set(hits.map((h) => h.object)).size} objetos · ` +
|
|
178
|
+
`${scanned} de ${total} objetos leídos` +
|
|
179
|
+
(total > refs.length ? ` — TOPE ALCANZADO: ${total - refs.length} objetos sin leer (sube max_objects o acota el alcance)` : "") +
|
|
180
|
+
(hits.length >= a.max_hits ? ` — tope de ${a.max_hits} coincidencias: puede haber más` : "");
|
|
181
|
+
const lines = hits.map((h) => `${h.object}${h.include !== "main" ? ` (${h.include})` : ""} L${h.line}: ${h.text}`);
|
|
182
|
+
const skip = skipped.length ? `\n\nNo se pudieron leer (${skipped.length}):\n${skipped.map((s) => ` ${s.object}: ${s.reason}`).join("\n")}` : "";
|
|
183
|
+
if (!hits.length)
|
|
184
|
+
return { text: `${head}.\nSe leyó el código y no aparece.${skip}`, structured };
|
|
185
|
+
return { text: `${head}\n\n${lines.join("\n")}${skip}`, structured };
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
//# sourceMappingURL=source_search.js.map
|
|
@@ -3,23 +3,12 @@ import { diffLines, unified } from "../../core/diff.js";
|
|
|
3
3
|
import { normalizeError, ToolError } from "../../core/errors.js";
|
|
4
4
|
import { resolveByTypePrefix, sqlLiteral } from "../../core/objects.js";
|
|
5
5
|
import { assertTrkorr } from "../../core/policy.js";
|
|
6
|
+
import { pool } from "../../core/concurrency.js";
|
|
6
7
|
import { selectRevisionPair, sourceObjectsOf, versionNumber } from "../../core/revisions.js";
|
|
7
8
|
import { describeOrder, orderHeaders } from "../../core/transport.js";
|
|
8
9
|
import { defineTool } from "../../core/tool.js";
|
|
9
10
|
const resolveRef = (c, ref) => resolveByTypePrefix(c, ref.name, ref.types);
|
|
10
11
|
const label = (r) => r ? `${r.version || "sin orden"} (v${versionNumber(r) || "?"}, ${r.date.slice(0, 10)}, ${r.author})` : "—";
|
|
11
|
-
/** Limita cuántas promesas corren a la vez (no saturar SAP). */
|
|
12
|
-
async function pool(items, n, fn) {
|
|
13
|
-
const out = new Array(items.length);
|
|
14
|
-
let next = 0;
|
|
15
|
-
await Promise.all(Array.from({ length: Math.min(n, items.length) }, async () => {
|
|
16
|
-
while (next < items.length) {
|
|
17
|
-
const i = next++;
|
|
18
|
-
out[i] = await fn(items[i]);
|
|
19
|
-
}
|
|
20
|
-
}));
|
|
21
|
-
return out;
|
|
22
|
-
}
|
|
23
12
|
export default defineTool({
|
|
24
13
|
name: "transport_diff",
|
|
25
14
|
timeoutMs: 180_000,
|
package/docs/TOOLS.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Referencia de tools — abap-adt-doZimple
|
|
2
2
|
|
|
3
|
-
Generado desde el código con `npm run docs`.
|
|
3
|
+
Generado desde el código con `npm run docs`. 51 tools en 9 grupos y 3 flujos guiados.
|
|
4
4
|
Producto de [DoZimple](https://dozimple.cl).
|
|
5
5
|
|
|
6
6
|
## Índice
|
|
7
7
|
|
|
8
8
|
- [Revisión de código y pases](#revision) — 5 tools: `transport_diff`, `transport_contents`, `co_change`, `inactive_objects`, `edit_preflight`
|
|
9
|
-
- [Calidad, ATC y remediación](#calidad) —
|
|
10
|
-
- [Exploración del repositorio](#exploracion) —
|
|
9
|
+
- [Calidad, ATC y remediación](#calidad) — 6 tools: `run_atc`, `atc_quickfix`, `api_release_state`, `sap_notes`, `syntax_check`, `run_unit_tests`
|
|
10
|
+
- [Exploración del repositorio](#exploracion) — 11 tools: `search_objects`, `get_source`, `where_used`, `source_search`, `object_versions`, `package_contents`, `ddic_type_info`, `transaction_info`, `function_modules`, `text_elements`, `enhancements`
|
|
11
11
|
- [Consulta de datos](#datos) — 2 tools: `sql_query`, `table_contents`
|
|
12
12
|
- [Diagnóstico de incidentes](#diagnostico) — 4 tools: `dumps`, `jobs`, `application_log`, `gateway_errors`
|
|
13
13
|
- [Documentación SAP](#documentacion) — 7 tools: `abap_feature_matrix`, `docs_search`, `docs_fetch`, `clean_core_objects`, `clean_core_object`, `abap_lint`, `docs_community_search`
|
|
@@ -185,6 +185,33 @@ Estado de liberación de un objeto SAP (clase, FM/BAPI, tabla, CDS…) por contr
|
|
|
185
185
|
|
|
186
186
|
\* obligatorio
|
|
187
187
|
|
|
188
|
+
### `sap_notes` — Notas SAP en el sistema (SNOTE)
|
|
189
|
+
|
|
190
|
+
Estado de notas SAP en este sistema, como en SNOTE: si está descargada, estado de implementación (completa, incompleta, versión anterior, se puede implementar, obsoleta, no se puede implementar), estado de tratamiento, versión, componente y título. Con notes=[…] responde por notas concretas (p. ej. «¿está la 2198647 en PRD?»); sin notas, lista las del estado pedido. Solo conoce las notas descargadas en el sistema: no consulta el portal.
|
|
191
|
+
|
|
192
|
+
| | |
|
|
193
|
+
|---|---|
|
|
194
|
+
| **Acceso** | Solo lectura |
|
|
195
|
+
| **Créditos** | [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) — Marcello Urbani (MIT, dependencia) |
|
|
196
|
+
|
|
197
|
+
| Parámetro | Tipo | Por defecto | Descripción |
|
|
198
|
+
|---|---|---|---|
|
|
199
|
+
| `system` | string | | Sistema SAP configurado. Obligatorio si hay varios y ninguno por defecto. |
|
|
200
|
+
| `notes` | lista de string | | |
|
|
201
|
+
| `implementation` | lista de `E` \| `U` \| `V` \| `N` \| `O` \| `-` | | Sin notes: filtrar por estado de implementación (E completa, U incompleta, V versión anterior, N se puede implementar, O obsoleta, - no se puede) |
|
|
202
|
+
| `component` | string | | Sin notes: prefijo de componente, p. ej. SD-BF o MM-PUR |
|
|
203
|
+
| `max` | number | 100 | |
|
|
204
|
+
|
|
205
|
+
\* obligatorio
|
|
206
|
+
|
|
207
|
+
Salida estructurada (`structuredContent`, además del texto):
|
|
208
|
+
|
|
209
|
+
| Campo | Tipo | Descripción |
|
|
210
|
+
|---|---|---|
|
|
211
|
+
| `notes` | lista de { note, downloaded, implementation, implementation_code, processing, version, component, title, processor } | |
|
|
212
|
+
| `total` | number | |
|
|
213
|
+
| `truncated` | boolean | |
|
|
214
|
+
|
|
188
215
|
### `syntax_check` — Chequeo de sintaxis SAP
|
|
189
216
|
|
|
190
217
|
Chequeo de sintaxis real de SAP (no abaplint). Si pasas `source`, se comprueba ESE código sin guardarlo: sirve para validar un cambio antes de escribirlo. Sin `source`, comprueba lo último guardado.
|
|
@@ -298,6 +325,41 @@ Lista de uso (where-used) de un objeto: quién lo referencia, con paquete y resp
|
|
|
298
325
|
|
|
299
326
|
\* obligatorio
|
|
300
327
|
|
|
328
|
+
### `source_search` — Buscar texto en el código
|
|
329
|
+
|
|
330
|
+
Busca un texto (o una expresión regular) en el código fuente de un paquete (con subpaquetes), de una orden de transporte o de una lista de objetos, y devuelve objeto, include, línea y la línea encontrada. Sirve donde where_used no llega: llamadas dinámicas, literales, código Z que el índice de referencias no cubre. Lee cada fuente, así que exige un alcance y tiene tope de objetos; los objetos que no se pudieron leer se listan aparte.
|
|
331
|
+
|
|
332
|
+
| | |
|
|
333
|
+
|---|---|
|
|
334
|
+
| **Acceso** | Solo lectura |
|
|
335
|
+
| **Créditos** | [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) — Marcello Urbani (MIT, dependencia)<br>[vibing-steampunk](https://github.com/oisee/vibing-steampunk) — oisee y contribuidores (MIT, idea) |
|
|
336
|
+
|
|
337
|
+
| Parámetro | Tipo | Por defecto | Descripción |
|
|
338
|
+
|---|---|---|---|
|
|
339
|
+
| `system` | string | | Sistema SAP configurado. Obligatorio si hay varios y ninguno por defecto. |
|
|
340
|
+
| `text` * | string | | Texto a buscar (sin distinguir mayúsculas), p. ej. RV_FLOW o 'CALL FUNCTION' |
|
|
341
|
+
| `regex` | boolean | false | Tratar text como expresión regular (máx. 200 caracteres) |
|
|
342
|
+
| `package` | string | | Alcance: paquete de desarrollo |
|
|
343
|
+
| `include_subpackages` | boolean | true | |
|
|
344
|
+
| `transport` | string | | Alcance: orden (o tarea) de transporte |
|
|
345
|
+
| `objects` | lista de string | | Alcance: lista de objetos por nombre |
|
|
346
|
+
| `ignore_comments` | boolean | false | No contar líneas de comentario |
|
|
347
|
+
| `max_objects` | number | 150 | |
|
|
348
|
+
| `max_hits` | number | 300 | |
|
|
349
|
+
|
|
350
|
+
\* obligatorio
|
|
351
|
+
|
|
352
|
+
Salida estructurada (`structuredContent`, además del texto):
|
|
353
|
+
|
|
354
|
+
| Campo | Tipo | Descripción |
|
|
355
|
+
|---|---|---|
|
|
356
|
+
| `scope` | string | |
|
|
357
|
+
| `objects_in_scope` | number | |
|
|
358
|
+
| `scanned` | number | Objetos leídos |
|
|
359
|
+
| `truncated` | boolean | true si el alcance tenía más objetos que max_objects o más coincidencias que max_hits |
|
|
360
|
+
| `hits` | lista de { object, type, include, line, text } | |
|
|
361
|
+
| `skipped` | lista de { object, reason } | Objetos que no se pudieron leer |
|
|
362
|
+
|
|
301
363
|
### `object_versions` — Versiones de un objeto
|
|
302
364
|
|
|
303
365
|
Historial de versiones de un objeto (fecha, autor, orden). Con show=N devuelve la fuente de la versión N de la lista (1 = la más reciente), para comparar con la actual.
|
|
@@ -405,6 +467,37 @@ Lee los símbolos de texto (TEXT-001…), textos de selección o encabezados de
|
|
|
405
467
|
|
|
406
468
|
\* obligatorio
|
|
407
469
|
|
|
470
|
+
### `enhancements` — Ampliaciones y BAdI
|
|
471
|
+
|
|
472
|
+
Qué ampliaciones hay y dónde. badi=NOMBRE: implementaciones de esa BAdI, nuevas y clásicas, activas o no. program + program_type (PROG, CLAS, FUGR): ampliaciones de código (puntos y secciones implícitos o explícitos) implementadas dentro de ese programa, clase o grupo — lo que cambia el comportamiento de un estándar sin tocar su código. prefix (p. ej. Z): implementaciones de ampliación de ese espacio de nombres, por tipo. Solo lectura.
|
|
473
|
+
|
|
474
|
+
| | |
|
|
475
|
+
|---|---|
|
|
476
|
+
| **Acceso** | Solo lectura |
|
|
477
|
+
| **Créditos** | [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) — Marcello Urbani (MIT, dependencia)<br>[vibing-steampunk](https://github.com/oisee/vibing-steampunk) — oisee y contribuidores (MIT, idea) |
|
|
478
|
+
|
|
479
|
+
| Parámetro | Tipo | Por defecto | Descripción |
|
|
480
|
+
|---|---|---|---|
|
|
481
|
+
| `system` | string | | Sistema SAP configurado. Obligatorio si hay varios y ninguno por defecto. |
|
|
482
|
+
| `badi` | string | | Definición de BAdI, p. ej. MB_MIGO_BADI |
|
|
483
|
+
| `program` | string | | Programa, clase o grupo de funciones ampliado, p. ej. SAPMV45A |
|
|
484
|
+
| `program_type` | `PROG` \| `CLAS` \| `FUGR` | "PROG" | |
|
|
485
|
+
| `prefix` | string | | Implementaciones cuyo nombre empieza así, p. ej. Z o ZSD |
|
|
486
|
+
| `tool_type` | string | | Con prefix: filtrar por tipo, p. ej. BADI_IMPL o HOOK_IMPL |
|
|
487
|
+
| `max` | number | 200 | |
|
|
488
|
+
|
|
489
|
+
\* obligatorio
|
|
490
|
+
|
|
491
|
+
Salida estructurada (`structuredContent`, además del texto):
|
|
492
|
+
|
|
493
|
+
| Campo | Tipo | Descripción |
|
|
494
|
+
|---|---|---|
|
|
495
|
+
| `mode` | `badi` \| `program` \| `prefix` | |
|
|
496
|
+
| `target` | string | |
|
|
497
|
+
| `rows` | lista de record | |
|
|
498
|
+
| `total` | number | |
|
|
499
|
+
| `truncated` | boolean | |
|
|
500
|
+
|
|
408
501
|
<a id="datos"></a>
|
|
409
502
|
## Consulta de datos
|
|
410
503
|
|
|
@@ -464,7 +557,7 @@ Filas de una tabla, vista o CDS, con columnas y filtro opcionales. Atajo de sql_
|
|
|
464
557
|
|
|
465
558
|
### `dumps` — Dumps (ST22)
|
|
466
559
|
|
|
467
|
-
Lista los dumps de ejecución (ST22): fecha, error, programa, usuario y texto corto. Filtra por usuario y por texto (error o programa). Con detail=N devuelve el dump N completo (qué pasó, análisis, dónde terminó, fuente, pila).
|
|
560
|
+
Lista los dumps de ejecución (ST22): fecha, error, programa, usuario y texto corto. Filtra por usuario y por texto (error o programa). Con detail=N devuelve el dump N completo (qué pasó, análisis, dónde terminó, fuente, pila). Con group_by (error, program, error_program, user, day) y days=N responde «los errores más frecuentes del periodo»: cuenta los dumps de los últimos N días agrupados, con primer y último caso, usuarios afectados y dónde terminó. La lista y el detalle salen del feed ADT (solo los más recientes); los grupos, de la cabecera de ST22 (SNAP_BEG).
|
|
468
561
|
|
|
469
562
|
| | |
|
|
470
563
|
|---|---|
|
|
@@ -479,9 +572,24 @@ Lista los dumps de ejecución (ST22): fecha, error, programa, usuario y texto co
|
|
|
479
572
|
| `contains` | string | | Filtra por error o programa, p. ej. CALL_FUNCTION_NOT_FOUND o ZFI_REPORTE |
|
|
480
573
|
| `max` | number | 20 | |
|
|
481
574
|
| `detail` | number | | Número de la lista para ver el dump completo |
|
|
575
|
+
| `group_by` | `error` \| `program` \| `error_program` \| `user` \| `day` | | Agrupar los dumps del periodo: por error, programa, error+programa, usuario o día |
|
|
576
|
+
| `days` | number | 7 | Periodo hacia atrás para group_by (días) |
|
|
482
577
|
|
|
483
578
|
\* obligatorio
|
|
484
579
|
|
|
580
|
+
Salida estructurada (`structuredContent`, además del texto):
|
|
581
|
+
|
|
582
|
+
| Campo | Tipo | Descripción |
|
|
583
|
+
|---|---|---|
|
|
584
|
+
| `mode` | `list` \| `detail` \| `groups` | |
|
|
585
|
+
| `total` | number | Dumps considerados (feed leído o filas del periodo) |
|
|
586
|
+
| `period_days` | number | |
|
|
587
|
+
| `since` | string | AAAAMMDD desde el que se contó |
|
|
588
|
+
| `truncated` | boolean | true si el periodo tiene más dumps de los que se pudieron leer |
|
|
589
|
+
| `group_by` | `error` \| `program` \| `error_program` \| `user` \| `day` | |
|
|
590
|
+
| `groups` | lista de { key, count, first, last, users, where } | |
|
|
591
|
+
| `dumps` | lista de { n, error, program, user } | |
|
|
592
|
+
|
|
485
593
|
### `jobs` — Jobs de fondo (SM37)
|
|
486
594
|
|
|
487
595
|
Jobs de fondo por nombre (admite *), usuario, estado y fecha, con sus pasos (programa y variante). Responde «¿corrió anoche el job de X?, ¿por qué se canceló?». El log detallado del job sigue en SM37.
|
package/package.json
CHANGED