@microi.net/cli 5.5.9 → 5.6.1
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/.codebuddy-plugin/marketplace.json +2 -2
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.workbuddy-plugin/marketplace.json +2 -2
- package/.workbuddy-plugin/plugin.json +1 -1
- package/assets/build-meta.json +5 -5
- package/cordis.patch.yml +1 -1
- package/package.json +1 -1
- package/scripts/mcp-server.js +99 -99
- package/scripts/microi-cli.js +39 -36
- package/scripts/microi-skills.meta.json +341 -338
- package/skills/.microi-skills-version.json +2 -2
- package/skills/.progressive-disclosure-manifest.json +3566 -3566
- package/skills/README.md +4 -4
- package/skills/ai-engine/SKILL.md +45 -45
- package/skills/ai-platform-governance/SKILL.md +177 -177
- package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -190
- package/skills/app-store/SKILL.md +21 -4
- package/skills/business-blueprint/SKILL.md +85 -85
- package/skills/datasource-engine/SKILL.md +91 -91
- package/skills/dos-orm/SKILL.md +15 -0
- package/skills/dos-orm/references/api-reference.md +229 -229
- package/skills/email-engine/SKILL.md +80 -80
- package/skills/email-engine/references/v8-email.md +34 -34
- package/skills/job-engine/SKILL.md +154 -154
- package/skills/message-notification/SKILL.md +27 -1
- package/skills/message-notification/agents/openai.yaml +5 -5
- package/skills/message-notification/references/contracts.md +102 -102
- package/skills/microi-ai-app-auth.js +652 -652
- package/skills/microi-ai-application/SKILL.md +102 -102
- package/skills/microi-ai-application/agents/openai.yaml +4 -4
- package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
- package/skills/microi-client-frontend/SKILL.md +47 -41
- package/skills/microi-client-frontend/references/progressive-01-3-/345/212/250/346/200/201/346/214/211/351/222/256/347/263/273/347/273/237.md +4 -4
- package/skills/microi-client-frontend/references/progressive-02-8-/350/277/220/350/241/214/346/227/266/351/253/230/351/242/221/345/235/221/345/244/215/347/233/230.md +30 -30
- package/skills/microi-client-frontend/references/progressive-03-vue3-/345/211/215/347/253/257/345/276/256/346/234/215/345/212/241/345/256/277/344/270/273/350/247/204/345/210/231.md +58 -58
- package/skills/microi-codex/SKILL.md +2 -0
- package/skills/microi-codex-installer/SKILL.md +216 -216
- package/skills/microi-codex-installer/agents/openai.yaml +7 -7
- package/skills/microi-datasource-mapping/SKILL.md +67 -67
- package/skills/microi-db-schema/SKILL.md +6 -6
- package/skills/microi-db-schema/references/core-tables.md +18 -18
- package/skills/microi-db-schema/references/form-component-options.md +256 -256
- package/skills/microi-db-schema/references/schema-overview.md +11 -11
- package/skills/microi-db-schema/references/schema.md +46 -46
- package/skills/microi-db-schema/references/table-catalog.md +57 -57
- package/skills/microi-deployment/SKILL.md +18 -1
- package/skills/microi-deployment/references/deployment-matrix.md +101 -101
- package/skills/microi-docs-coverage/SKILL.md +10 -0
- package/skills/microi-docs-coverage/references/capability-map.md +3 -2
- package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
- package/skills/microi-form-engine/SKILL.md +27 -3
- package/skills/microi-form-engine/references/component-catalog.md +218 -218
- package/skills/microi-form-engine/references/data-source-events.md +124 -124
- package/skills/microi-form-layout/SKILL.md +13 -13
- package/skills/microi-form-layout/references/progressive-01-3-/344/270/211/347/247/215/345/210/206/347/273/204/347/232/204/345/255/230/345/202/250/344/270/216/351/205/215/347/275/256.md +235 -235
- package/skills/microi-frontend-sdk/SKILL.md +4 -4
- package/skills/microi-frontend-sdk/references/progressive-01-token-/345/275/223/345/211/215/347/231/273/345/275/225/347/224/250/346/210/267/344/270/216/345/275/223/345/211/215/347/273/210/347/253/257/347/231/273/345/275/225/345/215/217/350/256/256.md +175 -175
- package/skills/microi-left-right-layout/SKILL.md +141 -141
- package/skills/microi-microservice/SKILL.md +2 -1
- package/skills/microi-microservice/references/runtime-delivery.md +278 -278
- package/skills/microi-mobile-app-quality/references/progressive-01-4-/351/207/215/350/246/201/346/214/211/351/222/256/345/277/205/351/241/273/345/270/246/345/233/276/346/240/207.md +213 -213
- package/skills/microi-solution-quotation/SKILL.md +78 -78
- package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
- package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
- package/skills/microi-sso/SKILL.md +6 -0
- package/skills/microi-sso/references/acceptance.md +49 -49
- package/skills/microi-sso/references/configuration-and-security.md +53 -53
- package/skills/microi-sso/references/inbound.md +53 -53
- package/skills/microi-sso/references/outbound.md +39 -39
- package/skills/microi-system-delivery/SKILL.md +5 -4
- package/skills/microi-system-delivery/references/progressive-02-/350/207/252/345/212/250/345/214/226/346/265/213/350/257/225/345/277/205/351/241/273/350/246/206/347/233/226/347/232/204/345/235/221.md +7 -2
- package/skills/microi-ui/SKILL.md +7 -2
- package/skills/microi-uniapp-frontend/SKILL.md +134 -134
- package/skills/microi-uniapp-frontend/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/210/206/347/261/273-/345/217/214/346/240/217/345/210/227/350/241/250/347/213/254/347/253/213/346/273/232/345/212/250.md +136 -136
- package/skills/microi-uniapp-frontend/references/progressive-02-/345/205/263/351/224/256/344/270/232/345/212/241/350/265/204/344/272/247/344/270/215/345/276/227/351/273/230/350/256/244/351/200/211/344/270/255.md +124 -124
- package/skills/module-engine/SKILL.md +30 -14
- package/skills/ocr-engine/SKILL.md +2 -2
- package/skills/ocr-engine/agents/openai.yaml +1 -1
- package/skills/page-engine/SKILL.md +28 -28
- package/skills/page-engine/references/progressive-01-/346/211/200/346/234/211/347/273/204/344/273/266/347/261/273/345/236/213.md +126 -126
- package/skills/page-engine/references/progressive-02-/347/211/210/346/234/254/345/216/206/345/217/262-/345/271/266/345/217/221/344/277/235/345/255/230/344/270/216/345/233/236/346/273/232.md +42 -42
- package/skills/performance-testing/SKILL.md +120 -120
- package/skills/playwright-e2e/SKILL.md +84 -84
- package/skills/playwright-e2e/references/progressive-01-/345/205/250/350/207/252/345/212/250/347/231/273/345/275/225-/345/205/215/351/252/214/350/257/201/347/240/201-/344/275/206/344/270/215/345/205/215/345/257/206/347/240/201-/345/277/205/350/257/273.md +68 -68
- package/skills/playwright-e2e/references/progressive-02-/346/226/207/345/255/227/345/257/271/346/257/224/345/272/246/344/270/216/345/217/257/350/257/273/346/200/247/350/207/252/345/212/250/345/214/226/346/243/200/346/237/245-/345/277/205/345/201/232.md +64 -64
- package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +20 -20
- package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +69 -69
- package/skills/print-engine/SKILL.md +11 -3
- package/skills/production-readonly-audit/SKILL.md +41 -41
- package/skills/report-engine/SKILL.md +71 -71
- package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
- package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
- package/skills/scripts/sync-embedded-skills.mjs +46 -0
- package/skills/scripts/validate-progressive-disclosure.mjs +8 -3
- package/skills/search-engine/SKILL.md +75 -75
- package/skills/spider-engine/SKILL.md +190 -190
- package/skills/system-observability/SKILL.md +169 -157
- package/skills/system-observability/references/memory-incident-triage.md +73 -73
- package/skills/translate-engine/SKILL.md +1 -1
- package/skills/ui-design/SKILL.md +3 -1
- package/skills/uniapp-mall-assets/SKILL.md +170 -170
- package/skills/unity-integration/SKILL.md +155 -155
- package/skills/unity-integration/agents/openai.yaml +4 -4
- package/skills/unity-integration/references/ai-app-delivery.md +103 -103
- package/skills/unity-integration/references/sdk-api.md +82 -82
- package/skills/unity-integration/references/toolbox-migration.md +66 -66
- package/skills/unity-integration/references/webgl-hosting.md +57 -57
- package/skills/v8-api-config/SKILL.md +4 -0
- package/skills/v8-cache-pattern/SKILL.md +306 -306
- package/skills/v8-crud-api/SKILL.md +29 -29
- package/skills/v8-crud-api/references/progressive-01-/346/237/245/350/257/242/345/210/227/350/241/250-/345/210/206/351/241/265.md +56 -56
- package/skills/v8-debugging/SKILL.md +143 -143
- package/skills/v8-explorer-tree/SKILL.md +64 -64
- package/skills/v8-export-import/references/progressive-01-excellayout-/351/253/230/347/272/247/350/207/252/347/224/261/345/270/203/345/261/200.md +201 -201
- package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +118 -118
- package/skills/v8-export-import/references/progressive-03-/345/256/211/345/205/250-/346/200/247/350/203/275/346/263/250/346/204/217.md +32 -32
- package/skills/v8-file-upload/SKILL.md +5 -2
- package/skills/v8-file-upload/references/progressive-01-/345/205/254/346/234/211/346/241/266-vs-/347/247/201/346/234/211/346/241/266.md +2 -2
- package/skills/v8-file-upload/references/progressive-02-office-/346/226/207/344/273/266/345/234/250/347/272/277/347/274/226/350/276/221/347/211/210/346/234/254/345/217/267/350/247/204/345/210/231.md +138 -138
- package/skills/v8-formengine-http/SKILL.md +126 -108
- package/skills/v8-frontend-events/SKILL.md +22 -22
- package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
- package/skills/v8-frontend-events/references/bluetooth-print.md +247 -247
- package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +50 -50
- package/skills/v8-http-integration/SKILL.md +106 -106
- package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +31 -31
- package/skills/v8-http-integration/references/progressive-02-/351/224/231/350/257/257/345/244/204/347/220/206/346/250/241/345/274/217.md +5 -5
- package/skills/v8-image-processing/SKILL.md +190 -190
- package/skills/v8-image-processing/agents/openai.yaml +4 -4
- package/skills/v8-image-processing/references/api-reference.md +623 -623
- package/skills/v8-menu-buttons/SKILL.md +103 -103
- package/skills/v8-menu-buttons/references/progressive-01-2-/346/214/211/351/222/256/345/257/271/350/261/241-schema.md +145 -145
- package/skills/v8-menu-buttons/references/progressive-02-8-/346/250/241/345/274/217-f-/345/220/216/345/217/260/344/273/273/345/212/241/346/214/211/351/222/256-/351/225/277/344/273/273/345/212/241.md +228 -228
- package/skills/v8-menu-buttons/references/progressive-03-10-/345/217/215/346/250/241/345/274/217-/351/201/277/345/205/215.md +43 -43
- package/skills/v8-mongodb/SKILL.md +65 -65
- package/skills/v8-mq-mqtt/SKILL.md +175 -175
- package/skills/v8-mq-mqtt/references/mqtt-production.md +342 -342
- package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +137 -137
- package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
- package/skills/v8-saas-multi-tenant/SKILL.md +223 -223
- package/skills/v8-security/SKILL.md +1 -1
- package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +122 -122
- package/skills/v8-sql-query/SKILL.md +7 -5
- package/skills/v8-table-event/SKILL.md +9 -2
- package/skills/v8-table-event/references/progressive-01-informv8-js-/350/241/250/345/215/225/346/211/223/345/274/200/344/272/213/344/273/266.md +1 -1
- package/skills/v8-table-event/references/progressive-02-/345/211/215/347/253/257/344/272/213/344/273/266/345/220/215-v8-eventname-/345/217/257/350/203/275/347/232/204/345/200/274.md +15 -15
- package/skills/v8-tcp-integration/SKILL.md +1 -1
- package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
- package/skills/v8-template-engine/SKILL.md +59 -59
- package/skills/v8-utilities/SKILL.md +92 -92
- package/skills/v8-utilities/references/client-api-index.md +143 -143
- package/skills/v8-utilities/references/platform-http-routes.md +83 -83
- package/skills/v8-utilities/references/server-api-index.md +74 -74
- package/skills/v8-workflow/SKILL.md +13 -4
- package/skills/vision-engine/SKILL.md +19 -19
- package/skills/vision-engine/references/architecture-and-acceptance.md +34 -34
- package/skills/workspace-conventions/SKILL.md +35 -12
- package/skills/workspace-conventions/references/progressive-01-/347/211/210/346/234/254/346/233/264/346/226/260/346/227/245/345/277/227/344/277/235/346/212/244/350/247/204/345/210/231-/345/274/272/345/210/266.md +2 -2
- package/skills/workspace-conventions/references/progressive-02-microi-net-api-/346/234/254/345/234/260/345/220/257/345/212/250/347/272/246/345/256/232.md +179 -179
- package/skills/workspace-conventions/references/progressive-03-cli-/344/270/216-ide-/346/217/222/344/273/266/351/224/231/347/211/210/345/205/261/345/255/230/347/272/246/345/256/232.md +21 -21
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: production-readonly-audit
|
|
3
|
-
description: Microi 正式环境只读业务巡检规范。用于在不改动线上数据的前提下,检查会员、余额、订单、库存卡、奖励、积分、兑换金额、日志和结算正确性等生产业务数据。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
> **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
|
|
7
|
-
|
|
8
|
-
# Microi 正式环境只读业务巡检
|
|
9
|
-
|
|
10
|
-
## 核心边界
|
|
11
|
-
|
|
12
|
-
- 正式环境已有真实用户、余额、订单、卡券或收益数据时,线上巡检默认是只读任务。
|
|
13
|
-
- 优先使用目标租户 MCP 的读表、查结构、查日志工具;不得调用新增、更新、删除、清库、重算、补发、扣减接口。
|
|
14
|
-
- 如果用户要求“检查是否正常”,先输出异常证据、影响范围、建议修复方式和回读验证方式;没有明确授权前,不修线上业务数据。
|
|
15
|
-
- MCP 不可用时,可使用官方后台登录 token + FormEngine `getTableData` 做只读兜底,但必须在结果中说明“非 MCP,只读 API 兜底”;不得调用 `add/upt/del/run` 维护接口。
|
|
16
|
-
- 任何资金/资产异常即使明显,也只能先报告。获得修复授权后,必须小范围条件更新,并在方案文档留下 SQL/接口说明、执行时间、影响行数和回读结果。
|
|
17
|
-
|
|
18
|
-
## 商城类固定巡检表
|
|
19
|
-
|
|
20
|
-
- 会员与团队:`mall_member`
|
|
21
|
-
- 提货卡与挂单:`mall_stock_card`、`mall_stock_card_listing`
|
|
22
|
-
- 抢购/约单/商品订单:`mall_buy_order`、`mall_appointment_order`、`mall_redeem_order`
|
|
23
|
-
- 积分/兑换金/奖励:`mall_point_log`、`mall_redeem_money_log`、`mall_reward_log`
|
|
24
|
-
- 上架服务费:`mall_storage_fee_order`
|
|
25
|
-
- 提货卡扣除/流转:`mall_stock_card_redeem`、`mall_stock_card_history`
|
|
26
|
-
- 规则配置:`mall_system_config`
|
|
27
|
-
|
|
28
|
-
## 固定检查项
|
|
29
|
-
|
|
30
|
-
- 提货卡:`CardNo` 必须为 6 位大写字母+数字;金额不得为负;`OwnerId` 必须存在;同一卡只能有一条活跃挂单;状态机必须和挂单/订单一致。
|
|
31
|
-
- 抢购订单:`PendingPay` 必须有关联锁定卡与付款截止时间;`Paid` 必须有支付凭证和付款时间;`Confirmed/Completed` 必须确认卡 OwnerId 已转为 BuyerId,并有对应流转历史。
|
|
32
|
-
- 上架服务费:服务费应等于持有价 × `PlatformServiceRate`;已支付服务费必须有对应负数积分流水。
|
|
33
|
-
- 推荐奖励:`Amount = BaseAmount × Rate / 100`;直推、间推、品牌补贴合计不得超过 `MaxPerformanceCommissionRate`;`Pending` 不得生成积分流水或增加余额;`Settled` 必须有积分流水并真实到账。
|
|
34
|
-
- 商品支付:提货卡专区订单必须生成 `mall_stock_card_redeem` 并扣卡余额;兑换金专区订单必须生成负数 `mall_redeem_money_log` 并扣兑换金。
|
|
35
|
-
- 会员资产:会员页展示的提货卡资产应以当前名下有效提货卡 `HoldPrice/StockValue` 汇总为准;如 `mall_member.StockValue` 是冗余字段,必须与实际资产保持一致。
|
|
36
|
-
|
|
37
|
-
## 输出要求
|
|
38
|
-
|
|
39
|
-
- 报告必须区分“硬错误”和“风险警告”。
|
|
40
|
-
- 不输出密码、token、支付凭证图片原文、完整身份证等敏感信息。
|
|
41
|
-
- 报告需包含巡检通道、读取表数量、状态分布、异常数量、核心异常证据、未覆盖范围。
|
|
1
|
+
---
|
|
2
|
+
name: production-readonly-audit
|
|
3
|
+
description: Microi 正式环境只读业务巡检规范。用于在不改动线上数据的前提下,检查会员、余额、订单、库存卡、奖励、积分、兑换金额、日志和结算正确性等生产业务数据。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
|
|
7
|
+
|
|
8
|
+
# Microi 正式环境只读业务巡检
|
|
9
|
+
|
|
10
|
+
## 核心边界
|
|
11
|
+
|
|
12
|
+
- 正式环境已有真实用户、余额、订单、卡券或收益数据时,线上巡检默认是只读任务。
|
|
13
|
+
- 优先使用目标租户 MCP 的读表、查结构、查日志工具;不得调用新增、更新、删除、清库、重算、补发、扣减接口。
|
|
14
|
+
- 如果用户要求“检查是否正常”,先输出异常证据、影响范围、建议修复方式和回读验证方式;没有明确授权前,不修线上业务数据。
|
|
15
|
+
- MCP 不可用时,可使用官方后台登录 token + FormEngine `getTableData` 做只读兜底,但必须在结果中说明“非 MCP,只读 API 兜底”;不得调用 `add/upt/del/run` 维护接口。
|
|
16
|
+
- 任何资金/资产异常即使明显,也只能先报告。获得修复授权后,必须小范围条件更新,并在方案文档留下 SQL/接口说明、执行时间、影响行数和回读结果。
|
|
17
|
+
|
|
18
|
+
## 商城类固定巡检表
|
|
19
|
+
|
|
20
|
+
- 会员与团队:`mall_member`
|
|
21
|
+
- 提货卡与挂单:`mall_stock_card`、`mall_stock_card_listing`
|
|
22
|
+
- 抢购/约单/商品订单:`mall_buy_order`、`mall_appointment_order`、`mall_redeem_order`
|
|
23
|
+
- 积分/兑换金/奖励:`mall_point_log`、`mall_redeem_money_log`、`mall_reward_log`
|
|
24
|
+
- 上架服务费:`mall_storage_fee_order`
|
|
25
|
+
- 提货卡扣除/流转:`mall_stock_card_redeem`、`mall_stock_card_history`
|
|
26
|
+
- 规则配置:`mall_system_config`
|
|
27
|
+
|
|
28
|
+
## 固定检查项
|
|
29
|
+
|
|
30
|
+
- 提货卡:`CardNo` 必须为 6 位大写字母+数字;金额不得为负;`OwnerId` 必须存在;同一卡只能有一条活跃挂单;状态机必须和挂单/订单一致。
|
|
31
|
+
- 抢购订单:`PendingPay` 必须有关联锁定卡与付款截止时间;`Paid` 必须有支付凭证和付款时间;`Confirmed/Completed` 必须确认卡 OwnerId 已转为 BuyerId,并有对应流转历史。
|
|
32
|
+
- 上架服务费:服务费应等于持有价 × `PlatformServiceRate`;已支付服务费必须有对应负数积分流水。
|
|
33
|
+
- 推荐奖励:`Amount = BaseAmount × Rate / 100`;直推、间推、品牌补贴合计不得超过 `MaxPerformanceCommissionRate`;`Pending` 不得生成积分流水或增加余额;`Settled` 必须有积分流水并真实到账。
|
|
34
|
+
- 商品支付:提货卡专区订单必须生成 `mall_stock_card_redeem` 并扣卡余额;兑换金专区订单必须生成负数 `mall_redeem_money_log` 并扣兑换金。
|
|
35
|
+
- 会员资产:会员页展示的提货卡资产应以当前名下有效提货卡 `HoldPrice/StockValue` 汇总为准;如 `mall_member.StockValue` 是冗余字段,必须与实际资产保持一致。
|
|
36
|
+
|
|
37
|
+
## 输出要求
|
|
38
|
+
|
|
39
|
+
- 报告必须区分“硬错误”和“风险警告”。
|
|
40
|
+
- 不输出密码、token、支付凭证图片原文、完整身份证等敏感信息。
|
|
41
|
+
- 报告需包含巡检通道、读取表数量、状态分布、异常数量、核心异常证据、未覆盖范围。
|
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: report-engine
|
|
3
|
-
description: Microi 报表引擎设计与验收规范。用于 Rpt_Report 虚拟表格、数据源、报表字段、查询/增删改接口替换、统计聚合、导出、行级权限和界面引擎图表。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
> **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
|
|
7
|
-
|
|
8
|
-
# Microi 报表引擎
|
|
9
|
-
|
|
10
|
-
## 定位
|
|
11
|
-
|
|
12
|
-
报表引擎以 `Rpt_Report`、报表字段配置和数据源引擎组成虚拟表格。表格展示使用报表引擎;ECharts 图表和仪表盘使用界面引擎。报表字段是虚拟字段,不能假设存在同名物理表列。
|
|
13
|
-
|
|
14
|
-
## 建模顺序
|
|
15
|
-
|
|
16
|
-
1. 明确指标口径、时间粒度、维度、权限和导出上限。
|
|
17
|
-
2. 创建只读数据源(SQL/V8/JSON),使用稳定 `DataSourceKey`。
|
|
18
|
-
3. 创建报表并绑定数据源。
|
|
19
|
-
4. 配置字段 `Name/Label/Component`、查询列、排序、格式和表内编辑。
|
|
20
|
-
5. 如需写操作,为查询/新增/修改/删除分别绑定专用接口引擎。
|
|
21
|
-
6. 菜单只暴露用户需要的报表入口,配置数据范围。
|
|
22
|
-
|
|
23
|
-
## 权限与 SQL
|
|
24
|
-
|
|
25
|
-
- 报表数据源必须应用当前租户和服务端用户数据范围。菜单权限不会自动保护任意聚合 SQL。
|
|
26
|
-
- 聚合结果也可能泄露敏感信息;小样本、人员薪资、客户金额等需最小分组阈值或字段脱敏。
|
|
27
|
-
- SQL 动态值参数化,维度/排序/指标使用白名单映射,不接收原始 SQL、列名或 `GROUP BY`。
|
|
28
|
-
- 管理员专用平台表不能作为普通用户报表数据源;只读委托表只有获得真实 `Read` 授权后才能查询,`mic_page/mic_print` 继续按角色权限处理。
|
|
29
|
-
|
|
30
|
-
## 写入型报表
|
|
31
|
-
|
|
32
|
-
统计报表通常来自多表。新增、修改、删除必须调用接口引擎,在一个事务中完成校验与写入:
|
|
33
|
-
|
|
34
|
-
```js
|
|
35
|
-
var result = await V8.ApiEngine.Run('report_adjust_inventory', {
|
|
36
|
-
RowId: V8.Form.Id,
|
|
37
|
-
ExpectedVersion: V8.Form.Version,
|
|
38
|
-
Quantity: Number(V8.Form.Quantity || 0)
|
|
39
|
-
});
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
接口引擎重新校验角色、记录范围、当前版本和业务状态。前端隐藏按钮、只读字段或报表行数据不能作为授权。
|
|
43
|
-
|
|
44
|
-
## 性能
|
|
45
|
-
|
|
46
|
-
- 默认分页,限制最大页数/每页行数/导出行数。
|
|
47
|
-
- 大聚合在数据库建立合适索引或使用预聚合表;不要每次全表扫描。
|
|
48
|
-
- 缓存 Key 包含 `OsClient + ReportId + 权限主体/授权版本 + 查询参数哈希`。
|
|
49
|
-
- 导出走后台任务;生成文件放私有桶并绑定菜单、记录和字段访问上下文。
|
|
50
|
-
|
|
51
|
-
## MCP 工作流
|
|
52
|
-
|
|
53
|
-
1. `microi_get_db_schema` 读取 `Rpt_Report`、数据源、字段、菜单和业务表。
|
|
54
|
-
2. `microi_save_data_source` 保存数据源并回读。
|
|
55
|
-
3. 创建/更新报表及字段;复杂写操作先创建接口引擎。
|
|
56
|
-
4. 设置菜单与角色权限。
|
|
57
|
-
5. 用真实普通角色验收列表、筛选、汇总、详情、编辑与导出。
|
|
58
|
-
|
|
59
|
-
## 图表
|
|
60
|
-
|
|
61
|
-
界面引擎图表应调用返回稳定 `{Code, Data}` 的统计接口引擎,不在前端下载明细再聚合。统计接口设置时间范围、维度和最大点数,返回空数据时保持稳定结构。
|
|
62
|
-
|
|
63
|
-
## 验收清单
|
|
64
|
-
|
|
65
|
-
- [ ] 指标口径、时区和小数精度明确
|
|
66
|
-
- [ ] 当前租户与行级范围在数据库查询中生效
|
|
67
|
-
- [ ] 虚拟字段与物理字段没有混用
|
|
68
|
-
- [ ] 写操作走事务接口引擎并校验并发版本
|
|
69
|
-
- [ ] 分页、聚合、导出均有资源上限
|
|
70
|
-
- [ ] 缓存按租户和权限隔离
|
|
71
|
-
- [ ] 普通角色、无权限角色、跨租户负向测试通过
|
|
1
|
+
---
|
|
2
|
+
name: report-engine
|
|
3
|
+
description: Microi 报表引擎设计与验收规范。用于 Rpt_Report 虚拟表格、数据源、报表字段、查询/增删改接口替换、统计聚合、导出、行级权限和界面引擎图表。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **Codex 非阻塞自动更新:** 当前宿主为 Codex 时,吾码 CLI、Codex 插件与工作区 AI/MCP 由后台自动更新;需要诊断时读取 `../microi-codex-installer/SKILL.md`。更新失败、等待空闲或尚未重载均不得阻断当前、正在进行或新建任务。非 Codex 宿主跳过此项。
|
|
7
|
+
|
|
8
|
+
# Microi 报表引擎
|
|
9
|
+
|
|
10
|
+
## 定位
|
|
11
|
+
|
|
12
|
+
报表引擎以 `Rpt_Report`、报表字段配置和数据源引擎组成虚拟表格。表格展示使用报表引擎;ECharts 图表和仪表盘使用界面引擎。报表字段是虚拟字段,不能假设存在同名物理表列。
|
|
13
|
+
|
|
14
|
+
## 建模顺序
|
|
15
|
+
|
|
16
|
+
1. 明确指标口径、时间粒度、维度、权限和导出上限。
|
|
17
|
+
2. 创建只读数据源(SQL/V8/JSON),使用稳定 `DataSourceKey`。
|
|
18
|
+
3. 创建报表并绑定数据源。
|
|
19
|
+
4. 配置字段 `Name/Label/Component`、查询列、排序、格式和表内编辑。
|
|
20
|
+
5. 如需写操作,为查询/新增/修改/删除分别绑定专用接口引擎。
|
|
21
|
+
6. 菜单只暴露用户需要的报表入口,配置数据范围。
|
|
22
|
+
|
|
23
|
+
## 权限与 SQL
|
|
24
|
+
|
|
25
|
+
- 报表数据源必须应用当前租户和服务端用户数据范围。菜单权限不会自动保护任意聚合 SQL。
|
|
26
|
+
- 聚合结果也可能泄露敏感信息;小样本、人员薪资、客户金额等需最小分组阈值或字段脱敏。
|
|
27
|
+
- SQL 动态值参数化,维度/排序/指标使用白名单映射,不接收原始 SQL、列名或 `GROUP BY`。
|
|
28
|
+
- 管理员专用平台表不能作为普通用户报表数据源;只读委托表只有获得真实 `Read` 授权后才能查询,`mic_page/mic_print` 继续按角色权限处理。
|
|
29
|
+
|
|
30
|
+
## 写入型报表
|
|
31
|
+
|
|
32
|
+
统计报表通常来自多表。新增、修改、删除必须调用接口引擎,在一个事务中完成校验与写入:
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
var result = await V8.ApiEngine.Run('report_adjust_inventory', {
|
|
36
|
+
RowId: V8.Form.Id,
|
|
37
|
+
ExpectedVersion: V8.Form.Version,
|
|
38
|
+
Quantity: Number(V8.Form.Quantity || 0)
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
接口引擎重新校验角色、记录范围、当前版本和业务状态。前端隐藏按钮、只读字段或报表行数据不能作为授权。
|
|
43
|
+
|
|
44
|
+
## 性能
|
|
45
|
+
|
|
46
|
+
- 默认分页,限制最大页数/每页行数/导出行数。
|
|
47
|
+
- 大聚合在数据库建立合适索引或使用预聚合表;不要每次全表扫描。
|
|
48
|
+
- 缓存 Key 包含 `OsClient + ReportId + 权限主体/授权版本 + 查询参数哈希`。
|
|
49
|
+
- 导出走后台任务;生成文件放私有桶并绑定菜单、记录和字段访问上下文。
|
|
50
|
+
|
|
51
|
+
## MCP 工作流
|
|
52
|
+
|
|
53
|
+
1. `microi_get_db_schema` 读取 `Rpt_Report`、数据源、字段、菜单和业务表。
|
|
54
|
+
2. `microi_save_data_source` 保存数据源并回读。
|
|
55
|
+
3. 创建/更新报表及字段;复杂写操作先创建接口引擎。
|
|
56
|
+
4. 设置菜单与角色权限。
|
|
57
|
+
5. 用真实普通角色验收列表、筛选、汇总、详情、编辑与导出。
|
|
58
|
+
|
|
59
|
+
## 图表
|
|
60
|
+
|
|
61
|
+
界面引擎图表应调用返回稳定 `{Code, Data}` 的统计接口引擎,不在前端下载明细再聚合。统计接口设置时间范围、维度和最大点数,返回空数据时保持稳定结构。
|
|
62
|
+
|
|
63
|
+
## 验收清单
|
|
64
|
+
|
|
65
|
+
- [ ] 指标口径、时区和小数精度明确
|
|
66
|
+
- [ ] 当前租户与行级范围在数据库查询中生效
|
|
67
|
+
- [ ] 虚拟字段与物理字段没有混用
|
|
68
|
+
- [ ] 写操作走事务接口引擎并校验并发版本
|
|
69
|
+
- [ ] 分页、聚合、导出均有资源上限
|
|
70
|
+
- [ ] 缓存按租户和权限隔离
|
|
71
|
+
- [ ] 普通角色、无权限角色、跨租户负向测试通过
|
|
@@ -1,204 +1,204 @@
|
|
|
1
|
-
import crypto from 'node:crypto'
|
|
2
|
-
import fs from 'node:fs'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { fileURLToPath } from 'node:url'
|
|
5
|
-
|
|
6
|
-
const skillsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
7
|
-
const manifestPath = path.join(skillsRoot, '.progressive-disclosure-manifest.json')
|
|
8
|
-
const maxEntryLines = 285
|
|
9
|
-
const keepBudgetLines = 170
|
|
10
|
-
const referenceBudgetLines = 245
|
|
11
|
-
|
|
12
|
-
const hash = (value) => crypto.createHash('sha256').update(value).digest('hex')
|
|
13
|
-
const lineCount = (value) => value.length === 0 ? 0 : value.split(/\r?\n/).length
|
|
14
|
-
const slug = (value) => value
|
|
15
|
-
.replace(/^#+\s*/, '')
|
|
16
|
-
.replace(/[`*_]/g, '')
|
|
17
|
-
.trim()
|
|
18
|
-
.toLowerCase()
|
|
19
|
-
.replace(/[^\p{L}\p{N}]+/gu, '-')
|
|
20
|
-
.replace(/^-|-$/g, '')
|
|
21
|
-
.slice(0, 48) || 'reference'
|
|
22
|
-
|
|
23
|
-
const splitAtHeading = (value, headingPattern) => {
|
|
24
|
-
const matches = [...value.matchAll(headingPattern)]
|
|
25
|
-
if (matches.length <= 1) return [value]
|
|
26
|
-
const parts = []
|
|
27
|
-
if (matches[0].index > 0) parts.push(value.slice(0, matches[0].index))
|
|
28
|
-
for (let index = 0; index < matches.length; index += 1) {
|
|
29
|
-
parts.push(value.slice(matches[index].index, matches[index + 1]?.index ?? value.length))
|
|
30
|
-
}
|
|
31
|
-
return parts.filter(Boolean)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const splitOversized = (value, eol) => {
|
|
35
|
-
if (lineCount(value) <= referenceBudgetLines) return [value]
|
|
36
|
-
let parts = splitAtHeading(value, /^###\s+.+$/gm)
|
|
37
|
-
if (parts.length === 1) parts = splitAtHeading(value, /^####\s+.+$/gm)
|
|
38
|
-
const output = []
|
|
39
|
-
for (const part of parts) {
|
|
40
|
-
if (lineCount(part) <= referenceBudgetLines) {
|
|
41
|
-
output.push(part)
|
|
42
|
-
continue
|
|
43
|
-
}
|
|
44
|
-
const lines = part.split(/\r?\n/)
|
|
45
|
-
let start = 0
|
|
46
|
-
while (start < lines.length) {
|
|
47
|
-
let end = Math.min(start + referenceBudgetLines - 8, lines.length)
|
|
48
|
-
if (end < lines.length) {
|
|
49
|
-
for (let cursor = end; cursor > start + 40; cursor -= 1) {
|
|
50
|
-
if (lines[cursor - 1].trim() === '') {
|
|
51
|
-
end = cursor
|
|
52
|
-
break
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const hasTrailingBreak = end < lines.length || part.endsWith(eol)
|
|
57
|
-
output.push(lines.slice(start, end).join(eol) + (hasTrailingBreak ? eol : ''))
|
|
58
|
-
start = end
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return output
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const headingOf = (value, fallback) => value.match(/^#{2,4}\s+(.+)$/m)?.[1]?.trim() || fallback
|
|
65
|
-
|
|
66
|
-
const skillDirectories = fs.readdirSync(skillsRoot, { withFileTypes: true })
|
|
67
|
-
.filter((entry) => entry.isDirectory() && fs.existsSync(path.join(skillsRoot, entry.name, 'SKILL.md')))
|
|
68
|
-
.map((entry) => entry.name)
|
|
69
|
-
.sort()
|
|
70
|
-
|
|
71
|
-
const manifest = {
|
|
72
|
-
version: 1,
|
|
73
|
-
generatedAt: new Date().toISOString(),
|
|
74
|
-
policy: {
|
|
75
|
-
maxEntryLines,
|
|
76
|
-
referenceBudgetLines,
|
|
77
|
-
note: 'Original prefix and ordered chunks reconstruct the exact pre-split SKILL.md bytes.'
|
|
78
|
-
},
|
|
79
|
-
skills: {}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
for (const skillName of skillDirectories) {
|
|
83
|
-
const skillPath = path.join(skillsRoot, skillName, 'SKILL.md')
|
|
84
|
-
const original = fs.readFileSync(skillPath, 'utf8')
|
|
85
|
-
if (lineCount(original) <= 300) continue
|
|
86
|
-
if (original.includes('<!-- microi-progressive:begin -->')) {
|
|
87
|
-
throw new Error(`${skillName} is already optimized; validate instead of running twice`)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const eol = original.includes('\r\n') ? '\r\n' : '\n'
|
|
91
|
-
const sectionMatches = [...original.matchAll(/^##\s+.+$/gm)]
|
|
92
|
-
if (sectionMatches.length < 2) {
|
|
93
|
-
throw new Error(`${skillName} needs at least two level-2 sections for safe semantic splitting`)
|
|
94
|
-
}
|
|
95
|
-
const prefix = original.slice(0, sectionMatches[0].index)
|
|
96
|
-
const sections = sectionMatches.map((match, index) => ({
|
|
97
|
-
heading: match[0].replace(/^##\s+/, '').trim(),
|
|
98
|
-
raw: original.slice(match.index, sectionMatches[index + 1]?.index ?? original.length),
|
|
99
|
-
}))
|
|
100
|
-
|
|
101
|
-
const kept = []
|
|
102
|
-
const moved = []
|
|
103
|
-
let keptLines = lineCount(prefix)
|
|
104
|
-
for (const section of sections) {
|
|
105
|
-
// Keep one contiguous prefix only. Picking a later short section after an
|
|
106
|
-
// earlier section was moved would reorder the original knowledge stream.
|
|
107
|
-
if (moved.length === 0 && (kept.length === 0 || keptLines + lineCount(section.raw) <= keepBudgetLines)) {
|
|
108
|
-
kept.push(section)
|
|
109
|
-
keptLines += lineCount(section.raw)
|
|
110
|
-
} else {
|
|
111
|
-
moved.push(section)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
if (moved.length === 0) {
|
|
115
|
-
moved.push(kept.pop())
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const fragments = moved.flatMap((section) => splitOversized(section.raw, eol).map((raw, index) => ({
|
|
119
|
-
raw,
|
|
120
|
-
heading: headingOf(raw, `${section.heading} (${index + 1})`),
|
|
121
|
-
})))
|
|
122
|
-
|
|
123
|
-
const referenceGroups = []
|
|
124
|
-
let current = []
|
|
125
|
-
let currentLines = 5
|
|
126
|
-
for (const fragment of fragments) {
|
|
127
|
-
const cost = lineCount(fragment.raw) + 3
|
|
128
|
-
if (current.length && currentLines + cost > referenceBudgetLines) {
|
|
129
|
-
referenceGroups.push(current)
|
|
130
|
-
current = []
|
|
131
|
-
currentLines = 5
|
|
132
|
-
}
|
|
133
|
-
current.push(fragment)
|
|
134
|
-
currentLines += cost
|
|
135
|
-
}
|
|
136
|
-
if (current.length) referenceGroups.push(current)
|
|
137
|
-
|
|
138
|
-
const referencesDirectory = path.join(skillsRoot, skillName, 'references')
|
|
139
|
-
fs.mkdirSync(referencesDirectory, { recursive: true })
|
|
140
|
-
const chunkRecords = []
|
|
141
|
-
let ordinal = 0
|
|
142
|
-
const wrapChunk = (raw, destination) => {
|
|
143
|
-
const id = `${skillName}-${String(ordinal).padStart(3, '0')}`
|
|
144
|
-
const record = {
|
|
145
|
-
id,
|
|
146
|
-
ordinal,
|
|
147
|
-
destination: destination.replaceAll('\\', '/'),
|
|
148
|
-
sha256: hash(raw),
|
|
149
|
-
lines: lineCount(raw),
|
|
150
|
-
heading: headingOf(raw, skillName),
|
|
151
|
-
}
|
|
152
|
-
chunkRecords.push(record)
|
|
153
|
-
ordinal += 1
|
|
154
|
-
return `<!-- microi-progressive:chunk id=${id} sha256=${record.sha256} -->${eol}${raw}<!-- /microi-progressive:chunk -->${eol}`
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const entryChunks = kept.map((section) => wrapChunk(section.raw, `${skillName}/SKILL.md`)).join('')
|
|
158
|
-
const referenceRecords = []
|
|
159
|
-
referenceGroups.forEach((group, index) => {
|
|
160
|
-
const fileName = `progressive-${String(index + 1).padStart(2, '0')}-${slug(group[0].heading)}.md`
|
|
161
|
-
const relative = `references/${fileName}`
|
|
162
|
-
const destination = `${skillName}/${relative}`
|
|
163
|
-
const body = group.map((fragment) => wrapChunk(fragment.raw, destination)).join('')
|
|
164
|
-
const title = `# ${skillName} 详细参考 ${index + 1}${eol}${eol}`
|
|
165
|
-
const note = `> 按需读取;本文件由 SKILL.md 的原章节无损拆分。${eol}${eol}`
|
|
166
|
-
fs.writeFileSync(path.join(referencesDirectory, fileName), title + note + body, 'utf8')
|
|
167
|
-
referenceRecords.push({
|
|
168
|
-
path: relative,
|
|
169
|
-
headings: group.map((fragment) => fragment.heading),
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
const routerLines = [
|
|
174
|
-
'## 详细参考路由(渐进披露)',
|
|
175
|
-
'',
|
|
176
|
-
'仅在当前任务涉及对应主题时读取;下列文件合计保留了原 SKILL.md 的全部详细知识。',
|
|
177
|
-
'',
|
|
178
|
-
...referenceRecords.map((reference) =>
|
|
179
|
-
`- [${reference.path}](${reference.path}):${reference.headings.join(';')}`),
|
|
180
|
-
'',
|
|
181
|
-
].join(eol)
|
|
182
|
-
const rewritten = prefix
|
|
183
|
-
+ `<!-- microi-progressive:begin -->${eol}`
|
|
184
|
-
+ entryChunks
|
|
185
|
-
+ routerLines
|
|
186
|
-
+ `<!-- microi-progressive:end -->${eol}`
|
|
187
|
-
if (lineCount(rewritten) > maxEntryLines) {
|
|
188
|
-
throw new Error(`${skillName} entry remains ${lineCount(rewritten)} lines; lower keepBudgetLines`)
|
|
189
|
-
}
|
|
190
|
-
fs.writeFileSync(skillPath, rewritten, 'utf8')
|
|
191
|
-
|
|
192
|
-
manifest.skills[skillName] = {
|
|
193
|
-
originalSha256: hash(original),
|
|
194
|
-
originalLines: lineCount(original),
|
|
195
|
-
prefixSha256: hash(prefix),
|
|
196
|
-
prefixBytes: Buffer.byteLength(prefix),
|
|
197
|
-
entryLines: lineCount(rewritten),
|
|
198
|
-
chunks: chunkRecords,
|
|
199
|
-
references: referenceRecords,
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
|
|
204
|
-
console.log(`Optimized ${Object.keys(manifest.skills).length} skills; manifest: ${manifestPath}`)
|
|
1
|
+
import crypto from 'node:crypto'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
const skillsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
7
|
+
const manifestPath = path.join(skillsRoot, '.progressive-disclosure-manifest.json')
|
|
8
|
+
const maxEntryLines = 285
|
|
9
|
+
const keepBudgetLines = 170
|
|
10
|
+
const referenceBudgetLines = 245
|
|
11
|
+
|
|
12
|
+
const hash = (value) => crypto.createHash('sha256').update(value).digest('hex')
|
|
13
|
+
const lineCount = (value) => value.length === 0 ? 0 : value.split(/\r?\n/).length
|
|
14
|
+
const slug = (value) => value
|
|
15
|
+
.replace(/^#+\s*/, '')
|
|
16
|
+
.replace(/[`*_]/g, '')
|
|
17
|
+
.trim()
|
|
18
|
+
.toLowerCase()
|
|
19
|
+
.replace(/[^\p{L}\p{N}]+/gu, '-')
|
|
20
|
+
.replace(/^-|-$/g, '')
|
|
21
|
+
.slice(0, 48) || 'reference'
|
|
22
|
+
|
|
23
|
+
const splitAtHeading = (value, headingPattern) => {
|
|
24
|
+
const matches = [...value.matchAll(headingPattern)]
|
|
25
|
+
if (matches.length <= 1) return [value]
|
|
26
|
+
const parts = []
|
|
27
|
+
if (matches[0].index > 0) parts.push(value.slice(0, matches[0].index))
|
|
28
|
+
for (let index = 0; index < matches.length; index += 1) {
|
|
29
|
+
parts.push(value.slice(matches[index].index, matches[index + 1]?.index ?? value.length))
|
|
30
|
+
}
|
|
31
|
+
return parts.filter(Boolean)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const splitOversized = (value, eol) => {
|
|
35
|
+
if (lineCount(value) <= referenceBudgetLines) return [value]
|
|
36
|
+
let parts = splitAtHeading(value, /^###\s+.+$/gm)
|
|
37
|
+
if (parts.length === 1) parts = splitAtHeading(value, /^####\s+.+$/gm)
|
|
38
|
+
const output = []
|
|
39
|
+
for (const part of parts) {
|
|
40
|
+
if (lineCount(part) <= referenceBudgetLines) {
|
|
41
|
+
output.push(part)
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
const lines = part.split(/\r?\n/)
|
|
45
|
+
let start = 0
|
|
46
|
+
while (start < lines.length) {
|
|
47
|
+
let end = Math.min(start + referenceBudgetLines - 8, lines.length)
|
|
48
|
+
if (end < lines.length) {
|
|
49
|
+
for (let cursor = end; cursor > start + 40; cursor -= 1) {
|
|
50
|
+
if (lines[cursor - 1].trim() === '') {
|
|
51
|
+
end = cursor
|
|
52
|
+
break
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const hasTrailingBreak = end < lines.length || part.endsWith(eol)
|
|
57
|
+
output.push(lines.slice(start, end).join(eol) + (hasTrailingBreak ? eol : ''))
|
|
58
|
+
start = end
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return output
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const headingOf = (value, fallback) => value.match(/^#{2,4}\s+(.+)$/m)?.[1]?.trim() || fallback
|
|
65
|
+
|
|
66
|
+
const skillDirectories = fs.readdirSync(skillsRoot, { withFileTypes: true })
|
|
67
|
+
.filter((entry) => entry.isDirectory() && fs.existsSync(path.join(skillsRoot, entry.name, 'SKILL.md')))
|
|
68
|
+
.map((entry) => entry.name)
|
|
69
|
+
.sort()
|
|
70
|
+
|
|
71
|
+
const manifest = {
|
|
72
|
+
version: 1,
|
|
73
|
+
generatedAt: new Date().toISOString(),
|
|
74
|
+
policy: {
|
|
75
|
+
maxEntryLines,
|
|
76
|
+
referenceBudgetLines,
|
|
77
|
+
note: 'Original prefix and ordered chunks reconstruct the exact pre-split SKILL.md bytes.'
|
|
78
|
+
},
|
|
79
|
+
skills: {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const skillName of skillDirectories) {
|
|
83
|
+
const skillPath = path.join(skillsRoot, skillName, 'SKILL.md')
|
|
84
|
+
const original = fs.readFileSync(skillPath, 'utf8')
|
|
85
|
+
if (lineCount(original) <= 300) continue
|
|
86
|
+
if (original.includes('<!-- microi-progressive:begin -->')) {
|
|
87
|
+
throw new Error(`${skillName} is already optimized; validate instead of running twice`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const eol = original.includes('\r\n') ? '\r\n' : '\n'
|
|
91
|
+
const sectionMatches = [...original.matchAll(/^##\s+.+$/gm)]
|
|
92
|
+
if (sectionMatches.length < 2) {
|
|
93
|
+
throw new Error(`${skillName} needs at least two level-2 sections for safe semantic splitting`)
|
|
94
|
+
}
|
|
95
|
+
const prefix = original.slice(0, sectionMatches[0].index)
|
|
96
|
+
const sections = sectionMatches.map((match, index) => ({
|
|
97
|
+
heading: match[0].replace(/^##\s+/, '').trim(),
|
|
98
|
+
raw: original.slice(match.index, sectionMatches[index + 1]?.index ?? original.length),
|
|
99
|
+
}))
|
|
100
|
+
|
|
101
|
+
const kept = []
|
|
102
|
+
const moved = []
|
|
103
|
+
let keptLines = lineCount(prefix)
|
|
104
|
+
for (const section of sections) {
|
|
105
|
+
// Keep one contiguous prefix only. Picking a later short section after an
|
|
106
|
+
// earlier section was moved would reorder the original knowledge stream.
|
|
107
|
+
if (moved.length === 0 && (kept.length === 0 || keptLines + lineCount(section.raw) <= keepBudgetLines)) {
|
|
108
|
+
kept.push(section)
|
|
109
|
+
keptLines += lineCount(section.raw)
|
|
110
|
+
} else {
|
|
111
|
+
moved.push(section)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (moved.length === 0) {
|
|
115
|
+
moved.push(kept.pop())
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const fragments = moved.flatMap((section) => splitOversized(section.raw, eol).map((raw, index) => ({
|
|
119
|
+
raw,
|
|
120
|
+
heading: headingOf(raw, `${section.heading} (${index + 1})`),
|
|
121
|
+
})))
|
|
122
|
+
|
|
123
|
+
const referenceGroups = []
|
|
124
|
+
let current = []
|
|
125
|
+
let currentLines = 5
|
|
126
|
+
for (const fragment of fragments) {
|
|
127
|
+
const cost = lineCount(fragment.raw) + 3
|
|
128
|
+
if (current.length && currentLines + cost > referenceBudgetLines) {
|
|
129
|
+
referenceGroups.push(current)
|
|
130
|
+
current = []
|
|
131
|
+
currentLines = 5
|
|
132
|
+
}
|
|
133
|
+
current.push(fragment)
|
|
134
|
+
currentLines += cost
|
|
135
|
+
}
|
|
136
|
+
if (current.length) referenceGroups.push(current)
|
|
137
|
+
|
|
138
|
+
const referencesDirectory = path.join(skillsRoot, skillName, 'references')
|
|
139
|
+
fs.mkdirSync(referencesDirectory, { recursive: true })
|
|
140
|
+
const chunkRecords = []
|
|
141
|
+
let ordinal = 0
|
|
142
|
+
const wrapChunk = (raw, destination) => {
|
|
143
|
+
const id = `${skillName}-${String(ordinal).padStart(3, '0')}`
|
|
144
|
+
const record = {
|
|
145
|
+
id,
|
|
146
|
+
ordinal,
|
|
147
|
+
destination: destination.replaceAll('\\', '/'),
|
|
148
|
+
sha256: hash(raw),
|
|
149
|
+
lines: lineCount(raw),
|
|
150
|
+
heading: headingOf(raw, skillName),
|
|
151
|
+
}
|
|
152
|
+
chunkRecords.push(record)
|
|
153
|
+
ordinal += 1
|
|
154
|
+
return `<!-- microi-progressive:chunk id=${id} sha256=${record.sha256} -->${eol}${raw}<!-- /microi-progressive:chunk -->${eol}`
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const entryChunks = kept.map((section) => wrapChunk(section.raw, `${skillName}/SKILL.md`)).join('')
|
|
158
|
+
const referenceRecords = []
|
|
159
|
+
referenceGroups.forEach((group, index) => {
|
|
160
|
+
const fileName = `progressive-${String(index + 1).padStart(2, '0')}-${slug(group[0].heading)}.md`
|
|
161
|
+
const relative = `references/${fileName}`
|
|
162
|
+
const destination = `${skillName}/${relative}`
|
|
163
|
+
const body = group.map((fragment) => wrapChunk(fragment.raw, destination)).join('')
|
|
164
|
+
const title = `# ${skillName} 详细参考 ${index + 1}${eol}${eol}`
|
|
165
|
+
const note = `> 按需读取;本文件由 SKILL.md 的原章节无损拆分。${eol}${eol}`
|
|
166
|
+
fs.writeFileSync(path.join(referencesDirectory, fileName), title + note + body, 'utf8')
|
|
167
|
+
referenceRecords.push({
|
|
168
|
+
path: relative,
|
|
169
|
+
headings: group.map((fragment) => fragment.heading),
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
const routerLines = [
|
|
174
|
+
'## 详细参考路由(渐进披露)',
|
|
175
|
+
'',
|
|
176
|
+
'仅在当前任务涉及对应主题时读取;下列文件合计保留了原 SKILL.md 的全部详细知识。',
|
|
177
|
+
'',
|
|
178
|
+
...referenceRecords.map((reference) =>
|
|
179
|
+
`- [${reference.path}](${reference.path}):${reference.headings.join(';')}`),
|
|
180
|
+
'',
|
|
181
|
+
].join(eol)
|
|
182
|
+
const rewritten = prefix
|
|
183
|
+
+ `<!-- microi-progressive:begin -->${eol}`
|
|
184
|
+
+ entryChunks
|
|
185
|
+
+ routerLines
|
|
186
|
+
+ `<!-- microi-progressive:end -->${eol}`
|
|
187
|
+
if (lineCount(rewritten) > maxEntryLines) {
|
|
188
|
+
throw new Error(`${skillName} entry remains ${lineCount(rewritten)} lines; lower keepBudgetLines`)
|
|
189
|
+
}
|
|
190
|
+
fs.writeFileSync(skillPath, rewritten, 'utf8')
|
|
191
|
+
|
|
192
|
+
manifest.skills[skillName] = {
|
|
193
|
+
originalSha256: hash(original),
|
|
194
|
+
originalLines: lineCount(original),
|
|
195
|
+
prefixSha256: hash(prefix),
|
|
196
|
+
prefixBytes: Buffer.byteLength(prefix),
|
|
197
|
+
entryLines: lineCount(rewritten),
|
|
198
|
+
chunks: chunkRecords,
|
|
199
|
+
references: referenceRecords,
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8')
|
|
204
|
+
console.log(`Optimized ${Object.keys(manifest.skills).length} skills; manifest: ${manifestPath}`)
|