buildcrew 1.0.0 → 1.1.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/README.md +0 -13
- package/bin/setup.js +269 -224
- package/package.json +2 -1
- package/templates/api-spec.md +52 -0
- package/templates/architecture.md +49 -0
- package/templates/design-system.md +66 -0
- package/templates/env-vars.md +36 -0
- package/templates/erd.md +32 -0
- package/templates/glossary.md +27 -0
- package/templates/user-flow.md +40 -0
package/README.md
CHANGED
|
@@ -258,19 +258,6 @@ model: opus # or sonnet, haiku
|
|
|
258
258
|
└── Debug: investigator
|
|
259
259
|
```
|
|
260
260
|
|
|
261
|
-
## Compared to gstack
|
|
262
|
-
|
|
263
|
-
| | buildcrew | gstack |
|
|
264
|
-
|---|-----------|--------|
|
|
265
|
-
| Install | `npx buildcrew` | `git clone` + Bun + `./setup` |
|
|
266
|
-
| Orchestration | Auto (constitution routes) | Manual (`/qa`, `/review`, ...) |
|
|
267
|
-
| Harness | `init` + `add` + custom .md | Manual CLAUDE.md |
|
|
268
|
-
| Dependencies | None | Bun, Playwright binary (~58MB) |
|
|
269
|
-
| Browser testing | Playwright MCP | Custom Playwright daemon |
|
|
270
|
-
| Pipeline docs | Auto-generated chain (01→07) | Per-skill output |
|
|
271
|
-
| Agents | 11 (5 opus, 6 sonnet) | 34 |
|
|
272
|
-
| Customization | Edit .md directly, never overwritten | May overwrite on update |
|
|
273
|
-
|
|
274
261
|
## License
|
|
275
262
|
|
|
276
263
|
MIT
|
package/bin/setup.js
CHANGED
|
@@ -3,14 +3,13 @@
|
|
|
3
3
|
import { readdir, copyFile, mkdir, readFile, writeFile, access } from "fs/promises";
|
|
4
4
|
import { join, dirname } from "path";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
|
-
import { createInterface } from "readline";
|
|
7
6
|
|
|
8
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
8
|
const AGENTS_SRC = join(__dirname, "..", "agents");
|
|
10
9
|
const TEMPLATES_SRC = join(__dirname, "..", "templates");
|
|
11
10
|
const TARGET_DIR = join(process.cwd(), ".claude", "agents");
|
|
12
11
|
const HARNESS_DIR = join(process.cwd(), ".claude", "harness");
|
|
13
|
-
const VERSION = "1.
|
|
12
|
+
const VERSION = "1.1.0";
|
|
14
13
|
|
|
15
14
|
const RESET = "\x1b[0m";
|
|
16
15
|
const BOLD = "\x1b[1m";
|
|
@@ -41,246 +40,330 @@ const TEMPLATES = {
|
|
|
41
40
|
"env-vars": { file: "env-vars.md", desc: "Environment variables & secrets guide", category: "system" },
|
|
42
41
|
};
|
|
43
42
|
|
|
44
|
-
// ───
|
|
45
|
-
|
|
46
|
-
function createPrompt() {
|
|
47
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
48
|
-
const ask = (q) => new Promise(resolve => rl.question(q, resolve));
|
|
49
|
-
const close = () => rl.close();
|
|
50
|
-
return { ask, close };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// ─── Auto-detect project info ───
|
|
43
|
+
// ─── Deep auto-detect ───
|
|
54
44
|
|
|
55
45
|
async function detectProject() {
|
|
56
|
-
const info = {
|
|
46
|
+
const info = {
|
|
47
|
+
name: "", description: "", stack: [], framework: "",
|
|
48
|
+
hasTS: false, hasTailwind: false, hasI18n: false, hasAuth: false, hasDB: false,
|
|
49
|
+
hasPayments: false, hasAI: false, deploy: "", dbName: "", authName: "", paymentName: "",
|
|
50
|
+
components: [], apiRoutes: [], locales: [],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// package.json
|
|
57
54
|
try {
|
|
58
55
|
const pkg = JSON.parse(await readFile(join(process.cwd(), "package.json"), "utf8"));
|
|
59
56
|
info.name = pkg.name || "";
|
|
57
|
+
info.description = pkg.description || "";
|
|
60
58
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
59
|
+
|
|
60
|
+
// Framework
|
|
61
61
|
if (allDeps["next"]) { info.framework = "Next.js"; info.stack.push("Next.js"); }
|
|
62
62
|
else if (allDeps["nuxt"]) { info.framework = "Nuxt"; info.stack.push("Nuxt"); }
|
|
63
63
|
else if (allDeps["react"]) { info.framework = "React"; info.stack.push("React"); }
|
|
64
64
|
else if (allDeps["vue"]) { info.framework = "Vue"; info.stack.push("Vue"); }
|
|
65
65
|
else if (allDeps["svelte"] || allDeps["@sveltejs/kit"]) { info.framework = "SvelteKit"; info.stack.push("SvelteKit"); }
|
|
66
66
|
else if (allDeps["express"]) { info.framework = "Express"; info.stack.push("Express"); }
|
|
67
|
+
|
|
68
|
+
// Core
|
|
67
69
|
if (allDeps["typescript"]) { info.hasTS = true; info.stack.push("TypeScript"); }
|
|
68
70
|
if (allDeps["tailwindcss"]) { info.hasTailwind = true; info.stack.push("TailwindCSS"); }
|
|
69
|
-
if (allDeps["
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
if (allDeps["framer-motion"]) info.stack.push("Framer Motion");
|
|
72
|
+
|
|
73
|
+
// i18n
|
|
74
|
+
if (allDeps["next-intl"] || allDeps["i18next"] || allDeps["react-intl"] || allDeps["next-i18next"]) {
|
|
75
|
+
info.hasI18n = true; info.stack.push("i18n");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// DB
|
|
79
|
+
if (allDeps["@supabase/supabase-js"]) { info.hasDB = true; info.dbName = "Supabase"; info.stack.push("Supabase"); }
|
|
80
|
+
else if (allDeps["prisma"] || allDeps["@prisma/client"]) { info.hasDB = true; info.dbName = "Prisma"; info.stack.push("Prisma"); }
|
|
81
|
+
else if (allDeps["drizzle-orm"]) { info.hasDB = true; info.dbName = "Drizzle"; info.stack.push("Drizzle"); }
|
|
82
|
+
else if (allDeps["mongoose"]) { info.hasDB = true; info.dbName = "MongoDB"; info.stack.push("MongoDB"); }
|
|
83
|
+
|
|
84
|
+
// Auth
|
|
85
|
+
if (allDeps["next-auth"] || allDeps["@auth/core"]) { info.hasAuth = true; info.authName = "NextAuth"; info.stack.push("NextAuth"); }
|
|
86
|
+
else if (allDeps["@supabase/auth-helpers-nextjs"] || allDeps["@supabase/ssr"]) { info.hasAuth = true; info.authName = "Supabase Auth"; info.stack.push("Supabase Auth"); }
|
|
87
|
+
else if (allDeps["firebase"]) { info.hasAuth = true; info.authName = "Firebase Auth"; info.stack.push("Firebase"); }
|
|
88
|
+
|
|
89
|
+
// Payments
|
|
90
|
+
if (allDeps["stripe"]) { info.hasPayments = true; info.paymentName = "Stripe"; info.stack.push("Stripe"); }
|
|
91
|
+
else if (allDeps["@paddle/paddle-js"]) { info.hasPayments = true; info.paymentName = "Paddle"; info.stack.push("Paddle"); }
|
|
92
|
+
else if (allDeps["@tosspayments/payment-sdk"]) { info.hasPayments = true; info.paymentName = "Toss Payments"; info.stack.push("Toss Payments"); }
|
|
93
|
+
|
|
94
|
+
// AI
|
|
95
|
+
if (allDeps["openai"]) { info.hasAI = true; info.stack.push("OpenAI"); }
|
|
96
|
+
else if (allDeps["@anthropic-ai/sdk"]) { info.hasAI = true; info.stack.push("Anthropic"); }
|
|
97
|
+
else if (allDeps["@google/generative-ai"]) { info.hasAI = true; info.stack.push("Google AI"); }
|
|
75
98
|
} catch {}
|
|
76
|
-
return info;
|
|
77
|
-
}
|
|
78
99
|
|
|
79
|
-
//
|
|
100
|
+
// Detect deploy from vercel.json / netlify.toml / fly.toml
|
|
101
|
+
if (await exists(join(process.cwd(), "vercel.json")) || await exists(join(process.cwd(), ".vercel"))) info.deploy = "Vercel";
|
|
102
|
+
else if (await exists(join(process.cwd(), "netlify.toml"))) info.deploy = "Netlify";
|
|
103
|
+
else if (await exists(join(process.cwd(), "fly.toml"))) info.deploy = "Fly.io";
|
|
104
|
+
else if (await exists(join(process.cwd(), "Dockerfile"))) info.deploy = "Docker";
|
|
80
105
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (items.length === 0) continue;
|
|
88
|
-
log(` ${CYAN}${label}${RESET}`);
|
|
89
|
-
for (const [name, info] of items) {
|
|
90
|
-
const installed = await exists(join(HARNESS_DIR, `${name}.md`));
|
|
91
|
-
const status = installed ? `${GREEN}installed${RESET}` : `${DIM}not installed${RESET}`;
|
|
92
|
-
log(` ${BOLD}${name.padEnd(16)}${RESET} ${status} ${DIM}${info.desc}${RESET}`);
|
|
93
|
-
}
|
|
94
|
-
log("");
|
|
106
|
+
// Scan components
|
|
107
|
+
try {
|
|
108
|
+
const compsDir = join(process.cwd(), "src", "components");
|
|
109
|
+
if (await exists(compsDir)) {
|
|
110
|
+
const files = await readdir(compsDir);
|
|
111
|
+
info.components = files.filter(f => f.endsWith(".tsx") || f.endsWith(".vue") || f.endsWith(".svelte")).map(f => f.replace(/\.\w+$/, ""));
|
|
95
112
|
}
|
|
96
|
-
|
|
97
|
-
log(` ${BOLD}Example:${RESET} npx buildcrew add erd\n`);
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const template = TEMPLATES[type];
|
|
102
|
-
if (!template) {
|
|
103
|
-
log(`\n ${RED}Unknown template: ${type}${RESET}`);
|
|
104
|
-
log(` ${DIM}Run ${BOLD}npx buildcrew add${RESET}${DIM} to see available templates.${RESET}\n`);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
113
|
+
} catch {}
|
|
107
114
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
// Scan API routes
|
|
116
|
+
try {
|
|
117
|
+
const apiDir = join(process.cwd(), "src", "app", "api");
|
|
118
|
+
if (await exists(apiDir)) {
|
|
119
|
+
const scanDir = async (dir, prefix = "") => {
|
|
120
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
121
|
+
const routes = [];
|
|
122
|
+
for (const e of entries) {
|
|
123
|
+
if (e.isDirectory()) routes.push(...await scanDir(join(dir, e.name), `${prefix}/${e.name}`));
|
|
124
|
+
else if (e.name === "route.ts" || e.name === "route.js") routes.push(prefix || "/");
|
|
125
|
+
}
|
|
126
|
+
return routes;
|
|
127
|
+
};
|
|
128
|
+
info.apiRoutes = await scanDir(apiDir);
|
|
129
|
+
}
|
|
130
|
+
} catch {}
|
|
113
131
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
132
|
+
// Scan i18n locales
|
|
133
|
+
try {
|
|
134
|
+
for (const localeDir of ["src/i18n/dictionaries", "src/locales", "locales", "messages", "public/locales"]) {
|
|
135
|
+
const dir = join(process.cwd(), localeDir);
|
|
136
|
+
if (await exists(dir)) {
|
|
137
|
+
const files = await readdir(dir);
|
|
138
|
+
info.locales = files.filter(f => f.endsWith(".json")).map(f => f.replace(".json", ""));
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch {}
|
|
119
143
|
|
|
120
|
-
|
|
121
|
-
await copyFile(join(TEMPLATES_SRC, template.file), target);
|
|
122
|
-
log(`\n ${GREEN} + ${RESET} .claude/harness/${type}.md`);
|
|
123
|
-
log(` ${DIM}Edit this file to fill in your project details.${RESET}`);
|
|
124
|
-
log(` ${DIM}All agents will read it automatically.${RESET}\n`);
|
|
144
|
+
return info;
|
|
125
145
|
}
|
|
126
146
|
|
|
127
|
-
// ─── Init:
|
|
147
|
+
// ─── Init: Zero-question harness generation ───
|
|
128
148
|
|
|
129
149
|
async function runInit(force) {
|
|
130
|
-
log(`\n ${BOLD}buildcrew init${RESET}
|
|
131
|
-
log(` ${DIM}Setting up project context and team rules.${RESET}`);
|
|
132
|
-
log(` ${DIM}All 11 agents will read these before every task.${RESET}\n`);
|
|
150
|
+
log(`\n ${BOLD}buildcrew init${RESET} v${VERSION}\n`);
|
|
133
151
|
|
|
134
152
|
if ((await exists(join(HARNESS_DIR, "project.md"))) && !force) {
|
|
135
153
|
log(` ${YELLOW}Harness already exists at .claude/harness/${RESET}`);
|
|
136
|
-
log(` ${DIM}Use ${BOLD}
|
|
154
|
+
log(` ${DIM}Use ${BOLD}--force${RESET}${DIM} to regenerate. Or just edit the files directly.${RESET}\n`);
|
|
137
155
|
return;
|
|
138
156
|
}
|
|
139
157
|
|
|
140
|
-
|
|
141
|
-
const
|
|
158
|
+
log(` ${DIM}Scanning project...${RESET}\n`);
|
|
159
|
+
const d = await detectProject();
|
|
142
160
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const stackAuto = detected.stack.length > 0 ? detected.stack.join(", ") : "not detected";
|
|
148
|
-
const stackExtra = await ask(` ${BOLD}Tech stack${RESET} ${DIM}(detected: ${stackAuto})${RESET}\n ${DIM}Add anything missing (comma-separated, or Enter):${RESET} `);
|
|
149
|
-
const deployTarget = await ask(` ${BOLD}Deploy target${RESET} ${DIM}(Vercel, AWS, etc.)${RESET}: `);
|
|
150
|
-
const prodUrl = await ask(` ${BOLD}Production URL${RESET} ${DIM}(or Enter to skip)${RESET}: `);
|
|
151
|
-
|
|
152
|
-
log(`\n ${CYAN}${BOLD}[2/3] Team Rules${RESET}\n`);
|
|
153
|
-
const conventions = await ask(` ${BOLD}Coding conventions${RESET}\n ${DIM}(e.g., "functional components only, no default exports")${RESET}\n : `);
|
|
154
|
-
const priorities = await ask(` ${BOLD}What to prioritize${RESET}\n ${DIM}(e.g., "UX over performance, security first")${RESET}\n : `);
|
|
155
|
-
const avoid = await ask(` ${BOLD}What to avoid${RESET}\n ${DIM}(e.g., "no class components, no any types")${RESET}\n : `);
|
|
156
|
-
const quality = await ask(` ${BOLD}Quality standards${RESET}\n ${DIM}(e.g., "all code must pass tsc, no console.log")${RESET}\n : `);
|
|
157
|
-
const reviewRules = await ask(` ${BOLD}Review rules${RESET}\n ${DIM}(e.g., "always check auth on API routes, mobile-first")${RESET}\n : `);
|
|
158
|
-
|
|
159
|
-
log(`\n ${CYAN}${BOLD}[3/3] Domain Knowledge${RESET}\n`);
|
|
160
|
-
const domain = await ask(` ${BOLD}Domain${RESET} ${DIM}(e.g., "e-commerce", "fintech")${RESET}: `);
|
|
161
|
-
const userTypes = await ask(` ${BOLD}User types${RESET} ${DIM}(e.g., "free, premium, admin")${RESET}: `);
|
|
162
|
-
const keyTerms = await ask(` ${BOLD}Key terms${RESET}\n ${DIM}(e.g., "reading=tarot session, spread=card layout")${RESET}\n : `);
|
|
163
|
-
const businessRules = await ask(` ${BOLD}Business rules${RESET}\n ${DIM}(e.g., "free=3 reads/day, premium=unlimited")${RESET}\n : `);
|
|
164
|
-
|
|
165
|
-
close();
|
|
166
|
-
|
|
167
|
-
await mkdir(HARNESS_DIR, { recursive: true });
|
|
168
|
-
const fullStack = [...detected.stack, ...(stackExtra ? stackExtra.split(",").map(s => s.trim()) : [])].filter(Boolean);
|
|
169
|
-
|
|
170
|
-
const projectMd = `# Project: ${name}
|
|
161
|
+
await mkdir(HARNESS_DIR, { recursive: true });
|
|
162
|
+
|
|
163
|
+
// ─── project.md ───
|
|
164
|
+
const projectMd = `# Project: ${d.name || "my-project"}
|
|
171
165
|
|
|
172
166
|
## Overview
|
|
173
|
-
${description || "
|
|
167
|
+
${d.description || "<!-- Describe what this project does in 1-2 sentences -->"}
|
|
174
168
|
|
|
175
169
|
## Tech Stack
|
|
176
|
-
${
|
|
170
|
+
${d.stack.map(s => `- ${s}`).join("\n") || "<!-- Add your tech stack -->"}
|
|
177
171
|
|
|
178
172
|
## Framework
|
|
179
|
-
${
|
|
173
|
+
${d.framework || "<!-- Not detected -->"}
|
|
180
174
|
|
|
181
175
|
## Infrastructure
|
|
182
|
-
- **Deploy**: ${
|
|
183
|
-
- **Production URL**:
|
|
184
|
-
- **TypeScript**: ${
|
|
185
|
-
- **CSS**: ${
|
|
186
|
-
- **i18n**: ${
|
|
187
|
-
- **Auth**: ${
|
|
188
|
-
- **Database**: ${
|
|
176
|
+
- **Deploy**: ${d.deploy || "<!-- Vercel / AWS / Netlify / etc. -->"}
|
|
177
|
+
- **Production URL**: <!-- https://your-app.com -->
|
|
178
|
+
- **TypeScript**: ${d.hasTS ? "Yes" : "No"}
|
|
179
|
+
- **CSS**: ${d.hasTailwind ? "TailwindCSS" : "<!-- Your CSS solution -->"}
|
|
180
|
+
- **i18n**: ${d.hasI18n ? `Yes (${d.locales.join(", ")})` : "No"}
|
|
181
|
+
- **Auth**: ${d.hasAuth ? d.authName : "No"}
|
|
182
|
+
- **Database**: ${d.hasDB ? d.dbName : "No"}
|
|
183
|
+
- **Payments**: ${d.hasPayments ? d.paymentName : "No"}
|
|
184
|
+
- **AI**: ${d.hasAI ? d.stack.find(s => ["OpenAI","Anthropic","Google AI"].includes(s)) || "Yes" : "No"}
|
|
185
|
+
|
|
186
|
+
## Key Components
|
|
187
|
+
${d.components.length > 0 ? d.components.map(c => `- ${c}`).join("\n") : "<!-- List your main components -->"}
|
|
188
|
+
|
|
189
|
+
## API Routes
|
|
190
|
+
${d.apiRoutes.length > 0 ? d.apiRoutes.map(r => `- \`/api${r}\``).join("\n") : "<!-- List your API endpoints -->"}
|
|
189
191
|
|
|
190
192
|
## Domain
|
|
191
|
-
- **Industry**:
|
|
192
|
-
- **User types**:
|
|
193
|
+
- **Industry**: <!-- e.g., e-commerce, fintech, entertainment -->
|
|
194
|
+
- **User types**: <!-- e.g., free users, premium users, admins -->
|
|
193
195
|
|
|
194
196
|
## Key Domain Terms
|
|
195
|
-
|
|
197
|
+
<!-- Define project-specific terms so agents use consistent language -->
|
|
198
|
+
<!-- - term = definition -->
|
|
196
199
|
|
|
197
200
|
## Business Rules
|
|
198
|
-
|
|
201
|
+
<!-- Rules that affect feature development -->
|
|
202
|
+
<!-- - Free users: 3 actions per day -->
|
|
203
|
+
<!-- - Premium: unlimited access -->
|
|
199
204
|
`;
|
|
200
205
|
|
|
201
|
-
|
|
206
|
+
// ─── rules.md ───
|
|
207
|
+
const rulesMd = `# Team Rules
|
|
202
208
|
|
|
203
|
-
|
|
209
|
+
All agents read this before every task. Edit freely.
|
|
204
210
|
|
|
205
211
|
## Coding Conventions
|
|
206
|
-
${
|
|
212
|
+
${d.framework === "Next.js" ? `- Use App Router patterns
|
|
213
|
+
- Server components by default, "use client" only when needed
|
|
214
|
+
- Functional components only` : "<!-- Add your coding conventions -->"}
|
|
215
|
+
${d.hasTS ? "- Strict TypeScript — no \`any\` types" : ""}
|
|
216
|
+
${d.hasTailwind ? "- Use TailwindCSS utility classes, avoid custom CSS" : ""}
|
|
217
|
+
- No \`console.log\` in production code
|
|
218
|
+
- No default exports (except pages/layouts)
|
|
207
219
|
|
|
208
220
|
## Priorities
|
|
209
|
-
|
|
221
|
+
- UX over premature optimization
|
|
222
|
+
- Mobile-first responsive design
|
|
223
|
+
${d.hasI18n ? `- i18n: all user-facing text must be in locale files (${d.locales.join(", ")})` : ""}
|
|
224
|
+
${d.hasPayments ? "- Payment security: amounts validated server-side only" : ""}
|
|
210
225
|
|
|
211
226
|
## What to Avoid
|
|
212
|
-
|
|
227
|
+
- Class components
|
|
228
|
+
- \`any\` type assertions
|
|
229
|
+
- Inline styles (use Tailwind or CSS modules)
|
|
230
|
+
- Hardcoded strings in UI (use i18n)
|
|
231
|
+
- Direct DB access from client components
|
|
213
232
|
|
|
214
233
|
## Quality Standards
|
|
215
|
-
${
|
|
234
|
+
${d.hasTS ? "- \`npx tsc --noEmit\` must pass" : ""}
|
|
235
|
+
- \`npm run lint\` must pass
|
|
236
|
+
- \`npm run build\` must pass
|
|
237
|
+
${d.hasI18n && d.locales.length > 0 ? `- All ${d.locales.length} locale files must be in sync` : ""}
|
|
238
|
+
- No unused imports or variables
|
|
216
239
|
|
|
217
240
|
## Review Standards
|
|
218
|
-
${
|
|
241
|
+
${d.hasAuth ? "- All API routes must check authentication" : ""}
|
|
242
|
+
${d.hasPayments ? "- Payment mutations must be server-side and atomic" : ""}
|
|
243
|
+
${d.hasAI ? "- AI-generated content treated as untrusted (sanitize before render)" : ""}
|
|
244
|
+
- Check for XSS on any user input rendering
|
|
245
|
+
- Verify responsive layout at 375px, 768px, 1440px
|
|
219
246
|
`;
|
|
220
247
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
];
|
|
239
|
-
for (const [name, desc] of suggestions) {
|
|
240
|
-
log(` ${DIM}npx buildcrew add ${CYAN}${name.padEnd(15)}${RESET} ${DIM}${desc}${RESET}`);
|
|
248
|
+
await writeFile(join(HARNESS_DIR, "project.md"), projectMd);
|
|
249
|
+
await writeFile(join(HARNESS_DIR, "rules.md"), rulesMd);
|
|
250
|
+
|
|
251
|
+
// Auto-add relevant templates
|
|
252
|
+
let templateCount = 0;
|
|
253
|
+
const autoTemplates = [];
|
|
254
|
+
if (d.hasDB) autoTemplates.push("erd");
|
|
255
|
+
if (d.apiRoutes.length > 0) autoTemplates.push("api-spec");
|
|
256
|
+
if (d.hasTailwind) autoTemplates.push("design-system");
|
|
257
|
+
if (d.hasI18n || d.components.length > 5) autoTemplates.push("user-flow");
|
|
258
|
+
autoTemplates.push("architecture");
|
|
259
|
+
|
|
260
|
+
for (const name of autoTemplates) {
|
|
261
|
+
const t = TEMPLATES[name];
|
|
262
|
+
if (t && t.file) {
|
|
263
|
+
await copyFile(join(TEMPLATES_SRC, t.file), join(HARNESS_DIR, `${name}.md`));
|
|
264
|
+
templateCount++;
|
|
241
265
|
}
|
|
266
|
+
}
|
|
242
267
|
|
|
243
|
-
|
|
268
|
+
// Print results
|
|
269
|
+
log(` ${GREEN}${BOLD}Harness generated!${RESET} (${2 + templateCount} files)\n`);
|
|
270
|
+
log(` ${GREEN} + ${RESET} project.md ${DIM}auto-detected from package.json${RESET}`);
|
|
271
|
+
log(` ${GREEN} + ${RESET} rules.md ${DIM}smart defaults for ${d.framework || "your stack"}${RESET}`);
|
|
272
|
+
for (const name of autoTemplates) {
|
|
273
|
+
log(` ${GREEN} + ${RESET} ${(name + ".md").padEnd(17)}${DIM}${TEMPLATES[name].desc}${RESET}`);
|
|
274
|
+
}
|
|
244
275
|
|
|
245
|
-
|
|
246
|
-
|
|
276
|
+
// Show what was detected
|
|
277
|
+
log(`\n ${BOLD}Detected:${RESET}`);
|
|
278
|
+
if (d.stack.length > 0) log(` ${CYAN}Stack${RESET} ${d.stack.join(", ")}`);
|
|
279
|
+
if (d.deploy) log(` ${CYAN}Deploy${RESET} ${d.deploy}`);
|
|
280
|
+
if (d.components.length > 0) log(` ${CYAN}Components${RESET} ${d.components.length} found`);
|
|
281
|
+
if (d.apiRoutes.length > 0) log(` ${CYAN}API Routes${RESET} ${d.apiRoutes.length} found`);
|
|
282
|
+
if (d.locales.length > 0) log(` ${CYAN}Locales${RESET} ${d.locales.join(", ")}`);
|
|
283
|
+
|
|
284
|
+
// Available extras
|
|
285
|
+
const remaining = Object.entries(TEMPLATES).filter(([name, t]) => t.file && !autoTemplates.includes(name));
|
|
286
|
+
if (remaining.length > 0) {
|
|
287
|
+
log(`\n ${BOLD}Add more:${RESET}`);
|
|
288
|
+
for (const [name, t] of remaining) {
|
|
289
|
+
log(` ${DIM}npx buildcrew add ${CYAN}${name}${RESET}`);
|
|
247
290
|
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
log(`\n ${BOLD}Next step:${RESET} Edit ${CYAN}.claude/harness/*.md${RESET} to fill in project-specific details.`);
|
|
294
|
+
log(` ${DIM}Look for <!-- comments --> — those are the parts to customize.${RESET}\n`);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// ─── Add: copy template ───
|
|
248
298
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
299
|
+
async function runAdd(type, force) {
|
|
300
|
+
if (!type) {
|
|
301
|
+
log(`\n ${BOLD}Available harness templates:${RESET}\n`);
|
|
302
|
+
const categories = { core: "Core (auto-created by init)", data: "Data", system: "System", design: "Design", domain: "Domain" };
|
|
303
|
+
for (const [cat, label] of Object.entries(categories)) {
|
|
304
|
+
const items = Object.entries(TEMPLATES).filter(([, v]) => v.category === cat);
|
|
305
|
+
if (items.length === 0) continue;
|
|
306
|
+
log(` ${CYAN}${label}${RESET}`);
|
|
307
|
+
for (const [name, info] of items) {
|
|
308
|
+
const installed = await exists(join(HARNESS_DIR, `${name}.md`));
|
|
309
|
+
const status = installed ? `${GREEN}installed${RESET}` : `${DIM}not installed${RESET}`;
|
|
310
|
+
log(` ${BOLD}${name.padEnd(16)}${RESET} ${status} ${DIM}${info.desc}${RESET}`);
|
|
311
|
+
}
|
|
312
|
+
log("");
|
|
313
|
+
}
|
|
314
|
+
log(` ${BOLD}Usage:${RESET} npx buildcrew add ${CYAN}<template>${RESET}\n`);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const template = TEMPLATES[type];
|
|
319
|
+
if (!template) {
|
|
320
|
+
log(`\n ${RED}Unknown template: ${type}${RESET}`);
|
|
321
|
+
log(` ${DIM}Run ${BOLD}npx buildcrew add${RESET}${DIM} to see available templates.${RESET}\n`);
|
|
322
|
+
return;
|
|
252
323
|
}
|
|
324
|
+
|
|
325
|
+
if (!template.file) {
|
|
326
|
+
log(`\n ${YELLOW}${type}.md is auto-generated by ${BOLD}npx buildcrew init${RESET}\n`);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const target = join(HARNESS_DIR, `${type}.md`);
|
|
331
|
+
if ((await exists(target)) && !force) {
|
|
332
|
+
log(`\n ${YELLOW}${type}.md already exists.${RESET} Use ${BOLD}--force${RESET} to overwrite.\n`);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
await mkdir(HARNESS_DIR, { recursive: true });
|
|
337
|
+
await copyFile(join(TEMPLATES_SRC, template.file), target);
|
|
338
|
+
log(`\n ${GREEN} + ${RESET} .claude/harness/${type}.md`);
|
|
339
|
+
log(` ${DIM}Edit to fill in your project details. Agents read it automatically.${RESET}\n`);
|
|
253
340
|
}
|
|
254
341
|
|
|
255
342
|
// ─── Harness status ───
|
|
256
343
|
|
|
257
344
|
async function runHarnessStatus() {
|
|
258
|
-
log(`\n ${BOLD}Harness
|
|
345
|
+
log(`\n ${BOLD}Harness${RESET} ${DIM}(.claude/harness/)${RESET}\n`);
|
|
259
346
|
|
|
260
347
|
if (!(await exists(HARNESS_DIR))) {
|
|
261
|
-
log(` ${YELLOW}No harness
|
|
262
|
-
log(` ${DIM}Run ${BOLD}npx buildcrew init${RESET}${DIM} to get started.${RESET}\n`);
|
|
348
|
+
log(` ${YELLOW}No harness found.${RESET} Run ${BOLD}npx buildcrew init${RESET}\n`);
|
|
263
349
|
return;
|
|
264
350
|
}
|
|
265
351
|
|
|
266
352
|
const files = (await readdir(HARNESS_DIR)).filter(f => f.endsWith(".md")).sort();
|
|
267
|
-
if (files.length === 0) {
|
|
268
|
-
log(` ${YELLOW}Harness directory is empty.${RESET}\n`);
|
|
269
|
-
return;
|
|
270
|
-
}
|
|
353
|
+
if (files.length === 0) { log(` ${YELLOW}Empty.${RESET}\n`); return; }
|
|
271
354
|
|
|
272
355
|
for (const file of files) {
|
|
273
356
|
const name = file.replace(".md", "");
|
|
274
|
-
const isTemplate = TEMPLATES[name];
|
|
275
357
|
const content = await readFile(join(HARNESS_DIR, file), "utf8");
|
|
276
358
|
const lines = content.split("\n").length;
|
|
277
|
-
const
|
|
278
|
-
const status =
|
|
279
|
-
const
|
|
280
|
-
|
|
359
|
+
const hasComments = content.includes("<!--");
|
|
360
|
+
const status = hasComments ? `${YELLOW}needs editing${RESET}` : `${GREEN}configured${RESET}`;
|
|
361
|
+
const t = TEMPLATES[name];
|
|
362
|
+
const desc = t ? `${DIM}${t.desc}${RESET}` : `${DIM}(custom)${RESET}`;
|
|
363
|
+
log(` ${BOLD}${name.padEnd(16)}${RESET} ${status} ${DIM}${lines} lines${RESET} ${desc}`);
|
|
281
364
|
}
|
|
282
365
|
|
|
283
|
-
log(`\n ${DIM}
|
|
366
|
+
log(`\n ${DIM}Edit files to replace <!-- comments --> with your content.${RESET}\n`);
|
|
284
367
|
}
|
|
285
368
|
|
|
286
369
|
// ─── Install agents ───
|
|
@@ -288,78 +371,48 @@ async function runHarnessStatus() {
|
|
|
288
371
|
async function runInstall(force) {
|
|
289
372
|
const files = (await readdir(AGENTS_SRC)).filter(f => f.endsWith(".md"));
|
|
290
373
|
log(`\n ${BOLD}buildcrew${RESET} v${VERSION}\n ${DIM}11 AI agents for Claude Code${RESET}\n`);
|
|
291
|
-
log(`${DIM} Installing to ${TARGET_DIR}${RESET}\n`);
|
|
292
374
|
|
|
293
375
|
await mkdir(TARGET_DIR, { recursive: true });
|
|
294
376
|
let installed = 0, skipped = 0;
|
|
295
377
|
|
|
296
378
|
for (const file of files) {
|
|
297
379
|
const target = join(TARGET_DIR, file);
|
|
298
|
-
if ((await exists(target)) && !force) {
|
|
299
|
-
log(` ${YELLOW}skip${RESET} ${file} ${DIM}(exists, use --force)${RESET}`);
|
|
300
|
-
skipped++;
|
|
301
|
-
continue;
|
|
302
|
-
}
|
|
380
|
+
if ((await exists(target)) && !force) { skipped++; continue; }
|
|
303
381
|
await copyFile(join(AGENTS_SRC, file), target);
|
|
304
|
-
log(` ${GREEN} +
|
|
382
|
+
log(` ${GREEN} + ${RESET} ${file}`);
|
|
305
383
|
installed++;
|
|
306
384
|
}
|
|
307
385
|
|
|
308
386
|
log("");
|
|
309
|
-
if (installed > 0) {
|
|
310
|
-
|
|
311
|
-
} else {
|
|
312
|
-
log(` ${YELLOW}All agents already exist.${RESET} Use ${BOLD}--force${RESET} to overwrite.\n`);
|
|
313
|
-
}
|
|
387
|
+
if (installed > 0) log(` ${GREEN}${BOLD}Done!${RESET} ${installed} agents installed.${skipped > 0 ? ` ${skipped} skipped.` : ""}\n`);
|
|
388
|
+
else log(` ${YELLOW}All agents already exist.${RESET} Use ${BOLD}--force${RESET} to overwrite.\n`);
|
|
314
389
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
log(` ${CYAN}Next:${RESET} Run ${BOLD}npx buildcrew init${RESET} to set up your project harness.\n`);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
let hasPlaywright = false;
|
|
321
|
-
try {
|
|
322
|
-
const settingsPath = join(process.env.HOME, ".claude", "settings.json");
|
|
323
|
-
if (await exists(settingsPath)) {
|
|
324
|
-
hasPlaywright = (await readFile(settingsPath, "utf8")).includes("playwright");
|
|
325
|
-
}
|
|
326
|
-
} catch {}
|
|
327
|
-
if (!hasPlaywright) {
|
|
328
|
-
log(` ${YELLOW}Optional:${RESET} Playwright MCP not detected.`);
|
|
329
|
-
log(` ${DIM}Setup: claude mcp add playwright -- npx @anthropic-ai/mcp-server-playwright${RESET}\n`);
|
|
390
|
+
if (!(await exists(join(HARNESS_DIR, "project.md")))) {
|
|
391
|
+
log(` ${CYAN}Next:${RESET} ${BOLD}npx buildcrew init${RESET} — auto-generates project harness from your codebase.\n`);
|
|
330
392
|
}
|
|
331
393
|
|
|
332
|
-
log(` ${BOLD}
|
|
394
|
+
log(` ${BOLD}Start:${RESET} @constitution [your request]\n`);
|
|
333
395
|
}
|
|
334
396
|
|
|
335
|
-
// ─── List
|
|
397
|
+
// ─── List / Uninstall ───
|
|
336
398
|
|
|
337
399
|
async function runList() {
|
|
338
400
|
const files = (await readdir(AGENTS_SRC)).filter(f => f.endsWith(".md"));
|
|
339
401
|
log(`\n ${BOLD}buildcrew${RESET} v${VERSION} — 11 agents\n`);
|
|
340
402
|
for (const file of files) {
|
|
341
403
|
const content = await readFile(join(AGENTS_SRC, file), "utf8");
|
|
342
|
-
const
|
|
343
|
-
const
|
|
344
|
-
const
|
|
345
|
-
const name = nameMatch ? nameMatch[1] : file.replace(".md", "");
|
|
346
|
-
const desc = descMatch ? descMatch[1] : "";
|
|
347
|
-
const model = modelMatch ? modelMatch[1] : "sonnet";
|
|
404
|
+
const name = (content.match(/^name:\s*(.+)$/m) || [])[1] || file.replace(".md", "");
|
|
405
|
+
const desc = (content.match(/^description:\s*(.+)$/m) || [])[1] || "";
|
|
406
|
+
const model = (content.match(/^model:\s*(.+)$/m) || [])[1] || "sonnet";
|
|
348
407
|
const modelTag = model === "opus" ? `${MAGENTA}opus${RESET}` : `${DIM}sonnet${RESET}`;
|
|
349
|
-
|
|
350
|
-
log(` ${color}${name.padEnd(20)}${RESET} ${modelTag} ${DIM}${desc.slice(0, 55)}${RESET}`);
|
|
408
|
+
log(` ${name === "constitution" ? BOLD : ""}${name.padEnd(20)}${RESET} ${modelTag} ${DIM}${desc.slice(0, 55)}${RESET}`);
|
|
351
409
|
}
|
|
352
410
|
log("");
|
|
353
411
|
}
|
|
354
412
|
|
|
355
|
-
// ─── Uninstall ───
|
|
356
|
-
|
|
357
413
|
async function runUninstall() {
|
|
358
414
|
const files = (await readdir(AGENTS_SRC)).filter(f => f.endsWith(".md"));
|
|
359
|
-
if (!(await exists(TARGET_DIR))) {
|
|
360
|
-
log(`${YELLOW}No .claude/agents/ directory found.${RESET}`);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
415
|
+
if (!(await exists(TARGET_DIR))) { log(`${YELLOW}No agents found.${RESET}`); return; }
|
|
363
416
|
let removed = 0;
|
|
364
417
|
for (const file of files) {
|
|
365
418
|
const target = join(TARGET_DIR, file);
|
|
@@ -376,38 +429,30 @@ async function main() {
|
|
|
376
429
|
const subcommand = args[1];
|
|
377
430
|
const force = args.includes("--force") || args.includes("-f");
|
|
378
431
|
|
|
379
|
-
if (args.includes("--version") || args.includes("-v")) {
|
|
380
|
-
log(`buildcrew v${VERSION}`);
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
432
|
+
if (args.includes("--version") || args.includes("-v")) { log(`buildcrew v${VERSION}`); return; }
|
|
383
433
|
|
|
384
434
|
if (args.includes("--help") || args.includes("-h")) {
|
|
385
435
|
log(`
|
|
386
436
|
${BOLD}buildcrew${RESET} v${VERSION} — 11 AI agents for Claude Code
|
|
387
437
|
|
|
388
438
|
${BOLD}Commands:${RESET}
|
|
389
|
-
npx buildcrew
|
|
390
|
-
npx buildcrew init
|
|
391
|
-
npx buildcrew add
|
|
392
|
-
npx buildcrew add <
|
|
393
|
-
npx buildcrew harness
|
|
439
|
+
npx buildcrew Install agents
|
|
440
|
+
npx buildcrew init Auto-generate project harness (zero questions)
|
|
441
|
+
npx buildcrew add List harness templates
|
|
442
|
+
npx buildcrew add <name> Add a harness template
|
|
443
|
+
npx buildcrew harness Show harness file status
|
|
394
444
|
|
|
395
445
|
${BOLD}Options:${RESET}
|
|
396
|
-
--force, -f
|
|
397
|
-
--list, -l
|
|
398
|
-
--uninstall
|
|
399
|
-
--version
|
|
400
|
-
|
|
401
|
-
${BOLD}Setup
|
|
402
|
-
${GREEN}1.${RESET} npx buildcrew
|
|
403
|
-
${GREEN}2.${RESET} npx buildcrew init
|
|
404
|
-
${GREEN}3.${RESET}
|
|
405
|
-
${GREEN}4.${RESET} @constitution [task]
|
|
406
|
-
|
|
407
|
-
${BOLD}Harness templates:${RESET}
|
|
408
|
-
erd, architecture, api-spec, design-system,
|
|
409
|
-
glossary, user-flow, env-vars
|
|
410
|
-
${DIM}Or create any .md file in .claude/harness/${RESET}
|
|
446
|
+
--force, -f Overwrite existing files
|
|
447
|
+
--list, -l List all agents
|
|
448
|
+
--uninstall Remove agents
|
|
449
|
+
--version Show version
|
|
450
|
+
|
|
451
|
+
${BOLD}Setup:${RESET}
|
|
452
|
+
${GREEN}1.${RESET} npx buildcrew ${DIM}Install agents${RESET}
|
|
453
|
+
${GREEN}2.${RESET} npx buildcrew init ${DIM}Auto-generate harness from codebase${RESET}
|
|
454
|
+
${GREEN}3.${RESET} Edit .claude/harness/ ${DIM}Customize (replace <!-- comments -->)${RESET}
|
|
455
|
+
${GREEN}4.${RESET} @constitution [task] ${DIM}Start working${RESET}
|
|
411
456
|
|
|
412
457
|
${BOLD}More info:${RESET} https://github.com/z1nun/buildcrew
|
|
413
458
|
`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "buildcrew",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "11 AI agents for Claude Code — auto-orchestrated dev team with 9 operating modes",
|
|
5
5
|
"author": "z1nun",
|
|
6
6
|
"license": "MIT",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"bin/",
|
|
13
13
|
"agents/",
|
|
14
|
+
"templates/",
|
|
14
15
|
"README.md",
|
|
15
16
|
"LICENSE"
|
|
16
17
|
],
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# API Specification
|
|
2
|
+
|
|
3
|
+
## Base URL
|
|
4
|
+
- **Development**: `http://localhost:3000/api`
|
|
5
|
+
- **Production**: `https://[your-domain]/api`
|
|
6
|
+
|
|
7
|
+
## Authentication
|
|
8
|
+
- **Method**: [Bearer token / Cookie / API key]
|
|
9
|
+
- **Header**: `Authorization: Bearer <token>`
|
|
10
|
+
|
|
11
|
+
## Endpoints
|
|
12
|
+
|
|
13
|
+
### [Resource Name]
|
|
14
|
+
|
|
15
|
+
#### `GET /api/[resource]`
|
|
16
|
+
- **Auth**: Required / Public
|
|
17
|
+
- **Description**: [what it does]
|
|
18
|
+
- **Query Params**: `?page=1&limit=20`
|
|
19
|
+
- **Response 200**:
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"data": [],
|
|
23
|
+
"total": 0
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
- **Response 401**: Unauthorized
|
|
27
|
+
|
|
28
|
+
#### `POST /api/[resource]`
|
|
29
|
+
- **Auth**: Required
|
|
30
|
+
- **Body**:
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"field": "value"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
- **Response 201**: Created
|
|
37
|
+
- **Response 400**: Validation error
|
|
38
|
+
|
|
39
|
+
## Error Format
|
|
40
|
+
```json
|
|
41
|
+
{
|
|
42
|
+
"error": "Error message",
|
|
43
|
+
"code": "ERROR_CODE"
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Rate Limits
|
|
48
|
+
- [e.g., "100 requests per minute per user"]
|
|
49
|
+
- [e.g., "AI endpoints: 10 requests per minute"]
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
*Agents use this to implement correct API calls, validate endpoints during review, and test during QA.*
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Architecture
|
|
2
|
+
|
|
3
|
+
## System Overview
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
[Client] → [Frontend] → [API Layer] → [Database]
|
|
7
|
+
↓ ↓
|
|
8
|
+
[Auth] [External APIs]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Frontend
|
|
12
|
+
- **Framework**: [Next.js / React / Vue / etc.]
|
|
13
|
+
- **Routing**: [App Router / Pages Router / file-based]
|
|
14
|
+
- **State Management**: [React Context / Zustand / Redux / etc.]
|
|
15
|
+
- **Styling**: [TailwindCSS / CSS Modules / styled-components]
|
|
16
|
+
|
|
17
|
+
## Backend / API
|
|
18
|
+
- **Type**: [Next.js API Routes / Express / serverless]
|
|
19
|
+
- **Authentication**: [Supabase Auth / NextAuth / custom]
|
|
20
|
+
- **Database**: [Supabase / PostgreSQL / MongoDB]
|
|
21
|
+
- **ORM**: [Prisma / Drizzle / Supabase client]
|
|
22
|
+
|
|
23
|
+
## External Services
|
|
24
|
+
| Service | Purpose | Auth Method |
|
|
25
|
+
|---------|---------|-------------|
|
|
26
|
+
| [e.g., OpenAI] | [AI generation] | [API key] |
|
|
27
|
+
| [e.g., Stripe] | [Payments] | [API key + webhook] |
|
|
28
|
+
|
|
29
|
+
## Directory Structure
|
|
30
|
+
```
|
|
31
|
+
src/
|
|
32
|
+
├── app/ [pages and API routes]
|
|
33
|
+
├── components/ [React components]
|
|
34
|
+
├── lib/ [utilities and helpers]
|
|
35
|
+
└── [other dirs]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Key Patterns
|
|
39
|
+
- [e.g., "Server components by default, 'use client' only when needed"]
|
|
40
|
+
- [e.g., "All DB access through lib/supabase/ helpers"]
|
|
41
|
+
- [e.g., "API routes validate input with zod"]
|
|
42
|
+
|
|
43
|
+
## Deploy
|
|
44
|
+
- **Platform**: [Vercel / AWS / etc.]
|
|
45
|
+
- **CI/CD**: [GitHub Actions / Vercel auto-deploy]
|
|
46
|
+
- **Environments**: [dev, staging, prod]
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
*Agents use this to understand system boundaries, make architecture-consistent decisions, and avoid breaking existing patterns.*
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Design System
|
|
2
|
+
|
|
3
|
+
## Colors
|
|
4
|
+
|
|
5
|
+
### Brand
|
|
6
|
+
| Token | Value | Usage |
|
|
7
|
+
|-------|-------|-------|
|
|
8
|
+
| `primary` | [#hex] | CTAs, links, active states |
|
|
9
|
+
| `secondary` | [#hex] | Secondary buttons, borders |
|
|
10
|
+
| `accent` | [#hex] | Highlights, badges |
|
|
11
|
+
|
|
12
|
+
### Semantic
|
|
13
|
+
| Token | Value | Usage |
|
|
14
|
+
|-------|-------|-------|
|
|
15
|
+
| `success` | [#hex] | Success messages, confirmations |
|
|
16
|
+
| `error` | [#hex] | Error messages, destructive actions |
|
|
17
|
+
| `warning` | [#hex] | Warnings, caution states |
|
|
18
|
+
| `info` | [#hex] | Informational messages |
|
|
19
|
+
|
|
20
|
+
### Neutral
|
|
21
|
+
| Token | Value | Usage |
|
|
22
|
+
|-------|-------|-------|
|
|
23
|
+
| `bg` | [#hex] | Page background |
|
|
24
|
+
| `surface` | [#hex] | Card/modal backgrounds |
|
|
25
|
+
| `border` | [#hex] | Borders, dividers |
|
|
26
|
+
| `text` | [#hex] | Primary text |
|
|
27
|
+
| `text-muted` | [#hex] | Secondary text, labels |
|
|
28
|
+
|
|
29
|
+
## Typography
|
|
30
|
+
|
|
31
|
+
| Level | Size | Weight | Line Height | Usage |
|
|
32
|
+
|-------|------|--------|-------------|-------|
|
|
33
|
+
| H1 | [rem] | [weight] | [lh] | Page titles |
|
|
34
|
+
| H2 | [rem] | [weight] | [lh] | Section titles |
|
|
35
|
+
| H3 | [rem] | [weight] | [lh] | Card titles |
|
|
36
|
+
| Body | [rem] | [weight] | [lh] | Default text |
|
|
37
|
+
| Small | [rem] | [weight] | [lh] | Captions, labels |
|
|
38
|
+
|
|
39
|
+
## Spacing
|
|
40
|
+
- **Base unit**: [4px / 8px]
|
|
41
|
+
- **Scale**: 4, 8, 12, 16, 24, 32, 48, 64
|
|
42
|
+
|
|
43
|
+
## Border Radius
|
|
44
|
+
- **Small**: [4px] — inputs, small cards
|
|
45
|
+
- **Medium**: [8px] — cards, modals
|
|
46
|
+
- **Large**: [16px] — large containers
|
|
47
|
+
- **Full**: [9999px] — pills, avatars
|
|
48
|
+
|
|
49
|
+
## Shadows
|
|
50
|
+
- **Small**: [shadow] — hover states
|
|
51
|
+
- **Medium**: [shadow] — cards, dropdowns
|
|
52
|
+
- **Large**: [shadow] — modals, popovers
|
|
53
|
+
|
|
54
|
+
## Components
|
|
55
|
+
| Component | Variants | Location |
|
|
56
|
+
|-----------|----------|----------|
|
|
57
|
+
| Button | primary, secondary, ghost, destructive | `src/components/Button` |
|
|
58
|
+
| [Component] | [variants] | [path] |
|
|
59
|
+
|
|
60
|
+
## Animation
|
|
61
|
+
- **Duration**: fast (150ms), normal (300ms), slow (500ms)
|
|
62
|
+
- **Easing**: ease-out for enter, ease-in for exit
|
|
63
|
+
- **Library**: [Framer Motion / CSS transitions / etc.]
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
*Agents use this to create visually consistent components, pick correct tokens, and follow established patterns.*
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Environment Variables
|
|
2
|
+
|
|
3
|
+
## Required (app won't work without these)
|
|
4
|
+
|
|
5
|
+
| Variable | Description | Where to Get |
|
|
6
|
+
|----------|-------------|-------------|
|
|
7
|
+
| `DATABASE_URL` | [Database connection string] | [Supabase dashboard / provider] |
|
|
8
|
+
| `NEXT_PUBLIC_SUPABASE_URL` | [Supabase project URL] | [Supabase dashboard] |
|
|
9
|
+
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | [Supabase public key] | [Supabase dashboard] |
|
|
10
|
+
| | | |
|
|
11
|
+
|
|
12
|
+
## Optional (features degrade gracefully)
|
|
13
|
+
|
|
14
|
+
| Variable | Description | Feature Affected |
|
|
15
|
+
|----------|-------------|-----------------|
|
|
16
|
+
| `OPENAI_API_KEY` | [AI API access] | [AI features disabled] |
|
|
17
|
+
| `STRIPE_SECRET_KEY` | [Payment processing] | [Payments disabled] |
|
|
18
|
+
| | | |
|
|
19
|
+
|
|
20
|
+
## Server-Only (never expose to client)
|
|
21
|
+
|
|
22
|
+
| Variable | Why Server-Only |
|
|
23
|
+
|----------|----------------|
|
|
24
|
+
| `SUPABASE_SERVICE_ROLE_KEY` | Bypasses RLS |
|
|
25
|
+
| `[API]_SECRET_KEY` | Payment/API secrets |
|
|
26
|
+
|
|
27
|
+
## Environments
|
|
28
|
+
|
|
29
|
+
| Env | .env file | Notes |
|
|
30
|
+
|-----|-----------|-------|
|
|
31
|
+
| Local | `.env.local` | Never committed |
|
|
32
|
+
| Preview | Vercel env vars | Auto from branch |
|
|
33
|
+
| Production | Vercel env vars | Manual setup |
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
*Agents use this to avoid exposing secrets, configure features correctly, and debug environment issues.*
|
package/templates/erd.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# ERD (Entity Relationship Diagram)
|
|
2
|
+
|
|
3
|
+
## Tables
|
|
4
|
+
|
|
5
|
+
### users
|
|
6
|
+
| Column | Type | Constraints | Description |
|
|
7
|
+
|--------|------|-------------|-------------|
|
|
8
|
+
| id | uuid | PK | |
|
|
9
|
+
| email | text | unique, not null | |
|
|
10
|
+
| created_at | timestamp | default now() | |
|
|
11
|
+
|
|
12
|
+
### [table_name]
|
|
13
|
+
| Column | Type | Constraints | Description |
|
|
14
|
+
|--------|------|-------------|-------------|
|
|
15
|
+
|
|
16
|
+
## Relationships
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
users 1──* orders (one user has many orders)
|
|
20
|
+
orders *──1 products (many orders reference one product)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Indexes
|
|
24
|
+
- `users.email` — unique index for login lookup
|
|
25
|
+
- [add your indexes]
|
|
26
|
+
|
|
27
|
+
## RLS Policies (if using Supabase)
|
|
28
|
+
- `users`: users can only read/update their own row
|
|
29
|
+
- [add your policies]
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
*Agents use this to understand your data model when planning features, writing queries, and reviewing security.*
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Glossary
|
|
2
|
+
|
|
3
|
+
Domain terms and their definitions. All agents use consistent terminology.
|
|
4
|
+
|
|
5
|
+
| Term | Definition | In Code As |
|
|
6
|
+
|------|-----------|------------|
|
|
7
|
+
| [e.g., reading] | [e.g., A tarot card reading session] | `Reading` type |
|
|
8
|
+
| [e.g., spread] | [e.g., The layout pattern for cards] | `SpreadType` enum |
|
|
9
|
+
| [e.g., interpretation] | [e.g., AI-generated meaning of cards] | `Interpretation` type |
|
|
10
|
+
| | | |
|
|
11
|
+
| | | |
|
|
12
|
+
|
|
13
|
+
## User Roles
|
|
14
|
+
| Role | Permissions | In Code As |
|
|
15
|
+
|------|------------|------------|
|
|
16
|
+
| [e.g., free user] | [e.g., 3 readings per day] | `role: "free"` |
|
|
17
|
+
| [e.g., premium] | [e.g., unlimited readings + chat] | `role: "premium"` |
|
|
18
|
+
| [e.g., admin] | [e.g., all access + user management] | `role: "admin"` |
|
|
19
|
+
|
|
20
|
+
## Status Flows
|
|
21
|
+
```
|
|
22
|
+
[e.g., Order: draft → pending → paid → fulfilled → completed]
|
|
23
|
+
[e.g., User: anonymous → registered → verified → premium]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
*Agents use this to write code with correct naming, understand business logic, and communicate clearly in docs.*
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# User Flows
|
|
2
|
+
|
|
3
|
+
## Core Flows
|
|
4
|
+
|
|
5
|
+
### [Flow Name, e.g., "Purchase Flow"]
|
|
6
|
+
```
|
|
7
|
+
[Entry Point] → [Step 1] → [Step 2] → [Step 3] → [Success]
|
|
8
|
+
↓ ↓ ↓
|
|
9
|
+
[Error A] [Error B] [Error C]
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
**Happy Path**:
|
|
13
|
+
1. User [action]
|
|
14
|
+
2. System [response]
|
|
15
|
+
3. User [action]
|
|
16
|
+
4. System [response] → Success
|
|
17
|
+
|
|
18
|
+
**Error Paths**:
|
|
19
|
+
- Step 1 fails: [what happens, where user goes]
|
|
20
|
+
- Step 2 fails: [what happens]
|
|
21
|
+
- Network error: [retry? fallback?]
|
|
22
|
+
|
|
23
|
+
**Edge Cases**:
|
|
24
|
+
- [e.g., user refreshes mid-flow]
|
|
25
|
+
- [e.g., session expires during checkout]
|
|
26
|
+
- [e.g., concurrent access from two devices]
|
|
27
|
+
|
|
28
|
+
### [Next Flow...]
|
|
29
|
+
|
|
30
|
+
## Page Map
|
|
31
|
+
```
|
|
32
|
+
/ → Home (landing)
|
|
33
|
+
/login → Auth
|
|
34
|
+
/dashboard → Main dashboard (auth required)
|
|
35
|
+
/dashboard/[id] → Detail page
|
|
36
|
+
/settings → User settings
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
*Agents use this to implement correct navigation, handle all error states, and test edge cases during QA.*
|