linear-ai-build 1.0.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.
Files changed (41) hide show
  1. package/.env.example +3 -0
  2. package/README.md +220 -0
  3. package/bin/cli.js +21 -0
  4. package/bin/cli.ts +51 -0
  5. package/bin/init.ts +252 -0
  6. package/commands/build-from-linear.md +193 -0
  7. package/commands/generate-stories.md +110 -0
  8. package/dist/bridge/config.d.ts +11 -0
  9. package/dist/bridge/config.js +24 -0
  10. package/dist/bridge/executor.d.ts +8 -0
  11. package/dist/bridge/executor.js +147 -0
  12. package/dist/bridge/index.d.ts +3 -0
  13. package/dist/bridge/index.js +137 -0
  14. package/dist/bridge/linear-helpers.d.ts +6 -0
  15. package/dist/bridge/linear-helpers.js +43 -0
  16. package/dist/bridge/reporter.d.ts +7 -0
  17. package/dist/bridge/reporter.js +79 -0
  18. package/dist/bridge/state.d.ts +10 -0
  19. package/dist/bridge/state.js +37 -0
  20. package/dist/bridge/transformer.d.ts +22 -0
  21. package/dist/bridge/transformer.js +142 -0
  22. package/dist/bridge/types.d.ts +44 -0
  23. package/dist/bridge/types.js +1 -0
  24. package/dist/bridge/watcher.d.ts +13 -0
  25. package/dist/bridge/watcher.js +104 -0
  26. package/dist/generate-sdk.d.ts +1 -0
  27. package/dist/generate-sdk.js +471 -0
  28. package/dist/index.d.ts +33 -0
  29. package/dist/index.js +232 -0
  30. package/package.json +61 -0
  31. package/src/bridge/config.ts +39 -0
  32. package/src/bridge/executor.ts +167 -0
  33. package/src/bridge/index.ts +156 -0
  34. package/src/bridge/linear-helpers.ts +49 -0
  35. package/src/bridge/reporter.ts +93 -0
  36. package/src/bridge/state.ts +50 -0
  37. package/src/bridge/transformer.ts +173 -0
  38. package/src/bridge/types.ts +45 -0
  39. package/src/bridge/watcher.ts +122 -0
  40. package/src/generate-sdk.ts +570 -0
  41. package/src/index.ts +287 -0
package/src/index.ts ADDED
@@ -0,0 +1,287 @@
1
+ import Anthropic from '@anthropic-ai/sdk';
2
+ import * as fs from 'fs/promises';
3
+ import * as path from 'path';
4
+
5
+ // Initialize Anthropic client
6
+ const anthropic = new Anthropic({
7
+ apiKey: process.env.ANTHROPIC_API_KEY!,
8
+ });
9
+
10
+ interface Story {
11
+ title: string;
12
+ description: string;
13
+ persona: string;
14
+ acceptanceCriteria: string[];
15
+ priority: 'high' | 'medium' | 'low';
16
+ tasks: Task[];
17
+ }
18
+
19
+ interface Task {
20
+ title: string;
21
+ description: string;
22
+ effort: number; // 1-5
23
+ parallel: boolean;
24
+ }
25
+
26
+ interface PRD {
27
+ title: string;
28
+ summary: string;
29
+ stories: Story[];
30
+ technicalNotes: string[];
31
+ }
32
+
33
+ /**
34
+ * Step 1: Generate PRD from feature description using Claude
35
+ */
36
+ async function generatePRD(featureDescription: string): Promise<PRD> {
37
+ console.log('šŸ¤” Generating PRD with Claude...\n');
38
+
39
+ const response = await anthropic.messages.create({
40
+ model: 'claude-sonnet-4-20250514',
41
+ max_tokens: 4000,
42
+ messages: [{
43
+ role: 'user',
44
+ content: `You are an expert product manager. Create a comprehensive Product Requirements Document (PRD) for the following feature:
45
+
46
+ FEATURE REQUEST:
47
+ ${featureDescription}
48
+
49
+ Generate a detailed PRD with:
50
+ 1. Executive summary
51
+ 2. 3-5 user stories following the format: "As a [persona], I want to [action] so that [benefit]"
52
+ 3. For each story, include acceptance criteria
53
+ 4. Technical notes and considerations
54
+
55
+ Respond ONLY with valid JSON matching this schema:
56
+ {
57
+ "title": "Feature Title",
58
+ "summary": "2-3 sentence executive summary",
59
+ "stories": [
60
+ {
61
+ "title": "Story title",
62
+ "description": "As a [persona], I want to [action] so that [benefit]",
63
+ "persona": "User type",
64
+ "acceptanceCriteria": ["criterion 1", "criterion 2"],
65
+ "priority": "high|medium|low"
66
+ }
67
+ ],
68
+ "technicalNotes": ["note 1", "note 2"]
69
+ }
70
+
71
+ Return ONLY the JSON, no markdown formatting or explanation.`
72
+ }]
73
+ });
74
+
75
+ const content = response.content[0];
76
+ if (content.type !== 'text') {
77
+ throw new Error('Unexpected response type');
78
+ }
79
+
80
+ // Parse JSON response
81
+ const prd: PRD = JSON.parse(content.text);
82
+
83
+ // Save PRD to file
84
+ const prdsDir = path.join(process.cwd(), '.linear', 'prds');
85
+ await fs.mkdir(prdsDir, { recursive: true });
86
+ const filename = `${prd.title.toLowerCase().replace(/\s+/g, '-')}-${Date.now()}.json`;
87
+ await fs.writeFile(
88
+ path.join(prdsDir, filename),
89
+ JSON.stringify(prd, null, 2)
90
+ );
91
+
92
+ console.log(`āœ… PRD generated and saved to .linear/prds/${filename}\n`);
93
+ return prd;
94
+ }
95
+
96
+ /**
97
+ * Step 2: Break down each story into tasks using Claude
98
+ */
99
+ async function generateTasks(story: Story): Promise<Task[]> {
100
+ console.log(` šŸ”Ø Breaking down: ${story.title}...`);
101
+
102
+ const response = await anthropic.messages.create({
103
+ model: 'claude-sonnet-4-20250514',
104
+ max_tokens: 2000,
105
+ messages: [{
106
+ role: 'user',
107
+ content: `Break down this user story into concrete development tasks:
108
+
109
+ STORY: ${story.title}
110
+ DESCRIPTION: ${story.description}
111
+ ACCEPTANCE CRITERIA:
112
+ ${story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join('\n')}
113
+
114
+ Create 3-7 specific tasks covering:
115
+ - Frontend/UI work
116
+ - Backend/API work
117
+ - Database changes (if needed)
118
+ - Testing requirements
119
+
120
+ For each task provide:
121
+ - Clear title (verb-object format like "Create login form" or "Implement JWT authentication")
122
+ - Description with technical details
123
+ - Effort estimate (1=small, 3=medium, 5=large)
124
+ - Whether it can run in parallel with other tasks
125
+
126
+ Respond ONLY with valid JSON array:
127
+ [
128
+ {
129
+ "title": "Task title",
130
+ "description": "Technical details of what needs to be done",
131
+ "effort": 1-5,
132
+ "parallel": true|false
133
+ }
134
+ ]
135
+
136
+ Return ONLY the JSON array, no markdown or explanation.`
137
+ }]
138
+ });
139
+
140
+ const content = response.content[0];
141
+ if (content.type !== 'text') {
142
+ throw new Error('Unexpected response type');
143
+ }
144
+
145
+ const tasks: Task[] = JSON.parse(content.text);
146
+ console.log(` āœ“ Generated ${tasks.length} tasks`);
147
+ return tasks;
148
+ }
149
+
150
+ /**
151
+ * Step 3: Create issues in Linear using MCP tools
152
+ */
153
+ async function createInLinear(prd: PRD, teamId: string, projectName?: string): Promise<string> {
154
+ console.log('\nšŸ“ Creating issues in Linear via MCP...\n');
155
+
156
+ // Build the tool use request for Claude
157
+ const toolMessages: Anthropic.MessageParam[] = [
158
+ {
159
+ role: 'user',
160
+ content: `You have access to Linear through MCP tools. Create a project and issues for this PRD:
161
+
162
+ PROJECT NAME: ${projectName || prd.title}
163
+ TEAM ID: ${teamId}
164
+
165
+ PRD DATA:
166
+ ${JSON.stringify(prd, null, 2)}
167
+
168
+ INSTRUCTIONS:
169
+ 1. First, search for existing projects with name "${projectName || prd.title}" to avoid duplicates
170
+ 2. If project doesn't exist, create it with the PRD summary as description
171
+ 3. For each story in the PRD:
172
+ - Create a parent issue with the story title and description
173
+ - Add label "user-story" if available
174
+ - Set priority based on the story's priority field
175
+ 4. For each task within a story:
176
+ - Create a child issue linked to the parent story
177
+ - Include effort estimate in the description
178
+ - Add label "task" if available
179
+
180
+ After creating all issues, provide a summary with:
181
+ - Project URL
182
+ - Number of stories created
183
+ - Number of tasks created
184
+ - Any issues encountered
185
+
186
+ Use Linear MCP tools to accomplish this.`
187
+ }
188
+ ];
189
+
190
+ const response = await anthropic.messages.create({
191
+ model: 'claude-sonnet-4-20250514',
192
+ max_tokens: 8000,
193
+ messages: toolMessages,
194
+ tools: [
195
+ {
196
+ type: 'mcp',
197
+ name: 'linear',
198
+ mcp_server_name: 'linear'
199
+ } as any
200
+ ]
201
+ });
202
+
203
+ // Process the response (Claude will have used Linear MCP tools)
204
+ let finalResponse = '';
205
+ for (const block of response.content) {
206
+ if (block.type === 'text') {
207
+ finalResponse += block.text;
208
+ }
209
+ }
210
+
211
+ console.log('\nāœ… Linear issues created!\n');
212
+ return finalResponse;
213
+ }
214
+
215
+ /**
216
+ * Main workflow
217
+ */
218
+ async function main() {
219
+ const args = process.argv.slice(2);
220
+
221
+ if (args.length === 0) {
222
+ console.log(`
223
+ Usage: npm start -- "Feature description" [teamId] [projectName]
224
+
225
+ Example:
226
+ npm start -- "Add user authentication with social login" "TEAM-123" "Auth Epic"
227
+
228
+ Environment variables required:
229
+ ANTHROPIC_API_KEY=sk-ant-...
230
+ LINEAR_API_KEY=lin_api_... (for MCP server)
231
+ `);
232
+ process.exit(1);
233
+ }
234
+
235
+ const featureDescription = args[0];
236
+ const teamId = args[1] || process.env.LINEAR_TEAM_ID;
237
+ const projectName = args[2];
238
+
239
+ if (!teamId) {
240
+ console.error('āŒ Error: Team ID required (provide as argument or LINEAR_TEAM_ID env var)');
241
+ process.exit(1);
242
+ }
243
+
244
+ try {
245
+ // Step 1: Generate PRD
246
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
247
+ console.log(' STEP 1: Generate PRD');
248
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
249
+ const prd = await generatePRD(featureDescription);
250
+
251
+ console.log(`šŸ“‹ PRD Summary:`);
252
+ console.log(` Title: ${prd.title}`);
253
+ console.log(` Stories: ${prd.stories.length}`);
254
+ console.log('');
255
+
256
+ // Step 2: Generate tasks for each story
257
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
258
+ console.log(' STEP 2: Generate Tasks');
259
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
260
+
261
+ for (const story of prd.stories) {
262
+ story.tasks = await generateTasks(story);
263
+ }
264
+
265
+ const totalTasks = prd.stories.reduce((sum, s) => sum + s.tasks.length, 0);
266
+ console.log(`\nāœ… Generated ${totalTasks} total tasks across ${prd.stories.length} stories\n`);
267
+
268
+ // Step 3: Create in Linear
269
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
270
+ console.log(' STEP 3: Create in Linear');
271
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
272
+
273
+ const summary = await createInLinear(prd, teamId, projectName);
274
+ console.log(summary);
275
+
276
+ } catch (error) {
277
+ console.error('āŒ Error:', error);
278
+ process.exit(1);
279
+ }
280
+ }
281
+
282
+ // Run if called directly
283
+ if (require.main === module) {
284
+ main();
285
+ }
286
+
287
+ export { generatePRD, generateTasks, createInLinear };