forge-cc 0.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/.forge.json +5 -0
- package/AGENTS.md +42 -0
- package/README.md +283 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +148 -0
- package/dist/cli.js.map +1 -0
- package/dist/config/loader.d.ts +2 -0
- package/dist/config/loader.js +44 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +57 -0
- package/dist/config/schema.js +15 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/gates/index.d.ts +11 -0
- package/dist/gates/index.js +106 -0
- package/dist/gates/index.js.map +1 -0
- package/dist/gates/lint-gate.d.ts +2 -0
- package/dist/gates/lint-gate.js +66 -0
- package/dist/gates/lint-gate.js.map +1 -0
- package/dist/gates/prd-gate.d.ts +7 -0
- package/dist/gates/prd-gate.js +193 -0
- package/dist/gates/prd-gate.js.map +1 -0
- package/dist/gates/runtime-gate.d.ts +5 -0
- package/dist/gates/runtime-gate.js +99 -0
- package/dist/gates/runtime-gate.js.map +1 -0
- package/dist/gates/tests-gate.d.ts +2 -0
- package/dist/gates/tests-gate.js +116 -0
- package/dist/gates/tests-gate.js.map +1 -0
- package/dist/gates/types-gate.d.ts +2 -0
- package/dist/gates/types-gate.js +59 -0
- package/dist/gates/types-gate.js.map +1 -0
- package/dist/gates/visual-gate.d.ts +6 -0
- package/dist/gates/visual-gate.js +118 -0
- package/dist/gates/visual-gate.js.map +1 -0
- package/dist/go/auto-chain.d.ts +107 -0
- package/dist/go/auto-chain.js +303 -0
- package/dist/go/auto-chain.js.map +1 -0
- package/dist/go/executor.d.ts +130 -0
- package/dist/go/executor.js +409 -0
- package/dist/go/executor.js.map +1 -0
- package/dist/go/finalize.d.ts +58 -0
- package/dist/go/finalize.js +200 -0
- package/dist/go/finalize.js.map +1 -0
- package/dist/go/linear-sync.d.ts +75 -0
- package/dist/go/linear-sync.js +239 -0
- package/dist/go/linear-sync.js.map +1 -0
- package/dist/go/verify-loop.d.ts +47 -0
- package/dist/go/verify-loop.js +172 -0
- package/dist/go/verify-loop.js.map +1 -0
- package/dist/hooks/pre-commit.d.ts +5 -0
- package/dist/hooks/pre-commit.js +69 -0
- package/dist/hooks/pre-commit.js.map +1 -0
- package/dist/linear/client.d.ts +108 -0
- package/dist/linear/client.js +388 -0
- package/dist/linear/client.js.map +1 -0
- package/dist/linear/issues.d.ts +20 -0
- package/dist/linear/issues.js +39 -0
- package/dist/linear/issues.js.map +1 -0
- package/dist/linear/milestones.d.ts +11 -0
- package/dist/linear/milestones.js +32 -0
- package/dist/linear/milestones.js.map +1 -0
- package/dist/linear/projects.d.ts +16 -0
- package/dist/linear/projects.js +50 -0
- package/dist/linear/projects.js.map +1 -0
- package/dist/reporter/human.d.ts +2 -0
- package/dist/reporter/human.js +63 -0
- package/dist/reporter/human.js.map +1 -0
- package/dist/reporter/json.d.ts +2 -0
- package/dist/reporter/json.js +4 -0
- package/dist/reporter/json.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +109 -0
- package/dist/server.js.map +1 -0
- package/dist/spec/generator.d.ts +14 -0
- package/dist/spec/generator.js +206 -0
- package/dist/spec/generator.js.map +1 -0
- package/dist/spec/interview.d.ts +104 -0
- package/dist/spec/interview.js +342 -0
- package/dist/spec/interview.js.map +1 -0
- package/dist/spec/linear-sync.d.ts +48 -0
- package/dist/spec/linear-sync.js +125 -0
- package/dist/spec/linear-sync.js.map +1 -0
- package/dist/spec/scanner.d.ts +45 -0
- package/dist/spec/scanner.js +473 -0
- package/dist/spec/scanner.js.map +1 -0
- package/dist/spec/templates.d.ts +345 -0
- package/dist/spec/templates.js +86 -0
- package/dist/spec/templates.js.map +1 -0
- package/dist/state/reader.d.ts +29 -0
- package/dist/state/reader.js +116 -0
- package/dist/state/reader.js.map +1 -0
- package/dist/state/writer.d.ts +60 -0
- package/dist/state/writer.js +222 -0
- package/dist/state/writer.js.map +1 -0
- package/dist/types.d.ts +64 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/browser.d.ts +10 -0
- package/dist/utils/browser.js +89 -0
- package/dist/utils/browser.js.map +1 -0
- package/hooks/pre-commit-verify.js +103 -0
- package/package.json +68 -0
- package/skills/README.md +33 -0
- package/skills/forge-go.md +332 -0
- package/skills/forge-spec.md +251 -0
- package/skills/forge-triage.md +133 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adaptive interview engine for spec generation.
|
|
3
|
+
*
|
|
4
|
+
* Pure logic — no side effects, no I/O. All state is passed in and returned.
|
|
5
|
+
* The interview leads with recommendations derived from codebase scan results,
|
|
6
|
+
* follows interesting threads based on user responses, and determines when
|
|
7
|
+
* enough info has been gathered for each PRD section.
|
|
8
|
+
*/
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Types
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/** PRD sections in priority order */
|
|
13
|
+
export const PRD_SECTIONS = [
|
|
14
|
+
"problem_and_goals",
|
|
15
|
+
"user_stories",
|
|
16
|
+
"technical_approach",
|
|
17
|
+
"scope",
|
|
18
|
+
"milestones",
|
|
19
|
+
];
|
|
20
|
+
/** Human-readable labels for each section */
|
|
21
|
+
export const SECTION_LABELS = {
|
|
22
|
+
problem_and_goals: "Problem & Goals",
|
|
23
|
+
user_stories: "User Stories",
|
|
24
|
+
technical_approach: "Technical Approach",
|
|
25
|
+
scope: "Scope",
|
|
26
|
+
milestones: "Milestones",
|
|
27
|
+
};
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Minimum answer thresholds per section
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
const MIN_ANSWERS = {
|
|
32
|
+
problem_and_goals: 2,
|
|
33
|
+
user_stories: 2,
|
|
34
|
+
technical_approach: 1,
|
|
35
|
+
scope: 1,
|
|
36
|
+
milestones: 1,
|
|
37
|
+
};
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
// Interview Creation
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
/**
|
|
42
|
+
* Initialize a new interview with codebase context.
|
|
43
|
+
* The scan results inform the recommendations attached to questions.
|
|
44
|
+
*/
|
|
45
|
+
export function createInterview(projectName, scanResults) {
|
|
46
|
+
return {
|
|
47
|
+
projectName,
|
|
48
|
+
scanResults,
|
|
49
|
+
questions: [],
|
|
50
|
+
answers: [],
|
|
51
|
+
nextQuestionId: 1,
|
|
52
|
+
askedSections: [],
|
|
53
|
+
answersSinceLastDraft: 0,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Question Generation
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
/**
|
|
60
|
+
* Generate 1-3 next questions based on current state.
|
|
61
|
+
* Prioritizes sections with the most gaps, leads with recommendations.
|
|
62
|
+
* Returns questions and the updated state (with new questions appended).
|
|
63
|
+
*/
|
|
64
|
+
export function generateNextQuestions(state) {
|
|
65
|
+
const statuses = getSectionStatuses(state);
|
|
66
|
+
const pendingFollowUp = findFollowUpOpportunities(state);
|
|
67
|
+
const newQuestions = [];
|
|
68
|
+
let nextId = state.nextQuestionId;
|
|
69
|
+
// Priority 1: Follow up on interesting threads (max 1 follow-up per batch)
|
|
70
|
+
if (pendingFollowUp.length > 0 && newQuestions.length < 3) {
|
|
71
|
+
const followUp = pendingFollowUp[0];
|
|
72
|
+
newQuestions.push({
|
|
73
|
+
id: `q${nextId++}`,
|
|
74
|
+
section: followUp.section,
|
|
75
|
+
text: followUp.text,
|
|
76
|
+
context: followUp.context,
|
|
77
|
+
depth: followUp.depth,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// Priority 2: Ask about incomplete sections in priority order
|
|
81
|
+
for (const section of PRD_SECTIONS) {
|
|
82
|
+
if (newQuestions.length >= 3)
|
|
83
|
+
break;
|
|
84
|
+
const status = statuses.find((s) => s.section === section);
|
|
85
|
+
if (status?.isComplete)
|
|
86
|
+
continue;
|
|
87
|
+
// Skip if we already have a question for this section in this batch
|
|
88
|
+
if (newQuestions.some((q) => q.section === section))
|
|
89
|
+
continue;
|
|
90
|
+
const question = generateSectionQuestion(state, section, nextId);
|
|
91
|
+
if (question) {
|
|
92
|
+
newQuestions.push(question);
|
|
93
|
+
nextId++;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// If we generated nothing (everything complete), return empty
|
|
97
|
+
if (newQuestions.length === 0) {
|
|
98
|
+
return { questions: [], state };
|
|
99
|
+
}
|
|
100
|
+
const updatedState = {
|
|
101
|
+
...state,
|
|
102
|
+
questions: [...state.questions, ...newQuestions],
|
|
103
|
+
nextQuestionId: nextId,
|
|
104
|
+
askedSections: [
|
|
105
|
+
...new Set([
|
|
106
|
+
...state.askedSections,
|
|
107
|
+
...newQuestions.map((q) => q.section),
|
|
108
|
+
]),
|
|
109
|
+
],
|
|
110
|
+
};
|
|
111
|
+
return { questions: newQuestions, state: updatedState };
|
|
112
|
+
}
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// Answer Recording
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
/**
|
|
117
|
+
* Record the user's answer and return updated state.
|
|
118
|
+
*/
|
|
119
|
+
export function recordAnswer(state, questionId, answer) {
|
|
120
|
+
const question = state.questions.find((q) => q.id === questionId);
|
|
121
|
+
if (!question) {
|
|
122
|
+
throw new Error(`Question not found: ${questionId}`);
|
|
123
|
+
}
|
|
124
|
+
const newAnswer = {
|
|
125
|
+
questionId,
|
|
126
|
+
section: question.section,
|
|
127
|
+
answer,
|
|
128
|
+
timestamp: Date.now(),
|
|
129
|
+
};
|
|
130
|
+
return {
|
|
131
|
+
...state,
|
|
132
|
+
answers: [...state.answers, newAnswer],
|
|
133
|
+
answersSinceLastDraft: state.answersSinceLastDraft + 1,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Draft Update Check
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
/**
|
|
140
|
+
* Returns true every 2-3 answers (triggers at 2, then every 3).
|
|
141
|
+
* The caller is responsible for resetting the counter after updating the draft.
|
|
142
|
+
*/
|
|
143
|
+
export function shouldUpdateDraft(state) {
|
|
144
|
+
return state.answersSinceLastDraft >= 2;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Reset the draft update counter (call after updating the PRD draft).
|
|
148
|
+
*/
|
|
149
|
+
export function markDraftUpdated(state) {
|
|
150
|
+
return {
|
|
151
|
+
...state,
|
|
152
|
+
answersSinceLastDraft: 0,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
// Completeness Check
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
/**
|
|
159
|
+
* Returns true when all sections have met their minimum answer thresholds.
|
|
160
|
+
*/
|
|
161
|
+
export function isComplete(state) {
|
|
162
|
+
return getSectionStatuses(state).every((s) => s.isComplete);
|
|
163
|
+
}
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
// Section Statuses
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
/**
|
|
168
|
+
* Get the completeness status for all sections.
|
|
169
|
+
*/
|
|
170
|
+
export function getSectionStatuses(state) {
|
|
171
|
+
return PRD_SECTIONS.map((section) => {
|
|
172
|
+
const answeredCount = state.answers.filter((a) => a.section === section).length;
|
|
173
|
+
const minRequired = MIN_ANSWERS[section];
|
|
174
|
+
return {
|
|
175
|
+
section,
|
|
176
|
+
answeredCount,
|
|
177
|
+
minRequired,
|
|
178
|
+
isComplete: answeredCount >= minRequired,
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
// Interview Summary
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
/**
|
|
186
|
+
* Build a structured summary of everything gathered, for PRD generation.
|
|
187
|
+
*/
|
|
188
|
+
export function getInterviewSummary(state) {
|
|
189
|
+
const statuses = getSectionStatuses(state);
|
|
190
|
+
const sections = {};
|
|
191
|
+
for (const section of PRD_SECTIONS) {
|
|
192
|
+
const sectionAnswers = state.answers.filter((a) => a.section === section);
|
|
193
|
+
const status = statuses.find((s) => s.section === section);
|
|
194
|
+
sections[section] = {
|
|
195
|
+
label: SECTION_LABELS[section],
|
|
196
|
+
answers: sectionAnswers.map((a) => {
|
|
197
|
+
const question = state.questions.find((q) => q.id === a.questionId);
|
|
198
|
+
return {
|
|
199
|
+
question: question?.text ?? "(unknown question)",
|
|
200
|
+
answer: a.answer,
|
|
201
|
+
};
|
|
202
|
+
}),
|
|
203
|
+
isComplete: status.isComplete,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
projectName: state.projectName,
|
|
208
|
+
sections,
|
|
209
|
+
scanResults: state.scanResults,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Internal Helpers
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
/**
|
|
216
|
+
* Generate a question for a specific section based on codebase context.
|
|
217
|
+
* Returns null if no meaningful question can be generated.
|
|
218
|
+
*/
|
|
219
|
+
function generateSectionQuestion(state, section, nextId) {
|
|
220
|
+
const answeredCount = state.answers.filter((a) => a.section === section).length;
|
|
221
|
+
const scan = state.scanResults;
|
|
222
|
+
const q = (text, context) => ({
|
|
223
|
+
id: `q${nextId}`,
|
|
224
|
+
section,
|
|
225
|
+
text,
|
|
226
|
+
context,
|
|
227
|
+
depth: 0,
|
|
228
|
+
});
|
|
229
|
+
switch (section) {
|
|
230
|
+
case "problem_and_goals": {
|
|
231
|
+
if (answeredCount === 0) {
|
|
232
|
+
const fw = scan.structure.frameworks.length > 0 ? scan.structure.frameworks.join(", ") : null;
|
|
233
|
+
return q("What problem does this project solve? What's the desired outcome when it's done?", `I found a ${scan.structure.language} project${fw ? ` using ${fw}` : ""}. Help me understand what you're building and why.`);
|
|
234
|
+
}
|
|
235
|
+
return q("How will you know this project is successful? What does 'done' look like?", "I want to define clear success criteria for the PRD.");
|
|
236
|
+
}
|
|
237
|
+
case "user_stories": {
|
|
238
|
+
if (answeredCount === 0) {
|
|
239
|
+
const pageRoutes = scan.routes.routes.filter((r) => r.type === "page");
|
|
240
|
+
const apiRoutes = scan.routes.routes.filter((r) => r.type === "api");
|
|
241
|
+
const hasRoutes = pageRoutes.length > 0;
|
|
242
|
+
const hasAPI = apiRoutes.length > 0;
|
|
243
|
+
const routeContext = hasRoutes
|
|
244
|
+
? `I see ${pageRoutes.length} page(s) and ${apiRoutes.length} API route(s).`
|
|
245
|
+
: hasAPI
|
|
246
|
+
? `I see ${apiRoutes.length} API route(s) but no pages.`
|
|
247
|
+
: "I don't see existing routes yet.";
|
|
248
|
+
return q("Who are the primary users of this feature? What do they need to accomplish?", `${routeContext} Understanding the users will help me structure milestones around their journeys.`);
|
|
249
|
+
}
|
|
250
|
+
return q("Are there secondary users or admin workflows to consider?", "Capturing all user types early prevents scope creep later.");
|
|
251
|
+
}
|
|
252
|
+
case "technical_approach": {
|
|
253
|
+
const fw = scan.structure.frameworks;
|
|
254
|
+
const fwLabel = fw.length > 0 ? fw.join(", ") : null;
|
|
255
|
+
const models = scan.dataAPIs.dataModels;
|
|
256
|
+
const hasDB = models.length > 0;
|
|
257
|
+
const dbLabel = hasDB
|
|
258
|
+
? models.slice(0, 3).map((m) => m.name).join(", ")
|
|
259
|
+
: "";
|
|
260
|
+
if (answeredCount === 0) {
|
|
261
|
+
return q("Are there specific technical decisions already made? Any constraints on architecture, APIs, or data storage?", `Current stack: ${scan.structure.language}${fwLabel ? `/${fwLabel}` : ""}${hasDB ? `, data models: [${dbLabel}]` : ""}. I'll build the technical approach around existing decisions.`);
|
|
262
|
+
}
|
|
263
|
+
return null; // One answer usually sufficient for technical approach
|
|
264
|
+
}
|
|
265
|
+
case "scope": {
|
|
266
|
+
const configFiles = scan.structure.configFiles;
|
|
267
|
+
if (answeredCount === 0) {
|
|
268
|
+
return q("What's explicitly OUT of scope? Are there any sacred files or areas of the codebase that should not be touched?", configFiles.length > 0
|
|
269
|
+
? `Key config files I found: ${configFiles.slice(0, 8).join(", ")}. Knowing what's off-limits helps me write safer milestones.`
|
|
270
|
+
: "Defining boundaries early prevents scope creep.");
|
|
271
|
+
}
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
case "milestones": {
|
|
275
|
+
if (answeredCount === 0) {
|
|
276
|
+
return q("How would you break this work into deliverable chunks? Any natural phases or dependencies between pieces?", "I'll structure Linear milestones around your phasing. Each milestone should be independently shippable.");
|
|
277
|
+
}
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Find opportunities for follow-up questions based on recent answers.
|
|
284
|
+
* Looks for keywords/patterns that suggest deeper exploration would be valuable.
|
|
285
|
+
*/
|
|
286
|
+
function findFollowUpOpportunities(state) {
|
|
287
|
+
const opportunities = [];
|
|
288
|
+
// Only consider the last 3 answers for follow-ups
|
|
289
|
+
const recentAnswers = state.answers.slice(-3);
|
|
290
|
+
for (const answer of recentAnswers) {
|
|
291
|
+
const question = state.questions.find((q) => q.id === answer.questionId);
|
|
292
|
+
if (!question)
|
|
293
|
+
continue;
|
|
294
|
+
// Don't follow up on follow-ups beyond depth 2
|
|
295
|
+
if (question.depth >= 2)
|
|
296
|
+
continue;
|
|
297
|
+
// Don't generate follow-ups for questions we've already followed up on
|
|
298
|
+
const hasFollowUp = state.questions.some((q) => q.depth > question.depth &&
|
|
299
|
+
q.section === question.section &&
|
|
300
|
+
state.questions.indexOf(q) > state.questions.indexOf(question));
|
|
301
|
+
if (hasFollowUp)
|
|
302
|
+
continue;
|
|
303
|
+
const lower = answer.answer.toLowerCase();
|
|
304
|
+
// Detect mentions of migration/breaking changes — worth digging into
|
|
305
|
+
if ((lower.includes("migrat") || lower.includes("breaking")) &&
|
|
306
|
+
question.section !== "scope") {
|
|
307
|
+
opportunities.push({
|
|
308
|
+
section: "scope",
|
|
309
|
+
text: "You mentioned migration/breaking changes. What existing data or APIs need to be preserved? Any backward compatibility requirements?",
|
|
310
|
+
context: `Based on your answer about ${SECTION_LABELS[question.section]}.`,
|
|
311
|
+
depth: question.depth + 1,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
// Detect mentions of multiple user types — worth expanding user stories
|
|
315
|
+
if ((lower.includes("admin") ||
|
|
316
|
+
lower.includes("different user") ||
|
|
317
|
+
lower.includes("roles")) &&
|
|
318
|
+
answer.section === "user_stories") {
|
|
319
|
+
opportunities.push({
|
|
320
|
+
section: "user_stories",
|
|
321
|
+
text: "You mentioned different user types/roles. Can you walk me through the key workflow for each type?",
|
|
322
|
+
context: "Multiple user types often need separate milestone planning.",
|
|
323
|
+
depth: question.depth + 1,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
// Detect mentions of external services — worth clarifying integration scope
|
|
327
|
+
if ((lower.includes("api") ||
|
|
328
|
+
lower.includes("third-party") ||
|
|
329
|
+
lower.includes("external") ||
|
|
330
|
+
lower.includes("integration")) &&
|
|
331
|
+
answer.section === "technical_approach") {
|
|
332
|
+
opportunities.push({
|
|
333
|
+
section: "technical_approach",
|
|
334
|
+
text: "You mentioned external integrations. Which services are critical path vs nice-to-have? Any rate limits or auth concerns?",
|
|
335
|
+
context: "External dependencies often need their own milestone.",
|
|
336
|
+
depth: question.depth + 1,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return opportunities;
|
|
341
|
+
}
|
|
342
|
+
//# sourceMappingURL=interview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interview.js","sourceRoot":"","sources":["../../src/spec/interview.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syncs a PRD to Linear: creates milestones, issues (one per user story),
|
|
3
|
+
* and transitions the project to "Planned".
|
|
4
|
+
*/
|
|
5
|
+
import { LinearClient } from "../linear/client.js";
|
|
6
|
+
import type { LinearMilestone, LinearIssue } from "../linear/client.js";
|
|
7
|
+
import type { PRDData } from "./templates.js";
|
|
8
|
+
export interface SyncedMilestone {
|
|
9
|
+
prdNumber: number;
|
|
10
|
+
name: string;
|
|
11
|
+
linearMilestone: LinearMilestone;
|
|
12
|
+
issues: SyncedIssue[];
|
|
13
|
+
}
|
|
14
|
+
export interface SyncedIssue {
|
|
15
|
+
userStoryId: string;
|
|
16
|
+
title: string;
|
|
17
|
+
linearIssue: LinearIssue;
|
|
18
|
+
}
|
|
19
|
+
export interface IssueError {
|
|
20
|
+
userStoryId: string;
|
|
21
|
+
title: string;
|
|
22
|
+
error: string;
|
|
23
|
+
}
|
|
24
|
+
export interface MilestoneError {
|
|
25
|
+
prdNumber: number;
|
|
26
|
+
name: string;
|
|
27
|
+
error: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SyncResult {
|
|
30
|
+
projectId: string;
|
|
31
|
+
milestonesCreated: number;
|
|
32
|
+
issuesCreated: number;
|
|
33
|
+
milestones: SyncedMilestone[];
|
|
34
|
+
errors: {
|
|
35
|
+
milestones: MilestoneError[];
|
|
36
|
+
issues: IssueError[];
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Sync a PRD to Linear:
|
|
41
|
+
* 1. Create milestones (one per PRD milestone)
|
|
42
|
+
* 2. Create issues under each milestone (one per user story)
|
|
43
|
+
* 3. Transition project to "Planned"
|
|
44
|
+
*
|
|
45
|
+
* Handles partial failures — if a milestone or issue fails to create,
|
|
46
|
+
* the error is captured and the sync continues with the rest.
|
|
47
|
+
*/
|
|
48
|
+
export declare function syncPRDToLinear(prdData: PRDData, projectId: string, teamId: string, client: LinearClient): Promise<SyncResult>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Syncs a PRD to Linear: creates milestones, issues (one per user story),
|
|
3
|
+
* and transitions the project to "Planned".
|
|
4
|
+
*/
|
|
5
|
+
import { createProjectMilestone } from "../linear/milestones.js";
|
|
6
|
+
import { createMilestoneIssue } from "../linear/issues.js";
|
|
7
|
+
import { transitionProject } from "../linear/projects.js";
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Helpers
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Build issue descriptions from user stories, including acceptance criteria.
|
|
13
|
+
*/
|
|
14
|
+
function buildIssueDescription(story) {
|
|
15
|
+
const lines = [story.description];
|
|
16
|
+
if (story.acceptanceCriteria.length > 0) {
|
|
17
|
+
lines.push("", "## Acceptance Criteria");
|
|
18
|
+
for (const criterion of story.acceptanceCriteria) {
|
|
19
|
+
lines.push(`- [ ] ${criterion}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return lines.join("\n");
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve which user stories belong to a milestone.
|
|
26
|
+
* Each milestone's `waves` contain agents with tasks — we map all
|
|
27
|
+
* user stories to the milestone based on the PRD structure.
|
|
28
|
+
* Since milestones don't have explicit userStory references in the schema,
|
|
29
|
+
* we distribute stories evenly across milestones, or assign all to the
|
|
30
|
+
* first milestone if there's only one.
|
|
31
|
+
*/
|
|
32
|
+
function assignStoriesToMilestones(milestones, stories) {
|
|
33
|
+
const map = new Map();
|
|
34
|
+
if (milestones.length === 0)
|
|
35
|
+
return map;
|
|
36
|
+
// Initialize all milestones with empty arrays
|
|
37
|
+
for (const m of milestones) {
|
|
38
|
+
map.set(m.number, []);
|
|
39
|
+
}
|
|
40
|
+
// Distribute stories across milestones round-robin
|
|
41
|
+
for (let i = 0; i < stories.length; i++) {
|
|
42
|
+
const milestoneIdx = i % milestones.length;
|
|
43
|
+
const milestoneNumber = milestones[milestoneIdx].number;
|
|
44
|
+
map.get(milestoneNumber).push(stories[i]);
|
|
45
|
+
}
|
|
46
|
+
return map;
|
|
47
|
+
}
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Main
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
/**
|
|
52
|
+
* Sync a PRD to Linear:
|
|
53
|
+
* 1. Create milestones (one per PRD milestone)
|
|
54
|
+
* 2. Create issues under each milestone (one per user story)
|
|
55
|
+
* 3. Transition project to "Planned"
|
|
56
|
+
*
|
|
57
|
+
* Handles partial failures — if a milestone or issue fails to create,
|
|
58
|
+
* the error is captured and the sync continues with the rest.
|
|
59
|
+
*/
|
|
60
|
+
export async function syncPRDToLinear(prdData, projectId, teamId, client) {
|
|
61
|
+
const result = {
|
|
62
|
+
projectId,
|
|
63
|
+
milestonesCreated: 0,
|
|
64
|
+
issuesCreated: 0,
|
|
65
|
+
milestones: [],
|
|
66
|
+
errors: {
|
|
67
|
+
milestones: [],
|
|
68
|
+
issues: [],
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
const storyMap = assignStoriesToMilestones(prdData.milestones, prdData.userStories);
|
|
72
|
+
// 1. Create milestones and their issues
|
|
73
|
+
for (const milestone of prdData.milestones) {
|
|
74
|
+
let linearMilestone;
|
|
75
|
+
try {
|
|
76
|
+
linearMilestone = await createProjectMilestone(client, projectId, `M${milestone.number}: ${milestone.name}`, milestone.goal);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
result.errors.milestones.push({
|
|
80
|
+
prdNumber: milestone.number,
|
|
81
|
+
name: milestone.name,
|
|
82
|
+
error: err instanceof Error ? err.message : String(err),
|
|
83
|
+
});
|
|
84
|
+
continue; // Skip issues for this milestone
|
|
85
|
+
}
|
|
86
|
+
result.milestonesCreated++;
|
|
87
|
+
const syncedMilestone = {
|
|
88
|
+
prdNumber: milestone.number,
|
|
89
|
+
name: milestone.name,
|
|
90
|
+
linearMilestone,
|
|
91
|
+
issues: [],
|
|
92
|
+
};
|
|
93
|
+
// Create issues for user stories assigned to this milestone
|
|
94
|
+
const stories = storyMap.get(milestone.number) ?? [];
|
|
95
|
+
for (const story of stories) {
|
|
96
|
+
try {
|
|
97
|
+
const linearIssue = await createMilestoneIssue(client, {
|
|
98
|
+
title: `${story.id}: ${story.title}`,
|
|
99
|
+
description: buildIssueDescription(story),
|
|
100
|
+
teamId,
|
|
101
|
+
projectId,
|
|
102
|
+
milestoneId: linearMilestone.id,
|
|
103
|
+
});
|
|
104
|
+
syncedMilestone.issues.push({
|
|
105
|
+
userStoryId: story.id,
|
|
106
|
+
title: story.title,
|
|
107
|
+
linearIssue,
|
|
108
|
+
});
|
|
109
|
+
result.issuesCreated++;
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
result.errors.issues.push({
|
|
113
|
+
userStoryId: story.id,
|
|
114
|
+
title: story.title,
|
|
115
|
+
error: err instanceof Error ? err.message : String(err),
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
result.milestones.push(syncedMilestone);
|
|
120
|
+
}
|
|
121
|
+
// 2. Transition project to "Planned"
|
|
122
|
+
await transitionProject(client, projectId, "Planned");
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=linear-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"linear-sync.js","sourceRoot":"","sources":["../../src/spec/linear-sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface StructureScanResult {
|
|
2
|
+
projectName: string;
|
|
3
|
+
frameworks: string[];
|
|
4
|
+
language: "typescript" | "javascript" | "unknown";
|
|
5
|
+
packageManager: "npm" | "yarn" | "pnpm" | "bun" | "unknown";
|
|
6
|
+
configFiles: string[];
|
|
7
|
+
topLevelDirs: string[];
|
|
8
|
+
entryPoints: string[];
|
|
9
|
+
}
|
|
10
|
+
export interface RouteInfo {
|
|
11
|
+
path: string;
|
|
12
|
+
file: string;
|
|
13
|
+
type: "page" | "api" | "layout" | "component" | "middleware";
|
|
14
|
+
}
|
|
15
|
+
export interface RoutesScanResult {
|
|
16
|
+
framework: string | null;
|
|
17
|
+
routeDir: string | null;
|
|
18
|
+
routes: RouteInfo[];
|
|
19
|
+
components: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface APIEndpoint {
|
|
22
|
+
method: string;
|
|
23
|
+
path: string;
|
|
24
|
+
file: string;
|
|
25
|
+
}
|
|
26
|
+
export interface DataModel {
|
|
27
|
+
name: string;
|
|
28
|
+
file: string;
|
|
29
|
+
source: "prisma" | "drizzle" | "mongoose" | "typeorm" | "sql" | "unknown";
|
|
30
|
+
}
|
|
31
|
+
export interface DataAPIsScanResult {
|
|
32
|
+
apiEndpoints: APIEndpoint[];
|
|
33
|
+
dataModels: DataModel[];
|
|
34
|
+
externalServices: string[];
|
|
35
|
+
databaseType: string | null;
|
|
36
|
+
}
|
|
37
|
+
export interface ScanAllResult {
|
|
38
|
+
structure: StructureScanResult;
|
|
39
|
+
routes: RoutesScanResult;
|
|
40
|
+
dataAPIs: DataAPIsScanResult;
|
|
41
|
+
}
|
|
42
|
+
export declare function scanStructure(projectDir: string): Promise<StructureScanResult>;
|
|
43
|
+
export declare function scanRoutes(projectDir: string): Promise<RoutesScanResult>;
|
|
44
|
+
export declare function scanDataAPIs(projectDir: string): Promise<DataAPIsScanResult>;
|
|
45
|
+
export declare function scanAll(projectDir: string): Promise<ScanAllResult>;
|