@open-mercato/cli 0.4.7-main-1768da2e43 → 0.4.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.
- package/build.mjs +8 -1
- package/dist/agentic/claude-code/CLAUDE.md.template +1 -0
- package/dist/agentic/claude-code/hooks/entity-migration-check.ts +121 -0
- package/dist/agentic/claude-code/mcp.json.example +13 -0
- package/dist/agentic/claude-code/settings.json +16 -0
- package/dist/agentic/codex/enforcement-rules.md +20 -0
- package/dist/agentic/codex/mcp.json.example +12 -0
- package/dist/agentic/cursor/hooks/entity-migration-check.mjs +69 -0
- package/dist/agentic/cursor/hooks.json +10 -0
- package/dist/agentic/cursor/mcp.json.example +13 -0
- package/dist/agentic/cursor/rules/entity-guard.mdc +29 -0
- package/dist/agentic/cursor/rules/generated-guard.mdc +12 -0
- package/dist/agentic/cursor/rules/open-mercato.mdc +34 -0
- package/dist/agentic/shared/AGENTS.md.template +180 -0
- package/dist/agentic/shared/ai/lessons.md +4 -0
- package/dist/agentic/shared/ai/skills/backend-ui-design/SKILL.md +197 -0
- package/dist/agentic/shared/ai/skills/backend-ui-design/references/ui-components.md +233 -0
- package/dist/agentic/shared/ai/skills/code-review/SKILL.md +144 -0
- package/dist/agentic/shared/ai/skills/code-review/references/review-checklist.md +77 -0
- package/dist/agentic/shared/ai/skills/data-model-design/SKILL.md +512 -0
- package/dist/agentic/shared/ai/skills/data-model-design/references/mikro-orm-cheatsheet.md +146 -0
- package/dist/agentic/shared/ai/skills/eject-and-customize/SKILL.md +318 -0
- package/dist/agentic/shared/ai/skills/integration-builder/SKILL.md +722 -0
- package/dist/agentic/shared/ai/skills/integration-builder/references/adapter-contracts.md +762 -0
- package/dist/agentic/shared/ai/skills/module-scaffold/SKILL.md +614 -0
- package/dist/agentic/shared/ai/skills/module-scaffold/references/naming-conventions.md +61 -0
- package/dist/agentic/shared/ai/skills/spec-writing/SKILL.md +76 -0
- package/dist/agentic/shared/ai/skills/spec-writing/references/spec-checklist.md +50 -0
- package/dist/agentic/shared/ai/skills/spec-writing/references/spec-template.md +86 -0
- package/dist/agentic/shared/ai/skills/system-extension/SKILL.md +858 -0
- package/dist/agentic/shared/ai/skills/system-extension/references/extension-contracts.md +271 -0
- package/dist/agentic/shared/ai/skills/troubleshooter/SKILL.md +421 -0
- package/dist/agentic/shared/ai/skills/troubleshooter/references/diagnostic-commands.md +101 -0
- package/dist/agentic/shared/ai/specs/README.md +26 -0
- package/dist/agentic/shared/ai/specs/SPEC-000-template.md +35 -0
- package/dist/lib/agentic-init.js +54 -0
- package/dist/lib/agentic-init.js.map +7 -0
- package/dist/lib/agentic-setup.js +209 -0
- package/dist/lib/agentic-setup.js.map +7 -0
- package/dist/lib/generators/module-registry.js +3 -3
- package/dist/lib/generators/module-registry.js.map +2 -2
- package/dist/lib/testing/integration.js +8 -2
- package/dist/lib/testing/integration.js.map +2 -2
- package/dist/mercato.js +5 -0
- package/dist/mercato.js.map +2 -2
- package/package.json +7 -4
- package/src/lib/agentic-init.ts +69 -0
- package/src/lib/agentic-setup.ts +277 -0
- package/src/lib/generators/module-registry.ts +3 -3
- package/src/lib/testing/integration.ts +8 -2
- package/src/mercato.ts +7 -0
package/build.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as esbuild from 'esbuild'
|
|
2
2
|
import { glob } from 'glob'
|
|
3
|
-
import { readFileSync, writeFileSync, chmodSync, existsSync } from 'node:fs'
|
|
3
|
+
import { readFileSync, writeFileSync, chmodSync, existsSync, cpSync } from 'node:fs'
|
|
4
4
|
import { dirname, join } from 'node:path'
|
|
5
5
|
import { fileURLToPath } from 'node:url'
|
|
6
6
|
|
|
@@ -92,4 +92,11 @@ const binContent = readFileSync(binPath, 'utf-8')
|
|
|
92
92
|
writeFileSync(binPath, '#!/usr/bin/env node\n' + binContent)
|
|
93
93
|
chmodSync(binPath, 0o755)
|
|
94
94
|
|
|
95
|
+
// Copy agentic source files from create-app so generators can read them at runtime
|
|
96
|
+
const agenticSrc = join(__dirname, '..', 'create-app', 'agentic')
|
|
97
|
+
if (existsSync(agenticSrc)) {
|
|
98
|
+
cpSync(agenticSrc, join(outdir, 'agentic'), { recursive: true })
|
|
99
|
+
console.log('Copied create-app/agentic/ → dist/agentic/')
|
|
100
|
+
}
|
|
101
|
+
|
|
95
102
|
console.log('CLI built successfully')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code PostToolUse hook: entity-migration-check
|
|
3
|
+
*
|
|
4
|
+
* Fires after Write/Edit/MultiEdit. Detects entity file modifications
|
|
5
|
+
* without an accompanying migration and blocks the agent with a reminder.
|
|
6
|
+
*
|
|
7
|
+
* Three-state logic:
|
|
8
|
+
* 1. Migration file was written → exit 0 (agent is already on it)
|
|
9
|
+
* 2. Not an entity file → exit 0 (not relevant)
|
|
10
|
+
* 3. Entity written, no fresh migration → block with migration instructions
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readdirSync, statSync } from 'node:fs'
|
|
14
|
+
import { join, relative } from 'node:path'
|
|
15
|
+
|
|
16
|
+
const ENTITY_PATTERN = /^src\/modules\/([^/]+)\/entities\/.*\.ts$/
|
|
17
|
+
const MIGRATION_PATTERN = /^src\/modules\/([^/]+)\/migrations\/.*\.ts$/
|
|
18
|
+
|
|
19
|
+
interface PostToolUseInput {
|
|
20
|
+
tool_input?: {
|
|
21
|
+
file_path?: string
|
|
22
|
+
content?: string
|
|
23
|
+
}
|
|
24
|
+
tool_name?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getRelativePath(filePath: string): string {
|
|
28
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd()
|
|
29
|
+
if (filePath.startsWith('/')) {
|
|
30
|
+
return relative(projectDir, filePath)
|
|
31
|
+
}
|
|
32
|
+
return filePath
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function hasFreshMigration(moduleId: string): boolean {
|
|
36
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd()
|
|
37
|
+
const migrationsDir = join(projectDir, 'src', 'modules', moduleId, 'migrations')
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
const files = readdirSync(migrationsDir)
|
|
41
|
+
const now = Date.now()
|
|
42
|
+
const thirtySecondsAgo = now - 30_000
|
|
43
|
+
|
|
44
|
+
for (const file of files) {
|
|
45
|
+
if (!file.endsWith('.ts')) continue
|
|
46
|
+
const stat = statSync(join(migrationsDir, file))
|
|
47
|
+
if (stat.mtimeMs > thirtySecondsAgo) {
|
|
48
|
+
return true
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
} catch {
|
|
52
|
+
// migrations directory doesn't exist yet — that's fine
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function main(): Promise<void> {
|
|
59
|
+
let input = ''
|
|
60
|
+
for await (const chunk of process.stdin) {
|
|
61
|
+
input += chunk
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!input.trim()) {
|
|
65
|
+
process.exit(0)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let data: PostToolUseInput
|
|
69
|
+
try {
|
|
70
|
+
data = JSON.parse(input)
|
|
71
|
+
} catch {
|
|
72
|
+
process.exit(0)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const filePath = data.tool_input?.file_path
|
|
76
|
+
if (!filePath) {
|
|
77
|
+
process.exit(0)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const rel = getRelativePath(filePath)
|
|
81
|
+
|
|
82
|
+
// State 1: Migration file was written → agent is already handling it
|
|
83
|
+
if (MIGRATION_PATTERN.test(rel)) {
|
|
84
|
+
process.exit(0)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// State 2: Not an entity file → not relevant
|
|
88
|
+
const entityMatch = rel.match(ENTITY_PATTERN)
|
|
89
|
+
if (!entityMatch) {
|
|
90
|
+
process.exit(0)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const moduleId = entityMatch[1]
|
|
94
|
+
|
|
95
|
+
// State 3: Entity written — check for fresh migration
|
|
96
|
+
if (hasFreshMigration(moduleId)) {
|
|
97
|
+
process.exit(0)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Block: entity modified without migration
|
|
101
|
+
const result = {
|
|
102
|
+
decision: 'block',
|
|
103
|
+
reason: [
|
|
104
|
+
`Entity file modified: ${rel}`,
|
|
105
|
+
'',
|
|
106
|
+
`Affected module: ${moduleId}`,
|
|
107
|
+
'',
|
|
108
|
+
'A database migration is likely needed. Ask the user whether to run:',
|
|
109
|
+
' yarn db:generate',
|
|
110
|
+
'',
|
|
111
|
+
'Then regenerate registries:',
|
|
112
|
+
' yarn generate',
|
|
113
|
+
'',
|
|
114
|
+
'Do NOT run migrations automatically — always confirm with the user first.',
|
|
115
|
+
].join('\n'),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
process.stdout.write(JSON.stringify(result))
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
main()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Copy to .mcp.json and set MCP_API_KEY (printed by yarn initialize)",
|
|
3
|
+
"mcpServers": {
|
|
4
|
+
"open-mercato": {
|
|
5
|
+
"command": "yarn",
|
|
6
|
+
"args": ["mercato", "mcp:dev"],
|
|
7
|
+
"env": {
|
|
8
|
+
"APP_URL": "http://localhost:3000",
|
|
9
|
+
"MCP_API_KEY": "${MCP_API_KEY}"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!-- CODEX_ENFORCEMENT_RULES_START -->
|
|
2
|
+
## CRITICAL rules — always follow without exception
|
|
3
|
+
|
|
4
|
+
1. **After editing any entity file** (`src/modules/<id>/entities/*.ts`):
|
|
5
|
+
- STOP immediately before any further action
|
|
6
|
+
- Tell the user: "I modified an entity in module <id>. Should I create a migration?"
|
|
7
|
+
- If yes: run `yarn db:generate`
|
|
8
|
+
- Show the generated migration to the user before applying
|
|
9
|
+
- Ask for confirmation, then run `yarn db:migrate`
|
|
10
|
+
- Run `yarn generate` after migration is applied
|
|
11
|
+
|
|
12
|
+
2. **After editing `src/modules.ts`**: immediately run `yarn generate`
|
|
13
|
+
|
|
14
|
+
3. **Never edit `.mercato/generated/*`**: edit the source and run `yarn generate` instead
|
|
15
|
+
|
|
16
|
+
4. **Before significant features**: check `.ai/specs/` for an existing spec.
|
|
17
|
+
If none exists, ask the user whether to create one first.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
<!-- CODEX_ENFORCEMENT_RULES_END -->
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor afterFileEdit hook: entity-migration-check
|
|
3
|
+
*
|
|
4
|
+
* Detects entity file modifications without an accompanying migration
|
|
5
|
+
* and exits non-zero to block the agent with a reminder.
|
|
6
|
+
*
|
|
7
|
+
* Three-state logic:
|
|
8
|
+
* 1. Migration file was written → exit 0 (agent is already on it)
|
|
9
|
+
* 2. Not an entity file → exit 0 (not relevant)
|
|
10
|
+
* 3. Entity written, no fresh migration → exit 1 with warning
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readdirSync, statSync } from 'node:fs'
|
|
14
|
+
import { join, resolve } from 'node:path'
|
|
15
|
+
|
|
16
|
+
const ENTITY_RE = /^src\/modules\/([^/]+)\/entities\/.*\.ts$/
|
|
17
|
+
const MIGRATION_RE = /^src\/modules\/([^/]+)\/migrations\/.*\.ts$/
|
|
18
|
+
|
|
19
|
+
function hasFreshMigration(projectDir, moduleId) {
|
|
20
|
+
const migrationsDir = join(projectDir, 'src', 'modules', moduleId, 'migrations')
|
|
21
|
+
try {
|
|
22
|
+
const files = readdirSync(migrationsDir)
|
|
23
|
+
const cutoff = Date.now() - 30_000
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
if (!file.endsWith('.ts')) continue
|
|
26
|
+
const stat = statSync(join(migrationsDir, file))
|
|
27
|
+
if (stat.mtimeMs > cutoff) return true
|
|
28
|
+
}
|
|
29
|
+
} catch {
|
|
30
|
+
// migrations directory doesn't exist yet
|
|
31
|
+
}
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const filePath = process.argv[2]
|
|
36
|
+
if (!filePath) process.exit(0)
|
|
37
|
+
|
|
38
|
+
const projectDir = resolve('.')
|
|
39
|
+
const rel = filePath.startsWith('/') ? filePath.slice(projectDir.length + 1) : filePath
|
|
40
|
+
|
|
41
|
+
// State 1: Migration file written
|
|
42
|
+
if (MIGRATION_RE.test(rel)) process.exit(0)
|
|
43
|
+
|
|
44
|
+
// State 2: Not an entity file
|
|
45
|
+
const match = rel.match(ENTITY_RE)
|
|
46
|
+
if (!match) process.exit(0)
|
|
47
|
+
|
|
48
|
+
const moduleId = match[1]
|
|
49
|
+
|
|
50
|
+
// State 3: Entity file without fresh migration
|
|
51
|
+
if (hasFreshMigration(projectDir, moduleId)) process.exit(0)
|
|
52
|
+
|
|
53
|
+
process.stderr.write(
|
|
54
|
+
[
|
|
55
|
+
`Entity file modified: ${rel}`,
|
|
56
|
+
'',
|
|
57
|
+
`Affected module: ${moduleId}`,
|
|
58
|
+
'',
|
|
59
|
+
'A database migration is likely needed. Ask the user whether to run:',
|
|
60
|
+
' yarn db:generate',
|
|
61
|
+
'',
|
|
62
|
+
'Then regenerate registries:',
|
|
63
|
+
' yarn generate',
|
|
64
|
+
'',
|
|
65
|
+
'Do NOT run migrations automatically — always confirm with the user first.',
|
|
66
|
+
'',
|
|
67
|
+
].join('\n'),
|
|
68
|
+
)
|
|
69
|
+
process.exit(1)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Copy to .cursor/mcp.json and set MCP_API_KEY (printed by yarn initialize)",
|
|
3
|
+
"mcpServers": {
|
|
4
|
+
"open-mercato": {
|
|
5
|
+
"command": "yarn",
|
|
6
|
+
"args": ["mercato", "mcp:dev"],
|
|
7
|
+
"env": {
|
|
8
|
+
"APP_URL": "http://localhost:3000",
|
|
9
|
+
"MCP_API_KEY": "${MCP_API_KEY}"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Entity modification guard — triggers migration workflow
|
|
3
|
+
globs:
|
|
4
|
+
- src/modules/*/entities/**/*.ts
|
|
5
|
+
alwaysApply: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Entity Modification — Migration Required
|
|
9
|
+
|
|
10
|
+
You are editing an entity file. After saving, you MUST:
|
|
11
|
+
|
|
12
|
+
1. **Create a migration** — ask the user before running:
|
|
13
|
+
```
|
|
14
|
+
yarn db:generate
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
2. **Show the migration** to the user and wait for confirmation before applying
|
|
18
|
+
|
|
19
|
+
3. **Apply the migration** (only after user confirms):
|
|
20
|
+
```
|
|
21
|
+
yarn db:migrate
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
4. **Regenerate registries**:
|
|
25
|
+
```
|
|
26
|
+
yarn generate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Do NOT skip any step. Do NOT run migrations without user confirmation.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Prevent editing auto-generated files
|
|
3
|
+
globs:
|
|
4
|
+
- .mercato/generated/**
|
|
5
|
+
alwaysApply: false
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# NEVER Edit Generated Files
|
|
9
|
+
|
|
10
|
+
This file is auto-generated by `yarn generate`. Any manual edits will be overwritten.
|
|
11
|
+
|
|
12
|
+
To change generated output, edit the source modules in `src/modules/` and run `yarn generate`.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Core Open Mercato conventions for standalone app development
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# {{PROJECT_NAME}} — Project Rules
|
|
7
|
+
|
|
8
|
+
## What this project is
|
|
9
|
+
|
|
10
|
+
A standalone Open Mercato application. Build domain features ON TOP of the framework.
|
|
11
|
+
To customise a core module beyond the extension system: `yarn mercato eject <module>`.
|
|
12
|
+
Never edit node_modules/@open-mercato/* directly.
|
|
13
|
+
|
|
14
|
+
## Task routing
|
|
15
|
+
|
|
16
|
+
Read AGENTS.md before starting any task — it maps task types to the minimum files to load.
|
|
17
|
+
|
|
18
|
+
## Module system
|
|
19
|
+
|
|
20
|
+
Modules in `src/modules/<id>/`. Register with `from: '@app'` in `src/modules.ts`.
|
|
21
|
+
Run `yarn generate` after any module or entity change. Never edit `.mercato/generated/*`.
|
|
22
|
+
|
|
23
|
+
## Entity and migration rule
|
|
24
|
+
|
|
25
|
+
After editing any entity in `src/modules/<id>/entities/`:
|
|
26
|
+
1. Create migration: `yarn db:generate`
|
|
27
|
+
2. Show migration to user, confirm before applying
|
|
28
|
+
3. Apply: `yarn db:migrate`
|
|
29
|
+
4. Regenerate: `yarn generate`
|
|
30
|
+
|
|
31
|
+
## Spec-driven development
|
|
32
|
+
|
|
33
|
+
Check `.ai/specs/` before any significant feature. If no spec exists, ask the user
|
|
34
|
+
whether to create one first.
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Agent Context Routing — {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
Read this file before any task. Load ONLY the files listed for your task type.
|
|
4
|
+
Do NOT load the entire src/ tree — Open Mercato apps can have many modules.
|
|
5
|
+
|
|
6
|
+
## What This Project Is
|
|
7
|
+
|
|
8
|
+
A standalone Open Mercato application built ON TOP of the framework.
|
|
9
|
+
The framework lives in `node_modules/@open-mercato/*`. Never edit `node_modules` directly.
|
|
10
|
+
To customise a built-in module beyond extensions, eject with `yarn mercato eject <module>`.
|
|
11
|
+
|
|
12
|
+
## Task → Context Map
|
|
13
|
+
|
|
14
|
+
Match your task, then load the listed file(s) BEFORE writing code. A task may match multiple rows.
|
|
15
|
+
|
|
16
|
+
### Module Development
|
|
17
|
+
|
|
18
|
+
| Task | Load |
|
|
19
|
+
|---|---|
|
|
20
|
+
| Scaffold a new module from scratch | `.ai/skills/module-scaffold/SKILL.md` |
|
|
21
|
+
| Design entities and relationships | `.ai/skills/data-model-design/SKILL.md` |
|
|
22
|
+
| Build backend UI (forms, tables, pages) | `.ai/skills/backend-ui-design/SKILL.md` |
|
|
23
|
+
| Build an integration provider | `.ai/skills/integration-builder/SKILL.md` |
|
|
24
|
+
|
|
25
|
+
### Extending Core Modules (UMES)
|
|
26
|
+
|
|
27
|
+
| Task | Load |
|
|
28
|
+
|---|---|
|
|
29
|
+
| Extend a core module (add fields, columns, menus, interceptors, enrichers) | `.ai/skills/system-extension/SKILL.md` |
|
|
30
|
+
| Eject and customize a core module | `.ai/skills/eject-and-customize/SKILL.md` |
|
|
31
|
+
| Add a response enricher to another module's API | `.ai/guides/core.md` → Response Enrichers |
|
|
32
|
+
| Add an API interceptor (before/after hooks) | `.ai/guides/core.md` → API Interceptors |
|
|
33
|
+
| Inject widgets into forms/tables/menus | `.ai/guides/core.md` → Widget Injection |
|
|
34
|
+
| Replace or wrap a UI component | `.ai/guides/core.md` → Component Replacement |
|
|
35
|
+
|
|
36
|
+
### Framework Feature Usage
|
|
37
|
+
|
|
38
|
+
| Task | Load |
|
|
39
|
+
|---|---|
|
|
40
|
+
| Add/modify an entity, create migration | `.ai/guides/core.md` → Module Files, then `yarn db:generate` |
|
|
41
|
+
| Add a REST API endpoint | `.ai/guides/core.md` → API Routes |
|
|
42
|
+
| Add a backend page | `.ai/guides/ui.md` → CrudForm / DataTable |
|
|
43
|
+
| Add event subscribers or emit events | `.ai/guides/events.md` |
|
|
44
|
+
| Add real-time browser updates (SSE) | `.ai/guides/events.md` → DOM Event Bridge |
|
|
45
|
+
| Add search to a module | `.ai/guides/search.md` |
|
|
46
|
+
| Add caching | `.ai/guides/cache.md` |
|
|
47
|
+
| Add background workers | `.ai/guides/queue.md` |
|
|
48
|
+
| Use i18n (translations) | `.ai/guides/shared.md` → i18n |
|
|
49
|
+
| Use encrypted queries | `.ai/guides/shared.md` → Encryption |
|
|
50
|
+
| Use apiCall / UI components | `.ai/guides/ui.md` |
|
|
51
|
+
| Add permissions (RBAC) | `.ai/guides/core.md` → Access Control |
|
|
52
|
+
| Add notifications | `.ai/guides/core.md` → Notifications |
|
|
53
|
+
| Add custom fields | `.ai/guides/core.md` → Custom Fields |
|
|
54
|
+
|
|
55
|
+
### Quality & Process
|
|
56
|
+
|
|
57
|
+
| Task | Load |
|
|
58
|
+
|---|---|
|
|
59
|
+
| Debug / fix errors | `.ai/skills/troubleshooter/SKILL.md` |
|
|
60
|
+
| Review code changes | `.ai/skills/code-review/SKILL.md` |
|
|
61
|
+
| Write a spec | `.ai/skills/spec-writing/SKILL.md`, `.ai/specs/SPEC-000-template.md` |
|
|
62
|
+
|
|
63
|
+
## Module Anatomy
|
|
64
|
+
|
|
65
|
+
Each module in `src/modules/<id>/` is self-contained and auto-discovered:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
src/modules/<id>/
|
|
69
|
+
├── index.ts # Module metadata
|
|
70
|
+
├── data/
|
|
71
|
+
│ ├── entities.ts # MikroORM entity classes
|
|
72
|
+
│ ├── validators.ts # Zod validation schemas
|
|
73
|
+
│ ├── extensions.ts # Cross-module entity links
|
|
74
|
+
│ └── enrichers.ts # Response enrichers
|
|
75
|
+
├── api/
|
|
76
|
+
│ ├── <resource>/route.ts # REST handlers (auto-discovered by method)
|
|
77
|
+
│ └── interceptors.ts # API route interception hooks
|
|
78
|
+
├── backend/ # Admin UI pages (auto-discovered)
|
|
79
|
+
│ └── page.tsx # → /backend/<module>
|
|
80
|
+
├── frontend/ # Public pages (auto-discovered)
|
|
81
|
+
├── subscribers/ # Event handlers (export metadata + default handler)
|
|
82
|
+
├── workers/ # Background jobs (export metadata + default handler)
|
|
83
|
+
├── widgets/
|
|
84
|
+
│ ├── injection/ # UI widgets injected into other modules
|
|
85
|
+
│ ├── injection-table.ts # Widget-to-slot mappings
|
|
86
|
+
│ └── components.ts # Component replacement/wrapper definitions
|
|
87
|
+
├── di.ts # Awilix DI registrations
|
|
88
|
+
├── acl.ts # Permission features
|
|
89
|
+
├── setup.ts # Tenant init, role features, seed data
|
|
90
|
+
├── events.ts # Typed event declarations
|
|
91
|
+
├── search.ts # Search indexing configuration
|
|
92
|
+
├── ce.ts # Custom entities / custom field sets
|
|
93
|
+
├── translations.ts # Translatable fields per entity
|
|
94
|
+
├── notifications.ts # Notification type definitions
|
|
95
|
+
└── notifications.client.ts # Client-side notification renderers
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Register in `src/modules.ts`: `{ id: '<id>', from: '@app' }`
|
|
99
|
+
|
|
100
|
+
## Critical Conventions
|
|
101
|
+
|
|
102
|
+
- After any module/entity change: `yarn generate`
|
|
103
|
+
- After any entity edit: `yarn db:generate` (never hand-write migrations)
|
|
104
|
+
- NEVER edit `.mercato/generated/*` — auto-generated
|
|
105
|
+
- NEVER edit `node_modules/@open-mercato/*` — eject instead
|
|
106
|
+
- Custom modules use `from: '@app'` in `src/modules.ts`
|
|
107
|
+
- Confirm migrations with user before `yarn db:migrate`
|
|
108
|
+
|
|
109
|
+
## Naming Conventions
|
|
110
|
+
|
|
111
|
+
- Module IDs: plural, snake_case (`order_items`)
|
|
112
|
+
- Event IDs: `module.entity.action` (singular entity, past tense: `sales.order.created`)
|
|
113
|
+
- DB tables: plural, snake_case with module prefix (`catalog_products`)
|
|
114
|
+
- DB columns: snake_case (`created_at`, `organization_id`)
|
|
115
|
+
- JS/TS identifiers: camelCase
|
|
116
|
+
- Feature IDs: `<module>.<action>` (`my_module.view`, `my_module.create`)
|
|
117
|
+
- UUID primary keys, explicit foreign keys, junction tables for M2M
|
|
118
|
+
|
|
119
|
+
## Key Imports Quick Reference
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// Translations
|
|
123
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
124
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
125
|
+
|
|
126
|
+
// API calls (MUST use — never raw fetch)
|
|
127
|
+
import { apiCall, apiCallOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
128
|
+
|
|
129
|
+
// CRUD forms
|
|
130
|
+
import { CrudForm, createCrud, updateCrud, deleteCrud } from '@open-mercato/ui/backend/crud'
|
|
131
|
+
import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
|
|
132
|
+
|
|
133
|
+
// UI components (MUST use — never raw <button>)
|
|
134
|
+
import { Button } from '@open-mercato/ui/primitives/button'
|
|
135
|
+
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
136
|
+
import { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
137
|
+
import { FormHeader, FormFooter } from '@open-mercato/ui/backend/forms'
|
|
138
|
+
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
139
|
+
|
|
140
|
+
// Encrypted queries (MUST use instead of em.find)
|
|
141
|
+
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
142
|
+
|
|
143
|
+
// Events
|
|
144
|
+
import { createModuleEvents } from '@open-mercato/shared/modules/events'
|
|
145
|
+
|
|
146
|
+
// Widget injection
|
|
147
|
+
import { InjectionPosition } from '@open-mercato/shared/modules/widgets/injection-position'
|
|
148
|
+
|
|
149
|
+
// Types
|
|
150
|
+
import type { ModuleSetupConfig } from '@open-mercato/shared/modules/setup'
|
|
151
|
+
import type { ResponseEnricher } from '@open-mercato/shared/lib/crud/response-enricher'
|
|
152
|
+
import type { ApiInterceptor } from '@open-mercato/shared/lib/crud/api-interceptor'
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Key Commands
|
|
156
|
+
|
|
157
|
+
| Command | Purpose |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `yarn dev` | Start dev server |
|
|
160
|
+
| `yarn generate` | Regenerate `.mercato/generated/` |
|
|
161
|
+
| `yarn db:generate` | Create migration for entity changes |
|
|
162
|
+
| `yarn db:migrate` | Apply pending migrations |
|
|
163
|
+
| `yarn initialize` | Bootstrap DB + first admin account |
|
|
164
|
+
| `yarn build` | Build for production |
|
|
165
|
+
| `yarn mercato eject <module>` | Copy a core module into `src/modules/` |
|
|
166
|
+
|
|
167
|
+
## Architecture Rules
|
|
168
|
+
|
|
169
|
+
- NO direct ORM relationships between modules — use foreign key IDs
|
|
170
|
+
- Always filter by `organization_id` for tenant-scoped entities
|
|
171
|
+
- Validate all inputs with Zod; derive types via `z.infer`
|
|
172
|
+
- Use DI (Awilix) for services; avoid `new`-ing directly
|
|
173
|
+
- No `any` types — use Zod schemas with `z.infer`, narrow with runtime checks
|
|
174
|
+
- Every dialog: `Cmd/Ctrl+Enter` submit, `Escape` cancel
|
|
175
|
+
- Keep `pageSize` at or below 100
|
|
176
|
+
- Every API route MUST export `openApi`
|
|
177
|
+
|
|
178
|
+
## Stack
|
|
179
|
+
|
|
180
|
+
Next.js App Router, TypeScript, MikroORM, Awilix DI, Zod
|