Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 186 additions & 0 deletions devel/200_69.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,100 @@
# [200_63] 命令行工具注册系统规范

## 如何测试

测试项一:字段级覆盖 description,不影响命令实现
1. 在任意临时目录创建 `gfproject.json`
2. 写入如下内容,只覆盖 `test.description`

```json
{
"tools": {
"test": {
"description": {
"en_US": "Run project-specific tests",
"zh_CN": "运行当前项目的测试"
}
}
}
}
```

3. 在该目录执行 `gf help`
4. 确认 `test` 的描述变成 `Run project-specific tests`
5. 确认 `help`、`version`、`doc`、`source` 等其他命令仍然正常显示
6. 执行 `gf test`
7. 确认 `gf test` 仍然走原有实现,不会报缺少 `organization/module`

测试项二:只覆盖嵌套字段,未覆盖的 description 子字段仍然保留
1. 在任意临时目录创建 `gfproject.json`
2. 写入如下内容,只覆盖 `test.description.zh_CN`

```json
{
"tools": {
"test": {
"description": {
"zh_CN": "运行测试(本地覆盖)"
}
}
}
}
```

3. 在该目录执行 `gf eval '(display (g_gfproject-load-config))'`
4. 确认输出中的 `tools.test.organization` 仍然是 `liii`
5. 确认输出中的 `tools.test.module` 仍然是 `goldtest`
6. 确认输出中的 `tools.test.description.en_US` 仍然保留内置值
7. 确认输出中的 `tools.test.description.zh_CN` 变成 `运行测试(本地覆盖)`

测试项三:本地把某个命令配置坏时,只回退该命令
1. 在任意临时目录创建 `gfproject.json`
2. 写入如下内容,故意把 `version.module` 改成不存在的模块

```json
{
"tools": {
"version": {
"module": "goldversion_missing"
}
}
}
```

3. 在该目录执行 `gf version`
4. 确认仍然输出正常版本信息,如 `Goldfish Scheme 17.11.48 by LiiiLabs`
5. 执行 `gf help`
6. 确认 `help` 仍然正常输出命令列表
7. 确认不会出现动态模块导入失败导致整个命令系统不可用

测试项四:help 命令通过 glue 复用 C++ 侧的合并结果
1. 在任意临时目录创建 `gfproject.json`
2. 写入如下内容

```json
{
"tools": {
"test": {
"description": {
"en_US": "Run project-specific tests"
}
}
}
}
```

3. 在该目录执行 `gf eval '(display (g_gfproject-load-config))'`
4. 记录输出中 `tools.test.description.en_US` 的值
5. 在同一目录执行 `gf help`
6. 确认 `help` 里显示的 `test` 描述与上一步 glue 输出一致

测试项五:源码树运行时也能找到内置 gfproject.json
1. 进入 Goldfish 源码根目录之外的任意临时目录
2. 不创建本地 `gfproject.json`
3. 执行 `/path/to/goldfish/bin/gf eval '(display (g_gfproject-load-config))'`
4. 确认输出中存在内置工具配置,如 `help`、`test`、`version`
5. 确认不是空的 `{"tools":{}}`

## 背景

目前 `gf` 的子命令 `doc`、`source` 和 `help` 是在 C++ 代码中硬编码的。为了支持通过 Scheme 实现更多的子命令,需要设计一个工具注册系统。
Expand Down Expand Up @@ -211,3 +306,94 @@ v17.11.47 打包后的版本所有子命令都无法使用,因为:
5. **Commit 5 - 动态注册**:通过 `gfproject.json` 动态注册 help/source/doc 命令,移除 C++ 硬编码逻辑
6. **Commit 6 - version 子命令**:迁移 `version` 命令为 Scheme 实现
7. **Commit 7 - help 优化**:优化 help 命令输出格式,`gf help <command>` 直接显示 `tools/<command>/README.md`

## 2026/04/13 gfproject.json 改为 C++ 字段级深度合并,Scheme 通过 glue 复用

### What
将 `gfproject.json` 的合并逻辑统一收敛到 C++ 侧实现,并通过 glue 暴露给 Scheme:

1. C++ 同时读取内置 `gfproject.json` 和当前目录 `gfproject.json`
2. `tools.<command>` 不再整对象覆盖,改为字段级深度合并
3. 嵌套对象字段继续递归合并,例如 `description.en_US` 和 `description.zh_CN`
4. `goldhelp.scm` 不再自己读文件和手写合并规则,而是通过 `g_gfproject-load-config` 直接消费 C++ 产出的最终结果

### Why
之前的实现存在三个关键问题:

1. 当前目录存在 `gfproject.json` 时,会把内置配置整体遮掉,不是真正的“合并”
2. 即使开始尝试合并,也只是 `tools.<command>` 的整对象覆盖;本地只改 `description` 时,会把内置的 `organization/module` 一起覆盖掉
3. `goldhelp.scm` 自己维护一套独立的读取和合并逻辑,容易和 C++ 运行时语义漂移

这会导致:

- 本地只想改帮助描述,却把命令实现配坏
- `gf help` 看到的结果和真实命令执行时的解析结果不一致
- 在源码树运行时,`gfproject.json` 的 lib 路径还可能定位错误

### How
在 `src/goldfish.hpp` 中新增并整理以下能力:

- `find_local_gfproject_json`:定位当前目录 `gfproject.json`
- `find_lib_gfproject_json`:定位内置 `gfproject.json`,同时支持 `gf_lib` 和 `gf_lib` 的父目录
- `load_json_file_or_empty`:安全读取 JSON object
- `gfproject_extract_tools`:统一提取 `tools`
- `gfproject_deep_merge_value`:对 `tools.<command>` 做字段级深度合并
- `load_gfproject_config_bundle`:同时保留 lib/local/merged 三份配置
- `load_gfproject_config`:返回最终 merged 配置

在 `glue_goldfish` 中新增 `g_gfproject-load-config`,返回合并后的 `gfproject.json` 字符串。

在 `tools/help/liii/goldhelp.scm` 中:

- 删除本地文件查找和手写合并逻辑
- `load-gfproject` 改为直接调用 `g_gfproject-load-config`

---

## 2026/04/13 工具执行改为单命令回退,避免局部坏配置拖垮整个命令系统

### What
调整动态工具执行逻辑:

1. 本地 `gfproject.json` 覆盖某个命令后,先尝试执行该命令的 merged 配置
2. 如果 merged 配置非法或无法导入,只回退这个命令到 lib 配置
3. 对 `help`、`version`、`eval`、`load`、`repl`、`run` 等本来就有内置实现的命令,若动态工具无法执行,再继续回退到内置逻辑
4. 新增 `help` 和 `version` 的普通子命令 fallback,不再只依赖 `--help/-h` 与 `--version/-v`

### Why
之前的问题不是单纯“合并错了”,而是失败粒度也不对:

1. 本地把某个命令的字段改坏时,错误会在动态加载阶段直接暴露,用户看到的是整个命令失败
2. `eval`、`load`、`run` 这类本来应该走内置逻辑的命令,只要被 `gfproject.json` 命中,就可能在动态工具分发阶段提前报错
3. `help` 和 `version` 缺少普通子命令的明确 fallback,回退链条不完整

我们真正需要的是:

- 本地配置优先
- 单命令出错时只影响该命令
- 该命令还能安全退回 lib 或内置实现

### How
在 `src/goldfish.hpp` 中新增并重构以下执行辅助函数:

- `resolve_gfproject_tool`:解析某个命令的 local/lib/merged 状态
- `goldfish_prepare_tool_main`:校验配置并准备动态模块执行
- `goldfish_run_tool_with_config`:用一份具体配置尝试执行一个命令
- `goldfish_finish_tool_error` / `goldfish_finish_tool_success`:统一处理命令执行后的收尾
- `goldfish_reset_captured_error_port`:在 fallback 前清理错误输出缓冲

主流程改为:

1. 动态工具分发先尝试 merged 配置
2. merged 配置失败时,只对当前命令回退到 lib 配置
3. lib 配置仍不适用且该命令有内置实现时,返回 `-1`,让主命令分发继续走内置 `help`、`version`、`eval`、`load`、`repl`、`run`

对应回归测试新增在:

- `tools/help/tests/liii/goldhelp/load-gfproject-test.scm`

覆盖场景包括:

1. 本地只覆盖 `description` 时,`organization/module` 仍保留
2. 本地只覆盖 `description.zh_CN` 时,`description.en_US` 仍保留
3. 本地把 `version.module` 改坏时,`gf version` 仍然能回退输出版本信息
Loading
Loading