@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,386 +0,0 @@
1
- import { createHash } from 'node:crypto'
2
- import {
3
- mkdtempSync,
4
- mkdirSync,
5
- readdirSync,
6
- existsSync,
7
- readFileSync,
8
- rmSync,
9
- statSync,
10
- symlinkSync,
11
- writeFileSync,
12
- } from 'node:fs'
13
- import { tmpdir } from 'node:os'
14
- import { dirname, join } from 'node:path'
15
-
16
- import { applyHarnessUpdate, runAgenticSetup } from '../agentic-setup'
17
-
18
- type ManifestEntry = {
19
- path: string
20
- sha256: string
21
- source: string
22
- userEditable: boolean
23
- }
24
-
25
- function write(path: string, content: string): void {
26
- mkdirSync(dirname(path), { recursive: true })
27
- writeFileSync(path, content)
28
- }
29
-
30
- function hash(content: string): string {
31
- return createHash('sha256').update(content).digest('hex')
32
- }
33
-
34
- function entry(path: string, content: string): ManifestEntry {
35
- return {
36
- path,
37
- sha256: hash(content),
38
- source: 'generated',
39
- userEditable: false,
40
- }
41
- }
42
-
43
- function writeManifest(root: string, files: ManifestEntry[]): string {
44
- const path = join(root, '.ai', 'harness', 'manifest.json')
45
- write(
46
- path,
47
- `${JSON.stringify({ version: 1, generator: 'open-mercato-agentic', files }, null, 2)}\n`,
48
- )
49
- return path
50
- }
51
-
52
- describe('applyHarnessUpdate', () => {
53
- let tempRoot: string
54
- let targetDir: string
55
- let stagingDir: string
56
-
57
- beforeEach(() => {
58
- tempRoot = mkdtempSync(join(tmpdir(), 'agentic-ownership-'))
59
- targetDir = join(tempRoot, 'app')
60
- stagingDir = join(tempRoot, 'candidate')
61
- mkdirSync(targetDir, { recursive: true })
62
- mkdirSync(stagingDir, { recursive: true })
63
- })
64
-
65
- afterEach(() => {
66
- rmSync(tempRoot, { recursive: true, force: true })
67
- })
68
-
69
- it('updates unchanged owned files while preserving modified and unknown files', () => {
70
- const oldUnchanged = 'old generated content\n'
71
- const oldExpectedModified = 'old generated editable content\n'
72
- const userModified = 'user customization\n'
73
- const unknownUserContent = 'private local skill\n'
74
- const oldMissing = 'previously generated then deleted\n'
75
-
76
- write(join(targetDir, '.ai', 'owned-unchanged.md'), oldUnchanged)
77
- write(join(targetDir, '.ai', 'owned-modified.md'), userModified)
78
- write(join(targetDir, '.ai', 'unknown-collision.md'), unknownUserContent)
79
- write(join(targetDir, '.ai', 'skills', 'custom-local', 'SKILL.md'), 'keep me\n')
80
- writeManifest(targetDir, [
81
- entry('.ai/owned-unchanged.md', oldUnchanged),
82
- entry('.ai/owned-modified.md', oldExpectedModified),
83
- entry('.ai/missing-owned.md', oldMissing),
84
- ])
85
-
86
- const nextFiles = new Map<string, string>([
87
- ['.ai/owned-unchanged.md', 'new generated content\n'],
88
- ['.ai/owned-modified.md', 'new generated editable content\n'],
89
- ['.ai/unknown-collision.md', 'new generated collision\n'],
90
- ['.ai/missing-owned.md', 'recreated generated content\n'],
91
- ])
92
- for (const [relativePath, content] of nextFiles) {
93
- write(join(stagingDir, relativePath), content)
94
- }
95
- writeManifest(
96
- stagingDir,
97
- [...nextFiles].map(([relativePath, content]) => entry(relativePath, content)),
98
- )
99
-
100
- const conflicts = applyHarnessUpdate(targetDir, stagingDir)
101
-
102
- expect(conflicts).toEqual([
103
- { path: '.ai/owned-modified.md', candidate: '.ai/owned-modified.md.incoming' },
104
- { path: '.ai/unknown-collision.md', candidate: '.ai/unknown-collision.md.incoming' },
105
- ])
106
- expect(readFileSync(join(targetDir, '.ai', 'owned-unchanged.md'), 'utf8')).toBe(
107
- nextFiles.get('.ai/owned-unchanged.md'),
108
- )
109
- expect(readFileSync(join(targetDir, '.ai', 'owned-modified.md'), 'utf8')).toBe(userModified)
110
- expect(readFileSync(join(targetDir, '.ai', 'owned-modified.md.incoming'), 'utf8')).toBe(
111
- nextFiles.get('.ai/owned-modified.md'),
112
- )
113
- expect(readFileSync(join(targetDir, '.ai', 'unknown-collision.md'), 'utf8')).toBe(
114
- unknownUserContent,
115
- )
116
- expect(readFileSync(join(targetDir, '.ai', 'unknown-collision.md.incoming'), 'utf8')).toBe(
117
- nextFiles.get('.ai/unknown-collision.md'),
118
- )
119
- expect(readFileSync(join(targetDir, '.ai', 'missing-owned.md'), 'utf8')).toBe(
120
- nextFiles.get('.ai/missing-owned.md'),
121
- )
122
- expect(readFileSync(join(targetDir, '.ai', 'skills', 'custom-local', 'SKILL.md'), 'utf8')).toBe(
123
- 'keep me\n',
124
- )
125
- expect(readFileSync(join(targetDir, '.ai', 'harness', 'manifest.json'), 'utf8')).toBe(
126
- readFileSync(join(stagingDir, '.ai', 'harness', 'manifest.json'), 'utf8'),
127
- )
128
- })
129
-
130
- it('never overwrites a user-edited incoming candidate on a later update', () => {
131
- const userFile = 'user-owned harness\n'
132
- const firstCandidate = 'first generated candidate\n'
133
- const secondCandidate = 'second generated candidate\n'
134
- write(join(targetDir, '.ai', 'owned.md'), userFile)
135
- writeManifest(targetDir, [entry('.ai/owned.md', 'original generated file\n')])
136
- write(join(stagingDir, '.ai', 'owned.md'), firstCandidate)
137
- writeManifest(stagingDir, [entry('.ai/owned.md', firstCandidate)])
138
-
139
- expect(applyHarnessUpdate(targetDir, stagingDir)).toEqual([
140
- { path: '.ai/owned.md', candidate: '.ai/owned.md.incoming' },
141
- ])
142
- write(join(targetDir, '.ai', 'owned.md.incoming'), 'user edited the candidate\n')
143
- write(join(stagingDir, '.ai', 'owned.md'), secondCandidate)
144
- writeManifest(stagingDir, [entry('.ai/owned.md', secondCandidate)])
145
-
146
- expect(applyHarnessUpdate(targetDir, stagingDir)).toEqual([
147
- { path: '.ai/owned.md', candidate: '.ai/owned.md.incoming.2' },
148
- ])
149
- expect(readFileSync(join(targetDir, '.ai', 'owned.md'), 'utf8')).toBe(userFile)
150
- expect(readFileSync(join(targetDir, '.ai', 'owned.md.incoming'), 'utf8')).toBe('user edited the candidate\n')
151
- expect(readFileSync(join(targetDir, '.ai', 'owned.md.incoming.2'), 'utf8')).toBe(secondCandidate)
152
- })
153
-
154
- it('removes retired owned files but preserves modified retired assets with ownership', () => {
155
- const unchanged = '.claude/generated.json'
156
- const modified = 'CLAUDE.md'
157
- write(join(targetDir, unchanged), 'old generated setting\n')
158
- write(join(targetDir, modified), 'locally edited instructions\n')
159
- writeManifest(targetDir, [
160
- entry(unchanged, 'old generated setting\n'),
161
- entry(modified, 'old generated instructions\n'),
162
- ])
163
- writeManifest(stagingDir, [])
164
-
165
- expect(applyHarnessUpdate(targetDir, stagingDir)).toEqual([
166
- { path: modified, candidate: null },
167
- ])
168
- expect(existsSync(join(targetDir, unchanged))).toBe(false)
169
- expect(readFileSync(join(targetDir, modified), 'utf8')).toBe('locally edited instructions\n')
170
- const manifest = JSON.parse(readFileSync(join(targetDir, '.ai', 'harness', 'manifest.json'), 'utf8')) as {
171
- files: ManifestEntry[]
172
- }
173
- expect(manifest.files.map((item) => item.path)).toEqual([modified])
174
- })
175
-
176
- it('leaves the prior manifest and app files untouched when candidate validation fails', () => {
177
- write(join(targetDir, '.ai', 'owned.md'), 'old\n')
178
- const oldManifestPath = writeManifest(targetDir, [entry('.ai/owned.md', 'old\n')])
179
- const oldManifest = readFileSync(oldManifestPath, 'utf8')
180
- writeManifest(stagingDir, [entry('.ai/missing-candidate.md', 'not written\n')])
181
-
182
- expect(() => applyHarnessUpdate(targetDir, stagingDir)).toThrow(
183
- 'Generated harness candidate is invalid for ".ai/missing-candidate.md".',
184
- )
185
- expect(readFileSync(join(targetDir, '.ai', 'owned.md'), 'utf8')).toBe('old\n')
186
- expect(readFileSync(oldManifestPath, 'utf8')).toBe(oldManifest)
187
- })
188
-
189
- it('rejects manifest paths that escape the app root before publishing', () => {
190
- const oldManifestPath = writeManifest(targetDir, [])
191
- const oldManifest = readFileSync(oldManifestPath, 'utf8')
192
- writeManifest(stagingDir, [entry('../escape.md', 'escape\n')])
193
-
194
- expect(() => applyHarnessUpdate(targetDir, stagingDir)).toThrow(
195
- 'Generated harness candidate is invalid for "../escape.md".',
196
- )
197
- expect(readFileSync(oldManifestPath, 'utf8')).toBe(oldManifest)
198
- })
199
-
200
- it('rejects a symlinked managed ancestor without writing outside the app', () => {
201
- if (process.platform === 'win32') return
202
- const outsideDir = join(tempRoot, 'outside')
203
- mkdirSync(join(outsideDir, 'harness'), { recursive: true })
204
- write(join(outsideDir, 'sentinel.txt'), 'outside sentinel\n')
205
- write(
206
- join(outsideDir, 'harness', 'manifest.json'),
207
- `${JSON.stringify({ version: 1, generator: 'outside', files: [] }, null, 2)}\n`,
208
- )
209
- const nextContent = 'must stay inside the app\n'
210
- write(join(stagingDir, '.ai', 'owned.md'), nextContent)
211
- writeManifest(stagingDir, [entry('.ai/owned.md', nextContent)])
212
- symlinkSync(outsideDir, join(targetDir, '.ai'), 'dir')
213
-
214
- expect(() => applyHarnessUpdate(targetDir, stagingDir)).toThrow(
215
- 'Harness-managed path contains a symbolic-link component',
216
- )
217
- expect(readFileSync(join(outsideDir, 'sentinel.txt'), 'utf8')).toBe('outside sentinel\n')
218
- expect(existsSync(join(outsideDir, 'owned.md'))).toBe(false)
219
- expect(readdirSync(outsideDir).sort()).toEqual(['harness', 'sentinel.txt'])
220
- })
221
- })
222
-
223
- describe('runAgenticSetup ownership modes', () => {
224
- let appDir: string
225
- let previousSkipExternal: string | undefined
226
-
227
- beforeEach(() => {
228
- appDir = mkdtempSync(join(tmpdir(), 'agentic-setup-app-'))
229
- write(
230
- join(appDir, 'src', 'modules.ts'),
231
- "export const enabledModules: Array<{ id: string }> = []\n",
232
- )
233
- previousSkipExternal = process.env.OM_SKIP_EXTERNAL_SKILLS
234
- process.env.OM_SKIP_EXTERNAL_SKILLS = '1'
235
- jest.spyOn(console, 'log').mockImplementation()
236
- jest.spyOn(console, 'warn').mockImplementation()
237
- })
238
-
239
- afterEach(() => {
240
- if (previousSkipExternal === undefined) delete process.env.OM_SKIP_EXTERNAL_SKILLS
241
- else process.env.OM_SKIP_EXTERNAL_SKILLS = previousSkipExternal
242
- jest.restoreAllMocks()
243
- rmSync(appDir, { recursive: true, force: true })
244
- })
245
-
246
- it('preserves local edits, recreates missing owned files, and reports incoming candidates', async () => {
247
- const ask = async () => ''
248
- await runAgenticSetup(appDir, ask, { tool: 'codex' })
249
- const originalGuide = readFileSync(join(appDir, '.ai', 'guides', 'architecture.md'), 'utf8')
250
- write(join(appDir, 'AGENTS.md'), '# My local harness rules\n')
251
- write(join(appDir, '.ai', 'skills', 'my-private-skill', 'SKILL.md'), '# Private\n')
252
- rmSync(join(appDir, '.ai', 'guides', 'architecture.md'))
253
-
254
- await runAgenticSetup(appDir, ask, { tool: 'codex', updateHarness: true })
255
-
256
- expect(readFileSync(join(appDir, 'AGENTS.md'), 'utf8')).toBe('# My local harness rules\n')
257
- expect(readFileSync(join(appDir, 'AGENTS.md.incoming'), 'utf8')).toContain(
258
- '<!-- CODEX_ENFORCEMENT_RULES_START -->',
259
- )
260
- expect(readFileSync(join(appDir, '.ai', 'guides', 'architecture.md'), 'utf8')).toBe(originalGuide)
261
- expect(readFileSync(join(appDir, '.ai', 'skills', 'my-private-skill', 'SKILL.md'), 'utf8')).toBe(
262
- '# Private\n',
263
- )
264
- expect(existsSync(join(appDir, '.ai', 'harness', 'manifest.json'))).toBe(true)
265
- const manifest = JSON.parse(
266
- readFileSync(join(appDir, '.ai', 'harness', 'manifest.json'), 'utf8'),
267
- ) as { files: ManifestEntry[] }
268
- expect(manifest.files.find((item) => item.path === '.ai/lessons.md')?.userEditable).toBe(true)
269
- expect(manifest.files.find((item) => item.path === '.ai/lessons/_template.md')?.userEditable).toBe(true)
270
- expect(existsSync(join(appDir, 'scripts', 'check-lessons.mjs'))).toBe(true)
271
- if (process.platform !== 'win32') {
272
- expect(statSync(join(appDir, 'scripts', 'install-skills.sh')).mode & 0o111).not.toBe(0)
273
- }
274
- })
275
-
276
- it('keeps force semantics by replacing exact generated targets', async () => {
277
- const ask = async () => ''
278
- await runAgenticSetup(appDir, ask, { tool: 'codex' })
279
- write(join(appDir, 'AGENTS.md'), '# My local harness rules\n')
280
-
281
- await runAgenticSetup(appDir, ask, { tool: 'codex', force: true })
282
-
283
- expect(readFileSync(join(appDir, 'AGENTS.md'), 'utf8')).toContain(
284
- '<!-- CODEX_ENFORCEMENT_RULES_START -->',
285
- )
286
- expect(existsSync(join(appDir, 'AGENTS.md.incoming'))).toBe(false)
287
- })
288
-
289
- it.each([
290
- ['top-level .ai', '.ai'],
291
- ['top-level scripts', 'scripts'],
292
- ['nested guides', join('.ai', 'guides')],
293
- ])('rejects a symlinked %s destination on initial setup before writing outside the app', async (_label, linkedRelative) => {
294
- if (process.platform === 'win32') return
295
- const targetDir = join(appDir, linkedRelative.replaceAll('/', '-'))
296
- const outsideDir = join(appDir, `outside-initial-${linkedRelative.replaceAll('/', '-')}`)
297
- write(
298
- join(targetDir, 'src', 'modules.ts'),
299
- "export const enabledModules: Array<{ id: string }> = []\n",
300
- )
301
- write(join(outsideDir, 'sentinel.txt'), 'outside sentinel\n')
302
- const linkedPath = join(targetDir, linkedRelative)
303
- mkdirSync(dirname(linkedPath), { recursive: true })
304
- symlinkSync(outsideDir, linkedPath, 'dir')
305
-
306
- await expect(runAgenticSetup(targetDir, async () => '', { tool: 'codex' })).rejects.toThrow(
307
- 'Harness-managed path contains a symbolic-link component',
308
- )
309
- expect(readFileSync(join(outsideDir, 'sentinel.txt'), 'utf8')).toBe('outside sentinel\n')
310
- expect(readdirSync(outsideDir)).toEqual(['sentinel.txt'])
311
- expect(existsSync(join(targetDir, 'AGENTS.md'))).toBe(false)
312
- })
313
-
314
- it.each([
315
- ['top-level .ai', '.ai'],
316
- ['top-level scripts', 'scripts'],
317
- ['nested guides', join('.ai', 'guides')],
318
- ])('rejects a symlinked %s destination on forced setup before writing outside the app', async (_label, linkedRelative) => {
319
- if (process.platform === 'win32') return
320
- const targetDir = join(appDir, `force-${linkedRelative.replaceAll('/', '-')}`)
321
- const outsideDir = join(appDir, `outside-force-${linkedRelative.replaceAll('/', '-')}`)
322
- write(
323
- join(targetDir, 'src', 'modules.ts'),
324
- "export const enabledModules: Array<{ id: string }> = []\n",
325
- )
326
- await runAgenticSetup(targetDir, async () => '', { tool: 'codex' })
327
- const originalAgents = readFileSync(join(targetDir, 'AGENTS.md'), 'utf8')
328
- write(join(outsideDir, 'sentinel.txt'), 'outside sentinel\n')
329
- const linkedPath = join(targetDir, linkedRelative)
330
- rmSync(linkedPath, { recursive: true, force: true })
331
- mkdirSync(dirname(linkedPath), { recursive: true })
332
- symlinkSync(outsideDir, linkedPath, 'dir')
333
-
334
- await expect(
335
- runAgenticSetup(targetDir, async () => '', { tool: 'codex', force: true }),
336
- ).rejects.toThrow('Harness-managed path contains a symbolic-link component')
337
- expect(readFileSync(join(outsideDir, 'sentinel.txt'), 'utf8')).toBe('outside sentinel\n')
338
- expect(readdirSync(outsideDir)).toEqual(['sentinel.txt'])
339
- expect(readFileSync(join(targetDir, 'AGENTS.md'), 'utf8')).toBe(originalAgents)
340
- })
341
-
342
- it('updates the harness without crashing when src/modules.ts is missing', async () => {
343
- rmSync(join(appDir, 'src', 'modules.ts'))
344
-
345
- await expect(runAgenticSetup(appDir, async () => '', {
346
- tool: 'codex',
347
- updateHarness: true,
348
- })).resolves.toBeUndefined()
349
-
350
- expect(existsSync(join(appDir, 'src', 'modules.ts'))).toBe(false)
351
- expect(existsSync(join(appDir, '.ai', 'harness', 'manifest.json'))).toBe(true)
352
- })
353
-
354
- it('removes unmodified tool-specific assets when switching tools', async () => {
355
- const ask = async () => ''
356
- await runAgenticSetup(appDir, ask, { tool: 'claude-code' })
357
- expect(existsSync(join(appDir, 'CLAUDE.md'))).toBe(true)
358
- expect(existsSync(join(appDir, '.claude', 'settings.json'))).toBe(true)
359
-
360
- await runAgenticSetup(appDir, ask, { tool: 'codex', updateHarness: true })
361
-
362
- expect(existsSync(join(appDir, 'CLAUDE.md'))).toBe(false)
363
- expect(existsSync(join(appDir, '.claude', 'settings.json'))).toBe(false)
364
- expect(existsSync(join(appDir, '.codex', 'mcp.json.example'))).toBe(true)
365
- })
366
-
367
- it('preserves the persisted tool selection on a bare harness update without prompting', async () => {
368
- const initialAsk = async () => ''
369
- await runAgenticSetup(appDir, initialAsk, { tool: 'claude-code,codex' })
370
- const ask = jest.fn(async () => {
371
- throw new Error('bare update must not prompt for a new tool selection')
372
- })
373
-
374
- await runAgenticSetup(appDir, ask, { updateHarness: true })
375
-
376
- expect(ask).not.toHaveBeenCalled()
377
- expect(existsSync(join(appDir, 'CLAUDE.md'))).toBe(true)
378
- expect(existsSync(join(appDir, '.claude', 'settings.json'))).toBe(true)
379
- expect(existsSync(join(appDir, '.codex', 'mcp.json.example'))).toBe(true)
380
- expect(existsSync(join(appDir, '.cursor', 'hooks.json'))).toBe(false)
381
- const tiers = JSON.parse(readFileSync(join(appDir, '.ai', 'skills', 'tiers.json'), 'utf8')) as {
382
- agents?: { ignore?: string[] }
383
- }
384
- expect(tiers.agents?.ignore).toEqual(['cursor'])
385
- })
386
- })
@@ -1,184 +0,0 @@
1
- import fs from 'node:fs'
2
- import os from 'node:os'
3
- import path from 'node:path'
4
- import { createInjectionWidgetsExtension } from '../generators/extensions/injection-widgets'
5
- import type { ModuleScanContext } from '../generators/extension'
6
-
7
- // A widget's `metadata.id` and the `widgetId` that mounts it are two hand-typed literals in two
8
- // different files. Nothing linked them, so a typo in either failed silently — the widget was never
9
- // mounted, or the table entry resolved to nothing. These cases drive the real scan → generate seam
10
- // (real filesystem fixtures, real AST extraction) and assert the reconciliation warnings.
11
-
12
- let tmpRoot: string
13
-
14
- beforeEach(() => {
15
- tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'om-injection-widgets-'))
16
- })
17
-
18
- afterEach(() => {
19
- fs.rmSync(tmpRoot, { recursive: true, force: true })
20
- jest.restoreAllMocks()
21
- })
22
-
23
- function widgetSource(id: string): string {
24
- return [
25
- "import type { InjectionWidgetModule } from '@open-mercato/shared/modules/widgets/injection'",
26
- "import Impl from './widget.client'",
27
- '',
28
- 'const widget: InjectionWidgetModule = {',
29
- ' metadata: {',
30
- ` id: '${id}',`,
31
- " title: 'Fixture widget',",
32
- ' },',
33
- ' Widget: Impl,',
34
- '}',
35
- '',
36
- 'export default widget',
37
- ].join('\n')
38
- }
39
-
40
- function tableSource(widgetIds: string[]): string {
41
- const entries = widgetIds
42
- .map((id, index) => ` 'detail:fixture:tab${index}': [{ widgetId: '${id}', kind: 'tab' }],`)
43
- .join('\n')
44
- return [
45
- "import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'",
46
- '',
47
- 'export const injectionTable: ModuleInjectionTable = {',
48
- entries,
49
- '}',
50
- '',
51
- 'export default injectionTable',
52
- ].join('\n')
53
- }
54
-
55
- /**
56
- * Lays out one module on disk in the shape the scanner expects and returns a scan context for it.
57
- * `declaredIds` each get `widgets/injection/<name>/widget.ts`; `referencedIds` go into the module's
58
- * `widgets/injection-table.ts` (omitted entirely when the list is empty).
59
- */
60
- function makeModule(moduleId: string, declaredIds: string[], referencedIds: string[]): ModuleScanContext {
61
- const base = path.join(tmpRoot, moduleId)
62
- const injectionDir = path.join(base, 'widgets', 'injection')
63
- fs.mkdirSync(injectionDir, { recursive: true })
64
-
65
- declaredIds.forEach((id, index) => {
66
- const dir = path.join(injectionDir, `w${index}`)
67
- fs.mkdirSync(dir, { recursive: true })
68
- fs.writeFileSync(path.join(dir, 'widget.ts'), widgetSource(id))
69
- })
70
-
71
- const tablePath = path.join(base, 'widgets', 'injection-table.ts')
72
- if (referencedIds.length > 0) fs.writeFileSync(tablePath, tableSource(referencedIds))
73
-
74
- const roots = { appBase: base, pkgBase: base }
75
- return {
76
- moduleId,
77
- roots,
78
- imps: { appBase: `@fixture/${moduleId}`, pkgBase: `@fixture/${moduleId}` },
79
- importIdRef: { value: 0 },
80
- sharedImports: [],
81
- resolveModuleFile: (() =>
82
- referencedIds.length > 0
83
- ? { absolutePath: tablePath, importPath: `@fixture/${moduleId}/widgets/injection-table` }
84
- : null) as unknown as ModuleScanContext['resolveModuleFile'],
85
- resolveFirstModuleFile: (() => null) as unknown as ModuleScanContext['resolveFirstModuleFile'],
86
- processStandaloneConfig: () => null,
87
- sanitizeGeneratedModuleSpecifier: (importPath: string) => importPath,
88
- }
89
- }
90
-
91
- function runGenerate(contexts: ModuleScanContext[]): string[] {
92
- const warn = jest.spyOn(console, 'warn').mockImplementation(() => {})
93
- const extension = createInjectionWidgetsExtension()
94
- for (const ctx of contexts) extension.scanModule(ctx)
95
- extension.generateOutput()
96
- return warn.mock.calls.map((call) => String(call[0]))
97
- }
98
-
99
- describe('injection widget id reconciliation at generate time', () => {
100
- it('stays silent when every declared widget is referenced and vice versa', () => {
101
- const warnings = runGenerate([makeModule('alpha', ['alpha.one', 'alpha.two'], ['alpha.one', 'alpha.two'])])
102
-
103
- expect(warnings).toEqual([])
104
- })
105
-
106
- it('warns when an injection table references a widget id nothing declares', () => {
107
- const warnings = runGenerate([makeModule('alpha', ['alpha.one'], ['alpha.one', 'alpha.typo'])])
108
-
109
- expect(warnings).toHaveLength(1)
110
- expect(warnings[0]).toContain('alpha.typo')
111
- expect(warnings[0]).toContain('references widget id')
112
- expect(warnings[0]).toContain('alpha')
113
- })
114
-
115
- it('stays silent about a declared widget that no table references', () => {
116
- // The mirror check is deliberately not implemented: `extractInjectionTableWidgetIds` reads only
117
- // statically-analyzable literals, so a table built by composition or computation exposes no ids
118
- // and every widget in such a module would be reported as unreferenced. Pinned so the omission is
119
- // a decision rather than an oversight.
120
- const warnings = runGenerate([makeModule('alpha', ['alpha.one', 'alpha.orphan'], ['alpha.one'])])
121
-
122
- expect(warnings).toEqual([])
123
- })
124
-
125
- it('reports only the unresolvable reference when the two sides disagree entirely', () => {
126
- const warnings = runGenerate([makeModule('alpha', ['alpha.declared'], ['alpha.referenced'])])
127
-
128
- expect(warnings).toHaveLength(1)
129
- expect(warnings[0]).toContain('alpha.referenced')
130
- })
131
-
132
- it('does not warn about a table assembled by composition, whose ids are invisible statically', () => {
133
- // Mirrors the real `example` module: `cond ? { ...a, ...b } : a`. The extractor sees no ids, so
134
- // there is nothing to reconcile — and critically, no false warning.
135
- const base = path.join(tmpRoot, 'composed')
136
- fs.mkdirSync(path.join(base, 'widgets', 'injection', 'w0'), { recursive: true })
137
- fs.writeFileSync(path.join(base, 'widgets', 'injection', 'w0', 'widget.ts'), widgetSource('composed.one'))
138
- const tablePath = path.join(base, 'widgets', 'injection-table.ts')
139
- fs.writeFileSync(
140
- tablePath,
141
- [
142
- "import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'",
143
- "const a: ModuleInjectionTable = { 'detail:x:tab': [{ widgetId: 'composed.one' }] }",
144
- "const b: ModuleInjectionTable = { 'detail:y:tab': [{ widgetId: 'composed.two' }] }",
145
- 'export const injectionTable: ModuleInjectionTable = process.env.FLAG ? { ...a, ...b } : a',
146
- 'export default injectionTable',
147
- ].join('\n'),
148
- )
149
-
150
- const ctx: ModuleScanContext = {
151
- ...makeModule('composed', [], []),
152
- resolveModuleFile: (() => ({
153
- absolutePath: tablePath,
154
- importPath: '@fixture/composed/widgets/injection-table',
155
- })) as unknown as ModuleScanContext['resolveModuleFile'],
156
- }
157
-
158
- expect(runGenerate([ctx])).toEqual([])
159
- })
160
-
161
- it('accepts a table in one module referencing a widget declared in another', () => {
162
- const warnings = runGenerate([
163
- makeModule('alpha', ['alpha.shared'], []),
164
- makeModule('beta', [], ['alpha.shared']),
165
- ])
166
-
167
- expect(warnings).toEqual([])
168
- })
169
-
170
- it('does not warn for the single-widget id inference', () => {
171
- // A lone widget whose metadata.id cannot be extracted adopts the table's single id; that
172
- // convenience path must not then report itself as a mismatch.
173
- const ctx = makeModule('gamma', [], ['gamma.inferred'])
174
- const dir = path.join(tmpRoot, 'gamma', 'widgets', 'injection', 'solo')
175
- fs.mkdirSync(dir, { recursive: true })
176
- fs.writeFileSync(path.join(dir, 'widget.ts'), 'export default { Widget: () => null }\n')
177
-
178
- expect(runGenerate([ctx])).toEqual([])
179
- })
180
-
181
- it('stays silent for a module with no injection widgets and no table', () => {
182
- expect(runGenerate([makeModule('delta', [], [])])).toEqual([])
183
- })
184
- })
@@ -1,65 +0,0 @@
1
- import path from 'node:path'
2
- import fs from 'node:fs'
3
- import os from 'node:os'
4
- import { loadAppEnv } from '../load-env'
5
-
6
- /**
7
- * Regression: bin.ts must load the app's `.env` before initTelemetry(), so
8
- * TELEMETRY_BACKEND set only in `.env` reaches worker and scheduler processes
9
- * (ensureEnvLoaded in run() happens after init — too late).
10
- */
11
-
12
- describe('loadAppEnv', () => {
13
- const TEST_KEY = 'OM_LOAD_ENV_TEST_BACKEND'
14
- let tmpDir: string
15
- let cwd: string
16
-
17
- beforeEach(() => {
18
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'load-env-'))
19
- cwd = process.cwd()
20
- process.chdir(tmpDir)
21
- delete process.env[TEST_KEY]
22
- })
23
-
24
- afterEach(() => {
25
- process.chdir(cwd)
26
- delete process.env[TEST_KEY]
27
- fs.rmSync(tmpDir, { recursive: true, force: true })
28
- })
29
-
30
- it('loads .env from the app directory into process.env', async () => {
31
- fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"env-test-app"}\n')
32
- fs.mkdirSync(path.join(tmpDir, 'src'))
33
- fs.writeFileSync(path.join(tmpDir, 'src', 'modules.ts'), 'export const enabledModules = []\n')
34
- fs.writeFileSync(path.join(tmpDir, '.env'), `${TEST_KEY}=console\n`)
35
-
36
- await loadAppEnv()
37
-
38
- expect(process.env[TEST_KEY]).toBe('console')
39
- })
40
-
41
- it('does not override values already set in the real process env', async () => {
42
- fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"env-test-app"}\n')
43
- fs.writeFileSync(path.join(tmpDir, '.env'), `${TEST_KEY}=from-dotenv\n`)
44
- process.env[TEST_KEY] = 'from-shell'
45
-
46
- await loadAppEnv()
47
-
48
- expect(process.env[TEST_KEY]).toBe('from-shell')
49
- })
50
-
51
- it('is a safe no-op when no .env exists', async () => {
52
- fs.writeFileSync(path.join(tmpDir, 'package.json'), '{"name":"env-test-app"}\n')
53
- await expect(loadAppEnv()).resolves.toBeUndefined()
54
- expect(process.env[TEST_KEY]).toBeUndefined()
55
- })
56
-
57
- it('bin.ts awaits loadAppEnv() before initTelemetry() (the guarded ordering)', () => {
58
- const binSource = fs.readFileSync(path.resolve(__dirname, '../../bin.ts'), 'utf8')
59
- const loadEnvCall = binSource.indexOf('await loadAppEnv()')
60
- const initTelemetryCall = binSource.indexOf('await initTelemetry()')
61
- expect(loadEnvCall).toBeGreaterThan(-1)
62
- expect(initTelemetryCall).toBeGreaterThan(-1)
63
- expect(loadEnvCall).toBeLessThan(initTelemetryCall)
64
- })
65
- })