@open-mercato/cli 0.6.7 → 0.6.8-canary.319.1.5b4be4bc52

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 (287) hide show
  1. package/.turbo/turbo-build.log +77 -4
  2. package/build.mjs +68 -11
  3. package/dist/agentic/claude-code/hooks/entity-migration-check.ts +32 -88
  4. package/dist/agentic/codex/enforcement-rules.md +2 -16
  5. package/dist/agentic/cursor/hooks/entity-migration-check.mjs +19 -50
  6. package/dist/agentic/cursor/rules/entity-guard.mdc +9 -21
  7. package/dist/agentic/cursor/rules/generated-guard.mdc +3 -5
  8. package/dist/agentic/cursor/rules/open-mercato.mdc +3 -29
  9. package/dist/agentic/guides/ai-workflows.md +76 -0
  10. package/dist/agentic/guides/architecture.md +107 -0
  11. package/dist/agentic/guides/backend-ui.md +84 -0
  12. package/dist/agentic/guides/contracts.md +92 -0
  13. package/dist/agentic/guides/extensions.md +121 -0
  14. package/dist/agentic/guides/framework-extension-points.md +36 -0
  15. package/dist/agentic/guides/integrations.md +81 -0
  16. package/dist/agentic/guides/module-facts.json +116757 -3951
  17. package/dist/agentic/guides/module-system.md +12 -127
  18. package/dist/agentic/guides/modules/ai_assistant.md +227 -28
  19. package/dist/agentic/guides/modules/api_docs.md +58 -2
  20. package/dist/agentic/guides/modules/api_keys.md +92 -7
  21. package/dist/agentic/guides/modules/attachments.md +119 -14
  22. package/dist/agentic/guides/modules/audit_logs.md +131 -10
  23. package/dist/agentic/guides/modules/auth.md +276 -33
  24. package/dist/agentic/guides/modules/business_rules.md +149 -14
  25. package/dist/agentic/guides/modules/catalog.md +482 -43
  26. package/dist/agentic/guides/modules/channel_gmail.md +82 -3
  27. package/dist/agentic/guides/modules/channel_imap.md +82 -3
  28. package/dist/agentic/guides/modules/checkout.md +305 -27
  29. package/dist/agentic/guides/modules/communication_channels.md +315 -33
  30. package/dist/agentic/guides/modules/configs.md +104 -8
  31. package/dist/agentic/guides/modules/content.md +57 -2
  32. package/dist/agentic/guides/modules/currencies.md +152 -17
  33. package/dist/agentic/guides/modules/customer_accounts.md +387 -45
  34. package/dist/agentic/guides/modules/customers.md +746 -85
  35. package/dist/agentic/guides/modules/dashboards.md +155 -10
  36. package/dist/agentic/guides/modules/data_sync.md +149 -16
  37. package/dist/agentic/guides/modules/design_system.md +102 -0
  38. package/dist/agentic/guides/modules/dictionaries.md +107 -12
  39. package/dist/agentic/guides/modules/directory.md +148 -15
  40. package/dist/agentic/guides/modules/entities.md +145 -13
  41. package/dist/agentic/guides/modules/events.md +59 -2
  42. package/dist/agentic/guides/modules/feature_toggles.md +119 -9
  43. package/dist/agentic/guides/modules/gateway_stripe.md +92 -4
  44. package/dist/agentic/guides/modules/generators.md +45 -2
  45. package/dist/agentic/guides/modules/inbox_ops.md +207 -27
  46. package/dist/agentic/guides/modules/integrations.md +149 -16
  47. package/dist/agentic/guides/modules/messages.md +223 -24
  48. package/dist/agentic/guides/modules/notifications.md +96 -8
  49. package/dist/agentic/guides/modules/onboarding.md +77 -6
  50. package/dist/agentic/guides/modules/payment_gateways.md +168 -21
  51. package/dist/agentic/guides/modules/perspectives.md +77 -7
  52. package/dist/agentic/guides/modules/planner.md +136 -18
  53. package/dist/agentic/guides/modules/portal.md +93 -2
  54. package/dist/agentic/guides/modules/progress.md +104 -14
  55. package/dist/agentic/guides/modules/query_index.md +154 -15
  56. package/dist/agentic/guides/modules/record_locks.md +206 -21
  57. package/dist/agentic/guides/modules/resources.md +197 -30
  58. package/dist/agentic/guides/modules/sales.md +687 -84
  59. package/dist/agentic/guides/modules/scheduler.md +133 -14
  60. package/dist/agentic/guides/modules/search.md +170 -6
  61. package/dist/agentic/guides/modules/security.md +294 -35
  62. package/dist/agentic/guides/modules/shipping_carriers.md +134 -18
  63. package/dist/agentic/guides/modules/sso.md +151 -26
  64. package/dist/agentic/guides/modules/staff.md +372 -57
  65. package/dist/agentic/guides/modules/storage_s3.md +95 -5
  66. package/dist/agentic/guides/modules/sync_akeneo.md +85 -3
  67. package/dist/agentic/guides/modules/sync_excel.md +75 -6
  68. package/dist/agentic/guides/modules/system_status_overlays.md +60 -2
  69. package/dist/agentic/guides/modules/translations.md +104 -10
  70. package/dist/agentic/guides/modules/webhooks.md +203 -24
  71. package/dist/agentic/guides/modules/wms.md +469 -47
  72. package/dist/agentic/guides/modules/workflows.md +302 -43
  73. package/dist/agentic/guides/reference-module-facts.json +6370 -0
  74. package/dist/agentic/guides/reference-modules/example.md +451 -0
  75. package/dist/agentic/guides/spec-delivery.md +42 -0
  76. package/dist/agentic/guides/testing-debugging.md +80 -0
  77. package/dist/agentic/guides/upstream/AGENTS.md +299 -0
  78. package/dist/agentic/guides/upstream/BACKWARD_COMPATIBILITY.md +316 -0
  79. package/dist/agentic/guides/upstream/manifest.json +8 -0
  80. package/dist/agentic/shared/AGENTS.md.template +120 -311
  81. package/dist/agentic/shared/ai/agentic.config.json +2 -0
  82. package/dist/agentic/shared/ai/harness/README.md +50 -0
  83. package/dist/agentic/shared/ai/harness/RELEASE.md +57 -0
  84. package/dist/agentic/shared/ai/harness/cases.json +15050 -0
  85. package/dist/agentic/shared/ai/harness/cases.schema.json +188 -0
  86. package/dist/agentic/shared/ai/harness/fixtures/index.json +51 -0
  87. package/dist/agentic/shared/ai/harness/fixtures/seeds.json +166 -0
  88. package/dist/agentic/shared/ai/harness/generated-code-review-policy.md +21 -0
  89. package/dist/agentic/shared/ai/harness/generated-code-review-response.schema.json +96 -0
  90. package/dist/agentic/shared/ai/harness/generated-code-review-result.schema.json +172 -0
  91. package/dist/agentic/shared/ai/harness/knowledge-change.schema.json +228 -0
  92. package/dist/agentic/shared/ai/harness/release-matrix.json +163 -0
  93. package/dist/agentic/shared/ai/harness/release-result.schema.json +219 -0
  94. package/dist/agentic/shared/ai/harness/result.schema.json +109 -0
  95. package/dist/agentic/shared/ai/harness/routing-response.schema.json +22 -0
  96. package/dist/agentic/shared/ai/harness/target-validation-result.schema.json +73 -0
  97. package/dist/agentic/shared/ai/harness/validators.json +82 -0
  98. package/dist/agentic/shared/ai/harness/writable-ast-oracles.mjs +2121 -0
  99. package/dist/agentic/shared/ai/harness/writable-behavior-oracles.mjs +1096 -0
  100. package/dist/agentic/shared/ai/harness/writable-spec-oracles.mjs +412 -0
  101. package/dist/agentic/shared/ai/lessons/_template.md +16 -0
  102. package/dist/agentic/shared/ai/lessons.md +26 -3
  103. package/dist/agentic/shared/ai/review-checklist.md +36 -0
  104. package/dist/agentic/shared/ai/skills/om-auto-implement-spec/SKILL.md +21 -0
  105. package/dist/agentic/shared/ai/skills/om-auto-review-pr/SKILL.md +4 -0
  106. package/dist/agentic/shared/ai/skills/om-auto-upgrade-0.6.6-to-0.6.7/SKILL.md +98 -0
  107. package/dist/agentic/shared/ai/skills/om-backend-ui-design/SKILL.md +21 -241
  108. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/crud-surfaces.md +31 -0
  109. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/frontend-and-design-system.md +53 -0
  110. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/page-and-navigation.md +15 -0
  111. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/quality-states.md +11 -0
  112. package/dist/agentic/shared/ai/skills/om-build-workflow/SKILL.md +30 -0
  113. package/dist/agentic/shared/ai/skills/om-build-workflow/references/activity-contracts.md +12 -0
  114. package/dist/agentic/shared/ai/skills/om-build-workflow/references/durability-and-progress.md +13 -0
  115. package/dist/agentic/shared/ai/skills/om-build-workflow/references/workflow-design.md +14 -0
  116. package/dist/agentic/shared/ai/skills/om-create-ai-agent/SKILL.md +28 -0
  117. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/attachments-and-overrides.md +12 -0
  118. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/module-agents-and-tools.md +22 -0
  119. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/orchestrator-agents.md +12 -0
  120. package/dist/agentic/shared/ai/skills/om-create-ai-agent/references/surface-selector.md +19 -0
  121. package/dist/agentic/shared/ai/skills/om-data-model-design/SKILL.md +17 -601
  122. package/dist/agentic/shared/ai/skills/om-data-model-design/references/integrity-and-concurrency.md +21 -0
  123. package/dist/agentic/shared/ai/skills/om-data-model-design/references/migration-workflow.md +15 -0
  124. package/dist/agentic/shared/ai/skills/om-data-model-design/references/schema-design.md +16 -0
  125. package/dist/agentic/shared/ai/skills/om-data-model-design/references/sensitive-data.md +10 -0
  126. package/dist/agentic/shared/ai/skills/om-eject-and-customize/SKILL.md +15 -310
  127. package/dist/agentic/shared/ai/skills/om-eject-and-customize/references/decision-and-procedure.md +19 -0
  128. package/dist/agentic/shared/ai/skills/om-evolve-harness/SKILL.md +31 -0
  129. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-template.md +100 -0
  130. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/case-workflow.md +16 -0
  131. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/knowledge-change.md +115 -0
  132. package/dist/agentic/shared/ai/skills/om-evolve-harness/references/owner-selection.md +17 -0
  133. package/dist/agentic/shared/ai/skills/om-framework-context/SKILL.md +25 -0
  134. package/dist/agentic/shared/ai/skills/om-framework-context/references/resolver-procedure.md +11 -0
  135. package/dist/agentic/shared/ai/skills/om-framework-context/references/skew-and-escalation.md +13 -0
  136. package/dist/agentic/shared/ai/skills/om-help/SKILL.md +15 -97
  137. package/dist/agentic/shared/ai/skills/om-help/references/delivery-workflows.md +18 -0
  138. package/dist/agentic/shared/ai/skills/om-help/references/task-families.md +20 -0
  139. package/dist/agentic/shared/ai/skills/om-implement-spec/SKILL.md +33 -152
  140. package/dist/agentic/shared/ai/skills/om-implement-spec/references/phases-and-gates.md +33 -0
  141. package/dist/agentic/shared/ai/skills/om-implement-spec/references/planning-and-progress.md +40 -0
  142. package/dist/agentic/shared/ai/skills/om-implement-spec/references/report-templates.md +27 -0
  143. package/dist/agentic/shared/ai/skills/om-implement-spec/references/spec-resolution.md +24 -0
  144. package/dist/agentic/shared/ai/skills/om-integration-builder/SKILL.md +25 -708
  145. package/dist/agentic/shared/ai/skills/om-integration-builder/references/package-and-activation.md +16 -0
  146. package/dist/agentic/shared/ai/skills/om-integration-builder/references/provider-families.md +25 -0
  147. package/dist/agentic/shared/ai/skills/om-integration-builder/references/security-and-reliability.md +15 -0
  148. package/dist/agentic/shared/ai/skills/om-integration-builder/references/sync-and-files.md +12 -0
  149. package/dist/agentic/shared/ai/skills/om-judge-agent-session/SKILL.md +34 -0
  150. package/dist/agentic/shared/ai/skills/om-judge-agent-session/agents/openai.yaml +4 -0
  151. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/agentic-setup.md +11 -0
  152. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/input-normalization.md +31 -0
  153. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/judge-workflow.md +39 -0
  154. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/report-template.md +43 -0
  155. package/dist/agentic/shared/ai/skills/om-judge-agent-session/references/rules.md +11 -0
  156. package/dist/agentic/shared/ai/skills/om-module-scaffold/SKILL.md +20 -820
  157. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/api-and-domain.md +32 -0
  158. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/business-one-shot-blueprints.md +172 -0
  159. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/data-and-migrations.md +14 -0
  160. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/discovery-surface-catalog.md +83 -0
  161. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/module-surfaces.md +40 -0
  162. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/planning.md +11 -0
  163. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/runtime-cache-and-queues.md +22 -0
  164. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/verification.md +13 -0
  165. package/dist/agentic/shared/ai/skills/om-share-this-session/SKILL.md +41 -0
  166. package/dist/agentic/shared/ai/skills/om-share-this-session/references/agentic-setup.md +19 -0
  167. package/dist/agentic/shared/ai/skills/om-share-this-session/references/bundle-preparation.md +44 -0
  168. package/dist/agentic/shared/ai/skills/om-share-this-session/references/consent-and-review.md +39 -0
  169. package/dist/agentic/shared/ai/skills/om-share-this-session/references/publication.md +20 -0
  170. package/dist/agentic/shared/ai/skills/om-share-this-session/references/report-templates.md +57 -0
  171. package/dist/agentic/shared/ai/skills/om-share-this-session/references/rules.md +11 -0
  172. package/dist/agentic/shared/ai/skills/om-share-this-session/scripts/prepare-share-bundle.mjs +486 -0
  173. package/dist/agentic/shared/ai/skills/om-system-extension/SKILL.md +20 -843
  174. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-branches.md +42 -0
  175. package/dist/agentic/shared/ai/skills/om-system-extension/references/mechanism-selector.md +27 -0
  176. package/dist/agentic/shared/ai/skills/om-system-extension/references/read-write-roundtrip.md +13 -0
  177. package/dist/agentic/shared/ai/skills/om-system-extension/references/unified-overrides.md +43 -0
  178. package/dist/agentic/shared/ai/skills/om-system-extension/references/verification.md +11 -0
  179. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/SKILL.md +15 -63
  180. package/dist/agentic/shared/ai/skills/om-trim-unused-modules/references/dependency-and-redirect.md +15 -0
  181. package/dist/agentic/shared/ai/skills/om-troubleshooter/SKILL.md +17 -467
  182. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnosis-map.md +29 -0
  183. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/regression-oracles.md +14 -0
  184. package/dist/agentic/shared/ai/skills/tiers.json +143 -21
  185. package/dist/agentic/shared/ai/skills/tiers.schema.json +52 -40
  186. package/dist/agentic/shared/ai/specs/README.md +4 -3
  187. package/dist/agentic/shared/ai/specs/SPEC-000-template.md +226 -10
  188. package/dist/agentic/shared/ai/trackers/github.md +108 -4
  189. package/dist/agentic/shared/scripts/agent-harness-tool-server.mjs +224 -0
  190. package/dist/agentic/shared/scripts/check-lessons.mjs +178 -0
  191. package/dist/agentic/shared/scripts/evaluate-agent-harness.mjs +3585 -0
  192. package/dist/agentic/shared/scripts/execution-sandbox.mjs +329 -0
  193. package/dist/agentic/shared/scripts/framework-context.mjs +888 -0
  194. package/dist/agentic/shared/scripts/install-skills.mjs +1029 -0
  195. package/dist/agentic/shared/scripts/install-skills.sh +4 -694
  196. package/dist/agentic/shared/scripts/prepare-agent-harness-fixture.mjs +222 -0
  197. package/dist/agentic/shared/scripts/run-agent-harness-release.mjs +1774 -0
  198. package/dist/agentic/shared/scripts/validate-knowledge-change.mjs +911 -0
  199. package/dist/bin.js +13 -1
  200. package/dist/bin.js.map +2 -2
  201. package/dist/lib/__integration__/TC-INT-008.spec.js +88 -8
  202. package/dist/lib/__integration__/TC-INT-008.spec.js.map +2 -2
  203. package/dist/lib/agentic-init.js +13 -3
  204. package/dist/lib/agentic-init.js.map +2 -2
  205. package/dist/lib/agentic-setup.js +434 -186
  206. package/dist/lib/agentic-setup.js.map +2 -2
  207. package/dist/lib/generators/extensions/injection-widgets.js +20 -0
  208. package/dist/lib/generators/extensions/injection-widgets.js.map +2 -2
  209. package/dist/lib/generators/module-extension-facts.js +1872 -0
  210. package/dist/lib/generators/module-extension-facts.js.map +7 -0
  211. package/dist/lib/generators/module-fact-sources.js +70 -0
  212. package/dist/lib/generators/module-fact-sources.js.map +7 -0
  213. package/dist/lib/generators/module-facts-discovery.js +64 -7
  214. package/dist/lib/generators/module-facts-discovery.js.map +2 -2
  215. package/dist/lib/generators/module-facts.js +1629 -52
  216. package/dist/lib/generators/module-facts.js.map +3 -3
  217. package/dist/lib/generators/module-override-targets.js +688 -0
  218. package/dist/lib/generators/module-override-targets.js.map +7 -0
  219. package/dist/lib/generators/module-registry.js +11 -5
  220. package/dist/lib/generators/module-registry.js.map +2 -2
  221. package/dist/lib/load-env.js +24 -0
  222. package/dist/lib/load-env.js.map +7 -0
  223. package/dist/lib/resolver.js +2 -0
  224. package/dist/lib/resolver.js.map +2 -2
  225. package/dist/lib/telemetry-init.js +358 -0
  226. package/dist/lib/telemetry-init.js.map +7 -0
  227. package/dist/lib/testing/integration.js +5 -2
  228. package/dist/lib/testing/integration.js.map +2 -2
  229. package/dist/mercato.js +23 -1
  230. package/dist/mercato.js.map +2 -2
  231. package/package.json +13 -11
  232. package/src/bin.ts +33 -1
  233. package/src/lib/__integration__/TC-INT-008.spec.ts +107 -9
  234. package/src/lib/__tests__/agentic-init.test.ts +41 -5
  235. package/src/lib/__tests__/agentic-setup.ownership.test.ts +386 -0
  236. package/src/lib/__tests__/injection-widget-id-validation.test.ts +184 -0
  237. package/src/lib/__tests__/load-env.test.ts +65 -0
  238. package/src/lib/__tests__/telemetry-init.test.ts +298 -0
  239. package/src/lib/agentic-init.ts +15 -3
  240. package/src/lib/agentic-setup.ts +573 -222
  241. package/src/lib/generators/__tests__/activation-adapter-coverage.test.ts +92 -0
  242. package/src/lib/generators/__tests__/module-extension-activations.test.ts +762 -0
  243. package/src/lib/generators/__tests__/module-extension-facts.test.ts +603 -0
  244. package/src/lib/generators/__tests__/module-facts.bc-guard.test.ts +139 -2
  245. package/src/lib/generators/__tests__/module-facts.computed-di-token.test.ts +88 -0
  246. package/src/lib/generators/__tests__/module-facts.customers.fixture.test.ts +1 -0
  247. package/src/lib/generators/__tests__/module-facts.discovery.test.ts +54 -10
  248. package/src/lib/generators/__tests__/module-facts.extension-hosts.test.ts +108 -0
  249. package/src/lib/generators/__tests__/module-facts.local-reference.test.ts +391 -0
  250. package/src/lib/generators/__tests__/module-facts.owned-contracts.test.ts +561 -0
  251. package/src/lib/generators/__tests__/module-facts.search-entity-id.test.ts +151 -0
  252. package/src/lib/generators/__tests__/module-facts.source-links.test.ts +160 -0
  253. package/src/lib/generators/__tests__/module-override-targets.roundtrip.test.ts +149 -0
  254. package/src/lib/generators/__tests__/module-override-targets.test.ts +328 -0
  255. package/src/lib/generators/__tests__/module-subset.test.ts +18 -0
  256. package/src/lib/generators/extensions/injection-widgets.ts +51 -0
  257. package/src/lib/generators/module-extension-facts.ts +2433 -0
  258. package/src/lib/generators/module-fact-sources.ts +108 -0
  259. package/src/lib/generators/module-facts-discovery.ts +104 -7
  260. package/src/lib/generators/module-facts.ts +2387 -138
  261. package/src/lib/generators/module-override-targets.ts +863 -0
  262. package/src/lib/generators/module-registry.ts +16 -6
  263. package/src/lib/load-env.ts +30 -0
  264. package/src/lib/resolver.ts +3 -0
  265. package/src/lib/telemetry-init.ts +461 -0
  266. package/src/lib/testing/__tests__/integration.test.ts +4 -0
  267. package/src/lib/testing/integration.ts +5 -2
  268. package/src/mercato.ts +26 -1
  269. package/dist/agentic/guides/cache.md +0 -45
  270. package/dist/agentic/guides/core.md +0 -453
  271. package/dist/agentic/guides/events.md +0 -117
  272. package/dist/agentic/guides/queue.md +0 -53
  273. package/dist/agentic/guides/search.md +0 -115
  274. package/dist/agentic/guides/shared.md +0 -108
  275. package/dist/agentic/guides/ui.md +0 -347
  276. package/dist/agentic/shared/ai/skills/om-auto-continue-pr-loop/SKILL.md +0 -27
  277. package/dist/agentic/shared/ai/skills/om-auto-create-pr-loop/SKILL.md +0 -27
  278. package/dist/agentic/shared/ai/skills/om-backend-ui-design/references/ui-components.md +0 -260
  279. package/dist/agentic/shared/ai/skills/om-data-model-design/references/mikro-orm-cheatsheet.md +0 -146
  280. package/dist/agentic/shared/ai/skills/om-help/references/skills-catalog.md +0 -84
  281. package/dist/agentic/shared/ai/skills/om-help/references/workflow-sequences.md +0 -193
  282. package/dist/agentic/shared/ai/skills/om-integration-builder/STANDALONE.md +0 -34
  283. package/dist/agentic/shared/ai/skills/om-integration-builder/references/adapter-contracts.md +0 -762
  284. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/naming-conventions.md +0 -50
  285. package/dist/agentic/shared/ai/skills/om-module-scaffold/references/navigation-patterns.md +0 -97
  286. package/dist/agentic/shared/ai/skills/om-system-extension/references/extension-contracts.md +0 -271
  287. package/dist/agentic/shared/ai/skills/om-troubleshooter/references/diagnostic-commands.md +0 -101
@@ -1,8 +1,8 @@
1
1
  # Tracker provider: GitHub
2
2
 
3
- This file is the GitHub implementation of the tracker operations contract (see `TEMPLATE.md` for the contract itself). Every skill in the collection performs issue/PR state management through **named tracker operations** — `**get-issue**`, `**comment-pr**`, and so on — and this file defines what each operation means for GitHub, using the `gh` CLI.
3
+ This file is the GitHub implementation of the tracker operations contract (see `TEMPLATE.md` for the contract itself). Skills perform issue/PR state management through **named tracker operations** — `**get-issue**`, `**comment-pr**`, and so on — and this file defines what each operation means for GitHub, using the `gh` CLI.
4
4
 
5
- How it is used at runtime: `om-setup-agent-pipeline` copies this file into the repository at `.ai/trackers/github.md`, and the config's `tracker` field selects it. When a skill says "tracker operation **get-pr**", execute the command documented under that operation heading in the repo's copy. The repo's copy is authoritative: teams extend or override any operation by editing it — add flags, swap a command, append repo-specific conventions — and every skill picks the change up on its next run. An operation not covered by an edit keeps its behavior from this file's text as copied.
5
+ At runtime: `om-setup-agent-pipeline` copies this file into the repository at `.ai/trackers/github.md`, and the config's `tracker` field selects it. When a skill says "tracker operation **get-pr**", execute the command documented under that operation heading in the repo's copy. The repo's copy is authoritative: teams extend or override any operation by editing it, and every skill picks the change up on its next run. An operation not covered by an edit keeps its behavior from this file's text as copied.
6
6
 
7
7
  ## Prerequisites
8
8
 
@@ -14,7 +14,7 @@ How it is used at runtime: `om-setup-agent-pipeline` copies this file into the r
14
14
  - Issue and PR identifiers are numbers; in text they are written `#123`.
15
15
  - A PR is linked to the issue it resolves with `Fixes #{issueId}` (or `Closes #{issueId}`) in the PR body; GitHub then closes the issue on merge. To reference without auto-closing, use a plain issue link.
16
16
  - PRs open as **drafts** when a skill says so; a human (or **mark-pr-ready**) promotes them.
17
- - Claim/lock signals on an issue or PR are: assignee set to the automation user, the `in-progress` label, and a `🤖`-prefixed claim comment. All three are set on claim; the label is guarded (below).
17
+ - Claim/lock signals on an issue or PR are: assignee set to the automation user, the `in-progress` label, and a `🤖`-prefixed claim comment. All three are set on claim; the label is guarded (below). The `ci-monitoring` label is **not** a claim/lock signal: it marks a PR whose work is finished and already reported while an agent watches CI, so a PR carrying `ci-monitoring` without any of the three signals above MUST be treated as unclaimed and is free to claim.
18
18
  - Long, multi-line comment bodies are posted with `--body-file` (or a heredoc via process substitution) so formatting is preserved.
19
19
  - CI status truth comes from **get-pr-checks**; the set of *required* checks comes from **get-required-checks** (branch protection). When branch protection is not readable (404), treat every reported check as required.
20
20
 
@@ -98,6 +98,84 @@ BASE_BRANCH=$(gh repo view --json defaultBranchRef --jq '.defaultBranchRef.name'
98
98
  [ -z "$BASE_BRANCH" ] && BASE_BRANCH="main"
99
99
  ```
100
100
 
101
+ ### Public session-share artifacts
102
+
103
+ #### publish-session-share
104
+ `{owner}/{repo}`, `{branch}`, `{shareName}`, and a local `{bundleDir}` → atomically publish the four reviewed session-share artifacts on a new public branch and return repository, branch, commit, branch URL, and raw artifact URLs as JSON.
105
+
106
+ This operation is intentionally strict: the repository must already be public; the branch must be a new slash-free `session-share-…` ref; and the bundle directory must contain regular, non-symlinked `session.json`, `generated-files.zip`, `manifest.json`, and `privacy-report.json` files within the documented size cap. Git blobs, a tree, and a commit are created first; the public ref is created last, so a preparation failure does not expose a partial branch.
107
+
108
+ ```bash
109
+ TARGET_REPO="{owner}/{repo}"
110
+ SHARE_BRANCH="{branch}"
111
+ SHARE_NAME="{shareName}"
112
+ BUNDLE_DIR="{bundleDir}"
113
+ printf '%s' "$TARGET_REPO" | grep -Eq '^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$' || exit 1
114
+ printf '%s' "$SHARE_NAME" | grep -Eq '^[a-z0-9][a-z0-9-]{1,46}[a-z0-9]$' || exit 1
115
+ [ "$SHARE_BRANCH" = "session-share-$SHARE_NAME" ] || exit 1
116
+ [ "$(gh repo view "$TARGET_REPO" --json visibility --jq .visibility)" = "PUBLIC" ] || exit 1
117
+ gh api "repos/${TARGET_REPO}/git/ref/heads/${SHARE_BRANCH}" >/dev/null 2>&1 && exit 1
118
+
119
+ SESSION_SHARE_TMP=$(mktemp -d)
120
+ trap 'rm -rf "$SESSION_SHARE_TMP"' EXIT HUP INT TERM
121
+ printf '[]\n' > "$SESSION_SHARE_TMP/tree.json"
122
+ TOTAL_BYTES=0
123
+ for ARTIFACT in session.json generated-files.zip manifest.json privacy-report.json; do
124
+ ARTIFACT_PATH="$BUNDLE_DIR/$ARTIFACT"
125
+ [ -f "$ARTIFACT_PATH" ] && [ ! -L "$ARTIFACT_PATH" ] || exit 1
126
+ ARTIFACT_BYTES=$(wc -c < "$ARTIFACT_PATH" | tr -d ' ')
127
+ TOTAL_BYTES=$((TOTAL_BYTES + ARTIFACT_BYTES))
128
+ [ "$ARTIFACT_BYTES" -le 26214400 ] && [ "$TOTAL_BYTES" -le 31457280 ] || exit 1
129
+ done
130
+
131
+ for ARTIFACT in session.json generated-files.zip manifest.json privacy-report.json; do
132
+ ARTIFACT_PATH="$BUNDLE_DIR/$ARTIFACT"
133
+ base64 < "$ARTIFACT_PATH" | tr -d '\n' > "$SESSION_SHARE_TMP/content.b64"
134
+ BLOB_SHA=$(jq -n --rawfile content "$SESSION_SHARE_TMP/content.b64" '{content:$content,encoding:"base64"}' \
135
+ | gh api -X POST "repos/${TARGET_REPO}/git/blobs" --input - --jq .sha) || exit 1
136
+ jq --arg path "$ARTIFACT" --arg sha "$BLOB_SHA" \
137
+ '. + [{path:$path,mode:"100644",type:"blob",sha:$sha}]' \
138
+ "$SESSION_SHARE_TMP/tree.json" > "$SESSION_SHARE_TMP/tree.next.json" || exit 1
139
+ mv "$SESSION_SHARE_TMP/tree.next.json" "$SESSION_SHARE_TMP/tree.json"
140
+ done
141
+
142
+ DEFAULT_BRANCH=$(gh repo view "$TARGET_REPO" --json defaultBranchRef --jq .defaultBranchRef.name) || exit 1
143
+ BASE_COMMIT=$(gh api "repos/${TARGET_REPO}/git/ref/heads/${DEFAULT_BRANCH}" --jq .object.sha) || exit 1
144
+ BASE_TREE=$(gh api "repos/${TARGET_REPO}/git/commits/${BASE_COMMIT}" --jq .tree.sha) || exit 1
145
+ TREE_SHA=$(jq -n --arg base "$BASE_TREE" --slurpfile entries "$SESSION_SHARE_TMP/tree.json" \
146
+ '{base_tree:$base,tree:$entries[0]}' \
147
+ | gh api -X POST "repos/${TARGET_REPO}/git/trees" --input - --jq .sha) || exit 1
148
+ COMMIT_SHA=$(jq -n --arg message "session share ${SHARE_NAME}" --arg tree "$TREE_SHA" --arg parent "$BASE_COMMIT" \
149
+ '{message:$message,tree:$tree,parents:[$parent]}' \
150
+ | gh api -X POST "repos/${TARGET_REPO}/git/commits" --input - --jq .sha) || exit 1
151
+ jq -n --arg ref "refs/heads/${SHARE_BRANCH}" --arg sha "$COMMIT_SHA" '{ref:$ref,sha:$sha}' \
152
+ | gh api -X POST "repos/${TARGET_REPO}/git/refs" --input - >/dev/null || exit 1
153
+
154
+ jq -n \
155
+ --arg repository "$TARGET_REPO" \
156
+ --arg branch "$SHARE_BRANCH" \
157
+ --arg commit "$COMMIT_SHA" \
158
+ --arg branchUrl "https://github.com/${TARGET_REPO}/tree/${SHARE_BRANCH}" \
159
+ --arg sessionUrl "https://raw.githubusercontent.com/${TARGET_REPO}/${SHARE_BRANCH}/session.json" \
160
+ --arg archiveUrl "https://raw.githubusercontent.com/${TARGET_REPO}/${SHARE_BRANCH}/generated-files.zip" \
161
+ --arg manifestUrl "https://raw.githubusercontent.com/${TARGET_REPO}/${SHARE_BRANCH}/manifest.json" \
162
+ --arg privacyUrl "https://raw.githubusercontent.com/${TARGET_REPO}/${SHARE_BRANCH}/privacy-report.json" \
163
+ '{repository:$repository,branch:$branch,commit:$commit,branchUrl:$branchUrl,artifacts:{session:$sessionUrl,archive:$archiveUrl,manifest:$manifestUrl,privacy:$privacyUrl}}'
164
+ ```
165
+
166
+ #### delete-session-share
167
+ `{owner}/{repo}`, `{branch}`, and `{shareName}` → remove only the exact derived session-share ref after a failed issue creation or an explicit retention cleanup request. Deleting a public ref cannot guarantee erasure from clones, forks, caches, logs, or archives.
168
+
169
+ ```bash
170
+ TARGET_REPO="{owner}/{repo}"
171
+ SHARE_BRANCH="{branch}"
172
+ SHARE_NAME="{shareName}"
173
+ printf '%s' "$TARGET_REPO" | grep -Eq '^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$' || exit 1
174
+ printf '%s' "$SHARE_NAME" | grep -Eq '^[a-z0-9][a-z0-9-]{1,46}[a-z0-9]$' || exit 1
175
+ [ "$SHARE_BRANCH" = "session-share-$SHARE_NAME" ] || exit 1
176
+ gh api -X DELETE "repos/${TARGET_REPO}/git/refs/heads/${SHARE_BRANCH}"
177
+ ```
178
+
101
179
  ### Issues
102
180
 
103
181
  #### get-issue
@@ -109,7 +187,9 @@ gh issue view {issueId} --repo {owner}/{repo} --json number,title,body,state,aut
109
187
  #### search-issues
110
188
  Query (text, `in:title,body`, state) → matching issues.
111
189
  ```bash
112
- gh issue list --repo {owner}/{repo} --state open --search "<query> in:title,body" --json number,title,url
190
+ ISSUE_STATE="{state}"
191
+ case "$ISSUE_STATE" in open|closed|all) ;; *) exit 1 ;; esac
192
+ gh issue list --repo {owner}/{repo} --state "$ISSUE_STATE" --search "<query> in:title,body" --json number,title,url
113
193
  ```
114
194
 
115
195
  #### create-issue
@@ -130,6 +210,12 @@ gh issue close {issueId} --repo {owner}/{repo} --reason completed --comment "<co
130
210
  gh issue comment {issueId} --repo {owner}/{repo} --body "<body>"
131
211
  ```
132
212
 
213
+ #### update-issue
214
+ `{issueId}`, new title and/or body (use a body-file for multi-line bodies). Edits the issue's own fields; does not touch labels or assignees (those have their own operations).
215
+ ```bash
216
+ gh issue edit {issueId} --repo {owner}/{repo} --title "<title>" --body-file <file>
217
+ ```
218
+
133
219
  #### assign-issue / unassign-issue
134
220
  ```bash
135
221
  gh issue edit {issueId} --repo {owner}/{repo} --add-assignee "<login>"
@@ -151,6 +237,12 @@ gh api repos/{owner}/{repo}/issues/comments/{commentId} --jq '{body,user:.user.l
151
237
  gh api repos/{owner}/{repo}/issues/{number}/comments --jq '.[] | {id,user:.user.login,body}'
152
238
  ```
153
239
 
240
+ #### update-comment
241
+ `{commentId}`, new body → rewrite an existing conversation comment in place (works for issue and PR conversation comments alike). This is how marker-idempotent comments (label rationale, verification, claim take-overs) are updated on re-runs: find your `🤖 …` marker via **list-issue-comments**, then update that comment instead of posting a new one. Use a body file so multi-line bodies survive.
242
+ ```bash
243
+ gh api -X PATCH repos/{owner}/{repo}/issues/comments/{commentId} -F body=@<path>
244
+ ```
245
+
154
246
  ### Pull requests
155
247
 
156
248
  #### get-pr
@@ -181,6 +273,17 @@ PR_URL=$(gh pr view --json url --jq .url)
181
273
  PR_NUMBER=$(gh pr view --json number --jq .number)
182
274
  ```
183
275
 
276
+ #### update-pr
277
+ `{prNumber}`, new title and/or new body → the PR's own title/body rewritten in place (not a comment), e.g. reframing a doc-originated spec PR that grew a feature implementation. Pass whichever of `--title` / `--body-file` changed; omit the other. Use a body file so multi-line bodies survive.
278
+ ```bash
279
+ gh pr edit {prNumber} --title "<title>"
280
+ gh pr edit {prNumber} --body-file <path-or-process-substitution>
281
+ ```
282
+ If `gh pr edit` silently no-ops in this repo (some GitHub setups do), fall back to the REST API:
283
+ ```bash
284
+ gh api -X PATCH repos/{owner}/{repo}/pulls/{prNumber} -f title="<title>" -f body="$(cat <path>)"
285
+ ```
286
+
184
287
  #### comment-pr
185
288
  `{prNumber}`, body. For long structured comments:
186
289
  ```bash
@@ -365,6 +468,7 @@ gh label create skip-qa --color 0e8a16 --description "Low risk, QA not
365
468
  gh label create qa-approved --color 0e8a16 --description "Manual QA passed"
366
469
  gh label create qa-self-verified --color c5def5 --description "Self-QA exception used"
367
470
  gh label create in-progress --color c5def5 --description "An automated skill is working on this"
471
+ gh label create ci-monitoring --color 00b8d9 --description "Work complete and reported; agent is watching CI results"
368
472
  gh label create do-not-close --color c5def5 --description "Humans only: never auto-close this issue"
369
473
  gh label create priority-low --color e4e669 --description "Cosmetic or follow-up work"
370
474
  gh label create priority-medium --color fbca04 --description "Ordinary bug or feature"
@@ -0,0 +1,224 @@
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
+ // Declarative provenance for a read outside the canonical example root. The server does
20
+ // NOT authorize on it — the read allowlist still governs what may be read — but it refuses
21
+ // an unrecognized value so the runner trace cannot carry an arbitrary string into the
22
+ // evaluator's reason-gated fallback accounting. Mirrors `cases.schema.json`.
23
+ const INSTALLED_FALLBACK_REASON_CODES = new Set([
24
+ 'SPECIALIST_ROUTE_NOT_DECLARED',
25
+ 'INSTALLED_VERSION_CONTRACT_MISMATCH',
26
+ ])
27
+
28
+ function fail(message) {
29
+ process.stderr.write(`agent-harness-tool-server: ${message}\n`)
30
+ process.exit(2)
31
+ }
32
+
33
+ function isInside(root, candidate) {
34
+ const relative = path.relative(root, candidate)
35
+ return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))
36
+ }
37
+
38
+ function isInstalledSourcePath(value, allowPatterns) {
39
+ const normalized = value.replaceAll('\\', '/')
40
+ const segments = normalized.split('/')
41
+ if (segments.length < 5 || segments[0] !== 'node_modules' || segments[1] !== '@open-mercato' || segments[3] !== 'src') return false
42
+ if (!segments[2] || segments.slice(4).some((entry) => !entry)) return false
43
+ return allowPatterns || !normalized.includes('*') && !normalized.includes('?')
44
+ }
45
+
46
+ function safeRelative(value, { allowInstalledSourcePatterns = false } = {}) {
47
+ if (typeof value !== 'string' || !value || value.includes('\0') || path.isAbsolute(value)) return false
48
+ const normalized = value.replaceAll('\\', '/')
49
+ if (normalized === '.' || normalized.startsWith('../') || normalized.includes('/../')) return false
50
+ const segments = normalized.split('/')
51
+ const basename = segments.at(-1) ?? ''
52
+ const installedSource = segments.includes('node_modules')
53
+ if (installedSource && !isInstalledSourcePath(normalized, allowInstalledSourcePatterns)) return false
54
+ if ((installedSource ? segments.slice(4) : segments).some((entry) => FORBIDDEN_SEGMENTS.has(entry))) return false
55
+ if (normalized === '.ai/harness' || normalized.startsWith('.ai/harness/')) return false
56
+ if (basename === '.env' || basename.startsWith('.env.') || FORBIDDEN_BASENAMES.has(basename)) return false
57
+ return !FORBIDDEN_SECRET_EXTENSIONS.has(path.extname(basename).toLowerCase())
58
+ }
59
+
60
+ function globToRegExp(pattern) {
61
+ let source = '^'
62
+ for (let index = 0; index < pattern.length; index += 1) {
63
+ const char = pattern[index]
64
+ if (char === '*' && pattern[index + 1] === '*') { source += '.*'; index += 1 }
65
+ else if (char === '*') source += '[^/]*'
66
+ else if (char === '?') source += '[^/]'
67
+ else source += char.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
68
+ }
69
+ return new RegExp(`${source}$`)
70
+ }
71
+
72
+ const rootArg = process.argv[2]
73
+ const mode = process.argv[3]
74
+ let allowedReads
75
+ let allowedWrites
76
+ let immutableRoots
77
+ try { allowedReads = JSON.parse(process.argv[4] ?? '[]') } catch { fail('allowed read set is invalid JSON') }
78
+ try { allowedWrites = JSON.parse(process.argv[5] ?? '[]') } catch { fail('allowed write set is invalid JSON') }
79
+ try { immutableRoots = JSON.parse(process.env.OM_HARNESS_IMMUTABLE_ROOTS ?? '[]') } catch { fail('immutable root set is invalid JSON') }
80
+ if (!path.isAbsolute(rootArg ?? '')) fail('root must be absolute')
81
+ if (!['read-only', 'writable'].includes(mode)) fail('mode must be read-only or writable')
82
+ if (!Array.isArray(allowedReads) || allowedReads.some((entry) => !safeRelative(entry, { allowInstalledSourcePatterns: true }) || ['*', '**'].includes(entry))) fail('allowed read set is invalid')
83
+ if (!Array.isArray(allowedWrites) || allowedWrites.some((entry) => !safeRelative(entry))) fail('allowed write set is invalid')
84
+ if (!Array.isArray(immutableRoots) || immutableRoots.some((entry) => !safeRelative(entry) || entry.includes('*') || entry.includes('?'))) fail('immutable root set is invalid')
85
+ const root = fs.realpathSync(rootArg)
86
+ const readMatchers = allowedReads.map(globToRegExp)
87
+ const writeMatchers = allowedWrites.map(globToRegExp)
88
+ const immutablePrefixes = immutableRoots.map((entry) => entry.replaceAll('\\', '/'))
89
+
90
+ // Root immutability precedence: declared read-only roots are resolved BEFORE the write
91
+ // allowlist, so a broader writable grant such as `src/modules/**` can never reach inside one.
92
+ function isImmutablePath(normalized) {
93
+ return immutablePrefixes.some((prefix) => normalized === prefix || normalized.startsWith(`${prefix}/`))
94
+ }
95
+
96
+ function resolveRead(relative) {
97
+ if (!safeRelative(relative)) throw new Error('path is outside the allowed app context')
98
+ const normalized = relative.replaceAll('\\', '/')
99
+ if (!readMatchers.some((matcher) => matcher.test(normalized))) throw new Error('path is outside the case read allowlist')
100
+ const lexical = path.resolve(root, relative)
101
+ if (!isInside(root, lexical)) throw new Error('path escapes the app root')
102
+ const real = fs.realpathSync(lexical)
103
+ if (isInstalledSourcePath(normalized, false)) {
104
+ const dependencyRoot = fs.realpathSync(path.join(root, 'node_modules'))
105
+ if (!isInside(dependencyRoot, real)) throw new Error('installed source resolves outside the protected dependency root')
106
+ } else if (!isInside(root, real)) throw new Error('path resolves outside the app root')
107
+ const stat = fs.statSync(real)
108
+ if (!stat.isFile() || stat.size > MAX_READ_BYTES) throw new Error('path must be a bounded regular file')
109
+ if (!SAFE_TEXT_EXTENSIONS.has(path.extname(real).toLowerCase()) && path.basename(real) !== 'AGENTS.md') throw new Error('path is not an allowed text file')
110
+ return { real, stat }
111
+ }
112
+
113
+ function resolveWrite(relative) {
114
+ if (mode !== 'writable') throw new Error('writes are disabled for this evaluation')
115
+ if (!safeRelative(relative)) throw new Error('path is outside the case write allowlist')
116
+ const normalized = relative.replaceAll('\\', '/')
117
+ if (isImmutablePath(normalized)) throw new Error('path is inside a read-only declared root and can never be written')
118
+ if (!writeMatchers.some((matcher) => matcher.test(normalized))) throw new Error('path is outside the case write allowlist')
119
+ const lexical = path.resolve(root, relative)
120
+ if (!isInside(root, lexical)) throw new Error('path escapes the app root')
121
+ let cursor = path.dirname(lexical)
122
+ const missing = []
123
+ while (cursor !== root && !fs.existsSync(cursor)) { missing.push(cursor); cursor = path.dirname(cursor) }
124
+ if (!isInside(root, cursor)) throw new Error('write ancestor escapes the app root')
125
+ for (let current = cursor; current !== root; current = path.dirname(current)) {
126
+ const stat = fs.lstatSync(current)
127
+ if (!stat.isDirectory() || stat.isSymbolicLink()) throw new Error('write ancestor must be a regular directory')
128
+ }
129
+ if (fs.existsSync(lexical)) {
130
+ const stat = fs.lstatSync(lexical)
131
+ if (!stat.isFile() || stat.isSymbolicLink() || !isInside(root, fs.realpathSync(lexical))) throw new Error('write target must be a contained regular file')
132
+ }
133
+ for (const directory of missing.reverse()) fs.mkdirSync(directory, { mode: 0o700 })
134
+ return lexical
135
+ }
136
+
137
+ const tools = [
138
+ {
139
+ name: 'read',
140
+ 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.',
141
+ inputSchema: {
142
+ type: 'object',
143
+ additionalProperties: false,
144
+ required: ['path'],
145
+ properties: {
146
+ path: { type: 'string' },
147
+ reason: { type: 'string', enum: [...INSTALLED_FALLBACK_REASON_CODES] },
148
+ capabilityId: { type: 'string' },
149
+ },
150
+ },
151
+ },
152
+ ...(mode === 'writable' ? [{
153
+ name: 'write',
154
+ description: 'Replace one exact app-relative file declared in the current case write allowlist. The content must be the complete file.',
155
+ inputSchema: { type: 'object', additionalProperties: false, required: ['path', 'content'], properties: { path: { type: 'string' }, content: { type: 'string' } } },
156
+ }] : []),
157
+ ]
158
+
159
+ function result(id, value) {
160
+ process.stdout.write(`${JSON.stringify({ jsonrpc: '2.0', id, result: value })}\n`)
161
+ }
162
+
163
+ function toolResult(id, text, isError = false) {
164
+ result(id, { content: [{ type: 'text', text }], ...(isError ? { isError: true } : {}) })
165
+ }
166
+
167
+ function handle(message) {
168
+ if (!message || message.jsonrpc !== '2.0') return
169
+ if (message.method === 'initialize') {
170
+ result(message.id, {
171
+ protocolVersion: message.params?.protocolVersion ?? '2025-06-18',
172
+ capabilities: { tools: { listChanged: false } },
173
+ serverInfo: { name: 'open-mercato-agent-harness', version: '1.0.0' },
174
+ })
175
+ return
176
+ }
177
+ if (message.method === 'ping') { result(message.id, {}); return }
178
+ if (message.method === 'tools/list') { result(message.id, { tools }); return }
179
+ if (message.method !== 'tools/call') return
180
+ try {
181
+ const name = message.params?.name
182
+ const input = message.params?.arguments ?? {}
183
+ if (name === 'read') {
184
+ if (input.reason !== undefined && !INSTALLED_FALLBACK_REASON_CODES.has(input.reason)) {
185
+ throw new Error('read reason must be a declared installed-source fallback reason code')
186
+ }
187
+ if (input.capabilityId !== undefined
188
+ && (typeof input.capabilityId !== 'string' || !/^[a-z0-9][a-z0-9.-]{0,63}$/.test(input.capabilityId))) {
189
+ throw new Error('read capabilityId must be a bounded surface-inventory capability id')
190
+ }
191
+ const { real } = resolveRead(input.path)
192
+ toolResult(message.id, fs.readFileSync(real, 'utf8'))
193
+ return
194
+ }
195
+ if (name === 'write') {
196
+ if (typeof input.content !== 'string' || Buffer.byteLength(input.content) > MAX_WRITE_BYTES) throw new Error('content must be a bounded string')
197
+ const target = resolveWrite(input.path)
198
+ const temporary = `${target}.om-harness-${process.pid}.tmp`
199
+ fs.writeFileSync(temporary, input.content, { encoding: 'utf8', mode: 0o600, flag: 'wx' })
200
+ fs.renameSync(temporary, target)
201
+ toolResult(message.id, `wrote ${input.path}`)
202
+ return
203
+ }
204
+ throw new Error('unknown tool')
205
+ } catch (error) {
206
+ toolResult(message.id, error instanceof Error ? error.message : String(error), true)
207
+ }
208
+ }
209
+
210
+ let buffer = ''
211
+ process.stdin.setEncoding('utf8')
212
+ process.stdin.on('data', (chunk) => {
213
+ buffer += chunk
214
+ while (true) {
215
+ const newline = buffer.indexOf('\n')
216
+ if (newline < 0) break
217
+ const line = buffer.slice(0, newline).replace(/\r$/, '')
218
+ buffer = buffer.slice(newline + 1)
219
+ if (!line.trim()) continue
220
+ try { handle(JSON.parse(line)) } catch (error) {
221
+ process.stderr.write(`agent-harness-tool-server: invalid request: ${error instanceof Error ? error.message : String(error)}\n`)
222
+ }
223
+ }
224
+ })
@@ -0,0 +1,178 @@
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()