@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,27 +0,0 @@
1
- # Extension Mechanism Selector
2
-
3
- Load this reference before choosing files.
4
-
5
- | Requirement | Smallest mechanism |
6
- |---|---|
7
- | Add computed/list/detail fields | Response enricher; add a widget only when UI is also needed. |
8
- | Enrich query-engine reads | Enricher with `queryEngine.enabled`; preserve `*.querying`/`*.queried` lifecycle contracts. |
9
- | Validate/rewrite host request or add response data | API interceptor. |
10
- | Add behavior before/after command execute or undo | `commands/interceptors.ts` command interceptor with stable `id`/`targetCommand`. |
11
- | Block/rewrite host mutation with post-success work | Mutation guard contract. |
12
- | Add form/table/menu/page content | Headless/rendered widget injection. |
13
- | Filter a client widget reaction | Widget `eventHandlers.filter.operations`. |
14
- | Persist app-owned data against host record | Extension entity with scalar host ID. |
15
- | React synchronously or asynchronously | Typed subscriber; use `metadata.sync`/`priority` only for in-pipeline lifecycle work. |
16
- | React to a browser event | Reactive notification handler, or `clientBroadcast` plus `useAppEvent`/`useOperationProgress`. |
17
- | Add provider setup/status/detail UI | Typed integration definition plus integration wizard/status/detail widget injection. |
18
- | Add vector, embedded AI, or provider/domain behavior | `vector.ts`, `<AiChat>`, or the typed payment/shipping/currency/workflow registry; add the owning specialist skill. |
19
- | Adjust component behavior | Props transform, wrapper, then replacement. |
20
- | Hide/replace supported route/page/agent/tool/etc. | `src/modules.ts` unified override. |
21
- | Change unsupported internals | Eject only after explicit approval. |
22
-
23
- Resolve exact targets from the named module sheet's `UMES hosts` and `UMES contributions` first. Use `.ai/guides/framework-extension-points.md` for framework-owned menus, dashboard, notification, integration, and shell targets. Follow `fact-ref` and `specialistRoute` provenance instead of inventing a generic target; use exact installed context only for one named fact gap. `optional-external` degrades when its package is absent, while an `unresolved` first-party target blocks implementation.
24
-
25
- If multiple mechanisms are required (for example editable field = widget + enricher + interceptor), follow the shared `roundTripId`, name one owner for each read/write/UI leg, and test them as one round trip. Select only facts marked bound: DataTable's rendered header/footer/toolbar/search-trailing, headless columns/row-actions/bulk-actions/filters, and component handle; CrudForm's base/header/fields, component handle, and declared lifecycle/operation-filter pipeline. Base prefixes and helper-only/unbound IDs are not mountable. Reactive notifications, messages/inbox, query/vector, integrations, and AI/workflow work also select their owning task route; UMES does not replace that specialist context.
26
-
27
- Prefer additive composition before replacement (`additive-before-replacement`). Ejection is the last resort after supported seams are proven insufficient and explicit approval is granted (`eject-last`).
@@ -1,13 +0,0 @@
1
- # Editable Extension Round Trip
2
-
3
- Load this reference for app fields/actions added to installed UI/data.
4
-
5
- 1. Define app-owned storage or an existing host-supported custom field; keep full scope and locking.
6
- 2. Add an enricher/detail read that returns a stable typed value, including explicit null.
7
- 3. Inject a typed field/control into the exact CrudForm/DataTable host.
8
- 4. Intercept/guard/dispatch the write through a command; validate scope/features/version and support clear-to-null.
9
- 5. Invalidate the owning data/cache/search projections after commit.
10
- 6. Map response data into initial values without truthy defaults.
11
- 7. Test create or first save, reload, edit, clear, stale version, denied/wildcard ACL, and host absence.
12
-
13
- Do not stop after rendering the field. A visible input without read and durable write paths is incomplete.
@@ -1,41 +0,0 @@
1
- # Unified Override Domains
2
-
3
- Load this reference when `src/modules.ts` must disable or replace an installed contribution. `entry.overrides` is the only canonical app-level override umbrella. Resolve the exact key from the named module sheet's generated `overrideTargets` facts first: each target carries the exact top-level `domain`, the complete `path` segments, the terminal `key` for keyed-record domains, the supported `modes`, a `factRef` to the module-owned fact being overridden, and a portable `source`. Follow `factRef` provenance to the underlying route/page/event/worker/widget/agent/tool/setup/ACL/DI/encryption fact. When the module sheet reports an `overrideTargetDiagnostics` entry (`missing-owned-fact`, `missing-source`, `unsupported-dynamic-key`, `unknown-framework-domain`) for the domain you need, treat it as a blocker and escalate to the named source for one missing detail — never guess a key. Framework-only settings such as `nav.groupOrder` live in `framework-extension-points.md`, not in a module's `overrideTargets`. For keyed override maps, `null` disables a supported contribution and a typed value replaces it; AI extensions and setup hooks have the special shapes below.
4
-
5
- | Domain | Shape/key |
6
- |---|---|
7
- | AI agents/tools | `overrides.ai.agents`, `overrides.ai.tools` — agent ID/tool name |
8
- | Additive AI extensions | `overrides.ai.extensions` — ordered `AiAgentExtension[]`; additive array, not a key-to-`null` replacement map |
9
- | API routes | `overrides.routes.api` — `METHOD /api/path` |
10
- | Backend/frontend pages | `overrides.routes.pages` — `/backend/...` or `/frontend/...` |
11
- | Event subscribers | `overrides.events.subscribers` — subscriber ID |
12
- | Workers | `overrides.workers` — resolved `ModuleWorker.id`; explicit `metadata.id` wins, otherwise generated fallback `<module>:workers:<path>` |
13
- | Injection widgets | `overrides.widgets.injection` — generated registry `entry.key` |
14
- | Component overrides | `overrides.widgets.components` — component handle |
15
- | Dashboard widgets | `overrides.widgets.dashboard` — generated registry `entry.key` |
16
- | Notification types/handlers | `overrides.notifications.types`, `.handlers` — stable ID |
17
- | API interceptors | `overrides.interceptors` — interceptor ID |
18
- | Command interceptors | `overrides.commandInterceptors` — interceptor ID |
19
- | Response enrichers | `overrides.enrichers` — enricher ID |
20
- | Page guards/middleware | `overrides.guards` — middleware ID |
21
- | CLI commands | `overrides.cli` — command string |
22
- | Setup hooks | `overrides.setup` — entry-scoped `defaultRoleFeatures`, `defaultCustomerRoleFeatures`, `seedDefaults`, `seedExamples`, `onTenantCreated`; hook booleans use `false` to disable |
23
- | ACL features | `overrides.acl.features` — feature ID |
24
- | DI bindings | `overrides.di` — container key |
25
- | Encryption maps | `overrides.encryption.maps` — entity ID |
26
-
27
- ## Rules
28
-
29
- - A global override map may be declared on an app override entry; it need not impersonate the module that originally contributed the keyed contract. Setup is entry-scoped. Do not add a competing route/widget/worker and hope load order wins.
30
- - API route methods are case-insensitive and paths normalize a leading/trailing slash; use the canonical displayed key. Disabling all methods removes the route entry.
31
- - Page keys name generated frontend/backend URL paths, not filesystem paths. After hiding a landing page, provide a safe accessible redirect.
32
- - Injection/dashboard keys are generated entry keys and may differ from widget metadata IDs. Worker keys are resolved descriptor IDs; inspect generated facts/source instead of constructing them from memory.
33
- - Require the generated `overrideTargets` entry's exact `domain`, `path`/`key`, and `modes` (`disable-replace`, `replace`, or `additive`). A stale or `unresolved` first-party correlation, or an `overrideTargetDiagnostics` row for that domain, is a blocker; it is not a fallback to a guessed key. Copy the `path` verbatim — do not flatten nested widget/AI segments.
34
- - `overrides.guards` targets backend/frontend page route middleware only (the `page-middleware` owned fact). A `data/guards.ts` mutation guard is a different contract with no override domain; it surfaces as an extension activation, never under `overrides.guards`. The target's `page-middleware-not-mutation-guard` note flags this.
35
- - Before adding a contribution, check the target module sheet's `incoming` index for an already-installed contributor of the same kind/target, and require a `bound` `activation` (not a broad capability host) before assuming a route/entity extension actually runs. A `capability-only` host is available but unproven; do not claim runtime invocation from it.
36
- - Keep AI extensions additive and ordered. Use agent/tool maps only for replacement or disable; do not encode an extension as a `null`-capable map entry.
37
- - Preserve replacement type/signature, auth/features, scope, stable ID, and observable side effects. A replacement may strengthen safety but never silently narrow a public contract.
38
- - Treat unknown/stale override diagnostics as failures. Run `yarn generate`, clear structural nav/module caches with the app's documented command, and verify the old contribution is absent in every bootstrap.
39
- - Verify host present/absent, wildcard ACL, direct URL/API access, registry uniqueness, cache/navigation cleanup, and rollback to the base contribution.
40
-
41
- For additive behavior use an enricher, interceptor, guard, widget/menu, extension entity, event subscriber, or component wrapper instead of replacing the whole contribution.
@@ -1,11 +0,0 @@
1
- # Extension Verification
2
-
3
- Load this reference after implementing the selected mechanism.
4
-
5
- - Confirm no installed/generated file changed and app module ownership is clear.
6
- - Confirm resolved host tokens match the installed version and generated facts.
7
- - Test host present and absent, authorized/denied/wildcard users, two scopes, and direct API access without UI.
8
- - Test list/detail/create/update/delete/action legs that the extension touches.
9
- - Test timeout/failure fallback, retry/idempotency, cache hit/miss, and search/index behavior where relevant.
10
- - Run `yarn generate`, focused tests, typecheck, and affected integration flow.
11
- - Disable/replace branches must remove stale navigation/cache/registry behavior and retain a safe backend landing route.
@@ -1,15 +0,0 @@
1
- # Module Dependency and Redirect Audit
2
-
3
- Load before disabling modules.
4
-
5
- For each enabled module, inspect:
6
-
7
- - declared/soft dependencies and `src/modules.ts` overrides;
8
- - app imports, pages, widgets, events/subscribers, DI resolves, setup, CLI, workers, AI/workflows;
9
- - provider/integration hosts and generated module facts;
10
- - backend navigation and landing destination;
11
- - auth, configs, entities, query index, and other bootstrap infrastructure.
12
-
13
- Classify uncertainty as keep until resolved. When dashboards is disabled, replace its backend landing render/import with a role/ACL-aware redirect to the first enabled main destination; use `/backend/profile` only when no accessible business page exists.
14
-
15
- After changes run generation, login/setup, backend root/sidebar/settings/profile, CLI/worker bootstrap, typecheck/build, and affected tests. Confirm no generated import still targets a disabled package/module.
@@ -1,16 +0,0 @@
1
- # Diagnosis Map
2
-
3
- Load the matching row only.
4
-
5
- | Failure | First trace |
6
- |---|---|
7
- | Scope/auth/401 loop | Auth-derived tenant/org, all-org mode, wildcard features, alternate route metadata. |
8
- | Lost/partial write | Validator/command, transaction/atomic flush, optimistic lock, post-commit side effects. |
9
- | Field round trip | Form ID/initial value, payload validator, command mapping, entity nullability, response transform. |
10
- | Generated/import failure | Discovery filename/exports, `src/modules.ts`, generation warnings, package exports/installed dist. |
11
- | Browser/CLI/worker mismatch | Each bootstrap's registry/DI init, generated imports, chunk-global identity. |
12
- | Cache/search stale | Command aliases, tags/invalidation, undo/sub-resource path, convergence/reindex. |
13
- | Hydration/navigation UI | Locale/timezone/env first render, page metadata, stable IDs, auth versus visibility. |
14
- | Provider sync | Idempotency, signature, retry class, cursor commit, mapping, webhook/poll race, reconciliation. |
15
-
16
- Trace from the public call site to the first broken invariant. Avoid broad refactors until the oracle proves the location.
@@ -1,14 +0,0 @@
1
- # Regression Oracles
2
-
3
- Load the relevant oracle family before implementing the fix.
4
-
5
- - **Scope:** two tenants/two orgs, permitted/denied/null/all-org/wildcard; invalid scope never widens.
6
- - **Atomic write:** inject failure between phases; no partial data or premature event/cache/index side effect.
7
- - **Locking:** current update succeeds; stale update/delete/action returns structured 409 and UI conflict.
8
- - **Field:** create, reload, edit, clear to null, reload; request/DB/response/UI agree.
9
- - **Bootstrap:** same registry/service behavior in browser/server, CLI, worker, and queue from packed install.
10
- - **Cache/search:** immediate write read uses defined consistency; convergence polling has deadline and no arbitrary sleep.
11
- - **Hydration:** server/client initial output matches across representative locale/timezone/environment.
12
- - **Provider:** duplicate/concurrent/retry/timeout/page failure; idempotency and cursor/mapping state remain correct; secrets absent.
13
-
14
- The oracle must fail on the pre-fix code for the intended reason and clean up its own fixtures.
@@ -1,187 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from 'node:fs'
4
- import path from 'node:path'
5
- import process from 'node:process'
6
-
7
- const MAX_READ_BYTES = 256 * 1024
8
- const MAX_WRITE_BYTES = 256 * 1024
9
- const SAFE_TEXT_EXTENSIONS = new Set([
10
- '.cjs', '.css', '.graphql', '.html', '.js', '.json', '.jsx', '.md', '.mdx',
11
- '.mjs', '.prisma', '.sql', '.ts', '.tsx', '.txt', '.xml', '.yaml', '.yml',
12
- ])
13
- const FORBIDDEN_SEGMENTS = new Set(['.git', '.docker', '.kube', '.aws', '.ssh', '.codex', '.claude', '.next', 'dist'])
14
- const FORBIDDEN_BASENAMES = new Set([
15
- '.npmrc', '.netrc', '.pypirc', '.git-credentials', 'secrets.json', 'credentials.json',
16
- 'service-account-credentials.json',
17
- ])
18
- const FORBIDDEN_SECRET_EXTENSIONS = new Set(['.key', '.pem', '.p12', '.pfx'])
19
-
20
- function fail(message) {
21
- process.stderr.write(`agent-harness-tool-server: ${message}\n`)
22
- process.exit(2)
23
- }
24
-
25
- function isInside(root, candidate) {
26
- const relative = path.relative(root, candidate)
27
- return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))
28
- }
29
-
30
- function isInstalledSourcePath(value, allowPatterns) {
31
- const normalized = value.replaceAll('\\', '/')
32
- const segments = normalized.split('/')
33
- if (segments.length < 5 || segments[0] !== 'node_modules' || segments[1] !== '@open-mercato' || segments[3] !== 'src') return false
34
- if (!segments[2] || segments.slice(4).some((entry) => !entry)) return false
35
- return allowPatterns || !normalized.includes('*') && !normalized.includes('?')
36
- }
37
-
38
- function safeRelative(value, { allowInstalledSourcePatterns = false } = {}) {
39
- if (typeof value !== 'string' || !value || value.includes('\0') || path.isAbsolute(value)) return false
40
- const normalized = value.replaceAll('\\', '/')
41
- if (normalized === '.' || normalized.startsWith('../') || normalized.includes('/../')) return false
42
- const segments = normalized.split('/')
43
- const basename = segments.at(-1) ?? ''
44
- const installedSource = segments.includes('node_modules')
45
- if (installedSource && !isInstalledSourcePath(normalized, allowInstalledSourcePatterns)) return false
46
- if ((installedSource ? segments.slice(4) : segments).some((entry) => FORBIDDEN_SEGMENTS.has(entry))) return false
47
- if (normalized === '.ai/harness' || normalized.startsWith('.ai/harness/')) return false
48
- if (basename === '.env' || basename.startsWith('.env.') || FORBIDDEN_BASENAMES.has(basename)) return false
49
- return !FORBIDDEN_SECRET_EXTENSIONS.has(path.extname(basename).toLowerCase())
50
- }
51
-
52
- function globToRegExp(pattern) {
53
- let source = '^'
54
- for (let index = 0; index < pattern.length; index += 1) {
55
- const char = pattern[index]
56
- if (char === '*' && pattern[index + 1] === '*') { source += '.*'; index += 1 }
57
- else if (char === '*') source += '[^/]*'
58
- else if (char === '?') source += '[^/]'
59
- else source += char.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
60
- }
61
- return new RegExp(`${source}$`)
62
- }
63
-
64
- const rootArg = process.argv[2]
65
- const mode = process.argv[3]
66
- let allowedReads
67
- let allowedWrites
68
- try { allowedReads = JSON.parse(process.argv[4] ?? '[]') } catch { fail('allowed read set is invalid JSON') }
69
- try { allowedWrites = JSON.parse(process.argv[5] ?? '[]') } catch { fail('allowed write set is invalid JSON') }
70
- if (!path.isAbsolute(rootArg ?? '')) fail('root must be absolute')
71
- if (!['read-only', 'writable'].includes(mode)) fail('mode must be read-only or writable')
72
- if (!Array.isArray(allowedReads) || allowedReads.some((entry) => !safeRelative(entry, { allowInstalledSourcePatterns: true }) || ['*', '**'].includes(entry))) fail('allowed read set is invalid')
73
- if (!Array.isArray(allowedWrites) || allowedWrites.some((entry) => !safeRelative(entry))) fail('allowed write set is invalid')
74
- const root = fs.realpathSync(rootArg)
75
- const readMatchers = allowedReads.map(globToRegExp)
76
- const writeMatchers = allowedWrites.map(globToRegExp)
77
-
78
- function resolveRead(relative) {
79
- if (!safeRelative(relative)) throw new Error('path is outside the allowed app context')
80
- const normalized = relative.replaceAll('\\', '/')
81
- if (!readMatchers.some((matcher) => matcher.test(normalized))) throw new Error('path is outside the case read allowlist')
82
- const lexical = path.resolve(root, relative)
83
- if (!isInside(root, lexical)) throw new Error('path escapes the app root')
84
- const real = fs.realpathSync(lexical)
85
- if (isInstalledSourcePath(normalized, false)) {
86
- const dependencyRoot = fs.realpathSync(path.join(root, 'node_modules'))
87
- if (!isInside(dependencyRoot, real)) throw new Error('installed source resolves outside the protected dependency root')
88
- } else if (!isInside(root, real)) throw new Error('path resolves outside the app root')
89
- const stat = fs.statSync(real)
90
- if (!stat.isFile() || stat.size > MAX_READ_BYTES) throw new Error('path must be a bounded regular file')
91
- if (!SAFE_TEXT_EXTENSIONS.has(path.extname(real).toLowerCase()) && path.basename(real) !== 'AGENTS.md') throw new Error('path is not an allowed text file')
92
- return { real, stat }
93
- }
94
-
95
- function resolveWrite(relative) {
96
- if (mode !== 'writable') throw new Error('writes are disabled for this evaluation')
97
- if (!safeRelative(relative) || !writeMatchers.some((matcher) => matcher.test(relative.replaceAll('\\', '/')))) throw new Error('path is outside the case write allowlist')
98
- const lexical = path.resolve(root, relative)
99
- if (!isInside(root, lexical)) throw new Error('path escapes the app root')
100
- let cursor = path.dirname(lexical)
101
- const missing = []
102
- while (cursor !== root && !fs.existsSync(cursor)) { missing.push(cursor); cursor = path.dirname(cursor) }
103
- if (!isInside(root, cursor)) throw new Error('write ancestor escapes the app root')
104
- for (let current = cursor; current !== root; current = path.dirname(current)) {
105
- const stat = fs.lstatSync(current)
106
- if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('write ancestor must be a regular directory')
107
- }
108
- if (fs.existsSync(lexical)) {
109
- const stat = fs.lstatSync(lexical)
110
- if (!stat.isFile() || stat.isSymbolicLink() || !isInside(root, fs.realpathSync(lexical))) throw new Error('write target must be a contained regular file')
111
- }
112
- for (const directory of missing.reverse()) fs.mkdirSync(directory, { mode: 0o700 })
113
- return lexical
114
- }
115
-
116
- const tools = [
117
- {
118
- name: 'read',
119
- description: 'Read one exact app-relative instruction, fact, source, or allowed target file. Fact-linked @open-mercato package source reads are allowed; absolute paths, discovery, credentials, dependency writes, Git, and harness internals are rejected.',
120
- inputSchema: { type: 'object', additionalProperties: false, required: ['path'], properties: { path: { type: 'string' } } },
121
- },
122
- ...(mode === 'writable' ? [{
123
- name: 'write',
124
- description: 'Replace one exact app-relative file declared in the current case write allowlist. The content must be the complete file.',
125
- inputSchema: { type: 'object', additionalProperties: false, required: ['path', 'content'], properties: { path: { type: 'string' }, content: { type: 'string' } } },
126
- }] : []),
127
- ]
128
-
129
- function result(id, value) {
130
- process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, result: value })}\n`)
131
- }
132
-
133
- function toolResult(id, text, isError = false) {
134
- result(id, { content: [{ type: 'text', text }], ...(isError ? { isError: true } : {}) })
135
- }
136
-
137
- function handle(message) {
138
- if (!message || message.jsonrpc !== '2.0') return
139
- if (message.method === 'initialize') {
140
- result(message.id, {
141
- protocolVersion: message.params?.protocolVersion ?? '2025-06-18',
142
- capabilities: { tools: { listChanged: false } },
143
- serverInfo: { name: 'open-mercato-agent-harness', version: '1.0.0' },
144
- })
145
- return
146
- }
147
- if (message.method === 'ping') { result(message.id, {}); return }
148
- if (message.method === 'tools/list') { result(message.id, { tools }); return }
149
- if (message.method !== 'tools/call') return
150
- try {
151
- const name = message.params?.name
152
- const input = message.params?.arguments ?? {}
153
- if (name === 'read') {
154
- const { real } = resolveRead(input.path)
155
- toolResult(message.id, fs.readFileSync(real, 'utf8'))
156
- return
157
- }
158
- if (name === 'write') {
159
- if (typeof input.content !== 'string' || Buffer.byteLength(input.content) > MAX_WRITE_BYTES) throw new Error('content must be a bounded string')
160
- const target = resolveWrite(input.path)
161
- const temporary = `${target}.om-harness-${process.pid}.tmp`
162
- fs.writeFileSync(temporary, input.content, { encoding: 'utf8', mode: 0o600, flag: 'wx' })
163
- fs.renameSync(temporary, target)
164
- toolResult(message.id, `wrote ${input.path}`)
165
- return
166
- }
167
- throw new Error('unknown tool')
168
- } catch (error) {
169
- toolResult(message.id, error instanceof Error ? error.message : String(error), true)
170
- }
171
- }
172
-
173
- let buffer = ''
174
- process.stdin.setEncoding('utf8')
175
- process.stdin.on('data', (chunk) => {
176
- buffer += chunk
177
- while (true) {
178
- const newline = buffer.indexOf('\n')
179
- if (newline < 0) break
180
- const line = buffer.slice(0, newline).replace(/\r$/, '')
181
- buffer = buffer.slice(newline + 1)
182
- if (!line.trim()) continue
183
- try { handle(JSON.parse(line)) } catch (error) {
184
- process.stderr.write(`agent-harness-tool-server: invalid request: ${error instanceof Error ? error.message : String(error)}\n`)
185
- }
186
- }
187
- })
@@ -1,178 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import fs from 'node:fs'
4
- import path from 'node:path'
5
- import { fileURLToPath, pathToFileURL } from 'node:url'
6
-
7
- export const LESSON_AREAS = new Set([
8
- 'architecture',
9
- 'module-data',
10
- 'umes',
11
- 'backend-ui',
12
- 'integration',
13
- 'ai-workflow',
14
- 'debugging',
15
- 'testing',
16
- 'framework-context',
17
- 'spec-pr',
18
- ])
19
-
20
- const INDEX_MAX_BYTES = 30 * 1024
21
- const RECORD_NAME_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*\.md$/
22
- const MODULE_PATTERN = /^[a-z0-9]+(?:_[a-z0-9]+)*$/
23
- const TOPIC_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
24
- const INDEX_ROW_PATTERN = /^- \[(.+)]\(lessons\/([a-z0-9-]+\.md)\) — area:([^;]+); module:([^;]+); topic:(.+)$/
25
-
26
- function parseFrontMatter(source, relativePath, errors) {
27
- const match = source.match(/^---\n([\s\S]*?)\n---\n\n# (.+)\n/)
28
- if (!match) {
29
- errors.push(`${relativePath}: expected JSON-valued front matter followed by one H1`)
30
- return null
31
- }
32
-
33
- const metadata = {}
34
- for (const line of match[1].split('\n')) {
35
- const separator = line.indexOf(':')
36
- if (separator < 1) {
37
- errors.push(`${relativePath}: malformed front matter line ${JSON.stringify(line)}`)
38
- continue
39
- }
40
- const key = line.slice(0, separator)
41
- try {
42
- metadata[key] = JSON.parse(line.slice(separator + 1).trim())
43
- } catch {
44
- errors.push(`${relativePath}: ${key} must use valid JSON syntax`)
45
- }
46
- }
47
-
48
- const expectedKeys = ['title', 'modules', 'areas', 'topics']
49
- const actualKeys = Object.keys(metadata).sort()
50
- if (JSON.stringify(actualKeys) !== JSON.stringify([...expectedKeys].sort())) {
51
- errors.push(`${relativePath}: front matter keys must be exactly ${expectedKeys.join(', ')}`)
52
- }
53
- if (typeof metadata.title !== 'string' || metadata.title.length === 0) {
54
- errors.push(`${relativePath}: title must be a non-empty string`)
55
- } else if (metadata.title !== match[2]) {
56
- errors.push(`${relativePath}: front matter title must match the H1`)
57
- }
58
-
59
- for (const key of ['modules', 'areas', 'topics']) {
60
- if (!Array.isArray(metadata[key])) errors.push(`${relativePath}: ${key} must be an array`)
61
- }
62
- if (!Array.isArray(metadata.modules) || metadata.modules.length === 0) {
63
- errors.push(`${relativePath}: modules must contain an owning module/package or platform`)
64
- } else if (metadata.modules.some((value) => typeof value !== 'string' || !MODULE_PATTERN.test(value))) {
65
- errors.push(`${relativePath}: modules must use lowercase snake_case tags`)
66
- }
67
- if (!Array.isArray(metadata.areas) || metadata.areas.length === 0) {
68
- errors.push(`${relativePath}: areas must contain at least one standalone router area`)
69
- } else if (metadata.areas.some((value) => !LESSON_AREAS.has(value))) {
70
- errors.push(`${relativePath}: areas contain a value outside the standalone router taxonomy`)
71
- }
72
- if (!Array.isArray(metadata.topics) || metadata.topics.length < 2 || metadata.topics.length > 6) {
73
- errors.push(`${relativePath}: topics must contain between two and six important concepts`)
74
- } else if (metadata.topics.some((value) => typeof value !== 'string' || !TOPIC_PATTERN.test(value))) {
75
- errors.push(`${relativePath}: topics must use lowercase kebab-case tags`)
76
- }
77
-
78
- for (const key of ['modules', 'areas', 'topics']) {
79
- if (Array.isArray(metadata[key]) && new Set(metadata[key]).size !== metadata[key].length) {
80
- errors.push(`${relativePath}: ${key} must not contain duplicates`)
81
- }
82
- }
83
-
84
- return metadata
85
- }
86
-
87
- export function checkLessonsCatalog(rootDir) {
88
- const errors = []
89
- const indexPath = path.join(rootDir, '.ai', 'lessons.md')
90
- const lessonsDir = path.join(rootDir, '.ai', 'lessons')
91
- if (!fs.existsSync(indexPath)) return [`${indexPath}: lesson index is missing`]
92
-
93
- const indexSource = fs.readFileSync(indexPath, 'utf8')
94
- const indexBytes = Buffer.byteLength(indexSource)
95
- if (indexBytes > INDEX_MAX_BYTES) {
96
- errors.push(`.ai/lessons.md: ${indexBytes} bytes exceeds the ${INDEX_MAX_BYTES}-byte progressive-loading budget`)
97
- }
98
- if (/\*\*(?:Context|Problem|Rule|Applies to)\*\*:/.test(indexSource)) {
99
- errors.push('.ai/lessons.md: full lesson prose belongs in .ai/lessons/, not the catalog')
100
- }
101
-
102
- const recordNames = fs.existsSync(lessonsDir)
103
- ? fs.readdirSync(lessonsDir, { withFileTypes: true })
104
- .filter((entry) => entry.isFile() && entry.name.endsWith('.md') && !entry.name.startsWith('_'))
105
- .map((entry) => entry.name)
106
- .sort()
107
- : []
108
- const records = new Map()
109
- const titles = new Set()
110
- for (const fileName of recordNames) {
111
- const relativePath = `.ai/lessons/${fileName}`
112
- if (!RECORD_NAME_PATTERN.test(fileName)) errors.push(`${relativePath}: filename must use kebab-case`)
113
- const metadata = parseFrontMatter(fs.readFileSync(path.join(lessonsDir, fileName), 'utf8'), relativePath, errors)
114
- if (!metadata) continue
115
- if (titles.has(metadata.title)) errors.push(`${relativePath}: duplicate lesson title ${JSON.stringify(metadata.title)}`)
116
- titles.add(metadata.title)
117
- records.set(fileName, metadata)
118
- }
119
-
120
- const indexed = new Set()
121
- for (const line of indexSource.split('\n')) {
122
- if (!line.startsWith('- [')) continue
123
- const match = line.match(INDEX_ROW_PATTERN)
124
- if (!match) {
125
- errors.push(`.ai/lessons.md: malformed catalog row ${JSON.stringify(line)}`)
126
- continue
127
- }
128
- const [, title, fileName, areaText, moduleText, topicText] = match
129
- if (indexed.has(fileName)) errors.push(`.ai/lessons.md: duplicate link to lessons/${fileName}`)
130
- indexed.add(fileName)
131
- const metadata = records.get(fileName)
132
- if (!metadata) {
133
- errors.push(`.ai/lessons.md: lessons/${fileName} does not exist`)
134
- continue
135
- }
136
- const indexedMetadata = {
137
- title,
138
- areas: areaText.split(','),
139
- modules: moduleText.split(','),
140
- topics: topicText.split(','),
141
- }
142
- for (const key of ['title', 'areas', 'modules', 'topics']) {
143
- if (JSON.stringify(indexedMetadata[key]) !== JSON.stringify(metadata[key])) {
144
- errors.push(`.ai/lessons.md: ${key} drift for lessons/${fileName}`)
145
- }
146
- }
147
- }
148
-
149
- for (const fileName of records.keys()) {
150
- if (!indexed.has(fileName)) errors.push(`.ai/lessons/${fileName}: record is missing from the catalog`)
151
- }
152
- const declaredCount = indexSource.match(/catalog indexes (\d+) focused lessons/)
153
- if (!declaredCount || Number(declaredCount[1]) !== records.size) {
154
- errors.push(`.ai/lessons.md: declared lesson count must equal ${records.size}`)
155
- }
156
-
157
- return errors
158
- }
159
-
160
- function parseRoot(args) {
161
- if (args.length === 0) return process.cwd()
162
- if (args.length === 2 && args[0] === '--root') return path.resolve(args[1])
163
- throw new Error('Usage: check-lessons.mjs [--root <app-root>]')
164
- }
165
-
166
- export function main(args = process.argv.slice(2)) {
167
- const rootDir = parseRoot(args)
168
- const errors = checkLessonsCatalog(rootDir)
169
- if (errors.length > 0) {
170
- for (const error of errors) console.error(`- ${error}`)
171
- process.exitCode = 1
172
- return
173
- }
174
- console.log(`Lessons catalog is valid: ${rootDir}`)
175
- }
176
-
177
- const entryPath = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : null
178
- if (entryPath === import.meta.url) main()