guardian-framework 0.1.23 → 0.1.25

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/cli.js CHANGED
@@ -1335,7 +1335,7 @@ __export(exports_package, {
1335
1335
  bin: () => bin,
1336
1336
  author: () => author
1337
1337
  });
1338
- var name = "guardian-framework", version = "0.1.23", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
1338
+ var name = "guardian-framework", version = "0.1.25", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
1339
1339
  var init_package = __esm(() => {
1340
1340
  exports = {
1341
1341
  ".": {
package/dist/exports.js CHANGED
@@ -995,7 +995,7 @@ __export(exports_package, {
995
995
  bin: () => bin,
996
996
  author: () => author
997
997
  });
998
- var name = "guardian-framework", version = "0.1.23", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
998
+ var name = "guardian-framework", version = "0.1.25", description = "Token-optimized agentic framework scaffolder with pi-first architecture", type = "module", main = "dist/exports.js", exports, types = "dist/exports.d.ts", bin, files, engines, scripts, publishConfig, pi, repository, homepage = "https://github.com/arman-jalili/guardian-framework#readme", bugs, dependencies, devDependencies, keywords, author = "Arman Wolkensteiner-Jalili", license = "MIT", package_default;
999
999
  var init_package = __esm(() => {
1000
1000
  exports = {
1001
1001
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "guardian-framework",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Token-optimized agentic framework scaffolder with pi-first architecture",
5
5
  "type": "module",
6
6
  "main": "dist/exports.js",
@@ -1,5 +1,5 @@
1
1
  {
2
- "timestamp": "2026-07-03T07:41:28Z",
2
+ "timestamp": "2026-07-03T07:52:53Z",
3
3
  "mode": "all",
4
4
  "stages_run": [],
5
5
  "summary": {
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: design-system-codegen
3
+ description: Minimal skill for design system architecture with DDD + adapter pattern. References full patterns on demand — never loads inline. Use when building design system components, tokens, or CSS integration.
4
+ model: inherit
5
+ tools: [Read, Grep]
6
+ ---
7
+
8
+ # Design System Code Generation — DDD + Adapter Pattern
9
+
10
+ > Full reference: `.pi/skills/design-system-enterprise-codegen.md`
11
+
12
+ ## Quick Reference
13
+
14
+ | When you need... | Read this section |
15
+ |-----------------|-------------------|
16
+ | Structure overview | Section 1 — DDD module layout, framework agnostic |
17
+ | Design tokens | Section 2 — ColorToken, SpacingToken, TypeScale as domain values |
18
+ | Variant engine | Section 3 — defineVariants(), type-safe component props |
19
+ | CSS adapter (Tailwind) | Section 4 — Tailwind class generation from tokens |
20
+ | CSS adapter (CSS Modules) | Section 4 — CSS variable generation |
21
+ | CSS adapter (MUI/Material) | Section 4 — MUI theme mapping |
22
+ | Component pattern | Section 5 — Button component using application layer |
23
+ | Theming (dark mode) | Section 6 — ThemeService, data-theme attribute |
24
+ | Testing | Section 7 — token tests, variant engine tests |
25
+ | Choosing your stack | Section 8 — Tailwind vs CSS Modules vs MUI by use case |
26
+
27
+ ## DDD Layer Contract for Design Systems
28
+
29
+ | Layer | Purpose | Example | CSS framework aware? |
30
+ |-------|---------|---------|---------------------|
31
+ | `domain/tokens/` | Typed design tokens | `ColorToken.PRIMARY` | No |
32
+ | `application/` | Variant resolution, theming | `defineVariants()` | No |
33
+ | `infrastructure/` | CSS adapter | `tailwind-adapter.ts` | **Yes** — this is the only layer |
34
+ | `interfaces/` | Rendered components | `button.tsx` | No (uses application layer) |
35
+
36
+ ## Rules
37
+ - NEVER read full reference — read specific sections
38
+ - Design tokens are pure TypeScript — zero CSS, zero framework imports
39
+ - Components import from `application/`, never from `infrastructure/` directly
40
+ - The adapter is the **only file** that changes when you switch CSS frameworks
41
+ - Pick one adapter per project — never mix Tailwind and MUI in the same component
@@ -1,158 +1,410 @@
1
1
  ---
2
2
  name: angular-enterprise-codegen
3
- description: Full reference for Angular enterprise code generation with DDD + Clean Architecture. Covers module structure, services, Signal stores, standalone components, RxJS patterns, and testing. Loaded on-demand via agents/angular-codegen.md skill.
3
+ description: Full reference for Angular enterprise code generation with DDD + Clean Architecture. Covers module structure, standalone components, Signal-based state, RxJS patterns, design system integration, lazy loading, testing, and performance. Loaded on-demand via agents/angular-codegen.md skill.
4
4
  ---
5
5
 
6
6
  # Angular Enterprise Code Generation — DDD + Clean Architecture
7
7
 
8
8
  > Canonical skill for generating production-grade Angular code with DDD patterns.
9
9
  > All code MUST follow these patterns. Validators enforce compliance.
10
+ >
11
+ > For design system and styling patterns, see: `.pi/skills/design-system-enterprise-codegen.md`
10
12
 
11
13
  ---
12
14
 
13
- ## 1. Project Structure
15
+ ## 1. Project Structure — DDD with Angular Standalone Components
14
16
 
15
17
  ```
16
- module/
17
- ├── domain/ # Pure business logic, no Angular imports
18
- │ ├── entity.ts
19
- │ ├── value.ts
20
- │ └── errors.ts
21
- ├── application/ # Injectable services, Signal stores
22
- │ ├── store.ts # Signal-based state
23
- │ ├── service.ts # @Injectable use case service
24
- │ └── dto.ts
25
- ├── infrastructure/ # HTTP clients, localStorage adapters
26
- │ ├── api-client.ts # HttpClient wrapper
27
- │ └── storage.ts
28
- └── interfaces/ # UI Components (standalone)
29
- ├── component.ts
30
- └── page.component.ts
18
+ src/
19
+ module/ # One bounded context
20
+ domain/ # Pure business logic, zero Angular imports
21
+ entity.ts # Domain entities with encapsulated state
22
+ value.ts # Immutable value objects
23
+ events.ts # Domain events
24
+ errors.ts # Typed error classes
25
+ application/ # Application logic
26
+ store.ts # Signal-based state management
27
+ service.ts # @Injectable use case service
28
+ facade.ts # Facade pattern (service → store)
29
+ dto.ts # Command/Query DTOs
30
+ infrastructure/ # External adapters
31
+ api-client.ts # HttpClient wrapper with interceptors
32
+ auth.ts # Auth provider SDK wrapper
33
+ analytics.ts # Analytics SDK wrapper
34
+ interfaces/ # UI layer
35
+ page.component.ts # Route component (standalone)
36
+ list.component.ts # Smart component
37
+ item.component.ts # Presentational component
38
+ form.component.ts # Form with Reactive Forms
39
+ shared/ # Cross-module shared code
40
+ ui/ # Design system components
41
+ button/
42
+ card/
43
+ modal/
44
+ lib/ # Shared utilities
45
+ pipes/ # Shared Angular pipes
31
46
  ```
32
47
 
33
48
  ### Dependency Rule
34
49
  ```
35
50
  domain → application → infrastructure → interfaces
51
+
52
+ shared/ (no framework deps)
36
53
  ```
37
54
 
55
+ - **domain/** — zero Angular imports. Pure TypeScript. No decorators.
56
+ - **application/** — imports from domain. Uses `@Injectable` for DI.
57
+ - **infrastructure/** — imports from domain + application. Uses Angular `HttpClient`.
58
+ - **interfaces/** — imports from application. Standalone components with `@Component`.
59
+
38
60
  ---
39
61
 
40
- ## 2. Domain Layer
62
+ ## 2. Component Architecture
41
63
 
42
- ```typescript
43
- // domain/entity.ts — No Angular imports
44
- export class Order {
45
- private readonly _id: string;
46
- private _status: OrderStatus = OrderStatus.PENDING;
47
-
48
- constructor(id?: string) {
49
- this._id = id ?? crypto.randomUUID();
50
- }
64
+ ### Smart vs Presentational Components
51
65
 
52
- get id(): string { return this._id; }
53
- get status(): OrderStatus { return this._status; }
66
+ | Type | Location | Responsibility |
67
+ |------|----------|---------------|
68
+ | Smart (Page) | `interfaces/page.component.ts` | Route handler, orchestrates data flow |
69
+ | Smart (List) | `interfaces/list.component.ts` | Uses store/service, passes data down |
70
+ | Presentational | `interfaces/item.component.ts` | Pure @Input/@Output, no DI |
71
+ | Form | `interfaces/form.component.ts` | Reactive Forms, validation |
54
72
 
55
- confirm(): void {
56
- if (this._status !== OrderStatus.PENDING) {
57
- throw new DomainError('INVALID_STATE', 'Only pending orders can be confirmed');
73
+ ```typescript
74
+ // interfaces/list.component.ts Smart component
75
+ @Component({
76
+ selector: 'app-order-list',
77
+ standalone: true,
78
+ imports: [OrderItemComponent, AsyncPipe],
79
+ template: `
80
+ @for (order of orders$ | async; track order.id) {
81
+ <app-order-item [order]="order" (select)="onSelect($event)" />
82
+ } @empty {
83
+ <empty-state />
58
84
  }
59
- this._status = OrderStatus.CONFIRMED;
85
+ `,
86
+ })
87
+ export class OrderListComponent {
88
+ private readonly service = inject(OrderService);
89
+ readonly orders$ = this.service.orders$;
90
+
91
+ onSelect(order: Order): void {
92
+ this.service.selectOrder(order.id);
60
93
  }
61
94
  }
95
+
96
+ // interfaces/item.component.ts — Presentational component
97
+ @Component({
98
+ selector: 'app-order-item',
99
+ standalone: true,
100
+ template: `
101
+ <div (click)="select.emit(order.id)">
102
+ <h3>{{ order.id }}</h3>
103
+ <span [class]="statusClass(order.status)">{{ order.status }}</span>
104
+ </div>
105
+ `,
106
+ })
107
+ export class OrderItemComponent {
108
+ @Input({ required: true }) order!: Order;
109
+ @Output() select = new EventEmitter<string>();
110
+ }
62
111
  ```
63
112
 
64
113
  ---
65
114
 
66
- ## 3. Application Layer
115
+ ## 3. State Management with Signals
67
116
 
68
117
  ```typescript
69
- // application/service.ts
70
- import { Injectable, signal } from '@angular/core';
71
- import { Order } from '../domain/entity';
118
+ // application/store.ts — Signal-based state
119
+ import { signal, computed, Injectable } from '@angular/core';
120
+ import { Order, OrderStatus } from '../domain/entity';
72
121
 
73
122
  @Injectable({ providedIn: 'root' })
74
- export class OrderService {
123
+ export class OrderStore {
75
124
  private readonly orders = signal<Order[]>([]);
125
+ private readonly selectedId = signal<string | null>(null);
126
+ private readonly isLoading = signal(false);
127
+
76
128
  readonly orders$ = this.orders.asReadonly();
129
+ readonly selectedOrder = computed(() => {
130
+ const id = this.selectedId();
131
+ return id ? this.orders().find(o => o.id === id) ?? null : null;
132
+ });
133
+ readonly isLoading$ = this.isLoading.asReadonly();
134
+ readonly pendingCount = computed(() =>
135
+ this.orders().filter(o => o.status === OrderStatus.PENDING).length
136
+ );
137
+
138
+ async loadOrders(): Promise<void> {
139
+ this.isLoading.set(true);
140
+ try {
141
+ const orders = await this.api.fetchOrders();
142
+ this.orders.set(orders);
143
+ } finally {
144
+ this.isLoading.set(false);
145
+ }
146
+ }
77
147
 
78
- confirmOrder(id: string): void {
79
- this.orders.update(items => {
80
- const order = items.find(o => o.id === id);
81
- if (!order) throw new DomainError('NOT_FOUND', 'Order not found');
82
- order.confirm();
83
- return [...items];
84
- });
148
+ selectOrder(id: string): void {
149
+ this.selectedId.set(id);
85
150
  }
86
151
  }
87
152
  ```
88
153
 
154
+ ### Rules
155
+ - ✅ Use `signal()` over `BehaviorSubject` for state (Angular 17+)
156
+ - ✅ Use `computed()` for derived state — no manual subscriptions
157
+ - ✅ Use `effect()` only for side effects (logging, syncing to localStorage)
158
+ - ❌ Don't mix Signals and RxJS in the same store — pick one pattern
159
+ - ❌ Don't use `async` pipe with Signals — Signals work with `@for` directly
160
+
89
161
  ---
90
162
 
91
- ## 4. Infrastructure Layer
163
+ ## 4. Data Flow with RxJS
92
164
 
93
165
  ```typescript
94
- // infrastructure/api-client.ts
95
- import { HttpClient } from '@angular/common/http';
96
- import { Injectable, inject } from '@angular/core';
97
-
166
+ // application/service.ts — RxJS for async operations
98
167
  @Injectable({ providedIn: 'root' })
99
- export class OrderApiClient {
168
+ export class OrderService {
100
169
  private readonly http = inject(HttpClient);
170
+ private readonly store = inject(OrderStore);
171
+
172
+ // Stream: debounced search → API call → update store
173
+ readonly search = new Subject<string>();
174
+
175
+ constructor() {
176
+ this.search.pipe(
177
+ debounceTime(300),
178
+ distinctUntilChanged(),
179
+ switchMap(query => this.http.get<Order[]>(`/api/orders?q=${query}`)),
180
+ catchError(err => {
181
+ console.error('Search failed', err);
182
+ return of([]);
183
+ }),
184
+ ).subscribe(orders => this.store.setOrders(orders));
185
+ }
101
186
 
102
- fetchOrders() {
103
- return this.http.get<Order[]>('/api/orders');
187
+ // Server mutation with optimistic update
188
+ confirmOrder(id: string): void {
189
+ const previous = this.store.orders$();
190
+ this.store.updateOrder(id, OrderStatus.CONFIRMED); // Optimistic
191
+
192
+ this.http.post(`/api/orders/${id}/confirm`, {}).pipe(
193
+ catchError(err => {
194
+ this.store.setOrders(previous); // Rollback
195
+ throw err;
196
+ }),
197
+ ).subscribe();
104
198
  }
105
199
  }
106
200
  ```
107
201
 
202
+ ### Rules
203
+ - ✅ Services handle async (HTTP, timers), Stores hold synchronous state
204
+ - ✅ Use `switchMap` for search/autocomplete (cancel previous)
205
+ - ✅ Use `exhaustMap` for mutations (ignore while in-flight)
206
+ - ✅ Always `catchError` in service streams — never let errors propagate unhandled
207
+ - ❌ Never subscribe in components — use `async` pipe or `@for` with Signals
208
+ - ❌ Never put business logic in RxJS pipes — that's the domain layer's job
209
+
108
210
  ---
109
211
 
110
- ## 5. Interfaces Layer (Components)
212
+ ## 5. Design System & CSS Architecture
111
213
 
112
- ```typescript
113
- // interfaces/order-list.component.ts
114
- import { Component, inject } from '@angular/core';
115
- import { OrderService } from '../application/service';
214
+ ### Structure
215
+ ```
216
+ src/
217
+ shared/
218
+ ui/ # Design system components
219
+ tokens/ # Design tokens
220
+ _colors.scss
221
+ _typography.scss
222
+ _spacing.scss
223
+ button/
224
+ button.component.ts
225
+ button.component.scss
226
+ button.test.ts
227
+ card/
228
+ form/
229
+ input/
230
+ select/
231
+ ```
232
+
233
+ ### Styling Strategy
234
+
235
+ | Approach | When to use |
236
+ |----------|-------------|
237
+ | Component SCSS (`@Component styleUrls`) | Component-scoped styles |
238
+ | Global SCSS (`styles.scss`) | CSS reset, typography, CSS custom properties |
239
+ | CSS Custom Properties | Design tokens, theming |
240
+ | Tailwind (via ngClass) | Layout, one-off adjustments |
241
+
242
+ ```scss
243
+ // shared/ui/tokens/_colors.scss
244
+ :root {
245
+ --color-primary: #0f766e;
246
+ --color-primary-foreground: #ffffff;
247
+ --color-secondary: #64748b;
248
+ --color-destructive: #dc2626;
249
+ --color-background: #ffffff;
250
+ --color-foreground: #0f172a;
251
+ }
116
252
 
253
+ // button.component.scss
254
+ @use '../../tokens/colors' as *;
255
+
256
+ .button {
257
+ display: inline-flex;
258
+ align-items: center;
259
+ justify-content: center;
260
+ border-radius: 6px;
261
+ font-size: 0.875rem;
262
+ font-weight: 500;
263
+ transition: all 0.2s;
264
+
265
+ &--primary {
266
+ background: var(--color-primary);
267
+ color: var(--color-primary-foreground);
268
+ }
269
+ &--secondary {
270
+ background: var(--color-secondary);
271
+ color: #fff;
272
+ }
273
+ &--sm { height: 2.25rem; padding: 0 0.75rem; }
274
+ &--md { height: 2.5rem; padding: 0 1rem; }
275
+ &--lg { height: 2.75rem; padding: 0 2rem; }
276
+ }
277
+ ```
278
+
279
+ ```typescript
280
+ // button.component.ts
117
281
  @Component({
118
- selector: 'app-order-list',
282
+ selector: 'app-button',
119
283
  standalone: true,
120
284
  template: `
121
- <div>
122
- @for (order of service.orders$(); track order.id) {
123
- <div>{{ order.id }} - {{ order.status }}</div>
124
- }
125
- </div>
285
+ <button [class]="'button button--' + variant() + ' button--' + size()">
286
+ <ng-content />
287
+ </button>
126
288
  `,
127
289
  })
128
- export class OrderListComponent {
129
- readonly service = inject(OrderService);
290
+ export class ButtonComponent {
291
+ readonly variant = input<'primary' | 'secondary'>('primary');
292
+ readonly size = input<'sm' | 'md' | 'lg'>('md');
130
293
  }
131
294
  ```
132
295
 
133
296
  ---
134
297
 
135
- ## 6. Testing
298
+ ## 6. Lazy Loading & Routing
136
299
 
137
300
  ```typescript
138
- import { TestBed } from '@angular/core/testing';
139
- import { OrderService } from './service';
301
+ // app.routes.ts Lazy-loaded modules
302
+ export const routes: Routes = [
303
+ {
304
+ path: 'orders',
305
+ loadChildren: () => import('./orders/orders.routes'),
306
+ canActivate: [AuthGuard],
307
+ },
308
+ {
309
+ path: 'checkout',
310
+ loadComponent: () => import('./checkout/page.component'),
311
+ },
312
+ ];
313
+
314
+ // orders/orders.routes.ts
315
+ export default [
316
+ { path: '', component: OrderListComponent },
317
+ { path: ':id', component: OrderDetailComponent },
318
+ ] satisfies Routes;
319
+ ```
140
320
 
141
- describe('OrderService', () => {
142
- let service: OrderService;
321
+ ---
143
322
 
144
- beforeEach(() => {
145
- TestBed.configureTestingModule({});
146
- service = TestBed.inject(OrderService);
147
- });
323
+ ## 7. Error Handling
324
+
325
+ ```typescript
326
+ // domain/errors.ts
327
+ export class DomainError extends Error {
328
+ constructor(
329
+ public readonly code: string,
330
+ message: string,
331
+ public readonly httpStatus: number = 400,
332
+ ) {
333
+ super(message);
334
+ this.name = 'DomainError';
335
+ }
336
+ }
337
+
338
+ // infrastructure/api-client.ts — HTTP interceptor
339
+ @Injectable()
340
+ export class ErrorInterceptor implements HttpInterceptor {
341
+ intercept(req: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> {
342
+ return next(req).pipe(
343
+ catchError((error: HttpErrorResponse) => {
344
+ const domainError = new DomainError(
345
+ error.error?.code ?? 'UNKNOWN',
346
+ error.error?.message ?? 'An unexpected error occurred',
347
+ error.status,
348
+ );
349
+ console.error(`[${domainError.code}] ${domainError.message}`);
350
+ return throwError(() => domainError);
351
+ }),
352
+ );
353
+ }
354
+ }
355
+ ```
148
356
 
149
- it('should confirm order', () => {
150
- // ... test using service.orders$()
357
+ ---
358
+
359
+ ## 8. Testing Patterns
360
+
361
+ | Test type | Tool | What to test |
362
+ |-----------|------|-------------|
363
+ | Unit | Jest / Vitest | `domain/` entities, value objects |
364
+ | Component | Angular TestBed | `interfaces/` components |
365
+ | Service | TestBed + HttpTestingController | HTTP interactions |
366
+ | E2E | Playwright | Full user flows |
367
+
368
+ ```typescript
369
+ // domain/entity.test.ts
370
+ describe('Order', () => {
371
+ it('transitions from pending to confirmed', () => {
372
+ const order = new Order();
373
+ order.confirm();
374
+ expect(order.status).toBe(OrderStatus.CONFIRMED);
151
375
  });
152
376
  });
153
377
  ```
154
378
 
155
379
  ---
156
380
 
157
- *Version: 1.0.0*
381
+ ## 9. Anti-Patterns — NEVER DO
382
+
383
+ ```typescript
384
+ // ❌ Business logic in components
385
+ export class OrderComponent {
386
+ calculateTotal(items: Item[]): number { ... } // BAD — belongs in domain/
387
+ }
388
+
389
+ // ❌ Subscribing in components without cleanup
390
+ ngOnInit() { this.service.orders$.subscribe(); } // BAD — use async pipe
391
+
392
+ // ❌ Mutable state without Signals
393
+ public orders: Order[] = []; // BAD — use signal()
394
+
395
+ // ❌ Direct HTTP calls in components
396
+ this.http.get('/api/orders'); // BAD — wrap in infrastructure/
397
+
398
+ // ❌ Domain entities with Angular decorators
399
+ export class Order {
400
+ @Input() id: string; // BAD — domain is Angular-free
401
+ }
402
+
403
+ // ❌ CSS in component TS
404
+ styles: [':host { display: block; }'] // BAD — use SCSS files
405
+ ```
406
+
407
+ ---
408
+
409
+ *Version: 1.1.0*
158
410
  *Last updated: 2026-07-03*