@mars-stack/core 2.0.1 → 3.0.1

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.
@@ -4,13 +4,16 @@ globs: **/server/**
4
4
  alwaysApply: false
5
5
  ---
6
6
 
7
- ## Prisma
7
+ ## Prisma (v7)
8
8
 
9
- - Import the lazy-loaded client: `import { prisma } from '@/lib/prisma'`.
9
+ - Import the database client: `import { prisma } from '@/lib/prisma'`.
10
+ - Import Prisma types (models, enums, namespaces): `import type { User } from '@db'` or `import { Prisma } from '@db'`. The `@db` alias maps to `prisma/generated/prisma/client`.
11
+ - The `datasource` URL lives in `prisma.config.ts`, **not** in the schema file.
12
+ - `PrismaClient` requires a driver adapter: `new PrismaClient({ adapter })` with `@prisma/adapter-pg`.
13
+ - After changing the schema: `yarn db:generate` then `yarn db:push` (dev) or `yarn db:migrate` (with migration history). `prisma generate` no longer auto-runs.
10
14
  - User-scoped queries MUST include `userId` from the authenticated session in the `where` clause, never from request parameters.
11
15
  - Use `$transaction` for multi-step writes that must be atomic.
12
16
  - New models go in `prisma/schema/` as separate `.prisma` files (multi-file schema).
13
- - After changing the schema: `yarn db:push` (dev) or `yarn db:migrate dev` (with migration history).
14
17
 
15
18
  ## Error Handling
16
19
 
@@ -12,8 +12,8 @@ alwaysApply: false
12
12
 
13
13
  ## Mocking
14
14
 
15
- - Use `@mars-stack/core/test-utils/mock-auth` to mock authenticated sessions in API route tests.
16
- - Use `@mars-stack/core/test-utils/factories` for consistent test data.
15
+ - Use `@mars-stack/core/test-utils` to mock authenticated sessions in API route tests.
16
+ - Use `@mars-stack/core/test-utils` for consistent test data.
17
17
  - Mock Prisma with `vi.mock('@/lib/prisma')` and provide typed mocks for each model method.
18
18
 
19
19
  ## Patterns
@@ -17,13 +17,13 @@ MARS uses a three-layer CSS token system. Never use raw Tailwind colour classes
17
17
 
18
18
  ## Component Architecture
19
19
 
20
- - **Primitives** (`@mars-stack/ui`): Single-responsibility, fully styled via tokens, accept `className` for overrides. Examples: `Button`, `Input`, `Badge`, `Avatar`, `Spinner`.
21
- - **Patterns** (`@mars-stack/ui`): Compose primitives, no business logic. Examples: `Card`, `Modal`, `Toast`, `FormField`, `EmptyState`.
20
+ - **Primitives** (`@mars-stack/ui` (primitives)): Single-responsibility, fully styled via tokens, accept `className` for overrides. Examples: `Button`, `Input`, `Badge`, `Avatar`, `Spinner`.
21
+ - **Patterns** (`@mars-stack/ui` (patterns)): Compose primitives, no business logic. Examples: `Card`, `Modal`, `Toast`, `FormField`, `EmptyState`.
22
22
  - **Feature components** (`src/features/<name>/components/`): Contain business logic, compose primitives and patterns.
23
23
 
24
24
  ## Rules
25
25
 
26
- - Always import components from `@mars-stack/ui` (both primitives and patterns are exported from this package).
26
+ - Always import components from barrel exports: `@mars-stack/ui` or `@mars-stack/ui`.
27
27
  - Use `clsx` for conditional class composition.
28
- - New shared primitives go in the `@mars-stack/ui` package.
28
+ - New primitives go in `@mars-stack/ui` (primitives) with a barrel re-export.
29
29
  - Components MUST support dark mode via the token system (tokens swap automatically with `.dark` class).
@@ -44,7 +44,9 @@ export const GET = withAuthNoParams(async (request: AuthenticatedRequest) => {
44
44
 
45
45
  ```typescript
46
46
  import { handleApiError } from '@/lib/mars';
47
- import { checkRateLimit, getClientIP, RATE_LIMITS, rateLimitResponse } from '@mars-stack/core/rate-limit';
47
+ import {
48
+ checkRateLimit, getClientIP, RATE_LIMITS, rateLimitResponse,
49
+ } from '@mars-stack/core/rate-limit';
48
50
  import { NextResponse } from 'next/server';
49
51
 
50
52
  export async function POST(request: Request) {
@@ -144,7 +144,7 @@ When adding overlay buttons inside an input (e.g. password visibility toggle):
144
144
 
145
145
  ## After Creating
146
146
 
147
- 1. Add the export to the appropriate barrel in the `@mars-stack/ui` package.
147
+ 1. Add the export to the `@mars-stack/ui` package barrel file.
148
148
  2. Export both the component and its props type.
149
149
 
150
150
  ## Checklist
@@ -143,9 +143,9 @@ If the feature should be toggleable, add it to `appConfig.features` in `src/conf
143
143
  Create `route.test.ts` next to each API route. Mock Prisma and auth:
144
144
 
145
145
  ```typescript
146
+ import { mockAuth } from '@mars-stack/core/test-utils';
146
147
  import { describe, it, expect, vi, beforeEach } from 'vitest';
147
148
  import { GET, POST } from './route';
148
- import { mockAuth } from '@mars-stack/core/test-utils';
149
149
 
150
150
  vi.mock('@/lib/prisma', () => ({
151
151
  prisma: {
@@ -19,8 +19,7 @@ The middleware at `src/middleware.ts` handles auth redirects automatically based
19
19
  ## Protected Page Template
20
20
 
21
21
  ```tsx
22
- import { Card, CardHeader, CardBody } from '@mars-stack/ui';
23
- import { H1, Paragraph } from '@mars-stack/ui';
22
+ import { Card, CardHeader, CardBody, H1, Paragraph } from '@mars-stack/ui';
24
23
 
25
24
  export default function WidgetsPage() {
26
25
  return (
@@ -69,9 +68,8 @@ For pages that need client-side data, create a client component in the feature m
69
68
  // src/features/widgets/components/WidgetList.tsx
70
69
  'use client';
71
70
 
71
+ import { Card, CardBody, Spinner, Badge, EmptyState } from '@mars-stack/ui';
72
72
  import { useEffect, useState } from 'react';
73
- import { Card, CardBody, EmptyState } from '@mars-stack/ui';
74
- import { Spinner, Badge } from '@mars-stack/ui';
75
73
  import type { Widget } from '@/features/widgets/types';
76
74
 
77
75
  export function WidgetList() {
@@ -117,9 +117,9 @@ export async function createItem(formData: FormData) {
117
117
  ```tsx
118
118
  'use client';
119
119
 
120
+ import { Button, Input } from '@mars-stack/ui';
120
121
  import { useActionState } from 'react';
121
122
  import { updateProfile } from '@/features/settings/server/actions';
122
- import { Button, Input } from '@mars-stack/ui';
123
123
 
124
124
  export function ProfileForm({ currentName }: { currentName: string }) {
125
125
  const [state, formAction, isPending] = useActionState(updateProfile, {
@@ -172,7 +172,7 @@ async function processEvent(eventId: string, handler: () => Promise<void>) {
172
172
 
173
173
  ## Environment Variables
174
174
 
175
- Add webhook secrets to your env validation schema:
175
+ Add webhook secrets to `src/core/env/index.ts` in `buildEnvSchema()`:
176
176
 
177
177
  ```typescript
178
178
  base.STRIPE_WEBHOOK_SECRET = z.string().min(1).optional();
@@ -171,10 +171,9 @@ Before writing any code, create an execution plan:
171
171
  **Skill:** `update-architecture-docs`
172
172
 
173
173
  1. Update `docs/QUALITY_SCORE.md` with the new feature grade
174
- 2. Update `ARCHITECTURE.md` if the feature introduces new patterns
175
- 3. Update `template/AGENTS.md` if the template structure changed
176
- 4. Mark the execution plan as complete
177
- 5. Move the plan from `active/` to `completed/`
174
+ 2. Update `AGENTS.md` if the feature introduces new patterns
175
+ 3. Mark the execution plan as complete
176
+ 4. Move the plan from `active/` to `completed/`
178
177
 
179
178
  ## Feature Flag Integration
180
179
 
@@ -15,11 +15,10 @@ MARS provides `Table` primitives (`Table`, `TableHead`, `TableBody`, `TableRow`,
15
15
  ```tsx
16
16
  'use client';
17
17
 
18
- import { useEffect, useState, useCallback } from 'react';
19
18
  import {
20
- Table, TableHead, TableBody, TableRow, TableHeaderCell, TableCell,
21
- Badge, Button, Spinner, Card, CardHeader, CardBody, EmptyState,
19
+ Table, TableHead, TableBody, TableRow, TableHeaderCell, TableCell, Badge, Button, Spinner, Card, CardHeader, CardBody, EmptyState,
22
20
  } from '@mars-stack/ui';
21
+ import { useEffect, useState, useCallback } from 'react';
23
22
 
24
23
  interface Item {
25
24
  id: string;
@@ -19,8 +19,10 @@ MARS forms use:
19
19
  ```tsx
20
20
  'use client';
21
21
 
22
+ import {
23
+ Button, Input, Select, Textarea, Checkbox, Card, CardHeader, CardBody, CardFooter, H2,
24
+ } from '@mars-stack/ui';
22
25
  import { useState, type FormEvent } from 'react';
23
- import { Button, Input, Select, Textarea, Checkbox, Card, CardHeader, CardBody, CardFooter, H2 } from '@mars-stack/ui';
24
26
  import { z } from 'zod';
25
27
 
26
28
  const formSchema = z.object({
@@ -8,7 +8,7 @@ Use this skill when the user asks to configure email, set up SendGrid, add Resen
8
8
 
9
9
  ## Architecture
10
10
 
11
- MARS uses a pluggable email service via `@/lib/mars` (which re-exports from `@mars-stack/core`). The active provider is set in `appConfig.services.email.provider`.
11
+ MARS uses a pluggable email service re-exported via `@/lib/mars`. The active provider is set in `appConfig.services.email.provider`.
12
12
 
13
13
  Available providers:
14
14
  - `console` -- logs emails to terminal (default, no setup needed)
@@ -42,7 +42,7 @@ RESEND_FROM_EMAIL="noreply@yourdomain.com"
42
42
 
43
43
  ### Step 3: Update Env Validation
44
44
 
45
- The env validation schema already conditionally validates email vars:
45
+ In `src/core/env/index.ts`, the schema already conditionally validates email vars:
46
46
 
47
47
  ```typescript
48
48
  if (appConfig.services.email.provider !== 'console') {
@@ -61,7 +61,7 @@ yarn add resend
61
61
 
62
62
  ### Step 2: Add the Provider
63
63
 
64
- In the email service module (via `@mars-stack/core`):
64
+ In the email service module (wired through `@/lib/mars`):
65
65
 
66
66
  ```typescript
67
67
  async function sendWithResend(params: SendEmailParams): Promise<void> {
@@ -31,7 +31,7 @@ GOOGLE_CLIENT_ID="your-client-id.apps.googleusercontent.com"
31
31
  GOOGLE_CLIENT_SECRET="your-client-secret"
32
32
  ```
33
33
 
34
- Update the env validation schema to validate these:
34
+ Update `src/core/env/index.ts` to validate these:
35
35
 
36
36
  ```typescript
37
37
  if (appConfig.features.googleOAuth) {
@@ -81,8 +81,8 @@ model Subscription {
81
81
  ```typescript
82
82
  // src/app/api/protected/billing/checkout/route.ts
83
83
  import { handleApiError, withAuthNoParams, type AuthenticatedRequest } from '@/lib/mars';
84
- import { getStripe } from '@/features/billing/server';
85
84
  import { prisma } from '@/lib/prisma';
85
+ import { getStripe } from '@/features/billing/server';
86
86
  import { NextResponse } from 'next/server';
87
87
  import { z } from 'zod';
88
88
 
@@ -133,8 +133,8 @@ export const POST = withAuthNoParams(async (request: AuthenticatedRequest) => {
133
133
  ```typescript
134
134
  // src/app/api/protected/billing/portal/route.ts
135
135
  import { handleApiError, withAuthNoParams, type AuthenticatedRequest } from '@/lib/mars';
136
- import { getStripe } from '@/features/billing/server';
137
136
  import { prisma } from '@/lib/prisma';
137
+ import { getStripe } from '@/features/billing/server';
138
138
  import { NextResponse } from 'next/server';
139
139
 
140
140
  export const POST = withAuthNoParams(async (request: AuthenticatedRequest) => {
@@ -181,8 +181,8 @@ export const POST = withAuthNoParams(async (request: AuthenticatedRequest) => {
181
181
  ```tsx
182
182
  'use client';
183
183
 
184
- import { useState, useRef } from 'react';
185
184
  import { Button, Spinner } from '@mars-stack/ui';
185
+ import { useState, useRef } from 'react';
186
186
 
187
187
  interface FileUploadProps {
188
188
  onUpload: (result: { url: string; pathname: string }) => void;
@@ -265,7 +265,7 @@ export function FileUpload({ onUpload, accept = 'image/*', maxSize = 10 }: FileU
265
265
 
266
266
  - [ ] Storage SDK installed
267
267
  - [ ] Environment variables set
268
- - [ ] Storage service created (note: storage will be available via `@mars-stack/core` in a future release)
268
+ - [ ] Storage service created (`src/core/storage/index.ts`)
269
269
  - [ ] Upload API route with size and type validation
270
270
  - [ ] Upload paths scoped by userId
271
271
  - [ ] Client upload component
@@ -627,16 +627,11 @@ If certain routes should only be accessible to team members, add team verificati
627
627
  | Teams | B | Team CRUD, invitations, role hierarchy, org switching |
628
628
  ```
629
629
 
630
- 2. Update `ARCHITECTURE.md`:
631
- - Add teams to the data model section
632
- - Document the multi-tenancy pattern
633
- - Add the role hierarchy diagram
634
-
635
- 3. Update `template/AGENTS.md`:
630
+ 2. Update `AGENTS.md`:
636
631
  - Add teams feature to directory listing
637
632
  - Document team-scoped query pattern
638
633
 
639
- 4. Mark execution plan as complete
634
+ 3. Mark execution plan as complete
640
635
 
641
636
  ## Data Scoping Decision
642
637
 
@@ -683,6 +678,5 @@ model Project {
683
678
  - [ ] Invitation emails (if email configured)
684
679
  - [ ] Unit tests for all API routes
685
680
  - [ ] E2E test for invitation flow
686
- - [ ] ARCHITECTURE.md updated with tenancy pattern
687
681
  - [ ] QUALITY_SCORE.md updated
688
682
  - [ ] Execution plan completed
@@ -18,9 +18,9 @@ MARS API route tests:
18
18
 
19
19
  ```typescript
20
20
  // src/app/api/protected/projects/route.test.ts
21
+ import { mockAuth, createTestRequest, createTestRequestWithBody } from '@mars-stack/core/test-utils';
21
22
  import { describe, it, expect, vi, beforeEach } from 'vitest';
22
23
  import { GET, POST } from './route';
23
- import { mockAuth, createTestRequest, createTestRequestWithBody } from '@mars-stack/core/test-utils';
24
24
 
25
25
  // Mock database
26
26
  const mockFindMany = vi.fn();
@@ -1,20 +1,18 @@
1
1
  # Skill: Update Architecture Docs
2
2
 
3
- Keep the Mars project documentation in sync with code changes. Covers when and how to update ARCHITECTURE.md, AGENTS.md, QUALITY_SCORE.md, and generated docs.
3
+ Keep the project documentation in sync with code changes. Covers when and how to update AGENTS.md, QUALITY_SCORE.md, and related docs.
4
4
 
5
5
  ## When to Use
6
6
 
7
7
  Use this skill when:
8
- - You have just added a new feature, service, or package
8
+ - You have just added a new feature, service, or module
9
9
  - You have changed the database schema
10
- - You have added or removed a skill or rule
11
10
  - You have completed an execution plan
12
11
  - The user asks to update docs, sync docs, or refresh documentation
13
12
  - Another skill's checklist includes "update docs"
14
13
 
15
14
  ## Prerequisites
16
15
 
17
- - Read `ARCHITECTURE.md` to understand the current documented state.
18
16
  - Read `AGENTS.md` for the project-level agent guide.
19
17
  - Read `docs/QUALITY_SCORE.md` for current quality grades.
20
18
 
@@ -23,49 +21,21 @@ Use this skill when:
23
21
  | Document | Location | Purpose | Update frequency |
24
22
  |----------|----------|---------|------------------|
25
23
  | `AGENTS.md` | Repo root | First file any agent reads. Overview + pointers | On structure changes |
26
- | `ARCHITECTURE.md` | Repo root | Detailed technical architecture | On arch changes |
27
24
  | `docs/QUALITY_SCORE.md` | Docs | Quality grades by domain | After every improvement |
28
- | `docs/generated/package-map.md` | Docs | Auto-generated package structure | Regenerate on package changes |
29
- | `docs/generated/skill-inventory.md` | Docs | Auto-generated skill list | Regenerate on skill changes |
30
- | `template/AGENTS.md` | Template | Consumer-facing project guide | On template structure changes |
31
- | `packages/core/cursor/manifest.json` | Core | Skill index with triggers | On skill add/remove |
25
+ | `docs/design-docs/` | Docs | Architecture decisions and design documents | On architectural changes |
32
26
 
33
27
  ## When to Update Each Document
34
28
 
35
- ### ARCHITECTURE.md
29
+ ### AGENTS.md
36
30
 
37
31
  Update when:
38
- - A new package is added to the monorepo
39
- - The build pipeline changes (new build step, tool change)
40
- - A new export path is added to a package
41
- - The security model changes (new auth pattern, new middleware)
42
- - The skill/rule system changes
43
-
44
- What to update:
45
- 1. Package layering diagram (if dependency graph changed)
46
- 2. Package export table (if new exports added)
47
- 3. Build pipeline section (if build process changed)
48
- 4. Security architecture section (if auth patterns changed)
49
- 5. Skills and rules section (if skill system changed)
50
-
51
- ### AGENTS.md (Root)
52
-
53
- Update when:
54
- - A new top-level directory is added
55
- - A new CLI command is added
56
- - The "How to Run" commands change
57
- - A new "How to" section is needed (e.g., "How to Add a Plugin")
58
-
59
- Keep it concise — AGENTS.md should fit in a single context window.
60
-
61
- ### template/AGENTS.md
62
-
63
- Update when:
64
- - The template directory structure changes
65
- - New route groups are added
66
- - New feature modules are added to the template
32
+ - The directory structure changes (new route groups, new features)
33
+ - New feature modules are added
67
34
  - The config structure changes
68
35
  - New "How to Run" commands are added
36
+ - Key constraints change
37
+
38
+ Keep it concise — AGENTS.md should fit in a single context window.
69
39
 
70
40
  ### docs/QUALITY_SCORE.md
71
41
 
@@ -78,31 +48,11 @@ How to update:
78
48
  1. Find the relevant row in the appropriate table
79
49
  2. Update the grade (A/B/C/D/F)
80
50
  3. Update the notes to reflect current state
81
- 4. Add date reference if significant change
82
51
 
83
52
  ```markdown
84
53
  | Dashboard | B | Basic layout with stat cards. Missing: chart widgets, date range filter |
85
54
  ```
86
55
 
87
- ### Generated Docs
88
-
89
- Regenerate `docs/generated/package-map.md` when:
90
- - A package's exports change
91
- - A new package is added
92
- - A package is removed
93
-
94
- Regenerate `docs/generated/skill-inventory.md` when:
95
- - A skill is added, removed, or renamed
96
- - Skill triggers or dependencies change
97
-
98
- ### manifest.json
99
-
100
- Update when:
101
- - A new skill is added (add entry with triggers, dependencies, capabilities)
102
- - A skill is removed (remove entry)
103
- - Skill triggers change (update triggers array)
104
- - Skill dependencies change (update dependencies array)
105
-
106
56
  ## Step-by-Step: After Adding a Feature
107
57
 
108
58
  1. **QUALITY_SCORE.md** — Update the grade for the relevant domain:
@@ -111,61 +61,24 @@ Update when:
111
61
  | Billing | B | Stripe checkout, portal, webhook. Missing: usage-based billing |
112
62
  ```
113
63
 
114
- 2. **ARCHITECTURE.md** — If the feature introduces a new architectural pattern:
115
-
116
- ```markdown
117
- ### Billing Architecture
118
-
119
- The billing system uses Stripe as the payment provider...
120
- ```
121
-
122
- 3. **template/AGENTS.md** — If the feature adds new directories or conventions:
64
+ 2. **AGENTS.md** — If the feature adds new directories or conventions:
123
65
 
124
66
  ```markdown
125
67
  ├── features/
126
68
  │ └── billing/ # Subscription management
127
69
  ```
128
70
 
129
- 4. **Execution plan** — Mark tasks complete, update verification:
71
+ 3. **Execution plan** — Mark tasks complete, update verification:
130
72
 
131
73
  ```markdown
132
74
  - [x] **3.1 Billing page** — Done. `src/app/(protected)/billing/page.tsx`
133
75
  ```
134
76
 
135
- ## Step-by-Step: After Adding a Skill
136
-
137
- 1. **manifest.json** — Add the skill entry:
138
-
139
- ```json
140
- "new-skill-name": {
141
- "file": "skills/mars-new-skill-name/SKILL.md",
142
- "triggers": ["trigger phrase 1", "trigger phrase 2"],
143
- "dependencies": ["dependency-skill"],
144
- "capabilities": ["file-edit", "terminal"]
145
- }
146
- ```
147
-
148
- 2. **skill-inventory.md** — Regenerate or manually add:
149
-
150
- ```markdown
151
- | new-skill-name | Add a new thing | file-edit, terminal |
152
- ```
153
-
154
- 3. **AGENTS.md** — If the skill represents a new "How to" workflow:
155
-
156
- ```markdown
157
- ## How to Add a New Thing
158
-
159
- 1. Create the skill file
160
- 2. Add to manifest
161
- 3. ...
162
- ```
163
-
164
77
  ## Step-by-Step: After a Schema Change
165
78
 
166
- 1. **ARCHITECTURE.md** — Update if the change affects the data model section
167
- 2. **template/AGENTS.md** — Update the schema file listing if new files added
168
- 3. **QUALITY_SCORE.md** — Update database-related grades
79
+ 1. **AGENTS.md** — Update the schema file listing if new schema files added
80
+ 2. **QUALITY_SCORE.md** — Update database-related grades
81
+ 3. **Design docs** — Add a design doc if the schema change represents a significant architectural decision
169
82
 
170
83
  ## Validation
171
84
 
@@ -179,11 +92,8 @@ After updating docs, verify:
179
92
  ## Checklist
180
93
 
181
94
  - [ ] Identified which documents need updating
182
- - [ ] ARCHITECTURE.md updated (if architectural change)
183
95
  - [ ] AGENTS.md updated (if structure or workflow change)
184
- - [ ] template/AGENTS.md updated (if template structure change)
185
96
  - [ ] QUALITY_SCORE.md grades updated
186
- - [ ] manifest.json updated (if skill change)
187
- - [ ] Generated docs regenerated (if package or skill change)
97
+ - [ ] Design docs updated (if architectural decision)
188
98
  - [ ] No broken internal links
189
99
  - [ ] Execution plan marked as complete (if applicable)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mars-stack/core",
3
- "version": "2.0.1",
3
+ "version": "3.0.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -45,6 +45,20 @@ function detectIdeEnvironment() {
45
45
  const ide = detectIdeEnvironment();
46
46
 
47
47
  // Cursor: copy rules + skills into .cursor/ (primary target)
48
+ const cursorDir = path.join(projectRoot, '.cursor');
49
+ const managedListPath = path.join(cursorDir, '.mars-managed');
50
+
51
+ let previouslyManaged = [];
52
+ if (fs.existsSync(managedListPath)) {
53
+ try {
54
+ previouslyManaged = JSON.parse(fs.readFileSync(managedListPath, 'utf-8'));
55
+ } catch {
56
+ previouslyManaged = [];
57
+ }
58
+ }
59
+
60
+ const currentlyManaged = [];
61
+
48
62
  const copies = [
49
63
  { src: 'rules', dest: '.cursor/rules' },
50
64
  { src: 'skills', dest: '.cursor/skills' },
@@ -59,21 +73,48 @@ for (const { src, dest } of copies) {
59
73
 
60
74
  const entries = fs.readdirSync(srcDir, { withFileTypes: true });
61
75
  for (const entry of entries) {
62
- if (entry.isFile() && entry.name.startsWith('mars-')) {
63
- fs.copyFileSync(
64
- path.join(srcDir, entry.name),
65
- path.join(destDir, entry.name),
66
- );
67
- } else if (entry.isDirectory() && entry.name.startsWith('mars-')) {
68
- fs.cpSync(
69
- path.join(srcDir, entry.name),
70
- path.join(destDir, entry.name),
71
- { recursive: true },
72
- );
76
+ if (!entry.name.startsWith('mars-')) continue;
77
+
78
+ const destPath = path.join(destDir, entry.name);
79
+ const managedKey = `${dest}/${entry.name}`;
80
+ currentlyManaged.push(managedKey);
81
+
82
+ if (entry.isFile()) {
83
+ fs.copyFileSync(path.join(srcDir, entry.name), destPath);
84
+ } else if (entry.isDirectory()) {
85
+ fs.cpSync(path.join(srcDir, entry.name), destPath, { recursive: true });
86
+ }
87
+
88
+ // Remove unprefixed duplicate from old naming scheme (e.g. security.mdc → removed)
89
+ const unprefixedName = entry.name.replace(/^mars-/, '');
90
+ const unprefixedPath = path.join(destDir, unprefixedName);
91
+ if (fs.existsSync(unprefixedPath)) {
92
+ fs.rmSync(unprefixedPath, { recursive: true, force: true });
73
93
  }
74
94
  }
75
95
  }
76
96
 
97
+ // Remove stale mars-* entries from previous versions that are no longer shipped
98
+ const currentSet = new Set(currentlyManaged);
99
+ for (const oldEntry of previouslyManaged) {
100
+ if (!currentSet.has(oldEntry)) {
101
+ const stalePath = path.join(projectRoot, oldEntry);
102
+ if (fs.existsSync(stalePath)) {
103
+ fs.rmSync(stalePath, { recursive: true, force: true });
104
+ }
105
+ }
106
+ }
107
+
108
+ // Write the managed list for future cleanup
109
+ fs.mkdirSync(cursorDir, { recursive: true });
110
+ fs.writeFileSync(managedListPath, JSON.stringify(currentlyManaged, null, 2));
111
+
112
+ // Copy manifest.json so it's available in scaffolded projects
113
+ const manifestSrc = path.join(packageDir, '..', 'cursor', 'manifest.json');
114
+ if (fs.existsSync(manifestSrc)) {
115
+ fs.copyFileSync(manifestSrc, path.join(cursorDir, 'manifest.json'));
116
+ }
117
+
77
118
  // Generate IDE-specific adapter files for non-Cursor environments
78
119
  if (ide !== 'cursor') {
79
120
  const manifestPath = path.join(packageDir, '..', 'cursor', 'manifest.json');