Files
sap-cli-skill/openspec/spec-coverage-report.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

235 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.
# Spec-to-Code 需求覆盖验证报告
> 生成时间: 2026-06-09
> 对照 `openspec/specs/` 下 7 个 spec 文件 vs `sapcli/` 源码
---
## 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** — 无 ObjectTypeConfigCDS 通过独立命令 `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` 中定义:
1. `list_objects(obj_type, package, prefix)` — 被 search.py, package_cmd.py 调用
2. `where_used(name, obj_uri, adt_type)` — 被 search.py 调用
3. `search_code(query, obj_type)` — 被 search.py 调用
4. `read_source_for_diff(name, obj_type)` — 被 diff_cmd.py 调用
5. `get_cds_source(name)` — 被 cds.py 调用
6. `create_cds(name, description, ddl_source)` — 被 cds.py 调用
7. `create_package(name, description, superpackage)` — 被 package_cmd.py 调用
8. `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
1. `transport_info(corr_nr)` — 传输请求详情查询
2. `transport_release(corr_nr)` — 释放传输请求
3. `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
1. `atc_check(name, obj_uri, variant)` — ATC 代码检查
2. `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,无法不指定来列出模板 |