@open-mercato/cli 0.6.7-develop.6863.1.61001cf9a8 → 0.6.7

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 (278) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/build.mjs +7 -24
  3. package/dist/agentic/claude-code/hooks/entity-migration-check.ts +88 -32
  4. package/dist/agentic/codex/enforcement-rules.md +16 -2
  5. package/dist/agentic/cursor/hooks/entity-migration-check.mjs +50 -19
  6. package/dist/agentic/cursor/rules/entity-guard.mdc +21 -9
  7. package/dist/agentic/cursor/rules/generated-guard.mdc +5 -3
  8. package/dist/agentic/cursor/rules/open-mercato.mdc +29 -3
  9. package/dist/agentic/guides/cache.md +45 -0
  10. package/dist/agentic/guides/core.md +453 -0
  11. package/dist/agentic/guides/events.md +117 -0
  12. package/dist/agentic/guides/module-facts.json +3833 -115419
  13. package/dist/agentic/guides/module-system.md +127 -12
  14. package/dist/agentic/guides/modules/ai_assistant.md +28 -227
  15. package/dist/agentic/guides/modules/api_docs.md +2 -58
  16. package/dist/agentic/guides/modules/api_keys.md +7 -92
  17. package/dist/agentic/guides/modules/attachments.md +14 -119
  18. package/dist/agentic/guides/modules/audit_logs.md +10 -131
  19. package/dist/agentic/guides/modules/auth.md +33 -276
  20. package/dist/agentic/guides/modules/business_rules.md +14 -149
  21. package/dist/agentic/guides/modules/catalog.md +43 -475
  22. package/dist/agentic/guides/modules/channel_gmail.md +3 -82
  23. package/dist/agentic/guides/modules/channel_imap.md +3 -82
  24. package/dist/agentic/guides/modules/checkout.md +27 -299
  25. package/dist/agentic/guides/modules/communication_channels.md +33 -315
  26. package/dist/agentic/guides/modules/configs.md +8 -104
  27. package/dist/agentic/guides/modules/content.md +2 -57
  28. package/dist/agentic/guides/modules/currencies.md +17 -152
  29. package/dist/agentic/guides/modules/customer_accounts.md +45 -387
  30. package/dist/agentic/guides/modules/customers.md +85 -746
  31. package/dist/agentic/guides/modules/dashboards.md +10 -155
  32. package/dist/agentic/guides/modules/data_sync.md +16 -149
  33. package/dist/agentic/guides/modules/dictionaries.md +12 -107
  34. package/dist/agentic/guides/modules/directory.md +15 -148
  35. package/dist/agentic/guides/modules/entities.md +13 -145
  36. package/dist/agentic/guides/modules/events.md +2 -59
  37. package/dist/agentic/guides/modules/feature_toggles.md +9 -119
  38. package/dist/agentic/guides/modules/gateway_stripe.md +4 -92
  39. package/dist/agentic/guides/modules/generators.md +2 -45
  40. package/dist/agentic/guides/modules/inbox_ops.md +27 -207
  41. package/dist/agentic/guides/modules/integrations.md +16 -146
  42. package/dist/agentic/guides/modules/messages.md +24 -223
  43. package/dist/agentic/guides/modules/notifications.md +8 -96
  44. package/dist/agentic/guides/modules/onboarding.md +6 -77
  45. package/dist/agentic/guides/modules/payment_gateways.md +21 -164
  46. package/dist/agentic/guides/modules/perspectives.md +7 -77
  47. package/dist/agentic/guides/modules/planner.md +18 -136
  48. package/dist/agentic/guides/modules/portal.md +2 -93
  49. package/dist/agentic/guides/modules/progress.md +14 -104
  50. package/dist/agentic/guides/modules/query_index.md +15 -154
  51. package/dist/agentic/guides/modules/record_locks.md +21 -206
  52. package/dist/agentic/guides/modules/resources.md +30 -197
  53. package/dist/agentic/guides/modules/sales.md +84 -678
  54. package/dist/agentic/guides/modules/scheduler.md +14 -133
  55. package/dist/agentic/guides/modules/search.md +6 -170
  56. package/dist/agentic/guides/modules/security.md +35 -294
  57. package/dist/agentic/guides/modules/shipping_carriers.md +18 -134
  58. package/dist/agentic/guides/modules/sso.md +26 -151
  59. package/dist/agentic/guides/modules/staff.md +57 -369
  60. package/dist/agentic/guides/modules/storage_s3.md +5 -95
  61. package/dist/agentic/guides/modules/sync_akeneo.md +3 -85
  62. package/dist/agentic/guides/modules/sync_excel.md +6 -75
  63. package/dist/agentic/guides/modules/system_status_overlays.md +2 -60
  64. package/dist/agentic/guides/modules/translations.md +10 -104
  65. package/dist/agentic/guides/modules/webhooks.md +24 -203
  66. package/dist/agentic/guides/modules/wms.md +46 -441
  67. package/dist/agentic/guides/modules/workflows.md +43 -302
  68. package/dist/agentic/guides/queue.md +53 -0
  69. package/dist/agentic/guides/search.md +115 -0
  70. package/dist/agentic/guides/shared.md +108 -0
  71. package/dist/agentic/guides/ui.md +347 -0
  72. package/dist/agentic/shared/AGENTS.md.template +311 -124
  73. package/dist/agentic/shared/ai/agentic.config.json +0 -2
  74. package/dist/agentic/shared/ai/lessons.md +3 -26
  75. package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +27 -0
  76. package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +27 -0
  77. package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +0 -4
  78. package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +241 -20
  79. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +260 -0
  80. package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +601 -16
  81. package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +146 -0
  82. package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +310 -14
  83. package/dist/agentic/shared/ai/skills/om-help/SKILL.md +97 -15
  84. package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +84 -0
  85. package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +193 -0
  86. package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +152 -33
  87. package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +708 -24
  88. package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +34 -0
  89. package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +762 -0
  90. package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +820 -20
  91. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +50 -0
  92. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +97 -0
  93. package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +843 -19
  94. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +271 -0
  95. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +63 -15
  96. package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +467 -16
  97. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +101 -0
  98. package/dist/agentic/shared/ai/skills/tiers.json +21 -143
  99. package/dist/agentic/shared/ai/skills/tiers.schema.json +40 -52
  100. package/dist/agentic/shared/ai/specs/README.md +3 -4
  101. package/dist/agentic/shared/ai/specs/SPEC-000-template.md +10 -226
  102. package/dist/agentic/shared/ai/trackers/github.md +4 -108
  103. package/dist/agentic/shared/scripts/install-skills.sh +694 -4
  104. package/dist/bin.js +1 -13
  105. package/dist/bin.js.map +2 -2
  106. package/dist/lib/__integration__/TC-INT-008.spec.js +8 -88
  107. package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
  108. package/dist/lib/agentic-init.js +3 -13
  109. package/dist/lib/agentic-init.js.map +2 -2
  110. package/dist/lib/agentic-setup.js +186 -418
  111. package/dist/lib/agentic-setup.js.map +2 -2
  112. package/dist/lib/generators/extensions/injection-widgets.js +0 -20
  113. package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
  114. package/dist/lib/generators/module-facts-discovery.js +7 -41
  115. package/dist/lib/generators/module-facts-discovery.js.map +2 -2
  116. package/dist/lib/generators/module-facts.js +45 -1387
  117. package/dist/lib/generators/module-facts.js.map +3 -3
  118. package/dist/lib/generators/module-registry.js +5 -11
  119. package/dist/lib/generators/module-registry.js.map +2 -2
  120. package/dist/lib/resolver.js +0 -2
  121. package/dist/lib/resolver.js.map +2 -2
  122. package/dist/lib/testing/integration.js +0 -5
  123. package/dist/lib/testing/integration.js.map +2 -2
  124. package/dist/mercato.js +1 -23
  125. package/dist/mercato.js.map +2 -2
  126. package/package.json +11 -13
  127. package/src/bin.ts +1 -33
  128. package/src/lib/__integration__/TC-INT-008.spec.ts +9 -107
  129. package/src/lib/__tests__/agentic-init.test.ts +5 -41
  130. package/src/lib/agentic-init.ts +3 -15
  131. package/src/lib/agentic-setup.ts +224 -530
  132. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +2 -88
  133. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +0 -1
  134. package/src/lib/generators/__tests__/module-facts.discovery.test.ts +10 -54
  135. package/src/lib/generators/__tests__/module-subset.test.ts +0 -18
  136. package/src/lib/generators/extensions/injection-widgets.ts +0 -51
  137. package/src/lib/generators/module-facts-discovery.ts +7 -58
  138. package/src/lib/generators/module-facts.ts +46 -1851
  139. package/src/lib/generators/module-registry.ts +6 -16
  140. package/src/lib/resolver.ts +0 -3
  141. package/src/lib/testing/__tests__/integration.test.ts +0 -4
  142. package/src/lib/testing/integration.ts +0 -5
  143. package/src/mercato.ts +1 -26
  144. package/dist/agentic/guides/ai-workflows.md +0 -76
  145. package/dist/agentic/guides/architecture.md +0 -107
  146. package/dist/agentic/guides/backend-ui.md +0 -80
  147. package/dist/agentic/guides/contracts.md +0 -92
  148. package/dist/agentic/guides/extensions.md +0 -121
  149. package/dist/agentic/guides/framework-extension-points.md +0 -36
  150. package/dist/agentic/guides/integrations.md +0 -81
  151. package/dist/agentic/guides/modules/design_system.md +0 -102
  152. package/dist/agentic/guides/spec-delivery.md +0 -22
  153. package/dist/agentic/guides/testing-debugging.md +0 -80
  154. package/dist/agentic/guides/upstream/AGENTS.md +0 -298
  155. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +0 -316
  156. package/dist/agentic/guides/upstream/manifest.json +0 -8
  157. package/dist/agentic/shared/ai/harness/README.md +0 -50
  158. package/dist/agentic/shared/ai/harness/RELEASE.md +0 -57
  159. package/dist/agentic/shared/ai/harness/cases.json +0 -13680
  160. package/dist/agentic/shared/ai/harness/cases.schema.json +0 -133
  161. package/dist/agentic/shared/ai/harness/fixtures/index.json +0 -49
  162. package/dist/agentic/shared/ai/harness/fixtures/seeds.json +0 -162
  163. package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +0 -21
  164. package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +0 -96
  165. package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +0 -172
  166. package/dist/agentic/shared/ai/harness/release-matrix.json +0 -158
  167. package/dist/agentic/shared/ai/harness/release-result.schema.json +0 -219
  168. package/dist/agentic/shared/ai/harness/result.schema.json +0 -76
  169. package/dist/agentic/shared/ai/harness/routing-response.schema.json +0 -12
  170. package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +0 -73
  171. package/dist/agentic/shared/ai/harness/validators.json +0 -78
  172. package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +0 -2121
  173. package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +0 -1096
  174. package/dist/agentic/shared/ai/lessons/_template.md +0 -16
  175. package/dist/agentic/shared/ai/review-checklist.md +0 -36
  176. package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +0 -21
  177. package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +0 -98
  178. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +0 -16
  179. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +0 -53
  180. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +0 -13
  181. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +0 -11
  182. package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +0 -30
  183. package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +0 -12
  184. package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +0 -13
  185. package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +0 -14
  186. package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +0 -28
  187. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +0 -12
  188. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +0 -22
  189. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +0 -12
  190. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +0 -19
  191. package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +0 -19
  192. package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +0 -13
  193. package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +0 -14
  194. package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +0 -10
  195. package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +0 -17
  196. package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +0 -29
  197. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +0 -100
  198. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +0 -16
  199. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +0 -17
  200. package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +0 -25
  201. package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +0 -11
  202. package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +0 -13
  203. package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +0 -18
  204. package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +0 -20
  205. package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +0 -33
  206. package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +0 -40
  207. package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +0 -27
  208. package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +0 -24
  209. package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +0 -16
  210. package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +0 -14
  211. package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +0 -15
  212. package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +0 -12
  213. package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +0 -34
  214. package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +0 -4
  215. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +0 -11
  216. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +0 -31
  217. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +0 -39
  218. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +0 -43
  219. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +0 -11
  220. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +0 -19
  221. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +0 -172
  222. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +0 -12
  223. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +0 -83
  224. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +0 -21
  225. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +0 -11
  226. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +0 -22
  227. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +0 -13
  228. package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +0 -41
  229. package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +0 -19
  230. package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +0 -44
  231. package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +0 -39
  232. package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +0 -20
  233. package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +0 -57
  234. package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +0 -11
  235. package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +0 -486
  236. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +0 -18
  237. package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +0 -27
  238. package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +0 -13
  239. package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +0 -41
  240. package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +0 -11
  241. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +0 -15
  242. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +0 -16
  243. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +0 -14
  244. package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +0 -187
  245. package/dist/agentic/shared/scripts/check-lessons.mjs +0 -178
  246. package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +0 -2958
  247. package/dist/agentic/shared/scripts/execution-sandbox.mjs +0 -329
  248. package/dist/agentic/shared/scripts/framework-context.mjs +0 -888
  249. package/dist/agentic/shared/scripts/install-skills.mjs +0 -1029
  250. package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +0 -222
  251. package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +0 -1774
  252. package/dist/lib/generators/module-extension-facts.js +0 -1840
  253. package/dist/lib/generators/module-extension-facts.js.map +0 -7
  254. package/dist/lib/generators/module-fact-sources.js +0 -70
  255. package/dist/lib/generators/module-fact-sources.js.map +0 -7
  256. package/dist/lib/generators/module-override-targets.js +0 -688
  257. package/dist/lib/generators/module-override-targets.js.map +0 -7
  258. package/dist/lib/load-env.js +0 -24
  259. package/dist/lib/load-env.js.map +0 -7
  260. package/dist/lib/telemetry-init.js +0 -358
  261. package/dist/lib/telemetry-init.js.map +0 -7
  262. package/src/lib/__tests__/agentic-setup.ownership.test.ts +0 -386
  263. package/src/lib/__tests__/injection-widget-id-validation.test.ts +0 -184
  264. package/src/lib/__tests__/load-env.test.ts +0 -65
  265. package/src/lib/__tests__/telemetry-init.test.ts +0 -298
  266. package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +0 -92
  267. package/src/lib/generators/__tests__/module-extension-activations.test.ts +0 -762
  268. package/src/lib/generators/__tests__/module-extension-facts.test.ts +0 -404
  269. package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +0 -108
  270. package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +0 -561
  271. package/src/lib/generators/__tests__/module-facts.source-links.test.ts +0 -119
  272. package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +0 -149
  273. package/src/lib/generators/__tests__/module-override-targets.test.ts +0 -304
  274. package/src/lib/generators/module-extension-facts.ts +0 -2365
  275. package/src/lib/generators/module-fact-sources.ts +0 -108
  276. package/src/lib/generators/module-override-targets.ts +0 -863
  277. package/src/lib/load-env.ts +0 -30
  278. package/src/lib/telemetry-init.ts +0 -461
@@ -1,33 +1,7 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import ts from 'typescript-js'
4
- import type {
5
- ModuleExtensionContributionFact,
6
- ModuleExtensionHostFact,
7
- ModuleExtensionSurfaceFacts,
8
- ModuleExtensionTargetRef,
9
- } from '@open-mercato/shared/modules/widgets/extension-points'
10
4
  import { toSnake } from '../utils'
11
- import { extractCommandIdsFromSource } from './module-registry'
12
- import { MODULE_CODE_EXTENSIONS } from './scanner'
13
- import type { ApiRouteInterceptorBridge } from './module-extension-facts'
14
- import {
15
- assertNoUnresolvedExtensionTargets,
16
- correlateIncomingExtensions,
17
- correlateModuleExtensionFacts,
18
- extractActivationTargetOwners,
19
- extractKnownApiRouteIds,
20
- extractKnownCommandIds,
21
- extractModuleExtensionFacts,
22
- renderFrameworkExtensionPointsMarkdown,
23
- withModuleExtensionFactExtractionCache,
24
- } from './module-extension-facts'
25
- import {
26
- collectModuleOverrideTargets,
27
- type ModuleOverrideTarget,
28
- type ModuleOverrideTargetDiagnostic,
29
- } from './module-override-targets'
30
- import { buildFactSourceLookup, type FactSourceLookup } from './module-fact-sources'
31
5
 
32
6
  export interface ModuleEntityFact {
33
7
  id: string
@@ -47,49 +21,6 @@ export interface ModuleApiRouteFact {
47
21
  path: string
48
22
  methods: string[]
49
23
  auth: Record<string, ApiRouteAuthRule>
50
- sourcePath: string | null
51
- }
52
-
53
- /**
54
- * Statically declared page metadata that affects extensibility or access, using the
55
- * runtime `PageMetadata` contract keys and the same alias precedence as
56
- * `resolvePageRouteMetadata` (`pageGroup ?? group`, `pageGroupKey ?? groupKey`,
57
- * `pageOrder ?? order`). Executable guards, icons, titles and translation results are
58
- * never serialized.
59
- */
60
- export interface ModulePageMetadata {
61
- requireAuth?: boolean
62
- requireCustomerAuth?: boolean
63
- requireFeatures?: string[]
64
- requireCustomerFeatures?: string[]
65
- navHidden?: boolean
66
- navGroup?: string
67
- navGroupKey?: string
68
- navOrder?: number
69
- pageContext?: 'main' | 'admin' | 'settings' | 'profile'
70
- }
71
-
72
- export interface ModulePageFact {
73
- path: string
74
- sourcePath: string
75
- /** Companion metadata file (`page.meta.*` / `meta.*`) when runtime reads metadata from one. */
76
- metadataSourcePath?: string
77
- metadata?: ModulePageMetadata
78
- }
79
-
80
- export interface ModuleCliCommandFact {
81
- command: string
82
- sourcePath: string
83
- }
84
-
85
- export interface ModuleAiToolFact {
86
- name: string
87
- sourcePath: string
88
- }
89
-
90
- export interface ModuleAiAgentFact {
91
- id: string
92
- sourcePath: string
93
24
  }
94
25
 
95
26
  export interface ModuleEventFact {
@@ -97,8 +28,6 @@ export interface ModuleEventFact {
97
28
  label?: string
98
29
  category: string | null
99
30
  entity: string | null
100
- clientBroadcast?: boolean
101
- portalBroadcast?: boolean
102
31
  }
103
32
 
104
33
  export interface ModuleHostTokens {
@@ -106,116 +35,11 @@ export interface ModuleHostTokens {
106
35
  tableIds: string[]
107
36
  }
108
37
 
109
- export type ModuleFactSourceRef = {
110
- sourcePath: string
111
- exportName?: string
112
- line?: number
113
- }
114
-
115
- export type ModuleFactRef = {
116
- factSection: string
117
- factKey: string
118
- }
119
-
120
- export type ModuleFactSourceKind =
121
- | 'module-metadata'
122
- | 'entity'
123
- | 'event'
124
- | 'acl-feature'
125
- | 'api-route'
126
- | 'di-registration'
127
- | 'search'
128
- | 'vector'
129
- | 'notification'
130
- | 'cli-command'
131
- | 'backend-page'
132
- | 'frontend-page'
133
- | 'ai-tool'
134
- | 'ai-agent'
135
- | 'ai-extension'
136
- | 'command'
137
- | 'subscriber'
138
- | 'worker'
139
- | 'page-middleware'
140
- | 'setup'
141
- | 'encryption'
142
- | 'custom-entity'
143
- | 'integration'
144
- | 'generator-plugin'
145
- | 'extension-host'
146
- | 'extension-contribution'
147
-
148
- export type ModuleOwnedContractKind =
149
- | 'module-metadata'
150
- | 'command'
151
- | 'worker'
152
- | 'page-middleware'
153
- | 'setup'
154
- | 'encryption'
155
- | 'di-registration'
156
- | 'custom-entity'
157
- | 'ai-extension'
158
- | 'generator-plugin'
159
-
160
- export type ModuleFactSafeScalar = string | number | boolean | null
161
-
162
- export type ModuleOwnedContractFact = {
163
- kind: ModuleOwnedContractKind
164
- id: string
165
- source: ModuleFactSourceRef
166
- metadata?: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]>
167
- }
168
-
169
- export type ModuleOwnedContracts = Partial<
170
- Record<ModuleOwnedContractKind, ModuleOwnedContractFact[]>
171
- >
172
-
173
- /**
174
- * Provenance pointer into a fact section that already serializes the declaration site.
175
- * `factKey` is omitted when it equals the owning entry's `id`, which is the common case.
176
- */
177
- export type ModuleFactIndexRef = {
178
- factSection: string
179
- factKey?: string
180
- }
181
-
182
- /**
183
- * One provenance entry per proven `(kind, id)` in the module. Exactly one of `source`
184
- * (the declaration site, for facts that have no other home in the JSON) or `factRef`
185
- * (a deterministic pointer into a fact section that already carries the source inline)
186
- * is present, so a consumer resolves any fact's origin through this single index.
187
- */
188
- export type ModuleFactSourceEntry = {
189
- kind: ModuleFactSourceKind
190
- id: string
191
- source?: ModuleFactSourceRef
192
- factRef?: ModuleFactIndexRef
193
- }
194
-
195
- export type ModuleFactDiagnostic = {
196
- code: 'duplicate-source' | 'unresolved-static-contract'
197
- kind: ModuleFactSourceKind
198
- id: string
199
- source?: ModuleFactSourceRef
200
- }
201
-
202
- export type ModuleDiRegistrationFact = {
203
- token: string
204
- registrationKind: 'function' | 'class' | 'value' | 'alias'
205
- providerSymbol?: string
206
- lifetime?: 'singleton' | 'scoped' | 'transient'
207
- injectionMode?: 'classic' | 'proxy'
208
- source: ModuleFactSourceRef
209
- }
210
-
211
38
  export interface ModuleFacts {
212
39
  module: string
213
40
  title: string | null
214
41
  description: string | null
215
42
  coreVersion: string | null
216
- sourcePackage: string | null
217
- sourceVersion: string | null
218
- sourceRoot: string
219
43
  entities: ModuleEntityFact[]
220
44
  events: ModuleEventFact[]
221
45
  aclFeatures: string[]
@@ -225,17 +49,6 @@ export interface ModuleFacts {
225
49
  hostTokens: ModuleHostTokens
226
50
  notifications: string[]
227
51
  cli: string[]
228
- backendPages: ModulePageFact[]
229
- frontendPages: ModulePageFact[]
230
- cliCommands: ModuleCliCommandFact[]
231
- aiTools: ModuleAiToolFact[]
232
- aiAgents: ModuleAiAgentFact[]
233
- extensionSurfaces?: ModuleExtensionSurfaceFacts
234
- factSources?: ModuleFactSourceEntry[]
235
- ownedContracts?: ModuleOwnedContracts
236
- factDiagnostics?: ModuleFactDiagnostic[]
237
- overrideTargets?: ModuleOverrideTarget[]
238
- overrideTargetDiagnostics?: ModuleOverrideTargetDiagnostic[]
239
52
  warnings: string[]
240
53
  }
241
54
 
@@ -251,8 +64,6 @@ export interface ExtractModuleFactsOptions {
251
64
  /** Explicit module source directory. When set it overrides `coreSrcRoot + moduleId`. */
252
65
  moduleRoot?: string
253
66
  coreVersion?: string | null
254
- sourcePackage?: string | null
255
- sourceVersion?: string | null
256
67
  registryPath?: string | null
257
68
  registrySource?: string | null
258
69
  }
@@ -262,7 +73,6 @@ export interface ModuleFactSource {
262
73
  moduleId: string
263
74
  moduleRoot: string
264
75
  from?: string
265
- packageVersion?: string | null
266
76
  }
267
77
 
268
78
  function readSourceFile(filePath: string): ts.SourceFile | null {
@@ -349,41 +159,10 @@ function collectCustomFieldEntityIds(ceFilePath: string | null): Set<string> {
349
159
  const sourceFile = readSourceFile(ceFilePath)
350
160
  if (!sourceFile) return result
351
161
 
352
- const readEntityId = (expression: ts.Expression): string | undefined => {
353
- const current = unwrapExpression(expression)
354
- if (ts.isStringLiteralLike(current)) {
355
- return current.text.includes(':') ? current.text : undefined
356
- }
357
-
358
- const readAccessPath = (candidate: ts.Expression): string[] | null => {
359
- const access = unwrapExpression(candidate)
360
- if (ts.isIdentifier(access)) return [access.text]
361
- if (ts.isPropertyAccessExpression(access)) {
362
- const parent = readAccessPath(access.expression)
363
- return parent ? [...parent, access.name.text] : null
364
- }
365
- if (ts.isElementAccessExpression(access) && access.argumentExpression) {
366
- const parent = readAccessPath(access.expression)
367
- const key = unwrapExpression(access.argumentExpression)
368
- return parent && ts.isStringLiteralLike(key) ? [...parent, key.text] : null
369
- }
370
- return null
371
- }
372
-
373
- const accessPath = readAccessPath(current)
374
- if (accessPath?.length === 3 && accessPath[0] === 'E') {
375
- return `${accessPath[1]}:${accessPath[2]}`
376
- }
377
- return undefined
378
- }
379
-
380
162
  const visit = (node: ts.Node): void => {
381
163
  if (ts.isObjectLiteralExpression(node)) {
382
- const initializer = getObjectPropertyInitializer(node, 'id')
383
- if (initializer) {
384
- const id = readEntityId(initializer)
385
- if (id) result.add(id)
386
- }
164
+ const id = readStringPropertyInitializer(node, 'id')
165
+ if (id && id.includes(':')) result.add(id)
387
166
  }
388
167
  node.forEachChild(visit)
389
168
  }
@@ -572,11 +351,7 @@ function extractEvents(eventsFilePath: string | null): ModuleEventFact[] {
572
351
  category: category ?? null,
573
352
  entity: entity ?? null,
574
353
  }
575
- const clientBroadcast = readBooleanPropertyInitializer(element, 'clientBroadcast')
576
- const portalBroadcast = readBooleanPropertyInitializer(element, 'portalBroadcast')
577
354
  if (label !== undefined) fact.label = label
578
- if (clientBroadcast !== undefined) fact.clientBroadcast = clientBroadcast
579
- if (portalBroadcast !== undefined) fact.portalBroadcast = portalBroadcast
580
355
  facts.push(fact)
581
356
  }
582
357
 
@@ -684,14 +459,13 @@ function parseApiRouteEntry(entryLiteral: ts.ObjectLiteralExpression): ModuleApi
684
459
  }
685
460
  }
686
461
 
687
- return { path: routePath, methods, auth, sourcePath: null }
462
+ return { path: routePath, methods, auth }
688
463
  }
689
464
 
690
465
  function extractApiRoutes(
691
466
  moduleId: string,
692
467
  registrySource: string | null,
693
468
  registryDescription: string,
694
- routeSourcePaths: ReadonlyMap<string, string>,
695
469
  warnings: string[],
696
470
  ): ModuleApiRouteFact[] {
697
471
  if (registrySource == null) {
@@ -719,7 +493,7 @@ function extractApiRoutes(
719
493
  const route = parseApiRouteEntry(element)
720
494
  if (route && !seenPaths.has(route.path)) {
721
495
  seenPaths.add(route.path)
722
- routes.push({ ...route, sourcePath: routeSourcePaths.get(route.path) ?? null })
496
+ routes.push(route)
723
497
  }
724
498
  }
725
499
  }
@@ -892,241 +666,36 @@ function extractCli(cliFilePath: string | null, warnings: string[]): string[] {
892
666
  function listSourceFilesRecursive(directory: string): string[] {
893
667
  const files: string[] = []
894
668
  for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
895
- if (entry.name === '__tests__' || entry.name === '__mocks__' || entry.name === 'node_modules') continue
669
+ if (entry.name === '__tests__' || entry.name === 'node_modules') continue
896
670
  const fullPath = path.join(directory, entry.name)
897
671
  if (entry.isDirectory()) {
898
672
  files.push(...listSourceFilesRecursive(fullPath))
899
- } else if (
900
- /\.tsx?$/.test(entry.name)
901
- && !entry.name.endsWith('.d.ts')
902
- && !/\.(?:test|spec)\.tsx?$/.test(entry.name)
903
- ) {
673
+ } else if (/\.tsx?$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
904
674
  files.push(fullPath)
905
675
  }
906
676
  }
907
677
  return files
908
678
  }
909
679
 
910
- function toPortableSourceRoot(moduleId: string, sourcePackage: string | null): string {
911
- return path.posix.join('node_modules', sourcePackage ?? '@open-mercato/core', 'src', 'modules', moduleId)
912
- }
913
-
914
- function toPortableSourcePath(moduleRoot: string, sourceRoot: string, filePath: string): string {
915
- const relativePath = path.relative(moduleRoot, filePath).split(path.sep).join('/')
916
- return path.posix.join(sourceRoot, relativePath)
917
- }
918
-
919
- function sourceHasDefaultExport(sourceFile: ts.SourceFile): boolean {
920
- return sourceFile.statements.some((statement) => {
921
- if (ts.isExportAssignment(statement) && !statement.isExportEquals) return true
922
- if (!ts.isFunctionDeclaration(statement) && !ts.isClassDeclaration(statement)) return false
923
- return statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.DefaultKeyword) ?? false
924
- })
925
- }
926
-
927
- function readRouteMetadataPath(sourceFile: ts.SourceFile): string | null {
928
- const metadata = findObjectLiteralDeclaration(sourceFile, 'metadata')
929
- return metadata ? readStringPropertyInitializer(metadata, 'path') ?? null : null
930
- }
931
-
932
- function extractApiRouteSourcePaths(
933
- moduleId: string,
934
- moduleRoot: string,
935
- sourceRoot: string,
936
- ): Map<string, string> {
937
- const apiRoot = path.join(moduleRoot, 'api')
938
- const sources = new Map<string, string>()
939
- if (!fs.existsSync(apiRoot)) return sources
940
- const methodDirectories = new Set(['get', 'post', 'put', 'patch', 'delete'])
941
-
942
- for (const filePath of listSourceFilesRecursive(apiRoot)) {
943
- const relativePath = path.relative(apiRoot, filePath).split(path.sep).join('/')
944
- const segments = relativePath.split('/')
945
- const fileName = segments.pop() as string
946
- const fileStem = fileName.replace(/\.tsx?$/, '')
947
- let routeSegments: string[]
948
- if (fileStem === 'route') {
949
- routeSegments = segments
950
- } else if (segments[0] && methodDirectories.has(segments[0].toLowerCase())) {
951
- routeSegments = [...segments.slice(1), fileStem]
952
- } else {
953
- routeSegments = [...segments, fileStem]
954
- }
955
- const defaultPath = `/${[moduleId, ...routeSegments].filter(Boolean).join('/')}`
956
- const sourceFile = readSourceFile(filePath)
957
- const routePath = sourceFile ? readRouteMetadataPath(sourceFile) ?? defaultPath : defaultPath
958
- sources.set(routePath, toPortableSourcePath(moduleRoot, sourceRoot, filePath))
959
- }
960
- return sources
961
- }
962
-
963
- function extractModulePages(
964
- moduleId: string,
965
- moduleRoot: string,
966
- sourceRoot: string,
967
- surface: 'backend' | 'frontend',
968
- ): ModulePageFact[] {
969
- const pageRoot = path.join(moduleRoot, surface)
970
- if (!fs.existsSync(pageRoot)) return []
971
- const pages: ModulePageFact[] = []
972
- const seen = new Set<string>()
973
-
974
- for (const filePath of listSourceFilesRecursive(pageRoot)) {
975
- if (!filePath.endsWith('.tsx')) continue
976
- const relativePath = path.relative(pageRoot, filePath).split(path.sep).join('/')
977
- const segments = relativePath.split('/')
978
- const fileName = segments.pop() as string
979
- const fileStem = fileName.replace(/\.tsx$/, '')
980
- const isModernPage = fileStem === 'page'
981
- if (!isModernPage && (fileStem === 'meta' || fileStem.endsWith('.meta') || /^[A-Z]/.test(fileStem))) continue
982
- const sourceFile = readSourceFile(filePath)
983
- if (!sourceFile || !sourceHasDefaultExport(sourceFile)) continue
984
- const routeSegments = isModernPage ? segments : [...segments, fileStem]
985
- const routePath = surface === 'frontend'
986
- ? `/${routeSegments.filter(Boolean).join('/')}`
987
- : isModernPage
988
- ? `/backend/${routeSegments.join('/') || moduleId}`
989
- : `/backend/${routeSegments[0] === moduleId
990
- ? routeSegments.filter(Boolean).join('/')
991
- : [moduleId, ...routeSegments].filter(Boolean).join('/')}`
992
- if (seen.has(routePath)) continue
993
- seen.add(routePath)
994
- const metadataFilePath = resolvePageMetadataFile(path.dirname(filePath))
995
- const metadataSourceFile = metadataFilePath ? readSourceFile(metadataFilePath) : sourceFile
996
- const metadata = metadataSourceFile ? readPageMetadata(metadataSourceFile) : undefined
997
- pages.push({
998
- path: routePath,
999
- sourcePath: toPortableSourcePath(moduleRoot, sourceRoot, filePath),
1000
- ...(metadataFilePath
1001
- ? { metadataSourcePath: toPortableSourcePath(moduleRoot, sourceRoot, metadataFilePath) }
1002
- : {}),
1003
- ...(metadata ? { metadata } : {}),
1004
- })
1005
- }
1006
- return pages.sort((left, right) => left.path.localeCompare(right.path))
1007
- }
1008
-
1009
- function findAncestorVariableDeclaration(node: ts.Node): ts.VariableDeclaration | null {
1010
- let current: ts.Node | undefined = node.parent
1011
- while (current) {
1012
- if (ts.isVariableDeclaration(current)) return current
1013
- current = current.parent
1014
- }
1015
- return null
1016
- }
1017
-
1018
- function findAncestorCallExpression(node: ts.Node): ts.CallExpression | null {
1019
- let current: ts.Node | undefined = node.parent
1020
- while (current) {
1021
- if (ts.isCallExpression(current)) return current
1022
- if (ts.isVariableDeclaration(current) || ts.isStatement(current)) return null
1023
- current = current.parent
1024
- }
1025
- return null
1026
- }
1027
-
1028
- function extractNamedAiFacts(
1029
- filePaths: readonly string[],
1030
- moduleRoot: string,
1031
- sourceRoot: string,
1032
- propertyName: 'name' | 'id',
1033
- ): Array<{ value: string; sourcePath: string }> {
1034
- const facts: Array<{ value: string; sourcePath: string }> = []
1035
- const seen = new Set<string>()
1036
- for (const filePath of filePaths) {
1037
- const sourceFile = readSourceFile(filePath)
1038
- if (!sourceFile) continue
1039
- const initializers = buildVariableInitializerMap(sourceFile)
1040
- const readDefinitionValue = (objectLiteral: ts.ObjectLiteralExpression): string | undefined => {
1041
- const initializer = getObjectPropertyInitializer(objectLiteral, propertyName)
1042
- if (!initializer) return undefined
1043
- const resolved = unwrapExpression(initializer)
1044
- if (ts.isStringLiteralLike(resolved)) return resolved.text
1045
- if (ts.isIdentifier(resolved)) {
1046
- const declarationInitializer = initializers.get(resolved.text)
1047
- if (declarationInitializer) {
1048
- const declarationValue = unwrapExpression(declarationInitializer)
1049
- if (ts.isStringLiteralLike(declarationValue)) return declarationValue.text
1050
- }
1051
- }
1052
- return undefined
1053
- }
1054
- const visit = (node: ts.Node): void => {
1055
- if (ts.isObjectLiteralExpression(node)) {
1056
- const value = readDefinitionValue(node)
1057
- if (value && !seen.has(value)) {
1058
- const declaration = findAncestorVariableDeclaration(node)
1059
- const declarationName = declaration && ts.isIdentifier(declaration.name) ? declaration.name.text : ''
1060
- const declarationType = declaration?.type?.getText(sourceFile) ?? ''
1061
- const call = findAncestorCallExpression(node)
1062
- const callee = call?.expression.getText(sourceFile) ?? ''
1063
- const isDefinition = propertyName === 'name'
1064
- ? /AiTool/.test(declarationType) || /tool$/i.test(declarationName) || /AiTool/.test(callee)
1065
- : /AiAgentDefinition/.test(declarationType) || /agent$/i.test(declarationName) || /defineAiAgent/.test(callee)
1066
- if (isDefinition) {
1067
- seen.add(value)
1068
- facts.push({
1069
- value,
1070
- sourcePath: toPortableSourcePath(moduleRoot, sourceRoot, filePath),
1071
- })
1072
- }
1073
- }
1074
- }
1075
- node.forEachChild(visit)
1076
- }
1077
- sourceFile.forEachChild(visit)
1078
- }
1079
- return facts.sort((left, right) => left.value.localeCompare(right.value))
1080
- }
1081
-
1082
- function extractAiTools(moduleRoot: string, sourceRoot: string): ModuleAiToolFact[] {
1083
- const files = new Set<string>()
1084
- const rootFile = resolveConventionFile(moduleRoot, 'ai-tools')
1085
- if (rootFile) files.add(rootFile)
1086
- const toolsDirectory = path.join(moduleRoot, 'ai-tools')
1087
- if (fs.existsSync(toolsDirectory)) {
1088
- for (const filePath of listSourceFilesRecursive(toolsDirectory)) files.add(filePath)
1089
- }
1090
- return extractNamedAiFacts([...files], moduleRoot, sourceRoot, 'name')
1091
- .map((fact) => ({ name: fact.value, sourcePath: fact.sourcePath }))
1092
- }
1093
-
1094
- function extractAiAgents(moduleRoot: string, sourceRoot: string): ModuleAiAgentFact[] {
1095
- const agentsFile = resolveConventionFile(moduleRoot, 'ai-agents')
1096
- if (!agentsFile) return []
1097
- return extractNamedAiFacts([agentsFile], moduleRoot, sourceRoot, 'id')
1098
- .map((fact) => ({ id: fact.value, sourcePath: fact.sourcePath }))
1099
- }
1100
-
1101
- function extractTableIds(moduleRoot: string): string[] {
1102
- if (!fs.existsSync(moduleRoot)) return []
680
+ function extractTableIds(backendDir: string): string[] {
681
+ if (!fs.existsSync(backendDir)) return []
1103
682
 
1104
683
  const tableIds: string[] = []
1105
684
  const seen = new Set<string>()
1106
- const collectLiteralValues = (expression: ts.Expression): string[] => {
1107
- const current = unwrapExpression(expression)
1108
- if (ts.isStringLiteralLike(current)) return [current.text]
1109
- if (ts.isConditionalExpression(current)) {
1110
- return [
1111
- ...collectLiteralValues(current.whenTrue),
1112
- ...collectLiteralValues(current.whenFalse),
1113
- ]
1114
- }
1115
- return []
1116
- }
1117
-
1118
- for (const filePath of listSourceFilesRecursive(moduleRoot)) {
685
+ for (const filePath of listSourceFilesRecursive(backendDir)) {
1119
686
  const sourceFile = readSourceFile(filePath)
1120
687
  if (!sourceFile) continue
1121
688
  const visit = (node: ts.Node): void => {
1122
689
  if (ts.isPropertyAssignment(node)) {
1123
690
  const propertyName = getPropertyName(node)
1124
- if (propertyName === 'tableId' || propertyName === 'extensionTableId') {
1125
- for (const value of collectLiteralValues(node.initializer)) {
1126
- if (value && !seen.has(value)) {
1127
- seen.add(value)
1128
- tableIds.push(value)
1129
- }
691
+ if (
692
+ (propertyName === 'tableId' || propertyName === 'extensionTableId') &&
693
+ ts.isStringLiteralLike(node.initializer)
694
+ ) {
695
+ const value = node.initializer.text
696
+ if (value && !seen.has(value)) {
697
+ seen.add(value)
698
+ tableIds.push(value)
1130
699
  }
1131
700
  }
1132
701
  }
@@ -1154,750 +723,13 @@ function extractModuleMeta(indexFilePath: string | null): { title: string | null
1154
723
  }
1155
724
  }
1156
725
 
1157
- function nodeLine(sourceFile: ts.SourceFile, node: ts.Node): number {
1158
- return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1
1159
- }
1160
-
1161
- function readPropertyNameText(name: ts.PropertyName): string | undefined {
1162
- if (ts.isIdentifier(name) || ts.isStringLiteralLike(name)) return name.text
1163
- return undefined
1164
- }
1165
-
1166
- function readMethodBearingPropertyNames(objectLiteral: ts.ObjectLiteralExpression): string[] {
1167
- const names: string[] = []
1168
- for (const property of objectLiteral.properties) {
1169
- if (ts.isMethodDeclaration(property)) {
1170
- const name = readPropertyNameText(property.name)
1171
- if (name) names.push(name)
1172
- continue
1173
- }
1174
- if (ts.isPropertyAssignment(property)) {
1175
- const name = readPropertyNameText(property.name)
1176
- if (!name) continue
1177
- const initializer = unwrapExpression(property.initializer)
1178
- if (ts.isArrowFunction(initializer) || ts.isFunctionExpression(initializer)) names.push(name)
1179
- }
1180
- }
1181
- return names.sort((left, right) => left.localeCompare(right))
1182
- }
1183
-
1184
- function readObjectKeyNames(objectLiteral: ts.ObjectLiteralExpression): string[] {
1185
- const names: string[] = []
1186
- for (const property of objectLiteral.properties) {
1187
- const name = getPropertyName(property)
1188
- if (name) names.push(name)
1189
- }
1190
- return names.sort((left, right) => left.localeCompare(right))
1191
- }
1192
-
1193
- function mapDiRegistrationKind(base: string | null): ModuleDiRegistrationFact['registrationKind'] | null {
1194
- switch (base) {
1195
- case 'asFunction':
1196
- return 'function'
1197
- case 'asClass':
1198
- return 'class'
1199
- case 'asValue':
1200
- return 'value'
1201
- case 'aliasTo':
1202
- return 'alias'
1203
- default:
1204
- return null
1205
- }
1206
- }
1207
-
1208
- function readDiChainMetadata(initializer: ts.Expression): {
1209
- lifetime?: ModuleDiRegistrationFact['lifetime']
1210
- injectionMode?: ModuleDiRegistrationFact['injectionMode']
1211
- } {
1212
- const result: {
1213
- lifetime?: ModuleDiRegistrationFact['lifetime']
1214
- injectionMode?: ModuleDiRegistrationFact['injectionMode']
1215
- } = {}
1216
- let current: ts.Expression = unwrapExpression(initializer)
1217
- while (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
1218
- const method = current.expression.name.text
1219
- if ((method === 'singleton' || method === 'scoped' || method === 'transient') && !result.lifetime) {
1220
- result.lifetime = method
1221
- } else if (method === 'proxy' && !result.injectionMode) {
1222
- result.injectionMode = 'proxy'
1223
- } else if (method === 'classic' && !result.injectionMode) {
1224
- result.injectionMode = 'classic'
1225
- }
1226
- current = unwrapExpression(current.expression.expression)
1227
- }
1228
- return result
1229
- }
1230
-
1231
- function readDiProviderSymbol(
1232
- initializer: ts.Expression,
1233
- kind: ModuleDiRegistrationFact['registrationKind'],
1234
- ): string | undefined {
1235
- let current: ts.Expression = unwrapExpression(initializer)
1236
- while (ts.isCallExpression(current) && ts.isPropertyAccessExpression(current.expression)) {
1237
- current = unwrapExpression(current.expression.expression)
1238
- }
1239
- if (!ts.isCallExpression(current)) return undefined
1240
- const argument = current.arguments[0]
1241
- if (!argument) return undefined
1242
- if (kind === 'value') return undefined
1243
- if (kind === 'alias' && ts.isStringLiteralLike(argument)) return argument.text
1244
- if ((kind === 'function' || kind === 'class') && ts.isIdentifier(argument)) return argument.text
1245
- return undefined
1246
- }
1247
-
1248
- function extractDiRegistrationFacts(
1249
- diFilePath: string | null,
1250
- sourcePath: string,
1251
- ): { facts: ModuleDiRegistrationFact[]; unresolvedTokens: string[] } {
1252
- if (!diFilePath) return { facts: [], unresolvedTokens: [] }
1253
- const sourceFile = readSourceFile(diFilePath)
1254
- if (!sourceFile) return { facts: [], unresolvedTokens: [] }
1255
-
1256
- const facts: ModuleDiRegistrationFact[] = []
1257
- const unresolvedTokens: string[] = []
1258
- const visit = (node: ts.Node): void => {
1259
- if (
1260
- ts.isCallExpression(node) &&
1261
- ts.isPropertyAccessExpression(node.expression) &&
1262
- node.expression.name.text === 'register'
1263
- ) {
1264
- const argument = node.arguments[0]
1265
- if (argument && ts.isObjectLiteralExpression(argument)) {
1266
- for (const property of argument.properties) {
1267
- if (!ts.isPropertyAssignment(property)) continue
1268
- const token = getPropertyName(property)
1269
- if (!token) continue
1270
- const kind = mapDiRegistrationKind(detectAwilixRegistrationKind(property.initializer))
1271
- if (!kind) {
1272
- if (!unresolvedTokens.includes(token)) unresolvedTokens.push(token)
1273
- continue
1274
- }
1275
- const chain = readDiChainMetadata(property.initializer)
1276
- const providerSymbol = readDiProviderSymbol(property.initializer, kind)
1277
- facts.push({
1278
- token,
1279
- registrationKind: kind,
1280
- ...(providerSymbol ? { providerSymbol } : {}),
1281
- ...(chain.lifetime ? { lifetime: chain.lifetime } : {}),
1282
- ...(chain.injectionMode ? { injectionMode: chain.injectionMode } : {}),
1283
- source: { sourcePath, exportName: token, line: nodeLine(sourceFile, property) },
1284
- })
1285
- }
1286
- }
1287
- }
1288
- node.forEachChild(visit)
1289
- }
1290
- sourceFile.forEachChild(visit)
1291
- return { facts, unresolvedTokens }
1292
- }
1293
-
1294
- function deriveLegacyDiTokens(facts: readonly ModuleDiRegistrationFact[]): string[] {
1295
- const tokens: string[] = []
1296
- const seen = new Set<string>()
1297
- for (const fact of facts) {
1298
- if (fact.registrationKind !== 'function' && fact.registrationKind !== 'class') continue
1299
- if (seen.has(fact.token)) continue
1300
- seen.add(fact.token)
1301
- tokens.push(fact.token)
1302
- }
1303
- return tokens
1304
- }
1305
-
1306
- function dedupeOwnedContractFacts(facts: readonly ModuleOwnedContractFact[]): ModuleOwnedContractFact[] {
1307
- const byId = new Map<string, ModuleOwnedContractFact>()
1308
- for (const fact of facts) {
1309
- const existing = byId.get(fact.id)
1310
- if (!existing) {
1311
- byId.set(fact.id, fact)
1312
- continue
1313
- }
1314
- if (fact.source.sourcePath.localeCompare(existing.source.sourcePath) < 0) byId.set(fact.id, fact)
1315
- }
1316
- return [...byId.values()].sort((left, right) => left.id.localeCompare(right.id))
1317
- }
1318
-
1319
- const PAGE_CONTEXT_VALUES = new Set(['main', 'admin', 'settings', 'profile'])
1320
-
1321
- function readAliasedStringProperty(
1322
- metadata: ts.ObjectLiteralExpression,
1323
- preferred: string,
1324
- fallback: string,
1325
- ): string | undefined {
1326
- return readStringPropertyInitializer(metadata, preferred) ?? readStringPropertyInitializer(metadata, fallback)
1327
- }
1328
-
1329
- function readNumericProperty(metadata: ts.ObjectLiteralExpression, propertyName: string): number | undefined {
1330
- const initializer = getObjectPropertyInitializer(metadata, propertyName)
1331
- if (!initializer || !ts.isNumericLiteral(initializer)) return undefined
1332
- return Number(initializer.text)
1333
- }
1334
-
1335
- function readPageMetadata(sourceFile: ts.SourceFile): ModulePageMetadata | undefined {
1336
- const metadata = findObjectLiteralDeclaration(sourceFile, 'metadata')
1337
- if (!metadata) return undefined
1338
- const result: ModulePageMetadata = {}
1339
- const requireAuth = readBooleanPropertyInitializer(metadata, 'requireAuth')
1340
- if (requireAuth !== undefined) result.requireAuth = requireAuth
1341
- const requireCustomerAuth = readBooleanPropertyInitializer(metadata, 'requireCustomerAuth')
1342
- if (requireCustomerAuth !== undefined) result.requireCustomerAuth = requireCustomerAuth
1343
- const requireFeatures = readStringArrayPropertyInitializer(metadata, 'requireFeatures')
1344
- if (requireFeatures && requireFeatures.length > 0) result.requireFeatures = requireFeatures
1345
- const requireCustomerFeatures = readStringArrayPropertyInitializer(metadata, 'requireCustomerFeatures')
1346
- if (requireCustomerFeatures && requireCustomerFeatures.length > 0) {
1347
- result.requireCustomerFeatures = requireCustomerFeatures
1348
- }
1349
- const navHidden = readBooleanPropertyInitializer(metadata, 'navHidden')
1350
- if (navHidden !== undefined) result.navHidden = navHidden
1351
- const navGroup = readAliasedStringProperty(metadata, 'pageGroup', 'group')
1352
- if (navGroup) result.navGroup = navGroup
1353
- const navGroupKey = readAliasedStringProperty(metadata, 'pageGroupKey', 'groupKey')
1354
- if (navGroupKey) result.navGroupKey = navGroupKey
1355
- const navOrder = readNumericProperty(metadata, 'pageOrder') ?? readNumericProperty(metadata, 'order')
1356
- if (navOrder !== undefined) result.navOrder = navOrder
1357
- const pageContext = readStringPropertyInitializer(metadata, 'pageContext')
1358
- if (pageContext && PAGE_CONTEXT_VALUES.has(pageContext)) {
1359
- result.pageContext = pageContext as ModulePageMetadata['pageContext']
1360
- }
1361
- return Object.keys(result).length > 0 ? result : undefined
1362
- }
1363
-
1364
- /**
1365
- * Mirrors the runtime companion-file convention: a page's metadata comes from
1366
- * `page.meta.*` (or `meta.*`) in the page directory when one exists, and only
1367
- * otherwise from the page component itself.
1368
- */
1369
- function resolvePageMetadataFile(pageDirectory: string): string | null {
1370
- for (const baseName of ['page.meta', 'meta']) {
1371
- for (const extension of MODULE_CODE_EXTENSIONS) {
1372
- const candidate = path.join(pageDirectory, `${baseName}${extension}`)
1373
- if (fs.existsSync(candidate)) return candidate
1374
- }
1375
- }
1376
- return null
1377
- }
1378
-
1379
- function extractModuleMetadataContract(
1380
- indexFilePath: string | null,
1381
- moduleId: string,
1382
- sourcePath: string | null,
1383
- ): ModuleOwnedContractFact | null {
1384
- if (!indexFilePath || !sourcePath) return null
1385
- const sourceFile = readSourceFile(indexFilePath)
1386
- if (!sourceFile) return null
1387
- const metadata = findObjectLiteralDeclaration(sourceFile, 'metadata')
1388
- if (!metadata) return null
1389
- const facts: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]> = {}
1390
- const name = readStringPropertyInitializer(metadata, 'name')
1391
- if (name) facts.name = name
1392
- const version = readStringPropertyInitializer(metadata, 'version')
1393
- if (version) facts.version = version
1394
- const requires = readStringArrayPropertyInitializer(metadata, 'requires')
1395
- if (requires && requires.length > 0) facts.requires = requires
1396
- const ejectable = readBooleanPropertyInitializer(metadata, 'ejectable')
1397
- if (ejectable !== undefined) facts.ejectable = ejectable
1398
- return {
1399
- kind: 'module-metadata',
1400
- id: moduleId,
1401
- source: { sourcePath, exportName: 'metadata', line: nodeLine(sourceFile, metadata) },
1402
- ...(Object.keys(facts).length > 0 ? { metadata: facts } : {}),
1403
- }
1404
- }
1405
-
1406
- function extractCommandContractCandidates(
1407
- moduleId: string,
1408
- moduleRoot: string,
1409
- sourceRoot: string,
1410
- ): Array<{ id: string; sourcePath: string }> {
1411
- const commandsRoot = path.join(moduleRoot, 'commands')
1412
- if (!fs.existsSync(commandsRoot)) return []
1413
- const candidates: Array<{ id: string; sourcePath: string }> = []
1414
- for (const filePath of listSourceFilesRecursive(commandsRoot)) {
1415
- if (path.basename(filePath).replace(/\.tsx?$/, '') === 'interceptors') continue
1416
- const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath)
1417
- for (const id of extractCommandIdsFromSource(filePath)) candidates.push({ id, sourcePath })
1418
- }
1419
- return candidates
1420
- }
1421
-
1422
- /**
1423
- * Runtime worker id: `metadata.id` when declared, otherwise the path-derived
1424
- * `<moduleId>:workers:<...segments>:<fileStem>` that `discoverWorkers` builds.
1425
- */
1426
- function deriveWorkerId(moduleId: string, workersRoot: string, filePath: string): string {
1427
- const relativePath = path.relative(workersRoot, filePath).split(path.sep).join('/')
1428
- const segments = relativePath.split('/')
1429
- const fileName = segments.pop() as string
1430
- const fileStem = fileName.replace(/\.tsx?$/, '')
1431
- return [moduleId, 'workers', ...segments, fileStem].filter(Boolean).join(':')
1432
- }
1433
-
1434
- /** Resolves a string property directly or through a local `const` in the same file. */
1435
- function readResolvedStringProperty(
1436
- objectLiteral: ts.ObjectLiteralExpression,
1437
- propertyName: string,
1438
- initializers: Map<string, ts.Expression>,
1439
- ): string | undefined {
1440
- const initializer = getObjectPropertyInitializer(objectLiteral, propertyName)
1441
- if (!initializer) return undefined
1442
- const resolved = unwrapExpression(initializer)
1443
- if (ts.isStringLiteralLike(resolved)) return resolved.text
1444
- if (ts.isIdentifier(resolved)) {
1445
- const declaration = initializers.get(resolved.text)
1446
- if (declaration) {
1447
- const declarationValue = unwrapExpression(declaration)
1448
- if (ts.isStringLiteralLike(declarationValue)) return declarationValue.text
1449
- }
1450
- }
1451
- return undefined
1452
- }
1453
-
1454
- function extractWorkerContracts(
1455
- moduleId: string,
1456
- moduleRoot: string,
1457
- sourceRoot: string,
1458
- ): { facts: ModuleOwnedContractFact[]; unresolved: ModuleFactDiagnostic[] } {
1459
- const workersRoot = path.join(moduleRoot, 'workers')
1460
- if (!fs.existsSync(workersRoot)) return { facts: [], unresolved: [] }
1461
- const facts: ModuleOwnedContractFact[] = []
1462
- const unresolved: ModuleFactDiagnostic[] = []
1463
- for (const filePath of listSourceFilesRecursive(workersRoot)) {
1464
- const sourceFile = readSourceFile(filePath)
1465
- if (!sourceFile) continue
1466
- const metadata = findObjectLiteralDeclaration(sourceFile, 'metadata')
1467
- if (!metadata) continue
1468
- const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath)
1469
- const source = { sourcePath, exportName: 'metadata', line: nodeLine(sourceFile, metadata) }
1470
- const initializers = buildVariableInitializerMap(sourceFile)
1471
- const workerId = readResolvedStringProperty(metadata, 'id', initializers)
1472
- ?? deriveWorkerId(moduleId, workersRoot, filePath)
1473
-
1474
- // Runtime skips any worker without a queue, so a worker missing the required
1475
- // property is not a contract; a present-but-dynamic queue is reported instead.
1476
- const hasQueueProperty = getObjectPropertyInitializer(metadata, 'queue') !== undefined
1477
- const queue = readResolvedStringProperty(metadata, 'queue', initializers)
1478
- if (!hasQueueProperty) {
1479
- unresolved.push({ code: 'unresolved-static-contract', kind: 'worker', id: workerId, source })
1480
- continue
1481
- }
1482
-
1483
- const workerMetadata: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]> = {}
1484
- if (queue) workerMetadata.queue = queue
1485
- else unresolved.push({ code: 'unresolved-static-contract', kind: 'worker', id: workerId, source })
1486
- const name = readResolvedStringProperty(metadata, 'name', initializers)
1487
- if (name) workerMetadata.name = name
1488
- const concurrencyInitializer = getObjectPropertyInitializer(metadata, 'concurrency')
1489
- if (concurrencyInitializer && ts.isNumericLiteral(concurrencyInitializer)) {
1490
- workerMetadata.concurrency = Number(concurrencyInitializer.text)
1491
- }
1492
- facts.push({
1493
- kind: 'worker',
1494
- id: workerId,
1495
- source,
1496
- ...(Object.keys(workerMetadata).length > 0 ? { metadata: workerMetadata } : {}),
1497
- })
1498
- }
1499
- return { facts: dedupeOwnedContractFacts(facts), unresolved }
1500
- }
1501
-
1502
- function extractPageMiddlewareContracts(
1503
- moduleRoot: string,
1504
- sourceRoot: string,
1505
- ): { facts: ModuleOwnedContractFact[]; unresolved: ModuleFactDiagnostic[] } {
1506
- const facts: ModuleOwnedContractFact[] = []
1507
- const unresolved: ModuleFactDiagnostic[] = []
1508
- for (const surface of ['backend', 'frontend'] as const) {
1509
- const filePath = resolveConventionFile(path.join(moduleRoot, surface), 'middleware')
1510
- if (!filePath) continue
1511
- const sourceFile = readSourceFile(filePath)
1512
- if (!sourceFile) continue
1513
- const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath)
1514
- const middlewareArray =
1515
- findArrayLiteralDeclaration(sourceFile, 'middleware') ??
1516
- (() => {
1517
- const defaultExport = findDefaultExportExpression(sourceFile)
1518
- return defaultExport ? unwrapArrayLiteral(defaultExport) : null
1519
- })()
1520
- if (!middlewareArray) continue
1521
- let index = 0
1522
- for (const element of middlewareArray.elements) {
1523
- if (!ts.isObjectLiteralExpression(element)) continue
1524
- const middlewareId = readStringPropertyInitializer(element, 'id')
1525
- if (!middlewareId) {
1526
- unresolved.push({
1527
- code: 'unresolved-static-contract',
1528
- kind: 'page-middleware',
1529
- id: `${sourcePath}#${index}`,
1530
- source: { sourcePath, line: nodeLine(sourceFile, element) },
1531
- })
1532
- index += 1
1533
- continue
1534
- }
1535
- const metadata: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]> = { surface }
1536
- const mode = readStringPropertyInitializer(element, 'mode')
1537
- if (mode) metadata.mode = mode
1538
- const priorityInitializer = getObjectPropertyInitializer(element, 'priority')
1539
- if (priorityInitializer && ts.isNumericLiteral(priorityInitializer)) {
1540
- metadata.priority = Number(priorityInitializer.text)
1541
- }
1542
- facts.push({
1543
- kind: 'page-middleware',
1544
- id: middlewareId,
1545
- source: { sourcePath, exportName: 'middleware', line: nodeLine(sourceFile, element) },
1546
- metadata,
1547
- })
1548
- index += 1
1549
- }
1550
- }
1551
- return { facts: dedupeOwnedContractFacts(facts), unresolved }
1552
- }
1553
-
1554
- function extractSetupContract(
1555
- moduleId: string,
1556
- setupFilePath: string | null,
1557
- sourcePath: string | null,
1558
- ): ModuleOwnedContractFact | null {
1559
- if (!setupFilePath || !sourcePath) return null
1560
- const sourceFile = readSourceFile(setupFilePath)
1561
- if (!sourceFile) return null
1562
- const setupObject =
1563
- findObjectLiteralDeclaration(sourceFile, 'setup') ??
1564
- (() => {
1565
- const defaultExport = findDefaultExportExpression(sourceFile)
1566
- if (!defaultExport) return null
1567
- const unwrapped = unwrapExpression(defaultExport)
1568
- return ts.isObjectLiteralExpression(unwrapped) ? unwrapped : null
1569
- })()
1570
- if (!setupObject) return null
1571
- const hooks = readMethodBearingPropertyNames(setupObject)
1572
- const metadata: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]> = {}
1573
- if (hooks.length > 0) metadata.hooks = hooks
1574
- const roleFeatures = getObjectPropertyInitializer(setupObject, 'defaultRoleFeatures')
1575
- if (roleFeatures && ts.isObjectLiteralExpression(roleFeatures)) {
1576
- const roles = readObjectKeyNames(roleFeatures)
1577
- if (roles.length > 0) metadata.roles = roles
1578
- }
1579
- const customerRoleFeatures = getObjectPropertyInitializer(setupObject, 'defaultCustomerRoleFeatures')
1580
- if (customerRoleFeatures && ts.isObjectLiteralExpression(customerRoleFeatures)) {
1581
- const customerRoles = readObjectKeyNames(customerRoleFeatures)
1582
- if (customerRoles.length > 0) metadata.customerRoles = customerRoles
1583
- }
1584
- return {
1585
- kind: 'setup',
1586
- id: `${moduleId}:setup`,
1587
- source: { sourcePath, exportName: 'setup', line: nodeLine(sourceFile, setupObject) },
1588
- ...(Object.keys(metadata).length > 0 ? { metadata } : {}),
1589
- }
1590
- }
1591
-
1592
- function extractEncryptionContracts(
1593
- encryptionFilePath: string | null,
1594
- sourcePath: string | null,
1595
- ): ModuleOwnedContractFact[] {
1596
- if (!encryptionFilePath || !sourcePath) return []
1597
- const sourceFile = readSourceFile(encryptionFilePath)
1598
- if (!sourceFile) return []
1599
- const mapsArray =
1600
- findArrayLiteralDeclaration(sourceFile, 'defaultEncryptionMaps') ??
1601
- findArrayLiteralDeclaration(sourceFile, 'encryptionMaps')
1602
- if (!mapsArray) return []
1603
- const facts: ModuleOwnedContractFact[] = []
1604
- for (const element of mapsArray.elements) {
1605
- if (!ts.isObjectLiteralExpression(element)) continue
1606
- const entityId = readStringPropertyInitializer(element, 'entityId')
1607
- if (!entityId) continue
1608
- const fieldsInitializer = getObjectPropertyInitializer(element, 'fields')
1609
- const fields: string[] = []
1610
- if (fieldsInitializer) {
1611
- const fieldsArray = unwrapArrayLiteral(fieldsInitializer)
1612
- if (fieldsArray) {
1613
- for (const fieldElement of fieldsArray.elements) {
1614
- if (!ts.isObjectLiteralExpression(fieldElement)) continue
1615
- const field = readStringPropertyInitializer(fieldElement, 'field')
1616
- if (field) fields.push(field)
1617
- }
1618
- }
1619
- }
1620
- facts.push({
1621
- kind: 'encryption',
1622
- id: entityId,
1623
- source: { sourcePath, exportName: 'defaultEncryptionMaps', line: nodeLine(sourceFile, element) },
1624
- ...(fields.length > 0 ? { metadata: { fields: fields.sort((left, right) => left.localeCompare(right)) } } : {}),
1625
- })
1626
- }
1627
- return dedupeOwnedContractFacts(facts)
1628
- }
1629
-
1630
- function extractCustomEntityContracts(
1631
- ceFilePath: string | null,
1632
- sourcePath: string | null,
1633
- ): ModuleOwnedContractFact[] {
1634
- if (!ceFilePath || !sourcePath) return []
1635
- const entityIds = [...collectCustomFieldEntityIds(ceFilePath)].sort((left, right) => left.localeCompare(right))
1636
- return entityIds.map((id) => ({
1637
- kind: 'custom-entity' as const,
1638
- id,
1639
- source: { sourcePath, exportName: 'default' },
1640
- }))
1641
- }
1642
-
1643
- /**
1644
- * Keyed AI override exports. Agent *extensions* (`aiAgentExtensions`) patch an agent
1645
- * additively and belong to the `ai.extensions` array; these two replace or disable an
1646
- * entry by key and map to exact `ai.agents.<id>` / `ai.tools.<id>` override targets.
1647
- */
1648
- const AI_OVERRIDE_EXPORTS = [
1649
- { exportName: 'aiAgentOverrides', target: 'agent' },
1650
- { exportName: 'aiToolOverrides', target: 'tool' },
1651
- ] as const
1652
-
1653
- function extractAiExtensionContracts(
1654
- moduleRoot: string,
1655
- sourceRoot: string,
1656
- ): ModuleOwnedContractFact[] {
1657
- const facts: ModuleOwnedContractFact[] = []
1658
- const sources: Array<{ basename: string; relativeDir: string }> = [
1659
- { basename: 'ai-agents', relativeDir: '.' },
1660
- { basename: 'ai-tools', relativeDir: '.' },
1661
- { basename: 'ai-agents.extensions', relativeDir: '.' },
1662
- { basename: 'ai-tools.extensions', relativeDir: '.' },
1663
- ]
1664
- for (const entry of sources) {
1665
- const filePath = resolveConventionFile(path.join(moduleRoot, entry.relativeDir), entry.basename)
1666
- if (!filePath) continue
1667
- const sourceFile = readSourceFile(filePath)
1668
- if (!sourceFile) continue
1669
- const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath)
1670
- const extensionsArray = findArrayLiteralDeclaration(sourceFile, 'aiAgentExtensions')
1671
- if (extensionsArray) {
1672
- for (const element of extensionsArray.elements) {
1673
- const objectLiteral = ts.isObjectLiteralExpression(element)
1674
- ? element
1675
- : ts.isCallExpression(element) && element.arguments[0] && ts.isObjectLiteralExpression(element.arguments[0])
1676
- ? element.arguments[0]
1677
- : null
1678
- if (!objectLiteral) continue
1679
- const targetAgentId = readStringPropertyInitializer(objectLiteral, 'targetAgentId')
1680
- if (!targetAgentId) continue
1681
- facts.push({
1682
- kind: 'ai-extension',
1683
- id: `agent-extension:${targetAgentId}`,
1684
- source: { sourcePath, exportName: 'aiAgentExtensions', line: nodeLine(sourceFile, objectLiteral) },
1685
- metadata: { target: 'agent', mode: 'extension', targetAgentId },
1686
- })
1687
- }
1688
- }
1689
- // `aiAgentOverrides` / `aiToolOverrides` are keyed replace-or-disable maps; only
1690
- // their keys are contract facts. Values (agent/tool definitions, `null` disables)
1691
- // are never read or serialized.
1692
- for (const { exportName, target } of AI_OVERRIDE_EXPORTS) {
1693
- const overridesObject = findObjectLiteralDeclaration(sourceFile, exportName)
1694
- if (!overridesObject) continue
1695
- for (const property of overridesObject.properties) {
1696
- const overrideId = getPropertyName(property)
1697
- if (!overrideId) continue
1698
- facts.push({
1699
- kind: 'ai-extension',
1700
- id: `${target}-override:${overrideId}`,
1701
- source: { sourcePath, exportName, line: nodeLine(sourceFile, property) },
1702
- metadata: { target, mode: 'override', overrideKey: overrideId },
1703
- })
1704
- }
1705
- }
1706
- }
1707
- return dedupeOwnedContractFacts(facts)
1708
- }
1709
-
1710
- function extractGeneratorPluginContracts(
1711
- moduleRoot: string,
1712
- sourceRoot: string,
1713
- ): { facts: ModuleOwnedContractFact[]; unresolved: ModuleFactDiagnostic[] } {
1714
- const filePath = resolveConventionFile(moduleRoot, 'generators')
1715
- if (!filePath) return { facts: [], unresolved: [] }
1716
- const sourceFile = readSourceFile(filePath)
1717
- if (!sourceFile) return { facts: [], unresolved: [] }
1718
- const pluginsArray = findArrayLiteralDeclaration(sourceFile, 'generatorPlugins')
1719
- if (!pluginsArray) return { facts: [], unresolved: [] }
1720
- const sourcePath = toPortableSourcePath(moduleRoot, sourceRoot, filePath)
1721
- const initializers = buildVariableInitializerMap(sourceFile)
1722
- const facts: ModuleOwnedContractFact[] = []
1723
- const unresolved: ModuleFactDiagnostic[] = []
1724
- let index = 0
1725
- for (const element of pluginsArray.elements) {
1726
- const objectLiteral = resolveToObjectLiteral(element, initializers)
1727
- if (!objectLiteral) {
1728
- index += 1
1729
- continue
1730
- }
1731
- const pluginId = readStringPropertyInitializer(objectLiteral, 'id')
1732
- if (!pluginId) {
1733
- unresolved.push({
1734
- code: 'unresolved-static-contract',
1735
- kind: 'generator-plugin',
1736
- id: `${sourcePath}#${index}`,
1737
- source: { sourcePath, exportName: 'generatorPlugins', line: nodeLine(sourceFile, objectLiteral) },
1738
- })
1739
- index += 1
1740
- continue
1741
- }
1742
- const metadata: Record<string, ModuleFactSafeScalar | ModuleFactSafeScalar[]> = {}
1743
- const conventionFile = readStringPropertyInitializer(objectLiteral, 'conventionFile')
1744
- if (conventionFile) metadata.conventionFile = conventionFile
1745
- const outputFileName = readStringPropertyInitializer(objectLiteral, 'outputFileName')
1746
- if (outputFileName) metadata.outputFileName = outputFileName
1747
- facts.push({
1748
- kind: 'generator-plugin',
1749
- id: pluginId,
1750
- source: { sourcePath, exportName: 'generatorPlugins', line: nodeLine(sourceFile, objectLiteral) },
1751
- ...(Object.keys(metadata).length > 0 ? { metadata } : {}),
1752
- })
1753
- index += 1
1754
- }
1755
- return { facts: dedupeOwnedContractFacts(facts), unresolved }
1756
- }
1757
-
1758
- function compareSourceRefs(left: ModuleFactSourceRef, right: ModuleFactSourceRef): number {
1759
- return (
1760
- left.sourcePath.localeCompare(right.sourcePath) ||
1761
- (left.line ?? 0) - (right.line ?? 0) ||
1762
- (left.exportName ?? '').localeCompare(right.exportName ?? '')
1763
- )
1764
- }
1765
-
1766
- function sameSourceRef(left: ModuleFactSourceRef, right: ModuleFactSourceRef): boolean {
1767
- return (
1768
- left.sourcePath === right.sourcePath &&
1769
- (left.line ?? null) === (right.line ?? null) &&
1770
- (left.exportName ?? null) === (right.exportName ?? null)
1771
- )
1772
- }
1773
-
1774
- // Every proven `(kind, id)` reaches `factSources`. Facts whose declaration site is already
1775
- // serialized inline (pages, CLI commands, routes, AI tools/agents, reused contributions) and
1776
- // the owned families (which carry `source` inside `ownedContracts`) are emitted as a typed
1777
- // `factRef` pointer instead of a duplicated source ref, so the index stays uniform without
1778
- // copying provenance payloads past the determinism byte guard. Duplicate detection runs over
1779
- // ALL candidates regardless of which projection they take.
1780
- type ProvenanceCandidate = {
1781
- kind: ModuleFactSourceKind
1782
- id: string
1783
- source?: ModuleFactSourceRef
1784
- factRef?: ModuleFactRef
1785
- }
1786
-
1787
- function provenanceCandidateOrderKey(candidate: ProvenanceCandidate): string {
1788
- if (candidate.source) {
1789
- return JSON.stringify([
1790
- 0,
1791
- candidate.source.sourcePath,
1792
- candidate.source.line ?? 0,
1793
- candidate.source.exportName ?? '',
1794
- ])
1795
- }
1796
- return JSON.stringify([1, candidate.factRef?.factSection ?? '', candidate.factRef?.factKey ?? ''])
1797
- }
1798
-
1799
- function buildProvenanceIndex(candidates: readonly ProvenanceCandidate[]): {
1800
- factSources: ModuleFactSourceEntry[]
1801
- diagnostics: ModuleFactDiagnostic[]
1802
- } {
1803
- const grouped = new Map<string, ProvenanceCandidate[]>()
1804
- for (const candidate of candidates) {
1805
- const key = JSON.stringify([candidate.kind, candidate.id])
1806
- const group = grouped.get(key) ?? []
1807
- group.push(candidate)
1808
- grouped.set(key, group)
1809
- }
1810
- // Keep the provenance index compact: sourcePath is the provenance and (kind,id) the
1811
- // stable identity, so exportName and line (recoverable from source) are dropped here.
1812
- // Owned families retain the richer source ref inside `ownedContracts`.
1813
- const factSources: ModuleFactSourceEntry[] = []
1814
- const diagnostics: ModuleFactDiagnostic[] = []
1815
- for (const group of grouped.values()) {
1816
- const sorted = [...group].sort(
1817
- (left, right) => provenanceCandidateOrderKey(left).localeCompare(provenanceCandidateOrderKey(right)),
1818
- )
1819
- const canonical = sorted[0]
1820
- factSources.push(
1821
- canonical.factRef
1822
- ? {
1823
- kind: canonical.kind,
1824
- id: canonical.id,
1825
- factRef: {
1826
- factSection: canonical.factRef.factSection,
1827
- ...(canonical.factRef.factKey === canonical.id
1828
- ? {}
1829
- : { factKey: canonical.factRef.factKey }),
1830
- },
1831
- }
1832
- : {
1833
- kind: canonical.kind,
1834
- id: canonical.id,
1835
- source: { sourcePath: (canonical.source as ModuleFactSourceRef).sourcePath },
1836
- },
1837
- )
1838
- const canonicalKey = provenanceCandidateOrderKey(canonical)
1839
- for (const rejected of sorted.slice(1)) {
1840
- if (provenanceCandidateOrderKey(rejected) === canonicalKey) continue
1841
- diagnostics.push({
1842
- code: 'duplicate-source',
1843
- kind: rejected.kind,
1844
- id: rejected.id,
1845
- ...(rejected.source ? { source: rejected.source } : {}),
1846
- })
1847
- }
1848
- }
1849
- factSources.sort(
1850
- (left, right) => left.kind.localeCompare(right.kind) || left.id.localeCompare(right.id),
1851
- )
1852
- diagnostics.sort(
1853
- (left, right) =>
1854
- left.code.localeCompare(right.code) ||
1855
- left.kind.localeCompare(right.kind) ||
1856
- left.id.localeCompare(right.id) ||
1857
- compareSourceRefs(left.source ?? { sourcePath: '' }, right.source ?? { sourcePath: '' }),
1858
- )
1859
- return { factSources, diagnostics }
1860
- }
1861
-
1862
- function buildOwnedContracts(
1863
- families: ReadonlyArray<[ModuleOwnedContractKind, ModuleOwnedContractFact[]]>,
1864
- ): ModuleOwnedContracts | undefined {
1865
- const result: ModuleOwnedContracts = {}
1866
- for (const [kind, facts] of families) {
1867
- if (facts.length === 0) continue
1868
- // Drop the (constant/redundant) exportName from the emitted source ref; the line
1869
- // anchor plus sourcePath already pin the declaration and keep the JSON compact.
1870
- result[kind] = facts.map((fact) => ({
1871
- ...fact,
1872
- source: {
1873
- sourcePath: fact.source.sourcePath,
1874
- ...(fact.source.line ? { line: fact.source.line } : {}),
1875
- },
1876
- }))
1877
- }
1878
- return Object.keys(result).length > 0 ? result : undefined
1879
- }
1880
-
1881
- const CONTRIBUTION_KIND_TO_SOURCE_KIND: Partial<Record<string, ModuleFactSourceKind>> = {
1882
- subscriber: 'subscriber',
1883
- }
1884
-
1885
- const REGISTRY_TO_SOURCE_KIND: Partial<Record<string, ModuleFactSourceKind>> = {
1886
- vector: 'vector',
1887
- integration: 'integration',
1888
- payment: 'integration',
1889
- shipping: 'integration',
1890
- currency: 'integration',
1891
- }
1892
-
1893
726
  export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFacts {
1894
- const { moduleId, coreVersion = null, sourcePackage = null, sourceVersion = null } = options
727
+ const { moduleId, coreVersion = null } = options
1895
728
  const moduleRoot = options.moduleRoot
1896
729
  ?? (options.coreSrcRoot ? path.join(options.coreSrcRoot, moduleId) : null)
1897
730
  if (!moduleRoot) {
1898
731
  throw new Error(`[internal] extractModuleFacts requires moduleRoot or coreSrcRoot for module "${moduleId}"`)
1899
732
  }
1900
- const sourceRoot = toPortableSourceRoot(moduleId, sourcePackage)
1901
733
 
1902
734
  const entitiesFilePath =
1903
735
  resolveConventionFile(path.join(moduleRoot, 'data'), 'entities') ??
@@ -1911,11 +743,7 @@ export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFa
1911
743
  const notificationsFilePath = resolveConventionFile(moduleRoot, 'notifications')
1912
744
  const cliFilePath = resolveConventionFile(moduleRoot, 'cli')
1913
745
  const indexFilePath = resolveConventionFile(moduleRoot, 'index')
1914
- const setupFilePath = resolveConventionFile(moduleRoot, 'setup')
1915
- const encryptionFilePath = resolveConventionFile(moduleRoot, 'encryption')
1916
-
1917
- const portableOf = (filePath: string | null): string | null =>
1918
- filePath ? toPortableSourcePath(moduleRoot, sourceRoot, filePath) : null
746
+ const backendDir = path.join(moduleRoot, 'backend')
1919
747
 
1920
748
  const warnings: string[] = []
1921
749
  const { title, description } = extractModuleMeta(indexFilePath)
@@ -1925,247 +753,21 @@ export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFa
1925
753
  const aclFeatures = extractAclFeatures(aclFilePath)
1926
754
 
1927
755
  const { source: registrySource, description: registryDescription } = resolveRegistrySource(options)
1928
- const apiRouteSourcePaths = extractApiRouteSourcePaths(moduleId, moduleRoot, sourceRoot)
1929
- const apiRoutes = extractApiRoutes(moduleId, registrySource, registryDescription, apiRouteSourcePaths, warnings)
1930
- const diRegistrations = extractDiRegistrationFacts(diFilePath, portableOf(diFilePath) ?? '')
1931
- const diTokens = deriveLegacyDiTokens(diRegistrations.facts)
1932
- for (const legacyToken of extractDiTokens(diFilePath)) {
1933
- if (!diTokens.includes(legacyToken)) diTokens.push(legacyToken)
1934
- }
756
+ const apiRoutes = extractApiRoutes(moduleId, registrySource, registryDescription, warnings)
757
+ const diTokens = extractDiTokens(diFilePath)
1935
758
  const searchEntities = extractSearchEntities(searchFilePath, warnings)
1936
759
  const notifications = extractNotifications(notificationsFilePath, warnings)
1937
760
  const cli = extractCli(cliFilePath, warnings)
1938
- const cliCommands = cliFilePath
1939
- ? cli.map((command) => ({
1940
- command,
1941
- sourcePath: toPortableSourcePath(moduleRoot, sourceRoot, cliFilePath),
1942
- }))
1943
- : []
1944
- const backendPages = extractModulePages(moduleId, moduleRoot, sourceRoot, 'backend')
1945
- const frontendPages = extractModulePages(moduleId, moduleRoot, sourceRoot, 'frontend')
1946
- const aiTools = extractAiTools(moduleRoot, sourceRoot)
1947
- const aiAgents = extractAiAgents(moduleRoot, sourceRoot)
1948
761
  const hostTokens: ModuleHostTokens = {
1949
762
  entityIds: extractHostEntityIds(entities),
1950
- tableIds: extractTableIds(moduleRoot),
1951
- }
1952
- const extensionSurfaces = extractModuleExtensionFacts({
1953
- moduleId,
1954
- moduleRoot,
1955
- sourceRoot,
1956
- entities,
1957
- events,
1958
- apiRoutes,
1959
- searchEntities,
1960
- notifications,
1961
- aiTools,
1962
- aiAgents,
1963
- })
1964
-
1965
- const indexSourcePath = portableOf(indexFilePath)
1966
- const diSourcePath = portableOf(diFilePath)
1967
- const moduleMetadataFact = extractModuleMetadataContract(indexFilePath, moduleId, indexSourcePath)
1968
- const commandCandidates = extractCommandContractCandidates(moduleId, moduleRoot, sourceRoot)
1969
- const commandFacts = dedupeOwnedContractFacts(
1970
- commandCandidates.map((candidate) => ({
1971
- kind: 'command' as const,
1972
- id: candidate.id,
1973
- source: { sourcePath: candidate.sourcePath },
1974
- })),
1975
- )
1976
- const workers = extractWorkerContracts(moduleId, moduleRoot, sourceRoot)
1977
- const pageMiddleware = extractPageMiddlewareContracts(moduleRoot, sourceRoot)
1978
- const setupFact = extractSetupContract(moduleId, setupFilePath, portableOf(setupFilePath))
1979
- const encryptionFacts = extractEncryptionContracts(encryptionFilePath, portableOf(encryptionFilePath))
1980
- const customEntityFacts = extractCustomEntityContracts(ceFilePath, portableOf(ceFilePath))
1981
- const aiExtensionFacts = extractAiExtensionContracts(moduleRoot, sourceRoot)
1982
- const generatorPlugins = extractGeneratorPluginContracts(moduleRoot, sourceRoot)
1983
- const diContractFacts = dedupeOwnedContractFacts(
1984
- diRegistrations.facts.map((fact) => ({
1985
- kind: 'di-registration' as const,
1986
- id: fact.token,
1987
- source: fact.source,
1988
- metadata: {
1989
- registrationKind: fact.registrationKind,
1990
- ...(fact.providerSymbol ? { providerSymbol: fact.providerSymbol } : {}),
1991
- ...(fact.lifetime ? { lifetime: fact.lifetime } : {}),
1992
- ...(fact.injectionMode ? { injectionMode: fact.injectionMode } : {}),
1993
- },
1994
- })),
1995
- )
1996
-
1997
- const ownedContracts = buildOwnedContracts([
1998
- ['module-metadata', moduleMetadataFact ? [moduleMetadataFact] : []],
1999
- ['command', commandFacts],
2000
- ['worker', workers.facts],
2001
- ['page-middleware', pageMiddleware.facts],
2002
- ['setup', setupFact ? [setupFact] : []],
2003
- ['encryption', encryptionFacts],
2004
- ['di-registration', diContractFacts],
2005
- ['custom-entity', customEntityFacts],
2006
- ['ai-extension', aiExtensionFacts],
2007
- ['generator-plugin', generatorPlugins.facts],
2008
- ])
2009
-
2010
- const provenanceCandidates: ProvenanceCandidate[] = []
2011
- const pushSource = (kind: ModuleFactSourceKind, id: string, source: ModuleFactSourceRef): void => {
2012
- provenanceCandidates.push({ kind, id, source })
2013
- }
2014
- const pushRef = (
2015
- kind: ModuleFactSourceKind,
2016
- id: string,
2017
- source: ModuleFactSourceRef,
2018
- factSection: string,
2019
- factKey: string,
2020
- ): void => {
2021
- provenanceCandidates.push({ kind, id, source, factRef: { factSection, factKey } })
2022
- }
2023
- const pushOwnedRefs = (kind: ModuleOwnedContractKind, facts: readonly ModuleOwnedContractFact[]): void => {
2024
- for (const fact of facts) pushRef(kind, fact.id, fact.source, `ownedContracts.${kind}`, fact.id)
2025
- }
2026
- if (moduleMetadataFact) {
2027
- pushRef(
2028
- 'module-metadata',
2029
- moduleId,
2030
- moduleMetadataFact.source,
2031
- 'ownedContracts.module-metadata',
2032
- moduleMetadataFact.id,
2033
- )
2034
- }
2035
- if (entitiesFilePath) {
2036
- const entitiesSourcePath = portableOf(entitiesFilePath) as string
2037
- for (const entity of entities) {
2038
- pushSource('entity', entity.id, { sourcePath: entitiesSourcePath, exportName: entity.class })
2039
- }
2040
- }
2041
- if (eventsFilePath) {
2042
- const eventsSourcePath = portableOf(eventsFilePath) as string
2043
- for (const event of events) pushSource('event', event.id, { sourcePath: eventsSourcePath, exportName: 'events' })
2044
- }
2045
- if (aclFilePath) {
2046
- const aclSourcePath = portableOf(aclFilePath) as string
2047
- for (const feature of aclFeatures) pushSource('acl-feature', feature, { sourcePath: aclSourcePath, exportName: 'features' })
2048
- }
2049
- for (const route of apiRoutes) {
2050
- if (route.sourcePath) {
2051
- pushRef('api-route', route.path, { sourcePath: route.sourcePath }, 'apiRoutes', route.path)
2052
- }
2053
- }
2054
- if (searchFilePath) {
2055
- const searchSourcePath = portableOf(searchFilePath) as string
2056
- for (const entityId of searchEntities) pushSource('search', entityId, { sourcePath: searchSourcePath, exportName: 'searchConfig' })
2057
- }
2058
- if (notificationsFilePath) {
2059
- const notificationsSourcePath = portableOf(notificationsFilePath) as string
2060
- for (const id of notifications) pushSource('notification', id, { sourcePath: notificationsSourcePath })
2061
- }
2062
- for (const command of cliCommands) {
2063
- pushRef('cli-command', command.command, { sourcePath: command.sourcePath }, 'cliCommands', command.command)
2064
- }
2065
- for (const page of backendPages) {
2066
- pushRef('backend-page', page.path, { sourcePath: page.sourcePath }, 'backendPages', page.path)
2067
- }
2068
- for (const page of frontendPages) {
2069
- pushRef('frontend-page', page.path, { sourcePath: page.sourcePath }, 'frontendPages', page.path)
2070
- }
2071
- for (const tool of aiTools) pushRef('ai-tool', tool.name, { sourcePath: tool.sourcePath }, 'aiTools', tool.name)
2072
- for (const agent of aiAgents) pushRef('ai-agent', agent.id, { sourcePath: agent.sourcePath }, 'aiAgents', agent.id)
2073
- for (const candidate of commandCandidates) {
2074
- pushRef('command', candidate.id, { sourcePath: candidate.sourcePath }, 'ownedContracts.command', candidate.id)
2075
- }
2076
- pushOwnedRefs('worker', workers.facts)
2077
- pushOwnedRefs('page-middleware', pageMiddleware.facts)
2078
- if (setupFact) pushOwnedRefs('setup', [setupFact])
2079
- pushOwnedRefs('encryption', encryptionFacts)
2080
- for (const fact of diRegistrations.facts) {
2081
- pushRef('di-registration', fact.token, fact.source, 'ownedContracts.di-registration', fact.token)
2082
- }
2083
- pushOwnedRefs('custom-entity', customEntityFacts)
2084
- pushOwnedRefs('ai-extension', aiExtensionFacts)
2085
- pushOwnedRefs('generator-plugin', generatorPlugins.facts)
2086
- const contributionRef = (contributionId: string): { factSection: string; factKey: string } => ({
2087
- factSection: 'extensionSurfaces.contributions',
2088
- factKey: contributionId,
2089
- })
2090
- for (const contribution of extensionSurfaces.contributions) {
2091
- const contributionSource: ModuleFactSourceRef = {
2092
- sourcePath: contribution.source.path,
2093
- ...(contribution.source.symbol ? { exportName: contribution.source.symbol } : {}),
2094
- }
2095
- const ref = contributionRef(contribution.id)
2096
- if (contribution.kind === 'subscriber') {
2097
- pushRef('subscriber', contribution.id, contributionSource, ref.factSection, ref.factKey)
2098
- continue
2099
- }
2100
- if (contribution.kind === 'specialized-registry') {
2101
- const details = contribution.details as { registry?: string; registryId?: string }
2102
- const sourceKind = details.registry ? REGISTRY_TO_SOURCE_KIND[details.registry] : undefined
2103
- if (sourceKind) {
2104
- pushRef(
2105
- sourceKind,
2106
- details.registryId ?? contribution.id,
2107
- contributionSource,
2108
- ref.factSection,
2109
- ref.factKey,
2110
- )
2111
- }
2112
- continue
2113
- }
2114
- const mapped = CONTRIBUTION_KIND_TO_SOURCE_KIND[contribution.kind]
2115
- if (mapped) pushRef(mapped, contribution.id, contributionSource, ref.factSection, ref.factKey)
2116
- }
2117
- // extension-host / extension-contribution provenance is emitted as typed `factRef` pointers
2118
- // rather than duplicated source refs: those facts already carry their own `source` inside
2119
- // `extensionSurfaces.{hosts,contributions}`, and copying every declared host spot (hundreds
2120
- // per large module) would push the shared JSON past the determinism byte guard asserted in
2121
- // `module-facts.bc-guard.test.ts`.
2122
- // Hosts are indexed by their registry `key`: `id` repeats across families (an entity id is
2123
- // also an api-entity id), while `key` is unique inside a module.
2124
- for (const host of extensionSurfaces.hosts) {
2125
- provenanceCandidates.push({
2126
- kind: 'extension-host',
2127
- id: host.key,
2128
- ...(host.source.kind === 'fact-ref' ? {} : { source: { sourcePath: host.source.path } }),
2129
- factRef: { factSection: 'extensionSurfaces.hosts', factKey: host.key },
2130
- })
2131
- }
2132
- for (const contribution of extensionSurfaces.contributions) {
2133
- pushRef(
2134
- 'extension-contribution',
2135
- contribution.id,
2136
- { sourcePath: contribution.source.path },
2137
- 'extensionSurfaces.contributions',
2138
- contribution.id,
2139
- )
763
+ tableIds: extractTableIds(backendDir),
2140
764
  }
2141
765
 
2142
- const provenance = buildProvenanceIndex(provenanceCandidates)
2143
- const factDiagnostics = [
2144
- ...provenance.diagnostics,
2145
- ...workers.unresolved,
2146
- ...pageMiddleware.unresolved,
2147
- ...generatorPlugins.unresolved,
2148
- ...diRegistrations.unresolvedTokens.map((token): ModuleFactDiagnostic => ({
2149
- code: 'unresolved-static-contract',
2150
- kind: 'di-registration',
2151
- id: token,
2152
- ...(diSourcePath ? { source: { sourcePath: diSourcePath, exportName: token } } : {}),
2153
- })),
2154
- ].sort(
2155
- (left, right) =>
2156
- left.code.localeCompare(right.code) ||
2157
- left.kind.localeCompare(right.kind) ||
2158
- left.id.localeCompare(right.id),
2159
- )
2160
-
2161
- const facts: ModuleFacts = {
766
+ return {
2162
767
  module: moduleId,
2163
768
  title,
2164
769
  description,
2165
770
  coreVersion,
2166
- sourcePackage,
2167
- sourceVersion,
2168
- sourceRoot,
2169
771
  entities,
2170
772
  events,
2171
773
  aclFeatures,
@@ -2175,43 +777,20 @@ export function extractModuleFacts(options: ExtractModuleFactsOptions): ModuleFa
2175
777
  hostTokens,
2176
778
  notifications,
2177
779
  cli,
2178
- backendPages,
2179
- frontendPages,
2180
- cliCommands,
2181
- aiTools,
2182
- aiAgents,
2183
- extensionSurfaces,
2184
- ...(provenance.factSources.length > 0 ? { factSources: provenance.factSources } : {}),
2185
- ...(ownedContracts ? { ownedContracts } : {}),
2186
- ...(factDiagnostics.length > 0 ? { factDiagnostics } : {}),
2187
780
  warnings,
2188
781
  }
2189
-
2190
- // Exact unified override targets (spec 2026-08-02-module-facts-exact-override-targets).
2191
- // Adapters consume the fully-populated owned facts + contributions above.
2192
- const overrides = collectModuleOverrideTargets(facts, buildFactSourceLookup(facts))
2193
- if (overrides.overrideTargets.length > 0) facts.overrideTargets = overrides.overrideTargets
2194
- if (overrides.overrideTargetDiagnostics.length > 0) {
2195
- facts.overrideTargetDiagnostics = overrides.overrideTargetDiagnostics
2196
- }
2197
- return facts
2198
782
  }
2199
783
 
2200
784
  export interface ModuleFactsJsonEvent {
2201
785
  id: string
2202
786
  category: string | null
2203
787
  entity: string | null
2204
- clientBroadcast?: boolean
2205
- portalBroadcast?: boolean
2206
788
  }
2207
789
 
2208
790
  export interface ModuleFactsJsonEntry {
2209
791
  title: string | null
2210
792
  description: string | null
2211
793
  coreVersion: string | null
2212
- sourcePackage: string | null
2213
- sourceVersion: string | null
2214
- sourceRoot: string
2215
794
  entities: ModuleEntityFact[]
2216
795
  events: ModuleFactsJsonEvent[]
2217
796
  aclFeatures: string[]
@@ -2221,54 +800,32 @@ export interface ModuleFactsJsonEntry {
2221
800
  hostTokens: ModuleHostTokens
2222
801
  notifications: string[]
2223
802
  cli: string[]
2224
- backendPages: ModulePageFact[]
2225
- frontendPages: ModulePageFact[]
2226
- cliCommands: ModuleCliCommandFact[]
2227
- aiTools: ModuleAiToolFact[]
2228
- aiAgents: ModuleAiAgentFact[]
2229
- extensionSurfaces?: ModuleExtensionSurfaceFacts
2230
- factSources?: ModuleFactSourceEntry[]
2231
- ownedContracts?: ModuleOwnedContracts
2232
- factDiagnostics?: ModuleFactDiagnostic[]
2233
- overrideTargets?: ModuleOverrideTarget[]
2234
- overrideTargetDiagnostics?: ModuleOverrideTargetDiagnostic[]
2235
803
  }
2236
804
 
2237
805
  const EMPTY_SECTION_MARKER = '_none_'
2238
806
 
2239
- function renderVersionStamp(
2240
- coreVersion: string | null,
2241
- sourcePackage: string | null,
2242
- sourceVersion: string | null,
2243
- ): string {
2244
- if (sourcePackage) {
2245
- return `<!-- generated from ${sourcePackage} ${sourceVersion || '<unknown>'}; core ${coreVersion || '<unknown>'} — R1 staleness stamp -->`
2246
- }
807
+ function renderVersionStamp(coreVersion: string | null): string {
2247
808
  const version = coreVersion && coreVersion.length > 0 ? coreVersion : '<unknown>'
2248
809
  return `<!-- generated from @open-mercato/core ${version} — R1 staleness stamp -->`
2249
810
  }
2250
811
 
2251
- function renderEntitiesSection(entities: ModuleEntityFact[], lookup: FactSourceLookup): string {
812
+ function renderEntitiesSection(entities: ModuleEntityFact[]): string {
2252
813
  if (entities.length === 0) return `## Entities\n\n${EMPTY_SECTION_MARKER}`
2253
- const header = '| Entity ID | Class | Table | Editable | CustomFields | Source |'
2254
- const divider = '|---|---|---|---|---|---|'
814
+ const header = '| Entity ID | Class | Table | Editable | CustomFields |'
815
+ const divider = '|---|---|---|---|---|'
2255
816
  const rows = entities.map(
2256
817
  (entity) =>
2257
- `| ${entity.id} | ${entity.class} | ${entity.table} | ${entity.editable ? 'yes' : 'no'} | ${entity.customFields ? 'yes' : 'no'} | ${renderLookedUpSource(lookup, 'entity', entity.id)} |`,
818
+ `| ${entity.id} | ${entity.class} | ${entity.table} | ${entity.editable ? 'yes' : 'no'} | ${entity.customFields ? 'yes' : 'no'} |`,
2258
819
  )
2259
820
  return ['## Entities', '', header, divider, ...rows].join('\n')
2260
821
  }
2261
822
 
2262
- function renderEventsSection(events: ModuleEventFact[], lookup: FactSourceLookup): string {
823
+ function renderEventsSection(events: ModuleEventFact[]): string {
2263
824
  const heading = `## Events (${events.length})`
2264
825
  if (events.length === 0) return `${heading}\n\n${EMPTY_SECTION_MARKER}`
2265
- const header = '| ID | Category | Entity | Browser transport | Source |'
2266
- const divider = '|---|---|---|---|---|'
2267
- const rows = events.map((event) => {
2268
- const transports = [event.clientBroadcast ? 'client' : null, event.portalBroadcast ? 'portal' : null]
2269
- .filter((value): value is string => value !== null)
2270
- return `| ${event.id} | ${event.category ?? '—'} | ${event.entity ?? '—'} | ${transports.join(', ') || '—'} | ${renderLookedUpSource(lookup, 'event', event.id)} |`
2271
- })
826
+ const header = '| ID | Category | Entity |'
827
+ const divider = '|---|---|---|'
828
+ const rows = events.map((event) => `| ${event.id} | ${event.category ?? '—'} | ${event.entity ?? '—'} |`)
2272
829
  return [heading, '', header, divider, ...rows].join('\n')
2273
830
  }
2274
831
 
@@ -2277,73 +834,6 @@ function renderInlineListSection(heading: string, values: string[]): string {
2277
834
  return `${heading}\n\n${values.join(' · ')}`
2278
835
  }
2279
836
 
2280
- function renderSourceLinkedListSection(
2281
- heading: string,
2282
- values: readonly string[],
2283
- lookup: FactSourceLookup,
2284
- kind: ModuleFactSourceKind,
2285
- idColumnLabel: string,
2286
- ): string {
2287
- if (values.length === 0) return `${heading}\n\n${EMPTY_SECTION_MARKER}`
2288
- const rows = values.map((value) => `| ${value} | ${renderLookedUpSource(lookup, kind, value)} |`)
2289
- return [heading, '', `| ${idColumnLabel} | Source |`, '|---|---|', ...rows].join('\n')
2290
- }
2291
-
2292
- function renderSourceLink(sourcePath: string): string {
2293
- return `[${sourcePath}](../../../${sourcePath})`
2294
- }
2295
-
2296
- function renderSourceRefLink(source: ModuleFactSourceRef): string {
2297
- const anchor = source.line ? `#L${source.line}` : ''
2298
- const label = source.line ? `${source.sourcePath}:${source.line}` : source.sourcePath
2299
- return `[${label}](../../../${source.sourcePath}${anchor})`
2300
- }
2301
-
2302
-
2303
- function renderLookedUpSource(lookup: FactSourceLookup, kind: ModuleFactSourceKind, id: string): string {
2304
- const source = lookup(kind, id)
2305
- return source ? renderSourceRefLink(source) : '—'
2306
- }
2307
-
2308
- function renderSafeScalar(value: ModuleFactSafeScalar | ModuleFactSafeScalar[] | undefined): string {
2309
- if (value === undefined || value === null) return '—'
2310
- if (Array.isArray(value)) return value.length > 0 ? value.map((entry) => String(entry)).join(', ') : '—'
2311
- return String(value)
2312
- }
2313
-
2314
- function renderOwnedContractMetadata(metadata: ModuleOwnedContractFact['metadata']): string {
2315
- if (!metadata) return '—'
2316
- const keys = Object.keys(metadata).sort((left, right) => left.localeCompare(right))
2317
- if (keys.length === 0) return '—'
2318
- return keys.map((key) => `${key}=${renderSafeScalar(metadata[key])}`).join('; ')
2319
- }
2320
-
2321
- function renderOwnedContractSection(
2322
- heading: string,
2323
- facts: readonly ModuleOwnedContractFact[] | undefined,
2324
- ): string {
2325
- if (!facts || facts.length === 0) return ''
2326
- const rows = facts.map(
2327
- (fact) => `| ${fact.id} | ${renderOwnedContractMetadata(fact.metadata)} | ${renderSourceRefLink(fact.source)} |`,
2328
- )
2329
- return [heading, '', '| ID | Metadata | Source |', '|---|---|---|', ...rows].join('\n')
2330
- }
2331
-
2332
- function renderDiRegistrationsSection(facts: readonly ModuleOwnedContractFact[] | undefined): string {
2333
- if (!facts || facts.length === 0) return ''
2334
- const rows = facts.map((fact) => {
2335
- const metadata = fact.metadata ?? {}
2336
- return `| ${fact.id} | ${renderSafeScalar(metadata.registrationKind)} | ${renderSafeScalar(metadata.lifetime)} | ${renderSafeScalar(metadata.injectionMode)} | ${renderSafeScalar(metadata.providerSymbol)} | ${renderSourceRefLink(fact.source)} |`
2337
- })
2338
- return [
2339
- '## DI registrations (rich)',
2340
- '',
2341
- '| Token | Kind | Lifetime | Injection | Provider | Source |',
2342
- '|---|---|---|---|---|---|',
2343
- ...rows,
2344
- ].join('\n')
2345
- }
2346
-
2347
837
  function describeAuthRule(rule: ApiRouteAuthRule | undefined): string {
2348
838
  if (!rule) return 'public'
2349
839
  if (rule.requireFeatures && rule.requireFeatures.length > 0) return rule.requireFeatures.join(', ')
@@ -2366,253 +856,44 @@ function renderApiRouteAuthCell(route: ModuleApiRouteFact): string {
2366
856
 
2367
857
  function renderApiRoutesSection(routes: ModuleApiRouteFact[]): string {
2368
858
  if (routes.length === 0) return `## API routes\n\n${EMPTY_SECTION_MARKER}`
2369
- const header = '| Path | Methods | Auth (per-method requireFeatures) | Source |'
2370
- const divider = '|---|---|---|---|'
859
+ const header = '| Path | Methods | Auth (per-method requireFeatures) |'
860
+ const divider = '|---|---|---|'
2371
861
  const rows = routes.map(
2372
- (route) => `| ${route.path} | ${route.methods.join(' ')} | ${renderApiRouteAuthCell(route)} | ${route.sourcePath ? renderSourceLink(route.sourcePath) : '—'} |`,
862
+ (route) => `| ${route.path} | ${route.methods.join(' ')} | ${renderApiRouteAuthCell(route)} |`,
2373
863
  )
2374
864
  return ['## API routes', '', header, divider, ...rows].join('\n')
2375
865
  }
2376
866
 
2377
- function renderLinkedFactsSection(
2378
- heading: string,
2379
- facts: ReadonlyArray<{ label: string; sourcePath: string }>,
2380
- ): string {
2381
- if (facts.length === 0) return `${heading}\n\n${EMPTY_SECTION_MARKER}`
2382
- const rows = facts.map((fact) => `| ${fact.label} | ${renderSourceLink(fact.sourcePath)} |`)
2383
- return [heading, '', '| ID / path | Source |', '|---|---|', ...rows].join('\n')
2384
- }
2385
-
2386
867
  function renderHostTokensSection(hostTokens: ModuleHostTokens): string {
2387
868
  const entityIdsLine = hostTokens.entityIds.length > 0 ? hostTokens.entityIds.join(' · ') : EMPTY_SECTION_MARKER
2388
869
  const tableIdsLine = hostTokens.tableIds.length > 0 ? hostTokens.tableIds.join(' · ') : EMPTY_SECTION_MARKER
2389
870
  return ['## Host extension points', '', `- Entity IDs: ${entityIdsLine}`, `- Table IDs: ${tableIdsLine}`].join('\n')
2390
871
  }
2391
872
 
2392
- function renderExtensionHostContext(host: ModuleExtensionSurfaceFacts['hosts'][number]): string {
2393
- return host.contextContract ?? host.runtimeContract ?? host.scopeContract ?? '—'
2394
- }
2395
-
2396
- function renderExtensionHostSource(host: ModuleExtensionHostFact): string {
2397
- if (host.source.kind === 'fact-ref') return `${host.source.factSection}:${host.source.factKey}`
2398
- return renderSourceRefLink({ sourcePath: host.source.path })
2399
- }
2400
-
2401
- function renderExtensionHostsSection(extensionSurfaces: ModuleExtensionSurfaceFacts): string {
2402
- const boundHosts = extensionSurfaces.hosts.filter((host) => host.bound)
2403
- if (boundHosts.length === 0) return `## UMES hosts\n\n${EMPTY_SECTION_MARKER}`
2404
- const rows = boundHosts.map((host) =>
2405
- `| ${host.id} | ${host.family} | ${host.capabilities.join(', ') || '—'} | ${renderExtensionHostContext(host)} | ${host.stability.toUpperCase()} | ${renderExtensionHostSource(host)} |`,
2406
- )
2407
- return [
2408
- '## UMES hosts',
2409
- '',
2410
- '| ID / pattern | Family | Supports | Context | Stability | Source |',
2411
- '|---|---|---|---|---|---|',
2412
- ...rows,
2413
- ].join('\n')
2414
- }
2415
-
2416
- function compactContributionDetails(contribution: ModuleExtensionContributionFact): string {
2417
- const details = contribution.details as unknown as Record<string, unknown>
2418
- return Object.keys(details).sort((left, right) => left.localeCompare(right)).flatMap((key) => {
2419
- const value = details[key]
2420
- if (value === undefined) return []
2421
- if (Array.isArray(value)) return [`${key}=${value.join(',') || 'none'}`]
2422
- if (value && typeof value === 'object') {
2423
- const nested = Object.keys(value as Record<string, unknown>).sort((left, right) => left.localeCompare(right)).map((nestedKey) => {
2424
- const nestedValue = (value as Record<string, unknown>)[nestedKey]
2425
- return `${nestedKey}:${Array.isArray(nestedValue) ? nestedValue.join(',') : String(nestedValue)}`
2426
- })
2427
- return [`${key}={${nested.join(';')}}`]
2428
- }
2429
- return [`${key}=${String(value)}`]
2430
- }).join('; ')
2431
- }
2432
-
2433
- function renderExtensionContributionsSection(extensionSurfaces: ModuleExtensionSurfaceFacts): string {
2434
- if (extensionSurfaces.contributions.length === 0) return `## UMES contributions\n\n${EMPTY_SECTION_MARKER}`
2435
- const rows = extensionSurfaces.contributions.map((contribution) => {
2436
- const targets = contribution.targets.map((entry) => entry.id).join(', ')
2437
- const resolution = contribution.targets.map((entry) => entry.resolution).join(', ')
2438
- const phases = [...(contribution.phases ?? []), ...(contribution.operations ?? [])].join(', ') || '—'
2439
- const source = renderSourceRefLink({ sourcePath: contribution.source.path })
2440
- return `| ${contribution.id} | ${contribution.kind} | ${targets || '—'} | ${phases} | ${compactContributionDetails(contribution)} | ${resolution || '—'} | ${source} |`
2441
- })
2442
- return [
2443
- '## UMES contributions',
2444
- '',
2445
- '| ID | Kind | Target | Phase / operations | Contract | Resolution | Source |',
2446
- '|---|---|---|---|---|---|---|',
2447
- ...rows,
2448
- ].join('\n')
2449
- }
2450
-
2451
- function renderExtensionDiagnosticsSection(extensionSurfaces: ModuleExtensionSurfaceFacts): string {
2452
- const unbound = extensionSurfaces.hosts.filter((host) => !host.bound)
2453
- if (unbound.length === 0 && extensionSurfaces.unresolved.length === 0) return ''
2454
- const diagnostics = [
2455
- ...unbound.map((host) => `- unbound-helper: ${host.id}`),
2456
- ...extensionSurfaces.unresolved.map((entry) => `- ${entry.reason}: ${entry.key} (${entry.source.path})`),
2457
- ]
2458
- return ['## UMES diagnostics', '', ...diagnostics].join('\n')
2459
- }
2460
-
2461
- function renderExtensionTargetRef(target: ModuleExtensionTargetRef): string {
2462
- const method = target.method ? ` ${target.method}` : ''
2463
- const owner = target.moduleId ? ` @${target.moduleId}` : ''
2464
- return `${target.kind}:${target.id}${method}${owner}`
2465
- }
2466
-
2467
- function renderActivationBindingsSection(extensionSurfaces: ModuleExtensionSurfaceFacts): string {
2468
- const activations = extensionSurfaces.activations ?? []
2469
- if (activations.length === 0) return `## Active extension bindings\n\n${EMPTY_SECTION_MARKER}`
2470
- const rows = activations.map((activation) => {
2471
- const phases = activation.phases && activation.phases.length > 0 ? activation.phases.join(', ') : '—'
2472
- const source = activation.source ? renderSourceRefLink(activation.source) : '—'
2473
- const bridge = activation.bridge ? `${activation.bridge.factSection}:${activation.bridge.factKey}` : '—'
2474
- return `| ${activation.id} | ${activation.kind} | ${renderExtensionTargetRef(activation.host)} | ${activation.contributionKinds.join(', ') || '—'} | ${phases} | ${bridge} | ${source} |`
2475
- })
2476
- return [
2477
- '## Active extension bindings',
2478
- '',
2479
- '| Activation | Kind | Host | Contribution kinds | Phases | Bridge | Source |',
2480
- '|---|---|---|---|---|---|---|',
2481
- ...rows,
2482
- ].join('\n')
2483
- }
2484
-
2485
- function renderIncomingContributionsSection(extensionSurfaces: ModuleExtensionSurfaceFacts): string {
2486
- const incoming = extensionSurfaces.incoming ?? []
2487
- if (incoming.length === 0) return `## Incoming installed contributions\n\n${EMPTY_SECTION_MARKER}`
2488
- const rows = incoming.map((entry) => {
2489
- const activation = entry.activationId ?? '—'
2490
- const source = entry.source ? renderSourceRefLink(entry.source) : '—'
2491
- return `| ${entry.contributorModuleId} | ${entry.contributionKind} | ${renderExtensionTargetRef(entry.target)} | ${entry.resolution} | ${activation} | ${entry.contributionId} · ${source} |`
2492
- })
2493
- return [
2494
- '## Incoming installed contributions',
2495
- '',
2496
- '| Contributor | Kind | Target | Resolution | Activation | Contribution · Source |',
2497
- '|---|---|---|---|---|---|',
2498
- ...rows,
2499
- ].join('\n')
2500
- }
2501
-
2502
- function renderContributionResolutionsSection(
2503
- extensionSurfaces: ModuleExtensionSurfaceFacts,
2504
- lookup: FactSourceLookup,
2505
- ): string {
2506
- const resolutions = extensionSurfaces.contributionResolutions ?? []
2507
- if (resolutions.length === 0) return `## Contribution resolutions\n\n${EMPTY_SECTION_MARKER}`
2508
- const rows = resolutions.map((resolution) => {
2509
- const activations = resolution.activationIds.length > 0 ? resolution.activationIds.join(', ') : '—'
2510
- const source = renderLookedUpSource(lookup, 'extension-contribution', resolution.contributionId)
2511
- return `| ${resolution.contributionId} | ${renderExtensionTargetRef(resolution.target)} | ${resolution.resolution} | ${activations} | ${source} |`
2512
- })
2513
- return [
2514
- '## Contribution resolutions',
2515
- '',
2516
- '| Contribution | Target | Resolution | Activations | Source |',
2517
- '|---|---|---|---|---|',
2518
- ...rows,
2519
- ].join('\n')
2520
- }
2521
-
2522
- function renderOverrideTargetPath(target: ModuleOverrideTarget): string {
2523
- let display = 'overrides'
2524
- for (let index = 0; index < target.path.length; index += 1) {
2525
- const segment = target.path[index]
2526
- const isKeyedTerminal = target.key !== undefined && index === target.path.length - 1
2527
- display += isKeyedTerminal ? `[${JSON.stringify(segment)}]` : `.${segment}`
2528
- }
2529
- return display
2530
- }
2531
-
2532
- function renderOverrideTargetsSection(targets: readonly ModuleOverrideTarget[] | undefined): string {
2533
- if (!targets || targets.length === 0) return `## Exact override targets\n\n${EMPTY_SECTION_MARKER}`
2534
- const header = '| Domain | Path / key | Supported modes | Referenced fact | Source |'
2535
- const divider = '|---|---|---|---|---|'
2536
- const rows = targets.map((target) => {
2537
- const path = `\`${renderOverrideTargetPath(target)}\``
2538
- const modes = target.modes.join(', ') || '—'
2539
- const fact = `${target.factRef.factSection}:${target.factRef.factKey}`
2540
- return `| ${target.domain} | ${path} | ${modes} | ${fact} | ${renderSourceRefLink(target.source)} |`
2541
- })
2542
- return ['## Exact override targets', '', header, divider, ...rows].join('\n')
2543
- }
2544
-
2545
873
  export function renderModuleFactsMarkdown(facts: ModuleFacts): string {
2546
- const extensionSurfaces = facts.extensionSurfaces ?? { hosts: [], contributions: [], unresolved: [] }
2547
- const lookup = buildFactSourceLookup(facts)
2548
874
  const sections = [
2549
875
  `# ${facts.module} — module facts (generated, do not edit)`,
2550
- renderVersionStamp(facts.coreVersion, facts.sourcePackage, facts.sourceVersion),
2551
- `Source root: ${renderSourceLink(facts.sourceRoot)}`,
876
+ renderVersionStamp(facts.coreVersion),
2552
877
  '',
2553
- renderEntitiesSection(facts.entities, lookup),
878
+ renderEntitiesSection(facts.entities),
2554
879
  '',
2555
- renderEventsSection(facts.events, lookup),
880
+ renderEventsSection(facts.events),
2556
881
  '',
2557
- renderSourceLinkedListSection(
2558
- `## ACL features (${facts.aclFeatures.length})`,
2559
- facts.aclFeatures,
2560
- lookup,
2561
- 'acl-feature',
2562
- 'Feature',
2563
- ),
882
+ renderInlineListSection(`## ACL features (${facts.aclFeatures.length})`, facts.aclFeatures),
2564
883
  '',
2565
884
  renderApiRoutesSection(facts.apiRoutes),
2566
885
  '',
2567
- renderLinkedFactsSection('## Backend pages', facts.backendPages.map((page) => ({ label: page.path, sourcePath: page.sourcePath }))),
2568
- '',
2569
- renderLinkedFactsSection('## Frontend pages', facts.frontendPages.map((page) => ({ label: page.path, sourcePath: page.sourcePath }))),
886
+ renderInlineListSection('## DI service tokens', facts.diTokens),
2570
887
  '',
2571
- renderSourceLinkedListSection('## DI service tokens', facts.diTokens, lookup, 'di-registration', 'Token'),
2572
- '',
2573
- renderSourceLinkedListSection('## Search entities', facts.searchEntities, lookup, 'search', 'Entity ID'),
888
+ renderInlineListSection('## Search entities', facts.searchEntities),
2574
889
  '',
2575
890
  renderHostTokensSection(facts.hostTokens),
2576
891
  '',
2577
- renderExtensionHostsSection(extensionSurfaces),
2578
- '',
2579
- renderExtensionContributionsSection(extensionSurfaces),
2580
- '',
2581
- renderActivationBindingsSection(extensionSurfaces),
2582
- '',
2583
- renderIncomingContributionsSection(extensionSurfaces),
2584
- '',
2585
- renderContributionResolutionsSection(extensionSurfaces, lookup),
892
+ renderInlineListSection('## Notifications', facts.notifications),
2586
893
  '',
2587
- renderExtensionDiagnosticsSection(extensionSurfaces),
2588
- '',
2589
- renderSourceLinkedListSection('## Notifications', facts.notifications, lookup, 'notification', 'Notification ID'),
2590
- '',
2591
- renderLinkedFactsSection('## CLI commands', facts.cliCommands.map((command) => ({ label: command.command, sourcePath: command.sourcePath }))),
2592
- '',
2593
- renderLinkedFactsSection('## AI tools / MCP capabilities', facts.aiTools.map((tool) => ({ label: tool.name, sourcePath: tool.sourcePath }))),
2594
- '',
2595
- renderLinkedFactsSection('## AI agents', facts.aiAgents.map((agent) => ({ label: agent.id, sourcePath: agent.sourcePath }))),
2596
- '',
2597
- renderOverrideTargetsSection(facts.overrideTargets),
894
+ renderInlineListSection('## CLI', facts.cli),
2598
895
  '',
2599
896
  ]
2600
- const owned = facts.ownedContracts ?? {}
2601
- const ownedSections = [
2602
- renderOwnedContractSection('## Owned contract — module metadata', owned['module-metadata']),
2603
- renderOwnedContractSection('## Domain commands', owned.command),
2604
- renderOwnedContractSection('## Workers', owned.worker),
2605
- renderOwnedContractSection('## Page middleware', owned['page-middleware']),
2606
- renderOwnedContractSection('## Setup', owned.setup),
2607
- renderOwnedContractSection('## Encryption', owned.encryption),
2608
- renderDiRegistrationsSection(owned['di-registration']),
2609
- renderOwnedContractSection('## Custom entities', owned['custom-entity']),
2610
- renderOwnedContractSection('## AI extensions', owned['ai-extension']),
2611
- renderOwnedContractSection('## Generator plugins', owned['generator-plugin']),
2612
- ].filter((section) => section.length > 0)
2613
- for (const section of ownedSections) {
2614
- sections.push(section, '')
2615
- }
2616
897
  return sections.join('\n')
2617
898
  }
2618
899
 
@@ -2621,17 +902,8 @@ export function toModuleFactsJsonEntry(facts: ModuleFacts): ModuleFactsJsonEntry
2621
902
  title: facts.title,
2622
903
  description: facts.description,
2623
904
  coreVersion: facts.coreVersion,
2624
- sourcePackage: facts.sourcePackage,
2625
- sourceVersion: facts.sourceVersion,
2626
- sourceRoot: facts.sourceRoot,
2627
905
  entities: facts.entities,
2628
- events: facts.events.map((event) => ({
2629
- id: event.id,
2630
- category: event.category,
2631
- entity: event.entity,
2632
- ...(event.clientBroadcast !== undefined ? { clientBroadcast: event.clientBroadcast } : {}),
2633
- ...(event.portalBroadcast !== undefined ? { portalBroadcast: event.portalBroadcast } : {}),
2634
- })),
906
+ events: facts.events.map((event) => ({ id: event.id, category: event.category, entity: event.entity })),
2635
907
  aclFeatures: facts.aclFeatures,
2636
908
  apiRoutes: facts.apiRoutes,
2637
909
  diTokens: facts.diTokens,
@@ -2639,25 +911,6 @@ export function toModuleFactsJsonEntry(facts: ModuleFacts): ModuleFactsJsonEntry
2639
911
  hostTokens: facts.hostTokens,
2640
912
  notifications: facts.notifications,
2641
913
  cli: facts.cli,
2642
- backendPages: facts.backendPages,
2643
- frontendPages: facts.frontendPages,
2644
- cliCommands: facts.cliCommands,
2645
- aiTools: facts.aiTools,
2646
- aiAgents: facts.aiAgents,
2647
- ...(facts.extensionSurfaces ? { extensionSurfaces: facts.extensionSurfaces } : {}),
2648
- ...(facts.factSources && facts.factSources.length > 0 ? { factSources: facts.factSources } : {}),
2649
- ...(facts.ownedContracts && Object.keys(facts.ownedContracts).length > 0
2650
- ? { ownedContracts: facts.ownedContracts }
2651
- : {}),
2652
- ...(facts.factDiagnostics && facts.factDiagnostics.length > 0
2653
- ? { factDiagnostics: facts.factDiagnostics }
2654
- : {}),
2655
- ...(facts.overrideTargets && facts.overrideTargets.length > 0
2656
- ? { overrideTargets: facts.overrideTargets }
2657
- : {}),
2658
- ...(facts.overrideTargetDiagnostics && facts.overrideTargetDiagnostics.length > 0
2659
- ? { overrideTargetDiagnostics: facts.overrideTargetDiagnostics }
2660
- : {}),
2661
914
  }
2662
915
  }
2663
916
 
@@ -2694,14 +947,9 @@ export interface ExtractAllModuleFactsResult {
2694
947
  factsByModule: Record<string, ModuleFacts>
2695
948
  markdownByModule: Record<string, string>
2696
949
  warnings: string[]
2697
- frameworkMarkdown: string
2698
950
  }
2699
951
 
2700
952
  export function extractAllModuleFacts(options: ExtractAllModuleFactsOptions): ExtractAllModuleFactsResult {
2701
- return withModuleExtensionFactExtractionCache(() => extractAllModuleFactsWithCache(options))
2702
- }
2703
-
2704
- function extractAllModuleFactsWithCache(options: ExtractAllModuleFactsOptions): ExtractAllModuleFactsResult {
2705
953
  const sources: ModuleFactSource[] = options.sources
2706
954
  ? [...options.sources]
2707
955
  : (options.coreSrcRoot
@@ -2719,65 +967,12 @@ function extractAllModuleFactsWithCache(options: ExtractAllModuleFactsOptions):
2719
967
  moduleId: source.moduleId,
2720
968
  moduleRoot: source.moduleRoot,
2721
969
  coreVersion: options.coreVersion ?? null,
2722
- sourcePackage: source.from ?? null,
2723
- sourceVersion: source.packageVersion ?? null,
2724
970
  registryPath: options.registryPath ?? null,
2725
971
  registrySource: options.registrySource ?? null,
2726
972
  })
2727
973
  factsByModule[source.moduleId] = facts
974
+ markdownByModule[source.moduleId] = renderModuleFactsMarkdown(facts)
2728
975
  warnings.push(...facts.warnings)
2729
976
  }
2730
- const surfacesByModule = Object.fromEntries(
2731
- Object.entries(factsByModule).map(([moduleId, facts]) => [
2732
- moduleId,
2733
- facts.extensionSurfaces ?? { hosts: [], contributions: [], unresolved: [] },
2734
- ]),
2735
- )
2736
- const correlated = correlateModuleExtensionFacts({
2737
- surfacesByModule,
2738
- entityIds: new Set(Object.values(factsByModule).flatMap((facts) => facts.entities.map((entity) => entity.id))),
2739
- eventIds: new Set(Object.values(factsByModule).flatMap((facts) => facts.events.map((event) => event.id))),
2740
- apiRoutes: new Set([
2741
- ...Object.values(factsByModule).flatMap((facts) => facts.apiRoutes.map((route) => route.path)),
2742
- ...sources.flatMap((source) => extractKnownApiRouteIds(source.moduleId, source.moduleRoot)),
2743
- ]),
2744
- commandIds: new Set(sources.flatMap((source) => extractKnownCommandIds(source.moduleId, source.moduleRoot))),
2745
- })
2746
- assertNoUnresolvedExtensionTargets(correlated)
2747
- const apiRouteOwners = new Map<string, {
2748
- moduleId: string
2749
- source: ModuleFactSourceRef
2750
- bridges: ApiRouteInterceptorBridge[]
2751
- }>()
2752
- const commandOwners = new Map<string, { moduleId: string; source: ModuleFactSourceRef }>()
2753
- for (const source of [...sources].sort((left, right) => left.moduleId.localeCompare(right.moduleId))) {
2754
- const facts = factsByModule[source.moduleId]
2755
- const sourceRoot = facts ? facts.sourceRoot : source.moduleId
2756
- const owners = extractActivationTargetOwners({
2757
- moduleId: source.moduleId,
2758
- moduleRoot: source.moduleRoot,
2759
- sourceRoot,
2760
- })
2761
- for (const route of owners.apiRoutes) {
2762
- if (!apiRouteOwners.has(route.id)) {
2763
- apiRouteOwners.set(route.id, { moduleId: source.moduleId, source: route.source, bridges: route.bridges })
2764
- }
2765
- }
2766
- for (const command of owners.commands) {
2767
- if (!commandOwners.has(command.id)) commandOwners.set(command.id, { moduleId: source.moduleId, source: command.source })
2768
- }
2769
- }
2770
- const withIncoming = correlateIncomingExtensions({
2771
- surfacesByModule: correlated,
2772
- apiRouteOwners,
2773
- commandOwners,
2774
- })
2775
- for (const moduleId of Object.keys(factsByModule).sort((left, right) => left.localeCompare(right))) {
2776
- factsByModule[moduleId].extensionSurfaces = withIncoming[moduleId]
2777
- markdownByModule[moduleId] = renderModuleFactsMarkdown(factsByModule[moduleId])
2778
- warnings.push(...correlated[moduleId].unresolved.map((entry) =>
2779
- `[module-facts] ${moduleId} ${entry.reason}: ${entry.key} (${entry.source.path})`,
2780
- ))
2781
- }
2782
- return { factsByModule, markdownByModule, warnings, frameworkMarkdown: renderFrameworkExtensionPointsMarkdown() }
977
+ return { factsByModule, markdownByModule, warnings }
2783
978
  }