@microi.net/cli 5.9.2 → 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 (199) 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 +384 -378
  16. package/skills/.microi-skills-version.json +2 -2
  17. package/skills/.progressive-disclosure-manifest.json +122 -115
  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 -203
  72. 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 -227
  73. 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 -129
  74. package/skills/microi-solution-quotation/SKILL.md +78 -78
  75. package/skills/microi-solution-quotation/agents/openai.yaml +4 -4
  76. package/skills/microi-solution-quotation/scripts/build_solution_quote.py +296 -296
  77. package/skills/microi-sso/SKILL.md +95 -91
  78. package/skills/microi-sso/references/acceptance.md +51 -49
  79. package/skills/microi-sso/references/configuration-and-security.md +53 -53
  80. package/skills/microi-sso/references/inbound.md +53 -53
  81. package/skills/microi-sso/references/outbound.md +43 -39
  82. package/skills/microi-ui/SKILL.md +192 -192
  83. 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
  84. package/skills/microi-uniapp-frontend/SKILL.md +206 -204
  85. 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
  86. 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
  87. package/skills/microi.v8.js +1921 -1921
  88. package/skills/module-engine/SKILL.md +255 -255
  89. package/skills/module-engine/references/module-config.md +204 -204
  90. package/skills/ocr-engine/SKILL.md +113 -113
  91. package/skills/ocr-engine/agents/openai.yaml +4 -4
  92. package/skills/page-engine/SKILL.md +206 -206
  93. package/skills/page-engine/examples/compact-dashboard.json +1444 -1444
  94. 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
  95. 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
  96. package/skills/performance-testing/SKILL.md +227 -221
  97. package/skills/playwright-e2e/SKILL.md +197 -196
  98. 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
  99. 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
  100. package/skills/playwright-e2e/references/progressive-03-microi-helper-/346/250/241/346/235/277.md +221 -221
  101. package/skills/playwright-e2e/references/progressive-04-ci-/345/273/272/350/256/256.md +115 -115
  102. package/skills/print-engine/SKILL.md +259 -259
  103. package/skills/production-readonly-audit/SKILL.md +41 -41
  104. package/skills/report-engine/SKILL.md +71 -71
  105. package/skills/report-engine/agents/openai.yaml +4 -4
  106. package/skills/scripts/optimize-progressive-disclosure.mjs +204 -204
  107. package/skills/scripts/refresh-progressive-disclosure.mjs +64 -64
  108. package/skills/scripts/sync-embedded-skills.mjs +46 -46
  109. package/skills/scripts/validate-progressive-disclosure.mjs +57 -57
  110. package/skills/search-engine/SKILL.md +75 -75
  111. package/skills/search-engine/agents/openai.yaml +4 -4
  112. package/skills/spider-engine/SKILL.md +190 -190
  113. package/skills/system-observability/SKILL.md +259 -246
  114. package/skills/system-observability/references/memory-incident-triage.md +77 -77
  115. package/skills/translate-engine/SKILL.md +140 -140
  116. package/skills/translate-engine/agents/openai.yaml +4 -4
  117. package/skills/ui-design/SKILL.md +223 -223
  118. package/skills/ui-design/assets/pattern-showcase/app.js +54 -54
  119. package/skills/ui-design/assets/pattern-showcase/index.html +163 -163
  120. package/skills/ui-design/assets/pattern-showcase/styles.css +311 -311
  121. package/skills/ui-design/assets/templates/MCI-DESIGN.md +206 -206
  122. package/skills/ui-design/references/design-pattern-library.md +184 -184
  123. package/skills/ui-design/references/mci-design-contract.md +163 -163
  124. package/skills/ui-design/references/motion-and-media.md +78 -78
  125. package/skills/ui-design/references/product-flow-recipes.md +94 -94
  126. 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
  127. package/skills/ui-design/references/progressive-02-/345/255/227/344/275/223.md +164 -164
  128. 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
  129. 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
  130. 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
  131. 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
  132. 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
  133. 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
  134. package/skills/uniapp-mall-assets/SKILL.md +176 -176
  135. package/skills/unity-integration/SKILL.md +178 -171
  136. package/skills/unity-integration/agents/openai.yaml +4 -4
  137. package/skills/unity-integration/references/ai-app-delivery.md +119 -119
  138. package/skills/unity-integration/references/sdk-api.md +82 -82
  139. package/skills/unity-integration/references/toolbox-migration.md +66 -66
  140. package/skills/unity-integration/references/webgl-hosting.md +57 -57
  141. package/skills/v8-api-config/SKILL.md +388 -388
  142. package/skills/v8-cache-pattern/SKILL.md +312 -312
  143. package/skills/v8-crud-api/SKILL.md +179 -178
  144. 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
  145. 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
  146. package/skills/v8-debugging/SKILL.md +284 -284
  147. package/skills/v8-explorer-tree/SKILL.md +228 -228
  148. package/skills/v8-export-import/SKILL.md +219 -219
  149. 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
  150. package/skills/v8-export-import/references/progressive-02-powerpoint-/345/257/274/345/207/272.md +202 -202
  151. 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
  152. package/skills/v8-file-upload/SKILL.md +255 -284
  153. package/skills/v8-file-upload/references/cdn-alias-repair.md +13 -0
  154. 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
  155. 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
  156. 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
  157. package/skills/v8-formengine-http/SKILL.md +238 -238
  158. package/skills/v8-frontend-events/SKILL.md +180 -180
  159. package/skills/v8-frontend-events/references/bluetooth-print-api.md +135 -135
  160. package/skills/v8-frontend-events/references/bluetooth-print.md +258 -258
  161. package/skills/v8-frontend-events/references/progressive-01-/345/210/227/350/241/250/344/272/213/344/273/266.md +219 -219
  162. package/skills/v8-http-integration/SKILL.md +182 -182
  163. package/skills/v8-http-integration/references/progressive-01-get-/350/257/267/346/261/202.md +220 -220
  164. 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
  165. package/skills/v8-image-processing/SKILL.md +190 -190
  166. package/skills/v8-image-processing/agents/openai.yaml +4 -4
  167. package/skills/v8-image-processing/references/api-reference.md +623 -623
  168. package/skills/v8-menu-buttons/SKILL.md +186 -186
  169. 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
  170. 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
  171. 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
  172. package/skills/v8-mongodb/SKILL.md +200 -200
  173. package/skills/v8-mq-mqtt/SKILL.md +178 -176
  174. package/skills/v8-mq-mqtt/references/mqtt-production.md +352 -342
  175. package/skills/v8-mq-mqtt/references/progressive-01-v8-mqtt-iot-/347/211/251/350/201/224/347/275/221.md +181 -181
  176. package/skills/v8-mq-mqtt/scripts/check-mqtt-skill-coverage.mjs +203 -203
  177. package/skills/v8-saas-multi-tenant/SKILL.md +305 -305
  178. package/skills/v8-security/SKILL.md +227 -210
  179. package/skills/v8-security/references/progressive-01-2-/346/235/203/351/231/220/346/240/241/351/252/214.md +200 -200
  180. package/skills/v8-security/references/progressive-02-7-/346/227/245/345/277/227/350/256/260/345/275/225.md +160 -160
  181. package/skills/v8-sql-query/SKILL.md +302 -302
  182. package/skills/v8-table-event/SKILL.md +176 -176
  183. 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
  184. 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
  185. package/skills/v8-tcp-integration/SKILL.md +158 -147
  186. package/skills/v8-tcp-integration/agents/openai.yaml +4 -4
  187. package/skills/v8-template-engine/SKILL.md +167 -167
  188. package/skills/v8-utilities/SKILL.md +104 -104
  189. package/skills/v8-utilities/references/client-api-index.md +143 -143
  190. package/skills/v8-utilities/references/platform-http-routes.md +83 -83
  191. package/skills/v8-utilities/references/server-api-index.md +188 -188
  192. package/skills/v8-workflow/SKILL.md +252 -252
  193. 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
  194. package/skills/v8-workflow/references/workflow-configuration.md +49 -49
  195. package/skills/vision-engine/SKILL.md +160 -160
  196. package/skills/vision-engine/agents/openai.yaml +4 -4
  197. package/skills/vision-engine/references/architecture-and-acceptance.md +194 -194
  198. package/skills/workspace-conventions/SKILL.md +13 -13
  199. 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
+ });