加入 langgraph-vivado-install, langgraph-plan-view, 更新 fft-analyse

This commit is contained in:
Myfro9
2026-05-22 17:18:38 -05:00
parent c7e4723d85
commit 15e8c5efa3
3 changed files with 259 additions and 24 deletions
+26 -14
View File
@@ -234,7 +234,10 @@ def analyse(
v = r.get(col, "").strip() v = r.get(col, "").strip()
if v and v.lower() not in ("x", "z", "nan"): if v and v.lower() not in ("x", "z", "nan"):
# If it looks like hex (has a-f OR is exactly 4 hex digits) # If it looks like hex (has a-f OR is exactly 4 hex digits)
if re.match(r"^[0-9a-fA-F]+$", v): if v.lower().startswith("0x") or (
re.match(r"^[0-9a-fA-F]+$", v)
and any(c in v.lower() for c in "abcdef")
):
return True return True
break break
return False return False
@@ -259,10 +262,14 @@ def analyse(
i_mean, q_mean = float(np.mean(i_data)), float(np.mean(q_data)) i_mean, q_mean = float(np.mean(i_data)), float(np.mean(q_data))
iq = (i_data[:fft_n] - i_mean) + 1j * (q_data[:fft_n] - q_mean) iq = (i_data[:fft_n] - i_mean) + 1j * (q_data[:fft_n] - q_mean)
win = np.hanning(fft_n) win = np.hanning(fft_n)
cg = float(np.sum(win)) cg = float(np.sum(win)) # coherent gain (for peak amplitude)
win_power = float(np.sum(win ** 2)) # for PSD normalisation
fft_data = np.fft.fftshift(np.fft.fft(iq * win, n=fft_n)) fft_data = np.fft.fftshift(np.fft.fft(iq * win, n=fft_n))
amp = np.abs(fft_data) / cg amp = np.abs(fft_data) / cg # peak amplitude (linear, for OBW / peaks)
dbfs = 20.0 * np.log10((amp + np.finfo(np.float64).eps) / full_scale) dbfs = 20.0 * np.log10((amp + np.finfo(np.float64).eps) / full_scale)
# Power spectral density: dBFS/Hz (normalised to full-scale power)
psd = (np.abs(fft_data) ** 2) / (full_scale ** 2 * win_power * fs_hz)
psd_dbfs_hz = 10.0 * np.log10(psd + np.finfo(np.float64).eps)
freq_hz = (np.arange(fft_n) - fft_n / 2.0) * fs_hz / fft_n freq_hz = (np.arange(fft_n) - fft_n / 2.0) * fs_hz / fft_n
freq_mhz = freq_hz / 1e6 freq_mhz = freq_hz / 1e6
@@ -279,6 +286,7 @@ def analyse(
result = dict( result = dict(
i_data=i_data, q_data=q_data, mag_time=mag_time, i_mean=i_mean, q_mean=q_mean, i_data=i_data, q_data=q_data, mag_time=mag_time, i_mean=i_mean, q_mean=q_mean,
fft_n=fft_n, freq_hz=freq_hz, freq_mhz=freq_mhz, amp=amp, dbfs=dbfs, fft_n=fft_n, freq_hz=freq_hz, freq_mhz=freq_mhz, amp=amp, dbfs=dbfs,
psd_dbfs_hz=psd_dbfs_hz,
obw90=obw90, obw99=obw99, frac90=frac90, frac99=frac99, peaks=peaks, obw90=obw90, obw99=obw99, frac90=frac90, frac99=frac99, peaks=peaks,
fs_hz=fs_hz, full_scale=full_scale, i_col=i_col, q_col=q_col, fs_hz=fs_hz, full_scale=full_scale, i_col=i_col, q_col=q_col,
input_name=input_path.name, skip=s, input_name=input_path.name, skip=s,
@@ -366,8 +374,9 @@ def _plot(result: dict, xlim_mhz, out_dir: Path, prefix: str, title: str | None)
axes[1].grid(True, alpha=0.3) axes[1].grid(True, alpha=0.3)
axes[1].legend(loc="upper right") axes[1].legend(loc="upper right")
# panel 3 — spectrum # panel 3 — spectrum (PSD)
axes[2].plot(freq_mhz, dbfs, color="#1f5a95", linewidth=1.0) psd_dbfs_hz = result["psd_dbfs_hz"]
axes[2].plot(freq_mhz, psd_dbfs_hz, color="#1f5a95", linewidth=1.0)
obw90, obw99 = result["obw90"], result["obw99"] obw90, obw99 = result["obw90"], result["obw99"]
if obw90 > 0: if obw90 > 0:
axes[2].axvline(-obw90 / 2, color="#ff7f0e", ls="--", lw=1.3, axes[2].axvline(-obw90 / 2, color="#ff7f0e", ls="--", lw=1.3,
@@ -377,23 +386,26 @@ def _plot(result: dict, xlim_mhz, out_dir: Path, prefix: str, title: str | None)
axes[2].axvline(-obw99 / 2, color="#9467bd", ls="--", lw=1.3, axes[2].axvline(-obw99 / 2, color="#9467bd", ls="--", lw=1.3,
label=f"99% OBW: {obw99:.2f} MHz") label=f"99% OBW: {obw99:.2f} MHz")
axes[2].axvline( obw99 / 2, color="#9467bd", ls="--", lw=1.3) axes[2].axvline( obw99 / 2, color="#9467bd", ls="--", lw=1.3)
# annotate top peaks using PSD value at peak bin
for p in result["peaks"][:5]: for p in result["peaks"][:5]:
if xlim_mhz[0] <= p["freq_mhz"] <= xlim_mhz[1]: if xlim_mhz[0] <= p["freq_mhz"] <= xlim_mhz[1]:
axes[2].plot(p["freq_mhz"], p["dbfs"], "o", color="#c23b22", ms=6) psd_val = float(psd_dbfs_hz[p["bin"]])
axes[2].annotate(f"{p['freq_mhz']:.2f} MHz", (p["freq_mhz"], p["dbfs"]), axes[2].plot(p["freq_mhz"], psd_val, "o", color="#c23b22", ms=6)
axes[2].annotate(f"{p['freq_mhz']:.2f} MHz\n{psd_val:.1f} dBFS/Hz",
(p["freq_mhz"], psd_val),
xytext=(0, 8), textcoords="offset points", xytext=(0, 8), textcoords="offset points",
ha="center", fontsize=11) ha="center", fontsize=10)
axes[2].set_title(f"{fft_n}-pt complex FFT, Hann window, DC removed, " axes[2].set_title(f"{fft_n}-pt complex FFT, Hann window, DC removed, "
f"fs = {fs_hz/1e6:.2f} MHz, FS ref = {result['full_scale']:.0f}") f"fs = {fs_hz/1e6:.2f} MHz, FS ref = {result['full_scale']:.0f}")
axes[2].set_xlabel("Frequency (MHz)") axes[2].set_xlabel("Frequency (MHz)")
axes[2].set_ylabel("Magnitude (dBFS)") axes[2].set_ylabel("PSD (dBFS/Hz)")
axes[2].set_xlim(xlim_mhz) axes[2].set_xlim(xlim_mhz)
in_view = (freq_mhz >= xlim_mhz[0]) & (freq_mhz <= xlim_mhz[1]) in_view = (freq_mhz >= xlim_mhz[0]) & (freq_mhz <= xlim_mhz[1])
if np.any(in_view): if np.any(in_view):
y_top = max(-20.0, float(np.ceil(np.max(dbfs[in_view]) / 10) * 10 + 10)) y_top = max(-60.0, float(np.ceil(np.max(psd_dbfs_hz[in_view]) / 10) * 10 + 10))
else: else:
y_top = 10.0 y_top = -60.0
axes[2].set_ylim(-140, y_top) axes[2].set_ylim(y_top - 120, y_top)
axes[2].grid(True, alpha=0.3) axes[2].grid(True, alpha=0.3)
axes[2].legend(loc="lower right") axes[2].legend(loc="lower right")
@@ -406,8 +418,8 @@ def _plot(result: dict, xlim_mhz, out_dir: Path, prefix: str, title: str | None)
def _auto_fs(path: Path) -> float: def _auto_fs(path: Path) -> float:
name = path.stem.lower() name = path.stem.lower()
for pat, hz in [("491", 491.52e6), ("245", 245.76e6), ("240m", 245.76e6), for pat, hz in [("491", 491.52e6), ("245", 245.76e6), ("240m", 240e6),
("122", 122.88e6), ("120m", 122.88e6), ("61", 61.44e6), ("122", 122.88e6), ("120m", 120e6), ("61", 61.44e6),
("250m", 250e6), ("125m", 125e6)]: ("250m", 250e6), ("125m", 125e6)]:
if pat in name: if pat in name:
return hz return hz
+83
View File
@@ -0,0 +1,83 @@
---
name: langgraph-plan-review
description: Use the LangGraph plan-review workflow in E:\LangGraph_Design\lab00 from the current Claude Code session. Trigger when the user asks to create Taskxx plans, run the Claude/Codex review loop, generate or revise Notes/Taskxx.md, or use the LangGraph workflow described by Task00.md. This skill uses the current Claude Code environment for Claude steps and only invokes the Python LangGraph app for Codex review and discuss file numbering.
---
# LangGraph Plan Review Workflow
Use this skill to run the workflow implemented in `E:\LangGraph_Design\lab00`.
## Invariants
- Use the current Claude Code session for all Claude Code work. Do not launch another `claude` process.
- Assume the current Claude Code session already has `--dangerously-skip-permissions`.
- Run Python through the recorded conda environment. `run_langgraph.ps1` resolves conda from `-CondaExe`, `CONDA_EXE`, `conda` on PATH, or the user-profile miniconda fallback:
```powershell
<resolved conda.exe> run -n langgraph python
```
- Codex CLI is installed in the `langgraph` conda env as `codex.cmd` at `C:\Users\bowei\.conda\envs\langgraph\codex.cmd`. It must be invoked via `cmd /c` because `.cmd` files are not directly executable by Python subprocess on Windows.
- Codex auth uses ChatGPT OAuth tokens stored in `C:\Users\bowei\.codex\auth.json`. No API key is needed.
- `run_langgraph.ps1` sets `CODEX_HOME` from `-CodexHome`, existing `CODEX_HOME`, or `<UserProfile>\.codex`.
- **Always use `review_task.py` as the Codex review shim.** Pass it via `--codex-review-command` as a JSON array with no spaces. This shim handles all Windows-specific codex invocation requirements: `cmd /c`, stdin prompt passing, `--skip-git-repo-check`, session resume, and explicit `cwd`.
- Keep all workflow files under `<Claude launch work_dir>\Notes` unless the user explicitly overrides `-NotesDir`.
- `TaskId` is the numeric suffix, such as `01`; write the plan to `Notes\Task01.md`.
- `discussxx-yy.md` numbering is managed by the LangGraph app for Codex review files. If Claude rejects a review comment, create the next numbered discuss file manually before rerunning review.
- The workflow uses LangGraph `interrupt()` and persists state in `workflow_state.db`.
- 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.
## Workflow
1. Determine `TaskId` from the user. If missing, ask for it.
2. Start or resume the LangGraph app:
```powershell
# Use double backslashes (\\) in the JSON string so Windows paths are valid JSON.
$codexCmd = '["C:\\Users\\bowei\\.conda\\envs\\langgraph\\python.exe","review_task.py","$task_path","$latest_discuss_path","$codex_session_id","$work_dir"]'
E:\LangGraph_Design\lab00\run_langgraph.ps1 `
-TaskId <TaskId> `
-Prompt "<original user request>" `
-CodexReviewCommand $codexCmd `
-Timeout 300
```
3. Inspect the returned JSON. If `status` is `waiting`, perform `action_required` in the current Claude Code session:
- `write_plan`: write `Task<TaskId>.md` with project background, task objective, and concrete implementation plan, then resume with `-Resume 'true'`.
- `decide`: read the discuss file and resume with `-Resume '{"accepts":true}'` or `-Resume '{"accepts":false,"reason":"..."}'`.
- `revise`: revise `Task<TaskId>.md`, then resume with `-Resume 'true'`.
- `write_rejection`: write the specified `output_path`, then resume with `-Resume 'true'`.
**Why `review_task.py`**: Using the shim keeps all codex-specific logic in Python, and the JSON array stays simple.
`run_langgraph.ps1` base64-encodes the JSON command and resume payload before passing them through `conda run`, so normal PowerShell single-quoted JSON can be used. **Windows paths inside the JSON string must use `\\` (double backslash) to produce valid JSON**, e.g. `C:\\Users\\bowei\\...`. `run_langgraph.ps1` automatically sets `PYTHONIOENCODING=utf-8` so prompts containing Unicode (Chinese, etc.) pass through correctly.
4. Inspect the command output after each resume.
- Check `"status"` to determine next action: `"waiting"` means perform the next `action_required`; `"done"` means the workflow is complete.
- If a new `latest_discuss_path` is present in the output, read that discuss file for context.
5. Stop when the workflow returns `{"status":"done", ...}`.
## Rejection File Rule
When the `action_required` is `write_rejection`, the interrupt payload already contains the exact file path:
```json
{"action": "write_rejection", "output_path": "Notes/discuss04-01.md", "reason_hint": "..."}
```
Write the rejection content directly to `action_required.output_path`:
```markdown
# discuss<TaskId>-<yy>
## Claude Code Rejection
<reason>
```
## Notes
- The LangGraph app automatically scans existing discuss files and writes the next Codex review file without overwriting old discussions.
- The app no longer accepts Claude command arguments; Claude actions happen in this current session.
- If `codex` is unavailable, report that the review step cannot run rather than simulating it.
- `review_task.py` passes the review prompt through stdin with `-` to avoid Windows command-line quoting issues with multiline prompts.
- `review_task.py` passes `--skip-git-repo-check` so the workflow can run from Claude launch directories that are not git repositories.
- `review_task.py` reads Codex final output via `--output-last-message` so intermediate JSONL progress messages are not treated as review findings.
- `review_task.py` persists the Codex session id through LangGraph state and uses `codex exec resume` on later review passes.
- `review_task.py` runs Codex with the original Claude Code launch working directory as cwd.
- Codex review output is normalized to JSON for `agents.py`: `{"has_serious_issue":true/false,"comment":"...","codex_session_id":"..."}`.
+145 -5
View File
@@ -7,9 +7,10 @@ description: 在 Windows 原生环境下自动化配置基于 Conda 的 LangGrap
本技能在 Windows 原生环境(Git Bash / 命令提示符)中自动完成以下工作: 本技能在 Windows 原生环境(Git Bash / 命令提示符)中自动完成以下工作:
1. 检测并安装 Conda 1. 检测并安装 Conda
2. 创建 `langgraph` 虚拟环境并安装所需包 2. 确认 conda 路径已永久写入 Windows 用户级 PATH
3. 迭代检测 Vivado 链路直至成功 3. 创建 `langgraph` 虚拟环境并安装所需包
4. 写出配置文件与冒烟测试脚本并运行验证 4. 迭代检测 Vivado 链路直至成功
5. 写出配置文件与冒烟测试脚本并运行验证
--- ---
@@ -17,7 +18,9 @@ description: 在 Windows 原生环境下自动化配置基于 Conda 的 LangGrap
``` ```
Step 1: Conda 检测与安装 Step 1: Conda 检测与安装
Step 1b: 确认 conda 路径已写入 Windows PATH(新安装或已有均执行)
Step 2: 创建 langgraph 环境,安装依赖 Step 2: 创建 langgraph 环境,安装依赖
Step 2c: 检测并安装 Codex CLI,验证 Auth
Step 3: Vivado 链路迭代检测(循环直到成功) Step 3: Vivado 链路迭代检测(循环直到成功)
Step 4: 写出 env_config.json,提示持久化 Step 4: 写出 env_config.json,提示持久化
Step 5: 生成并运行 smoke_test.py Step 5: 生成并运行 smoke_test.py
@@ -32,14 +35,68 @@ Step 5: 生成并运行 smoke_test.py
conda --version conda --version
``` ```
- **若成功**:记录版本,进入 Step 2。 - **若成功**:记录版本,进入 **Step 1b**。
- **若失败**:执行安装命令: - **若失败**:执行安装命令:
```bash ```bash
winget install -e --id Anaconda.Miniconda3 winget install -e --id Anaconda.Miniconda3
``` ```
安装完成后,提示用户: 安装完成后,提示用户:
> "Miniconda 安装完成。请重启 Shell 或运行 `conda init bash` 使 conda 命令生效,然后重新触发本技能。" > "Miniconda 安装完成。请重启 Shell 或运行 `conda init bash` 使 conda 命令生效,然后重新触发本技能。"
**在用户确认 conda 可用之前,不继续执行后续步骤。** **在用户确认 conda 可用之前,不继续执行后续步骤。用户确认后,进入 Step 1b。**
---
## Step 1b — 确认 conda 路径已写入 Windows PATH
无论 conda 是刚安装的还是之前已存在,都必须执行此步骤,确保 conda 的路径被永久写入 Windows 用户级 PATH,方便在任何新 Shell 中直接使用。
### 1. 定位 conda 所在目录
在 PowerShell 中运行:
```powershell
$condaPath = (Get-Command conda -ErrorAction SilentlyContinue).Source
$condaBin = if ($condaPath) { Split-Path $condaPath -Parent } else { $null }
Write-Host "conda 所在目录: $condaBin"
```
若 `$condaBin` 为空(conda 命令本身来自 conda init 脚本而非独立可执行文件),则改用:
```powershell
# 方法 B:通过 conda info 获取 base 目录,再拼接 condabin
$baseDir = (conda info --base 2>$null).Trim()
$condaBin = Join-Path $baseDir "condabin"
Write-Host "conda condabin 目录: $condaBin"
```
### 2. 检查路径是否已在 PATH 中
```powershell
$userPath = [System.Environment]::GetEnvironmentVariable("PATH", "User")
$sysPath = [System.Environment]::GetEnvironmentVariable("PATH", "Machine")
$inUserPath = ($userPath -split ";") -contains $condaBin
$inSysPath = ($sysPath -split ";") -contains $condaBin
if ($inUserPath -or $inSysPath) {
Write-Host "[OK] conda 路径已在 PATH 中,无需修改。"
} else {
Write-Host "[INFO] conda 路径不在 PATH 中,准备写入用户级 PATH..."
}
```
### 3. 若不在 PATH 中则写入
```powershell
if (-not ($inUserPath -or $inSysPath)) {
$newUserPath = $condaBin + ";" + $userPath
[System.Environment]::SetEnvironmentVariable("PATH", $newUserPath, "User")
Write-Host "[OK] 已将以下路径写入用户级 PATH: $condaBin"
Write-Host " 新开终端后即可直接使用 conda 命令。"
}
```
> **为什么写用户级 PATH 而非系统级?** 用户级 PATH 不需要管理员权限,且只影响当前用户,更安全。若该机器是多用户共享环境,可改为 `"Machine"` 并以管理员身份运行。
完成后进入 Step 2。
--- ---
@@ -70,6 +127,89 @@ conda run -n langgraph python -c "import langgraph; print('LangGraph OK:', langg
--- ---
## Step 2c — 检测并安装 Codex CLI,验证 Auth
Codex CLI 是 OpenAI 发布的命令行代码审查工具,以 npm 全局包形式安装在 `langgraph` conda 环境中,供 LangGraph 工作流的 Codex review 节点调用。
### 2c-1. 检测 Codex CLI 是否已安装
在 PowerShell 中运行:
```powershell
$condaEnvPath = (C:\Users\bowei\miniconda3\Scripts\conda.exe run -n langgraph python -c "import sys; print(sys.prefix)").Trim()
$codexCmd = Join-Path $condaEnvPath "codex.cmd"
if (Test-Path $codexCmd) {
Write-Host "[OK] Codex CLI 已安装: $codexCmd"
$codexInstalled = $true
} else {
Write-Host "[INFO] 未检测到 Codex CLI,准备安装..."
$codexInstalled = $false
}
```
### 2c-2. 若未安装,通过 npm 安装
首先确认 npm 可用(Node.js 需已安装在 conda 环境或系统 PATH 中):
```powershell
C:\Users\bowei\miniconda3\Scripts\conda.exe run -n langgraph npm --version
```
- **若 npm 可用**:执行安装:
```powershell
C:\Users\bowei\miniconda3\Scripts\conda.exe run -n langgraph npm install -g @openai/codex
```
安装完成后,再次执行 2c-1 检查 `codex.cmd` 是否出现。若仍不存在,报告错误并停止,请用户手动检查 npm 全局安装路径。
- **若 npm 不可用**:停止并提示:
> "langgraph conda 环境中未找到 npm。请先安装 Node.js(建议通过 `winget install OpenJS.NodeJS.LTS`),重启 Shell 后重新触发本技能。"
**在用户确认 npm 可用之前,不继续后续步骤。**
### 2c-3. 验证 Codex CLI 可执行
```powershell
$env:CODEX_HOME = "C:\Users\bowei\.codex"
cmd /c "$codexCmd" --version
```
- 若命令输出版本号(如 `0.133.0`):记录版本,继续。
- 若命令失败:报告错误(输出内容),停止执行,请用户检查 Node.js 版本兼容性。
> **为什么用 `cmd /c`?** `.cmd` 文件在 Windows 上不能直接被 PowerShell 或 Python subprocess 执行,必须通过 `cmd /c` 转发。
### 2c-4. 验证 Codex Auth
Codex CLI 的认证 token 存储在 `$CODEX_HOME\auth.json`(默认 `C:\Users\bowei\.codex\auth.json`)。
```powershell
$codexHome = if ($env:CODEX_HOME) { $env:CODEX_HOME } else { "$env:USERPROFILE\.codex" }
$authFile = Join-Path $codexHome "auth.json"
if (Test-Path $authFile) {
$authContent = (Get-Content $authFile -Raw -Encoding utf8).Trim()
if ($authContent.Length -gt 20) {
Write-Host "[OK] Codex auth.json 存在且内容有效。"
$authOk = $true
} else {
Write-Host "[WARN] auth.json 内容过短,可能已损坏或未完成认证。"
$authOk = $false
}
} else {
Write-Host "[WARN] 未找到 auth.json: $authFile"
$authOk = $false
}
```
- **若 `$authOk = $true`**:继续进入 Step 3。
- **若 `$authOk = $false`**:停止并提示:
> "Codex CLI 尚未完成认证,或认证已失效。请在终端运行 `codex` 并按提示完成 ChatGPT OAuth 登录,登录成功后 auth.json 将自动写入 `C:\Users\bowei\.codex\`。完成后重新触发本技能或手动继续到 Step 3。"
**在用户确认认证完成之前,不继续后续步骤。**
> **为什么检查 auth.json 而非运行 `codex auth status`?** Codex CLI 目前没有独立的状态查询子命令;auth.json 存在且非空即表示 OAuth 流程已完成。若需更严格验证,可在 Step 5 冒烟测试中加入一次受控的 `codex exec --ephemeral` 调用。
---
## Step 3 — Vivado 链路迭代检测循环 ## Step 3 — Vivado 链路迭代检测循环
这是本技能的核心环节。目标是确保在 `langgraph` 环境中可以调用 Vivado。 这是本技能的核心环节。目标是确保在 `langgraph` 环境中可以调用 Vivado。