Files
sap-cli-skill/docs/功能矩阵分析.md
T
吴让宇 c5905a5b1e refactor: 本仓升为唯一源(原 sap-cli 源码仓归档)
方向反转:此前 SKILL.md 是「模板渲染产物」、sap-cli 是源;现 sap-cli 归档,
sap-cli-skill 承接开发与分发,SKILL.md 回归手工维护的正本。

迁移(来自 sap-cli,共 104 文件):
- tests/           692 例测试(15 个文件的内联 sys.path 改指 assets/)
- openspec/        SDD 规格与归档变更(42 文件)
- docs/            开发文档与 ADT 原理(含 dev/CLAUDE.md、AGENTS.md)
- .claude/         rules 副本 + settings.json(供 Claude Code)
- .github/ .hermes/ .pre-commit-config.yaml .editorconfig CLAUDE.md
- scripts/ 保持仅 setup.py(pack_skill.py 已随旧仓归档,不迁)

修复(迁移暴露的真实缺陷):
- assets/pyproject.toml 的 build-backend 写作 `setuptools.backends._legacy:_Backend`,
  该模块在 setuptools 中不存在 → `pip install -e` 从来装不上。改为 build_meta。
  实测:临时 venv 安装成功,sap-cli --help 正常列出 31 个命令
- pyproject readme 指向不存在的 assets/README.md(editable 安装会失败)→ 改内联文本
- pyproject urls 改指 sap-cli-skill

机制调整:
- .github/workflows/ci.yml 适配 assets/ 布局;顶部注明该工作流仅 GitHub 执行,
  本仓在 Gitee 不会自动跑
- pre-commit 增本地测试门禁(Gitee 上真正生效的那道)
- .gitignore 合并旧仓完整规则(保留 log/ 下 md 知识库入库,只忽略运行日志)
- 大文件上限 100KB→1MB(架构图 512KB)

守卫测试 tests/unit/test_repo_guards.py(10 → 18 例):
- SKILL.md 须记录 parser 全部 CLI 命令 / 铁律 1-5 须为真实小节标题 / 示例不得违反铁律 5
- references/ 规则齐备;.claude/rules 与 references 必须一致(实测抓到一次真实漂移)
- VERSION == sapcli.__version__ == README 版本
- 仓内不得再出现 pack_skill.py / skill-src(防废弃流程回潮)

698 tests OK;editable 安装与 CLI 入口经临时 venv 实测通过。
docs/RELEASING.md 重写为单源开发流程。
2026-09-11 00:40:15 +08:00

193 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# sap-cli 功能矩阵分析报告
> 分析基于源码静态审查(`sapcli/types.py`、`sapcli/cli/parser.py`、`sapcli/commands/*`、`sapcli/client/*`)。
> 生成时间:2026-06-18
---
## 第一部分:已实现的命令完整列表
**25 个顶层命令**(含子命令则为 **38 个操作**)。命令分发逻辑见 `sapcli/cli/app.py``command_map`(外加 `config`/`auth` 两个不走 SAP 连接的命令)。
| # | 命令 | 子命令 | 所在文件 | 功能描述 | 是否需 SAP 连接 |
|---|------|--------|----------|----------|----------------|
| 1 | `download` | — | `commands/crud.py::cmd_download` | 下载对象源代码到本地文件 | ✅ |
| 2 | `sync` | — | `commands/crud.py::cmd_sync` | 同步单对象源码并激活(检查→写入→语法检查→激活) | ✅ |
| 2b| `sync --all` | — | `commands/batch.py::cmd_sync_all` | 批量同步清单内所有对象(拓扑排序) | ✅ |
| 3 | `upload` | — | `commands/upload.py::cmd_upload` | 仅上传源码(锁定→写入→解锁,不检查不激活) | ✅ |
| 4 | `syntax-check` | — | `commands/syntax_check.py::cmd_syntax_check` | 远程对象语法检查(不上传不激活) | ✅ |
| 5 | `activate` | — | `commands/activate.py::cmd_activate` | 单独激活对象(单/批量模式) | ✅ |
| 6 | `delete` | — | `commands/crud.py::cmd_delete` | 从 SAP 删除对象(交互确认) | ✅ |
| 7 | `info` | — | `commands/crud.py::cmd_info` | 查询对象元数据 | ✅ |
| 8 | `create` | — | `commands/crud.py::cmd_create` | 创建开发对象(含 DDIC/函数组) | ✅ |
| 9 | `diff` | — | `commands/diff_cmd.py::cmd_diff` | 本地 vs SAP 代码差异对比 | ✅ |
| 10 | `list` | — | `commands/search.py::cmd_list` | 列出 SAP 对象(类型/包/前缀过滤) | ✅ |
| 11 | `whereused` | — | `commands/search.py::cmd_whereused` | Where-Used 引用查询 | ✅ |
| 12 | `search` | — | `commands/search.py::cmd_search` | 源代码搜索 | ✅ |
| 13 | `check` | — | `commands/quality.py::cmd_check` | ATC 代码检查 | ✅ |
| 14 | `format` | — | `commands/quality.py::cmd_format` | 代码格式化(Pretty Printer + 写回 + 激活) | ✅ |
| 15 | `transport` | `list` | `commands/transport.py::_transport_list` | 列出可修改的传输请求 | ✅ |
| 15b| | `info` | `::_transport_info` | 查看传输请求详情 | ✅ |
| 15c| | `release` | `::_transport_release` | 释放传输请求(交互确认) | ✅ |
| 15d| | `objects` | `::_transport_objects` | 列出传输请求中对象 | ✅ |
| 16 | `package` | `create` | `commands/package_cmd.py::_package_create` | 创建 ABAP 包 | ✅ |
| 16b| | `info` | `::_package_info` | 查看包详情 | ✅ |
| 16c| | `list` | `::_package_list` | 列出包中对象 | ✅ |
| 17 | `cds` | `download` | `commands/cds.py::_cds_download` | 下载 CDS View DDL 源码 | ✅ |
| 17b| | `sync` | `::_cds_sync` | 同步本地 DDL 到 SAP(含创建/激活) | ✅ |
| 17c| | `create` | `::_cds_create` | 创建新的 CDS View | ✅ |
| 18 | `init` | — | `commands/batch.py::cmd_init` | 扫描本地→查询 SAP→生成 manifest.json | ✅ |
| 19 | `refresh` | — | `commands/batch.py::cmd_refresh` | 刷新清单中对象状态 | ✅ |
| 20 | `analyze` | — | `commands/analyze.py::cmd_analyze` | 本地依赖自动分析(正则匹配) | ✅ |
| 21 | `scaffold` | — | `commands/scaffold.py::cmd_scaffold` | 项目模板创建(4 种模板) | ❌(本地) |
| 22 | `config` | `show`/`list-profiles`/`set` | `commands/config_cmd.py::cmd_config` | 配置管理 | ❌ |
| 23 | `auth` | `login`/`logout`/`status` | `auth.py::cmd_auth_*` | keyring 密钥管理 | ❌ |
| 24 | `show-table` | — | `commands/ddl_query.py::cmd_show_table` | 查看 DDIC 表字段结构(SE11 | ✅ |
| 25 | `read-table` | — | `commands/ddl_query.py::cmd_read_table` | 查询表数据(freestyle SQL | ✅ |
| 26 | `run-program` | — | `commands/program_run.py::cmd_run_program` | 远程执行 ABAP 程序(SA38 | ✅ |
---
## 第二部分:已注册的对象类型完整列表
**16 个对象类型**,全部在 `sapcli/types.py` 中通过 `_register()` 注册。
| # | key | label | 对象端点 (obj_uri) | 源码端点 (src_uri) | has_source | create_content_type |
|---|-----|-------|-------------------|-------------------|-----------|---------------------|
| 1 | `report` | 程序(REPORT) | `/programs/programs/{name}` | `/programs/programs/{name}/source/main` | ✅ | `vnd.sap.adt.programs.programs.v2+xml` |
| 2 | `class` | 类(CLASS) | `/oo/classes/{name}` | `/oo/classes/{name}/source/main` | ✅ | `vnd.sap.adt.oo.classes.v1+xml` |
| 3 | `function` | 函数模块 | `/functions/groups/{group}/fmodules/{name}` | `.../source/main` | ✅ | `vnd.sap.adt.functions.fmodules.v1+xml` |
| 4 | `functiongroup` | 函数组 | `/functions/groups/{name}` | — (None) | ❌ | `vnd.sap.adt.functions.groups.v2+xml` |
| 5 | `interface` | 接口 | `/oo/interfaces/{name}` | `/oo/interfaces/{name}/source/main` | ✅ | `vnd.sap.adt.oo.interfaces.v1+xml` |
| 6 | `domain` | 域(DOMAIN) | `/ddic/domains/{name}` | `/ddic/domains/{name}/source/main` | ✅ | `vnd.sap.adt.domains.v2+xml` |
| 7 | `dataelement` | 数据元素 | `/ddic/dataelements/{name}` | `.../source/main` | ✅ | `vnd.sap.adt.dataelements.v2+xml` |
| 8 | `table` | 透明表 | `/ddic/tables/{name}` | `.../source/main` | ✅ | `vnd.sap.adt.ddic.tables.v1+xml` |
| 9 | `structure` | 结构 | `/ddic/structures/{name}` | `.../source/main` | ✅ | `vnd.sap.adt.ddic.structures.v1+xml` |
| 10 | `tabletype` | 表类型 | `/vit/.../object_name/{name}` | — (None) | ❌ | `vnd.sap.adt.ddic.tabletypes.v1+xml` |
| 11 | `include` | Include程序 | `/programs/includes/{name}` | `.../source/main` | ✅ | `vnd.sap.adt.programs.includes.v2+xml` |
| 12 | `cdsview` | CDS视图 | `/dds/ddl/sources/{name}` | `/dds/ddl/sources/{name}/content` | ✅ | `text/plain` |
| 13 | `messageclass` | 消息类 | `/oo/t100/messages/classes/{name}` | — (None) | ❌ | `vnd.sap.adt.t100.message.classes.v1+xml` |
| 14 | `view` | 数据库视图 | `/ddic/views/{name}` | `.../source/main` | ✅ | `vnd.sap.adt.ddic.views.v1+xml` |
| 15 | `searchhelp` | 搜索帮助 | `/ddic/searchhelps/{name}` | — (None) | ❌ | `vnd.sap.adt.ddic.searchhelps.v1+xml` |
| 16 | `lockobject` | 锁对象 | `/ddic/lockobjects/{name}` | — (None) | ❌ | `vnd.sap.adt.ddic.lockobjects.v1+xml` |
**关键判定规则**`has_source=False` 的 5 种类型(functiongroup / tabletype / messageclass / searchhelp / lockobject**无法**执行源码类操作(download / sync / upload / syntax-check / format / diff),命令会在 `parse_object_name` 后因 `src_uri is None``InvalidNameError`
### client.py 中各操作的 HTTP 实现
| 操作 | client 方法 | HTTP 调用 | 所在 Mixin |
|------|------------|----------|-----------|
| 读源码 | `get_source` | `GET {src_uri}` (text/plain) | `_source.py` |
| 写源码 | `set_source` | `PUT {src_uri}` (含 corrNr 重试) | `_source.py` |
| 锁定 | `lock` | `POST {obj_uri}?_action=LOCK` | `_source.py` |
| 解锁 | `unlock` | `POST {obj_uri}?_action=UNLOCK` | `_source.py` |
| 激活 | `activate` | `POST /adt/activation?method=activate` | `_source.py` |
| 语法检查 | `syntax_check` | `POST /adt/activation?method=check` | `_source.py` |
| 删除 | `delete_object` | `DELETE {obj_uri}` (先锁) | `_ddic.py` |
| 创建(通用) | `create_object` | `POST {collection_uri}` | `_ddic.py` |
| 创建(DDIC) | `create_ddic_object` | `POST`/`PUT {src_uri}` (DDL) | `_ddic.py` |
| 状态查询 | `get_object_status` | `GET {obj_uri}` (+ lock 探测 corrNr) | `_ddic.py` |
| 存在检查 | `object_exists` | `GET {obj_uri}` (200/404) | `_base.py` |
| 函数组创建 | `create_function_group` | `POST {collection}` | `_ddic.py` |
| CDS 源码 | `get_cds_source`/`create_cds` | `GET/POST /ddic/ddlsources/*` | `_ddic.py` |
| 对象列表 | `list_objects` | `GET /repository/informationsystem/search` | `_search.py` |
| 引用查询 | `where_used` | `POST /usage/whereusedlist` | `_search.py` |
| 代码搜索 | `search_code` | `POST /repository/structuredsearch` | `_search.py` |
| ATC 检查 | `atc_check` | `POST /atos/checks` | `_ddic.py` |
| 格式化 | `pretty_print` | `POST /prettyprinter` | `_ddic.py` |
| 表结构 | `get_table_fields` | `GET /datapreview/ddic/{name}/metadata` | `_ddic.py` |
| 表数据 | `query_table_data` | `POST /datapreview/freestyle` | `_ddic.py` |
| 运行程序 | `run_program` | `POST /programs/programrun/{name}` | `_ddic.py` |
---
## 第三部分:半实现 / 受限功能列表
### 🔴 关键限制(会导致功能不可用)
1. **`create` 通用命令不支持 6 种类型** — `client/_ddic.py::_build_create_body` 仅覆盖 9 种类型,对其余直接 `raise ValueError("不支持的对象类型")`
-`include``cdsview``messageclass``view``searchhelp``lockobject`
- 其中 `cdsview` 有独立的 `cds create` 命令可绕过;其余 5 种**完全无法创建**。
2. **CDS View 端点不一致(潜在 Bug** — 两套 URI 并存:
- `types.py` 注册的 cdsview`/sap/bc/adt/dds/ddl/sources/{name}` + `/content`
- `cds.py` 命令 & `client._ddic.py` 硬编码:`/sap/bc/adt/ddic/ddlsources/{name}` + `/source/main`
- 后果:`download --type cdsview` 走 types.py 的 URI,可能 404;而 `cds download` 走另一套 URI。
3. **`tabletype` 创建必须传 `--definition`** — `_create_ddic` 对 tabletype 无默认模板,缺 `line_type` 时直接 `raise CreateError`
### 🟡 功能降级(有 fallback 但不完整)
4. **`info` 命令 Accept 头映射不全** — `cmd_info``INFO_ACCEPT` 字典只覆盖 9 种类型,以下 7 种回退到 `application/xml`(可能触发 406 后再回退):
- ⚠️ `structure``include``cdsview``messageclass``view``searchhelp``lockobject`
5. **`list`/`whereused`/`search` 的 ADT 类型码映射不全** — `search.py::_TYPE_FILTER_MAP` 缺 2 种:
- ⚠️ `searchhelp``lockobject`(会原样传 key 当类型码,ADT 端可能不识别)
6. **NW 7.40 激活空响应**`client/_source.py::activate` 注释标注:NW 7.40 返回空响应时已实现「双重激活」变通,但仍可能提示「对象仍 inactive,请在 SE09 手动激活」(返回 W 级消息)。
7. **`tabletype` 用 VIT 端点** — `parse_object_name` 对 tabletype 做大写处理,注释说明「VIT 端点需要大写名称以返回完整元数据」,暗示该端点行为特殊。
### 🟢 设计限制(非 Bug
8. **`functiongroup` 是容器对象** — `has_source=False`download/sync/upload/format 等源码操作明确拒绝(提示「不包含可编辑的源代码文件」)。
9. **scaffold 模板中的 `TODO`**`scaffold.py` 模板字符串内的 `\" TODO: 填充数据` / `\" TODO: 调用 BAPI 函数` 是**生成的代码占位符**,非 CLI 自身 TODO。
10. **DDIC 源码下载语义** — domain/dataelement/table/structure 虽有 `src_uri`,但其 `source/main` 返回的是 DDL/XML 定义而非可编辑 ABAP 文本,download 技术可用但语义需注意。
### 未发现的问题
- ✅ 未发现 `NotImplementedError``pass` 占位方法、或裸 `...` 存根。
- ✅ 所有 parser 注册的命令在 `app.py` 均有对应 handler(无悬空命令)。
---
## 第四部分:命令 × 类型 完整交叉矩阵
**图例**:✅ 完全支持 | ⚠️ 部分支持/有降级 | ❌ 不支持 | ➖ 不适用(命令与对象类型无关)
> 仅列出「带 `--type` 参数、面向单个对象类型」的 14 个命令。`init`/`refresh`/`sync --all`/`analyze`/`config`/`auth`/`transport`/`package`/`scaffold`/`show-table`/`read-table`/`run-program`/`cds` 为项目级或领域专属命令,对所有类型行为一致,不纳入交叉矩阵。
| 命令 \ 类型 | report | class | function | functiongroup | interface | domain | dataelement | table | structure | tabletype | include | cdsview | messageclass | view | searchhelp | lockobject |
|:---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| **download** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅¹ | ❌ | ✅ | ❌ | ❌ |
| **sync** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅¹ | ❌ | ✅ | ❌ | ❌ |
| **upload** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅¹ | ❌ | ✅ | ❌ | ❌ |
| **syntax-check** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅¹ | ❌ | ✅ | ❌ | ❌ |
| **format** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅¹ | ❌ | ✅ | ❌ | ❌ |
| **diff** | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅¹ | ❌ | ✅ | ❌ | ❌ |
| **info** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️² | ⚠️² | ⚠️² | ⚠️² | ⚠️² | ⚠️² | ⚠️² | ⚠️² |
| **delete** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **activate** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **create** | ✅ | ✅ | ✅³ | ✅³ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️⁴ | ❌⁵ | ⚠️⁶ | ❌⁵ | ❌⁵ | ❌⁵ | ❌⁵ |
| **check (ATC)** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **whereused** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️⁷ | ⚠️⁷ |
| **list** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️⁷ | ⚠️⁷ |
| **search** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ⚠️⁷ | ⚠️⁷ |
**脚注**
- ¹ **cdsview 经通用源码命令**:走 `types.py``/dds/ddl/sources/{name}/content` 端点,与专用 `cds` 命令的 `/ddic/ddlsources/{name}/source/main` 不一致,可能失败(见第三部分#2)。
- ² **info 降级**:无显式 Accept 头,回退 `application/xml`(见第三部分#4)。
- ³ **function/functiongroup 特殊创建**function 自动创建函数组;functiongroup 走 `create_function_group` 专用路径。
-**tabletype 创建受限**:必须 `--definition` 指定 line_type,无默认模板(见第三部分#3)。
-**create 不支持**`_build_create_body` 直接 `ValueError`(见第三部分#1)。`messageclass`/`view`/`searchhelp`/`lockobject`/`include` 无任何创建路径。
-**cdsview 经通用 create 失败**:但可用专用 `cds create` 命令。
-**ADT 类型码缺失**`_TYPE_FILTER_MAP` 无 searchhelp/lockobject 映射,过滤参数原样透传(见第三部分#5)。
### 支持度统计
| 命令 | ✅ 完全 | ⚠️ 部分 | ❌ 不支持 |
|:---|:---:|:---:|:---:|
| download/sync/upload/syntax-check/format/diff | 11 | 0 | 5 |
| info | 9 | 7 | 0 |
| delete | 16 | 0 | 0 |
| activate / check | 16 | 0 | 0 |
| create | 9 | 2 | 5 |
| whereused / list / search | 14 | 2 | 0 |
**结论**`delete`/`activate`/`check` 三命令对全部 16 种类型通用;源码类 6 命令对 11 种 has_source 类型通用;**`create` 是缺口最大的命令**(6 种类型无法创建)。
---
*报告结束。如需复查某条判定,可按文中引用的文件路径与行号定位源码。*