@gherk/reactive-architecture 1.3.0 → 1.3.2

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/dist/index.js CHANGED
@@ -22,6 +22,8 @@ import { registerFolderStructureTool } from './tools/folder-structure.js';
22
22
  import { registerBackendGuideTool } from './tools/backend-guide.js';
23
23
  import { registerBackendFolderStructureTool } from './tools/backend-folder-structure.js';
24
24
  import { registerAuditProjectTool } from './tools/audit-project.js';
25
+ import { registerScanElementsTool } from './tools/scan-elements.js';
26
+ import { registerCreateElementTool } from './tools/create-element.js';
25
27
  // Resources
26
28
  import { registerPhilosophyResource } from './resources/philosophy-resource.js';
27
29
  // Prompts
@@ -42,6 +44,8 @@ registerFolderStructureTool(server);
42
44
  registerBackendGuideTool(server);
43
45
  registerBackendFolderStructureTool(server);
44
46
  registerAuditProjectTool(server);
47
+ registerScanElementsTool(server);
48
+ registerCreateElementTool(server);
45
49
  // Register resources
46
50
  registerPhilosophyResource(server);
47
51
  // Register prompts
@@ -50,7 +54,7 @@ registerPlanFeaturePrompt(server);
50
54
  async function main() {
51
55
  const transport = new StdioServerTransport();
52
56
  await server.connect(transport);
53
- console.error('🚀 Reactive Frontend MCP Server running on stdio');
57
+ // console.error('🚀 Reactive Frontend MCP Server running on stdio');
54
58
  }
55
59
  main().catch((error) => {
56
60
  console.error('Fatal error:', error);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,gFAAgF;AAChF,mCAAmC;AACnC,gFAAgF;AAChF,qEAAqE;AACrE,qDAAqD;AACrD,mDAAmD;AACnD,EAAE;AACF,wEAAwE;AACxE,kCAAkC;AAClC,gFAAgF;AAEhF,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,QAAQ;AACR,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,kCAAkC,EAAE,MAAM,qCAAqC,CAAC;AACzF,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,YAAY;AACZ,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAEhF,UAAU;AACV,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,wBAAwB;AACxB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IACzB,IAAI,EAAE,uBAAuB;IAC7B,OAAO,EAAE,OAAO;CACnB,CAAC,CAAC;AAEH,qBAAqB;AACrB,6BAA6B,CAAC,MAAM,CAAC,CAAC;AACtC,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,0BAA0B,CAAC,MAAM,CAAC,CAAC;AACnC,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC/B,2BAA2B,CAAC,MAAM,CAAC,CAAC;AACpC,wBAAwB,CAAC,MAAM,CAAC,CAAC;AACjC,kCAAkC,CAAC,MAAM,CAAC,CAAC;AAC3C,wBAAwB,CAAC,MAAM,CAAC,CAAC;AAEjC,qBAAqB;AACrB,0BAA0B,CAAC,MAAM,CAAC,CAAC;AAEnC,mBAAmB;AACnB,yBAAyB,CAAC,MAAM,CAAC,CAAC;AAElC,8BAA8B;AAC9B,KAAK,UAAU,IAAI;IACf,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,kDAAkD,CAAC,CAAC;AACtE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,gFAAgF;AAChF,mCAAmC;AACnC,gFAAgF;AAChF,qEAAqE;AACrE,qDAAqD;AACrD,mDAAmD;AACnD,EAAE;AACF,wEAAwE;AACxE,kCAAkC;AAClC,gFAAgF;AAEhF,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,QAAQ;AACR,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,0BAA0B,EAAE,MAAM,6BAA6B,CAAC;AACzE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,2BAA2B,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,kCAAkC,EAAE,MAAM,qCAAqC,CAAC;AACzF,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,YAAY;AACZ,OAAO,EAAE,0BAA0B,EAAE,MAAM,oCAAoC,CAAC;AAEhF,UAAU;AACV,OAAO,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAC;AAEtE,wBAAwB;AACxB,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IACzB,IAAI,EAAE,uBAAuB;IAC7B,OAAO,EAAE,OAAO;CACnB,CAAC,CAAC;AAEH,qBAAqB;AACrB,6BAA6B,CAAC,MAAM,CAAC,CAAC;AACtC,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,oBAAoB,CAAC,MAAM,CAAC,CAAC;AAC7B,0BAA0B,CAAC,MAAM,CAAC,CAAC;AACnC,sBAAsB,CAAC,MAAM,CAAC,CAAC;AAC/B,2BAA2B,CAAC,MAAM,CAAC,CAAC;AACpC,wBAAwB,CAAC,MAAM,CAAC,CAAC;AACjC,kCAAkC,CAAC,MAAM,CAAC,CAAC;AAC3C,wBAAwB,CAAC,MAAM,CAAC,CAAC;AACjC,wBAAwB,CAAC,MAAM,CAAC,CAAC;AACjC,yBAAyB,CAAC,MAAM,CAAC,CAAC;AAElC,qBAAqB;AACrB,0BAA0B,CAAC,MAAM,CAAC,CAAC;AAEnC,mBAAmB;AACnB,yBAAyB,CAAC,MAAM,CAAC,CAAC;AAElC,8BAA8B;AAC9B,KAAK,UAAU,IAAI;IACf,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,qEAAqE;AACzE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC"}
@@ -1,8 +1,8 @@
1
1
  export declare const PHILOSOPHY_OVERVIEW = "\n# Reactive Frontend Architecture Philosophy\n\n## Core Principle\nEvery frontend feature MUST be structured following **three clearly separated layers**:\n\n```\nComponent \u2192 Store \u2192 Service\n (UI) (State) (Data Access)\n```\n\nThis is a **strict, non-negotiable pattern**. It applies to EVERY feature, in EVERY framework (Angular, React, Vue, Svelte, or any other). The technology changes, the philosophy does NOT.\n\n## Why This Pattern?\n\n1. **Predictability**: Data always flows in one direction. You always know where state lives and how it changes.\n2. **Testability**: Each layer can be tested in isolation. Components test rendering, stores test logic, services test data access.\n3. **Reusability**: Services can be shared across stores. Stores can be shared across components. Components are self-contained.\n4. **Scalability**: Adding features doesn't increase complexity because each feature follows the same pattern.\n5. **AI-Friendly**: When AI assistants follow this pattern consistently, the codebase remains coherent regardless of who (or what) writes the code.\n";
2
- export declare const COMPONENT_LAYER = "\n# Component Layer (UI / Presentation)\n\n## Responsibility\nThe component is **ONLY** responsible for:\n- Rendering UI based on state from the store\n- Capturing user interactions (clicks, input, navigation)\n- Dispatching actions/calls to the store\n- Presentational logic ONLY (formatting dates, toggling CSS classes)\n\n## Rules\n\n### MUST\n- Read all data from the store (via selectors, signals, observables, or reactive primitives)\n- Delegate ALL side effects to the store (loading data, submitting forms, etc.)\n- Be as \"dumb\" as possible \u2014 a template with holes filled by the store\n- Handle only UI concerns: show/hide, enable/disable, format display values\n\n### MUST NOT\n- Call HTTP/fetch/axios directly \u2014 NEVER\n- Contain business logic (validation rules, calculations, transformations)\n- Hold application state in local variables (only ephemeral UI state like \"is dropdown open\")\n- Know about the data access layer (services) \u2014 it only knows the store\n- Import or inject services directly\n\n## Ephemeral UI State (The Exception)\nComponents CAN hold purely ephemeral UI state that has no business meaning:\n- `isDropdownOpen`\n- `currentTabIndex`\n- `isModalVisible`\n\nThis state dies with the component and has no impact on application logic.\n\n## Mental Model\n> Think of the component as a **TV screen**: it displays what the store tells it, and it has a remote control (user interactions) that sends commands to the store. The TV never fetches channels itself.\n";
2
+ export declare const COMPONENT_LAYER = "\n# Component Layer (UI / Presentation)\n\n## Responsibility\nThe component is **ONLY** responsible for:\n- Rendering UI based on state from the store\n- Capturing user interactions (clicks, input, navigation)\n- Dispatching actions/calls to the store\n- Presentational logic ONLY (formatting dates, toggling CSS classes)\n\n## Rules\n\n### MUST\n- Read all data from the store (via selectors, signals, observables, or reactive primitives)\n- Delegate ALL side effects to the store (loading data, submitting forms, etc.)\n- Be as \"dumb\" as possible \u2014 a template with holes filled by the store\n- Handle only UI concerns: show/hide, enable/disable, format display values\n\n### MUST NOT\n- Call HTTP/fetch/axios directly \u2014 NEVER\n- Contain business logic (validation rules, calculations, transformations)\n- Hold application state in local variables (only ephemeral UI state like \"is dropdown open\")\n- **Know about the data access layer (services)** \u2014 it only knows the store\n- **Import or inject services directly** \u2014 CRITICAL VIOLATION\n- Call **service.method()** \u2014 ALWAYS go through **store.method()**\n\n## Ephemeral UI State (The Exception)\nComponents CAN hold purely ephemeral UI state that has no business meaning:\n- `isDropdownOpen`\n- `currentTabIndex`\n- `isModalVisible`\n\nThis state dies with the component and has no impact on application logic.\n\n## Mental Model\n> Think of the component as a **TV screen**: it displays what the store tells it, and it has a remote control (user interactions) that sends commands to the store. The TV never fetches channels itself.\n";
3
3
  export declare const STORE_LAYER = "\n# Store Layer (State Management / Business Logic)\n\n## Responsibility\nThe store is the **single source of truth** for a feature's state. It:\n- Holds the feature state (data, loading, error, filters, pagination, etc.)\n- Exposes reactive selectors/computed values for components to consume\n- Contains business logic (when to load, how to transform, validation orchestration)\n- Orchestrates service calls (calls the service, handles the response, updates state)\n\n## Rules\n\n### MUST\n- Be the ONLY place where feature state lives\n- Expose state as reactive primitives (signals, observables, reactive refs \u2014 depending on framework)\n- Handle loading/error/success states explicitly\n- Call services for any data access needs\n- Be the orchestrator: component asks \u2192 store decides \u2192 service fetches \u2192 store updates \u2192 component reacts\n\n### MUST NOT\n- Make HTTP calls directly \u2014 delegate to services\n- Know about the DOM or UI rendering\n- Depend on component lifecycle (it should be independent)\n- Import component code\n\n## State Shape Convention\nEvery feature store should manage at minimum:\n```typescript\n{\n data: T | T[]; // The actual feature data\n loading: boolean; // Is data being fetched/submitted?\n error: string | null; // Error message if something failed\n loaded: boolean; // Has data been loaded at least once?\n}\n```\n\nAdditional state as needed:\n- `filters`, `sorting`, `pagination` for list features\n- `selectedItem` for detail views\n- `formData`, `formErrors` for form features\n\n## Mental Model\n> Think of the store as the **brain**: it knows the current state of everything, decides what to do when the component asks for something, and tells the service to go fetch or send data. It then updates its knowledge based on what comes back.\n";
4
4
  export declare const SERVICE_LAYER = "\n# Service Layer (Data Access)\n\n## Responsibility\nThe service is a **pure data access layer**. It:\n- Makes HTTP requests (GET, POST, PUT, DELETE)\n- Handles WebSocket connections\n- Accesses localStorage/sessionStorage\n- Interacts with any external data source\n\n## Rules\n\n### MUST\n- Be stateless \u2014 no internal state, no caching (that's the store's job)\n- Return data in a clean format (observables, promises, or the framework's preferred async primitive)\n- Handle request configuration (headers, base URL, interceptors)\n- Map API responses to domain models/interfaces if needed\n- Be reusable across different stores\n\n### MUST NOT\n- Hold state \u2014 NEVER\n- Contain business logic (no \"if user is admin, do X\")\n- Know about components or stores (it's a pure utility)\n- Handle loading/error state (that's the store's job)\n- Transform data for display purposes (that's the component's job)\n\n## Service Method Convention\n```typescript\n// GOOD: Clean, simple, one job\ngetUsers(): Observable<User[]> | Promise<User[]>\ngetUserById(id: string): Observable<User> | Promise<User>\ncreateUser(data: CreateUserDto): Observable<User> | Promise<User>\nupdateUser(id: string, data: UpdateUserDto): Observable<User> | Promise<User>\ndeleteUser(id: string): Observable<void> | Promise<void>\n\n// BAD: Doing too much\ngetUsersAndTransformForDisplay() // \u274C Transformation is store/component job\ngetUsersIfNotCached() // \u274C Caching is the store's job\ngetUsersAndSetLoading() // \u274C Loading state is the store's job\n```\n\n## Mental Model\n> Think of the service as a **messenger/courier**: it goes out, gets or delivers the package, and comes back. It doesn't open the package, doesn't decide what to do with it, and doesn't remember what it delivered yesterday.\n";
5
- export declare const ANTI_PATTERNS = "\n# Anti-patterns \u2014 What NOT To Do\n\n## 1. HTTP in Components \u274C\n```typescript\n// \u274C WRONG: Component calling HTTP directly\nclass UserListComponent {\n users = [];\n async ngOnInit() {\n this.users = await fetch('/api/users').then(r => r.json());\n }\n}\n\n// \u2705 CORRECT: Component reads from store\nclass UserListComponent {\n users = this.userStore.users;\n constructor(private userStore: UserStore) {\n this.userStore.loadUsers();\n }\n}\n```\n\n## 2. Business Logic in Services \u274C\n```typescript\n// \u274C WRONG: Service deciding business rules\nclass UserService {\n async getVisibleUsers() {\n const users = await this.http.get('/users');\n return users.filter(u => u.role !== 'admin' && u.isActive);\n }\n}\n\n// \u2705 CORRECT: Service returns raw data, store filters\nclass UserService {\n getUsers() { return this.http.get('/users'); }\n}\nclass UserStore {\n visibleUsers = computed(() =>\n this.users().filter(u => u.role !== 'admin' && u.isActive)\n );\n}\n```\n\n## 3. State in Services \u274C\n```typescript\n// \u274C WRONG: Service holding state\nclass UserService {\n private users: User[] = [];\n private loading = false;\n\n async loadUsers() {\n this.loading = true;\n this.users = await fetch('/api/users');\n this.loading = false;\n }\n}\n\n// \u2705 CORRECT: State lives in the store\nclass UserStore {\n readonly users = signal<User[]>([]);\n readonly loading = signal(false);\n\n async loadUsers() {\n this.loading.set(true);\n const data = await this.userService.getUsers();\n this.users.set(data);\n this.loading.set(false);\n }\n}\n```\n\n## 4. Component Holding Business State \u274C\n```typescript\n// \u274C WRONG: Component managing complex state\nclass UserListComponent {\n users: User[] = [];\n filteredUsers: User[] = [];\n filters = { search: '', role: 'all' };\n loading = false;\n error = null;\n\n applyFilters() { /* complex filtering */ }\n}\n\n// \u2705 CORRECT: Store manages state, component just reads\nclass UserListComponent {\n users = this.store.filteredUsers;\n loading = this.store.loading;\n error = this.store.error;\n}\n```\n\n## 5. Store Accessing DOM \u274C\n```typescript\n// \u274C WRONG: Store manipulating DOM\nclass UserStore {\n showSuccessMessage() {\n document.getElementById('toast').style.display = 'block';\n }\n}\n\n// \u2705 CORRECT: Store updates state, component reacts\nclass UserStore {\n readonly notification = signal<string | null>(null);\n}\n// Component template reacts to notification signal\n```\n\n## 6. Skipping the Store \u274C\n```typescript\n// \u274C WRONG: Component calling service directly\nclass UserComponent {\n constructor(private userService: UserService) {}\n async save() {\n await this.userService.createUser(this.formData);\n }\n}\n\n// \u2705 CORRECT: Component goes through store\nclass UserComponent {\n constructor(private userStore: UserStore) {}\n save() {\n this.userStore.createUser(this.formData);\n }\n}\n```\n";
5
+ export declare const ANTI_PATTERNS = "\n# Anti-patterns \u2014 What NOT To Do\n\n## 1. HTTP in Components \u274C\n```typescript\n// \u274C WRONG: Component calling HTTP directly\nclass UserListComponent {\n users = [];\n async ngOnInit() {\n this.users = await fetch('/api/users').then(r => r.json());\n }\n}\n\n// \u2705 CORRECT: Component reads from store\nclass UserListComponent {\n users = this.userStore.users;\n constructor(private userStore: UserStore) {\n this.userStore.loadUsers();\n }\n}\n```\n\n## 2. Business Logic in Services \u274C\n```typescript\n// \u274C WRONG: Service deciding business rules\nclass UserService {\n async getVisibleUsers() {\n const users = await this.http.get('/users');\n return users.filter(u => u.role !== 'admin' && u.isActive);\n }\n}\n\n// \u2705 CORRECT: Service returns raw data, store filters\nclass UserService {\n getUsers() { return this.http.get('/users'); }\n}\nclass UserStore {\n visibleUsers = computed(() =>\n this.users().filter(u => u.role !== 'admin' && u.isActive)\n );\n}\n```\n\n## 3. State in Services \u274C\n```typescript\n// \u274C WRONG: Service holding state\nclass UserService {\n private users: User[] = [];\n private loading = false;\n\n async loadUsers() {\n this.loading = true;\n this.users = await fetch('/api/users');\n this.loading = false;\n }\n}\n\n// \u2705 CORRECT: State lives in the store\nclass UserStore {\n readonly users = signal<User[]>([]);\n readonly loading = signal(false);\n\n async loadUsers() {\n this.loading.set(true);\n const data = await this.userService.getUsers();\n this.users.set(data);\n this.loading.set(false);\n }\n}\n```\n\n## 4. Component Holding Business State \u274C\n```typescript\n// \u274C WRONG: Component managing complex state\nclass UserListComponent {\n users: User[] = [];\n filteredUsers: User[] = [];\n filters = { search: '', role: 'all' };\n loading = false;\n error = null;\n\n applyFilters() { /* complex filtering */ }\n}\n\n// \u2705 CORRECT: Store manages state, component just reads\nclass UserListComponent {\n users = this.store.filteredUsers;\n loading = this.store.loading;\n error = this.store.error;\n}\n```\n\n## 5. Store Accessing DOM \u274C\n```typescript\n// \u274C WRONG: Store manipulating DOM\nclass UserStore {\n showSuccessMessage() {\n document.getElementById('toast').style.display = 'block';\n }\n}\n\n// \u2705 CORRECT: Store updates state, component reacts\nclass UserStore {\n readonly notification = signal<string | null>(null);\n}\n// Component template reacts to notification signal\n```\n\n## 6. Skipping the Store (CRITICAL VIOLATION) \u274C\n```typescript\n// \u274C WRONG: Component calling service directly.\n// NEVER inject a Service into a Component.\nclass UserComponent {\n constructor(private userService: UserService) {}\n async save() {\n await this.userService.createUser(this.formData);\n }\n}\n\n// \u2705 CORRECT: Component goes through store\nclass UserComponent {\n constructor(private userStore: UserStore) {}\n save() {\n this.userStore.createUser(this.formData);\n }\n}\n```\n";
6
6
  export declare const FILE_STRUCTURE = "\n# File Structure Convention\n\nEvery feature should follow this directory structure:\n\n```\nsrc/\n\u251C\u2500\u2500 features/\n\u2502 \u2514\u2500\u2500 users/ # Feature folder\n\u2502 \u251C\u2500\u2500 components/ # All components for this feature\n\u2502 \u2502 \u251C\u2500\u2500 user-list/\n\u2502 \u2502 \u2502 \u251C\u2500\u2500 user-list.component.ts\n\u2502 \u2502 \u2502 \u251C\u2500\u2500 user-list.component.html # (if framework uses templates)\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 user-list.component.css\n\u2502 \u2502 \u251C\u2500\u2500 user-detail/\n\u2502 \u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u2502 \u2514\u2500\u2500 user-form/\n\u2502 \u2502 \u2514\u2500\u2500 ...\n\u2502 \u251C\u2500\u2500 store/ # Feature store\n\u2502 \u2502 \u251C\u2500\u2500 user.store.ts\n\u2502 \u2502 \u2514\u2500\u2500 user.store.spec.ts\n\u2502 \u251C\u2500\u2500 services/ # Data access services\n\u2502 \u2502 \u251C\u2500\u2500 user.service.ts\n\u2502 \u2502 \u2514\u2500\u2500 user.service.spec.ts\n\u2502 \u251C\u2500\u2500 models/ # TypeScript interfaces & types\n\u2502 \u2502 \u2514\u2500\u2500 user.model.ts\n\u2502 \u2514\u2500\u2500 index.ts # Public API barrel file\n\u251C\u2500\u2500 shared/ # Cross-feature shared code\n\u2502 \u251C\u2500\u2500 components/ # Reusable UI components\n\u2502 \u251C\u2500\u2500 services/ # Shared services (auth, http interceptor)\n\u2502 \u251C\u2500\u2500 stores/ # Shared stores (auth, ui, notifications)\n\u2502 \u2514\u2500\u2500 models/ # Shared interfaces & types\n\u2514\u2500\u2500 core/ # App-wide singletons\n \u251C\u2500\u2500 auth/\n \u251C\u2500\u2500 layout/\n \u2514\u2500\u2500 config/\n```\n\n## Naming Conventions\n- Feature folders: **kebab-case** (e.g., `user-management`)\n- Store files: `<feature>.store.ts`\n- Service files: `<feature>.service.ts`\n- Model files: `<feature>.model.ts`\n- Component files: `<component-name>.component.ts`\n- Test files: `<file>.spec.ts` or `<file>.test.ts`\n\n## Barrel Files (index.ts)\nEvery feature folder MUST have an `index.ts` that exports the public API:\n```typescript\n// features/users/index.ts\nexport { UserListComponent } from './components/user-list/user-list.component';\nexport { UserStore } from './store/user.store';\nexport { UserService } from './services/user.service';\nexport type { User, CreateUserDto } from './models/user.model';\n```\n";
7
7
  export declare const IMPLEMENTATION_FLOW = "\n# Implementation Flow \u2014 How to Build a Feature\n\nWhen implementing a new feature, ALWAYS follow this order:\n\n## Step 1: Define the Model\nCreate the TypeScript interfaces/types that represent the data:\n```typescript\n// models/user.model.ts\nexport interface User {\n id: string;\n name: string;\n email: string;\n role: 'admin' | 'user';\n isActive: boolean;\n}\n\nexport interface CreateUserDto {\n name: string;\n email: string;\n role: 'admin' | 'user';\n}\n```\n\n## Step 2: Build the Service\nCreate the data access layer (HTTP calls):\n```typescript\n// services/user.service.ts\n// Returns promises or observables, nothing more\n```\n\n## Step 3: Build the Store\nCreate the state management layer:\n```typescript\n// store/user.store.ts\n// Holds state, calls service, exposes reactive selectors\n```\n\n## Step 4: Build the Component\nCreate the UI that reads from the store:\n```typescript\n// components/user-list/user-list.component.ts\n// Reads from store, dispatches to store, renders UI\n```\n\n## Step 5: Write Tests\n- Service tests: Mock HTTP, verify requests\n- Store tests: Mock service, verify state transitions\n- Component tests: Mock store, verify rendering & interactions\n\n## Why This Order?\n- **Model first**: defines the contract between all layers\n- **Service second**: it has no dependencies, easiest to build and test\n- **Store third**: depends only on service, contains the logic\n- **Component last**: depends on store, is purely visual\n- **Tests alongside or after**: ensures each layer works correctly\n\nThis is a **bottom-up** approach that ensures you never build a layer without its dependency being ready.\n";
8
8
  export declare function getFullPhilosophy(): string;
@@ -1 +1 @@
1
- {"version":3,"file":"philosophy.d.ts","sourceRoot":"","sources":["../../src/knowledge/philosophy.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,mBAAmB,+kCAoB/B,CAAC;AAEF,eAAO,MAAM,eAAe,0+CAmC3B,CAAC;AAEF,eAAO,MAAM,WAAW,2zDA2CvB,CAAC;AAEF,eAAO,MAAM,aAAa,gyDA2CzB,CAAC;AAEF,eAAO,MAAM,aAAa,y9FA8HzB,CAAC;AAEF,eAAO,MAAM,cAAc,4rFAuD1B,CAAC;AAEF,eAAO,MAAM,mBAAmB,6oDA0D/B,CAAC;AAEF,wBAAgB,iBAAiB,IAAI,MAAM,CAU1C;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYzD;AAED,eAAO,MAAM,kBAAkB,UAQ9B,CAAC"}
1
+ {"version":3,"file":"philosophy.d.ts","sourceRoot":"","sources":["../../src/knowledge/philosophy.ts"],"names":[],"mappings":"AAQA,eAAO,MAAM,mBAAmB,+kCAoB/B,CAAC;AAEF,eAAO,MAAM,eAAe,qlDAoC3B,CAAC;AAEF,eAAO,MAAM,WAAW,2zDA2CvB,CAAC;AAEF,eAAO,MAAM,aAAa,gyDA2CzB,CAAC;AAEF,eAAO,MAAM,aAAa,4hGA+HzB,CAAC;AAEF,eAAO,MAAM,cAAc,4rFAuD1B,CAAC;AAEF,eAAO,MAAM,mBAAmB,6oDA0D/B,CAAC;AAEF,wBAAgB,iBAAiB,IAAI,MAAM,CAU1C;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYzD;AAED,eAAO,MAAM,kBAAkB,UAQ9B,CAAC"}
@@ -48,8 +48,9 @@ The component is **ONLY** responsible for:
48
48
  - Call HTTP/fetch/axios directly — NEVER
49
49
  - Contain business logic (validation rules, calculations, transformations)
50
50
  - Hold application state in local variables (only ephemeral UI state like "is dropdown open")
51
- - Know about the data access layer (services) — it only knows the store
52
- - Import or inject services directly
51
+ - **Know about the data access layer (services)** — it only knows the store
52
+ - **Import or inject services directly** — CRITICAL VIOLATION
53
+ - Call **service.method()** — ALWAYS go through **store.method()**
53
54
 
54
55
  ## Ephemeral UI State (The Exception)
55
56
  Components CAN hold purely ephemeral UI state that has no business meaning:
@@ -258,9 +259,10 @@ class UserStore {
258
259
  // Component template reacts to notification signal
259
260
  \`\`\`
260
261
 
261
- ## 6. Skipping the Store ❌
262
+ ## 6. Skipping the Store (CRITICAL VIOLATION)
262
263
  \`\`\`typescript
263
- // ❌ WRONG: Component calling service directly
264
+ // ❌ WRONG: Component calling service directly.
265
+ // NEVER inject a Service into a Component.
264
266
  class UserComponent {
265
267
  constructor(private userService: UserService) {}
266
268
  async save() {
@@ -1 +1 @@
1
- {"version":3,"file":"philosophy.js","sourceRoot":"","sources":["../../src/knowledge/philosophy.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,mDAAmD;AACnD,gFAAgF;AAChF,yEAAyE;AACzE,yEAAyE;AACzE,sDAAsD;AACtD,gFAAgF;AAEhF,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;CAoBlC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAmC9B,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2C1B,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2C5B,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8H5B,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuD7B,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0DlC,CAAC;AAEF,MAAM,UAAU,iBAAiB;IAC7B,OAAO;QACH,mBAAmB;QACnB,eAAe;QACf,WAAW;QACX,aAAa;QACb,aAAa;QACb,cAAc;QACd,mBAAmB;KACtB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAe;IACtC,MAAM,QAAQ,GAA2B;QACrC,QAAQ,EAAE,mBAAmB;QAC7B,SAAS,EAAE,eAAe;QAC1B,KAAK,EAAE,WAAW;QAClB,OAAO,EAAE,aAAa;QACtB,eAAe,EAAE,aAAa;QAC9B,gBAAgB,EAAE,cAAc;QAChC,qBAAqB,EAAE,mBAAmB;KAC7C,CAAC;IAEF,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;AACrC,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAC9B,UAAU;IACV,WAAW;IACX,OAAO;IACP,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,qBAAqB;CACxB,CAAC"}
1
+ {"version":3,"file":"philosophy.js","sourceRoot":"","sources":["../../src/knowledge/philosophy.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,mDAAmD;AACnD,gFAAgF;AAChF,yEAAyE;AACzE,yEAAyE;AACzE,sDAAsD;AACtD,gFAAgF;AAEhF,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;CAoBlC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoC9B,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2C1B,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2C5B,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+H5B,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuD7B,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0DlC,CAAC;AAEF,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,mBAAmB;QACnB,eAAe;QACf,WAAW;QACX,aAAa;QACb,aAAa;QACb,cAAc;QACd,mBAAmB;KACpB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,MAAM,QAAQ,GAA2B;QACvC,QAAQ,EAAE,mBAAmB;QAC7B,SAAS,EAAE,eAAe;QAC1B,KAAK,EAAE,WAAW;QAClB,OAAO,EAAE,aAAa;QACtB,eAAe,EAAE,aAAa;QAC9B,gBAAgB,EAAE,cAAc;QAChC,qBAAqB,EAAE,mBAAmB;KAC3C,CAAC;IAEF,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC;AACnC,CAAC;AAED,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,UAAU;IACV,WAAW;IACX,OAAO;IACP,SAAS;IACT,eAAe;IACf,gBAAgB;IAChB,qBAAqB;CACtB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerCreateElementTool(server: McpServer): void;
3
+ //# sourceMappingURL=create-element.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-element.d.ts","sourceRoot":"","sources":["../../src/tools/create-element.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAmBpE,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,SAAS,QAuH1D"}
@@ -0,0 +1,126 @@
1
+ import { z } from 'zod';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ function toKebabCase(str) {
5
+ return str
6
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
7
+ .replace(/[\s_]+/g, '-')
8
+ .toLowerCase();
9
+ }
10
+ function toPascalCase(str) {
11
+ return str
12
+ .split(/[-_\s]+/)
13
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
14
+ .join('');
15
+ }
16
+ export function registerCreateElementTool(server) {
17
+ server.tool('create_element', {
18
+ projectPath: z.string().describe('Absolute path to the project root'),
19
+ name: z.string().describe('Name of the element (e.g. "user-card", "custom-input")'),
20
+ type: z.enum(['atomic', 'form-control']).default('atomic').describe('Type of element: atomic (display/interactive) or form-control (implements ControlValueAccessor)'),
21
+ inputs: z.array(z.string()).optional().describe('List of input names (e.g. ["title", "isActive"])'),
22
+ }, async ({ projectPath, name, type, inputs = [] }) => {
23
+ const kebab = toKebabCase(name);
24
+ const pascal = toPascalCase(name);
25
+ const elementsDir = path.join(projectPath, 'src/app/elements', kebab);
26
+ if (fs.existsSync(elementsDir)) {
27
+ return {
28
+ content: [{
29
+ type: 'text',
30
+ text: `Error: Element '${kebab}' already exists at ${elementsDir}`
31
+ }]
32
+ };
33
+ }
34
+ fs.mkdirSync(elementsDir, { recursive: true });
35
+ let tsContent = '';
36
+ const cssContent = `:host {\n display: block;\n}\n`;
37
+ let htmlContent = `<p>${kebab} works!</p>`;
38
+ const inputSignals = inputs.map(i => ` ${i} = input<string>('');`).join('\n');
39
+ if (type === 'form-control') {
40
+ tsContent = `import { Component, forwardRef, signal } from '@angular/core';
41
+ import { CommonModule } from '@angular/common';
42
+ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
43
+
44
+ @Component({
45
+ selector: 'ghk-${kebab}',
46
+ standalone: true,
47
+ imports: [CommonModule],
48
+ templateUrl: './${kebab}.component.html',
49
+ styleUrl: './${kebab}.component.css',
50
+ providers: [{
51
+ provide: NG_VALUE_ACCESSOR,
52
+ useExisting: forwardRef(() => ${pascal}Component),
53
+ multi: true
54
+ }]
55
+ })
56
+ export class ${pascal}Component implements ControlValueAccessor {
57
+ ${inputSignals}
58
+
59
+ value = signal<string>('');
60
+ disabled = signal<boolean>(false);
61
+
62
+ onChange: (value: string) => void = () => {};
63
+ onTouched: () => void = () => {};
64
+
65
+ writeValue(obj: any): void {
66
+ this.value.set(obj || '');
67
+ }
68
+
69
+ registerOnChange(fn: any): void {
70
+ this.onChange = fn;
71
+ }
72
+
73
+ registerOnTouched(fn: any): void {
74
+ this.onTouched = fn;
75
+ }
76
+
77
+ setDisabledState(isDisabled: boolean): void {
78
+ this.disabled.set(isDisabled);
79
+ }
80
+
81
+ updateValue(newValue: string) {
82
+ this.value.set(newValue);
83
+ this.onChange(newValue);
84
+ this.onTouched();
85
+ }
86
+ }
87
+ `;
88
+ htmlContent = `<input
89
+ [value]="value()"
90
+ (input)="updateValue($any($event.target).value)"
91
+ [disabled]="disabled()"
92
+ class="ghk-input"
93
+ />`;
94
+ }
95
+ else {
96
+ // Atomic Component (Signal-based)
97
+ tsContent = `import { Component, input } from '@angular/core';
98
+ import { CommonModule } from '@angular/common';
99
+
100
+ @Component({
101
+ selector: 'ghk-${kebab}',
102
+ standalone: true,
103
+ imports: [CommonModule],
104
+ templateUrl: './${kebab}.component.html',
105
+ styleUrl: './${kebab}.component.css'
106
+ })
107
+ export class ${pascal}Component {
108
+ ${inputSignals}
109
+ }
110
+ `;
111
+ if (inputs.length > 0) {
112
+ htmlContent = `<div>\n${inputs.map(i => ` <p>{{ ${i}() }}</p>`).join('\n')}\n</div>`;
113
+ }
114
+ }
115
+ fs.writeFileSync(path.join(elementsDir, `${kebab}.component.ts`), tsContent);
116
+ fs.writeFileSync(path.join(elementsDir, `${kebab}.component.html`), htmlContent);
117
+ fs.writeFileSync(path.join(elementsDir, `${kebab}.component.css`), cssContent);
118
+ return {
119
+ content: [{
120
+ type: 'text',
121
+ text: `✅ Created atomic element '${kebab}' in src/app/elements/${kebab}\n- Component: ${pascal}Component\n- Check it out and add styles!`
122
+ }]
123
+ };
124
+ });
125
+ }
126
+ //# sourceMappingURL=create-element.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-element.js","sourceRoot":"","sources":["../../src/tools/create-element.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerScanElementsTool(server: McpServer): void;
3
+ //# sourceMappingURL=scan-elements.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan-elements.d.ts","sourceRoot":"","sources":["../../src/tools/scan-elements.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKpE,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,QA6FzD"}
@@ -0,0 +1,83 @@
1
+ import { z } from 'zod';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ export function registerScanElementsTool(server) {
5
+ server.tool('scan_elements', {
6
+ projectPath: z.string().describe('Absolute path to the project root'),
7
+ elementsPath: z.string().optional().describe('Relative path to elements folder (default: src/app/elements)'),
8
+ }, async ({ projectPath, elementsPath = 'src/app/elements' }) => {
9
+ const fullElementsPath = path.join(projectPath, elementsPath);
10
+ if (!fs.existsSync(fullElementsPath)) {
11
+ return {
12
+ content: [{
13
+ type: 'text',
14
+ text: `No elements folder found at ${fullElementsPath}. Recommend creating atomic components in 'src/app/elements'.`
15
+ }]
16
+ };
17
+ }
18
+ const elements = [];
19
+ function scanDir(dir) {
20
+ const files = fs.readdirSync(dir);
21
+ for (const file of files) {
22
+ const fullPath = path.join(dir, file);
23
+ const stat = fs.statSync(fullPath);
24
+ if (stat.isDirectory()) {
25
+ scanDir(fullPath);
26
+ }
27
+ else if (file.endsWith('.component.ts')) {
28
+ const content = fs.readFileSync(fullPath, 'utf-8');
29
+ // Extract selector
30
+ const selectorMatch = content.match(/selector:\s*['"`]([^'"`]+)['"`]/);
31
+ const selector = selectorMatch ? selectorMatch[1] : 'unknown-selector';
32
+ // Extract inputs (Signal inputs)
33
+ const inputs = [];
34
+ const lines = content.split('\n');
35
+ for (const line of lines) {
36
+ const trimmed = line.trim();
37
+ if ((trimmed.includes('input<') || trimmed.includes('input.required<')) && !trimmed.startsWith('//')) {
38
+ inputs.push(trimmed);
39
+ }
40
+ }
41
+ // Extract standalone flag
42
+ const isStandalone = content.includes('standalone: true');
43
+ // Component Name (Class name)
44
+ const classMatch = content.match(/export\s+class\s+(\w+)/);
45
+ const className = classMatch ? classMatch[1] : 'UnknownComponent';
46
+ elements.push(`
47
+ - **<${selector}>** (${className})
48
+ Location: ${path.relative(projectPath, fullPath)}
49
+ Standalone: ${isStandalone}
50
+ Inputs:
51
+ ${inputs.map(i => ` * ${i}`).join('\n')}
52
+ `);
53
+ }
54
+ }
55
+ }
56
+ try {
57
+ scanDir(fullElementsPath);
58
+ }
59
+ catch (err) {
60
+ return {
61
+ content: [{
62
+ type: 'text',
63
+ text: `Error scanning elements: ${err.message}`
64
+ }]
65
+ };
66
+ }
67
+ if (elements.length === 0) {
68
+ return {
69
+ content: [{
70
+ type: 'text',
71
+ text: `No components found in ${fullElementsPath}.`
72
+ }]
73
+ };
74
+ }
75
+ return {
76
+ content: [{
77
+ type: 'text',
78
+ text: `FOUND ${elements.length} ATOMIC ELEMENTS (REUSE THESE PREFERABLY):\n${elements.join('\n')}`
79
+ }]
80
+ };
81
+ });
82
+ }
83
+ //# sourceMappingURL=scan-elements.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan-elements.js","sourceRoot":"","sources":["../../src/tools/scan-elements.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gherk/reactive-architecture",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "MCP server that teaches AI assistants the reactive architecture philosophy: Component → Store → Service + Controller → Service → Repository",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",