@thanh01.pmt/domain-kit 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/README.md +1101 -0
- package/dist/assembly/index.cjs +213 -0
- package/dist/assembly/index.cjs.map +1 -0
- package/dist/assembly/index.d.cts +66 -0
- package/dist/assembly/index.d.ts +66 -0
- package/dist/assembly/index.mjs +4 -0
- package/dist/assembly/index.mjs.map +1 -0
- package/dist/chunk-3H2HX7PR.mjs +324 -0
- package/dist/chunk-3H2HX7PR.mjs.map +1 -0
- package/dist/chunk-3VVPSRAM.mjs +1297 -0
- package/dist/chunk-3VVPSRAM.mjs.map +1 -0
- package/dist/chunk-DRVOV5ZD.mjs +314 -0
- package/dist/chunk-DRVOV5ZD.mjs.map +1 -0
- package/dist/chunk-F4RZNBOE.mjs +3 -0
- package/dist/chunk-F4RZNBOE.mjs.map +1 -0
- package/dist/chunk-GEUDHZE7.mjs +446 -0
- package/dist/chunk-GEUDHZE7.mjs.map +1 -0
- package/dist/chunk-HH4UX65C.mjs +100 -0
- package/dist/chunk-HH4UX65C.mjs.map +1 -0
- package/dist/chunk-KMYENICQ.mjs +3 -0
- package/dist/chunk-KMYENICQ.mjs.map +1 -0
- package/dist/chunk-KNDBODUQ.mjs +153 -0
- package/dist/chunk-KNDBODUQ.mjs.map +1 -0
- package/dist/chunk-MM2RWYNW.mjs +3 -0
- package/dist/chunk-MM2RWYNW.mjs.map +1 -0
- package/dist/chunk-MOKNFLUD.mjs +851 -0
- package/dist/chunk-MOKNFLUD.mjs.map +1 -0
- package/dist/chunk-ONQHJ4OH.mjs +211 -0
- package/dist/chunk-ONQHJ4OH.mjs.map +1 -0
- package/dist/chunk-PM42MMDJ.mjs +516 -0
- package/dist/chunk-PM42MMDJ.mjs.map +1 -0
- package/dist/chunk-RKRNOVKO.mjs +166 -0
- package/dist/chunk-RKRNOVKO.mjs.map +1 -0
- package/dist/chunk-VFART56O.mjs +884 -0
- package/dist/chunk-VFART56O.mjs.map +1 -0
- package/dist/chunk-XMHKWHVK.mjs +218 -0
- package/dist/chunk-XMHKWHVK.mjs.map +1 -0
- package/dist/conceptEscalator-YfMKIKCZ.d.ts +40 -0
- package/dist/conceptEscalator-ltRLtPhf.d.cts +40 -0
- package/dist/concepts/index.cjs +155 -0
- package/dist/concepts/index.cjs.map +1 -0
- package/dist/concepts/index.d.cts +79 -0
- package/dist/concepts/index.d.ts +79 -0
- package/dist/concepts/index.mjs +4 -0
- package/dist/concepts/index.mjs.map +1 -0
- package/dist/curriculumFeedSchema-TEeQg2bY.d.cts +549 -0
- package/dist/curriculumFeedSchema-TEeQg2bY.d.ts +549 -0
- package/dist/detector/index.cjs +889 -0
- package/dist/detector/index.cjs.map +1 -0
- package/dist/detector/index.d.cts +112 -0
- package/dist/detector/index.d.ts +112 -0
- package/dist/detector/index.mjs +3 -0
- package/dist/detector/index.mjs.map +1 -0
- package/dist/domainProfileSchema-CwT3Ffsw.d.cts +105 -0
- package/dist/domainProfileSchema-CwT3Ffsw.d.ts +105 -0
- package/dist/extractors/index.cjs +387 -0
- package/dist/extractors/index.cjs.map +1 -0
- package/dist/extractors/index.d.cts +46 -0
- package/dist/extractors/index.d.ts +46 -0
- package/dist/extractors/index.mjs +4 -0
- package/dist/extractors/index.mjs.map +1 -0
- package/dist/feed/index.cjs +421 -0
- package/dist/feed/index.cjs.map +1 -0
- package/dist/feed/index.d.cts +23 -0
- package/dist/feed/index.d.ts +23 -0
- package/dist/feed/index.mjs +4 -0
- package/dist/feed/index.mjs.map +1 -0
- package/dist/graph/index.cjs +2167 -0
- package/dist/graph/index.cjs.map +1 -0
- package/dist/graph/index.d.cts +226 -0
- package/dist/graph/index.d.ts +226 -0
- package/dist/graph/index.mjs +4 -0
- package/dist/graph/index.mjs.map +1 -0
- package/dist/graphVerifier-DsTP9uAN.d.ts +37 -0
- package/dist/graphVerifier-QjgDAJce.d.cts +37 -0
- package/dist/hybridGraphSchema-BCgXicgA.d.cts +2840 -0
- package/dist/hybridGraphSchema-BCgXicgA.d.ts +2840 -0
- package/dist/index.cjs +5539 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +18 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.mjs +17 -0
- package/dist/index.mjs.map +1 -0
- package/dist/keywordExtractor-CKaXqSku.d.ts +34 -0
- package/dist/keywordExtractor-zPAz2isq.d.cts +34 -0
- package/dist/llmClient-ysPhLjcH.d.cts +16 -0
- package/dist/llmClient-ysPhLjcH.d.ts +16 -0
- package/dist/parsers/index.cjs +323 -0
- package/dist/parsers/index.cjs.map +1 -0
- package/dist/parsers/index.d.cts +98 -0
- package/dist/parsers/index.d.ts +98 -0
- package/dist/parsers/index.mjs +4 -0
- package/dist/parsers/index.mjs.map +1 -0
- package/dist/pipeline/index.cjs +2166 -0
- package/dist/pipeline/index.cjs.map +1 -0
- package/dist/pipeline/index.d.cts +45 -0
- package/dist/pipeline/index.d.ts +45 -0
- package/dist/pipeline/index.mjs +8 -0
- package/dist/pipeline/index.mjs.map +1 -0
- package/dist/projectGraphSchema-DnD7orZV.d.cts +2581 -0
- package/dist/projectGraphSchema-DnD7orZV.d.ts +2581 -0
- package/dist/schemas/index.cjs +675 -0
- package/dist/schemas/index.cjs.map +1 -0
- package/dist/schemas/index.d.cts +253 -0
- package/dist/schemas/index.d.ts +253 -0
- package/dist/schemas/index.mjs +4 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { parseSwiftFile, parseTsFile, parsePythonFile, parseCppFile, mergeSwiftResults, mergeTsResults, mergePythonResults, mergeCppResults } from './chunk-DRVOV5ZD.mjs';
|
|
2
|
+
import { extractKeywords } from './chunk-XMHKWHVK.mjs';
|
|
3
|
+
import { extractScaffold, extractProjectOverview, extractFeatureStepsBatched, extractFeatureSteps, verifyProjectGraph, escalateAndMapConcepts, createLlmClient, llmChatJson } from './chunk-MOKNFLUD.mjs';
|
|
4
|
+
import { resolveConcepts } from './chunk-KNDBODUQ.mjs';
|
|
5
|
+
import { assembleRoadmap } from './chunk-ONQHJ4OH.mjs';
|
|
6
|
+
import { readFileSync, readdirSync } from 'fs';
|
|
7
|
+
import { relative, join, extname } from 'path';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
|
|
10
|
+
// src/extractors/fileSelector.ts
|
|
11
|
+
async function selectFilesWithLlm(filePaths, goal, techStack, llmConfig) {
|
|
12
|
+
if (filePaths.length <= 15) {
|
|
13
|
+
return { selected: filePaths, reasoning: "Too few files to filter" };
|
|
14
|
+
}
|
|
15
|
+
const client = createLlmClient(llmConfig);
|
|
16
|
+
const treeLines = buildFileTree(filePaths);
|
|
17
|
+
const systemPrompt = 'You are a software architect analyzing a project repository.\nGiven a file tree and a project goal, select which files are MOST IMPORTANT to read for understanding the project architecture and implementation.\n\nRULES:\n1. Prioritize source code files (Swift, TS, Python, C++) over config/docs\n2. Include entry points (App.swift, main.py, index.ts)\n3. Include core domain files (models, services, views, controllers)\n4. EXCLUDE test files, mock files, generated files\n5. EXCLUDE config files (package.json, Podfile, etc.) unless they reveal architecture\n6. Include files that directly implement the features mentioned in the goal\n7. Return 20-40 files maximum (fewer is better if the project is small)\n\nReturn JSON: {"selected": ["path/to/file1", "path/to/file2"], "reasoning": "brief explanation"}';
|
|
18
|
+
const userPrompt = `Project goal: ${goal}
|
|
19
|
+
Tech stack: ${techStack}
|
|
20
|
+
|
|
21
|
+
FILE TREE (${filePaths.length} files):
|
|
22
|
+
${treeLines}
|
|
23
|
+
|
|
24
|
+
Select the most important files to analyze. Return JSON.`;
|
|
25
|
+
try {
|
|
26
|
+
const result = await llmChatJson(client, systemPrompt, userPrompt, {
|
|
27
|
+
temperature: 0.1,
|
|
28
|
+
maxTokens: 4096
|
|
29
|
+
});
|
|
30
|
+
const selected = Array.isArray(result.selected) ? result.selected : [];
|
|
31
|
+
const reasoning = String(result.reasoning || "");
|
|
32
|
+
const pathSet = new Set(filePaths);
|
|
33
|
+
const validSelected = selected.filter((p) => pathSet.has(p));
|
|
34
|
+
if (validSelected.length === 0) {
|
|
35
|
+
return { selected: filePaths, reasoning: "LLM selection empty, using all files" };
|
|
36
|
+
}
|
|
37
|
+
return { selected: validSelected, reasoning };
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.warn(`[WARN] LLM file selection failed (${err}), using all files`);
|
|
40
|
+
return { selected: filePaths, reasoning: `LLM failed: ${err}` };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function buildFileTree(filePaths) {
|
|
44
|
+
const byDir = /* @__PURE__ */ new Map();
|
|
45
|
+
for (const fp of filePaths) {
|
|
46
|
+
const parts = fp.split("/");
|
|
47
|
+
const dir = parts.length > 1 ? parts.slice(0, -1).join("/") : "(root)";
|
|
48
|
+
const file = parts[parts.length - 1];
|
|
49
|
+
if (!byDir.has(dir)) byDir.set(dir, []);
|
|
50
|
+
byDir.get(dir).push(file);
|
|
51
|
+
}
|
|
52
|
+
const lines = [];
|
|
53
|
+
for (const [dir, files] of byDir) {
|
|
54
|
+
lines.push(`${dir}/`);
|
|
55
|
+
for (const f of files.sort()) {
|
|
56
|
+
lines.push(` ${f}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return lines.join("\n");
|
|
60
|
+
}
|
|
61
|
+
function collectSourceFiles(dir, options) {
|
|
62
|
+
const allowedExts = [".swift", ".ts", ".tsx", ".js", ".jsx", ".py", ".ino", ".cpp", ".c", ".h", ".hpp"];
|
|
63
|
+
const maxFiles = 70;
|
|
64
|
+
const gitFiles = collectViaGit(dir, allowedExts, maxFiles);
|
|
65
|
+
if (gitFiles.length > 0) return gitFiles;
|
|
66
|
+
return collectViaFilesystem(dir, allowedExts, maxFiles);
|
|
67
|
+
}
|
|
68
|
+
function collectViaGit(dir, allowedExts, maxFiles) {
|
|
69
|
+
try {
|
|
70
|
+
const raw = execSync("git ls-files --cached --others --exclude-standard", {
|
|
71
|
+
cwd: dir,
|
|
72
|
+
encoding: "utf-8",
|
|
73
|
+
timeout: 5e3,
|
|
74
|
+
maxBuffer: 1024 * 1024
|
|
75
|
+
});
|
|
76
|
+
const extSet = new Set(allowedExts.map((e) => e.toLowerCase()));
|
|
77
|
+
return raw.split("\n").filter((line) => line.length > 0).filter((line) => extSet.has(extname(line).toLowerCase())).slice(0, maxFiles).map((rel) => join(dir, rel));
|
|
78
|
+
} catch {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function collectViaFilesystem(dir, allowedExts, maxFiles, skipDirs) {
|
|
83
|
+
const skipSet = new Set([
|
|
84
|
+
"node_modules",
|
|
85
|
+
".build",
|
|
86
|
+
"Pods",
|
|
87
|
+
".git",
|
|
88
|
+
".venv",
|
|
89
|
+
"venv",
|
|
90
|
+
"__pycache__",
|
|
91
|
+
"build",
|
|
92
|
+
"dist",
|
|
93
|
+
".idea",
|
|
94
|
+
".vscode",
|
|
95
|
+
"test",
|
|
96
|
+
"tests",
|
|
97
|
+
"demo",
|
|
98
|
+
"sample",
|
|
99
|
+
"example",
|
|
100
|
+
"playground"
|
|
101
|
+
]);
|
|
102
|
+
const extSet = new Set(allowedExts.map((e) => e.toLowerCase()));
|
|
103
|
+
const result = [];
|
|
104
|
+
function walk(currentDir) {
|
|
105
|
+
if (result.length >= maxFiles) return;
|
|
106
|
+
let entries;
|
|
107
|
+
try {
|
|
108
|
+
entries = readdirSync(currentDir, { withFileTypes: true });
|
|
109
|
+
} catch {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
if (result.length >= maxFiles) return;
|
|
114
|
+
const fullPath = join(currentDir, entry.name);
|
|
115
|
+
if (entry.isDirectory()) {
|
|
116
|
+
if (!skipSet.has(entry.name)) {
|
|
117
|
+
walk(fullPath);
|
|
118
|
+
}
|
|
119
|
+
} else if (entry.isFile()) {
|
|
120
|
+
const ext = extname(entry.name).toLowerCase();
|
|
121
|
+
if (extSet.has(ext)) {
|
|
122
|
+
result.push(fullPath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
walk(dir);
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
function collectSpecificFileContents(dir, relativePaths, maxChars = 5e5) {
|
|
131
|
+
const contents = /* @__PURE__ */ new Map();
|
|
132
|
+
let totalChars = 0;
|
|
133
|
+
for (const relPath of relativePaths) {
|
|
134
|
+
const fullPath = join(dir, relPath);
|
|
135
|
+
try {
|
|
136
|
+
const content = readFileSync(fullPath, "utf-8");
|
|
137
|
+
if (totalChars + content.length > maxChars) {
|
|
138
|
+
const remaining = maxChars - totalChars;
|
|
139
|
+
if (remaining > 100) {
|
|
140
|
+
contents.set(relPath, content.slice(0, remaining));
|
|
141
|
+
totalChars += remaining;
|
|
142
|
+
}
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
contents.set(relPath, content);
|
|
146
|
+
totalChars += content.length;
|
|
147
|
+
} catch {
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return contents;
|
|
151
|
+
}
|
|
152
|
+
function detectPrimaryLanguage(filePaths) {
|
|
153
|
+
const counts = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const fp of filePaths) {
|
|
155
|
+
const ext = extname(fp).toLowerCase();
|
|
156
|
+
let lang = "unknown";
|
|
157
|
+
if (ext === ".swift") lang = "Swift";
|
|
158
|
+
else if (ext === ".ts" || ext === ".tsx") lang = "TypeScript";
|
|
159
|
+
else if (ext === ".js" || ext === ".jsx") lang = "JavaScript";
|
|
160
|
+
else if (ext === ".py") lang = "Python";
|
|
161
|
+
else if (ext === ".ino" || ext === ".cpp" || ext === ".c" || ext === ".h") lang = "C++";
|
|
162
|
+
else if (ext === ".kt" || ext === ".kts") lang = "Kotlin";
|
|
163
|
+
counts.set(lang, (counts.get(lang) || 0) + 1);
|
|
164
|
+
}
|
|
165
|
+
let maxCount = 0;
|
|
166
|
+
let primary = "unknown";
|
|
167
|
+
for (const [lang, count] of counts) {
|
|
168
|
+
if (count > maxCount) {
|
|
169
|
+
maxCount = count;
|
|
170
|
+
primary = lang;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return primary;
|
|
174
|
+
}
|
|
175
|
+
async function runProjectGraphPipeline(options) {
|
|
176
|
+
const { repoDir, goal, techStack, llmConfig, embeddings, onProgress } = options;
|
|
177
|
+
const log = (step, msg) => {
|
|
178
|
+
console.log(`[${step}] ${msg}`);
|
|
179
|
+
onProgress?.(step, msg);
|
|
180
|
+
};
|
|
181
|
+
log("STEP_1", "Scanning file tree (names only)...");
|
|
182
|
+
const allFilePaths = collectSourceFiles(repoDir);
|
|
183
|
+
const allRelPaths = allFilePaths.map((fp) => relative(repoDir, fp));
|
|
184
|
+
log("STEP_1", `Found ${allRelPaths.length} source files in repository`);
|
|
185
|
+
log("STEP_1", "Asking LLM to select relevant files from file tree...");
|
|
186
|
+
const { selected: selectedPaths, reasoning } = await selectFilesWithLlm(
|
|
187
|
+
allRelPaths,
|
|
188
|
+
goal,
|
|
189
|
+
techStack,
|
|
190
|
+
llmConfig
|
|
191
|
+
);
|
|
192
|
+
log("STEP_1", `LLM selected ${selectedPaths.length}/${allRelPaths.length} files: ${reasoning.slice(0, 100)}`);
|
|
193
|
+
log("STEP_1", "Reading content for selected files...");
|
|
194
|
+
const fileContentsMap = collectSpecificFileContents(repoDir, selectedPaths);
|
|
195
|
+
const filePaths = selectedPaths;
|
|
196
|
+
const primaryLang = detectPrimaryLanguage(filePaths);
|
|
197
|
+
log("STEP_1", `Primary language: ${primaryLang}, ${filePaths.length} files loaded (${fileContentsMap.size} with content)`);
|
|
198
|
+
log("STEP_1", "Parsing source files with AST-level analysis...");
|
|
199
|
+
const allParseResults = [];
|
|
200
|
+
for (const [relPath, content] of fileContentsMap) {
|
|
201
|
+
const fullPath = relPath;
|
|
202
|
+
if (fullPath.endsWith(".swift")) {
|
|
203
|
+
allParseResults.push(parseSwiftFile(fullPath, content));
|
|
204
|
+
} else if (fullPath.endsWith(".ts") || fullPath.endsWith(".tsx") || fullPath.endsWith(".js") || fullPath.endsWith(".jsx")) {
|
|
205
|
+
allParseResults.push(parseTsFile(fullPath, content));
|
|
206
|
+
} else if (fullPath.endsWith(".py")) {
|
|
207
|
+
allParseResults.push(parsePythonFile(fullPath, content));
|
|
208
|
+
} else if (fullPath.endsWith(".ino") || fullPath.endsWith(".cpp") || fullPath.endsWith(".c") || fullPath.endsWith(".h")) {
|
|
209
|
+
allParseResults.push(parseCppFile(fullPath, content));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
let mergedContext;
|
|
213
|
+
if (primaryLang === "Swift") {
|
|
214
|
+
mergedContext = mergeSwiftResults(allParseResults.filter((r) => "property_wrappers" in r));
|
|
215
|
+
} else if (primaryLang === "TypeScript" || primaryLang === "JavaScript") {
|
|
216
|
+
mergedContext = mergeTsResults(allParseResults.filter((r) => !("docstrings" in r) && !("property_wrappers" in r) && !("frameworks_used" in r)));
|
|
217
|
+
} else if (primaryLang === "Python") {
|
|
218
|
+
mergedContext = mergePythonResults(allParseResults.filter((r) => "docstrings" in r));
|
|
219
|
+
} else {
|
|
220
|
+
mergedContext = mergeCppResults(allParseResults.filter((r) => "error_handling" in r && !("docstrings" in r) && !("property_wrappers" in r)));
|
|
221
|
+
}
|
|
222
|
+
log("STEP_2", "Extracting keywords from AST analysis...");
|
|
223
|
+
const astKeywords = extractKeywords(mergedContext);
|
|
224
|
+
log("STEP_2", `Extracted ${astKeywords.length} keywords`);
|
|
225
|
+
log("STEP_3", "Building SDK API index...");
|
|
226
|
+
const sdkApiIndex = {
|
|
227
|
+
sdk_apis: astKeywords.map((kw) => ({
|
|
228
|
+
name: kw.keyword,
|
|
229
|
+
kind: kw.source,
|
|
230
|
+
used_in_demo: true
|
|
231
|
+
}))
|
|
232
|
+
};
|
|
233
|
+
log("STEP_4", "Generating project graph via LLM (C0: Scaffold, C1: Overview, C2: Steps)...");
|
|
234
|
+
log("STEP_4_C0", "Extracting FOUNDATION & SETUP feature (F0)...");
|
|
235
|
+
const scaffoldFeature = await extractScaffold({
|
|
236
|
+
goal,
|
|
237
|
+
techStack,
|
|
238
|
+
fileList: filePaths,
|
|
239
|
+
llmConfig
|
|
240
|
+
});
|
|
241
|
+
log("STEP_4_C1", "Extracting project overview (features meta)...");
|
|
242
|
+
const overview = await extractProjectOverview({
|
|
243
|
+
goal,
|
|
244
|
+
techStack,
|
|
245
|
+
sdkApiIndex,
|
|
246
|
+
fileContentsMap,
|
|
247
|
+
astKeywords,
|
|
248
|
+
llmConfig
|
|
249
|
+
});
|
|
250
|
+
const product = overview.product;
|
|
251
|
+
const pblStep = {
|
|
252
|
+
id: "F0-S1",
|
|
253
|
+
sequence: 1,
|
|
254
|
+
name: "Frame the problem & understand user needs (Design Thinking)",
|
|
255
|
+
description: "BEFORE CODING \u2014 frame the problem and understand what the user needs",
|
|
256
|
+
files: [],
|
|
257
|
+
api_usage: [],
|
|
258
|
+
keywords: ["Design Thinking", "User Needs", "Problem Statement", "User Journey", "PBL"],
|
|
259
|
+
completion_level: "base",
|
|
260
|
+
platform: "app",
|
|
261
|
+
intent: "Understand user needs and frame problem before writing code",
|
|
262
|
+
outcome: { user_visible: "Defined problem statement", technical: "Problem frame context" },
|
|
263
|
+
acceptance: ["Problem statement is clear", "Primary users identified"],
|
|
264
|
+
requirement_ids: [],
|
|
265
|
+
concept_codes: [],
|
|
266
|
+
effort: { estimated_minutes: 15, complexity: "low" }
|
|
267
|
+
};
|
|
268
|
+
let featuresFull = [];
|
|
269
|
+
if (scaffoldFeature) {
|
|
270
|
+
const scaffoldSteps = [pblStep, ...scaffoldFeature.steps || []].map((s, i) => ({
|
|
271
|
+
...s,
|
|
272
|
+
id: `F0-S${i + 1}`,
|
|
273
|
+
sequence: i + 1
|
|
274
|
+
}));
|
|
275
|
+
featuresFull.push({
|
|
276
|
+
...scaffoldFeature,
|
|
277
|
+
id: "F0",
|
|
278
|
+
name: "FOUNDATION & SETUP",
|
|
279
|
+
steps: scaffoldSteps
|
|
280
|
+
});
|
|
281
|
+
} else {
|
|
282
|
+
featuresFull.push({
|
|
283
|
+
id: "F0",
|
|
284
|
+
name: "FOUNDATION & SETUP",
|
|
285
|
+
description: "Frame the problem, get familiar with the tools",
|
|
286
|
+
steps: [pblStep]
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const overviewFeatures = product?.features || [];
|
|
290
|
+
featuresFull = [...featuresFull, ...overviewFeatures];
|
|
291
|
+
log("STEP_4_C2", `Extracting steps for ${overviewFeatures.length} features...`);
|
|
292
|
+
const nonF0Features = overviewFeatures.filter((f) => f.id !== "F0");
|
|
293
|
+
if (nonF0Features.length > 0) {
|
|
294
|
+
try {
|
|
295
|
+
const batchedSteps = await extractFeatureStepsBatched(
|
|
296
|
+
goal,
|
|
297
|
+
techStack,
|
|
298
|
+
sdkApiIndex,
|
|
299
|
+
fileContentsMap,
|
|
300
|
+
nonF0Features,
|
|
301
|
+
llmConfig
|
|
302
|
+
);
|
|
303
|
+
for (const feature of nonF0Features) {
|
|
304
|
+
const steps = batchedSteps.get(feature.id || "");
|
|
305
|
+
if (steps) feature.steps = steps;
|
|
306
|
+
}
|
|
307
|
+
} catch (err) {
|
|
308
|
+
log("STEP_4_C2", `Batch extraction failed (${err}), falling back to per-feature...`);
|
|
309
|
+
for (const feature of nonF0Features) {
|
|
310
|
+
try {
|
|
311
|
+
const steps = await extractFeatureSteps({
|
|
312
|
+
goal,
|
|
313
|
+
techStack,
|
|
314
|
+
sdkApiIndex,
|
|
315
|
+
fileContentsMap,
|
|
316
|
+
feature,
|
|
317
|
+
llmConfig
|
|
318
|
+
});
|
|
319
|
+
feature.steps = steps;
|
|
320
|
+
} catch (e) {
|
|
321
|
+
log("STEP_4_C2", `Failed for ${feature.id}: ${e}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const projectGraph = {
|
|
327
|
+
schema_version: 3,
|
|
328
|
+
project: {
|
|
329
|
+
id: overview.project?.name?.toString().toLowerCase().replace(/\s+/g, "-") || "project",
|
|
330
|
+
name: overview.project?.name?.toString() || "Project",
|
|
331
|
+
purpose: goal,
|
|
332
|
+
version: "0.1.0",
|
|
333
|
+
project_type: "app",
|
|
334
|
+
platform: overview.project?.platforms || ["ios"],
|
|
335
|
+
tech_stack: techStack.split(",").reduce((acc, t) => {
|
|
336
|
+
const key = t.trim().toLowerCase();
|
|
337
|
+
if (key) acc[key] = [t.trim()];
|
|
338
|
+
return acc;
|
|
339
|
+
}, {}),
|
|
340
|
+
architecture: overview.architecture?.layers || []
|
|
341
|
+
},
|
|
342
|
+
product: {
|
|
343
|
+
goals: product?.goals || [],
|
|
344
|
+
users: product?.users || [],
|
|
345
|
+
journeys: product?.user_journeys || [],
|
|
346
|
+
requirements: [],
|
|
347
|
+
development_stages: product?.development_stages || [],
|
|
348
|
+
purpose: String(product?.purpose || ""),
|
|
349
|
+
problem_statement: String(product?.problem_statement || ""),
|
|
350
|
+
primary_users: product?.primary_users || []
|
|
351
|
+
},
|
|
352
|
+
features: featuresFull,
|
|
353
|
+
capabilities: [],
|
|
354
|
+
implementation: {
|
|
355
|
+
tasks: []
|
|
356
|
+
},
|
|
357
|
+
missing_gaps: [],
|
|
358
|
+
tech_debt: []
|
|
359
|
+
};
|
|
360
|
+
log("STEP_5", "Verifying project graph against source code...");
|
|
361
|
+
const { graph: verifiedGraph, hallucinations } = verifyProjectGraph(projectGraph, {
|
|
362
|
+
repoDir,
|
|
363
|
+
sdkApiIndex,
|
|
364
|
+
fileContentsMap
|
|
365
|
+
});
|
|
366
|
+
log("STEP_5", `Verification complete: ${hallucinations.length} hallucinations found`);
|
|
367
|
+
log("STEP_6", "Escalating keywords to neutral concepts...");
|
|
368
|
+
const featureConcepts = await escalateAndMapConcepts({
|
|
369
|
+
projectGraph: verifiedGraph,
|
|
370
|
+
fileContentsMap,
|
|
371
|
+
llmConfig
|
|
372
|
+
});
|
|
373
|
+
log("STEP_6", `Concept mapping complete: ${featureConcepts.size} features mapped`);
|
|
374
|
+
for (const feature of verifiedGraph.features || []) {
|
|
375
|
+
const fid = feature.id || "";
|
|
376
|
+
const fConcepts = featureConcepts.get(fid) || [];
|
|
377
|
+
for (const step of feature.steps || []) {
|
|
378
|
+
const stepConcepts = fConcepts.filter((cm) => {
|
|
379
|
+
const stepKws = [...step.keywords || [], ...step.api_usage || []];
|
|
380
|
+
return stepKws.some((k) => k === cm.keyword);
|
|
381
|
+
}).map((cm) => cm.concept_code);
|
|
382
|
+
const existing = step.concept_codes || [];
|
|
383
|
+
step.concept_codes = [.../* @__PURE__ */ new Set([...existing, ...stepConcepts])];
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
log("STEP_7", "Resolving keywords to Master Tree concepts...");
|
|
387
|
+
const llmKeywords = [];
|
|
388
|
+
const seenKw = /* @__PURE__ */ new Set();
|
|
389
|
+
for (const feature of verifiedGraph.features || []) {
|
|
390
|
+
for (const step of feature.steps || []) {
|
|
391
|
+
for (const kw of [...step.keywords || [], ...step.api_usage || []]) {
|
|
392
|
+
if (!seenKw.has(kw.toLowerCase())) {
|
|
393
|
+
seenKw.add(kw.toLowerCase());
|
|
394
|
+
llmKeywords.push({ keyword: kw, source: "llm_step", platform: "app", weight: 1, context: "LLM-generated step keyword" });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const astKwMap = new Map(astKeywords.map((k) => [k.keyword.toLowerCase(), k]));
|
|
400
|
+
const mergedKeywords = [];
|
|
401
|
+
const mergedSeen = /* @__PURE__ */ new Set();
|
|
402
|
+
for (const kw of [...astKeywords, ...llmKeywords]) {
|
|
403
|
+
const key = kw.keyword.toLowerCase();
|
|
404
|
+
if (!mergedSeen.has(key)) {
|
|
405
|
+
mergedSeen.add(key);
|
|
406
|
+
mergedKeywords.push(astKwMap.get(key) || kw);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const depthOverrides = /* @__PURE__ */ new Map();
|
|
410
|
+
for (const mappings of featureConcepts.values()) {
|
|
411
|
+
for (const cm of mappings) {
|
|
412
|
+
if (!depthOverrides.has(cm.keyword)) {
|
|
413
|
+
depthOverrides.set(cm.keyword, cm.depth);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const resolvedConcepts = await resolveConcepts({
|
|
418
|
+
keywords: mergedKeywords,
|
|
419
|
+
goal,
|
|
420
|
+
embeddings,
|
|
421
|
+
depthOverrides,
|
|
422
|
+
llmConfig
|
|
423
|
+
});
|
|
424
|
+
log("STEP_7", `Resolved: ${resolvedConcepts.summary.resolved_count}, Proposed: ${resolvedConcepts.summary.proposed_count} (${resolvedConcepts.summary.search_mode})`);
|
|
425
|
+
log("STEP_8", "Assembling roadmap with keyword tracking...");
|
|
426
|
+
const roadmap = assembleRoadmap({
|
|
427
|
+
projectGraph: verifiedGraph,
|
|
428
|
+
featureConcepts,
|
|
429
|
+
sessionMinutes: 90,
|
|
430
|
+
overheadFactor: 1.15,
|
|
431
|
+
maxMilestoneMinutes: 120
|
|
432
|
+
});
|
|
433
|
+
log("STEP_8", `Roadmap assembled: ${roadmap.phases.length} phases, ${roadmap.phases.reduce((s, p) => s + p.milestones.length, 0)} milestones`);
|
|
434
|
+
return {
|
|
435
|
+
projectGraph: verifiedGraph,
|
|
436
|
+
resolvedConcepts,
|
|
437
|
+
roadmap,
|
|
438
|
+
hallucinations,
|
|
439
|
+
keywords: astKeywords,
|
|
440
|
+
featureConcepts
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export { runProjectGraphPipeline };
|
|
445
|
+
//# sourceMappingURL=chunk-GEUDHZE7.mjs.map
|
|
446
|
+
//# sourceMappingURL=chunk-GEUDHZE7.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/extractors/fileSelector.ts","../src/utils/fileUtils.ts","../src/pipeline/projectGraphPipeline.ts"],"names":["relative"],"mappings":"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","file":"chunk-GEUDHZE7.mjs","sourcesContent":["/**\n * LLM-based File Selector — sends file tree to LLM, asks it to pick\n * which files are most relevant to the project goal.\n *\n * This mirrors knowledge-tree's approach:\n * 1. git_tracker generates project_structure.txt (file tree)\n * 2. Agent/LLM reads tree → selects important files\n * 3. Only selected files are read for analysis\n */\n\nimport { createLlmClient, llmChatJson, type LlmClientConfig } from '../utils/llmClient';\n\nexport interface FileSelectionResult {\n selected: string[];\n reasoning: string;\n}\n\n/**\n * Ask LLM to select which files from the file tree are relevant to the goal.\n * Input: file tree (names only, very small) + goal\n * Output: list of file paths to analyze\n */\nexport async function selectFilesWithLlm(\n filePaths: string[],\n goal: string,\n techStack: string,\n llmConfig?: LlmClientConfig,\n): Promise<FileSelectionResult> {\n // If very few files, no need to filter\n if (filePaths.length <= 15) {\n return { selected: filePaths, reasoning: 'Too few files to filter' };\n }\n\n const client = createLlmClient(llmConfig);\n\n // Build file tree for LLM (just names, grouped by directory)\n const treeLines = buildFileTree(filePaths);\n\n const systemPrompt = (\n 'You are a software architect analyzing a project repository.\\n'\n + 'Given a file tree and a project goal, select which files are MOST IMPORTANT '\n + 'to read for understanding the project architecture and implementation.\\n\\n'\n + 'RULES:\\n'\n + '1. Prioritize source code files (Swift, TS, Python, C++) over config/docs\\n'\n + '2. Include entry points (App.swift, main.py, index.ts)\\n'\n + '3. Include core domain files (models, services, views, controllers)\\n'\n + '4. EXCLUDE test files, mock files, generated files\\n'\n + '5. EXCLUDE config files (package.json, Podfile, etc.) unless they reveal architecture\\n'\n + '6. Include files that directly implement the features mentioned in the goal\\n'\n + '7. Return 20-40 files maximum (fewer is better if the project is small)\\n\\n'\n + 'Return JSON: {\"selected\": [\"path/to/file1\", \"path/to/file2\"], \"reasoning\": \"brief explanation\"}'\n );\n\n const userPrompt = (\n `Project goal: ${goal}\\n`\n + `Tech stack: ${techStack}\\n\\n`\n + `FILE TREE (${filePaths.length} files):\\n${treeLines}\\n\\n`\n + 'Select the most important files to analyze. Return JSON.'\n );\n\n try {\n const result = await llmChatJson(client, systemPrompt, userPrompt, {\n temperature: 0.1,\n maxTokens: 4096,\n });\n\n const selected = Array.isArray(result.selected) ? result.selected as string[] : [];\n const reasoning = String(result.reasoning || '');\n\n // Validate: only keep files that exist in the original list\n const pathSet = new Set(filePaths);\n const validSelected = selected.filter(p => pathSet.has(p));\n\n if (validSelected.length === 0) {\n // LLM returned nothing valid — fallback to all files\n return { selected: filePaths, reasoning: 'LLM selection empty, using all files' };\n }\n\n return { selected: validSelected, reasoning };\n } catch (err) {\n console.warn(`[WARN] LLM file selection failed (${err}), using all files`);\n return { selected: filePaths, reasoning: `LLM failed: ${err}` };\n }\n}\n\n/**\n * Build a compact file tree string for LLM consumption.\n * Groups files by top-level directory, shows only filenames.\n */\nfunction buildFileTree(filePaths: string[]): string {\n const byDir = new Map<string, string[]>();\n\n for (const fp of filePaths) {\n const parts = fp.split('/');\n const dir = parts.length > 1 ? parts.slice(0, -1).join('/') : '(root)';\n const file = parts[parts.length - 1];\n if (!byDir.has(dir)) byDir.set(dir, []);\n byDir.get(dir)!.push(file);\n }\n\n const lines: string[] = [];\n for (const [dir, files] of byDir) {\n lines.push(`${dir}/`);\n for (const f of files.sort()) {\n lines.push(` ${f}`);\n }\n }\n\n return lines.join('\\n');\n}\n","/**\n * File I/O utilities for domain-kit pipeline.\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'node:fs';\nimport { join, extname, relative, resolve } from 'node:path';\nimport { execSync } from 'node:child_process';\n\n/**\n * Read JSON file safely, returns null if not found or invalid.\n */\nexport function readJsonFile<T = unknown>(filePath: string): T | null {\n try {\n if (!existsSync(filePath)) return null;\n const content = readFileSync(filePath, 'utf-8');\n return JSON.parse(content) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Write JSON file, creating parent directories if needed.\n */\nexport function writeJsonFile(filePath: string, data: unknown): void {\n mkdirSync(join(filePath, '..'), { recursive: true });\n writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\n}\n\n/**\n * Read text file safely, returns null if not found.\n */\nexport function readTextFile(filePath: string): string | null {\n try {\n if (!existsSync(filePath)) return null;\n return readFileSync(filePath, 'utf-8');\n } catch {\n return null;\n }\n}\n\n/**\n * Collect source files from a directory, filtering by allowed extensions.\n */\nexport function collectSourceFiles(\n dir: string,\n options?: {\n allowedExtensions?: string[];\n skipDirs?: string[];\n maxFiles?: number;\n },\n): string[] {\n const allowedExts = options?.allowedExtensions || ['.swift', '.ts', '.tsx', '.js', '.jsx', '.py', '.ino', '.cpp', '.c', '.h', '.hpp'];\n const maxFiles = options?.maxFiles || 70;\n\n // Try git ls-files first — respects .gitignore, handles submodules, symlinks\n const gitFiles = collectViaGit(dir, allowedExts, maxFiles);\n if (gitFiles.length > 0) return gitFiles;\n\n // Fallback: filesystem walk (non-git directories)\n return collectViaFilesystem(dir, allowedExts, maxFiles, options?.skipDirs);\n}\n\n/**\n * Collect source files using `git ls-files`.\n * Respects .gitignore, handles symlinks, much faster than walk.\n */\nfunction collectViaGit(dir: string, allowedExts: string[], maxFiles: number): string[] {\n try {\n const raw = execSync('git ls-files --cached --others --exclude-standard', {\n cwd: dir,\n encoding: 'utf-8',\n timeout: 5000,\n maxBuffer: 1024 * 1024,\n });\n const extSet = new Set(allowedExts.map(e => e.toLowerCase()));\n return raw\n .split('\\n')\n .filter(line => line.length > 0)\n .filter(line => extSet.has(extname(line).toLowerCase()))\n .slice(0, maxFiles)\n .map(rel => join(dir, rel));\n } catch {\n // Not a git repo or git not available — fall through to filesystem walk\n return [];\n }\n}\n\n/**\n * Collect source files using recursive directory walk (non-git fallback).\n */\nfunction collectViaFilesystem(\n dir: string,\n allowedExts: string[],\n maxFiles: number,\n skipDirs?: string[],\n): string[] {\n const skipSet = new Set(skipDirs || [\n 'node_modules', '.build', 'Pods', '.git', '.venv', 'venv',\n '__pycache__', 'build', 'dist', '.idea', '.vscode',\n 'test', 'tests', 'demo', 'sample', 'example', 'playground',\n ]);\n const extSet = new Set(allowedExts.map(e => e.toLowerCase()));\n const result: string[] = [];\n\n function walk(currentDir: string) {\n if (result.length >= maxFiles) return;\n let entries;\n try {\n entries = readdirSync(currentDir, { withFileTypes: true });\n } catch {\n return; // permission error\n }\n for (const entry of entries) {\n if (result.length >= maxFiles) return;\n const fullPath = join(currentDir, entry.name);\n if (entry.isDirectory()) {\n if (!skipSet.has(entry.name)) {\n walk(fullPath);\n }\n } else if (entry.isFile()) {\n const ext = extname(entry.name).toLowerCase();\n if (extSet.has(ext)) {\n result.push(fullPath);\n }\n }\n }\n }\n\n walk(dir);\n return result;\n}\n\n/**\n * Collect file contents map (relative path → content).\n */\nexport function collectFileContents(\n dir: string,\n options?: {\n allowedExtensions?: string[];\n skipDirs?: string[];\n maxFiles?: number;\n maxChars?: number;\n },\n): { paths: string[]; contents: Map<string, string> } {\n const maxChars = options?.maxChars || 500000;\n const files = collectSourceFiles(dir, options);\n const contents = new Map<string, string>();\n let totalChars = 0;\n\n for (const filePath of files) {\n const relPath = relative(dir, filePath);\n try {\n const content = readFileSync(filePath, 'utf-8');\n if (totalChars + content.length > maxChars) {\n const remaining = maxChars - totalChars;\n if (remaining > 100) {\n contents.set(relPath, content.slice(0, remaining));\n totalChars += remaining;\n }\n break;\n }\n contents.set(relPath, content);\n totalChars += content.length;\n } catch {\n // Skip unreadable files\n }\n }\n\n return { paths: files.map(f => relative(dir, f)), contents };\n}\n\n/**\n * Read contents of SPECIFIC files (by relative path) — no directory scan.\n * Used for 2-pass approach: scan tree first, then read only selected files.\n */\nexport function collectSpecificFileContents(\n dir: string,\n relativePaths: string[],\n maxChars: number = 500000,\n): Map<string, string> {\n const contents = new Map<string, string>();\n let totalChars = 0;\n\n for (const relPath of relativePaths) {\n const fullPath = join(dir, relPath);\n try {\n const content = readFileSync(fullPath, 'utf-8');\n if (totalChars + content.length > maxChars) {\n const remaining = maxChars - totalChars;\n if (remaining > 100) {\n contents.set(relPath, content.slice(0, remaining));\n totalChars += remaining;\n }\n break;\n }\n contents.set(relPath, content);\n totalChars += content.length;\n } catch {\n // Skip unreadable files\n }\n }\n\n return contents;\n}\n\n/**\n * Detect primary language from file extensions.\n */\nexport function detectPrimaryLanguage(filePaths: string[]): string {\n const counts = new Map<string, number>();\n for (const fp of filePaths) {\n const ext = extname(fp).toLowerCase();\n let lang = 'unknown';\n if (ext === '.swift') lang = 'Swift';\n else if (ext === '.ts' || ext === '.tsx') lang = 'TypeScript';\n else if (ext === '.js' || ext === '.jsx') lang = 'JavaScript';\n else if (ext === '.py') lang = 'Python';\n else if (ext === '.ino' || ext === '.cpp' || ext === '.c' || ext === '.h') lang = 'C++';\n else if (ext === '.kt' || ext === '.kts') lang = 'Kotlin';\n counts.set(lang, (counts.get(lang) || 0) + 1);\n }\n\n let maxCount = 0;\n let primary = 'unknown';\n for (const [lang, count] of counts) {\n if (count > maxCount) {\n maxCount = count;\n primary = lang;\n }\n }\n return primary;\n}\n\n/**\n * Read README.md and extract project description.\n */\nexport function readReadmeDescription(repoDir: string): string | null {\n const readmePath = join(repoDir, 'README.md');\n if (!existsSync(readmePath)) return null;\n try {\n const content = readFileSync(readmePath, 'utf-8');\n const lines = content.split('\\n').map(l => l.trim()).filter(Boolean);\n // Skip patterns: badges, images, table of contents, HTML tags\n const skipPatterns = [/^\\[!/, /^<img/, /^<a /, /^#+\\s+Table of/i, /^---$/];\n let foundTitle = false;\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.startsWith('#') && !line.startsWith('##')) {\n foundTitle = true;\n continue;\n }\n if (!foundTitle) continue;\n // Skip empty lines, badges, images, TOC, horizontal rules\n if (skipPatterns.some(p => p.test(line))) continue;\n if (line.startsWith('#')) return null; // hit next heading — no description found\n if (line.startsWith('```')) return null; // code block\n if (line.startsWith('|')) return null; // table\n if (line.length > 15) return line;\n }\n return null;\n } catch {\n return null;\n }\n}\n","/**\n * Project Graph Pipeline — full orchestrator.\n *\n * Ported from knowledge-tree/generate_roadmap_v3.py PipelineOrchestrator\n * Coordinates: AST analysis → keyword extraction → LLM graph generation →\n * verification → concept escalation → concept resolution → assembly.\n */\n\nimport type { ProjectGraph, Feature } from '../schemas/projectGraphSchema';\nimport { parseSwiftFile, mergeSwiftResults, type SwiftParseResult } from '../parsers/swift';\nimport { parseTsFile, mergeTsResults, type TsParseResult } from '../parsers/typescript';\nimport { parsePythonFile, mergePythonResults, type PythonParseResult } from '../parsers/python';\nimport { parseCppFile, mergeCppResults, type CppParseResult } from '../parsers/cpp';\nimport { extractKeywords, type Keyword, type SourceContext } from '../extractors/keywordExtractor';\nimport { extractScaffold } from '../graph/scaffoldExtractor';\nimport { extractProjectOverview } from '../graph/overviewExtractor';\nimport { extractFeatureSteps, extractFeatureStepsBatched } from '../graph/stepExtractor';\nimport { verifyProjectGraph, type Hallucination } from '../graph/graphVerifier';\nimport { escalateAndMapConcepts, type ConceptMapping } from '../graph/conceptEscalator';\nimport { resolveConcepts, type ResolveConceptsResult } from '../concepts/conceptResolver';\nimport { assembleRoadmap, type AssembledRoadmap } from '../assembly/roadmapAssembler';\nimport { selectFilesWithLlm } from '../extractors/fileSelector';\nimport { collectSourceFiles, collectSpecificFileContents, detectPrimaryLanguage, readReadmeDescription } from '../utils/fileUtils';\nimport { relative } from 'node:path';\nimport { createLlmClient, type LlmClientConfig } from '../utils/llmClient';\n\nexport interface PipelineOptions {\n repoDir: string;\n goal: string;\n techStack: string;\n llmConfig?: LlmClientConfig;\n embeddings?: Record<string, { embedding?: number[]; name?: string; description?: string; keywords?: string }>;\n onProgress?: (step: string, message: string) => void;\n}\n\nexport interface PipelineResult {\n projectGraph: ProjectGraph;\n resolvedConcepts: ResolveConceptsResult;\n roadmap: AssembledRoadmap;\n hallucinations: Hallucination[];\n keywords: Keyword[];\n featureConcepts: Map<string, ConceptMapping[]>;\n}\n\n/**\n * Run the full project graph generation pipeline.\n */\nexport async function runProjectGraphPipeline(\n options: PipelineOptions,\n): Promise<PipelineResult> {\n const { repoDir, goal, techStack, llmConfig, embeddings, onProgress } = options;\n\n const log = (step: string, msg: string) => {\n console.log(`[${step}] ${msg}`);\n onProgress?.(step, msg);\n };\n\n // ─── STEP 1A: File Tree Scan (names only — no content reading) ─────────\n log('STEP_1', 'Scanning file tree (names only)...');\n\n const allFilePaths = collectSourceFiles(repoDir);\n const allRelPaths = allFilePaths.map(fp => relative(repoDir, fp));\n log('STEP_1', `Found ${allRelPaths.length} source files in repository`);\n\n // ─── STEP 1B: LLM File Selection (sends tiny file tree to LLM) ────────\n log('STEP_1', 'Asking LLM to select relevant files from file tree...');\n\n const { selected: selectedPaths, reasoning } = await selectFilesWithLlm(\n allRelPaths, goal, techStack, llmConfig,\n );\n log('STEP_1', `LLM selected ${selectedPaths.length}/${allRelPaths.length} files: ${reasoning.slice(0, 100)}`);\n\n // ─── STEP 1C: Read content only for LLM-selected files ─────────────────\n log('STEP_1', 'Reading content for selected files...');\n\n const fileContentsMap = collectSpecificFileContents(repoDir, selectedPaths);\n const filePaths = selectedPaths;\n const primaryLang = detectPrimaryLanguage(filePaths);\n log('STEP_1', `Primary language: ${primaryLang}, ${filePaths.length} files loaded (${fileContentsMap.size} with content)`);\n\n // Parse source files\n log('STEP_1', 'Parsing source files with AST-level analysis...');\n\n const allParseResults: SourceContext[] = [];\n for (const [relPath, content] of fileContentsMap) {\n const fullPath = relPath;\n if (fullPath.endsWith('.swift')) {\n allParseResults.push(parseSwiftFile(fullPath, content));\n } else if (fullPath.endsWith('.ts') || fullPath.endsWith('.tsx') || fullPath.endsWith('.js') || fullPath.endsWith('.jsx')) {\n allParseResults.push(parseTsFile(fullPath, content));\n } else if (fullPath.endsWith('.py')) {\n allParseResults.push(parsePythonFile(fullPath, content));\n } else if (fullPath.endsWith('.ino') || fullPath.endsWith('.cpp') || fullPath.endsWith('.c') || fullPath.endsWith('.h')) {\n allParseResults.push(parseCppFile(fullPath, content));\n }\n }\n\n // Merge parse results by language\n let mergedContext: SourceContext;\n if (primaryLang === 'Swift') {\n mergedContext = mergeSwiftResults(allParseResults.filter((r): r is SwiftParseResult => 'property_wrappers' in r));\n } else if (primaryLang === 'TypeScript' || primaryLang === 'JavaScript') {\n mergedContext = mergeTsResults(allParseResults.filter((r): r is TsParseResult => !('docstrings' in r) && !('property_wrappers' in r) && !('frameworks_used' in r)));\n } else if (primaryLang === 'Python') {\n mergedContext = mergePythonResults(allParseResults.filter((r): r is PythonParseResult => 'docstrings' in r));\n } else {\n mergedContext = mergeCppResults(allParseResults.filter((r): r is CppParseResult => 'error_handling' in r && !('docstrings' in r) && !('property_wrappers' in r)));\n }\n\n // ─── STEP 2: Keyword Extraction ──────────────────────────────────────────\n log('STEP_2', 'Extracting keywords from AST analysis...');\n\n const astKeywords = extractKeywords(mergedContext);\n log('STEP_2', `Extracted ${astKeywords.length} keywords`);\n\n // ─── STEP 3: Build SDK API Index (simplified — use AST keywords as proxy) ──\n log('STEP_3', 'Building SDK API index...');\n\n const sdkApiIndex = {\n sdk_apis: astKeywords.map(kw => ({\n name: kw.keyword,\n kind: kw.source,\n used_in_demo: true,\n })),\n };\n\n // ─── STEP 4: LLM Project Graph Generation ────────────────────────────────\n log('STEP_4', 'Generating project graph via LLM (C0: Scaffold, C1: Overview, C2: Steps)...');\n\n // C0: Scaffold (F0) — uses file list (names only, not content)\n log('STEP_4_C0', 'Extracting FOUNDATION & SETUP feature (F0)...');\n const scaffoldFeature = await extractScaffold({\n goal,\n techStack,\n fileList: filePaths,\n llmConfig,\n });\n\n // C1: Overview (features meta) — uses already-filtered fileContentsMap\n log('STEP_4_C1', 'Extracting project overview (features meta)...');\n const overview = await extractProjectOverview({\n goal,\n techStack,\n sdkApiIndex,\n fileContentsMap,\n astKeywords,\n llmConfig,\n });\n\n // Build PBL step (Design Thinking)\n const product = overview.product as Record<string, unknown> | undefined;\n const pblStep: Feature['steps'] extends (infer T)[] ? T : never = {\n id: 'F0-S1',\n sequence: 1,\n name: 'Frame the problem & understand user needs (Design Thinking)',\n description: 'BEFORE CODING — frame the problem and understand what the user needs',\n files: [],\n api_usage: [],\n keywords: ['Design Thinking', 'User Needs', 'Problem Statement', 'User Journey', 'PBL'],\n completion_level: 'base',\n platform: 'app',\n intent: 'Understand user needs and frame problem before writing code',\n outcome: { user_visible: 'Defined problem statement', technical: 'Problem frame context' },\n acceptance: ['Problem statement is clear', 'Primary users identified'],\n requirement_ids: [],\n concept_codes: [],\n effort: { estimated_minutes: 15, complexity: 'low' },\n };\n\n // Merge scaffold F0 with PBL step\n let featuresFull: Feature[] = [];\n if (scaffoldFeature) {\n const scaffoldSteps = [pblStep, ...(scaffoldFeature.steps || [])].map((s, i) => ({\n ...s,\n id: `F0-S${i + 1}`,\n sequence: i + 1,\n }));\n featuresFull.push({\n ...scaffoldFeature,\n id: 'F0',\n name: 'FOUNDATION & SETUP',\n steps: scaffoldSteps,\n });\n } else {\n featuresFull.push({\n id: 'F0',\n name: 'FOUNDATION & SETUP',\n description: 'Frame the problem, get familiar with the tools',\n steps: [pblStep],\n } as Feature);\n }\n\n // Add features from overview\n const overviewFeatures = (product?.features || []) as Feature[];\n featuresFull = [...featuresFull, ...overviewFeatures];\n\n // C2: Extract steps per feature\n log('STEP_4_C2', `Extracting steps for ${overviewFeatures.length} features...`);\n\n const nonF0Features = overviewFeatures.filter(f => f.id !== 'F0');\n if (nonF0Features.length > 0) {\n try {\n const batchedSteps = await extractFeatureStepsBatched(\n goal, techStack, sdkApiIndex, fileContentsMap, nonF0Features, llmConfig,\n );\n for (const feature of nonF0Features) {\n const steps = batchedSteps.get(feature.id || '');\n if (steps) feature.steps = steps;\n }\n } catch (err) {\n log('STEP_4_C2', `Batch extraction failed (${err}), falling back to per-feature...`);\n for (const feature of nonF0Features) {\n try {\n const steps = await extractFeatureSteps({\n goal, techStack, sdkApiIndex, fileContentsMap, feature, llmConfig,\n });\n feature.steps = steps;\n } catch (e) {\n log('STEP_4_C2', `Failed for ${feature.id}: ${e}`);\n }\n }\n }\n }\n\n // Build project graph\n const projectGraph: ProjectGraph = {\n schema_version: 3,\n project: {\n id: (overview.project as Record<string, unknown>)?.name?.toString().toLowerCase().replace(/\\s+/g, '-') || 'project',\n name: (overview.project as Record<string, unknown>)?.name?.toString() || 'Project',\n purpose: goal,\n version: '0.1.0',\n project_type: 'app',\n platform: ((overview.project as Record<string, unknown>)?.platforms as string[]) || ['ios'],\n tech_stack: techStack.split(',').reduce((acc, t) => {\n const key = t.trim().toLowerCase();\n if (key) acc[key] = [t.trim()];\n return acc;\n }, {} as Record<string, string[]>),\n architecture: (overview.architecture as Record<string, unknown>)?.layers as string[] || [],\n },\n product: {\n goals: (product?.goals as Array<{ id: string; description: string }>) || [],\n users: (product?.users as Array<{ id: string; goals: string[] }>) || [],\n journeys: (product?.user_journeys as Array<{ id: string; name: string; requirement_ids: string[]; steps: string[]; feature_ids: string[] }>) || [],\n requirements: [],\n development_stages: (product?.development_stages as Array<{ stage: string; product_state: string; need: string[]; learn: string[]; validation: string; cross_feature_value: string; temporary_approach: string }>) || [],\n purpose: String(product?.purpose || ''),\n problem_statement: String(product?.problem_statement || ''),\n primary_users: (product?.primary_users as string[]) || [],\n },\n features: featuresFull,\n capabilities: [],\n implementation: {\n tasks: [],\n },\n missing_gaps: [],\n tech_debt: [],\n };\n\n // ─── STEP 5: Verification ────────────────────────────────────────────────\n log('STEP_5', 'Verifying project graph against source code...');\n\n const { graph: verifiedGraph, hallucinations } = verifyProjectGraph(projectGraph, {\n repoDir,\n sdkApiIndex,\n fileContentsMap,\n });\n log('STEP_5', `Verification complete: ${hallucinations.length} hallucinations found`);\n\n // ─── STEP 6: Concept Escalation ──────────────────────────────────────────\n log('STEP_6', 'Escalating keywords to neutral concepts...');\n\n const featureConcepts = await escalateAndMapConcepts({\n projectGraph: verifiedGraph,\n fileContentsMap,\n llmConfig,\n });\n log('STEP_6', `Concept mapping complete: ${featureConcepts.size} features mapped`);\n\n // Propagate concept_codes back into verified graph steps\n for (const feature of verifiedGraph.features || []) {\n const fid = feature.id || '';\n const fConcepts = featureConcepts.get(fid) || [];\n for (const step of feature.steps || []) {\n const stepConcepts = fConcepts\n .filter(cm => {\n // Match concept to step: check if step contains the keyword\n const stepKws = [...(step.keywords || []), ...(step.api_usage || [])];\n return stepKws.some(k => k === cm.keyword);\n })\n .map(cm => cm.concept_code);\n // Merge with any existing concept_codes from LLM\n const existing = step.concept_codes || [];\n step.concept_codes = [...new Set([...existing, ...stepConcepts])];\n }\n }\n\n // ─── STEP 7: Concept Resolution ──────────────────────────────────────────\n log('STEP_7', 'Resolving keywords to Master Tree concepts...');\n\n // Collect LLM-generated keywords from verified graph (not just AST)\n const llmKeywords: Keyword[] = [];\n const seenKw = new Set<string>();\n for (const feature of verifiedGraph.features || []) {\n for (const step of feature.steps || []) {\n for (const kw of [...(step.keywords || []), ...(step.api_usage || [])]) {\n if (!seenKw.has(kw.toLowerCase())) {\n seenKw.add(kw.toLowerCase());\n llmKeywords.push({ keyword: kw, source: 'llm_step', platform: 'app', weight: 1.0, context: 'LLM-generated step keyword' });\n }\n }\n }\n }\n // Merge with AST keywords (AST takes precedence for source/weight)\n const astKwMap = new Map(astKeywords.map(k => [k.keyword.toLowerCase(), k]));\n const mergedKeywords: Keyword[] = [];\n const mergedSeen = new Set<string>();\n for (const kw of [...astKeywords, ...llmKeywords]) {\n const key = kw.keyword.toLowerCase();\n if (!mergedSeen.has(key)) {\n mergedSeen.add(key);\n mergedKeywords.push(astKwMap.get(key) || kw);\n }\n }\n\n // Build depth overrides from concept escalation\n const depthOverrides = new Map<string, 'ulo' | 'cio' | 'sio'>();\n for (const mappings of featureConcepts.values()) {\n for (const cm of mappings) {\n if (!depthOverrides.has(cm.keyword)) {\n depthOverrides.set(cm.keyword, cm.depth);\n }\n }\n }\n\n const resolvedConcepts = await resolveConcepts({\n keywords: mergedKeywords,\n goal,\n embeddings,\n depthOverrides,\n llmConfig,\n });\n log('STEP_7', `Resolved: ${resolvedConcepts.summary.resolved_count}, Proposed: ${resolvedConcepts.summary.proposed_count} (${resolvedConcepts.summary.search_mode})`);\n\n // ─── STEP 8: Assembly ────────────────────────────────────────────────────\n log('STEP_8', 'Assembling roadmap with keyword tracking...');\n\n const roadmap = assembleRoadmap({\n projectGraph: verifiedGraph,\n featureConcepts,\n sessionMinutes: 90,\n overheadFactor: 1.15,\n maxMilestoneMinutes: 120,\n });\n log('STEP_8', `Roadmap assembled: ${roadmap.phases.length} phases, ${roadmap.phases.reduce((s, p) => s + p.milestones.length, 0)} milestones`);\n\n return {\n projectGraph: verifiedGraph,\n resolvedConcepts,\n roadmap,\n hallucinations,\n keywords: astKeywords,\n featureConcepts,\n };\n}\n"]}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
// src/schemas/curriculumFeedSchema.ts
|
|
4
|
+
var FeedKeywordsSchema = z.object({
|
|
5
|
+
all: z.array(z.string()).default([]),
|
|
6
|
+
new: z.array(z.string()).default([]),
|
|
7
|
+
prerequisite: z.array(z.string()).default([])
|
|
8
|
+
});
|
|
9
|
+
var FeedScaffoldCandidateSchema = z.object({
|
|
10
|
+
item: z.string(),
|
|
11
|
+
reason: z.string().default(""),
|
|
12
|
+
minutes_saved: z.number().int().nonnegative().default(0),
|
|
13
|
+
suggest: z.enum(["provide_full", "provide_partial", "provide_reference"]).default("provide_full")
|
|
14
|
+
});
|
|
15
|
+
var FeedNodeSchema = z.object({
|
|
16
|
+
id: z.string().min(1),
|
|
17
|
+
kind: z.enum(["skill", "concept", "product_step"]),
|
|
18
|
+
name: z.string().min(1),
|
|
19
|
+
concept_codes: z.array(z.string()).default([]),
|
|
20
|
+
keywords: FeedKeywordsSchema,
|
|
21
|
+
estimated_minutes: z.number().int().nonnegative(),
|
|
22
|
+
bloom_hint: z.enum(["Remember", "Understand", "Apply", "Analyze", "Evaluate", "Create"]).nullable().default(null),
|
|
23
|
+
depth_hint: z.enum(["ulo", "cio", "sio"]).nullable().default(null),
|
|
24
|
+
completion_level: z.enum(["mvp", "standard", "stretch"]).default("standard"),
|
|
25
|
+
scaffolding: z.object({
|
|
26
|
+
scaffold_candidates: z.array(FeedScaffoldCandidateSchema).default([]),
|
|
27
|
+
core_value_minutes: z.number().int().nonnegative().default(0)
|
|
28
|
+
}).default({ scaffold_candidates: [], core_value_minutes: 0 }),
|
|
29
|
+
references: z.array(z.object({ file: z.string().min(1), evidence: z.string().default("") })).default([]),
|
|
30
|
+
feature_id: z.string().nullable().default(null),
|
|
31
|
+
concept_ref: z.string().nullable().default(null),
|
|
32
|
+
// Progressive-completion bookkeeping (feed v2, additive):
|
|
33
|
+
// phase_id = development phase that introduces/uses this node;
|
|
34
|
+
// introduce_aspect = how the phase teaches a concept node (first intro vs advanced revisit).
|
|
35
|
+
phase_id: z.string().default(""),
|
|
36
|
+
introduce_aspect: z.enum(["new", "advanced"]).nullable().default(null)
|
|
37
|
+
});
|
|
38
|
+
var FeedEdgeSchema = z.object({
|
|
39
|
+
from: z.string().min(1),
|
|
40
|
+
to: z.string().min(1),
|
|
41
|
+
kind: z.enum(["knowledge", "task"]),
|
|
42
|
+
reason: z.string().default("")
|
|
43
|
+
});
|
|
44
|
+
var FeedGroupingSchema = z.object({
|
|
45
|
+
id: z.string().min(1),
|
|
46
|
+
name: z.string().min(1),
|
|
47
|
+
node_ids: z.array(z.string()).min(1),
|
|
48
|
+
rationale: z.string().default("")
|
|
49
|
+
});
|
|
50
|
+
var FeedPhaseSchema = z.object({
|
|
51
|
+
id: z.string().min(1),
|
|
52
|
+
order: z.number().int().positive(),
|
|
53
|
+
name: z.string().min(1),
|
|
54
|
+
product_completion: z.string().min(1),
|
|
55
|
+
node_ids: z.array(z.string()).min(1)
|
|
56
|
+
});
|
|
57
|
+
var CurriculumFeedSchema = z.object({
|
|
58
|
+
schema_version: z.literal(2),
|
|
59
|
+
source: z.object({
|
|
60
|
+
graph_type: z.enum(["project_graph", "knowledge_graph", "hybrid_graph"]),
|
|
61
|
+
warnings: z.array(z.string()).default([]),
|
|
62
|
+
hallucination_count: z.number().int().nonnegative().default(0)
|
|
63
|
+
}),
|
|
64
|
+
learning_nodes: z.array(FeedNodeSchema).min(1, "curriculum feed must contain at least one learning node"),
|
|
65
|
+
dependency_edges: z.array(FeedEdgeSchema).default([]),
|
|
66
|
+
suggested_groupings: z.array(FeedGroupingSchema).default([]),
|
|
67
|
+
// Progressive completion phases (cross-feature). When non-empty, these — not
|
|
68
|
+
// suggested_groupings — define the teaching sequence and unit structure.
|
|
69
|
+
phases: z.array(FeedPhaseSchema).default([])
|
|
70
|
+
});
|
|
71
|
+
function validateCurriculumFeed(feed) {
|
|
72
|
+
const parsed = CurriculumFeedSchema.parse(feed);
|
|
73
|
+
const ids = new Set(parsed.learning_nodes.map((n) => n.id));
|
|
74
|
+
if (ids.size !== parsed.learning_nodes.length) {
|
|
75
|
+
throw new Error("curriculum feed contains duplicate node ids");
|
|
76
|
+
}
|
|
77
|
+
for (const e of parsed.dependency_edges) {
|
|
78
|
+
if (!ids.has(e.from) || !ids.has(e.to)) {
|
|
79
|
+
throw new Error("curriculum feed edge " + e.from + " -> " + e.to + " references unknown node");
|
|
80
|
+
}
|
|
81
|
+
if (e.from === e.to) throw new Error("curriculum feed self-loop edge on " + e.from);
|
|
82
|
+
}
|
|
83
|
+
for (const g of parsed.suggested_groupings) {
|
|
84
|
+
for (const id of g.node_ids) {
|
|
85
|
+
if (!ids.has(id)) throw new Error("grouping " + g.id + " references unknown node " + id);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
for (const ph of parsed.phases) {
|
|
89
|
+
for (const id of ph.node_ids) {
|
|
90
|
+
if (!ids.has(id)) throw new Error("phase " + ph.id + " references unknown node " + id);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const phaseIds = parsed.phases.map((p) => p.id);
|
|
94
|
+
if (new Set(phaseIds).size !== phaseIds.length) throw new Error("curriculum feed contains duplicate phase ids");
|
|
95
|
+
return parsed;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export { CurriculumFeedSchema, FeedEdgeSchema, FeedGroupingSchema, FeedKeywordsSchema, FeedNodeSchema, FeedPhaseSchema, FeedScaffoldCandidateSchema, validateCurriculumFeed };
|
|
99
|
+
//# sourceMappingURL=chunk-HH4UX65C.mjs.map
|
|
100
|
+
//# sourceMappingURL=chunk-HH4UX65C.mjs.map
|