@chappibunny/repolens 1.4.0 โ 1.5.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 +32 -0
- package/LICENSE +21 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/ai/generate-sections.js +77 -97
- package/src/ai/prompts.js +215 -0
- package/src/ai/provider.js +213 -7
- package/src/analyzers/codeowners.js +146 -0
- package/src/analyzers/context-builder.js +11 -1
- package/src/analyzers/monorepo-detector.js +155 -0
- package/src/core/scan.js +5 -0
- package/src/docs/generate-doc-set.js +30 -12
- package/src/publishers/index.js +40 -11
- package/src/renderers/render.js +40 -5
- package/src/utils/doc-cache.js +78 -0
- package/src/utils/retry.js +18 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RepoLens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.5.1
|
|
6
|
+
|
|
7
|
+
### ๐ Bug Fixes
|
|
8
|
+
|
|
9
|
+
- **[object Object] rendering fix**: All 6 structured AI renderers now use safe coercion helpers (`safeStr`, `toBulletList`, `toHeadingSections`) that handle strings, arrays, objects, and null values robustly. Prevents `[object Object]` from appearing in Notion/Confluence/Markdown output when AI returns unexpected shapes.
|
|
10
|
+
|
|
11
|
+
### ๐ง Improvements โ Robustness
|
|
12
|
+
|
|
13
|
+
- **Fetch timeout**: All HTTP requests via `fetchWithRetry` now enforce a 30-second timeout (configurable via `timeoutMs`). Prevents publishers from hanging indefinitely on stalled connections. `AbortError` is converted to a friendly timeout message.
|
|
14
|
+
- **Per-document error isolation**: Extended analysis (GraphQL, TypeScript, dependency graph, drift detection) now wraps each phase in try/catch. A failing analyzer no longer blocks the entire doc generation pipeline.
|
|
15
|
+
- **Partial-success publishing**: Publisher orchestration no longer throws on the first failure. If Notion fails but Confluence/Markdown succeed, all remaining publishers still run. Failures are logged as warnings. Only throws if *all* publishers fail.
|
|
16
|
+
|
|
17
|
+
### ๐ Test Coverage
|
|
18
|
+
|
|
19
|
+
- **251 tests** passing across **18 test files** (up from 224/17).
|
|
20
|
+
- New `tests/robustness.test.js` with 27 tests covering: rate limiter, context builder, flow inference, Discord integration, PR comment module, telemetry, write-doc-set file I/O, fetch timeout, doc generation error isolation, and partial-success publishing.
|
|
21
|
+
|
|
22
|
+
## 1.5.0
|
|
23
|
+
|
|
24
|
+
### ๐ New Features (Tier 3 โ Differentiation)
|
|
25
|
+
|
|
26
|
+
- **Document caching**: Hash-based caching skips redundant API calls for unchanged documents. Notion, Confluence, and GitHub Wiki publishers now receive only changed pages; Markdown always gets the full set. Cache persists in `.repolens/doc-hashes.json`.
|
|
27
|
+
- **Structured AI output**: AI sections now request JSON-mode responses with schema validation. If JSON parsing or schema validation fails, a single re-prompt is attempted before falling back to plain-text AI, then deterministic generation. All 6 AI document types have JSON schemas and Markdown renderers.
|
|
28
|
+
- **Multi-provider AI**: Added native adapters for Anthropic (Messages API) and Google Gemini alongside existing OpenAI-compatible support. Set `REPOLENS_AI_PROVIDER` to `anthropic`, `google`, or `openai_compatible` (default). Azure OpenAI uses the OpenAI-compatible adapter.
|
|
29
|
+
- **Monorepo awareness**: Automatic detection of npm/yarn workspaces, pnpm workspaces, and Lerna configurations. Scan results include workspace metadata. System Overview renderer shows package inventory table. AI context includes monorepo structure.
|
|
30
|
+
- **CODEOWNERS integration**: Parses `CODEOWNERS` / `.github/CODEOWNERS` / `docs/CODEOWNERS` files. Maps file ownership to modules via last-match-wins pattern matching. Module Catalog now displays an "Owners" column when CODEOWNERS is present. Ownership data is included in artifacts.
|
|
31
|
+
|
|
32
|
+
### ๐ Test Coverage
|
|
33
|
+
|
|
34
|
+
- **219 tests** passing across **17 test files** (up from 188/16).
|
|
35
|
+
- New `tests/tier3.test.js` with 31 tests covering caching, monorepo detection, CODEOWNERS parsing, multi-provider AI config, and structured output rendering.
|
|
36
|
+
|
|
5
37
|
## 1.4.0
|
|
6
38
|
|
|
7
39
|
### ๐ Bug Fixes (Tier 1 โ Production)
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Charl Van Zyl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
RepoLens scans your repository, generates living architecture documentation, and publishes it to Notion, Confluence, GitHub Wiki, or Markdown โ automatically on every push. Engineers get technical docs. Stakeholders get readable system overviews. Nobody writes a word.
|
|
19
19
|
|
|
20
|
-
> Stable as of v1.0 โ [API guarantees](STABILITY.md) ยท [Security hardened](SECURITY.md) ยท v1.
|
|
20
|
+
> Stable as of v1.0 โ [API guarantees](STABILITY.md) ยท [Security hardened](SECURITY.md) ยท v1.5.0
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
package/package.json
CHANGED
|
@@ -11,129 +11,109 @@ import {
|
|
|
11
11
|
createDeveloperOnboardingPrompt,
|
|
12
12
|
createModuleSummaryPrompt,
|
|
13
13
|
createRouteSummaryPrompt,
|
|
14
|
-
createAPIDocumentationPrompt
|
|
14
|
+
createAPIDocumentationPrompt,
|
|
15
|
+
AI_SCHEMAS,
|
|
16
|
+
renderStructuredToMarkdown,
|
|
15
17
|
} from "./prompts.js";
|
|
16
18
|
import { info, warn } from "../utils/logger.js";
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Try structured JSON mode first, fall back to plain-text AI, then deterministic.
|
|
22
|
+
*/
|
|
23
|
+
async function generateWithStructuredFallback(key, promptText, maxTokens, fallbackFn) {
|
|
24
|
+
if (!isAIEnabled()) return fallbackFn();
|
|
25
|
+
|
|
26
|
+
const schema = AI_SCHEMAS[key];
|
|
27
|
+
|
|
28
|
+
// Try structured JSON mode
|
|
29
|
+
if (schema) {
|
|
30
|
+
info(`Generating ${key} with structured AI...`);
|
|
31
|
+
const jsonPrompt = promptText + `\n\nRespond ONLY with a JSON object matching this schema: ${JSON.stringify({ required: schema.required })}. No markdown, no explanation โ just the JSON object.`;
|
|
32
|
+
|
|
33
|
+
const result = await generateText({
|
|
34
|
+
system: SYSTEM_PROMPT,
|
|
35
|
+
user: jsonPrompt,
|
|
36
|
+
maxTokens,
|
|
37
|
+
jsonMode: true,
|
|
38
|
+
jsonSchema: schema,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (result.success && result.parsed) {
|
|
42
|
+
const md = renderStructuredToMarkdown(key, result.parsed);
|
|
43
|
+
if (md) return md;
|
|
44
|
+
}
|
|
45
|
+
// If structured mode failed, fall through to plain-text
|
|
46
|
+
warn(`Structured AI failed for ${key}, trying plain-text mode...`);
|
|
21
47
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
48
|
+
|
|
49
|
+
// Plain-text AI fallback
|
|
50
|
+
info(`Generating ${key} with AI...`);
|
|
25
51
|
const result = await generateText({
|
|
26
52
|
system: SYSTEM_PROMPT,
|
|
27
|
-
user:
|
|
28
|
-
maxTokens
|
|
53
|
+
user: promptText,
|
|
54
|
+
maxTokens,
|
|
29
55
|
});
|
|
30
|
-
|
|
56
|
+
|
|
31
57
|
if (!result.success) {
|
|
32
58
|
warn("AI generation failed, using fallback");
|
|
33
|
-
return
|
|
59
|
+
return fallbackFn();
|
|
34
60
|
}
|
|
35
|
-
|
|
61
|
+
|
|
36
62
|
return result.text;
|
|
37
63
|
}
|
|
38
64
|
|
|
65
|
+
export async function generateExecutiveSummary(context) {
|
|
66
|
+
return generateWithStructuredFallback(
|
|
67
|
+
"executive_summary",
|
|
68
|
+
createExecutiveSummaryPrompt(context),
|
|
69
|
+
1500,
|
|
70
|
+
() => getFallbackExecutiveSummary(context),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
39
74
|
export async function generateSystemOverview(context) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const result = await generateText({
|
|
47
|
-
system: SYSTEM_PROMPT,
|
|
48
|
-
user: createSystemOverviewPrompt(context),
|
|
49
|
-
maxTokens: 1200
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
if (!result.success) {
|
|
53
|
-
return getFallbackSystemOverview(context);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return result.text;
|
|
75
|
+
return generateWithStructuredFallback(
|
|
76
|
+
"system_overview",
|
|
77
|
+
createSystemOverviewPrompt(context),
|
|
78
|
+
1200,
|
|
79
|
+
() => getFallbackSystemOverview(context),
|
|
80
|
+
);
|
|
57
81
|
}
|
|
58
82
|
|
|
59
83
|
export async function generateBusinessDomains(context) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const result = await generateText({
|
|
67
|
-
system: SYSTEM_PROMPT,
|
|
68
|
-
user: createBusinessDomainsPrompt(context),
|
|
69
|
-
maxTokens: 2000
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
if (!result.success) {
|
|
73
|
-
return getFallbackBusinessDomains(context);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return result.text;
|
|
84
|
+
return generateWithStructuredFallback(
|
|
85
|
+
"business_domains",
|
|
86
|
+
createBusinessDomainsPrompt(context),
|
|
87
|
+
2000,
|
|
88
|
+
() => getFallbackBusinessDomains(context),
|
|
89
|
+
);
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
export async function generateArchitectureOverview(context) {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const result = await generateText({
|
|
87
|
-
system: SYSTEM_PROMPT,
|
|
88
|
-
user: createArchitectureOverviewPrompt(context),
|
|
89
|
-
maxTokens: 1800
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
if (!result.success) {
|
|
93
|
-
return getFallbackArchitectureOverview(context);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return result.text;
|
|
93
|
+
return generateWithStructuredFallback(
|
|
94
|
+
"architecture_overview",
|
|
95
|
+
createArchitectureOverviewPrompt(context),
|
|
96
|
+
1800,
|
|
97
|
+
() => getFallbackArchitectureOverview(context),
|
|
98
|
+
);
|
|
97
99
|
}
|
|
98
100
|
|
|
99
101
|
export async function generateDataFlows(flows, context) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const result = await generateText({
|
|
107
|
-
system: SYSTEM_PROMPT,
|
|
108
|
-
user: createDataFlowsPrompt(flows, context),
|
|
109
|
-
maxTokens: 1800
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
if (!result.success) {
|
|
113
|
-
return getFallbackDataFlows(flows);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return result.text;
|
|
102
|
+
return generateWithStructuredFallback(
|
|
103
|
+
"data_flows",
|
|
104
|
+
createDataFlowsPrompt(flows, context),
|
|
105
|
+
1800,
|
|
106
|
+
() => getFallbackDataFlows(flows),
|
|
107
|
+
);
|
|
117
108
|
}
|
|
118
109
|
|
|
119
110
|
export async function generateDeveloperOnboarding(context) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const result = await generateText({
|
|
127
|
-
system: SYSTEM_PROMPT,
|
|
128
|
-
user: createDeveloperOnboardingPrompt(context),
|
|
129
|
-
maxTokens: 2200
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
if (!result.success) {
|
|
133
|
-
return getFallbackDeveloperOnboarding(context);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
return result.text;
|
|
111
|
+
return generateWithStructuredFallback(
|
|
112
|
+
"developer_onboarding",
|
|
113
|
+
createDeveloperOnboardingPrompt(context),
|
|
114
|
+
2200,
|
|
115
|
+
() => getFallbackDeveloperOnboarding(context),
|
|
116
|
+
);
|
|
137
117
|
}
|
|
138
118
|
|
|
139
119
|
// Fallback generators (deterministic, no AI)
|
package/src/ai/prompts.js
CHANGED
|
@@ -353,3 +353,218 @@ Dependencies:
|
|
|
353
353
|
Risks:
|
|
354
354
|
[if applicable]`;
|
|
355
355
|
}
|
|
356
|
+
|
|
357
|
+
// --- JSON schemas for structured AI output ---
|
|
358
|
+
|
|
359
|
+
export const AI_SCHEMAS = {
|
|
360
|
+
executive_summary: {
|
|
361
|
+
required: ["whatItDoes", "whoItServes", "coreCapabilities", "mainAreas", "risks"],
|
|
362
|
+
description: "Executive summary for mixed audience",
|
|
363
|
+
},
|
|
364
|
+
system_overview: {
|
|
365
|
+
required: ["snapshot", "layers", "domains", "patterns", "observations"],
|
|
366
|
+
description: "High-level system overview",
|
|
367
|
+
},
|
|
368
|
+
business_domains: {
|
|
369
|
+
required: ["domains"],
|
|
370
|
+
description: "Business domain breakdown",
|
|
371
|
+
},
|
|
372
|
+
architecture_overview: {
|
|
373
|
+
required: ["style", "layers", "strengths", "weaknesses"],
|
|
374
|
+
description: "Architecture overview for engineers",
|
|
375
|
+
},
|
|
376
|
+
data_flows: {
|
|
377
|
+
required: ["flows"],
|
|
378
|
+
description: "Data flow documentation",
|
|
379
|
+
},
|
|
380
|
+
developer_onboarding: {
|
|
381
|
+
required: ["startHere", "mainFolders", "coreFlows", "complexityHotspots"],
|
|
382
|
+
description: "Developer onboarding guide",
|
|
383
|
+
},
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Render a structured JSON response into Markdown for the given document type.
|
|
388
|
+
*/
|
|
389
|
+
export function renderStructuredToMarkdown(key, parsed) {
|
|
390
|
+
switch (key) {
|
|
391
|
+
case "executive_summary":
|
|
392
|
+
return renderExecutiveSummaryJSON(parsed);
|
|
393
|
+
case "system_overview":
|
|
394
|
+
return renderSystemOverviewJSON(parsed);
|
|
395
|
+
case "business_domains":
|
|
396
|
+
return renderBusinessDomainsJSON(parsed);
|
|
397
|
+
case "architecture_overview":
|
|
398
|
+
return renderArchitectureOverviewJSON(parsed);
|
|
399
|
+
case "data_flows":
|
|
400
|
+
return renderDataFlowsJSON(parsed);
|
|
401
|
+
case "developer_onboarding":
|
|
402
|
+
return renderDeveloperOnboardingJSON(parsed);
|
|
403
|
+
default:
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Safely convert any AI response value to a readable string.
|
|
410
|
+
* Handles: strings, arrays (of strings or objects), plain objects, and other types.
|
|
411
|
+
*/
|
|
412
|
+
function safeStr(val) {
|
|
413
|
+
if (val == null) return "";
|
|
414
|
+
if (typeof val === "string") return val;
|
|
415
|
+
if (Array.isArray(val)) return val.map(safeStr).join(", ");
|
|
416
|
+
if (typeof val === "object") {
|
|
417
|
+
// Try common field patterns the AI might use
|
|
418
|
+
if (val.name) return val.description ? `${val.name}: ${val.description}` : val.name;
|
|
419
|
+
if (val.title) return val.description ? `${val.title}: ${val.description}` : val.title;
|
|
420
|
+
// Fallback: render object key/value pairs
|
|
421
|
+
return Object.entries(val).map(([k, v]) => `${k}: ${typeof v === "string" ? v : safeStr(v)}`).join("; ");
|
|
422
|
+
}
|
|
423
|
+
return String(val);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Convert a value to a bullet list.
|
|
428
|
+
* Handles strings, arrays of strings, arrays of objects, and plain objects.
|
|
429
|
+
*/
|
|
430
|
+
function toBulletList(val) {
|
|
431
|
+
if (val == null) return "";
|
|
432
|
+
if (typeof val === "string") return val;
|
|
433
|
+
if (Array.isArray(val)) {
|
|
434
|
+
return val.map(item => {
|
|
435
|
+
if (typeof item === "string") return `- ${item}`;
|
|
436
|
+
if (typeof item === "object" && item !== null) {
|
|
437
|
+
const label = item.name || item.title || Object.keys(item)[0] || "";
|
|
438
|
+
const desc = item.description || item[Object.keys(item)[0]];
|
|
439
|
+
if (label && desc && label !== desc) return `- **${label}**: ${desc}`;
|
|
440
|
+
return `- ${safeStr(item)}`;
|
|
441
|
+
}
|
|
442
|
+
return `- ${String(item)}`;
|
|
443
|
+
}).join("\n");
|
|
444
|
+
}
|
|
445
|
+
if (typeof val === "object") {
|
|
446
|
+
// Object with key/value pairs โ render as list
|
|
447
|
+
return Object.entries(val).map(([k, v]) => `- **${k}**: ${typeof v === "string" ? v : safeStr(v)}`).join("\n");
|
|
448
|
+
}
|
|
449
|
+
return String(val);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Convert a value to a heading-based section list (### heading per item).
|
|
454
|
+
*/
|
|
455
|
+
function toHeadingSections(val) {
|
|
456
|
+
if (val == null) return "";
|
|
457
|
+
if (typeof val === "string") return val;
|
|
458
|
+
if (Array.isArray(val)) {
|
|
459
|
+
return val.map(item => {
|
|
460
|
+
if (typeof item === "string") return `### ${item}`;
|
|
461
|
+
if (typeof item === "object" && item !== null) {
|
|
462
|
+
const label = item.name || item.title || Object.keys(item)[0] || "Section";
|
|
463
|
+
const desc = item.description || item[Object.keys(item)[0]] || "";
|
|
464
|
+
return `### ${label}\n\n${typeof desc === "string" ? desc : safeStr(desc)}`;
|
|
465
|
+
}
|
|
466
|
+
return `### ${String(item)}`;
|
|
467
|
+
}).join("\n\n");
|
|
468
|
+
}
|
|
469
|
+
if (typeof val === "object") {
|
|
470
|
+
return Object.entries(val).map(([k, v]) => `### ${k}\n\n${typeof v === "string" ? v : safeStr(v)}`).join("\n\n");
|
|
471
|
+
}
|
|
472
|
+
return String(val);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function renderExecutiveSummaryJSON(d) {
|
|
476
|
+
let md = `# Executive Summary\n\n`;
|
|
477
|
+
md += `## What This System Does\n\n${safeStr(d.whatItDoes)}\n\n`;
|
|
478
|
+
md += `## Who It Serves\n\n${safeStr(d.whoItServes)}\n\n`;
|
|
479
|
+
md += `## Core Capabilities\n\n${toBulletList(d.coreCapabilities)}\n\n`;
|
|
480
|
+
md += `## Main System Areas\n\n${toBulletList(d.mainAreas)}\n\n`;
|
|
481
|
+
if (d.dependencies) md += `## Key Dependencies\n\n${toBulletList(d.dependencies)}\n\n`;
|
|
482
|
+
md += `## Operational and Architectural Risks\n\n${toBulletList(d.risks)}\n\n`;
|
|
483
|
+
if (d.focusAreas) md += `## Recommended Focus Areas\n\n${toBulletList(d.focusAreas)}\n`;
|
|
484
|
+
return md;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function renderSystemOverviewJSON(d) {
|
|
488
|
+
let md = `# System Overview\n\n`;
|
|
489
|
+
md += `## Repository Snapshot\n\n${safeStr(d.snapshot)}\n\n`;
|
|
490
|
+
md += `## Main Architectural Layers\n\n${toBulletList(d.layers)}\n\n`;
|
|
491
|
+
md += `## Dominant Domains\n\n${toBulletList(d.domains)}\n\n`;
|
|
492
|
+
md += `## Main Technology Patterns\n\n${toBulletList(d.patterns)}\n\n`;
|
|
493
|
+
md += `## Key Observations\n\n${toBulletList(d.observations)}\n`;
|
|
494
|
+
return md;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function renderBusinessDomainsJSON(d) {
|
|
498
|
+
let md = `# Business Domains\n\n`;
|
|
499
|
+
if (!Array.isArray(d.domains)) {
|
|
500
|
+
// Handle object-style domains: { "Auth": { description: "..." }, ... }
|
|
501
|
+
if (typeof d.domains === "object" && d.domains !== null) {
|
|
502
|
+
for (const [name, info] of Object.entries(d.domains)) {
|
|
503
|
+
const desc = typeof info === "string" ? info : info?.description || safeStr(info);
|
|
504
|
+
md += `## ${name}\n\n${desc}\n\n`;
|
|
505
|
+
if (info?.modules) md += `**Key modules:** ${safeStr(info.modules)}\n\n`;
|
|
506
|
+
if (info?.userFunctionality) md += `**User-visible functionality:** ${info.userFunctionality}\n\n`;
|
|
507
|
+
if (info?.dependencies) md += `**Dependencies:** ${safeStr(info.dependencies)}\n\n`;
|
|
508
|
+
}
|
|
509
|
+
return md;
|
|
510
|
+
}
|
|
511
|
+
return md + safeStr(d.domains);
|
|
512
|
+
}
|
|
513
|
+
for (const dom of d.domains) {
|
|
514
|
+
const name = dom.name || dom.title || safeStr(dom);
|
|
515
|
+
md += `## ${name}\n\n${dom.description || ""}\n\n`;
|
|
516
|
+
if (dom.modules) md += `**Key modules:** ${safeStr(dom.modules)}\n\n`;
|
|
517
|
+
if (dom.userFunctionality) md += `**User-visible functionality:** ${dom.userFunctionality}\n\n`;
|
|
518
|
+
if (dom.dependencies) md += `**Dependencies:** ${safeStr(dom.dependencies)}\n\n`;
|
|
519
|
+
}
|
|
520
|
+
return md;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function renderArchitectureOverviewJSON(d) {
|
|
524
|
+
let md = `# Architecture Overview\n\n`;
|
|
525
|
+
md += `## Architecture Style\n\n${safeStr(d.style)}\n\n`;
|
|
526
|
+
md += `## Layers\n\n${toHeadingSections(d.layers)}\n\n`;
|
|
527
|
+
md += `## Architectural Strengths\n\n${toBulletList(d.strengths)}\n\n`;
|
|
528
|
+
md += `## Architectural Weaknesses\n\n${toBulletList(d.weaknesses)}\n`;
|
|
529
|
+
return md;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function renderDataFlowsJSON(d) {
|
|
533
|
+
let md = `# Data Flows\n\n`;
|
|
534
|
+
if (!Array.isArray(d.flows)) {
|
|
535
|
+
if (typeof d.flows === "object" && d.flows !== null) {
|
|
536
|
+
for (const [name, info] of Object.entries(d.flows)) {
|
|
537
|
+
const desc = typeof info === "string" ? info : info?.description || safeStr(info);
|
|
538
|
+
md += `## ${name}\n\n${desc}\n\n`;
|
|
539
|
+
if (info?.steps) md += `**Steps:**\n${toBulletList(info.steps)}\n\n`;
|
|
540
|
+
if (info?.modules) md += `**Involved modules:** ${safeStr(info.modules)}\n\n`;
|
|
541
|
+
}
|
|
542
|
+
return md;
|
|
543
|
+
}
|
|
544
|
+
return md + safeStr(d.flows);
|
|
545
|
+
}
|
|
546
|
+
for (const flow of d.flows) {
|
|
547
|
+
const name = flow.name || flow.title || safeStr(flow);
|
|
548
|
+
md += `## ${name}\n\n${flow.description || ""}\n\n`;
|
|
549
|
+
if (flow.steps) {
|
|
550
|
+
const steps = Array.isArray(flow.steps)
|
|
551
|
+
? flow.steps.map((s, i) => `${i + 1}. ${safeStr(s)}`).join("\n")
|
|
552
|
+
: safeStr(flow.steps);
|
|
553
|
+
md += `**Steps:**\n${steps}\n\n`;
|
|
554
|
+
}
|
|
555
|
+
if (flow.modules) md += `**Involved modules:** ${safeStr(flow.modules)}\n\n`;
|
|
556
|
+
if (flow.criticalDependencies) md += `**Critical dependencies:** ${safeStr(flow.criticalDependencies)}\n\n`;
|
|
557
|
+
}
|
|
558
|
+
return md;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function renderDeveloperOnboardingJSON(d) {
|
|
562
|
+
let md = `# Developer Onboarding\n\n`;
|
|
563
|
+
md += `## Start Here\n\n${safeStr(d.startHere)}\n\n`;
|
|
564
|
+
md += `## Main Folders\n\n${toBulletList(d.mainFolders)}\n\n`;
|
|
565
|
+
md += `## Core Product Flows\n\n${toBulletList(d.coreFlows)}\n\n`;
|
|
566
|
+
if (d.importantRoutes) md += `## Important Routes\n\n${toBulletList(d.importantRoutes)}\n\n`;
|
|
567
|
+
if (d.sharedLibraries) md += `## Important Shared Libraries\n\n${toBulletList(d.sharedLibraries)}\n\n`;
|
|
568
|
+
md += `## Known Complexity Hotspots\n\n${toBulletList(d.complexityHotspots)}\n`;
|
|
569
|
+
return md;
|
|
570
|
+
}
|