@thinkingai/ae-cli 6.1.20 → 6.1.23

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 (338) hide show
  1. package/README.md +2 -1
  2. package/README.zh.md +2 -1
  3. package/dist/{auth-FUM37MHF.js → auth-XZNXFRJS.js} +5 -5
  4. package/dist/{capability-AXFQW5WM.js → capability-FYTU3X6L.js} +42 -12
  5. package/dist/{chunk-LNZBEQXW.js → chunk-4NRCTUZJ.js} +27041 -474
  6. package/dist/{chunk-VXNMYUXU.js → chunk-57RRFUEZ.js} +23 -7
  7. package/dist/{chunk-WZ6YVQSF.js → chunk-7FTTULED.js} +1 -1
  8. package/dist/{chunk-ATSM5XAW.js → chunk-7XGFSUOM.js} +2 -2
  9. package/dist/chunk-CCS66K4K.js +155 -0
  10. package/dist/{chunk-I4WQAEYB.js → chunk-DVMLWQD4.js} +8 -186
  11. package/dist/{chunk-CZU3V3DQ.js → chunk-DX5CDO34.js} +27 -6
  12. package/dist/chunk-EQ255HKO.js +38248 -0
  13. package/dist/{chunk-BBEFP4SB.js → chunk-HBGADCKA.js} +3 -3
  14. package/dist/{chunk-QJQZH5GQ.js → chunk-HFGZGYCG.js} +10 -6
  15. package/dist/{chunk-ECBLHAZO.js → chunk-JOCDD4ON.js} +1 -1
  16. package/dist/{chunk-TKHSULJT.js → chunk-KPQY44JQ.js} +6 -0
  17. package/dist/{chunk-4P355ZWB.js → chunk-MAPZ7VB6.js} +21 -1
  18. package/dist/chunk-V2NUHPXF.js +196 -0
  19. package/dist/{chunk-RSG4ONOI.js → chunk-VYXXOKYS.js} +5 -5
  20. package/dist/{community-report-client-XXWGSBSD.js → community-report-client-CU22W53K.js} +1 -1
  21. package/dist/{config-EXUGQN5T.js → config-XO3LI3SA.js} +4 -4
  22. package/dist/context-IFJI3LIT.js +46 -0
  23. package/dist/{data-integration-4NZ53OMT.js → data-integration-HNGUKFIC.js} +44 -35
  24. package/dist/index.js +101 -64
  25. package/dist/{local-data-upload-client-KYOKVYZV.js → local-data-upload-client-GTRIT64F.js} +3 -2
  26. package/dist/{memory-ATNKZNW5.js → memory-QGPSNCQS.js} +4 -4
  27. package/dist/{metadata-VZVC7YMH.js → metadata-DI5BMYFU.js} +8 -8
  28. package/dist/{model-E4JMQ4V2.js → model-RNPQXIBI.js} +4 -4
  29. package/dist/{personal-semantic-preference-7S2SQ3UV.js → personal-semantic-preference-5C3WUXFI.js} +11 -8
  30. package/dist/{project-semantic-2SZP2OPO.js → project-semantic-3LF6Q6JZ.js} +27 -21
  31. package/dist/{sync-YV3E66IF.js → sync-O45NXEQY.js} +7 -6
  32. package/dist/{te-agent-JB5T3PO7.js → te-agent-K5MSPNHJ.js} +5 -5
  33. package/dist/{te-analysis-3YJAAT2D.js → te-analysis-AISWCOOZ.js} +1086 -354
  34. package/dist/{te-community-UDBI672N.js → te-community-4OYU3BDA.js} +4 -4
  35. package/dist/{te-dataops-ZLYOCXZ4.js → te-dataops-NFIPB6FX.js} +5 -5
  36. package/dist/{te-engage-4XG6OJML.js → te-engage-BAM3GBFX.js} +7 -7
  37. package/dist/{te-experiment-VXUWPINJ.js → te-experiment-GUTZXZ5Z.js} +132 -14
  38. package/dist/{te-kb-WYQWHFSC.js → te-kb-3TS73NO6.js} +454 -194
  39. package/dist/{te-system-7G6F2LJA.js → te-system-STMHLS5J.js} +12 -7
  40. package/dist/{te-team-E7FBBXMQ.js → te-team-3I5AOX3N.js} +6 -6
  41. package/dist/{update-D47BUG25.js → update-RZXLPCRH.js} +4 -4
  42. package/package.json +21 -14
  43. package/skills/ae-analysis/SKILL.md +91 -78
  44. package/skills/ae-analysis/references/adhoc_export.md +11 -18
  45. package/skills/ae-analysis/references/adhoc_run.md +15 -7
  46. package/skills/ae-analysis/references/agent_review_detail.md +11 -0
  47. package/skills/ae-analysis/references/agent_review_evidence.md +46 -0
  48. package/skills/ae-analysis/references/agent_review_list.md +9 -0
  49. package/skills/ae-analysis/references/agent_review_preflight.md +96 -0
  50. package/skills/ae-analysis/references/agent_review_priorities_comparisons.md +76 -0
  51. package/skills/ae-analysis/references/agent_review_records.md +9 -0
  52. package/skills/ae-analysis/references/agent_review_retry.md +12 -0
  53. package/skills/ae-analysis/references/agent_review_review.md +16 -0
  54. package/skills/ae-analysis/references/agent_review_submit_to_page.md +184 -0
  55. package/skills/ae-analysis/references/ai_models/attribution.md +29 -0
  56. package/skills/ae-analysis/references/ai_models/distribution.md +40 -0
  57. package/skills/ae-analysis/references/ai_models/event.md +108 -0
  58. package/skills/ae-analysis/references/ai_models/funnel.md +46 -0
  59. package/skills/ae-analysis/references/ai_models/heat_map.md +26 -0
  60. package/skills/ae-analysis/references/ai_models/interval.md +20 -0
  61. package/skills/ae-analysis/references/ai_models/path.md +55 -0
  62. package/skills/ae-analysis/references/ai_models/prop_analysis.md +43 -0
  63. package/skills/ae-analysis/references/ai_models/rank_list.md +25 -0
  64. package/skills/ae-analysis/references/ai_models/retention.md +70 -0
  65. package/skills/ae-analysis/references/ai_models/revenue.md +28 -0
  66. package/skills/ae-analysis/references/ai_models/sql.md +74 -0
  67. package/skills/ae-analysis/references/ai_models/tag.md +19 -0
  68. package/skills/ae-analysis/references/ai_models.md +32 -473
  69. package/skills/ae-analysis/references/alert_create.md +7 -7
  70. package/skills/ae-analysis/references/alert_delete.md +4 -6
  71. package/skills/ae-analysis/references/alert_detail_list.md +7 -9
  72. package/skills/ae-analysis/references/alert_get.md +5 -7
  73. package/skills/ae-analysis/references/alert_job_list.md +4 -6
  74. package/skills/ae-analysis/references/alert_list.md +6 -8
  75. package/skills/ae-analysis/references/alert_notice_config_list.md +4 -6
  76. package/skills/ae-analysis/references/alert_start.md +5 -7
  77. package/skills/ae-analysis/references/alert_stop.md +5 -7
  78. package/skills/ae-analysis/references/alert_update.md +8 -8
  79. package/skills/ae-analysis/references/analysis_data_export.md +72 -0
  80. package/skills/ae-analysis/references/analysis_data_retrieval.md +32 -196
  81. package/skills/ae-analysis/references/analysis_gateway_assets.md +23 -180
  82. package/skills/ae-analysis/references/analysis_interpretation.md +37 -17
  83. package/skills/ae-analysis/references/asset_abnormal_get.md +0 -1
  84. package/skills/ae-analysis/references/asset_abnormal_list.md +0 -1
  85. package/skills/ae-analysis/references/asset_authentication_export.md +4 -0
  86. package/skills/ae-analysis/references/asset_authentication_list.md +7 -1
  87. package/skills/ae-analysis/references/asset_authentication_update.md +5 -1
  88. package/skills/ae-analysis/references/asset_batch_dashboard_schedule_freeze.md +8 -6
  89. package/skills/ae-analysis/references/asset_batch_delete.md +6 -6
  90. package/skills/ae-analysis/references/asset_batch_disable_auto_backup.md +4 -5
  91. package/skills/ae-analysis/references/asset_batch_disable_auto_update.md +5 -6
  92. package/skills/ae-analysis/references/asset_batch_handover.md +4 -5
  93. package/skills/ae-analysis/references/asset_batch_info_export.md +4 -5
  94. package/skills/ae-analysis/references/asset_batch_sql_export.md +5 -6
  95. package/skills/ae-analysis/references/asset_dependency_list.md +4 -5
  96. package/skills/ae-analysis/references/asset_export.md +4 -5
  97. package/skills/ae-analysis/references/asset_impact_list.md +5 -9
  98. package/skills/ae-analysis/references/asset_lineage_get.md +5 -6
  99. package/skills/ae-analysis/references/asset_list.md +4 -5
  100. package/skills/ae-analysis/references/asset_query_history_list.md +4 -8
  101. package/skills/ae-analysis/references/asset_recent_list.md +0 -1
  102. package/skills/ae-analysis/references/asset_search.md +12 -9
  103. package/skills/ae-analysis/references/asset_url_get.md +3 -4
  104. package/skills/ae-analysis/references/bi_panel_copy.md +2 -2
  105. package/skills/ae-analysis/references/bi_panel_page_data_export.md +4 -12
  106. package/skills/ae-analysis/references/bi_panel_page_data_run.md +2 -0
  107. package/skills/ae-analysis/references/catalog_export.md +4 -17
  108. package/skills/ae-analysis/references/catalog_list.md +4 -4
  109. package/skills/ae-analysis/references/check_delete.md +5 -7
  110. package/skills/ae-analysis/references/check_export.md +11 -11
  111. package/skills/ae-analysis/references/check_get.md +8 -10
  112. package/skills/ae-analysis/references/check_list.md +4 -6
  113. package/skills/ae-analysis/references/check_retry.md +8 -10
  114. package/skills/ae-analysis/references/check_run.md +2 -4
  115. package/skills/ae-analysis/references/command_index.md +44 -26
  116. package/skills/ae-analysis/references/dashboard_report_data_export.md +5 -13
  117. package/skills/ae-analysis/references/dashboard_report_data_run.md +1 -1
  118. package/skills/ae-analysis/references/dashboard_update.md +14 -3
  119. package/skills/ae-analysis/references/datatable_columns_get.md +0 -1
  120. package/skills/ae-analysis/references/datatable_influence_list.md +0 -1
  121. package/skills/ae-analysis/references/datatable_version_get.md +0 -1
  122. package/skills/ae-analysis/references/datatable_version_list.md +0 -1
  123. package/skills/ae-analysis/references/debug_device_add.md +1 -2
  124. package/skills/ae-analysis/references/drilldown_entities_export.md +3 -3
  125. package/skills/ae-analysis/references/drilldown_events_export.md +3 -2
  126. package/skills/ae-analysis/references/drilldown_user_events_export.md +2 -2
  127. package/skills/ae-analysis/references/entity_detail_export.md +3 -2
  128. package/skills/ae-analysis/references/entity_detail_run.md +1 -0
  129. package/skills/ae-analysis/references/event_blacklist_add.md +5 -7
  130. package/skills/ae-analysis/references/event_blacklist_list.md +4 -6
  131. package/skills/ae-analysis/references/event_blacklist_update.md +6 -8
  132. package/skills/ae-analysis/references/event_changelog_list.md +0 -1
  133. package/skills/ae-analysis/references/event_create.md +0 -1
  134. package/skills/ae-analysis/references/event_detail_export.md +3 -2
  135. package/skills/ae-analysis/references/event_detail_run.md +1 -0
  136. package/skills/ae-analysis/references/event_export.md +0 -1
  137. package/skills/ae-analysis/references/event_get.md +0 -1
  138. package/skills/ae-analysis/references/event_hide_update.md +0 -1
  139. package/skills/ae-analysis/references/event_influence_list.md +0 -1
  140. package/skills/ae-analysis/references/event_list.md +0 -1
  141. package/skills/ae-analysis/references/event_property_bundle_export.md +5 -14
  142. package/skills/ae-analysis/references/event_property_bundle_import.md +2 -6
  143. package/skills/ae-analysis/references/event_relation_update.md +0 -1
  144. package/skills/ae-analysis/references/event_update.md +0 -1
  145. package/skills/ae-analysis/references/exchange_config_update.md +0 -1
  146. package/skills/ae-analysis/references/exchange_rate_refresh.md +0 -1
  147. package/skills/ae-analysis/references/exchange_rule_list.md +0 -1
  148. package/skills/ae-analysis/references/exchange_rule_update.md +0 -1
  149. package/skills/ae-analysis/references/exchange_rule_validate.md +0 -1
  150. package/skills/ae-analysis/references/governance_recommendation_decisions.md +23 -0
  151. package/skills/ae-analysis/references/governance_recommendation_export.md +108 -0
  152. package/skills/ae-analysis/references/governance_recommendation_submit.md +41 -0
  153. package/skills/ae-analysis/references/history_tag_data_drilldown_export.md +4 -4
  154. package/skills/ae-analysis/references/history_tag_data_export.md +3 -3
  155. package/skills/ae-analysis/references/ingest_error_list.md +7 -9
  156. package/skills/ae-analysis/references/ingest_summary.md +6 -8
  157. package/skills/ae-analysis/references/live_data_export.md +10 -10
  158. package/skills/ae-analysis/references/live_data_list.md +7 -9
  159. package/skills/ae-analysis/references/metadata_resolution.md +78 -0
  160. package/skills/ae-analysis/references/metric_export.md +0 -1
  161. package/skills/ae-analysis/references/metric_get.md +0 -1
  162. package/skills/ae-analysis/references/metric_list.md +4 -5
  163. package/skills/ae-analysis/references/metric_update.md +0 -1
  164. package/skills/ae-analysis/references/operation_record_export.md +4 -5
  165. package/skills/ae-analysis/references/operation_record_list.md +4 -5
  166. package/skills/ae-analysis/references/personal_semantic_preference_list.md +18 -1
  167. package/skills/ae-analysis/references/plan_change_log_export.md +10 -10
  168. package/skills/ae-analysis/references/plan_change_log_list.md +4 -6
  169. package/skills/ae-analysis/references/plan_delete_items.md +2 -4
  170. package/skills/ae-analysis/references/plan_export.md +9 -9
  171. package/skills/ae-analysis/references/plan_generate.md +2 -4
  172. package/skills/ae-analysis/references/plan_get.md +4 -6
  173. package/skills/ae-analysis/references/plan_import_excel.md +6 -8
  174. package/skills/ae-analysis/references/plan_save_items.md +2 -4
  175. package/skills/ae-analysis/references/plan_sync_from_meta.md +4 -6
  176. package/skills/ae-analysis/references/project_access_detail_get.md +0 -1
  177. package/skills/ae-analysis/references/project_data_power_get.md +0 -1
  178. package/skills/ae-analysis/references/project_data_power_list.md +0 -1
  179. package/skills/ae-analysis/references/project_data_power_upsert.md +0 -1
  180. package/skills/ae-analysis/references/project_entity_create.md +0 -1
  181. package/skills/ae-analysis/references/project_entity_event_list.md +0 -1
  182. package/skills/ae-analysis/references/project_entity_get.md +0 -1
  183. package/skills/ae-analysis/references/project_entity_list.md +0 -1
  184. package/skills/ae-analysis/references/project_entity_update.md +0 -1
  185. package/skills/ae-analysis/references/project_function_list.md +0 -1
  186. package/skills/ae-analysis/references/project_info_get.md +0 -1
  187. package/skills/ae-analysis/references/project_info_list.md +0 -1
  188. package/skills/ae-analysis/references/project_info_update.md +0 -1
  189. package/skills/ae-analysis/references/project_mark_time_create.md +0 -1
  190. package/skills/ae-analysis/references/project_mark_time_list.md +0 -1
  191. package/skills/ae-analysis/references/project_mark_time_update.md +0 -1
  192. package/skills/ae-analysis/references/project_member_add.md +6 -5
  193. package/skills/ae-analysis/references/project_member_batch_update.md +0 -1
  194. package/skills/ae-analysis/references/project_member_candidate_list.md +4 -3
  195. package/skills/ae-analysis/references/project_member_handover_run.md +2 -3
  196. package/skills/ae-analysis/references/project_member_list.md +0 -1
  197. package/skills/ae-analysis/references/project_member_receiver_list.md +0 -1
  198. package/skills/ae-analysis/references/project_member_update.md +0 -1
  199. package/skills/ae-analysis/references/project_owner_update.md +0 -1
  200. package/skills/ae-analysis/references/project_permission_binding_list.md +0 -1
  201. package/skills/ae-analysis/references/project_role_function_list.md +0 -1
  202. package/skills/ae-analysis/references/project_role_get.md +0 -1
  203. package/skills/ae-analysis/references/project_role_list.md +0 -1
  204. package/skills/ae-analysis/references/project_role_upsert.md +32 -2
  205. package/skills/ae-analysis/references/project_role_user_list.md +0 -1
  206. package/skills/ae-analysis/references/project_semantic_knowledge_wiki.md +214 -0
  207. package/skills/ae-analysis/references/project_semantic_knowledge_wiki_plan_schema.md +93 -0
  208. package/skills/ae-analysis/references/project_timezone_get.md +0 -1
  209. package/skills/ae-analysis/references/project_timezone_overview.md +0 -1
  210. package/skills/ae-analysis/references/project_timezone_update.md +0 -1
  211. package/skills/ae-analysis/references/project_user_id_items_update.md +0 -1
  212. package/skills/ae-analysis/references/property_changelog_list.md +0 -1
  213. package/skills/ae-analysis/references/property_create.md +0 -1
  214. package/skills/ae-analysis/references/property_export.md +0 -1
  215. package/skills/ae-analysis/references/property_get.md +0 -1
  216. package/skills/ae-analysis/references/property_hide_update.md +0 -1
  217. package/skills/ae-analysis/references/property_influence_list.md +0 -1
  218. package/skills/ae-analysis/references/property_list.md +3 -4
  219. package/skills/ae-analysis/references/property_related_events.md +0 -1
  220. package/skills/ae-analysis/references/property_relation_update.md +0 -1
  221. package/skills/ae-analysis/references/property_update.md +0 -1
  222. package/skills/ae-analysis/references/report_create.md +4 -2
  223. package/skills/ae-analysis/references/report_data_export.md +11 -9
  224. package/skills/ae-analysis/references/report_data_run.md +14 -7
  225. package/skills/ae-analysis/references/report_get.md +5 -3
  226. package/skills/ae-analysis/references/report_list.md +2 -0
  227. package/skills/ae-analysis/references/report_update.md +5 -3
  228. package/skills/ae-analysis/references/rule_create.md +5 -6
  229. package/skills/ae-analysis/references/rule_delete.md +5 -5
  230. package/skills/ae-analysis/references/rule_list.md +3 -4
  231. package/skills/ae-analysis/references/rule_schema.md +3 -4
  232. package/skills/ae-analysis/references/rule_update.md +5 -6
  233. package/skills/ae-analysis/references/sdk_sample_generate.md +2 -4
  234. package/skills/ae-analysis/references/sql_table_columns.md +1 -1
  235. package/skills/ae-analysis/references/super_metadata_batch_create.md +1 -1
  236. package/skills/ae-analysis/references/super_metadata_batch_edit.md +1 -1
  237. package/skills/ae-analysis/references/system_member_add.md +1 -2
  238. package/skills/ae-analysis/references/system_member_status_update.md +1 -1
  239. package/skills/ae-analysis/references/system_ops_alert_contact_list.md +1 -1
  240. package/skills/ae-analysis/references/system_ops_alert_contact_test.md +0 -1
  241. package/skills/ae-analysis/references/system_query_monitor_overview.md +3 -3
  242. package/skills/ae-analysis/references/system_query_task_export.md +4 -14
  243. package/skills/ae-analysis/references/system_query_task_get.md +1 -1
  244. package/skills/ae-analysis/references/system_query_task_list.md +5 -6
  245. package/skills/ae-analysis/references/system_receiver_address_upsert.md +3 -3
  246. package/skills/ae-analysis/references/system_receiver_detection_get.md +1 -1
  247. package/skills/ae-analysis/references/system_third_party_login_disable.md +1 -1
  248. package/skills/ae-analysis/references/system_usage_trend_export.md +3 -12
  249. package/skills/ae-analysis/references/user_cluster_list.md +1 -1
  250. package/skills/ae-analysis/references/user_cluster_member_export.md +4 -4
  251. package/skills/ae-analysis/references/user_tag_list.md +1 -1
  252. package/skills/ae-analysis/references/user_tag_member_export.md +4 -4
  253. package/skills/ae-analysis/references/virtual_event_create.md +10 -5
  254. package/skills/ae-analysis/references/virtual_event_get.md +0 -1
  255. package/skills/ae-analysis/references/virtual_property_create.md +0 -1
  256. package/skills/ae-analysis/references/virtual_property_sql_rule_update.md +0 -1
  257. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-project-semantic-wiki.mjs +1054 -0
  258. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/company-kb.mjs +112 -0
  259. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-rules.md +30 -0
  260. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/generate-build-ir.mjs +2735 -0
  261. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/package-wiki-source-zip.mjs +376 -0
  262. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/plan-kb-source-sync.mjs +264 -0
  263. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/precompiled-source.mjs +49 -0
  264. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/upload-kb-sources-concurrently.mjs +200 -0
  265. package/skills/ae-analysis/scripts/read_results.py +197 -0
  266. package/skills/ae-analysis-global/SKILL.md +1 -1
  267. package/skills/ae-capability/SKILL.md +1 -1
  268. package/skills/ae-community/SKILL.md +1 -1
  269. package/skills/ae-current-context/SKILL.md +53 -0
  270. package/skills/ae-data-integration/SKILL.md +5 -4
  271. package/skills/ae-data-integration/references/lark-bitable-source.md +288 -0
  272. package/skills/ae-data-integration/references/lark-sheet-source.md +130 -0
  273. package/skills/ae-data-integration/references/manual-export-source.md +83 -0
  274. package/skills/ae-data-integration/references/reuse.md +2 -0
  275. package/skills/ae-data-integration/references/sink-upload.md +32 -0
  276. package/skills/ae-data-integration/references/tracking-plan.md +4 -1
  277. package/skills/ae-data-integration/references/ue-mapping.md +2 -2
  278. package/skills/ae-data-integration-helper/SKILL.md +5 -5
  279. package/skills/ae-data-integration-helper/references/android_sdk_faq.md +3 -3
  280. package/skills/ae-data-integration-helper/references/c_sdk_faq.md +3 -3
  281. package/skills/ae-data-integration-helper/references/cocoscreator_sdk_faq.md +1 -1
  282. package/skills/ae-data-integration-helper/references/cpp_server_sdk_faq.md +3 -3
  283. package/skills/ae-data-integration-helper/references/ios_sdk_faq.md +1 -1
  284. package/skills/ae-data-integration-helper/references/java_sdk_faq.md +3 -3
  285. package/skills/ae-data-integration-helper/references/javascript_sdk_faq.md +1 -1
  286. package/skills/ae-data-integration-helper/references/logbus2_guide.md +1 -1
  287. package/skills/ae-data-integration-helper/references/logbus2_parser_plugin.md +2 -2
  288. package/skills/ae-data-integration-helper/references/minigame_sdk_faq.md +2 -2
  289. package/skills/ae-data-integration-helper/references/miniprogram_sdk_faq.md +1 -1
  290. package/skills/ae-data-integration-helper/references/python_sdk_faq.md +2 -2
  291. package/skills/ae-data-integration-helper/references/restful_api_notes.md +2 -2
  292. package/skills/ae-data-integration-helper/references/sdk_log_guide.md +3 -3
  293. package/skills/ae-data-integration-helper/references/sdk_usage_notes.md +11 -11
  294. package/skills/ae-data-integration-helper/references/unity_sdk_faq.md +1 -1
  295. package/skills/ae-dataops/SKILL.md +1 -1
  296. package/skills/ae-engage/SKILL.md +4 -3
  297. package/skills/ae-engage/references/build-task-save-guide.md +3 -0
  298. package/skills/ae-engage/references/flow-metric-update.md +115 -1
  299. package/skills/ae-engage/references/save-flow.md +3 -0
  300. package/skills/ae-engage/references/save-task.md +21 -0
  301. package/skills/ae-engage/references/task-metric-update.md +151 -0
  302. package/skills/ae-experiment/SKILL.md +14 -7
  303. package/skills/ae-experiment/references/save_submit_external_experiment.md +52 -0
  304. package/skills/ae-experiment-design/SKILL.md +3 -3
  305. package/skills/ae-experiment-design/references/platform-operations.md +1 -1
  306. package/skills/ae-experiment-insight/SKILL.md +2 -2
  307. package/skills/ae-experiment-insight/references/platform-operations.md +1 -1
  308. package/skills/ae-generate-tracking-code/SKILL.md +2 -1
  309. package/skills/ae-generate-tracking-code/references/client-sdk-insert.md +1 -1
  310. package/skills/ae-generate-tracking-code/references/datax-config.md +1 -1
  311. package/skills/ae-generate-tracking-code/references/logbus-config.md +1 -1
  312. package/skills/ae-generate-tracking-code/references/modes.md +1 -1
  313. package/skills/ae-generate-tracking-code/references/server-sdk-insert.md +5 -5
  314. package/skills/ae-generate-tracking-code/references/snippet-delivery.md +3 -3
  315. package/skills/ae-generate-tracking-plan/SKILL.md +2 -2
  316. package/skills/ae-generate-tracking-plan/references/te-api.md +1 -0
  317. package/skills/ae-generate-tracking-plan/references/xlsx-schema.md +3 -2
  318. package/skills/ae-kb/SKILL.md +41 -2
  319. package/skills/ae-kb/references/analysis-workflow.md +292 -0
  320. package/skills/ae-kb/references/source-mutations.md +95 -0
  321. package/skills/ae-kb-discovery/SKILL.md +5 -1
  322. package/skills/ae-metadata/SKILL.md +1 -1
  323. package/skills/ae-migrate-tracking-code/SKILL.md +195 -0
  324. package/skills/ae-migrate-tracking-code/references/ae-preset-properties.md +76 -0
  325. package/skills/ae-migrate-tracking-code/references/ir.md +167 -0
  326. package/skills/ae-migrate-tracking-code/references/mapping-framework.md +112 -0
  327. package/skills/ae-migrate-tracking-code/references/providers/README.md +58 -0
  328. package/skills/ae-migrate-tracking-code/references/providers/amplitude.md +208 -0
  329. package/skills/ae-migrate-tracking-code/references/providers/firebase.md +160 -0
  330. package/skills/ae-migrate-tracking-code/references/providers/ga4.md +92 -0
  331. package/skills/ae-migrate-tracking-code/references/providers/mixpanel.md +150 -0
  332. package/skills/ae-migrate-tracking-code/references/providers/sensors-data.md +146 -0
  333. package/skills/ae-project-semantic/SKILL.md +1 -1
  334. package/skills/ae-system/SKILL.md +4 -4
  335. package/skills/ae-team/SKILL.md +2 -2
  336. package/skills/ae-use-agent/references/local-agent.md +1 -1
  337. package/dist/chunk-TAL6CZH6.js +0 -72
  338. package/skills/ae-analysis/metadata_resolution.md +0 -184
@@ -0,0 +1,1054 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createHash, randomBytes } from 'node:crypto';
4
+ import { execFileSync } from 'node:child_process';
5
+ import { createRequire } from 'node:module';
6
+ import fs from 'node:fs';
7
+ import os from 'node:os';
8
+ import path from 'node:path';
9
+ import { precompiledSource, normalizeSourceForHash } from './precompiled-source.mjs';
10
+
11
+ const require = createRequire(import.meta.url);
12
+ const archiverModule = require('archiver');
13
+ const archiver = archiverModule.default ?? archiverModule;
14
+ const ZipArchive = archiverModule.ZipArchive;
15
+
16
+ const args = parseArgs(process.argv.slice(2));
17
+ const packageRoot = path.resolve(required(args, 'asset-package'));
18
+ const semanticPlanPath = path.resolve(required(args, 'semantic-plan'));
19
+ const outputRoot = path.resolve(required(args, 'output'));
20
+ const archivePath = path.resolve(args.archive || `${outputRoot}.zip`);
21
+ const uploadSourcesPath = path.resolve(args['upload-sources-output'] || `${outputRoot}-kb-upload-sources`);
22
+ const projectNameOverride = clean(args['project-name']);
23
+ const force = Boolean(args.force);
24
+ const keepBuildIr = Boolean(args['keep-build-ir']);
25
+ const allowAllVisible = Boolean(args['allow-all-visible']);
26
+
27
+ assertSafePaths(packageRoot, outputRoot, archivePath, uploadSourcesPath);
28
+
29
+ const descriptor = readJson(path.join(packageRoot, '.asset-package.json'));
30
+ const packageManifest = readJson(path.join(packageRoot, 'manifest.json'));
31
+ if (String(descriptor.schema_version ?? packageManifest.schema_version) !== '3.0') {
32
+ fail('Project semantic asset package schema 3.0 is required.');
33
+ }
34
+ if ((descriptor.truncated ?? packageManifest.truncated) === true) {
35
+ fail('A truncated asset package cannot produce a project semantic knowledge Wiki.');
36
+ }
37
+ const filterPolicy = asObject(descriptor.filter_policy ?? packageManifest.filter_policy);
38
+ const packageAssetScope = clean(descriptor.asset_scope ?? packageManifest.asset_scope);
39
+ if (packageAssetScope === 'governed') {
40
+ if (filterPolicy.authenticated_entry_assets_only !== true || filterPolicy.dependency_closure !== true) {
41
+ fail('The governed Wiki package must declare authenticated_entry_assets_only=true and dependency_closure=true.');
42
+ }
43
+ } else if (packageAssetScope === 'all_visible') {
44
+ if (!allowAllVisible) {
45
+ fail('The Wiki defaults to governed packages. Pass --allow-all-visible only when the user explicitly requested all visible assets.');
46
+ }
47
+ } else {
48
+ fail(`The Wiki supports asset_scope=governed by default or asset_scope=all_visible with --allow-all-visible, received ${packageAssetScope || 'unknown'}.`);
49
+ }
50
+
51
+ const semanticProjectId = Number(descriptor.project_id ?? packageManifest.project_id);
52
+ const snapshotHash = clean(descriptor.snapshot_hash ?? packageManifest.snapshot_hash);
53
+ if (!Number.isInteger(semanticProjectId) || semanticProjectId < 1) fail('A valid semantic project ID is required.');
54
+ if (!/^[0-9a-f]{64}$/i.test(snapshotHash)) fail('A valid asset-package snapshot hash is required.');
55
+ const projectName = projectNameOverride || clean(descriptor.project_name ?? packageManifest.project_name)
56
+ || `Project ${semanticProjectId}`;
57
+ const uploadSourceNamespace = normalizeUploadSourceNamespace(args['upload-source-namespace'] || `pskb-p${semanticProjectId}`);
58
+ const snapshotDate = normalizeSnapshotDate(args['snapshot-date'] || descriptor.generated_at || packageManifest.generated_at);
59
+
60
+ const semanticPlan = readJson(semanticPlanPath);
61
+ if (clean(semanticPlan.schema_version) !== '2.0') fail('Project semantic knowledge Wiki plan schema 2.0 is required.');
62
+ if (clean(semanticPlan.source_snapshot_hash) !== snapshotHash) fail('Semantic plan snapshot hash does not match the asset package.');
63
+ if (clean(semanticPlan.generation_method) !== 'agent_semantic_synthesis') {
64
+ fail('Semantic plan generation_method must be agent_semantic_synthesis.');
65
+ }
66
+
67
+ const dashboards = readIndex('dashboard-catalog');
68
+ const reports = readIndex('report-catalog');
69
+ const metrics = readIndex('metric-catalog');
70
+ const containers = readIndex('asset-containers');
71
+ const dashboardById = indexBy(dashboards, 'resource_key');
72
+ const reportById = indexBy(reports, 'resource_key');
73
+ const metricById = indexBy(metrics, 'resource_key');
74
+ const dashboardFileNames = titleFirstAssetFileMap(dashboards, 'dashboard');
75
+ const reportFileNames = titleFirstAssetFileMap(reports, 'report');
76
+ const metricFileNames = titleFirstAssetFileMap(metrics, 'metric');
77
+ const titleFirstAssetPathMap = titleFirstAssetPathRewrites();
78
+ const containerById = indexBy(containers, 'container_key');
79
+ const domains = asArray(semanticPlan.domains);
80
+ const appendices = asArray(semanticPlan.appendices);
81
+ const domainByDashboardId = new Map();
82
+ const appendixByDashboardId = new Map();
83
+ for (const domain of domains) for (const id of asArray(domain.dashboard_ids).map(clean)) domainByDashboardId.set(id, domain);
84
+ for (const appendix of appendices) for (const id of asArray(appendix.dashboard_ids).map(clean)) appendixByDashboardId.set(id, appendix);
85
+
86
+ const planModel = validateSemanticPlan();
87
+ const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'project-semantic-knowledge-wiki-'));
88
+ const buildIr = path.join(scratch, 'build-ir');
89
+
90
+ try {
91
+ const generator = path.join(import.meta.dirname, 'generate-build-ir.mjs');
92
+ const generatorArgs = [generator, '--asset-package', packageRoot, '--domain-plan', semanticPlanPath,
93
+ '--output', buildIr, '--project-name', projectName];
94
+ if (allowAllVisible) generatorArgs.push('--allow-all-visible');
95
+ execFileSync(process.execPath, generatorArgs, { stdio: 'pipe' });
96
+
97
+ const corpus = readJson(path.join(buildIr, 'corpus.json'));
98
+ const conflictReportIds = new Set(reports
99
+ .filter((report) => fs.readFileSync(path.join(buildIr, 'briefs', 'reports', fileName('report', report.resource_key)), 'utf8')
100
+ .includes('semantic_conflict'))
101
+ .map((report) => clean(report.resource_key)));
102
+ const authorityRows = [];
103
+
104
+ materialize(outputRoot, force, (staging) => {
105
+ const wikiRoot = path.join(staging, 'wiki');
106
+ for (const directory of [
107
+ 'domains', 'technical-appendices', 'source-containers', 'dashboards', 'reports', 'metrics',
108
+ 'metadata', 'recall-cards', 'governance',
109
+ ]) fs.mkdirSync(path.join(wikiRoot, directory), { recursive: true });
110
+
111
+ for (const metadataType of fs.readdirSync(path.join(buildIr, 'metadata'), { withFileTypes: true })
112
+ .filter((entry) => entry.isDirectory()).map((entry) => entry.name)) {
113
+ fs.mkdirSync(path.join(wikiRoot, 'metadata', metadataType), { recursive: true });
114
+ }
115
+
116
+ for (const domain of domains) {
117
+ const id = clean(domain.domain_id);
118
+ const source = readText(path.join(buildIr, 'domains', fileName('domain', id)));
119
+ const cards = asArray(domain.recall_cards);
120
+ const cardLinks = cards.map((card) => `- [${escapeLink(card.questions?.[0] || card.intent || card.card_id)}](../recall-cards/${fileName('recall-card', card.card_id)})`).join('\n');
121
+ writeText(path.join(wikiRoot, 'domains', fileName('domain', id)),
122
+ replaceFrontmatter(source, domainFrontmatter(domain))
123
+ + `\n## 场景召回卡\n\n${cardLinks || '- 无'}\n`);
124
+ }
125
+
126
+ for (const appendix of appendices) {
127
+ const id = clean(appendix.appendix_id);
128
+ const source = readText(path.join(buildIr, 'appendices', fileName('appendix', id)))
129
+ .replaceAll('../appendices/', '../technical-appendices/');
130
+ writeText(path.join(wikiRoot, 'technical-appendices', fileName('appendix', id)),
131
+ replaceFrontmatter(source, appendixFrontmatter(appendix)));
132
+ }
133
+
134
+ copyIndex(path.join(buildIr, 'domains', 'index.md'), path.join(wikiRoot, 'domains', 'index.md'));
135
+ copyIndex(path.join(buildIr, 'appendices', 'index.md'), path.join(wikiRoot, 'technical-appendices', 'index.md'));
136
+ copyIndex(path.join(buildIr, 'source-containers', 'index.md'), path.join(wikiRoot, 'source-containers', 'index.md'));
137
+
138
+ for (const container of containers) {
139
+ const id = clean(container.container_key);
140
+ const source = readText(path.join(buildIr, 'source-containers', fileName('container', id)));
141
+ writeText(path.join(wikiRoot, 'source-containers', fileName('container', id)), source);
142
+ }
143
+
144
+ for (const dashboard of dashboards) {
145
+ const id = clean(dashboard.resource_key);
146
+ const authority = dashboardAuthority(dashboard);
147
+ authorityRows.push({ resource_type: 'dashboard', resource_key: id, title: assetReadableTitle(dashboard, 'dashboard'), ...authority });
148
+ writeMergedAsset(wikiRoot, 'dashboards', dashboard, authority);
149
+ }
150
+
151
+ for (const report of reports) {
152
+ const id = clean(report.resource_key);
153
+ const authority = reportAuthority(report, conflictReportIds);
154
+ authorityRows.push({ resource_type: 'report', resource_key: id, title: assetReadableTitle(report, 'report'), ...authority });
155
+ writeMergedAsset(wikiRoot, 'reports', report, authority);
156
+ }
157
+
158
+ for (const metric of metrics) {
159
+ const id = clean(metric.resource_key);
160
+ const authority = metricAuthority(metric);
161
+ authorityRows.push({ resource_type: 'metric', resource_key: id, title: assetReadableTitle(metric, 'metric'), ...authority });
162
+ writeMergedAsset(wikiRoot, 'metrics', metric, authority);
163
+ }
164
+
165
+ for (const type of fs.readdirSync(path.join(buildIr, 'metadata'), { withFileTypes: true })
166
+ .filter((entry) => entry.isDirectory()).map((entry) => entry.name)) {
167
+ for (const file of fs.readdirSync(path.join(buildIr, 'metadata', type)).filter((name) => name.endsWith('.md'))) {
168
+ const source = readText(path.join(buildIr, 'metadata', type, file));
169
+ const briefPath = path.join(buildIr, 'briefs', 'metadata', type, file);
170
+ const brief = fs.existsSync(briefPath) ? readText(briefPath) : '';
171
+ const identity = frontmatterValue(source, 'evidence_id') || file.replace(/\.md$/, '');
172
+ const authority = {
173
+ authority_role: 'dependency_only', certification_state: frontmatterValue(source, 'authenticated') === 'true' ? 'certified' : 'uncertified',
174
+ definition_state: 'valid', runtime_policy: 'warn', standalone_recall: false, canonical_asset_id: null,
175
+ };
176
+ authorityRows.push({ resource_type: 'metadata', resource_key: identity, title: firstHeading(source), ...authority });
177
+ writeText(path.join(wikiRoot, 'metadata', type, file), mergePage(source, brief,
178
+ authorityFrontmatter('project-analysis-metadata', identity, firstHeading(source), authority)));
179
+ }
180
+ }
181
+
182
+ copyIndex(path.join(buildIr, 'dashboards', 'index.md'), path.join(wikiRoot, 'dashboards', 'index.md'));
183
+ copyIndex(path.join(buildIr, 'reports', 'index.md'), path.join(wikiRoot, 'reports', 'index.md'));
184
+ copyIndex(path.join(buildIr, 'metrics', 'index.md'), path.join(wikiRoot, 'metrics', 'index.md'));
185
+ copyIndex(path.join(buildIr, 'metadata', 'index.md'), path.join(wikiRoot, 'metadata', 'index.md'));
186
+
187
+ for (const card of planModel.cards) {
188
+ writeText(path.join(wikiRoot, 'recall-cards', fileName('recall-card', card.card_id)), renderRecallCard(card));
189
+ }
190
+ writeText(path.join(wikiRoot, 'recall-cards', 'index.md'), renderRecallCardIndex(planModel.cards));
191
+
192
+ const coverage = readText(path.join(buildIr, 'indexes', 'coverage.md'))
193
+ .replaceAll('../appendices/', '../technical-appendices/')
194
+ .replaceAll('../briefs/reports/', '../reports/')
195
+ .replaceAll('../briefs/metadata/', '../metadata/');
196
+ writeText(path.join(wikiRoot, 'governance', 'coverage.md'), coverage);
197
+ writeText(path.join(wikiRoot, 'governance', 'authority-index.md'), renderAuthorityIndex(authorityRows));
198
+ writeText(path.join(wikiRoot, 'governance', 'index.md'), renderGovernanceIndex(conflictReportIds.size));
199
+ writeText(path.join(wikiRoot, 'governance', 'source-project-boundary.md'), renderSourceProjectBoundary(sourceProjectReferencesFromPackage()));
200
+ writeText(path.join(wikiRoot, 'project-overview.md'), renderProjectOverview(corpus));
201
+ writeText(path.join(wikiRoot, 'index.md'), renderWikiIndex(corpus));
202
+ writeText(path.join(wikiRoot, 'log.md'), renderLog(corpus));
203
+ writeText(path.join(staging, 'index.md'), renderSnapshotIndex(corpus));
204
+
205
+ const sourceProjectRefs = sourceProjectReferencesFromPackage();
206
+ const sourceProjectIds = [...new Set(sourceProjectRefs.map((row) => row.source_project_id))].sort((a, b) => a - b);
207
+ const blockedCount = authorityRows.filter((row) => row.runtime_policy === 'block').length;
208
+ const unknownSqlCount = reports.filter((report) => clean(report.definition_kind) === 'SQL'
209
+ && sqlFinalDefinitionState(report) === 'unknown').length;
210
+ const bundleManifest = {
211
+ schema_version: '1.0',
212
+ package_kind: 'ae_project_semantic_knowledge_wiki',
213
+ semantic_project_id: semanticProjectId,
214
+ source_project_ids: sourceProjectIds,
215
+ source_project_id_status: sourceProjectRefs.length ? 'detected_in_packaged_definitions' : 'not_exposed',
216
+ project_name: projectName,
217
+ source_snapshot_hash: snapshotHash,
218
+ semantic_plan_hash: sha256(fs.readFileSync(semanticPlanPath)),
219
+ asset_package_schema_version: '3.0',
220
+ asset_scope: packageAssetScope,
221
+ wiki_schema_version: '1.0',
222
+ generated_at: descriptor.generated_at ?? packageManifest.generated_at ?? null,
223
+ counts: { ...corpus.counts, recall_cards: planModel.cards.length, blocked_assets: blockedCount, unknown_sql_reports: unknownSqlCount },
224
+ quality_warnings: [...asArray(corpus.warnings),
225
+ ...(sourceProjectRefs.filter((row) => Number(row.source_project_id) !== semanticProjectId).length
226
+ ? [`${sourceProjectRefs.filter((row) => Number(row.source_project_id) !== semanticProjectId).length} packaged asset reference(s) mention another project: ${sourceProjectRefs.filter((row) => Number(row.source_project_id) !== semanticProjectId).slice(0, 5).map((row) => `${row.resource_type}:${row.resource_key}->${row.source_project_id}`).join(', ')}`] : []),
227
+ ...(unknownSqlCount ? [`${unknownSqlCount} SQL reports have unknown semantics and are blocked.`] : []),
228
+ ],
229
+ };
230
+ writeJson(path.join(staging, 'manifest.json'), bundleManifest);
231
+
232
+ rewriteTitleFirstAssetLinks(staging, titleFirstAssetPathMap);
233
+ assertNoBareNumericAssetFiles(wikiRoot);
234
+ hideMarkdownFrontmatter(staging);
235
+ assertNoForbiddenPublishedLayers(staging);
236
+ assertSnapshotEntryPoint(staging);
237
+ assertMarkdownLinks(staging);
238
+ });
239
+
240
+ const uploadSources = writeFlatUploadSources(outputRoot, uploadSourcesPath, force);
241
+ assertPrecompiledSourceQuality(uploadSourcesPath);
242
+ await writeDeterministicZip(outputRoot, archivePath, force);
243
+ const resultManifest = readJson(path.join(outputRoot, 'manifest.json'));
244
+ process.stdout.write(`${JSON.stringify({
245
+ output_path: outputRoot,
246
+ archive_path: archivePath,
247
+ archive_bytes: fs.statSync(archivePath).size,
248
+ upload_sources_path: uploadSourcesPath,
249
+ upload_source_count: uploadSources.count,
250
+ upload_source_namespace: uploadSources.namespace,
251
+ upload_manifest_path: uploadSources.manifestPath,
252
+ ...resultManifest,
253
+ }, null, 2)}\n`);
254
+ } finally {
255
+ if (keepBuildIr) {
256
+ process.stderr.write(`Build IR preserved at ${buildIr}\n`);
257
+ } else {
258
+ fs.rmSync(scratch, { recursive: true, force: true });
259
+ }
260
+ }
261
+
262
+ function validateSemanticPlan() {
263
+ if (!domains.length) fail('The semantic plan must contain at least one business domain.');
264
+ const cards = [];
265
+ const cardIds = new Set();
266
+ const excludedByAsset = new Map();
267
+ const preferredByAsset = new Map();
268
+ for (const domain of domains) {
269
+ const domainId = clean(domain.domain_id);
270
+ const domainDashboardIds = new Set(asArray(domain.dashboard_ids).map(clean));
271
+ const domainReportIds = new Set(reports.filter((report) => asArray(report.dashboard_refs)
272
+ .some((ref) => domainDashboardIds.has(clean(ref.dashboard_id)))).map((report) => clean(report.resource_key)));
273
+ const domainMetricIds = new Set(reports.filter((report) => domainReportIds.has(clean(report.resource_key)))
274
+ .flatMap((report) => asArray(report.metric_occurrences).map((item) => clean(item.metric_key)).filter(Boolean)));
275
+ const recallCards = asArray(domain.recall_cards);
276
+ if (!recallCards.length) fail(`Domain ${domainId || '<unknown>'} must contain at least one recall card.`);
277
+ for (const value of recallCards) {
278
+ const card = asObject(value);
279
+ const cardId = clean(card.card_id);
280
+ if (!/^[a-z0-9][a-z0-9_-]{1,79}$/.test(cardId) || cardIds.has(cardId)) fail(`Invalid or duplicate recall card ID: ${cardId || '<empty>'}.`);
281
+ cardIds.add(cardId);
282
+ if (!asArray(card.questions).map(clean).filter(Boolean).length || !clean(card.intent)) fail(`Recall card ${cardId} requires questions and intent.`);
283
+ assertAgentAuthoredRecallCard(card, domainId);
284
+ const preferred = asArray(card.preferred_asset_refs);
285
+ if (!preferred.length) fail(`Recall card ${cardId} requires at least one preferred asset.`);
286
+ for (const ref of [...preferred, ...asArray(card.fallback_asset_refs)]) {
287
+ validateAssetRef(ref, cardId);
288
+ const type = clean(ref.resource_type);
289
+ const key = clean(ref.resource_key);
290
+ const inDomain = type === 'dashboard' ? domainDashboardIds.has(key)
291
+ : type === 'report' ? domainReportIds.has(key)
292
+ : type === 'metric' ? domainMetricIds.has(key) : false;
293
+ if (!inDomain) fail(`Recall card ${cardId} references ${type}:${key} outside domain ${domainId}.`);
294
+ if (preferred.includes(ref)) addMulti(preferredByAsset, `${type}:${key}`, cardId);
295
+ }
296
+ for (const ref of asArray(card.excluded_asset_refs)) {
297
+ validateAssetRef(ref, cardId);
298
+ if (!clean(ref.reason)) fail(`Recall card ${cardId} excluded asset ${clean(ref.resource_key)} is missing a reason.`);
299
+ addMulti(excludedByAsset, `${clean(ref.resource_type)}:${clean(ref.resource_key)}`, {
300
+ card_id: cardId, reason: clean(ref.reason), canonical_resource_key: clean(ref.canonical_resource_key),
301
+ });
302
+ }
303
+ cards.push({ ...card, domain_id: domainId, domain_title: clean(domain.title) });
304
+ }
305
+ }
306
+
307
+ const sqlReports = reports.filter((report) => clean(report.definition_kind) === 'SQL');
308
+ const sqlByReportId = new Map();
309
+ for (const summary of asArray(semanticPlan.sql_report_semantics)) {
310
+ const row = asObject(summary);
311
+ const reportId = clean(row.report_id);
312
+ if (!reportById.has(reportId) || clean(reportById.get(reportId).definition_kind) !== 'SQL') {
313
+ fail(`SQL semantics references unknown or non-SQL report ${reportId || '<empty>'}.`);
314
+ }
315
+ if (sqlByReportId.has(reportId)) fail(`Duplicate SQL semantics for report ${reportId}.`);
316
+ for (const field of ['business_purpose', 'statistical_grain', 'evidence_locator']) {
317
+ if (!clean(row[field])) fail(`SQL report ${reportId} is missing ${field}.`);
318
+ }
319
+ for (const field of ['input_parameters', 'output_fields', 'key_filters', 'default_limits', 'applicable_questions', 'non_applicable_questions']) {
320
+ if (!Array.isArray(row[field])) fail(`SQL report ${reportId} must declare ${field} as an array.`);
321
+ }
322
+ if (!['valid', 'unknown'].includes(clean(row.definition_state))) fail(`SQL report ${reportId} has invalid definition_state.`);
323
+ if (clean(row.evidence_locator) !== clean(reportById.get(reportId).source_detail_path)) {
324
+ fail(`SQL report ${reportId} evidence_locator must equal its packaged source_detail_path.`);
325
+ }
326
+ if (clean(row.definition_state) === 'valid'
327
+ && (!row.output_fields.length || !row.applicable_questions.length || !row.non_applicable_questions.length)) {
328
+ fail(`SQL report ${reportId} marked valid requires output fields plus applicable and non-applicable questions.`);
329
+ }
330
+ sqlByReportId.set(reportId, row);
331
+ }
332
+ const missingSql = sqlReports.map((report) => clean(report.resource_key)).filter((id) => !sqlByReportId.has(id));
333
+ if (missingSql.length && !allowAllVisible) {
334
+ fail(`Semantic plan is missing SQL semantics for ${missingSql.length} report(s): ${missingSql.join(', ')}`);
335
+ }
336
+
337
+ return { cards, excludedByAsset, preferredByAsset, sqlByReportId, missingSqlByReportId: new Set(missingSql) };
338
+ }
339
+
340
+ function assertAgentAuthoredRecallCard(card, domainId) {
341
+ const cardId = clean(card.card_id);
342
+ const values = [card.intent, card.live_execution_reason, ...asArray(card.questions)].map(clean);
343
+ const refs = [...asArray(card.preferred_asset_refs), ...asArray(card.fallback_asset_refs), ...asArray(card.excluded_asset_refs)].map(asObject);
344
+ const reasons = refs.map((ref) => clean(ref.reason));
345
+ const combined = [...values, ...reasons].join('\n');
346
+ if (isGenericRecallText(combined)) {
347
+ fail(`Recall card ${cardId} in domain ${domainId || '<unknown>'} uses generic routing text; Agent must author concrete applicability, fallback, exclusion and execution boundaries.`);
348
+ }
349
+ for (const [index, reason] of reasons.entries()) {
350
+ if (!reason || reason.length < 10) {
351
+ fail(`Recall card ${cardId} asset reference ${index + 1} needs a concrete Agent-authored reason.`);
352
+ }
353
+ }
354
+ }
355
+
356
+ function isGenericRecallText(value) {
357
+ const normalized = clean(value).toLowerCase();
358
+ return /该认证看板的子报表直接覆盖|具体口径以资产页为准|同主题补充视角|使用前确认报表适用范围|知识库记录定义和使用边界;当前数量、客户名单及性能需实时执行|当前数量、客户名单及性能需实时执行|相关资产|主要资产|补充视角/.test(normalized);
359
+ }
360
+
361
+ function validateAssetRef(value, cardId) {
362
+ const ref = asObject(value);
363
+ const type = clean(ref.resource_type);
364
+ const key = clean(ref.resource_key);
365
+ const exists = type === 'dashboard' ? dashboardById.has(key) : type === 'report' ? reportById.has(key) : type === 'metric' ? metricById.has(key) : false;
366
+ if (!exists) fail(`Recall card ${cardId} has unknown asset reference ${type || '<type>'}:${key || '<key>'}.`);
367
+ if (!clean(ref.reason)) fail(`Recall card ${cardId} asset ${type}:${key} is missing a reason.`);
368
+ }
369
+
370
+ function dashboardAuthority(dashboard) {
371
+ const id = clean(dashboard.resource_key);
372
+ const isAppendix = appendixByDashboardId.has(id);
373
+ const excluded = planModel.excludedByAsset.get(`dashboard:${id}`) ?? [];
374
+ const blocked = isAppendix || excluded.length > 0;
375
+ return {
376
+ authority_role: isAppendix ? 'appendix' : 'canonical',
377
+ certification_state: dashboard.authenticated === true ? 'certified' : 'uncertified',
378
+ definition_state: 'valid', runtime_policy: blocked ? 'block' : 'allow', standalone_recall: !blocked,
379
+ canonical_asset_id: canonicalId('dashboard', excluded),
380
+ };
381
+ }
382
+
383
+ function reportAuthority(report, conflictIds) {
384
+ const id = clean(report.resource_key);
385
+ const hasBusinessDomain = asArray(report.dashboard_refs).some((ref) => domainByDashboardId.has(clean(ref.dashboard_id)));
386
+ const onlyAppendix = !hasBusinessDomain && asArray(report.dashboard_refs).some((ref) => appendixByDashboardId.has(clean(ref.dashboard_id)));
387
+ const dependencyOnly = report.authenticated !== true && asArray(report.dashboard_refs).length > 0;
388
+ const excluded = planModel.excludedByAsset.get(`report:${id}`) ?? [];
389
+ const conflict = conflictIds.has(id);
390
+ const sqlDefinitionState = sqlFinalDefinitionState(report);
391
+ const unknown = sqlDefinitionState === 'unknown'
392
+ || (clean(report.definition_kind) !== 'SQL' && !asArray(report.metric_occurrences).length);
393
+ const blocked = onlyAppendix || conflict || excluded.length > 0 || sqlDefinitionState === 'unknown';
394
+ return {
395
+ authority_role: onlyAppendix ? 'appendix' : dependencyOnly ? 'dependency_only' : 'canonical',
396
+ certification_state: report.authenticated === true ? 'certified' : 'uncertified',
397
+ definition_state: conflict ? 'conflict' : unknown ? 'unknown' : 'valid',
398
+ runtime_policy: blocked ? 'block' : dependencyOnly || unknown ? 'warn' : 'allow',
399
+ standalone_recall: !blocked && !dependencyOnly,
400
+ canonical_asset_id: canonicalId('report', excluded),
401
+ };
402
+ }
403
+
404
+ function sqlFinalDefinitionState(report) {
405
+ if (clean(report.definition_kind) !== 'SQL') return null;
406
+ const id = clean(report.resource_key);
407
+ const generated = path.join(buildIr, 'reports', fileName('report', id));
408
+ if (fs.existsSync(generated)) {
409
+ const match = readText(generated).match(/^- 定义状态:\s*(valid|unknown)\s*$/m);
410
+ if (match) return match[1];
411
+ }
412
+ return clean(planModel.sqlByReportId.get(id)?.definition_state) || 'unknown';
413
+ }
414
+
415
+ function metricAuthority(metric) {
416
+ const id = clean(metric.resource_key);
417
+ const dependencyOnly = metric.authenticated !== true;
418
+ const excluded = planModel.excludedByAsset.get(`metric:${id}`) ?? [];
419
+ const unknown = !asArray(metric.metric_occurrences).length;
420
+ const blocked = excluded.length > 0;
421
+ return {
422
+ authority_role: dependencyOnly ? 'dependency_only' : 'canonical',
423
+ certification_state: metric.authenticated === true ? 'certified' : 'uncertified',
424
+ definition_state: unknown ? 'unknown' : 'valid',
425
+ runtime_policy: blocked ? 'block' : dependencyOnly || unknown ? 'warn' : 'allow',
426
+ standalone_recall: !blocked && !dependencyOnly,
427
+ canonical_asset_id: canonicalId('metric', excluded),
428
+ };
429
+ }
430
+
431
+ function writeMergedAsset(wikiRoot, directory, asset, authority, extra = '') {
432
+ const id = clean(asset.resource_key);
433
+ const source = readText(path.join(buildIr, directory, fileName(directory.slice(0, -1), id)))
434
+ .replaceAll('../appendices/', '../technical-appendices/')
435
+ .replace(/- 源详情: `([^`]+)`/g, '- 资产包源定位(精确证据已内嵌): `$1`');
436
+ const brief = readText(path.join(buildIr, 'briefs', directory, fileName(directory.slice(0, -1), id)));
437
+ const type = directory === 'dashboards' ? 'project-dashboard' : directory === 'reports' ? 'project-report' : 'project-reusable-metric';
438
+ const title = assetReadableTitle(asset, directory.slice(0, -1));
439
+ writeText(path.join(wikiRoot, directory, assetOutputFileName(directory, id)),
440
+ ensureReadableHeading(mergePage(source, brief, authorityFrontmatter(type, id, title, authority), extra), title));
441
+ }
442
+
443
+ function mergePage(source, brief, frontmatter, extra = '') {
444
+ const title = firstHeading(source) || firstHeading(brief);
445
+ const sourceBody = stripFirstHeading(stripFrontmatter(source)).trim();
446
+ if (/^## Agent 使用摘要$/m.test(sourceBody)) {
447
+ return `${frontmatter}\n# ${title}\n\n${sourceBody}${extra ? `\n\n${extra.trim()}\n` : '\n'}`;
448
+ }
449
+ const briefBody = stripFirstHeading(stripFrontmatter(brief))
450
+ .replace(/\n## 来源\n[\s\S]*$/m, '')
451
+ .replace(/^.*原始(?:看板|报表|指标|元数据)定义.*$/gm, '')
452
+ .replaceAll('../../metadata/', '../metadata/')
453
+ .replaceAll('详见原始报表定义', '详见本页“证据定位”')
454
+ .trim();
455
+ return `${frontmatter}\n# ${title}\n\n${briefBody}\n${extra}\n\n## 证据定位\n\n${sourceBody}\n`;
456
+ }
457
+
458
+
459
+ function renderRecallCard(card) {
460
+ const title = clean(card.questions?.[0] || card.intent || card.card_id);
461
+ const alternateQuestions = asArray(card.questions).map(clean).filter((value) => value && value !== title);
462
+ const refs = (values, heading) => `## ${heading}
463
+
464
+ ${asArray(values).map((ref) => {
465
+ const linkPath = assetPath(clean(ref.resource_type), clean(ref.resource_key));
466
+ return `- [${escapeLink(recallAssetLabel(ref))}](${linkPath}):${clean(ref.reason)}`;
467
+ }).join('\n') || '- 无'}`;
468
+ return `---
469
+ type: project-semantic-recall-card
470
+ card_id: ${yaml(card.card_id)}
471
+ domain_id: ${yaml(card.domain_id)}
472
+ source_snapshot_hash: ${yaml(snapshotHash)}
473
+ requires_live_execution: ${Boolean(card.requires_live_execution)}
474
+ ---
475
+
476
+ # ${title}
477
+
478
+ ## Agent 使用摘要
479
+
480
+ - 适用问题:${title}
481
+ - 意图:${clean(card.intent) || '未说明'}
482
+ - 首选资产:${asArray(card.preferred_asset_refs).map(recallAssetLabel).join('、') || '无'}
483
+ - 备选资产:${asArray(card.fallback_asset_refs).map(recallAssetLabel).join('、') || '无'}
484
+ - 排除资产:${asArray(card.excluded_asset_refs).map(recallAssetLabel).join('、') || '无'}
485
+ - 实时执行:${Boolean(card.requires_live_execution) ? `需要,${clean(card.live_execution_reason) || '当前数值、客户名单、趋势或明细需要实时执行。'}` : '不强制,但当前数值仍需按具体资产能力实时确认。'}
486
+
487
+ ## 用户问法
488
+
489
+ ${alternateQuestions.map((value) => `- ${value}`).join('\n') || '- 同页面标题。'}
490
+
491
+ ## 意图
492
+
493
+ ${clean(card.intent)}
494
+
495
+ ## Agent 执行顺序
496
+
497
+ 1. 先进入首选资产确认业务范围、指标和维度。
498
+ 2. 首选资产不足以回答时,再使用备选资产补充证据。
499
+ 3. 排除资产只用于解释为什么不采用,不能作为当前问题的主要答案来源。
500
+ 4. 需要当前数值、客户名单、趋势或明细时,必须实时执行报表/查询。
501
+
502
+ ${refs(card.preferred_asset_refs, '首选资产')}
503
+
504
+ ${refs(card.fallback_asset_refs, '备选资产')}
505
+
506
+ ${refs(card.excluded_asset_refs, '排除资产')}
507
+
508
+ ## 实时执行边界
509
+
510
+ - 是否需要执行实时数据:${Boolean(card.requires_live_execution)}
511
+ - 原因:${clean(card.live_execution_reason) || '未说明'}
512
+ `;
513
+ }
514
+
515
+ function recallAssetLabel(ref) {
516
+ const type = clean(ref.resource_type);
517
+ const key = clean(ref.resource_key);
518
+ const row = type === 'dashboard' ? dashboardById.get(key)
519
+ : type === 'report' ? reportById.get(key)
520
+ : type === 'metric' ? metricById.get(key)
521
+ : null;
522
+ const title = assetReadableTitle({ ...asObject(row), title: ref.display_name || ref.title || row?.title, resource_key: key }, type);
523
+ const identity = `${type}:${key}`;
524
+ return title && title !== key ? `${title}(${identity})` : identity;
525
+ }
526
+
527
+ function renderRecallCardIndex(cards) {
528
+ return `# 场景召回卡\n\n首轮召回先使用这里的业务问题与权威资产路由,不直接宽搜全部资产页。\n\n${cards.map((card) => `- [${escapeLink(card.questions?.[0] || card.intent || card.card_id)}](${fileName('recall-card', card.card_id)})(${clean(card.domain_title)})`).join('\n')}\n`;
529
+ }
530
+
531
+ function renderAuthorityIndex(rows) {
532
+ return `# 资产权威状态\n\n## Agent 判定规则\n\n- \`runtime_policy=allow\`:可以作为主要答案依据,但当前数值仍需实时执行。\n- \`runtime_policy=warn\`:只能作为依赖或辅助线索,回答时需要说明它不是独立召回入口。\n- \`runtime_policy=block\`:不能直接回答,必须回到 canonical 资产、业务域或等待资产作者确认。\n- \`authority_role=dependency_only\`:只在报表/指标引用时使用,不能单独作为业务入口。\n- \`definition_state=conflict\` 或 \`unknown\`:不要断言精确口径,只能描述已知证据和待确认点。\n\n| 类型 | 资产 | Authority role | Certification | Definition | Runtime | Standalone recall | Canonical |\n| --- | --- | --- | --- | --- | --- | --- | --- |\n${rows.sort((a, b) => `${a.resource_type}:${a.resource_key}`.localeCompare(`${b.resource_type}:${b.resource_key}`, 'en')).map((row) => `| ${row.resource_type} | \`${row.resource_key}\` ${escapeTable(row.title)} | ${row.authority_role} | ${row.certification_state} | ${row.definition_state} | ${row.runtime_policy} | ${row.standalone_recall} | ${row.canonical_asset_id ?? '-'} |`).join('\n')}\n`;
533
+ }
534
+
535
+ function renderSourceProjectBoundary(rows) {
536
+ const external = rows.filter((row) => Number(row.source_project_id) !== semanticProjectId);
537
+ return `# 项目来源边界
538
+
539
+ ## Agent 使用方式
540
+
541
+ - 本知识库的语义项目 ID 是 ${semanticProjectId}。资产定义中出现其他项目 ID 时,只能视为打包定义中的跨项目引用线索。
542
+ - 不要把外部项目 ID 当作本项目正式业务范围;回答前需要回到对应资产页和实时权限确认。
543
+ - 若跨项目引用来自字段粒度、实体或公式依赖,优先按当前资产的认证状态与召回卡使用,不能扩展为外部项目知识。
544
+
545
+ ## 跨项目引用
546
+
547
+ ${external.length ? `| 资产 | 来源项目 | 定位 |
548
+ | --- | ---: | --- |
549
+ ${external.map((row) => `| ${row.resource_type}:${row.resource_key} ${escapeTable(row.title)} | ${row.source_project_id} | ${escapeTable(row.locator)} |`).join('\n')}` : '未发现非本项目来源 ID。'}
550
+ `;
551
+ }
552
+
553
+ function renderGovernanceIndex(conflicts) {
554
+ return `# 治理与质量\n\n- [资产权威状态](authority-index.md)\n- [覆盖、物理来源与未归属资产](coverage.md)\n- [项目来源边界](source-project-boundary.md)\n\n## Agent 使用方式\n\n- 回答前先看资产页 frontmatter 的 \`runtime_policy\`、\`definition_state\` 和 \`authority_role\`。\n- \`runtime_policy=block\` 的资产不能直接回答;如果存在 canonical 资产,优先跳转到 canonical。\n- \`dependency_only\` 元数据只解释报表/指标依赖,不单独发起召回。\n- 语义冲突表示多个资产表达同一业务含义但口径不同,不能只凭名称选择资产;优先使用业务域、召回卡或用户确认。\n- 项目来源边界用于识别资产包里引用了其他项目 ID 的字段;跨项目引用只能作为来源线索,不代表当前项目事实。\n\n当前检测到 ${conflicts} 张语义冲突报表。\n`;
555
+ }
556
+
557
+ function renderWikiIndex(corpus) {
558
+ return `---\ntype: project-semantic-knowledge-wiki-index\nsemantic_project_id: ${semanticProjectId}\nproject_name: ${yaml(projectName)}\nsource_snapshot_hash: ${yaml(snapshotHash)}\n---\n\n# ${projectName} 项目语义知识库 Wiki\n\n## 查询顺序\n\n1. [场景召回卡](recall-cards/index.md)\n2. [业务域](domains/index.md)\n3. [看板](dashboards/index.md)、[报表](reports/index.md)、[可复用指标](metrics/index.md)\n4. [分析元数据](metadata/index.md)\n5. [治理与质量](governance/index.md)\n\n技术、演示、副本和定向诊断资产只在[技术附录](technical-appendices/index.md)中追溯。物理空间在[source containers](source-containers/index.md)中保留,不作为业务域。\n\n${countSummary(corpus)}\n`;
559
+ }
560
+
561
+ function renderSnapshotIndex(corpus) {
562
+ const title = String(projectName).replace(/\r?\n/g, ' ').replace(/^#+\s*/, '');
563
+ return `# ${title} 项目语义知识库 Wiki\n\n这是可导入知识库源文件体系的项目语义 Wiki,共包含 ${corpus.counts.dashboards} 张看板、${corpus.counts.reports} 张报表和 ${corpus.counts.analysis_metadata} 条分析元数据。\n\n## 查询顺序\n\n1. [场景召回卡](wiki/recall-cards/index.md)\n2. [业务域](wiki/domains/index.md)\n3. [看板](wiki/dashboards/index.md)、[报表](wiki/reports/index.md)、[可复用指标](wiki/metrics/index.md)\n4. [分析元数据](wiki/metadata/index.md)\n5. [治理与质量](wiki/governance/index.md)\n\n技术、演示、副本和定向诊断资产只在[技术附录](wiki/technical-appendices/index.md)中追溯。物理空间在[source containers](wiki/source-containers/index.md)中保留,不作为业务域。\n\n${countSummary(corpus)}\n`;
564
+ }
565
+
566
+ function renderProjectOverview(corpus) {
567
+ return `# ${projectName} 项目语义知识库概览\n\n- Semantic project ID: ${semanticProjectId}\n- Snapshot hash: \`${snapshotHash}\`\n- Asset scope: ${packageAssetScope}\n- Business domains: ${corpus.counts.business_domains}\n- Dashboards: ${corpus.counts.dashboards}\n- Reports: ${corpus.counts.reports}\n- Reusable metrics: ${corpus.counts.reusable_metrics}\n- Metadata: ${corpus.counts.analysis_metadata}\n\nCommon 资产包是权威来源。本 Wiki 将用途总结和精确证据合并为单一页面,不再发布第二套 raw/briefs 语料。\n`;
568
+ }
569
+
570
+ function renderLog(corpus) {
571
+ return `# Build log\n\n## Snapshot ${snapshotHash}\n\n- Semantic project ID: ${semanticProjectId}\n- Business domains: ${corpus.counts.business_domains}\n- Reports: ${corpus.counts.reports}\n- Semantic plan hash: \`${sha256(fs.readFileSync(semanticPlanPath))}\`\n`;
572
+ }
573
+
574
+ function countSummary(corpus) {
575
+ return `## 数量\n\n| 资产 | 数量 |\n| --- | ---: |\n| 业务域 | ${corpus.counts.business_domains} |\n| 技术附录 | ${corpus.counts.technical_appendices} |\n| 看板 | ${corpus.counts.dashboards} |\n| 报表 | ${corpus.counts.reports} |\n| 可复用指标 | ${corpus.counts.reusable_metrics} |\n| 分析元数据 | ${corpus.counts.analysis_metadata} |\n| 场景召回卡 | ${planModel.cards.length} |`;
576
+ }
577
+
578
+ function authorityFrontmatter(type, resourceKey, title, authority) {
579
+ return `---\ntype: ${type}\nsemantic_project_id: ${semanticProjectId}\nresource_key: ${yaml(resourceKey)}\ntitle: ${yaml(title)}\nsource_snapshot_hash: ${yaml(snapshotHash)}\nauthority_role: ${authority.authority_role}\ncertification_state: ${authority.certification_state}\ndefinition_state: ${authority.definition_state}\nruntime_policy: ${authority.runtime_policy}\nstandalone_recall: ${authority.standalone_recall}\ncanonical_asset_id: ${authority.canonical_asset_id ? yaml(authority.canonical_asset_id) : 'null'}\n---`;
580
+ }
581
+
582
+ function domainFrontmatter(domain) {
583
+ return `---\ntype: project-semantic-business-domain\nsemantic_project_id: ${semanticProjectId}\ndomain_id: ${yaml(domain.domain_id)}\ntitle: ${yaml(domain.title)}\nsource_snapshot_hash: ${yaml(snapshotHash)}\n---`;
584
+ }
585
+
586
+ function appendixFrontmatter(appendix) {
587
+ return `---\ntype: project-semantic-technical-appendix\nsemantic_project_id: ${semanticProjectId}\nappendix_id: ${yaml(appendix.appendix_id)}\ntitle: ${yaml(appendix.title)}\nsource_snapshot_hash: ${yaml(snapshotHash)}\nauthority_role: appendix\nruntime_policy: block\nstandalone_recall: false\n---`;
588
+ }
589
+
590
+ function canonicalId(type, excluded) {
591
+ const key = asArray(excluded).map((row) => clean(row.canonical_resource_key)).find(Boolean);
592
+ return key ? `${type}:${key}` : null;
593
+ }
594
+
595
+ function assetPath(type, key) {
596
+ if (type === 'dashboard') return `../dashboards/${dashboardFileNames.get(key) ?? fileName('dashboard', key)}`;
597
+ if (type === 'report') return `../reports/${reportFileNames.get(key) ?? fileName('report', key)}`;
598
+ if (type === 'metric') return `../metrics/${metricFileNames.get(key) ?? fileName('metric', key)}`;
599
+ fail(`Unsupported recall-card asset type ${type}.`);
600
+ }
601
+
602
+ function assetOutputFileName(directory, key) {
603
+ if (directory === 'dashboards') return dashboardFileNames.get(key) ?? fileName('dashboard', key);
604
+ if (directory === 'reports') return reportFileNames.get(key) ?? fileName('report', key);
605
+ if (directory === 'metrics') return metricFileNames.get(key) ?? fileName('metric', key);
606
+ return fileName(directory.slice(0, -1), key);
607
+ }
608
+
609
+ function titleFirstAssetFileMap(values, kind) {
610
+ const stems = new Map();
611
+ const result = new Map();
612
+ for (const asset of values) {
613
+ const key = clean(asset.resource_key);
614
+ const titleStem = slug(assetReadableTitle(asset, kind)) || kind;
615
+ const keyStem = slug(key) || 'id';
616
+ const base = slug([titleStem, keyStem].filter(Boolean).join('-'));
617
+ const stem = stems.has(base) ? `${base}-${sha256(`${kind}:${key}`).slice(0, 8)}` : base;
618
+ stems.set(base, true);
619
+ result.set(key, `${stem}.md`);
620
+ }
621
+ return result;
622
+ }
623
+
624
+ function titleFirstAssetPathRewrites() {
625
+ const mappings = new Map();
626
+ for (const [key, file] of dashboardFileNames) {
627
+ mappings.set(`wiki/dashboards/${fileName('dashboard', key)}`, `wiki/dashboards/${file}`);
628
+ }
629
+ for (const [key, file] of reportFileNames) {
630
+ mappings.set(`wiki/reports/${fileName('report', key)}`, `wiki/reports/${file}`);
631
+ }
632
+ for (const [key, file] of metricFileNames) {
633
+ mappings.set(`wiki/metrics/${fileName('metric', key)}`, `wiki/metrics/${file}`);
634
+ }
635
+ return mappings;
636
+ }
637
+
638
+ function assetReadableTitle(asset, kind) {
639
+ const title = cleanAssetTitle(asset.title);
640
+ if (isMeaningfulTitle(title)) return title;
641
+ if (kind === 'report') {
642
+ const dashboardTitle = asArray(asset.dashboard_refs)
643
+ .map((ref) => clean(ref.dashboard_name))
644
+ .find(isMeaningfulTitle);
645
+ if (dashboardTitle) return `${dashboardTitle} 报表`;
646
+ const containerTitle = asArray(asset.source_container_refs)
647
+ .map((ref) => clean(ref.container_title))
648
+ .find(isMeaningfulTitle);
649
+ if (containerTitle) return `${containerTitle} 报表`;
650
+ }
651
+ if (kind === 'dashboard') {
652
+ const containerTitle = clean(asObject(asset.source_container_ref).container_title);
653
+ if (isMeaningfulTitle(containerTitle)) return `${containerTitle} 看板`;
654
+ }
655
+ if (kind === 'metric') return '可复用指标';
656
+ return kind === 'dashboard' ? '看板' : kind === 'report' ? '报表' : kind;
657
+ }
658
+
659
+ function cleanAssetTitle(value) {
660
+ return clean(value)
661
+ .replace(/^(?:[??]\s*){2,}(?=\S)/, '')
662
+ .replace(/\s+/g, ' ')
663
+ .trim();
664
+ }
665
+
666
+ function isMeaningfulTitle(value) {
667
+ const normalized = cleanAssetTitle(value);
668
+ if (!normalized || /^undefined$/i.test(normalized)) return false;
669
+ const stem = slug(normalized);
670
+ return Boolean(stem) && /\p{L}/u.test(normalized) && !/^[._-]+$/.test(stem) && !/^\d+$/.test(stem);
671
+ }
672
+
673
+ function ensureReadableHeading(content, title) {
674
+ const current = firstHeading(content);
675
+ if (isMeaningfulTitle(current)) return content;
676
+ return content.replace(/^# .+$/m, `# ${title}`);
677
+ }
678
+
679
+ function sourceProjectReferencesFromPackage() {
680
+ const rows = [];
681
+ for (const [resourceType, values] of [['dashboard', dashboards], ['report', reports], ['metric', metrics]]) {
682
+ for (const asset of values) collectSourceProjectReferences(asset, rows, {
683
+ resource_type: resourceType,
684
+ resource_key: clean(asset.resource_key),
685
+ title: assetReadableTitle(asset, resourceType),
686
+ });
687
+ }
688
+ const seen = new Set();
689
+ return rows.filter((row) => {
690
+ const key = `${row.resource_type}:${row.resource_key}:${row.source_project_id}:${row.locator}`;
691
+ if (seen.has(key)) return false;
692
+ seen.add(key);
693
+ return true;
694
+ }).sort((a, b) => `${a.resource_type}:${a.resource_key}:${a.locator}`.localeCompare(`${b.resource_type}:${b.resource_key}:${b.locator}`, 'en'));
695
+ }
696
+
697
+ function collectSourceProjectReferences(value, rows, asset, stack = []) {
698
+ if (Array.isArray(value)) {
699
+ value.forEach((item, index) => collectSourceProjectReferences(item, rows, asset, [...stack, String(index)]));
700
+ return;
701
+ }
702
+ if (!value || typeof value !== 'object') return;
703
+ for (const [key, item] of Object.entries(value)) {
704
+ const nextStack = [...stack, key];
705
+ if (['projectId', 'source_project_id'].includes(key) && Number.isInteger(Number(item))) {
706
+ rows.push({ ...asset, source_project_id: Number(item), locator: nextStack.join('.') });
707
+ } else {
708
+ collectSourceProjectReferences(item, rows, asset, nextStack);
709
+ }
710
+ }
711
+ }
712
+
713
+ function sourceProjectIdsFromPackage() {
714
+ const ids = new Set();
715
+ collectProjectIds([...dashboards, ...reports, ...metrics], ids);
716
+ return [...ids].filter((id) => Number.isInteger(id) && id > 0).sort((a, b) => a - b);
717
+ }
718
+
719
+ function collectProjectIds(value, ids) {
720
+ if (Array.isArray(value)) return value.forEach((item) => collectProjectIds(item, ids));
721
+ if (!value || typeof value !== 'object') return;
722
+ for (const [key, item] of Object.entries(value)) {
723
+ if (['projectId', 'source_project_id'].includes(key) && Number.isInteger(Number(item))) ids.add(Number(item));
724
+ else collectProjectIds(item, ids);
725
+ }
726
+ }
727
+
728
+ function assertNoForbiddenPublishedLayers(root) {
729
+ for (const name of ['raw', 'briefs', '_source']) {
730
+ if (fs.existsSync(path.join(root, name)) || fs.existsSync(path.join(root, 'wiki', name))) {
731
+ fail(`Published Wiki bundle must not contain ${name}/.`);
732
+ }
733
+ }
734
+ }
735
+
736
+ function hideMarkdownFrontmatter(root) {
737
+ for (const file of listFiles(root).filter((value) => value.endsWith('.md'))) {
738
+ const content = readText(file);
739
+ const hidden = content.replace(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/, '<!--\npage_metadata:\n$1\n-->\n\n');
740
+ if (hidden !== content) writeText(file, hidden);
741
+ }
742
+ }
743
+
744
+ function assertSnapshotEntryPoint(root) {
745
+ const content = readText(path.join(root, 'index.md'));
746
+ const requiredLinks = [
747
+ 'wiki/recall-cards/index.md', 'wiki/domains/index.md', 'wiki/dashboards/index.md',
748
+ 'wiki/reports/index.md', 'wiki/metrics/index.md', 'wiki/metadata/index.md',
749
+ 'wiki/governance/index.md', 'wiki/technical-appendices/index.md',
750
+ 'wiki/source-containers/index.md',
751
+ ];
752
+ const missing = requiredLinks.filter((target) => !content.includes(`(${target})`));
753
+ if (missing.length) fail(`Snapshot root index is missing required navigation: ${missing.join(', ')}.`);
754
+ if (/^---$/m.test(content) || content.includes('进入项目语义知识库 Wiki')) {
755
+ fail('Snapshot root index must be a display-ready navigation page, not a frontmatter or wrapper page.');
756
+ }
757
+ }
758
+
759
+ function rewriteTitleFirstAssetLinks(root, relPathMap) {
760
+ for (const file of listFiles(root).filter((value) => value.endsWith('.md'))) {
761
+ const currentRel = path.relative(root, file).replaceAll(path.sep, '/');
762
+ const rewritten = readText(file).replace(/\[([^\]]*)\]\(([^)]+)\)/g, (match, label, target) => {
763
+ const [targetPath, fragment = ''] = String(target).split('#');
764
+ if (!targetPath || /^(https?:|mailto:)/.test(targetPath)) return match;
765
+ const resolved = path.posix.normalize(path.posix.join(path.posix.dirname(currentRel), decodeURIComponent(targetPath)));
766
+ const mapped = relPathMap.get(resolved);
767
+ if (!mapped) return match;
768
+ const relative = path.posix.relative(path.posix.dirname(currentRel), mapped) || path.posix.basename(mapped);
769
+ return `[${label}](${relative}${fragment ? `#${fragment}` : ''})`;
770
+ });
771
+ if (rewritten !== readText(file)) writeText(file, rewritten);
772
+ }
773
+ }
774
+
775
+ function assertNoBareNumericAssetFiles(wikiRoot) {
776
+ const offenders = ['dashboards', 'reports', 'metrics'].flatMap((directory) => {
777
+ const dir = path.join(wikiRoot, directory);
778
+ return fs.readdirSync(dir).filter((file) => /^[0-9]+\.md$/.test(file)).map((file) => `${directory}/${file}`);
779
+ });
780
+ if (offenders.length) fail(`Published Wiki asset files must be title-first, not bare numeric IDs: ${offenders.slice(0, 10).join(', ')}.`);
781
+ }
782
+
783
+ function assertMarkdownLinks(root) {
784
+ const files = listFiles(root).filter((file) => file.endsWith('.md'));
785
+ const missing = [];
786
+ for (const file of files) {
787
+ const content = stripFencedCodeBlocks(readText(file));
788
+ for (const match of content.matchAll(/\[[^\]]*\]\(([^)]+)\)/g)) {
789
+ const target = match[1].split('#')[0];
790
+ if (!target || /^(https?:|mailto:)/.test(target)) continue;
791
+ const resolved = path.resolve(path.dirname(file), decodeURIComponent(target));
792
+ if (!fs.existsSync(resolved)) missing.push(`${path.relative(root, file)} -> ${target}`);
793
+ }
794
+ }
795
+ if (missing.length) fail(`Wiki contains ${missing.length} broken Markdown link(s): ${missing.slice(0, 12).join(', ')}.`);
796
+ }
797
+
798
+ function stripFencedCodeBlocks(content) {
799
+ return content.replace(/^```[\s\S]*?^```/gm, '');
800
+ }
801
+
802
+ async function writeDeterministicZip(root, target, replace) {
803
+ if (fs.existsSync(target) && !replace) fail(`Archive already exists: ${target}. Pass --force to replace it.`);
804
+ fs.mkdirSync(path.dirname(target), { recursive: true });
805
+ const temporary = `${target}.tmp-${process.pid}-${randomBytes(4).toString('hex')}`;
806
+ await new Promise((resolve, reject) => {
807
+ const output = fs.createWriteStream(temporary, { mode: 0o600 });
808
+ const zip = typeof archiver === 'function'
809
+ ? archiver('zip', { zlib: { level: 9 } })
810
+ : new ZipArchive({ zlib: { level: 9 } });
811
+ output.on('close', resolve);
812
+ output.on('error', reject);
813
+ zip.on('error', reject);
814
+ zip.pipe(output);
815
+ for (const file of listFiles(root).sort()) {
816
+ zip.append(fs.readFileSync(file), {
817
+ name: path.relative(root, file).replaceAll(path.sep, '/'), mode: 0o600,
818
+ date: new Date('1980-01-01T00:00:00.000Z'),
819
+ });
820
+ }
821
+ void zip.finalize();
822
+ });
823
+ if (fs.existsSync(target)) fs.rmSync(target, { force: true });
824
+ fs.renameSync(temporary, target);
825
+ }
826
+
827
+
828
+ function assertPrecompiledSourceQuality(root) {
829
+ const issues = [];
830
+ const maxIssues = 30;
831
+ for (const file of listFiles(root).filter((value) => value.endsWith('.md')).sort()) {
832
+ const rel = path.relative(root, file).replaceAll(path.sep, '/');
833
+ if (rel === 'kb-upload-manifest.json') continue;
834
+ const visible = stripHtmlComments(readText(file));
835
+ for (const [label, pattern] of forbiddenVisibleSourcePatterns()) {
836
+ const match = visible.match(pattern);
837
+ if (match) issues.push(`${rel}: ${label}: ${match[0].slice(0, 80)}`);
838
+ if (issues.length >= maxIssues) break;
839
+ }
840
+ if (issues.length >= maxIssues) break;
841
+ }
842
+ if (issues.length) {
843
+ fail(`Precompiled KB source quality check failed; Agent-authored source still contains template or parser noise. Examples: ${issues.join(' | ')}`);
844
+ }
845
+ }
846
+
847
+ function stripHtmlComments(value) {
848
+ return clean(value).replace(/<!--[\s\S]*?-->/g, '');
849
+ }
850
+
851
+ function forbiddenVisibleSourcePatterns() {
852
+ return [
853
+ ['generic recall routing text', new RegExp('该认证看板的子报表直接覆盖|同主题补充视角|具体口径以资产页为准|使用前确认报表适用范围')],
854
+ ['undefined metric prefix', new RegExp('\\bundefined[._-]+[\\p{L}\\p{N}_-]+', 'u')],
855
+ ['unreadable question-mark title prefix', new RegExp('(?:^|[\\n#\\-::])\\s*[??]\\s+[??]\\s+\\S', 'u')],
856
+ ['raw aggregation code', new RegExp('[((\\s::]A\\d{3}(?:_\\d+)?[))\\s,,;;。]')],
857
+ ['raw filter operator code', new RegExp('[((\\s::]C\\d{2,3}[))\\s,,;;。]')],
858
+ ['SQL parser placeholder', new RegExp('\\$\\{(?:Text|Selector|PartDate|Number|Date|DateTime|MultiSelector)\\s*:[^}]+\\}|SQL\\s*输出字段|来源表达式|where 条件', 'i')],
859
+ ];
860
+ }
861
+
862
+ function writeFlatUploadSources(sourceRoot, targetRoot, replace) {
863
+ if (fs.existsSync(targetRoot) && !replace) fail(`Upload sources output already exists: ${targetRoot}. Pass --force to replace it.`);
864
+ const markdownFiles = listFiles(sourceRoot)
865
+ .filter((file) => file.endsWith('.md'))
866
+ .sort();
867
+ const relToFlat = new Map(markdownFiles.map((file) => {
868
+ const rel = path.relative(sourceRoot, file).replaceAll(path.sep, '/');
869
+ return [rel, flatUploadFileName(rel, uploadSourceNamespace)];
870
+ }));
871
+ const manifestSources = [];
872
+ materialize(targetRoot, replace, (staging) => {
873
+ for (const file of markdownFiles) {
874
+ const rel = path.relative(sourceRoot, file).replaceAll(path.sep, '/');
875
+ const flatName = relToFlat.get(rel);
876
+ const content = precompiledSource(rewriteLinksForFlatUpload(readText(file), rel, relToFlat));
877
+ const contentHash = syncContentHash(content);
878
+ const identity = uploadSourceIdentity(rel, content);
879
+ manifestSources.push({
880
+ display_name: flatName,
881
+ file: flatName,
882
+ namespace: uploadSourceNamespace,
883
+ snapshot_date: snapshotDate,
884
+ snapshot_hash: snapshotHash,
885
+ source_kind: identity.source_kind,
886
+ asset_type: identity.asset_type,
887
+ asset_key: identity.asset_key,
888
+ title: firstHeading(content),
889
+ content_hash: contentHash,
890
+ source_path: rel,
891
+ });
892
+ writeText(path.join(staging, flatName), `<!--\nkb_source_namespace: ${uploadSourceNamespace}\nsnapshot_date: ${snapshotDate ?? 'null'}\nsnapshot_hash: ${snapshotHash}\nsource_kind: ${identity.source_kind}\nasset_type: ${identity.asset_type ?? 'null'}\nasset_key: ${identity.asset_key ?? 'null'}\ncontent_hash: ${contentHash}\nsource_path: ${rel}\n-->\n\n${content}`);
893
+ }
894
+ const manifest = readJson(path.join(sourceRoot, 'manifest.json'));
895
+ const manifestContent = precompiledSource(`# Package manifest\n\n\`\`\`json\n${JSON.stringify(manifest, null, 2)}\n\`\`\`\n`);
896
+ const manifestName = `${uploadSourceNamespace}-manifest.md`;
897
+ const manifestHash = syncContentHash(manifestContent);
898
+ manifestSources.push({
899
+ display_name: manifestName,
900
+ file: manifestName,
901
+ namespace: uploadSourceNamespace,
902
+ snapshot_date: snapshotDate,
903
+ snapshot_hash: snapshotHash,
904
+ source_kind: 'manifest',
905
+ asset_type: null,
906
+ asset_key: null,
907
+ title: 'Package manifest',
908
+ content_hash: manifestHash,
909
+ source_path: 'manifest.json',
910
+ });
911
+ writeText(path.join(staging, manifestName), `<!--\nkb_source_namespace: ${uploadSourceNamespace}\nsnapshot_date: ${snapshotDate ?? 'null'}\nsnapshot_hash: ${snapshotHash}\nsource_kind: manifest\nasset_type: null\nasset_key: null\ncontent_hash: ${manifestHash}\nsource_path: manifest.json\n-->\n\n${manifestContent}`);
912
+ writeJson(path.join(staging, 'kb-upload-manifest.json'), {
913
+ schema_version: '1.0',
914
+ manifest_kind: 'ae_project_semantic_kb_upload_sources',
915
+ namespace: uploadSourceNamespace,
916
+ semantic_project_id: semanticProjectId,
917
+ project_name: projectName,
918
+ snapshot_date: snapshotDate,
919
+ snapshot_hash: snapshotHash,
920
+ source_count: manifestSources.length,
921
+ sources: manifestSources.sort((a, b) => a.display_name.localeCompare(b.display_name, 'en')),
922
+ });
923
+ });
924
+ const basenames = listFiles(targetRoot).filter((file) => file.endsWith('.md')).map((file) => path.basename(file));
925
+ if (new Set(basenames).size !== basenames.length) fail('Flat upload sources contain duplicate file names.');
926
+ return {
927
+ count: basenames.length,
928
+ namespace: uploadSourceNamespace,
929
+ manifestPath: path.join(targetRoot, 'kb-upload-manifest.json'),
930
+ };
931
+ }
932
+
933
+ function flatUploadFileName(rel, namespace) {
934
+ const identity = uploadSourceIdentity(rel);
935
+ const stem = slug([namespace, identity.file_stem].filter(Boolean).join('-'));
936
+ if (stem.length <= 150) return `${stem}.md`;
937
+ return `${stem.slice(0, 125)}-${sha256(rel).slice(0, 16)}.md`;
938
+ }
939
+
940
+ function uploadSourceIdentity(rel, content = '') {
941
+ const normalized = rel.replaceAll('\\', '/');
942
+ if (normalized === 'index.md') return { source_kind: 'index', asset_type: null, asset_key: null, file_stem: 'index' };
943
+ if (normalized === 'wiki/index.md') return { source_kind: 'index', asset_type: null, asset_key: null, file_stem: 'wiki-index' };
944
+ const match = normalized.match(/^wiki\/([^/]+)\/(.+)\.md$/);
945
+ if (!match) {
946
+ const fallback = normalized.replace(/\.md$/i, '').replace(/\//g, '-');
947
+ return { source_kind: 'wiki-page', asset_type: null, asset_key: null, file_stem: fallback };
948
+ }
949
+ const section = match[1];
950
+ const rest = match[2];
951
+ const stem = rest.split('/').join('-');
952
+ if (section === 'dashboards') return namedUploadSource('dashboard', stem, frontmatterValue(content, 'resource_key'));
953
+ if (section === 'reports') return namedUploadSource('report', stem, frontmatterValue(content, 'resource_key'));
954
+ if (section === 'metrics') return namedUploadSource('metric', stem, frontmatterValue(content, 'resource_key'));
955
+ if (section === 'domains') return namedUploadSource('domain', stem);
956
+ if (section === 'recall-cards') return namedUploadSource('recall-card', stem);
957
+ if (section === 'technical-appendices') return namedUploadSource('appendix', stem);
958
+ if (section === 'source-containers') return namedUploadSource('source-container', stem);
959
+ if (section === 'governance') return namedUploadSource('governance', stem);
960
+ if (section === 'metadata') return {
961
+ source_kind: 'metadata',
962
+ asset_type: 'metadata',
963
+ asset_key: stem === 'index' ? null : stem,
964
+ file_stem: `metadata-${stem}`,
965
+ };
966
+ return namedUploadSource(section, stem);
967
+ }
968
+
969
+ function namedUploadSource(kind, stem, resourceKey = '') {
970
+ const key = stem === 'index' ? null : clean(resourceKey) || stem;
971
+ return {
972
+ source_kind: kind,
973
+ asset_type: key ? kind : null,
974
+ asset_key: key,
975
+ file_stem: key ? `${kind}-${key}` : `${kind}-index`,
976
+ };
977
+ }
978
+
979
+ function rewriteLinksForFlatUpload(content, currentRel, relToFlat) {
980
+ return content.replace(/\[([^\]]*)\]\(([^)]+)\)/g, (match, label, target) => {
981
+ const [targetPath, fragment = ''] = String(target).split('#');
982
+ if (!targetPath || /^(https?:|mailto:)/.test(targetPath)) return match;
983
+ const resolved = path.posix.normalize(path.posix.join(path.posix.dirname(currentRel), decodeURIComponent(targetPath)));
984
+ const flat = relToFlat.get(resolved);
985
+ if (!flat) return match;
986
+ return `[${label}](${flat}${fragment ? `#${fragment}` : ''})`;
987
+ });
988
+ }
989
+
990
+ function syncContentHash(content) { return sha256(normalizeSourceForHash(content)); }
991
+
992
+ function materialize(output, replace, writer) {
993
+ if (fs.existsSync(output) && !replace) fail(`Output already exists: ${output}. Pass --force to replace it.`);
994
+ const parent = path.dirname(output);
995
+ fs.mkdirSync(parent, { recursive: true });
996
+ const staging = path.join(parent, `.${path.basename(output)}.staging-${process.pid}-${randomBytes(4).toString('hex')}`);
997
+ const backup = path.join(parent, `.${path.basename(output)}.backup-${process.pid}-${randomBytes(4).toString('hex')}`);
998
+ fs.mkdirSync(staging, { recursive: true });
999
+ try {
1000
+ writer(staging);
1001
+ if (fs.existsSync(output)) fs.renameSync(output, backup);
1002
+ fs.renameSync(staging, output);
1003
+ fs.rmSync(backup, { recursive: true, force: true });
1004
+ } catch (error) {
1005
+ fs.rmSync(staging, { recursive: true, force: true });
1006
+ if (!fs.existsSync(output) && fs.existsSync(backup)) fs.renameSync(backup, output);
1007
+ throw error;
1008
+ }
1009
+ }
1010
+
1011
+ function replaceFrontmatter(content, frontmatter) { return `${frontmatter}\n${stripFrontmatter(content).trimStart()}`; }
1012
+ function stripFrontmatter(content) { return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, ''); }
1013
+ function stripFirstHeading(content) { return content.trimStart().replace(/^# .*\r?\n+/, ''); }
1014
+ function firstHeading(content) { return content.match(/^# (.+)$/m)?.[1]?.trim() || 'Untitled'; }
1015
+ function frontmatterValue(content, key) { return content.match(new RegExp(`^${key}:\\s*(.+)$`, 'm'))?.[1]?.replace(/^['"]|['"]$/g, '') || ''; }
1016
+ function copyIndex(source, target) { writeText(target, readText(source).replaceAll('../appendices/', '../technical-appendices/')); }
1017
+ function fileName(prefix, value) { return `${slug(clean(value) || prefix)}.md`; }
1018
+ function slug(value) { return value.normalize('NFKC').toLowerCase().replace(/[^\p{L}\p{N}._-]+/gu, '-').replace(/^-+|-+$/g, '').slice(0, 120) || 'item'; }
1019
+ function yaml(value) { return JSON.stringify(value ?? null); }
1020
+ function escapeLink(value) { return clean(value).replaceAll('[', '\\[').replaceAll(']', '\\]'); }
1021
+ function escapeTable(value) { return clean(value).replaceAll('|', '\\|').replaceAll('\n', ' '); }
1022
+ function sha256(value) { return createHash('sha256').update(value).digest('hex'); }
1023
+ function addMulti(map, key, value) { map.set(key, [...(map.get(key) ?? []), value]); }
1024
+ function clean(value) { return value == null ? '' : String(value).trim(); }
1025
+ function asArray(value) { return Array.isArray(value) ? value : []; }
1026
+ function asObject(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }
1027
+ function indexBy(values, key) { return new Map(values.map((value) => [clean(value[key]), value])); }
1028
+ function readText(file) { return fs.readFileSync(file, 'utf8'); }
1029
+ function readJson(file) { return JSON.parse(readText(file)); }
1030
+ function writeText(file, value) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, value.endsWith('\n') ? value : `${value}\n`, 'utf8'); }
1031
+ function writeJson(file, value) { writeText(file, JSON.stringify(value, null, 2)); }
1032
+ function readIndex(name) { return readText(path.join(packageRoot, 'indexes', `${name}.jsonl`)).split(/\r?\n/).filter(Boolean).map(JSON.parse).filter((row) => row.record_type !== 'header'); }
1033
+ function listFiles(root) { return fs.readdirSync(root, { withFileTypes: true }).flatMap((entry) => entry.isDirectory() ? listFiles(path.join(root, entry.name)) : [path.join(root, entry.name)]); }
1034
+ function parseArgs(values) { const out = {}; for (let i = 0; i < values.length; i += 1) { const value = values[i]; if (!value.startsWith('--')) fail(`Unexpected argument: ${value}`); const key = value.slice(2); if (['force', 'keep-build-ir', 'allow-all-visible'].includes(key)) out[key] = true; else out[key] = values[++i]; } return out; }
1035
+ function required(values, key) { const value = clean(values[key]); if (!value) fail(`--${key} is required.`); return value; }
1036
+ function normalizeUploadSourceNamespace(value) {
1037
+ const namespace = slug(clean(value));
1038
+ if (!/^[a-z0-9][a-z0-9._-]{1,79}$/.test(namespace)) fail('Upload source namespace must be 2-80 characters and use lowercase letters, numbers, dot, underscore, or dash.');
1039
+ return namespace;
1040
+ }
1041
+ function normalizeSnapshotDate(value) {
1042
+ const text = clean(value);
1043
+ if (!text) return null;
1044
+ const date = text.match(/^\d{4}-\d{2}-\d{2}/)?.[0];
1045
+ if (!date) fail('Snapshot date must start with YYYY-MM-DD.');
1046
+ return date;
1047
+ }
1048
+ function assertSafePaths(source, output, archive, uploadSources) {
1049
+ if (source === output || output.startsWith(`${source}${path.sep}`)) fail('Output must not overwrite or be nested inside the asset package.');
1050
+ if (archive.startsWith(`${output}${path.sep}`)) fail('Archive must not be nested inside the output directory.');
1051
+ if (uploadSources === output || uploadSources.startsWith(`${output}${path.sep}`)) fail('Upload sources output must not be nested inside the review Wiki output.');
1052
+ if (uploadSources === source || uploadSources.startsWith(`${source}${path.sep}`)) fail('Upload sources output must not be nested inside the asset package.');
1053
+ }
1054
+ function fail(message) { process.stderr.write(`${message}\n`); process.exit(1); }