@weavelogic/knowledge-graph-agent 0.12.0 → 0.12.1
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/dist/node_modules/@typescript-eslint/project-service/dist/index.js +1 -1
- package/dist/node_modules/tinyglobby/dist/index.js +1 -1
- package/dist/sparc/sparc-planner.d.ts +194 -30
- package/dist/sparc/sparc-planner.d.ts.map +1 -1
- package/dist/sparc/sparc-planner.js +1144 -341
- package/dist/sparc/sparc-planner.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync
|
|
2
|
-
import { join,
|
|
1
|
+
import { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync, statSync } from "fs";
|
|
2
|
+
import { join, basename, relative, extname } from "path";
|
|
3
|
+
import matter from "gray-matter";
|
|
3
4
|
import { createLogger } from "../utils/logger.js";
|
|
4
5
|
import { createDecisionLogManager } from "./decision-log.js";
|
|
5
6
|
import { createConsensusBuilder, ConsensusBuilder } from "./consensus.js";
|
|
@@ -10,9 +11,11 @@ class SPARCPlanner {
|
|
|
10
11
|
plan;
|
|
11
12
|
decisionLog;
|
|
12
13
|
consensusBuilder;
|
|
14
|
+
parsedDocs = [];
|
|
13
15
|
constructor(options) {
|
|
14
16
|
this.options = {
|
|
15
17
|
outputDir: join(options.projectRoot, ".sparc"),
|
|
18
|
+
docsDir: "docs",
|
|
16
19
|
parallelResearch: true,
|
|
17
20
|
reviewPasses: 3,
|
|
18
21
|
autoConsensus: true,
|
|
@@ -31,7 +34,8 @@ class SPARCPlanner {
|
|
|
31
34
|
});
|
|
32
35
|
logger.info("SPARC Planner initialized", {
|
|
33
36
|
planId: this.plan.id,
|
|
34
|
-
projectRoot: this.options.projectRoot
|
|
37
|
+
projectRoot: this.options.projectRoot,
|
|
38
|
+
docsDir: this.options.docsDir
|
|
35
39
|
});
|
|
36
40
|
}
|
|
37
41
|
/**
|
|
@@ -112,7 +116,8 @@ class SPARCPlanner {
|
|
|
112
116
|
logger.info("SPARC planning completed", {
|
|
113
117
|
planId: this.plan.id,
|
|
114
118
|
status: this.plan.status,
|
|
115
|
-
tasks: this.plan.tasks.length
|
|
119
|
+
tasks: this.plan.tasks.length,
|
|
120
|
+
docsAnalyzed: this.parsedDocs.length
|
|
116
121
|
});
|
|
117
122
|
return this.plan;
|
|
118
123
|
} catch (error) {
|
|
@@ -122,134 +127,251 @@ class SPARCPlanner {
|
|
|
122
127
|
}
|
|
123
128
|
}
|
|
124
129
|
/**
|
|
125
|
-
* Execute research phase
|
|
130
|
+
* Execute research phase - read and parse all documentation
|
|
126
131
|
*/
|
|
127
132
|
async executeResearchPhase() {
|
|
128
133
|
logger.info("Executing research phase");
|
|
129
134
|
this.plan.currentPhase = "specification";
|
|
135
|
+
const docsPath = join(this.options.projectRoot, this.options.docsDir);
|
|
136
|
+
if (existsSync(docsPath)) {
|
|
137
|
+
this.parsedDocs = await this.readDocsDirectory(docsPath);
|
|
138
|
+
for (const doc of this.parsedDocs) {
|
|
139
|
+
const finding = this.createFindingFromDoc(doc);
|
|
140
|
+
this.plan.researchFindings.push(finding);
|
|
141
|
+
}
|
|
142
|
+
this.addDecision(
|
|
143
|
+
"Documentation Analysis",
|
|
144
|
+
`Analyzed ${this.parsedDocs.length} documentation files from ${this.options.docsDir}/`,
|
|
145
|
+
"specification",
|
|
146
|
+
"high",
|
|
147
|
+
`Found ${this.parsedDocs.filter((d) => d.type === "feature").length} feature docs, ${this.parsedDocs.filter((d) => d.type === "requirement").length} requirement docs, ${this.parsedDocs.filter((d) => d.type === "architecture").length} architecture docs`
|
|
148
|
+
);
|
|
149
|
+
} else {
|
|
150
|
+
logger.warn("No docs directory found", { docsPath });
|
|
151
|
+
}
|
|
130
152
|
const srcPath = join(this.options.projectRoot, "src");
|
|
131
153
|
if (existsSync(srcPath)) {
|
|
132
154
|
this.plan.existingCode = await this.analyzeExistingCode(srcPath);
|
|
133
155
|
this.addDecision(
|
|
134
156
|
"Existing Code Integration",
|
|
135
|
-
|
|
157
|
+
`Found ${this.plan.existingCode.fileCount} files in src/`,
|
|
136
158
|
"specification",
|
|
137
159
|
"high",
|
|
138
|
-
|
|
139
|
-
["Start from scratch", "Partial rewrite", "Full integration"]
|
|
160
|
+
`Languages: ${Object.entries(this.plan.existingCode.languages).map(([k, v]) => `${k}: ${v}`).join(", ")}`
|
|
140
161
|
);
|
|
141
162
|
}
|
|
142
|
-
const researchTasks = this.defineResearchTasks();
|
|
143
|
-
for (const task of researchTasks) {
|
|
144
|
-
const finding = await this.executeResearchTask(task);
|
|
145
|
-
this.plan.researchFindings.push(finding);
|
|
146
|
-
}
|
|
147
163
|
logger.info("Research phase completed", {
|
|
164
|
+
docsAnalyzed: this.parsedDocs.length,
|
|
148
165
|
findings: this.plan.researchFindings.length,
|
|
149
166
|
hasExistingCode: !!this.plan.existingCode
|
|
150
167
|
});
|
|
151
168
|
}
|
|
152
169
|
/**
|
|
153
|
-
*
|
|
170
|
+
* Read all documentation files from a directory
|
|
154
171
|
*/
|
|
155
|
-
async
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
srcPath,
|
|
159
|
-
fileCount: 0,
|
|
160
|
-
languages: {},
|
|
161
|
-
keyFiles: [],
|
|
162
|
-
patterns: [],
|
|
163
|
-
dependencies: [],
|
|
164
|
-
entryPoints: []
|
|
165
|
-
};
|
|
166
|
-
const analyzeDir = (dir) => {
|
|
172
|
+
async readDocsDirectory(docsPath) {
|
|
173
|
+
const docs = [];
|
|
174
|
+
const readDir = (dir) => {
|
|
167
175
|
const entries = readdirSync(dir);
|
|
168
176
|
for (const entry of entries) {
|
|
169
177
|
const fullPath = join(dir, entry);
|
|
170
178
|
const stat = statSync(fullPath);
|
|
171
179
|
if (stat.isDirectory()) {
|
|
172
180
|
if (!entry.startsWith(".") && entry !== "node_modules") {
|
|
173
|
-
|
|
181
|
+
readDir(fullPath);
|
|
174
182
|
}
|
|
175
|
-
} else if (stat.isFile()) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
if (entry === "package.json") {
|
|
183
|
-
try {
|
|
184
|
-
const pkg = JSON.parse(readFileSync(fullPath, "utf-8"));
|
|
185
|
-
analysis.dependencies = Object.keys(pkg.dependencies || {});
|
|
186
|
-
} catch {
|
|
187
|
-
}
|
|
183
|
+
} else if (stat.isFile() && (entry.endsWith(".md") || entry.endsWith(".mdx"))) {
|
|
184
|
+
try {
|
|
185
|
+
const doc = this.parseDocFile(fullPath);
|
|
186
|
+
docs.push(doc);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
logger.warn("Failed to parse doc file", { path: fullPath, error });
|
|
188
189
|
}
|
|
189
190
|
}
|
|
190
191
|
}
|
|
191
192
|
};
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
readDir(docsPath);
|
|
194
|
+
return docs;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Parse a markdown documentation file
|
|
198
|
+
*/
|
|
199
|
+
parseDocFile(filePath) {
|
|
200
|
+
const content = readFileSync(filePath, "utf-8");
|
|
201
|
+
const { data: frontmatter, content: body } = matter(content);
|
|
202
|
+
const filename = basename(filePath);
|
|
203
|
+
const headings = this.extractHeadings(body);
|
|
204
|
+
const sections = this.extractSections(body, headings);
|
|
205
|
+
const codeBlocks = this.extractCodeBlocks(body);
|
|
206
|
+
const links = this.extractLinks(body);
|
|
207
|
+
let title = frontmatter.title || "";
|
|
208
|
+
if (!title && headings.length > 0) {
|
|
209
|
+
title = headings[0].text;
|
|
195
210
|
}
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
analysis.testCoverage = { hasTests: true };
|
|
211
|
+
if (!title) {
|
|
212
|
+
title = filename.replace(/\.(md|mdx)$/, "");
|
|
199
213
|
}
|
|
200
|
-
|
|
214
|
+
const type = this.classifyDocument(filePath, frontmatter, title, body);
|
|
215
|
+
return {
|
|
216
|
+
path: filePath,
|
|
217
|
+
filename,
|
|
218
|
+
title,
|
|
219
|
+
content: body,
|
|
220
|
+
frontmatter,
|
|
221
|
+
headings,
|
|
222
|
+
sections,
|
|
223
|
+
codeBlocks,
|
|
224
|
+
links,
|
|
225
|
+
type
|
|
226
|
+
};
|
|
201
227
|
}
|
|
202
228
|
/**
|
|
203
|
-
*
|
|
229
|
+
* Extract headings from markdown content
|
|
204
230
|
*/
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
agentType: "researcher",
|
|
217
|
-
description: "Research applicable design patterns",
|
|
218
|
-
topics: ["design patterns", "architecture patterns", "best practices"],
|
|
219
|
-
context: this.plan.existingCode?.patterns || []
|
|
220
|
-
},
|
|
221
|
-
{
|
|
222
|
-
id: "research-technology",
|
|
223
|
-
agentType: "researcher",
|
|
224
|
-
description: "Research technology choices and alternatives",
|
|
225
|
-
topics: ["technology stack", "frameworks", "libraries"],
|
|
226
|
-
context: this.plan.existingCode?.dependencies || []
|
|
231
|
+
extractHeadings(content) {
|
|
232
|
+
const headings = [];
|
|
233
|
+
const lines = content.split("\n");
|
|
234
|
+
lines.forEach((line, index) => {
|
|
235
|
+
const match = line.match(/^(#{1,6})\s+(.+)$/);
|
|
236
|
+
if (match) {
|
|
237
|
+
headings.push({
|
|
238
|
+
level: match[1].length,
|
|
239
|
+
text: match[2].trim(),
|
|
240
|
+
line: index + 1
|
|
241
|
+
});
|
|
227
242
|
}
|
|
228
|
-
|
|
243
|
+
});
|
|
244
|
+
return headings;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Extract sections from markdown content based on headings
|
|
248
|
+
*/
|
|
249
|
+
extractSections(content, headings) {
|
|
250
|
+
const sections = [];
|
|
251
|
+
const lines = content.split("\n");
|
|
252
|
+
for (let i = 0; i < headings.length; i++) {
|
|
253
|
+
const heading = headings[i];
|
|
254
|
+
const nextHeading = headings[i + 1];
|
|
255
|
+
const startLine = heading.line;
|
|
256
|
+
const endLine = nextHeading ? nextHeading.line - 1 : lines.length;
|
|
257
|
+
const sectionContent = lines.slice(startLine, endLine).join("\n").trim();
|
|
258
|
+
sections.push({
|
|
259
|
+
heading: heading.text,
|
|
260
|
+
content: sectionContent,
|
|
261
|
+
level: heading.level
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
return sections;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Extract code blocks from markdown
|
|
268
|
+
*/
|
|
269
|
+
extractCodeBlocks(content) {
|
|
270
|
+
const blocks = [];
|
|
271
|
+
const regex = /```(\w*)\n([\s\S]*?)```/g;
|
|
272
|
+
let match;
|
|
273
|
+
while ((match = regex.exec(content)) !== null) {
|
|
274
|
+
blocks.push({
|
|
275
|
+
language: match[1] || "text",
|
|
276
|
+
code: match[2].trim()
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return blocks;
|
|
229
280
|
}
|
|
230
281
|
/**
|
|
231
|
-
*
|
|
282
|
+
* Extract links from markdown
|
|
232
283
|
*/
|
|
233
|
-
|
|
284
|
+
extractLinks(content) {
|
|
285
|
+
const links = [];
|
|
286
|
+
const regex = /\[([^\]]+)\]\(([^)]+)\)/g;
|
|
287
|
+
let match;
|
|
288
|
+
while ((match = regex.exec(content)) !== null) {
|
|
289
|
+
links.push(match[2]);
|
|
290
|
+
}
|
|
291
|
+
return links;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Classify document type based on content and path
|
|
295
|
+
*/
|
|
296
|
+
classifyDocument(filePath, frontmatter, title, content) {
|
|
297
|
+
const lowerPath = filePath.toLowerCase();
|
|
298
|
+
const lowerTitle = title.toLowerCase();
|
|
299
|
+
const lowerContent = content.toLowerCase();
|
|
300
|
+
if (frontmatter.type) {
|
|
301
|
+
const type = String(frontmatter.type).toLowerCase();
|
|
302
|
+
if (type.includes("feature")) return "feature";
|
|
303
|
+
if (type.includes("require")) return "requirement";
|
|
304
|
+
if (type.includes("arch")) return "architecture";
|
|
305
|
+
if (type.includes("api")) return "api";
|
|
306
|
+
if (type.includes("spec")) return "spec";
|
|
307
|
+
if (type.includes("guide")) return "guide";
|
|
308
|
+
}
|
|
309
|
+
if (lowerPath.includes("/features/") || lowerPath.includes("/feature/")) return "feature";
|
|
310
|
+
if (lowerPath.includes("/requirements/") || lowerPath.includes("/req/")) return "requirement";
|
|
311
|
+
if (lowerPath.includes("/architecture/") || lowerPath.includes("/arch/")) return "architecture";
|
|
312
|
+
if (lowerPath.includes("/api/") || lowerPath.includes("/apis/")) return "api";
|
|
313
|
+
if (lowerPath.includes("/spec/") || lowerPath.includes("/specs/")) return "spec";
|
|
314
|
+
if (lowerPath.includes("/guide/") || lowerPath.includes("/guides/")) return "guide";
|
|
315
|
+
if (lowerTitle.includes("feature") || lowerTitle.includes("epic")) return "feature";
|
|
316
|
+
if (lowerTitle.includes("requirement") || lowerTitle.includes("req-")) return "requirement";
|
|
317
|
+
if (lowerTitle.includes("architecture") || lowerTitle.includes("design")) return "architecture";
|
|
318
|
+
if (lowerTitle.includes("api") || lowerTitle.includes("endpoint")) return "api";
|
|
319
|
+
if (lowerTitle.includes("spec")) return "spec";
|
|
320
|
+
const featureKeywords = ["user story", "as a user", "acceptance criteria", "feature:", "epic:"];
|
|
321
|
+
const reqKeywords = ["shall", "must", "requirement", "req-", "functional requirement", "non-functional"];
|
|
322
|
+
const archKeywords = ["component", "module", "service", "architecture", "system design", "data flow"];
|
|
323
|
+
const apiKeywords = ["endpoint", "request", "response", "http", "rest", "graphql", "method:"];
|
|
324
|
+
if (featureKeywords.some((kw) => lowerContent.includes(kw))) return "feature";
|
|
325
|
+
if (reqKeywords.some((kw) => lowerContent.includes(kw))) return "requirement";
|
|
326
|
+
if (archKeywords.some((kw) => lowerContent.includes(kw))) return "architecture";
|
|
327
|
+
if (apiKeywords.some((kw) => lowerContent.includes(kw))) return "api";
|
|
328
|
+
return "unknown";
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Create a research finding from a parsed document
|
|
332
|
+
*/
|
|
333
|
+
createFindingFromDoc(doc) {
|
|
234
334
|
return {
|
|
235
|
-
id: `finding_${
|
|
236
|
-
agent:
|
|
237
|
-
topic:
|
|
238
|
-
summary:
|
|
239
|
-
details:
|
|
240
|
-
confidence: "medium",
|
|
241
|
-
evidence:
|
|
335
|
+
id: `finding_${doc.filename.replace(/[^a-z0-9]/gi, "_")}`,
|
|
336
|
+
agent: "doc-analyzer",
|
|
337
|
+
topic: doc.type,
|
|
338
|
+
summary: doc.title,
|
|
339
|
+
details: this.summarizeDocContent(doc),
|
|
340
|
+
confidence: doc.frontmatter.status === "approved" ? "high" : "medium",
|
|
341
|
+
evidence: [doc.path],
|
|
242
342
|
relatedFindings: [],
|
|
243
343
|
kgReferences: [],
|
|
344
|
+
vectorResults: [],
|
|
244
345
|
timestamp: /* @__PURE__ */ new Date()
|
|
245
346
|
};
|
|
246
347
|
}
|
|
247
348
|
/**
|
|
248
|
-
*
|
|
349
|
+
* Summarize document content
|
|
350
|
+
*/
|
|
351
|
+
summarizeDocContent(doc) {
|
|
352
|
+
const lines = [];
|
|
353
|
+
lines.push(`**Document Type:** ${doc.type}`);
|
|
354
|
+
lines.push(`**File:** ${relative(this.options.projectRoot, doc.path)}`);
|
|
355
|
+
if (doc.headings.length > 0) {
|
|
356
|
+
lines.push(`**Sections:** ${doc.headings.map((h) => h.text).join(", ")}`);
|
|
357
|
+
}
|
|
358
|
+
if (doc.codeBlocks.length > 0) {
|
|
359
|
+
lines.push(`**Code Examples:** ${doc.codeBlocks.length} blocks (${[...new Set(doc.codeBlocks.map((b) => b.language))].join(", ")})`);
|
|
360
|
+
}
|
|
361
|
+
const contentPreview = doc.content.substring(0, 500).replace(/\n/g, " ").trim();
|
|
362
|
+
if (contentPreview) {
|
|
363
|
+
lines.push(`**Preview:** ${contentPreview}...`);
|
|
364
|
+
}
|
|
365
|
+
return lines.join("\n");
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Execute specification phase - extract requirements and features from docs
|
|
249
369
|
*/
|
|
250
370
|
async executeSpecificationPhase() {
|
|
251
371
|
logger.info("Executing specification phase");
|
|
252
372
|
this.plan.currentPhase = "specification";
|
|
373
|
+
const requirements = this.extractRequirementsFromDocs();
|
|
374
|
+
const features = this.extractFeaturesFromDocs();
|
|
253
375
|
const spec = {
|
|
254
376
|
id: `spec_${this.plan.id}`,
|
|
255
377
|
projectName: this.options.name,
|
|
@@ -259,27 +381,364 @@ class SPARCPlanner {
|
|
|
259
381
|
summary: this.options.description,
|
|
260
382
|
problemStatement: this.extractProblemStatement(),
|
|
261
383
|
goals: this.extractGoals(),
|
|
262
|
-
requirements
|
|
263
|
-
features
|
|
384
|
+
requirements,
|
|
385
|
+
features,
|
|
264
386
|
constraints: this.extractConstraints(),
|
|
265
|
-
assumptions:
|
|
387
|
+
assumptions: this.extractAssumptions(),
|
|
266
388
|
outOfScope: [],
|
|
267
|
-
successMetrics: this.
|
|
389
|
+
successMetrics: this.extractSuccessMetrics(),
|
|
268
390
|
kgReferences: []
|
|
269
391
|
};
|
|
270
392
|
this.plan.specification = spec;
|
|
271
393
|
this.addDecision(
|
|
272
|
-
"Requirements
|
|
273
|
-
`
|
|
394
|
+
"Requirements Extracted",
|
|
395
|
+
`Extracted ${requirements.length} requirements and ${features.length} features from documentation`,
|
|
274
396
|
"specification",
|
|
275
|
-
"high",
|
|
276
|
-
"
|
|
397
|
+
requirements.length > 0 ? "high" : "low",
|
|
398
|
+
`Sources: ${this.parsedDocs.filter((d) => d.type === "requirement" || d.type === "feature").map((d) => d.filename).join(", ")}`
|
|
277
399
|
);
|
|
278
400
|
logger.info("Specification phase completed", {
|
|
279
|
-
requirements:
|
|
280
|
-
features:
|
|
401
|
+
requirements: requirements.length,
|
|
402
|
+
features: features.length
|
|
281
403
|
});
|
|
282
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Extract requirements from documentation
|
|
407
|
+
*/
|
|
408
|
+
extractRequirementsFromDocs() {
|
|
409
|
+
const requirements = [];
|
|
410
|
+
let reqCounter = 1;
|
|
411
|
+
const reqDocs = this.parsedDocs.filter((d) => d.type === "requirement" || d.type === "spec");
|
|
412
|
+
for (const doc of reqDocs) {
|
|
413
|
+
for (const section of doc.sections) {
|
|
414
|
+
const lowerHeading = section.heading.toLowerCase();
|
|
415
|
+
if (lowerHeading.includes("requirement") || lowerHeading.includes("shall") || lowerHeading.includes("must")) {
|
|
416
|
+
requirements.push(this.createRequirement(
|
|
417
|
+
`REQ-${String(reqCounter++).padStart(3, "0")}`,
|
|
418
|
+
section.heading,
|
|
419
|
+
section.content,
|
|
420
|
+
"functional",
|
|
421
|
+
doc.path
|
|
422
|
+
));
|
|
423
|
+
}
|
|
424
|
+
if (lowerHeading.includes("non-functional") || lowerHeading.includes("performance") || lowerHeading.includes("security") || lowerHeading.includes("scalability")) {
|
|
425
|
+
requirements.push(this.createRequirement(
|
|
426
|
+
`NFR-${String(reqCounter++).padStart(3, "0")}`,
|
|
427
|
+
section.heading,
|
|
428
|
+
section.content,
|
|
429
|
+
"non-functional",
|
|
430
|
+
doc.path
|
|
431
|
+
));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
const bulletReqs = this.extractBulletRequirements(doc.content);
|
|
435
|
+
for (const req of bulletReqs) {
|
|
436
|
+
requirements.push(this.createRequirement(
|
|
437
|
+
`REQ-${String(reqCounter++).padStart(3, "0")}`,
|
|
438
|
+
req.title,
|
|
439
|
+
req.description,
|
|
440
|
+
"functional",
|
|
441
|
+
doc.path
|
|
442
|
+
));
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const featureDocs = this.parsedDocs.filter((d) => d.type === "feature");
|
|
446
|
+
for (const doc of featureDocs) {
|
|
447
|
+
for (const section of doc.sections) {
|
|
448
|
+
if (section.heading.toLowerCase().includes("requirement") || section.heading.toLowerCase().includes("acceptance")) {
|
|
449
|
+
requirements.push(this.createRequirement(
|
|
450
|
+
`REQ-${String(reqCounter++).padStart(3, "0")}`,
|
|
451
|
+
`${doc.title}: ${section.heading}`,
|
|
452
|
+
section.content,
|
|
453
|
+
"functional",
|
|
454
|
+
doc.path
|
|
455
|
+
));
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return requirements;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Extract bullet point requirements from content
|
|
463
|
+
*/
|
|
464
|
+
extractBulletRequirements(content) {
|
|
465
|
+
const reqs = [];
|
|
466
|
+
const lines = content.split("\n");
|
|
467
|
+
for (const line of lines) {
|
|
468
|
+
const match = line.match(/^[-*]\s+(.+(?:shall|must|should).+)$/i);
|
|
469
|
+
if (match) {
|
|
470
|
+
const text = match[1].trim();
|
|
471
|
+
reqs.push({
|
|
472
|
+
title: text.substring(0, 60) + (text.length > 60 ? "..." : ""),
|
|
473
|
+
description: text
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return reqs;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Create a requirement object
|
|
481
|
+
*/
|
|
482
|
+
createRequirement(id, title, description, type, source) {
|
|
483
|
+
const criteria = this.extractAcceptanceCriteria(description);
|
|
484
|
+
return {
|
|
485
|
+
id,
|
|
486
|
+
type,
|
|
487
|
+
description: description.substring(0, 500),
|
|
488
|
+
priority: this.inferPriority(description),
|
|
489
|
+
source: relative(this.options.projectRoot, source),
|
|
490
|
+
acceptanceCriteria: criteria.length > 0 ? criteria : [`${title} is implemented and working`],
|
|
491
|
+
relatedRequirements: []
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Extract acceptance criteria from text
|
|
496
|
+
*/
|
|
497
|
+
extractAcceptanceCriteria(text) {
|
|
498
|
+
const criteria = [];
|
|
499
|
+
const lines = text.split("\n");
|
|
500
|
+
let inCriteriaSection = false;
|
|
501
|
+
for (const line of lines) {
|
|
502
|
+
const lowerLine = line.toLowerCase();
|
|
503
|
+
if (lowerLine.includes("acceptance criteria") || lowerLine.includes("criteria:")) {
|
|
504
|
+
inCriteriaSection = true;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (inCriteriaSection) {
|
|
508
|
+
const match = line.match(/^[-*]\s+(.+)$/);
|
|
509
|
+
if (match) {
|
|
510
|
+
criteria.push(match[1].trim());
|
|
511
|
+
} else if (line.match(/^#{1,6}\s/)) {
|
|
512
|
+
inCriteriaSection = false;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (line.match(/^\s*(Given|When|Then)\s+/i)) {
|
|
516
|
+
criteria.push(line.trim());
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return criteria.slice(0, 10);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Infer priority from text
|
|
523
|
+
*/
|
|
524
|
+
inferPriority(text) {
|
|
525
|
+
const lower = text.toLowerCase();
|
|
526
|
+
if (lower.includes("critical") || lower.includes("must have") || lower.includes("p0")) {
|
|
527
|
+
return "must-have";
|
|
528
|
+
}
|
|
529
|
+
if (lower.includes("high priority") || lower.includes("should have") || lower.includes("p1")) {
|
|
530
|
+
return "should-have";
|
|
531
|
+
}
|
|
532
|
+
if (lower.includes("nice to have") || lower.includes("could have") || lower.includes("p2")) {
|
|
533
|
+
return "could-have";
|
|
534
|
+
}
|
|
535
|
+
if (lower.includes("future") || lower.includes("won't") || lower.includes("p3")) {
|
|
536
|
+
return "won-t-have";
|
|
537
|
+
}
|
|
538
|
+
return "should-have";
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Extract features from documentation
|
|
542
|
+
*/
|
|
543
|
+
extractFeaturesFromDocs() {
|
|
544
|
+
const features = [];
|
|
545
|
+
let featCounter = 1;
|
|
546
|
+
const featureDocs = this.parsedDocs.filter((d) => d.type === "feature");
|
|
547
|
+
for (const doc of featureDocs) {
|
|
548
|
+
features.push(this.createFeatureFromDoc(doc, `FEAT-${String(featCounter++).padStart(3, "0")}`));
|
|
549
|
+
}
|
|
550
|
+
const specDocs = this.parsedDocs.filter((d) => d.type === "spec" || d.type === "unknown");
|
|
551
|
+
for (const doc of specDocs) {
|
|
552
|
+
for (const section of doc.sections) {
|
|
553
|
+
const lowerHeading = section.heading.toLowerCase();
|
|
554
|
+
if (lowerHeading.includes("feature") || lowerHeading.includes("capability") || lowerHeading.includes("functionality")) {
|
|
555
|
+
features.push({
|
|
556
|
+
id: `FEAT-${String(featCounter++).padStart(3, "0")}`,
|
|
557
|
+
name: section.heading,
|
|
558
|
+
description: section.content.substring(0, 500),
|
|
559
|
+
userStories: this.extractUserStories(section.content),
|
|
560
|
+
requirements: [],
|
|
561
|
+
complexity: this.inferComplexity(section.content),
|
|
562
|
+
dependencies: [],
|
|
563
|
+
parallelizable: true
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (features.length === 0) {
|
|
569
|
+
for (const doc of this.parsedDocs) {
|
|
570
|
+
if (doc.headings.length > 0) {
|
|
571
|
+
for (const heading of doc.headings.filter((h) => h.level <= 2)) {
|
|
572
|
+
const section = doc.sections.find((s) => s.heading === heading.text);
|
|
573
|
+
if (section && section.content.length > 100) {
|
|
574
|
+
features.push({
|
|
575
|
+
id: `FEAT-${String(featCounter++).padStart(3, "0")}`,
|
|
576
|
+
name: heading.text,
|
|
577
|
+
description: section.content.substring(0, 500),
|
|
578
|
+
userStories: this.extractUserStories(section.content),
|
|
579
|
+
requirements: [],
|
|
580
|
+
complexity: this.inferComplexity(section.content),
|
|
581
|
+
dependencies: [],
|
|
582
|
+
parallelizable: true
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return features;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Create a feature from a document
|
|
593
|
+
*/
|
|
594
|
+
createFeatureFromDoc(doc, id) {
|
|
595
|
+
return {
|
|
596
|
+
id,
|
|
597
|
+
name: doc.title,
|
|
598
|
+
description: doc.content.substring(0, 1e3),
|
|
599
|
+
userStories: this.extractUserStories(doc.content),
|
|
600
|
+
requirements: [],
|
|
601
|
+
complexity: this.inferComplexity(doc.content),
|
|
602
|
+
dependencies: this.extractDependencies(doc),
|
|
603
|
+
parallelizable: true
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Extract user stories from text
|
|
608
|
+
*/
|
|
609
|
+
extractUserStories(text) {
|
|
610
|
+
const stories = [];
|
|
611
|
+
const regex = /As a[n]?\s+([^,]+),?\s+I want\s+([^,]+),?\s+(?:so that|because)\s+([^.]+)/gi;
|
|
612
|
+
let match;
|
|
613
|
+
while ((match = regex.exec(text)) !== null) {
|
|
614
|
+
stories.push(match[0]);
|
|
615
|
+
}
|
|
616
|
+
const lines = text.split("\n");
|
|
617
|
+
for (const line of lines) {
|
|
618
|
+
if (line.toLowerCase().includes("user story") || line.match(/^[-*]\s+As a/i)) {
|
|
619
|
+
stories.push(line.replace(/^[-*]\s+/, "").trim());
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return stories.slice(0, 5);
|
|
623
|
+
}
|
|
624
|
+
/**
|
|
625
|
+
* Infer complexity from content
|
|
626
|
+
*/
|
|
627
|
+
inferComplexity(text) {
|
|
628
|
+
const lower = text.toLowerCase();
|
|
629
|
+
const codeBlocks = (text.match(/```/g) || []).length / 2;
|
|
630
|
+
const wordCount = text.split(/\s+/).length;
|
|
631
|
+
if (lower.includes("complex") || lower.includes("difficult") || codeBlocks > 5 || wordCount > 1e3) {
|
|
632
|
+
return "very-high";
|
|
633
|
+
}
|
|
634
|
+
if (lower.includes("moderate") || codeBlocks > 3 || wordCount > 500) {
|
|
635
|
+
return "high";
|
|
636
|
+
}
|
|
637
|
+
if (lower.includes("simple") || lower.includes("basic") || wordCount < 200) {
|
|
638
|
+
return "low";
|
|
639
|
+
}
|
|
640
|
+
return "medium";
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Extract dependencies from document links
|
|
644
|
+
*/
|
|
645
|
+
extractDependencies(doc) {
|
|
646
|
+
const deps = [];
|
|
647
|
+
for (const link of doc.links) {
|
|
648
|
+
if (link.startsWith("./") || link.startsWith("../") || !link.startsWith("http")) {
|
|
649
|
+
const linkedDoc = this.parsedDocs.find((d) => d.path.includes(link.replace(".md", "")));
|
|
650
|
+
if (linkedDoc && linkedDoc.type === "feature") {
|
|
651
|
+
deps.push(linkedDoc.title);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
return deps;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Extract problem statement from docs
|
|
659
|
+
*/
|
|
660
|
+
extractProblemStatement() {
|
|
661
|
+
for (const doc of this.parsedDocs) {
|
|
662
|
+
for (const section of doc.sections) {
|
|
663
|
+
const lower = section.heading.toLowerCase();
|
|
664
|
+
if (lower.includes("problem") || lower.includes("overview") || lower.includes("background")) {
|
|
665
|
+
return section.content.substring(0, 1e3);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
return `Building: ${this.options.description}`;
|
|
670
|
+
}
|
|
671
|
+
/**
|
|
672
|
+
* Extract goals from docs
|
|
673
|
+
*/
|
|
674
|
+
extractGoals() {
|
|
675
|
+
const goals = [];
|
|
676
|
+
for (const doc of this.parsedDocs) {
|
|
677
|
+
for (const section of doc.sections) {
|
|
678
|
+
const lower = section.heading.toLowerCase();
|
|
679
|
+
if (lower.includes("goal") || lower.includes("objective") || lower.includes("outcome")) {
|
|
680
|
+
const bullets = section.content.match(/^[-*]\s+(.+)$/gm);
|
|
681
|
+
if (bullets) {
|
|
682
|
+
goals.push(...bullets.map((b) => b.replace(/^[-*]\s+/, "").trim()));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
return goals.length > 0 ? goals.slice(0, 10) : ["Complete implementation as documented"];
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Extract constraints from docs
|
|
691
|
+
*/
|
|
692
|
+
extractConstraints() {
|
|
693
|
+
const constraints = [];
|
|
694
|
+
for (const doc of this.parsedDocs) {
|
|
695
|
+
for (const section of doc.sections) {
|
|
696
|
+
const lower = section.heading.toLowerCase();
|
|
697
|
+
if (lower.includes("constraint") || lower.includes("limitation") || lower.includes("restriction")) {
|
|
698
|
+
const bullets = section.content.match(/^[-*]\s+(.+)$/gm);
|
|
699
|
+
if (bullets) {
|
|
700
|
+
constraints.push(...bullets.map((b) => b.replace(/^[-*]\s+/, "").trim()));
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
return constraints;
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Extract assumptions from docs
|
|
709
|
+
*/
|
|
710
|
+
extractAssumptions() {
|
|
711
|
+
const assumptions = [];
|
|
712
|
+
for (const doc of this.parsedDocs) {
|
|
713
|
+
for (const section of doc.sections) {
|
|
714
|
+
if (section.heading.toLowerCase().includes("assumption")) {
|
|
715
|
+
const bullets = section.content.match(/^[-*]\s+(.+)$/gm);
|
|
716
|
+
if (bullets) {
|
|
717
|
+
assumptions.push(...bullets.map((b) => b.replace(/^[-*]\s+/, "").trim()));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
return assumptions;
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Extract success metrics from docs
|
|
726
|
+
*/
|
|
727
|
+
extractSuccessMetrics() {
|
|
728
|
+
const metrics = [];
|
|
729
|
+
for (const doc of this.parsedDocs) {
|
|
730
|
+
for (const section of doc.sections) {
|
|
731
|
+
const lower = section.heading.toLowerCase();
|
|
732
|
+
if (lower.includes("metric") || lower.includes("success") || lower.includes("kpi")) {
|
|
733
|
+
const bullets = section.content.match(/^[-*]\s+(.+)$/gm);
|
|
734
|
+
if (bullets) {
|
|
735
|
+
metrics.push(...bullets.map((b) => b.replace(/^[-*]\s+/, "").trim()));
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return metrics.length > 0 ? metrics : ["All documented features implemented", "All tests passing"];
|
|
741
|
+
}
|
|
283
742
|
/**
|
|
284
743
|
* Execute pseudocode phase
|
|
285
744
|
*/
|
|
@@ -287,14 +746,66 @@ class SPARCPlanner {
|
|
|
287
746
|
logger.info("Executing pseudocode phase");
|
|
288
747
|
this.plan.currentPhase = "pseudocode";
|
|
289
748
|
const algorithms = [];
|
|
749
|
+
const apiDocs = this.parsedDocs.filter((d) => d.type === "api");
|
|
750
|
+
for (const doc of apiDocs) {
|
|
751
|
+
algorithms.push(this.createAlgorithmFromDoc(doc));
|
|
752
|
+
}
|
|
290
753
|
for (const feature of this.plan.specification?.features || []) {
|
|
291
|
-
const
|
|
292
|
-
|
|
754
|
+
const featureDoc = this.parsedDocs.find((d) => d.title === feature.name);
|
|
755
|
+
if (featureDoc && featureDoc.codeBlocks.length > 0) {
|
|
756
|
+
algorithms.push(this.createAlgorithmFromDoc(featureDoc));
|
|
757
|
+
}
|
|
293
758
|
}
|
|
294
759
|
this.plan.algorithms = algorithms;
|
|
295
|
-
logger.info("Pseudocode phase completed", {
|
|
296
|
-
|
|
297
|
-
|
|
760
|
+
logger.info("Pseudocode phase completed", { algorithms: algorithms.length });
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Create algorithm design from document
|
|
764
|
+
*/
|
|
765
|
+
createAlgorithmFromDoc(doc) {
|
|
766
|
+
const steps = [];
|
|
767
|
+
let stepNum = 1;
|
|
768
|
+
for (const section of doc.sections) {
|
|
769
|
+
if (section.level >= 2) {
|
|
770
|
+
const relevantCodeBlock = doc.codeBlocks.find(
|
|
771
|
+
(block) => section.content.includes(block.code.substring(0, 50))
|
|
772
|
+
);
|
|
773
|
+
steps.push({
|
|
774
|
+
step: stepNum++,
|
|
775
|
+
description: section.heading,
|
|
776
|
+
pseudocode: relevantCodeBlock?.code ?? `// Implement ${section.heading}`,
|
|
777
|
+
inputs: [],
|
|
778
|
+
outputs: []
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
if (steps.length === 0 && doc.codeBlocks.length > 0) {
|
|
783
|
+
for (const block of doc.codeBlocks) {
|
|
784
|
+
steps.push({
|
|
785
|
+
step: stepNum++,
|
|
786
|
+
description: `${block.language} implementation`,
|
|
787
|
+
pseudocode: block.code,
|
|
788
|
+
inputs: [],
|
|
789
|
+
outputs: []
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return {
|
|
794
|
+
id: `algo_${doc.filename.replace(/[^a-z0-9]/gi, "_")}`,
|
|
795
|
+
name: doc.title,
|
|
796
|
+
purpose: doc.content.substring(0, 200),
|
|
797
|
+
steps: steps.length > 0 ? steps : [{
|
|
798
|
+
step: 1,
|
|
799
|
+
description: "Implement feature",
|
|
800
|
+
pseudocode: "// Implementation needed",
|
|
801
|
+
inputs: [],
|
|
802
|
+
outputs: []
|
|
803
|
+
}],
|
|
804
|
+
timeComplexity: "O(n)",
|
|
805
|
+
spaceComplexity: "O(1)",
|
|
806
|
+
edgeCases: [],
|
|
807
|
+
relatedFeatures: []
|
|
808
|
+
};
|
|
298
809
|
}
|
|
299
810
|
/**
|
|
300
811
|
* Execute architecture phase
|
|
@@ -302,73 +813,310 @@ class SPARCPlanner {
|
|
|
302
813
|
async executeArchitecturePhase() {
|
|
303
814
|
logger.info("Executing architecture phase");
|
|
304
815
|
this.plan.currentPhase = "architecture";
|
|
816
|
+
const components = this.extractComponentsFromDocs();
|
|
817
|
+
const patterns = this.extractPatternsFromDocs();
|
|
305
818
|
const arch = {
|
|
306
819
|
id: `arch_${this.plan.id}`,
|
|
307
820
|
version: "1.0.0",
|
|
308
821
|
createdAt: /* @__PURE__ */ new Date(),
|
|
309
|
-
overview: this.
|
|
310
|
-
patterns
|
|
311
|
-
components
|
|
822
|
+
overview: this.extractArchitectureOverview(),
|
|
823
|
+
patterns,
|
|
824
|
+
components,
|
|
312
825
|
decisions: [],
|
|
313
|
-
dataFlow: this.
|
|
314
|
-
securityConsiderations: this.
|
|
826
|
+
dataFlow: this.extractDataFlow(),
|
|
827
|
+
securityConsiderations: this.extractSecurityConsiderations(),
|
|
315
828
|
scalabilityConsiderations: [],
|
|
316
829
|
diagrams: []
|
|
317
830
|
};
|
|
318
831
|
this.plan.architecture = arch;
|
|
319
832
|
this.addDecision(
|
|
320
|
-
"Architecture
|
|
321
|
-
`
|
|
833
|
+
"Architecture Defined",
|
|
834
|
+
`Identified ${components.length} components with patterns: ${patterns.join(", ")}`,
|
|
322
835
|
"architecture",
|
|
323
|
-
"medium",
|
|
324
|
-
|
|
325
|
-
["Microservices", "Monolith", "Modular monolith"]
|
|
836
|
+
components.length > 0 ? "medium" : "low",
|
|
837
|
+
`Based on analysis of ${this.parsedDocs.filter((d) => d.type === "architecture").length} architecture docs`
|
|
326
838
|
);
|
|
327
839
|
logger.info("Architecture phase completed", {
|
|
328
|
-
components:
|
|
329
|
-
patterns:
|
|
840
|
+
components: components.length,
|
|
841
|
+
patterns: patterns.length
|
|
330
842
|
});
|
|
331
843
|
}
|
|
332
844
|
/**
|
|
333
|
-
*
|
|
845
|
+
* Extract components from documentation
|
|
846
|
+
*/
|
|
847
|
+
extractComponentsFromDocs() {
|
|
848
|
+
const components = [];
|
|
849
|
+
let compCounter = 1;
|
|
850
|
+
const archDocs = this.parsedDocs.filter((d) => d.type === "architecture");
|
|
851
|
+
for (const doc of archDocs) {
|
|
852
|
+
for (const section of doc.sections) {
|
|
853
|
+
const lower = section.heading.toLowerCase();
|
|
854
|
+
if (lower.includes("component") || lower.includes("module") || lower.includes("service") || lower.includes("layer")) {
|
|
855
|
+
components.push({
|
|
856
|
+
id: `COMP-${String(compCounter++).padStart(3, "0")}`,
|
|
857
|
+
name: section.heading,
|
|
858
|
+
type: this.inferComponentType(section.heading, section.content),
|
|
859
|
+
description: section.content.substring(0, 500),
|
|
860
|
+
responsibilities: this.extractResponsibilities(section.content),
|
|
861
|
+
interfaces: [],
|
|
862
|
+
dependencies: [],
|
|
863
|
+
technologies: this.extractTechnologies(section.content),
|
|
864
|
+
path: doc.path
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
if (components.length === 0) {
|
|
870
|
+
for (const feature of this.plan.specification?.features || []) {
|
|
871
|
+
components.push({
|
|
872
|
+
id: `COMP-${String(compCounter++).padStart(3, "0")}`,
|
|
873
|
+
name: `${feature.name} Module`,
|
|
874
|
+
type: "module",
|
|
875
|
+
description: feature.description.substring(0, 300),
|
|
876
|
+
responsibilities: [`Implement ${feature.name}`],
|
|
877
|
+
interfaces: [],
|
|
878
|
+
dependencies: feature.dependencies,
|
|
879
|
+
technologies: []
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
return components;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Infer component type
|
|
887
|
+
*/
|
|
888
|
+
inferComponentType(heading, content) {
|
|
889
|
+
const lower = (heading + " " + content).toLowerCase();
|
|
890
|
+
if (lower.includes("service") || lower.includes("microservice")) return "service";
|
|
891
|
+
if (lower.includes("api") || lower.includes("endpoint")) return "api";
|
|
892
|
+
if (lower.includes("database") || lower.includes("storage") || lower.includes("db")) return "database";
|
|
893
|
+
if (lower.includes("ui") || lower.includes("frontend") || lower.includes("component")) return "ui";
|
|
894
|
+
if (lower.includes("library") || lower.includes("util")) return "library";
|
|
895
|
+
if (lower.includes("infrastructure") || lower.includes("deploy")) return "infrastructure";
|
|
896
|
+
return "module";
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Extract responsibilities from content
|
|
900
|
+
*/
|
|
901
|
+
extractResponsibilities(content) {
|
|
902
|
+
const responsibilities = [];
|
|
903
|
+
const lines = content.split("\n");
|
|
904
|
+
let inResponsibilitySection = false;
|
|
905
|
+
for (const line of lines) {
|
|
906
|
+
const lower = line.toLowerCase();
|
|
907
|
+
if (lower.includes("responsibilit") || lower.includes("function") || lower.includes("purpose")) {
|
|
908
|
+
inResponsibilitySection = true;
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
if (inResponsibilitySection) {
|
|
912
|
+
const match = line.match(/^[-*]\s+(.+)$/);
|
|
913
|
+
if (match) {
|
|
914
|
+
responsibilities.push(match[1].trim());
|
|
915
|
+
} else if (line.match(/^#{1,6}\s/)) {
|
|
916
|
+
inResponsibilitySection = false;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return responsibilities.slice(0, 5);
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Extract technologies mentioned
|
|
924
|
+
*/
|
|
925
|
+
extractTechnologies(content) {
|
|
926
|
+
const techs = [];
|
|
927
|
+
const knownTechs = [
|
|
928
|
+
"TypeScript",
|
|
929
|
+
"JavaScript",
|
|
930
|
+
"Python",
|
|
931
|
+
"Go",
|
|
932
|
+
"Rust",
|
|
933
|
+
"Java",
|
|
934
|
+
"React",
|
|
935
|
+
"Vue",
|
|
936
|
+
"Angular",
|
|
937
|
+
"Node.js",
|
|
938
|
+
"Express",
|
|
939
|
+
"FastAPI",
|
|
940
|
+
"PostgreSQL",
|
|
941
|
+
"MongoDB",
|
|
942
|
+
"Redis",
|
|
943
|
+
"SQLite",
|
|
944
|
+
"MySQL",
|
|
945
|
+
"Docker",
|
|
946
|
+
"Kubernetes",
|
|
947
|
+
"AWS",
|
|
948
|
+
"GCP",
|
|
949
|
+
"Azure",
|
|
950
|
+
"GraphQL",
|
|
951
|
+
"REST",
|
|
952
|
+
"gRPC",
|
|
953
|
+
"WebSocket"
|
|
954
|
+
];
|
|
955
|
+
for (const tech of knownTechs) {
|
|
956
|
+
if (content.toLowerCase().includes(tech.toLowerCase())) {
|
|
957
|
+
techs.push(tech);
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return techs;
|
|
961
|
+
}
|
|
962
|
+
/**
|
|
963
|
+
* Extract patterns from documentation
|
|
964
|
+
*/
|
|
965
|
+
extractPatternsFromDocs() {
|
|
966
|
+
const patterns = [];
|
|
967
|
+
const knownPatterns = [
|
|
968
|
+
"MVC",
|
|
969
|
+
"MVVM",
|
|
970
|
+
"MVP",
|
|
971
|
+
"Microservices",
|
|
972
|
+
"Monolith",
|
|
973
|
+
"Serverless",
|
|
974
|
+
"Event-Driven",
|
|
975
|
+
"CQRS",
|
|
976
|
+
"Event Sourcing",
|
|
977
|
+
"Domain-Driven Design",
|
|
978
|
+
"Repository Pattern",
|
|
979
|
+
"Factory Pattern",
|
|
980
|
+
"Singleton",
|
|
981
|
+
"Observer",
|
|
982
|
+
"Clean Architecture",
|
|
983
|
+
"Hexagonal",
|
|
984
|
+
"Layered",
|
|
985
|
+
"Modular"
|
|
986
|
+
];
|
|
987
|
+
const allContent = this.parsedDocs.map((d) => d.content).join(" ").toLowerCase();
|
|
988
|
+
for (const pattern of knownPatterns) {
|
|
989
|
+
if (allContent.includes(pattern.toLowerCase())) {
|
|
990
|
+
patterns.push(pattern);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
return patterns.length > 0 ? patterns : ["Modular"];
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Extract architecture overview
|
|
997
|
+
*/
|
|
998
|
+
extractArchitectureOverview() {
|
|
999
|
+
const archDocs = this.parsedDocs.filter((d) => d.type === "architecture");
|
|
1000
|
+
if (archDocs.length > 0) {
|
|
1001
|
+
return archDocs[0].content.substring(0, 1e3);
|
|
1002
|
+
}
|
|
1003
|
+
return `Architecture for ${this.options.name}: ${this.options.description}`;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Extract data flow description
|
|
1007
|
+
*/
|
|
1008
|
+
extractDataFlow() {
|
|
1009
|
+
for (const doc of this.parsedDocs) {
|
|
1010
|
+
for (const section of doc.sections) {
|
|
1011
|
+
if (section.heading.toLowerCase().includes("data flow") || section.heading.toLowerCase().includes("flow")) {
|
|
1012
|
+
return section.content;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return "Data flow documentation not found";
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Extract security considerations
|
|
1020
|
+
*/
|
|
1021
|
+
extractSecurityConsiderations() {
|
|
1022
|
+
const considerations = [];
|
|
1023
|
+
for (const doc of this.parsedDocs) {
|
|
1024
|
+
for (const section of doc.sections) {
|
|
1025
|
+
if (section.heading.toLowerCase().includes("security")) {
|
|
1026
|
+
const bullets = section.content.match(/^[-*]\s+(.+)$/gm);
|
|
1027
|
+
if (bullets) {
|
|
1028
|
+
considerations.push(...bullets.map((b) => b.replace(/^[-*]\s+/, "").trim()));
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
return considerations;
|
|
1034
|
+
}
|
|
1035
|
+
/**
|
|
1036
|
+
* Execute refinement phase - generate development tasks
|
|
334
1037
|
*/
|
|
335
1038
|
async executeRefinementPhase() {
|
|
336
1039
|
logger.info("Executing refinement phase");
|
|
337
1040
|
this.plan.currentPhase = "refinement";
|
|
338
1041
|
const tasks = [];
|
|
339
1042
|
tasks.push(this.createTask(
|
|
340
|
-
"
|
|
341
|
-
|
|
1043
|
+
"Documentation Analysis Complete",
|
|
1044
|
+
`Analyze all ${this.parsedDocs.length} documentation files`,
|
|
342
1045
|
"specification",
|
|
343
1046
|
"research",
|
|
344
1047
|
"high",
|
|
345
|
-
|
|
1048
|
+
2,
|
|
1049
|
+
true
|
|
346
1050
|
));
|
|
347
|
-
for (const
|
|
1051
|
+
for (const feature of this.plan.specification?.features || []) {
|
|
1052
|
+
tasks.push(this.createTask(
|
|
1053
|
+
`Design: ${feature.name}`,
|
|
1054
|
+
`Design the implementation approach for ${feature.name}: ${feature.description.substring(0, 200)}`,
|
|
1055
|
+
"pseudocode",
|
|
1056
|
+
"design",
|
|
1057
|
+
this.mapComplexityToPriority(feature.complexity),
|
|
1058
|
+
this.estimateDesignHours(feature.complexity),
|
|
1059
|
+
true
|
|
1060
|
+
));
|
|
348
1061
|
tasks.push(this.createTask(
|
|
349
|
-
`Implement ${
|
|
350
|
-
`Implement
|
|
1062
|
+
`Implement: ${feature.name}`,
|
|
1063
|
+
`Implement ${feature.name}
|
|
1064
|
+
|
|
1065
|
+
User Stories:
|
|
1066
|
+
${feature.userStories.map((s) => `- ${s}`).join("\n")}`,
|
|
351
1067
|
"refinement",
|
|
352
1068
|
"implementation",
|
|
353
|
-
this.
|
|
354
|
-
this.
|
|
1069
|
+
this.mapComplexityToPriority(feature.complexity),
|
|
1070
|
+
this.estimateImplementationHours(feature.complexity),
|
|
1071
|
+
true
|
|
1072
|
+
));
|
|
1073
|
+
tasks.push(this.createTask(
|
|
1074
|
+
`Test: ${feature.name}`,
|
|
1075
|
+
`Create tests for ${feature.name}`,
|
|
1076
|
+
"refinement",
|
|
1077
|
+
"testing",
|
|
1078
|
+
"medium",
|
|
1079
|
+
this.estimateTestHours(feature.complexity),
|
|
1080
|
+
true
|
|
355
1081
|
));
|
|
356
1082
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
1083
|
+
for (const component of this.plan.architecture?.components || []) {
|
|
1084
|
+
if (!tasks.find((t) => t.name.includes(component.name))) {
|
|
1085
|
+
tasks.push(this.createTask(
|
|
1086
|
+
`Build: ${component.name}`,
|
|
1087
|
+
`Implement the ${component.name} component
|
|
1088
|
+
|
|
1089
|
+
Responsibilities:
|
|
1090
|
+
${component.responsibilities.map((r) => `- ${r}`).join("\n")}`,
|
|
1091
|
+
"refinement",
|
|
1092
|
+
"implementation",
|
|
1093
|
+
"medium",
|
|
1094
|
+
4,
|
|
1095
|
+
true
|
|
1096
|
+
));
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
for (const req of this.plan.specification?.requirements || []) {
|
|
1100
|
+
if (!tasks.find((t) => t.description.includes(req.description.substring(0, 50)))) {
|
|
1101
|
+
tasks.push(this.createTask(
|
|
1102
|
+
`Requirement: ${req.id}`,
|
|
1103
|
+
req.description,
|
|
1104
|
+
"refinement",
|
|
1105
|
+
"implementation",
|
|
1106
|
+
this.mapPriorityToTaskPriority(req.priority),
|
|
1107
|
+
2,
|
|
1108
|
+
true
|
|
1109
|
+
));
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
365
1112
|
tasks.push(this.createTask(
|
|
366
1113
|
"Integration Testing",
|
|
367
|
-
|
|
1114
|
+
`Create integration tests for all ${(this.plan.specification?.features || []).length} features`,
|
|
368
1115
|
"refinement",
|
|
369
1116
|
"testing",
|
|
370
|
-
"
|
|
371
|
-
|
|
1117
|
+
"high",
|
|
1118
|
+
8,
|
|
1119
|
+
false
|
|
372
1120
|
));
|
|
373
1121
|
tasks.push(this.createTask(
|
|
374
1122
|
"API Documentation",
|
|
@@ -376,7 +1124,17 @@ class SPARCPlanner {
|
|
|
376
1124
|
"completion",
|
|
377
1125
|
"documentation",
|
|
378
1126
|
"medium",
|
|
379
|
-
4
|
|
1127
|
+
4,
|
|
1128
|
+
true
|
|
1129
|
+
));
|
|
1130
|
+
tasks.push(this.createTask(
|
|
1131
|
+
"Final Review",
|
|
1132
|
+
"Review all implementations against requirements and documentation",
|
|
1133
|
+
"completion",
|
|
1134
|
+
"review",
|
|
1135
|
+
"high",
|
|
1136
|
+
4,
|
|
1137
|
+
false
|
|
380
1138
|
));
|
|
381
1139
|
this.plan.tasks = tasks;
|
|
382
1140
|
this.calculateParallelGroups();
|
|
@@ -386,51 +1144,10 @@ class SPARCPlanner {
|
|
|
386
1144
|
parallelGroups: this.plan.parallelGroups.length
|
|
387
1145
|
});
|
|
388
1146
|
}
|
|
389
|
-
/**
|
|
390
|
-
* Execute review phase
|
|
391
|
-
*/
|
|
392
|
-
async executeReviewPhase() {
|
|
393
|
-
logger.info("Executing review phase");
|
|
394
|
-
this.plan.currentPhase = "completion";
|
|
395
|
-
this.plan.decisionLog = this.decisionLog.getLog();
|
|
396
|
-
const reviewProcess = createReviewProcess({
|
|
397
|
-
plan: this.plan,
|
|
398
|
-
passes: this.options.reviewPasses,
|
|
399
|
-
autoFix: false,
|
|
400
|
-
strictMode: false
|
|
401
|
-
});
|
|
402
|
-
const result = await reviewProcess.executeReview();
|
|
403
|
-
logger.info("Review phase completed", {
|
|
404
|
-
status: result.overallStatus,
|
|
405
|
-
totalFindings: result.totalFindings,
|
|
406
|
-
criticalFindings: result.criticalFindings
|
|
407
|
-
});
|
|
408
|
-
return result;
|
|
409
|
-
}
|
|
410
|
-
/**
|
|
411
|
-
* Add a decision to the log
|
|
412
|
-
*/
|
|
413
|
-
addDecision(title, description, phase, confidence, rationale, alternatives) {
|
|
414
|
-
const decision = this.decisionLog.addDecision({
|
|
415
|
-
title,
|
|
416
|
-
description,
|
|
417
|
-
phase,
|
|
418
|
-
confidence,
|
|
419
|
-
rationale,
|
|
420
|
-
alternatives,
|
|
421
|
-
decidedBy: "sparc-planner"
|
|
422
|
-
});
|
|
423
|
-
if (this.options.autoConsensus && ConsensusBuilder.needsConsensus(confidence)) {
|
|
424
|
-
logger.info("Building consensus for low-confidence decision", {
|
|
425
|
-
decisionId: decision.id,
|
|
426
|
-
confidence
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
1147
|
/**
|
|
431
1148
|
* Create a SPARC task
|
|
432
1149
|
*/
|
|
433
|
-
createTask(name, description, phase, type, priority, estimatedHours) {
|
|
1150
|
+
createTask(name, description, phase, type, priority, estimatedHours, parallelizable) {
|
|
434
1151
|
return {
|
|
435
1152
|
id: `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
436
1153
|
name,
|
|
@@ -440,12 +1157,97 @@ class SPARCPlanner {
|
|
|
440
1157
|
priority,
|
|
441
1158
|
estimatedHours,
|
|
442
1159
|
dependencies: [],
|
|
443
|
-
parallelizable
|
|
1160
|
+
parallelizable,
|
|
444
1161
|
status: "pending",
|
|
445
1162
|
contextLinks: [],
|
|
446
1163
|
kgReferences: []
|
|
447
1164
|
};
|
|
448
1165
|
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Map complexity to priority
|
|
1168
|
+
*/
|
|
1169
|
+
mapComplexityToPriority(complexity) {
|
|
1170
|
+
switch (complexity) {
|
|
1171
|
+
case "very-high":
|
|
1172
|
+
return "critical";
|
|
1173
|
+
case "high":
|
|
1174
|
+
return "high";
|
|
1175
|
+
case "medium":
|
|
1176
|
+
return "medium";
|
|
1177
|
+
case "low":
|
|
1178
|
+
return "low";
|
|
1179
|
+
default:
|
|
1180
|
+
return "medium";
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Map requirement priority to task priority
|
|
1185
|
+
*/
|
|
1186
|
+
mapPriorityToTaskPriority(priority) {
|
|
1187
|
+
switch (priority) {
|
|
1188
|
+
case "must-have":
|
|
1189
|
+
return "critical";
|
|
1190
|
+
case "should-have":
|
|
1191
|
+
return "high";
|
|
1192
|
+
case "could-have":
|
|
1193
|
+
return "medium";
|
|
1194
|
+
case "won-t-have":
|
|
1195
|
+
return "low";
|
|
1196
|
+
default:
|
|
1197
|
+
return "medium";
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* Estimate design hours based on complexity
|
|
1202
|
+
*/
|
|
1203
|
+
estimateDesignHours(complexity) {
|
|
1204
|
+
switch (complexity) {
|
|
1205
|
+
case "very-high":
|
|
1206
|
+
return 8;
|
|
1207
|
+
case "high":
|
|
1208
|
+
return 4;
|
|
1209
|
+
case "medium":
|
|
1210
|
+
return 2;
|
|
1211
|
+
case "low":
|
|
1212
|
+
return 1;
|
|
1213
|
+
default:
|
|
1214
|
+
return 2;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Estimate implementation hours based on complexity
|
|
1219
|
+
*/
|
|
1220
|
+
estimateImplementationHours(complexity) {
|
|
1221
|
+
switch (complexity) {
|
|
1222
|
+
case "very-high":
|
|
1223
|
+
return 40;
|
|
1224
|
+
case "high":
|
|
1225
|
+
return 20;
|
|
1226
|
+
case "medium":
|
|
1227
|
+
return 8;
|
|
1228
|
+
case "low":
|
|
1229
|
+
return 4;
|
|
1230
|
+
default:
|
|
1231
|
+
return 8;
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Estimate test hours based on complexity
|
|
1236
|
+
*/
|
|
1237
|
+
estimateTestHours(complexity) {
|
|
1238
|
+
switch (complexity) {
|
|
1239
|
+
case "very-high":
|
|
1240
|
+
return 16;
|
|
1241
|
+
case "high":
|
|
1242
|
+
return 8;
|
|
1243
|
+
case "medium":
|
|
1244
|
+
return 4;
|
|
1245
|
+
case "low":
|
|
1246
|
+
return 2;
|
|
1247
|
+
default:
|
|
1248
|
+
return 4;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
449
1251
|
/**
|
|
450
1252
|
* Calculate parallel task groups
|
|
451
1253
|
*/
|
|
@@ -472,6 +1274,94 @@ class SPARCPlanner {
|
|
|
472
1274
|
this.plan.criticalPath = critical;
|
|
473
1275
|
this.plan.executionOrder = this.plan.tasks.map((t) => t.id);
|
|
474
1276
|
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Analyze existing code in src directory
|
|
1279
|
+
*/
|
|
1280
|
+
async analyzeExistingCode(srcPath) {
|
|
1281
|
+
const analysis = {
|
|
1282
|
+
hasSrcDirectory: true,
|
|
1283
|
+
srcPath,
|
|
1284
|
+
fileCount: 0,
|
|
1285
|
+
languages: {},
|
|
1286
|
+
keyFiles: [],
|
|
1287
|
+
patterns: [],
|
|
1288
|
+
dependencies: [],
|
|
1289
|
+
entryPoints: []
|
|
1290
|
+
};
|
|
1291
|
+
const analyzeDir = (dir) => {
|
|
1292
|
+
const entries = readdirSync(dir);
|
|
1293
|
+
for (const entry of entries) {
|
|
1294
|
+
const fullPath = join(dir, entry);
|
|
1295
|
+
const stat = statSync(fullPath);
|
|
1296
|
+
if (stat.isDirectory()) {
|
|
1297
|
+
if (!entry.startsWith(".") && entry !== "node_modules") {
|
|
1298
|
+
analyzeDir(fullPath);
|
|
1299
|
+
}
|
|
1300
|
+
} else if (stat.isFile()) {
|
|
1301
|
+
analysis.fileCount++;
|
|
1302
|
+
const ext = extname(entry);
|
|
1303
|
+
analysis.languages[ext] = (analysis.languages[ext] || 0) + 1;
|
|
1304
|
+
if (entry === "index.ts" || entry === "index.js") {
|
|
1305
|
+
analysis.entryPoints.push(relative(this.options.projectRoot, fullPath));
|
|
1306
|
+
}
|
|
1307
|
+
if (entry === "package.json") {
|
|
1308
|
+
try {
|
|
1309
|
+
const pkg = JSON.parse(readFileSync(fullPath, "utf-8"));
|
|
1310
|
+
analysis.dependencies = Object.keys(pkg.dependencies || {});
|
|
1311
|
+
} catch {
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
analyzeDir(srcPath);
|
|
1318
|
+
if (analysis.languages[".ts"] > 0 || analysis.languages[".tsx"] > 0) {
|
|
1319
|
+
analysis.patterns.push("TypeScript");
|
|
1320
|
+
}
|
|
1321
|
+
if (existsSync(join(this.options.projectRoot, "tests")) || existsSync(join(this.options.projectRoot, "__tests__"))) {
|
|
1322
|
+
analysis.patterns.push("Test-Driven Development");
|
|
1323
|
+
analysis.testCoverage = { hasTests: true };
|
|
1324
|
+
}
|
|
1325
|
+
return analysis;
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Execute review phase
|
|
1329
|
+
*/
|
|
1330
|
+
async executeReviewPhase() {
|
|
1331
|
+
logger.info("Executing review phase");
|
|
1332
|
+
this.plan.currentPhase = "completion";
|
|
1333
|
+
this.plan.decisionLog = this.decisionLog.getLog();
|
|
1334
|
+
const reviewProcess = createReviewProcess({
|
|
1335
|
+
plan: this.plan,
|
|
1336
|
+
passes: this.options.reviewPasses,
|
|
1337
|
+
autoFix: false,
|
|
1338
|
+
strictMode: false
|
|
1339
|
+
});
|
|
1340
|
+
const result = await reviewProcess.executeReview();
|
|
1341
|
+
logger.info("Review phase completed", {
|
|
1342
|
+
status: result.overallStatus,
|
|
1343
|
+
totalFindings: result.totalFindings,
|
|
1344
|
+
criticalFindings: result.criticalFindings
|
|
1345
|
+
});
|
|
1346
|
+
return result;
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Add a decision to the log
|
|
1350
|
+
*/
|
|
1351
|
+
addDecision(title, description, phase, confidence, rationale, alternatives) {
|
|
1352
|
+
this.decisionLog.addDecision({
|
|
1353
|
+
title,
|
|
1354
|
+
description,
|
|
1355
|
+
phase,
|
|
1356
|
+
confidence,
|
|
1357
|
+
rationale,
|
|
1358
|
+
alternatives,
|
|
1359
|
+
decidedBy: "sparc-planner"
|
|
1360
|
+
});
|
|
1361
|
+
if (this.options.autoConsensus && ConsensusBuilder.needsConsensus(confidence)) {
|
|
1362
|
+
logger.info("Low confidence decision - would trigger consensus", { title, confidence });
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
475
1365
|
/**
|
|
476
1366
|
* Update plan statistics
|
|
477
1367
|
*/
|
|
@@ -513,6 +1403,7 @@ class SPARCPlanner {
|
|
|
513
1403
|
`**Status:** ${this.plan.status}`,
|
|
514
1404
|
`**Version:** ${this.plan.version}`,
|
|
515
1405
|
`**Created:** ${this.plan.createdAt.toISOString()}`,
|
|
1406
|
+
`**Docs Analyzed:** ${this.parsedDocs.length} files`,
|
|
516
1407
|
"",
|
|
517
1408
|
"## Summary",
|
|
518
1409
|
"",
|
|
@@ -525,16 +1416,58 @@ class SPARCPlanner {
|
|
|
525
1416
|
`| Total Tasks | ${this.plan.statistics.totalTasks} |`,
|
|
526
1417
|
`| Parallelizable | ${this.plan.statistics.parallelizableTasks} |`,
|
|
527
1418
|
`| Estimated Hours | ${this.plan.statistics.estimatedHours} |`,
|
|
1419
|
+
`| Documentation Files Analyzed | ${this.parsedDocs.length} |`,
|
|
528
1420
|
`| Research Findings | ${this.plan.statistics.researchFindings} |`,
|
|
529
1421
|
`| Decisions | ${this.plan.statistics.decisions} |`,
|
|
530
1422
|
""
|
|
531
1423
|
];
|
|
1424
|
+
if (this.parsedDocs.length > 0) {
|
|
1425
|
+
lines.push("## Documentation Sources");
|
|
1426
|
+
lines.push("");
|
|
1427
|
+
const byType = /* @__PURE__ */ new Map();
|
|
1428
|
+
for (const doc of this.parsedDocs) {
|
|
1429
|
+
const existing = byType.get(doc.type) || [];
|
|
1430
|
+
existing.push(doc);
|
|
1431
|
+
byType.set(doc.type, existing);
|
|
1432
|
+
}
|
|
1433
|
+
for (const [type, docs] of byType.entries()) {
|
|
1434
|
+
lines.push(`### ${type.charAt(0).toUpperCase() + type.slice(1)} Documents (${docs.length})`);
|
|
1435
|
+
lines.push("");
|
|
1436
|
+
for (const doc of docs) {
|
|
1437
|
+
lines.push(`- **${doc.title}** - \`${relative(this.options.projectRoot, doc.path)}\``);
|
|
1438
|
+
}
|
|
1439
|
+
lines.push("");
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
532
1442
|
if (this.plan.specification) {
|
|
533
1443
|
lines.push("## Specification");
|
|
534
1444
|
lines.push("");
|
|
535
1445
|
lines.push(`- **Requirements:** ${this.plan.specification.requirements.length}`);
|
|
536
1446
|
lines.push(`- **Features:** ${this.plan.specification.features.length}`);
|
|
537
1447
|
lines.push("");
|
|
1448
|
+
if (this.plan.specification.features.length > 0) {
|
|
1449
|
+
lines.push("### Features");
|
|
1450
|
+
lines.push("");
|
|
1451
|
+
for (const feature of this.plan.specification.features) {
|
|
1452
|
+
lines.push(`#### ${feature.id}: ${feature.name}`);
|
|
1453
|
+
lines.push("");
|
|
1454
|
+
lines.push(feature.description.substring(0, 300) + "...");
|
|
1455
|
+
lines.push("");
|
|
1456
|
+
lines.push(`- **Complexity:** ${feature.complexity}`);
|
|
1457
|
+
if (feature.userStories.length > 0) {
|
|
1458
|
+
lines.push(`- **User Stories:** ${feature.userStories.length}`);
|
|
1459
|
+
}
|
|
1460
|
+
lines.push("");
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
if (this.plan.specification.requirements.length > 0) {
|
|
1464
|
+
lines.push("### Requirements");
|
|
1465
|
+
lines.push("");
|
|
1466
|
+
for (const req of this.plan.specification.requirements) {
|
|
1467
|
+
lines.push(`- **${req.id}** (${req.type}, ${req.priority}): ${req.description.substring(0, 100)}...`);
|
|
1468
|
+
}
|
|
1469
|
+
lines.push("");
|
|
1470
|
+
}
|
|
538
1471
|
}
|
|
539
1472
|
if (this.plan.architecture) {
|
|
540
1473
|
lines.push("## Architecture");
|
|
@@ -542,20 +1475,43 @@ class SPARCPlanner {
|
|
|
542
1475
|
lines.push(`- **Components:** ${this.plan.architecture.components.length}`);
|
|
543
1476
|
lines.push(`- **Patterns:** ${this.plan.architecture.patterns.join(", ")}`);
|
|
544
1477
|
lines.push("");
|
|
1478
|
+
if (this.plan.architecture.components.length > 0) {
|
|
1479
|
+
lines.push("### Components");
|
|
1480
|
+
lines.push("");
|
|
1481
|
+
for (const comp of this.plan.architecture.components) {
|
|
1482
|
+
lines.push(`#### ${comp.id}: ${comp.name}`);
|
|
1483
|
+
lines.push("");
|
|
1484
|
+
lines.push(`- **Type:** ${comp.type}`);
|
|
1485
|
+
lines.push(`- **Description:** ${comp.description.substring(0, 200)}`);
|
|
1486
|
+
if (comp.technologies.length > 0) {
|
|
1487
|
+
lines.push(`- **Technologies:** ${comp.technologies.join(", ")}`);
|
|
1488
|
+
}
|
|
1489
|
+
lines.push("");
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
545
1492
|
}
|
|
546
|
-
lines.push("## Tasks");
|
|
1493
|
+
lines.push("## Development Tasks");
|
|
547
1494
|
lines.push("");
|
|
1495
|
+
const tasksByPhase = /* @__PURE__ */ new Map();
|
|
548
1496
|
for (const task of this.plan.tasks) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
lines.push(
|
|
555
|
-
lines.push(`- **Parallelizable:** ${task.parallelizable ? "Yes" : "No"}`);
|
|
556
|
-
lines.push("");
|
|
557
|
-
lines.push(task.description);
|
|
1497
|
+
const existing = tasksByPhase.get(task.phase) || [];
|
|
1498
|
+
existing.push(task);
|
|
1499
|
+
tasksByPhase.set(task.phase, existing);
|
|
1500
|
+
}
|
|
1501
|
+
for (const [phase, tasks] of tasksByPhase.entries()) {
|
|
1502
|
+
lines.push(`### ${phase.charAt(0).toUpperCase() + phase.slice(1)} Phase (${tasks.length} tasks)`);
|
|
558
1503
|
lines.push("");
|
|
1504
|
+
for (const task of tasks) {
|
|
1505
|
+
lines.push(`#### ${task.name}`);
|
|
1506
|
+
lines.push("");
|
|
1507
|
+
lines.push(`- **Type:** ${task.type}`);
|
|
1508
|
+
lines.push(`- **Priority:** ${task.priority}`);
|
|
1509
|
+
lines.push(`- **Estimated:** ${task.estimatedHours}h`);
|
|
1510
|
+
lines.push(`- **Parallelizable:** ${task.parallelizable ? "Yes" : "No"}`);
|
|
1511
|
+
lines.push("");
|
|
1512
|
+
lines.push(task.description.substring(0, 500));
|
|
1513
|
+
lines.push("");
|
|
1514
|
+
}
|
|
559
1515
|
}
|
|
560
1516
|
if (this.plan.reviewResult) {
|
|
561
1517
|
lines.push("## Review Result");
|
|
@@ -575,165 +1531,6 @@ class SPARCPlanner {
|
|
|
575
1531
|
}
|
|
576
1532
|
return lines.join("\n");
|
|
577
1533
|
}
|
|
578
|
-
// Helper methods for content generation
|
|
579
|
-
extractProblemStatement() {
|
|
580
|
-
return `Problem to solve: ${this.options.description}`;
|
|
581
|
-
}
|
|
582
|
-
extractGoals() {
|
|
583
|
-
return [
|
|
584
|
-
"Implement all specified features",
|
|
585
|
-
"Ensure code quality and maintainability",
|
|
586
|
-
"Provide comprehensive documentation",
|
|
587
|
-
"Enable parallel development where possible"
|
|
588
|
-
];
|
|
589
|
-
}
|
|
590
|
-
generateRequirements() {
|
|
591
|
-
return [
|
|
592
|
-
{
|
|
593
|
-
id: "req-001",
|
|
594
|
-
type: "functional",
|
|
595
|
-
description: "Core functionality as described",
|
|
596
|
-
priority: "must-have",
|
|
597
|
-
source: "User request",
|
|
598
|
-
acceptanceCriteria: ["Feature works as specified"],
|
|
599
|
-
relatedRequirements: []
|
|
600
|
-
},
|
|
601
|
-
{
|
|
602
|
-
id: "req-002",
|
|
603
|
-
type: "non-functional",
|
|
604
|
-
description: "Code must be well-documented",
|
|
605
|
-
priority: "should-have",
|
|
606
|
-
source: "Best practices",
|
|
607
|
-
acceptanceCriteria: ["API documentation exists"],
|
|
608
|
-
relatedRequirements: []
|
|
609
|
-
},
|
|
610
|
-
{
|
|
611
|
-
id: "req-003",
|
|
612
|
-
type: "non-functional",
|
|
613
|
-
description: "Test coverage must be adequate",
|
|
614
|
-
priority: "should-have",
|
|
615
|
-
source: "Best practices",
|
|
616
|
-
acceptanceCriteria: ["Unit tests exist for core functionality"],
|
|
617
|
-
relatedRequirements: []
|
|
618
|
-
}
|
|
619
|
-
];
|
|
620
|
-
}
|
|
621
|
-
generateFeatures() {
|
|
622
|
-
return [
|
|
623
|
-
{
|
|
624
|
-
id: "feat-001",
|
|
625
|
-
name: "Core Implementation",
|
|
626
|
-
description: "Main feature implementation",
|
|
627
|
-
userStories: [`As a user, I want ${this.options.description}`],
|
|
628
|
-
requirements: ["req-001"],
|
|
629
|
-
complexity: "medium",
|
|
630
|
-
dependencies: [],
|
|
631
|
-
parallelizable: true
|
|
632
|
-
}
|
|
633
|
-
];
|
|
634
|
-
}
|
|
635
|
-
extractConstraints() {
|
|
636
|
-
return [
|
|
637
|
-
"Must follow existing code patterns if present",
|
|
638
|
-
"Must maintain backwards compatibility"
|
|
639
|
-
];
|
|
640
|
-
}
|
|
641
|
-
generateSuccessMetrics() {
|
|
642
|
-
return [
|
|
643
|
-
"All tests pass",
|
|
644
|
-
"Documentation is complete",
|
|
645
|
-
"Code review approved"
|
|
646
|
-
];
|
|
647
|
-
}
|
|
648
|
-
generateAlgorithmDesign(feature) {
|
|
649
|
-
return {
|
|
650
|
-
id: `algo_${feature.id}`,
|
|
651
|
-
name: `${feature.name} Algorithm`,
|
|
652
|
-
purpose: feature.description,
|
|
653
|
-
steps: [
|
|
654
|
-
{
|
|
655
|
-
step: 1,
|
|
656
|
-
description: "Initialize",
|
|
657
|
-
pseudocode: "// Initialize component",
|
|
658
|
-
inputs: [],
|
|
659
|
-
outputs: []
|
|
660
|
-
},
|
|
661
|
-
{
|
|
662
|
-
step: 2,
|
|
663
|
-
description: "Process",
|
|
664
|
-
pseudocode: "// Process input",
|
|
665
|
-
inputs: ["input"],
|
|
666
|
-
outputs: ["result"]
|
|
667
|
-
},
|
|
668
|
-
{
|
|
669
|
-
step: 3,
|
|
670
|
-
description: "Return",
|
|
671
|
-
pseudocode: "// Return result",
|
|
672
|
-
inputs: ["result"],
|
|
673
|
-
outputs: ["output"]
|
|
674
|
-
}
|
|
675
|
-
],
|
|
676
|
-
timeComplexity: "O(n)",
|
|
677
|
-
spaceComplexity: "O(1)",
|
|
678
|
-
edgeCases: ["Empty input", "Invalid input"],
|
|
679
|
-
relatedFeatures: [feature.id]
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
generateArchitectureOverview() {
|
|
683
|
-
return `Architecture for ${this.options.name}: ${this.options.description}`;
|
|
684
|
-
}
|
|
685
|
-
identifyArchitecturePatterns() {
|
|
686
|
-
const patterns = ["Modular"];
|
|
687
|
-
if (this.plan.existingCode?.patterns.includes("TypeScript")) {
|
|
688
|
-
patterns.push("Type-Safe");
|
|
689
|
-
}
|
|
690
|
-
if (this.plan.existingCode?.patterns.includes("Test-Driven Development")) {
|
|
691
|
-
patterns.push("TDD");
|
|
692
|
-
}
|
|
693
|
-
return patterns;
|
|
694
|
-
}
|
|
695
|
-
generateComponents() {
|
|
696
|
-
return [
|
|
697
|
-
{
|
|
698
|
-
id: "comp-core",
|
|
699
|
-
name: "Core Module",
|
|
700
|
-
type: "module",
|
|
701
|
-
description: "Core functionality module",
|
|
702
|
-
responsibilities: ["Main feature implementation"],
|
|
703
|
-
interfaces: [],
|
|
704
|
-
dependencies: [],
|
|
705
|
-
technologies: ["TypeScript"]
|
|
706
|
-
},
|
|
707
|
-
{
|
|
708
|
-
id: "comp-api",
|
|
709
|
-
name: "API Layer",
|
|
710
|
-
type: "api",
|
|
711
|
-
description: "Public API interface",
|
|
712
|
-
responsibilities: ["Expose public API"],
|
|
713
|
-
interfaces: [],
|
|
714
|
-
dependencies: ["comp-core"],
|
|
715
|
-
technologies: ["TypeScript"]
|
|
716
|
-
}
|
|
717
|
-
];
|
|
718
|
-
}
|
|
719
|
-
describeDataFlow() {
|
|
720
|
-
return "Input -> API Layer -> Core Module -> Output";
|
|
721
|
-
}
|
|
722
|
-
identifySecurityConsiderations() {
|
|
723
|
-
return [
|
|
724
|
-
"Input validation",
|
|
725
|
-
"Error handling"
|
|
726
|
-
];
|
|
727
|
-
}
|
|
728
|
-
getComponentPriority(component) {
|
|
729
|
-
if (component.dependencies.length === 0) return "high";
|
|
730
|
-
return "medium";
|
|
731
|
-
}
|
|
732
|
-
estimateComponentHours(component) {
|
|
733
|
-
const base = 4;
|
|
734
|
-
const depFactor = component.dependencies.length * 2;
|
|
735
|
-
return base + depFactor;
|
|
736
|
-
}
|
|
737
1534
|
/**
|
|
738
1535
|
* Get the current plan
|
|
739
1536
|
*/
|
|
@@ -746,6 +1543,12 @@ class SPARCPlanner {
|
|
|
746
1543
|
getDecisionLog() {
|
|
747
1544
|
return this.decisionLog;
|
|
748
1545
|
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Get parsed documentation
|
|
1548
|
+
*/
|
|
1549
|
+
getParsedDocs() {
|
|
1550
|
+
return this.parsedDocs;
|
|
1551
|
+
}
|
|
749
1552
|
}
|
|
750
1553
|
function createSPARCPlanner(options) {
|
|
751
1554
|
return new SPARCPlanner(options);
|