方向反转:此前 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 重写为单源开发流程。
15 KiB
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 |
第三部分:半实现 / 受限功能列表
🔴 关键限制(会导致功能不可用)
-
create通用命令不支持 6 种类型 —client/_ddic.py::_build_create_body仅覆盖 9 种类型,对其余直接raise ValueError("不支持的对象类型"):- ❌
include、cdsview、messageclass、view、searchhelp、lockobject - 其中
cdsview有独立的cds create命令可绕过;其余 5 种完全无法创建。
- ❌
-
CDS View 端点不一致(潜在 Bug) — 两套 URI 并存:
types.py注册的 cdsview:/sap/bc/adt/dds/ddl/sources/{name}+/contentcds.py命令 &client._ddic.py硬编码:/sap/bc/adt/ddic/ddlsources/{name}+/source/main- 后果:
download --type cdsview走 types.py 的 URI,可能 404;而cds download走另一套 URI。
-
tabletype创建必须传--definition—_create_ddic对 tabletype 无默认模板,缺line_type时直接raise CreateError。
🟡 功能降级(有 fallback 但不完整)
-
info命令 Accept 头映射不全 —cmd_info的INFO_ACCEPT字典只覆盖 9 种类型,以下 7 种回退到application/xml(可能触发 406 后再回退):- ⚠️
structure、include、cdsview、messageclass、view、searchhelp、lockobject
- ⚠️
-
list/whereused/search的 ADT 类型码映射不全 —search.py::_TYPE_FILTER_MAP缺 2 种:- ⚠️
searchhelp、lockobject(会原样传 key 当类型码,ADT 端可能不识别)
- ⚠️
-
NW 7.40 激活空响应 —
client/_source.py::activate注释标注:NW 7.40 返回空响应时已实现「双重激活」变通,但仍可能提示「对象仍 inactive,请在 SE09 手动激活」(返回 W 级消息)。 -
tabletype用 VIT 端点 —parse_object_name对 tabletype 做大写处理,注释说明「VIT 端点需要大写名称以返回完整元数据」,暗示该端点行为特殊。
🟢 设计限制(非 Bug)
-
functiongroup是容器对象 —has_source=False,download/sync/upload/format 等源码操作明确拒绝(提示「不包含可编辑的源代码文件」)。 -
scaffold 模板中的
TODO—scaffold.py模板字符串内的\" TODO: 填充数据/\" TODO: 调用 BAPI 函数是生成的代码占位符,非 CLI 自身 TODO。 -
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 种类型无法创建)。
报告结束。如需复查某条判定,可按文中引用的文件路径与行号定位源码。