@thinkingai/ae-cli 6.1.24 → 6.1.26

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 (195) hide show
  1. package/README.md +35 -10
  2. package/README.zh.md +32 -10
  3. package/bin/ae-cli.js +6 -1
  4. package/cli-installation-guide.md +91 -0
  5. package/dist/{auth-XZNXFRJS.js → auth-YHMC3GDH.js} +168 -10
  6. package/dist/automation-NWWVLWG2.js +459 -0
  7. package/dist/{capability-FYTU3X6L.js → capability-3QLLIVFB.js} +11 -6
  8. package/dist/chunk-4655PUNX.js +43 -0
  9. package/dist/{chunk-4NRCTUZJ.js → chunk-5XJYLEVC.js} +4 -4
  10. package/dist/{chunk-JOCDD4ON.js → chunk-7QXISROD.js} +1 -1
  11. package/dist/{chunk-EQ255HKO.js → chunk-AP7YP2NX.js} +2 -2
  12. package/dist/{chunk-DVMLWQD4.js → chunk-CT7QDJS6.js} +2 -2
  13. package/dist/chunk-D6ACFILC.js +56 -0
  14. package/dist/{chunk-HFGZGYCG.js → chunk-EOYSKMXN.js} +264 -10
  15. package/dist/{chunk-57RRFUEZ.js → chunk-FMNNGNYO.js} +43 -8
  16. package/dist/{chunk-7XGFSUOM.js → chunk-GIK6EDUG.js} +291 -84
  17. package/dist/chunk-HVHR4SCP.js +133 -0
  18. package/dist/{chunk-CCS66K4K.js → chunk-KAEHZZH4.js} +3 -3
  19. package/dist/{chunk-DX5CDO34.js → chunk-KFNMQTZI.js} +580 -214
  20. package/dist/{chunk-HBGADCKA.js → chunk-PLE722AJ.js} +28 -11
  21. package/dist/chunk-RK6GLDGI.js +27 -0
  22. package/dist/{chunk-V2NUHPXF.js → chunk-S6M42KND.js} +1 -1
  23. package/dist/{chunk-7FTTULED.js → chunk-UQ77JXYH.js} +7 -3
  24. package/dist/{chunk-MAPZ7VB6.js → chunk-WNGFSIOB.js} +20 -1
  25. package/dist/{chunk-VYXXOKYS.js → chunk-XCHAYMSF.js} +7 -4
  26. package/dist/{community-report-client-CU22W53K.js → community-report-client-PEERQCX2.js} +1 -1
  27. package/dist/{config-XO3LI3SA.js → config-AHPMEW5A.js} +25 -6
  28. package/dist/context-YAE3QXP3.js +136 -0
  29. package/dist/{data-integration-HNGUKFIC.js → data-integration-HESZET7E.js} +6 -6
  30. package/dist/index.js +167 -53
  31. package/dist/{local-data-upload-client-GTRIT64F.js → local-data-upload-client-TWMHFLUA.js} +1 -1
  32. package/dist/memory-62BJOULV.js +580 -0
  33. package/dist/{metadata-DI5BMYFU.js → metadata-C7CEDHOJ.js} +7 -7
  34. package/dist/{model-RNPQXIBI.js → model-SBV2JFJI.js} +9 -5
  35. package/dist/{personal-semantic-preference-5C3WUXFI.js → personal-semantic-preference-QECDPHND.js} +119 -11
  36. package/dist/project-semantic-Z4QAMBWF.js +356 -0
  37. package/dist/{sync-O45NXEQY.js → sync-SU33KYX7.js} +12 -8
  38. package/dist/{te-agent-K5MSPNHJ.js → te-agent-F5QBZSQA.js} +423 -494
  39. package/dist/{te-analysis-AISWCOOZ.js → te-analysis-2ECAAIFK.js} +1227 -154
  40. package/dist/{te-community-4OYU3BDA.js → te-community-PFLDLPP4.js} +3 -3
  41. package/dist/{te-dataops-NFIPB6FX.js → te-dataops-FOQSRIAZ.js} +644 -276
  42. package/dist/{te-engage-BAM3GBFX.js → te-engage-QKUGIWSR.js} +6 -6
  43. package/dist/{te-experiment-GUTZXZ5Z.js → te-experiment-26PXR2UO.js} +6 -6
  44. package/dist/{te-kb-3TS73NO6.js → te-kb-MHDOST62.js} +666 -244
  45. package/dist/{te-system-STMHLS5J.js → te-system-J2FQF5ER.js} +6 -6
  46. package/dist/{te-team-3I5AOX3N.js → te-team-5UOZPTU3.js} +6 -6
  47. package/dist/things-VPPXXLHB.js +1091 -0
  48. package/dist/{update-RZXLPCRH.js → update-EEUTEI6R.js} +59 -51
  49. package/dist/workspace-O3EEIYQM.js +554 -0
  50. package/package.json +30 -11
  51. package/skills/ae-agent/SKILL.md +95 -140
  52. package/skills/ae-agent/references/add-model.md +1 -1
  53. package/skills/ae-agent/references/create-agent.md +1 -1
  54. package/skills/ae-agent/references/find-archived-conversations.md +12 -5
  55. package/skills/ae-agent/references/get-agent.md +1 -1
  56. package/skills/ae-agent/references/list-agents.md +2 -2
  57. package/skills/ae-agent/references/list-models.md +2 -2
  58. package/skills/ae-agent/references/notification.md +40 -0
  59. package/skills/ae-agent/references/restore-conversation.md +3 -1
  60. package/skills/ae-analysis/SKILL.md +55 -23
  61. package/skills/ae-analysis/references/adhoc_export.md +1 -1
  62. package/skills/ae-analysis/references/adhoc_run.md +1 -1
  63. package/skills/ae-analysis/references/agent_review_submit_to_page.md +2 -0
  64. package/skills/ae-analysis/references/ai_models/event.md +6 -1
  65. package/skills/ae-analysis/references/ai_models/funnel.md +12 -4
  66. package/skills/ae-analysis/references/ai_models/heat_map.md +23 -0
  67. package/skills/ae-analysis/references/ai_models/interval.md +24 -0
  68. package/skills/ae-analysis/references/ai_models/path.md +20 -0
  69. package/skills/ae-analysis/references/ai_models/prop_analysis.md +2 -0
  70. package/skills/ae-analysis/references/ai_models/rank_list.md +30 -0
  71. package/skills/ae-analysis/references/ai_models/retention.md +45 -1
  72. package/skills/ae-analysis/references/ai_models/revenue.md +20 -2
  73. package/skills/ae-analysis/references/ai_models/session.md +76 -0
  74. package/skills/ae-analysis/references/ai_models/tag.md +2 -0
  75. package/skills/ae-analysis/references/ai_models.md +100 -3
  76. package/skills/ae-analysis/references/analysis_data_retrieval.md +4 -0
  77. package/skills/ae-analysis/references/analysis_drilldown_contract.md +4 -1
  78. package/skills/ae-analysis/references/asset_batch_info_export.md +10 -0
  79. package/skills/ae-analysis/references/asset_batch_sql_export.md +13 -1
  80. package/skills/ae-analysis/references/asset_export.md +12 -1
  81. package/skills/ae-analysis/references/asset_list.md +1 -0
  82. package/skills/ae-analysis/references/asset_search.md +11 -5
  83. package/skills/ae-analysis/references/bi_panel_list.md +1 -1
  84. package/skills/ae-analysis/references/catalog_list.md +1 -1
  85. package/skills/ae-analysis/references/collaboration.md +48 -0
  86. package/skills/ae-analysis/references/command_index.md +29 -19
  87. package/skills/ae-analysis/references/dashboard_list.md +4 -4
  88. package/skills/ae-analysis/references/dashboard_move.md +21 -0
  89. package/skills/ae-analysis/references/dashboard_report_add.md +2 -0
  90. package/skills/ae-analysis/references/dashboard_report_data_export.md +2 -0
  91. package/skills/ae-analysis/references/dashboard_report_get.md +19 -0
  92. package/skills/ae-analysis/references/dashboard_report_update.md +27 -0
  93. package/skills/ae-analysis/references/dashboard_update.md +1 -1
  94. package/skills/ae-analysis/references/drilldown_session_details_run.md +41 -0
  95. package/skills/ae-analysis/references/event_export.md +3 -1
  96. package/skills/ae-analysis/references/event_report_presentation.md +76 -0
  97. package/skills/ae-analysis/references/feedback_submit.md +37 -0
  98. package/skills/ae-analysis/references/governance_recommendation_auto_review.md +77 -0
  99. package/skills/ae-analysis/references/governance_recommendation_export.md +11 -0
  100. package/skills/ae-analysis/references/metadata_resolution.md +18 -3
  101. package/skills/ae-analysis/references/metric_export.md +3 -1
  102. package/skills/ae-analysis/references/metric_list.md +1 -1
  103. package/skills/ae-analysis/references/operation_record_export.md +10 -0
  104. package/skills/ae-analysis/references/personal_semantic_preference_add.md +2 -2
  105. package/skills/ae-analysis/references/personal_semantic_preference_get.md +4 -4
  106. package/skills/ae-analysis/references/personal_semantic_preference_list.md +4 -4
  107. package/skills/ae-analysis/references/personal_semantic_preference_review.md +9 -0
  108. package/skills/ae-analysis/references/personal_semantic_preference_review_list.md +7 -0
  109. package/skills/ae-analysis/references/personal_semantic_preference_submit.md +7 -0
  110. package/skills/ae-analysis/references/project_semantic_knowledge_wiki.md +91 -32
  111. package/skills/ae-analysis/references/project_semantic_knowledge_wiki_plan_schema.md +99 -1
  112. package/skills/ae-analysis/references/property_export.md +3 -1
  113. package/skills/ae-analysis/references/report_create.md +18 -4
  114. package/skills/ae-analysis/references/report_data_export.md +6 -0
  115. package/skills/ae-analysis/references/report_get.md +7 -1
  116. package/skills/ae-analysis/references/report_list.md +5 -5
  117. package/skills/ae-analysis/references/report_update.md +21 -4
  118. package/skills/ae-analysis/references/sql_table_columns.md +4 -4
  119. package/skills/ae-analysis/references/sql_table_list.md +5 -5
  120. package/skills/ae-analysis/references/system_query_monitor_overview.md +2 -0
  121. package/skills/ae-analysis/references/system_query_task_cancel.md +2 -0
  122. package/skills/ae-analysis/references/system_query_task_export.md +6 -2
  123. package/skills/ae-analysis/references/system_query_task_get.md +2 -0
  124. package/skills/ae-analysis/references/system_query_task_list.md +7 -3
  125. package/skills/ae-analysis/references/system_query_task_options.md +2 -2
  126. package/skills/ae-analysis/references/system_query_task_realtime_list.md +15 -0
  127. package/skills/ae-analysis/references/user_cluster_models.md +8 -0
  128. package/skills/ae-analysis/references/user_tag_create.md +7 -1
  129. package/skills/ae-analysis/references/user_tag_get.md +1 -1
  130. package/skills/ae-analysis/references/user_tag_models.md +20 -2
  131. package/skills/ae-analysis/references/user_tag_refresh.md +1 -1
  132. package/skills/ae-analysis/references/user_tag_update.md +2 -2
  133. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-empty-skeleton.mjs +105 -0
  134. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/build-project-semantic-wiki.mjs +669 -29
  135. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/company-kb.mjs +21 -8
  136. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-rules.md +28 -10
  137. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/default-compile-schema.md +66 -0
  138. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/generate-build-ir.mjs +285 -24
  139. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/package-wiki-source-zip.mjs +262 -30
  140. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/plan-kb-source-sync.mjs +1 -1
  141. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/precompiled-source.mjs +46 -2
  142. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/prepare-compile-schema.mjs +64 -0
  143. package/skills/ae-analysis/scripts/project-semantic-knowledge-wiki/refresh-state-parts.mjs +91 -0
  144. package/skills/ae-automation/SKILL.md +16 -0
  145. package/skills/ae-automation/references/cli.md +53 -0
  146. package/skills/ae-automation/references/command_index.md +17 -0
  147. package/skills/ae-capability/SKILL.md +73 -2
  148. package/skills/ae-capability/references/collaboration.md +48 -0
  149. package/skills/ae-community/SKILL.md +5 -1
  150. package/skills/ae-community/references/collaboration.md +48 -0
  151. package/skills/ae-current-context/SKILL.md +3 -2
  152. package/skills/ae-data-integration/SKILL.md +4 -0
  153. package/skills/ae-data-integration/references/collaboration.md +48 -0
  154. package/skills/ae-dataops/SKILL.md +54 -74
  155. package/skills/ae-dataops/references/collaboration.md +48 -0
  156. package/skills/ae-dataops/references/dataops-backfill.md +23 -18
  157. package/skills/ae-dataops/references/dataops-flow-create.md +74 -23
  158. package/skills/ae-dataops/references/dataops-flow-monitor.md +45 -13
  159. package/skills/ae-dataops/references/dataops-integration.md +57 -30
  160. package/skills/ae-dataops/references/dataops-query.md +21 -4
  161. package/skills/ae-dataops/references/dataops-table.md +143 -11
  162. package/skills/ae-engage/SKILL.md +21 -6
  163. package/skills/ae-engage/references/channel-mgmt.md +3 -3
  164. package/skills/ae-engage/references/collaboration.md +48 -0
  165. package/skills/ae-engage/references/scene-config-channel.md +4 -4
  166. package/skills/ae-generate-tracking-plan/SKILL.md +4 -0
  167. package/skills/ae-generate-tracking-plan/references/collaboration.md +48 -0
  168. package/skills/ae-kb/SKILL.md +70 -12
  169. package/skills/ae-kb/references/analysis-workflow.md +73 -36
  170. package/skills/ae-kb/references/collaboration.md +48 -0
  171. package/skills/ae-kb/references/query-workflow.md +67 -33
  172. package/skills/ae-kb/references/schema-import.md +32 -0
  173. package/skills/ae-kb/references/source-mutations.md +6 -4
  174. package/skills/ae-kb/references/text-sources.md +53 -0
  175. package/skills/ae-kb-discovery/SKILL.md +8 -3
  176. package/skills/ae-kb-discovery/references/collaboration.md +48 -0
  177. package/skills/ae-metadata/SKILL.md +20 -12
  178. package/skills/ae-metadata/references/collaboration.md +48 -0
  179. package/skills/ae-team/SKILL.md +23 -17
  180. package/skills/ae-things/SKILL.md +85 -0
  181. package/skills/ae-things/references/cli.md +103 -0
  182. package/skills/ae-things-progress/SKILL.md +43 -0
  183. package/skills/ae-things-review/SKILL.md +42 -0
  184. package/skills/ae-workspace/SKILL.md +43 -0
  185. package/skills/ae-workspace/references/commands.md +51 -0
  186. package/dist/chunk-E2JKXMVO.js +0 -29
  187. package/dist/context-IFJI3LIT.js +0 -46
  188. package/dist/memory-QGPSNCQS.js +0 -892
  189. package/dist/project-semantic-3LF6Q6JZ.js +0 -1119
  190. package/skills/ae-agent/references/create-automation.md +0 -125
  191. package/skills/ae-agent/references/list-automations.md +0 -56
  192. package/skills/ae-agent/references/update-automation.md +0 -107
  193. package/skills/ae-project-semantic/SKILL.md +0 -193
  194. package/skills/ae-project-semantic/references/query-routing-v5.md +0 -165
  195. package/skills/ae-project-semantic/references/recommendation-quality.md +0 -68
@@ -23,6 +23,7 @@ const projectNameOverride = clean(args['project-name']);
23
23
  const force = Boolean(args.force);
24
24
  const keepBuildIr = Boolean(args['keep-build-ir']);
25
25
  const allowAllVisible = Boolean(args['allow-all-visible']);
26
+ const allowSemanticPlanSnapshotDrift = Boolean(args['allow-semantic-plan-snapshot-drift']);
26
27
 
27
28
  assertSafePaths(packageRoot, outputRoot, archivePath, uploadSourcesPath);
28
29
 
@@ -59,7 +60,9 @@ const snapshotDate = normalizeSnapshotDate(args['snapshot-date'] || descriptor.g
59
60
 
60
61
  const semanticPlan = readJson(semanticPlanPath);
61
62
  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.source_snapshot_hash) !== snapshotHash && !allowSemanticPlanSnapshotDrift) {
64
+ fail('Semantic plan snapshot hash does not match the asset package.');
65
+ }
63
66
  if (clean(semanticPlan.generation_method) !== 'agent_semantic_synthesis') {
64
67
  fail('Semantic plan generation_method must be agent_semantic_synthesis.');
65
68
  }
@@ -68,9 +71,12 @@ const dashboards = readIndex('dashboard-catalog');
68
71
  const reports = readIndex('report-catalog');
69
72
  const metrics = readIndex('metric-catalog');
70
73
  const containers = readIndex('asset-containers');
74
+ const adoptedPersonalSemantics = readPackageJsonl('sources/adopted-personal-semantics.jsonl');
75
+ const eventGroups = readOptionalIndex('event-groups');
71
76
  const dashboardById = indexBy(dashboards, 'resource_key');
72
77
  const reportById = indexBy(reports, 'resource_key');
73
78
  const metricById = indexBy(metrics, 'resource_key');
79
+ const adoptedPersonalBySourceId = indexBy(adoptedPersonalSemantics, 'source_id');
74
80
  const dashboardFileNames = titleFirstAssetFileMap(dashboards, 'dashboard');
75
81
  const reportFileNames = titleFirstAssetFileMap(reports, 'report');
76
82
  const metricFileNames = titleFirstAssetFileMap(metrics, 'metric');
@@ -78,12 +84,14 @@ const titleFirstAssetPathMap = titleFirstAssetPathRewrites();
78
84
  const containerById = indexBy(containers, 'container_key');
79
85
  const domains = asArray(semanticPlan.domains);
80
86
  const appendices = asArray(semanticPlan.appendices);
87
+ const personalSemanticIntegrations = asArray(semanticPlan.personal_semantic_integrations);
81
88
  const domainByDashboardId = new Map();
82
89
  const appendixByDashboardId = new Map();
83
90
  for (const domain of domains) for (const id of asArray(domain.dashboard_ids).map(clean)) domainByDashboardId.set(id, domain);
84
91
  for (const appendix of appendices) for (const id of asArray(appendix.dashboard_ids).map(clean)) appendixByDashboardId.set(id, appendix);
85
92
 
86
93
  const planModel = validateSemanticPlan();
94
+ const semanticPlanState = buildSemanticPlanState();
87
95
  const scratch = fs.mkdtempSync(path.join(os.tmpdir(), 'project-semantic-knowledge-wiki-'));
88
96
  const buildIr = path.join(scratch, 'build-ir');
89
97
 
@@ -92,6 +100,7 @@ try {
92
100
  const generatorArgs = [generator, '--asset-package', packageRoot, '--domain-plan', semanticPlanPath,
93
101
  '--output', buildIr, '--project-name', projectName];
94
102
  if (allowAllVisible) generatorArgs.push('--allow-all-visible');
103
+ if (allowSemanticPlanSnapshotDrift) generatorArgs.push('--allow-semantic-plan-snapshot-drift');
95
104
  execFileSync(process.execPath, generatorArgs, { stdio: 'pipe' });
96
105
 
97
106
  const corpus = readJson(path.join(buildIr, 'corpus.json'));
@@ -105,8 +114,11 @@ try {
105
114
  const wikiRoot = path.join(staging, 'wiki');
106
115
  for (const directory of [
107
116
  'domains', 'technical-appendices', 'source-containers', 'dashboards', 'reports', 'metrics',
108
- 'metadata', 'recall-cards', 'governance',
117
+ 'metadata', 'recall-cards', 'personal-semantics', 'governance', 'common', 'event-map', 'asset-index',
109
118
  ]) fs.mkdirSync(path.join(wikiRoot, directory), { recursive: true });
119
+ for (const type of ['background', 'preference', 'experience', 'asset-context']) {
120
+ fs.mkdirSync(path.join(wikiRoot, 'personal-semantics', type), { recursive: true });
121
+ }
110
122
 
111
123
  for (const metadataType of fs.readdirSync(path.join(buildIr, 'metadata'), { withFileTypes: true })
112
124
  .filter((entry) => entry.isDirectory()).map((entry) => entry.name)) {
@@ -117,10 +129,11 @@ try {
117
129
  const id = clean(domain.domain_id);
118
130
  const source = readText(path.join(buildIr, 'domains', fileName('domain', id)));
119
131
  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');
132
+ const cardLinks = cards.map((card) => `- [${escapeLink(card.intent || card.card_id)}](../recall-cards/${fileName('recall-card', card.card_id)})`).join('\n');
121
133
  writeText(path.join(wikiRoot, 'domains', fileName('domain', id)),
122
134
  replaceFrontmatter(source, domainFrontmatter(domain))
123
- + `\n## 场景召回卡\n\n${cardLinks || '- 无'}\n`);
135
+ + `\n## 场景召回卡\n\n${cardLinks || '- 无'}\n`
136
+ + renderTargetedKnowledge('domain', id, '../personal-semantics/'));
124
137
  }
125
138
 
126
139
  for (const appendix of appendices) {
@@ -180,7 +193,7 @@ try {
180
193
  }
181
194
 
182
195
  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'));
196
+ writeText(path.join(wikiRoot, 'reports', 'index.md'), renderReportIndex());
184
197
  copyIndex(path.join(buildIr, 'metrics', 'index.md'), path.join(wikiRoot, 'metrics', 'index.md'));
185
198
  copyIndex(path.join(buildIr, 'metadata', 'index.md'), path.join(wikiRoot, 'metadata', 'index.md'));
186
199
 
@@ -189,18 +202,36 @@ try {
189
202
  }
190
203
  writeText(path.join(wikiRoot, 'recall-cards', 'index.md'), renderRecallCardIndex(planModel.cards));
191
204
 
205
+ writeText(path.join(wikiRoot, 'common', 'index.md'), renderCommonIndex());
206
+ writeText(path.join(wikiRoot, 'asset-index', 'index.md'), renderAssetIndex());
207
+ writeEventMap(wikiRoot, buildIr);
208
+
209
+ for (const integration of planModel.personalSemanticIntegrations) {
210
+ const contextPath = personalSemanticContextPath(integration.context_type);
211
+ writeText(path.join(wikiRoot, 'personal-semantics', contextPath,
212
+ fileName('personal-semantic', integration.integration_id)), renderPersonalSemanticIntegration(integration));
213
+ }
214
+ writeText(path.join(wikiRoot, 'personal-semantics', 'index.md'),
215
+ renderPersonalSemanticIndex(planModel.personalSemanticIntegrations));
216
+ for (const type of ['background', 'preference', 'experience', 'asset_context']) {
217
+ writeText(path.join(wikiRoot, 'personal-semantics', personalSemanticContextPath(type), 'index.md'),
218
+ renderPersonalSemanticTypeIndex(type, planModel.personalSemanticIntegrations));
219
+ }
220
+
192
221
  const coverage = readText(path.join(buildIr, 'indexes', 'coverage.md'))
193
222
  .replaceAll('../appendices/', '../technical-appendices/')
194
223
  .replaceAll('../briefs/reports/', '../reports/')
195
224
  .replaceAll('../briefs/metadata/', '../metadata/');
196
225
  writeText(path.join(wikiRoot, 'governance', 'coverage.md'), coverage);
197
226
  writeText(path.join(wikiRoot, 'governance', 'authority-index.md'), renderAuthorityIndex(authorityRows));
227
+ writeText(path.join(wikiRoot, 'governance', 'knowledge-adoption.md'), renderKnowledgeAdoptionIndex());
198
228
  writeText(path.join(wikiRoot, 'governance', 'index.md'), renderGovernanceIndex(conflictReportIds.size));
199
229
  writeText(path.join(wikiRoot, 'governance', 'source-project-boundary.md'), renderSourceProjectBoundary(sourceProjectReferencesFromPackage()));
200
230
  writeText(path.join(wikiRoot, 'project-overview.md'), renderProjectOverview(corpus));
201
231
  writeText(path.join(wikiRoot, 'index.md'), renderWikiIndex(corpus));
202
232
  writeText(path.join(wikiRoot, 'log.md'), renderLog(corpus));
203
233
  writeText(path.join(staging, 'index.md'), renderSnapshotIndex(corpus));
234
+ writeText(path.join(staging, 'refresh-state.md'), renderRefreshState(semanticPlanState));
204
235
 
205
236
  const sourceProjectRefs = sourceProjectReferencesFromPackage();
206
237
  const sourceProjectIds = [...new Set(sourceProjectRefs.map((row) => row.source_project_id))].sort((a, b) => a - b);
@@ -215,12 +246,18 @@ try {
215
246
  source_project_id_status: sourceProjectRefs.length ? 'detected_in_packaged_definitions' : 'not_exposed',
216
247
  project_name: projectName,
217
248
  source_snapshot_hash: snapshotHash,
218
- semantic_plan_hash: sha256(fs.readFileSync(semanticPlanPath)),
249
+ semantic_plan_hash: semanticPlanState.plan_hash,
250
+ semantic_plan_state_schema_version: semanticPlanState.schema_version,
251
+ semantic_plan_fragment_count: semanticPlanState.fragments.length,
219
252
  asset_package_schema_version: '3.0',
220
253
  asset_scope: packageAssetScope,
221
- wiki_schema_version: '1.0',
254
+ wiki_schema_version: '2.0',
255
+ navigation_taxonomy_version: '2.1',
222
256
  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 },
257
+ counts: { ...corpus.counts, recall_cards: planModel.cards.length,
258
+ adopted_personal_semantics: adoptedPersonalSemantics.length,
259
+ personal_semantic_integrations: planModel.personalSemanticIntegrations.length,
260
+ blocked_assets: blockedCount, unknown_sql_reports: unknownSqlCount },
224
261
  quality_warnings: [...asArray(corpus.warnings),
225
262
  ...(sourceProjectRefs.filter((row) => Number(row.source_project_id) !== semanticProjectId).length
226
263
  ? [`${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(', ')}`] : []),
@@ -265,6 +302,8 @@ function validateSemanticPlan() {
265
302
  const cardIds = new Set();
266
303
  const excludedByAsset = new Map();
267
304
  const preferredByAsset = new Map();
305
+ const domainIds = new Set(domains.map((domain) => clean(domain.domain_id)).filter(Boolean));
306
+ const projectBusinessModel = validateProjectBusinessModel(semanticPlan.project_business_model, domainIds);
268
307
  for (const domain of domains) {
269
308
  const domainId = clean(domain.domain_id);
270
309
  const domainDashboardIds = new Set(asArray(domain.dashboard_ids).map(clean));
@@ -272,6 +311,7 @@ function validateSemanticPlan() {
272
311
  .some((ref) => domainDashboardIds.has(clean(ref.dashboard_id)))).map((report) => clean(report.resource_key)));
273
312
  const domainMetricIds = new Set(reports.filter((report) => domainReportIds.has(clean(report.resource_key)))
274
313
  .flatMap((report) => asArray(report.metric_occurrences).map((item) => clean(item.metric_key)).filter(Boolean)));
314
+ validateDomainJudgmentModel(domain.domain_judgment_model, domainId, domainDashboardIds, domainReportIds, domainMetricIds);
275
315
  const recallCards = asArray(domain.recall_cards);
276
316
  if (!recallCards.length) fail(`Domain ${domainId || '<unknown>'} must contain at least one recall card.`);
277
317
  for (const value of recallCards) {
@@ -293,6 +333,25 @@ function validateSemanticPlan() {
293
333
  if (!inDomain) fail(`Recall card ${cardId} references ${type}:${key} outside domain ${domainId}.`);
294
334
  if (preferred.includes(ref)) addMulti(preferredByAsset, `${type}:${key}`, cardId);
295
335
  }
336
+ const routing = asObject(card.routing);
337
+ if (card.routing != null) {
338
+ for (const field of ['applicable_when', 'answer_grain', 'stop_when', 'fallback_when_unavailable', 'evidence_boundary']) {
339
+ if (!clean(routing[field])) fail(`Recall card ${cardId} routing requires ${field}.`);
340
+ }
341
+ if (!asArray(routing.branches).length) fail(`Recall card ${cardId} routing requires branches.`);
342
+ for (const branch of asArray(routing.branches)) {
343
+ if (!clean(branch.when) || !asArray(branch.asset_refs).length) {
344
+ fail(`Recall card ${cardId} routing branch requires when and asset_refs.`);
345
+ }
346
+ for (const ref of asArray(branch.asset_refs)) {
347
+ validateAssetRef(ref, cardId);
348
+ if (![...preferred, ...asArray(card.fallback_asset_refs)].some((allowed) =>
349
+ clean(allowed.resource_type) === clean(ref.resource_type) && clean(allowed.resource_key) === clean(ref.resource_key))) {
350
+ fail(`Recall card ${cardId} routing branch references an asset outside preferred/fallback refs.`);
351
+ }
352
+ }
353
+ }
354
+ }
296
355
  for (const ref of asArray(card.excluded_asset_refs)) {
297
356
  validateAssetRef(ref, cardId);
298
357
  if (!clean(ref.reason)) fail(`Recall card ${cardId} excluded asset ${clean(ref.resource_key)} is missing a reason.`);
@@ -333,8 +392,208 @@ function validateSemanticPlan() {
333
392
  if (missingSql.length && !allowAllVisible) {
334
393
  fail(`Semantic plan is missing SQL semantics for ${missingSql.length} report(s): ${missingSql.join(', ')}`);
335
394
  }
395
+ const personalIntegrations = validatePersonalSemanticIntegrations(domainIds, cardIds);
396
+
397
+ return {
398
+ cards,
399
+ personalSemanticIntegrations: personalIntegrations,
400
+ excludedByAsset,
401
+ preferredByAsset,
402
+ sqlByReportId,
403
+ missingSqlByReportId: new Set(missingSql),
404
+ projectBusinessModel,
405
+ };
406
+ }
407
+
408
+ function validatePersonalSemanticIntegrations(domainIds, cardIds) {
409
+ const integrationIds = new Set();
410
+ const referencedSourceIds = new Set();
411
+ const integrations = [];
412
+ for (const [index, value] of personalSemanticIntegrations.entries()) {
413
+ const row = asObject(value);
414
+ const integrationId = clean(row.integration_id);
415
+ if (!/^[a-z0-9][a-z0-9_-]{1,79}$/.test(integrationId) || integrationIds.has(integrationId)) {
416
+ fail(`Invalid or duplicate personal semantic integration ID: ${integrationId || `<index ${index}>`}.`);
417
+ }
418
+ integrationIds.add(integrationId);
419
+ const contextType = normalizePersonalSemanticContextType(row.context_type);
420
+ const sourceIds = asArray(row.source_ids).map(clean).filter(Boolean);
421
+ if (!sourceIds.length) fail(`Personal semantic integration ${integrationId} requires source_ids.`);
422
+ for (const sourceId of sourceIds) {
423
+ const source = adoptedPersonalBySourceId.get(sourceId);
424
+ if (!source) fail(`Personal semantic integration ${integrationId} references unknown source ${sourceId}.`);
425
+ if (normalizePersonalSemanticContextType(source.context_type) !== contextType) {
426
+ fail(`Personal semantic integration ${integrationId} mixes context_type ${contextType} with source ${sourceId}:${source.context_type}.`);
427
+ }
428
+ referencedSourceIds.add(sourceId);
429
+ }
430
+ const title = clean(row.title);
431
+ const projectUsage = clean(row.project_usage);
432
+ const boundaries = asArray(row.boundaries).map(clean).filter(Boolean);
433
+ const conflictPolicy = clean(row.conflict_policy);
434
+ if (!title || !projectUsage) fail(`Personal semantic integration ${integrationId} requires title and project_usage.`);
435
+ if (!['compatible', 'scoped', 'blocked'].includes(conflictPolicy)) {
436
+ fail(`Personal semantic integration ${integrationId} has invalid conflict_policy.`);
437
+ }
438
+ if (!boundaries.length) fail(`Personal semantic integration ${integrationId} requires boundaries.`);
439
+ const target = validatePersonalSemanticIntegrationTarget(row.integration_target, integrationId, domainIds, cardIds);
440
+ integrations.push({ ...row, integration_id: integrationId, context_type: contextType,
441
+ source_ids: sourceIds, title, project_usage: projectUsage, boundaries, conflict_policy: conflictPolicy,
442
+ integration_target: target });
443
+ }
444
+ const missing = adoptedPersonalSemantics
445
+ .map((source) => clean(source.source_id))
446
+ .filter((sourceId) => sourceId && !referencedSourceIds.has(sourceId));
447
+ if (missing.length) {
448
+ fail(`Semantic plan is missing adopted personal semantic source(s): ${missing.join(', ')}`);
449
+ }
450
+ return integrations;
451
+ }
452
+
453
+ function validatePersonalSemanticIntegrationTarget(value, integrationId, domainIds, cardIds) {
454
+ const target = asObject(value);
455
+ const type = clean(target.target_type);
456
+ const id = clean(target.target_id);
457
+ if (!['project', 'domain', 'asset', 'recall_card'].includes(type)) {
458
+ fail(`Personal semantic integration ${integrationId} has invalid integration_target.target_type.`);
459
+ }
460
+ if (type === 'project') return { target_type: type, target_id: id || 'project' };
461
+ if (!id) fail(`Personal semantic integration ${integrationId} target ${type} requires target_id.`);
462
+ if (type === 'domain' && !domainIds.has(id)) fail(`Personal semantic integration ${integrationId} references unknown domain ${id}.`);
463
+ if (type === 'recall_card' && !cardIds.has(id)) fail(`Personal semantic integration ${integrationId} references unknown recall card ${id}.`);
464
+ if (type === 'asset') {
465
+ const [assetType, assetKey] = id.includes(':') ? id.split(/:(.*)/s).filter(Boolean) : ['', id];
466
+ const exists = assetType === 'dashboard' ? dashboardById.has(assetKey)
467
+ : assetType === 'report' ? reportById.has(assetKey)
468
+ : assetType === 'metric' ? metricById.has(assetKey)
469
+ : dashboardById.has(assetKey) || reportById.has(assetKey) || metricById.has(assetKey);
470
+ if (!exists) fail(`Personal semantic integration ${integrationId} references unknown asset target ${id}.`);
471
+ }
472
+ return { target_type: type, target_id: id };
473
+ }
474
+
475
+ function validateProjectBusinessModel(value, domainIds) {
476
+ const model = asObject(value);
477
+ const businessPositioning = clean(model.business_positioning);
478
+ const audienceRoles = asArray(model.audience_roles).map(clean).filter(Boolean);
479
+ const businessObjects = asArray(model.business_objects).map((item, index) => {
480
+ const row = asObject(item);
481
+ const objectId = clean(row.object_id);
482
+ const name = clean(row.name);
483
+ const meaning = clean(row.meaning);
484
+ if (!/^[a-z0-9][a-z0-9_-]{1,79}$/.test(objectId)) fail(`project_business_model.business_objects[${index}] has invalid object_id.`);
485
+ if (!name || !meaning) fail(`project_business_model.business_objects[${index}] requires name and meaning.`);
486
+ const evidenceRefs = normalizePlanAssetRefs(row.evidence_refs, `business object ${objectId}`);
487
+ if (!evidenceRefs.length) fail(`business object ${objectId} requires evidence_refs.`);
488
+ return { object_id: objectId, name, meaning, evidence_refs: evidenceRefs };
489
+ });
490
+ const objectNames = new Set(businessObjects.map((item) => item.name));
491
+ const objectRelationships = asArray(model.object_relationships).map((item, index) => {
492
+ const row = asObject(item);
493
+ const fromObject = clean(row.from_object);
494
+ const toObject = clean(row.to_object);
495
+ const relationship = clean(row.relationship);
496
+ if (!fromObject || !toObject || !relationship) {
497
+ fail(`project_business_model.object_relationships[${index}] requires from_object, to_object and relationship.`);
498
+ }
499
+ if (!objectNames.has(fromObject) || !objectNames.has(toObject)) {
500
+ fail(`object relationship ${fromObject || '<empty>'}->${toObject || '<empty>'} must reference declared business object names.`);
501
+ }
502
+ const evidenceRefs = normalizePlanAssetRefs(row.evidence_refs, `object relationship ${fromObject}->${toObject}`);
503
+ if (!evidenceRefs.length) fail(`object relationship ${fromObject}->${toObject} requires evidence_refs.`);
504
+ return { from_object: fromObject, to_object: toObject, relationship, evidence_refs: evidenceRefs };
505
+ });
506
+ const decisionChains = asArray(model.decision_chains).map((item, index) => {
507
+ const row = asObject(item);
508
+ const chainId = clean(row.chain_id);
509
+ const title = clean(row.title);
510
+ const decisionQuestion = clean(row.decision_question);
511
+ const signals = asArray(row.signals).map(clean).filter(Boolean);
512
+ const decisionSteps = asArray(row.decision_steps).map(clean).filter(Boolean);
513
+ const preferredDomainIds = asArray(row.preferred_domain_ids).map(clean).filter(Boolean);
514
+ const assetRefs = normalizePlanAssetRefs(row.asset_refs, `decision chain ${chainId || index + 1}`);
515
+ const boundaries = asArray(row.boundaries).map(clean).filter(Boolean);
516
+ if (!/^[a-z0-9][a-z0-9_-]{1,79}$/.test(chainId)) fail(`project_business_model.decision_chains[${index}] has invalid chain_id.`);
517
+ if (!title || !decisionQuestion) fail(`decision chain ${chainId} requires title and decision_question.`);
518
+ if (signals.length < 2) fail(`decision chain ${chainId} requires at least two signals.`);
519
+ if (decisionSteps.length < 2) fail(`decision chain ${chainId} requires at least two decision_steps.`);
520
+ if (!preferredDomainIds.length || preferredDomainIds.some((id) => !domainIds.has(id))) {
521
+ fail(`decision chain ${chainId} must reference existing preferred_domain_ids.`);
522
+ }
523
+ if (!assetRefs.length) fail(`decision chain ${chainId} requires asset_refs.`);
524
+ if (!boundaries.length) fail(`decision chain ${chainId} requires boundaries.`);
525
+ return {
526
+ chain_id: chainId,
527
+ title,
528
+ decision_question: decisionQuestion,
529
+ signals,
530
+ decision_steps: decisionSteps,
531
+ preferred_domain_ids: preferredDomainIds,
532
+ asset_refs: assetRefs,
533
+ requires_live_execution: Boolean(row.requires_live_execution),
534
+ boundaries,
535
+ };
536
+ });
537
+ const nonGoals = asArray(model.non_goals).map(clean).filter(Boolean);
538
+ if (!businessPositioning) fail('project_business_model requires business_positioning.');
539
+ if (!audienceRoles.length) fail('project_business_model requires audience_roles.');
540
+ if (!businessObjects.length) fail('project_business_model requires business_objects.');
541
+ if (!objectRelationships.length) fail('project_business_model requires object_relationships.');
542
+ if (!decisionChains.length) fail('project_business_model requires decision_chains.');
543
+ if (!nonGoals.length) fail('project_business_model requires non_goals.');
544
+ return {
545
+ business_positioning: businessPositioning,
546
+ audience_roles: audienceRoles,
547
+ business_objects: businessObjects,
548
+ object_relationships: objectRelationships,
549
+ decision_chains: decisionChains,
550
+ non_goals: nonGoals,
551
+ };
552
+ }
553
+
554
+ function validateDomainJudgmentModel(value, domainId, domainDashboardIds, domainReportIds, domainMetricIds) {
555
+ const model = asObject(value);
556
+ const businessStateJudged = clean(model.business_state_judged);
557
+ const mainObjects = asArray(model.main_objects).map(clean).filter(Boolean);
558
+ const signals = asArray(model.signals).map(clean).filter(Boolean);
559
+ const decisionPath = asArray(model.decision_path).map(clean).filter(Boolean);
560
+ const boundaries = asArray(model.boundaries).map(clean).filter(Boolean);
561
+ const attachmentLogic = asArray(model.asset_attachment_logic);
562
+ if (!businessStateJudged) fail(`Domain ${domainId} is missing domain_judgment_model.business_state_judged.`);
563
+ if (!mainObjects.length) fail(`Domain ${domainId} is missing domain_judgment_model.main_objects.`);
564
+ if (signals.length < 2) fail(`Domain ${domainId} domain_judgment_model requires at least two signals.`);
565
+ if (decisionPath.length < 2) fail(`Domain ${domainId} domain_judgment_model requires at least two decision_path steps.`);
566
+ if (!attachmentLogic.length) fail(`Domain ${domainId} domain_judgment_model requires asset_attachment_logic.`);
567
+ if (!boundaries.length) fail(`Domain ${domainId} domain_judgment_model requires boundaries.`);
568
+ for (const [index, item] of attachmentLogic.entries()) {
569
+ const row = asObject(item);
570
+ const ref = normalizePlanAssetRef(row.asset_ref, `domain ${domainId} asset_attachment_logic[${index}]`);
571
+ const role = clean(row.role);
572
+ const reason = clean(row.reason);
573
+ if (!['primary_entry', 'supporting_evidence', 'drilldown', 'exclusion'].includes(role)) {
574
+ fail(`domain ${domainId} asset_attachment_logic[${index}] has invalid role.`);
575
+ }
576
+ if (!reason || reason.length < 10) fail(`domain ${domainId} asset_attachment_logic[${index}] requires concrete reason.`);
577
+ const inDomain = ref.resource_type === 'dashboard' ? domainDashboardIds.has(ref.resource_key)
578
+ : ref.resource_type === 'report' ? domainReportIds.has(ref.resource_key)
579
+ : ref.resource_type === 'metric' ? domainMetricIds.has(ref.resource_key) : false;
580
+ if (!inDomain) fail(`domain ${domainId} asset_attachment_logic references ${ref.resource_type}:${ref.resource_key} outside domain closure.`);
581
+ }
582
+ }
336
583
 
337
- return { cards, excludedByAsset, preferredByAsset, sqlByReportId, missingSqlByReportId: new Set(missingSql) };
584
+ function normalizePlanAssetRefs(values, label) {
585
+ return asArray(values).map((value, index) => normalizePlanAssetRef(value, `${label} asset_refs[${index}]`));
586
+ }
587
+
588
+ function normalizePlanAssetRef(value, label) {
589
+ const ref = asObject(value);
590
+ const type = clean(ref.resource_type);
591
+ const key = clean(ref.resource_key);
592
+ const exists = type === 'dashboard' ? dashboardById.has(key)
593
+ : type === 'report' ? reportById.has(key)
594
+ : type === 'metric' ? metricById.has(key) : false;
595
+ if (!exists) fail(`${label} references unknown asset ${type || '<type>'}:${key || '<key>'}.`);
596
+ return { resource_type: type, resource_key: key };
338
597
  }
339
598
 
340
599
  function assertAgentAuthoredRecallCard(card, domainId) {
@@ -436,8 +695,36 @@ function writeMergedAsset(wikiRoot, directory, asset, authority, extra = '') {
436
695
  const brief = readText(path.join(buildIr, 'briefs', directory, fileName(directory.slice(0, -1), id)));
437
696
  const type = directory === 'dashboards' ? 'project-dashboard' : directory === 'reports' ? 'project-report' : 'project-reusable-metric';
438
697
  const title = assetReadableTitle(asset, directory.slice(0, -1));
698
+ const authorityNotice = renderAssetAuthorityNotice(authority);
699
+ const mergedExtra = [authorityNotice, extra.trim(), renderTargetedKnowledge('asset', `${directory.slice(0, -1)}:${id}`, '../personal-semantics/')].filter(Boolean).join('\n\n');
439
700
  writeText(path.join(wikiRoot, directory, assetOutputFileName(directory, id)),
440
- ensureReadableHeading(mergePage(source, brief, authorityFrontmatter(type, id, title, authority), extra), title));
701
+ ensureReadableHeading(mergePage(source, brief, authorityFrontmatter(type, id, title, authority), mergedExtra), title));
702
+ }
703
+
704
+ function renderAssetAuthorityNotice(authority) {
705
+ const runtimePolicy = clean(authority.runtime_policy);
706
+ const definitionState = clean(authority.definition_state);
707
+ const authorityRole = clean(authority.authority_role);
708
+ const canonical = clean(authority.canonical_asset_id);
709
+ const lines = [];
710
+ if (runtimePolicy === 'block') {
711
+ lines.push('- 召回限制:该资产不能作为直接回答入口。');
712
+ } else if (runtimePolicy === 'warn') {
713
+ lines.push('- 召回限制:该资产只能作为辅助线索,回答时需要说明其依赖或待确认边界。');
714
+ }
715
+ if (authorityRole === 'dependency_only') {
716
+ lines.push('- 权威边界:该资产来自依赖闭包,只能支撑承载看板或业务域,不能单独代表正式业务入口。');
717
+ }
718
+ if (definitionState === 'conflict') {
719
+ lines.push('- 口径状态:存在同名或近义资产口径冲突,应优先回到业务域、召回卡或让用户确认。');
720
+ } else if (definitionState === 'unknown') {
721
+ lines.push('- 口径状态:定义证据不足,只能作为待核验线索。');
722
+ }
723
+ if (canonical) {
724
+ lines.push(`- 推荐入口:优先使用 \`${canonical}\`,本页只保留来源和排除理由。`);
725
+ }
726
+ if (!lines.length) return '';
727
+ return `## 召回与权威边界\n\n${lines.join('\n')}`;
441
728
  }
442
729
 
443
730
  function mergePage(source, brief, frontmatter, extra = '') {
@@ -465,6 +752,12 @@ ${asArray(values).map((ref) => {
465
752
  const linkPath = assetPath(clean(ref.resource_type), clean(ref.resource_key));
466
753
  return `- [${escapeLink(recallAssetLabel(ref))}](${linkPath}):${clean(ref.reason)}`;
467
754
  }).join('\n') || '- 无'}`;
755
+ const routing = asObject(card.routing);
756
+ const branches = asArray(routing.branches).map((branch) => {
757
+ const item = asObject(branch);
758
+ const targets = asArray(item.asset_refs).map(recallAssetLabel).join('、') || '回到业务域核对资产';
759
+ return `- 当${clean(item.when)}:使用 ${targets}。${clean(item.reason)}`;
760
+ });
468
761
  return `---
469
762
  type: project-semantic-recall-card
470
763
  card_id: ${yaml(card.card_id)}
@@ -492,12 +785,15 @@ ${alternateQuestions.map((value) => `- ${value}`).join('\n') || '- 同页面标
492
785
 
493
786
  ${clean(card.intent)}
494
787
 
495
- ## Agent 执行顺序
788
+ ## 条件路由与停止
496
789
 
497
- 1. 先进入首选资产确认业务范围、指标和维度。
498
- 2. 首选资产不足以回答时,再使用备选资产补充证据。
499
- 3. 排除资产只用于解释为什么不采用,不能作为当前问题的主要答案来源。
500
- 4. 需要当前数值、客户名单、趋势或明细时,必须实时执行报表/查询。
790
+ - 适用条件:${clean(routing.applicable_when) || clean(card.intent)}
791
+ - 答案对象与粒度:${clean(routing.answer_grain) || '先按用户问法确认对象、统计粒度与时间范围。'}
792
+ - 必要参数:${asArray(routing.required_parameters).map(clean).filter(Boolean).join('、') || '按首选资产的输入定义核对。'}
793
+ ${branches.join('\n') || `- 问法和资产定义匹配时使用首选资产;不匹配时先回到业务域确认对象与口径,再考虑备选资产。`}
794
+ - 停止条件:${clean(routing.stop_when) || '取得对象、粒度、范围、时间和权威状态均匹配的最小证据后停止扩搜。'}
795
+ - 无匹配或不可用时:${clean(routing.fallback_when_unavailable) || '回到业务域按对象、粒度、过滤和时间筛选域内资产;仍不匹配则说明缺口。'}
796
+ - 执行边界:${clean(routing.evidence_boundary) || '排除资产不能作为当前答案;当前数值、客户名单、趋势或明细必须实时执行。'}
501
797
 
502
798
  ${refs(card.preferred_asset_refs, '首选资产')}
503
799
 
@@ -509,6 +805,7 @@ ${refs(card.excluded_asset_refs, '排除资产')}
509
805
 
510
806
  - 是否需要执行实时数据:${Boolean(card.requires_live_execution)}
511
807
  - 原因:${clean(card.live_execution_reason) || '未说明'}
808
+ ${renderTargetedKnowledge('recall_card', clean(card.card_id), '../personal-semantics/')}
512
809
  `;
513
810
  }
514
811
 
@@ -525,11 +822,140 @@ function recallAssetLabel(ref) {
525
822
  }
526
823
 
527
824
  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`;
825
+ return `# 场景召回卡\n\n先按问法、对象、粒度和参数选择匹配的卡;卡内按条件分支找到最小证据并在满足停止条件后停止。无匹配卡时,回到[业务领域](../domains/index.md),按对象、粒度、过滤和时间筛选域内资产;仍无可用资产时说明具体缺口。\n\n${cards.map((card) => `- [${escapeLink(card.intent || card.card_id)}](${fileName('recall-card', card.card_id)})(${clean(card.domain_title)})`).join('\n')}\n`;
826
+ }
827
+
828
+ function renderReportIndex() {
829
+ const assigned = new Set();
830
+ const sections = domains.map((domain) => {
831
+ const dashboardIds = new Set(asArray(domain.dashboard_ids).map(clean));
832
+ const matching = reports.filter((report) => asArray(report.dashboard_refs)
833
+ .some((ref) => dashboardIds.has(clean(ref.dashboard_id))))
834
+ .sort((a, b) => clean(a.resource_key).localeCompare(clean(b.resource_key), 'en'));
835
+ for (const report of matching) assigned.add(clean(report.resource_key));
836
+ const questions = asArray(domain.primary_questions).map(clean).filter(Boolean);
837
+ const cards = asArray(domain.recall_cards)
838
+ .map((card) => `- 问题入口:[${escapeLink(clean(card.questions?.[0]) || clean(card.intent))}](../recall-cards/${fileName('recall-card', card.card_id)})`);
839
+ return `## ${clean(domain.title)}\n\n- 业务问题:${questions.join(';') || clean(domain.summary)}\n- [领域判断](../domains/${fileName('domain', domain.domain_id)})\n${cards.join('\n')}\n${matching.map((report) => `- [${escapeLink(assetReadableTitle(report, 'report'))}](${reportFileNames.get(clean(report.resource_key))})(report:${clean(report.resource_key)})`).join('\n') || '- 本快照无该域报表,按领域页核对其他资产。'}\n`;
840
+ });
841
+ const unassigned = reports.filter((report) => !assigned.has(clean(report.resource_key)))
842
+ .sort((a, b) => clean(a.resource_key).localeCompare(clean(b.resource_key), 'en'));
843
+ return `# 报表\n\n先按业务问题选择领域或场景,再核对报表的对象、粒度、过滤和时间。点名报表时可直接进入报表页;未归属报表不自动视为业务入口。\n\n${sections.join('\n')}\n## 其他与未归属报表\n\n${unassigned.map((report) => `- [${escapeLink(assetReadableTitle(report, 'report'))}](${reportFileNames.get(clean(report.resource_key))})(report:${clean(report.resource_key)})`).join('\n') || '- 无'}\n`;
844
+ }
845
+
846
+ function renderPersonalSemanticIntegration(integration) {
847
+ const sourceRows = integration.source_ids.map((sourceId) => adoptedPersonalBySourceId.get(sourceId)).filter(Boolean);
848
+ const provenanceRows = sourceRows.map((source) => {
849
+ const payload = asObject(source.payload);
850
+ return `| \`${clean(source.source_id)}\` | ${escapeTable(personalSemanticOwnerName(payload))} | ${escapeTable(formatPersonalSemanticTime(payload.updated_at ?? source.updated_at))} | ${escapeTable(payload.heat_count)} |`;
851
+ }).join('\n');
852
+ const sourceSummaries = sourceRows.map((source) => {
853
+ const payload = asObject(source.payload);
854
+ return `### ${clean(payload.title) || clean(source.source_id)}\n\n- Source ID: \`${clean(source.source_id)}\`\n- 来源人:${personalSemanticOwnerName(payload)}\n- 更新时间:${formatPersonalSemanticTime(payload.updated_at ?? source.updated_at)}\n- 热度:${clean(payload.heat_count) || '0'}\n- 摘要:${clean(payload.summary) || 'unknown'}\n- 内容:${clean(payload.content) || 'unknown'}\n- 关键词:${asArray(payload.keywords).map(clean).filter(Boolean).join('、') || '无'}\n- 资源引用:${renderPersonalSemanticResourceRefs(payload.resource_refs)}\n`;
855
+ }).join('\n');
856
+ return `---\ntype: project-semantic-personal-integration\nsemantic_project_id: ${semanticProjectId}\nintegration_id: ${yaml(integration.integration_id)}\ncontext_type: ${yaml(integration.context_type)}\nsource_snapshot_hash: ${yaml(snapshotHash)}\ntarget_type: ${yaml(integration.integration_target.target_type)}\ntarget_id: ${yaml(integration.integration_target.target_id)}\nconflict_policy: ${yaml(integration.conflict_policy)}\n---\n\n# ${clean(integration.title)}\n\n## Agent 使用摘要\n\n- 类型:${personalSemanticContextLabel(integration.context_type)}\n- 项目用法:${clean(integration.project_usage)}\n- 作用目标:${renderPersonalSemanticTarget(integration.integration_target)}\n- 冲突策略:${clean(integration.conflict_policy)}\n- 边界:${joinChineseClauses(integration.boundaries)}\n\n## 来源\n\n| Source ID | 来源人 | 更新时间 | 热度 |\n| --- | --- | --- | ---: |\n${provenanceRows || '| - | - | - | - |'}\n\n## 原始个人语义摘要\n\n${sourceSummaries || '无'}\n`;
857
+ }
858
+
859
+ function personalSemanticOwnerName(payload) {
860
+ return clean(payload.owner_name) || clean(payload.owner_display_name) || '未知用户';
861
+ }
862
+
863
+ function renderPersonalSemanticIndex(integrations) {
864
+ const rows = integrations
865
+ .slice()
866
+ .sort((left, right) => `${left.context_type}:${left.integration_id}`.localeCompare(`${right.context_type}:${right.integration_id}`, 'en'));
867
+ return `# 知识采纳记录\n\n已采纳的补充知识按目标写入项目、业务领域、场景或资产页;这里保留来源和适用边界以便追溯。\n\n- [背景](background/index.md)\n- [偏好](preference/index.md)\n- [经验](experience/index.md)\n- [资产上下文](asset-context/index.md)\n\n## 采纳项\n\n${rows.map((row) => `- [${escapeLink(row.title)}](${personalSemanticContextPath(row.context_type)}/${fileName('personal-semantic', row.integration_id)}):${personalSemanticContextLabel(row.context_type)},${clean(row.project_usage)}`).join('\n') || '- 无已采纳补充知识。'}\n`;
868
+ }
869
+
870
+ function renderTargetedKnowledge(type, id, relativeRoot) {
871
+ const rows = planModel.personalSemanticIntegrations.filter((integration) => {
872
+ const target = integration.integration_target;
873
+ if (target.target_type !== type) return false;
874
+ if (type !== 'asset') return target.target_id === id;
875
+ return target.target_id === id || target.target_id === id.split(':')[1];
876
+ });
877
+ if (!rows.length) return '';
878
+ return `\n## 项目补充知识\n\n${rows.map((row) => `- [${escapeLink(row.title)}](${relativeRoot}${personalSemanticContextPath(row.context_type)}/${fileName('personal-semantic', row.integration_id)}):${clean(row.project_usage)};适用边界:${joinChineseClauses(row.boundaries)}`).join('\n')}\n`;
879
+ }
880
+
881
+ function renderKnowledgeAdoptionIndex() {
882
+ return `# 知识采纳记录\n\n[查看已采纳补充知识及来源](../personal-semantics/index.md)。内容按适用目标进入项目、领域、场景和资产页;个人偏好只在声明的范围内生效。\n`;
883
+ }
884
+
885
+ function renderCommonIndex() {
886
+ const model = planModel.projectBusinessModel;
887
+ const objects = asArray(model.business_objects).map((row) => `- **${clean(row.name)}**:${clean(row.meaning)}`).join('\n');
888
+ const relationships = asArray(model.object_relationships).map((row) => `- ${clean(row.from_object)} → ${clean(row.to_object)}:${clean(row.relationship)}`).join('\n');
889
+ const supplements = planModel.personalSemanticIntegrations.filter((row) => row.integration_target.target_type === 'project')
890
+ .map((row) => `- [${escapeLink(row.title)}](../personal-semantics/${personalSemanticContextPath(row.context_type)}/${fileName('personal-semantic', row.integration_id)}):${clean(row.project_usage)};适用边界:${joinChineseClauses(row.boundaries)}`).join('\n');
891
+ return `# 项目共识\n\n这里收录跨业务领域适用的项目对象、关系与已采纳补充知识。领域特有的判断方法保留在对应领域页。\n\n## 共同业务对象\n\n${objects || '- 无。'}\n\n## 对象关系\n\n${relationships || '- 无。'}\n\n## 跨领域补充知识\n\n${supplements || '- 暂无。'}\n\n[查看完整项目判断链路](../project-overview.md#业务判断链路)。\n`;
892
+ }
893
+
894
+ function renderAssetIndex() {
895
+ return `# 事件与资产目录\n\n事件公共分组帮助按产品模块找到事件;分组名称本身不定义业务领域或认证状态。业务含义以领域判断和事件定义为准。\n\n- [事件地图](../event-map/index.md)\n- [看板](../dashboards/index.md)\n- [报表](../reports/index.md)\n- [可复用指标](../metrics/index.md)\n- [分析元数据](../metadata/index.md)\n`;
896
+ }
897
+
898
+ function writeEventMap(wikiRoot, buildIrRoot) {
899
+ const eventPages = new Map();
900
+ const eventsDir = path.join(buildIrRoot, 'metadata', 'events');
901
+ if (fs.existsSync(eventsDir)) for (const name of fs.readdirSync(eventsDir).filter((item) => item.endsWith('.md'))) {
902
+ const content = readText(path.join(eventsDir, name));
903
+ const key = frontmatterValue(content, 'resource_key');
904
+ if (key) eventPages.set(key, name);
905
+ }
906
+ const groups = eventGroups.filter((group) => clean(group.catalog_scope) === 'project_public');
907
+ const seen = new Set();
908
+ const links = [];
909
+ for (const group of groups) {
910
+ const name = clean(group.group_name);
911
+ if (!name || seen.has(name)) fail(`Duplicate or empty public event group: ${name || '<empty>'}.`);
912
+ seen.add(name);
913
+ const members = asArray(group.events).filter((event) => eventPages.has(clean(event.event_name)));
914
+ if (!members.length) continue;
915
+ const file = fileName('event-group', `group-${name}-${sha256(name).slice(0, 8)}`);
916
+ links.push(`- [${escapeLink(name)}](${file})(${members.length} 个已导出事件)`);
917
+ writeText(path.join(wikiRoot, 'event-map', file), `# ${name}\n\n来源:项目公共事件目录。此分组仅用于查找事件,不自动定义业务领域或事件认证状态。\n\n${members.map((event) => `- [${escapeLink(clean(event.event_title) || clean(event.event_name))}(\`${clean(event.event_name)}\`)](../metadata/events/${eventPages.get(clean(event.event_name))})`).join('\n')}\n`);
918
+ }
919
+ writeText(path.join(wikiRoot, 'event-map', 'index.md'), `# 事件地图\n\n按项目公共事件分组查找已纳入资产包的事件。未纳入当前资产范围的事件不会在这里出现;事件分组不等于业务领域。\n\n${links.join('\n') || '- 当前资产范围内没有可展示的公共事件分组。'}\n\n[按元数据类型查找事件](../metadata/index.md)。\n`);
920
+ }
921
+
922
+ function renderPersonalSemanticTypeIndex(type, integrations) {
923
+ const normalized = normalizePersonalSemanticContextType(type);
924
+ const rows = integrations.filter((row) => normalizePersonalSemanticContextType(row.context_type) === normalized)
925
+ .sort((left, right) => left.integration_id.localeCompare(right.integration_id, 'en'));
926
+ return `# ${personalSemanticContextLabel(normalized)}\n\n${personalSemanticContextUsage(normalized)}\n\n${rows.map((row) => `- [${escapeLink(row.title)}](${fileName('personal-semantic', row.integration_id)}):${clean(row.project_usage)}`).join('\n') || '- 暂无。'}\n`;
927
+ }
928
+
929
+ function renderPersonalSemanticTarget(target) {
930
+ const type = clean(target.target_type);
931
+ const id = clean(target.target_id);
932
+ if (type === 'domain') return `业务域 \`${id}\``;
933
+ if (type === 'recall_card') return `召回卡 \`${id}\``;
934
+ if (type === 'asset') return `资产 \`${id}\``;
935
+ return '项目整体';
936
+ }
937
+
938
+ function renderPersonalSemanticResourceRefs(value) {
939
+ const refs = asArray(value).map(asObject).map((ref) => {
940
+ const type = clean(ref.resource_type);
941
+ const key = clean(ref.resource_key);
942
+ return type && key ? `${type}:${key}` : clean(ref);
943
+ }).filter(Boolean);
944
+ return refs.join('、') || '无';
945
+ }
946
+
947
+ function formatPersonalSemanticTime(value) {
948
+ const text = clean(value);
949
+ if (!text) return 'unknown';
950
+ if (/^\d{12,}$/.test(text)) {
951
+ const date = new Date(Number(text));
952
+ if (!Number.isNaN(date.getTime())) return date.toISOString().replace('T', ' ').replace('.000Z', ' UTC');
953
+ }
954
+ return text;
529
955
  }
530
956
 
531
957
  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`;
958
+ 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
959
  }
534
960
 
535
961
  function renderSourceProjectBoundary(rows) {
@@ -551,20 +977,52 @@ ${external.map((row) => `| ${row.resource_type}:${row.resource_key} ${escapeTabl
551
977
  }
552
978
 
553
979
  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`;
980
+ return `# 治理与来源\n\n- [知识采纳记录](knowledge-adoption.md)\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
981
  }
556
982
 
557
983
  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`;
984
+ return `---\ntype: project-semantic-knowledge-wiki-index\nsemantic_project_id: ${semanticProjectId}\nproject_name: ${yaml(projectName)}\nsource_snapshot_hash: ${yaml(snapshotHash)}\nnavigation_taxonomy_version: "2.1"\n---\n\n# ${projectName} 项目语义知识库 Wiki\n\n## 问题意图路由\n\n- 业务判断或当前数值:先选[业务领域](domains/index.md)及其[场景召回卡](recall-cards/index.md),按对象、粒度、参数选择分支并实时执行。\n- 点名资产、字段定义或依赖关系:从[事件与资产目录](asset-index/index.md)定位资产,再核对[治理与来源](governance/index.md);依赖元数据可解释定义与关系,但不能单独证明业务状态。\n- 产品机制或共同背景:先读[项目共识](common/index.md),再进入相关业务域。\n- 无匹配场景卡:进入相关业务域,按对象、粒度、过滤和时间筛选域内资产;仍无合适资产就说明缺口,不编造答案。\n- 已取得对象、粒度、时间、范围和权威性匹配的最小证据后停止扩搜;需要当前值时按权限执行。\n\n## 知识导航\n\n1. [项目总览](project-overview.md):项目定位、对象关系和判断链路。\n2. [项目共识](common/index.md):跨领域适用的知识。\n3. [业务领域](domains/index.md):按业务问题进入判断路径和证据。\n4. [事件与资产目录](asset-index/index.md):按事件公共分组或资产类型查找。\n5. [治理与来源](governance/index.md):采纳、权威状态和边界。\n\n技术、演示、副本和定向诊断资产只在[技术附录](technical-appendices/index.md)中追溯。物理空间在[source containers](source-containers/index.md)中保留,不作为业务域。\n\n${countSummary(corpus)}\n`;
559
985
  }
560
986
 
561
987
  function renderSnapshotIndex(corpus) {
562
988
  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`;
989
+ return `# ${title} 项目语义知识库 Wiki\n\n## 问题意图路由\n\n- 业务判断或当前数值:先选[业务领域](wiki/domains/index.md)和匹配的[场景召回卡](wiki/recall-cards/index.md),按对象、粒度和参数选择分支。\n- 点名资产、字段定义或依赖关系:从[事件与资产目录](wiki/asset-index/index.md)定位,再核对[治理与来源](wiki/governance/index.md)。\n- 产品机制或共同背景:先读[项目共识](wiki/common/index.md)。\n- 无匹配场景卡:回到相关业务域,按对象、粒度、过滤和时间筛选;仍无合适资产则说明缺口。\n- 证据已经匹配对象、粒度、范围、时间和权威状态时停止扩搜;当前数值需要授权实时执行。\n\n## 知识导航\n\n1. [项目总览](wiki/project-overview.md)\n2. [项目共识](wiki/common/index.md)\n3. [业务领域](wiki/domains/index.md)\n4. [事件与资产目录](wiki/asset-index/index.md)\n5. [治理与来源](wiki/governance/index.md)\n\n${countSummary(corpus)}\n`;
564
990
  }
565
991
 
566
992
  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`;
993
+ return `# ${projectName} 项目总览\n\n${renderProjectBusinessModel(planModel.projectBusinessModel)}${renderTargetedKnowledge('project', 'project', 'personal-semantics/')}\n\n## 资产与治理范围\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`;
994
+ }
995
+
996
+ function renderProjectBusinessModel(model) {
997
+ return `## 项目定位\n\n${clean(model.business_positioning)}\n\n## 使用角色\n\n${asArray(model.audience_roles).map((role) => `- ${clean(role)}`).join('\n')}\n\n## 业务对象\n\n${asArray(model.business_objects).map((item) => {
998
+ const evidence = asArray(item.evidence_refs).map(renderOverviewAssetRef).join('、');
999
+ return `- ${clean(item.name)}(\`${clean(item.object_id)}\`):${appendEvidenceSentence(item.meaning, evidence)}`;
1000
+ }).join('\n')}\n\n## 对象关系\n\n${asArray(model.object_relationships).map((item) => {
1001
+ const evidence = asArray(item.evidence_refs).map(renderOverviewAssetRef).join('、');
1002
+ return `- ${clean(item.from_object)} -> ${clean(item.to_object)}:${appendEvidenceSentence(item.relationship, evidence)}`;
1003
+ }).join('\n')}\n\n## 业务判断链路\n\n${asArray(model.decision_chains).map(renderDecisionChain).join('\n\n')}\n\n## 不承载的内容\n\n${asArray(model.non_goals).map((item) => `- ${clean(item)}`).join('\n')}`;
1004
+ }
1005
+
1006
+ function appendEvidenceSentence(value, evidence) {
1007
+ const body = clean(value).replace(/[。;;,,\s]+$/u, '');
1008
+ return `${body}${evidence ? `。证据:${evidence}` : ''}`;
1009
+ }
1010
+
1011
+ function renderDecisionChain(chain) {
1012
+ const domains = asArray(chain.preferred_domain_ids)
1013
+ .map((id) => `[\`${clean(id)}\`](domains/${fileName('domain', id)})`).join('、');
1014
+ const assets = asArray(chain.asset_refs).map(renderOverviewAssetRef).join('、');
1015
+ return `### ${clean(chain.title)}\n\n- 判断问题:${stripClausePunctuation(chain.decision_question)}\n- 判断信号:${joinChineseClauses(chain.signals)}\n- 判断步骤:\n${renderNumberedSteps(chain.decision_steps, ' ')}\n- 业务域:${domains || '未指定'}\n- 关联资产:${assets || '未指定'}\n- 实时执行:${chain.requires_live_execution ? '需要' : '不需要'}\n- 边界:${joinChineseClauses(chain.boundaries)}`;
1016
+ }
1017
+
1018
+ function renderOverviewAssetRef(ref) {
1019
+ const type = clean(ref.resource_type);
1020
+ const key = clean(ref.resource_key);
1021
+ const label = recallAssetLabel(ref);
1022
+ if (type === 'dashboard') return `[${escapeLink(label)}](dashboards/${dashboardFileNames.get(key) ?? fileName('dashboard', key)})`;
1023
+ if (type === 'report') return `[${escapeLink(label)}](reports/${reportFileNames.get(key) ?? fileName('report', key)})`;
1024
+ if (type === 'metric') return `[${escapeLink(label)}](metrics/${metricFileNames.get(key) ?? fileName('metric', key)})`;
1025
+ return `${type}:${key}`;
568
1026
  }
569
1027
 
570
1028
  function renderLog(corpus) {
@@ -572,7 +1030,7 @@ function renderLog(corpus) {
572
1030
  }
573
1031
 
574
1032
  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} |`;
1033
+ return `## 数量\n\n| 内容 | 数量 |\n| --- | ---: |\n| 业务领域 | ${corpus.counts.business_domains} |\n| 公共事件分组 | ${eventGroups.length} |\n| 看板 | ${corpus.counts.dashboards} |\n| 报表 | ${corpus.counts.reports} |\n| 可复用指标 | ${corpus.counts.reusable_metrics} |\n| 分析元数据 | ${corpus.counts.analysis_metadata} |\n| 场景召回卡 | ${planModel.cards.length} |\n| 已采纳补充知识 | ${planModel.personalSemanticIntegrations.length} |`;
576
1034
  }
577
1035
 
578
1036
  function authorityFrontmatter(type, resourceKey, title, authority) {
@@ -599,6 +1057,35 @@ function assetPath(type, key) {
599
1057
  fail(`Unsupported recall-card asset type ${type}.`);
600
1058
  }
601
1059
 
1060
+ function normalizePersonalSemanticContextType(value) {
1061
+ const type = clean(value).toLowerCase().replace('-', '_');
1062
+ if (!['background', 'preference', 'experience', 'asset_context'].includes(type)) {
1063
+ fail(`Invalid adopted personal semantic context_type: ${clean(value) || '<empty>'}.`);
1064
+ }
1065
+ return type;
1066
+ }
1067
+
1068
+ function personalSemanticContextPath(value) {
1069
+ const type = normalizePersonalSemanticContextType(value);
1070
+ return type === 'asset_context' ? 'asset-context' : type;
1071
+ }
1072
+
1073
+ function personalSemanticContextLabel(value) {
1074
+ const type = normalizePersonalSemanticContextType(value);
1075
+ if (type === 'background') return '项目背景';
1076
+ if (type === 'preference') return '偏好';
1077
+ if (type === 'experience') return '经验';
1078
+ return '资产上下文';
1079
+ }
1080
+
1081
+ function personalSemanticContextUsage(value) {
1082
+ const type = normalizePersonalSemanticContextType(value);
1083
+ if (type === 'background') return '用于补充项目、业务域和对象关系的背景信息。';
1084
+ if (type === 'preference') return '用于补充项目内的回答偏好、入口选择和路由方式。';
1085
+ if (type === 'experience') return '用于补充已经验证过的分析路径、风险提示和经验规则。';
1086
+ return '用于补充具体资产的使用语境;引用资产不存在时不能作为当前资产事实。';
1087
+ }
1088
+
602
1089
  function assetOutputFileName(directory, key) {
603
1090
  if (directory === 'dashboards') return dashboardFileNames.get(key) ?? fileName('dashboard', key);
604
1091
  if (directory === 'reports') return reportFileNames.get(key) ?? fileName('report', key);
@@ -744,10 +1231,9 @@ function hideMarkdownFrontmatter(root) {
744
1231
  function assertSnapshotEntryPoint(root) {
745
1232
  const content = readText(path.join(root, 'index.md'));
746
1233
  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',
1234
+ 'wiki/project-overview.md',
1235
+ 'wiki/common/index.md', 'wiki/domains/index.md', 'wiki/asset-index/index.md',
1236
+ 'wiki/governance/index.md',
751
1237
  ];
752
1238
  const missing = requiredLinks.filter((target) => !content.includes(`(${target})`));
753
1239
  if (missing.length) fail(`Snapshot root index is missing required navigation: ${missing.join(', ')}.`);
@@ -876,6 +1362,7 @@ function writeFlatUploadSources(sourceRoot, targetRoot, replace) {
876
1362
  const content = precompiledSource(rewriteLinksForFlatUpload(readText(file), rel, relToFlat));
877
1363
  const contentHash = syncContentHash(content);
878
1364
  const identity = uploadSourceIdentity(rel, content);
1365
+ const semanticPlanFragmentRefs = semanticPlanFragmentRefsForSource(identity, rel);
879
1366
  manifestSources.push({
880
1367
  display_name: flatName,
881
1368
  file: flatName,
@@ -888,8 +1375,9 @@ function writeFlatUploadSources(sourceRoot, targetRoot, replace) {
888
1375
  title: firstHeading(content),
889
1376
  content_hash: contentHash,
890
1377
  source_path: rel,
1378
+ semantic_plan_fragment_refs: semanticPlanFragmentRefs,
891
1379
  });
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}`);
1380
+ 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}\nsemantic_plan_fragment_refs: ${semanticPlanFragmentRefs.join(',') || 'null'}\n-->\n\n${content}`);
893
1381
  }
894
1382
  const manifest = readJson(path.join(sourceRoot, 'manifest.json'));
895
1383
  const manifestContent = precompiledSource(`# Package manifest\n\n\`\`\`json\n${JSON.stringify(manifest, null, 2)}\n\`\`\`\n`);
@@ -907,16 +1395,19 @@ function writeFlatUploadSources(sourceRoot, targetRoot, replace) {
907
1395
  title: 'Package manifest',
908
1396
  content_hash: manifestHash,
909
1397
  source_path: 'manifest.json',
1398
+ semantic_plan_fragment_refs: [],
910
1399
  });
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}`);
1400
+ 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\nsemantic_plan_fragment_refs: null\n-->\n\n${manifestContent}`);
912
1401
  writeJson(path.join(staging, 'kb-upload-manifest.json'), {
913
1402
  schema_version: '1.0',
914
1403
  manifest_kind: 'ae_project_semantic_kb_upload_sources',
1404
+ navigation_taxonomy_version: '2.1',
915
1405
  namespace: uploadSourceNamespace,
916
1406
  semantic_project_id: semanticProjectId,
917
1407
  project_name: projectName,
918
1408
  snapshot_date: snapshotDate,
919
1409
  snapshot_hash: snapshotHash,
1410
+ semantic_plan_state: semanticPlanState,
920
1411
  source_count: manifestSources.length,
921
1412
  sources: manifestSources.sort((a, b) => a.display_name.localeCompare(b.display_name, 'en')),
922
1413
  });
@@ -940,6 +1431,7 @@ function flatUploadFileName(rel, namespace) {
940
1431
  function uploadSourceIdentity(rel, content = '') {
941
1432
  const normalized = rel.replaceAll('\\', '/');
942
1433
  if (normalized === 'index.md') return { source_kind: 'index', asset_type: null, asset_key: null, file_stem: 'index' };
1434
+ if (normalized === 'refresh-state.md') return { source_kind: 'refresh-state', asset_type: null, asset_key: null, file_stem: 'refresh-state' };
943
1435
  if (normalized === 'wiki/index.md') return { source_kind: 'index', asset_type: null, asset_key: null, file_stem: 'wiki-index' };
944
1436
  const match = normalized.match(/^wiki\/([^/]+)\/(.+)\.md$/);
945
1437
  if (!match) {
@@ -954,6 +1446,17 @@ function uploadSourceIdentity(rel, content = '') {
954
1446
  if (section === 'metrics') return namedUploadSource('metric', stem, frontmatterValue(content, 'resource_key'));
955
1447
  if (section === 'domains') return namedUploadSource('domain', stem);
956
1448
  if (section === 'recall-cards') return namedUploadSource('recall-card', stem);
1449
+ if (section === 'personal-semantics') {
1450
+ if (rest.endsWith('/index') || rest === 'index') {
1451
+ return {
1452
+ source_kind: 'personal-semantic',
1453
+ asset_type: null,
1454
+ asset_key: null,
1455
+ file_stem: `personal-semantic-${stem}`,
1456
+ };
1457
+ }
1458
+ return namedUploadSource('personal-semantic', stem, frontmatterValue(content, 'integration_id'));
1459
+ }
957
1460
  if (section === 'technical-appendices') return namedUploadSource('appendix', stem);
958
1461
  if (section === 'source-containers') return namedUploadSource('source-container', stem);
959
1462
  if (section === 'governance') return namedUploadSource('governance', stem);
@@ -966,6 +1469,122 @@ function uploadSourceIdentity(rel, content = '') {
966
1469
  return namedUploadSource(section, stem);
967
1470
  }
968
1471
 
1472
+ function semanticPlanFragmentRefsForSource(identity, rel) {
1473
+ const refs = new Set();
1474
+ const sourceKind = clean(identity.source_kind);
1475
+ const assetType = clean(identity.asset_type);
1476
+ const assetKey = clean(identity.asset_key);
1477
+ if (sourceKind === 'refresh-state') {
1478
+ for (const fragment of semanticPlanState.fragments) refs.add(fragment.id);
1479
+ return [...refs].sort();
1480
+ }
1481
+ if (rel === 'wiki/project-overview.md') refs.add('project:model');
1482
+ if (sourceKind === 'domain' && assetKey) refs.add(`domain:${assetKey}`);
1483
+ if (sourceKind === 'appendix' && assetKey) refs.add(`appendix:${assetKey}`);
1484
+ if (sourceKind === 'recall-card' && assetKey) refs.add(`recall-card:${assetKey}`);
1485
+ if (sourceKind === 'personal-semantic' && assetKey) {
1486
+ refs.add(`personal-semantic-integration:${assetKey}`);
1487
+ }
1488
+ if (assetType === 'dashboard' && assetKey) {
1489
+ const domain = domainByDashboardId.get(assetKey);
1490
+ const appendix = appendixByDashboardId.get(assetKey);
1491
+ if (domain) refs.add(`domain:${clean(domain.domain_id)}`);
1492
+ if (appendix) refs.add(`appendix:${clean(appendix.appendix_id)}`);
1493
+ }
1494
+ if (assetType === 'report' && assetKey) {
1495
+ if (planModel.sqlByReportId.has(assetKey)) refs.add(`report:${assetKey}:sql_semantics`);
1496
+ const report = reportById.get(assetKey);
1497
+ for (const dashboardRef of asArray(report?.dashboard_refs)) {
1498
+ const dashboardId = clean(dashboardRef.dashboard_id);
1499
+ const domain = domainByDashboardId.get(dashboardId);
1500
+ const appendix = appendixByDashboardId.get(dashboardId);
1501
+ if (domain) refs.add(`domain:${clean(domain.domain_id)}`);
1502
+ if (appendix) refs.add(`appendix:${clean(appendix.appendix_id)}`);
1503
+ }
1504
+ }
1505
+ if (sourceKind === 'index' && rel === 'wiki/recall-cards/index.md') {
1506
+ for (const card of planModel.cards) refs.add(`recall-card:${clean(card.card_id)}`);
1507
+ }
1508
+ if (sourceKind === 'index' && rel === 'wiki/domains/index.md') {
1509
+ for (const domain of domains) refs.add(`domain:${clean(domain.domain_id)}`);
1510
+ }
1511
+ if (sourceKind === 'personal-semantic' && /\/index\.md$/.test(rel)) {
1512
+ for (const integration of planModel.personalSemanticIntegrations) refs.add(`personal-semantic-integration:${clean(integration.integration_id)}`);
1513
+ }
1514
+ return [...refs].filter(Boolean).sort();
1515
+ }
1516
+
1517
+ function buildSemanticPlanState() {
1518
+ const fragments = [];
1519
+ const add = (id, kind, value, fields = {}) => {
1520
+ const fragmentId = clean(id);
1521
+ if (!fragmentId) return;
1522
+ fragments.push({
1523
+ id: fragmentId,
1524
+ kind,
1525
+ ...fields,
1526
+ hash: sha256(stableJson(value)),
1527
+ });
1528
+ };
1529
+ add('project:model', 'project', semanticPlan.project_business_model ?? null);
1530
+ for (const domain of domains) {
1531
+ const id = clean(domain.domain_id);
1532
+ const { recall_cards: _recallCards, ...domainWithoutCards } = asObject(domain);
1533
+ add(`domain:${id}`, 'domain', domainWithoutCards, { domain_id: id });
1534
+ for (const card of asArray(domain.recall_cards)) {
1535
+ const cardId = clean(card.card_id);
1536
+ add(`recall-card:${cardId}`, 'recall-card', { ...asObject(card), domain_id: id }, { card_id: cardId, domain_id: id });
1537
+ }
1538
+ }
1539
+ for (const appendix of appendices) {
1540
+ const id = clean(appendix.appendix_id);
1541
+ add(`appendix:${id}`, 'appendix', appendix, { appendix_id: id });
1542
+ }
1543
+ for (const [reportId, summary] of [...planModel.sqlByReportId.entries()].sort(([left], [right]) => left.localeCompare(right, 'en'))) {
1544
+ add(`report:${reportId}:sql_semantics`, 'sql-report', summary, { report_id: reportId });
1545
+ }
1546
+ for (const integration of planModel.personalSemanticIntegrations) {
1547
+ const id = clean(integration.integration_id);
1548
+ add(`personal-semantic-integration:${id}`, 'personal-semantic-integration', integration, {
1549
+ integration_id: id,
1550
+ context_type: clean(integration.context_type),
1551
+ source_ids: integration.source_ids,
1552
+ });
1553
+ }
1554
+ fragments.sort((left, right) => left.id.localeCompare(right.id, 'en'));
1555
+ return {
1556
+ schema_version: '1.0',
1557
+ plan_schema_version: clean(semanticPlan.schema_version),
1558
+ plan_hash: sha256(stableJson(semanticPlan)),
1559
+ fragment_count: fragments.length,
1560
+ fragments,
1561
+ };
1562
+ }
1563
+
1564
+ function renderRefreshState(state) {
1565
+ const payload = {
1566
+ schema_version: '1.0',
1567
+ semantic_plan: semanticPlan,
1568
+ semantic_plan_state: state,
1569
+ };
1570
+ const encoded = Buffer.from(JSON.stringify(payload, null, 2)).toString('base64url');
1571
+ return `# Refresh State
1572
+
1573
+ This managed source stores the reusable semantic plan and incremental-refresh state for generated project semantic sources. It is not business evidence.
1574
+
1575
+ <!-- pskb_refresh_state_base64
1576
+ ${encoded}
1577
+ -->
1578
+ `;
1579
+ }
1580
+
1581
+ function stableJson(value) {
1582
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
1583
+ if (!value || typeof value !== 'object') return JSON.stringify(value);
1584
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right, 'en'));
1585
+ return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableJson(item)}`).join(',')}}`;
1586
+ }
1587
+
969
1588
  function namedUploadSource(kind, stem, resourceKey = '') {
970
1589
  const key = stem === 'index' ? null : clean(resourceKey) || stem;
971
1590
  return {
@@ -1027,11 +1646,32 @@ function asObject(value) { return value && typeof value === 'object' && !Array.i
1027
1646
  function indexBy(values, key) { return new Map(values.map((value) => [clean(value[key]), value])); }
1028
1647
  function readText(file) { return fs.readFileSync(file, 'utf8'); }
1029
1648
  function readJson(file) { return JSON.parse(readText(file)); }
1649
+
1650
+ function stripClausePunctuation(value) {
1651
+ return clean(value).replace(/[。;;,,\s]+$/u, '');
1652
+ }
1653
+
1654
+ function joinChineseClauses(value) {
1655
+ const values = asArray(value).map(stripClausePunctuation).filter(Boolean);
1656
+ return values.join(';') || '无';
1657
+ }
1658
+
1659
+ function renderNumberedSteps(value, indent = '') {
1660
+ const values = asArray(value).map(stripClausePunctuation).filter(Boolean);
1661
+ if (!values.length) return `${indent}- 无`;
1662
+ return values.map((item, index) => `${indent}${index + 1}. ${item}`).join('\n');
1663
+ }
1030
1664
  function writeText(file, value) { fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file, value.endsWith('\n') ? value : `${value}\n`, 'utf8'); }
1031
1665
  function writeJson(file, value) { writeText(file, JSON.stringify(value, null, 2)); }
1032
1666
  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'); }
1667
+ function readOptionalIndex(name) { return fs.existsSync(path.join(packageRoot, 'indexes', `${name}.jsonl`)) ? readIndex(name) : []; }
1668
+ function readPackageJsonl(rel) {
1669
+ const file = path.join(packageRoot, rel);
1670
+ if (!fs.existsSync(file)) return [];
1671
+ return readText(file).split(/\r?\n/).filter(Boolean).map(JSON.parse).filter((row) => row.record_type !== 'header');
1672
+ }
1033
1673
  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; }
1674
+ 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', 'allow-semantic-plan-snapshot-drift'].includes(key)) out[key] = true; else out[key] = values[++i]; } return out; }
1035
1675
  function required(values, key) { const value = clean(values[key]); if (!value) fail(`--${key} is required.`); return value; }
1036
1676
  function normalizeUploadSourceNamespace(value) {
1037
1677
  const namespace = slug(clean(value));