# CodePlanet 开发指南

> **Cursor 同步**：本指南副本位于 [`.cursor/开发指南.md`](../.cursor/开发指南.md)，并由 [`.cursor/rules/`](../.cursor/rules/) 在会话中自动注入。  
> 项目约定：后续开发须持续保持 `.cursor/` 与 `docs/` 内容一致（见 [`.cursor/README.md`](../.cursor/README.md)）。

**平台终点（北极星）**：视觉检测全栈演进路线见 [`docs/项目目标与架构终点.md`](项目目标与架构终点.md)（Ops / Runtime / 插件 / yslib / VisionObject）。

## R0 平台契约（已交付）

- **yslib Schema**：[`docs/schemas/yslib-v1.schema.json`](schemas/yslib-v1.schema.json)；示例项目 [`examples/projects/demo/`](../examples/projects/demo/)
- **更新 manifest Schema（草案）**：[`docs/schemas/update-manifest-v1.schema.json`](schemas/update-manifest-v1.schema.json)
- **ProjectValidator**：`src/core/project/ProjectValidator.*` — Ops 打开 `.yslib` 后 **Ctrl+B** 校验；Launcher 预检复用同一 API（R1.5）
- **AppPaths 扩展**：`userRoot()`、`userConfigDir()`、`projectsRoot()`、`launcherLogPath()`、`runtimeDumpPath()`、`projectLogsDir()`；**`dataDir()` 固定为 `%LOCALAPPDATA%/CodePlanet/`**（Qt GenericDataLocation，三端共用）
- **用户数据**：`USER/config/editor.json`；出厂默认布局 `resources/config/editor.default.json`
- **`global` 关键字**：Lexer/Parser 语法占位（R1 迁移 `static` 语义）
- **文档**：[迁移-static-global.md](迁移-static-global.md)、[附录-路径与日志约定.md](附录-路径与日志约定.md)
- **验收 / 回归**：`scripts/run_r0_regression.ps1`（自动化 + GUI 手动清单，分工见 `.cursor/开发指南.md` §R0 回归约定）

## R1 语义迁移（已交付）

- **`ProjectGlobalStore`**：`src/core/project/ProjectGlobalStore.*` — `global` 跨任务存储（按 yslib 路径）
- **`static`**：任务内持久，存于 `RuntimeWorker` 会话表；**不再**写 scope 0
- **`import`**：Lexer/Parser + `ScriptAnalyzer` 静态分析；**运行时链接**（R2 `executeImport`）
- **`StaticMigrationAnalyzer`** + **迁移提示** Dock（手动扫描，见项目树右键）
- **项目窗口**：`USER/projects/` 下 **每个子目录一个根节点**（与磁盘文件夹一一对应）；主 `.yslib` 优先 `{文件夹名}.yslib`，同目录多 yslib 时其余在文件树中打开
- **`recent_projects`** + **文件 → 最近项目** + 启动选择对话框
- **`YslibStructureWidget`**：yslib scripts + main entry 编辑；**视图 → 项目结构**
- **迁移提示 Dock**：**视图 → 迁移提示**
- **回归**：`scripts/run_r1_regression.ps1`

## R1.5 Launcher v1（已交付）

- **`CodePlanetr.exe`**：`src/launcher/` — 产线启动入口（5 秒倒计时、F8 修复模式、预检、建议性更新、进程监护）
- **`LauncherService`**：编排 `ProjectValidator` 预检、U 盘/在线 manifest 验签、拉起目标进程并写 `launcher.log`
- **`UpdateManifestVerifier`**：RSA-SHA256 + canonical JSON 实装；公钥 `resources/config/update.pub`
- **`ParentProcessGuard`**：`--parent-pid` / `--nonce` 校验；`CODEPLANET_DEV=1` 开发豁免
- **`DatabaseService::defaultProjectPath()`**：Launcher 读取默认 yslib（`app_settings.default_project_path`）
- **R1.5 过渡策略**：优先 `CodeRuntime.exe`；仓库布局（检出含 `CodePlanet.pro`）或 `CODEPLANET_DEV=1` 时可回退 `CodeEditor.exe`
- **回归**：`scripts/run_r1_5_regression.ps1`（分层 + Qt Test + GUI 手动清单）
- **bin 独立测试**：`scripts/deploy_bin.ps1`、`scripts/deploy_staging.ps1`（→ `dist/CodePlanet/bin`）、`scripts/test_parent_guard_intercept.ps1`、`scripts/run_launcher_dev.ps1`

**Launcher 分层**：仅依赖 `src/core`、`src/services` 与 Qt 模块，**不得** link `src/gui/` / `src/logic/`。

**Launcher 链接约定（R1.5）**：`launcher.pro` 仅编入 **ProjectValidator 静态分析链**（`Lexer` / `Parser` / `ExprParser` / `ScriptAnalyzer`），**禁止**编入 `AstInterpreter`、`AstRuntime`、`ExprEvaluator`、`FunctionDispatcher`、`PluginManager`（完整 AST/插件运行时在 Ops/Runtime 侧）。新增 Launcher 依赖前须确认是否仅为静态预检所需。

## R2 Runtime 最小闭环（已交付）

- **`CodeRuntime.exe`**：`src/runtime/` — 产线运行入口（`ParentProcessGuard` + `--project`）
- **`RuntimeOrchestrator`** + **`MainTaskWorker`** / **`SubTaskWorker`**：按 yslib `tasks` 调度；子任务 R2 用 `simulateTrigger()`
- **import 运行时**：`AstInterpreter::executeImport` 合并模块函数；静态 YS405/跨模块 call 检查
- **global 冲突**：`ScriptAnalyzer::analyzeProjectGlobals` → YS408；接入 `ProjectValidator` Ctrl+B
- **`#include` 相对路径**：`AppPaths::resolveIncludeRelative(includeName, scriptAbsolutePath)`
- **`MainTaskTimerService`** + `FunctionLib.ini` `timer_*` API；`FunctionDispatcher` 内置 dispatch；子任务 YS406
- **Runtime UI**：日志 / global 表 / processGroups 树（150ms 刷新）；**i18n**：Launcher 启动时 `--locale=zh_CN|en`
- **回归样例**：`examples/projects/demoV1.1/`（import + timer + global）
- **回归**：`scripts/run_r2_regression.ps1`（分层 + Qt Test + Runtime 构建重试/日志 + 父进程拦截 + GUI 手动清单；默认 fail-fast，可选 `-AllowStaleRuntime`）
- **bin 部署**：`scripts/deploy_bin.ps1` 含三 exe；`scripts/test_runtime_parent_guard_intercept.ps1`
- **调试稳态约束（R2）**：`AstRuntime::needsReload/loadSource` 统一 CRLF/LF 后再比较源码；`AstInterpreter::executeReturn` 回退到最近函数帧（覆盖嵌套块 `return`）；Debug 构建启用 `assertDebugInvariants` 断言暂停态栈一致性；空语句脚本 `resetExecution` 进入 `Completed`（避免 `Paused + 空栈` 断言崩溃）；`global` 在未绑定 `ProjectGlobalStore` 的单脚本调试场景回退到参数槽（避免 F5/F6/F10 出现未定义）；**F2/F3** 在当前文件首行就地重置（`RunDebugReset`），不回跳旧 IP，重置后可直接 F5/F6/F10；`functionDeclAtLine` 在首轮 `sourceFilePath` 场景回退路径匹配，保证“首轮/F2后”函数声明扫描一致；**F6 可视策略**：未执行语句行跳过，但结构行（`else/case/default/}`）及空白/注释保留可见；`switch` 命中前允许看到 case 标签，`break/continue/return` 后直接落到下一可执行语句；for Step Over 迭代落循环体首行；**结束态** `RunSessionEnded(completed|stopped|error)` — completed 保留 EOF 箭头，stopped 清箭头（Stop 路径不回写第1行箭头），error 保留 IP 须 Reset；Idle 打开 `.ys` 自动置首行箭头，切到 `.yslib`/非 `.ys` 自动清理箭头与调试路径
- **项目树默认展开**：`ProjectTreeWidget` 启动/刷新后仅展开 active 项目，无 active 时全部折叠

## R3 更新闭环与触发链路（已交付）

- **Apply 主流程**：`LauncherService::applyUpdate()` — manifest 缓存 → zip 定位/下载 → **`zipSha256` 必填** → staging 解压 → `ProjectValidator` → 原子替换（保留 `logs/`）→ 失败回滚；**`QtConcurrent` 后台 Apply**，UI 不长时间阻塞
- **安全基建**：`FileService` 安全解压（防 zip slip）、目录备份/替换；`UpdateManifestVerifier::verifyFileSha256`；`AppPaths::update*Path()`；HTTPS 下载 **超时 abort**，不使用不完整 manifest/zip
- **组件更新（manifest）**：`runtime` / `launcher` / `editor` 段（`setupUrl` + `setupSha256`）；Apply 后 **非阻断** 引导运行 NSIS 补丁包
- **触发链路**：`SubTaskWorker::onHardwareTrigger()` + 队列 burst 不丢触发；`TriggerMockService`；Runtime `--trigger-mock=camA` 与 UI「Mock 硬件触发」
- **mini dump**：`MiniDumpWriter` → `%LOCALAPPDATA%/CodePlanet/dumps/`
- **NSIS 安装/补丁**：`installer/nsis/*.nsi`；**一键打包** `installer/build_release_packages.ps1`（清 staging → 复制 `bin\` → windeployqt → makensis）；或 `scripts/build_nsis_packages.ps1`；安装根目录 **`$INSTDIR`** + 注册表 App Paths `CodePlanetr.exe`
- **U 盘更新测试脚本**：默认输出 `dist/CodePlanetrUpdate/`；可用 `installer/run_r3_update_pipeline.ps1` 串行执行 build/prepare/sign/verify，`installer/copy_codeplanetr_update_to_drive.ps1 -DriveLetter G` 复制到 U 盘；manifest 签名 canonical 必须与 Qt 字母序规则一致
- **Launcher 倒计时页**：优先显示本次会话新增 `launcher.log`，无新增日志时显示 `config/license.txt`
- **安装默认目录**：NSIS 默认建议路径为 `D:\CodePlanet`（用户仍可在目录页改盘符）
- **Apply 项目路径归一化**：优先定位当前安装目录 `USER/projects`，避免误用历史 `C:\Program Files\CodePlanet` 路径
- **组件补丁引导策略**：Apply 前弹出勾选窗口并先做可用性扫描；Runtime/Editor 可自动启动补丁安装，Launcher 因文件占用保留手动安装
- **补丁安装覆盖策略**：Runtime/Editor 补丁安装时 Qt DLL/插件若已存在则跳过覆盖，仅补充缺失文件，避免 Launcher 占用导致弹窗阻塞
- **倒计时自动启动策略**：倒计时到期后，若检测到已验证更新则进入更新页面，由用户手动选择“应用更新/跳过”；若无更新则直接启动 Runtime（未指定 yslib 时按失败流程提示）
- **版本元数据约定**：唯一维护入口为 `resources/config/release_version.json`；脚本仅自动更新 `productVersion/shortVersion/build`，`launcher/runtime/editor` 保留手工值
- **发包命名约定**：全量包保留 `CodeplanetrSetup-latest.exe`，每次构建前自动归档旧包为 `CodeplanetrSetup-{productVersion}.exe`；组件补丁包文件名版本优先读取 `runtimePackageVersion/launcherPackageVersion/editorPackageVersion`（缺省回退对应组件版本前三段）
- **demo payload 版本约定**：`prepare_r3_udisk_payload.ps1` 默认读取 `release_version.json.demoProjectVersion`，用于 `demo-{version}.zip` 与 manifest `projects.version`
- **构建清理约定**：`dist/CodePlanet/bin` 与 `dist/CodePlanet/Update/*` 每次构建均清理；仅 `dist/CodePlanet/Download` 保留全量包历史归档
- **shortVersion 约定**：由 `productVersion` 前三段自动同步，手工改版本请改 `productVersion`
- **回归**：`scripts/run_r3_update_regression.ps1`；`scripts/run_r3_regression.ps1`（= R2 + update + NSIS smoke）
- **Mock 工具**：`scripts/mock_hardware_trigger.ps1`

### R3 验签排障沉淀（必读）

- `zipSha256`（项目 zip 文件哈希）与 `canonical_sha256`（manifest 规范化文本哈希）是不同对象，不能互相比较
- 运行设备**不依赖 OpenSSL**；OpenSSL 仅用于开发机签名与本地验签脚本
- 若出现“脚本验签通过但 Launcher 失败”，先对比 `canonical_sha256`：不一致通常是 canonical 规则或文件版本不一致
- 签名脚本 canonical 必须与 Qt 字母序规则一致；脚本升级后旧 manifest 需要重新签名
- `plugins[].path` 兼容两种相对路径：`bin/plugins/...`（相对安装根）和 `plugins/...`（相对 `applicationDirPath`）

### `bin/` 与 `dist/CodePlanet/bin/`（勿维护两套）

| 目录 | 来源 | 用途 |
|------|------|------|
| **`bin/`** | Qt Creator 编译（各 `.pro` → `DESTDIR = $$ROOT/bin`） | 日常开发运行、`deploy_bin.ps1` |
| **`dist/CodePlanet/bin/`** | `deploy_staging.ps1` 从 `bin/` 复制 + windeployqt | **仅** NSIS 编译时打包素材；**可整目录删除**后重建 |

NSIS **不会**在客户机读取 `dist/`；`makensis` 在开发机把 staging 文件嵌入 exe。发版步骤见 `docs/AST审计执行报告/R3-验收与操作手册.md` §3。

### Release 本机验收（R2 起推荐）

1. Qt Creator：**Release** Kit → **构建全部**（覆盖 `bin/*.exe`）
2. 项目根目录：`powershell -NoProfile -ExecutionPolicy Bypass -File scripts\deploy_bin.ps1 -Config Release`（**windeployqt**，须与 exe 同 Debug/Release）
3. 启动：`powershell -File scripts\run_launcher_dev.ps1 -Config Release` 或 `bin\CodePlanetr.exe`

改界面 `tr()` 文案时才需 `update_i18n_ts.bat` → 编辑 `.ts` → `generate_i18n.bat`；普通 C++ 改动 **不必**单独「编译翻译」。

**Runtime 分层**：依赖 `src/logic/`、`src/core/`、`src/services/` 与最小 Qt Widgets；**不含** Ops 编辑器。Ops **`RuntimeWorker`** 不变（F5 调试）。

**core 源文件链接约定（R2）**：凡目标编入 `FunctionDispatcher.cpp`，须同时编入 `MainTaskTimerService.cpp`。`ScriptAnalyzer` 对 timer 的静态检查仅用头文件内联 `isTimerFunction`，Launcher 无需链接 timer 实现。

**多脚本参数槽（R2）**：产线 Runtime 按 yslib `scripts[]` 为每个脚本分配 `scriptId`（从 1 递增）。`RuntimeTaskContext::setScripts` 与 `AstRuntime::setScriptId` 会调用 `ensureSourceParameterSlots(id+1)`，避免 `sourceParameterL.at(id)` 越界。主任务循环脚本中 **`timer_create` 仅应执行一次**（用 `static` 守卫），每 tick 会重入 `main()`。

**头文件约定（R1）**：GUI 使用 `ast::AnalyzeContext` 时须 `#include "ScriptAnalyzer.h"`（勿仅含 `AstCommon.h`）。

**Qt MOC 约定**：含 `Q_OBJECT` 的类，`.h` 须列入 `src/editor/editor.pro` 的 `HEADERS`，否则链接报 LNK2019。

**中间产物**：`editor.pro` / `tests.pro` 的 `MOC_DIR`、`OBJECTS_DIR` 使用 `$$OUT_PWD`，避免影子构建 Debug/Release 共用 moc 导致 LNK2038。改 `.pro` 后重新 qmake 并全量构建；旧版若曾在源码树 `build/moc` 留下产物，请删除后再编。

## 环境

- Qt 5.15+ 或 Qt 6.x（Widgets、Sql、Concurrent 模块）
- C++17 编译器（MSVC / MinGW）
- 使用 Qt Creator 打开根目录 [`CodePlanet.pro`](../CodePlanet.pro)

## 构建

```bash
qmake CodePlanet.pro
# MSVC: nmake   /  MinGW: mingw32-make
```

**MSVC + Qt 5.11 注意**：`editor.pro` 须显式 `QMAKE_CXXFLAGS += /std:c++17`（P5 使用 `std::optional`）；改 `.pro` 后请在 Qt Creator 执行 **重新运行 qmake** 再编译。

**Launcher 头文件约定**：类内嵌套类型（如 `CachedUpdate`、`ApplyResult`）若作为 `static` 成员函数参数/返回值，须在**首次使用之前**完成定义（MSVC 否则报 C3646「未知重写说明符」）；避免重复声明同一成员函数。

输出：`bin/CodeEditor.exe`、`bin/CodePlanetr.exe`、**`bin/CodeRuntime.exe`**，插件在 `bin/plugins/BuiltinFunctions.dll`。

## 目录结构（禁止在旧路径新增代码）

```
src/editor       入口、MainWindow、Application（CodeEditor.exe）
src/runtime      CodeRuntime（产线任务执行 + 状态 UI）
src/launcher     CodePlanetr（启动守门）
src/gui          编辑器与 IDE 控件
src/logic        ControlLogiLib、RuntimeWorker 运行编排
src/core         运行时、类型、参数、图像、函数元数据
src/services     AppPaths、FileService、DatabaseService、PluginManager、DebugLogService
plugins/         Qt 动态库（IFunctionPlugin）
resources/       config/FunctionLib.ini、icons/、resources.qrc
examples/        示例 .ys 脚本
tests/           Qt Test 回归（CodePlanetTests）
scripts/         i18n 脚本、check_layer_includes.ps1、run_tests.ps1、run_all_checks.ps1
```

**勿保留** 已废弃的 `CodePlanetr/` 嵌套副本、`EG_File/`、根目录 `ICO/`、根目录 `FunctionLib.ini` 等旧布局。

## 依赖规则

- `gui` 可依赖 `core/functions`、`services`
- `logic` 依赖 `core/runtime`，**不得**依赖 `gui`
- `core` **不得**依赖 `gui` 或 `logic`
- 插件仅通过 `FunctionCallContext` 访问运行时，不 include GUI 头文件

## 分层检查（P4）

编译期仍使用单 `editor.pro` 目标；分层约束由脚本强制执行：

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File scripts/check_layer_includes.ps1
```

- `src/logic` 不得 `#include` 任何 `gui/` 头文件
- `src/core` 不得 `#include` `gui/` 或 `logic/` 头文件
- 违规时 exit 1 并打印文件路径；CI 或提交前建议运行

## 质量检查（P5）

本项目在 Windows 上使用 **PowerShell + 批处理** 跑分层检查与 Qt Test，无需手动配置 PATH。Agent 与开发者提交前建议执行。

### 一键检查（推荐）

在**项目根目录**打开 PowerShell，执行：

```powershell
cd E:\HuaweiMoveData\Users\MateBookXPro\Desktop\CodePlanetr
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\run_all_checks.ps1
```

成功标志：最后一行输出 **`All checks passed.`**，进程 exit code 为 **0**。

脚本顺序：

1. `scripts/check_layer_includes.ps1` — 分层 `#include` 违规扫描  
2. `scripts/run_tests.ps1` → 内部调用 `scripts/build_tests.bat` — 编译并运行 `bin\CodePlanetTests.exe`

### 本机工具链路径（已写入脚本默认值）

| 组件 | 路径 |
|------|------|
| Qt 5.11.1（msvc2017_64） | `D:\Qt\Qt5.11.1\5.11.1\msvc2017_64` |
| qmake | 上述目录下 `bin\qmake.exe` |
| Visual Studio 2017 | `D:\Visual Studio\2017\Community` |
| vcvars | `...\VC\Auxiliary\Build\vcvars64.bat` |

若 Qt 安装位置变更，可临时覆盖：

```powershell
$env:QTDIR = "D:\Qt\Qt5.11.1\5.11.1\msvc2017_64"
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\run_all_checks.ps1
```

或编辑 `scripts/build_tests.bat` / `scripts/run_tests.ps1` 中的候选路径。

### 分项运行

| 目的 | 命令 |
|------|------|
| 仅分层检查 | `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\check_layer_includes.ps1` |
| 仅单元测试 | `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\run_tests.ps1` |
| 仅测试（不经过 PowerShell 包装） | 双击或 `cmd /c scripts\build_tests.bat` |

测试产物：`bin\CodePlanetTests.exe`、`bin\testresults.xml`（XML 报告）。

### 自动化未覆盖项（需 GUI 手动）

- `examples\ast_debug.ys`：Ctrl+B → F5 / F6 / F10  
- `examples\expr_p1_test.ys`：F5  
- 参数面板运行中修改 / 删除变量  

详见 `docs/AST审计执行报告/P5-阶段交付报告.md` §6。

## 调试日志（P4 + P5）

- 统一出口：`ControlLogiLib::publishDebugMessage` → `DebugLogService::log(lineNo, msg)` + `SendDebugMessage` 信号 → UI
- 插件：`FunctionCallContext::sendDebugMessage` → `FunctionDispatcher::setDebugMessageSink`（P5）
- 全局 `SendDebugMessage` 回调（`parameterparser.h`）已 **deprecated**，仅经 `Init_DebugMessage` 注册后转发
- `RuntimeWorker::debugMessage` 在 AST 解析失败时从 `(at line:col)` 提取行号写入 DB

## 最近文件（P4）

- `DatabaseService::addRecentFile` / `recentFiles` / `removeRecentFile`
- UI：文件菜单 →「最近打开」，读取 SQLite `recent_files` 表

## 命名与 API 约定

| 约定 | 说明 |
|------|------|
| 类名 | `PascalCase`，与文件名一致 |
| 信号槽 | 动词开头，如 `RunButtonClick`、`SendDebugMessage` |
| 路径 | **禁止**硬编码绝对路径；使用 `AppPaths::` |
| 读写文件 | `FileService::readTextFile` / `writeTextFile`（UTF-8） |
| 脚本 | 扩展名 `.ys`、库清单 `.yslib` |

P4 已清理拼写错误 API（`src/` 无旧名残留）：

| 旧名 | 处置 / 当前 API |
|------|-----------------|
| `HeightLightError` | 移除 → `CodeEditor::applyBuildDiagnostics(BuildResult)` 绘制波浪线 |
| `SendBulidMessage` | 移除 → `CodeEditorWidget::handleBuildResult(BuildResult)` 填充问题面板 |
| `ParaUpdata` | 重命名 → `ControlLogiLib::updateParameters()` |

Build 信号链路：`CodeEditor::buildFinished(ast::BuildResult)` → `CodeEditorWidget::handleBuildResult`（connect 在 `codeeditorwidget2.cpp`）。

## FunctionCatalog（P2）

- `FunctionCatalog` 为 `FunctionLib.ini` 唯一解析入口（`FileService` 读文件）
- 控制流/高亮/补全关键字在 INI 的 `[ControlFlow]`、`[Highlight]` 等段配置，禁止在编辑器源码重复硬编码
- `Application` 启动加载 Catalog 后校验插件函数名

## 编辑器模块（P3）

`src/gui/editor/` 已拆分，`codeeditor.cpp` 仅保留核心编辑逻辑（≤800 行）：

| 文件 | 职责 |
|------|------|
| `codeeditor.cpp` | 构造、搜索高亮、键盘/鼠标核心、调试箭头等 |
| `buildprogram.cpp` | Ctrl+B 入口，调用 `ScriptAnalyzer` 并绘制诊断波浪线 |
| `ScriptAnalyzer.cpp` | 静态检查引擎（词法/语法/语义，见下表） |
| `LineNumberArea.cpp` | 行号 gutter 绘制与断点/折叠点击 |
| `EditorFoldManager.cpp` | 折叠区域解析与 toggle |
| `CppSyntaxHighlighter.cpp` | 语法高亮（与 `YsHighlightScanner` 并存） |
| `CustomScrollBar.cpp` | 搜索匹配滚动条标记 |
| `CodeEditorCompletion.cpp` | 函数库补全 |
| `EditorBracketHighlight.cpp` | 括号/参数高亮 |
| `CodeEditorIndent.cpp` | 自动缩进 |

### Ctrl+B 静态检查（ScriptAnalyzer）

| 阶段 | 代码前缀 | 说明 |
|------|----------|------|
| 预处理 | YS001 | 字符串引号未成对 |
| 词法 | YS1xx | Lexer 错误 |
| 语法 | YS2xx | Parser 错误 |
| 语义 | YS301 | 作用域内重复变量（警告） |
| 语义 | YS302 | 重复 function 定义（错误） |
| 语义 | YS303 | 未定义函数/指令（错误） |
| 语义 | YS304/305 | break/continue 位置非法 |
| 语义 | YS306 | 函数参数名重复 |
| 语义 | YS307 | 库函数参数个数不匹配（警告） |

**运行策略**（与 VS Code/MSVC 一致）：**错误**阻止 F5/F6；**警告**不阻止。F5/F6 预检为静默模式，仅在有错误时切到「问题」面板；Ctrl+B 始终打开问题面板。

Debug 日志：`ReceiverDebugMessage` 直接 `m_debugEdit->append`，**无** 100ms `CycTimer`。

## 新增脚本函数

1. 编辑 `resources/config/FunctionLib.ini`（CSV 函数行 + 可选 `[Plugin]`）
   - **插件函数也须写 CSV 行**（含 `para_count`），否则 Ctrl+B 的 YS307 无法校验参数个数
2. 在插件中实现 `IFunctionPlugin::execute`（或扩展 `BuiltinFunctions`）
3. `supportedFunctions()` 注册函数名（匹配时不区分大小写）
4. 编译插件，部署到 `bin/plugins/`
5. 用 `examples/` 脚本验证

## Git

- 不提交 `*.pro.user`、`build/`、`bin/`
- 提交说明写清「为什么」

## 编码

- 源码与脚本默认 **UTF-8**
- 旧 GBK 文件由 `FileService` 自动回退解码
- MSVC：`editor.pro` 启用 `/utf-8`；勿使用 `#pragma execution_character_set`；固定中文用 `QString::fromUtf8(u8"...")`

## 界面国际化

- `Application::setLocale` + `tr()` + 各控件 `retranslateUi()`
- 维护流程见 [国际化维护说明.md](国际化维护说明.md)
- 脚本：`scripts/update_i18n_ts.bat`、`scripts/generate_i18n.bat`

## 维护 `.cursor` 与 `docs`

修改架构或上述约定时，请同时更新：

- `.cursor/开发指南.md` 与 `.cursor/rules/*.mdc`
- `docs/` 下对应文档

## 相关文档

- [架构说明.md](架构说明.md)
- [插件开发指南.md](插件开发指南.md)
- [国际化维护说明.md](国际化维护说明.md)
- [分析报告/项目分析报告.md](分析报告/项目分析报告.md)
- [维护文件/AST逻辑架构.md](维护文件/AST逻辑架构.md)
- [.cursor/README.md](../.cursor/README.md)
