siluzan-tso-cli 1.1.26-beta.2 → 1.1.26-beta.3

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/README.md CHANGED
@@ -51,7 +51,7 @@ siluzan-tso init -d /path/to/skills # 写入自定义目录
51
51
  siluzan-tso init --force # 强制覆盖已存在文件
52
52
  ```
53
53
 
54
- > **注意**:当前为测试版(1.1.26-beta.2),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
54
+ > **注意**:当前为测试版(1.1.26-beta.3),供内部测试使用。正式发布后安装命令将改为 `npm install -g siluzan-tso-cli`。
55
55
 
56
56
  | 助手 | 建议 `--ai` |
57
57
  | ----------------------- | ------------------------------------ |
@@ -17,7 +17,7 @@ allowed-tools: Bash(siluzan-tso:*) Read Write
17
17
 
18
18
  > **Agent 纪律(每个新任务必读)**:Read `references/core/agent-conventions.md`,再按 `SKILL.md` 任务表 Read 域文档。禁止跨话题复用参数记忆;数据类任务一律 `--json-out` + **仅用代码**读落盘 JSON(见 `references/core/tips.md`)——**禁止**用 Read 工具打开 `writtenFiles` 里的完整 `*.json`。
19
19
  >
20
- > **报告/Excel 交付前**:Read `references/core/deliverable-preflight.md`,运行 `node scripts/check-deliverable-snap.mjs <snapDir>`;币种只认 `currencyCode`,禁止默认 Google=美金。
20
+ > **报告/Excel 交付前**:Read `references/core/deliverable-preflight.md`,**Read 最终产物**并按自检表确认币种与章节完整;币种只认当次 `list-accounts` `currencyCode`。
21
21
  >
22
22
  > **开户**:首次进入开户话题须先向用户罗列该媒体(或未指明媒体时六平台)**全部必填项**,见 `references/accounts/open-account-by-media.md` §「首次响应硬规范」。
23
23
  >
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "slug": "siluzan-tso",
3
- "version": "1.1.26-beta.2",
4
- "publishedAt": 1780539987793
3
+ "version": "1.1.26-beta.3",
4
+ "publishedAt": 1780540125141
5
5
  }
@@ -9,7 +9,7 @@
9
9
  | `core/setup.md` | 安装、登录(手机验证码优先)、配置、更新 |
10
10
  | `core/agent-conventions.md` | **Agent 必读**:加载纪律、硬规范、数据转换、时间/币种/批量 |
11
11
  | `core/tips.md` | `--json-out` 协议、outline→脚本读 JSON |
12
- | `core/deliverable-preflight.md` | **交付前自检**:币种 + manifest 完整性;`scripts/check-deliverable-snap.mjs` |
12
+ | `core/deliverable-preflight.md` | **交付前审阅最终产物**:Agent Read 报告文件并对照自检表(币种/章节) |
13
13
  | `core/playbooks.md` | P1–P7 高频任务标准动作 |
14
14
  | `core/workflows.md` | 多命令串联业务流程 |
15
15
  | `core/skill-authoring.md` | Skill 文档维护最佳实践(维护者) |
@@ -37,7 +37,7 @@
37
37
  2. 涉及写入/修改/删除的操作必须与用户确认
38
38
  3. 按计划执行,说明每步意图
39
39
  4. 用成对的读命令复核写入结果;异步任务每 5s 轮询直到完成
40
- 5. **交付前**(报告/Excel/含金额话术):Read `references/core/deliverable-preflight.md`,运行 `node scripts/check-deliverable-snap.mjs`;币种须来自 `list-accounts` / `overview` 的 `currencyCode`
40
+ 5. **交付前**(报告/Excel/含金额话术):Read `references/core/deliverable-preflight.md`,**Read 最终产物文件**并按自检表 A/B/C 逐项确认后再发给用户
41
41
  6. 全部完成后预测用户下一步操作
42
42
 
43
43
  ### 执行模式速查
@@ -136,7 +136,7 @@
136
136
  - **符号**:`CNY` → **¥**;`USD` → **$**。多账户按 `currencyCode` 分表,**禁止**跨币种求和。
137
137
  - **金额单位统一为「元」**:报告保留 2 位小数。
138
138
  - **Google 广告诊断报告**:`report-templates/google-ads-diagnosis.md` § 撰写硬约束 — 每日趋势金额/CPA 必须 2 位小数;每个模块除表格外须有「分析」+「建议」,禁止只贴数据。
139
- - **交付前自检**:报告/Excel 交付前运行 `scripts/check-deliverable-snap.mjs`(见 `deliverable-preflight.md`);自检失败禁止交付。
139
+ - **交付前审阅产物**:报告/Excel 交付前 Agent **Read 最终文件**并对照 `deliverable-preflight.md` 自检表;未审阅禁止交付。
140
140
  - **品牌名优先级**:(1) 用户明确提供 → (2) `list-accounts.mag.advertiserName` → (3) 网址域名占位 `[待确认品牌名]`。**严禁**把英文域名翻译为虚构中文品牌。
141
141
 
142
142
  ---
@@ -1,107 +1,106 @@
1
- # 交付前自检(币种 + 数据完整)
1
+ # 交付前审阅最终产物(Agent 自检)
2
2
 
3
- > 用户反馈 Agent 产出物常见两类问题:**币种错误**(Google 默认美金、符号混用)、**报告缺章/缺数**(少拉维度、Read JSON 截断后手填)。本节给出**可执行**防线。
3
+ > 用户反馈:产出物常出现**币种错误**、**报告缺章/缺数**。
4
+ > **不靠外部校验脚本**——由 **Agent 在交付用户前,亲自查看已生成的最终文件**(HTML / Markdown / Excel 等),对照清单结论后再发送。
4
5
 
5
6
  ---
6
7
 
7
- ## 根因(简要)
8
+ ## 原则
8
9
 
9
- | 问题 | 典型原因 |
10
- | ---- | -------- |
11
- | 币种错误 | 未先 `list-accounts` `currencyCode`;报告脚本写死 `USD`;CNY 用 `$` |
12
- | 数据缺失 | 未按模板拉全 `--sections`;未读 `manifest` 就写报告;Read JSON 截断后编造;多账户漏 ID |
10
+ | 必须 | 禁止 |
11
+ | ---- | ---- |
12
+ | **Read 最终交付文件**(你刚写出的 `.html` / `.md` 等) | Read 打开 `--json-out` 落盘的业务 `*.json` 来「核对」(见 `tips.md`) |
13
+ | 对照当次 `report-templates/*.md` 章节逐项勾选 | 未打开产物就声称「报告已好」 |
14
+ | 币种与**当次对话**里 `list-accounts` / CLI 表格已确认的 `currencyCode` 一致 | 凭媒体猜 USD;CNY 用 `$` |
15
+ | 缺数据的章节写 `[ 数据不可用:… ]` | 空白章节、编造数字填坑 |
16
+
17
+ 拉数阶段仍用 `--json-out` + **代码**读 JSON 生成产物;**审阅阶段**只看**最终产物** + 记忆中的账户元数据(账户 ID、区间、币种),不跑 `check-*.mjs` 类脚本。
13
18
 
14
19
  ---
15
20
 
16
- ## 强制流程(报告/Excel 交付前)
21
+ ## 审阅时机
17
22
 
18
- 与 `references/core/agent-conventions.md`「计划 → 确认 → 执行 → **验证**」一致,**交付前增加验证步**:
23
+ 在以下动作**之后、发给用户之前**:
19
24
 
20
- ### 1. 币种(第一步就做)
25
+ 1. 已用脚本/CLI 拉数并生成报告文件(或 Excel)。
26
+ 2. 文件已写入磁盘路径(你知道路径)。
21
27
 
22
- ```bash
23
- siluzan-tso list-accounts -m Google -k <mediaCustomerId> --json-out ./snap
24
- node -e "
25
- const d=require('./snap/list-accounts-google.json');
26
- const ma=(d.items||[]).map(x=>x.ma||x).find(m=>String(m.mediaCustomerId)===process.argv[1]);
27
- if(!ma){console.error('账户未找到');process.exit(1);}
28
- console.log(ma.currencyCode, ma.mediaCustomerName);
29
- " <mediaCustomerId>
30
- ```
28
+ **然后必须 Read 该文件**(或 Excel 时对用户说明各 Sheet 名称与首行摘要——若无法 Read 二进制 xlsx,须在对话中贴出**自检表**逐条勾选,并说明依据来自生成脚本的 stdout 摘要,而非猜)。
31
29
 
32
- - 将输出的 **`currencyCode`** 写入报告生成脚本(从 JSON 读,勿写死)。
33
- - 首行必须有:`统计区间:YYYY-MM-DD ~ YYYY-MM-DD(货币:CNY|USD)`。
34
- - 符号:**CNY → ¥**,**USD → $**。详见 `references/accounts/currency.md`。
30
+ ---
35
31
 
36
- ### 2. 拉数完整(对照模板 + manifest)
32
+ ## 自检表 A · 币种(每条必勾)
37
33
 
38
- - 周期报告:按 `report-templates/google-period-report.md` 默认 7 维(或用户追加维)一次 `google-analysis --sections … --json-out`。
39
- - 拉数后打开 **`manifest-<accountId>.json`**(用脚本 `JSON.parse`,勿 Read 整份若很大),核对 `artifacts[].section` 与模板一致。
34
+ 对照 `references/accounts/currency.md`:
40
35
 
41
- ### 3. 写报告只用脚本读 JSON
36
+ - [ ] 报告**封面/首行**含 `统计区间:…(货币:CNY)` 或 `(货币:USD)`(或等价英文标注)
37
+ - [ ] 代码与**一致**:`CNY` → 全文金额用 **¥**;`USD` → **$**;未混用
38
+ - [ ] 与当次 `list-accounts -k …` 得到的 **`currencyCode` 相同**(回顾对话中 CLI 表格或你记录的「币种」一行)
39
+ - [ ] 多账户报告:分币种分表,**无** CNY+USD 直接相加的一行「总计」
40
+ - [ ] 未出现「Google 账户所以是美金」类表述
42
41
 
43
- `references/core/tips.md`:**禁止** Read 业务 `*.json`;用 `node`/Python 聚合后写 HTML/xlsx。
42
+ **不通过** 改产物文件后**重新 Read** 再审,不得交付。
44
43
 
45
- ### 4. 交付前运行自检脚本(必做)
44
+ ---
46
45
 
47
- Skill 自带脚本(安装后在 skill 根目录):
46
+ ## 自检表 B · 结构完整(对照当次模板)
48
47
 
49
- ```bash
50
- node scripts/check-deliverable-snap.mjs ./snap --preset p4-google
51
- # 或自定义:--required overview,campaigns,geographic
52
- # 已知账户为 CNY 时:--expect-currency CNY
53
- ```
48
+ 先确认当次用的是哪份模板(如 `google-period-report.md`、`okki-weekly-google-client.md`、`google-inquiry-analysis.md`),再逐项:
54
49
 
55
- | 参数 | 说明 |
56
- | ---- | ---- |
57
- | `<snapDir>` | `--json-out` 目录 |
58
- | `--preset p4-google` | 对齐 Google 周期报告默认 7 维 |
59
- | `--preset p1-google` | 单账户画像:list-accounts + stats + ad-campaigns |
60
- | `--required a,b` | 额外必填 section(逗号分隔,前缀匹配) |
61
- | `--expect-currency CNY\|USD` | 与 `list-accounts` / `overview` 中 `currencyCode` 对照 |
50
+ - [ ] 模板要求的**每一章/Sheet**在产物里都存在(标题或 Sheet 名可对应)
51
+ - [ ] **无**整章空白(除非该章标注 `[ 数据不可用:… ]`)
52
+ - [ ] 执行摘要 / 概览:有消耗、展示、点击等**至少一项非空数字**(或明确写无数据)
53
+ - [ ] 周期报告默认维(P4):概览、日趋势、系列、设备、地域、关键词等**均有对应段落**(Meta 无接口的维度按模板写「未提供」)
54
+ - [ ] 优化建议:独立一节,**不是**只有表格没有文字
55
+ - [ ] Google 诊断:每模块除表格外有「分析」+「建议」(见 `google-ads-diagnosis.md`)
62
56
 
63
- **退出码 1** 补拉缺失维度或修币种后再交付。**禁止**在自检失败时仍把报告发给用户。
57
+ **不通过**补拉 CLI 维度、重跑生成逻辑、**重写产物**后再 Read 审阅。
64
58
 
65
- ### 5. 报告正文抽检(脚本输出对照)
59
+ ---
66
60
 
67
- 交付前用脚本打印 3 个汇总(示例):
61
+ ## 自检表 C · 数字可信(抽样,不读大 JSON)
68
62
 
69
- ```bash
70
- node -e "
71
- const o=require('./snap/overview-<id>_<start>-<end>.json');
72
- const r=o.record||{};
73
- const p=r.currentPeriod||{};
74
- console.log('spend',p.spend,'clicks',p.clicks,'currency',r.currencyCode);
75
- "
76
- ```
63
+ 不打开落盘 JSON,用以下**已掌握信息**对照产物:
77
64
 
78
- 报告中的总消耗、币种须与上述一致(允许四舍五入,**不允许数量级或币种错误**)。
65
+ - [ ] 报告总消耗/CPA 数量级与你在生成过程中 **CLI/脚本 stdout 打印的汇总**一致(同一会话里应有过一行 summary;若无,先补跑一次极小 `node -e` 只打印 totals 再对照——**仅用于核对,不把 JSON 贴进对话**)
66
+ - [ ] 账户 ID、账户名、统计区间与用户需求一致
67
+ - [ ] 无「示例账户」「占位 123456」等模板残留
68
+ - [ ] 表格行数与预期同一(如 P3 要求每个 `-a` ID 都有一行)
69
+
70
+ **不通过** → 修正产物或重新生成,禁止手改数字糊弄。
79
71
 
80
72
  ---
81
73
 
82
- ## Playbook 挂钩
74
+ ## 向用户交付时的格式(建议)
83
75
 
84
- | Playbook | 自检命令 |
85
- | -------- | -------- |
86
- | P1 | `--preset p1-google` + `--expect-currency`(来自 list-accounts) |
87
- | P4 | `--preset p4-google` |
88
- | P6 / P7 | 按 `okki-weekly-google-client.md` / `google-inquiry-analysis.md` 自建 `--required`(含各月 `campaigns`、`geographic` 等) |
76
+ 审阅通过后,消息中附带简短**自检结论**(证明你看过产物):
77
+
78
+ ```text
79
+ 交付前自检(已通过):
80
+ - 产物:./out/report-xxx.html
81
+ - 币种:CNY(来自 list-accounts,与报告首行一致)
82
+ - 章节:8/8 默认维度齐全;关键词章 [ 数据不可用:接口超时 ] 已标注
83
+ - 区间:2026-04-01 ~ 2026-04-30
84
+ ```
85
+
86
+ 若某项未通过,**不要**写「已通过」,应说明正在补全。
89
87
 
90
88
  ---
91
89
 
92
- ## 长期改进(可选)
90
+ ## 与 Playbook 的关系
93
91
 
94
- | 手段 | 说明 |
95
- | ---- | ---- |
96
- | **skill-eval 场景** | 断言 Agent `list-accounts` 且报告含 `(货币:XXX)` |
97
- | **CLI `snap check`** | 将本脚本收编为 `siluzan-tso snap check`(待开发) |
98
- | **报告模板内嵌币种占位** | 脚本写 `{currencyCode}` 而非手写符号 |
92
+ | Playbook | 审阅时重点 |
93
+ | -------- | ---------- |
94
+ | P1 | 画像是否含 stats + 系列要点;币种 |
95
+ | P4 | `google-period-report.md` 8 块是否齐 |
96
+ | P4-FB | 无接口维度是否写「未提供」 |
97
+ | P6 / P7 | 按各自 `report-templates/*.md` Sheet/章节表逐项 |
99
98
 
100
99
  ---
101
100
 
102
101
  ## 相关文档
103
102
 
104
103
  - `references/accounts/currency.md`
105
- - `report-templates/REPORT-WORKFLOW.md` § 步骤 4–5
106
- - `references/core/tips.md`
104
+ - `report-templates/REPORT-WORKFLOW.md`
105
+ - `references/core/tips.md`(拉数阶段)
107
106
  - `references/core/playbooks.md`
@@ -24,7 +24,7 @@
24
24
  4. `ad campaigns -a <mediaCustomerId> --start <S> --end <D> --json-out ./snap-p1`
25
25
  5. 诊断/画像报告:Read `report-templates/google-ads-diagnosis.md`(**硬约束:每日趋势 2 位小数、每模块必有分析**)+ `google-account-diagnosis-report.md`(章节与 CLI);`google-analysis --json-out` 须含 `daily-metrics`(按日趋势)。
26
26
  6. 首行标注统计区间和货币;HTML 可对齐 `report-template.html` + `section-*` 区块。
27
- 7. 交付前:`node scripts/check-deliverable-snap.mjs ./snap-p1 --preset p1-google --expect-currency <来自 list-accounts>`。
27
+ 7. 交付前:**Read 最终产物**,按 `deliverable-preflight.md` 自检表确认币种与章节。
28
28
 
29
29
  ---
30
30
 
@@ -67,7 +67,7 @@ siluzan-tso accounts-digest -m Google -a id1,id2,... --start <S> --end <D> --jso
67
67
  1. 确认时间范围;区间 > 3 个月时分段(季度/月)。
68
68
  2. 按 `google-period-report.md` 默认维度执行 `google-analysis --json-out <dir>`。
69
69
  3. 报告须含:账户概览、投放趋势、Top 关键词/系列/地区分布、优化建议。
70
- 4. 交付前:`node scripts/check-deliverable-snap.mjs <dir> --preset p4-google --expect-currency <currencyCode>`。
70
+ 4. 交付前:**Read 最终 HTML/报告文件**,对照 `google-period-report.md` `deliverable-preflight.md` 自检。
71
71
 
72
72
  ---
73
73
 
@@ -51,11 +51,11 @@
51
51
 
52
52
  ---
53
53
 
54
- ### 步骤 4b:交付前自检(**必做**)
54
+ ### 步骤 4b:交付前审阅最终产物(**必做**)
55
55
 
56
- - Read `references/core/deliverable-preflight.md`。
57
- - 运行 `node scripts/check-deliverable-snap.mjs <snapDir> --preset p4-google`(或按模板 `--required` / `--expect-currency`)。
58
- - 退出码非 0:**补拉数据或修正币种**,不得交付。
56
+ - Read `references/core/deliverable-preflight.md` 自检表 A/B/C。
57
+ - **Read 刚生成的报告文件**(HTML/Markdown 等),对照当次 `report-templates/*.md` 逐项确认章节与币种。
58
+ - 未通过:**修改产物或补拉数据后重新生成**,再次 Read 审阅;不得在未审阅时交付。
59
59
 
60
60
  ---
61
61
 
@@ -9,7 +9,7 @@ $ErrorActionPreference = 'Stop'
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  $PKG_NAME = 'siluzan-tso-cli'
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- $PKG_VERSION = '1.1.26-beta.2'
12
+ $PKG_VERSION = '1.1.26-beta.3'
13
13
  $CLI_BIN = 'siluzan-tso'
14
14
  $SKILL_LABEL = 'Siluzan TSO'
15
15
  $INSTALL_CMD = 'npm install -g siluzan-tso-cli@beta'
@@ -9,7 +9,7 @@ set -euo pipefail
9
9
  # -- Package info (injected at build time) ------------------------------------
10
10
  readonly PKG_NAME="siluzan-tso-cli"
11
11
  # PKG_VERSION 锁定到与本脚本同批构建产物一致的版本,避免与 dist/skill 错位
12
- readonly PKG_VERSION="1.1.26-beta.2"
12
+ readonly PKG_VERSION="1.1.26-beta.3"
13
13
  readonly CLI_BIN="siluzan-tso"
14
14
  readonly SKILL_LABEL="Siluzan TSO"
15
15
  readonly INSTALL_CMD="npm install -g siluzan-tso-cli@beta"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "siluzan-tso-cli",
3
- "version": "1.1.26-beta.2",
3
+ "version": "1.1.26-beta.3",
4
4
  "description": "Siluzan 广告账户管理 CLI — 查询账户、余额、消耗数据,管理绑定关系与充值。",
5
5
  "keywords": [
6
6
  "ad-account",
@@ -1,170 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * 交付前快照自检(币种 + 缺文件/空数据)
4
- *
5
- * 用法:
6
- * node scripts/check-deliverable-snap.mjs <snapDir> [--preset p4-google] [--required a,b]
7
- * node scripts/check-deliverable-snap.mjs ./snap --expect-currency CNY
8
- *
9
- * 退出码:0 通过;1 有 error;2 用法错误
10
- */
11
- import fs from "node:fs";
12
- import path from "node:path";
13
-
14
- const PRESETS = {
15
- "p4-google": [
16
- "overview",
17
- "daily-metrics",
18
- "dimension-summary",
19
- "campaigns",
20
- "devices",
21
- "geographic",
22
- "keywords",
23
- ],
24
- "p1-google": ["list-accounts", "stats", "ad-campaigns"],
25
- };
26
-
27
- function parseArgs(argv) {
28
- const snapDir = argv[2];
29
- if (!snapDir) return { error: "缺少 snapDir" };
30
- let preset;
31
- let required = [];
32
- let expectCurrency;
33
- for (let i = 3; i < argv.length; i++) {
34
- if (argv[i] === "--preset" && argv[i + 1]) {
35
- preset = argv[++i];
36
- } else if (argv[i] === "--required" && argv[i + 1]) {
37
- required = argv[++i].split(",").map((s) => s.trim()).filter(Boolean);
38
- } else if (argv[i] === "--expect-currency" && argv[i + 1]) {
39
- expectCurrency = argv[++i].toUpperCase();
40
- }
41
- }
42
- if (preset) {
43
- const p = PRESETS[preset];
44
- if (!p) return { error: `未知 preset:${preset},可选:${Object.keys(PRESETS).join(", ")}` };
45
- required = [...new Set([...required, ...p])];
46
- }
47
- return { snapDir: path.resolve(snapDir), required, expectCurrency };
48
- }
49
-
50
- function readJson(filePath) {
51
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
52
- }
53
-
54
- function listManifests(dir) {
55
- return fs.readdirSync(dir).filter((f) => /^manifest(-.*)?\.json$/.test(f) || /^report-manifest(-.*)?\.json$/.test(f) || /^cli-manifest(-.*)?\.json$/.test(f));
56
- }
57
-
58
- function sectionMatches(artifactSection, required) {
59
- return required.some((r) => artifactSection === r || artifactSection.startsWith(`${r}-`) || artifactSection.includes(r));
60
- }
61
-
62
- function itemCount(payload) {
63
- if (!payload || typeof payload !== "object") return null;
64
- if (Array.isArray(payload.items)) return payload.items.length;
65
- if (Array.isArray(payload.data?.items)) return payload.data.items.length;
66
- if (payload.record && typeof payload.record === "object") return 1;
67
- if (Array.isArray(payload.data)) return payload.data.length;
68
- return null;
69
- }
70
-
71
- function extractCurrencyFromPayload(payload, section) {
72
- if (!payload || typeof payload !== "object") return null;
73
- const rec = payload.record ?? payload.data?.record;
74
- if (rec?.currencyCode) return String(rec.currencyCode);
75
- const items = payload.items ?? payload.data?.items;
76
- if (Array.isArray(items) && items.length > 0) {
77
- const ma = items[0].ma ?? items[0];
78
- if (ma?.currencyCode) return String(ma.currencyCode);
79
- if (items[0].currencyCode) return String(items[0].currencyCode);
80
- }
81
- if (section.includes("list-accounts") && Array.isArray(items) && items[0]?.ma?.currencyCode) {
82
- return String(items[0].ma.currencyCode);
83
- }
84
- return null;
85
- }
86
-
87
- function main() {
88
- const parsed = parseArgs(process.argv);
89
- if (parsed.error) {
90
- console.error(parsed.error);
91
- process.exit(2);
92
- }
93
- const { snapDir, required, expectCurrency } = parsed;
94
- if (!fs.existsSync(snapDir)) {
95
- console.error(`目录不存在:${snapDir}`);
96
- process.exit(2);
97
- }
98
-
99
- const manifests = listManifests(snapDir);
100
- if (manifests.length === 0) {
101
- console.error(`未找到 manifest / report-manifest / cli-manifest:${snapDir}`);
102
- process.exit(1);
103
- }
104
-
105
- const errors = [];
106
- const warnings = [];
107
- let currencySeen = null;
108
-
109
- for (const mf of manifests) {
110
- const man = readJson(path.join(snapDir, mf));
111
- const arts = man.artifacts ?? [];
112
- console.log(`\n📋 ${mf}(${arts.length} 个 artifact)`);
113
-
114
- if (required.length > 0) {
115
- for (const req of required) {
116
- const hit = arts.some((a) => sectionMatches(String(a.section ?? ""), [req]));
117
- if (!hit) errors.push(`[${mf}] 缺少维度/命令:${req}`);
118
- else console.log(` ✓ 已拉取:${req}`);
119
- }
120
- }
121
-
122
- for (const art of arts) {
123
- const fp = path.join(snapDir, art.file);
124
- if (!fs.existsSync(fp)) {
125
- errors.push(`[${mf}] 文件缺失:${art.file}`);
126
- continue;
127
- }
128
- let payload;
129
- try {
130
- payload = readJson(fp);
131
- } catch (e) {
132
- errors.push(`[${mf}] JSON 解析失败:${art.file}`);
133
- continue;
134
- }
135
- const n = itemCount(payload);
136
- const sec = String(art.section ?? "");
137
- if (n === 0) warnings.push(`[${mf}] ${art.file} items 为空(章节可能写「无数据」)`);
138
- const ccy = extractCurrencyFromPayload(payload, sec);
139
- if (ccy) {
140
- if (!currencySeen) currencySeen = ccy;
141
- else if (currencySeen !== ccy) warnings.push(`[${mf}] 币种不一致:${ccy} vs 先前 ${currencySeen}`);
142
- if (sec.includes("overview") || sec.includes("list-accounts")) {
143
- console.log(` 💱 币种来源 ${art.file}:${ccy}`);
144
- }
145
- }
146
- }
147
- }
148
-
149
- if (expectCurrency) {
150
- if (!currencySeen) warnings.push(`未从快照解析到 currencyCode,无法对照 --expect-currency ${expectCurrency}`);
151
- else if (currencySeen !== expectCurrency) {
152
- errors.push(`币种不符:快照为 ${currencySeen},期望 ${expectCurrency}(报告须用 ¥/ $ 与代码一致)`);
153
- } else console.log(`\n✓ 币种与期望一致:${expectCurrency}`);
154
- } else if (currencySeen) {
155
- console.log(`\n💱 建议在报告首行写:统计区间:…(货币:${currencySeen})`);
156
- }
157
-
158
- if (warnings.length) {
159
- console.log("\n⚠️ 警告:");
160
- warnings.forEach((w) => console.log(` - ${w}`));
161
- }
162
- if (errors.length) {
163
- console.log("\n❌ 错误:");
164
- errors.forEach((e) => console.log(` - ${e}`));
165
- process.exit(1);
166
- }
167
- console.log("\n✅ 交付前自检通过(仍须确认报告正文金额来自脚本、非手填)\n");
168
- }
169
-
170
- main();