diff --git a/.gitignore b/.gitignore index 0a1dc58..f3c5465 100644 --- a/.gitignore +++ b/.gitignore @@ -348,3 +348,6 @@ web-design-guidelines/SKILL.md *.mjs *.pckl *.pyc + +*.sqlite +**/.langgraph_api/ diff --git a/langgraph-plan-review/.langgraph_api/.langgraph_ops.pckl b/langgraph-plan-review/.langgraph_api/.langgraph_ops.pckl deleted file mode 100644 index c3b8e8e..0000000 Binary files a/langgraph-plan-review/.langgraph_api/.langgraph_ops.pckl and /dev/null differ diff --git a/langgraph-plan-review/SKILL.md b/langgraph-plan-review/SKILL.md index fc8096f..3cc469c 100644 --- a/langgraph-plan-review/SKILL.md +++ b/langgraph-plan-review/SKILL.md @@ -7,31 +7,38 @@ description: Use the LangGraph plan-review workflow from the current Claude Code Use this skill to run the LangGraph plan-review workflow. All scripts are bundled under the skill's own `scripts\` directory (`C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\`) — no dependency on `E:\LangGraph_Design\lab00`. -## ⚠️ 强制执行规则(MANDATORY — read first) +## 运行方式:进程内驱动,无 dev server(2026-07 起) -**plan-review 工作流必须、且只能通过 `langgraph dev` 服务器来驱动。** +**本 skill 已去掉 `langgraph dev` 服务器**。工作流由进程内 runner **`scripts\clean_run.py`** 直接驱动: +它 `build_workflow()` 编译图、用 **SqliteSaver**(`scripts\.plan_review_state.sqlite`)做跨调用持久化、 +自己处理 `interrupt`/`Command(resume=...)`。不再需要 `manage_dev_server.ps1` / `run_langgraph.ps1` / +`run_workflow.py`(这些是旧的 dev-server 路径,保留仅作参考,勿再使用)。 -- ✅ **唯一允许的入口**:`run_langgraph.ps1` → `run_workflow.py`。后者是一个**纯 REST/SDK 客户端** - (`langgraph_sdk.get_sync_client(url=http://127.0.0.1:2024)`),每次 start/resume 都把 run - 投递到 `langgraph dev` 服务器执行。先用 `manage_dev_server.ps1 start` 把服务器拉起。 - 这样 LangGraph Studio 才能实时追踪线程、节点、中断与状态。 +- **入口**:用 `langgraph` conda 环境的 python 跑 `clean_run.py`: + ```powershell + $PY = "C:\Users\bowei\.conda\envs\langgraph\python.exe" + $RUN = "C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\clean_run.py" + # 首次(到 write_plan 中断): + & $PY $RUN --task-id 10 --work-dir "" --notes-dir Notes --prompt "<原始请求>" + # resume(写完/改完 Task10.md 后;resume 值 base64(JSON)): + $resume = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes('true')) + & $PY $RUN --task-id 10 --work-dir "" --notes-dir Notes --resume-b64 $resume --codex-review-command-b64 $CMD --timeout 900 + ``` + 每次调用推进到下一个 `interrupt` 或结束,打印一行 JSON:`{"status":"waiting","action_required":{...}}` + 或 `{"status":"done","has_serious_issue":...}`。`action_required` 的处理同下(write_plan / decide / + revise / write_rejection),resume 值:`write_plan`/`revise`/`write_rejection` 用 `true`;`decide` 用 + `{"accepts":true}` 或 `{"accepts":false,"reason":"..."}`。 -- 🚫 **绝对禁止**:直接 `import` / 在进程内运行本 skill 的图定义模块 - (`scripts\graph.py`、`scripts\workflow.py`、`scripts\agents.py`)来跑工作流。包括但不限于: - - `python -c "from graph import ...; graph.invoke(...)"` 或 `.stream(...)` - - 写临时脚本 build 编译图后在进程内调用 - - 任何绕过 `http://127.0.0.1:2024` 的「就地执行」方式 +- **codex 评审的两个必备条件**(本次踩坑记录): + 1. **codex CLI 要足够新**。config.toml 若配了较新模型(如 `gpt-5.6-sol`),旧 codex 会返回 400 + `requires a newer version of Codex`。升级:`npm i -g @openai/codex@latest`(≥0.144.5 实测可用)。 + 2. **codex 子进程要能到 chatgpt.com**。`clean_run.py` 会自动读 Windows 系统代理(HKCU Internet + Settings 的 `ProxyServer`)设进 `HTTP(S)_PROXY`,让 codex 走代理;也可用 `PLAN_REVIEW_PROXY` 覆盖。 + 3. **codex 超时**:`review_task.py` 的内层 codex 超时取 argv[5](默认 300s,评审常不够)。用 + `--codex-review-command-b64` 传一条**末尾带超时秒数**的命令(见 Workflow),并把 `--timeout` 设一致。 - 这类调用在 dev server 进程**之外**运行,Studio 追踪不到状态,**一律不允许**—— - 即使只是想「快速验证一个节点」也不行。 - -- ℹ️ **例外说明**:`review_task.py`(Codex 评审 shim)会直接调 `codex`,这是**合规的**—— - 它是由运行在 dev server 内的图通过 `codex_review_command` 调起的评审步骤,不是绕过 - dev server 去驱动工作流本身。区别在于:驱动图 = 必须走 dev server;图内部调用的工具 = 正常。 - -- 🛠 **改了 `graph.py`/`workflow.py`/`agents.py` 后**:dev server 内存里仍是旧代码。先 - `manage_dev_server.ps1 stop` 再 `start`(或让 `--reload` 生效),再经 `run_langgraph.ps1` - 重跑,使改动在被追踪的运行中生效。 +- **改了 `workflow.py`/`agents.py`/`clean_run.py` 后**:无 dev server 缓存,下次 `clean_run.py` 直接生效。 + 若图结构改到与旧 checkpoint 不兼容,删 `scripts\.plan_review_state.sqlite` 重新开始该 thread。 ## Pre-flight: Environment Setup @@ -75,7 +82,10 @@ conda run -n langgraph langgraph --version - The workflow uses LangGraph `interrupt()` and persists state via the langgraph dev server. - Each `run_langgraph.ps1` invocation advances the graph to the next interrupt or completion. Use `-Resume` to continue the same thread after completing the requested Claude Code action. -## Dev Server Lifecycle +## Dev Server Lifecycle ⛔ DEPRECATED + +> **已弃用(2026-07 起)。** 本 skill 现用 `clean_run.py` 进程内驱动,**不再需要 dev server**。 +> 下面这套 `manage_dev_server.ps1` / `langgraph dev` / Studio 仅作历史参考,正常流程不要用。 The `langgraph dev` server must be running for the entire duration of a workflow session. Use `manage_dev_server.ps1` to start it before the first `run_langgraph.ps1` call and stop it after the workflow completes. @@ -99,6 +109,10 @@ C:\Users\bowei\.claude\skills\langgraph-plan-review\scripts\manage_dev_server.ps ## Workflow +> **注意**:下列步骤是**旧的 dev-server 流程**(`manage_dev_server.ps1` + `run_langgraph.ps1`)。 +> 正常请用上方"**运行方式:进程内驱动**"节的 `clean_run.py`。以下 action 语义(write_plan / decide / +> revise / write_rejection 及其 resume 值)两种流程通用,可参考。 + 1. Determine `TaskId` from the user. If missing, ask for it. 2. Start the dev server (idempotent): ```powershell diff --git a/langgraph-plan-review/scripts/clean_run.py b/langgraph-plan-review/scripts/clean_run.py new file mode 100644 index 0000000..12e6730 --- /dev/null +++ b/langgraph-plan-review/scripts/clean_run.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +"""In-process driver for the plan-review graph — NO langgraph dev server. + +Builds the compiled graph in-process (workflow.build_workflow) with a SqliteSaver so +state survives between invocations, and drives interrupts directly. Sets HTTP(S)_PROXY +from the Windows system proxy (or PLAN_REVIEW_PROXY) so the codex subprocess spawned by +the review node can reach its backend through the proxy. + +CLI (mirrors the old run_workflow.py): + --task-id 10 [--work-dir DIR] [--notes-dir Notes] [--prompt "..."] + [--resume-b64 ] [--codex-review-command-b64 ] [--timeout 600] +Prints one JSON line: {"status":"waiting","action_required":{...}} or {"status":"done",...}. +""" +from __future__ import annotations +import os, sys, json, base64, argparse, sqlite3 +from pathlib import Path + +SCRIPTS = Path(__file__).resolve().parent +sys.path.insert(0, str(SCRIPTS)) + + +def resolve_proxy() -> str: + px = os.environ.get("PLAN_REVIEW_PROXY", "").strip() + if not px: + try: + import winreg + k = winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Internet Settings", + ) + enable, _ = winreg.QueryValueEx(k, "ProxyEnable") + server, _ = winreg.QueryValueEx(k, "ProxyServer") + if enable and server and "=" not in str(server): # simple host:port, not per-scheme + px = "http://" + str(server) + except Exception: + px = "" + if px: + for v in ("HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", + "http_proxy", "https_proxy", "all_proxy"): + os.environ[v] = px + return px + + +def open_saver(db_path: str): + from langgraph.checkpoint.sqlite import SqliteSaver + conn = sqlite3.connect(db_path, check_same_thread=False) + saver = SqliteSaver(conn) + try: + saver.setup() + except Exception: + pass + return saver + + +def extract_interrupt(result): + if not isinstance(result, dict): + return None + ints = result.get("__interrupt__") + if not ints: + return None + first = ints[0] if isinstance(ints, (list, tuple)) else ints + return getattr(first, "value", first) + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--task-id", required=True) + ap.add_argument("--work-dir", default=".") + ap.add_argument("--notes-dir", default="Notes") + ap.add_argument("--prompt", default="") + ap.add_argument("--resume-b64", default="") + ap.add_argument("--codex-review-command-b64", default="") + ap.add_argument("--timeout", type=int, default=600) + ap.add_argument("--db", default=str(SCRIPTS / ".plan_review_state.sqlite")) + args = ap.parse_args() + + proxy = resolve_proxy() + + from workflow import build_workflow + from langgraph.types import Command + + codex_cmd = None + if args.codex_review_command_b64: + codex_cmd = json.loads(base64.b64decode(args.codex_review_command_b64).decode("utf-8")) + + saver = open_saver(args.db) + graph = build_workflow(checkpointer=saver) + + config = {"configurable": {"thread_id": str(args.task_id), "codex_timeout": args.timeout}} + if codex_cmd: + config["configurable"]["codex_review_command"] = codex_cmd + + if args.resume_b64: + resume_val = json.loads(base64.b64decode(args.resume_b64).decode("utf-8")) + result = graph.invoke(Command(resume=resume_val), config) + else: + init = { + "task_id": str(args.task_id), + "user_prompt": args.prompt, + "work_dir": args.work_dir, + "notes_dir": args.notes_dir, + "codex_session_id": "", + } + result = graph.invoke(init, config) + + payload = extract_interrupt(result) + if payload is not None: + out = {"status": "waiting", "thread_id": str(args.task_id), + "action_required": payload, "proxy": proxy} + else: + out = {"status": "done", "thread_id": str(args.task_id), + "has_serious_issue": bool(result.get("has_serious_issue")), + "review_comment": result.get("review_comment", ""), + "proxy": proxy} + print(json.dumps(out, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools-sync/SKILL.md b/tools-sync/SKILL.md new file mode 100644 index 0000000..6659d88 --- /dev/null +++ b/tools-sync/SKILL.md @@ -0,0 +1,162 @@ +--- +name: tools-sync +description: 在本机统一同步并配置 Claude Code 与 Codex 的 skills、MCP server 和开发环境:将 ~\.claude\skills 和 ~\.codex\skills 与 git.rpchannel.com 上的 bowei/skills-win 仓库保持同步,审计 skills 中的硬编码参数并迁移到本机 ~\skills_env.md,校验 langgraph conda 环境,并确保 E:\MCP_design 下所有 MCP 都已在 Claude Code 和 Codex 中注册。当用户提到"同步skills"、"skills同步"、"tools sync"、"tools-sync"、"同步codex"、"同步MCP"、"环境同步"、"skills-win"、"把skills推上去/拉下来"、"新电脑配置claude/codex环境"、"检查MCP注册"、"skills_env"时,必须使用此技能,即使用户没有明确说"skill"二字。 +--- + +# tools-sync:Claude Code / Codex 工具链同步 + +## 目标与总体思路 + +一台开发机上有两个 AI 编码工具(Claude Code 和 Codex),它们各自有 skills 目录和 MCP +注册表。这个 skill 的职责是让它们保持一致,并且可以在一台新电脑上从零复原整套配置: + +- **skills 的唯一真源**是 `http://git.rpchannel.com/bowei/skills-win.git`(remote 名 `origin`)。 + `~\.claude\skills` 是主工作副本,`~\.codex\skills` 是同一仓库的第二个检出(镜像)。 + Codex 没有注册命令——它在会话启动时自动扫描 `~\.codex\skills` 下所有含 `SKILL.md` + 的目录,所以"注册到 Codex" = "目录出现在那里且含 SKILL.md"。 +- **机器相关的参数不进 git**。每台电脑的软件安装路径、IP、账号等都记录在 + `~\skills_env.md`(用户主目录下,天然在两个仓库检出之外,不会被 git 同步)。 + skills 里引用参数时写"见 ~\skills_env.md 的 XXX 条目",不写死路径。 +- **凭据不写进任何会被同步的文件**。git.rpchannel.com 的凭据优先用 Windows + 凭据管理器(`cmdkey /list` 里应有 `git:http://git.rpchannel.com` 条目); + 只有在新机器首次配置时,才从 `~\skills_env.md` 读取用户名/密码完成首次 + clone(之后 credential helper 会缓存)。 + +完整跑一遍下面 6 个阶段就是一次"同步"。用户只要求其中一部分(比如"把 skills 推上去") +时,跑对应阶段即可,但阶段 0 永远先做。 + +## 阶段 0 — 读取本机环境档案 ~\skills_env.md + +先读 `~\skills_env.md`。后续所有阶段的路径、URL、账号都以它为准。 + +- 如果文件不存在(通常是新电脑):从本 skill 的 + `references/skills_env_template.md` 复制一份到 `~\skills_env.md`, + 然后逐项核对模板里的路径在本机是否真实存在(`Test-Path`),把不符的改成实际值, + 缺失的软件记为"未安装"。凭据字段让用户填写或从用户处确认,不要猜。 +- 如果存在:快速通读,注意 `status` 类标记和上次同步遗留的 TODO。 + +## 阶段 1 — 同步 ~\.claude\skills(主副本) + +用 `scripts/sync_repos.ps1` 做机械部分;它失败或报冲突时再人工介入。 + +```powershell +# 从 skill 目录运行;RepoUrl 取自 skills_env.md +powershell -File \scripts\sync_repos.ps1 -RepoUrl +``` + +脚本对主副本做的事:`git add -A` → 有变更则 commit(消息格式 +`sync: <机器名> <日期> <一句话摘要>`)→ `git pull --rebase` → `git push`。 + +人工介入的情形: + +- **rebase 冲突**:逐个文件看冲突双方,skills 文本冲突通常保留两边语义合并; + 解不了就 `git rebase --abort` 并向用户报告冲突文件清单,让用户裁决。 +- **push 被拒(认证)**:检查凭据管理器条目;新机器上用 + `git config --global credential.helper manager` 后重试一次,让 credential + helper 弹窗或从 skills_env.md 读凭据拼一次性 URL 完成首次认证。 + 认证成功后确认凭据已被缓存(再 fetch 一次不应再要密码)。 +- **不该入库的文件**:commit 前扫一眼 `git status`,运行时产物(.sqlite、.pckl、 + 日志、缓存目录)应加进 `.gitignore` 而不是提交。看到就顺手补 `.gitignore`。 + +## 阶段 2 — 同步 ~\.codex\skills(镜像检出) + +同一个脚本会接着处理 codex 侧。逻辑和原因: + +- 已是 git 仓库 → `git pull --rebase` 即可。 +- 还不是 git 仓库(首次转换)→ 脚本执行 `git init` + `remote add origin` + + `fetch` + `git reset origin/main`,然后**停下来**列出"本地与仓库不一致"的文件。 + 这一步停是有原因的:codex 目录里可能有比仓库新的本地修改或 codex 独有的 + skill。处理原则——codex 独有且有价值的 skill,先复制到 `~\.claude\skills` + 走阶段 1 入库,再回来;确认没有需要保留的差异后,用 `-Force` 重跑让脚本 + `git checkout -f` 对齐到仓库版本。codex 自带的系统 skills(`.system/`、 + vercel 系列等)已在 `.gitignore` 里,不会被动。 +- 同步完成后校验注册:仓库里每个顶层 skill 目录都应在 `~\.codex\skills` + 出现且含 `SKILL.md`。列出两侧目录做差集,差集应为空(忽略 .gitignore + 中的 codex 专属条目)。提醒用户:Codex 需要开新会话才会扫到新 skills。 + +## 阶段 3 — 硬编码审计与 skills_env.md 回填 + +目的:让仓库里的 skills 在任何一台按 `~\skills_env.md` 配置好的电脑上都能用。 + +在 `~\.claude\skills`(排除 `.system`)里搜这几类硬编码: + +- 盘符绝对路径:正则 `[A-Za-z]:[\\/]`(如 `D:\Xilinx\Vivado`、`E:\MCP_design`) +- IP 地址:`\d+\.\d+\.\d+\.\d+` +- 明文凭据:`password`、`密码`、常见口令字样 + +对每个命中做判断,不要机械替换: + +- **机器相关的值**(安装路径、板卡 IP、conda 路径)→ 确认已记录在 + `~\skills_env.md` 对应条目(没有就补上),然后把 skill 原文改成引用式写法, + 例如"Vivado 路径见 `~\skills_env.md` 的 `VIVADO_PATH`"。skill 的执行逻辑 + 改为:运行时先读 skills_env.md 取值。 +- **明文密码出现在会被 git 同步的文件里** → 这是必须当场修复的问题: + 从文件里删掉,移入 `~\skills_env.md`,并向用户报告(密码已进过 git + 历史的话,建议用户改密码)。 +- **协议性/普适的值**(如标准端口号、公开 URL)→ 保留,不算硬编码。 + +一次审计不必把所有历史 skills 全部重构完;至少保证:新增/本次改动的 skill 零硬编码, +存量 skill 的命中清单写进最终报告,让用户决定分批改。改动之后回到阶段 1 再 +commit + push 一次,并在 codex 侧 pull,保证两边一致。 + +## 阶段 4 — langgraph 开发环境校验 + +很多 skills(vivado-mcp、fpga 系列、langgraph 系列)依赖 conda 的 `langgraph` 环境。 +校验方式(路径以 skills_env.md 的 `CONDA_LANGGRAPH_PYTHON` 为准): + +```powershell +& -c "import langgraph, langchain; print('langgraph env OK')" +``` + +失败(环境不存在或包缺失)→ 调用 `langgraph-vivado-install` skill 完成安装, +装好后把实际的 python 路径回填到 `~\skills_env.md`。 + +## 阶段 5 — MCP server 注册校验(Claude Code + Codex 两边) + +MCP 的清单真源是 `E:\MCP_design\mcp_list.md`(路径见 skills_env.md 的 +`MCP_DESIGN_ROOT`),每行一个 MCP 工程目录。对清单里的每一项: + +1. **Claude Code 侧**:`claude mcp list` 或读 `~\.claude.json` 的 `mcpServers`, + 确认存在对应条目。缺失的用 `claude mcp add ...` 注册,命令、解释器 + 路径和 env 变量都取自 `~\skills_env.md` 的 MCP 配置节。 +2. **Codex 侧**:读 `~\.codex\config.toml` 的 `[mcp_servers.]` 节。 + 缺失的按 skills_env.md 里的同一份配置补写 TOML 节(stdio 类型, + 记得带 `PYTHONIOENCODING=utf-8`、`PYTHONUTF8=1`,Codex 在 Windows 上 + 没有这两个变量时中文输出会乱码)。 +3. **可启动性抽查**:对新注册的 MCP,直接运行其 command + args 数秒, + 确认进程能起来、没有立刻报 import/路径错误(stdio MCP 起来后会静默等 + 输入,这就算通过,Ctrl-C 掉即可)。 + +注意两侧配置语义相同但格式不同(JSON vs TOML),同一个 MCP 在两边的 +command/args/env 应当一致;发现两边漂移(比如只有一边改了 VIVADO_PATH)时, +以 skills_env.md 记录的值为准改齐两边。 + +## 阶段 6 — 输出同步报告 + +最后用这个结构向用户汇报(有问题的条目放前面): + +``` +# tools-sync 同步报告 <日期> +## skills 同步 +- ~\.claude\skills: +- ~\.codex\skills: +## 硬编码审计 +- 本次修复: <清单> +- 遗留待改: <清单> +## 环境 +- langgraph env: OK / 已重装 / 失败原因 +- MCP 注册: <两侧各缺了什么、补了什么、抽查结果> +## 需要用户决定的事项 +- <冲突、密码泄露、codex 独有 skill 去留……没有则写"无"> +``` + +## 边界与安全 + +- 本 skill 会 push 到远端仓库——commit 前把将要提交的文件列表给用户过目 + (自动化场景下至少写进报告)。绝不 `push --force`。 +- `~\skills_env.md` 含凭据,任何情况下不将其内容或凭据字段写入仓库内文件、 + commit message 或对外输出。 +- 不动 `~\.codex\skills\.system\`(Codex 自带系统 skills)和 + `~\.claude\skills\.system\`。 +- 修改 `~\.codex\config.toml` 前先备份为 `config.toml.bak-<日期>`,TOML 写坏 + 会导致 Codex 起不来。 diff --git a/tools-sync/references/skills_env_template.md b/tools-sync/references/skills_env_template.md new file mode 100644 index 0000000..ba19620 --- /dev/null +++ b/tools-sync/references/skills_env_template.md @@ -0,0 +1,68 @@ +# skills_env — 本机环境档案(模板) + +> 复制本文件到 `~\skills_env.md` 后逐项核对。此文件**只留在本机,不进 git**: +> 每台电脑的路径/IP/凭据不同。skills 中凡是机器相关的参数都从这里取值。 +> 值为 `KEY = value` 形式,方便脚本和模型直接检索。 + +## 同步仓库 + +``` +SKILLS_REPO_URL = http://git.rpchannel.com/bowei/skills-win.git +SKILLS_REPO_USER = bowei +SKILLS_REPO_PASS = <填写,或留空表示已存入 Windows 凭据管理器> +CLAUDE_SKILLS_DIR = C:\Users\\.claude\skills +CODEX_SKILLS_DIR = C:\Users\\.codex\skills +``` + +凭据说明:首次 clone 成功后运行 `git config --global credential.helper manager`, +让 Windows 凭据管理器接管,此后 `SKILLS_REPO_PASS` 可以清空。 + +## Python / Conda + +``` +CONDA_ROOT = C:\ProgramData\miniconda3 +CONDA_LANGGRAPH_PYTHON = C:\Users\\.conda\envs\langgraph\python.exe +``` + +## EDA / 硬件工具 + +``` +VIVADO_PATH = D:\Xilinx\Vivado\2018.1\bin\vivado.bat +KICAD_PYTHONPATH = C:\Program Files\KiCad\10.0\bin\Lib\site-packages +MATLAB_MCP_EXE = C:\Users\\.matlab\agentic-toolkits\bin\matlab-mcp-server.exe +``` + +## 板卡 / 仪器 IP + +``` +N9320B_HOST = 192.168.1.147 # Keysight N9320B 频谱仪 +SMOD_BOARD_IP = 192.168.1.143 # SMOD 板(ATSC 激励器远程升级) +``` + +## MCP 注册配置(真源:MCP_DESIGN_ROOT\mcp_list.md) + +``` +MCP_DESIGN_ROOT = E:\MCP_design +``` + +每个 MCP 在 Claude Code(`~\.claude.json` 的 `mcpServers`)和 Codex +(`~\.codex\config.toml` 的 `[mcp_servers.*]`)中应使用**同一份** command/args/env。 +标准配置如下(路径变量引用上文条目): + +| name | command | args | env | +|------------|----------------------------------|---------------------------------------------------|-----| +| vivado-mcp | {CONDA_LANGGRAPH_PYTHON} | E:\MCP_design\vivado-mcp-main\run_server.py(Codex)/ -m vivado_mcp(Claude) | VIVADO_PATH={VIVADO_PATH} | +| mcp_n9320b | {CONDA_LANGGRAPH_PYTHON} | E:\MCP_design\mcp_n9320b\n9320b_mcp.py | N9320B_HOST={N9320B_HOST} | +| cubemx | E:\MCP_design\MCP_cubemx\cubemx-mcp\.venv\Scripts\python.exe | E:\MCP_design\MCP_cubemx\cubemx-mcp\server\main.py | PYTHONPATH=E:\MCP_design\MCP_cubemx\cubemx-mcp\server | +| kicad | node | E:\MCP_design\KiCAD-MCP-Server\dist\index.js | NODE_ENV=production; PYTHONPATH={KICAD_PYTHONPATH}; KICAD_AUTO_LAUNCH=false | +| simetrix | \Scripts\mcp-simetrix.exe | | SIMETRIX_PROJECT_ROOT=E:\ | +| matlab | {MATLAB_MCP_EXE} | --matlab-session-mode=existing --extension-file=...\simulink\tools\tools.json | | + +Codex 侧所有 stdio MCP 额外加 `PYTHONIOENCODING=utf-8`、`PYTHONUTF8=1`。 + +## 本机状态备注(同步时更新) + +``` +LAST_SYNC = <日期> +TODO = <遗留事项> +``` diff --git a/tools-sync/scripts/sync_repos.ps1 b/tools-sync/scripts/sync_repos.ps1 new file mode 100644 index 0000000..acca70d --- /dev/null +++ b/tools-sync/scripts/sync_repos.ps1 @@ -0,0 +1,113 @@ +# tools-sync: mechanical git sync of the two skills checkouts. +# Claude side (~\.claude\skills) is the primary working copy: commit -> pull --rebase -> push. +# Codex side (~\.codex\skills) is a mirror checkout of the same repo: bootstrap if needed, then pull. +# Judgment calls (conflicts, codex-only skills worth keeping) are left to the caller. + +param( + [Parameter(Mandatory = $true)][string]$RepoUrl, + [string]$ClaudeSkills = "$env:USERPROFILE\.claude\skills", + [string]$CodexSkills = "$env:USERPROFILE\.codex\skills", + [string]$Branch = "main", + # Bootstrap of the codex checkout stops when local files differ from the repo; + # -Force lets it overwrite them (caller must have rescued codex-only edits first). + [switch]$Force, + # Report what would happen without committing/pushing/checking out. + [switch]$DryRun +) + +$ErrorActionPreference = "Stop" + +function Invoke-Git { + param([string]$Dir, [string[]]$GitArgs) + # PS 5.1: git writes progress/warnings to stderr; 2>&1 wraps them in ErrorRecords + # which terminate under EAP=Stop. Relax EAP locally and stringify every line. + $prev = $ErrorActionPreference + $ErrorActionPreference = "Continue" + $out = & git -C $Dir @GitArgs 2>&1 | ForEach-Object { "$_" } + $code = $LASTEXITCODE + $ErrorActionPreference = $prev + return @{ Code = $code; Out = (($out -join "`n")).Trim() } +} + +function Fail([string]$Msg) { Write-Host "FAIL: $Msg"; exit 1 } + +# ---------- Phase 1: primary checkout (~\.claude\skills) ---------- +Write-Host "=== [1/2] primary: $ClaudeSkills ===" +if (-not (Test-Path (Join-Path $ClaudeSkills ".git"))) { + Fail "$ClaudeSkills is not a git repo. Clone $RepoUrl there first (new machine: git clone $RepoUrl `"$ClaudeSkills`")." +} + +$st = Invoke-Git $ClaudeSkills @("status", "--porcelain") +if ($st.Out) { + Write-Host "local changes:"; Write-Host $st.Out + if ($DryRun) { + Write-Host "(dry-run) would commit these changes" + } else { + Invoke-Git $ClaudeSkills @("add", "-A") | Out-Null + $msg = "sync: $env:COMPUTERNAME $(Get-Date -Format yyyy-MM-dd)" + $r = Invoke-Git $ClaudeSkills @("commit", "-m", $msg) + if ($r.Code -ne 0) { Fail "commit failed:`n$($r.Out)" } + Write-Host "committed: $msg" + } +} else { + Write-Host "working tree clean" +} + +if (-not $DryRun) { + $r = Invoke-Git $ClaudeSkills @("pull", "--rebase", "origin", $Branch) + if ($r.Code -ne 0) { + Write-Host $r.Out + Fail "pull --rebase failed on primary. Resolve conflicts (or 'git rebase --abort') then re-run." + } + Write-Host $r.Out + $r = Invoke-Git $ClaudeSkills @("push", "origin", $Branch) + if ($r.Code -ne 0) { Write-Host $r.Out; Fail "push failed (auth? see skill Phase 1)." } + Write-Host "primary in sync with origin/$Branch" +} + +# ---------- Phase 2: mirror checkout (~\.codex\skills) ---------- +Write-Host "=== [2/2] mirror: $CodexSkills ===" +if (-not (Test-Path $CodexSkills)) { New-Item -ItemType Directory -Force $CodexSkills | Out-Null } + +if (-not (Test-Path (Join-Path $CodexSkills ".git"))) { + Write-Host "bootstrapping mirror as a checkout of $RepoUrl" + if ($DryRun) { Write-Host "(dry-run) would git init + fetch + align to origin/$Branch"; exit 0 } + Invoke-Git $CodexSkills @("init") | Out-Null + Invoke-Git $CodexSkills @("remote", "add", "origin", $RepoUrl) | Out-Null + $r = Invoke-Git $CodexSkills @("fetch", "origin", $Branch) + if ($r.Code -ne 0) { Write-Host $r.Out; Fail "fetch failed during bootstrap." } + # Point index at origin/main without touching files, so status shows local-vs-repo drift. + Invoke-Git $CodexSkills @("reset", "origin/$Branch") | Out-Null + Invoke-Git $CodexSkills @("branch", "-f", $Branch, "origin/$Branch") | Out-Null + Invoke-Git $CodexSkills @("checkout", $Branch) 2>$null | Out-Null + $drift = Invoke-Git $CodexSkills @("status", "--porcelain") + $modified = ($drift.Out -split "`n" | Where-Object { $_ -match '^\s*(M|D)' }) + if ($modified -and -not $Force) { + Write-Host "these tracked files differ locally from the repo:" + $modified | ForEach-Object { Write-Host " $_" } + Fail "codex dir has local drift. Rescue anything worth keeping into the primary checkout, then re-run with -Force." + } + $r = Invoke-Git $CodexSkills @("checkout", "-f", $Branch) + if ($r.Code -ne 0) { Write-Host $r.Out; Fail "checkout failed during bootstrap." } + Invoke-Git $CodexSkills @("branch", "--set-upstream-to=origin/$Branch", $Branch) | Out-Null + Write-Host "mirror bootstrapped at origin/$Branch (untracked codex-local files left untouched)" +} else { + if ($DryRun) { + Invoke-Git $CodexSkills @("fetch", "origin", $Branch) | Out-Null + $r = Invoke-Git $CodexSkills @("log", "--oneline", "HEAD..origin/$Branch") + Write-Host "(dry-run) mirror is behind by:" + if ($r.Out) { Write-Host $r.Out } else { Write-Host " (nothing, or not yet a checkout)" } + } else { + $st2 = Invoke-Git $CodexSkills @("status", "--porcelain") + if ($st2.Out) { + Write-Host "WARNING: mirror has local changes (should be edited on the primary side instead):" + Write-Host $st2.Out + } + $r = Invoke-Git $CodexSkills @("pull", "--rebase", "origin", $Branch) + if ($r.Code -ne 0) { Write-Host $r.Out; Fail "pull failed on mirror." } + Write-Host $r.Out + Write-Host "mirror in sync with origin/$Branch" + } +} + +Write-Host "=== sync_repos done ==="