# 02 架构与目录 源码落地:`D:\ideaProject\gcode`(本次只定结构,不建库)。 ## 分层 ```text ┌─────────────────────────────────────────────┐ │ ui/detect + tui / repl │ │ 探测终端、画界面、收集确认(y/N、diff) │ ├─────────────────────────────────────────────┤ │ graph + session + prompt │ │ ReAct、checkpoint、静态/动态提示词 │ ├─────────────────────────────────────────────┤ │ models(注册表 + openai_compatible) │ ├─────────────────────────────────────────────┤ │ tools + safety + mcp │ │ 文件/命令沙箱、HITL、Playwright MCP │ └─────────────────────────────────────────────┘ ``` 依赖只能从上到下。工具不得 `import` 图模块去改全局 `llm` / `agent`。TUI 与 REPL 都只消费 `graph.astream(...)`。 ```mermaid flowchart LR detect[终端探测] -->|WT 等| tui[自研TUI] detect -->|conhost| repl[REPL回退] tui --> graph[LangGraph ReAct] repl --> graph graph --> llmCall[llm_call] llmCall -->|tool_calls| toolNode[tool_node] toolNode --> safety[HITL] safety --> llmCall llmCall -->|纯文本| uiOut[TUI或REPL渲染] ``` ## 目录骨架 ```text D:\ideaProject\gcode\ pyproject.toml README.md src/gcode/ __init__.py cli.py # 入口:解析参数、探测、选 UI config.py # 单一 home:%APPDATA%\gcode 与 /.gcode windows.py # 代码页、默认 PowerShell、Job Object / taskkill prompt.py graph.py # StateGraph:llm_call / tool_node session.py # sqlite checkpointer + thread_id safety.py # 写文件 diff、shell 确认;回调由 UI 注入 ui/ detect.py repl.py tui/ # 自研 Textual,不拷 Qoze CSS/logo models/ registry.py # 读 models.yaml openai_compatible.py tools/ fs.py shell.py mcp/ client.py # 薄客户端,默认只起 @playwright/mcp tests/ test_path_sandbox.py test_graph_smoke.py test_detect_terminal.py ``` 项目级数据:`/.gcode/`(checkpoints、rules、debug log)。 用户级:`%APPDATA%\gcode\`(`models.yaml`、mcp 配置、token 统计)。禁止再混用 `~\.gcode` 与 `%APPDATA%` 两套根。 ## Prompt 怎么写 不要一上来写 400 行。对照 Qoze 的 [system_prompt.py](D:\ideaProject\QozeCode-main\utils\system_prompt.py) 学 **静态/动态拆分**,不要整段粘贴。G-CODE 工具清单不同,抄过来模型会去调不存在的 `dispatch_subagent`。 每次加一段之前问: 1. 模型此刻有哪些工具?没实现的一句不写 2. 最容易做错的 3 件事?只为这些写硬规则 3. 静态还是动态?每次一样 → System;会变(cwd、git、rules)→ 动态段 4. 能用代码拦住的不要只靠文字(路径沙箱在 `read_file` 里拒绝) 5. 用失败任务加规则,不用想象加规则 静态段先控制在 80~150 行:身份、工具怎么选、5~8 条硬规则、ReAct 与「连续 3 次无进展就问人」、中文推理、TUI 内避免 emoji。 动态段保持短:cwd、目录树摘要、git 摘要、`.gcode/rules`。 ## 多模型 不复制 Qoze 的枚举树。`models.yaml` 示例: ```yaml providers: deepseek: api: openai_compatible base_url: https://api.deepseek.com/v1 api_key_env: DEEPSEEK_API_KEY models: - id: deepseek-v4-flash vision: false reasoning: false - id: deepseek-v4-pro vision: false reasoning: true agicto: api: openai_compatible base_url: https://api.agicto.cn/v1 api_key_env: GCODE_API_KEY models: - id: deepseek-v4-flash vision: false reasoning: false ``` 一期只实现 `openai_compatible`。加一家模型改 yaml,不改 Python。`reasoning_content` 尽量显式解析,避免猴子补丁 LangChain。Vertex / Anthropic / Bedrock 有账号再加 adapter。 启动:`gcode --model deepseek-v4-flash` 或 TUI 内选择。 ## 人在回路 `safety.py` 定义确认点,UI 注入回调(TUI 用对话框,REPL 用 y/N): | 动作 | 展示 | 拒绝时 | |------|------|--------| | 写文件 / 替换 | unified diff | ToolMessage:「用户拒绝本次写入」 | | `execute_command` | 完整命令 + 工作目录 | ToolMessage:「用户拒绝执行该命令」 | 读文件、列目录、只读 git 不必确认。安装依赖、构建、测试、`git commit` / `push` 必须走确认(prompt 硬规则 + HITL 双保险)。 ## 工具 一期内置: - `read_file` / `list_dir` / `replace_in_file`:仅 cwd 或用户级 gcode 目录;拒绝 `..` - `execute_command`:默认 PowerShell;输出 UTF-8 失败则 GBK;超时用 Job Object 或 `taskkill /T`,不要只 `terminate()` 留孤儿 浏览器不进 `tools/` 进程内实现,见下一节。 ## Playwright MCP(仅途径 A) 官方:`npx -y @playwright/mcp@latest`。 G-CODE 薄 MCP 客户端:stdio 拉起 → 把 tools 动态交给 `bind_tools`。MCP 只当 Playwright 插座,一期不接 GitHub / Postgres。不复活 Qoze 已注释的 `browser_tool.py`,不默认 `chrome-devtools-mcp`(那是 `--remote-debugging-port=9222` 附着已开 Chrome)。 硬依赖 Node.js / npx。检测不到则 TUI/REPL 提示「浏览器工具不可用,请安装 Node.js」,没有 Python Playwright 备选。 ## 相对 Qoze 必须避开的结构 - 模块级全局 `llm`、`agent`、`tools_by_name` 被 MCP/TUI 到处改 - `mcp_tools` / `subagent_tool` 反向 import `qoze_code_agent` - `qoze_tui.py` 里命令路由、拼消息、token、流式全混在一个 App 类 - 配置路径分裂:`%APPDATA%\qoze` vs `~\.qoze` - shell 一律按 UTF-8 解码(cmd 的 `dir` 常为 GBK)