@chappibunny/repolens 0.4.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/CHANGELOG.md +219 -0
- package/README.md +899 -0
- package/RELEASE.md +52 -0
- package/bin/repolens.js +2 -0
- package/package.json +61 -0
- package/src/ai/document-plan.js +133 -0
- package/src/ai/generate-sections.js +271 -0
- package/src/ai/prompts.js +312 -0
- package/src/ai/provider.js +134 -0
- package/src/analyzers/context-builder.js +146 -0
- package/src/analyzers/domain-inference.js +127 -0
- package/src/analyzers/flow-inference.js +198 -0
- package/src/cli.js +271 -0
- package/src/core/config-schema.js +266 -0
- package/src/core/config.js +18 -0
- package/src/core/diff.js +45 -0
- package/src/core/scan.js +312 -0
- package/src/delivery/comment.js +139 -0
- package/src/docs/generate-doc-set.js +123 -0
- package/src/docs/write-doc-set.js +85 -0
- package/src/doctor.js +174 -0
- package/src/init.js +540 -0
- package/src/migrate.js +251 -0
- package/src/publishers/index.js +33 -0
- package/src/publishers/markdown.js +32 -0
- package/src/publishers/notion.js +325 -0
- package/src/publishers/publish.js +31 -0
- package/src/renderers/render.js +256 -0
- package/src/renderers/renderDiff.js +139 -0
- package/src/renderers/renderMap.js +224 -0
- package/src/utils/branch.js +93 -0
- package/src/utils/logger.js +26 -0
- package/src/utils/retry.js +55 -0
- package/src/utils/update-check.js +150 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
// Strict prompt templates for AI-generated documentation sections
|
|
2
|
+
|
|
3
|
+
export const SYSTEM_PROMPT = `You are a senior software architect and technical writer.
|
|
4
|
+
Your job is to turn structured repository analysis into clear documentation.
|
|
5
|
+
|
|
6
|
+
Rules:
|
|
7
|
+
- Use only the supplied context.
|
|
8
|
+
- Never invent files, modules, routes, APIs, or business capabilities.
|
|
9
|
+
- If context is insufficient, say so briefly and continue with what is known.
|
|
10
|
+
- Write for mixed audiences when requested.
|
|
11
|
+
- Prefer clear prose and short sections over bullet spam.
|
|
12
|
+
- Be concrete and practical.
|
|
13
|
+
- Output valid markdown only.
|
|
14
|
+
- Do not mention AI, LLMs, or that you are an assistant.
|
|
15
|
+
- No markdown tables unless specifically requested.
|
|
16
|
+
- Use simple formatting: headings, paragraphs, lists.
|
|
17
|
+
- Maximum 2 heading levels deep within sections.`;
|
|
18
|
+
|
|
19
|
+
export function createExecutiveSummaryPrompt(context) {
|
|
20
|
+
return `Write an executive summary for a mixed audience of technical and non-technical readers.
|
|
21
|
+
|
|
22
|
+
Use this context:
|
|
23
|
+
${JSON.stringify(context, null, 2)}
|
|
24
|
+
|
|
25
|
+
Requirements:
|
|
26
|
+
- Explain what the system appears to do based on the modules and routes.
|
|
27
|
+
- Explain the main system areas using the domain information.
|
|
28
|
+
- Explain the business capabilities implied by the codebase structure.
|
|
29
|
+
- Mention key external dependencies only if they are present in the context.
|
|
30
|
+
- Mention architectural or operational risks if they are strongly supported by the context.
|
|
31
|
+
- Do not mention file counts more than once.
|
|
32
|
+
- Maximum 500 words.
|
|
33
|
+
- Use this structure:
|
|
34
|
+
|
|
35
|
+
# Executive Summary
|
|
36
|
+
|
|
37
|
+
## What this system does
|
|
38
|
+
|
|
39
|
+
## Who it serves
|
|
40
|
+
|
|
41
|
+
## Core capabilities
|
|
42
|
+
|
|
43
|
+
## Main system areas
|
|
44
|
+
|
|
45
|
+
## Key dependencies
|
|
46
|
+
|
|
47
|
+
## Operational and architectural risks
|
|
48
|
+
|
|
49
|
+
## Recommended focus areas`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createSystemOverviewPrompt(context) {
|
|
53
|
+
return `Write a system overview for a mixed audience.
|
|
54
|
+
|
|
55
|
+
Use this context:
|
|
56
|
+
${JSON.stringify(context, null, 2)}
|
|
57
|
+
|
|
58
|
+
Requirements:
|
|
59
|
+
- Provide a concise, high-level orientation to the codebase.
|
|
60
|
+
- Technical enough for developers, readable enough for everyone else.
|
|
61
|
+
- Focus on what is observable from the structure.
|
|
62
|
+
- Maximum 400 words.
|
|
63
|
+
- Use this structure:
|
|
64
|
+
|
|
65
|
+
# System Overview
|
|
66
|
+
|
|
67
|
+
## Repository snapshot
|
|
68
|
+
|
|
69
|
+
## Main architectural layers
|
|
70
|
+
|
|
71
|
+
## Dominant domains
|
|
72
|
+
|
|
73
|
+
## Main technology patterns
|
|
74
|
+
|
|
75
|
+
## Where most logic lives
|
|
76
|
+
|
|
77
|
+
## Key observations`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function createBusinessDomainsPrompt(context) {
|
|
81
|
+
return `Write business domain documentation for a mixed audience, especially non-technical readers.
|
|
82
|
+
|
|
83
|
+
Use this context:
|
|
84
|
+
${JSON.stringify(context, null, 2)}
|
|
85
|
+
|
|
86
|
+
Requirements:
|
|
87
|
+
- Translate codebase structure into business language.
|
|
88
|
+
- For each domain in the context, explain:
|
|
89
|
+
- Business responsibility
|
|
90
|
+
- Main directories/modules
|
|
91
|
+
- Dependencies
|
|
92
|
+
- User-visible functionality (inferred from structure)
|
|
93
|
+
- Maximum 150 words per domain.
|
|
94
|
+
- Use this structure:
|
|
95
|
+
|
|
96
|
+
# Business Domains
|
|
97
|
+
|
|
98
|
+
## Domain: [Name]
|
|
99
|
+
|
|
100
|
+
[Business explanation]
|
|
101
|
+
|
|
102
|
+
Main modules:
|
|
103
|
+
- [module paths]
|
|
104
|
+
|
|
105
|
+
User-visible functionality:
|
|
106
|
+
[what users can do]
|
|
107
|
+
|
|
108
|
+
Dependencies:
|
|
109
|
+
[other domains this depends on]`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function createArchitectureOverviewPrompt(context) {
|
|
113
|
+
return `Write an architecture overview for engineers, architects, and technical PMs.
|
|
114
|
+
|
|
115
|
+
Use this context:
|
|
116
|
+
${JSON.stringify(context, null, 2)}
|
|
117
|
+
|
|
118
|
+
Requirements:
|
|
119
|
+
- Explain the layered architecture based on observable patterns.
|
|
120
|
+
- Describe flows across layers.
|
|
121
|
+
- Be specific about what is present in the context.
|
|
122
|
+
- Maximum 600 words.
|
|
123
|
+
- Use this structure:
|
|
124
|
+
|
|
125
|
+
# Architecture Overview
|
|
126
|
+
|
|
127
|
+
## Architecture style
|
|
128
|
+
|
|
129
|
+
## Application layer
|
|
130
|
+
|
|
131
|
+
## UI / component layer
|
|
132
|
+
|
|
133
|
+
## Domain / service layer
|
|
134
|
+
|
|
135
|
+
## Data / integration layer
|
|
136
|
+
|
|
137
|
+
## Cross-cutting concerns
|
|
138
|
+
|
|
139
|
+
## Dependency flow
|
|
140
|
+
|
|
141
|
+
## Architectural strengths
|
|
142
|
+
|
|
143
|
+
## Architectural weaknesses`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function createDataFlowsPrompt(flows, context) {
|
|
147
|
+
return `Write data flow documentation for a mixed audience.
|
|
148
|
+
|
|
149
|
+
Use this flow information:
|
|
150
|
+
${JSON.stringify(flows, null, 2)}
|
|
151
|
+
|
|
152
|
+
And this context:
|
|
153
|
+
${JSON.stringify(context, null, 2)}
|
|
154
|
+
|
|
155
|
+
Requirements:
|
|
156
|
+
- Explain major information flows in plain language.
|
|
157
|
+
- For each flow, describe the journey from user action to system response.
|
|
158
|
+
- Be concrete about which modules are involved.
|
|
159
|
+
- Maximum 200 words per flow.
|
|
160
|
+
- Use this structure:
|
|
161
|
+
|
|
162
|
+
# Data Flows
|
|
163
|
+
|
|
164
|
+
## [Flow Name]
|
|
165
|
+
|
|
166
|
+
[Plain language description of the flow]
|
|
167
|
+
|
|
168
|
+
Flow steps:
|
|
169
|
+
[numbered steps]
|
|
170
|
+
|
|
171
|
+
Involved modules:
|
|
172
|
+
- [module paths]
|
|
173
|
+
|
|
174
|
+
Critical dependencies:
|
|
175
|
+
[what this flow depends on]`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function createDeveloperOnboardingPrompt(context) {
|
|
179
|
+
return `Write developer onboarding documentation to help new engineers get productive quickly.
|
|
180
|
+
|
|
181
|
+
Use this context:
|
|
182
|
+
${JSON.stringify(context, null, 2)}
|
|
183
|
+
|
|
184
|
+
Requirements:
|
|
185
|
+
- Guide new developers through the codebase structure.
|
|
186
|
+
- Point out the most important folders and files.
|
|
187
|
+
- Explain core product flows.
|
|
188
|
+
- Highlight complexity hotspots.
|
|
189
|
+
- Be practical and actionable.
|
|
190
|
+
- Maximum 800 words.
|
|
191
|
+
- Use this structure:
|
|
192
|
+
|
|
193
|
+
# Developer Onboarding
|
|
194
|
+
|
|
195
|
+
## Start here
|
|
196
|
+
|
|
197
|
+
## Main folders
|
|
198
|
+
|
|
199
|
+
## Core product flows
|
|
200
|
+
|
|
201
|
+
## Important routes
|
|
202
|
+
|
|
203
|
+
## Important shared libraries
|
|
204
|
+
|
|
205
|
+
## Common change areas
|
|
206
|
+
|
|
207
|
+
## What to understand first
|
|
208
|
+
|
|
209
|
+
## Known complexity hotspots`;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function createModuleSummaryPrompt(module, context) {
|
|
213
|
+
return `Write a short module documentation section.
|
|
214
|
+
|
|
215
|
+
Module: ${module.key}
|
|
216
|
+
File count: ${module.fileCount}
|
|
217
|
+
Type: ${module.type}
|
|
218
|
+
Domain: ${module.domain}
|
|
219
|
+
|
|
220
|
+
Additional context:
|
|
221
|
+
${JSON.stringify(context, null, 2)}
|
|
222
|
+
|
|
223
|
+
Requirements:
|
|
224
|
+
- Explain the module's likely purpose.
|
|
225
|
+
- List concrete responsibilities.
|
|
226
|
+
- Mention important dependencies from context.
|
|
227
|
+
- Mention complexity or centrality if supported.
|
|
228
|
+
- No speculation beyond the provided context.
|
|
229
|
+
- Maximum 180 words.
|
|
230
|
+
- Use this structure:
|
|
231
|
+
|
|
232
|
+
## ${module.key}
|
|
233
|
+
|
|
234
|
+
Purpose:
|
|
235
|
+
[brief explanation]
|
|
236
|
+
|
|
237
|
+
Responsibilities:
|
|
238
|
+
- [concrete responsibilities]
|
|
239
|
+
|
|
240
|
+
Main dependencies:
|
|
241
|
+
[from context]
|
|
242
|
+
|
|
243
|
+
Complexity notes:
|
|
244
|
+
[if applicable]`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function createRouteSummaryPrompt(route, context) {
|
|
248
|
+
return `Write a route documentation section.
|
|
249
|
+
|
|
250
|
+
Route: ${route.path}
|
|
251
|
+
File: ${route.file}
|
|
252
|
+
Type: ${route.type}
|
|
253
|
+
|
|
254
|
+
Additional context:
|
|
255
|
+
${JSON.stringify(context, null, 2)}
|
|
256
|
+
|
|
257
|
+
Requirements:
|
|
258
|
+
- Explain the user purpose of this route.
|
|
259
|
+
- Explain the technical role.
|
|
260
|
+
- Mention main dependencies.
|
|
261
|
+
- Maximum 120 words.
|
|
262
|
+
- Use this structure:
|
|
263
|
+
|
|
264
|
+
## ${route.path}
|
|
265
|
+
|
|
266
|
+
User purpose:
|
|
267
|
+
[what users do here]
|
|
268
|
+
|
|
269
|
+
Technical role:
|
|
270
|
+
[what this route does]
|
|
271
|
+
|
|
272
|
+
Main dependencies:
|
|
273
|
+
[modules this uses]
|
|
274
|
+
|
|
275
|
+
Notes:
|
|
276
|
+
[any important observations]`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function createAPIDocumentationPrompt(api, context) {
|
|
280
|
+
return `Write API endpoint documentation.
|
|
281
|
+
|
|
282
|
+
API: ${api.methods.join(", ")} ${api.path}
|
|
283
|
+
File: ${api.file}
|
|
284
|
+
|
|
285
|
+
Additional context:
|
|
286
|
+
${JSON.stringify(context, null, 2)}
|
|
287
|
+
|
|
288
|
+
Requirements:
|
|
289
|
+
- Explain the purpose in plain language and technical language.
|
|
290
|
+
- Describe what it returns or does.
|
|
291
|
+
- Mention dependencies and integrations.
|
|
292
|
+
- Mention risks if any are apparent.
|
|
293
|
+
- Maximum 150 words.
|
|
294
|
+
- Use this structure:
|
|
295
|
+
|
|
296
|
+
## ${api.methods.join(", ")} ${api.path}
|
|
297
|
+
|
|
298
|
+
Purpose:
|
|
299
|
+
[plain language explanation]
|
|
300
|
+
|
|
301
|
+
Source file:
|
|
302
|
+
${api.file}
|
|
303
|
+
|
|
304
|
+
Used by:
|
|
305
|
+
[routes or components]
|
|
306
|
+
|
|
307
|
+
Dependencies:
|
|
308
|
+
[external services or internal modules]
|
|
309
|
+
|
|
310
|
+
Risks:
|
|
311
|
+
[if applicable]`;
|
|
312
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
// Provider-agnostic AI text generation
|
|
2
|
+
|
|
3
|
+
import { warn, info } from "../utils/logger.js";
|
|
4
|
+
|
|
5
|
+
const DEFAULT_TIMEOUT_MS = 60000;
|
|
6
|
+
const DEFAULT_TEMPERATURE = 0.2;
|
|
7
|
+
const DEFAULT_MAX_TOKENS = 2500;
|
|
8
|
+
|
|
9
|
+
export async function generateText({ system, user, temperature, maxTokens }) {
|
|
10
|
+
// Check if AI is enabled
|
|
11
|
+
const enabled = process.env.REPOLENS_AI_ENABLED === "true";
|
|
12
|
+
|
|
13
|
+
if (!enabled) {
|
|
14
|
+
return {
|
|
15
|
+
success: false,
|
|
16
|
+
error: "AI features are disabled. Set REPOLENS_AI_ENABLED=true to enable.",
|
|
17
|
+
fallback: true
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Get provider configuration from environment
|
|
22
|
+
const provider = process.env.REPOLENS_AI_PROVIDER || "openai_compatible";
|
|
23
|
+
const baseUrl = process.env.REPOLENS_AI_BASE_URL;
|
|
24
|
+
const apiKey = process.env.REPOLENS_AI_API_KEY;
|
|
25
|
+
const model = process.env.REPOLENS_AI_MODEL || "gpt-4-turbo-preview";
|
|
26
|
+
const timeoutMs = parseInt(process.env.REPOLENS_AI_TIMEOUT_MS || DEFAULT_TIMEOUT_MS);
|
|
27
|
+
|
|
28
|
+
// Validate configuration
|
|
29
|
+
if (!apiKey) {
|
|
30
|
+
warn("REPOLENS_AI_API_KEY not set. AI features disabled.");
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: "Missing API key",
|
|
34
|
+
fallback: true
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!baseUrl && provider === "openai_compatible") {
|
|
39
|
+
warn("REPOLENS_AI_BASE_URL not set. Using OpenAI default.");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const result = await callOpenAICompatibleAPI({
|
|
44
|
+
baseUrl: baseUrl || "https://api.openai.com/v1",
|
|
45
|
+
apiKey,
|
|
46
|
+
model,
|
|
47
|
+
system,
|
|
48
|
+
user,
|
|
49
|
+
temperature: temperature ?? DEFAULT_TEMPERATURE,
|
|
50
|
+
maxTokens: maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
51
|
+
timeoutMs
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
text: result,
|
|
57
|
+
fallback: false
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
} catch (error) {
|
|
61
|
+
warn(`AI generation failed: ${error.message}`);
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: error.message,
|
|
65
|
+
fallback: true
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function callOpenAICompatibleAPI({ baseUrl, apiKey, model, system, user, temperature, maxTokens, timeoutMs }) {
|
|
71
|
+
const url = `${baseUrl}/chat/completions`;
|
|
72
|
+
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const response = await fetch(url, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: {
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
"Authorization": `Bearer ${apiKey}`
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
model,
|
|
85
|
+
messages: [
|
|
86
|
+
{ role: "system", content: system },
|
|
87
|
+
{ role: "user", content: user }
|
|
88
|
+
],
|
|
89
|
+
temperature,
|
|
90
|
+
max_tokens: maxTokens
|
|
91
|
+
}),
|
|
92
|
+
signal: controller.signal
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
clearTimeout(timeoutId);
|
|
96
|
+
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
const errorText = await response.text();
|
|
99
|
+
throw new Error(`API error (${response.status}): ${errorText}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const data = await response.json();
|
|
103
|
+
|
|
104
|
+
if (!data.choices || data.choices.length === 0) {
|
|
105
|
+
throw new Error("No completion returned from API");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return data.choices[0].message.content;
|
|
109
|
+
|
|
110
|
+
} catch (error) {
|
|
111
|
+
clearTimeout(timeoutId);
|
|
112
|
+
|
|
113
|
+
if (error.name === "AbortError") {
|
|
114
|
+
throw new Error(`Request timeout after ${timeoutMs}ms`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function isAIEnabled() {
|
|
122
|
+
return process.env.REPOLENS_AI_ENABLED === "true";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function getAIConfig() {
|
|
126
|
+
return {
|
|
127
|
+
enabled: isAIEnabled(),
|
|
128
|
+
provider: process.env.REPOLENS_AI_PROVIDER || "openai_compatible",
|
|
129
|
+
model: process.env.REPOLENS_AI_MODEL || "gpt-4-turbo-preview",
|
|
130
|
+
hasApiKey: !!process.env.REPOLENS_AI_API_KEY,
|
|
131
|
+
temperature: parseFloat(process.env.REPOLENS_AI_TEMPERATURE || DEFAULT_TEMPERATURE),
|
|
132
|
+
maxTokens: parseInt(process.env.REPOLENS_AI_MAX_TOKENS || DEFAULT_MAX_TOKENS)
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Build structured AI context from deterministic scan results
|
|
2
|
+
|
|
3
|
+
import { groupModulesByDomain } from "./domain-inference.js";
|
|
4
|
+
|
|
5
|
+
export function buildAIContext(scanResult, config) {
|
|
6
|
+
const { filesCount, modules, api, pages, metadata } = scanResult;
|
|
7
|
+
|
|
8
|
+
// Get domain hints from config if available
|
|
9
|
+
const customHints = config.domains
|
|
10
|
+
? Object.entries(config.domains).map(([key, domain]) => ({
|
|
11
|
+
match: domain.match || [],
|
|
12
|
+
domain: key,
|
|
13
|
+
description: domain.description || key
|
|
14
|
+
}))
|
|
15
|
+
: [];
|
|
16
|
+
|
|
17
|
+
// Group modules by business domain
|
|
18
|
+
const domainGroups = groupModulesByDomain(modules, customHints);
|
|
19
|
+
|
|
20
|
+
// Identify top modules
|
|
21
|
+
const topModules = modules
|
|
22
|
+
.slice(0, 15)
|
|
23
|
+
.map(m => ({
|
|
24
|
+
key: m.key,
|
|
25
|
+
fileCount: m.fileCount,
|
|
26
|
+
type: inferModuleType(m.key)
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Categorize routes
|
|
30
|
+
const pageRoutes = pages?.slice(0, 50).map(p => ({
|
|
31
|
+
path: p.path,
|
|
32
|
+
file: p.file,
|
|
33
|
+
type: "page"
|
|
34
|
+
})) || [];
|
|
35
|
+
|
|
36
|
+
const apiRoutes = api?.slice(0, 50).map(a => ({
|
|
37
|
+
path: a.path,
|
|
38
|
+
file: a.file,
|
|
39
|
+
methods: a.methods,
|
|
40
|
+
type: "api"
|
|
41
|
+
})) || [];
|
|
42
|
+
|
|
43
|
+
// Build technology profile
|
|
44
|
+
const techStack = {
|
|
45
|
+
frameworks: metadata?.frameworks || [],
|
|
46
|
+
languages: metadata?.languages ? [...metadata.languages] : [],
|
|
47
|
+
buildTools: metadata?.buildTools || [],
|
|
48
|
+
testFrameworks: metadata?.testFrameworks || []
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Identify key architectural patterns
|
|
52
|
+
const patterns = inferArchitecturalPatterns(modules);
|
|
53
|
+
|
|
54
|
+
// Build compact context object
|
|
55
|
+
return {
|
|
56
|
+
project: {
|
|
57
|
+
name: config.project.name,
|
|
58
|
+
filesScanned: filesCount,
|
|
59
|
+
modulesDetected: modules.length,
|
|
60
|
+
pagesDetected: pages?.length || 0,
|
|
61
|
+
apiRoutesDetected: api?.length || 0
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
domains: domainGroups.map(d => ({
|
|
65
|
+
name: d.name,
|
|
66
|
+
description: d.description,
|
|
67
|
+
moduleCount: d.modules.length,
|
|
68
|
+
fileCount: d.totalFiles,
|
|
69
|
+
topModules: d.modules.slice(0, 5).map(m => m.key)
|
|
70
|
+
})),
|
|
71
|
+
|
|
72
|
+
topModules,
|
|
73
|
+
|
|
74
|
+
routes: {
|
|
75
|
+
pages: pageRoutes,
|
|
76
|
+
apis: apiRoutes
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
techStack,
|
|
80
|
+
|
|
81
|
+
patterns,
|
|
82
|
+
|
|
83
|
+
repoRoots: config.module_roots || []
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function inferModuleType(modulePath) {
|
|
88
|
+
const lower = modulePath.toLowerCase();
|
|
89
|
+
|
|
90
|
+
if (lower.includes("api")) return "api";
|
|
91
|
+
if (lower.includes("component")) return "ui";
|
|
92
|
+
if (lower.includes("lib") || lower.includes("util")) return "library";
|
|
93
|
+
if (lower.includes("hook")) return "hooks";
|
|
94
|
+
if (lower.includes("store") || lower.includes("state")) return "state";
|
|
95
|
+
if (lower.includes("page") || lower.includes("route")) return "route";
|
|
96
|
+
if (lower.includes("app")) return "app";
|
|
97
|
+
|
|
98
|
+
return "other";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function inferArchitecturalPatterns(modules) {
|
|
102
|
+
const patterns = [];
|
|
103
|
+
|
|
104
|
+
const hasAppRouter = modules.some(m => m.key.includes("app/"));
|
|
105
|
+
const hasPagesRouter = modules.some(m => m.key.includes("pages/"));
|
|
106
|
+
const hasComponents = modules.some(m => m.key.includes("component"));
|
|
107
|
+
const hasLib = modules.some(m => m.key.includes("lib"));
|
|
108
|
+
const hasHooks = modules.some(m => m.key.includes("hook"));
|
|
109
|
+
const hasStore = modules.some(m => m.key.includes("store") || m.key.includes("state"));
|
|
110
|
+
const hasApi = modules.some(m => m.key.includes("api"));
|
|
111
|
+
|
|
112
|
+
if (hasAppRouter) patterns.push("Next.js App Router");
|
|
113
|
+
if (hasPagesRouter) patterns.push("Next.js Pages Router");
|
|
114
|
+
if (hasComponents && hasLib) patterns.push("Layered component architecture");
|
|
115
|
+
if (hasHooks) patterns.push("React hooks pattern");
|
|
116
|
+
if (hasStore) patterns.push("Centralized state management");
|
|
117
|
+
if (hasApi) patterns.push("API route pattern");
|
|
118
|
+
|
|
119
|
+
return patterns;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function buildModuleContext(modules, config) {
|
|
123
|
+
const customHints = config.domains
|
|
124
|
+
? Object.entries(config.domains).map(([key, domain]) => ({
|
|
125
|
+
match: domain.match || [],
|
|
126
|
+
domain: key,
|
|
127
|
+
description: domain.description || key
|
|
128
|
+
}))
|
|
129
|
+
: [];
|
|
130
|
+
|
|
131
|
+
const domainGroups = groupModulesByDomain(modules, customHints);
|
|
132
|
+
|
|
133
|
+
return modules.map(module => {
|
|
134
|
+
const domain = domainGroups.find(d =>
|
|
135
|
+
d.modules.some(m => m.key === module.key)
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
key: module.key,
|
|
140
|
+
fileCount: module.fileCount,
|
|
141
|
+
type: inferModuleType(module.key),
|
|
142
|
+
domain: domain?.name || "Other",
|
|
143
|
+
domainDescription: domain?.description || "Uncategorized"
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// Domain inference - map folder/file names to business domains
|
|
2
|
+
|
|
3
|
+
const DEFAULT_DOMAIN_HINTS = [
|
|
4
|
+
{
|
|
5
|
+
match: ["auth", "login", "signup", "session", "user", "account"],
|
|
6
|
+
domain: "Authentication",
|
|
7
|
+
description: "User authentication and identity flows"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
match: ["stock", "chart", "price", "market", "watchlist", "ticker", "quote"],
|
|
11
|
+
domain: "Market Data & Analysis",
|
|
12
|
+
description: "Market data retrieval, analysis, and visualization"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
match: ["article", "newsletter", "news", "research", "content", "blog", "post"],
|
|
16
|
+
domain: "Content & Research",
|
|
17
|
+
description: "Content publishing, research, and insight delivery"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
match: ["portfolio", "positions", "holdings", "trades", "orders"],
|
|
21
|
+
domain: "Portfolio Management",
|
|
22
|
+
description: "Portfolio tracking and trading functionality"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
match: ["alert", "notification", "email", "sms", "webhook"],
|
|
26
|
+
domain: "Alerts & Notifications",
|
|
27
|
+
description: "User notification and alerting system"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
match: ["payment", "subscription", "billing", "stripe", "checkout"],
|
|
31
|
+
domain: "Payments & Billing",
|
|
32
|
+
description: "Payment processing and subscription management"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
match: ["api", "endpoint", "route", "handler"],
|
|
36
|
+
domain: "API Layer",
|
|
37
|
+
description: "Backend API endpoints and request handling"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
match: ["component", "ui", "button", "form", "modal", "dialog"],
|
|
41
|
+
domain: "UI Components",
|
|
42
|
+
description: "Reusable user interface components"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
match: ["hook", "hooks", "use"],
|
|
46
|
+
domain: "React Hooks",
|
|
47
|
+
description: "Custom React hooks for state and behavior"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
match: ["store", "state", "redux", "zustand", "context"],
|
|
51
|
+
domain: "State Management",
|
|
52
|
+
description: "Application state management"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
match: ["lib", "util", "helper", "common", "shared"],
|
|
56
|
+
domain: "Shared Utilities",
|
|
57
|
+
description: "Common utilities and helper functions"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
match: ["data", "database", "db", "prisma", "sql"],
|
|
61
|
+
domain: "Data Layer",
|
|
62
|
+
description: "Database access and data persistence"
|
|
63
|
+
}
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
export function inferDomain(modulePath, customHints = []) {
|
|
67
|
+
const hints = [...customHints, ...DEFAULT_DOMAIN_HINTS];
|
|
68
|
+
const lowerPath = modulePath.toLowerCase();
|
|
69
|
+
|
|
70
|
+
for (const hint of hints) {
|
|
71
|
+
for (const keyword of hint.match) {
|
|
72
|
+
if (lowerPath.includes(keyword)) {
|
|
73
|
+
return {
|
|
74
|
+
domain: hint.domain,
|
|
75
|
+
description: hint.description,
|
|
76
|
+
confidence: "pattern_match",
|
|
77
|
+
matchedKeyword: keyword
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
domain: "Other",
|
|
85
|
+
description: "Uncategorized module",
|
|
86
|
+
confidence: "none",
|
|
87
|
+
matchedKeyword: null
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function groupModulesByDomain(modules, customHints = []) {
|
|
92
|
+
const domainGroups = {};
|
|
93
|
+
|
|
94
|
+
for (const module of modules) {
|
|
95
|
+
const inference = inferDomain(module.key, customHints);
|
|
96
|
+
const domainName = inference.domain;
|
|
97
|
+
|
|
98
|
+
if (!domainGroups[domainName]) {
|
|
99
|
+
domainGroups[domainName] = {
|
|
100
|
+
name: domainName,
|
|
101
|
+
description: inference.description,
|
|
102
|
+
modules: [],
|
|
103
|
+
totalFiles: 0
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
domainGroups[domainName].modules.push({
|
|
108
|
+
...module,
|
|
109
|
+
inference
|
|
110
|
+
});
|
|
111
|
+
domainGroups[domainName].totalFiles += module.fileCount;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Sort domains by total files (most important first)
|
|
115
|
+
const sortedDomains = Object.values(domainGroups)
|
|
116
|
+
.sort((a, b) => b.totalFiles - a.totalFiles);
|
|
117
|
+
|
|
118
|
+
return sortedDomains;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function inferRouteDomain(routePath, customHints = []) {
|
|
122
|
+
return inferDomain(routePath, customHints);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function inferApiDomain(apiPath, customHints = []) {
|
|
126
|
+
return inferDomain(apiPath, customHints);
|
|
127
|
+
}
|