@open-mercato/cli 0.5.1-develop.3043.1a796c3920 → 0.6.0
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/AGENTS.md +7 -0
- package/dist/agentic/shared/AGENTS.md.template +40 -5
- package/dist/agentic/shared/ai/qa/tests/playwright.config.ts +5 -0
- package/dist/agentic/shared/ai/skills/auto-continue-pr-loop/SKILL.md +592 -0
- package/dist/agentic/shared/ai/skills/auto-create-pr-loop/SKILL.md +749 -0
- package/dist/agentic/shared/ai/skills/data-model-design/SKILL.md +91 -3
- package/dist/agentic/shared/ai/skills/implement-spec/SKILL.md +9 -5
- package/dist/agentic/shared/ai/skills/module-scaffold/SKILL.md +57 -7
- package/dist/agentic/shared/ai/skills/spec-writing/SKILL.md +11 -2
- package/dist/agentic/shared/ai/skills/spec-writing/references/spec-checklist.md +24 -7
- package/dist/lib/agentic-setup.js +15 -6
- package/dist/lib/agentic-setup.js.map +2 -2
- package/dist/lib/db/commands.js +17 -3
- package/dist/lib/db/commands.js.map +2 -2
- package/package.json +10 -11
- package/src/lib/agentic-setup.ts +15 -6
- package/src/lib/db/__tests__/commands.test.ts +57 -0
- package/src/lib/db/commands.ts +21 -3
|
@@ -16,7 +16,8 @@ Design entities, relationships, and manage the migration lifecycle following Ope
|
|
|
16
16
|
5. [Cross-Module References](#5-cross-module-references)
|
|
17
17
|
6. [Migration Lifecycle](#6-migration-lifecycle)
|
|
18
18
|
7. [Advanced Patterns](#7-advanced-patterns)
|
|
19
|
-
8. [
|
|
19
|
+
8. [Sensitive Data and Encryption Maps](#8-sensitive-data-and-encryption-maps)
|
|
20
|
+
9. [Anti-Patterns](#9-anti-patterns)
|
|
20
21
|
|
|
21
22
|
---
|
|
22
23
|
|
|
@@ -483,7 +484,89 @@ export class TicketHistory {
|
|
|
483
484
|
|
|
484
485
|
---
|
|
485
486
|
|
|
486
|
-
## 8.
|
|
487
|
+
## 8. Sensitive Data and Encryption Maps
|
|
488
|
+
|
|
489
|
+
When the developer asks for "we need this column encrypted", "store this securely", "this is PII", "GDPR", or "encryption at rest" — and whenever you are designing a column that will hold names, addresses, contact information, free-text notes about people, integration credentials, secrets, or any data subject to a data-processing agreement — use the framework's **encryption-maps mechanism**. Do NOT hand-roll AES, raw `crypto.subtle`, custom KMS calls, or "TODO encrypt later" stubs.
|
|
490
|
+
|
|
491
|
+
The mechanism gives you:
|
|
492
|
+
|
|
493
|
+
- Per-tenant Data Encryption Keys (DEKs) resolved through the configured KMS (Vault by default, env-fallback in dev).
|
|
494
|
+
- Declarative, per-entity, per-field encryption with optional deterministic-hash sibling columns for equality lookups (for example login by email).
|
|
495
|
+
- Boot-time auto-application: every enabled module's `defaultEncryptionMaps` is collected during `auth:setup` and applied when `TENANT_DATA_ENCRYPTION=yes`.
|
|
496
|
+
- A `findWithDecryption` / `findOneWithDecryption` read API that transparently decrypts on read.
|
|
497
|
+
|
|
498
|
+
### When encryption is mandatory
|
|
499
|
+
|
|
500
|
+
| Field example | Encrypt? |
|
|
501
|
+
|---|---|
|
|
502
|
+
| First name, last name, preferred name | Yes |
|
|
503
|
+
| Email, phone | Yes — usually with a `hashField` for lookups |
|
|
504
|
+
| Postal address (line 1/2, city, region, postal code, country) | Yes |
|
|
505
|
+
| Free-text comments / notes / activity bodies that mention people | Yes |
|
|
506
|
+
| Integration secrets, API keys, OAuth tokens, webhook signing keys | Yes |
|
|
507
|
+
| Document numbers (tax IDs, national IDs) | Yes |
|
|
508
|
+
| Status enums, counters, timestamps, FKs, currency codes | No |
|
|
509
|
+
| Public catalog metadata (product titles for a public storefront) | Usually no |
|
|
510
|
+
|
|
511
|
+
If you are unsure, default to encrypting and confirm with the user — re-introducing encryption later requires a backfill, but turning it off later is a single map edit.
|
|
512
|
+
|
|
513
|
+
### Declare the map in `<module>/encryption.ts`
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
import type { ModuleEncryptionMap } from '@open-mercato/shared/modules/encryption'
|
|
517
|
+
|
|
518
|
+
export const defaultEncryptionMaps: ModuleEncryptionMap[] = [
|
|
519
|
+
{
|
|
520
|
+
entityId: '<module_id>:<entity>', // matches the entity's table id (colon-separated)
|
|
521
|
+
fields: [
|
|
522
|
+
{ field: 'first_name' },
|
|
523
|
+
{ field: 'last_name' },
|
|
524
|
+
{ field: 'phone' },
|
|
525
|
+
// Sibling deterministic hash for equality lookups (e.g. login by email).
|
|
526
|
+
// Add a matching `<field>_hash varchar` column to the entity.
|
|
527
|
+
{ field: 'email', hashField: 'email_hash' },
|
|
528
|
+
],
|
|
529
|
+
},
|
|
530
|
+
]
|
|
531
|
+
|
|
532
|
+
export default defaultEncryptionMaps
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Read with decryption — never raw `em.find`
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
539
|
+
|
|
540
|
+
// Signature: (em, entityName, where, options?, scope?). MikroORM FindOptions go in slot 4
|
|
541
|
+
// (pass `undefined` if you have none), the decryption scope `{ tenantId, organizationId }` in slot 5.
|
|
542
|
+
const records = await findWithDecryption(em, '<Entity>', filter, undefined, { tenantId, organizationId })
|
|
543
|
+
const single = await findOneWithDecryption(em, '<Entity>', { id }, undefined, { tenantId, organizationId })
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
Calling `em.find` on an encrypted column returns ciphertext, breaks search, and silently leaks bug surface. The `findWithDecryption` family is the one entry point.
|
|
547
|
+
|
|
548
|
+
### Apply maps to existing tenants
|
|
549
|
+
|
|
550
|
+
```bash
|
|
551
|
+
yarn mercato entities seed-encryption --tenant <tenantId> [--organization <orgId>]
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
New tenants pick up the maps automatically during `auth:setup`. Toggling the **Encrypted** flag on a custom field via the admin UI also only applies to data written **after** the change — backfill historical plaintext rows by running `yarn mercato entities rotate-encryption-key --tenant <tenantId> --org <organizationId>` (without `--old-key` it skips already-encrypted fields and just encrypts plaintext). Use `yarn mercato entities decrypt-database` to roll back. For full UI flows and CLI options see <https://docs.open-mercato.dev/user-guide/encryption>.
|
|
555
|
+
|
|
556
|
+
### Vector search caveat
|
|
557
|
+
|
|
558
|
+
The `vector` module stores raw embeddings unencrypted in the vector store (e.g. pgvector). Even though the source text is decrypted only transiently to compute embeddings, treat the embeddings as sensitive: avoid embedding raw high-sensitivity text and rely on disk-level / managed-database encryption-at-rest for the vector column.
|
|
559
|
+
|
|
560
|
+
### Environment switches
|
|
561
|
+
|
|
562
|
+
- `TENANT_DATA_ENCRYPTION=yes|no` (default `yes`) — set to `no` to run the hooks as no-op (validation still applies).
|
|
563
|
+
- `TENANT_DATA_ENCRYPTION_DEBUG=yes` — log map evaluation, KMS calls, cache hits.
|
|
564
|
+
- `VAULT_ADDR` / `VAULT_TOKEN` / `VAULT_KV_PATH` — HashiCorp Vault KMS configuration.
|
|
565
|
+
- `TENANT_DATA_ENCRYPTION_FALLBACK_KEY` — local/dev fallback key when Vault is unavailable. In dev, `AUTH_SECRET` / `NEXTAUTH_SECRET` is used as a last resort; production falls back to noop KMS.
|
|
566
|
+
|
|
567
|
+
---
|
|
568
|
+
|
|
569
|
+
## 9. Anti-Patterns
|
|
487
570
|
|
|
488
571
|
| Anti-Pattern | Problem | Correct Pattern |
|
|
489
572
|
|-------------|---------|-----------------|
|
|
@@ -498,6 +581,10 @@ export class TicketHistory {
|
|
|
498
581
|
| Storing arrays as comma-separated strings | Can't query, no integrity | Use `jsonb` arrays or junction tables |
|
|
499
582
|
| UUID FK without index | Slow joins | Always `@Index()` on FK columns |
|
|
500
583
|
| Nullable required fields | Data integrity issues | Use `!` assertion for required, `null` for optional |
|
|
584
|
+
| Hand-rolled AES / `crypto.subtle` / custom KMS for sensitive columns | Per-tenant key isolation, hash lookups, key rotation, and admin UI all break | Declare `<module>/encryption.ts` with `defaultEncryptionMaps`; let the framework manage DEKs and Vault |
|
|
585
|
+
| Reading encrypted columns with raw `em.find` / `em.findOne` | Returns ciphertext, breaks search, silent data corruption | Use `findWithDecryption` / `findOneWithDecryption` with `{ tenantId, organizationId }` |
|
|
586
|
+
| Storing PII as plaintext "for now" / TODO comments | GDPR violation, leaks at rest, expensive backfill later | Encrypt from day one; toggling later only protects new writes |
|
|
587
|
+
| Encrypting an `email` column without a `hashField` | Login / equality lookups stop working | Declare a sibling `hashField` (e.g. `email_hash`) in the encryption map and add the matching `varchar` column |
|
|
501
588
|
|
|
502
589
|
---
|
|
503
590
|
|
|
@@ -515,7 +602,8 @@ export class TicketHistory {
|
|
|
515
602
|
- **MUST** specify `length` on all `varchar` columns
|
|
516
603
|
- **MUST NOT** use ORM relationship decorators across module boundaries
|
|
517
604
|
- **MUST NOT** rename or drop columns in a single release
|
|
518
|
-
- **MUST
|
|
605
|
+
- **MUST** declare encrypted columns in `<module>/encryption.ts` exporting `defaultEncryptionMaps: ModuleEncryptionMap[]`, and read them via `findWithDecryption` / `findOneWithDecryption` from `@open-mercato/shared/lib/encryption/find` — see section 8
|
|
606
|
+
- **MUST NOT** hand-roll AES / KMS calls or store sensitive columns as plaintext "for now" — use the encryption-maps mechanism in section 8
|
|
519
607
|
- Use `jsonb` for flexible/nested data, proper columns for queryable/sortable data
|
|
520
608
|
- Use junction tables for many-to-many relationships
|
|
521
609
|
- Derive TypeScript types from Zod schemas, never duplicate type definitions
|
|
@@ -40,14 +40,18 @@ For every piece of code, enforce these code-review rules inline:
|
|
|
40
40
|
| Area | Rule |
|
|
41
41
|
|------|------|
|
|
42
42
|
| Types | No `any` — use zod + `z.infer` |
|
|
43
|
-
| API routes | Export `openApi` and `metadata` with
|
|
44
|
-
|
|
|
43
|
+
| API routes | Export `openApi` and per-method `metadata` with `requireAuth` / `requireFeatures` (no top-level `export const requireAuth`) |
|
|
44
|
+
| **CRUD APIs** | **Use `makeCrudRoute({ entity, entityId, operations, schema, indexer: { entityType } })` from `@open-mercato/shared/lib/crud/factory`. Custom write routes MUST call `validateCrudMutationGuard` before the mutation and `runCrudMutationGuardAfterSuccess` after success. See `AGENTS.md` → Mandatory Module Mechanisms.** |
|
|
45
|
+
| Entities | Standard columns, snake_case, UUID PKs, indexed `organization_id` + `tenant_id` |
|
|
45
46
|
| Security | `findWithDecryption`, tenant scoping, zod validation |
|
|
46
|
-
|
|
|
47
|
-
|
|
|
47
|
+
| **Encryption maps** | **For every PII / GDPR-relevant column the phase touches, declare in `<module>/encryption.ts` exporting `defaultEncryptionMaps` (type from `@open-mercato/shared/modules/encryption`). Reads via `findWithDecryption` / `findOneWithDecryption` (5-arg `(em, entity, where, options?, scope?)`). Equality-lookup columns declare a sibling `hashField`. NEVER hand-rolled AES/KMS, `crypto.subtle`, or "encrypt later" stubs. See `AGENTS.md` → CRITICAL Rule #11 (Encryption maps) + the "Encryption maps" row of the Mandatory Module Mechanisms table; `.ai/skills/data-model-design/SKILL.md` § Sensitive Data and Encryption Maps; `.ai/skills/module-scaffold/SKILL.md` § Encryption maps.** |
|
|
48
|
+
| UI | `<CrudForm>`/`<DataTable>` (with stable `entityId` + `extensionTableId`), `apiCall` (never raw `fetch`), `flash()`, `<LoadingMessage>`/`<ErrorMessage>` |
|
|
49
|
+
| **Design System** | **Semantic status tokens (no `text-red-*` / `bg-green-*`); Tailwind text scale (no `text-[13px]` / `text-[11px]`); shared primitives `StatusBadge` / `Alert` / `FormField` / `SectionHeader` / `CollapsibleSection` / `LoadingMessage` / `Spinner` / `DataLoader` / `EmptyState`; lucide-react icons in PAGE BODY (never inline `<svg>`); `aria-label` on every icon-only button; Boy Scout rule on touched lines. See `AGENTS.md` → CRITICAL Rule #10 (Strict Design System alignment) + `.ai/skills/backend-ui-design/SKILL.md`.** |
|
|
50
|
+
| **Cache** | **Resolve via DI (`container.resolve('cache')`); tag with `tenant:<id>` / `org:<id>`; declare invalidation per write path. NEVER `new Redis(...)` or raw SQLite.** |
|
|
51
|
+
| Events | `createModuleEvents()` with `as const`, subscribers export `metadata`; cross-module side effects via subscribers, never direct imports |
|
|
48
52
|
| i18n | `useT()` client, `resolveTranslations()` server, no hardcoded strings |
|
|
49
53
|
| Imports | Package-level `@open-mercato/<pkg>/...` for framework imports |
|
|
50
|
-
| Mutations | `useGuardedMutation` when not using CrudForm |
|
|
54
|
+
| Mutations | `useGuardedMutation` when not using CrudForm; pass `retryLastMutation` in injection context |
|
|
51
55
|
| Keyboard | `Cmd/Ctrl+Enter` submit, `Escape` cancel on dialogs |
|
|
52
56
|
| Naming | Modules plural snake_case, events `module.entity.past_tense`, features `module.action` |
|
|
53
57
|
|
|
@@ -41,8 +41,9 @@ Before writing any code, ask the developer:
|
|
|
41
41
|
- [ ] Background workers
|
|
42
42
|
- [ ] CLI commands
|
|
43
43
|
- [ ] Custom fields support
|
|
44
|
+
- [ ] **Sensitive / GDPR-relevant fields** (PII, contact info, addresses, free-text notes about people, integration credentials, secrets) — if yes, an `encryption.ts` declaring `defaultEncryptionMaps` is mandatory; see section 11 → Encryption maps
|
|
44
45
|
|
|
45
|
-
If the developer provides a brief description, infer reasonable defaults and confirm.
|
|
46
|
+
If the developer provides a brief description, infer reasonable defaults and confirm. When key fields include names, emails, phones, addresses, free-text comments, or external API keys, treat the encryption checkbox as `yes` by default and confirm with the user rather than skipping it silently.
|
|
46
47
|
|
|
47
48
|
---
|
|
48
49
|
|
|
@@ -57,6 +58,7 @@ src/modules/<module_id>/
|
|
|
57
58
|
├── setup.ts # Tenant init, role features
|
|
58
59
|
├── di.ts # Awilix DI registrations
|
|
59
60
|
├── events.ts # Typed event declarations (if needed)
|
|
61
|
+
├── encryption.ts # Tenant data encryption maps (only if entity has sensitive/GDPR fields)
|
|
60
62
|
├── data/
|
|
61
63
|
│ ├── entities.ts # MikroORM entity classes
|
|
62
64
|
│ └── validators.ts # Zod validation schemas
|
|
@@ -178,7 +180,7 @@ Use `makeCrudRoute` for standard CRUD. Each HTTP method lives in its own file.
|
|
|
178
180
|
**File**: `src/modules/<module_id>/api/get/<entities>.ts`
|
|
179
181
|
|
|
180
182
|
```typescript
|
|
181
|
-
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/
|
|
183
|
+
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
|
|
182
184
|
import { <Entity> } from '../../data/entities'
|
|
183
185
|
|
|
184
186
|
const handler = makeCrudRoute({
|
|
@@ -201,7 +203,7 @@ export const openApi = {
|
|
|
201
203
|
**File**: `src/modules/<module_id>/api/post/<entities>.ts`
|
|
202
204
|
|
|
203
205
|
```typescript
|
|
204
|
-
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/
|
|
206
|
+
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
|
|
205
207
|
import { <Entity> } from '../../data/entities'
|
|
206
208
|
import { create<Entity>Schema } from '../../data/validators'
|
|
207
209
|
|
|
@@ -225,7 +227,7 @@ export const openApi = {
|
|
|
225
227
|
**File**: `src/modules/<module_id>/api/put/<entities>.ts`
|
|
226
228
|
|
|
227
229
|
```typescript
|
|
228
|
-
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/
|
|
230
|
+
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
|
|
229
231
|
import { <Entity> } from '../../data/entities'
|
|
230
232
|
import { update<Entity>Schema } from '../../data/validators'
|
|
231
233
|
|
|
@@ -249,7 +251,7 @@ export const openApi = {
|
|
|
249
251
|
**File**: `src/modules/<module_id>/api/delete/<entities>.ts`
|
|
250
252
|
|
|
251
253
|
```typescript
|
|
252
|
-
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/
|
|
254
|
+
import { makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'
|
|
253
255
|
import { <Entity> } from '../../data/entities'
|
|
254
256
|
|
|
255
257
|
const handler = makeCrudRoute({
|
|
@@ -346,7 +348,7 @@ export const metadata = {
|
|
|
346
348
|
|
|
347
349
|
```tsx
|
|
348
350
|
'use client'
|
|
349
|
-
import { CrudForm } from '@open-mercato/ui/backend/
|
|
351
|
+
import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
|
|
350
352
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
351
353
|
|
|
352
354
|
export default function Create<Entity>Page() {
|
|
@@ -384,7 +386,7 @@ export const metadata = {
|
|
|
384
386
|
|
|
385
387
|
```tsx
|
|
386
388
|
'use client'
|
|
387
|
-
import { CrudForm } from '@open-mercato/ui/backend/
|
|
389
|
+
import { CrudForm } from '@open-mercato/ui/backend/CrudForm'
|
|
388
390
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
389
391
|
|
|
390
392
|
export default function Edit<Entity>Page({ params }: { params: { id: string } }) {
|
|
@@ -573,6 +575,52 @@ export default function registerCli(program: any) {
|
|
|
573
575
|
}
|
|
574
576
|
```
|
|
575
577
|
|
|
578
|
+
### Encryption maps (sensitive / GDPR-relevant fields)
|
|
579
|
+
|
|
580
|
+
**Mandatory** when the entity stores PII, contact info, addresses, free-text notes about people, integration credentials, secrets, or anything subject to a data-processing agreement. Do NOT hand-roll AES, KMS calls, or "TODO encrypt later" stubs — the framework provides per-tenant DEKs and a declarative field-level map.
|
|
581
|
+
|
|
582
|
+
**File**: `src/modules/<module_id>/encryption.ts`
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
import type { ModuleEncryptionMap } from '@open-mercato/shared/modules/encryption'
|
|
586
|
+
|
|
587
|
+
export const defaultEncryptionMaps: ModuleEncryptionMap[] = [
|
|
588
|
+
{
|
|
589
|
+
entityId: '<module_id>:<entity>', // matches data/entities.ts table id, colon-separated
|
|
590
|
+
fields: [
|
|
591
|
+
{ field: 'first_name' },
|
|
592
|
+
{ field: 'last_name' },
|
|
593
|
+
{ field: 'phone' },
|
|
594
|
+
// Add a hashField for deterministic equality lookups (e.g. login by email):
|
|
595
|
+
{ field: 'email', hashField: 'email_hash' },
|
|
596
|
+
],
|
|
597
|
+
},
|
|
598
|
+
]
|
|
599
|
+
|
|
600
|
+
export default defaultEncryptionMaps
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
**Read paths** — never `em.find` an encrypted column directly:
|
|
604
|
+
|
|
605
|
+
```typescript
|
|
606
|
+
import { findWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
607
|
+
|
|
608
|
+
// Signature: (em, entityName, where, options?, scope?) — MikroORM FindOptions in slot 4
|
|
609
|
+
// (pass `undefined` when none), decryption scope in slot 5.
|
|
610
|
+
const records = await findWithDecryption(em, '<Entity>', filter, undefined, { tenantId, organizationId })
|
|
611
|
+
const single = await findOneWithDecryption(em, '<Entity>', { id }, undefined, { tenantId, organizationId })
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
**Apply to existing tenants** after declaring or updating maps:
|
|
615
|
+
|
|
616
|
+
```bash
|
|
617
|
+
yarn mercato entities seed-encryption --tenant <tenantId> [--organization <orgId>]
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
New tenants pick up `defaultEncryptionMaps` automatically during `auth:setup`. Toggling the **Encrypted** flag for a field only applies to data written **after** the change — historical plaintext rows stay as they were until backfilled via `yarn mercato entities rotate-encryption-key --tenant <tenantId> --org <organizationId>` (without `--old-key` the command only encrypts plaintext and skips already-encrypted fields). Use `yarn mercato entities decrypt-database` to roll back. For end-to-end usage and admin UI flows see <https://docs.open-mercato.dev/user-guide/encryption>.
|
|
621
|
+
|
|
622
|
+
> Tip: when `email` (or any other column) needs deterministic lookups while encrypted, declare a sibling `hashField` in the map and add a matching `varchar` column to the entity. The framework keeps the hash in sync on writes; queries can target the hash instead of the cleartext column.
|
|
623
|
+
|
|
576
624
|
---
|
|
577
625
|
|
|
578
626
|
## 12. Wire & Verify
|
|
@@ -658,3 +706,5 @@ yarn dev # Start dev server
|
|
|
658
706
|
- **MUST NOT** run `yarn db:migrate` without explicit user confirmation
|
|
659
707
|
- **MUST NOT** create ORM relationships (`@ManyToOne`, `@OneToMany`) to entities in other modules
|
|
660
708
|
- **MUST NOT** edit `.mercato/generated/*` files manually
|
|
709
|
+
- **MUST** declare `<module>/encryption.ts` exporting `defaultEncryptionMaps` whenever the entity stores sensitive / GDPR-relevant fields (PII, contact info, addresses, free-text notes about people, integration credentials, secrets) — and read those columns via `findWithDecryption` / `findOneWithDecryption`
|
|
710
|
+
- **MUST NOT** hand-roll AES/KMS calls or store "we'll encrypt this later" plaintext for sensitive columns — use the encryption-maps mechanism described in section 11 → Encryption maps
|
|
@@ -60,6 +60,9 @@ Use the [Specification Template](references/spec-template.md). Adapt if needed,
|
|
|
60
60
|
3. **Singularity Law**: Singular naming for entities, commands, events, feature IDs.
|
|
61
61
|
4. **Undo Contract**: Is the "Undo" logic as detailed as the "Execute"?
|
|
62
62
|
5. **Module Isolation**: Using Event Bus for side effects or cheating with direct imports?
|
|
63
|
+
6. **Canonical Mechanisms**: Does the spec reach for the framework primitives (`makeCrudRoute`, `<CrudForm>`, `<DataTable>`, `apiCall` / `useGuardedMutation`, DI-resolved cache, `createModuleEvents`) or invent its own substitute? See `AGENTS.md` → **Mandatory Module Mechanisms** for the full canon and links. No raw `fetch`, no raw `<form>`, no `new Redis(...)`, no manual cross-module ORM joins.
|
|
64
|
+
7. **Sensitive Data**: For every PII / GDPR / address / contact / free-text-about-people / integration-credential column the spec proposes, does it declare an `encryption.ts` `defaultEncryptionMaps` entry and route reads through `findWithDecryption`? See `AGENTS.md` → CRITICAL Rule #11 (Encryption maps) + the "Encryption maps for sensitive data" row of the Mandatory Module Mechanisms table and `.ai/skills/data-model-design/SKILL.md` § Sensitive Data and Encryption Maps. No hand-rolled AES, no `crypto.subtle`, no "TODO encrypt later".
|
|
65
|
+
8. **Design System**: Does every UI mock / className snippet in the spec match the DS canon — semantic status tokens (no `text-red-*` / `bg-green-*`), Tailwind text scale (no `text-[11px]` / `text-[13px]`), shared primitives (`StatusBadge`, `Alert`, `FormField`, `SectionHeader`, `CollapsibleSection`, `LoadingMessage` / `Spinner` / `DataLoader`, `EmptyState`), lucide-react icons in page body (never inline `<svg>`), dialog `Cmd/Ctrl+Enter` submit + `Escape` cancel, `aria-label` on every icon-only button? See `AGENTS.md` → CRITICAL Rule #10 (Strict Design System alignment for every UI change) and `.ai/skills/backend-ui-design/SKILL.md`. Specs that touch existing pages MUST honour the Boy Scout rule.
|
|
63
66
|
|
|
64
67
|
## Quick Rule Reference
|
|
65
68
|
|
|
@@ -68,9 +71,15 @@ Use the [Specification Template](references/spec-template.md). Adapt if needed,
|
|
|
68
71
|
- **`organization_id`** is mandatory for all tenant-scoped entities.
|
|
69
72
|
- **Undoability** is the default for state changes.
|
|
70
73
|
- **Zod validation** for all API inputs.
|
|
74
|
+
- **Encryption maps** for every sensitive / GDPR-relevant column (declare in `<module>/encryption.ts`, read via `findWithDecryption`) — see `AGENTS.md` → Data Encryption.
|
|
75
|
+
- **Canonical primitives** for CRUD APIs (`makeCrudRoute`), backend forms (`CrudForm`), tables (`DataTable`), HTTP (`apiCall` — never raw `fetch`), non-`CrudForm` writes (`useGuardedMutation`), cache (DI-resolved `@open-mercato/cache`), events (`createModuleEvents`) — see `AGENTS.md` → Mandatory Module Mechanisms.
|
|
76
|
+
- **Design System** tokens and shared UI primitives — no hardcoded status colors, no arbitrary text sizes, no inline `<svg>` in page-body UI. See `AGENTS.md` → Design System.
|
|
71
77
|
|
|
72
78
|
## Reference Materials
|
|
73
79
|
|
|
74
80
|
- [Spec Template](references/spec-template.md)
|
|
75
|
-
- [Spec Checklist](references/spec-checklist.md)
|
|
76
|
-
- [AGENTS.md](../../../AGENTS.md)
|
|
81
|
+
- [Spec Checklist](references/spec-checklist.md) — § 3 covers encryption maps; § 5 covers canonical mechanisms + DS
|
|
82
|
+
- [AGENTS.md](../../../AGENTS.md) — Mandatory Module Mechanisms table; CRITICAL Rule #10 (Design System); CRITICAL Rule #11 (Encryption maps)
|
|
83
|
+
- [`.ai/skills/data-model-design/SKILL.md`](../data-model-design/SKILL.md) → Sensitive Data and Encryption Maps
|
|
84
|
+
- [`.ai/skills/module-scaffold/SKILL.md`](../module-scaffold/SKILL.md) → Encryption maps
|
|
85
|
+
- [`.ai/skills/backend-ui-design/SKILL.md`](../backend-ui-design/SKILL.md) — DS-compliant pages
|
|
@@ -19,26 +19,42 @@ Every item must be answered in the spec or marked N/A with justification.
|
|
|
19
19
|
|
|
20
20
|
## 3. Data Integrity & Security
|
|
21
21
|
|
|
22
|
-
- [ ] Entities include `id`, `organization_id`, `created_at`, `updated_at`
|
|
22
|
+
- [ ] Entities include `id`, `organization_id`, `tenant_id`, `created_at`, `updated_at`, `deleted_at`, `is_active`
|
|
23
23
|
- [ ] Write operations define transaction boundaries
|
|
24
24
|
- [ ] Input validation uses zod schemas
|
|
25
25
|
- [ ] All user input validated before business logic/persistence
|
|
26
|
-
- [ ] Auth guards
|
|
27
|
-
- [ ] Tenant isolation: every scoped query filters by `organization_id`
|
|
26
|
+
- [ ] Auth guards declared per-method in `metadata` (`requireAuth`, `requireFeatures`) — never legacy top-level `export const requireAuth`
|
|
27
|
+
- [ ] Tenant isolation: every scoped query filters by `organization_id` (and `tenant_id` where applicable)
|
|
28
|
+
- [ ] **Encryption maps mechanism is used (no hand-rolled crypto).** For every PII / GDPR-relevant column the spec proposes — names, addresses, contacts, free-text notes about people, integration credentials, secrets, document numbers — the spec MUST declare them in a module-level `<module>/encryption.ts` exporting `defaultEncryptionMaps: ModuleEncryptionMap[]` (type from `@open-mercato/shared/modules/encryption`). Reads MUST go through `findWithDecryption` / `findOneWithDecryption` (5-arg `(em, entity, where, options?, scope?)`) from `@open-mercato/shared/lib/encryption/find`. Equality-lookup columns (e.g. login email) declare a sibling `hashField`. No `crypto.subtle`, no custom KMS calls, no "TODO encrypt later". See `AGENTS.md` → CRITICAL Rule #11 (Encryption maps) + the "Encryption maps for sensitive data" row of the Mandatory Module Mechanisms table; `.ai/skills/data-model-design/SKILL.md` § Sensitive Data and Encryption Maps.
|
|
28
29
|
|
|
29
30
|
## 4. Commands, Events & Naming
|
|
30
31
|
|
|
31
32
|
- [ ] Naming is singular and consistent
|
|
32
33
|
- [ ] All mutations are commands with undo logic
|
|
33
|
-
- [ ] Events declared in
|
|
34
|
+
- [ ] Events declared in `<module>/events.ts` via `createModuleEvents({ moduleId, events } as const)` before emitting; cross-module side effects use `subscribers/*.ts`, never direct cross-module imports
|
|
34
35
|
- [ ] Side-effect reversibility is documented
|
|
35
36
|
|
|
36
|
-
## 5. API & UI
|
|
37
|
+
## 5. API & UI — Canonical Mechanisms
|
|
37
38
|
|
|
38
39
|
- [ ] API contracts are complete (request/response/errors)
|
|
39
40
|
- [ ] Routes include `openApi` expectations
|
|
40
|
-
- [ ]
|
|
41
|
-
- [ ]
|
|
41
|
+
- [ ] **Canonical mechanisms — no DIY substitutes.** The spec MUST reach for the framework primitives, not invent its own. See `AGENTS.md` → **Mandatory Module Mechanisms**.
|
|
42
|
+
- [ ] **CRUD APIs** use `makeCrudRoute({ entity, entityId, operations, schema, indexer: { entityType } })` from `@open-mercato/shared/lib/crud/factory`. Custom write routes call `validateCrudMutationGuard` before mutation and `runCrudMutationGuardAfterSuccess` after.
|
|
43
|
+
- [ ] **API route files export `metadata`** with per-method `requireAuth` / `requireFeatures` (no top-level `export const requireAuth`).
|
|
44
|
+
- [ ] **Backend forms** use `<CrudForm>` from `@open-mercato/ui/backend/CrudForm` with helpers `createCrud` / `updateCrud` / `deleteCrud` from `@open-mercato/ui/backend/utils/crud`, throwing `createCrudFormError` from `@open-mercato/ui/backend/utils/serverErrors` for field-level errors. No raw `<form>`, no raw `fetch`.
|
|
45
|
+
- [ ] **Lists** use `<DataTable entityId apiPath columns />` from `@open-mercato/ui/backend/DataTable` with stable `entityId` / `extensionTableId` so widget injection (columns / row actions / bulk actions / filters / toolbar) keeps working.
|
|
46
|
+
- [ ] **HTTP clients** use `apiCall` / `apiCallOrThrow` / `readApiResultOrThrow` from `@open-mercato/ui/backend/utils/apiCall` — never raw `fetch`.
|
|
47
|
+
- [ ] **Non-`CrudForm` writes** are wrapped in `useGuardedMutation(...).runMutation(...)` and pass `retryLastMutation` in the injection context.
|
|
48
|
+
- [ ] **Cache** is resolved via DI (`container.resolve('cache')`) — never `new Redis(...)` or raw SQLite. Tags include `tenant:<id>` / `org:<id>`.
|
|
49
|
+
- [ ] **Design System compliance for every UI mock and className snippet in the spec.** See `AGENTS.md` → CRITICAL Rule #10 (Strict Design System alignment) and `.ai/skills/backend-ui-design/SKILL.md`.
|
|
50
|
+
- [ ] Use semantic status tokens (`text-status-error-text`, `bg-status-success-bg`, `border-status-warning-border`, `text-status-info-icon`, `text-destructive`, `bg-destructive`) — NEVER hardcoded shades like `text-red-500`, `bg-green-100`, `text-amber-*`, `text-emerald-*`, `bg-blue-*`. Status tokens already cover dark mode; no `dark:` overrides.
|
|
51
|
+
- [ ] Use the Tailwind text scale (`text-xs` 12, `text-sm` 14, `text-base` 16, `text-lg` 18, `text-xl` 20, `text-2xl` 24) or `text-overline` for 11px uppercase labels — NEVER arbitrary sizes (`text-[11px]`, `text-[13px]`, `text-[15px]`, `p-[13px]`, `rounded-[24px]`, `z-[9999]`).
|
|
52
|
+
- [ ] Use shared primitives instead of raw HTML: `<Alert variant=...>` for inline status, `flash('msg', 'success|error|warning|info')` for toasts, `useConfirmDialog()` for destructive confirmations, `<StatusBadge>` for entity status, `<FormField label error>` to wrap form inputs, `<SectionHeader title count action>` for section headers, `<CollapsibleSection>` for collapsible regions, `<LoadingMessage>` / `<Spinner>` / `<DataLoader>` for async states, `<EmptyState>` (or DataTable `emptyState` prop) for empty lists.
|
|
53
|
+
- [ ] Use lucide-react icons in PAGE BODY UI (`Page`, `DataTable`, `CrudForm`, cards, buttons) — never inline `<svg>`. Sizes from the `size-{3|4|5|6}` scale; `strokeWidth` is not overridden per-instance. `page.meta.ts` icons follow the `React.createElement('svg', …)` pattern.
|
|
54
|
+
- [ ] Every dialog supports `Cmd/Ctrl+Enter` to submit and `Escape` to cancel.
|
|
55
|
+
- [ ] Every icon-only button has an `aria-label`.
|
|
56
|
+
- [ ] Boy Scout rule: when the spec edits an existing page, any line touched gets migrated to semantic tokens / DS scale.
|
|
57
|
+
- [ ] i18n keys are planned for user-facing strings (`useT()` client-side, `resolveTranslations()` server-side; never hard-coded labels)
|
|
42
58
|
- [ ] Pagination limits defined (`pageSize <= 100`)
|
|
43
59
|
|
|
44
60
|
## 6. Risks & Anti-Patterns
|
|
@@ -48,3 +64,4 @@ Every item must be answered in the spec or marked N/A with justification.
|
|
|
48
64
|
- [ ] Does not introduce cross-module ORM links
|
|
49
65
|
- [ ] Does not skip undoability for state changes
|
|
50
66
|
- [ ] Does not mix MVP with speculative future phases
|
|
67
|
+
- [ ] Does not introduce hand-rolled AES, raw `fetch`, raw `<form>`, `new Redis(...)`, or arbitrary Tailwind sizes / status colors
|
|
@@ -136,17 +136,26 @@ function generateShared(config) {
|
|
|
136
136
|
"ai/skills/auto-upgrade-0.4.10-to-0.5.0/SKILL.md",
|
|
137
137
|
join(targetDir, ".ai", "skills", "auto-upgrade-0.4.10-to-0.5.0", "SKILL.md")
|
|
138
138
|
);
|
|
139
|
-
for (const autoSkill of [
|
|
139
|
+
for (const autoSkill of [
|
|
140
|
+
"auto-create-pr",
|
|
141
|
+
"auto-continue-pr",
|
|
142
|
+
"auto-create-pr-loop",
|
|
143
|
+
"auto-continue-pr-loop",
|
|
144
|
+
"auto-review-pr",
|
|
145
|
+
"auto-fix-github"
|
|
146
|
+
]) {
|
|
140
147
|
copyFile(
|
|
141
148
|
srcDir,
|
|
142
149
|
`ai/skills/${autoSkill}/SKILL.md`,
|
|
143
150
|
join(targetDir, ".ai", "skills", autoSkill, "SKILL.md")
|
|
144
151
|
);
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
if (existsSync(join(srcDir, "ai", "skills", autoSkill, "STANDALONE.md"))) {
|
|
153
|
+
copyFile(
|
|
154
|
+
srcDir,
|
|
155
|
+
`ai/skills/${autoSkill}/STANDALONE.md`,
|
|
156
|
+
join(targetDir, ".ai", "skills", autoSkill, "STANDALONE.md")
|
|
157
|
+
);
|
|
158
|
+
}
|
|
150
159
|
}
|
|
151
160
|
copyFile(
|
|
152
161
|
srcDir,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/lib/agentic-setup.ts"],
|
|
4
|
-
"sourcesContent": ["/**\n * Agentic setup for the CLI `agentic:init` command.\n *\n * Source files live in packages/create-app/agentic/ and are copied\n * to packages/cli/dist/agentic/ during build (see build.mjs).\n * This module reads those files at runtime \u2014 no embedded string constants.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, symlinkSync, lstatSync, unlinkSync, readdirSync } from 'node:fs'\nimport { join, dirname, basename } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n// In the built output (dist/lib/agentic-setup.js), __dirname is dist/lib/.\n// agentic/ is copied to dist/agentic/ by build.mjs.\nconst AGENTIC_DIR = join(__dirname, '..', 'agentic')\nconst GUIDES_DIR = join(AGENTIC_DIR, 'guides')\n\ntype AskFn = (question: string) => Promise<string>\n\ninterface AgenticSetupOptions {\n tool?: string\n force?: boolean\n}\n\ninterface AgenticConfig {\n projectName: string\n targetDir: string\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction resolvePlaceholders(content: string, config: AgenticConfig): string {\n return content.replace(/\\{\\{PROJECT_NAME\\}\\}/g, config.projectName)\n}\n\nfunction ensureDir(filePath: string): void {\n const dir = dirname(filePath)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n}\n\nfunction writeTemplate(srcDir: string, srcRelative: string, destPath: string, config: AgenticConfig): void {\n const srcPath = join(srcDir, srcRelative)\n const content = readFileSync(srcPath, 'utf-8')\n ensureDir(destPath)\n writeFileSync(destPath, resolvePlaceholders(content, config))\n}\n\nfunction copyFile(srcDir: string, srcRelative: string, destPath: string): void {\n const srcPath = join(srcDir, srcRelative)\n ensureDir(destPath)\n copyFileSync(srcPath, destPath)\n}\n\n// \u2500\u2500\u2500 Generators \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction generateShared(config: AgenticConfig): void {\n const { targetDir } = config\n const srcDir = join(AGENTIC_DIR, 'shared')\n\n // AGENTS.md\n writeTemplate(srcDir, 'AGENTS.md.template', join(targetDir, 'AGENTS.md'), config)\n\n // .ai/ structure\n writeTemplate(srcDir, 'ai/specs/README.md', join(targetDir, '.ai', 'specs', 'README.md'), config)\n copyFile(srcDir, 'ai/specs/SPEC-000-template.md', join(targetDir, '.ai', 'specs', 'SPEC-000-template.md'))\n copyFile(srcDir, 'ai/lessons.md', join(targetDir, '.ai', 'lessons.md'))\n\n // .ai/skills/\n writeTemplate(\n srcDir,\n 'ai/skills/spec-writing/SKILL.md',\n join(targetDir, '.ai', 'skills', 'spec-writing', 'SKILL.md'),\n config,\n )\n copyFile(\n srcDir,\n 'ai/skills/spec-writing/references/spec-template.md',\n join(targetDir, '.ai', 'skills', 'spec-writing', 'references', 'spec-template.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/spec-writing/references/spec-checklist.md',\n join(targetDir, '.ai', 'skills', 'spec-writing', 'references', 'spec-checklist.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/backend-ui-design/SKILL.md',\n join(targetDir, '.ai', 'skills', 'backend-ui-design', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/backend-ui-design/references/ui-components.md',\n join(targetDir, '.ai', 'skills', 'backend-ui-design', 'references', 'ui-components.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/code-review/SKILL.md',\n join(targetDir, '.ai', 'skills', 'code-review', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/code-review/references/review-checklist.md',\n join(targetDir, '.ai', 'skills', 'code-review', 'references', 'review-checklist.md'),\n )\n copyFile(srcDir, 'ai/skills/integration-builder/SKILL.md', join(targetDir, '.ai', 'skills', 'integration-builder', 'SKILL.md'))\n copyFile(\n srcDir,\n 'ai/skills/integration-builder/references/adapter-contracts.md',\n join(targetDir, '.ai', 'skills', 'integration-builder', 'references', 'adapter-contracts.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/system-extension/SKILL.md',\n join(targetDir, '.ai', 'skills', 'system-extension', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/system-extension/references/extension-contracts.md',\n join(targetDir, '.ai', 'skills', 'system-extension', 'references', 'extension-contracts.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/module-scaffold/SKILL.md',\n join(targetDir, '.ai', 'skills', 'module-scaffold', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/module-scaffold/references/naming-conventions.md',\n join(targetDir, '.ai', 'skills', 'module-scaffold', 'references', 'naming-conventions.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/module-scaffold/references/navigation-patterns.md',\n join(targetDir, '.ai', 'skills', 'module-scaffold', 'references', 'navigation-patterns.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/troubleshooter/SKILL.md',\n join(targetDir, '.ai', 'skills', 'troubleshooter', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/troubleshooter/references/diagnostic-commands.md',\n join(targetDir, '.ai', 'skills', 'troubleshooter', 'references', 'diagnostic-commands.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/eject-and-customize/SKILL.md',\n join(targetDir, '.ai', 'skills', 'eject-and-customize', 'SKILL.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/data-model-design/SKILL.md',\n join(targetDir, '.ai', 'skills', 'data-model-design', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/data-model-design/references/mikro-orm-cheatsheet.md',\n join(targetDir, '.ai', 'skills', 'data-model-design', 'references', 'mikro-orm-cheatsheet.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/implement-spec/SKILL.md',\n join(targetDir, '.ai', 'skills', 'implement-spec', 'SKILL.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/integration-tests/SKILL.md',\n join(targetDir, '.ai', 'skills', 'integration-tests', 'SKILL.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/auto-upgrade-0.4.10-to-0.5.0/SKILL.md',\n join(targetDir, '.ai', 'skills', 'auto-upgrade-0.4.10-to-0.5.0', 'SKILL.md'),\n )\n\n for (const autoSkill of ['auto-create-pr', 'auto-continue-pr', 'auto-review-pr', 'auto-fix-github']) {\n copyFile(\n srcDir,\n `ai/skills/${autoSkill}/SKILL.md`,\n join(targetDir, '.ai', 'skills', autoSkill, 'SKILL.md'),\n )\n copyFile(\n srcDir,\n `ai/skills/${autoSkill}/STANDALONE.md`,\n join(targetDir, '.ai', 'skills', autoSkill, 'STANDALONE.md'),\n )\n }\n\n copyFile(\n srcDir,\n 'ai/skills/trim-unused-modules/SKILL.md',\n join(targetDir, '.ai', 'skills', 'trim-unused-modules', 'SKILL.md'),\n )\n\n copyFile(srcDir, 'ai/qa/tests/playwright.config.ts', join(targetDir, '.ai', 'qa', 'tests', 'playwright.config.ts'))\n\n if (existsSync(GUIDES_DIR)) {\n const guidesDestDir = join(targetDir, '.ai', 'guides')\n for (const file of readdirSync(GUIDES_DIR)) {\n if (!file.endsWith('.md')) continue\n const srcPath = join(GUIDES_DIR, file)\n const destPath = join(guidesDestDir, file)\n ensureDir(destPath)\n copyFileSync(srcPath, destPath)\n }\n }\n}\n\nfunction generateClaudeCode(config: AgenticConfig): void {\n const { targetDir } = config\n const srcDir = join(AGENTIC_DIR, 'claude-code')\n\n writeTemplate(srcDir, 'CLAUDE.md.template', join(targetDir, 'CLAUDE.md'), config)\n copyFile(srcDir, 'settings.json', join(targetDir, '.claude', 'settings.json'))\n copyFile(srcDir, 'hooks/entity-migration-check.ts', join(targetDir, '.claude', 'hooks', 'entity-migration-check.ts'))\n copyFile(srcDir, 'mcp.json.example', join(targetDir, '.mcp.json.example'))\n\n // Symlink .claude/skills \u2192 ../.ai/skills\n ensureSkillsLink(join(targetDir, '.claude', 'skills'), join('..', '.ai', 'skills'))\n}\n\nfunction generateCodex(config: AgenticConfig): void {\n const { targetDir } = config\n const srcDir = join(AGENTIC_DIR, 'codex')\n\n const agentsPath = join(targetDir, 'AGENTS.md')\n if (existsSync(agentsPath)) {\n const enforcement = readFileSync(join(srcDir, 'enforcement-rules.md'), 'utf-8')\n let agents = readFileSync(agentsPath, 'utf-8')\n const MARKER_START = '<!-- CODEX_ENFORCEMENT_RULES_START -->'\n const MARKER_END = '<!-- CODEX_ENFORCEMENT_RULES_END -->'\n\n if (agents.includes(MARKER_START)) {\n const startIdx = agents.indexOf(MARKER_START)\n const endIdx = agents.indexOf(MARKER_END)\n if (endIdx !== -1) {\n agents = agents.slice(0, startIdx) + enforcement + agents.slice(endIdx + MARKER_END.length)\n }\n } else {\n const firstNewline = agents.indexOf('\\n')\n if (firstNewline !== -1) {\n agents = agents.slice(0, firstNewline + 1) + '\\n' + enforcement + '\\n' + agents.slice(firstNewline + 1)\n } else {\n agents = agents + '\\n\\n' + enforcement\n }\n }\n writeFileSync(agentsPath, agents)\n }\n\n copyFile(srcDir, 'mcp.json.example', join(targetDir, '.codex', 'mcp.json.example'))\n\n // Symlink .codex/skills \u2192 ../.ai/skills\n ensureSkillsLink(join(targetDir, '.codex', 'skills'), join('..', '.ai', 'skills'))\n}\n\nfunction generateCursor(config: AgenticConfig): void {\n const { targetDir } = config\n const srcDir = join(AGENTIC_DIR, 'cursor')\n\n writeTemplate(srcDir, 'rules/open-mercato.mdc', join(targetDir, '.cursor', 'rules', 'open-mercato.mdc'), config)\n copyFile(srcDir, 'rules/entity-guard.mdc', join(targetDir, '.cursor', 'rules', 'entity-guard.mdc'))\n copyFile(srcDir, 'rules/generated-guard.mdc', join(targetDir, '.cursor', 'rules', 'generated-guard.mdc'))\n copyFile(srcDir, 'hooks.json', join(targetDir, '.cursor', 'hooks.json'))\n copyFile(srcDir, 'hooks/entity-migration-check.mjs', join(targetDir, '.cursor', 'hooks', 'entity-migration-check.mjs'))\n copyFile(srcDir, 'mcp.json.example', join(targetDir, '.cursor', 'mcp.json.example'))\n\n // Symlink .cursor/skills \u2192 ../.ai/skills\n ensureSkillsLink(join(targetDir, '.cursor', 'skills'), join('..', '.ai', 'skills'))\n}\n\nfunction ensureSkillsLink(linkPath: string, target: string): void {\n ensureDir(linkPath)\n if (existsSync(linkPath) && !lstatSync(linkPath).isSymbolicLink()) {\n return\n }\n if (lstatSync(linkPath, { throwIfNoEntry: false })?.isSymbolicLink()) {\n unlinkSync(linkPath)\n }\n symlinkSync(target, linkPath)\n}\n\n// \u2500\u2500\u2500 Wizard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst TOOLS = [\n { key: '1', label: 'Claude Code (Anthropic)', id: 'claude-code' },\n { key: '2', label: 'Codex (OpenAI)', id: 'codex' },\n { key: '3', label: 'Cursor (Anysphere)', id: 'cursor' },\n { key: '4', label: 'Multiple tools (select individually)', id: 'multiple' },\n { key: '5', label: 'Skip \u2014 set up manually later', id: 'skip' },\n] as const\n\nconst SELECTABLE = TOOLS.filter((t) => t.id !== 'multiple' && t.id !== 'skip')\n\nasync function promptSelection(ask: AskFn): Promise<string[]> {\n console.log('')\n console.log('\uD83E\uDD16 Agentic workflow setup')\n console.log('')\n console.log(' Which AI coding tool will you use with this project?')\n console.log('')\n for (const tool of TOOLS) {\n console.log(` ${tool.key}. ${tool.label}`)\n }\n console.log('')\n\n const answer = (await ask(' Enter number(s) separated by comma [1]: ')).trim() || '1'\n\n if (answer === '5') return ['skip']\n\n if (answer === '4') {\n const selected: string[] = []\n for (const tool of SELECTABLE) {\n const yn = await ask(` Include ${tool.label}? [y/N]: `)\n if (yn.toLowerCase() === 'y' || yn.toLowerCase() === 'yes') {\n selected.push(tool.id)\n }\n }\n return selected.length > 0 ? selected : ['skip']\n }\n\n const keys = answer.split(',').map((s) => s.trim())\n const ids: string[] = []\n for (const key of keys) {\n const tool = TOOLS.find((t) => t.key === key)\n if (tool && tool.id !== 'multiple' && tool.id !== 'skip') {\n ids.push(tool.id)\n }\n }\n return ids.length > 0 ? ids : ['skip']\n}\n\nexport async function runAgenticSetup(\n targetDir: string,\n ask: AskFn,\n options?: AgenticSetupOptions,\n): Promise<void> {\n let selectedIds: string[]\n\n if (options?.tool) {\n selectedIds = options.tool.split(',').map((t) => t.trim())\n } else {\n selectedIds = await promptSelection(ask)\n }\n\n if (selectedIds.includes('skip')) {\n console.log('')\n console.log(' Skipped agentic setup.')\n console.log('')\n return\n }\n\n const config: AgenticConfig = {\n projectName: basename(targetDir),\n targetDir,\n }\n\n generateShared(config)\n if (selectedIds.includes('claude-code')) generateClaudeCode(config)\n if (selectedIds.includes('codex')) generateCodex(config)\n if (selectedIds.includes('cursor')) generateCursor(config)\n\n console.log('')\n console.log(' Agentic setup complete:')\n if (selectedIds.includes('claude-code')) {\n console.log(' \u2713 Claude Code \u2014 CLAUDE.md, .claude/hooks/, .mcp.json.example')\n }\n if (selectedIds.includes('codex')) {\n console.log(' \u2713 Codex \u2014 AGENTS.md enforcement rules, .codex/mcp.json.example')\n }\n if (selectedIds.includes('cursor')) {\n console.log(' \u2713 Cursor \u2014 .cursor/rules/, .cursor/hooks/, .cursor/mcp.json.example')\n }\n console.log('')\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["/**\n * Agentic setup for the CLI `agentic:init` command.\n *\n * Source files live in packages/create-app/agentic/ and are copied\n * to packages/cli/dist/agentic/ during build (see build.mjs).\n * This module reads those files at runtime \u2014 no embedded string constants.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, copyFileSync, symlinkSync, lstatSync, unlinkSync, readdirSync } from 'node:fs'\nimport { join, dirname, basename } from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\n// In the built output (dist/lib/agentic-setup.js), __dirname is dist/lib/.\n// agentic/ is copied to dist/agentic/ by build.mjs.\nconst AGENTIC_DIR = join(__dirname, '..', 'agentic')\nconst GUIDES_DIR = join(AGENTIC_DIR, 'guides')\n\ntype AskFn = (question: string) => Promise<string>\n\ninterface AgenticSetupOptions {\n tool?: string\n force?: boolean\n}\n\ninterface AgenticConfig {\n projectName: string\n targetDir: string\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction resolvePlaceholders(content: string, config: AgenticConfig): string {\n return content.replace(/\\{\\{PROJECT_NAME\\}\\}/g, config.projectName)\n}\n\nfunction ensureDir(filePath: string): void {\n const dir = dirname(filePath)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n}\n\nfunction writeTemplate(srcDir: string, srcRelative: string, destPath: string, config: AgenticConfig): void {\n const srcPath = join(srcDir, srcRelative)\n const content = readFileSync(srcPath, 'utf-8')\n ensureDir(destPath)\n writeFileSync(destPath, resolvePlaceholders(content, config))\n}\n\nfunction copyFile(srcDir: string, srcRelative: string, destPath: string): void {\n const srcPath = join(srcDir, srcRelative)\n ensureDir(destPath)\n copyFileSync(srcPath, destPath)\n}\n\n// \u2500\u2500\u2500 Generators \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction generateShared(config: AgenticConfig): void {\n const { targetDir } = config\n const srcDir = join(AGENTIC_DIR, 'shared')\n\n // AGENTS.md\n writeTemplate(srcDir, 'AGENTS.md.template', join(targetDir, 'AGENTS.md'), config)\n\n // .ai/ structure\n writeTemplate(srcDir, 'ai/specs/README.md', join(targetDir, '.ai', 'specs', 'README.md'), config)\n copyFile(srcDir, 'ai/specs/SPEC-000-template.md', join(targetDir, '.ai', 'specs', 'SPEC-000-template.md'))\n copyFile(srcDir, 'ai/lessons.md', join(targetDir, '.ai', 'lessons.md'))\n\n // .ai/skills/\n writeTemplate(\n srcDir,\n 'ai/skills/spec-writing/SKILL.md',\n join(targetDir, '.ai', 'skills', 'spec-writing', 'SKILL.md'),\n config,\n )\n copyFile(\n srcDir,\n 'ai/skills/spec-writing/references/spec-template.md',\n join(targetDir, '.ai', 'skills', 'spec-writing', 'references', 'spec-template.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/spec-writing/references/spec-checklist.md',\n join(targetDir, '.ai', 'skills', 'spec-writing', 'references', 'spec-checklist.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/backend-ui-design/SKILL.md',\n join(targetDir, '.ai', 'skills', 'backend-ui-design', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/backend-ui-design/references/ui-components.md',\n join(targetDir, '.ai', 'skills', 'backend-ui-design', 'references', 'ui-components.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/code-review/SKILL.md',\n join(targetDir, '.ai', 'skills', 'code-review', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/code-review/references/review-checklist.md',\n join(targetDir, '.ai', 'skills', 'code-review', 'references', 'review-checklist.md'),\n )\n copyFile(srcDir, 'ai/skills/integration-builder/SKILL.md', join(targetDir, '.ai', 'skills', 'integration-builder', 'SKILL.md'))\n copyFile(\n srcDir,\n 'ai/skills/integration-builder/references/adapter-contracts.md',\n join(targetDir, '.ai', 'skills', 'integration-builder', 'references', 'adapter-contracts.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/system-extension/SKILL.md',\n join(targetDir, '.ai', 'skills', 'system-extension', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/system-extension/references/extension-contracts.md',\n join(targetDir, '.ai', 'skills', 'system-extension', 'references', 'extension-contracts.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/module-scaffold/SKILL.md',\n join(targetDir, '.ai', 'skills', 'module-scaffold', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/module-scaffold/references/naming-conventions.md',\n join(targetDir, '.ai', 'skills', 'module-scaffold', 'references', 'naming-conventions.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/module-scaffold/references/navigation-patterns.md',\n join(targetDir, '.ai', 'skills', 'module-scaffold', 'references', 'navigation-patterns.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/troubleshooter/SKILL.md',\n join(targetDir, '.ai', 'skills', 'troubleshooter', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/troubleshooter/references/diagnostic-commands.md',\n join(targetDir, '.ai', 'skills', 'troubleshooter', 'references', 'diagnostic-commands.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/eject-and-customize/SKILL.md',\n join(targetDir, '.ai', 'skills', 'eject-and-customize', 'SKILL.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/data-model-design/SKILL.md',\n join(targetDir, '.ai', 'skills', 'data-model-design', 'SKILL.md'),\n )\n copyFile(\n srcDir,\n 'ai/skills/data-model-design/references/mikro-orm-cheatsheet.md',\n join(targetDir, '.ai', 'skills', 'data-model-design', 'references', 'mikro-orm-cheatsheet.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/implement-spec/SKILL.md',\n join(targetDir, '.ai', 'skills', 'implement-spec', 'SKILL.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/integration-tests/SKILL.md',\n join(targetDir, '.ai', 'skills', 'integration-tests', 'SKILL.md'),\n )\n\n copyFile(\n srcDir,\n 'ai/skills/auto-upgrade-0.4.10-to-0.5.0/SKILL.md',\n join(targetDir, '.ai', 'skills', 'auto-upgrade-0.4.10-to-0.5.0', 'SKILL.md'),\n )\n\n for (const autoSkill of [\n 'auto-create-pr',\n 'auto-continue-pr',\n 'auto-create-pr-loop',\n 'auto-continue-pr-loop',\n 'auto-review-pr',\n 'auto-fix-github',\n ]) {\n copyFile(\n srcDir,\n `ai/skills/${autoSkill}/SKILL.md`,\n join(targetDir, '.ai', 'skills', autoSkill, 'SKILL.md'),\n )\n if (existsSync(join(srcDir, 'ai', 'skills', autoSkill, 'STANDALONE.md'))) {\n copyFile(\n srcDir,\n `ai/skills/${autoSkill}/STANDALONE.md`,\n join(targetDir, '.ai', 'skills', autoSkill, 'STANDALONE.md'),\n )\n }\n }\n\n copyFile(\n srcDir,\n 'ai/skills/trim-unused-modules/SKILL.md',\n join(targetDir, '.ai', 'skills', 'trim-unused-modules', 'SKILL.md'),\n )\n\n copyFile(srcDir, 'ai/qa/tests/playwright.config.ts', join(targetDir, '.ai', 'qa', 'tests', 'playwright.config.ts'))\n\n if (existsSync(GUIDES_DIR)) {\n const guidesDestDir = join(targetDir, '.ai', 'guides')\n for (const file of readdirSync(GUIDES_DIR)) {\n if (!file.endsWith('.md')) continue\n const srcPath = join(GUIDES_DIR, file)\n const destPath = join(guidesDestDir, file)\n ensureDir(destPath)\n copyFileSync(srcPath, destPath)\n }\n }\n}\n\nfunction generateClaudeCode(config: AgenticConfig): void {\n const { targetDir } = config\n const srcDir = join(AGENTIC_DIR, 'claude-code')\n\n writeTemplate(srcDir, 'CLAUDE.md.template', join(targetDir, 'CLAUDE.md'), config)\n copyFile(srcDir, 'settings.json', join(targetDir, '.claude', 'settings.json'))\n copyFile(srcDir, 'hooks/entity-migration-check.ts', join(targetDir, '.claude', 'hooks', 'entity-migration-check.ts'))\n copyFile(srcDir, 'mcp.json.example', join(targetDir, '.mcp.json.example'))\n\n // Symlink .claude/skills \u2192 ../.ai/skills\n ensureSkillsLink(join(targetDir, '.claude', 'skills'), join('..', '.ai', 'skills'))\n}\n\nfunction generateCodex(config: AgenticConfig): void {\n const { targetDir } = config\n const srcDir = join(AGENTIC_DIR, 'codex')\n\n const agentsPath = join(targetDir, 'AGENTS.md')\n if (existsSync(agentsPath)) {\n const enforcement = readFileSync(join(srcDir, 'enforcement-rules.md'), 'utf-8')\n let agents = readFileSync(agentsPath, 'utf-8')\n const MARKER_START = '<!-- CODEX_ENFORCEMENT_RULES_START -->'\n const MARKER_END = '<!-- CODEX_ENFORCEMENT_RULES_END -->'\n\n if (agents.includes(MARKER_START)) {\n const startIdx = agents.indexOf(MARKER_START)\n const endIdx = agents.indexOf(MARKER_END)\n if (endIdx !== -1) {\n agents = agents.slice(0, startIdx) + enforcement + agents.slice(endIdx + MARKER_END.length)\n }\n } else {\n const firstNewline = agents.indexOf('\\n')\n if (firstNewline !== -1) {\n agents = agents.slice(0, firstNewline + 1) + '\\n' + enforcement + '\\n' + agents.slice(firstNewline + 1)\n } else {\n agents = agents + '\\n\\n' + enforcement\n }\n }\n writeFileSync(agentsPath, agents)\n }\n\n copyFile(srcDir, 'mcp.json.example', join(targetDir, '.codex', 'mcp.json.example'))\n\n // Symlink .codex/skills \u2192 ../.ai/skills\n ensureSkillsLink(join(targetDir, '.codex', 'skills'), join('..', '.ai', 'skills'))\n}\n\nfunction generateCursor(config: AgenticConfig): void {\n const { targetDir } = config\n const srcDir = join(AGENTIC_DIR, 'cursor')\n\n writeTemplate(srcDir, 'rules/open-mercato.mdc', join(targetDir, '.cursor', 'rules', 'open-mercato.mdc'), config)\n copyFile(srcDir, 'rules/entity-guard.mdc', join(targetDir, '.cursor', 'rules', 'entity-guard.mdc'))\n copyFile(srcDir, 'rules/generated-guard.mdc', join(targetDir, '.cursor', 'rules', 'generated-guard.mdc'))\n copyFile(srcDir, 'hooks.json', join(targetDir, '.cursor', 'hooks.json'))\n copyFile(srcDir, 'hooks/entity-migration-check.mjs', join(targetDir, '.cursor', 'hooks', 'entity-migration-check.mjs'))\n copyFile(srcDir, 'mcp.json.example', join(targetDir, '.cursor', 'mcp.json.example'))\n\n // Symlink .cursor/skills \u2192 ../.ai/skills\n ensureSkillsLink(join(targetDir, '.cursor', 'skills'), join('..', '.ai', 'skills'))\n}\n\nfunction ensureSkillsLink(linkPath: string, target: string): void {\n ensureDir(linkPath)\n if (existsSync(linkPath) && !lstatSync(linkPath).isSymbolicLink()) {\n return\n }\n if (lstatSync(linkPath, { throwIfNoEntry: false })?.isSymbolicLink()) {\n unlinkSync(linkPath)\n }\n symlinkSync(target, linkPath)\n}\n\n// \u2500\u2500\u2500 Wizard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst TOOLS = [\n { key: '1', label: 'Claude Code (Anthropic)', id: 'claude-code' },\n { key: '2', label: 'Codex (OpenAI)', id: 'codex' },\n { key: '3', label: 'Cursor (Anysphere)', id: 'cursor' },\n { key: '4', label: 'Multiple tools (select individually)', id: 'multiple' },\n { key: '5', label: 'Skip \u2014 set up manually later', id: 'skip' },\n] as const\n\nconst SELECTABLE = TOOLS.filter((t) => t.id !== 'multiple' && t.id !== 'skip')\n\nasync function promptSelection(ask: AskFn): Promise<string[]> {\n console.log('')\n console.log('\uD83E\uDD16 Agentic workflow setup')\n console.log('')\n console.log(' Which AI coding tool will you use with this project?')\n console.log('')\n for (const tool of TOOLS) {\n console.log(` ${tool.key}. ${tool.label}`)\n }\n console.log('')\n\n const answer = (await ask(' Enter number(s) separated by comma [1]: ')).trim() || '1'\n\n if (answer === '5') return ['skip']\n\n if (answer === '4') {\n const selected: string[] = []\n for (const tool of SELECTABLE) {\n const yn = await ask(` Include ${tool.label}? [y/N]: `)\n if (yn.toLowerCase() === 'y' || yn.toLowerCase() === 'yes') {\n selected.push(tool.id)\n }\n }\n return selected.length > 0 ? selected : ['skip']\n }\n\n const keys = answer.split(',').map((s) => s.trim())\n const ids: string[] = []\n for (const key of keys) {\n const tool = TOOLS.find((t) => t.key === key)\n if (tool && tool.id !== 'multiple' && tool.id !== 'skip') {\n ids.push(tool.id)\n }\n }\n return ids.length > 0 ? ids : ['skip']\n}\n\nexport async function runAgenticSetup(\n targetDir: string,\n ask: AskFn,\n options?: AgenticSetupOptions,\n): Promise<void> {\n let selectedIds: string[]\n\n if (options?.tool) {\n selectedIds = options.tool.split(',').map((t) => t.trim())\n } else {\n selectedIds = await promptSelection(ask)\n }\n\n if (selectedIds.includes('skip')) {\n console.log('')\n console.log(' Skipped agentic setup.')\n console.log('')\n return\n }\n\n const config: AgenticConfig = {\n projectName: basename(targetDir),\n targetDir,\n }\n\n generateShared(config)\n if (selectedIds.includes('claude-code')) generateClaudeCode(config)\n if (selectedIds.includes('codex')) generateCodex(config)\n if (selectedIds.includes('cursor')) generateCursor(config)\n\n console.log('')\n console.log(' Agentic setup complete:')\n if (selectedIds.includes('claude-code')) {\n console.log(' \u2713 Claude Code \u2014 CLAUDE.md, .claude/hooks/, .mcp.json.example')\n }\n if (selectedIds.includes('codex')) {\n console.log(' \u2713 Codex \u2014 AGENTS.md enforcement rules, .codex/mcp.json.example')\n }\n if (selectedIds.includes('cursor')) {\n console.log(' \u2713 Cursor \u2014 .cursor/rules/, .cursor/hooks/, .cursor/mcp.json.example')\n }\n console.log('')\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/lib/db/commands.js
CHANGED
|
@@ -65,6 +65,16 @@ function getMigrationSnapshotName(resolver) {
|
|
|
65
65
|
void resolver;
|
|
66
66
|
return ".snapshot-open-mercato";
|
|
67
67
|
}
|
|
68
|
+
function shouldCreateInitialModuleMigration(migrationsPath, snapshotName) {
|
|
69
|
+
const snapshotPath = path.join(migrationsPath, `${snapshotName}.json`);
|
|
70
|
+
if (fs.existsSync(snapshotPath)) return false;
|
|
71
|
+
if (!fs.existsSync(migrationsPath)) return true;
|
|
72
|
+
const migrationFiles = fs.readdirSync(migrationsPath).filter((file) => /^Migration.*\.(ts|js)$/.test(file) && !file.endsWith(".d.ts"));
|
|
73
|
+
return migrationFiles.length === 0;
|
|
74
|
+
}
|
|
75
|
+
function resolveGeneratedMigrationPath(fileName, migrationsPath) {
|
|
76
|
+
return path.isAbsolute(fileName) ? fileName : path.join(migrationsPath, fileName);
|
|
77
|
+
}
|
|
68
78
|
let tsxLoaderRegistered = false;
|
|
69
79
|
let temporaryModuleCounter = 0;
|
|
70
80
|
async function ensureTsxLoaderRegistered() {
|
|
@@ -176,6 +186,8 @@ async function dbGenerate(resolver, options = {}) {
|
|
|
176
186
|
fs.mkdirSync(migrationsPath, { recursive: true });
|
|
177
187
|
const tableName = `mikro_orm_migrations_${sanitizedModId}`;
|
|
178
188
|
validateTableName(tableName);
|
|
189
|
+
const snapshotName = getMigrationSnapshotName(resolver);
|
|
190
|
+
const createInitialMigration = shouldCreateInitialModuleMigration(migrationsPath, snapshotName);
|
|
179
191
|
const orm = await MikroORM.init({
|
|
180
192
|
driver: PostgreSqlDriver,
|
|
181
193
|
clientUrl: getClientUrl(),
|
|
@@ -187,7 +199,7 @@ async function dbGenerate(resolver, options = {}) {
|
|
|
187
199
|
path: migrationsPath,
|
|
188
200
|
glob: "!(*.d).{ts,js}",
|
|
189
201
|
tableName,
|
|
190
|
-
snapshotName
|
|
202
|
+
snapshotName,
|
|
191
203
|
dropTables: false
|
|
192
204
|
},
|
|
193
205
|
schemaGenerator: {
|
|
@@ -204,10 +216,10 @@ async function dbGenerate(resolver, options = {}) {
|
|
|
204
216
|
} : void 0
|
|
205
217
|
});
|
|
206
218
|
try {
|
|
207
|
-
const diff = await orm.migrator.create();
|
|
219
|
+
const diff = await orm.migrator.create(void 0, false, createInitialMigration);
|
|
208
220
|
if (diff && diff.fileName) {
|
|
209
221
|
try {
|
|
210
|
-
const orig = diff.fileName;
|
|
222
|
+
const orig = resolveGeneratedMigrationPath(diff.fileName, migrationsPath);
|
|
211
223
|
const base = path.basename(orig);
|
|
212
224
|
const dir = path.dirname(orig);
|
|
213
225
|
const ext = path.extname(base);
|
|
@@ -431,7 +443,9 @@ export {
|
|
|
431
443
|
dbMigrate,
|
|
432
444
|
getMigrationSnapshotName,
|
|
433
445
|
makeConstraintDropsIdempotent,
|
|
446
|
+
resolveGeneratedMigrationPath,
|
|
434
447
|
sanitizeModuleId,
|
|
448
|
+
shouldCreateInitialModuleMigration,
|
|
435
449
|
validateTableName
|
|
436
450
|
};
|
|
437
451
|
//# sourceMappingURL=commands.js.map
|