@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
@@ -6,35 +6,16 @@
6
6
  * This module reads those files at runtime — no embedded string constants.
7
7
  */
8
8
 
9
- import {
10
- chmodSync,
11
- copyFileSync,
12
- existsSync,
13
- lstatSync,
14
- mkdirSync,
15
- mkdtempSync,
16
- readFileSync,
17
- readdirSync,
18
- realpathSync,
19
- renameSync,
20
- rmSync,
21
- statSync,
22
- writeFileSync,
23
- } from 'node:fs'
24
- import { createHash } from 'node:crypto'
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, symlinkSync, lstatSync, unlinkSync, readdirSync } from 'node:fs'
25
10
  import { spawnSync } from 'node:child_process'
26
- import { tmpdir } from 'node:os'
27
- import { join, dirname, basename, relative, resolve, sep } from 'node:path'
11
+ import { join, dirname, basename } from 'node:path'
28
12
  import { fileURLToPath } from 'node:url'
29
13
  import { Project, SyntaxKind } from 'ts-morph'
30
14
 
31
- const moduleDir = dirname(fileURLToPath(import.meta.url))
32
- // In the built output (dist/lib/agentic-setup.js), moduleDir is dist/lib/.
15
+ const __dirname = dirname(fileURLToPath(import.meta.url))
16
+ // In the built output (dist/lib/agentic-setup.js), __dirname is dist/lib/.
33
17
  // agentic/ is copied to dist/agentic/ by build.mjs.
34
- const bundledAgenticDir = join(moduleDir, '..', 'agentic')
35
- const AGENTIC_DIR = existsSync(bundledAgenticDir)
36
- ? bundledAgenticDir
37
- : join(moduleDir, '..', '..', '..', 'create-app', 'agentic')
18
+ const AGENTIC_DIR = join(__dirname, '..', 'agentic')
38
19
  const GUIDES_DIR = join(AGENTIC_DIR, 'guides')
39
20
 
40
21
  type AskFn = (question: string) => Promise<string>
@@ -42,7 +23,6 @@ type AskFn = (question: string) => Promise<string>
42
23
  interface AgenticSetupOptions {
43
24
  tool?: string
44
25
  force?: boolean
45
- updateHarness?: boolean
46
26
  }
47
27
 
48
28
  interface AgenticConfig {
@@ -50,19 +30,6 @@ interface AgenticConfig {
50
30
  targetDir: string
51
31
  }
52
32
 
53
- interface HarnessManifestFile {
54
- path: string
55
- sha256: string
56
- source: string
57
- userEditable: boolean
58
- }
59
-
60
- interface HarnessManifest {
61
- version: number
62
- generator: string
63
- files: HarnessManifestFile[]
64
- }
65
-
66
33
  // ─── Helpers ─────────────────────────────────────────────────────────────
67
34
 
68
35
  function resolvePlaceholders(content: string, config: AgenticConfig): string {
@@ -92,14 +59,14 @@ function copyFile(srcDir: string, srcRelative: string, destPath: string): void {
92
59
  // AST-parse the static `enabledModules` array literal in the app's src/modules.ts
93
60
  // and collect each entry's `id`. Only the static literal is read (conditional
94
61
  // .push()/spread entries are intentionally not seen — see spec D6).
95
- function readEnabledModuleIds(modulesPath: string): { parsed: boolean; ids: string[] } {
96
- if (!existsSync(modulesPath)) return { parsed: false, ids: [] }
62
+ function readEnabledModuleIds(modulesPath: string): string[] {
63
+ if (!existsSync(modulesPath)) return []
97
64
  try {
98
65
  const project = new Project({ useInMemoryFileSystem: true })
99
66
  const sourceFile = project.createSourceFile('modules.ts', readFileSync(modulesPath, 'utf-8'))
100
67
  const declaration = sourceFile.getVariableDeclaration('enabledModules')
101
68
  const arrayLiteral = declaration?.getInitializerIfKind(SyntaxKind.ArrayLiteralExpression)
102
- if (!arrayLiteral) return { parsed: false, ids: [] }
69
+ if (!arrayLiteral) return []
103
70
  const ids: string[] = []
104
71
  for (const element of arrayLiteral.getElements()) {
105
72
  const objectLiteral = element.asKind(SyntaxKind.ObjectLiteralExpression)
@@ -108,9 +75,9 @@ function readEnabledModuleIds(modulesPath: string): { parsed: boolean; ids: stri
108
75
  const idValue = idProperty?.getInitializerIfKind(SyntaxKind.StringLiteral)?.getLiteralValue()
109
76
  if (idValue) ids.push(idValue)
110
77
  }
111
- return { parsed: true, ids }
78
+ return ids
112
79
  } catch {
113
- return { parsed: false, ids: [] }
80
+ return []
114
81
  }
115
82
  }
116
83
 
@@ -125,23 +92,48 @@ function selectModuleFactSheets(targetDir: string, modulesSubdir: string): strin
125
92
  .map((file) => file.replace(/\.md$/, ''))
126
93
  : []
127
94
  if (available.length === 0) return []
128
- const parsed = readEnabledModuleIds(join(targetDir, 'src', 'modules.ts'))
129
- if (!parsed.parsed) return available
130
- const enabled = new Set(parsed.ids)
95
+ const enabled = new Set(readEnabledModuleIds(join(targetDir, 'src', 'modules.ts')))
131
96
  const selected = available.filter((moduleId) => enabled.has(moduleId))
132
- return selected
97
+ return selected.length > 0 ? selected : available
133
98
  }
134
99
 
135
100
  const MODULE_GUIDES_START = '<!-- om:module-guides:start -->'
136
101
  const MODULE_GUIDES_END = '<!-- om:module-guides:end -->'
137
102
 
138
- export function renderModuleGuidesBlock(selected: string[]): string {
103
+ // Read each module's guide label from the bundled `module-facts.json` (emitted by
104
+ // build.mjs from the generator's extraction of each module's own `metadata`). The
105
+ // label falls back description → title → generic, so the CLI never re-declares
106
+ // specific module names or descriptions. A missing/malformed sidecar degrades to an
107
+ // empty map (generic labels), never a throw.
108
+ function readModuleGuideLabels(guidesDir: string): Record<string, string> {
109
+ const factsPath = join(guidesDir, 'module-facts.json')
110
+ if (!existsSync(factsPath)) return {}
111
+ try {
112
+ const parsed = JSON.parse(readFileSync(factsPath, 'utf-8')) as Record<
113
+ string,
114
+ { description?: unknown; title?: unknown }
115
+ >
116
+ const labels: Record<string, string> = {}
117
+ for (const [moduleId, entry] of Object.entries(parsed)) {
118
+ const label =
119
+ (entry && typeof entry.description === 'string' && entry.description) ||
120
+ (entry && typeof entry.title === 'string' && entry.title) ||
121
+ ''
122
+ if (label) labels[moduleId] = label
123
+ }
124
+ return labels
125
+ } catch {
126
+ return {}
127
+ }
128
+ }
129
+
130
+ function renderModuleGuidesBlock(selected: string[], labels: Record<string, string>): string {
139
131
  if (selected.length === 0) return '_No module fact-sheets are bundled for this app._'
140
- return [
141
- `Enabled module facts: ${selected.map((moduleId) => `\`${moduleId}\``).join(',')}.`,
142
- '',
143
- 'Load `.ai/guides/modules/<id>.md` only for a named or targeted installed module/host; never preload all module facts.',
144
- ].join('\n')
132
+ const rows = selected.map((moduleId) => {
133
+ const label = labels[moduleId] ?? `Use the ${moduleId} module`
134
+ return `| ${label} | \`.ai/guides/modules/${moduleId}.md\` |`
135
+ })
136
+ return ['| Task | Load |', '|---|---|', ...rows].join('\n')
145
137
  }
146
138
 
147
139
  // Regenerate the marker-delimited Module-Specific Guides block in the written
@@ -150,6 +142,7 @@ export function renderModuleGuidesBlock(selected: string[]): string {
150
142
  function injectModuleGuides(
151
143
  agentsMdPath: string,
152
144
  selected: string[],
145
+ labels: Record<string, string> = {},
153
146
  ): void {
154
147
  if (!existsSync(agentsMdPath)) return
155
148
  const content = readFileSync(agentsMdPath, 'utf-8')
@@ -163,428 +156,188 @@ function injectModuleGuides(
163
156
  }
164
157
  const before = content.slice(0, startIndex + MODULE_GUIDES_START.length)
165
158
  const after = content.slice(endIndex)
166
- const next = `${before}\n${renderModuleGuidesBlock(selected)}\n${after}`
159
+ const next = `${before}\n${renderModuleGuidesBlock(selected, labels)}\n${after}`
167
160
  if (next !== content) writeFileSync(agentsMdPath, next)
168
161
  }
169
162
 
170
- const TEXT_EXTENSIONS = new Set(['.cjs', '.json', '.md', '.mdc', '.mjs', '.sh', '.ts', '.txt'])
171
-
172
- function isTextAsset(path: string): boolean {
173
- const dot = path.lastIndexOf('.')
174
- return dot === -1 || TEXT_EXTENSIONS.has(path.slice(dot))
175
- }
176
-
177
- function listFiles(root: string): string[] {
178
- if (!existsSync(root)) return []
179
- const files: string[] = []
180
- for (const entry of readdirSync(root, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
181
- const absolute = join(root, entry.name)
182
- if (entry.isDirectory()) files.push(...listFiles(absolute))
183
- else if (entry.isFile()) files.push(absolute)
184
- }
185
- return files
186
- }
187
-
188
- function copyTree(sourceRoot: string, destinationRoot: string, config: AgenticConfig): void {
189
- for (const sourcePath of listFiles(sourceRoot)) {
190
- const destinationPath = join(destinationRoot, relative(sourceRoot, sourcePath))
191
- ensureDir(destinationPath)
192
- if (isTextAsset(sourcePath)) {
193
- writeFileSync(destinationPath, resolvePlaceholders(readFileSync(sourcePath, 'utf8'), config))
194
- } else {
195
- copyFileSync(sourcePath, destinationPath)
196
- }
197
- if (process.platform !== 'win32') chmodSync(destinationPath, statSync(sourcePath).mode & 0o777)
198
- }
199
- }
200
-
201
- function hashFile(path: string): string {
202
- return createHash('sha256').update(readFileSync(path)).digest('hex')
203
- }
204
-
205
- function canonicalHarnessRoot(root: string): string {
206
- const resolved = resolve(root)
207
- if (!existsSync(resolved)) {
208
- throw new Error(`Harness root must be an existing directory: ${resolved}`)
209
- }
210
- const canonical = realpathSync(resolved)
211
- const entry = lstatSync(canonical, { throwIfNoEntry: false })
212
- if (!entry || !entry.isDirectory()) {
213
- throw new Error(`Harness root must be an existing directory: ${resolved}`)
214
- }
215
- return canonical
216
- }
217
-
218
- /**
219
- * Validate a managed path without following any link below the canonical app
220
- * root. A lexical containment check alone is insufficient: `app/.ai -> /tmp`
221
- * would otherwise make an apparently safe update write outside the app.
222
- */
223
- function assertManagedPath(root: string, path: string, options: { leaf?: 'file' | 'any' } = {}): void {
224
- const absolutePath = resolve(path)
225
- const normalizedRelative = relative(root, absolutePath)
226
- if (
227
- normalizedRelative === '..' ||
228
- normalizedRelative.startsWith(`..${sep}`)
229
- ) {
230
- throw new Error(`Harness-managed path escapes its root: ${absolutePath}`)
231
- }
232
- if (normalizedRelative === '') return
233
-
234
- const components = normalizedRelative.split(sep).filter(Boolean)
235
- let current = root
236
- for (let index = 0; index < components.length; index += 1) {
237
- current = join(current, components[index])
238
- const entry = lstatSync(current, { throwIfNoEntry: false })
239
- if (!entry) return
240
- if (entry.isSymbolicLink()) {
241
- throw new Error(`Harness-managed path contains a symbolic-link component: ${current}`)
242
- }
243
- const isLeaf = index === components.length - 1
244
- if (!isLeaf && !entry.isDirectory()) {
245
- throw new Error(`Harness-managed path component is not a directory: ${current}`)
246
- }
247
- if (isLeaf && options.leaf === 'file' && !entry.isFile()) {
248
- throw new Error(`Harness-managed file is not a regular file: ${current}`)
249
- }
250
- }
251
- }
252
-
253
- function ensureManagedParent(root: string, filePath: string): void {
254
- const parent = dirname(filePath)
255
- const normalizedRelative = relative(root, parent)
256
- if (normalizedRelative === '..' || normalizedRelative.startsWith(`..${sep}`)) {
257
- throw new Error(`Harness-managed path escapes its root: ${filePath}`)
258
- }
259
- let current = root
260
- for (const component of normalizedRelative.split(sep).filter(Boolean)) {
261
- assertManagedPath(root, current)
262
- current = join(current, component)
263
- const entry = lstatSync(current, { throwIfNoEntry: false })
264
- if (!entry) mkdirSync(current)
265
- const created = lstatSync(current, { throwIfNoEntry: false })
266
- if (!created || created.isSymbolicLink() || !created.isDirectory()) {
267
- throw new Error(`Harness-managed path component is not a real directory: ${current}`)
268
- }
269
- }
270
- }
271
-
272
- function harnessGeneratorId(): string {
273
- try {
274
- const upstream = JSON.parse(readFileSync(join(GUIDES_DIR, 'upstream', 'manifest.json'), 'utf8')) as {
275
- generator?: unknown
276
- }
277
- const generator = typeof upstream.generator === 'string' ? upstream.generator : ''
278
- const version = generator.slice(generator.lastIndexOf('@') + 1)
279
- if (version && version !== generator) return `open-mercato-agentic@${version}`
280
- } catch {
281
- // Source-mode tests may not have built upstream snapshots yet.
282
- }
283
- return 'open-mercato-agentic@unknown'
284
- }
163
+ // ─── Generators ──────────────────────────────────────────────────────────
285
164
 
286
- function externalSkillNames(targetDir: string): Set<string> {
287
- try {
288
- const tiers = JSON.parse(readFileSync(join(targetDir, '.ai', 'skills', 'tiers.json'), 'utf8')) as {
289
- external?: { skills?: unknown }
290
- }
291
- return new Set(Array.isArray(tiers.external?.skills) ? tiers.external.skills.filter((name): name is string => typeof name === 'string') : [])
292
- } catch {
293
- return new Set()
294
- }
295
- }
165
+ function generateShared(config: AgenticConfig): void {
166
+ const { targetDir } = config
167
+ const srcDir = join(AGENTIC_DIR, 'shared')
296
168
 
297
- function atomicCopyFile(sourcePath: string, destinationPath: string, managedRoot?: string): void {
298
- if (managedRoot) {
299
- assertManagedPath(managedRoot, destinationPath, { leaf: 'file' })
300
- ensureManagedParent(managedRoot, destinationPath)
301
- } else {
302
- ensureDir(destinationPath)
303
- }
304
- const temporaryPath = `${destinationPath}.tmp-${process.pid}-${Date.now()}`
305
- try {
306
- if (managedRoot) assertManagedPath(managedRoot, temporaryPath, { leaf: 'file' })
307
- copyFileSync(sourcePath, temporaryPath)
308
- if (process.platform !== 'win32') chmodSync(temporaryPath, statSync(sourcePath).mode & 0o777)
309
- if (managedRoot) {
310
- assertManagedPath(managedRoot, dirname(destinationPath))
311
- assertManagedPath(managedRoot, destinationPath, { leaf: 'file' })
312
- assertManagedPath(managedRoot, temporaryPath, { leaf: 'file' })
313
- }
314
- renameSync(temporaryPath, destinationPath)
315
- } finally {
316
- if (!managedRoot || !lstatSync(temporaryPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
317
- rmSync(temporaryPath, { force: true })
318
- }
319
- }
320
- }
169
+ // Resolve which per-module fact-sheets this app gets (enabled ∩ bundled allowlist).
170
+ const selectedModules = selectModuleFactSheets(targetDir, join(GUIDES_DIR, 'modules'))
171
+ const moduleGuideLabels = readModuleGuideLabels(GUIDES_DIR)
321
172
 
322
- function atomicWriteFile(destinationPath: string, content: string, managedRoot?: string): void {
323
- if (managedRoot) {
324
- assertManagedPath(managedRoot, destinationPath, { leaf: 'file' })
325
- ensureManagedParent(managedRoot, destinationPath)
326
- } else {
327
- ensureDir(destinationPath)
328
- }
329
- const temporaryPath = `${destinationPath}.tmp-${process.pid}-${Date.now()}`
330
- try {
331
- if (managedRoot) assertManagedPath(managedRoot, temporaryPath, { leaf: 'file' })
332
- writeFileSync(temporaryPath, content)
333
- if (managedRoot) {
334
- assertManagedPath(managedRoot, dirname(destinationPath))
335
- assertManagedPath(managedRoot, destinationPath, { leaf: 'file' })
336
- assertManagedPath(managedRoot, temporaryPath, { leaf: 'file' })
337
- }
338
- renameSync(temporaryPath, destinationPath)
339
- } finally {
340
- if (!managedRoot || !lstatSync(temporaryPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
341
- rmSync(temporaryPath, { force: true })
342
- }
173
+ // AGENTS.md
174
+ writeTemplate(srcDir, 'AGENTS.md.template', join(targetDir, 'AGENTS.md'), config)
175
+ injectModuleGuides(join(targetDir, 'AGENTS.md'), selectedModules, moduleGuideLabels)
176
+
177
+ // .ai/ structure
178
+ writeTemplate(srcDir, 'ai/specs/README.md', join(targetDir, '.ai', 'specs', 'README.md'), config)
179
+ copyFile(srcDir, 'ai/specs/SPEC-000-template.md', join(targetDir, '.ai', 'specs', 'SPEC-000-template.md'))
180
+ copyFile(srcDir, 'ai/lessons.md', join(targetDir, '.ai', 'lessons.md'))
181
+
182
+ // .ai/skills/ — the shared skills-mixin manifest + tracker + external installer.
183
+ // Skills owned by the external open-mercato/skills collection (code review, spec
184
+ // writing, integration tests, prepare-issue, the auto-* PR family) are NOT copied
185
+ // here; the user installs them with `yarn install-skills` (npx skills add). Only
186
+ // standalone-only + kept-local skills and repo-local override folders are copied.
187
+ copyFile(srcDir, 'ai/skills/tiers.json', join(targetDir, '.ai', 'skills', 'tiers.json'))
188
+ copyFile(srcDir, 'ai/skills/tiers.schema.json', join(targetDir, '.ai', 'skills', 'tiers.schema.json'))
189
+ copyFile(srcDir, 'ai/agentic.config.json', join(targetDir, '.ai', 'agentic.config.json'))
190
+ copyFile(srcDir, 'ai/trackers/github.md', join(targetDir, '.ai', 'trackers', 'github.md'))
191
+ copyFile(srcDir, 'scripts/install-skills.sh', join(targetDir, 'scripts', 'install-skills.sh'))
192
+
193
+ copyFile(
194
+ srcDir,
195
+ 'ai/skills/om-backend-ui-design/SKILL.md',
196
+ join(targetDir, '.ai', 'skills', 'om-backend-ui-design', 'SKILL.md'),
197
+ )
198
+ copyFile(
199
+ srcDir,
200
+ 'ai/skills/om-backend-ui-design/references/ui-components.md',
201
+ join(targetDir, '.ai', 'skills', 'om-backend-ui-design', 'references', 'ui-components.md'),
202
+ )
203
+ copyFile(srcDir, 'ai/skills/om-integration-builder/SKILL.md', join(targetDir, '.ai', 'skills', 'om-integration-builder', 'SKILL.md'))
204
+ if (existsSync(join(srcDir, 'ai', 'skills', 'om-integration-builder', 'STANDALONE.md'))) {
205
+ copyFile(
206
+ srcDir,
207
+ 'ai/skills/om-integration-builder/STANDALONE.md',
208
+ join(targetDir, '.ai', 'skills', 'om-integration-builder', 'STANDALONE.md'),
209
+ )
343
210
  }
344
- }
345
-
346
- function resolveManifestPath(root: string, relativePath: string): string | null {
347
- if (!relativePath || relativePath.includes('\0')) return null
348
- const absolutePath = resolve(root, relativePath)
349
- const normalizedRelative = relative(root, absolutePath)
350
- if (
351
- normalizedRelative === '' ||
352
- normalizedRelative === '..' ||
353
- normalizedRelative.startsWith(`..${sep}`)
354
- ) {
355
- return null
211
+ copyFile(
212
+ srcDir,
213
+ 'ai/skills/om-integration-builder/references/adapter-contracts.md',
214
+ join(targetDir, '.ai', 'skills', 'om-integration-builder', 'references', 'adapter-contracts.md'),
215
+ )
216
+ if (existsSync(join(srcDir, 'ai', 'skills', 'om-integration-builder', 'STANDALONE.md'))) {
217
+ copyFile(
218
+ srcDir,
219
+ 'ai/skills/om-integration-builder/STANDALONE.md',
220
+ join(targetDir, '.ai', 'skills', 'om-integration-builder', 'STANDALONE.md'),
221
+ )
356
222
  }
357
- return absolutePath
358
- }
359
223
 
360
- function normalizeManifestRelativePath(path: string): string {
361
- return path.replace(/\\/g, '/')
362
- }
224
+ copyFile(
225
+ srcDir,
226
+ 'ai/skills/om-system-extension/SKILL.md',
227
+ join(targetDir, '.ai', 'skills', 'om-system-extension', 'SKILL.md'),
228
+ )
229
+ copyFile(
230
+ srcDir,
231
+ 'ai/skills/om-system-extension/references/extension-contracts.md',
232
+ join(targetDir, '.ai', 'skills', 'om-system-extension', 'references', 'extension-contracts.md'),
233
+ )
363
234
 
364
- function readHarnessManifest(manifestPath: string): HarnessManifest | null {
365
- if (!existsSync(manifestPath)) return null
366
- try {
367
- const parsed = JSON.parse(readFileSync(manifestPath, 'utf8')) as Partial<HarnessManifest>
368
- if (
369
- typeof parsed.version !== 'number' ||
370
- typeof parsed.generator !== 'string' ||
371
- !Array.isArray(parsed.files)
372
- ) {
373
- return null
374
- }
375
- const files: HarnessManifestFile[] = []
376
- for (const entry of parsed.files) {
377
- if (
378
- !entry ||
379
- typeof entry.path !== 'string' ||
380
- typeof entry.sha256 !== 'string' ||
381
- !/^[a-f0-9]{64}$/.test(entry.sha256) ||
382
- typeof entry.source !== 'string' ||
383
- typeof entry.userEditable !== 'boolean'
384
- ) {
385
- return null
386
- }
387
- files.push({ ...entry, path: normalizeManifestRelativePath(entry.path) })
388
- }
389
- return { version: parsed.version, generator: parsed.generator, files }
390
- } catch {
391
- return null
392
- }
393
- }
235
+ copyFile(
236
+ srcDir,
237
+ 'ai/skills/om-module-scaffold/SKILL.md',
238
+ join(targetDir, '.ai', 'skills', 'om-module-scaffold', 'SKILL.md'),
239
+ )
240
+ copyFile(
241
+ srcDir,
242
+ 'ai/skills/om-module-scaffold/references/naming-conventions.md',
243
+ join(targetDir, '.ai', 'skills', 'om-module-scaffold', 'references', 'naming-conventions.md'),
244
+ )
245
+ copyFile(
246
+ srcDir,
247
+ 'ai/skills/om-module-scaffold/references/navigation-patterns.md',
248
+ join(targetDir, '.ai', 'skills', 'om-module-scaffold', 'references', 'navigation-patterns.md'),
249
+ )
394
250
 
395
- function targetPathsForTree(sourceRoot: string, destinationRoot: string): string[] {
396
- return listFiles(sourceRoot).map((sourcePath) => join(destinationRoot, relative(sourceRoot, sourcePath)))
397
- }
251
+ copyFile(
252
+ srcDir,
253
+ 'ai/skills/om-troubleshooter/SKILL.md',
254
+ join(targetDir, '.ai', 'skills', 'om-troubleshooter', 'SKILL.md'),
255
+ )
256
+ copyFile(
257
+ srcDir,
258
+ 'ai/skills/om-troubleshooter/references/diagnostic-commands.md',
259
+ join(targetDir, '.ai', 'skills', 'om-troubleshooter', 'references', 'diagnostic-commands.md'),
260
+ )
398
261
 
399
- function finalizeHarnessManifest(config: AgenticConfig, selectedTools: string[]): void {
400
- const { targetDir } = config
401
- const srcDir = join(AGENTIC_DIR, 'shared')
402
- const selectedModules = selectModuleFactSheets(targetDir, join(GUIDES_DIR, 'modules'))
403
- const paths = new Set<string>([
404
- join(targetDir, 'AGENTS.md'),
405
- ...targetPathsForTree(join(srcDir, 'ai'), join(targetDir, '.ai')),
406
- ...targetPathsForTree(join(srcDir, 'scripts'), join(targetDir, 'scripts')),
407
- ])
408
- for (const file of readdirSync(GUIDES_DIR)) {
409
- if (file.endsWith('.md') || file === 'module-facts.json') paths.add(join(targetDir, '.ai', 'guides', file))
410
- }
411
- for (const file of listFiles(join(GUIDES_DIR, 'upstream'))) {
412
- paths.add(join(targetDir, '.ai', 'guides', 'upstream', relative(join(GUIDES_DIR, 'upstream'), file)))
413
- }
414
- for (const moduleId of selectedModules) paths.add(join(targetDir, '.ai', 'guides', 'modules', `${moduleId}.md`))
415
- if (selectedTools.includes('claude-code')) {
416
- paths.add(join(targetDir, 'CLAUDE.md'))
417
- paths.add(join(targetDir, '.claude', 'settings.json'))
418
- paths.add(join(targetDir, '.claude', 'hooks', 'entity-migration-check.ts'))
419
- paths.add(join(targetDir, '.mcp.json.example'))
420
- }
421
- if (selectedTools.includes('codex')) paths.add(join(targetDir, '.codex', 'mcp.json.example'))
422
- if (selectedTools.includes('cursor')) {
423
- for (const file of listFiles(join(AGENTIC_DIR, 'cursor'))) {
424
- const rel = relative(join(AGENTIC_DIR, 'cursor'), file)
425
- paths.add(join(targetDir, '.cursor', rel))
426
- }
427
- }
428
- const manifestPath = join(targetDir, '.ai', 'harness', 'manifest.json')
429
- const externalSkills = externalSkillNames(targetDir)
430
- paths.delete(manifestPath)
431
- const files = [...paths]
432
- .filter((path) => existsSync(path))
433
- .sort((left, right) => left.localeCompare(right))
434
- .map((path) => {
435
- const relativePath = normalizeManifestRelativePath(relative(targetDir, path))
436
- const skillName = relativePath.match(/^\.ai\/skills\/([^/]+)\//)?.[1]
437
- return {
438
- path: relativePath,
439
- sha256: hashFile(path),
440
- source: skillName ? (externalSkills.has(skillName) ? 'external-override' : 'local-skill') : 'generated',
441
- userEditable:
442
- relativePath === 'AGENTS.md'
443
- || relativePath === '.ai/agentic.config.json'
444
- || relativePath === '.ai/lessons.md'
445
- || relativePath.startsWith('.ai/lessons/'),
446
- }
447
- })
448
- atomicWriteFile(
449
- manifestPath,
450
- `${JSON.stringify({ version: 1, generator: harnessGeneratorId(), files }, null, 2)}\n`,
262
+ copyFile(
263
+ srcDir,
264
+ 'ai/skills/om-eject-and-customize/SKILL.md',
265
+ join(targetDir, '.ai', 'skills', 'om-eject-and-customize', 'SKILL.md'),
451
266
  )
452
- }
453
267
 
454
- /**
455
- * Publish a fully generated harness candidate using the previous ownership
456
- * manifest as the only source of ownership. Modified owned files and exact-path
457
- * collisions with unknown files stay untouched; their candidate is written next
458
- * to them as `<path>.incoming` for explicit review.
459
- */
460
- export type HarnessUpdateConflict = { path: string; candidate: string | null }
461
-
462
- function preserveIncomingCandidate(
463
- targetRoot: string,
464
- destinationPath: string,
465
- sourcePath: string,
466
- relativePath: string,
467
- ): string {
468
- for (let suffix = 1; ; suffix += 1) {
469
- const label = suffix === 1 ? '.incoming' : `.incoming.${suffix}`
470
- const candidatePath = `${destinationPath}${label}`
471
- assertManagedPath(targetRoot, candidatePath, { leaf: 'file' })
472
- if (!existsSync(candidatePath)) {
473
- atomicCopyFile(sourcePath, candidatePath, targetRoot)
474
- return `${relativePath}${label}`
475
- }
476
- if (hashFile(candidatePath) === hashFile(sourcePath)) return `${relativePath}${label}`
477
- }
478
- }
268
+ copyFile(
269
+ srcDir,
270
+ 'ai/skills/om-data-model-design/SKILL.md',
271
+ join(targetDir, '.ai', 'skills', 'om-data-model-design', 'SKILL.md'),
272
+ )
273
+ copyFile(
274
+ srcDir,
275
+ 'ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md',
276
+ join(targetDir, '.ai', 'skills', 'om-data-model-design', 'references', 'mikro-orm-cheatsheet.md'),
277
+ )
479
278
 
480
- export function applyHarnessUpdate(
481
- targetDir: string,
482
- stagingDir: string,
483
- options: { force?: boolean } = {},
484
- ): HarnessUpdateConflict[] {
485
- const targetRoot = canonicalHarnessRoot(targetDir)
486
- const stagingRoot = canonicalHarnessRoot(stagingDir)
487
- const targetManifestPath = join(targetRoot, '.ai', 'harness', 'manifest.json')
488
- const stagingManifestPath = join(stagingRoot, '.ai', 'harness', 'manifest.json')
489
- assertManagedPath(targetRoot, targetManifestPath, { leaf: 'file' })
490
- assertManagedPath(stagingRoot, stagingManifestPath, { leaf: 'file' })
491
- const candidateManifest = readHarnessManifest(stagingManifestPath)
492
- if (!candidateManifest) {
493
- throw new Error('Generated harness candidate has a missing or invalid ownership manifest.')
494
- }
279
+ copyFile(
280
+ srcDir,
281
+ 'ai/skills/om-implement-spec/SKILL.md',
282
+ join(targetDir, '.ai', 'skills', 'om-implement-spec', 'SKILL.md'),
283
+ )
495
284
 
496
- const previousManifest = readHarnessManifest(targetManifestPath)
497
- const previousFiles = new Map(previousManifest?.files.map((entry) => [entry.path, entry]) ?? [])
498
- const candidatePaths = new Set(candidateManifest.files.map((entry) => entry.path))
499
- const candidates: Array<{
500
- entry: HarnessManifestFile
501
- sourcePath: string
502
- destinationPath: string
503
- }> = []
504
-
505
- // Validate the complete candidate before writing anything into the app.
506
- for (const entry of candidateManifest.files) {
507
- const sourcePath = resolveManifestPath(stagingRoot, entry.path)
508
- const destinationPath = resolveManifestPath(targetRoot, entry.path)
509
- if (sourcePath) assertManagedPath(stagingRoot, sourcePath, { leaf: 'file' })
510
- if (destinationPath) assertManagedPath(targetRoot, destinationPath, { leaf: 'file' })
511
- if (!sourcePath || !destinationPath || !existsSync(sourcePath) || hashFile(sourcePath) !== entry.sha256) {
512
- throw new Error(`Generated harness candidate is invalid for ${JSON.stringify(entry.path)}.`)
513
- }
514
- candidates.push({ entry, sourcePath, destinationPath })
515
- }
285
+ copyFile(
286
+ srcDir,
287
+ 'ai/skills/om-help/SKILL.md',
288
+ join(targetDir, '.ai', 'skills', 'om-help', 'SKILL.md'),
289
+ )
290
+ copyFile(
291
+ srcDir,
292
+ 'ai/skills/om-help/references/skills-catalog.md',
293
+ join(targetDir, '.ai', 'skills', 'om-help', 'references', 'skills-catalog.md'),
294
+ )
295
+ copyFile(
296
+ srcDir,
297
+ 'ai/skills/om-help/references/workflow-sequences.md',
298
+ join(targetDir, '.ai', 'skills', 'om-help', 'references', 'workflow-sequences.md'),
299
+ )
516
300
 
517
- const conflicts: HarnessUpdateConflict[] = []
518
- for (const { entry, sourcePath, destinationPath } of candidates) {
519
- if (options.force) {
520
- atomicCopyFile(sourcePath, destinationPath, targetRoot)
521
- continue
522
- }
523
- if (!existsSync(destinationPath)) {
524
- atomicCopyFile(sourcePath, destinationPath, targetRoot)
525
- continue
526
- }
301
+ copyFile(
302
+ srcDir,
303
+ 'ai/skills/om-auto-upgrade-0.4.10-to-0.5.0/SKILL.md',
304
+ join(targetDir, '.ai', 'skills', 'om-auto-upgrade-0.4.10-to-0.5.0', 'SKILL.md'),
305
+ )
527
306
 
528
- const currentHash = hashFile(destinationPath)
529
- const previousEntry = previousFiles.get(entry.path)
530
- if (currentHash === entry.sha256 || (previousEntry && currentHash === previousEntry.sha256)) {
531
- atomicCopyFile(sourcePath, destinationPath, targetRoot)
307
+ // Slim repo-local OVERRIDE folders (SKILL.md only) for the external auto-* PR
308
+ // family + single autofix skill. The workflow bodies live in open-mercato/skills
309
+ // (installed via `yarn install-skills`); these overrides adjust them for a
310
+ // standalone app and are read on top of the external skill's built-in workflow.
311
+ // om-prepare-test-env's override carries environment knowledge (cross-platform
312
+ // mercato CLI runner commands) rather than tracker adjustments.
313
+ for (const autoSkill of [
314
+ 'om-auto-create-pr',
315
+ 'om-auto-continue-pr',
316
+ 'om-auto-create-pr-loop',
317
+ 'om-auto-continue-pr-loop',
318
+ 'om-auto-review-pr',
319
+ 'om-auto-fix-issue',
320
+ 'om-prepare-test-env',
321
+ ]) {
322
+ if (!existsSync(join(srcDir, 'ai', 'skills', autoSkill, 'SKILL.md'))) {
532
323
  continue
533
324
  }
534
-
535
- const candidate = preserveIncomingCandidate(targetRoot, destinationPath, sourcePath, entry.path)
536
- conflicts.push({ path: entry.path, candidate })
537
- }
538
-
539
- const retainedRetiredEntries: HarnessManifestFile[] = []
540
- for (const previousEntry of previousManifest?.files ?? []) {
541
- if (candidatePaths.has(previousEntry.path)) continue
542
- const destinationPath = resolveManifestPath(targetRoot, previousEntry.path)
543
- if (!destinationPath || !existsSync(destinationPath)) continue
544
- assertManagedPath(targetRoot, destinationPath, { leaf: 'file' })
545
- let unchanged = false
546
- try {
547
- unchanged = hashFile(destinationPath) === previousEntry.sha256
548
- } catch {
549
- // A user may replace a generated file with another filesystem object.
550
- // Preserve it and retain the ownership tombstone for a later review.
551
- }
552
- if (unchanged) {
553
- assertManagedPath(targetRoot, dirname(destinationPath))
554
- assertManagedPath(targetRoot, destinationPath, { leaf: 'file' })
555
- rmSync(destinationPath, { force: true })
556
- } else {
557
- retainedRetiredEntries.push(previousEntry)
558
- conflicts.push({ path: previousEntry.path, candidate: null })
559
- }
325
+ copyFile(
326
+ srcDir,
327
+ `ai/skills/${autoSkill}/SKILL.md`,
328
+ join(targetDir, '.ai', 'skills', autoSkill, 'SKILL.md'),
329
+ )
560
330
  }
561
331
 
562
- // The candidate hashes deliberately remain in the manifest for conflicts. If
563
- // the user accepts an .incoming file, a later update can recognize it as an
564
- // unmodified owned file. Until then the hash mismatch keeps preserving it.
565
- const finalManifest: HarnessManifest = {
566
- ...candidateManifest,
567
- files: [...candidateManifest.files, ...retainedRetiredEntries],
568
- }
569
- atomicWriteFile(targetManifestPath, `${JSON.stringify(finalManifest, null, 2)}\n`, targetRoot)
570
- return conflicts
571
- }
572
-
573
- // ─── Generators ──────────────────────────────────────────────────────────
574
-
575
- function generateShared(config: AgenticConfig): void {
576
- const { targetDir } = config
577
- const srcDir = join(AGENTIC_DIR, 'shared')
578
-
579
- // Resolve which per-module fact-sheets this app gets (enabled ∩ bundled allowlist).
580
- const selectedModules = selectModuleFactSheets(targetDir, join(GUIDES_DIR, 'modules'))
332
+ copyFile(
333
+ srcDir,
334
+ 'ai/skills/om-trim-unused-modules/SKILL.md',
335
+ join(targetDir, '.ai', 'skills', 'om-trim-unused-modules', 'SKILL.md'),
336
+ )
581
337
 
582
- // One recursive mapping mirrors create-app's shared emitter.
583
- writeTemplate(srcDir, 'AGENTS.md.template', join(targetDir, 'AGENTS.md'), config)
584
- copyTree(join(srcDir, 'ai'), join(targetDir, '.ai'), config)
585
- copyTree(join(srcDir, 'scripts'), join(targetDir, 'scripts'), config)
338
+ copyFile(srcDir, 'ai/qa/tests/playwright.config.ts', join(targetDir, '.ai', 'qa', 'tests', 'playwright.config.ts'))
586
339
 
587
- // Routed conceptual guides are copied wholesale (framework-wide). Per-module
340
+ // Package & conceptual guides are copied wholesale (framework-wide). Per-module
588
341
  // fact-sheets (.ai/guides/modules/<module>.md) are filtered to the app's enabled
589
342
  // module set; the combined module-facts.json sidecar is copied as-is.
590
343
  if (existsSync(GUIDES_DIR)) {
@@ -604,9 +357,6 @@ function generateShared(config: AgenticConfig): void {
604
357
  copyFileSync(moduleFactsPath, destPath)
605
358
  }
606
359
 
607
-
608
- copyTree(join(GUIDES_DIR, 'upstream'), join(guidesDestDir, 'upstream'), config)
609
-
610
360
  const modulesSubdir = join(GUIDES_DIR, 'modules')
611
361
  for (const moduleId of selectedModules) {
612
362
  const destPath = join(guidesDestDir, 'modules', `${moduleId}.md`)
@@ -614,8 +364,6 @@ function generateShared(config: AgenticConfig): void {
614
364
  copyFileSync(join(modulesSubdir, `${moduleId}.md`), destPath)
615
365
  }
616
366
  }
617
-
618
- injectModuleGuides(join(targetDir, 'AGENTS.md'), selectedModules)
619
367
  }
620
368
 
621
369
  function generateClaudeCode(config: AgenticConfig): void {
@@ -627,7 +375,8 @@ function generateClaudeCode(config: AgenticConfig): void {
627
375
  copyFile(srcDir, 'hooks/entity-migration-check.ts', join(targetDir, '.claude', 'hooks', 'entity-migration-check.ts'))
628
376
  copyFile(srcDir, 'mcp.json.example', join(targetDir, '.mcp.json.example'))
629
377
 
630
- // The installer exclusively owns Claude's per-skill compatibility links.
378
+ // Symlink .claude/skills → ../.ai/skills
379
+ ensureSkillsLink(join(targetDir, '.claude', 'skills'), join('..', '.ai', 'skills'))
631
380
  }
632
381
 
633
382
  function generateCodex(config: AgenticConfig): void {
@@ -679,6 +428,17 @@ function generateCursor(config: AgenticConfig): void {
679
428
  // which scripts/install-skills.sh populates.
680
429
  }
681
430
 
431
+ function ensureSkillsLink(linkPath: string, target: string): void {
432
+ ensureDir(linkPath)
433
+ if (existsSync(linkPath) && !lstatSync(linkPath).isSymbolicLink()) {
434
+ return
435
+ }
436
+ if (lstatSync(linkPath, { throwIfNoEntry: false })?.isSymbolicLink()) {
437
+ unlinkSync(linkPath)
438
+ }
439
+ symlinkSync(target, linkPath)
440
+ }
441
+
682
442
  // ─── Wizard ──────────────────────────────────────────────────────────────
683
443
 
684
444
  const TOOLS = [
@@ -691,35 +451,6 @@ const TOOLS = [
691
451
 
692
452
  const SELECTABLE = TOOLS.filter((t) => t.id !== 'multiple' && t.id !== 'skip')
693
453
 
694
- function persistedAgentSelection(targetDir: string): string[] | null {
695
- const selectableIds = SELECTABLE.map((tool) => tool.id)
696
- const tiersPath = join(targetDir, '.ai', 'skills', 'tiers.json')
697
- try {
698
- const tiers = JSON.parse(readFileSync(tiersPath, 'utf8')) as { agents?: { ignore?: unknown } }
699
- if (Array.isArray(tiers.agents?.ignore)
700
- && tiers.agents.ignore.every((id): id is string => typeof id === 'string')) {
701
- const ignored = new Set(tiers.agents.ignore)
702
- return selectableIds.filter((id) => !ignored.has(id))
703
- }
704
- } catch {
705
- // Fall through to the ownership manifest and existing generated assets.
706
- }
707
-
708
- const manifest = readHarnessManifest(join(targetDir, '.ai', 'harness', 'manifest.json'))
709
- const owned = new Set(manifest?.files.map((entry) => entry.path) ?? [])
710
- const selected = SELECTABLE.filter((tool) => {
711
- if (tool.id === 'claude-code') {
712
- return owned.has('CLAUDE.md') || owned.has('.claude/settings.json')
713
- || existsSync(join(targetDir, 'CLAUDE.md')) || existsSync(join(targetDir, '.claude', 'settings.json'))
714
- }
715
- if (tool.id === 'codex') {
716
- return owned.has('.codex/mcp.json.example') || existsSync(join(targetDir, '.codex', 'mcp.json.example'))
717
- }
718
- return owned.has('.cursor/hooks.json') || existsSync(join(targetDir, '.cursor', 'hooks.json'))
719
- }).map((tool) => tool.id)
720
- return selected.length > 0 ? selected : null
721
- }
722
-
723
454
  async function promptSelection(ask: AskFn): Promise<string[]> {
724
455
  console.log('')
725
456
  console.log('🤖 Agentic workflow setup')
@@ -766,8 +497,6 @@ export async function runAgenticSetup(
766
497
 
767
498
  if (options?.tool) {
768
499
  selectedIds = options.tool.split(',').map((t) => t.trim())
769
- } else if (options?.updateHarness) {
770
- selectedIds = persistedAgentSelection(targetDir) ?? await promptSelection(ask)
771
500
  } else {
772
501
  selectedIds = await promptSelection(ask)
773
502
  }
@@ -784,37 +513,12 @@ export async function runAgenticSetup(
784
513
  targetDir,
785
514
  }
786
515
 
787
- const stagingDir = mkdtempSync(join(tmpdir(), 'open-mercato-harness-'))
788
- try {
789
- const modulesSourcePath = join(targetDir, 'src', 'modules.ts')
790
- const modulesCandidatePath = join(stagingDir, 'src', 'modules.ts')
791
- if (existsSync(modulesSourcePath)) {
792
- ensureDir(modulesCandidatePath)
793
- copyFileSync(modulesSourcePath, modulesCandidatePath)
794
- }
795
-
796
- const stagingConfig: AgenticConfig = {
797
- projectName: config.projectName,
798
- targetDir: stagingDir,
799
- }
800
- generateHarness(stagingConfig, selectedIds)
801
- const conflicts = applyHarnessUpdate(targetDir, stagingDir, {
802
- force: options?.force,
803
- })
804
- if (conflicts.length > 0) {
805
- console.warn('')
806
- console.warn(' ⚠ Preserved locally modified harness files:')
807
- for (const conflict of conflicts) {
808
- const detail = conflict.candidate
809
- ? `candidate: ${conflict.candidate}`
810
- : 'retired asset kept because it has local changes'
811
- console.warn(` • ${conflict.path} (${detail})`)
812
- }
813
- }
814
- } finally {
815
- rmSync(stagingDir, { recursive: true, force: true })
816
- }
516
+ generateShared(config)
517
+ if (selectedIds.includes('claude-code')) generateClaudeCode(config)
518
+ if (selectedIds.includes('codex')) generateCodex(config)
519
+ if (selectedIds.includes('cursor')) generateCursor(config)
817
520
 
521
+ persistAgentSelection(targetDir, selectedIds)
818
522
  installSkills(targetDir)
819
523
 
820
524
  console.log('')
@@ -836,16 +540,6 @@ export async function runAgenticSetup(
836
540
  console.log('')
837
541
  }
838
542
 
839
- function generateHarness(config: AgenticConfig, selectedIds: string[]): void {
840
- generateShared(config)
841
- if (selectedIds.includes('claude-code')) generateClaudeCode(config)
842
- if (selectedIds.includes('codex')) generateCodex(config)
843
- if (selectedIds.includes('cursor')) generateCursor(config)
844
-
845
- persistAgentSelection(config.targetDir, selectedIds)
846
- finalizeHarnessManifest(config, selectedIds)
847
- }
848
-
849
543
  /**
850
544
  * Persist the agent selection so later `yarn install-skills` runs keep honoring
851
545
  * it: agents the user did not pick go into `agents.ignore` in tiers.json and
@@ -865,11 +559,11 @@ function persistAgentSelection(targetDir: string, selectedIds: string[]): void {
865
559
  }
866
560
 
867
561
  function installSkills(targetDir: string): void {
868
- const installScript = join(targetDir, 'scripts', 'install-skills.mjs')
562
+ const installScript = join(targetDir, 'scripts', 'install-skills.sh')
869
563
  if (!existsSync(installScript)) return
870
564
  console.log('')
871
565
  console.log(' Installing agent skills (local tiers + external open-mercato/skills subset)...')
872
- const result = spawnSync(process.execPath, [installScript], { cwd: targetDir, stdio: 'inherit' })
566
+ const result = spawnSync('sh', [installScript], { cwd: targetDir, stdio: 'inherit' })
873
567
  if (result.error || result.status !== 0) {
874
568
  console.log(' ⚠ Skill installation did not complete; run `yarn install-skills` inside the app when online.')
875
569
  }