Skip to content

[200_69] Fix gfproject.json merge logic#695

Closed
MoonL79 wants to merge 5 commits intomainfrom
200_69/fix-gfproject-json-merge
Closed

[200_69] Fix gfproject.json merge logic#695
MoonL79 wants to merge 5 commits intomainfrom
200_69/fix-gfproject-json-merge

Conversation

@MoonL79
Copy link
Copy Markdown
Collaborator

@MoonL79 MoonL79 commented Apr 12, 2026

修复 gfproject.json 合并逻辑问题

修改内容

  1. src/goldfish.hpp:

    • 只合并 tools section
    • lib tools 作为基础,local tools 覆盖(除 help 外)
    • help 命令始终使用内部实现
    • 工具配置不完整时回退到内置实现
  2. tools/help/liii/goldhelp.scm:

    • 支持查找 local 和 lib 路径
    • 正确合并,遵循相同规则
    • help 工具不合并
  3. gfproject.json:

    • fix 工具移除 organization/module(未完全迁移到 Scheme)

测试

  • 从子目录运行时显示内部帮助 ✅
  • 配置不完整时回退到内置实现 ✅

mingshen added 5 commits April 12, 2026 10:25
- load_gfproject_config 只合并 tools section
- lib tools 作为基础,本地 tools 覆盖(除 help 外)
- help 工具不合并,始终使用内部实现
- goldhelp.scm 遵循相同的合并逻辑
- find-gfproject-path 'lib 优先使用 GF_LIB,否则从 executable 向上查找
- 遵循与 C++ 代码相同的搜索逻辑
- fix 工具不补全 organization/module(未完全迁移到 Scheme)
- goldhelp.scm 手动遍历合并 tools,遵循相同合并规则
- help 工具不合并,始终使用内部实现
- 在 gfproject.json 工具加载时跳过 help
- 添加显式的 help 命令处理,调用 display_help()
- 确保无论是否有 local gfproject.json,help 都使用内部实现
- 当 gfproject.json 中工具配置缺少 organization/module 时,返回 -1
- 当工具目录不存在时,返回 -1
- 调用方会继续处理内置命令,实现 local 优先于内置
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 12, 2026

Greptile Summary

This PR fixes the gfproject.json merge logic so that lib and local configs are properly combined (lib as base, local overrides except help), and routes gf help permanently to the C++ display_help() to avoid path-resolution failures from subdirectories.

  • load-gfproject in goldhelp.scm returns the merged tools object rather than a full config, but display-help and display-tool-help still call (json-ref config "tools") on the return value — they will receive json-null, silently listing no dynamic tools and treating every tool name as "Unknown command".
  • load_gfproject_config is called twice per tool dispatch in goldfish.hpp (once in repl_for_community_edition and again inside goldfish_run_tool), reading and parsing both JSON files from disk each time.

Confidence Score: 4/5

Mostly safe for the primary gf tool dispatch path, but the Scheme-side load-gfproject API is broken.

The C++ merge and help-bypass logic is correct and the primary gf <tool> dispatch works as intended. The P1 bug (wrong return type from load-gfproject) currently has no user-visible impact because C++ now intercepts all help commands before reaching the Scheme module. However the exported Scheme API (liii goldhelp) is demonstrably broken, warranting attention before merge.

tools/help/liii/goldhelp.scmload-gfproject returns the wrong type; its callers display-help and display-tool-help must be updated.

Important Files Changed

Filename Overview
tools/help/liii/goldhelp.scm Adds lib/local path resolution and merge logic for gfproject.json, but load-gfproject now returns the merged tools object instead of a full config, breaking display-help and display-tool-help which still call (json-ref config "tools") on the result.
src/goldfish.hpp Rewrites config loading to merge lib and local gfproject.json (tools only), always routes help to C++ display_help(), and changes fallback return value from 1 to -1 for incomplete tool configs; load_gfproject_config is called twice per tool invocation.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["gf <command>"] --> B{command == 'help'?}
    B -- Yes --> C["C++ display_help()\n(static, hardcoded)"]
    B -- No --> D["load_gfproject_config()\nmerge lib + local gfproject.json"]
    D --> E{command in\nmerged tools?}
    E -- No --> F[Fall through to built-in handlers]
    E -- Yes --> G["goldfish_run_tool()\ncalls load_gfproject_config() again"]
    G --> H{org + module\npresent? tool_root found?}
    H -- No --> I["return -1\nfall through to built-in"]
    H -- Yes --> J["Import Scheme module\ncall main()"]
    J --> K{return value}
    K -- "-1" --> I
    K -- "0 / positive" --> L[Return result]
    I --> F
Loading

Reviews (1): Last reviewed commit: "[200_69] fix: 工具配置不完整时回退到内置实现" | Re-trigger Greptile

Comment on lines 96 to +110
(define (load-gfproject)
"Load and parse gfproject.json, return tools object"
(let ((path (find-gfproject-path)))
(if path
(let ((content (path-read-text path)))
(string->json content))
(string->json "{}"))))
"Load and merge gfproject.json from local and lib, local takes precedence"
(let ((local-path (find-gfproject-path 'local))
(lib-path (find-gfproject-path 'lib)))
(let ((lib-config (if lib-path
(let ((content (path-read-text lib-path)))
(string->json content))
(make-json)))
(local-config (if local-path
(let ((content (path-read-text local-path)))
(string->json content))
(make-json))))
(let ((lib-tools (json-ref lib-config "tools"))
(local-tools (json-ref local-config "tools")))
(json-merge-tools lib-tools local-tools)))))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 load-gfproject returns merged tools, not a full config object

json-merge-tools returns the tools object directly (e.g. {"help":{…},"fix":{…}}), so load-gfproject now returns that. But every caller still does (json-ref config "tools") on the return value — looking for a key named "tools" inside an object that has no such key. The result is json-null, causing display-help to show no dynamic tools and display-tool-help to always print "Unknown command: …".

The fix is to wrap the merged tools back into a config object:

(define (load-gfproject)
  "Load and merge gfproject.json from local and lib, local takes precedence"
  (let ((local-path (find-gfproject-path 'local))
        (lib-path   (find-gfproject-path 'lib)))
    (let ((lib-config   (if lib-path
                            (string->json (path-read-text lib-path))
                            (make-json)))
          (local-config (if local-path
                            (string->json (path-read-text local-path))
                            (make-json))))
      (let ((merged (make-json)))
        (json-set! merged "tools"
          (json-merge-tools (json-ref lib-config "tools")
                            (json-ref local-config "tools")))
        merged))))

Comment on lines 5357 to 5365
@@ -5325,6 +5364,15 @@ repl_for_community_edition (s7_scheme* sc, int argc, char** argv) {
// If tool_ret == -1, tool config exists but execution failed, fall through to check other commands
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 load_gfproject_config called twice per tool invocation

load_gfproject_config(gf_lib) is called here (line 5357) to check whether the command is present, and then called a second time at the very start of goldfish_run_tool (line 4030). Each call reads and parses both JSON files from disk. Passing the already-loaded config into goldfish_run_tool would avoid the duplicate I/O:

json gfproject_config = load_gfproject_config (gf_lib);
if (command != "help" && gfproject_config.contains ("tools") && gfproject_config["tools"].contains (command)) {
    int tool_ret = goldfish_run_tool (sc, gf_lib, command, gfproject_config, errmsg, old_port, gc_loc);

Then update goldfish_run_tool's signature to accept a const json& config parameter instead of loading it internally.

Comment on lines +58 to +67
(let ((exe-path (executable)))
(if exe-path
(let search ((dir (path-dirname (path exe-path))))
(if dir
(let ((gfproject-path (path->string (path-join dir (path "gfproject.json")))))
(if (file-exists? gfproject-path)
gfproject-path
(search (path-dirname dir))))
#f))
#f)))))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Recursive path-dirname search may not terminate

The fallback search walks up the directory tree by repeatedly calling (path-dirname dir). If path-dirname returns the same path for the filesystem root (e.g. "/""/") instead of returning #f, the search loop will spin forever. A termination guard comparing the current dir with its parent would make this safe:

(let search ((dir (path-dirname (path exe-path))))
  (if dir
      (let ((parent (path-dirname dir)))
        (if (or (not parent)
                (string=? (path->string dir) (path->string parent)))
            #f
            (let ((gfproject-path (path->string (path-join dir (path "gfproject.json")))))
              (if (file-exists? gfproject-path)
                  gfproject-path
                  (search parent)))))
      #f))

@@ -5325,6 +5364,15 @@ repl_for_community_edition (s7_scheme* sc, int argc, char** argv) {
// If tool_ret == -1, tool config exists but execution failed, fall through to check other commands
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Misleading comment for tool_ret == -1

The comment says "tool config exists but execution failed", but -1 is now returned for several non-failure conditions: missing organization/module fields, or missing tool_root. A more accurate comment would be:

Suggested change
// If tool_ret == -1, tool config exists but execution failed, fall through to check other commands
// If tool_ret == -1, tool config is incomplete (missing org/module or tool root not found), fall back to built-in

@da-liii da-liii closed this Apr 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants