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 重写为单源开发流程。
This commit is contained in:
吴让宇
2026-09-11 00:40:15 +08:00
parent e786742bcb
commit c5905a5b1e
104 changed files with 20744 additions and 10 deletions
+234
View File
@@ -0,0 +1,234 @@
# 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,无法不指定来列出模板 |