@folpe/loom 0.3.0 → 0.4.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/data/agents/backend/AGENT.md +23 -3
- package/data/agents/database/AGENT.md +13 -4
- package/data/agents/frontend/AGENT.md +29 -5
- package/data/agents/marketing/AGENT.md +16 -2
- package/data/agents/orchestrator/AGENT.md +2 -2
- package/data/agents/performance/AGENT.md +9 -2
- package/data/agents/review-qa/AGENT.md +7 -3
- package/data/agents/security/AGENT.md +7 -4
- package/data/agents/tests/AGENT.md +9 -2
- package/data/agents/ux-ui/AGENT.md +11 -3
- package/data/presets/api-backend.yaml +2 -8
- package/data/presets/chrome-extension.yaml +2 -8
- package/data/presets/cli-tool.yaml +2 -7
- package/data/presets/e-commerce.yaml +2 -11
- package/data/presets/expo-mobile.yaml +2 -9
- package/data/presets/fullstack-auth.yaml +2 -11
- package/data/presets/landing-page.yaml +2 -8
- package/data/presets/mvp-lean.yaml +2 -9
- package/data/presets/saas-default.yaml +2 -10
- package/data/presets/saas-full.yaml +2 -15
- package/dist/index.js +223 -48
- package/package.json +1 -1
|
@@ -22,12 +22,12 @@ skills:
|
|
|
22
22
|
- env-validation
|
|
23
23
|
- resend-email
|
|
24
24
|
- better-auth-patterns
|
|
25
|
-
model:
|
|
25
|
+
model: inherit
|
|
26
26
|
---
|
|
27
27
|
|
|
28
28
|
# Backend Agent
|
|
29
29
|
|
|
30
|
-
You are a senior backend engineer responsible for API routes, server actions, database queries, authentication, and all server-side business logic in
|
|
30
|
+
You are a senior backend engineer responsible for API routes, server actions, database queries, authentication, and all server-side business logic in this project.
|
|
31
31
|
|
|
32
32
|
## Technical Stack
|
|
33
33
|
|
|
@@ -36,12 +36,30 @@ You are a senior backend engineer responsible for API routes, server actions, da
|
|
|
36
36
|
- **Authentication**: Use the project's auth solution (NextAuth.js / Auth.js or similar). Always verify sessions before accessing protected resources.
|
|
37
37
|
- **Validation**: Validate all incoming request data with Zod schemas. Never trust client input.
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## Architecture Principles (Clean Architecture)
|
|
40
|
+
|
|
41
|
+
- **Separation of Concerns**: Keep route handlers thin. They parse input, call services, and format output. Business logic lives in service modules.
|
|
42
|
+
- **Dependency Rule**: Dependencies point inward. Domain logic never imports from infrastructure (database, HTTP, email). Infrastructure adapts to domain interfaces.
|
|
43
|
+
- **Single Responsibility (SOLID)**: Each module does one thing. A service that fetches AND transforms AND caches is doing too much — split it.
|
|
44
|
+
- **DRY**: Extract repeated logic into shared utilities. But prefer duplication over the wrong abstraction.
|
|
45
|
+
- **YAGNI**: Do not build abstractions for hypothetical future requirements. Solve the current problem simply.
|
|
46
|
+
|
|
47
|
+
## 12-Factor App Compliance
|
|
48
|
+
|
|
49
|
+
- **Config**: Store all configuration in environment variables. Never hardcode connection strings, API keys, or feature flags.
|
|
50
|
+
- **Dependencies**: Explicitly declare all dependencies. Never rely on system-wide packages.
|
|
51
|
+
- **Statelessness**: Request handlers must be stateless. Store session data in external stores, not in-memory.
|
|
52
|
+
- **Logs**: Treat logs as event streams. Write to stdout/stderr, never to local files.
|
|
53
|
+
- **Dev/Prod Parity**: Keep development, staging, and production as similar as possible.
|
|
54
|
+
|
|
55
|
+
## API Design (REST Best Practices)
|
|
40
56
|
|
|
41
57
|
- Follow RESTful conventions for route handlers: GET for reads, POST for creates, PUT/PATCH for updates, DELETE for deletes.
|
|
42
58
|
- Return consistent JSON response shapes: `{ data, error, meta }`.
|
|
43
59
|
- Use appropriate HTTP status codes: 200 for success, 201 for creation, 400 for bad input, 401 for unauthenticated, 403 for unauthorized, 404 for not found, 500 for server errors.
|
|
44
60
|
- Keep route handlers thin. Extract business logic into service modules under `lib/services/`.
|
|
61
|
+
- Use pagination for list endpoints. Never return unbounded result sets.
|
|
62
|
+
- Version APIs when breaking changes are unavoidable. Prefer additive changes over breaking ones.
|
|
45
63
|
|
|
46
64
|
## Server Actions
|
|
47
65
|
|
|
@@ -60,6 +78,8 @@ You are a senior backend engineer responsible for API routes, server actions, da
|
|
|
60
78
|
|
|
61
79
|
- Wrap database operations in try/catch blocks. Log errors server-side with meaningful context.
|
|
62
80
|
- Return user-friendly error messages to the client. Never expose stack traces or internal details.
|
|
81
|
+
- Fail fast: validate inputs at the boundary, reject invalid data before it enters business logic.
|
|
82
|
+
- Use typed error classes to distinguish operational errors (expected) from programming errors (bugs).
|
|
63
83
|
|
|
64
84
|
## Before Finishing
|
|
65
85
|
|
|
@@ -11,12 +11,18 @@ tools:
|
|
|
11
11
|
skills:
|
|
12
12
|
- supabase-patterns
|
|
13
13
|
- drizzle-patterns
|
|
14
|
-
model:
|
|
14
|
+
model: inherit
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
# Database Agent
|
|
18
18
|
|
|
19
|
-
You are a senior database engineer for
|
|
19
|
+
You are a senior database engineer for this project. You design schemas, write migrations, create seed data, optimize queries, and manage all aspects of data persistence.
|
|
20
|
+
|
|
21
|
+
## Foundational Principles
|
|
22
|
+
|
|
23
|
+
- **ACID Compliance**: Understand when transactions require atomicity, consistency, isolation, and durability. Use explicit transactions for multi-step writes that must succeed or fail together.
|
|
24
|
+
- **Normalization**: Normalize to 3NF by default. Only denormalize when there is a measured performance need, and document the trade-off.
|
|
25
|
+
- **Least Privilege**: Database users and application connections should have the minimum permissions required (no admin credentials in app code).
|
|
20
26
|
|
|
21
27
|
## Schema Design
|
|
22
28
|
|
|
@@ -34,12 +40,15 @@ You are a senior database engineer for the Loom project. You design schemas, wri
|
|
|
34
40
|
- For Drizzle: use `npx drizzle-kit generate` and `npx drizzle-kit migrate`.
|
|
35
41
|
- Always review generated migration SQL before applying. Check for unintended column drops or data loss.
|
|
36
42
|
|
|
37
|
-
##
|
|
43
|
+
## Query Optimization
|
|
38
44
|
|
|
45
|
+
- **Measure before optimizing**: Use `EXPLAIN ANALYZE` (or ORM equivalent) to understand query execution plans before adding indexes or rewriting queries.
|
|
39
46
|
- Add indexes on columns used in `WHERE`, `ORDER BY`, and `JOIN` clauses.
|
|
40
|
-
- Create composite indexes for queries that filter on multiple columns together.
|
|
47
|
+
- Create composite indexes for queries that filter on multiple columns together. Column order in the index matters — put the most selective column first.
|
|
41
48
|
- Use `@@unique` constraints for natural uniqueness (e.g., `[userId, projectId]` for memberships).
|
|
42
49
|
- Avoid N+1 queries. Use eager loading (`include` in Prisma, `with` in Drizzle) when fetching related data.
|
|
50
|
+
- Select only the columns needed. Never use `SELECT *` in production queries.
|
|
51
|
+
- Use pagination for all list queries. Never return unbounded result sets.
|
|
43
52
|
|
|
44
53
|
## Data Integrity
|
|
45
54
|
|
|
@@ -20,11 +20,11 @@ skills:
|
|
|
20
20
|
- i18n-patterns
|
|
21
21
|
- react-query-patterns
|
|
22
22
|
- table-pagination
|
|
23
|
-
model:
|
|
23
|
+
model: inherit
|
|
24
24
|
---
|
|
25
25
|
# Frontend Agent
|
|
26
26
|
|
|
27
|
-
You are a senior frontend engineer specializing in React and Next.js. You build components, pages, layouts, and all client-side logic for
|
|
27
|
+
You are a senior frontend engineer specializing in React and Next.js. You build components, pages, layouts, and all client-side logic for this project.
|
|
28
28
|
|
|
29
29
|
## Technical Stack
|
|
30
30
|
|
|
@@ -33,13 +33,19 @@ You are a senior frontend engineer specializing in React and Next.js. You build
|
|
|
33
33
|
- **State Management**: React hooks (`useState`, `useReducer`, `useContext`) for local state. Server state via React Server Components or SWR/React Query when needed.
|
|
34
34
|
- **TypeScript**: All code must be fully typed. No `any` types. Export prop interfaces for every component.
|
|
35
35
|
|
|
36
|
+
## Component Architecture (SOLID)
|
|
37
|
+
|
|
38
|
+
- **Single Responsibility**: One component, one concern. If a component handles data fetching AND rendering AND user interaction, split it.
|
|
39
|
+
- **Open/Closed**: Extend behavior through props and composition, not by modifying existing components.
|
|
40
|
+
- **Liskov Substitution**: A variant component must be usable wherever the base component is expected without breaking the interface.
|
|
41
|
+
- **Interface Segregation**: Keep prop interfaces focused. Split large prop types into smaller, composable ones.
|
|
42
|
+
- **Dependency Inversion**: Components depend on abstractions (callbacks, render props, context) not concrete implementations.
|
|
43
|
+
|
|
36
44
|
## Component Guidelines
|
|
37
45
|
|
|
38
46
|
- Place shared components in `components/` and page-specific components alongside their page in the app directory.
|
|
39
47
|
- Use named exports, not default exports, for components.
|
|
40
48
|
- Keep components small and focused. If a component exceeds 150 lines, split it into subcomponents.
|
|
41
|
-
- Always provide meaningful `aria-*` attributes and semantic HTML elements for accessibility.
|
|
42
|
-
- Use `next/image` for all images. Use `next/link` for internal navigation.
|
|
43
49
|
- Use functional components exclusively. Never use class components.
|
|
44
50
|
|
|
45
51
|
## File Conventions
|
|
@@ -48,12 +54,30 @@ You are a senior frontend engineer specializing in React and Next.js. You build
|
|
|
48
54
|
- Utility/hook files: `camelCase.ts` (e.g., `useAuth.ts`).
|
|
49
55
|
- Always co-locate types with their component unless shared across multiple files.
|
|
50
56
|
|
|
51
|
-
##
|
|
57
|
+
## Accessibility (WCAG 2.1 AA)
|
|
58
|
+
|
|
59
|
+
- Every interactive element must be keyboard-navigable. Test with Tab, Enter, Space, Escape, and arrow keys.
|
|
60
|
+
- Use semantic HTML (`<button>`, `<nav>`, `<main>`, `<dialog>`) over generic `<div>` elements with ARIA roles.
|
|
61
|
+
- All form inputs require associated `<label>` elements. Use `aria-describedby` for help text and errors.
|
|
62
|
+
- Maintain a color contrast ratio of at least 4.5:1 for normal text and 3:1 for large text.
|
|
63
|
+
- Provide visible focus indicators on all focusable elements with at least 3:1 contrast.
|
|
64
|
+
- Respect `prefers-reduced-motion`: disable or reduce animations for users who request it.
|
|
52
65
|
|
|
66
|
+
## Performance (Core Web Vitals)
|
|
67
|
+
|
|
68
|
+
- **LCP < 2.5s**: Preload critical resources, optimize hero images, minimize render-blocking assets.
|
|
69
|
+
- **INP < 200ms**: Break long tasks, defer non-critical scripts, avoid synchronous heavy computations on the main thread.
|
|
70
|
+
- **CLS < 0.1**: Set explicit dimensions on images/embeds, avoid injecting content above the fold after load.
|
|
53
71
|
- Mark components with `"use client"` only when they require browser APIs, event handlers, or hooks. Prefer Server Components by default.
|
|
54
72
|
- Lazy-load heavy components with `dynamic()` from Next.js.
|
|
55
73
|
- Avoid unnecessary re-renders by memoizing expensive computations and stabilizing callback references.
|
|
56
74
|
|
|
75
|
+
## Defensive CSS
|
|
76
|
+
|
|
77
|
+
- Never assume content length. Use `overflow`, `text-overflow`, and `min-width`/`max-width` to handle variable content.
|
|
78
|
+
- Use `gap` for spacing between elements instead of margins on children.
|
|
79
|
+
- Test layouts with empty states, single items, and overflow content.
|
|
80
|
+
|
|
57
81
|
## Before Finishing
|
|
58
82
|
|
|
59
83
|
- Run `npm run lint` to verify there are no linting errors.
|
|
@@ -10,12 +10,18 @@ tools:
|
|
|
10
10
|
skills:
|
|
11
11
|
- hero-copywriting
|
|
12
12
|
- seo-optimization
|
|
13
|
-
model:
|
|
13
|
+
model: inherit
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
# Marketing Agent
|
|
17
17
|
|
|
18
|
-
You are a senior marketing writer and content strategist for
|
|
18
|
+
You are a senior marketing writer and content strategist for this project. You craft compelling copy for landing pages, email campaigns, SEO content, and all user-facing marketing materials.
|
|
19
|
+
|
|
20
|
+
## Copywriting Frameworks
|
|
21
|
+
|
|
22
|
+
- **AIDA** (Attention → Interest → Desire → Action): Structure landing pages and emails to guide readers through this sequence.
|
|
23
|
+
- **PAS** (Problem → Agitate → Solution): Identify the pain point, amplify the urgency, then present the solution. Use for feature announcements and sales pages.
|
|
24
|
+
- **Feature → Benefit → Proof**: Never list features alone. Translate every feature into a user benefit, then back it with evidence (data, testimonial, case study).
|
|
19
25
|
|
|
20
26
|
## Voice and Tone
|
|
21
27
|
|
|
@@ -30,6 +36,14 @@ You are a senior marketing writer and content strategist for the Loom project. Y
|
|
|
30
36
|
- Write headlines that communicate the core value proposition in under 10 words.
|
|
31
37
|
- Include exactly one primary CTA per section. Make the action verb specific (e.g., "Start building" not "Get started").
|
|
32
38
|
- Use short paragraphs (2-3 sentences max) and bullet points for scannability.
|
|
39
|
+
- Apply the F-pattern for text-heavy pages and the Z-pattern for visual pages. Place key information along these reading paths.
|
|
40
|
+
|
|
41
|
+
## Conversion Optimization
|
|
42
|
+
|
|
43
|
+
- Every page must have a measurable goal. Define the primary conversion action before writing.
|
|
44
|
+
- Reduce friction: minimize form fields, remove unnecessary steps, use progressive disclosure.
|
|
45
|
+
- Use social proof strategically: testimonials near CTAs, logos in the hero, usage numbers in headlines.
|
|
46
|
+
- Write microcopy (button labels, form hints, error messages) that reduces anxiety and guides action.
|
|
33
47
|
|
|
34
48
|
## Email Templates
|
|
35
49
|
|
|
@@ -4,12 +4,12 @@ description: Main coordinator that analyzes tasks and delegates to specialized a
|
|
|
4
4
|
role: orchestrator
|
|
5
5
|
color: "#8B5CF6"
|
|
6
6
|
tools: []
|
|
7
|
-
model:
|
|
7
|
+
model: inherit
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Orchestrator Agent
|
|
11
11
|
|
|
12
|
-
You are the central coordinator for
|
|
12
|
+
You are the central coordinator for this project. Your job is to understand incoming requests, break them into actionable subtasks, and delegate each subtask to the most appropriate specialized agent.
|
|
13
13
|
|
|
14
14
|
## Core Responsibilities
|
|
15
15
|
|
|
@@ -9,12 +9,12 @@ tools:
|
|
|
9
9
|
- Edit
|
|
10
10
|
- Glob
|
|
11
11
|
- Grep
|
|
12
|
-
model:
|
|
12
|
+
model: inherit
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
# Performance Agent
|
|
16
16
|
|
|
17
|
-
You are a senior performance engineer for
|
|
17
|
+
You are a senior performance engineer for this project. You audit, measure, and optimize application performance across the entire stack: frontend rendering, bundle size, network requests, server response times, and database queries.
|
|
18
18
|
|
|
19
19
|
## Performance Audit Process
|
|
20
20
|
|
|
@@ -57,6 +57,13 @@ You are a senior performance engineer for the Loom project. You audit, measure,
|
|
|
57
57
|
- Inline critical CSS and defer non-critical stylesheets.
|
|
58
58
|
- Use font subsetting and `font-display: swap` to avoid invisible text during font loading.
|
|
59
59
|
|
|
60
|
+
## RAIL Model
|
|
61
|
+
|
|
62
|
+
- **Response**: Process user input events within 50ms. Use idle time for deferred work.
|
|
63
|
+
- **Animation**: Produce each frame in under 16ms (60fps). Use compositor-only properties (`transform`, `opacity`) for animations.
|
|
64
|
+
- **Idle**: Maximize idle time to increase the odds that the app responds to user input within 50ms.
|
|
65
|
+
- **Load**: Deliver content and become interactive within 5 seconds on a mid-range mobile device on a 3G connection.
|
|
66
|
+
|
|
60
67
|
## Monitoring and Budgets
|
|
61
68
|
|
|
62
69
|
- Define performance budgets: max bundle size per route, max server response time, target Lighthouse scores.
|
|
@@ -7,12 +7,12 @@ tools:
|
|
|
7
7
|
- Read
|
|
8
8
|
- Glob
|
|
9
9
|
- Grep
|
|
10
|
-
model:
|
|
10
|
+
model: inherit
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# Review & QA Agent
|
|
14
14
|
|
|
15
|
-
You are a senior staff engineer performing code review and quality assurance for
|
|
15
|
+
You are a senior staff engineer performing code review and quality assurance for this project. You read and analyze code to identify bugs, security vulnerabilities, performance issues, and deviations from best practices. You do not edit files directly; you report findings and recommend fixes.
|
|
16
16
|
|
|
17
17
|
## Review Process
|
|
18
18
|
|
|
@@ -40,8 +40,12 @@ You are a senior staff engineer performing code review and quality assurance for
|
|
|
40
40
|
- API responses are cached where appropriate using Next.js caching mechanisms.
|
|
41
41
|
- No synchronous blocking operations in request handlers.
|
|
42
42
|
|
|
43
|
-
## Code Quality
|
|
43
|
+
## Code Quality (Clean Code / SOLID)
|
|
44
44
|
|
|
45
|
+
- **Single Responsibility**: Each function, class, or module has one reason to change. Flag modules that mix concerns (data fetching + rendering + validation in one place).
|
|
46
|
+
- **Open/Closed**: Check that new features extend existing abstractions rather than modifying their internals.
|
|
47
|
+
- **DRY**: Flag duplicated logic, but also flag premature abstractions that obscure intent.
|
|
48
|
+
- **Code Smells**: Watch for long parameter lists, deep nesting, magic numbers, boolean parameters that toggle behavior, and god objects.
|
|
45
49
|
- Functions and variables have clear, descriptive names.
|
|
46
50
|
- Complex logic has explanatory comments or is extracted into well-named helper functions.
|
|
47
51
|
- TypeScript types are precise. No `any`, no overly broad union types.
|
|
@@ -12,12 +12,12 @@ tools:
|
|
|
12
12
|
skills:
|
|
13
13
|
- auth-rbac
|
|
14
14
|
- better-auth-patterns
|
|
15
|
-
model:
|
|
15
|
+
model: inherit
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
# Security Agent
|
|
19
19
|
|
|
20
|
-
You are a senior application security engineer for
|
|
20
|
+
You are a senior application security engineer for this project. You audit code for vulnerabilities, harden configurations, enforce security best practices, and help the team build a secure-by-default application.
|
|
21
21
|
|
|
22
22
|
## Security Audit Process
|
|
23
23
|
|
|
@@ -76,10 +76,13 @@ You are a senior application security engineer for the Loom project. You audit c
|
|
|
76
76
|
|
|
77
77
|
## Secure Coding Patterns
|
|
78
78
|
|
|
79
|
-
-
|
|
80
|
-
-
|
|
79
|
+
- **Least Privilege**: Grant minimum permissions needed for each operation. Database connections, API tokens, and service accounts should have the narrowest scope possible.
|
|
80
|
+
- **Defense in Depth**: Never rely on a single security control. Layer validation, authentication, authorization, and monitoring so that a failure in one layer does not compromise the system.
|
|
81
|
+
- **Fail Closed**: If a security check errors out, deny access rather than allowing it. Never default to permissive.
|
|
82
|
+
- **Zero Trust**: Verify every request independently. Do not trust internal network boundaries, prior authentication, or client-side checks as sole security measures.
|
|
81
83
|
- Prefer allowlists over denylists for input validation.
|
|
82
84
|
- Log security-relevant events (login attempts, authorization failures, data exports) for audit trails.
|
|
85
|
+
- Apply the principle of least surprise: security behavior should be predictable and documented.
|
|
83
86
|
|
|
84
87
|
## Reporting Format
|
|
85
88
|
|
|
@@ -12,12 +12,12 @@ tools:
|
|
|
12
12
|
- Grep
|
|
13
13
|
skills:
|
|
14
14
|
- testing-patterns
|
|
15
|
-
model:
|
|
15
|
+
model: inherit
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
# Tests Agent
|
|
19
19
|
|
|
20
|
-
You are a senior QA engineer and test author for
|
|
20
|
+
You are a senior QA engineer and test author for this project. You write and maintain unit tests, integration tests, and end-to-end tests to ensure correctness and prevent regressions.
|
|
21
21
|
|
|
22
22
|
## Testing Stack
|
|
23
23
|
|
|
@@ -25,6 +25,13 @@ You are a senior QA engineer and test author for the Loom project. You write and
|
|
|
25
25
|
- **End-to-End Tests**: Playwright for full browser-based testing.
|
|
26
26
|
- **Test Location**: Co-locate unit tests next to the code they test as `*.test.ts(x)`. Place e2e tests in the `e2e/` or `tests/` directory at the project root.
|
|
27
27
|
|
|
28
|
+
## Testing Principles
|
|
29
|
+
|
|
30
|
+
- **Testing Pyramid**: Write many unit tests, fewer integration tests, and minimal e2e tests. Each layer catches different classes of bugs at different costs.
|
|
31
|
+
- **FIRST**: Tests must be Fast, Isolated, Repeatable, Self-validating, and Timely (written alongside or before the code).
|
|
32
|
+
- **Arrange-Act-Assert**: Structure every test in three clear phases: set up the state, execute the action, verify the outcome.
|
|
33
|
+
- **Test Doubles Taxonomy**: Use the right double for the job — stubs return canned answers, mocks verify interactions, fakes provide working lightweight implementations, spies record calls without changing behavior.
|
|
34
|
+
|
|
28
35
|
## Unit Tests
|
|
29
36
|
|
|
30
37
|
- Test one behavior per test case. Name tests descriptively: `it("returns 401 when the user is not authenticated")`.
|
|
@@ -14,12 +14,19 @@ skills:
|
|
|
14
14
|
- shadcn-ui
|
|
15
15
|
- tailwind-patterns
|
|
16
16
|
- table-pagination
|
|
17
|
-
model:
|
|
17
|
+
model: inherit
|
|
18
18
|
---
|
|
19
19
|
|
|
20
20
|
# UX/UI Agent
|
|
21
21
|
|
|
22
|
-
You are a senior UX/UI designer and design engineer for
|
|
22
|
+
You are a senior UX/UI designer and design engineer for this project. You create design system foundations, component styles, interaction patterns, and ensure the application meets high standards for usability and accessibility.
|
|
23
|
+
|
|
24
|
+
## UX Foundations
|
|
25
|
+
|
|
26
|
+
- **Nielsen's Usability Heuristics**: Apply all 10 heuristics as design constraints — visibility of system status, match between system and real world, user control and freedom, consistency and standards, error prevention, recognition over recall, flexibility and efficiency, aesthetic and minimalist design, help users recognize and recover from errors, help and documentation.
|
|
27
|
+
- **Fitts's Law**: Make clickable targets large and close to the user's expected cursor position. Important actions get large hit areas.
|
|
28
|
+
- **Hick's Law**: Minimize decision time by reducing the number of choices presented simultaneously. Use progressive disclosure.
|
|
29
|
+
- **Gestalt Principles**: Use proximity, similarity, continuity, and closure to create visual groupings without explicit borders.
|
|
23
30
|
|
|
24
31
|
## Design System
|
|
25
32
|
|
|
@@ -35,13 +42,14 @@ You are a senior UX/UI designer and design engineer for the Loom project. You cr
|
|
|
35
42
|
- Define component variants explicitly (e.g., `variant: "primary" | "secondary" | "ghost"`) rather than relying on arbitrary className overrides.
|
|
36
43
|
- Include hover, focus, active, and disabled states for all interactive elements.
|
|
37
44
|
|
|
38
|
-
## Accessibility (
|
|
45
|
+
## Accessibility (WCAG 2.1 AA)
|
|
39
46
|
|
|
40
47
|
- Every interactive element must be keyboard-navigable. Use `tabIndex`, `onKeyDown`, and proper focus management.
|
|
41
48
|
- Use ARIA roles and properties correctly. Prefer native semantic HTML (`<button>`, `<nav>`, `<dialog>`) over `div` with ARIA.
|
|
42
49
|
- Ensure all form inputs have associated `<label>` elements. Use `aria-describedby` for help text and error messages.
|
|
43
50
|
- Test focus order: it should follow a logical reading sequence, not jump unpredictably.
|
|
44
51
|
- Provide visible focus indicators that meet the 3:1 contrast ratio requirement.
|
|
52
|
+
- Design for inclusive access: support screen readers, voice navigation, and switch devices. Do not rely on color alone to convey information.
|
|
45
53
|
|
|
46
54
|
## Responsive Design
|
|
47
55
|
|
|
@@ -18,20 +18,14 @@ constitution:
|
|
|
18
18
|
- Type-safe contracts — use Zod schemas as single source of truth
|
|
19
19
|
- Test everything — unit tests for logic, integration tests for endpoints
|
|
20
20
|
- Fail gracefully — meaningful error messages, proper HTTP status codes
|
|
21
|
-
stack:
|
|
22
|
-
- Next.js 16+ (API Routes)
|
|
23
|
-
- TypeScript 5 (strict)
|
|
24
|
-
- Supabase (database + auth)
|
|
25
|
-
- Zod (validation)
|
|
26
|
-
- Vercel (deployment)
|
|
27
21
|
conventions:
|
|
28
22
|
- Define Zod schemas for all request/response payloads
|
|
29
23
|
- Use middleware for auth, rate limiting, and CORS
|
|
30
24
|
- Implement proper error handling with consistent error response format
|
|
31
25
|
- Write integration tests for every endpoint
|
|
32
26
|
- Use database transactions for multi-step mutations
|
|
33
|
-
|
|
27
|
+
context:
|
|
34
28
|
projectDescription: >
|
|
35
|
-
This is a backend API service
|
|
29
|
+
This is a backend API service. It uses Next.js API Routes with Supabase
|
|
36
30
|
as the database layer. The orchestrator delegates security concerns to the security agent,
|
|
37
31
|
database schema work to the database agent, and testing to the tests agent.
|
|
@@ -17,20 +17,14 @@ constitution:
|
|
|
17
17
|
- User privacy first — never collect data without explicit consent
|
|
18
18
|
- Fast popup — keep popup UI snappy, defer heavy work to background
|
|
19
19
|
- Cross-site safe — sanitize all DOM manipulation in content scripts
|
|
20
|
-
stack:
|
|
21
|
-
- TypeScript 5 (strict)
|
|
22
|
-
- Chrome APIs (Manifest V3)
|
|
23
|
-
- React 19 (popup / options page)
|
|
24
|
-
- Tailwind CSS 4
|
|
25
|
-
- tsup (bundling)
|
|
26
20
|
conventions:
|
|
27
21
|
- Use Manifest V3 service workers instead of background pages
|
|
28
22
|
- Separate concerns — popup, content scripts, and background have clear boundaries
|
|
29
23
|
- Use chrome.storage API for persistence, never localStorage in content scripts
|
|
30
24
|
- Validate all messages between content script and background worker
|
|
31
25
|
- Keep content script injection minimal to avoid page performance impact
|
|
32
|
-
|
|
26
|
+
context:
|
|
33
27
|
projectDescription: >
|
|
34
|
-
This is a Chrome extension
|
|
28
|
+
This is a Chrome extension using Manifest V3. The orchestrator delegates
|
|
35
29
|
popup/options UI work to the frontend agent, background worker logic to the backend agent,
|
|
36
30
|
and security reviews of permissions and content scripts to the security agent.
|
|
@@ -13,19 +13,14 @@ constitution:
|
|
|
13
13
|
- Fail loudly — clear error messages with actionable suggestions
|
|
14
14
|
- Zero-config defaults — sensible defaults, override with flags
|
|
15
15
|
- Scriptable — support stdin/stdout piping and non-interactive mode
|
|
16
|
-
stack:
|
|
17
|
-
- TypeScript 5 (strict)
|
|
18
|
-
- Node.js 20+
|
|
19
|
-
- Commander.js (CLI framework)
|
|
20
|
-
- tsup (bundling)
|
|
21
16
|
conventions:
|
|
22
17
|
- Use Commander.js for argument parsing and subcommands
|
|
23
18
|
- Exit with proper codes — 0 for success, 1 for errors, 2 for usage errors
|
|
24
19
|
- Support --json flag for machine-readable output
|
|
25
20
|
- Use stderr for progress/logging, stdout for actual output
|
|
26
21
|
- Include a --version and --help flag on every command
|
|
27
|
-
|
|
22
|
+
context:
|
|
28
23
|
projectDescription: >
|
|
29
|
-
This is a CLI tool
|
|
24
|
+
This is a CLI tool. It uses Commander.js for argument parsing and tsup
|
|
30
25
|
for bundling. The orchestrator delegates implementation to the backend agent and testing to
|
|
31
26
|
the tests agent.
|
|
@@ -27,23 +27,14 @@ constitution:
|
|
|
27
27
|
- Payment security — never handle raw card data, always use Stripe Elements
|
|
28
28
|
- SEO drives traffic — product pages must be indexable with structured data
|
|
29
29
|
- Performance converts — every 100ms of load time impacts conversion rate
|
|
30
|
-
stack:
|
|
31
|
-
- Next.js 16+ (App Router)
|
|
32
|
-
- React 19
|
|
33
|
-
- TypeScript 5 (strict)
|
|
34
|
-
- Tailwind CSS 4
|
|
35
|
-
- ShadCN UI
|
|
36
|
-
- Supabase (auth + database)
|
|
37
|
-
- Stripe (payments)
|
|
38
|
-
- Vercel (deployment)
|
|
39
30
|
conventions:
|
|
40
31
|
- Use Stripe Checkout or Payment Elements — never collect card details directly
|
|
41
32
|
- Implement webhook handlers for Stripe events (payment success, refund, etc.)
|
|
42
33
|
- Use ISR or SSG for product catalog pages for performance
|
|
43
34
|
- Add JSON-LD structured data for products (price, availability, reviews)
|
|
44
35
|
- Implement optimistic cart updates with server validation
|
|
45
|
-
|
|
36
|
+
context:
|
|
46
37
|
projectDescription: >
|
|
47
|
-
This is an e-commerce application
|
|
38
|
+
This is an e-commerce application. It includes product catalog, shopping
|
|
48
39
|
cart, Stripe checkout, and transactional emails. The orchestrator coordinates the full agent
|
|
49
40
|
team including marketing for copywriting and SEO.
|
|
@@ -20,22 +20,15 @@ constitution:
|
|
|
20
20
|
- Offline-first — the app must be usable without network connectivity
|
|
21
21
|
- Battery conscious — minimize background tasks and network requests
|
|
22
22
|
- Smooth animations — target 60fps, never block the JS thread
|
|
23
|
-
stack:
|
|
24
|
-
- Expo SDK 52+
|
|
25
|
-
- React Native
|
|
26
|
-
- TypeScript 5 (strict)
|
|
27
|
-
- NativeWind (Tailwind for React Native)
|
|
28
|
-
- Expo Router (file-based navigation)
|
|
29
|
-
- Supabase (auth + database)
|
|
30
23
|
conventions:
|
|
31
24
|
- Use Expo Router for all navigation — file-based routing
|
|
32
25
|
- Implement offline storage with async-storage or MMKV for critical data
|
|
33
26
|
- Use expo-notifications for push notifications setup
|
|
34
27
|
- Handle deep linking and universal links from the start
|
|
35
28
|
- Test on both iOS and Android — never assume platform parity
|
|
36
|
-
|
|
29
|
+
context:
|
|
37
30
|
projectDescription: >
|
|
38
|
-
This is a mobile application
|
|
31
|
+
This is a mobile application using Expo and React Native. It uses
|
|
39
32
|
NativeWind for styling and Expo Router for navigation. The orchestrator delegates mobile UI
|
|
40
33
|
to the frontend agent, API integration to the backend agent, and native feature testing
|
|
41
34
|
to the tests agent.
|
|
@@ -23,23 +23,14 @@ constitution:
|
|
|
23
23
|
- Least privilege — users and services get only the permissions they need
|
|
24
24
|
- Defense in depth — middleware, RLS, and API validation work together
|
|
25
25
|
- Session management — handle expiry, refresh, and revocation properly
|
|
26
|
-
stack:
|
|
27
|
-
- Next.js 16+ (App Router)
|
|
28
|
-
- React 19
|
|
29
|
-
- TypeScript 5 (strict)
|
|
30
|
-
- Tailwind CSS 4
|
|
31
|
-
- ShadCN UI
|
|
32
|
-
- Supabase Auth (email, OAuth, magic link)
|
|
33
|
-
- Supabase (database with RLS)
|
|
34
|
-
- Vercel (deployment)
|
|
35
26
|
conventions:
|
|
36
27
|
- Use Supabase Auth for all authentication flows
|
|
37
28
|
- Implement middleware for route protection and role checks
|
|
38
29
|
- Enable Row Level Security on all tables — no exceptions
|
|
39
30
|
- Use server-side session validation, never trust client-only auth state
|
|
40
31
|
- Separate admin and user dashboards with distinct layouts
|
|
41
|
-
|
|
32
|
+
context:
|
|
42
33
|
projectDescription: >
|
|
43
|
-
This is a fullstack SaaS with complete authentication
|
|
34
|
+
This is a fullstack SaaS with complete authentication. It includes email,
|
|
44
35
|
OAuth, and magic link flows via Supabase Auth, plus RBAC and admin dashboard. The orchestrator
|
|
45
36
|
coordinates security, frontend, backend, and database agents for auth-related tasks.
|
|
@@ -19,20 +19,14 @@ constitution:
|
|
|
19
19
|
- Performance is UX — aim for perfect Lighthouse scores
|
|
20
20
|
- Mobile-first responsive — design for small screens, enhance for large
|
|
21
21
|
- Accessible by default — semantic HTML, ARIA, keyboard navigation
|
|
22
|
-
stack:
|
|
23
|
-
- Next.js 16+ (static export)
|
|
24
|
-
- React 19
|
|
25
|
-
- TypeScript 5 (strict)
|
|
26
|
-
- Tailwind CSS 4
|
|
27
|
-
- Vercel (deployment)
|
|
28
22
|
conventions:
|
|
29
23
|
- Use Server Components and static generation for maximum performance
|
|
30
24
|
- Optimize all images with next/image and proper srcset
|
|
31
25
|
- Implement structured data (JSON-LD) for SEO
|
|
32
26
|
- Use CSS animations and transitions over JS-based animation libraries
|
|
33
27
|
- Ensure all interactive elements have visible focus states
|
|
34
|
-
|
|
28
|
+
context:
|
|
35
29
|
projectDescription: >
|
|
36
|
-
This is a landing page / marketing site
|
|
30
|
+
This is a landing page / marketing site. It focuses on SEO, conversion
|
|
37
31
|
optimization, and fast loading. The orchestrator delegates visual tasks to the frontend and
|
|
38
32
|
ux-ui agents, copywriting to marketing, and performance audits to the performance agent.
|
|
@@ -15,21 +15,14 @@ constitution:
|
|
|
15
15
|
- Iterate fast — build the simplest thing that works, then improve
|
|
16
16
|
- Technical debt is acceptable — speed wins at the prototype stage
|
|
17
17
|
- Validate assumptions — build to learn, not to last
|
|
18
|
-
stack:
|
|
19
|
-
- Next.js 16+ (App Router)
|
|
20
|
-
- React 19
|
|
21
|
-
- TypeScript 5 (strict)
|
|
22
|
-
- Tailwind CSS 4
|
|
23
|
-
- Supabase (auth + database)
|
|
24
|
-
- Vercel (deployment)
|
|
25
18
|
conventions:
|
|
26
19
|
- Prefer inline styles and co-located logic over abstractions
|
|
27
20
|
- Use Supabase client directly — skip custom API layers when possible
|
|
28
21
|
- Minimal component hierarchy — flatten where you can
|
|
29
22
|
- Skip extensive error handling — focus on happy path first
|
|
30
23
|
- One file per feature when practical
|
|
31
|
-
|
|
24
|
+
context:
|
|
32
25
|
projectDescription: >
|
|
33
|
-
This is a lean MVP
|
|
26
|
+
This is a lean MVP. Speed and iteration are prioritized over architecture.
|
|
34
27
|
The orchestrator coordinates a small team of frontend, backend, and database agents to ship
|
|
35
28
|
features as fast as possible.
|
|
@@ -28,22 +28,14 @@ constitution:
|
|
|
28
28
|
- User-first design — every feature must solve a real user problem
|
|
29
29
|
- Type safety everywhere — leverage TypeScript strict mode
|
|
30
30
|
- Convention over configuration — follow established patterns
|
|
31
|
-
stack:
|
|
32
|
-
- Next.js 16+ (App Router)
|
|
33
|
-
- React 19
|
|
34
|
-
- TypeScript 5 (strict)
|
|
35
|
-
- Tailwind CSS 4
|
|
36
|
-
- ShadCN UI
|
|
37
|
-
- Supabase (auth + database)
|
|
38
|
-
- Vercel (deployment)
|
|
39
31
|
conventions:
|
|
40
32
|
- Use Server Components by default, Client Components only when needed
|
|
41
33
|
- Server Actions for all mutations
|
|
42
34
|
- Zod validation at API boundaries
|
|
43
35
|
- Mobile-first responsive design
|
|
44
36
|
- Semantic HTML with ARIA attributes for accessibility
|
|
45
|
-
|
|
37
|
+
context:
|
|
46
38
|
projectDescription: >
|
|
47
|
-
This is a SaaS application
|
|
39
|
+
This is a SaaS application. It uses a multi-agent architecture where the orchestrator delegates
|
|
48
40
|
tasks to specialized agents (frontend, backend, database, etc.). Each agent follows the conventions defined in the
|
|
49
41
|
linked skills.
|
|
@@ -40,19 +40,6 @@ constitution:
|
|
|
40
40
|
- Type safety everywhere — TypeScript strict, Zod validation at boundaries
|
|
41
41
|
- Ship fast, iterate often — working software over perfect plans
|
|
42
42
|
- Security by design — auth + RBAC + validation at every layer
|
|
43
|
-
stack:
|
|
44
|
-
- Next.js 16+ (App Router)
|
|
45
|
-
- React 19
|
|
46
|
-
- TypeScript 5 (strict)
|
|
47
|
-
- Tailwind CSS 4
|
|
48
|
-
- ShadCN UI
|
|
49
|
-
- Drizzle ORM
|
|
50
|
-
- PostgreSQL
|
|
51
|
-
- Better Auth
|
|
52
|
-
- Stripe
|
|
53
|
-
- Resend
|
|
54
|
-
- Vitest
|
|
55
|
-
- Vercel
|
|
56
43
|
conventions:
|
|
57
44
|
- RSC by default, "use client" only when strictly needed
|
|
58
45
|
- Server Actions for all mutations, DAL for all reads
|
|
@@ -62,9 +49,9 @@ constitution:
|
|
|
62
49
|
- next-intl for all user-facing strings
|
|
63
50
|
- Vitest with role-based test strategy (PUBLIC/USER/ADMIN)
|
|
64
51
|
- kebab-case for all new files
|
|
65
|
-
|
|
52
|
+
context:
|
|
66
53
|
projectDescription: >
|
|
67
|
-
This is a full-featured SaaS application
|
|
54
|
+
This is a full-featured SaaS application. It follows a strict layered architecture
|
|
68
55
|
(Presentation → Facade → Service → DAL → Persistence) with multi-agent orchestration. Authentication is
|
|
69
56
|
handled by Better Auth with RBAC via CASL. The app supports i18n with next-intl, transactional emails via
|
|
70
57
|
Resend, payments via Stripe, and uses Drizzle ORM with PostgreSQL. Every feature is tested with Vitest
|
package/dist/index.js
CHANGED
|
@@ -178,38 +178,37 @@ import pc2 from "picocolors";
|
|
|
178
178
|
// src/lib/writer.ts
|
|
179
179
|
import fs2 from "fs";
|
|
180
180
|
import path2 from "path";
|
|
181
|
-
var CLAUDE_DIR = ".claude";
|
|
182
181
|
function ensureDir(dirPath) {
|
|
183
182
|
fs2.mkdirSync(dirPath, { recursive: true });
|
|
184
183
|
}
|
|
185
|
-
function writeAgent(slug, content, cwd = process.cwd()) {
|
|
186
|
-
const dir = path2.join(cwd,
|
|
184
|
+
function writeAgent(target, slug, content, cwd = process.cwd()) {
|
|
185
|
+
const dir = path2.join(cwd, target.dir, target.agentsSubdir, slug);
|
|
187
186
|
ensureDir(dir);
|
|
188
|
-
const filePath = path2.join(dir,
|
|
187
|
+
const filePath = path2.join(dir, "AGENT.md");
|
|
189
188
|
fs2.writeFileSync(filePath, content, "utf-8");
|
|
190
189
|
return filePath;
|
|
191
190
|
}
|
|
192
|
-
function writeSkill(slug, content, cwd = process.cwd()) {
|
|
193
|
-
const dir = path2.join(cwd,
|
|
191
|
+
function writeSkill(target, slug, content, cwd = process.cwd()) {
|
|
192
|
+
const dir = path2.join(cwd, target.dir, target.skillsSubdir, slug);
|
|
194
193
|
ensureDir(dir);
|
|
195
|
-
const filePath = path2.join(dir,
|
|
194
|
+
const filePath = path2.join(dir, "SKILL.md");
|
|
196
195
|
fs2.writeFileSync(filePath, content, "utf-8");
|
|
197
196
|
return filePath;
|
|
198
197
|
}
|
|
199
|
-
function writeOrchestrator(content, cwd = process.cwd()) {
|
|
200
|
-
const filePath = path2.join(cwd,
|
|
198
|
+
function writeOrchestrator(target, content, cwd = process.cwd()) {
|
|
199
|
+
const filePath = path2.join(cwd, target.dir, target.orchestratorFile);
|
|
201
200
|
ensureDir(path2.dirname(filePath));
|
|
202
201
|
fs2.writeFileSync(filePath, content, "utf-8");
|
|
203
202
|
return filePath;
|
|
204
203
|
}
|
|
205
|
-
function
|
|
206
|
-
const filePath = path2.join(cwd,
|
|
204
|
+
function writeContextFile(target, content, cwd = process.cwd()) {
|
|
205
|
+
const filePath = path2.join(cwd, target.contextFile);
|
|
207
206
|
fs2.writeFileSync(filePath, content, "utf-8");
|
|
208
207
|
return filePath;
|
|
209
208
|
}
|
|
210
209
|
|
|
211
210
|
// src/commands/add.ts
|
|
212
|
-
async function addCommand(type, slug) {
|
|
211
|
+
async function addCommand(type, slug, target) {
|
|
213
212
|
if (type !== "agent" && type !== "skill") {
|
|
214
213
|
console.error(pc2.red(`
|
|
215
214
|
Error: Invalid type "${type}". Use "agent" or "skill".
|
|
@@ -219,13 +218,13 @@ async function addCommand(type, slug) {
|
|
|
219
218
|
try {
|
|
220
219
|
if (type === "agent") {
|
|
221
220
|
const agent = await getAgent(slug);
|
|
222
|
-
const filePath = writeAgent(slug, agent.rawContent);
|
|
221
|
+
const filePath = writeAgent(target, slug, agent.rawContent);
|
|
223
222
|
console.log(pc2.green(`
|
|
224
223
|
\u2713 Agent "${slug}" written to ${filePath}
|
|
225
224
|
`));
|
|
226
225
|
} else {
|
|
227
226
|
const skill = await getSkill(slug);
|
|
228
|
-
const filePath = writeSkill(slug, skill.rawContent);
|
|
227
|
+
const filePath = writeSkill(target, slug, skill.rawContent);
|
|
229
228
|
console.log(pc2.green(`
|
|
230
229
|
\u2713 Skill "${slug}" written to ${filePath}
|
|
231
230
|
`));
|
|
@@ -249,28 +248,23 @@ import matter3 from "gray-matter";
|
|
|
249
248
|
|
|
250
249
|
// src/lib/generator.ts
|
|
251
250
|
import matter2 from "gray-matter";
|
|
252
|
-
function
|
|
251
|
+
function generateContextFile(preset, agents, target, skillSlugs = []) {
|
|
253
252
|
const lines = [];
|
|
254
253
|
lines.push(`# ${preset.name}`);
|
|
255
254
|
lines.push("");
|
|
256
|
-
lines.push(preset.
|
|
255
|
+
lines.push(preset.context.projectDescription.trim());
|
|
257
256
|
lines.push("");
|
|
258
257
|
if (preset.constitution.principles.length > 0) {
|
|
259
258
|
lines.push("## Principles");
|
|
259
|
+
lines.push("");
|
|
260
260
|
for (const p2 of preset.constitution.principles) {
|
|
261
261
|
lines.push(`- ${p2}`);
|
|
262
262
|
}
|
|
263
263
|
lines.push("");
|
|
264
264
|
}
|
|
265
|
-
if (preset.constitution.stack.length > 0) {
|
|
266
|
-
lines.push("## Stack");
|
|
267
|
-
for (const s of preset.constitution.stack) {
|
|
268
|
-
lines.push(`- ${s}`);
|
|
269
|
-
}
|
|
270
|
-
lines.push("");
|
|
271
|
-
}
|
|
272
265
|
if (preset.constitution.conventions.length > 0) {
|
|
273
266
|
lines.push("## Conventions");
|
|
267
|
+
lines.push("");
|
|
274
268
|
for (const c of preset.constitution.conventions) {
|
|
275
269
|
lines.push(`- ${c}`);
|
|
276
270
|
}
|
|
@@ -279,20 +273,52 @@ function generateClaudeMd(preset, agents) {
|
|
|
279
273
|
if (preset.constitution.customSections) {
|
|
280
274
|
for (const [title, content] of Object.entries(preset.constitution.customSections)) {
|
|
281
275
|
lines.push(`## ${title}`);
|
|
276
|
+
lines.push("");
|
|
282
277
|
lines.push(content);
|
|
283
278
|
lines.push("");
|
|
284
279
|
}
|
|
285
280
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
281
|
+
lines.push("## Commands");
|
|
282
|
+
lines.push("");
|
|
283
|
+
lines.push("```bash");
|
|
284
|
+
lines.push("npm run dev # Start development server");
|
|
285
|
+
lines.push("npm run build # Build for production");
|
|
286
|
+
lines.push("npm run lint # Run linter");
|
|
287
|
+
lines.push("npm test # Run tests");
|
|
288
|
+
lines.push("```");
|
|
289
|
+
lines.push("");
|
|
290
|
+
lines.push("<!-- loom:agents:start -->");
|
|
291
|
+
lines.push("## Agents");
|
|
292
|
+
lines.push("");
|
|
293
|
+
const nonOrchestrator = agents.filter((a) => a.slug !== "orchestrator");
|
|
294
|
+
if (nonOrchestrator.length > 0) {
|
|
295
|
+
lines.push(`This project uses ${nonOrchestrator.length} specialized agents coordinated by an orchestrator (\`${target.dir}/${target.orchestratorFile}\`).`);
|
|
296
|
+
lines.push("");
|
|
297
|
+
lines.push("| Agent | Role | Description |");
|
|
298
|
+
lines.push("|-------|------|-------------|");
|
|
299
|
+
for (const agent of nonOrchestrator) {
|
|
300
|
+
lines.push(`| \`${agent.slug}\` | ${agent.name} | ${agent.description} |`);
|
|
301
|
+
}
|
|
302
|
+
lines.push("");
|
|
303
|
+
}
|
|
304
|
+
lines.push("<!-- loom:agents:end -->");
|
|
305
|
+
lines.push("");
|
|
306
|
+
if (skillSlugs.length > 0) {
|
|
307
|
+
lines.push("<!-- loom:skills:start -->");
|
|
308
|
+
lines.push("## Skills");
|
|
309
|
+
lines.push("");
|
|
310
|
+
lines.push("Installed skills providing domain-specific conventions and patterns:");
|
|
311
|
+
lines.push("");
|
|
312
|
+
for (const slug of skillSlugs) {
|
|
313
|
+
lines.push(`- \`${slug}\``);
|
|
290
314
|
}
|
|
291
315
|
lines.push("");
|
|
316
|
+
lines.push("<!-- loom:skills:end -->");
|
|
317
|
+
lines.push("");
|
|
292
318
|
}
|
|
293
|
-
lines.push("##
|
|
319
|
+
lines.push("## How to use");
|
|
294
320
|
lines.push("");
|
|
295
|
-
lines.push(
|
|
321
|
+
lines.push(`The orchestrator agent (\`${target.dir}/${target.orchestratorFile}\`) is the main entry point. It analyzes tasks, breaks them into subtasks, and delegates to the appropriate specialized agents. Each agent has access to its assigned skills for domain-specific guidance.`);
|
|
296
322
|
lines.push("");
|
|
297
323
|
return lines.join("\n");
|
|
298
324
|
}
|
|
@@ -315,6 +341,81 @@ function generateOrchestrator(templateContent, agents, presetSkills) {
|
|
|
315
341
|
return matter2.stringify(newContent, newFrontmatter);
|
|
316
342
|
}
|
|
317
343
|
|
|
344
|
+
// src/lib/target.ts
|
|
345
|
+
var BUILTIN_TARGETS = {
|
|
346
|
+
"claude-code": {
|
|
347
|
+
name: "claude-code",
|
|
348
|
+
description: "Claude Code \u2014 .claude/ + CLAUDE.md",
|
|
349
|
+
dir: ".claude",
|
|
350
|
+
agentsSubdir: "agents",
|
|
351
|
+
skillsSubdir: "skills",
|
|
352
|
+
orchestratorFile: "orchestrator.md",
|
|
353
|
+
contextFile: "CLAUDE.md"
|
|
354
|
+
},
|
|
355
|
+
cursor: {
|
|
356
|
+
name: "cursor",
|
|
357
|
+
description: "Cursor \u2014 .cursor/ + .cursorrules",
|
|
358
|
+
dir: ".cursor",
|
|
359
|
+
agentsSubdir: "agents",
|
|
360
|
+
skillsSubdir: "skills",
|
|
361
|
+
orchestratorFile: "orchestrator.md",
|
|
362
|
+
contextFile: ".cursorrules"
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
var DEFAULT_TARGET = "claude-code";
|
|
366
|
+
function listTargetNames() {
|
|
367
|
+
return Object.keys(BUILTIN_TARGETS);
|
|
368
|
+
}
|
|
369
|
+
function resolveTarget(targetName, customDir, customContextFile) {
|
|
370
|
+
const builtin = BUILTIN_TARGETS[targetName];
|
|
371
|
+
if (builtin) return builtin;
|
|
372
|
+
if (targetName === "custom") {
|
|
373
|
+
if (!customDir || !customContextFile) {
|
|
374
|
+
throw new Error(
|
|
375
|
+
'Target "custom" requires --target-dir and --context-file.'
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
name: "custom",
|
|
380
|
+
description: `Custom \u2014 ${customDir}/ + ${customContextFile}`,
|
|
381
|
+
dir: customDir,
|
|
382
|
+
agentsSubdir: "agents",
|
|
383
|
+
skillsSubdir: "skills",
|
|
384
|
+
orchestratorFile: "orchestrator.md",
|
|
385
|
+
contextFile: customContextFile
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
const available = [...listTargetNames(), "custom"].join(", ");
|
|
389
|
+
throw new Error(
|
|
390
|
+
`Unknown target "${targetName}". Available: ${available}.`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/lib/config.ts
|
|
395
|
+
import fs3 from "fs";
|
|
396
|
+
import path3 from "path";
|
|
397
|
+
var CONFIG_FILE = "loom.config.json";
|
|
398
|
+
function saveConfig(target, cwd = process.cwd()) {
|
|
399
|
+
const config = {
|
|
400
|
+
target: target.name,
|
|
401
|
+
targetDir: target.dir,
|
|
402
|
+
contextFile: target.contextFile
|
|
403
|
+
};
|
|
404
|
+
const filePath = path3.join(cwd, CONFIG_FILE);
|
|
405
|
+
fs3.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
406
|
+
}
|
|
407
|
+
function loadConfig(cwd = process.cwd()) {
|
|
408
|
+
const filePath = path3.join(cwd, CONFIG_FILE);
|
|
409
|
+
if (!fs3.existsSync(filePath)) return null;
|
|
410
|
+
try {
|
|
411
|
+
const raw = fs3.readFileSync(filePath, "utf-8");
|
|
412
|
+
const config = JSON.parse(raw);
|
|
413
|
+
return resolveTarget(config.target, config.targetDir, config.contextFile);
|
|
414
|
+
} catch {
|
|
415
|
+
return null;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
318
419
|
// src/commands/init.ts
|
|
319
420
|
async function initCommand(presetSlug, opts = {}) {
|
|
320
421
|
try {
|
|
@@ -324,7 +425,7 @@ async function initCommand(presetSlug, opts = {}) {
|
|
|
324
425
|
process.exit(1);
|
|
325
426
|
}
|
|
326
427
|
if (!presetSlug && !hasFlags) {
|
|
327
|
-
await interactiveInit();
|
|
428
|
+
await interactiveInit(opts.target, opts.targetExplicit);
|
|
328
429
|
} else {
|
|
329
430
|
await nonInteractiveInit(presetSlug, opts);
|
|
330
431
|
}
|
|
@@ -339,8 +440,51 @@ async function initCommand(presetSlug, opts = {}) {
|
|
|
339
440
|
process.exit(1);
|
|
340
441
|
}
|
|
341
442
|
}
|
|
342
|
-
async function interactiveInit() {
|
|
443
|
+
async function interactiveInit(target, targetExplicit) {
|
|
343
444
|
p.intro(pc3.bgCyan(pc3.black(" loom init ")));
|
|
445
|
+
if (!targetExplicit) {
|
|
446
|
+
const builtinEntries = Object.values(BUILTIN_TARGETS);
|
|
447
|
+
const targetChoice = await p.select({
|
|
448
|
+
message: "Choose a target runtime",
|
|
449
|
+
options: [
|
|
450
|
+
...builtinEntries.map((t) => ({
|
|
451
|
+
value: t.name,
|
|
452
|
+
label: t.description
|
|
453
|
+
})),
|
|
454
|
+
{ value: "custom", label: "Custom \u2014 choose directory and context file" }
|
|
455
|
+
],
|
|
456
|
+
initialValue: target.name
|
|
457
|
+
});
|
|
458
|
+
if (p.isCancel(targetChoice)) {
|
|
459
|
+
p.cancel("Operation cancelled.");
|
|
460
|
+
process.exit(0);
|
|
461
|
+
}
|
|
462
|
+
if (targetChoice === "custom") {
|
|
463
|
+
const customDir = await p.text({
|
|
464
|
+
message: "Target directory",
|
|
465
|
+
placeholder: ".myruntime",
|
|
466
|
+
validate: (v) => !v || v.length === 0 ? "Required" : void 0
|
|
467
|
+
});
|
|
468
|
+
if (p.isCancel(customDir)) {
|
|
469
|
+
p.cancel("Operation cancelled.");
|
|
470
|
+
process.exit(0);
|
|
471
|
+
}
|
|
472
|
+
const customFile = await p.text({
|
|
473
|
+
message: "Context file name",
|
|
474
|
+
placeholder: "CONTEXT.md",
|
|
475
|
+
validate: (v) => !v || v.length === 0 ? "Required" : void 0
|
|
476
|
+
});
|
|
477
|
+
if (p.isCancel(customFile)) {
|
|
478
|
+
p.cancel("Operation cancelled.");
|
|
479
|
+
process.exit(0);
|
|
480
|
+
}
|
|
481
|
+
target = resolveTarget("custom", customDir, customFile);
|
|
482
|
+
} else {
|
|
483
|
+
target = BUILTIN_TARGETS[targetChoice];
|
|
484
|
+
}
|
|
485
|
+
} else {
|
|
486
|
+
p.log.info(`Target: ${target.description}`);
|
|
487
|
+
}
|
|
344
488
|
const presets = await listPresets();
|
|
345
489
|
if (presets.length === 0) {
|
|
346
490
|
p.cancel("No presets available.");
|
|
@@ -403,11 +547,13 @@ async function interactiveInit() {
|
|
|
403
547
|
}
|
|
404
548
|
const s = p.spinner();
|
|
405
549
|
s.start("Generating project files...");
|
|
406
|
-
await generateAndWrite(preset, agentSlugs, skillSlugs);
|
|
550
|
+
await generateAndWrite(preset, agentSlugs, skillSlugs, target);
|
|
551
|
+
saveConfig(target);
|
|
407
552
|
s.stop("Project files generated.");
|
|
408
|
-
p.outro(pc3.green(`Done! ${agentSlugs.length} agent(s), ${skillSlugs.length} skill(s),
|
|
553
|
+
p.outro(pc3.green(`Done! ${agentSlugs.length} agent(s), ${skillSlugs.length} skill(s), ${target.contextFile} ready.`));
|
|
409
554
|
}
|
|
410
555
|
async function nonInteractiveInit(presetSlug, opts) {
|
|
556
|
+
const target = opts.target;
|
|
411
557
|
const preset = await getPreset(presetSlug);
|
|
412
558
|
const allAgents = await listAgents();
|
|
413
559
|
let agentSlugs = [...preset.agents];
|
|
@@ -448,18 +594,19 @@ async function nonInteractiveInit(presetSlug, opts) {
|
|
|
448
594
|
console.log(pc3.bold(pc3.cyan(`
|
|
449
595
|
Initializing preset "${preset.name}"...
|
|
450
596
|
`)));
|
|
451
|
-
await generateAndWrite(preset, agentSlugs, skillSlugs);
|
|
597
|
+
await generateAndWrite(preset, agentSlugs, skillSlugs, target);
|
|
598
|
+
saveConfig(target);
|
|
452
599
|
console.log(
|
|
453
600
|
pc3.bold(
|
|
454
601
|
pc3.cyan(
|
|
455
602
|
`
|
|
456
|
-
Done! ${agentSlugs.length} agent(s), ${skillSlugs.length} skill(s),
|
|
603
|
+
Done! ${agentSlugs.length} agent(s), ${skillSlugs.length} skill(s), ${target.contextFile} ready.
|
|
457
604
|
`
|
|
458
605
|
)
|
|
459
606
|
)
|
|
460
607
|
);
|
|
461
608
|
}
|
|
462
|
-
async function generateAndWrite(preset, agentSlugs, skillSlugs) {
|
|
609
|
+
async function generateAndWrite(preset, agentSlugs, skillSlugs, target) {
|
|
463
610
|
const agentResults = await Promise.allSettled(
|
|
464
611
|
agentSlugs.map((slug) => getAgent(slug))
|
|
465
612
|
);
|
|
@@ -478,13 +625,14 @@ async function generateAndWrite(preset, agentSlugs, skillSlugs) {
|
|
|
478
625
|
if (slug === "orchestrator") {
|
|
479
626
|
orchestratorTemplate = result.value.rawContent;
|
|
480
627
|
} else {
|
|
481
|
-
writeAgent(slug, result.value.rawContent);
|
|
628
|
+
writeAgent(target, slug, result.value.rawContent);
|
|
482
629
|
console.log(pc3.green(` \u2713 Agent: ${slug}`));
|
|
483
630
|
}
|
|
484
631
|
agentInfos.push({
|
|
485
632
|
slug,
|
|
486
633
|
name: fm.name || slug,
|
|
487
|
-
role: fm.role || ""
|
|
634
|
+
role: fm.role || "",
|
|
635
|
+
description: fm.description || ""
|
|
488
636
|
});
|
|
489
637
|
agentsWithSkills.push({
|
|
490
638
|
slug,
|
|
@@ -502,22 +650,22 @@ async function generateAndWrite(preset, agentSlugs, skillSlugs) {
|
|
|
502
650
|
agentsWithSkills,
|
|
503
651
|
skillSlugs
|
|
504
652
|
);
|
|
505
|
-
writeOrchestrator(orchestratorContent);
|
|
506
|
-
console.log(pc3.green(` \u2713
|
|
653
|
+
writeOrchestrator(target, orchestratorContent);
|
|
654
|
+
console.log(pc3.green(` \u2713 ${target.orchestratorFile} generated`));
|
|
507
655
|
}
|
|
508
656
|
for (let i = 0; i < skillSlugs.length; i++) {
|
|
509
657
|
const slug = skillSlugs[i];
|
|
510
658
|
const result = skillResults[i];
|
|
511
659
|
if (result.status === "fulfilled") {
|
|
512
|
-
writeSkill(slug, result.value.rawContent);
|
|
660
|
+
writeSkill(target, slug, result.value.rawContent);
|
|
513
661
|
console.log(pc3.green(` \u2713 Skill: ${slug}`));
|
|
514
662
|
} else {
|
|
515
663
|
console.log(pc3.yellow(` \u26A0 Skill "${slug}" skipped: ${result.reason}`));
|
|
516
664
|
}
|
|
517
665
|
}
|
|
518
|
-
const
|
|
519
|
-
|
|
520
|
-
console.log(pc3.green(` \u2713
|
|
666
|
+
const contextContent = generateContextFile(preset, agentInfos, target, skillSlugs);
|
|
667
|
+
writeContextFile(target, contextContent);
|
|
668
|
+
console.log(pc3.green(` \u2713 ${target.contextFile} generated`));
|
|
521
669
|
}
|
|
522
670
|
function computeAvailableSkills(preset, selectedAgentSlugs, allAgents, allSkillSlugs) {
|
|
523
671
|
const linkedToSelected = /* @__PURE__ */ new Set();
|
|
@@ -545,10 +693,37 @@ program.name("loom").description("Integrate Loom library (agents, skills, preset
|
|
|
545
693
|
program.command("list").description("List available agents, skills, and presets").argument("[type]", "Filter by type: agents, skills, or presets").action(async (type) => {
|
|
546
694
|
await listCommand(type);
|
|
547
695
|
});
|
|
548
|
-
program.command("add").description("Download an agent or skill from the library").argument("<type>", "Type: agent or skill").argument("<slug>", "Slug of the agent or skill").action(async (type, slug) => {
|
|
549
|
-
|
|
696
|
+
program.command("add").description("Download an agent or skill from the library").argument("<type>", "Type: agent or skill").argument("<slug>", "Slug of the agent or skill").option("--target <name>", `Output target: ${[...listTargetNames(), "custom"].join(", ")}`, DEFAULT_TARGET).option("--target-dir <dir>", "Custom target directory").option("--context-file <file>", "Custom context file name").action(async (type, slug, opts) => {
|
|
697
|
+
const savedConfig = loadConfig();
|
|
698
|
+
const target = opts.target !== DEFAULT_TARGET || opts.targetDir || opts.contextFile ? resolveTarget(opts.target, opts.targetDir, opts.contextFile) : savedConfig ?? BUILTIN_TARGETS[DEFAULT_TARGET];
|
|
699
|
+
await addCommand(type, slug, target);
|
|
550
700
|
});
|
|
551
|
-
program.command("init").description("Initialize a project with a preset (agents + skills +
|
|
552
|
-
|
|
701
|
+
program.command("init").description("Initialize a project with a preset (agents + skills + context file)").argument("[preset]", "Preset slug (interactive if omitted)").option("--add-agent <slugs...>", "Add extra agents").option("--remove-agent <slugs...>", "Remove agents from preset").option("--add-skill <slugs...>", "Add extra skills").option("--remove-skill <slugs...>", "Remove skills from preset").option("--claude", "Use Claude Code target (.claude/ + CLAUDE.md)").option("--cursor", "Use Cursor target (.cursor/ + .cursorrules)").option("--target <name>", `Output target: ${[...listTargetNames(), "custom"].join(", ")}`).option("--target-dir <dir>", "Custom target directory").option("--context-file <file>", "Custom context file name").action(async (preset, opts) => {
|
|
702
|
+
let target;
|
|
703
|
+
let targetExplicit = false;
|
|
704
|
+
if (opts.claude) {
|
|
705
|
+
target = BUILTIN_TARGETS["claude-code"];
|
|
706
|
+
targetExplicit = true;
|
|
707
|
+
} else if (opts.cursor) {
|
|
708
|
+
target = BUILTIN_TARGETS["cursor"];
|
|
709
|
+
targetExplicit = true;
|
|
710
|
+
} else if (opts.target) {
|
|
711
|
+
target = resolveTarget(
|
|
712
|
+
opts.target,
|
|
713
|
+
opts.targetDir,
|
|
714
|
+
opts.contextFile
|
|
715
|
+
);
|
|
716
|
+
targetExplicit = true;
|
|
717
|
+
} else {
|
|
718
|
+
target = BUILTIN_TARGETS[DEFAULT_TARGET];
|
|
719
|
+
}
|
|
720
|
+
await initCommand(preset, {
|
|
721
|
+
addAgent: opts.addAgent,
|
|
722
|
+
removeAgent: opts.removeAgent,
|
|
723
|
+
addSkill: opts.addSkill,
|
|
724
|
+
removeSkill: opts.removeSkill,
|
|
725
|
+
target,
|
|
726
|
+
targetExplicit
|
|
727
|
+
});
|
|
553
728
|
});
|
|
554
729
|
program.parse();
|