方向反转:此前 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
15 KiB
Spec-to-Code 需求覆盖验证报告
生成时间: 2026-06-09 对照
openspec/specs/下 7 个 spec 文件 vssapcli/源码
1. Connection (connection/spec.md)
| # | Scenario | 状态 | 说明 |
|---|---|---|---|
| 1.1 | Successful login | ✅已实现 | client.py:login() 使用 HTTP Basic Auth,app.py:101-102 打印"登录成功" |
| 1.2 | Login failure | ✅已实现 | client.py:62 raise LoginError,app.py:144-146 捕获 SapCliError 以非零退出码退出 |
| 1.3 | Config not found | ✅已实现 | config.py:25-29 SAPConfig.__post_init__ 检查缺失字段抛 ConfigError,app.py:91-95 打印缺失提示和环境变量名称 |
| 1.4 | Load from config file | ✅已实现 | config.py:32-49 搜索并读取 config.ini |
| 1.5 | Environment variable override | ✅已实现 | config.py:53-57 os.environ.get("SAP_XXX", sap.get(...)) 环境变量优先 |
| 1.6 | Profile selection (--profile) | ❌未实现 | parser.py:69 定义了 --profile 参数,但 config.py:51 硬编码 cfg["SAP"],未使用 args.profile |
| 1.7 | Client number default "100" | ✅已实现 | config.py:55 sap.get("client", "100") |
| 1.8 | Custom config path (--config) | ✅已实现 | parser.py:67 + app.py:88 传入 args.config |
| 1.9 | No command specified | ✅已实现 | app.py:77-79 检测 not args.command 打印帮助并以 exit(1) 退出 |
小计: 8/9 ✅, 1 ❌
2. Object Lifecycle (object-lifecycle/spec.md)
| # | Scenario | 状态 | 说明 |
|---|---|---|---|
| 2.1 | Create with default template | ⚠️部分实现 | crud.py:31-58 仅有 report/class/function/interface 四种模板,其他 12 种类型无默认模板 |
| 2.2 | Create with custom source file | ✅已实现 | crud.py:690-695 --source 参数读取文件 |
| 2.3 | Create DDIC with --definition | ✅已实现 | crud.py:771-876 domain/dataelement/table/structure/tabletype 有 JSON→XML 转换 |
| 2.4 | Create message class | ❌未实现 | types.py 未注册 messageclass 对象类型,无法 create/download/info/delete |
| 2.5 | Create database view | ❌未实现 | types.py 未注册 view 对象类型 |
| 2.6 | Create search help | ❌未实现 | types.py 未注册 searchhelp 对象类型 |
| 2.7 | Create lock object | ❌未实现 | types.py 未注册 lockobject 对象类型 |
| 2.8 | Create already existing object | ✅已实现 | crud.py:642-644 检测 object_exists 后抛 ObjectAlreadyExistsError |
| 2.9 | Create with --corr_nr | ✅已实现 | parser.py:110 + crud.py:652-655 传入传输请求号 |
| 2.10 | Download existing object | ✅已实现 | crud.py:73-122 完整实现,文件名小写+.abap |
| 2.11 | Download non-existent object | ✅已实现 | crud.py:95-98 检测不存在抛 ObjectNotFoundError |
| 2.12 | Download function type (group/module) | ✅已实现 | types.py:152-167 解析 组名/模块名 格式 |
| 2.13 | Download type without source | ✅已实现 | crud.py:82-85 检测 src_uri is None 报错 |
| 2.14 | Download view DDL source | ❌未实现 | view 类型未在 types.py 注册 |
| 2.15 | Full sync success (lock→write→unlock→check→activate) | ✅已实现 | crud.py:226-317 完整五步流程,每步报告进度 |
| 2.16 | Auto-create on sync | ✅已实现 | crud.py:213-224 检测不存在则自动创建空对象 |
| 2.17 | Syntax check failure | ✅已实现 | crud.py:277-287 报告行号和错误描述 |
| 2.18 | Activation failure | ✅已实现 | crud.py:308-317 报告激活错误详情 |
| 2.19 | Sync with --corr_nr | ✅已实现 | crud.py:230-231 贯穿整个流程 |
| 2.20 | Query existing object info | ✅已实现 | crud.py:371-449 显示名称/类型/描述/状态/负责人等元数据 |
| 2.21 | Query non-existent object | ✅已实现 | crud.py:406-408 404 检测 |
| 2.22 | Query message class info | ❌未实现 | messageclass 未注册 |
| 2.23 | Query view info | ❌未实现 | view 未注册 |
| 2.24 | Delete with confirmation | ✅已实现 | crud.py:483-486 input("yes") 确认 |
| 2.25 | Delete cancelled | ✅已实现 | crud.py:484-486 非确认则取消 |
| 2.26 | Delete non-existent object | ✅已实现 | crud.py:474-477 检测不存在抛 ObjectNotFoundError |
| 2.27 | Delete message class | ❌未实现 | messageclass 未注册 |
| 2.28 | Delete lock object | ❌未实现 | lockobject 未注册 |
小计: 16/28 ✅, 7 ❌, 1 ⚠️
缺失对象类型汇总
以下 5 种对象类型在 spec 中列出但未在 types.py 中注册:
- messageclass — 无 ObjectTypeConfig、无 URI 模板、无 create/download/info/delete
- view — 无 ObjectTypeConfig、无 URI 模板
- searchhelp — 无 ObjectTypeConfig、无 URI 模板
- lockobject — 无 ObjectTypeConfig、无 URI 模板
- include — 无 ObjectTypeConfig(仅在 scanner.py 的 DIRECTORY_TYPE_MAP 中映射,但 parse_object_name 会报错)
- cdsview — 无 ObjectTypeConfig(CDS 通过独立命令
cds子系统处理,不走通用 CRUD 流程)
3. Batch Operations (batch-ops/spec.md)
| # | Scenario | 状态 | 说明 |
|---|---|---|---|
| 3.1 | Init new project | ✅已实现 | batch.py:19-96 扫描→查询→生成 manifest.json |
| 3.2 | Manifest tracks objects | ✅已实现 | manifest.py 完整 CRUD + to_dict/from_dict 持久化 |
| 3.3 | Query pending objects | ✅已实现 | manifest.py:139-146 pending_objects() 方法 |
| 3.4 | Scan standard directories | ✅已实现 | scanner.py:13-29 14 种目录映射 + _scan_flat_directory |
| 3.5 | Function directory structure | ✅已实现 | scanner.py:91-121 子目录=组名,文件名=模块名 |
| 3.6 | Skip hidden files | ✅已实现 | scanner.py:77 filename.startswith(".") 跳过 |
| 3.7 | Sort by dependency | ✅已实现 | sorter.py:40-109 Kahn 算法拓扑排序 + TYPE_PRIORITY |
| 3.8 | Detect cyclic dependency | ✅已实现 | sorter.py:100-105 抛 CyclicDependencyError 并显示循环链 |
| 3.9 | Ignore external dependencies | ✅已实现 | sorter.py:63-65 dep not in names 时忽略 |
| 3.10 | Sync all objects (--all) | ✅已实现 | batch.py:164-328 完整批量同步 |
| 3.11 | Dry run mode (--dry-run) | ✅已实现 | batch.py:172,224-241 仅显示计划不执行 |
| 3.12 | Fail fast mode (--fail-fast) | ✅已实现 | batch.py:173,287-289 失败立即 break |
| 3.13 | Refresh object status | ✅已实现 | batch.py:99-161 逐个查询 SAP 状态更新清单 |
小计: 13/13 ✅
4. Search and Browse (search-browse/spec.md)
| # | Scenario | 状态 | 说明 |
|---|---|---|---|
| 4.1 | List by type with prefix | ⚠️部分实现 | search.py:31-71 命令层已实现,但调用 client.list_objects() — client.py 中缺少该方法定义 |
| 4.2 | List without filter | ⚠️部分实现 | 同上,命令层有,client 层缺失 |
| 4.3 | Find references (whereused) | ⚠️部分实现 | search.py:74-109 命令层已实现,但调用 client.where_used() — client.py 中缺少该方法定义 |
| 4.4 | No references found | ⚠️部分实现 | search.py:97-98 显示无引用信息,但依赖缺失的 client 方法 |
| 4.5 | Search by keyword | ⚠️部分实现 | search.py:112-147 命令层已实现,但调用 client.search_code() — client.py 中缺少该方法定义 |
| 4.6 | Search no results | ⚠️部分实现 | search.py:136-137 显示无结果信息,但依赖缺失的 client 方法 |
| 4.7 | Diff with differences | ⚠️部分实现 | diff_cmd.py:16-90 命令层完整,但调用 client.read_source_for_diff() — client.py 中缺少该方法定义 |
| 4.8 | Diff identical | ⚠️部分实现 | diff_cmd.py:70-72 检测无差异,但依赖缺失的 client 方法 |
| 4.9 | Download CDS view | ⚠️部分实现 | cds.py:31-61 命令层完整,但调用 client.get_cds_source() — client.py 中缺少该方法定义 |
| 4.10 | Create CDS view | ⚠️部分实现 | cds.py:130-171 命令层完整,但调用 client.create_cds() — client.py 中缺少该方法定义 |
| 4.11 | Query package info | ⚠️部分实现 | package_cmd.py:56-77 命令层完整,但调用 client.get_package_info() — client.py 中缺少该方法定义 |
| 4.12 | Create package | ⚠️部分实现 | package_cmd.py:29-53 命令层完整,但调用 client.create_package() — client.py 中缺少该方法定义 |
小计: 0/12 ✅, 12 ⚠️ (命令层均已完成,但 ADTClient 缺少对应的 API 方法)
client.py 缺失的方法清单
以下方法被 commands/ 调用但未在 client.py 中定义:
list_objects(obj_type, package, prefix)— 被 search.py, package_cmd.py 调用where_used(name, obj_uri, adt_type)— 被 search.py 调用search_code(query, obj_type)— 被 search.py 调用read_source_for_diff(name, obj_type)— 被 diff_cmd.py 调用get_cds_source(name)— 被 cds.py 调用create_cds(name, description, ddl_source)— 被 cds.py 调用create_package(name, description, superpackage)— 被 package_cmd.py 调用get_package_info(name)— 被 package_cmd.py 调用
5. Transport Management (transport/spec.md)
| # | Scenario | 状态 | 说明 |
|---|---|---|---|
| 5.1 | List transport requests | ✅已实现 | transport.py:32-56 + client.py:249-280 list_transport_requests() |
| 5.2 | Query transport info | ⚠️部分实现 | transport.py:59-79 命令层已实现,但调用 client.transport_info() — client.py 中缺少该方法定义 |
| 5.3 | Release transport request | ⚠️部分实现 | transport.py:82-107 命令层已实现,但调用 client.transport_release() — client.py 中缺少该方法定义 |
| 5.4 | List objects in transport | ⚠️部分实现 | transport.py:110-136 命令层已实现,但调用 client.transport_objects() — client.py 中缺少该方法定义 |
小计: 1/4 ✅, 3 ⚠️
client.py 缺失的方法(Transport)
transport_info(corr_nr)— 传输请求详情查询transport_release(corr_nr)— 释放传输请求transport_objects(corr_nr)— 传输请求中的对象列表
6. Code Quality (quality/spec.md)
| # | Scenario | 状态 | 说明 |
|---|---|---|---|
| 6.1 | Check with findings | ⚠️部分实现 | quality.py:14-61 命令层完整,但调用 client.atc_check() — client.py 中缺少该方法定义 |
| 6.2 | Check clean | ⚠️部分实现 | quality.py:38-39 显示通过信息,但依赖缺失的 client 方法 |
| 6.3 | Format source code | ⚠️部分实现 | quality.py:64-132 命令层完整,但调用 client.pretty_print() — client.py 中缺少该方法定义 |
| 6.4 | Analyze project dependencies | ✅已实现 | analyze.py:39-126 本地源码解析,正则匹配 TYPE REF TO / CALL FUNCTION 等模式 |
| 6.5 | Scaffold from template | ✅已实现 | scaffold.py:198-239 4 种模板(alv-report/bapi-wrapper/interface-class/data-model) |
| 6.6 | List available templates | ❌未实现 | spec 要求不指定模板时列出可用模板,但 parser.py:207 --template 为 required=True,无法不指定 |
小计: 2/6 ✅, 3 ⚠️, 1 ❌
client.py 缺失的方法(Quality)
atc_check(name, obj_uri, variant)— ATC 代码检查pretty_print(source)— ABAP Pretty Printer
7. Configuration Management (config/spec.md)
| # | Scenario | 状态 | 说明 |
|---|---|---|---|
| 7.1 | Show current config | ✅已实现 | config_cmd.py:20-40 显示主机/客户端/用户名,密码掩码显示 |
| 7.2 | Set config value | ❌未实现 | parser.py:123-126 未定义 config set 子命令,config_cmd.py 无 set 逻辑 |
| 7.3 | List profiles | ✅已实现 | config_cmd.py:43-66 list-profiles 子命令 |
| 7.4 | Use specific profile | ❌未实现 | --profile 参数已定义但 config.py 硬编码使用 [SAP] section |
| 7.5 | Save password to keyring | ⚠️部分实现 | auth.py 有 set_password() 和 cmd_auth_login(),但 auth 模块未集成到 app.py 命令路由,parser.py 也未注册 auth 子命令 |
| 7.6 | Load password from keyring | ⚠️部分实现 | auth.py:resolve_password() 存在,但 未被 config.py 或 app.py 调用 |
小计: 2/6 ✅, 2 ❌, 2 ⚠️
汇总统计
| Spec 领域 | 总需求 | ✅已实现 | ❌未实现 | ⚠️部分实现 | 覆盖率 |
|---|---|---|---|---|---|
| Connection | 9 | 8 | 1 | 0 | 88.9% |
| Object Lifecycle | 28 | 16 | 7 | 1 | 57.1% |
| Batch Operations | 13 | 13 | 0 | 0 | 100% |
| Search & Browse | 12 | 0 | 0 | 12 | 0% (命令层100%,API层0%) |
| Transport | 4 | 1 | 0 | 3 | 25% |
| Code Quality | 6 | 2 | 1 | 3 | 33.3% |
| Config Management | 6 | 2 | 2 | 2 | 33.3% |
| 总计 | 78 | 42 | 11 | 21 | 53.8% |
关键缺失点总结
A. client.py 缺失的 13 个 ADT API 方法
这些方法被 commands/ 层调用但未实现,是最关键的缺口:
| 方法 | 调用者 | 涉及 Spec |
|---|---|---|
list_objects(obj_type, package, prefix) |
search.py, package_cmd.py | search-browse |
where_used(name, obj_uri, adt_type) |
search.py | search-browse |
search_code(query, obj_type) |
search.py | search-browse |
read_source_for_diff(name, obj_type) |
diff_cmd.py | search-browse |
get_cds_source(name) |
cds.py | search-browse |
create_cds(name, description, ddl_source) |
cds.py | search-browse |
create_package(name, description, superpackage) |
package_cmd.py | search-browse |
get_package_info(name) |
package_cmd.py | search-browse |
transport_info(corr_nr) |
transport.py | transport |
transport_release(corr_nr) |
transport.py | transport |
transport_objects(corr_nr) |
transport.py | transport |
atc_check(name, obj_uri, variant) |
quality.py | quality |
pretty_print(source) |
quality.py | quality |
B. types.py 缺失的 5 个对象类型注册
| 类型 | Spec 要求 | 当前状态 |
|---|---|---|
| messageclass | create/download/info/delete | ❌ 未注册,无 URI |
| view | create/download/info | ❌ 未注册,无 URI |
| searchhelp | create | ❌ 未注册,无 URI |
| lockobject | create/delete | ❌ 未注册,无 URI |
| include | (扫描可用) | ❌ scanner.py 映射了目录但 types.py 未注册 |
C. 配置/认证缺口
| 缺失项 | 详情 |
|---|---|
--profile 不可用 |
parser.py 定义了参数但 config.py 忽略,始终读 [SAP] section |
config set 子命令 |
spec 要求通过命令行设置配置项,parser.py 未注册,无实现 |
| keyring 集成 | auth.py 模块完整但未被 app.py/config.py 引入调用链 |
| scaffold 无模板列表 | --template 为 required,无法不指定来列出模板 |