@guandata/guanetl 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,54 @@
1
+ # Changelog
2
+
3
+ ## @guandata/guanetl 0.1.11 - 2026-05-28
4
+
5
+ - 优化试用账号场景下的运行上下文识别,提升命令执行记录与问题排查的一致性。
6
+ - 重新构建发布产物,纳入共享运行上下文处理更新。
7
+
8
+ ## @guandata/guanetl 0.1.10 - 2026-05-26
9
+
10
+ - 构建流程会将 npm 包版本注入到 `guanetl` 二进制,确保运行时版本信息与发布包一致。
11
+ - 改进 `preview` 命令的 profile 解析与调用上下文处理,提升多环境执行时的稳定性。
12
+ - 补充 CLI 入口、构建脚本和 `preview` 行为测试,提升发布产物可靠性。
13
+
14
+ ## @guandata/guanetl 0.1.9 - 2026-05-20
15
+
16
+ - 新增无 `-skill` 后缀的 `guanetl` npm 包与 CLI 入口,提供 ETL 创建、编辑、导出、预览、保存、运行、调度、任务状态、删除等能力。
17
+ - 补充 ETL 工作区、脚本运行、结构校验与 lint 能力,并随包提供使用文档和参考资料。
18
+ - 改进 npm 打包与安装流程,补齐 Linux 构建目标、发布前检查和 skill 同步逻辑。
19
+ - 修复 Windows 终端启动 Go 二进制时的中文编码问题,运行前自动切换到 UTF-8。
20
+ ## @guandata/guanetl 0.1.8 - 2026-05-18
21
+
22
+ - 新增 `delete` 命令,支持安全删除 ETL,并提供 cascade dry-run 预检查。
23
+ - 新增 `mkdir-pair` 命令,可同时创建 ETL 和 DATA_SET 目录。
24
+ - 新增 `lint` 命令,用于静态检查导出的 ETL 定义。
25
+ - 优化 merge 行为,保留 base 节点的 opaque metadata。
26
+ - 补充 preview 0 行、save/run 等任务排查说明,并改进工作流相关共享能力。
27
+
28
+ ## @guandata/guanetl 0.1.7 - 2026-05-14
29
+
30
+ - 调度配置补充 `triggerType`,并支持上游触发类型的 ETL 调度场景。
31
+ - 优化 CLI 运行时错误展示,减少误导性的 usage 输出。
32
+ - 更新 ETL 生成飞轮式工作流说明,改进复杂 ETL 生成与验证的使用指引。
33
+
34
+ ## @guandata/guanetl 0.1.6 - 2026-05-12
35
+
36
+ - 修复请求体 stdin、环境变量命名和 URL 编码相关问题,提升 ETL 保存、执行等 API 调用稳定性。
37
+ - 更新发布版本,纳入跨 skill API 调用解耦后的实现。
38
+
39
+ ## @guandata/guanetl 0.1.5 - 2026-04-24
40
+
41
+ - 优化 AI 使用说明,增加速查表、错误映射、migrationHint 和创建校验提示。
42
+ - 统一添加 `version` 命令和 `prepublishOnly` 自动构建流程,改善 npm 发布与本地验证体验。
43
+ - 优化 Skill description 与同步提示文本,提升 AI 调用时的命中准确性。
44
+
45
+ ## @guandata/guanetl 0.1.4 - 2026-04-10
46
+
47
+ - 发布版本同步,无面向使用者的实质功能变化。
48
+
49
+ ## @guandata/guanetl 0.1.3 - 2026-04-07
50
+
51
+ - 新增 `BasicOutputDatasetInDir` 支持,可指定输出数据集目录。
52
+ - 降低 `save` 命令错误输出噪音,超长异常会写入文件,便于排查。
53
+ - npm 安装脚本改用 `spawnSync` + shell,修复 Windows 下 `install-skill` 的 ENOENT 问题。
54
+ - 同步 evals 目录到安装包,确保示例和验证材料随包分发。
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # guanetl
2
+
3
+ 观远 ETL 本地开发工具,支持拉取、编辑、导出、预览、保存 ETL。
4
+
5
+ ## 本地安装(开发/测试阶段)
6
+
7
+ ```bash
8
+ # 编译所有平台 binary(需要 Go 工具链)
9
+ npm run build
10
+
11
+ # 安装到本地 node global
12
+ npm link
13
+ ```
14
+
15
+ 安装后即可在终端使用:
16
+
17
+ ```bash
18
+ guanetl edit <etl_id> --dir <work_dir>
19
+ guanetl export --dir <work_dir>
20
+ guanetl preview <node_id> --dir <work_dir>
21
+ guanetl save --dir <work_dir>
22
+ ```
23
+
24
+ 说明:npm 包名为 `@guandata/guanetl`,用户侧 CLI 命令统一为 `guanetl`。
25
+
26
+ 标准 ETL 写入闭环:`create/edit → export → preview → save → run --wait`。
27
+
28
+ > **触发成功 ≠ ETL 执行成功**:`run` 返回"执行已触发"仅表示后端接受了请求。使用 `run --wait` 等待终态,FAILED 时会展示真实错误消息。
29
+
30
+ 也可以为 AI Coding Assistant 安装 Skill:
31
+
32
+ ```bash
33
+ guanetl install-skill
34
+ ```
35
+
36
+ ## 版本更新
37
+
38
+ ### 0.1.11
39
+
40
+ - 优化试用账号场景下的运行上下文识别,提升命令执行记录与问题排查的一致性。
41
+ - 重新构建发布产物,纳入共享运行上下文处理更新。
42
+
43
+ ## 卸载
44
+
45
+ ```bash
46
+ npm unlink -g @guandata/guanetl
47
+ ```
48
+
49
+ ## 支持平台
50
+
51
+ - macOS (Apple Silicon / Intel)
52
+ - Windows (x64)
53
+
54
+ ## 开发
55
+
56
+ ```bash
57
+ # 编译所有平台 binary(需要 Go 工具链)
58
+ npm run build
59
+
60
+ # 发布到内部 Nexus npm 仓库
61
+ npm publish
62
+ ```
package/bin/run.js ADDED
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+
3
+ "use strict";
4
+
5
+ const { execFileSync, execSync, spawnSync } = require("child_process");
6
+ const path = require("path");
7
+ const fs = require("fs");
8
+
9
+ const PLATFORM_MAP = {
10
+ "darwin-arm64": "guanetl-darwin-arm64",
11
+ "darwin-x64": "guanetl-darwin-x64",
12
+ "linux-x64": "guanetl-linux-x64",
13
+ "linux-arm64": "guanetl-linux-arm64",
14
+ "win32-x64": "guanetl-win32-x64.exe",
15
+ };
16
+
17
+ function getBinaryPath() {
18
+ const key = `${process.platform}-${process.arch}`;
19
+ const binaryName = PLATFORM_MAP[key];
20
+ if (!binaryName) {
21
+ console.error(
22
+ `Unsupported platform: ${key}\nSupported: ${Object.keys(PLATFORM_MAP).join(", ")}`
23
+ );
24
+ process.exit(1);
25
+ }
26
+
27
+ const binaryPath = path.join(__dirname, "..", "binaries", binaryName);
28
+ if (!fs.existsSync(binaryPath)) {
29
+ console.error(
30
+ `Binary not found: ${binaryPath}\nRun 'npm run build' to compile binaries.`
31
+ );
32
+ process.exit(1);
33
+ }
34
+
35
+ return binaryPath;
36
+ }
37
+
38
+ if (process.argv[2] === "version" && process.argv.length === 3) {
39
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
40
+ console.log(pkg.version);
41
+ process.exit(0);
42
+ }
43
+
44
+ if (process.argv[2] === "install-skill") {
45
+ const pkgRoot = path.join(__dirname, "..");
46
+ const extraArgs = process.argv.slice(3);
47
+ const args = [
48
+ "skills",
49
+ "add",
50
+ pkgRoot,
51
+ "--skill",
52
+ "guanetl",
53
+ "-g",
54
+ "-y",
55
+ ...extraArgs,
56
+ ];
57
+ console.log("Installing guanetl to AI coding assistants...");
58
+ const result = spawnSync("npx", args, { stdio: "inherit", env: process.env, shell: true });
59
+ if (result.error) throw result.error;
60
+ process.exit(result.status || 0);
61
+ }
62
+
63
+ const binary = getBinaryPath();
64
+
65
+ try {
66
+ if (process.platform !== "win32") {
67
+ fs.chmodSync(binary, 0o755);
68
+ }
69
+ } catch (_) {
70
+ // chmod may fail in read-only environments
71
+ }
72
+
73
+ if (process.platform === "win32") {
74
+ try { execSync("chcp 65001", { stdio: "ignore" }); } catch (_) {}
75
+ }
76
+
77
+ try {
78
+ execFileSync(binary, process.argv.slice(2), {
79
+ stdio: "inherit",
80
+ env: { ...process.env, GUANETL_PROG_NAME: "guanetl" },
81
+ });
82
+ } catch (err) {
83
+ if (err.status != null) {
84
+ process.exit(err.status);
85
+ }
86
+ throw err;
87
+ }
Binary file
Binary file
Binary file
Binary file
Binary file
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "@guandata/guanetl",
3
+ "version": "0.1.11",
4
+ "description": "观远 ETL 本地开发工具 - 拉取、编辑、导出、预览、保存 ETL",
5
+ "bin": {
6
+ "guanetl": "bin/run.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "binaries/",
11
+ "skills/",
12
+ "CHANGELOG.md",
13
+ "README.md"
14
+ ],
15
+ "keywords": [
16
+ "guandata",
17
+ "etl",
18
+ "cli",
19
+ "agent-skill"
20
+ ],
21
+ "license": "UNLICENSED",
22
+ "os": [
23
+ "darwin",
24
+ "linux",
25
+ "win32"
26
+ ],
27
+ "engines": {
28
+ "node": ">=14"
29
+ },
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }
@@ -0,0 +1,324 @@
1
+ ---
2
+ name: guanetl
3
+ description: 当用户要新建、修改、调试、定时调度、立即执行、取消执行、保存发布观远 BI / Guandata 的 ETL,或给出 ETL ID、dataFlowId、etl.go、meta.json、SQL 节点、dsId、节点报错时,优先使用这个 skill。即使用户只说"帮我改一下这个 ETL""这个 ETL 导出失败""新建一个观远 ETL""把这个 ETL 配成每天 2 点跑""取消正在跑的 ETL""保存后立即执行并等完成",也要主动使用。它把线上 ETL 拉到本地工作目录,约束 AI 只编辑 etl/ 下文件,并完成 create/edit → export → preview → save → run/schedule 闭环。查 ETL/数据集元信息走 guancli;数据源/数据集 CRUD 走 guands。
4
+ compatibility: "Requires Node.js 14+. Install via npm link (local) or npm install -g @guandata/guanetl (from internal Nexus registry). CLI command: guanetl."
5
+ ---
6
+
7
+ # guanetl
8
+
9
+ 这是一个执行型 skill,不是只读分析 skill。
10
+
11
+ - `guancli` 负责找 ETL / 数据集 / 字段上下文。
12
+ - `guanetl` 负责把 ETL 拉到本地、修改 `etl/`、再完成 `export -> preview -> save`。
13
+
14
+ ## 何时使用
15
+
16
+ 遇到这些任务就用:
17
+
18
+ - 新建观远 BI ETL。
19
+ - 修改已有 ETL 的节点、SQL、字段、输出。
20
+ - 用户给了 ETL ID / dataFlowId,希望直接拉下来改。
21
+ - 用户保存 ETL 后希望立即触发执行并等待完成。
22
+ - 用户希望配置 ETL 定时执行调度。
23
+ - 用户希望取消正在执行的 ETL 任务。
24
+ - 用户遇到 `export`、预览、保存失败,希望定位并修复。
25
+ - 用户提到 `etl.go`、`meta.json`、节点 ID、SQL 节点、输入输出数据集。
26
+
27
+ 只查信息时不要单独用它:
28
+
29
+ - ETL 清单、目录树、节点详情、血缘、数据集结构、数据集预览,优先用 `guancli`。
30
+
31
+ ## 固定工作方式
32
+
33
+ ### 0. 先确认前置条件
34
+
35
+ - 确认底层 CLI 认证正常:guancli 需先执行 `guancli auth use <profile>`,guancli-lite 需设置 `GUANCLI_BASE_URL`/`GUANCLI_TOKEN` 环境变量。
36
+ - 如果用户指定的资源(目录、数据集、ETL)搜索不到,先用 `guancli auth list` 列出所有可用环境,提示用户确认是否需要切换环境。
37
+
38
+ ### Artifact contract(本地可测契约)
39
+
40
+ `guanetl` 是源文件驱动的 ETL 构建 harness。工作目录中只有 `etl/` 是可编辑事实源;导入、导出、保存过程产生的 JSON 都是系统状态或派生产物。
41
+
42
+ | 路径/产物 | 类型 | 规则 |
43
+ |---|---|---|
44
+ | `etl/etl.go` | 可编辑事实源 | 定义 `DefineETL()`,只在这里表达节点、依赖、输出意图 |
45
+ | `etl/*.sql` | 可编辑事实源 | 仅供 `BasicSqlScript(..., ReadSQLFile(...))` 引用,新增 SQL 节点必须同步新增文件 |
46
+ | `etl/meta.json` | 可编辑事实源(谨慎) | 只有需要保留导入 ETL 的额外 metadata 时才改 |
47
+ | `_guanetl_state.json` | 系统状态 | 不手改;由 create/edit/export/preview 维护 |
48
+ | `_input.json` / `_base_etl.json` | 服务端快照 | 不手改;保存时会重新拉取线上 edit base 并合并 |
49
+ | `_exported.json` | 派生产物 | 不手改;由 `export` 从 `etl/` 重新生成 |
50
+
51
+ 本地可测 gate:
52
+
53
+ 1. `go test ./...` 覆盖 workspace、merge、runner helper、request/merge 逻辑。
54
+ 2. 对 fixture 工作目录执行 `guanetl export --dir <fixture>`,比较 `_exported.json` 的关键节点、字段、SQL 文件引用和输出数据集配置。
55
+ 3. `preview/save/run/schedule` 属于线上闭环:只有在认证、目标 ETL 和数据集明确后执行,并在汇报中说明是否实际跑到这些步骤。
56
+
57
+ 失败修复顺序固定为:读失败输出 -> 判断是源代码/SQL/节点配置/线上任务问题 -> 修改 `etl/` 中最小责任文件 -> 重跑失败命令 -> 再重跑依赖的后续验证。不要为了修一个 export/preview 错误去重写整个 ETL。
58
+
59
+ ### 开发态生成飞轮
60
+
61
+ 当任务目标是“评估自然语言生成 ETL 的质量 / benchmark / 失败复跑 / 回灌规则”,不要把普通单次编辑流程硬扩成评估系统。使用仓库根目录的 `.skill/etl-generation-flywheel/`:运行证据放在 `etl_runs/<run-id>`,真正的 ETL 工作目录放在 `etl_workspaces/<run-id>`,再按 `export -> preview -> data_fit -> save/run -> final_judgment` 闭环推进。
62
+
63
+ ### 1. 先分场景
64
+
65
+ - `编辑已有 ETL`
66
+ - `新建 ETL`
67
+ - `修复失败`
68
+
69
+ ### 2. 缺上下文先查,不要猜
70
+
71
+ 缺 ETL ID / dsId / 字段时,先用:
72
+
73
+ ```bash
74
+ guancli auth status
75
+ guancli etl search <关键词>
76
+ guancli etl get <etl_id>
77
+ guancli ds search <关键词>
78
+ guancli ds get <ds_id>
79
+ guancli ds preview <ds_id> --limit 20
80
+ ```
81
+
82
+ ### 3. 真正改代码前先读这些
83
+
84
+ 至少先读:
85
+
86
+ - `etl/etl.go`
87
+ - `etl/*.sql`
88
+ - `references/ETL_AI_DEVELOP.md`
89
+
90
+ 必要时再读:
91
+
92
+ - `etl/meta.json`
93
+ - `guancli etl get <etl_id>` 的线上结构
94
+
95
+ ### 4. 只改 `etl/`
96
+
97
+ 只允许修改这些文件:
98
+
99
+ - `etl/etl.go`
100
+ - `etl/meta.json`
101
+ - `etl/*.sql`
102
+
103
+ 不要手改这些系统文件:
104
+
105
+ - `_guanetl_state.json`
106
+ - `_base_etl.json`
107
+ - `_input.json`
108
+ - `_exported.json`
109
+
110
+ ### 5. 改完永远按这个顺序
111
+
112
+ 1. `export`
113
+ 2. 需要看结果时再 `preview`
114
+ 3. 确认无误后才 `save`
115
+ 4. 需要立即执行时用 `run`
116
+
117
+ 如果 `export` 没过,不要直接 `preview` 或 `save`。
118
+
119
+ ## 三种场景的默认流程
120
+
121
+ ### 编辑已有 ETL
122
+
123
+ 1. 如 ETL ID 不明确,先用 `guancli` 查。
124
+ 2. 执行:
125
+
126
+ ```bash
127
+ guanetl edit <etl_id> --dir <work_dir>
128
+ ```
129
+
130
+ 3. 阅读 `etl/` 下文件并修改。
131
+ 4. 执行:
132
+
133
+ ```bash
134
+ guanetl export --dir <work_dir>
135
+ guanetl preview <node_id> --dir <work_dir>
136
+ guanetl save --dir <work_dir>
137
+ guanetl run <etl_id> --wait # 可选:保存后触发执行并等待完成
138
+ ```
139
+
140
+ **再次修改已 save 过的 ETL 时**:即使本地还保留着上次的工作目录,也必须重新执行 `guanetl edit <etl_id> --dir <新目录>` 拉取服务端最新版本,不要在旧工作目录上直接改了再 save。原因:服务端版本可能已经变化,直接复用旧 base 会导致合并冲突(如"输出数据集目录中存在同名文件")。
141
+
142
+ ### 新建 ETL
143
+
144
+ 1. 先用 `guancli` 查输入数据集和字段。
145
+ 2. 如需在指定子目录下创建,先查目录树并按需创建子目录:
146
+
147
+ ```bash
148
+ guancli etl tree
149
+ guanetl mkdir "ODS" --parent <parent_dir_id>
150
+ ```
151
+
152
+ 3. 执行:
153
+
154
+ ```bash
155
+ guanetl create --name "ETL名称" --dir <work_dir>
156
+ guanetl create --name "ETL名称" --dir <work_dir> --parent-dir <dir_id>
157
+ ```
158
+
159
+ 4. 在 `etl/` 中实现 ETL。
160
+ 5. 再执行 `export -> preview -> save`。
161
+ 6. 需要立即执行时:`run <etl_id> --wait`。
162
+
163
+ ### 修复失败
164
+
165
+ 1. 先确认失败发生在 `export`、`preview` 还是 `save`。
166
+ 2. 没有工作目录时,先 `edit` 或 `create` 恢复上下文。
167
+ 3. 只做最小必要修改。
168
+ 4. 从失败步骤往后重跑,不要整条链路乱重做。
169
+
170
+ ## 保存与执行说明
171
+
172
+ ### save 的工作机制
173
+
174
+ `save` 不是简单把本地 JSON 上传。它的内部流程是:
175
+
176
+ 1. 读取本地 `_exported.json`(由 `export` 生成)
177
+ 2. 从 BI 服务端拉取当前版本的 ETL 作为合并基准(edit API)
178
+ 3. 将本地修改合并到服务端版本
179
+ 4. 调用 direct-save API 写入
180
+
181
+ 用户不需要手写服务端保存 payload。只要 `etl/` 修改正确、`export` 通过,`save` 就能完成服务端保存。
182
+
183
+ ### run --wait 与任务状态
184
+
185
+ > **触发成功 ≠ ETL 执行成功**。`run` 返回 `✓ ETL 执行已触发` 仅表示后端接受了执行请求,ETL 可能在运行中失败。
186
+
187
+ - 不加 `--wait` 时,`run` 只触发并返回 `taskId`。
188
+ - 加 `--wait` 后,CLI 会轮询任务状态直到终态(FINISHED / FAILED / CANCELED),FAILED 时会展示真实错误消息。
189
+ - 如果不加 `--wait` 后想查状态:`task status <taskId>` 或 `task wait <taskId>`。
190
+
191
+ ### 排查执行失败
192
+
193
+ ```bash
194
+ # 1. 触发执行并等待完成
195
+ guanetl run <etl_id> --wait
196
+
197
+ # 2. 如果已经触发但不知道结果,查任务状态
198
+ guanetl task status <taskId>
199
+
200
+ # 3. 等待任务完成并查看真实错误
201
+ guanetl task wait <taskId>
202
+
203
+ # 4. 用 guancli 查看任务详情(包含更多诊断信息)
204
+ guancli task detail <taskId>
205
+ ```
206
+
207
+ 常见失败原因:
208
+ - 输入数据集权限不足(有读权限但缺少运行权限)
209
+ - SQL 语法错误(本地 preview 通过但全量数据触发不同的执行路径)
210
+ - 输出数据集目录冲突
211
+ - 上游数据集数据为空或 schema 变更
212
+
213
+ ### preview 返回 0 行
214
+
215
+ preview 成功但行数为 0 是合法结果,**不一定代表有错误**。排查顺序:
216
+ 1. 确认输入数据集本身有数据(用 `guancli ds preview <dsId> --limit 5`)
217
+ 2. 如果节点有 FILTER_ROWS 或 WHERE 条件,临时简化筛选条件再 preview
218
+
219
+ ## 调度配置(Schedule)
220
+
221
+ 配置 ETL 的触发方式:定时执行(CRON)或上游数据集更新后触发。默认不开启,必须显式调用 `schedule` 命令。
222
+
223
+ ### 触发模式(--trigger)
224
+
225
+ | trigger | API triggerType | 说明 |
226
+ |---|---|---|
227
+ | `cron`(默认) | `CRON` | 按配置的 cron 定时执行 |
228
+ | `upstream` | `AFTER_REFRESH` | 任一上游数据集更新完成后触发 |
229
+ | `upstream-all` | `AFTER_ALL_REFRESH` | 全部上游数据集更新完成后触发 |
230
+
231
+ ### 定时触发示例
232
+
233
+ ```bash
234
+ guanetl schedule <etl_id> --cron-type daily --hour 3 --minute 0 # 每天 03:00
235
+ guanetl schedule <etl_id> --cron-type hourly --interval 6 # 每 6 小时
236
+ guanetl schedule <etl_id> --cron-type minute --interval 30 # 每 30 分钟
237
+ guanetl schedule <etl_id> --cron-type weekly --day-of-week MON --hour 5 # 每周一 05:00
238
+ guanetl schedule <etl_id> --cron-type monthly --day-of-month 1 --hour 3 # 每月 1 号 03:00
239
+ guanetl schedule <etl_id> --disable # 关闭调度
240
+ ```
241
+
242
+ ### 上游触发示例
243
+
244
+ ```bash
245
+ # 任一上游数据集更新后执行
246
+ guanetl schedule <etl_id> --trigger upstream --inputs <dsId1>,<dsId2>
247
+
248
+ # 全部上游数据集更新后执行
249
+ guanetl schedule <etl_id> --trigger upstream-all --inputs <dsId1>,<dsId2>
250
+ ```
251
+
252
+ ### 定时调度类型说明
253
+
254
+ | cron-type | 参数 | 说明 |
255
+ |---|---|---|
256
+ | `daily` | `--hour`, `--minute` | 每天指定时间执行 |
257
+ | `hourly` | `--interval`, `--minute` | 每隔 N 小时执行 |
258
+ | `minute` | `--interval` | 每隔 N 分钟执行(最小 5) |
259
+ | `weekly` | `--day-of-week`, `--hour`, `--minute` | 每周指定日期时间执行 |
260
+ | `monthly` | `--day-of-month`, `--hour`, `--minute` | 每月指定日期时间执行 |
261
+
262
+ - `--day-of-week`: `MON`, `TUE`, `WED`, `THU`, `FRI`, `SAT`, `SUN`
263
+ - `--day-of-month`: `1`-`31` 或 `L`(最后一天)
264
+
265
+ **调度频率限制**:定时模式最高 5 分钟一次,低于 1 小时会输出性能警告。
266
+
267
+ ## 任务管理(Task)
268
+
269
+ - `run` 不加 `--wait` 时只返回 `taskId`,后续可用 `task status <taskId>` 查询状态,或用 `task wait <taskId>` 等待完成。
270
+ - 并行触发多个 ETL 时:先 `run etl1`、`run etl2`(不加 --wait),记录 taskId,再 `task wait <t1> <t2>` 统一等待。
271
+ - 需要停止正在执行的任务时,用 `task cancel <taskId>`。
272
+
273
+ ```bash
274
+ guanetl task status <taskId> # 查询任务状态
275
+ guanetl task wait <taskId> # 等待任务完成
276
+ guanetl task wait <t1> <t2> --timeout 600 # 等待多个任务
277
+ guanetl task cancel <taskId> # 取消执行中的任务
278
+ guanetl task cancel <t1> <t2> # 批量取消多个任务
279
+ ```
280
+
281
+ ## 硬约束
282
+
283
+ - 不要改 `DefineETL()` 函数签名。
284
+ - 节点 ID 必须唯一,且是 `id_数字`。
285
+ - 节点顺序必须满足依赖顺序。
286
+ - 能用专用节点时,不要先写 `SQL_SCRIPT`。
287
+ - 等值 JOIN 优先 `JOIN_DATA`,不要用 SQL 模拟。
288
+ - 输入字段 schema 优先维护在 `BasicInputDataset(..., []Field{...})` 中。
289
+ - 新增 SQL 节点时,要同步新增对应 `.sql` 文件。
290
+ - 对 `edit` 导入出来的 ETL,优先局部修改,不要为“重构”大面积推翻。
291
+ - **文件访问边界**:只允许访问以下路径,不要主动探索或读取用户未明确授权的其他目录:
292
+ - 当前工作目录(`--dir` 指定的目录或 cwd)
293
+ - 本 skill 的 `references/` 目录(如 `ETL_AI_DEVELOP.md`)
294
+ - `guancli` / `guanetl` CLI 工具的输出
295
+ - 用户在对话中明确提到或授权的路径
296
+ - 禁止为了"查找参考代码""寻找类似实现"而主动扫描用户机器上的其他项目目录,这会引起安全顾虑。
297
+
298
+ 具体函数、枚举值和推荐骨架,都看 `references/ETL_AI_DEVELOP.md`。
299
+
300
+ ## ETL 节点类型速查(详细用法见 references/ETL_AI_DEVELOP.md)
301
+
302
+ 优先用专用节点,最后才用 SQL:
303
+
304
+ | 节点函数 | 用途 | 何时使用 | 关键约束 |
305
+ |---|---|---|---|
306
+ | `BasicInputDataset` | 引入数据集 | 每个 ETL 至少一个 | 必须提供 `[]Field` schema |
307
+ | `BasicOutputDataset` | 输出数据集 | 每个 ETL 至少一个(叶子节点) | `DefineETL()` 返回值 |
308
+ | `BasicSelectColumns` | 选列/重命名 | 只需要部分字段或改名 | - |
309
+ | `BasicFilterRows` | 行筛选 | 按条件过滤数据 | combineType: `AND`/`OR` |
310
+ | `BasicJoinData` | 等值 JOIN | 两表关联 | **优先用这个,不要写 SQL JOIN** |
311
+ | `BasicGroupBy` | 分组聚合 | SUM/COUNT/AVG 等 | 聚合类型: `SUM`/`COUNT`/`AVG`/`MIN`/`MAX` |
312
+ | `BasicCalculator` | 新增计算列 | 公式计算新字段 | - |
313
+ | `BasicRemoveDuplicates` | 去重 | 按指定列去重 | - |
314
+ | `BasicAppendRows` | 上下合并 | 多表 UNION | unionType: `INCLUDE_SHARED`/`INCLUDE_ALL` |
315
+ | `BasicSqlScript` | 自定义 SQL | 上述节点无法满足时 | SQL 放独立 `.sql` 文件,上游用 `input1`/`input2` 引用 |
316
+
317
+ ## 最后怎么向用户汇报
318
+
319
+ 至少说明这四件事:
320
+
321
+ - 处理的是哪个 ETL / 工作目录。
322
+ - 改了哪些 `etl/` 文件。
323
+ - `export`、`preview`、`save`、`run` 哪些已经成功。
324
+ - 如果没闭环,卡在哪一步,下一步最小动作是什么。
@@ -0,0 +1,41 @@
1
+ {
2
+ "skill_name": "guanetl",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "帮我修改一个观远 BI 的 ETL。ETL ID 是 etl_abcd1234,我要把订单表和用户表按 user_id 做等值关联,补出用户等级,再按订单日期筛选最近 30 天,最后输出到一个新的结果数据集。你直接拉下来改,并告诉我应该预览哪个节点。",
7
+ "expected_output": "代理应识别为编辑已有 ETL 场景,先获取并导入 ETL,再基于专用节点优先的规则修改 etl/ 下文件,执行 export,并建议或执行 preview 针对最终输出节点或关键中间节点。",
8
+ "files": [],
9
+ "expectations": [
10
+ "使用 guanetl 而不是只停留在 guancli 的只读分析",
11
+ "先执行 edit 获取本地工作目录,再修改 etl/ 文件",
12
+ "对等值关联优先选择 JOIN_DATA,而不是直接写 SQL_SCRIPT",
13
+ "在改动后执行 export 验证,并给出可预览的节点建议"
14
+ ]
15
+ },
16
+ {
17
+ "id": 2,
18
+ "prompt": "新建一个观远 ETL,名字叫“门店销售日报汇总”。输入来自两个数据集:一个是门店销量明细,一个是门店维表。我现在没有 dsId,你先帮我找,再把这个 ETL 建出来,最后输出每个门店每天的销售额和订单数。",
19
+ "expected_output": "代理应识别为新建 ETL 场景,先联动 guancli 查找合适数据集和字段,再使用 create 初始化工作目录,按照 ETL_AI_DEVELOP 约束编写 etl.go,最后进入 export、preview、save 流程。",
20
+ "files": [],
21
+ "expectations": [
22
+ "先通过 guancli 查找 dsId 和字段,而不是直接假设输入数据集",
23
+ "执行 create 创建新的工作目录",
24
+ "在真正编写节点前参考 ETL_AI_DEVELOP.md",
25
+ "输出中包含 export、preview、save 的后续闭环步骤"
26
+ ]
27
+ },
28
+ {
29
+ "id": 3,
30
+ "prompt": "我这个观远 ETL 本地 export 失败了,报错像是 SQL 校验不过。工作目录已经有了,你帮我定位问题并修掉,不要乱改根目录里的系统文件。",
31
+ "expected_output": "代理应聚焦修复失败场景,先确定工作目录和失败步骤,检查 etl/ 下的 etl.go、SQL 文件和相关约束,做最小必要修改,只重跑 export 以及后续需要的 preview/save 步骤。",
32
+ "files": [],
33
+ "expectations": [
34
+ "明确只修改 etl/ 子目录中的文件",
35
+ "先分析 export 报错和 SQL/节点配置问题,再做代码修改",
36
+ "修复后重新执行 export,而不是直接 save",
37
+ "避免修改 _base_etl.json、_input.json、_exported.json 等系统文件"
38
+ ]
39
+ }
40
+ ]
41
+ }
@@ -0,0 +1,281 @@
1
+ # GuanETL AI 开发参考
2
+
3
+ 这份参考只保留 AI 真正常用、且最容易写错的部分。
4
+
5
+ ## 先读什么
6
+
7
+ 写 ETL 前,先按这个顺序看:
8
+
9
+ 1. `etl/etl.go`
10
+ 2. `etl/*.sql`
11
+ 3. 必要时 `etl/meta.json`
12
+ 4. 缺上下文时,用 `guancli` 查 ETL / ds / 字段,不要猜
13
+
14
+ ## 不可违反的规则
15
+
16
+ ```go
17
+ import . "guanetl/internal/framework"
18
+
19
+ func DefineETL() []Node
20
+ ```
21
+
22
+ - 不要改点导入和 `DefineETL()` 签名。
23
+ - 返回值必须是叶子节点数组,通常是一个或多个 `OUTPUT_DATASET`。
24
+ - 节点 ID 必须唯一,格式必须是 `id_数字`。
25
+ - 下游节点必须写在上游节点之后。
26
+ - 只改 `etl/` 目录,不要手改 `_base_etl.json`、`_input.json`、`_exported.json`。
27
+ - 对 `edit` 导入出来的 ETL,优先局部修改,尽量保留现有节点顺序和文件命名。
28
+
29
+ ## 节点选择顺序
30
+
31
+ 优先用专用节点,最后才用 SQL:
32
+
33
+ 1. `BasicInputDataset`
34
+ 2. `BasicSelectColumns`
35
+ 3. `BasicFilterRows`
36
+ 4. `BasicJoinData`
37
+ 5. `BasicGroupBy`
38
+ 6. `BasicCalculator`
39
+ 7. `BasicRemoveDuplicates`
40
+ 8. `BasicAppendRows`
41
+ 9. `BasicSqlScript`
42
+
43
+ 特别注意:
44
+
45
+ - 等值 JOIN 不要写成 SQL,优先 `JOIN_DATA`。
46
+ - 只是筛选、选列、聚合、去重时,不要默认上 SQL。
47
+ - SQL 很长或有很多 CTE 时,优先拆成多个节点。
48
+
49
+ ## 输入字段 schema 放哪里
50
+
51
+ 输入字段 schema 以 `BasicInputDataset(..., []Field{...})` 为主。
52
+
53
+ ```go
54
+ orders := BasicInputDataset("id_1001", "订单", "ds_orders", []Field{
55
+ BasicField("user_id", "STRING"),
56
+ BasicField("amount", "DOUBLE"),
57
+ BasicField("order_date", "DATE"),
58
+ }, Position{X: 100, Y: 100})
59
+ ```
60
+
61
+ - `guanetl export` 会把这里的字段带到最终 JSON。
62
+ - `meta.json` 主要保留 ETL 名称等元信息。
63
+ - 字段相关报错时,优先检查 `[]Field`,不要先怀疑 `meta.json`。
64
+
65
+ ## 最常用函数
66
+
67
+ ### 输入 / 输出
68
+
69
+ ```go
70
+ BasicInputDataset(id, name, inputDsID string, fields []Field, position Position)
71
+ BasicOutputDataset(id, name string, source Node, outputDsName string, position Position)
72
+ BasicOutputDatasetInDir(id, name string, source Node, outputDsName, parentDirId string, position Position)
73
+ ```
74
+
75
+ `BasicOutputDatasetInDir` 允许指定输出数据集的父目录 ID,避免落到根目录。目录 ID 可通过 `guancli ds tree` 获取。
76
+
77
+ ### 选列
78
+
79
+ ```go
80
+ BasicSelectColumns(id, name string, source Node, columns []ColumnSetting, position Position)
81
+ ```
82
+
83
+ ```go
84
+ []ColumnSetting{
85
+ BasicColumnSetting("user_id"),
86
+ {Name: "amount", NewName: "pay_amount"},
87
+ }
88
+ ```
89
+
90
+ ### 筛选
91
+
92
+ ```go
93
+ BasicFilterRows(id, name string, source Node, combineType string, conditions []FilterCondition, position Position)
94
+ ```
95
+
96
+ - `combineType` 只能是 `AND` 或 `OR`
97
+ - 高频操作符:`EQ` `NE` `LT` `LE` `GT` `GE` `IN` `BT` `IS_NULL` `NOT_NULL` `CONTAINS` `NOT_CONTAINS` `STARTSWITH` `NOT_STARTSWITH` `ENDSWITH` `NOT_ENDSWITH`
98
+
99
+ ### 等值 JOIN
100
+
101
+ ```go
102
+ BasicJoinData(id, name string, leftSource, rightSource Node, joinType string, joinColumns []JoinColumnPair, outputColumns []JoinOutputColumn, position Position)
103
+ ```
104
+
105
+ - `joinType` 只能是 `INNER` `LEFT_OUTER` `RIGHT_OUTER` `FULL_OUTER`
106
+
107
+ ### 聚合
108
+
109
+ ```go
110
+ BasicGroupBy(id, name string, source Node, groupByColumns []GroupByColumn, aggregationColumns []AggregationColumn, position Position)
111
+ ```
112
+
113
+ - 聚合类型只用:`SUM` `COUNT` `COUNT_DISTINCT` `MIN` `MAX` `AVG` `FIRST_NOT_NULL`
114
+
115
+ ### SQL
116
+
117
+ ```go
118
+ BasicSqlScript(id, name string, sources []Node, sql string, position Position)
119
+ ```
120
+
121
+ 推荐把 SQL 放到独立文件:
122
+
123
+ ```go
124
+ node := BasicSqlScript(
125
+ "id_1005",
126
+ "复杂转换",
127
+ []Node{inputA, inputB},
128
+ ReadSQLFile("node_1005.sql"),
129
+ Position{X: 900, Y: 100},
130
+ )
131
+ ```
132
+
133
+ SQL 规则:
134
+
135
+ - 上游节点在 SQL 中用 `input1`、`input2` 这类名字引用。
136
+ - 特殊列名、中文列名、带空格列名,用反引号。
137
+ - 新增 SQL 节点时,要同步新增 `.sql` 文件。
138
+
139
+ ## 推荐骨架
140
+
141
+ ```go
142
+ package main
143
+
144
+ import . "guanetl/internal/framework"
145
+
146
+ func DefineETL() []Node {
147
+ inputA := BasicInputDataset("id_1001", "输入A", "ds_a", []Field{
148
+ BasicField("id", "STRING"),
149
+ }, Position{X: 100, Y: 100})
150
+
151
+ transformed := BasicFilterRows(
152
+ "id_1002",
153
+ "筛选A",
154
+ inputA,
155
+ "AND",
156
+ []FilterCondition{},
157
+ Position{X: 320, Y: 100},
158
+ )
159
+
160
+ output := BasicOutputDataset("id_1003", "输出", transformed, "结果数据集", Position{X: 560, Y: 100})
161
+ return []Node{output}
162
+ }
163
+ ```
164
+
165
+ ## 每次改完都检查
166
+
167
+ 1. `DefineETL()` 返回的是叶子节点吗?
168
+ 2. 新节点 ID 唯一吗?格式对吗?
169
+ 3. 节点顺序符合依赖吗?
170
+ 4. `JOIN_DATA` / `GROUP_BY` / `FILTER_ROWS` 的枚举值合法吗?
171
+ 5. 新增 SQL 文件了吗?文件名和代码引用一致吗?
172
+ 6. 输入字段 schema 足够支撑后续节点吗?
173
+
174
+ ## 验证顺序
175
+
176
+ 始终按这个顺序:
177
+
178
+ 1. `guanetl export --dir <work_dir>`
179
+ 2. 需要看结果时:`guanetl preview <node_id> --dir <work_dir>`
180
+ 3. 确认无误后:`guanetl save --dir <work_dir>`
181
+
182
+ 如果 `export` 没过,不要直接 `save`。
183
+
184
+ ## Appendix: Framework Surface
185
+
186
+ 这部分只列 AI 写 ETL 时最值得记住的公开函数和合法枚举。
187
+ 源码真相在 runner framework 中,但不要把源码实现细节当成日常写法。
188
+
189
+ ### 推荐优先使用的 Basic 函数
190
+
191
+ ```go
192
+ BasicInputDataset(id, name, inputDsID string, fields []Field, position Position)
193
+ BasicOutputDataset(id, name string, source Node, outputDsName string, position Position)
194
+ BasicOutputDatasetInDir(id, name string, source Node, outputDsName, parentDirId string, position Position)
195
+ BasicSelectColumns(id, name string, source Node, columns []ColumnSetting, position Position)
196
+ BasicFilterRows(id, name string, source Node, combineType string, conditions []FilterCondition, position Position)
197
+ BasicJoinData(id, name string, leftSource, rightSource Node, joinType string, joinColumns []JoinColumnPair, outputColumns []JoinOutputColumn, position Position)
198
+ BasicGroupBy(id, name string, source Node, groupByColumns []GroupByColumn, aggregationColumns []AggregationColumn, position Position)
199
+ BasicCalculator(id, name string, source Node, formulas []Formula, position Position)
200
+ BasicRemoveDuplicates(id, name string, source Node, columnNames []string, position Position)
201
+ BasicAppendRows(id, name string, sources []Node, unionType string, schemaSource string, position Position)
202
+ BasicSqlScript(id, name string, sources []Node, sql string, position Position)
203
+ ```
204
+
205
+ ### 只有需要完整配置时再用的 New 函数
206
+
207
+ ```go
208
+ NewInputDataset(id, name string, config InputDatasetConfig)
209
+ NewOutputDataset(id, name string, source Node, config OutputDatasetConfig)
210
+ NewSelectColumns(id, name string, source Node, config SelectColumnsConfig)
211
+ NewFilterRows(id, name string, source Node, config FilterRowsConfig)
212
+ NewJoinData(id, name string, leftSource, rightSource Node, config JoinDataConfig)
213
+ NewGroupBy(id, name string, source Node, config GroupByConfig)
214
+ NewCalculator(id, name string, source Node, config CalculatorConfig)
215
+ NewRemoveDuplicates(id, name string, source Node, config RemoveDuplicatesConfig)
216
+ NewAppendRows(id, name string, sources []Node, config AppendRowsConfig)
217
+ NewSqlScript(id, name string, sources []Node, config SqlScriptConfig)
218
+ ```
219
+
220
+ 默认优先 `Basic*`,除非你明确需要手动控制更底层配置。
221
+
222
+ ### 高频辅助函数
223
+
224
+ ```go
225
+ BasicField(name, fieldType string)
226
+ BasicColumnSetting(name string)
227
+ BasicJoinColumnPair(leftColumn, rightColumn string)
228
+ BasicJoinOutputFromLeft(columnName string)
229
+ BasicJoinOutputFromRight(columnName string)
230
+ BasicJoinOutputFromLeftWithAlias(columnName, alias string)
231
+ BasicJoinOutputFromRightWithAlias(columnName, alias string)
232
+ BasicGroupByColumn(name, columnType string)
233
+ BasicGroupByColumnWithAlias(name, columnType, newName string)
234
+ BasicAggregationColumn(name, columnType, aggregationType string)
235
+ BasicAggregationColumnWithAlias(name, columnType, aggregationType, newName string)
236
+ BasicFilterCondition(columnName, operator string, filterValues []FilterValue)
237
+ BasicFilterValue(value string)
238
+ BasicFilterColumnValue(columnName string)
239
+ ReadSQLFile(filename string)
240
+ ```
241
+
242
+ ### 合法枚举
243
+
244
+ 筛选组合:
245
+
246
+ - `AND`
247
+ - `OR`
248
+
249
+ 筛选操作符:
250
+
251
+ - `EQ` `NE`
252
+ - `LT` `LE` `GT` `GE`
253
+ - `IN`
254
+ - `BT`
255
+ - `IS_NULL` `NOT_NULL`
256
+ - `CONTAINS` `NOT_CONTAINS`
257
+ - `STARTSWITH` `NOT_STARTSWITH`
258
+ - `ENDSWITH` `NOT_ENDSWITH`
259
+
260
+ JOIN 类型:
261
+
262
+ - `INNER`
263
+ - `LEFT_OUTER`
264
+ - `RIGHT_OUTER`
265
+ - `FULL_OUTER`
266
+
267
+ 聚合类型:
268
+
269
+ - `SUM`
270
+ - `COUNT`
271
+ - `COUNT_DISTINCT`
272
+ - `MIN`
273
+ - `MAX`
274
+ - `AVG`
275
+ - `FIRST_NOT_NULL`
276
+
277
+ APPEND_ROWS unionType:
278
+
279
+ - `INCLUDE_SHARED`
280
+ - `INCLUDE_ALL`
281
+ - `INCLUDE_FROM`