Compare commits

..
3 Commits
Author SHA1 Message Date
Myfro9andClaude Opus 5 7d81f59ee0 zynq-imx6-flow: analyze 阶段增加频谱仪取证要求
RF/发射类验收不能只靠数字侧证据。analyze 节点新增一节,规定用已注册的
频谱仪 MCP(N9320B / DSA815)取证:需求点名哪台就用哪台,未点名默认
DSA815;只读抓图优先,改了设置要还原;artifact 路径、峰值频率/功率与
扫描设置一并写进 analysis_log。频谱仪连不上时报缺证,不得仅凭数字证据
判定 RF 需求通过。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tMFjKGgxdmxJ28dyXdx1u
2026-09-02 14:54:09 -05:00
Myfro9andClaude Opus 5 bb71470e07 sync: WIN11-BOWEI 2026-09-02 新增 bmem 记忆 skill;imx6-flow/plan-review 文档与脚本更新
远端 pack 服务仍故障(git-upload-pack 返回 0008NAK 后截断),push 待服务器恢复。
未跟踪的 5 个归档临时副本(rtl-ip-replace / sim-rtl-flow / simulink_rtl_dev /
simulink-rtl-redev / zynq-imx6-flow)刻意不入库,待 pull 后由远端提供。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0135YszNe4pwwJxwaLqKTCKG
2026-09-02 14:53:28 -05:00
Myfro9andClaude Opus 4.8 9c1012f5b8 sync: WIN11-BOWEI 2026-08-10 MCP审计脚本python回退到conda(MS Store存根修复)
sync_mcp_registrations.ps1 默认用 PATH 上的 python 解析 Codex TOML,
但 Windows 上常见的是 Microsoft Store python 存根(exit 49, 无 stdlib)。
新增 Resolve-PythonCommand: python 不带 tomllib 时自动回退到 skills_env.md
的 CONDA_LANGGRAPH_PYTHON, 再退到 python3/py。显式 -PythonCommand 仍优先。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-02 14:53:28 -05:00
8 changed files with 643 additions and 5 deletions
+282
View File
@@ -0,0 +1,282 @@
---
name: bmem
description: 用 bmem 记忆系统读写跨机器、跨 agent 的长期记忆——判断一条信息该进全局/项目/机器/log 哪一层、把 ./Notes 下的 PRxx/Taskxx/Resultxx 导入记忆、会话收尾该沉淀什么、怎么查以前做过什么、以及 bmem 命令报错时怎么修。当用户提到"记住"、"记一下"、"存到记忆里"、"之前/以前是不是做过"、"上次怎么解决的"、"查一下历史"、"这台机器装了什么"、"这个项目在别的机器上在哪"、"账号密码记下来"、"同步记忆"、"这个项目不做了/从记忆里删掉"、"bmem"、"记忆系统",或者在一个根目录有 bmem_<prjID>.md 的项目里开始实质性工作、需要知道过去的决策和踩过的坑时,必须使用此技能——即使用户没有说"skill"或"bmem"两个字。用户改完 ./Notes 下的需求/方案/结果文档、或一段实质性工作收尾时也要用它。bmem 命令报错(未加入 bmem / 不在项目内 / PG 不可达 / git 冲突 / 重复注册)时同样用它。
---
# bmem:Claude Code 与 Codex 共享的跨机器长期记忆
bmem 把记忆分四层:三层是 Markdown(用 gitea 在多机之间同步),第四层 log 是中心
PostgreSQL + 本机 SQLite 镜像。**Markdown 是三层记忆的唯一事实源,SQLite 镜像是可
随时删掉重建的派生物。**
一句话分工:**Markdown 记知识(我们知道什么、为什么这么定),log 记经历(什么时候、发生了什么)。**
| 层 | 回答什么 | 存在哪 | 判据 |
|---|---|---|---|
| 全局 global | 我通常怎么工作、跨项目的通用规则 | `~/bmem.md` | 换个项目还成立吗?成立 → global |
| 项目 project | 这个项目是什么、为什么这样设计 | `<项目根>/bmem_<prjID>.md` | 项目结束后还有意义吗?没有 → project |
| 机器 machine | 这台机器有什么、能做什么、账号配在哪 | `~/bmem_<machineID>.md` | 换台机器就不成立了吗?是 → machine |
| log | 这个项目发生过什么、Notes 里写了什么 | 中心 PG + `~/.bmem/bmem_local.sqlite` | 这是"发生了一件事"还是"我们知道一件事"?发生 → log |
`~/bmem.md` 里还有两张注册表:哪些机器加入了系统、每个项目在每台机器上的绝对路径。
**项目身份是 prjID,不是路径**——同一个项目在不同机器上路径不同,引用项目内文件要用相对路径。
## 先确认它能用
```bash
bmem help
```
`bmem` 不在 PATH 上时,用 `<langgraph conda env>/Scripts/bmem.exe`(路径见 `~/skills_env.md`)。
本机还没加入系统会提示 `run bmem install first`;当前目录不属于任何项目会提示先 `bmem init`。
## 开工之前:先把记忆取回来
bmem 没有 `context` 命令——**记忆就是几个 Markdown 文件,直接读**:
1. `~/bmem.md` 的「全局规则」节:适用于所有项目的硬约束,**先读这个**。
2. 当前项目根目录的 `bmem_<prjID>.md`:项目简介、规则/约束、设计决策、当前状态与未解决问题。
3. 需要了解本机环境(工具装在哪、账号怎么配)时再读 `~/bmem_<machineID>.md`。
要查"以前做过没有、上次怎么解决的",用本 skill 自带的检索脚本查 log 层:
```bash
python <skill>/scripts/bmem_recall.py "时延 扩展" # 语义条目 + 文档章节 + 事件
python <skill>/scripts/bmem_recall.py "CP 长度" --project 52-link
python <skill>/scripts/bmem_recall.py --kind decision # 这个项目做过哪些决策
python <skill>/scripts/bmem_recall.py --recent 10 # 最近发生了什么
```
它只读本机镜像,不连中心库,离线可用。中文按 bigram 索引,**两到四个字的词组比整句命中率高**。
**为什么值得在动手前花这一分钟**:你即将做的决定可能三个月前已经做过,理由写在某条
decision 里;你即将踩的坑可能上次已经踩过并记了下来。事后返工的成本远大于开头查一次。
命中只是线索,不是依据——**按结果里的 `项目:Notes/xxx.md § N` 回读原文再下结论。**
## ./Notes 是这个记忆系统的一等公民
用户的工作习惯是:`PRxx.md`(需求)→ `Taskxx.md`(方案)→ `Resultxx.md`(结果),
都放在项目的 `./Notes` 下。bmem 按文件名把它们认成一组任务,全文入库、按标题拆成章节。
```bash
bmem notes # 扫描 ./Notes,把变化同步进记忆
bmem notes --check # 只比对差异,不写入任何内容
bmem notes --sync # 导入后立即同步到中心库
```
每次扫描会报出四种状态:**新增 / 变更 / 一致 / 消失**。判定用的是内容的 canonical
sha256(去 BOM、CRLF→LF、NFC),所以改了空行或换行符不会被误判成变更,反复跑是安全的——
内容没变就什么都不写。
两件值得知道的事:
- **文档从 ./Notes 里删掉后会被标记为"消失"**:它的历史修订全部保留(那是历史),
但不再计入任务闭环——否则闭环会显示成"由一个已不存在的文件闭合",那是假信息。
文件放回来会自动恢复。
- **项目记忆 `bmem_<prjID>.md` 里有一个自动维护的「Notes 索引」小节**:任务闭环表
(含「最近更新」= 日期 + 内容指纹)+ 其它文档清单。它完全由文件名、标题和内容 sha
确定性推导(不经 LLM),**内容变了它就变、没变它就一动不动**(连日期都不写,
否则会天天重写文件、每台机器都收到空更新);且只动这一节,手写的「设计决策」等内容不会被碰。
好处是:**只读项目记忆就能知道有哪些文档、哪些任务还没闭环、各自是不是最新的**,
不必查数据库。
**目录铁律(这是全局规则 G-0001)**:`./Notes` 根目录只允许放用户手动创建、或用户明确要求
创建的 md 文档。**你不能把运行过程中自动产生的状态、日志、中间结果写进 `./Notes/` 根目录**,
那些只能写到 `./Notes/auto/`(该子目录不会被导入记忆)。临时脚本和分析产物同理。
什么时候跑 `bmem notes`:用户新写或改完一篇 PR/Task/Result 之后、一段实质性工作收尾时。
重复跑是安全的——内容没变就什么都不做(不脏数据、不写 journal)。
### 语义条目:把文档内容变成可检索的结构
`bmem notes` 只做到"全文 + 章节"。**把文档内容抽成需求/决策/结论/遗留问题/参数/账号指针,
是你的活**——bmem 自己不调用 LLM。跑完 `bmem notes` 它会打印新修订清单和 sha256,
按这个格式写 JSON 再喂回去:
```json
{"facts": [
{"rel_path": "Notes/Task07.md",
"content_sha256": "<该修订的 sha256,从 bmem notes 的输出里抄>",
"items": [
{"kind": "decision", "title": "N2 的 CP 取 2us",
"body": "1us 余量不足,4us 损失过多有效符号时间;2us 是鲁棒性与效率的折中。",
"status": "done", "event_date": "2026-09-01", "section_no": 3}
]}
]}
```
```bash
bmem notes --facts facts.json --agent claude-code --model <你的模型名>
```
`kind` 取值:`requirement` `decision` `result` `issue` `param` `credential-pointer` `lesson` `other`。
`status` 取值:`open` `done` `superseded`(可省略)。
三件事值得知道:
- 条目**挂在具体修订的 sha256 下**,所以文档一改,旧条目自动退出"当前"视图,不会拿过期结论骗人。
- 同一修订**重复提交 = 重抽**,bmem 会写成新一代 `extraction_gen`,旧代保留但不再是当前——
抽得不好可以重来,不用怕污染。
- 抽取失败不影响主流程:全文和章节已经进库了,检索仍然兜得住。
**抽什么值得抽**:设计取舍和它的理由、被否决的方案和否决理由、遗留问题、关键参数、
踩过的坑。**不值得抽**:文档结构的复述、代码里已经写清楚的东西。
## 判断:这条信息该进哪一层
这是用 bmem 时唯一真正需要判断力的地方。用最上面那张表的判据问自己一遍。
```bash
# 全局:跨项目成立
bmem add --global "改完 Simulink 原生模型必须同步仿真模型,否则 RTL 已变而仿真还在验证旧行为"
# 项目:这个项目的约束/结论(默认写进「项目规则/约束」)
bmem add --prj "ADC 采样率固定 61.44MSps,下游所有速率按它推导"
# 项目:设计决策要写进「设计决策」节,并说清备选项和否决理由
bmem add --prj --section "设计决策" "N2 的 CP 取 2us:1us 余量不足,4us 损失过多有效符号时间"
# 机器:只对这台机器成立的事实
bmem add --machine "ADRV9009 评估板接在这台机器上,JTAG 走 USB-Blaster II"
bmem add --machine --section "账号/服务配置" "gitea 账号 bowei,凭证在 Windows 凭据管理器"
```
条目会自动编号(G-/P-/D-/M-)并带日期,追加写入,不会覆盖已有内容。
**`--section` 决定它落在文件的哪一节,不给就用默认节。** 各层可用的节和默认值:
| 层 | 默认节 | 其它可用节 |
|---|---|---|
| `--global` | 全局规则 | (通常就这一个) |
| `--prj` | 项目规则/约束 | 设计决策、当前状态 / 未解决问题、项目简介 |
| `--machine` | 工具环境 | 账号/服务配置、本机项目位置 |
节名要**原样写全**(含空格,如 `--section "当前状态 / 未解决问题"`);写一个不存在的节名会新建一节。
这件事值得留意:**尚未解决的问题不是"规则/约束"**。用默认节会把一条待办混进项目铁律里,
半年后读的人分不清哪些是必须遵守的、哪些只是还没做完的。用
`bmem add --prj --section "当前状态 / 未解决问题" "..."` 直接放对位置——
不要写完再去手工编辑 Markdown 搬家,那样绕过了自动编号,也容易破坏表格和已有条目。
**凭证的处理**:默认只在机器记忆里记「账号 + 凭证放在哪」,明文口令留在本机
`~/.bmem/config.toml`(不进任何仓库)。用户明确要求把某条明文写进机器记忆时照做——
那是他的私有 gitea,由他决定;但不要自作主张替他把口令写进去。
## 什么不值得记
写太多等于没记,检索会被噪音淹没:
- **代码和 git 历史已经说清楚的**:文件结构、某次修复的 diff、函数签名。记忆要回答"为什么这么改"。
- **只对当前这轮对话有意义的中间状态**:临时变量、调试到一半的猜测、下一步计划。
- **未经验证的猜测**——除非明确写成 `issue` 并标 `status: open`。
- **会话转录全文**。
反过来有一类东西特别值得记而常常被忘记:**被否决的方案和否决它的理由**。
只记结论,半年后会有人(很可能是你自己)重新提出同一个被否决的方案。
## 收尾之前
一段实质性工作结束前:
1. 该写的记忆按层写进去(`bmem add`);
2. 用户新增/改动的 Notes 文档导进来,并附上本次会话摘要:
```bash
bmem notes --session-summary <摘要文件> --agent claude-code
```
摘要文件写到临时目录或 `./Notes/auto/`,**不要写进 `./Notes/` 根目录**。
3. 对新修订生成语义条目 JSON 并入库(见上);
4. 同步出去:
```bash
bmem sync
```
`bmem sync` 做两件事:git 同步三层 Markdown(工作副本 ↔ `~/.bmem/repo` clone ↔ gitea),
以及与中心 PostgreSQL 双向同步 log 层。**它只碰 bmem 自己的文件,不会动你项目的 git 分支。**
## 多机与本机状态
```bash
bmem machines # 所有机器 + 各自的项目和路径
bmem machines --project 52-link # 这个项目在哪些机器上、各自路径、最近谁动过
bmem machines --json # 结构化输出
bmem sqlite # 本机镜像:位置、表结构、项目情况、内容摘要
bmem sqlite --schema # additionally 每张表的列定义
```
两条都**只读本机镜像、不连中心库**,离线可用。
`bmem machines` 会报注册表漂移:某机器只在 `bmem.md` 里有 = 它还没 push 或你还没 pull 到;
只在镜像里有 = **本机的 bmem.md 落后了**。这不是错误,是状态,读的时候要把它当成
"我看到的可能不是最新的"。
## 新项目、新机器
```bash
bmem install # 每台机器一次:生成 machineID、拉记忆仓库、装 CLAUDE.md/AGENTS.md adapter
bmem init # 每个项目一次,在项目根目录跑
```
`bmem init` 自己会判断三种情况:本机已注册过就提示不要重复注册;`bmem.md` 里已有这个项目
(说明别的机器建过)就从记忆仓库把 `bmem_<prjID>.md` 取回来并登记本机路径;全新项目才新建。
**所以在第二台机器上直接跑 `bmem init` 是对的**,不会造成身份分裂。
`bmem install` 幂等,可以反复跑:它会顺带把机器记忆里的 OS、用户根目录刷新成当前实际值。
## 从本机注销一个项目
```bash
bmem delete # 只打印会动哪些东西,不做任何改动
bmem delete --yes --sync # 确认执行并同步给其它机器
```
**它只注销本机**:删掉项目根目录的 `bmem_<prjID>.md`(**直接删除,不留备份文件**——
`find_project` 是向上查找的,残留一份会让它下面所有子目录都被误判成这个项目的根;
删除前会先把内容存进记忆仓库 `~/.bmem/repo/projects/`,所以内容不丢)、
`bmem.md` 里本机那一行、机器记忆的位置行、项目 `CLAUDE.md`/`AGENTS.md` 里的 BMEM 块,
以及中心库里 `project_locations` 中本机那一行。
**它不会删**:`./Notes` 下的任何文档、log 层的历史(修订/章节/条目/事件都留在中心库和镜像里)、
其它机器的登记、记忆仓库里的 `projects/bmem_<prjID>.md`。所以将来在任意机器上
`bmem init` 就能重新接上,历史一条不少。
三件要注意的事:
- **默认只预览**。不加 `--yes` 什么都不会发生,输出会逐项列出"将删除"与"不会删除"。
破坏性操作先看清楚再动手,这是这条命令唯一的使用纪律。
- 预览会告诉你**还有哪些机器登记着这个项目**。如果显示"本机是最后一台",说明删完就没有
任何机器持有它了——不是不能删,但要清楚这一步的含义。
- 中心库的删除是**排队执行**的:离线也能删,删除意图记在本地并写进 journal,
下次 `bmem sync` 成功后其它机器才会看到。所以删完记得 sync。
## 出问题时
| 症状 | 含义与处理 |
|---|---|
| `pg: 不可用(cannot reach PostgreSQL…)` | 中心库连不上。**数据没丢**:留在本机 outbox 和 `~/.bmem/journal/` 里,网络恢复后重跑 `bmem sync` 即可。所有命令本地照常可用 |
| `git: 冲突,已中止 rebase` | 两台机器改了同一个记忆文件。bmem **不会自动合并**(Markdown 是事实源),去 `~/.bmem/repo` 手工合并再 `bmem sync`。本地内容一条没丢 |
| `pull failed before any merge` | 传输失败,不是冲突。本地提交完好,push 仍会照常尝试 |
| 本地库损坏 / 想清空重来 | 直接删 `~/.bmem/bmem_local.sqlite` 再 `bmem sync`:已同步部分从中心库拉回,未推送部分由 journal 回放补齐。**这是设计好的恢复路径,不是下策** |
| `项目 X 已在本机注册,无需重复注册` | 正常提示,不是错误 |
| `facts reference an unknown revision` | facts JSON 里的 sha256 对不上。先跑一次 `bmem notes`,用它打印出来的 sha256 |
| `unknown fact kind` / `unknown status` | 枚举值写错了,对照上面的取值表 |
| 想知道还有多少没推上去 | `bmem sqlite` 底部的「未推送」和 journal 那一行 |
**一条通则**:bmem 的报告里出现的不都是待办。「pg 不可用」「bmem.md 落后了」描述的是状态,
先判断这个状态该不该改,再决定要不要动手。为了让输出变干净而执行有副作用的命令,
是把手段当成了目的。
## 命令速查
| 用途 | 命令 |
|---|---|
| 读记忆 | 直接读 `~/bmem.md`、`bmem_<prjID>.md`、`~/bmem_<machineID>.md` |
| 查历史 | `scripts/bmem_recall.py <关键词>` / `--kind` / `--recent` |
| 写记忆 | `bmem add --global\|--prj\|--machine [--section 节名] <内容>` |
| 导入 Notes | `bmem notes [--check] [--session-summary F] [--facts F] [--sync]` |
| 看多机/本机状态 | `bmem machines [--project P] [--json]` / `bmem sqlite [--schema] [--json]` |
| 同步 | `bmem sync` |
| 安装与初始化 | `bmem install` / `bmem init` |
| 注销项目 | `bmem delete`(预览)/ `bmem delete --yes --sync`(执行) |
| 帮助 | `bmem help` |
已知边界:当前 gitea 的 pack 服务故障,**push 正常但 clone/fetch 失败**,所以第二台机器
暂时拉不到 Markdown 层(log 层走 PostgreSQL 不受影响,Notes 全文在中心库里)。
看到 `bmem machines` 报"本机的 bmem.md 落后了"时,先想到这条。
+51
View File
@@ -0,0 +1,51 @@
{
"skill_name": "bmem",
"notes": "评测在隔离沙箱里跑:BMEM_HOME / BMEM_USER_HOME 指向临时目录,PG 与 git remote 关闭,每个 run 一份独立副本,绝不触碰用户真实记忆。沙箱预置了一个 mine-radio 项目:5 篇 Notes、3 条语义条目(含一条'CP 取 2us'的决策和一条 open issue),以及项目/机器记忆若干条。",
"evals": [
{
"id": 0,
"name": "recall-before-work",
"prompt": "我准备动手改 mine-radio 的 OFDM 参数,想把 CP 从 2us 改成 1us 来提吞吐。动手之前先帮我确认下有没有什么坑。",
"expected_output": "先查记忆,发现 1us 这个方案半年前已被明确否决(巷道实测多径时延扩展 1.6us,1us 会引入符号间干扰),带来源引用地告诉用户,而不是泛泛讲 CP 原理。理想情况还会提到相关的 open issue(帧 182 CP 边界错位)。不改任何源文件。",
"assertions": [
{"id": "a0", "text": "查询了记忆系统(跑了 recall 助手,或读了项目记忆 bmem_mine-radio.md / Notes/Task00.md)"},
{"id": "a1", "text": "明确指出 1us 这个方案此前已被否决,而不只是泛泛讨论 CP 的取舍"},
{"id": "a2", "text": "给出了否决的具体依据:巷道实测多径时延扩展约 1.6us,1us 小于它"},
{"id": "a3", "text": "回答里带了可回溯的来源(Notes/Task00.md、bmem_mine-radio.md 或具体条目编号)"},
{"id": "a4", "text": "提到了记忆里那条 open issue(帧 182 附近 CP 边界错位)"},
{"id": "a5", "text": "没有修改任何项目源文件或 Notes 文档"}
],
"files": []
},
{
"id": 1,
"name": "session-closeout",
"prompt": "自适应调制那部分做完了,Result01.md 我刚写完。帮我把这次的东西沉淀到记忆里。",
"expected_output": "跑 bmem notes 把 Result01.md 导入(新增 1 条修订),按打印出的 sha256 生成语义条目 JSON 并用 --facts 入库(至少抓到 CQI 上报周期 10ms 偏慢、改 5ms 这个实测发现),附会话摘要,最后 sync。临时文件不得写进 Notes 根目录。",
"assertions": [
{"id": "b0", "text": "运行了 bmem notes,Result01.md 被导入(本地库 notes_revisions 出现 Notes/Result01.md)"},
{"id": "b1", "text": "生成了 facts JSON 并通过 bmem notes --facts 入库(doc_facts 行数增加)"},
{"id": "b2", "text": "facts 里的 content_sha256 与 Result01.md 的真实修订一致(没有伪造或用错 sha)"},
{"id": "b3", "text": "抽出的条目抓住了实质发现:CQI 上报周期 10ms 偏慢 / 改为 5ms"},
{"id": "b4", "text": "没有把摘要、JSON 等中间文件写进 Notes/ 根目录(全局规则 G-0001)"},
{"id": "b5", "text": "记录了本次会话摘要(用了 --session-summary,sessions 表出现记录)"},
{"id": "b6", "text": "执行了 bmem sync,并正确说明 PG 不可用时数据留在本地 outbox/journal 不会丢"}
],
"files": []
},
{
"id": 2,
"name": "layer-routing",
"prompt": "记两件事:一是这台机器的 Vivado 装在 D:\\Xilinx\\Vivado\\2024.2;二是以后所有项目里,涉及定点位宽的改动都必须先跑一遍定点仿真再上板。",
"expected_output": "两条信息分属不同层:Vivado 安装路径只对这台机器成立 → 机器记忆;定点仿真规则跨项目成立 → 全局记忆。正确路由到 ~/bmem_lab-win-01.md 与 ~/bmem.md,而不是一股脑塞进项目记忆。",
"assertions": [
{"id": "c0", "text": "Vivado 安装路径写进了机器记忆文件 bmem_lab-win-01.md"},
{"id": "c1", "text": "定点仿真规则写进了全局记忆文件 bmem.md 的全局规则节"},
{"id": "c2", "text": "两条都没有被错误地写进项目记忆 bmem_mine-radio.md"},
{"id": "c3", "text": "用的是 bmem add 命令写入,而不是手工编辑 Markdown 文件"},
{"id": "c4", "text": "向用户说明了这两条为什么分别进机器层与全局层"}
],
"files": []
}
]
}
+193
View File
@@ -0,0 +1,193 @@
#!/usr/bin/env python
"""在本机 bmem 镜像里检索历史:语义条目 / 文档章节 / 事件。
bmem 的 CLI 目前只负责写入与同步,没有检索命令,但它把 Notes 全文、章节拆分、
语义条目和事件都存进了本机 SQLite 镜像。这个脚本把 bmem 当库调用,只读地把它们
查出来——不改任何数据,不连中心库,离线可用。
用法:
python bmem_recall.py "多径 时延" # 三类都查
python bmem_recall.py "CP 长度" --project amem # 限定项目
python bmem_recall.py "时间戳" --kind decision # 只看某类语义条目
python bmem_recall.py --recent 10 # 不给关键词=看最近发生了什么
中文按 bigram 检索(bmem 写入时做了滑窗补偿),所以两到四个字的词组比整句命中率高。
"""
from __future__ import annotations
import argparse
import os
import subprocess
import sys
from pathlib import Path
def _bootstrap() -> None:
"""bmem 装在 langgraph conda 环境里,当前解释器没有时换一个再跑。"""
try:
import bmem # noqa: F401
return
except ImportError:
pass
home = Path(os.path.expanduser("~"))
candidates = [
home / "miniconda3" / "envs" / "langgraph" / "python.exe",
home / ".conda" / "envs" / "langgraph" / "python.exe",
home / "miniconda3" / "envs" / "langgraph" / "bin" / "python",
home / ".conda" / "envs" / "langgraph" / "bin" / "python",
]
for python in candidates:
if python.exists() and Path(sys.executable).resolve() != python.resolve():
env = dict(os.environ, PYTHONUTF8="1", PYTHONIOENCODING="utf-8")
raise SystemExit(
subprocess.run([str(python), __file__, *sys.argv[1:]], env=env).returncode
)
raise SystemExit(
"找不到 bmem 包。它以 editable 方式装在 langgraph conda 环境里;\n"
"确认 `bmem help` 能跑,或查 ~/skills_env.md 里记录的解释器路径。"
)
_bootstrap()
from bmem import config as config_mod # noqa: E402
from bmem.localdb import Database, fts_query # noqa: E402
def _print_facts(db: Database, query: str, prj_id: str | None, kind: str | None, limit: int) -> int:
sql = (
"SELECT f.prj_id, f.rel_path, f.kind, f.status, f.title, f.body, f.event_date, "
"f.extraction_gen FROM doc_facts f WHERE 1=1"
)
params: list[object] = []
if prj_id:
sql += " AND f.prj_id = ?"
params.append(prj_id)
if kind:
sql += " AND f.kind = ?"
params.append(kind)
if query:
sql += " AND (f.title LIKE ? OR f.body LIKE ?)"
params += [f"%{query}%", f"%{query}%"]
sql += " ORDER BY f.event_date DESC NULLS LAST, f.rel_path, f.fact_no LIMIT ?"
params.append(limit)
rows = db.rows(sql, params)
if not rows:
return 0
print("=== 语义条目 (doc_facts) ===")
for row in rows:
status = f" [{row['status']}]" if row["status"] else ""
date = f" {row['event_date']}" if row["event_date"] else ""
print(f"\n[{row['kind']}]{status}{date} {row['title']}")
print(f" 来源 {row['prj_id']}:{row['rel_path']} (第 {row['extraction_gen']} 代抽取)")
body = row["body"].strip().replace("\n", "\n ")
print(f" {body[:400]}{'…' if len(row['body']) > 400 else ''}")
return len(rows)
def _print_sections(db: Database, query: str, prj_id: str | None, limit: int) -> int:
"""章节级命中:比整篇文档精确,直接告诉你该回读哪一节。"""
sql = (
"SELECT s.prj_id, s.rel_path, s.section_no, s.heading, s.level, s.body_md "
"FROM note_sections s JOIN ("
" SELECT prj_id, rel_path, content_sha256 FROM notes_revisions r1 "
" WHERE imported_at = (SELECT max(imported_at) FROM notes_revisions r2 "
" WHERE r2.prj_id=r1.prj_id AND r2.rel_path=r1.rel_path)"
") latest ON latest.prj_id=s.prj_id AND latest.rel_path=s.rel_path "
" AND latest.content_sha256=s.content_sha256 "
"WHERE s.body_md LIKE ?"
)
params: list[object] = [f"%{query}%"]
if prj_id:
sql += " AND s.prj_id = ?"
params.append(prj_id)
sql += " ORDER BY s.prj_id, s.rel_path, s.section_no LIMIT ?"
params.append(limit)
rows = db.rows(sql, params)
if not rows:
return 0
print("\n=== 文档章节 (note_sections,仅最新修订) ===")
for row in rows:
heading = row["heading"] or "(前言)"
print(f"\n{row['prj_id']}:{row['rel_path']} § {row['section_no']} {heading}")
body = row["body_md"]
pos = body.lower().find(query.lower())
start = max(0, pos - 120) if pos >= 0 else 0
snippet = body[start : start + 400].strip().replace("\n", "\n ")
print(f" …{snippet}…")
return len(rows)
def _print_events(db: Database, query: str, prj_id: str | None, limit: int) -> int:
sql = "SELECT ts, machine_id, prj_id, event_type, result, summary FROM events WHERE 1=1"
params: list[object] = []
if prj_id:
sql += " AND prj_id = ?"
params.append(prj_id)
if query:
sql += " AND summary LIKE ?"
params.append(f"%{query}%")
sql += " ORDER BY ts DESC LIMIT ?"
params.append(limit)
rows = db.rows(sql, params)
if not rows:
return 0
print("\n=== 事件 (events) ===")
for row in rows:
result = f" -> {row['result']}" if row["result"] else ""
print(
f" {row['ts'][:16]} {row['machine_id']} {row['prj_id']} "
f"[{row['event_type']}{result}] {row['summary'][:100]}"
)
return len(rows)
def main() -> int:
parser = argparse.ArgumentParser(description="在本机 bmem 镜像里只读检索历史")
parser.add_argument("query", nargs="?", default="", help="关键词;中文用两到四个字的词组")
parser.add_argument("--project", help="限定 prjID")
parser.add_argument("--kind", help="限定语义条目类型:decision/issue/requirement/result/param/…")
parser.add_argument("--limit", type=int, default=8)
parser.add_argument("--recent", type=int, metavar="N", help="不检索,直接看最近 N 条事件")
args = parser.parse_args()
try:
cfg = config_mod.load()
except Exception as exc: # noqa: BLE001
print(f"本机还没加入 bmem:{exc}\n先跑 `bmem install`。", file=sys.stderr)
return 1
if not cfg.db_path.exists():
print(f"本地镜像不存在:{cfg.db_path}\n跑一次 `bmem sync` 会从中心库重建。", file=sys.stderr)
return 1
with Database.open(cfg) as db:
if args.recent:
_print_events(db, "", args.project, args.recent)
return 0
if not args.query and not args.kind:
parser.error("给一个关键词,或用 --kind 列举某类条目,或用 --recent N 看最近发生了什么")
hits = _print_facts(db, args.query, args.project, args.kind, args.limit)
if args.query:
# 空关键词配 LIKE '%%' 会把整库倒出来,只在真的有关键词时查这两类
hits += _print_sections(db, args.query, args.project, args.limit)
hits += _print_events(db, args.query, args.project, args.limit)
if hits == 0:
print(f"没有命中「{args.query}」。")
print("中文按 bigram 索引,换更短的词组再试;也可能这条历史确实没记过。")
print(f"(FTS 等价查询串:{fts_query(args.query)})")
else:
print(f"\n共 {hits} 条命中。命中只是线索——回读原文再下结论。")
return 0
if __name__ == "__main__":
raise SystemExit(main())
+30 -2
View File
@@ -135,7 +135,7 @@ it, then resume the **same `--thread-id`** with a JSON payload. Repeat until exi
| `action` | excalidraw node | What you (the session) do | Resume JSON |
|---|---|---|---|
| `analyze` | 通过ila/串口分析 | Grab imx6 serial log + pull ILA waveform (vivado-mcp); summarize | `{"analysis_log":"<摘要/路径>"}` |
| `analyze` | 通过ila/串口分析 | Grab imx6 serial log + ILA; for RF/TX requirements also capture the requested analyzer spectrum; summarize all evidence | `{"analysis_log":"<摘要/路径>"}` |
| `meets_requirement` | 符合设计要求? | Judge the analysis against the design requirement | `{"meets": true}` or `{"meets": false}` |
| `propose` | 分析原因,提出修改方案 | Diagnose root cause; decide what to change + next program mode | `{"reason":"...","need_fpga": true/false,"need_imx6": true/false,"program_mode":"bitstream"或"flash"}` |
| `modify_fpga` | 改 RTL→综合→出 bit/flash | Edit RTL, run Vivado synth/impl, produce bit + flash files | `{"bit_path":"...","flash_path":"...","program_mode":"flash"}` |
@@ -159,6 +159,33 @@ Notes:
set in `propose`/`modify_fpga` chooses bitstream (download bit) vs flash (re-burn +
full power/connect re-sequence). This is the big closed loop.
### Spectrum-analyzer evidence during `analyze`
When the requirement includes RF or transmitter output, augment the ILA/console evidence
with a registered spectrum-analyzer MCP:
- N9320B (tool prefix `mcp__mcp_n9320b__`): call `get_idn`, then `get_settings`,
then `get_spectrum` with `mark_peak=true` and a stable `csv_path`.
- DSA815 (tool prefix `mcp__mcp_dsa815__`): call `dsa_get_identity`, then
`dsa_get_status`, then `dsa_capture_plot`.
Use the analyzer named by the requirement. If none is named, prefer DSA815. Capture both
only when the requirement asks for a cross-check or results need independent confirmation.
For DSA815 specifically:
1. Call `dsa_get_identity`, then `dsa_get_status` to verify serial `DSA8A194401321` and
record the current sweep/unit settings.
2. Call `dsa_capture_plot` for the current Trace; use `dsa_capture_spectrum` only when
downstream numerical processing needs all 601 points.
3. Put the PNG/CSV paths, peak frequency/power, span, RBW and VBW in `analysis_log`.
4. Prefer read-only capture. If the requirement forces a frequency/bandwidth change,
save the original status, use the validated setting tools, and restore the original
values after capture. Never enable `dsa_scpi_write` for routine flow work.
Always put the selected analyzer identity, artifact paths and measured settings in
`analysis_log`. If identity/connectivity fails, record that the RF evidence is unavailable
and do not claim the RF acceptance criterion passed from ILA or console data alone.
## Step 3 — Finish
On exit 0 (`status: done`), report to the user: iterations completed and that the
@@ -274,7 +301,8 @@ Gotchas seen on a real install (2026-07-20):
`imx_atsc_app.bak_pre_rx1`), md5-verify.
5. Cold-boot (tools_hw blast_off→power_off→power_on, TX-safe) so rc.local runs the new app.
6. Verify: network SSH grep `/var/module_log/imx_atsc_app.log` for `TALISE_radioOn` / `fb init state ok`
/ `fpga[0xBB]=0x03` (sync=1/tvalid=1); spectrum for TX; (ILA needs an `.ltx` → skip unless re-impl).
/ `fpga[0xBB]=0x03` (sync=1/tvalid=1); for TX use `mcp_dsa815.dsa_capture_plot`
and retain PNG/CSV + peak/settings; (ILA needs an `.ltx` → skip unless re-impl).
> The `git HEAD` app (b07d73b) is the known-good **RX1-on-framerA (M=4/lane0x03/RX1RX2) + correct TX**
> build. On-device `imx_atsc_app.rx1` and any `M=2/lane0x04` variant **break TX** — do not use them.
+18 -1
View File
@@ -36,7 +36,24 @@ Use this skill to run the LangGraph plan-review workflow. All scripts are bundle
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` 设一致。
`--codex-review-command-b64` 传一条**末尾带超时秒数**的命令(见 Workflow)。**`--timeout`(外层
agents.py 超时)必须明显大于内层值**(如内层 1500 / 外层 1800)——两者相等时外层会在 codex
正常收尾前把它杀掉,报 `subprocess.TimeoutExpired`(2026-08-26 踩坑)。
- **2026-08-26 踩坑补充**(三条都已实测):
1. **npm 最新版 codex 也可能太旧**:服务端 models 列表新增 `max` reasoning 档位后,npm 的
0.149.1 解析失败(`unknown variant 'max'`)直接退出。VS Code ChatGPT 扩展自带的 codex 更新
(如 `%USERPROFILE%\.vscode\extensions\openai.chatgpt-<最新版本号>-win32-x64\bin\windows-x86_64\codex.exe`,
0.150.0-alpha.8 可用)。`review_task.py` 支持 `CODEX_EXE` 环境变量指定该二进制,调用
`clean_run.py` 前 `$env:CODEX_EXE = "<扩展codex.exe路径>"` 即可(扩展目录版本号会随更新变化,
用 Get-ChildItem 找最新一个)。
2. **Windows elevated 沙箱挂起**:config.toml 有 `[windows] sandbox = "elevated"` 时,codex 在
ChatGPT 应用之外独立运行会让**每条 shell 命令挂起**,评审读不到文件(表现为评审意见说
"cannot open file")。`review_task.py` 已固定加 `-c sandbox_mode=read-only` 规避(评审只需读;
注意 `codex exec resume` 不认 `--sandbox` 旗标,只能用 `-c`)。
3. **UTF-8**:直接跑 `clean_run.py` 时先设 `$env:PYTHONUTF8='1'; $env:PYTHONIOENCODING='utf-8'`,
否则父进程按 cp1252 解码 codex 输出中的中文会 UnicodeDecodeError(旧 dev-server 路径由
run_langgraph.ps1 代设,clean_run 路径需要自己设)。
- **改了 `workflow.py`/`agents.py`/`clean_run.py` 后**:无 dev server 缓存,下次 `clean_run.py` 直接生效。
若图结构改到与旧 checkpoint 不兼容,删 `scripts\.plan_review_state.sqlite` 重新开始该 thread。
+8 -1
View File
@@ -121,7 +121,14 @@ def main() -> int:
os.close(fd)
last_message_path = Path(last_message_name)
try:
flags = ["--json", "--skip-git-repo-check", "--output-last-message", str(last_message_path)]
# read-only sandbox: review needs no writes, and on Windows the
# config.toml "elevated" sandbox hangs every shell command when codex
# runs outside the ChatGPT app (probed 2026-08-26). Use -c because
# "codex exec resume" does not accept the --sandbox flag.
# (unquoted value: fails TOML parse -> codex falls back to the raw
# string, which survives cmd /c quoting untouched)
flags = ["--json", "--skip-git-repo-check", "-c", "sandbox_mode=read-only",
"--output-last-message", str(last_message_path)]
if codex_session_id:
command_args = ["cmd", "/c", codex, "exec", "resume"] + flags + [codex_session_id, "-"]
else:
@@ -18,6 +18,41 @@ $script:CodexBackupMade = $false
$script:Failures = @()
$CodexOnlyEnv = @("PYTHONIOENCODING", "PYTHONUTF8")
function Test-TomllibPython([string]$Candidate) {
# tomllib needs CPython 3.11+; the Microsoft Store 'python' stub exits 49 and has no stdlib.
if (-not $Candidate) { return $false }
try { & $Candidate -c "import tomllib" 2>&1 | Out-Null } catch { return $false }
return ($LASTEXITCODE -eq 0)
}
function Resolve-PythonCommand([string]$Requested, [bool]$Explicit) {
# An explicit -PythonCommand is authoritative; only auto-probe the default.
if ($Explicit) { return $Requested }
if (Test-TomllibPython $Requested) { return $Requested }
$envFile = "$env:USERPROFILE\skills_env.md"
if (Test-Path -LiteralPath $envFile) {
$line = Select-String -LiteralPath $envFile -Pattern '^\s*CONDA_LANGGRAPH_PYTHON\s*=\s*(.+?)\s*$' |
Select-Object -First 1
if ($line) {
$cand = $line.Matches[0].Groups[1].Value.Trim()
if ((Test-Path -LiteralPath $cand) -and (Test-TomllibPython $cand)) {
Write-Host "python: falling back to CONDA_LANGGRAPH_PYTHON ($cand)"
return $cand
}
}
}
# Single-token candidates only: '& $cmd' treats the whole string as one exe name.
foreach ($cand in @("python3", "py")) {
if ((Get-Command $cand -ErrorAction SilentlyContinue) -and (Test-TomllibPython $cand)) {
Write-Host "python: falling back to '$cand'"
return $cand
}
}
throw "No tomllib-capable Python found ('$Requested' unusable). Pass -PythonCommand <path to CPython 3.11+>."
}
$PythonCommand = Resolve-PythonCommand $PythonCommand $PSBoundParameters.ContainsKey('PythonCommand')
function ConvertTo-StringMap($Object) {
$result = [ordered]@{}
if ($null -eq $Object) { return $result }
+26 -1
View File
@@ -109,7 +109,7 @@ Perform it, then resume the **same `--thread-id`**. Repeat until exit 0.
| `action` | excalidraw node | What you (the session) do | Resume JSON |
|---|---|---|---|
| `analyze` | 通过ila/串口分析 | Grab ILA + the `sources` consoles (ps_console, and imx6_console if xmod); summarize | `{"analysis_log":"<摘要/路径>"}` |
| `analyze` | 通过ila/串口分析 | Grab ILA + listed consoles; for RF/TX requirements also capture the requested analyzer spectrum; summarize all evidence | `{"analysis_log":"<摘要/路径>"}` |
| `meets_requirement` | 符合设计要求? | Judge analysis vs the design requirement | `{"meets": true}` or `{"meets": false}` |
| `propose` | 分析原因,提方案 | Diagnose; **review the plan with `/langgraph-plan-review`**; decide what to change + next program mode | `{"reason":"...","need_fpga":bool,"need_imx6":bool,"need_ps":bool,"program_mode":"bitstream"或"flash"}` |
| `modify_fpga` | 改 RTL→综合→bit/flash | Edit RTL, run Vivado synth/impl, produce bit + flash | `{"bit_path":"...","flash_path":"..."}` |
@@ -142,6 +142,31 @@ Notes:
re-sequence (`flash_burn → power_off`). This is the big closed loop.
- `"$PY" "$RUN" status --thread-id zynq-<name>` re-prints the current state without advancing.
### Spectrum-analyzer evidence during `analyze`
For RF/transmitter acceptance, use a registered analyzer MCP in addition to ILA and
console logs:
- N9320B (tool prefix `mcp__mcp_n9320b__`): `get_idn` → `get_settings` →
`get_spectrum` with `mark_peak=true` and a stable `csv_path`.
- DSA815 (tool prefix `mcp__mcp_dsa815__`): `dsa_get_identity` → `dsa_get_status` →
`dsa_capture_plot`.
Use the analyzer named by the requirement. If none is named, prefer DSA815; use both only
for an explicit cross-check or independent confirmation. For DSA815 specifically:
1. `dsa_get_identity` → `dsa_get_status` verifies serial `DSA8A194401321` and records
the current sweep, unit, RBW and VBW.
2. `dsa_capture_plot` captures the current Trace and returns PNG/CSV evidence; call
`dsa_capture_spectrum` only when all 601 points are needed for numerical analysis.
3. Put artifact paths, peak frequency/power and sweep settings in `analysis_log`.
4. Prefer read-only capture. If a required mask/channel needs different settings,
record the original status and restore it after capture. Do not enable raw SCPI write.
Always record the selected analyzer identity with the RF artifacts. If the analyzer cannot
be verified or reached, report the missing RF evidence and do not mark an RF requirement
as passed from digital evidence alone.
## Step 3 — Finish
On exit 0 (`status: done`), report iterations completed and whether the design met