feat(skills): pull group skills from netx /mcp resources, drop bundled copy

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
oliver 2026-10-16 23:14:39 +08:00
parent 277a61894d
commit fa82f530e9
31 changed files with 2074 additions and 2027 deletions

View file

@ -6,7 +6,7 @@
# 1. replaces `persona` with presets/netxops/PERSONA.md
# 2. ensures the `netxops-tools` row below is present
# 3. aligns workflow engine package to what the host can resolve
# 4. copies presets/netxops/skills/
# Group skills are pulled from netx `/mcp` resources into `$DSH_HOME/netxops-mcp-skills/`.
# Desktop also syncs a declarative `@deepseek-ai/dsh-agent-preset` row into
# the profile patch. DSH has no preset inheritance.

View file

@ -1,14 +0,0 @@
# Mirrored from netx/skills 鈥?do not long-edit here
Canonical: `D:\project\chatgpt\netx\skills`
`powershell
powershell -File .\scripts\sync-skills-from-netx.ps1
`
Runtime prefers `NETX_SKILLS_ROOT` or sibling `../netx/skills`.
Groups: **ops** (`netx-ops`) + **topology** (`netx-topology`). One group 鈫?one skill.
**Keep netx-ops pure netx** (alarms / inventory / CLI). Knowledge-base triage belongs in
operator-subset `_skills/kb-*` packs registered by the plugin 鈥?do **not** write KB linkage
back into `netx-ops`.

View file

@ -1,66 +0,0 @@
---
name: netx-biz-monitor
description: >-
Overnight cutover / biz_state monitor analysis via netxops host tools
(netx__listBizMonitors / listBizMonitorBatches / getBizMonitor*). List
projects/tasks then batches when no id is given; read definitions, board,
reds/diffs with evidence (device-raw A/B, show commands), and judge tool
false-positive vs real business fault. Does not create templates/tasks.
---
# netx-biz-monitor(割接 / 业务监控只读分析)
人配好监控与任务;AI 当晚做归因。
工具在 **netxops**(`netx__*`),直连 netx REST,**不是** netx-mcp。
## 工具(能力组 `bizMonitor`)
| 工具 | 用途 |
|------|------|
| `netx__listBizMonitors` | **入口目录**:列出割接/监控对比项目 + 业务监控任务(**无需** project_id) |
| `netx__listBizMonitorBatches` | **批次目录**:`project_id`→割接批次;`task_id`→采集批次;`kind=runs`→evaluate run |
| `netx__getBizMonitorContext` | 任务定义:项目、监控/对比模板、归一化规则、端口映射、关联任务与命令项 |
| `netx__getBizMonitorBoard` | 批次进度、evaluate run、sheet 卡、verdict 统计 |
| `netx__listBizMonitorReds` | 红单 + **evidence** |
| `netx__getBizMonitorDiffs` | run 明细(可滤 color=red) |
| `netx__getBizCollectBatch` | 某次采集:commands / metrics |
| `netx__getBizCollectCommandRaw` | 单命令全量 show 输出 |
交叉验证真障时再用 ops:`netx__execManagedNe` / 拓扑路径。
## 字段约定(必守)
- **`old_key` / `new_key`**:设备采集原文 **A/B**(大屏与结论用这个)
- **`match_*` / evidence.iface.normalized|map_***:内部配对,**不要**把映射后的 BB 说成两侧端口
- **evidence.*.command.raw_command**:实际 show 命令
- **evidence.*.device / collect**:哪台设备、何时采的
## 推荐顺序
1. 无 id → `listBizMonitors`(`kind=projects|tasks|all`,可加 `q` / `purpose`)
2. `listBizMonitorBatches(project_id)` 或 `(task_id)` — 拿到 **完整 batch_id**;需要 run 时再 `kind=runs` / `include_runs`
3. `getBizMonitorContext(project_id)` — 弄清盯什么
4. `getBizMonitorBoard(batch_id)` — 进度 / missing / skipped
5. `listBizMonitorReds` 或 `getBizMonitorDiffs(color=red)`
6. 看 evidence:parse 失败?current_missing?映射 miss?归一化误伤?
7. 需要原文 → `getBizCollectCommandRaw`
8. 像真障 → `execManagedNe` 现场核对
## Verdict 速查(skill 解释,工具不硬编码)
| verdict | 含义(窗口内) |
|---------|----------------|
| migrated | expect 项已迁到新侧 |
| anomaly | 异常(多为红) |
| unfinished | expect 未完成(验收常红) |
| migrating / expected / ok | 过程态 / 正常 |
| lost / not_involved | 丢失或无关 |
## 误报优先查
1. `parse_status` 非 ok / 行数为 0
2. board:`current_missing` / `collect_skipped`
3. 端口映射漏配、归一化前后不一致导致假 anomaly
4. 再谈真实业务问题
配置整改与关红单由人做;本 skill **只读分析**。

View file

@ -1,218 +0,0 @@
---
name: netx-ops
description: netx 运维:告警、网元、只读 CLI。
---
# netx-ops(告警 · 清单 · 纳管登录)
处理 netx **告警 / 网元清单 / 只读 CLI** 时遵循本手册。
画布布图不在本 skill(见 **netx-topology**)。本手册里的「路径」仅指 `findTopologyPaths` 查两端关联。
**工具名**:DSH 为 `netx__` + stem(如 `netx__queryNmsAlarms`)。下文写 stem 时,调用一律加 `netx__` 前缀。
**两套「一批」(勿混):**
| 说法 | 含义 | 写法 |
|------|------|------|
| **多台一批** | 一次工具调用打多台设备 | `nms_ne_ids`/`ne_ids` + 共享 `commands`,或 `targets` |
| **同台多条** | 一次工具调用、**一台**上发多条命令 | 单个 `ne_id`/`nms_ne_id` + `commands:[…]` |
禁止:同轮对多台各调一次 `execManagedNe`(假并行、会串行)。
---
## 1. 总原则
1. **先新鲜度,再结论**:`runNmsDiagnostics` 或 `aggregateNmsAlarms` → 读 `meta.last_seen_*`。过旧则按快照:时间窗落在 min~max 内,禁止默认「现在−30 分钟」。
2. **证据优先**:有告警须有级别计数和/或 Top `host_name`;否则写「证据不足」,禁止臆造根因。
3. **对用户**:只展示 **host_name**;**严禁**出现任何 UUID/`ne_id`。无 `host_name` 的行当垃圾丢弃(不顶替 label、不提「缺失」)。工具入参仍可用 id,但不得写入对用户可见正文。
4. **短问**:能 ≤3 次工具调用就闭环的,先聚合/过滤,禁止无过滤翻全库。复杂关联(dying gasp、A<>B CLI)不受 3 次硬顶,但仍须有过滤与目标。
5. **「能否登录」**:必须走 §3;禁止只查 inventory 下结论。
---
## 2. 工具选用(按需,非逐步必跑)
按意图选步,**不是**每次从 ① 跑到 ⑧。
| 意图 | 工具 |
|------|------|
| 新鲜度 / 态势 / Top | `runNmsDiagnostics`、`aggregateNmsAlarms` |
| 一页现告警 | `queryNmsAlarms` |
| 可引用明细 | `queryNmsAlarmsRaw`(`field_preset=evidence`);字段不明可先 `listNmsAlarmFields` |
| 自定义聚合 | `aggregateNmsAlarmsRaw`(如 `group_by=alarm_host_name`) |
| 复杂条件 | `sqlQueryNms`(只读 SELECT,设超时) |
| 绰号→真名 / 是否在 NMS | `queryNmsNeInventory` / `getNmsNe` |
| 两端路径 / 对端 | `findTopologyPaths` |
| 只读登录 | `listCliTargets` 或 `listManagedNe` → **一次** `execManagedNe` |
`getManagedNe` **仅**接受纳管 `ne_id`。NMS 侧 id 用 `execManagedNe(nms_ne_id=…)`,禁止塞进 `getManagedNe`。
---
## 3. 「能否登录」决策树(强制)
1. `listCliTargets(keyword=主机或IP)` **或** `listManagedNe(keyword=…, connect_status=pass)`
- **命中** → `execManagedNe`,命令按厂商:`show version`(中兴/Cisco 等)或 `display version`(华为);以实测为准
- **未命中** → `queryNmsNeInventory(keyword=…)`:说明 NMS 有无及 `connection_status`;并写明:**未进 netx CLI 通道则本通道不能登录**(不等于设备不存在)
2. 已在 CLI 目标列表中:禁止只凭 inventory 说能/不能登。
3. 本会话 `listCliTargets`、`listManagedNe` **各最多一次**,缓存结果;同台多条命令进同一次 `commands[]`。
---
## 4. CLI:多台一批(强制)
查 **≥2 台** 时:必须 **一次** `execManagedNe`。
- 命令相同:`nms_ne_ids` 或 `ne_ids` + 共享 `commands`
- 命令不同(厂商/角色不同):`targets=[{nms_ne_id|ne_id, commands:[…]}, …]`,仍一次调用
- 超时:加大 `read_timeout_sec` 或减条数;禁止同参盲重试
- 只读:`show` / `display` / `ping` / `traceroute` 及带 `?` 的探索;配置类不做
**✓ 同命令多台**
```json
{ "nms_ne_ids": ["uuid-a", "uuid-b"], "commands": ["show version"], "concurrency": 4 }
```
**✓ 混厂商(多台一批)**
```json
{
"targets": [
{ "nms_ne_id": "uuid-zte", "commands": ["show opticalinfo brief"] },
{ "nms_ne_id": "uuid-hw", "commands": ["display optical-module brief"] }
],
"read_timeout_sec": 90
}
```
(示例中的 uuid 仅作工具入参示意,**禁止**出现在对用户回复里。)
记不清命令、要用 `?` 探索 → 见 **§8**(同台多条分裂,不是多台同探)。
---
## 5. 现场短问配方
| 用户说法 | 做法 |
|----------|------|
| Critical Top / 高危排名 | `aggregateNmsAlarms(severity=critical, top_ne=20)` |
| 现网告警多少 / 态势 | `runNmsDiagnostics` 或 `aggregateNmsAlarms` → 级别 + 新鲜度 |
| 断纤 / LOS / 光缆 | Raw:`keyword=LOS` 和/或 `Fiber Break` → **有 host_name 的列表 + 计数** |
| 光功率**门限**(某区域) | Raw:`keyword=optical power`(或 Input optical power)+ 主机名前缀;**禁止**当断纤配方 |
| 离线 / BN EMS / 失联 | Raw:`keyword=BN EMS` 或 communication failure |
| dying gasp | 本端 dying gasp → `findTopologyPaths`/端口找对端 → 对端近时窗 BN EMS;**禁止只答一端** |
| CRC / 拥塞 bandwidth / license / 风扇温度 | Raw 对应 keyword;区域用主机名前缀 |
| 单机当前告警(已给 host) | 限定该 `host_name`;**禁止**跑无关日报/license 流程 |
| 告警码 NNNN | 按 code 过滤;只列有 `host_name` 的 |
| 时间窗(如 17:50–18:15) | 先新鲜度;时区以用户为准(未声明则沿用对话语境,现场常见 WIB/UTC+7) |
| 能否登录 / SSH | §3 |
| A<>B 容量 / 两端光功率 | **两端端口光模块 CLI 实读**,不是带宽利用率告警 tally(除非用户只要告警):解析两端 host → 路径/LLDP → **多台一批** optic CLI |
| 哪段断了 + LOS 主机 | `object_name` + `findTopologyPaths` |
| BGP/OSPF/LDP 等 | 指定 host 或双端协议类 Raw;要比时间就对齐;**默认不当断纤** |
### 口语约定
- **区域** = `host_name` 在第一个 `-` 之前的前缀(如 `ACH-`),大小写不敏感 starts-with。
- **A<>B / capacity / 两端光功率** = 互联口 SFP/光功率 CLI;≠「带宽利用率超阈值」告警清单。
- **断纤清单** ≠ **光功率门限清单**(后者 keyword=`optical power`)。
- 绰号:先 `queryNmsNeInventory` 解析成 `host_name`;解析不到则说明无法解析,禁止瞎编。
- 「继续 / YES / 确认」:接着上一未完成任务,不整段重开。
### 常见 cause 子串(keyword / 证据标签)
| 意图 | 典型子串 |
|------|----------|
| 断纤 / LOS | `ETPI) LOS`、`Fiber Break`、`Missing laser module` |
| 光功率门限 | `Input optical power(dBm) threshold`、`Output optical power` |
| 拥塞 | `bandwidth usage rate` |
| CRC | `CRC error` |
| 离线 | `BN EMS`、`NE communication failure` |
| dying gasp | `Remote dying gasp` |
| License | `Permanent license`、`No enough license` |
| 环境 | `System Power off`、`undervoltage`、`temperature`、`Fan` |
Neighbour / PW / Tunnel 等控制面量多 **不当断纤**,除非用户问的就是该协议族。
---
## 6. 禁止
1. 无 severity / keyword / host / 区域 / 时间过滤翻全量告警。
2. 把 NMS UUID 当作 `getManagedNe` 的 `ne_id`。
3. 同轮对多台各调一次 `execManagedNe`。
4. 光功率门限清单误用断纤/LOS 配方。
5. 单机告警问句跑无关定时/license 流程。
6. 终稿只有过程叙述、无 Result/Evidence。
7. CLI 失败后对**同一错误命令、同一台**盲重试(应换命令或 `?` 探索)。
8. 对用户输出 UUID,或用无 `host_name` 的行凑数。
9. 因一台设备命令失败,就放弃该命令在其他设备上的使用。
---
## 7. 终稿
`*主题 — 范围*` + Result / Evidence / Next;短、可扫读;大表只给 Top(且仅含有 `host_name` 的)。
速查:[reference.md](reference.md)。
---
## 8. 附录:厂商差异与 `?` 探索
仅在记不清命令、报错或需盲查时用。与 §4「多台一批」不同:此处是 **一台设备上多条探索命令**。
### 厂商前缀
| 厂商 | 只读习惯 |
|------|----------|
| 华为(含部分 VRP) | 多为 `display …` |
| 中兴 / Cisco / 多数其他 | 多为 `show …` |
混厂商多台:用 `targets`。同厂商多台:可先共享 `commands`,但须接受下面「同厂也可能不一致」。
### 同厂不同设备命令也可能不一致
检查整网时**一定会**碰到:同为中兴/华为,A 台能敲的命令 B 台报错或参数不同(版本、牌号、角色差异)。
- **禁止**因一台失败就认定「这条命令全网作废、以后都不用」。
- **应**:失败台单独换命令或走 `?` 探索;其余已成功的台继续用原命令。
- **优先复用本会话已成功过的命令**(含同厂其他台验证过的):新台/下一批先试这些,再对失败子集另探;不要一失败就换全员命令。
- 多台一批时:用 `targets` 给失败台换命令,或先一批共同命令 → 只对失败子集再一批探测;不要为了一台把成功台的结果丢掉重来。
### `?` 规则(`show` / `display` / `ping` 等同一套)
1. 报错中的 **`^`** 标出错位置 → **`^` 之前**已正确 → 对该前缀加 `?` 列下一级。
2. `?` 可紧接在部分单词后(`show optical?`、`display inter?`),不必强制空格再写 `?`。
3. 输出含 `<cr>` → 命令已完整,可回车执行。
4. Incomplete → 未写完,继续 `?`;Unrecognized → 走错,退回上一层换词。
5. **禁止**对同一错误整句反复重试。
例:
```text
<r1>display interface briaf
^
Error: Wrong parameter found at '^' position.
```
→ 改为 `display interface ?`,再选 `brief` 等合法下级。`show` 同理。
### 同台分裂(一台、多条、一次调用)
某一层 `… ?` 列出多个候选且还需下钻时:在 **同一台** 一次 `execManagedNe` 的 `commands[]` 中放入多条同级探索,综合结果再往下裂。
**不是**多台同时探同一条 `?`。
例(已见 `display ip routing-table ?` 后):
```text
display ip routing-table protocol ?
display ip routing-table all-vpn-instance ?
display ip routing-table all-routes ?
```
对应一次调用形如:`execManagedNe(nms_ne_id=…, commands=["display ip routing-table protocol ?", "… all-vpn-instance ?", "… all-routes ?"])`。
### ZTE 光模块
优先 `show opticalinfo brief` → 其次 `show optical brief` → 仍不对则 `show optical?` / `show opticalinfo?`;同级候选用上面的同台分裂,勿盲猜整句。

View file

@ -1,49 +0,0 @@
# netx-ops 速查
## 两套「一批」
- **多台一批**:一次调用打多台(`nms_ne_ids`/`targets`)
- **同台多条**:一台上一次 `commands[]` 多条(含多个 `… ?` 分裂)
- 禁止:同轮多台各调一次 `execManagedNe`
## 新鲜度
- `runNmsDiagnostics` / `aggregateNmsAlarms` → `meta.last_seen_*`
- 快照:时间窗 ∈ min~max;勿默认「现在−30 分钟」
## 能否登录
1. `listCliTargets` / `listManagedNe` → 命中则实测 `show version` 或 `display version`
2. 未命中 → inventory 说明有无;未进 CLI 通道则本通道不能登
## 短路径
| 意图 | 调用 |
|------|------|
| Critical Top | `aggregateNmsAlarms(severity=critical, top_ne=20)` |
| 态势 | `runNmsDiagnostics` / `aggregateNmsAlarms` |
| 断纤 / LOS | Raw `keyword=LOS` / `Fiber Break` |
| 光功率门限 | Raw `keyword=optical power` + 前缀(≠ 断纤) |
| 离线 | Raw `keyword=BN EMS` |
| 单机告警 | 限定 `host_name` |
| 证据 | `queryNmsAlarmsRaw(field_preset=evidence)` |
| 清单 | `queryNmsNeInventory` / `getNmsNe` |
| 多台 CLI | `execManagedNe(nms_ne_ids=…)` 或 `targets` |
| 长任务 / ≥4 台 | `execManagedNe(async=true)` → `getNeExecJob(job_id=…)` |
| 设备能力 | `getManagedNe` → 读 `capability.recommended_mode` |
| 路径 | `findTopologyPaths` |
| A<>B 光 | 两端 host → 路径 → 多台一批 optic CLI |
## 展示
- 只展示 **host_name**;严禁 UUID
- 无 `host_name`:丢弃
- DSH:前缀 `netx__`
## 厂商与 `?`
- 华为 `display`;其他多 `show`;方法同一套 `?`
- **同厂也可能命令不一**:一台失败 ≠ 全网作废;失败台另探,成功台继续原命令;**已成功过的命令优先复用**
- `^` 前正确 → 加 `?`(可接词尾)
- **同台分裂**:一台 `commands[]` 多条 `… ?`;**不是**多台同探
- ZTE 光:`opticalinfo brief` → `optical brief` → `show optical?`

View file

@ -1,378 +0,0 @@
---
name: netx-topology
description: >-
用 netx-topology MCP / DSH topology 组查邻接、分类打标;先 dual_unit 一次抽最大核心眼,
再换其它算法下沉/布图(不污染 Fabric)。触发:画拓扑、布图、拖图、分类、LLDP、Fabric、netx-topology。
---
# netx 拓扑(通用)
## Hosts(MCP + DSH)
| Host | Tools |
|------|--------|
| **MCP** `netx-topology-mcp` | 裸名(`sinkTopologyDualUnits` …);安装见 [`docs/MCP_TOPOLOGY.md`](../../../docs/MCP_TOPOLOGY.md) |
| **DSH** `dsh-netxops` | `netx__` + 同 stem。整组工具 + skill **`netx-topology`**(能力组 **topology**,默认关)。缺布局引擎能力时用 MCP。 |
Canonical: `netx/skills/topology/netx-topology/`(唯一正文)。
**原则**:
1. **第一步用 dual_units**:从**核心**出发,选**覆盖网元最多**的那一只眼(`prefer_top_eye` + max cover)。
2. **`sinkTopologyDualUnits` / `layout_dual_unit` 整图流程只用一次**——只沉这一只最大眼;**禁止**再连抽下一批 dual_unit。
3. **后续下沉换算法**:`suggestSinkHubs` → `move_nodes(park)` / `projectTopologyNeighbors`;**不要**再 `sinkTopologyDualUnits`。
4. **眼图已定型 → 门控精修**:对该 sink 只用下表「允许」动作;禁止全局拆眼工具。
5. 布眼目标:少交叉、眼心空旷、少重叠(椭圆弧带;长链在眼外)。对照人工金标时还要看:**紧凑度、正交边、贴边清开**(见「算法天花板」)。
**禁止**写临时 py 穷举坐标或直接调 HTTP;验证与压交叉**只调 MCP**(或 DSH 已实现的同名工具)。不造 Fabric 边。
**脱敏(硬)**:勿把客户网元名、站点/区域名、具体交叉数、具体 view_id 写进本 skill。角色只用通用词:门户 / 枢纽 / 汇聚 / 接入 / 末梢。**禁止**在 skill 正文写站点缩写或设备角色缩写当专名举例。
---
## 完整处理思路(一次大区接入眼)
按阶段执行;每阶段只调 MCP,读返回再决定下一步。
```
① 认眼
analyze(structure) → dual_units 里取覆盖最大的核心门户对
② 一次 sink
sinkTopologyDualUnits(max_units=1, prefer_top_eye, layout_batch)
→ 同一 sink 贯穿全程;眼形不对只 layout_dual_unit 重布同一对门户
③ 换算法下沉(禁 dual)
循环:suggestSinkHubs(pick:1) → move_nodes(park) → until_limit(crossing)
空批次后:orphan_batch / 对根−sink−门户 分批 park;仍禁 dual
④ 眼定型精修(门控序列,见下)
until_limit(crossing) → until_limit(total)
→ clear_edge_hits(portal_ids) → pull_far_chains(portal_ids)
→ compact_bbox(portal_ids, outlier_only) → 可选再 clear / total
⑤ 停手(算法天花板,**默认无范本**)
overlaps=0;until_limit stall;pull/compact/clear 均 moved≈0
→ 交付:眼可读 + ov=0 + 交叉可接受;util/正交/贴边缺口 → 手拖或改初布
→ 勿再发明无门控全局工具硬挤
```
**范本现实(硬)**:
- **日常默认没有范本**;有人工图也几乎不是同一张网 → **禁止**把 `align_reference` / 抄坐标当主路径。
- `align_reference` **仅**用于:同网同成员画布的 A/B 对照、调试、回放(共享 `fabric_node_id`)。跨网 ID 对不上,抄了也无意义。
- 生产要缩短与人工观感差距 → **改初布配方**(正交/紧凑从 dual 一次就偏地铁风),不是现场找金标对齐。
**从零重布(无范本算力路径)**:
- 眼已在 sink、只需甩掉坏坐标 / 金标残留:对本 sink **`layout_dual_unit`(同一门户对)→ 立刻门控精修**。这**不算**再 sink 一批 dual。
- 初布后交叉会先飙高、贴边变差属正常;**不要**因此改走 `polish_crossings` / `fix_overlaps`。
- 纯算力压交叉后 util/bbox 常崩(枢纽被 `until_limit` 甩远)→ **必须**接 `pull_far_chains` → `compact_bbox`;缺这两个 action 就先重启 MCP,勿半截交付。
- 无范本时:交叉可压到可读,但仍常高于「抄同网坐标」路径;util/正交缺口靠 pull + 初布,**不是**再堆 until_limit。
**开局核对 catalog(硬)**:
- 精修前先 `layoutTopologyView(catalog=true)`:须见 `pull_far_chains`、`compact_bbox`,且 `version`/`rev` 为当前包。
- 缺 action 或 `rev` 过旧 → **重启** netx-topology MCP 再干;禁止用旧进程硬跑、禁止改走 HTTP/临时 py。
**精修硬门控(已写进算法,skill 须遵守)**:
- 任何落笔:**全局交叉不得升高**;升高 → 弃该候选。
- **最小化展开**(bundle):从小压缩步长试探;步长须清开 footprint(成员互叠也算失败)。
- **侵入** = 展开后踏入外人框 / 留下任意 footprint 重叠 → 换 tip,勿硬塞。
- `until_limit` 只接受 `overlaps=0`;apply 遇 `overlaps_remain` → 修算法,**勿**对眼 sink 跑 `fix_overlaps`。
- `portal_ids` **只钉眼门户**;`freeze_layers=["core"]` 会冻住 sink 上所有核心(含旁路枢纽)——慎用。
- 旁路枢纽也要动时:`protect_rigid=off`(仍受 `portal_ids` 约束)。
- `clear_edge_hits`:**禁止**放宽交叉(含 `preserve_axis`);交叉升高一律拒。
- `pull_far_chains`:相对**门户中点**缩放远走廊/孤立点/远叶(不是朝外圈枢纽);枢纽本身在外围时朝枢纽缩**收不了 bbox**。
- `compact_bbox`:默认 **farthest-K**(`outlier_only`);全图/分位均匀缩易叠点 → 拒。
- `until_limit`:`stop_reason=max_moves` **≠** stall → **再调**直到 `stall`(可提高 `max_moves`,但仍以 stall 为准)。
- 大眼 `max_jump` 用 4k–8k;勿收到 &lt;2k。大图会 background → `job_status`(可数分钟);勿中途改走拆眼工具。
---
## 算法天花板(经验固化)
大区接入眼跑完门控序列后,常见结果:
| 维度 | 算法能做到 | 再挤会怎样 | 交付策略 |
|------|------------|------------|----------|
| 交叉 | 从零 dual 后 `until_limit` 可大幅压下;无范本时仍常高于抄坐标路径 | 再挪极值尖端易抬交叉;`max_moves` 停了要续跑到 stall | **停**于 stall;残余当弦交叉可接受 |
| 重叠 | 硬零 | — | 必须保持 |
| 紧凑 util / bbox | 压交叉常先**炸开** bbox;`pull_far_chains` 多轮收远走廊 | 缺 pull 就半截交付;强缩常 `Δg>0` 拒 | **必跑** pull→compact 到 `moved=0`;其余手拖 |
| 贴边 clearance | `clear` + `objective=total` 能降一批 | 与交叉/紧凑拉扯,清到个位数很难 | stall 后手拖擦边点 |
| 正交 axis | `total` 略抬;远低于人工地铁风 | `preserve_axis` 清边若放宽交叉会毁掉前面成果(已禁 slack) | 手拖走廊;勿为轴牺牲交叉 |
| 直链 / 环穿 | bundle 后仍有折角与穿环 | 全局 straighten 拆眼 | 勿清零;结构优先 |
**判定「算法到头」**(门控精修,无范本):
1. `overlaps=0`
2. `until_limit`(crossing 与 total)均 stall
3. pull/compact/clear `moved≈0`
4. 目视:眼心空、双门户可辨
**可选对照**(仅评测):对**同网**人工画布与算法 sink 各 `analyze`,只比 `summary`;**勿**因此去 `align_reference` 当交付手段。
**明确无效 / 禁止再试**(眼 sink):
- 再次 `sinkTopologyDualUnits` / `until_empty` / 新块画布抽 dual
- `polish_crossings` / `straighten_channels` / `untangle` / `fix_overlaps` / `orbit_sweep(round=true)`
- 临时 py 穷举坐标、无门控的整图缩放/平移极值点
- 为追 util 关掉交叉门控或给 `clear` 开交叉 slack
- **把跨网/他网人工图当范本抄坐标**(含滥用 `align_reference`)
---
## 主路径(必循)
```
analyze(structure) → 读 dual_units(认核心最大眼)
→ 有 level:先打标;巨图勿先全量 level_bands
→ 【仅一次】sinkTopologyDualUnits(
max_units=1, prefer_top_eye=true, prefer_pure=false,
sink_layers=[access], keep_layers=[core,agg], layout_batch=true,
max_nodes/max_batch_nodes 放行大眼
)
→ 眼形不对:可对本 sink 重跑 layout_dual_unit(仍算同一只眼;勿再 sink 下一批 dual)
→ 【换算法】suggestSinkHubs → move_nodes(park) 分批迁入同一 sink;每批后 until_limit;禁止再 dual
→ 【眼定型】门控精修序列(下节);禁止全局拆眼
→ 根图可 level_bands(只动根上残留,不动已定型的眼 sink)
```
停手:最大眼可读、眼心空、`overlaps≈0`、门户清晰;其余节点用非 dual 手段靠上去即可。交叉允许来自跨走廊弦边。
**dual 一次用尽**:`until_empty`、再次 `sinkTopologyDualUnits`、为下一批 dual 新建块画布 —— **一律禁止**(日常)。
**布眼几何**(仅那一次):嵌套半椭圆;中轴只有双门户;长链/尾巴放眼外禁穿环。眼坏了重跑 `layout_dual_unit`,勿全局拆眼。
---
## 眼图定型后的精修(门控序列)
眼 sink 一经验收,**禁止**再对该画布跑会整图拆眼的工具。
| 允许 | 禁止(眼 sink 上) |
|------|-------------------|
| `analyze(detail=hotspots\|both)` 只读 | `polish_crossings` / `straighten_channels` |
| `orbit_sweep`:**`until_limit=true`** 或 `node_id` preview→pick | `orbit_sweep(round=true)` 全图轮扫 |
| `clear_edge_hits`(**须** `portal_ids`;交叉不升) | `fix_overlaps` / `untangle` 批处理整图 |
| `pull_far_chains`(朝**门户中点**收远走廊/孤立点;同上门控) | `layout` / 再次 `sinkTopologyDualUnits` |
| `compact_bbox`(farthest-K;交叉不升、ov=0) | 临时 py 穷举坐标 |
| `updateTopologyViewPositions` 手拖 1~少数点 | `align_reference` 当无同网范本时的交付手段 |
| 眼形整体崩了才 `layout_dual_unit` 重布同一门户对 | |
**推荐节奏**(一次序列,读返回再决定是否重复某一环):
```
0) catalog=true 核对 pull_far_chains / compact_bbox(缺则重启 MCP)
0b) 从零重布时:layout_dual_unit(同一门户对, apply) // 交叉飙高正常
1) until_limit + objective=crossing + portal_ids + bundle
→ stop_reason=max_moves 则续跑,直到 stall
2) until_limit + objective=total // 交叉不升;抬贴边/正交;亦可 max_moves→续跑
3) clear_edge_hits(portal_ids, top_n/max_moves 可加大;勿 preserve_axis 除非确认不抬交叉)
4) pull_far_chains(portal_ids) // 可 2~3 轮直到 moved=0;压交叉后必做
5) compact_bbox(portal_ids, outlier_only) // 常 accepted=false,正常
6) 可选:再 clear 或 total;再 stall → 停手
```
`until_limit` 调用模板:
```
layoutTopologyView({
action: "orbit_sweep", mode: "apply",
params: {
until_limit: true,
objective: "crossing", // 再一轮改 total
portal_ids: [<门户A>, <门户B>],
protect_rigid: "off",
max_degree: 14,
max_jump: 8000, // 大眼用 4k–8k;勿 <2k
max_stretch: 32,
max_moves: 40, // 触顶只说明配额满,不是算法到头
bundle: true
}
})
→ 读 local.op:start/end_crossings、moves[]、stop_reason
→ max_moves → 再调;stall → 进入下一步
```
`round=true` ≠ `until_limit`:前者一批 top_n 死拿 pick#1;后者循环到 stall。
勿为清零交叉拆弧带;以 `verdict.total` + 目视眼形 +「算法到头」四条为准。
---
## 阶段 0 — 画布与成员
1. `getTopologyTree` → **`view_id`**(文件夹 physical 画布)。
2. 建根/子区域只用 `createTopologyFolder`。
3. 缺层级/区域时:`classifyTopologyFabricNodes`(`match` → `tag(level, dry_run)` → `tag`;或 `unmatched` / `apply_rules`)。**level** 越小越靠外(0 外部 / 1 核心 / 2 汇聚 / 3 接入;可用 1.1、2.1 子层)。**勿**切片建图。
4. `analyzeTopologyViewLayout({ view_id, detail: "structure" })` 读 `dual_units` / `shape` / `layers` —— **先认核心最大眼**(`unit_count` / 各 unit `node_count`)。
5. `addTopologyViewNodes`(可按 `role` 大档或后续 level)/ `projectTopologyNeighbors`(区域画布务必 `region_folder_id`)。
6. 多余点用 `removeTopologyViewNodes` **移出画布**(不删 Fabric)。`region:…` 幽灵点勿当网元拖。
---
## 形状与 dual_units(只服务「第一只眼」)
```
analyzeTopologyViewLayout({ view_id, detail: "structure" })
```
| 字段 | 用途 |
|------|------|
| `shape.primary` | `chains` / `star` / `mesh` / `mixed_blocks` |
| `dual_units` | **从核心出发**:core–core → core–agg → …,再取 **node_count 最大**的一只做唯一 sink |
| `advice.block_plan` | 其余块怎么靠(非 dual 连抽) |
| `gravity.type` | 链图勿当 hub 花瓣 |
- **第一步必选最大核心眼**:`prefer_top_eye=true`,`prefer_pure=false`,`max_units=1`;目标是**一块吃进尽量多网元**,不要求交叉=0。
- **禁止**把 dual_units 当成「抽干源图」的循环泵;剩余网元见下方「后续下沉」。
- **禁止**再用互斥 soft_block 把通路切开。
- **链图 / 小图**:可不走 dual;preview `corridor`/`compact`。
---
## 根图 → 子区域:第一次(dual,仅一次)
```
sinkTopologyDualUnits({
source_view_id: <根图>,
sink_view_id: <子区域>, // 同一张接入画布贯穿全程
max_units: 1,
max_nodes: 300,
max_batch_nodes: 400,
layout_batch: true,
sink_layers: ["access"],
keep_layers: ["core", "agg"],
prefer_top_eye: true,
prefer_pure: false,
// 勿 until_empty;勿随后再调本工具
})
→ 眼形验收 / 手拖;必要时 layout_dual_unit 重布同一眼
```
- **同一 sink**:后续非 dual 迁入也进**同一个** `sink_view_id`;禁止块1/块2/块3。
- **眼定型后**:对该 sink **只门控精修**(见上节)。
---
## 后续下沉(换算法,禁用 dual)
源图仍有接入 / 旁路网元时,**不要**再 `sinkTopologyDualUnits`。
0. **先问算法要批次**(推荐)
```
suggestSinkHubs({
source_view_id: <根图>,
sink_view_id: <眼 sink>,
pick: 1,
})
→ batch.fabric_node_ids / move_nodes 可直接套用
```
排序:剩余领地大优先、agg 优于 core;眼门户不当 batch 头。
**每批迁完立刻** `until_limit(crossing, portal_ids)`,再取下一批;勿攒多批再一起 polish。
`suggestSinkHubs` 空了但根上还有孤立末梢/旁路:清掉已在 sink 的重复点,再对 `root−sink−眼门户` 分批 `move_nodes(park)`(每批后 `until_limit`)。也会直接吐 `orphan_batch_*`。勿再 dual。
1. **指定迁移 + 扫角停靠**
```
layoutTopologyView({
action: "move_nodes",
source_view_id: <FROM>, view_id: <TO>,
mode: "apply",
params: {
fabric_node_ids: [...],
park: true,
remove_from_source: true,
},
})
```
2. **投影邻居**:`projectTopologyNeighbors`(区域画布带 `region_folder_id`),再手拖或 `orbit_sweep`。
3. **根图收尾**:`level_bands` 只钉**根图**上残留的 core/agg;**勿**对已定型眼 sink 跑全局带。
4. **小连通块**:源上剩余短链/星,可对**源图**或迁入后**单点**处理;勿对眼 sink `layout(recipe=…)`。
---
## 分层布局(特殊场景)
当网络需呈现层级结构(外部→接入→核心→汇聚→客户等)时:
### 分层通用规则
1. **顶层:外部/对接网络**
2. **次顶层:终端客户接入层**
3. **中间层:核心层**
4. **核心下层:汇聚层**
5. **底层:接入层/孤立层**
### 优化流程
1. **先手动后算法**:用户手动拖拽保证业务结构,再用算法优化
2. **分层约束**:`updateTopologyViewPositions` 按层钉 y;`orbit_sweep` 可传 `y_min/y_max`
3. **分步(非眼画布 / 根图)**:可贴边、分层钉 y;**已定型眼 sink 除外**——只允许门控精修 / 手拖
### 冲突处理
| 冲突场景 | 处理方式 |
|---------|---------|
| 交叉 vs 结构 | **结构优先**(最大眼 / 分层) |
| 算法 vs 手动 | **手动优先** |
| polish 拆眼 | **禁止**对 dual 眼走 straighten 主路径 |
| 算法 vs 金标观感 | 交叉可赢;紧凑/正交/贴边交给手拖 |
---
## layoutTopologyView(精简)
| action | 用途 |
|--------|------|
| `layout` | 小图 / 剩余块:`compact` / `corridor` / `rings` / `unstick` |
| `layout_dual_unit` | **仅**重布已沉的那一只眼;椭圆弧带 + 空心眼 |
| `move_nodes` / `sink_nodes` | **后续下沉主路径**;`park` 块扫 |
| `orbit_sweep` | **眼后精修主工具**:`until_limit` 或 `node_id`;`objective=crossing\|total` |
| `clear_edge_hits` | 眼 sink **可**(须 `portal_ids`;交叉不升) |
| `pull_far_chains` | 眼 sink **可**:朝门户中点收远走廊/孤立点 |
| `align_reference` | **非主路径**:仅同网同成员 A/B 调试;跨网禁用 |
| `compact_bbox` | 眼 sink **可**:farthest-K 收 bbox |
| `level_bands` | 仅根图/未定型画布;巨图在 dual 一次之后钉根 |
| `polish_crossings` | **眼 sink 禁用** |
| `fix_overlaps` / `untangle` / `straighten_channels` | **眼 sink 禁用** |
| `job_status` / `job_cancel` | 后台 job |
眼 sink 精修 = 门控序列 + 手拖补观感;不要排全局流水线,也不要在 stall 后继续发明新全局动作。
---
## 验收
| 块 | 看什么 |
|----|--------|
| `overlap` | 硬零(权重 0.24) |
| `crossing` | crossings/cpl;`top_nodes` / `top_edges` |
| `sparsity` / `edge_axis` / `edge_clearance` | 对照人工:紧凑、正交、贴边(算法常弱于此) |
| `rings` / `chains` | 环被穿、走廊折角 |
| `verdict.total` | ≈70 可交付(ov=0);结构可读优先于刷分 |
| `score_profile` | `auto`(默认)/ `default` / `eye`;大稀疏斜边画布自动走 eye |
**评分修订(相对旧权)**:util 甜区下限 0.08;贴边分=节点命中∪hits/link;rings 降权;eye 再降 axis、抬 util/clearance;`compactness` 仅诊断不加权。
图标 25px;推荐中心距 Δx≥200、Δy≥170。
---
## 工具速查
| 工具 | 作用 |
|------|------|
| `getTopologyTree` / `getTopologyView` | 树与画布 |
| `createTopologyFolder` | 新建根/区域 |
| `add` / `remove` / `updateTopologyViewPositions` | 成员与手拖 |
| `sinkTopologyDualUnits` | **仅一次**:最大核心眼 → sink |
| `suggestSinkHubs` | **后续下沉批次**(含 orphan_batch) |
| `move_nodes`(经 layoutTopologyView) | **后续下沉** |
| `projectTopologyNeighbors` | 投影邻居 |
| `copyTopologyViewNodes` | 克隆沙箱 |
| `queryTopologyFabricNodes` | 库存 |
| neighborhood / edges | 邻接 |
| `layoutTopologyView` | 上表 action |
| `analyzeTopologyViewLayout` | structure + 验收 |
---
## 代码热更
1. 本仓 MCP 用 `PYTHONPATH=…/src`(见用户 `~/.cursor/mcp.json`),改源码后不必为加载而 pip install。
2. **必须重启** stdio 进程(杀旧 `python -m netx_topology_mcp` 后由 Cursor 拉起;或 MCP 面板禁用/启用)。
3. `layoutTopologyView(catalog=true)` 核对:`version` / `rev`(`NETX_MCP_REV`)+ action 含 `pull_far_chains` / `compact_bbox`。
4. 机器上常残留多份旧 MCP 进程 → 精修前 catalog 若仍缺 action,**杀光再启**;勿用旧进程半截交付,勿改走 HTTP/临时 py。
拓扑页开「实时同步」可看落笔。勿用告警/CLI MCP 写拓扑。