@chappibunny/repolens 1.3.1 โ†’ 1.5.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/CHANGELOG.md CHANGED
@@ -2,6 +2,44 @@
2
2
 
3
3
  All notable changes to RepoLens will be documented in this file.
4
4
 
5
+ ## 1.5.0
6
+
7
+ ### ๐Ÿš€ New Features (Tier 3 โ€” Differentiation)
8
+
9
+ - **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`.
10
+ - **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.
11
+ - **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.
12
+ - **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.
13
+ - **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.
14
+
15
+ ### ๐Ÿ“Š Test Coverage
16
+
17
+ - **219 tests** passing across **17 test files** (up from 188/16).
18
+ - New `tests/tier3.test.js` with 31 tests covering caching, monorepo detection, CODEOWNERS parsing, multi-provider AI config, and structured output rendering.
19
+
20
+ ## 1.4.0
21
+
22
+ ### ๐Ÿ› Bug Fixes (Tier 1 โ€” Production)
23
+
24
+ - **Confluence CDATA injection**: Code blocks containing `]]>` no longer break Confluence XML storage format. Applied standard CDATA escape pattern (`]]]]><![CDATA[>`).
25
+ - **Discord branding**: Replaced 3 leftover "RABITAI" references with "RepoLens" in Discord webhook embeds (title, footer, error title).
26
+ - **CLI branding**: Updated 2 remaining "RABITAI" banner strings to "RepoLens" in CLI output and help text.
27
+ - **Markdown publisher missing mappings**: Added 9 missing document-type filename mappings (`executive_summary`, `business_domains`, `architecture_overview`, `data_flows`, `developer_onboarding`, `graphql_schema`, `type_graph`, `dependency_graph`, `architecture_drift`). All 15 document plan keys now map to output filenames.
28
+ - **Relative link rewriting**: Notion and Confluence publishers now strip relative markdown links (`./path.md`, `../path.md`) that can't resolve in external platforms. Links are replaced with their display text.
29
+
30
+ ### ๐Ÿ”ง Improvements (Tier 2 โ€” Robustness)
31
+
32
+ - **Generic domain defaults**: Replaced 12 fintech-specific domain hints with 15 universally applicable domains (Authentication, Analytics, Content Management, Search, Notifications, Payments, API Layer, UI Components, Hooks, State, Utilities, Data Layer, Config, Testing, Background Jobs). Domain inference no longer maps "chart" to "Market Data" in non-finance apps.
33
+ - **Real import-based system map**: `renderSystemMap()` now uses actual import edges from the dependency graph analyzer when available, instead of heuristic guessing. Diagram labels indicate whether relationships are from "Real import analysis" or "Heuristic inference".
34
+ - **AI context size limiting**: Added `truncateContext()` function to AI prompts with a 12K character cap. Progressive pruning: routes/pages to 15, domains to 8, top modules to 10, then hard-truncate. Prevents token overflow on large codebases.
35
+ - **Doctor env var validation**: `repolens doctor` now checks for publisher-specific environment variables (NOTION_TOKEN, CONFLUENCE_URL/EMAIL/API_TOKEN, GITHUB_TOKEN, REPOLENS_AI_API_KEY) and warns when required vars are missing for configured publishers.
36
+ - **Truncation warnings**: Renderers now display notes when output is truncated โ€” modules >100 in catalog, routes >200 in route map, files/routes >25 and modules >40 in architecture diff.
37
+ - **Watch mode tests**: Added 3 new tests for `src/watch.js` covering no-directory handling, watcher setup, and node_modules filtering.
38
+
39
+ ### ๐Ÿ“Š Test Coverage
40
+
41
+ - **188 tests** passing across **16 test files** (up from 185/15).
42
+
5
43
  ## 1.3.1
6
44
 
7
45
  ### ๐Ÿ“ Documentation Overhaul
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
@@ -9,6 +9,7 @@
9
9
  ```
10
10
 
11
11
  [![npm version](https://img.shields.io/npm/v/@chappibunny/repolens)](https://www.npmjs.com/package/@chappibunny/repolens)
12
+ [![VS Code Extension](https://img.shields.io/visual-studio-marketplace/v/CHAPIBUNNY.repolens-architecture?label=VS%20Code)](https://marketplace.visualstudio.com/items?itemName=CHAPIBUNNY.repolens-architecture)
12
13
  [![Tests](https://img.shields.io/badge/tests-185%20passing-brightgreen)](https://github.com/CHAPIBUNNY/repolens/actions)
13
14
  [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
14
15
 
@@ -16,7 +17,7 @@
16
17
 
17
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.
18
19
 
19
- > Stable as of v1.0 โ€” [API guarantees](STABILITY.md) ยท [Security hardened](SECURITY.md) ยท v1.3.1
20
+ > Stable as of v1.0 โ€” [API guarantees](STABILITY.md) ยท [Security hardened](SECURITY.md) ยท v1.5.0
20
21
 
21
22
  ---
22
23
 
@@ -113,6 +114,28 @@ For alternative methods, see [INSTALLATION.md](INSTALLATION.md).
113
114
 
114
115
  ---
115
116
 
117
+ ## ๐ŸŽจ VS Code Extension
118
+
119
+ **View your architecture directly in VS Code** โ€” browse modules, visualize dependencies, and explore your codebase structure without leaving the editor.
120
+
121
+ **Install from Marketplace:**
122
+ ```
123
+ ext install CHAPIBUNNY.repolens-architecture
124
+ ```
125
+
126
+ [**โ†’ Get it on Visual Studio Marketplace**](https://marketplace.visualstudio.com/items?itemName=CHAPIBUNNY.repolens-architecture)
127
+
128
+ **Features:**
129
+ - ๐Ÿ—๏ธ **Architecture Explorer** โ€” Tree view of your system structure
130
+ - ๐Ÿ“Š **Dependency Visualizer** โ€” Interactive dependency graphs
131
+ - ๐Ÿ“ **Module Browser** โ€” Navigate modules by domain and function
132
+ - ๐Ÿ” **Command Palette** โ€” Quick access to architecture insights
133
+ - ๐Ÿ“ˆ **System Metrics** โ€” Real-time architecture health indicators
134
+
135
+ The extension reads your `.repolens.yml` configuration and provides an interactive UI for exploring the documentation that RepoLens generates.
136
+
137
+ ---
138
+
116
139
  ## ๐ŸŽ“ Onboarding Guide
117
140
 
118
141
  Step-by-step setup for publishers, AI features, Notion, Confluence, GitHub Wiki, Discord, and CI/CD automation.
@@ -238,9 +261,11 @@ When you open a pull request, RepoLens posts:
238
261
 
239
262
  v1.0+ features complete โ€” CLI, config schema, and plugin interface are frozen.
240
263
 
264
+ **Completed:**
265
+ - [x] VS Code extension ([available on Marketplace](https://marketplace.visualstudio.com/items?itemName=CHAPIBUNNY.repolens-architecture))
266
+
241
267
  **Next:**
242
268
  - [ ] Obsidian publisher
243
- - [ ] VS Code extension
244
269
  - [ ] GitHub App
245
270
 
246
271
  See [ROADMAP.md](ROADMAP.md) for detailed planning.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chappibunny/repolens",
3
- "version": "1.3.1",
3
+ "version": "1.5.0",
4
4
  "description": "AI-assisted documentation intelligence system for technical and non-technical audiences",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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
- export async function generateExecutiveSummary(context) {
19
- if (!isAIEnabled()) {
20
- return getFallbackExecutiveSummary(context);
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
- info("Generating executive summary with AI...");
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: createExecutiveSummaryPrompt(context),
28
- maxTokens: 1500
53
+ user: promptText,
54
+ maxTokens,
29
55
  });
30
-
56
+
31
57
  if (!result.success) {
32
58
  warn("AI generation failed, using fallback");
33
- return getFallbackExecutiveSummary(context);
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
- if (!isAIEnabled()) {
41
- return getFallbackSystemOverview(context);
42
- }
43
-
44
- info("Generating system overview with AI...");
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
- if (!isAIEnabled()) {
61
- return getFallbackBusinessDomains(context);
62
- }
63
-
64
- info("Generating business domains with AI...");
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
- if (!isAIEnabled()) {
81
- return getFallbackArchitectureOverview(context);
82
- }
83
-
84
- info("Generating architecture overview with AI...");
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
- if (!isAIEnabled()) {
101
- return getFallbackDataFlows(flows);
102
- }
103
-
104
- info("Generating data flows with AI...");
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
- if (!isAIEnabled()) {
121
- return getFallbackDeveloperOnboarding(context);
122
- }
123
-
124
- info("Generating developer onboarding with AI...");
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
@@ -1,5 +1,48 @@
1
1
  // Strict prompt templates for AI-generated documentation sections
2
2
 
3
+ const MAX_CONTEXT_CHARS = 12000; // ~3000 tokens, safe for all models
4
+
5
+ /**
6
+ * Truncate a context object to fit within token limits.
7
+ * Prunes large arrays (routes, modules, domains) to keep context compact.
8
+ */
9
+ function truncateContext(context) {
10
+ let json = JSON.stringify(context, null, 2);
11
+ if (json.length <= MAX_CONTEXT_CHARS) return json;
12
+
13
+ // Progressively shrink: reduce array sizes
14
+ const trimmed = { ...context };
15
+
16
+ // Trim routes
17
+ if (trimmed.routes) {
18
+ if (trimmed.routes.pages && trimmed.routes.pages.length > 15) {
19
+ trimmed.routes = { ...trimmed.routes, pages: trimmed.routes.pages.slice(0, 15) };
20
+ }
21
+ if (trimmed.routes.apis && trimmed.routes.apis.length > 15) {
22
+ trimmed.routes = { ...trimmed.routes, apis: trimmed.routes.apis.slice(0, 15) };
23
+ }
24
+ }
25
+
26
+ // Trim domains
27
+ if (Array.isArray(trimmed.domains) && trimmed.domains.length > 8) {
28
+ trimmed.domains = trimmed.domains.slice(0, 8);
29
+ }
30
+
31
+ // Trim top modules
32
+ if (Array.isArray(trimmed.topModules) && trimmed.topModules.length > 10) {
33
+ trimmed.topModules = trimmed.topModules.slice(0, 10);
34
+ }
35
+
36
+ json = JSON.stringify(trimmed, null, 2);
37
+
38
+ // Final hard truncation if still over limit
39
+ if (json.length > MAX_CONTEXT_CHARS) {
40
+ json = json.slice(0, MAX_CONTEXT_CHARS) + "\n... (context truncated for token limit)";
41
+ }
42
+
43
+ return json;
44
+ }
45
+
3
46
  export const SYSTEM_PROMPT = `You are a senior software architect and technical writer.
4
47
  Your job is to turn structured repository analysis into clear documentation.
5
48
 
@@ -20,7 +63,7 @@ export function createExecutiveSummaryPrompt(context) {
20
63
  return `Write an executive summary for a mixed audience of technical and non-technical readers.
21
64
 
22
65
  Use this context:
23
- ${JSON.stringify(context, null, 2)}
66
+ ${truncateContext(context)}
24
67
 
25
68
  Requirements:
26
69
  - Explain what the system appears to do based on the modules and routes.
@@ -53,7 +96,7 @@ export function createSystemOverviewPrompt(context) {
53
96
  return `Write a system overview for a mixed audience.
54
97
 
55
98
  Use this context:
56
- ${JSON.stringify(context, null, 2)}
99
+ ${truncateContext(context)}
57
100
 
58
101
  Requirements:
59
102
  - Provide a concise, high-level orientation to the codebase.
@@ -81,7 +124,7 @@ export function createBusinessDomainsPrompt(context) {
81
124
  return `Write business domain documentation for a mixed audience, especially non-technical readers.
82
125
 
83
126
  Use this context:
84
- ${JSON.stringify(context, null, 2)}
127
+ ${truncateContext(context)}
85
128
 
86
129
  Requirements:
87
130
  - Translate codebase structure into business language.
@@ -113,7 +156,7 @@ export function createArchitectureOverviewPrompt(context) {
113
156
  return `Write an architecture overview for engineers, architects, and technical PMs.
114
157
 
115
158
  Use this context:
116
- ${JSON.stringify(context, null, 2)}
159
+ ${truncateContext(context)}
117
160
 
118
161
  Requirements:
119
162
  - Explain the layered architecture based on observable patterns.
@@ -147,10 +190,10 @@ export function createDataFlowsPrompt(flows, context) {
147
190
  return `Write data flow documentation for a mixed audience.
148
191
 
149
192
  Use this flow information:
150
- ${JSON.stringify(flows, null, 2)}
193
+ ${truncateContext(flows)}
151
194
 
152
195
  And this context:
153
- ${JSON.stringify(context, null, 2)}
196
+ ${truncateContext(context)}
154
197
 
155
198
  Requirements:
156
199
  - Explain major information flows in plain language.
@@ -179,7 +222,7 @@ export function createDeveloperOnboardingPrompt(context) {
179
222
  return `Write developer onboarding documentation to help new engineers get productive quickly.
180
223
 
181
224
  Use this context:
182
- ${JSON.stringify(context, null, 2)}
225
+ ${truncateContext(context)}
183
226
 
184
227
  Requirements:
185
228
  - Guide new developers through the codebase structure.
@@ -218,7 +261,7 @@ Type: ${module.type}
218
261
  Domain: ${module.domain}
219
262
 
220
263
  Additional context:
221
- ${JSON.stringify(context, null, 2)}
264
+ ${truncateContext(context)}
222
265
 
223
266
  Requirements:
224
267
  - Explain the module's likely purpose.
@@ -252,7 +295,7 @@ File: ${route.file}
252
295
  Type: ${route.type}
253
296
 
254
297
  Additional context:
255
- ${JSON.stringify(context, null, 2)}
298
+ ${truncateContext(context)}
256
299
 
257
300
  Requirements:
258
301
  - Explain the user purpose of this route.
@@ -283,7 +326,7 @@ API: ${api.methods.join(", ")} ${api.path}
283
326
  File: ${api.file}
284
327
 
285
328
  Additional context:
286
- ${JSON.stringify(context, null, 2)}
329
+ ${truncateContext(context)}
287
330
 
288
331
  Requirements:
289
332
  - Explain the purpose in plain language and technical language.
@@ -310,3 +353,125 @@ Dependencies:
310
353
  Risks:
311
354
  [if applicable]`;
312
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
+ function renderExecutiveSummaryJSON(d) {
409
+ let md = `# Executive Summary\n\n`;
410
+ md += `## What This System Does\n\n${d.whatItDoes}\n\n`;
411
+ md += `## Who It Serves\n\n${d.whoItServes}\n\n`;
412
+ md += `## Core Capabilities\n\n`;
413
+ if (Array.isArray(d.coreCapabilities)) {
414
+ md += d.coreCapabilities.map(c => `- ${c}`).join("\n") + "\n\n";
415
+ } else {
416
+ md += `${d.coreCapabilities}\n\n`;
417
+ }
418
+ md += `## Main System Areas\n\n${Array.isArray(d.mainAreas) ? d.mainAreas.map(a => `- **${a.name || a}**${a.description ? `: ${a.description}` : ""}`).join("\n") : d.mainAreas}\n\n`;
419
+ if (d.dependencies) md += `## Key Dependencies\n\n${Array.isArray(d.dependencies) ? d.dependencies.map(dep => `- ${dep}`).join("\n") : d.dependencies}\n\n`;
420
+ md += `## Operational and Architectural Risks\n\n${Array.isArray(d.risks) ? d.risks.map(r => `- ${r}`).join("\n") : d.risks}\n\n`;
421
+ if (d.focusAreas) md += `## Recommended Focus Areas\n\n${Array.isArray(d.focusAreas) ? d.focusAreas.map(f => `- ${f}`).join("\n") : d.focusAreas}\n`;
422
+ return md;
423
+ }
424
+
425
+ function renderSystemOverviewJSON(d) {
426
+ let md = `# System Overview\n\n`;
427
+ md += `## Repository Snapshot\n\n${d.snapshot}\n\n`;
428
+ md += `## Main Architectural Layers\n\n${Array.isArray(d.layers) ? d.layers.map(l => `- **${l.name || l}**${l.description ? `: ${l.description}` : ""}`).join("\n") : d.layers}\n\n`;
429
+ md += `## Dominant Domains\n\n${Array.isArray(d.domains) ? d.domains.map(dom => `- ${dom}`).join("\n") : d.domains}\n\n`;
430
+ md += `## Main Technology Patterns\n\n${Array.isArray(d.patterns) ? d.patterns.map(p => `- ${p}`).join("\n") : d.patterns}\n\n`;
431
+ md += `## Key Observations\n\n${Array.isArray(d.observations) ? d.observations.map(o => `- ${o}`).join("\n") : d.observations}\n`;
432
+ return md;
433
+ }
434
+
435
+ function renderBusinessDomainsJSON(d) {
436
+ let md = `# Business Domains\n\n`;
437
+ if (!Array.isArray(d.domains)) return md + d.domains;
438
+ for (const dom of d.domains) {
439
+ md += `## ${dom.name}\n\n${dom.description || ""}\n\n`;
440
+ if (dom.modules) md += `**Key modules:** ${Array.isArray(dom.modules) ? dom.modules.join(", ") : dom.modules}\n\n`;
441
+ if (dom.userFunctionality) md += `**User-visible functionality:** ${dom.userFunctionality}\n\n`;
442
+ if (dom.dependencies) md += `**Dependencies:** ${Array.isArray(dom.dependencies) ? dom.dependencies.join(", ") : dom.dependencies}\n\n`;
443
+ }
444
+ return md;
445
+ }
446
+
447
+ function renderArchitectureOverviewJSON(d) {
448
+ let md = `# Architecture Overview\n\n`;
449
+ md += `## Architecture Style\n\n${d.style}\n\n`;
450
+ md += `## Layers\n\n${Array.isArray(d.layers) ? d.layers.map(l => `### ${l.name || l}\n\n${l.description || ""}`).join("\n\n") : d.layers}\n\n`;
451
+ md += `## Architectural Strengths\n\n${Array.isArray(d.strengths) ? d.strengths.map(s => `- ${s}`).join("\n") : d.strengths}\n\n`;
452
+ md += `## Architectural Weaknesses\n\n${Array.isArray(d.weaknesses) ? d.weaknesses.map(w => `- ${w}`).join("\n") : d.weaknesses}\n`;
453
+ return md;
454
+ }
455
+
456
+ function renderDataFlowsJSON(d) {
457
+ let md = `# Data Flows\n\n`;
458
+ if (!Array.isArray(d.flows)) return md + d.flows;
459
+ for (const flow of d.flows) {
460
+ md += `## ${flow.name}\n\n${flow.description || ""}\n\n`;
461
+ if (flow.steps) md += `**Steps:**\n${Array.isArray(flow.steps) ? flow.steps.map((s, i) => `${i + 1}. ${s}`).join("\n") : flow.steps}\n\n`;
462
+ if (flow.modules) md += `**Involved modules:** ${Array.isArray(flow.modules) ? flow.modules.join(", ") : flow.modules}\n\n`;
463
+ if (flow.criticalDependencies) md += `**Critical dependencies:** ${flow.criticalDependencies}\n\n`;
464
+ }
465
+ return md;
466
+ }
467
+
468
+ function renderDeveloperOnboardingJSON(d) {
469
+ let md = `# Developer Onboarding\n\n`;
470
+ md += `## Start Here\n\n${d.startHere}\n\n`;
471
+ md += `## Main Folders\n\n${Array.isArray(d.mainFolders) ? d.mainFolders.map(f => `- **${f.name || f}**${f.description ? `: ${f.description}` : ""}`).join("\n") : d.mainFolders}\n\n`;
472
+ md += `## Core Product Flows\n\n${Array.isArray(d.coreFlows) ? d.coreFlows.map(f => `- ${f}`).join("\n") : d.coreFlows}\n\n`;
473
+ if (d.importantRoutes) md += `## Important Routes\n\n${Array.isArray(d.importantRoutes) ? d.importantRoutes.map(r => `- ${r}`).join("\n") : d.importantRoutes}\n\n`;
474
+ if (d.sharedLibraries) md += `## Important Shared Libraries\n\n${Array.isArray(d.sharedLibraries) ? d.sharedLibraries.map(l => `- ${l}`).join("\n") : d.sharedLibraries}\n\n`;
475
+ md += `## Known Complexity Hotspots\n\n${Array.isArray(d.complexityHotspots) ? d.complexityHotspots.map(h => `- ${h}`).join("\n") : d.complexityHotspots}\n`;
476
+ return md;
477
+ }