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.
- package/.env.example +3 -0
- package/README.md +220 -0
- package/bin/cli.js +21 -0
- package/bin/cli.ts +51 -0
- package/bin/init.ts +252 -0
- package/commands/build-from-linear.md +193 -0
- package/commands/generate-stories.md +110 -0
- package/dist/bridge/config.d.ts +11 -0
- package/dist/bridge/config.js +24 -0
- package/dist/bridge/executor.d.ts +8 -0
- package/dist/bridge/executor.js +147 -0
- package/dist/bridge/index.d.ts +3 -0
- package/dist/bridge/index.js +137 -0
- package/dist/bridge/linear-helpers.d.ts +6 -0
- package/dist/bridge/linear-helpers.js +43 -0
- package/dist/bridge/reporter.d.ts +7 -0
- package/dist/bridge/reporter.js +79 -0
- package/dist/bridge/state.d.ts +10 -0
- package/dist/bridge/state.js +37 -0
- package/dist/bridge/transformer.d.ts +22 -0
- package/dist/bridge/transformer.js +142 -0
- package/dist/bridge/types.d.ts +44 -0
- package/dist/bridge/types.js +1 -0
- package/dist/bridge/watcher.d.ts +13 -0
- package/dist/bridge/watcher.js +104 -0
- package/dist/generate-sdk.d.ts +1 -0
- package/dist/generate-sdk.js +471 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +232 -0
- package/package.json +61 -0
- package/src/bridge/config.ts +39 -0
- package/src/bridge/executor.ts +167 -0
- package/src/bridge/index.ts +156 -0
- package/src/bridge/linear-helpers.ts +49 -0
- package/src/bridge/reporter.ts +93 -0
- package/src/bridge/state.ts +50 -0
- package/src/bridge/transformer.ts +173 -0
- package/src/bridge/types.ts +45 -0
- package/src/bridge/watcher.ts +122 -0
- package/src/generate-sdk.ts +570 -0
- 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 };
|