@microi.net/cli 5.9.1 → 5.9.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.
Files changed (200) hide show
  1. package/.codebuddy-plugin/marketplace.json +2 -2
  2. package/.codebuddy-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.workbuddy-plugin/marketplace.json +2 -2
  5. package/.workbuddy-plugin/plugin.json +1 -1
  6. package/LICENSE +21 -21
  7. package/README.md +71 -71
  8. package/assets/build-meta.json +5 -5
  9. package/assets/feature-matrix.json +138 -138
  10. package/assets/logo.svg +4 -4
  11. package/cordis.patch.yml +1 -1
  12. package/package.json +1 -1
  13. package/scripts/microi-codex-broker.js +466 -450
  14. package/scripts/microi-codex-router.js +634 -618
  15. package/scripts/microi-skills.meta.json +386 -377
  16. package/skills/.microi-skills-version.json +2 -2
  17. package/skills/.progressive-disclosure-manifest.json +148 -123
  18. package/skills/README.md +287 -287
  19. package/skills/ai-engine/SKILL.md +269 -269
  20. package/skills/ai-engine/agents/openai.yaml +4 -4
  21. package/skills/ai-engine/references/ai-employees.md +48 -48
  22. package/skills/ai-engine/references/self-hosted-digital-human.md +59 -59
  23. package/skills/ai-platform-governance/SKILL.md +177 -177
  24. package/skills/ai-platform-governance/references/progressive-01-/345/212/237/350/203/275/345/274/200/345/205/263.md +190 -190
  25. package/skills/app-store/SKILL.md +525 -525
  26. package/skills/app-store/agents/openai.yaml +4 -4
  27. package/skills/business-blueprint/SKILL.md +193 -193
  28. package/skills/datasource-engine/SKILL.md +93 -93
  29. package/skills/datasource-engine/agents/openai.yaml +4 -4
  30. package/skills/dos-orm/SKILL.md +101 -95
  31. package/skills/dos-orm/references/api-reference.md +229 -229
  32. package/skills/email-engine/SKILL.md +81 -81
  33. package/skills/email-engine/references/v8-email.md +34 -34
  34. package/skills/job-engine/SKILL.md +176 -176
  35. package/skills/job-engine/agents/openai.yaml +4 -4
  36. package/skills/message-notification/SKILL.md +156 -156
  37. package/skills/message-notification/agents/openai.yaml +5 -5
  38. package/skills/message-notification/references/contracts.md +102 -102
  39. package/skills/microi/SKILL.md +14 -14
  40. package/skills/microi-ai-app-auth.js +652 -652
  41. package/skills/microi-ai-application/SKILL.md +115 -115
  42. package/skills/microi-ai-application/agents/openai.yaml +4 -4
  43. package/skills/microi-ai-application/references/frontend-baseline.md +164 -164
  44. package/skills/microi-client-frontend/SKILL.md +242 -242
  45. 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 +144 -144
  46. 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 +196 -196
  47. 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 +167 -167
  48. package/skills/microi-codex-installer/SKILL.md +231 -231
  49. package/skills/microi-codex-installer/agents/openai.yaml +7 -7
  50. package/skills/microi-datasource-mapping/SKILL.md +122 -122
  51. package/skills/microi-db-schema/SKILL.md +175 -175
  52. package/skills/microi-db-schema/agents/openai.yaml +4 -4
  53. package/skills/microi-db-schema/references/core-tables.md +695 -695
  54. package/skills/microi-db-schema/references/form-component-options.md +256 -256
  55. package/skills/microi-db-schema/references/schema-overview.md +202 -202
  56. package/skills/microi-db-schema/references/schema.md +646 -646
  57. package/skills/microi-db-schema/references/table-catalog.md +1599 -1599
  58. package/skills/microi-deployment/SKILL.md +239 -220
  59. package/skills/microi-deployment/references/deployment-matrix.md +109 -109
  60. package/skills/microi-docs-coverage/SKILL.md +133 -133
  61. package/skills/microi-docs-coverage/references/capability-map.md +90 -90
  62. package/skills/microi-docs-coverage/scripts/audit-doc-skill-coverage.mjs +894 -894
  63. package/skills/microi-form-engine/SKILL.md +338 -333
  64. package/skills/microi-form-engine/references/component-catalog.md +218 -218
  65. package/skills/microi-form-engine/references/data-source-events.md +124 -124
  66. package/skills/microi-frontend-sdk/SKILL.md +194 -194
  67. 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 +176 -176
  68. package/skills/microi-left-right-layout/SKILL.md +141 -141
  69. package/skills/microi-microservice/SKILL.md +330 -328
  70. package/skills/microi-microservice/references/runtime-delivery.md +278 -278
  71. package/skills/microi-mobile-app-quality/SKILL.md +203 -197
  72. package/skills/microi-mobile-app-quality/references/mobile-first-delivery.md +75 -0
  73. 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 +227 -224
  74. package/skills/microi-mobile-app-quality/references/progressive-02-9-/344/270/273/351/242/230/345/210/207/346/215/242/345/277/205/351/241/273/347/234/237/345/256/236/344/270/224/345/205/250/345/261/200/347/224/237/346/225/210.md +129 -128
  75. package/skills/microi-solution-quotation/SKILL.md +78 -78
  76. package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
  77. package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
  78. package/skills/microi-sso/SKILL.md +95 -91
  79. package/skills/microi-sso/references/acceptance.md +51 -49
  80. package/skills/microi-sso/references/configuration-and-security.md +53 -53
  81. package/skills/microi-sso/references/inbound.md +53 -53
  82. package/skills/microi-sso/references/outbound.md +43 -39
  83. package/skills/microi-ui/SKILL.md +192 -192
  84. package/skills/microi-ui/references/progressive-01-/347/247/273/345/212/250/347/253/257/345/234/272/346/231/257/350/223/235/345/233/276.md +183 -183
  85. package/skills/microi-uniapp-frontend/SKILL.md +206 -199
  86. 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 +261 -261
  87. 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 +154 -154
  88. package/skills/microi.v8.js +1921 -1921
  89. package/skills/module-engine/SKILL.md +255 -255
  90. package/skills/module-engine/references/module-config.md +204 -204
  91. package/skills/ocr-engine/SKILL.md +113 -113
  92. package/skills/ocr-engine/agents/openai.yaml +4 -4
  93. package/skills/page-engine/SKILL.md +206 -206
  94. package/skills/page-engine/examples/compact-dashboard.json +1444 -1444
  95. 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 +234 -234
  96. 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 +60 -60
  97. package/skills/performance-testing/SKILL.md +227 -221
  98. package/skills/playwright-e2e/SKILL.md +197 -196
  99. 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 +173 -173
  100. 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 +183 -183
  101. package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -221
  102. package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +115 -115
  103. package/skills/print-engine/SKILL.md +259 -259
  104. package/skills/production-readonly-audit/SKILL.md +41 -41
  105. package/skills/report-engine/SKILL.md +71 -71
  106. package/skills/report-engine/agents/openai.yaml +4 -4
  107. package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
  108. package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
  109. package/skills/scripts/sync-embedded-skills.mjs +46 -46
  110. package/skills/scripts/validate-progressive-disclosure.mjs +57 -57
  111. package/skills/search-engine/SKILL.md +75 -75
  112. package/skills/search-engine/agents/openai.yaml +4 -4
  113. package/skills/spider-engine/SKILL.md +190 -190
  114. package/skills/system-observability/SKILL.md +259 -246
  115. package/skills/system-observability/references/memory-incident-triage.md +77 -77
  116. package/skills/translate-engine/SKILL.md +140 -140
  117. package/skills/translate-engine/agents/openai.yaml +4 -4
  118. package/skills/ui-design/SKILL.md +223 -223
  119. package/skills/ui-design/assets/pattern-showcase/app.js +54 -54
  120. package/skills/ui-design/assets/pattern-showcase/index.html +163 -163
  121. package/skills/ui-design/assets/pattern-showcase/styles.css +311 -311
  122. package/skills/ui-design/assets/templates/MCI-DESIGN.md +206 -206
  123. package/skills/ui-design/references/design-pattern-library.md +184 -184
  124. package/skills/ui-design/references/mci-design-contract.md +163 -163
  125. package/skills/ui-design/references/motion-and-media.md +78 -78
  126. package/skills/ui-design/references/product-flow-recipes.md +94 -94
  127. package/skills/ui-design/references/progressive-01-/351/242/234/350/211/262/344/275/223/347/263/273-css-variables-/346/224/257/346/214/201/344/270/273/351/242/230/345/210/207/346/215/242.md +218 -218
  128. package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +164 -164
  129. package/skills/ui-design/references/progressive-03-/345/212/250/346/225/210/350/247/204/350/214/203-/344/270/260/345/257/214/344/275/206/344/270/215/345/215/241.md +235 -235
  130. package/skills/ui-design/references/progressive-04-/347/273/204/344/273/266/351/243/216/346/240/274/351/200/237/346/237/245.md +152 -152
  131. package/skills/ui-design/references/progressive-05-/347/247/273/345/212/250/347/253/257/344/270/223/347/224/250/350/247/204/350/214/203.md +238 -238
  132. package/skills/ui-design/references/progressive-06-/344/270/273/351/242/230/345/210/207/346/215/242/345/256/236/347/216/260.md +194 -194
  133. package/skills/ui-design/references/progressive-07-/351/200/237/346/237/245-/344/273/216/345/244/264/346/220/255/345/273/272/344/270/200/344/270/252/347/247/273/345/212/250/347/253/257/351/241/265/351/235/242.md +207 -207
  134. package/skills/ui-design/references/progressive-08-/350/241/250/345/215/225/345/210/206/347/273/204/350/247/204/350/214/203-tabs-vs-collapsegroup-/345/274/272/345/210/266.md +142 -142
  135. package/skills/uniapp-mall-assets/SKILL.md +176 -176
  136. package/skills/unity-integration/SKILL.md +178 -171
  137. package/skills/unity-integration/agents/openai.yaml +4 -4
  138. package/skills/unity-integration/references/ai-app-delivery.md +119 -119
  139. package/skills/unity-integration/references/sdk-api.md +82 -82
  140. package/skills/unity-integration/references/toolbox-migration.md +66 -66
  141. package/skills/unity-integration/references/webgl-hosting.md +57 -57
  142. package/skills/v8-api-config/SKILL.md +388 -388
  143. package/skills/v8-cache-pattern/SKILL.md +312 -312
  144. package/skills/v8-crud-api/SKILL.md +179 -178
  145. 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 +226 -226
  146. package/skills/v8-crud-api/references/progressive-02-where-/346/235/241/344/273/266/350/257/255/346/263/225/351/200/237/346/237/245.md +49 -49
  147. package/skills/v8-debugging/SKILL.md +284 -284
  148. package/skills/v8-explorer-tree/SKILL.md +228 -228
  149. package/skills/v8-export-import/SKILL.md +219 -219
  150. 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 +211 -211
  151. package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -202
  152. 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 +42 -42
  153. package/skills/v8-file-upload/SKILL.md +255 -284
  154. package/skills/v8-file-upload/references/cdn-alias-repair.md +13 -0
  155. 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 +263 -263
  156. 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 +161 -161
  157. package/skills/v8-file-upload/references/progressive-03-/347/251/272/347/233/256/345/275/225/346/240/207/350/256/260/344/270/216/350/277/234/347/250/213/344/270/213/350/275/275.md +36 -0
  158. package/skills/v8-formengine-http/SKILL.md +238 -238
  159. package/skills/v8-frontend-events/SKILL.md +180 -180
  160. package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
  161. package/skills/v8-frontend-events/references/bluetooth-print.md +258 -258
  162. package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -219
  163. package/skills/v8-http-integration/SKILL.md +182 -182
  164. package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -220
  165. 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 +44 -44
  166. package/skills/v8-image-processing/SKILL.md +190 -190
  167. package/skills/v8-image-processing/agents/openai.yaml +4 -4
  168. package/skills/v8-image-processing/references/api-reference.md +623 -623
  169. package/skills/v8-menu-buttons/SKILL.md +186 -186
  170. package/skills/v8-menu-buttons/references/progressive-01-2-/346/214/211/351/222/256/345/257/271/350/261/241-schema.md +222 -222
  171. 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
  172. 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 +104 -104
  173. package/skills/v8-mongodb/SKILL.md +200 -200
  174. package/skills/v8-mq-mqtt/SKILL.md +178 -176
  175. package/skills/v8-mq-mqtt/references/mqtt-production.md +352 -342
  176. package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -181
  177. package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
  178. package/skills/v8-saas-multi-tenant/SKILL.md +305 -305
  179. package/skills/v8-security/SKILL.md +227 -210
  180. package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +200 -200
  181. package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +160 -160
  182. package/skills/v8-sql-query/SKILL.md +302 -302
  183. package/skills/v8-table-event/SKILL.md +176 -176
  184. 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 +216 -216
  185. 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 +46 -46
  186. package/skills/v8-tcp-integration/SKILL.md +158 -147
  187. package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
  188. package/skills/v8-template-engine/SKILL.md +167 -167
  189. package/skills/v8-utilities/SKILL.md +104 -104
  190. package/skills/v8-utilities/references/client-api-index.md +143 -143
  191. package/skills/v8-utilities/references/platform-http-routes.md +83 -83
  192. package/skills/v8-utilities/references/server-api-index.md +188 -188
  193. package/skills/v8-workflow/SKILL.md +252 -252
  194. package/skills/v8-workflow/references/progressive-01-/350/212/202/347/202/271/345/274/200/345/247/213-v8-/344/272/213/344/273/266.md +180 -180
  195. package/skills/v8-workflow/references/workflow-configuration.md +49 -49
  196. package/skills/vision-engine/SKILL.md +160 -160
  197. package/skills/vision-engine/agents/openai.yaml +4 -4
  198. package/skills/vision-engine/references/architecture-and-acceptance.md +194 -194
  199. package/skills/workspace-conventions/SKILL.md +13 -13
  200. 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 +2 -2
@@ -1,894 +1,894 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from "node:fs/promises";
4
- import path from "node:path";
5
- import process from "node:process";
6
- import { fileURLToPath } from "node:url";
7
-
8
- const scriptDir = path.dirname(fileURLToPath(import.meta.url));
9
- const repoRoot = path.resolve(scriptDir, "..", "..", "..");
10
- const docRoot = path.join(repoRoot, "microi.doc", "docs", "doc");
11
- const skillRoot = path.join(repoRoot, "microi.skills");
12
- const skillReadmePath = path.join(skillRoot, "README.md");
13
- const mapPath = path.join(
14
- skillRoot,
15
- "microi-docs-coverage",
16
- "references",
17
- "capability-map.md",
18
- );
19
- const componentSourcePath = path.join(
20
- repoRoot,
21
- "Microi.Client",
22
- "src",
23
- "views",
24
- "form-engine",
25
- "diy-field-component",
26
- "diy-component-list.json",
27
- );
28
- const componentReferencePath = path.join(
29
- skillRoot,
30
- "microi-form-engine",
31
- "references",
32
- "component-catalog.md",
33
- );
34
- const bluetoothOfficialDocPath = path.join(
35
- docRoot,
36
- "v8-engine",
37
- "v8-client.md",
38
- );
39
- const bluetoothAdapterPath = path.join(
40
- repoRoot,
41
- "Microi.Client",
42
- "src",
43
- "utils",
44
- "v8-print.js",
45
- );
46
- const bluetoothEditorDefinitionPath = path.join(
47
- repoRoot,
48
- "Microi.Client",
49
- "src",
50
- "views",
51
- "form-engine",
52
- "diy-components",
53
- "v8-api-definitions.js",
54
- );
55
- const bluetoothTscPath = path.join(
56
- repoRoot,
57
- "Microi.Client",
58
- "src",
59
- "utils",
60
- "ble",
61
- "tsc.js",
62
- );
63
- const bluetoothEscPath = path.join(
64
- repoRoot,
65
- "Microi.Client",
66
- "src",
67
- "utils",
68
- "ble",
69
- "esc.js",
70
- );
71
- const bluetoothReferencePaths = [
72
- path.join(
73
- skillRoot,
74
- "v8-frontend-events",
75
- "references",
76
- "bluetooth-print.md",
77
- ),
78
- path.join(
79
- skillRoot,
80
- "v8-frontend-events",
81
- "references",
82
- "bluetooth-print-api.md",
83
- ),
84
- ];
85
- const bluetoothMountPaths = [
86
- path.join(repoRoot, "Microi.Client", "src", "utils", "diy.common.js"),
87
- path.join(
88
- repoRoot,
89
- "Microi.Client",
90
- "src",
91
- "views",
92
- "form-engine",
93
- "diy-form.vue",
94
- ),
95
- path.join(
96
- repoRoot,
97
- "Microi.Client",
98
- "src",
99
- "views",
100
- "form-engine",
101
- "diy-table.vue",
102
- ),
103
- ];
104
- const defaultReportDir = path.join(repoRoot, ".tmp", "reports");
105
- const excludedDocs = new Set(["about/update-log.md"]);
106
-
107
- const v8TokenPattern =
108
- /(?<![A-Za-z0-9_$])V8(?:(?:\?\.|\.)[A-Za-z_$][A-Za-z0-9_$]*)+/g;
109
- const namedApiPattern =
110
- /(?<![A-Za-z0-9_$])(?:(?:AutoNumber|Button|CodeEditor|DataUtils|DbSession|Divider|Entity|EventBus|EventReplace|FromSection|ImgUpload|JsonTable|NavigateType|OpenTable|ShardingRouter|SqlFunc|SqlSubQuery|StaticText|System|TableChild|TagInput|Textarea|Transfer|DiyCommon|V8Engine|V8ExtensionRegistry)(?:(?:\?\.|\.)[A-Za-z_$][A-Za-z0-9_$]*)+|Microi(?:\.DataSourceEngine\.Run|\.CheckResult))/g;
111
- const mcpToolPattern =
112
- /(?<![@A-Za-z0-9_])microi_(?:codex|(?:create|drop|execute|generate|get|import|inspect|list|plan|publish|query|redis|run|save|scaffold|sync|update|upload|validate)_[a-z0-9_]+)/g;
113
- const httpRoutePattern =
114
- /(?<![A-Za-z0-9_])\/(?:api|apiengine)\/[A-Za-z0-9_{}.$~:/?=+&%\-]*/gi;
115
- const globalFunctionPattern =
116
- /\b(?:DateNow|DateFormat|DateAdd|setTimeout)\s*\(|\bconsole\.log\s*\(/g;
117
- const mapRowPattern = /^\|\s*`([^`]+\.md)`\s*\|\s*([^|]+?)\s*\|/;
118
- const dynamicBases = new Set([
119
- "V8.CacheData",
120
- "V8.ClientModel",
121
- "V8.CurrentRow",
122
- "V8.CurrentUser",
123
- "V8.Field",
124
- "V8.Form",
125
- "V8.Header",
126
- "V8.OldForm",
127
- "V8.OsClientModel",
128
- "V8.Param",
129
- "V8.Result",
130
- "V8.Row",
131
- "V8.ScanCodeRes",
132
- "V8.SysConfig",
133
- "V8.TableModel",
134
- ]);
135
-
136
- async function exists(target) {
137
- try {
138
- await fs.access(target);
139
- return true;
140
- } catch {
141
- return false;
142
- }
143
- }
144
-
145
- async function walk(root, predicate) {
146
- const result = [];
147
- const entries = await fs.readdir(root, { withFileTypes: true });
148
- for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
149
- const target = path.join(root, entry.name);
150
- if (entry.isDirectory()) {
151
- result.push(...(await walk(target, predicate)));
152
- } else if (predicate(target)) {
153
- result.push(target);
154
- }
155
- }
156
- return result;
157
- }
158
-
159
- function toPosix(value) {
160
- return value.split(path.sep).join("/");
161
- }
162
-
163
- function parseReportDir() {
164
- const index = process.argv.indexOf("--report-dir");
165
- if (index === -1) return defaultReportDir;
166
- if (!process.argv[index + 1]) {
167
- throw new Error("--report-dir requires a path");
168
- }
169
- return path.resolve(process.argv[index + 1]);
170
- }
171
-
172
- async function readText(target) {
173
- return (await fs.readFile(target, "utf8")).replace(/^\uFEFF/, "");
174
- }
175
-
176
- async function collectDocs() {
177
- const files = await walk(docRoot, (target) => target.endsWith(".md"));
178
- const docs = new Map();
179
- for (const file of files) {
180
- const relative = toPosix(path.relative(docRoot, file));
181
- if (!excludedDocs.has(relative)) docs.set(relative, file);
182
- }
183
- return docs;
184
- }
185
-
186
- async function collectSkillNames() {
187
- const entries = await fs.readdir(skillRoot, { withFileTypes: true });
188
- const names = new Set();
189
- for (const entry of entries) {
190
- if (!entry.isDirectory()) continue;
191
- const skillFile = path.join(skillRoot, entry.name, "SKILL.md");
192
- if (await exists(skillFile)) names.add(entry.name);
193
- }
194
- return names;
195
- }
196
-
197
- async function collectReadmeSkillNames() {
198
- const names = new Set();
199
- const pattern = /`([^`/\\]+)[/\\]SKILL\.md`/g;
200
- for (const match of (await readText(skillReadmePath)).matchAll(pattern)) {
201
- names.add(match[1]);
202
- }
203
- return names;
204
- }
205
-
206
- async function parseCapabilityMap() {
207
- const mapped = new Map();
208
- for (const line of (await readText(mapPath)).split(/\r?\n/)) {
209
- const match = line.match(mapRowPattern);
210
- if (!match) continue;
211
- const docPath = match[1].replaceAll("\\", "/");
212
- const owners = match[2]
213
- .split(",")
214
- .map((owner) => owner.trim().replaceAll("`", ""))
215
- .filter(Boolean);
216
- mapped.set(docPath, owners);
217
- }
218
- return mapped;
219
- }
220
-
221
- function normalizeV8Token(rawToken) {
222
- const token = rawToken.replaceAll("?.", ".");
223
- for (const base of [...dynamicBases].sort((a, b) => b.length - a.length)) {
224
- if (token.startsWith(`${base}.`)) {
225
- return {
226
- token: base,
227
- reason: `${token} -> ${base} (dynamic data property)`,
228
- };
229
- }
230
- }
231
- if (token.startsWith("V8.Print.BLEInformation.")) {
232
- return {
233
- token: "V8.Print.BLEInformation",
234
- reason:
235
- `${token} -> V8.Print.BLEInformation ` +
236
- "(diagnostic data property)",
237
- };
238
- }
239
- if (token.startsWith("V8.Dbs.") && token !== "V8.Dbs.Open") {
240
- return {
241
- token: "V8.Dbs",
242
- reason: `${token} -> V8.Dbs (dynamic extension database)`,
243
- };
244
- }
245
- return { token, reason: null };
246
- }
247
-
248
- async function extractTokens(files) {
249
- const tokens = new Set();
250
- const normalizedExamples = new Map();
251
- for (const file of files) {
252
- const matches = (await readText(file)).match(v8TokenPattern) ?? [];
253
- for (const rawToken of matches) {
254
- const normalized = normalizeV8Token(rawToken);
255
- tokens.add(normalized.token);
256
- if (!normalized.reason) continue;
257
- if (!normalizedExamples.has(normalized.token)) {
258
- normalizedExamples.set(normalized.token, new Set());
259
- }
260
- normalizedExamples.get(normalized.token).add(normalized.reason);
261
- }
262
- }
263
- return { tokens, normalizedExamples };
264
- }
265
-
266
- function normalizeNamedApi(rawToken) {
267
- const token = rawToken.replaceAll("?.", ".");
268
- if (/\.(?:cs|js|ts|vue)$/i.test(token)) {
269
- return {
270
- token: null,
271
- reason: `${token} (source filename, not callable API)`,
272
- };
273
- }
274
- return { token, reason: null };
275
- }
276
-
277
- async function extractNamedApis(files) {
278
- const names = new Set();
279
- const ignoredExamples = new Set();
280
- for (const file of files) {
281
- const matches = (await readText(file)).match(namedApiPattern) ?? [];
282
- for (const rawToken of matches) {
283
- const normalized = normalizeNamedApi(rawToken);
284
- if (normalized.token) {
285
- names.add(normalized.token);
286
- } else if (normalized.reason) {
287
- ignoredExamples.add(normalized.reason);
288
- }
289
- }
290
- }
291
- return { names, ignoredExamples };
292
- }
293
-
294
- async function extractMcpTools(files) {
295
- const names = new Set();
296
- for (const file of files) {
297
- const matches = (await readText(file)).match(mcpToolPattern) ?? [];
298
- for (const match of matches) names.add(match);
299
- }
300
- return names;
301
- }
302
-
303
- function normalizeHttpRoute(rawRoute) {
304
- const cleaned = rawRoute.replace(/[.,;:)\]]+$/g, "");
305
- const pathOnly = cleaned.split("?")[0];
306
- const lower = pathOnly.toLowerCase();
307
-
308
- if (lower.startsWith("/apiengine/")) {
309
- if (lower.includes("--osclient--")) {
310
- return {
311
- route: "/apiengine/{apienginekey}--osclient--{osclient}--",
312
- reason:
313
- `${cleaned} -> ` +
314
- "/apiengine/{ApiEngineKey}--OsClient--{OsClient}--",
315
- };
316
- }
317
- return {
318
- route: "/apiengine/{apienginekey}",
319
- reason: `${cleaned} -> /apiengine/{ApiEngineKey}`,
320
- };
321
- }
322
-
323
- if (lower === "/api/example" || lower.startsWith("/api/example/")) {
324
- return {
325
- route: "/api/{controller}/{action}",
326
- reason: `${cleaned} -> /api/{Controller}/{Action} (generic example)`,
327
- };
328
- }
329
-
330
- const formPrefix = "/api/formengine/";
331
- if (lower.startsWith(formPrefix)) {
332
- const segment = lower.slice(formPrefix.length);
333
- const compact = segment.replaceAll("-", "");
334
- const operations = [
335
- "gettabledataanonymous",
336
- "getformdataanonymous",
337
- "addformdataanonymous",
338
- "uptformdatabywhere",
339
- "delformdatabywhere",
340
- "gettabledata",
341
- "getformdata",
342
- "addformdata",
343
- "uptformdata",
344
- "delformdata",
345
- ];
346
- for (const operation of operations) {
347
- if (compact === operation) {
348
- return { route: `${formPrefix}${operation}`, reason: null };
349
- }
350
- if (compact.startsWith(operation)) {
351
- return {
352
- route: `${formPrefix}${operation}-{formenginekey}`,
353
- reason:
354
- `${cleaned} -> ` +
355
- `/api/formengine/${operation}-{FormEngineKey}`,
356
- };
357
- }
358
- }
359
- }
360
-
361
- return { route: lower, reason: null };
362
- }
363
-
364
- async function extractRoutes(files) {
365
- const routes = new Set();
366
- const normalizedExamples = new Map();
367
- for (const file of files) {
368
- const matches = (await readText(file)).match(httpRoutePattern) ?? [];
369
- for (const rawRoute of matches) {
370
- const normalized = normalizeHttpRoute(rawRoute);
371
- routes.add(normalized.route);
372
- if (!normalized.reason) continue;
373
- if (!normalizedExamples.has(normalized.route)) {
374
- normalizedExamples.set(normalized.route, new Set());
375
- }
376
- normalizedExamples.get(normalized.route).add(normalized.reason);
377
- }
378
- }
379
- return { routes, normalizedExamples };
380
- }
381
-
382
- async function extractGlobalFunctions(files) {
383
- const functions = new Set();
384
- for (const file of files) {
385
- const matches = (await readText(file)).match(globalFunctionPattern) ?? [];
386
- for (const match of matches) {
387
- functions.add(match.replace(/\s*\($/, ""));
388
- }
389
- }
390
- return functions;
391
- }
392
-
393
- async function inspectComponents() {
394
- const source = JSON.parse(await readText(componentSourcePath));
395
- const reference = await readText(componentReferencePath);
396
- const controls = source
397
- .map((item) => item.Control)
398
- .filter((control) => typeof control === "string" && control);
399
- const missing = controls.filter(
400
- (control) => !reference.includes(`\`${control}\``),
401
- );
402
- return { controls: new Set(controls), missing: sorted(new Set(missing)) };
403
- }
404
-
405
- function extractBuilderMethods(source) {
406
- return new Set(
407
- [...source.matchAll(/jpPrinter\.([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*function\s*\(/g)]
408
- .map((match) => match[1]),
409
- );
410
- }
411
-
412
- function extractPrintMembers(source) {
413
- const startMatch = /\b(?:var|let|const)\s+Print\s*=\s*\{/.exec(source);
414
- const start = startMatch ? startMatch.index : -1;
415
- const end = source.indexOf("\n return Print;", start);
416
- if (start === -1 || end === -1) return new Set();
417
-
418
- const block = source.slice(start, end);
419
- const members = new Set(
420
- [...block.matchAll(
421
- /^\s{8}([A-Za-z_$][A-Za-z0-9_$]*):\s*(?:async\s+)?function\s*\(/gm,
422
- )].map((match) => match[1]),
423
- );
424
- for (const match of block.matchAll(
425
- /^\s{8}(createNew(?:ESC)?):\s*[A-Za-z_$][A-Za-z0-9_$.]*,/gm,
426
- )) {
427
- members.add(match[1]);
428
- }
429
- for (const internalName of ["setTipHandler", "initializeConnection"]) {
430
- members.delete(internalName);
431
- }
432
- for (const name of [...members]) {
433
- if (name.startsWith("_")) members.delete(name);
434
- }
435
- if (/^\s{8}BLEInformation:\s*/m.test(block)) {
436
- members.add("BLEInformation");
437
- }
438
- return members;
439
- }
440
-
441
- function findUndocumentedNames(names, text) {
442
- return sorted([...names].filter((name) => !text.includes(name)));
443
- }
444
-
445
- async function inspectBluetoothPrint() {
446
- const [
447
- adapter,
448
- tsc,
449
- esc,
450
- officialDoc,
451
- editorDefinitions,
452
- ...references
453
- ] = await Promise.all([
454
- readText(bluetoothAdapterPath),
455
- readText(bluetoothTscPath),
456
- readText(bluetoothEscPath),
457
- readText(bluetoothOfficialDocPath),
458
- readText(bluetoothEditorDefinitionPath),
459
- ...bluetoothReferencePaths.map((target) => readText(target)),
460
- ]);
461
- const skillReference = references.join("\n");
462
- const printMembers = extractPrintMembers(adapter);
463
- const tscMethods = extractBuilderMethods(tsc);
464
- const escMethods = extractBuilderMethods(esc);
465
- const mountCallsMissing = [];
466
- const mountsMissingFromOfficialDoc = [];
467
- const mountsMissingFromSkill = [];
468
-
469
- for (const mountPath of bluetoothMountPaths) {
470
- const source = await readText(mountPath);
471
- const relative = toPosix(path.relative(repoRoot, mountPath));
472
- if (
473
- !source.includes("initV8Print") ||
474
- !/initV8Print\s*\(/.test(source)
475
- ) {
476
- mountCallsMissing.push(relative);
477
- }
478
- if (!officialDoc.includes(relative)) {
479
- mountsMissingFromOfficialDoc.push(relative);
480
- }
481
- if (!skillReference.includes(relative.replace("Microi.Client/", ""))) {
482
- mountsMissingFromSkill.push(relative);
483
- }
484
- }
485
-
486
- return {
487
- printMembers,
488
- tscMethods,
489
- escMethods,
490
- printMissingFromOfficialDoc: findUndocumentedNames(
491
- printMembers,
492
- officialDoc,
493
- ),
494
- printMissingFromEditorDefinitions: findUndocumentedNames(
495
- printMembers,
496
- editorDefinitions,
497
- ),
498
- printMissingFromSkill: findUndocumentedNames(printMembers, skillReference),
499
- tscMissingFromOfficialDoc: findUndocumentedNames(tscMethods, officialDoc),
500
- tscMissingFromEditorDefinitions: findUndocumentedNames(
501
- tscMethods,
502
- editorDefinitions,
503
- ),
504
- tscMissingFromSkill: findUndocumentedNames(tscMethods, skillReference),
505
- escMissingFromOfficialDoc: findUndocumentedNames(escMethods, officialDoc),
506
- escMissingFromEditorDefinitions: findUndocumentedNames(
507
- escMethods,
508
- editorDefinitions,
509
- ),
510
- escMissingFromSkill: findUndocumentedNames(escMethods, skillReference),
511
- mountCallsMissing: sorted(mountCallsMissing),
512
- mountsMissingFromOfficialDoc: sorted(mountsMissingFromOfficialDoc),
513
- mountsMissingFromSkill: sorted(mountsMissingFromSkill),
514
- };
515
- }
516
-
517
- function sorted(values) {
518
- return [...values].sort((a, b) => a.localeCompare(b));
519
- }
520
-
521
- function renderList(items, empty = "无") {
522
- return items.length ? items.map((item) => `- \`${item}\``) : [`- ${empty}`];
523
- }
524
-
525
- function buildReport({
526
- docs,
527
- mapped,
528
- skills,
529
- readmeSkills,
530
- docTokens,
531
- skillTokens,
532
- normalizedExamples,
533
- docNamedApis,
534
- skillNamedApis,
535
- ignoredNamedApiExamples,
536
- docMcpTools,
537
- skillMcpTools,
538
- docRoutes,
539
- skillRoutes,
540
- normalizedRoutes,
541
- docGlobalFunctions,
542
- skillGlobalFunctions,
543
- componentControls,
544
- missingComponents,
545
- bluetooth,
546
- }) {
547
- const unmapped = sorted([...docs.keys()].filter((key) => !mapped.has(key)));
548
- const stale = sorted([...mapped.keys()].filter((key) => !docs.has(key)));
549
- const unknownOwners = [];
550
- const ownerless = [];
551
- for (const [doc, owners] of mapped) {
552
- if (!owners.length) ownerless.push(doc);
553
- for (const owner of owners) {
554
- if (!skills.has(owner)) unknownOwners.push(`${doc}: ${owner}`);
555
- }
556
- }
557
- unknownOwners.sort((a, b) => a.localeCompare(b));
558
- ownerless.sort((a, b) => a.localeCompare(b));
559
- const uncovered = sorted(
560
- [...docTokens].filter((token) => !skillTokens.has(token)),
561
- );
562
- const uncoveredNamedApis = sorted(
563
- [...docNamedApis].filter((name) => !skillNamedApis.has(name)),
564
- );
565
- const uncoveredMcpTools = sorted(
566
- [...docMcpTools].filter((name) => !skillMcpTools.has(name)),
567
- );
568
- const uncoveredRoutes = sorted(
569
- [...docRoutes].filter((route) => !skillRoutes.has(route)),
570
- );
571
- const uncoveredGlobalFunctions = sorted(
572
- [...docGlobalFunctions].filter(
573
- (functionName) => !skillGlobalFunctions.has(functionName),
574
- ),
575
- );
576
- const skillsMissingFromReadme = sorted(
577
- [...skills].filter((skill) => !readmeSkills.has(skill)),
578
- );
579
- const staleReadmeSkills = sorted(
580
- [...readmeSkills].filter((skill) => !skills.has(skill)),
581
- );
582
- const bluetoothIssues = [
583
- ...bluetooth.printMissingFromOfficialDoc.map(
584
- (name) => `official doc missing V8.Print.${name}`,
585
- ),
586
- ...bluetooth.printMissingFromSkill.map(
587
- (name) => `Skill missing V8.Print.${name}`,
588
- ),
589
- ...bluetooth.printMissingFromEditorDefinitions.map(
590
- (name) => `editor definitions missing V8.Print.${name}`,
591
- ),
592
- ...bluetooth.tscMissingFromOfficialDoc.map(
593
- (name) => `official doc missing TSC.${name}`,
594
- ),
595
- ...bluetooth.tscMissingFromSkill.map(
596
- (name) => `Skill missing TSC.${name}`,
597
- ),
598
- ...bluetooth.tscMissingFromEditorDefinitions.map(
599
- (name) => `editor definitions missing TSC.${name}`,
600
- ),
601
- ...bluetooth.escMissingFromOfficialDoc.map(
602
- (name) => `official doc missing ESC.${name}`,
603
- ),
604
- ...bluetooth.escMissingFromSkill.map(
605
- (name) => `Skill missing ESC.${name}`,
606
- ),
607
- ...bluetooth.escMissingFromEditorDefinitions.map(
608
- (name) => `editor definitions missing ESC.${name}`,
609
- ),
610
- ...bluetooth.mountCallsMissing.map(
611
- (name) => `source mount missing initV8Print: ${name}`,
612
- ),
613
- ...bluetooth.mountsMissingFromOfficialDoc.map(
614
- (name) => `official doc missing mount: ${name}`,
615
- ),
616
- ...bluetooth.mountsMissingFromSkill.map(
617
- (name) => `Skill missing mount: ${name}`,
618
- ),
619
- ];
620
- const ok =
621
- !unmapped.length &&
622
- !stale.length &&
623
- !unknownOwners.length &&
624
- !ownerless.length &&
625
- !uncovered.length &&
626
- !uncoveredNamedApis.length &&
627
- !uncoveredMcpTools.length &&
628
- !uncoveredRoutes.length &&
629
- !uncoveredGlobalFunctions.length &&
630
- !missingComponents.length &&
631
- !bluetoothIssues.length &&
632
- !skillsMissingFromReadme.length &&
633
- !staleReadmeSkills.length;
634
-
635
- const generatedAt = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
636
- const lines = [
637
- "# Microi 中文文档与 Skills 覆盖审计",
638
- "",
639
- `- 生成时间:\`${generatedAt}\``,
640
- `- 中文文档:\`${docs.size}\`(已排除更新日志与英文站)`,
641
- `- Skills:\`${skills.size}\``,
642
- `- 文档标准化 V8 名称:\`${docTokens.size}\``,
643
- `- 文档其它具名 API:\`${docNamedApis.size}\``,
644
- `- 文档 MCP 工具名:\`${docMcpTools.size}\``,
645
- `- 文档标准化 HTTP 路由:\`${docRoutes.size}\``,
646
- `- 文档平台全局函数:\`${docGlobalFunctions.size}\``,
647
- `- 当前表单组件:\`${componentControls.size}\``,
648
- `- 蓝牙打印公开入口:\`${bluetooth.printMembers.size}\``,
649
- `- TSC 构建器方法:\`${bluetooth.tscMethods.size}\``,
650
- `- ESC/POS 构建器方法:\`${bluetooth.escMethods.size}\``,
651
- `- Skill 标准化 V8 名称:\`${skillTokens.size}\``,
652
- `- 结论:**${ok ? "PASS" : "FAIL"}**`,
653
- "",
654
- "## 未映射中文文档",
655
- "",
656
- ...renderList(unmapped),
657
- "",
658
- "## 映射中已不存在的中文文档",
659
- "",
660
- ...renderList(stale),
661
- "",
662
- "## 不存在或缺失的责任 Skill",
663
- "",
664
- ...renderList([...unknownOwners, ...ownerless]),
665
- "",
666
- "## Skills 总目录漏项或失效项",
667
- "",
668
- ...renderList([
669
- ...skillsMissingFromReadme.map((skill) => `missing: ${skill}`),
670
- ...staleReadmeSkills.map((skill) => `stale: ${skill}`),
671
- ]),
672
- "",
673
- "## 文档出现但 Skills 未覆盖的 V8 名称",
674
- "",
675
- ...renderList(uncovered),
676
- "",
677
- "## 文档出现但 Skills 未覆盖的其它具名 API",
678
- "",
679
- ...renderList(uncoveredNamedApis),
680
- "",
681
- "## 文档出现但 Skills 未覆盖的 MCP 工具名",
682
- "",
683
- ...renderList(uncoveredMcpTools),
684
- "",
685
- "## 文档出现但 Skills 未覆盖的平台 HTTP 路由",
686
- "",
687
- ...renderList(uncoveredRoutes),
688
- "",
689
- "## 文档出现但 Skills 未覆盖的平台全局函数",
690
- "",
691
- ...renderList(uncoveredGlobalFunctions),
692
- "",
693
- "## 当前组件清单未进入表单 Skill 的控件",
694
- "",
695
- ...renderList(missingComponents),
696
- "",
697
- "## 蓝牙打印源码、官方文档与 Skills 未对齐项",
698
- "",
699
- ...renderList(bluetoothIssues),
700
- "",
701
- "## 已排除的源码文件名",
702
- "",
703
- ...renderList(sorted(ignoredNamedApiExamples)),
704
- "",
705
- "## 已归一的动态示例",
706
- "",
707
- ];
708
-
709
- if (normalizedExamples.size) {
710
- for (const key of sorted(normalizedExamples.keys())) {
711
- lines.push(`### \`${key}\``, "");
712
- for (const reason of sorted(normalizedExamples.get(key))) {
713
- lines.push(`- \`${reason}\``);
714
- }
715
- lines.push("");
716
- }
717
- } else {
718
- lines.push("- 无", "");
719
- }
720
-
721
- lines.push("## 已归一的 HTTP 路由示例", "");
722
- if (normalizedRoutes.size) {
723
- for (const key of sorted(normalizedRoutes.keys())) {
724
- lines.push(`### \`${key}\``, "");
725
- for (const reason of sorted(normalizedRoutes.get(key))) {
726
- lines.push(`- \`${reason}\``);
727
- }
728
- lines.push("");
729
- }
730
- } else {
731
- lines.push("- 无", "");
732
- }
733
-
734
- const normalizedPayload = {};
735
- for (const key of sorted(normalizedExamples.keys())) {
736
- normalizedPayload[key] = sorted(normalizedExamples.get(key));
737
- }
738
- const normalizedRoutePayload = {};
739
- for (const key of sorted(normalizedRoutes.keys())) {
740
- normalizedRoutePayload[key] = sorted(normalizedRoutes.get(key));
741
- }
742
- return {
743
- ok,
744
- markdown: `${lines.join("\n").trimEnd()}\n`,
745
- payload: {
746
- generatedAt,
747
- ok,
748
- counts: {
749
- docs: docs.size,
750
- skills: skills.size,
751
- docV8Tokens: docTokens.size,
752
- skillV8Tokens: skillTokens.size,
753
- docNamedApis: docNamedApis.size,
754
- skillNamedApis: skillNamedApis.size,
755
- docMcpTools: docMcpTools.size,
756
- skillMcpTools: skillMcpTools.size,
757
- docHttpRoutes: docRoutes.size,
758
- skillHttpRoutes: skillRoutes.size,
759
- docGlobalFunctions: docGlobalFunctions.size,
760
- skillGlobalFunctions: skillGlobalFunctions.size,
761
- componentControls: componentControls.size,
762
- bluetoothPrintMembers: bluetooth.printMembers.size,
763
- bluetoothTscMethods: bluetooth.tscMethods.size,
764
- bluetoothEscMethods: bluetooth.escMethods.size,
765
- },
766
- unmappedDocs: unmapped,
767
- staleMappedDocs: stale,
768
- unknownOwners,
769
- ownerlessDocs: ownerless,
770
- skillsMissingFromReadme,
771
- staleReadmeSkills,
772
- uncoveredV8Tokens: uncovered,
773
- uncoveredNamedApis,
774
- uncoveredMcpTools,
775
- uncoveredHttpRoutes: uncoveredRoutes,
776
- uncoveredGlobalFunctions,
777
- missingComponents,
778
- bluetoothIssues,
779
- bluetooth: {
780
- printMembers: sorted(bluetooth.printMembers),
781
- tscMethods: sorted(bluetooth.tscMethods),
782
- escMethods: sorted(bluetooth.escMethods),
783
- printMissingFromOfficialDoc: bluetooth.printMissingFromOfficialDoc,
784
- printMissingFromEditorDefinitions:
785
- bluetooth.printMissingFromEditorDefinitions,
786
- printMissingFromSkill: bluetooth.printMissingFromSkill,
787
- tscMissingFromOfficialDoc: bluetooth.tscMissingFromOfficialDoc,
788
- tscMissingFromEditorDefinitions:
789
- bluetooth.tscMissingFromEditorDefinitions,
790
- tscMissingFromSkill: bluetooth.tscMissingFromSkill,
791
- escMissingFromOfficialDoc: bluetooth.escMissingFromOfficialDoc,
792
- escMissingFromEditorDefinitions:
793
- bluetooth.escMissingFromEditorDefinitions,
794
- escMissingFromSkill: bluetooth.escMissingFromSkill,
795
- mountCallsMissing: bluetooth.mountCallsMissing,
796
- mountsMissingFromOfficialDoc: bluetooth.mountsMissingFromOfficialDoc,
797
- mountsMissingFromSkill: bluetooth.mountsMissingFromSkill,
798
- },
799
- ignoredNamedApiExamples: sorted(ignoredNamedApiExamples),
800
- normalizedExamples: normalizedPayload,
801
- normalizedRoutes: normalizedRoutePayload,
802
- },
803
- };
804
- }
805
-
806
- async function main() {
807
- if (
808
- !(await exists(docRoot)) ||
809
- !(await exists(skillRoot)) ||
810
- !(await exists(skillReadmePath)) ||
811
- !(await exists(mapPath)) ||
812
- !(await exists(componentSourcePath)) ||
813
- !(await exists(componentReferencePath)) ||
814
- !(await exists(bluetoothOfficialDocPath)) ||
815
- !(await exists(bluetoothAdapterPath)) ||
816
- !(await exists(bluetoothEditorDefinitionPath)) ||
817
- !(await exists(bluetoothTscPath)) ||
818
- !(await exists(bluetoothEscPath)) ||
819
- !(
820
- await Promise.all(bluetoothReferencePaths.map((target) => exists(target)))
821
- ).every(Boolean) ||
822
- !(
823
- await Promise.all(bluetoothMountPaths.map((target) => exists(target)))
824
- ).every(Boolean)
825
- ) {
826
- throw new Error(
827
- "Microi docs, skills, capability map, or component catalog is missing",
828
- );
829
- }
830
- const reportDir = parseReportDir();
831
- const docs = await collectDocs();
832
- const skills = await collectSkillNames();
833
- const readmeSkills = await collectReadmeSkillNames();
834
- const mapped = await parseCapabilityMap();
835
- const docExtraction = await extractTokens([...docs.values()]);
836
- const docNamedApis = await extractNamedApis([...docs.values()]);
837
- const docMcpTools = await extractMcpTools([...docs.values()]);
838
- const docRoutes = await extractRoutes([...docs.values()]);
839
- const docGlobalFunctions = await extractGlobalFunctions([...docs.values()]);
840
- const skillMarkdown = await walk(skillRoot, (target) =>
841
- target.endsWith(".md"),
842
- );
843
- const skillExtraction = await extractTokens(skillMarkdown);
844
- const skillNamedApis = await extractNamedApis(skillMarkdown);
845
- const skillMcpTools = await extractMcpTools(skillMarkdown);
846
- const skillRoutes = await extractRoutes(skillMarkdown);
847
- const skillGlobalFunctions = await extractGlobalFunctions(skillMarkdown);
848
- const components = await inspectComponents();
849
- const bluetooth = await inspectBluetoothPrint();
850
- const report = buildReport({
851
- docs,
852
- mapped,
853
- skills,
854
- readmeSkills,
855
- docTokens: docExtraction.tokens,
856
- skillTokens: skillExtraction.tokens,
857
- normalizedExamples: docExtraction.normalizedExamples,
858
- docNamedApis: docNamedApis.names,
859
- skillNamedApis: skillNamedApis.names,
860
- ignoredNamedApiExamples: docNamedApis.ignoredExamples,
861
- docMcpTools,
862
- skillMcpTools,
863
- docRoutes: docRoutes.routes,
864
- skillRoutes: skillRoutes.routes,
865
- normalizedRoutes: docRoutes.normalizedExamples,
866
- docGlobalFunctions,
867
- skillGlobalFunctions,
868
- componentControls: components.controls,
869
- missingComponents: components.missing,
870
- bluetooth,
871
- });
872
-
873
- await fs.mkdir(reportDir, { recursive: true });
874
- const markdownPath = path.join(
875
- reportDir,
876
- "microi-doc-skill-coverage.md",
877
- );
878
- const jsonPath = path.join(reportDir, "microi-doc-skill-coverage.json");
879
- await fs.writeFile(markdownPath, report.markdown, "utf8");
880
- await fs.writeFile(
881
- jsonPath,
882
- `${JSON.stringify(report.payload, null, 2)}\n`,
883
- "utf8",
884
- );
885
- process.stdout.write(report.markdown);
886
- console.log(`Markdown report: ${markdownPath}`);
887
- console.log(`JSON report: ${jsonPath}`);
888
- process.exitCode = report.ok ? 0 : 1;
889
- }
890
-
891
- main().catch((error) => {
892
- console.error(error.message);
893
- process.exitCode = 2;
894
- });
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "node:fs/promises";
4
+ import path from "node:path";
5
+ import process from "node:process";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url));
9
+ const repoRoot = path.resolve(scriptDir, "..", "..", "..");
10
+ const docRoot = path.join(repoRoot, "microi.doc", "docs", "doc");
11
+ const skillRoot = path.join(repoRoot, "microi.skills");
12
+ const skillReadmePath = path.join(skillRoot, "README.md");
13
+ const mapPath = path.join(
14
+ skillRoot,
15
+ "microi-docs-coverage",
16
+ "references",
17
+ "capability-map.md",
18
+ );
19
+ const componentSourcePath = path.join(
20
+ repoRoot,
21
+ "Microi.Client",
22
+ "src",
23
+ "views",
24
+ "form-engine",
25
+ "diy-field-component",
26
+ "diy-component-list.json",
27
+ );
28
+ const componentReferencePath = path.join(
29
+ skillRoot,
30
+ "microi-form-engine",
31
+ "references",
32
+ "component-catalog.md",
33
+ );
34
+ const bluetoothOfficialDocPath = path.join(
35
+ docRoot,
36
+ "v8-engine",
37
+ "v8-client.md",
38
+ );
39
+ const bluetoothAdapterPath = path.join(
40
+ repoRoot,
41
+ "Microi.Client",
42
+ "src",
43
+ "utils",
44
+ "v8-print.js",
45
+ );
46
+ const bluetoothEditorDefinitionPath = path.join(
47
+ repoRoot,
48
+ "Microi.Client",
49
+ "src",
50
+ "views",
51
+ "form-engine",
52
+ "diy-components",
53
+ "v8-api-definitions.js",
54
+ );
55
+ const bluetoothTscPath = path.join(
56
+ repoRoot,
57
+ "Microi.Client",
58
+ "src",
59
+ "utils",
60
+ "ble",
61
+ "tsc.js",
62
+ );
63
+ const bluetoothEscPath = path.join(
64
+ repoRoot,
65
+ "Microi.Client",
66
+ "src",
67
+ "utils",
68
+ "ble",
69
+ "esc.js",
70
+ );
71
+ const bluetoothReferencePaths = [
72
+ path.join(
73
+ skillRoot,
74
+ "v8-frontend-events",
75
+ "references",
76
+ "bluetooth-print.md",
77
+ ),
78
+ path.join(
79
+ skillRoot,
80
+ "v8-frontend-events",
81
+ "references",
82
+ "bluetooth-print-api.md",
83
+ ),
84
+ ];
85
+ const bluetoothMountPaths = [
86
+ path.join(repoRoot, "Microi.Client", "src", "utils", "diy.common.js"),
87
+ path.join(
88
+ repoRoot,
89
+ "Microi.Client",
90
+ "src",
91
+ "views",
92
+ "form-engine",
93
+ "diy-form.vue",
94
+ ),
95
+ path.join(
96
+ repoRoot,
97
+ "Microi.Client",
98
+ "src",
99
+ "views",
100
+ "form-engine",
101
+ "diy-table.vue",
102
+ ),
103
+ ];
104
+ const defaultReportDir = path.join(repoRoot, ".tmp", "reports");
105
+ const excludedDocs = new Set(["about/update-log.md"]);
106
+
107
+ const v8TokenPattern =
108
+ /(?<![A-Za-z0-9_$])V8(?:(?:\?\.|\.)[A-Za-z_$][A-Za-z0-9_$]*)+/g;
109
+ const namedApiPattern =
110
+ /(?<![A-Za-z0-9_$])(?:(?:AutoNumber|Button|CodeEditor|DataUtils|DbSession|Divider|Entity|EventBus|EventReplace|FromSection|ImgUpload|JsonTable|NavigateType|OpenTable|ShardingRouter|SqlFunc|SqlSubQuery|StaticText|System|TableChild|TagInput|Textarea|Transfer|DiyCommon|V8Engine|V8ExtensionRegistry)(?:(?:\?\.|\.)[A-Za-z_$][A-Za-z0-9_$]*)+|Microi(?:\.DataSourceEngine\.Run|\.CheckResult))/g;
111
+ const mcpToolPattern =
112
+ /(?<![@A-Za-z0-9_])microi_(?:codex|(?:create|drop|execute|generate|get|import|inspect|list|plan|publish|query|redis|run|save|scaffold|sync|update|upload|validate)_[a-z0-9_]+)/g;
113
+ const httpRoutePattern =
114
+ /(?<![A-Za-z0-9_])\/(?:api|apiengine)\/[A-Za-z0-9_{}.$~:/?=+&%\-]*/gi;
115
+ const globalFunctionPattern =
116
+ /\b(?:DateNow|DateFormat|DateAdd|setTimeout)\s*\(|\bconsole\.log\s*\(/g;
117
+ const mapRowPattern = /^\|\s*`([^`]+\.md)`\s*\|\s*([^|]+?)\s*\|/;
118
+ const dynamicBases = new Set([
119
+ "V8.CacheData",
120
+ "V8.ClientModel",
121
+ "V8.CurrentRow",
122
+ "V8.CurrentUser",
123
+ "V8.Field",
124
+ "V8.Form",
125
+ "V8.Header",
126
+ "V8.OldForm",
127
+ "V8.OsClientModel",
128
+ "V8.Param",
129
+ "V8.Result",
130
+ "V8.Row",
131
+ "V8.ScanCodeRes",
132
+ "V8.SysConfig",
133
+ "V8.TableModel",
134
+ ]);
135
+
136
+ async function exists(target) {
137
+ try {
138
+ await fs.access(target);
139
+ return true;
140
+ } catch {
141
+ return false;
142
+ }
143
+ }
144
+
145
+ async function walk(root, predicate) {
146
+ const result = [];
147
+ const entries = await fs.readdir(root, { withFileTypes: true });
148
+ for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
149
+ const target = path.join(root, entry.name);
150
+ if (entry.isDirectory()) {
151
+ result.push(...(await walk(target, predicate)));
152
+ } else if (predicate(target)) {
153
+ result.push(target);
154
+ }
155
+ }
156
+ return result;
157
+ }
158
+
159
+ function toPosix(value) {
160
+ return value.split(path.sep).join("/");
161
+ }
162
+
163
+ function parseReportDir() {
164
+ const index = process.argv.indexOf("--report-dir");
165
+ if (index === -1) return defaultReportDir;
166
+ if (!process.argv[index + 1]) {
167
+ throw new Error("--report-dir requires a path");
168
+ }
169
+ return path.resolve(process.argv[index + 1]);
170
+ }
171
+
172
+ async function readText(target) {
173
+ return (await fs.readFile(target, "utf8")).replace(/^\uFEFF/, "");
174
+ }
175
+
176
+ async function collectDocs() {
177
+ const files = await walk(docRoot, (target) => target.endsWith(".md"));
178
+ const docs = new Map();
179
+ for (const file of files) {
180
+ const relative = toPosix(path.relative(docRoot, file));
181
+ if (!excludedDocs.has(relative)) docs.set(relative, file);
182
+ }
183
+ return docs;
184
+ }
185
+
186
+ async function collectSkillNames() {
187
+ const entries = await fs.readdir(skillRoot, { withFileTypes: true });
188
+ const names = new Set();
189
+ for (const entry of entries) {
190
+ if (!entry.isDirectory()) continue;
191
+ const skillFile = path.join(skillRoot, entry.name, "SKILL.md");
192
+ if (await exists(skillFile)) names.add(entry.name);
193
+ }
194
+ return names;
195
+ }
196
+
197
+ async function collectReadmeSkillNames() {
198
+ const names = new Set();
199
+ const pattern = /`([^`/\\]+)[/\\]SKILL\.md`/g;
200
+ for (const match of (await readText(skillReadmePath)).matchAll(pattern)) {
201
+ names.add(match[1]);
202
+ }
203
+ return names;
204
+ }
205
+
206
+ async function parseCapabilityMap() {
207
+ const mapped = new Map();
208
+ for (const line of (await readText(mapPath)).split(/\r?\n/)) {
209
+ const match = line.match(mapRowPattern);
210
+ if (!match) continue;
211
+ const docPath = match[1].replaceAll("\\", "/");
212
+ const owners = match[2]
213
+ .split(",")
214
+ .map((owner) => owner.trim().replaceAll("`", ""))
215
+ .filter(Boolean);
216
+ mapped.set(docPath, owners);
217
+ }
218
+ return mapped;
219
+ }
220
+
221
+ function normalizeV8Token(rawToken) {
222
+ const token = rawToken.replaceAll("?.", ".");
223
+ for (const base of [...dynamicBases].sort((a, b) => b.length - a.length)) {
224
+ if (token.startsWith(`${base}.`)) {
225
+ return {
226
+ token: base,
227
+ reason: `${token} -> ${base} (dynamic data property)`,
228
+ };
229
+ }
230
+ }
231
+ if (token.startsWith("V8.Print.BLEInformation.")) {
232
+ return {
233
+ token: "V8.Print.BLEInformation",
234
+ reason:
235
+ `${token} -> V8.Print.BLEInformation ` +
236
+ "(diagnostic data property)",
237
+ };
238
+ }
239
+ if (token.startsWith("V8.Dbs.") && token !== "V8.Dbs.Open") {
240
+ return {
241
+ token: "V8.Dbs",
242
+ reason: `${token} -> V8.Dbs (dynamic extension database)`,
243
+ };
244
+ }
245
+ return { token, reason: null };
246
+ }
247
+
248
+ async function extractTokens(files) {
249
+ const tokens = new Set();
250
+ const normalizedExamples = new Map();
251
+ for (const file of files) {
252
+ const matches = (await readText(file)).match(v8TokenPattern) ?? [];
253
+ for (const rawToken of matches) {
254
+ const normalized = normalizeV8Token(rawToken);
255
+ tokens.add(normalized.token);
256
+ if (!normalized.reason) continue;
257
+ if (!normalizedExamples.has(normalized.token)) {
258
+ normalizedExamples.set(normalized.token, new Set());
259
+ }
260
+ normalizedExamples.get(normalized.token).add(normalized.reason);
261
+ }
262
+ }
263
+ return { tokens, normalizedExamples };
264
+ }
265
+
266
+ function normalizeNamedApi(rawToken) {
267
+ const token = rawToken.replaceAll("?.", ".");
268
+ if (/\.(?:cs|js|ts|vue)$/i.test(token)) {
269
+ return {
270
+ token: null,
271
+ reason: `${token} (source filename, not callable API)`,
272
+ };
273
+ }
274
+ return { token, reason: null };
275
+ }
276
+
277
+ async function extractNamedApis(files) {
278
+ const names = new Set();
279
+ const ignoredExamples = new Set();
280
+ for (const file of files) {
281
+ const matches = (await readText(file)).match(namedApiPattern) ?? [];
282
+ for (const rawToken of matches) {
283
+ const normalized = normalizeNamedApi(rawToken);
284
+ if (normalized.token) {
285
+ names.add(normalized.token);
286
+ } else if (normalized.reason) {
287
+ ignoredExamples.add(normalized.reason);
288
+ }
289
+ }
290
+ }
291
+ return { names, ignoredExamples };
292
+ }
293
+
294
+ async function extractMcpTools(files) {
295
+ const names = new Set();
296
+ for (const file of files) {
297
+ const matches = (await readText(file)).match(mcpToolPattern) ?? [];
298
+ for (const match of matches) names.add(match);
299
+ }
300
+ return names;
301
+ }
302
+
303
+ function normalizeHttpRoute(rawRoute) {
304
+ const cleaned = rawRoute.replace(/[.,;:)\]]+$/g, "");
305
+ const pathOnly = cleaned.split("?")[0];
306
+ const lower = pathOnly.toLowerCase();
307
+
308
+ if (lower.startsWith("/apiengine/")) {
309
+ if (lower.includes("--osclient--")) {
310
+ return {
311
+ route: "/apiengine/{apienginekey}--osclient--{osclient}--",
312
+ reason:
313
+ `${cleaned} -> ` +
314
+ "/apiengine/{ApiEngineKey}--OsClient--{OsClient}--",
315
+ };
316
+ }
317
+ return {
318
+ route: "/apiengine/{apienginekey}",
319
+ reason: `${cleaned} -> /apiengine/{ApiEngineKey}`,
320
+ };
321
+ }
322
+
323
+ if (lower === "/api/example" || lower.startsWith("/api/example/")) {
324
+ return {
325
+ route: "/api/{controller}/{action}",
326
+ reason: `${cleaned} -> /api/{Controller}/{Action} (generic example)`,
327
+ };
328
+ }
329
+
330
+ const formPrefix = "/api/formengine/";
331
+ if (lower.startsWith(formPrefix)) {
332
+ const segment = lower.slice(formPrefix.length);
333
+ const compact = segment.replaceAll("-", "");
334
+ const operations = [
335
+ "gettabledataanonymous",
336
+ "getformdataanonymous",
337
+ "addformdataanonymous",
338
+ "uptformdatabywhere",
339
+ "delformdatabywhere",
340
+ "gettabledata",
341
+ "getformdata",
342
+ "addformdata",
343
+ "uptformdata",
344
+ "delformdata",
345
+ ];
346
+ for (const operation of operations) {
347
+ if (compact === operation) {
348
+ return { route: `${formPrefix}${operation}`, reason: null };
349
+ }
350
+ if (compact.startsWith(operation)) {
351
+ return {
352
+ route: `${formPrefix}${operation}-{formenginekey}`,
353
+ reason:
354
+ `${cleaned} -> ` +
355
+ `/api/formengine/${operation}-{FormEngineKey}`,
356
+ };
357
+ }
358
+ }
359
+ }
360
+
361
+ return { route: lower, reason: null };
362
+ }
363
+
364
+ async function extractRoutes(files) {
365
+ const routes = new Set();
366
+ const normalizedExamples = new Map();
367
+ for (const file of files) {
368
+ const matches = (await readText(file)).match(httpRoutePattern) ?? [];
369
+ for (const rawRoute of matches) {
370
+ const normalized = normalizeHttpRoute(rawRoute);
371
+ routes.add(normalized.route);
372
+ if (!normalized.reason) continue;
373
+ if (!normalizedExamples.has(normalized.route)) {
374
+ normalizedExamples.set(normalized.route, new Set());
375
+ }
376
+ normalizedExamples.get(normalized.route).add(normalized.reason);
377
+ }
378
+ }
379
+ return { routes, normalizedExamples };
380
+ }
381
+
382
+ async function extractGlobalFunctions(files) {
383
+ const functions = new Set();
384
+ for (const file of files) {
385
+ const matches = (await readText(file)).match(globalFunctionPattern) ?? [];
386
+ for (const match of matches) {
387
+ functions.add(match.replace(/\s*\($/, ""));
388
+ }
389
+ }
390
+ return functions;
391
+ }
392
+
393
+ async function inspectComponents() {
394
+ const source = JSON.parse(await readText(componentSourcePath));
395
+ const reference = await readText(componentReferencePath);
396
+ const controls = source
397
+ .map((item) => item.Control)
398
+ .filter((control) => typeof control === "string" && control);
399
+ const missing = controls.filter(
400
+ (control) => !reference.includes(`\`${control}\``),
401
+ );
402
+ return { controls: new Set(controls), missing: sorted(new Set(missing)) };
403
+ }
404
+
405
+ function extractBuilderMethods(source) {
406
+ return new Set(
407
+ [...source.matchAll(/jpPrinter\.([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*function\s*\(/g)]
408
+ .map((match) => match[1]),
409
+ );
410
+ }
411
+
412
+ function extractPrintMembers(source) {
413
+ const startMatch = /\b(?:var|let|const)\s+Print\s*=\s*\{/.exec(source);
414
+ const start = startMatch ? startMatch.index : -1;
415
+ const end = source.indexOf("\n return Print;", start);
416
+ if (start === -1 || end === -1) return new Set();
417
+
418
+ const block = source.slice(start, end);
419
+ const members = new Set(
420
+ [...block.matchAll(
421
+ /^\s{8}([A-Za-z_$][A-Za-z0-9_$]*):\s*(?:async\s+)?function\s*\(/gm,
422
+ )].map((match) => match[1]),
423
+ );
424
+ for (const match of block.matchAll(
425
+ /^\s{8}(createNew(?:ESC)?):\s*[A-Za-z_$][A-Za-z0-9_$.]*,/gm,
426
+ )) {
427
+ members.add(match[1]);
428
+ }
429
+ for (const internalName of ["setTipHandler", "initializeConnection"]) {
430
+ members.delete(internalName);
431
+ }
432
+ for (const name of [...members]) {
433
+ if (name.startsWith("_")) members.delete(name);
434
+ }
435
+ if (/^\s{8}BLEInformation:\s*/m.test(block)) {
436
+ members.add("BLEInformation");
437
+ }
438
+ return members;
439
+ }
440
+
441
+ function findUndocumentedNames(names, text) {
442
+ return sorted([...names].filter((name) => !text.includes(name)));
443
+ }
444
+
445
+ async function inspectBluetoothPrint() {
446
+ const [
447
+ adapter,
448
+ tsc,
449
+ esc,
450
+ officialDoc,
451
+ editorDefinitions,
452
+ ...references
453
+ ] = await Promise.all([
454
+ readText(bluetoothAdapterPath),
455
+ readText(bluetoothTscPath),
456
+ readText(bluetoothEscPath),
457
+ readText(bluetoothOfficialDocPath),
458
+ readText(bluetoothEditorDefinitionPath),
459
+ ...bluetoothReferencePaths.map((target) => readText(target)),
460
+ ]);
461
+ const skillReference = references.join("\n");
462
+ const printMembers = extractPrintMembers(adapter);
463
+ const tscMethods = extractBuilderMethods(tsc);
464
+ const escMethods = extractBuilderMethods(esc);
465
+ const mountCallsMissing = [];
466
+ const mountsMissingFromOfficialDoc = [];
467
+ const mountsMissingFromSkill = [];
468
+
469
+ for (const mountPath of bluetoothMountPaths) {
470
+ const source = await readText(mountPath);
471
+ const relative = toPosix(path.relative(repoRoot, mountPath));
472
+ if (
473
+ !source.includes("initV8Print") ||
474
+ !/initV8Print\s*\(/.test(source)
475
+ ) {
476
+ mountCallsMissing.push(relative);
477
+ }
478
+ if (!officialDoc.includes(relative)) {
479
+ mountsMissingFromOfficialDoc.push(relative);
480
+ }
481
+ if (!skillReference.includes(relative.replace("Microi.Client/", ""))) {
482
+ mountsMissingFromSkill.push(relative);
483
+ }
484
+ }
485
+
486
+ return {
487
+ printMembers,
488
+ tscMethods,
489
+ escMethods,
490
+ printMissingFromOfficialDoc: findUndocumentedNames(
491
+ printMembers,
492
+ officialDoc,
493
+ ),
494
+ printMissingFromEditorDefinitions: findUndocumentedNames(
495
+ printMembers,
496
+ editorDefinitions,
497
+ ),
498
+ printMissingFromSkill: findUndocumentedNames(printMembers, skillReference),
499
+ tscMissingFromOfficialDoc: findUndocumentedNames(tscMethods, officialDoc),
500
+ tscMissingFromEditorDefinitions: findUndocumentedNames(
501
+ tscMethods,
502
+ editorDefinitions,
503
+ ),
504
+ tscMissingFromSkill: findUndocumentedNames(tscMethods, skillReference),
505
+ escMissingFromOfficialDoc: findUndocumentedNames(escMethods, officialDoc),
506
+ escMissingFromEditorDefinitions: findUndocumentedNames(
507
+ escMethods,
508
+ editorDefinitions,
509
+ ),
510
+ escMissingFromSkill: findUndocumentedNames(escMethods, skillReference),
511
+ mountCallsMissing: sorted(mountCallsMissing),
512
+ mountsMissingFromOfficialDoc: sorted(mountsMissingFromOfficialDoc),
513
+ mountsMissingFromSkill: sorted(mountsMissingFromSkill),
514
+ };
515
+ }
516
+
517
+ function sorted(values) {
518
+ return [...values].sort((a, b) => a.localeCompare(b));
519
+ }
520
+
521
+ function renderList(items, empty = "无") {
522
+ return items.length ? items.map((item) => `- \`${item}\``) : [`- ${empty}`];
523
+ }
524
+
525
+ function buildReport({
526
+ docs,
527
+ mapped,
528
+ skills,
529
+ readmeSkills,
530
+ docTokens,
531
+ skillTokens,
532
+ normalizedExamples,
533
+ docNamedApis,
534
+ skillNamedApis,
535
+ ignoredNamedApiExamples,
536
+ docMcpTools,
537
+ skillMcpTools,
538
+ docRoutes,
539
+ skillRoutes,
540
+ normalizedRoutes,
541
+ docGlobalFunctions,
542
+ skillGlobalFunctions,
543
+ componentControls,
544
+ missingComponents,
545
+ bluetooth,
546
+ }) {
547
+ const unmapped = sorted([...docs.keys()].filter((key) => !mapped.has(key)));
548
+ const stale = sorted([...mapped.keys()].filter((key) => !docs.has(key)));
549
+ const unknownOwners = [];
550
+ const ownerless = [];
551
+ for (const [doc, owners] of mapped) {
552
+ if (!owners.length) ownerless.push(doc);
553
+ for (const owner of owners) {
554
+ if (!skills.has(owner)) unknownOwners.push(`${doc}: ${owner}`);
555
+ }
556
+ }
557
+ unknownOwners.sort((a, b) => a.localeCompare(b));
558
+ ownerless.sort((a, b) => a.localeCompare(b));
559
+ const uncovered = sorted(
560
+ [...docTokens].filter((token) => !skillTokens.has(token)),
561
+ );
562
+ const uncoveredNamedApis = sorted(
563
+ [...docNamedApis].filter((name) => !skillNamedApis.has(name)),
564
+ );
565
+ const uncoveredMcpTools = sorted(
566
+ [...docMcpTools].filter((name) => !skillMcpTools.has(name)),
567
+ );
568
+ const uncoveredRoutes = sorted(
569
+ [...docRoutes].filter((route) => !skillRoutes.has(route)),
570
+ );
571
+ const uncoveredGlobalFunctions = sorted(
572
+ [...docGlobalFunctions].filter(
573
+ (functionName) => !skillGlobalFunctions.has(functionName),
574
+ ),
575
+ );
576
+ const skillsMissingFromReadme = sorted(
577
+ [...skills].filter((skill) => !readmeSkills.has(skill)),
578
+ );
579
+ const staleReadmeSkills = sorted(
580
+ [...readmeSkills].filter((skill) => !skills.has(skill)),
581
+ );
582
+ const bluetoothIssues = [
583
+ ...bluetooth.printMissingFromOfficialDoc.map(
584
+ (name) => `official doc missing V8.Print.${name}`,
585
+ ),
586
+ ...bluetooth.printMissingFromSkill.map(
587
+ (name) => `Skill missing V8.Print.${name}`,
588
+ ),
589
+ ...bluetooth.printMissingFromEditorDefinitions.map(
590
+ (name) => `editor definitions missing V8.Print.${name}`,
591
+ ),
592
+ ...bluetooth.tscMissingFromOfficialDoc.map(
593
+ (name) => `official doc missing TSC.${name}`,
594
+ ),
595
+ ...bluetooth.tscMissingFromSkill.map(
596
+ (name) => `Skill missing TSC.${name}`,
597
+ ),
598
+ ...bluetooth.tscMissingFromEditorDefinitions.map(
599
+ (name) => `editor definitions missing TSC.${name}`,
600
+ ),
601
+ ...bluetooth.escMissingFromOfficialDoc.map(
602
+ (name) => `official doc missing ESC.${name}`,
603
+ ),
604
+ ...bluetooth.escMissingFromSkill.map(
605
+ (name) => `Skill missing ESC.${name}`,
606
+ ),
607
+ ...bluetooth.escMissingFromEditorDefinitions.map(
608
+ (name) => `editor definitions missing ESC.${name}`,
609
+ ),
610
+ ...bluetooth.mountCallsMissing.map(
611
+ (name) => `source mount missing initV8Print: ${name}`,
612
+ ),
613
+ ...bluetooth.mountsMissingFromOfficialDoc.map(
614
+ (name) => `official doc missing mount: ${name}`,
615
+ ),
616
+ ...bluetooth.mountsMissingFromSkill.map(
617
+ (name) => `Skill missing mount: ${name}`,
618
+ ),
619
+ ];
620
+ const ok =
621
+ !unmapped.length &&
622
+ !stale.length &&
623
+ !unknownOwners.length &&
624
+ !ownerless.length &&
625
+ !uncovered.length &&
626
+ !uncoveredNamedApis.length &&
627
+ !uncoveredMcpTools.length &&
628
+ !uncoveredRoutes.length &&
629
+ !uncoveredGlobalFunctions.length &&
630
+ !missingComponents.length &&
631
+ !bluetoothIssues.length &&
632
+ !skillsMissingFromReadme.length &&
633
+ !staleReadmeSkills.length;
634
+
635
+ const generatedAt = new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
636
+ const lines = [
637
+ "# Microi 中文文档与 Skills 覆盖审计",
638
+ "",
639
+ `- 生成时间:\`${generatedAt}\``,
640
+ `- 中文文档:\`${docs.size}\`(已排除更新日志与英文站)`,
641
+ `- Skills:\`${skills.size}\``,
642
+ `- 文档标准化 V8 名称:\`${docTokens.size}\``,
643
+ `- 文档其它具名 API:\`${docNamedApis.size}\``,
644
+ `- 文档 MCP 工具名:\`${docMcpTools.size}\``,
645
+ `- 文档标准化 HTTP 路由:\`${docRoutes.size}\``,
646
+ `- 文档平台全局函数:\`${docGlobalFunctions.size}\``,
647
+ `- 当前表单组件:\`${componentControls.size}\``,
648
+ `- 蓝牙打印公开入口:\`${bluetooth.printMembers.size}\``,
649
+ `- TSC 构建器方法:\`${bluetooth.tscMethods.size}\``,
650
+ `- ESC/POS 构建器方法:\`${bluetooth.escMethods.size}\``,
651
+ `- Skill 标准化 V8 名称:\`${skillTokens.size}\``,
652
+ `- 结论:**${ok ? "PASS" : "FAIL"}**`,
653
+ "",
654
+ "## 未映射中文文档",
655
+ "",
656
+ ...renderList(unmapped),
657
+ "",
658
+ "## 映射中已不存在的中文文档",
659
+ "",
660
+ ...renderList(stale),
661
+ "",
662
+ "## 不存在或缺失的责任 Skill",
663
+ "",
664
+ ...renderList([...unknownOwners, ...ownerless]),
665
+ "",
666
+ "## Skills 总目录漏项或失效项",
667
+ "",
668
+ ...renderList([
669
+ ...skillsMissingFromReadme.map((skill) => `missing: ${skill}`),
670
+ ...staleReadmeSkills.map((skill) => `stale: ${skill}`),
671
+ ]),
672
+ "",
673
+ "## 文档出现但 Skills 未覆盖的 V8 名称",
674
+ "",
675
+ ...renderList(uncovered),
676
+ "",
677
+ "## 文档出现但 Skills 未覆盖的其它具名 API",
678
+ "",
679
+ ...renderList(uncoveredNamedApis),
680
+ "",
681
+ "## 文档出现但 Skills 未覆盖的 MCP 工具名",
682
+ "",
683
+ ...renderList(uncoveredMcpTools),
684
+ "",
685
+ "## 文档出现但 Skills 未覆盖的平台 HTTP 路由",
686
+ "",
687
+ ...renderList(uncoveredRoutes),
688
+ "",
689
+ "## 文档出现但 Skills 未覆盖的平台全局函数",
690
+ "",
691
+ ...renderList(uncoveredGlobalFunctions),
692
+ "",
693
+ "## 当前组件清单未进入表单 Skill 的控件",
694
+ "",
695
+ ...renderList(missingComponents),
696
+ "",
697
+ "## 蓝牙打印源码、官方文档与 Skills 未对齐项",
698
+ "",
699
+ ...renderList(bluetoothIssues),
700
+ "",
701
+ "## 已排除的源码文件名",
702
+ "",
703
+ ...renderList(sorted(ignoredNamedApiExamples)),
704
+ "",
705
+ "## 已归一的动态示例",
706
+ "",
707
+ ];
708
+
709
+ if (normalizedExamples.size) {
710
+ for (const key of sorted(normalizedExamples.keys())) {
711
+ lines.push(`### \`${key}\``, "");
712
+ for (const reason of sorted(normalizedExamples.get(key))) {
713
+ lines.push(`- \`${reason}\``);
714
+ }
715
+ lines.push("");
716
+ }
717
+ } else {
718
+ lines.push("- 无", "");
719
+ }
720
+
721
+ lines.push("## 已归一的 HTTP 路由示例", "");
722
+ if (normalizedRoutes.size) {
723
+ for (const key of sorted(normalizedRoutes.keys())) {
724
+ lines.push(`### \`${key}\``, "");
725
+ for (const reason of sorted(normalizedRoutes.get(key))) {
726
+ lines.push(`- \`${reason}\``);
727
+ }
728
+ lines.push("");
729
+ }
730
+ } else {
731
+ lines.push("- 无", "");
732
+ }
733
+
734
+ const normalizedPayload = {};
735
+ for (const key of sorted(normalizedExamples.keys())) {
736
+ normalizedPayload[key] = sorted(normalizedExamples.get(key));
737
+ }
738
+ const normalizedRoutePayload = {};
739
+ for (const key of sorted(normalizedRoutes.keys())) {
740
+ normalizedRoutePayload[key] = sorted(normalizedRoutes.get(key));
741
+ }
742
+ return {
743
+ ok,
744
+ markdown: `${lines.join("\n").trimEnd()}\n`,
745
+ payload: {
746
+ generatedAt,
747
+ ok,
748
+ counts: {
749
+ docs: docs.size,
750
+ skills: skills.size,
751
+ docV8Tokens: docTokens.size,
752
+ skillV8Tokens: skillTokens.size,
753
+ docNamedApis: docNamedApis.size,
754
+ skillNamedApis: skillNamedApis.size,
755
+ docMcpTools: docMcpTools.size,
756
+ skillMcpTools: skillMcpTools.size,
757
+ docHttpRoutes: docRoutes.size,
758
+ skillHttpRoutes: skillRoutes.size,
759
+ docGlobalFunctions: docGlobalFunctions.size,
760
+ skillGlobalFunctions: skillGlobalFunctions.size,
761
+ componentControls: componentControls.size,
762
+ bluetoothPrintMembers: bluetooth.printMembers.size,
763
+ bluetoothTscMethods: bluetooth.tscMethods.size,
764
+ bluetoothEscMethods: bluetooth.escMethods.size,
765
+ },
766
+ unmappedDocs: unmapped,
767
+ staleMappedDocs: stale,
768
+ unknownOwners,
769
+ ownerlessDocs: ownerless,
770
+ skillsMissingFromReadme,
771
+ staleReadmeSkills,
772
+ uncoveredV8Tokens: uncovered,
773
+ uncoveredNamedApis,
774
+ uncoveredMcpTools,
775
+ uncoveredHttpRoutes: uncoveredRoutes,
776
+ uncoveredGlobalFunctions,
777
+ missingComponents,
778
+ bluetoothIssues,
779
+ bluetooth: {
780
+ printMembers: sorted(bluetooth.printMembers),
781
+ tscMethods: sorted(bluetooth.tscMethods),
782
+ escMethods: sorted(bluetooth.escMethods),
783
+ printMissingFromOfficialDoc: bluetooth.printMissingFromOfficialDoc,
784
+ printMissingFromEditorDefinitions:
785
+ bluetooth.printMissingFromEditorDefinitions,
786
+ printMissingFromSkill: bluetooth.printMissingFromSkill,
787
+ tscMissingFromOfficialDoc: bluetooth.tscMissingFromOfficialDoc,
788
+ tscMissingFromEditorDefinitions:
789
+ bluetooth.tscMissingFromEditorDefinitions,
790
+ tscMissingFromSkill: bluetooth.tscMissingFromSkill,
791
+ escMissingFromOfficialDoc: bluetooth.escMissingFromOfficialDoc,
792
+ escMissingFromEditorDefinitions:
793
+ bluetooth.escMissingFromEditorDefinitions,
794
+ escMissingFromSkill: bluetooth.escMissingFromSkill,
795
+ mountCallsMissing: bluetooth.mountCallsMissing,
796
+ mountsMissingFromOfficialDoc: bluetooth.mountsMissingFromOfficialDoc,
797
+ mountsMissingFromSkill: bluetooth.mountsMissingFromSkill,
798
+ },
799
+ ignoredNamedApiExamples: sorted(ignoredNamedApiExamples),
800
+ normalizedExamples: normalizedPayload,
801
+ normalizedRoutes: normalizedRoutePayload,
802
+ },
803
+ };
804
+ }
805
+
806
+ async function main() {
807
+ if (
808
+ !(await exists(docRoot)) ||
809
+ !(await exists(skillRoot)) ||
810
+ !(await exists(skillReadmePath)) ||
811
+ !(await exists(mapPath)) ||
812
+ !(await exists(componentSourcePath)) ||
813
+ !(await exists(componentReferencePath)) ||
814
+ !(await exists(bluetoothOfficialDocPath)) ||
815
+ !(await exists(bluetoothAdapterPath)) ||
816
+ !(await exists(bluetoothEditorDefinitionPath)) ||
817
+ !(await exists(bluetoothTscPath)) ||
818
+ !(await exists(bluetoothEscPath)) ||
819
+ !(
820
+ await Promise.all(bluetoothReferencePaths.map((target) => exists(target)))
821
+ ).every(Boolean) ||
822
+ !(
823
+ await Promise.all(bluetoothMountPaths.map((target) => exists(target)))
824
+ ).every(Boolean)
825
+ ) {
826
+ throw new Error(
827
+ "Microi docs, skills, capability map, or component catalog is missing",
828
+ );
829
+ }
830
+ const reportDir = parseReportDir();
831
+ const docs = await collectDocs();
832
+ const skills = await collectSkillNames();
833
+ const readmeSkills = await collectReadmeSkillNames();
834
+ const mapped = await parseCapabilityMap();
835
+ const docExtraction = await extractTokens([...docs.values()]);
836
+ const docNamedApis = await extractNamedApis([...docs.values()]);
837
+ const docMcpTools = await extractMcpTools([...docs.values()]);
838
+ const docRoutes = await extractRoutes([...docs.values()]);
839
+ const docGlobalFunctions = await extractGlobalFunctions([...docs.values()]);
840
+ const skillMarkdown = await walk(skillRoot, (target) =>
841
+ target.endsWith(".md"),
842
+ );
843
+ const skillExtraction = await extractTokens(skillMarkdown);
844
+ const skillNamedApis = await extractNamedApis(skillMarkdown);
845
+ const skillMcpTools = await extractMcpTools(skillMarkdown);
846
+ const skillRoutes = await extractRoutes(skillMarkdown);
847
+ const skillGlobalFunctions = await extractGlobalFunctions(skillMarkdown);
848
+ const components = await inspectComponents();
849
+ const bluetooth = await inspectBluetoothPrint();
850
+ const report = buildReport({
851
+ docs,
852
+ mapped,
853
+ skills,
854
+ readmeSkills,
855
+ docTokens: docExtraction.tokens,
856
+ skillTokens: skillExtraction.tokens,
857
+ normalizedExamples: docExtraction.normalizedExamples,
858
+ docNamedApis: docNamedApis.names,
859
+ skillNamedApis: skillNamedApis.names,
860
+ ignoredNamedApiExamples: docNamedApis.ignoredExamples,
861
+ docMcpTools,
862
+ skillMcpTools,
863
+ docRoutes: docRoutes.routes,
864
+ skillRoutes: skillRoutes.routes,
865
+ normalizedRoutes: docRoutes.normalizedExamples,
866
+ docGlobalFunctions,
867
+ skillGlobalFunctions,
868
+ componentControls: components.controls,
869
+ missingComponents: components.missing,
870
+ bluetooth,
871
+ });
872
+
873
+ await fs.mkdir(reportDir, { recursive: true });
874
+ const markdownPath = path.join(
875
+ reportDir,
876
+ "microi-doc-skill-coverage.md",
877
+ );
878
+ const jsonPath = path.join(reportDir, "microi-doc-skill-coverage.json");
879
+ await fs.writeFile(markdownPath, report.markdown, "utf8");
880
+ await fs.writeFile(
881
+ jsonPath,
882
+ `${JSON.stringify(report.payload, null, 2)}\n`,
883
+ "utf8",
884
+ );
885
+ process.stdout.write(report.markdown);
886
+ console.log(`Markdown report: ${markdownPath}`);
887
+ console.log(`JSON report: ${jsonPath}`);
888
+ process.exitCode = report.ok ? 0 : 1;
889
+ }
890
+
891
+ main().catch((error) => {
892
+ console.error(error.message);
893
+ process.exitCode = 2;
894
+ });