---
name: meegle-plugin-shared
version: 1.0.0
description: "Meegle 插件开发共享基础：插件工程识别、Device Code OAuth 认证、Token 管理、安全规则。所有 meegle-plugin-* skill 的公共前置依赖，不单独对用户触发。"
metadata:
  requires:
    bins: ["npx"]
  cliHelp: "lpm --help"
---

# Meegle 插件开发共享规则

> 本文件只给通用前置规则（根原则 / 工程识别 / 认证 / 安全）。引用的 references 在下文各小节被触发条件时就地点名，**不要在进入 skill 的第一轮就预加载**。

## 三条根原则

### 根原则 1：无源即停（输出可溯源）
AI 输出每一项有业务语义的内容，必须可追溯到合法信息源；找不到 → 立即停下问用户。

### 根原则 2：数据完整性（写入前先拿基线）
任何对远端配置的写入都是全量覆盖，提交前以远端基线为参照、对比差异、对减少项让用户确认。

### 根原则 3：真实数据动作显式确认
任何对远端产生真实影响的动作，执行前向用户列出"将要做什么 + 影响哪些数据"，等用户显式同意才执行。

**五类必须先确认的动作**：
1. **创建插件**（在 Meegle 后台生成插件记录，pluginId 从此绑定）
2. **配置点位**（`local-config set` 等，把 plugin.config.json 全量推到后台）
3. **配置基本信息**（更新插件名称 / 简短描述 / 详细描述 / 分类等元信息）
4. **发布插件**（`publish`，发到 Meegle 市场，用户可见，最不可逆）
5. **申请 OpenAPI 权限**（`lpm perm apply` 前列出即将申请的 scope 清单、等用户点头）——给插件 app 授 OpenAPI scope 是对 app 权限的真实变更：拓宽这个插件 app 能做的事、可能要走后台审批、在 app 的权限列表里可见，和创建 / 配置点位 / 配置基本信息 / 发布同级

> 遇到未分类的 CRITICAL → 按"无源即停"默认处理（最保守动作：停下问用户）。

## 插件工程识别

**判定**：cwd 存在 `plugin.config.json` 且含 `pluginId`（`MII_` 开头）。

**识别后**：CLI 自动从配置读 `siteDomain` / `pluginId` / `pluginSecret` / `resources`，skill 不需要手动传。这些字段由 CLI 维护（`local-config set` 一步完成"推+拉+生成模板"），skill 只读、不直接 Edit/Write。

**执行约定（CRITICAL — 工作目录锚定，本规则唯一权威源）**：agent 的 shell 在每条命令之间**不保证保留 cwd**，且 primary cwd 不一定等于插件工程目录（插件可能嵌在子目录、或你正坐在别的仓里）。因此**每一条 `lpm` 命令都带 `--cwd <插件工程绝对路径>` 前缀**，例如 `lpm --cwd /abs/path/to/plugin local-config diff`。`--cwd` 让 CLI 在执行前先 chdir 到插件根，`plugin.config.json` 与 `.lpm-cache` 都按它解析；目录不对时 CLI 直接 fail-fast（错误信息带 `--cwd` 提示），不会静默跑到错的地方。识别 / `create` 通过后**立即把插件工程的绝对路径记下来**（有 checkpoint 时用 `lpm --cwd <abs> ai state set` 写进 `.lpm-cache/state.json`），之后每条命令都用它。

## 认证

Token 由 CLI 按 `siteDomain` 分域管理。

**`--site-domain` 传参规则**：
- `create` / `login` 由 AI 传 `--site-domain`（用户未指定时**先跑 `lpm whoami`，一切以它列出的真实登录站点为准**：列出了已登录站点就原样列出全部真实站点供用户挑选——忽略 `← 建议` 标记、不预选、不附自定义项；仅当 whoami 报「未登录」时才用「飞书项目 / Meegle / 自定义域名」通用项让用户贴 URL）
- 其他命令不传 `--site-domain`，CLI 自动从 `plugin.config.json` 读取

**前置约定**：用户在接入插件开发前应已执行过 `lpm login`（见接入文档 Step 2）。skill 直接调 CLI，token 有效性由 CLI 在命令运行时判定。

**遇 auth 错误时**：CLI 会 stderr 打印完整登录指引（方式 A token 粘贴 / 方式 B Device Code OAuth + 可执行命令）。AI **逐字转呈** 给用户，由用户执行 `lpm login` 后重试命令。

## 安全规则

- **CLI 维护的文件/目录**（`.lpm/` 内部目录、`plugin.config.json`）只通过 CLI 命令操作；`plugin.config.json` 的点位数据由 `local-config set`（本地暂存）+ `update --source-type=local`（推远端 + 拉模板代码）两步维护，skill 读它、不直接写
- **禁止输出密钥**（accessToken、pluginSecret）到终端明文

### 自建后端红线

插件的"自建后端那一半"（webhook 接收 / 调 OpenAPI / 写回，由 feature「→ 后端那一半」编排、服务端代码交后端会话写）：

- **OpenAPI 鉴权凭据进后端 env，不进前端 bundle**（`AUTHORING.md §8.1`）——后端代码里只写 `process.env.X` 引用，真值由用户设进后端运行环境；不从 `plugin.config.json` grep / decrypt `pluginSecret` 粘进任何地方
- **后端运行时拿 Meego 数据只走 OpenAPI**（`AUTHORING.md §8.2`）——不依赖 `lark-project` skill、不 `child_process.exec('lpm ...')`；`lark-project` / `lpm` 是开发期本地工具，没有面向运行时的鉴权与稳定性 SLA

### 全量提交约束（CRITICAL — 防数据丢失，本规则唯一权威源）

`local-config set` 是**全量替换**——提交什么就存什么，远端不做合并。**遗漏任何现有点位都会导致该点位被永久删除。**

**操作流程（不可省略任何一步）**：
1. 先 `local-config get --remote` 获取远端完整配置作为基础
2. 在完整配置基础上做局部增删改
3. 将修改后的**完整配置**作为 draft 三步提交：`local-config set --from <draft>`（本地校验 + 写入 `point.config.local.json`）→ `local-config diff`（预览改动；有删除时 exit 2 必须转呈用户获取确认）→ `update --source-type=local`（推远端 + 拉回模板代码）

**底线**：必须以 `get --remote` 的基线构造完整 draft 再提交；不能只传变更部分，不能直接 Edit/Write `plugin.config.json`。

### `local-config set` 用户确认 gate（CRITICAL）

`local-config set` 是全量替换、不可静默回滚，**执行前 MUST 列 ADDED / MODIFIED / DELETED 三类差异清单 + 等用户明示同意**（"确认" / "OK" / "推送"）才能跑。用户拒绝 / 提改 → 回上游改 draft 重跑，不就地改绕过。

清单格式、Checkpoint 恢复、变更字段对比展示、与 A2 删除 gate 分工见 [`feature-config-apply.md §A0`](feature-config-apply.md)（格式唯一权威源）。

### 删除点位前置检查协议（CRITICAL — 所有调用路径前置 gate）

减少点位（draft / 本地配置丢了远端还在的点位）在三处都会被**硬拦下、`exit 2`**，都打印同一份清单（`⚠️ DELETION_REQUIRES_CONFIRMATION` 标题 + 每项 `type[key] "name"`）：

- `local-config set`：写本地前比对远端，丢点位 → exit 2，**不写本地**
- `local-config diff`：预览，丢点位 → exit 2
- `update --source-type=local`：推送前比对远端，丢点位 → exit 2，**不推送**

**任一处 exit 2 → 立即停止**：把 CLI 的 stderr **逐字转呈给用户**，等用户明示同意删除具体点位。不要静默删除、不要"帮用户总结"、不要自己判断"这是废弃点位应该删"。

用户明示同意后，才用 `--allow-delete` 绕过那一步重跑（`set` / `update` 均支持；`diff` 是纯预览无此参数）。**没有用户明示同意，绝不得加 `--allow-delete`**——删除远端点位不可逆。远端不可达时这些检查降级为告警放行（push 命中后端会再校）。

### 无源即停（CRITICAL — 根原则 1 的执行细则）

适用范围：代码、API 调用、字段值、配置参数等所有 AI 输出场景。

**合法信息源**：用户显式输入 / 粘贴、用户授权的 `<PLACEHOLDER: ...>`、权威工具的精确返回（点位标准能力 doc、飞书项目知识 MCP、CLI 实测输出）、工程已有代码/配置。

**非法信息源**（典型的"看起来合理但编造"）：AI 经验类比（"通常 SDK 都有 getX/setX"）、概念脑补（从 schema 字段名推断 SDK 方法名）、命名约定猜测（"通常驼峰所以这里也是"）。

**遇到无源时的统一动作（强制话术 + 强制停产出）**：

触发"无源即停"时 AI **MUST**：

1. **立即停止产出**：本轮不得再调任何产出工具（Write / Edit 写入点位 JSON、代码文件、CLI 命令等）。"停下来问用户" ≠ "嘴上停、同时继续写"。
2. **逐字输出下述话术**（允许填入具体内容、可选项 A/B/C 可剪裁为场景相关的版本，但"无法找到 … 依据 / 可选 / 禁止猜测 / 请告诉我选哪个" 四要素必须齐全）：

```
我无法在合法信息源中找到 "<具体内容>" 的依据。

可选：
A. 跳过此项，输出 TODO 占位（推荐——不写错的好过写错的）
B. 你提供真实值 / 示例 / 文档链接
C. 提供检索关键词，我重新查（可能找错了）

禁止我自行猜测——猜出来的内容会通过表层校验但运行时全废。
请告诉我选哪个。
```

3. **等待用户明确选择 A/B/C 之一** 才能继续；不得"选 A 同时继续产出其他项"的并发动作（同一批产出里任一项无源，整批阻塞）。

## MCP 检索技巧（飞书项目知识 MCP）

`mcp__feishu-project-knowledge__search_meegle_plugin_docs` 是 schema / point-type doc 没覆盖时的 fallback 主力。**实测有效模板**（按场景挑用）：

| 场景 | 关键词模板 | 实测示例 |
|---|---|---|
| **点位上下文 API**（getContext / 字段值读写 / watch 等） | `<PointName 英文> 上下文` | `Page 上下文` / `CustomField 上下文` / `Control 上下文` |
| **点位整套介绍**（配置 + 开发 + 数据通信） | 直接搜 PointName 英文 | `CustomField` / `LiteAppComponent` / `Schedule` / `Button` |
| **总览 / 选型** | doc 入口词 | `添加插件功能` / `添加构成` / `客户端开发概述` |
| **具体功能** | 业务功能词 + 技术词 | `表格列 控件 DSL` / `字段值 读取` / `自动化连接器 配置` / `事件订阅` |
| **OpenAPI 接口** | API 中文名 + "接口" | `获取工作项详情 接口` / `创建自定义字段 接口` |
| **错码 / 限制 / 沙箱** | 直接关键词 | `客户端沙箱限制` / `客户端资源限制` / `saveFieldValue 错码` |

**通用搜索心法**：
- **MCP 是 fuzzy 文本检索**，描述性查询（业务功能 + 技术词）比单查 API 名命中相关 doc 更全
- **用官方术语**：`工作项`（不是"任务"）/ `空间`（不是"项目"）/ `字段类型`（不是"自定义字段类型"）/ `控件`（不是"组件"）
- **中英双语** 高难关键词同时拟（如 `排期 schedule` / `字段配置 field config`）
- **搜不到 ≠ 不存在** — 改换近义词 / 上一层概念 / 业务场景描述 重试 1-2 次；分概念查再汇总

**MCP 缓存协议**（防 context 爆炸 — 本节是权威源，其他 doc 引用本节不复述）：

1. MCP 返回后立即把**完整原文** Write 到 `.lpm-cache/mcp/<slug>.md`，按子主题用 `## <主题>` 分节
2. **chat 回复只给路径 + ≤100 字摘要**，原文不回流
3. **重访同一查询**用 `lpm ai peek .lpm-cache/mcp/<slug>.md "<章节标题>"` 按需取节，不要 Read 整份；7 天以上视为过期重拉

`.lpm-cache/` 由 CLI 在 create/init 时自动写入 `.gitignore`，在 `local-config set` / `publish` 成功后按子目录自动清理，AI 只负责写入、不负责清理。

## Checkpoint（进度追踪）

state.json 的读写走 `lpm ai state get` / `lpm ai state set '<json>'`——CLI 经 `workspacePaths()` 把路径解析到插件根、`assertPluginRoot` 守卫，配合 `--cwd` 在任意目录都落到插件的 `.lpm-cache/state.json`。

**判断规则**：子 skill 执行前用 `lpm --cwd <projectRoot> ai state get` 读 checkpoint：
- **有输出** → 处于 workflow 编排中，按 [`references/checkpoint.md`](checkpoint.md) 的协议在每个 CLI 命令前后用 `lpm --cwd <projectRoot> ai state set` 更新 checkpoint
- **空输出** → 独立调用，不写 checkpoint

## 错误处理

遇到错误 → Read [`references/errors.md`](errors.md) 查对应处理方式。
