@chappibunny/repolens 1.15.4 → 1.15.5
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 +29 -0
- package/README.md +9 -9
- package/llms-install.md +1 -0
- package/package.json +1 -1
- package/src/ai/generate-sections.js +130 -52
- package/src/analyzers/context-builder.js +96 -57
- package/src/analyzers/dependency-graph.js +59 -1
- package/src/analyzers/domain-inference.js +35 -4
- package/src/analyzers/flow-inference.js +27 -23
- package/src/analyzers/security-patterns.js +29 -3
- package/src/core/scan.js +50 -4
- package/src/docs/generate-doc-set.js +21 -0
- package/src/renderers/render.js +3 -3
- package/src/renderers/renderAnalysis.js +28 -9
- package/src/utils/path-roles.js +58 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RepoLens will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 1.15.5
|
|
6
|
+
|
|
7
|
+
Fixes twelve output problems found by running 1.15.4 on mattiasw/ExifReader (a plain-JavaScript library with a flat `src/` and a build-helper `bin/`), and the two leftovers from 1.15.4.
|
|
8
|
+
|
|
9
|
+
### Output accuracy
|
|
10
|
+
|
|
11
|
+
- A file directly under a module root belongs to that root. A flat `src/` is one module (`src`), not 65 one-file modules. Sub-directories are still their own modules. Go packages follow the same rule, so `middleware/` is one module and not `middleware/compress.go`
|
|
12
|
+
- Module fan-in and fan-out count distinct other modules, not file-to-file edges, so "Orchestrator (coordinates 212 modules)" becomes the real number
|
|
13
|
+
- A package with `main`, `module`, `exports`, or `types` is a **library**, not a command-line tool. A package with only a `bin` is a CLI. A package with both is a library unless its description does not say library, parser, SDK, toolkit, or framework and says CLI or command-line. Libraries get no "CLI tool architecture" pattern, no "Interface: CLI" row, and no "command-line interface" text. The route map note says library. Flows start at the package entry (`Library Flow`), not at `bin/` helpers or `webpack.config`
|
|
14
|
+
- Patterns are described by their name, not by the file names cited as evidence. "Acyclic dependency graph" no longer gets the CLI description. Every pattern has its own explanation and benefit line. The headline names the pattern, not its evidence
|
|
15
|
+
- `build-pages.js` is not a route module. Page, route, and view only count as directory names for files
|
|
16
|
+
- `.d.ts` files, type tests, and a `typescript` devDependency do not make a JavaScript library "TypeScript"
|
|
17
|
+
- `filter`, `parse`, and `constant` no longer pick the Search & Discovery, Analysis & Detection, and Configuration domains
|
|
18
|
+
- Hubs, coupling advice, and the dependency graph ignore tests, examples, docs, and build scripts, and are measured against source files. Hub threshold is 7% of source files (minimum 5)
|
|
19
|
+
- External packages are split into runtime and development and test tooling (`12 (1 runtime, 11 dev/test tooling)`), from the root `package.json`
|
|
20
|
+
- Security: `innerHTML = ''` and other fixed-string assignments are not reported. Findings in examples, docs, scripts, tools, benchmarks, and `build*.js` files are low severity and labeled "(build/example code)". Hardcoded credentials keep their severity
|
|
21
|
+
- Orphan files over 5% of code files are a warning and cost 5 health points (10 over 10%, 20 over 20%). Below 5% they read "within normal range". The old +5 bonus for a cycle-free graph is gone, so 100/100 no longer sits next to an orphan warning
|
|
22
|
+
- Route Map and GraphQL Schema are not written when no routes or GraphQL schema were found. Naming them in `documentation.sections` keeps them
|
|
23
|
+
- Tests, examples, and build helpers are no longer flows. Build configs such as `webpack.config` are not flow entry points
|
|
24
|
+
|
|
25
|
+
### 1.15.4 leftovers
|
|
26
|
+
|
|
27
|
+
- `lib/ogg` is typed and grouped by `ogg`, not by sitting under `lib/`. Folders with no keyword of their own join a **Core Source** domain and a "Source Modules" layer instead of "Shared Utilities" and "Shared Libraries". `lib/common` still reads as shared utilities
|
|
28
|
+
- Hub flows skip barrels (`lib/index`), leaf helpers such as error classes, tests, and examples, so there is no "lib Integration Flow"
|
|
29
|
+
|
|
30
|
+
### Docs
|
|
31
|
+
|
|
32
|
+
- README, AI.md, MIGRATION.md, KNOWN_ISSUES.md, ROADMAP.md, ARCHITECTURE.md, CONFIGURATION.md, and `.github/copilot-instructions.md` describe the new behavior
|
|
33
|
+
|
|
5
34
|
## 1.15.4
|
|
6
35
|
|
|
7
36
|
Fixes nine output problems found by running 1.15.3 on Borewit/music-metadata (a v12 TypeScript library).
|
package/README.md
CHANGED
|
@@ -100,7 +100,7 @@ Run `npx @chappibunny/repolens migrate` to automatically update your workflow fi
|
|
|
100
100
|
|
|
101
101
|
## 📋 What It Generates
|
|
102
102
|
|
|
103
|
-
**17 document types** for three audiences — no manual writing required:
|
|
103
|
+
**17 document types** for three audiences — no manual writing required. Route Map and GraphQL Schema are skipped when the repository has no routes or GraphQL schema:
|
|
104
104
|
|
|
105
105
|
| Audience | Documents |
|
|
106
106
|
|---|---|
|
|
@@ -241,9 +241,9 @@ If `npx` reports `Unknown command: mcp`, an `.npmrc` with `min-release-age` (or
|
|
|
241
241
|
|
|
242
242
|
## GitHub Action
|
|
243
243
|
|
|
244
|
-
`action.yml` is a composite action named **RepoLens Architecture Docs**. `publish`, `demo`, and `doctor` run the published npm package (`npx --yes @chappibunny/repolens@1.15.
|
|
244
|
+
`action.yml` is a composite action named **RepoLens Architecture Docs**. `publish`, `demo`, and `doctor` run the published npm package (`npx --yes @chappibunny/repolens@1.15.5`). `comment` and `badge` run from the action checkout. Inputs are `command` (`publish`, `demo`, `doctor`, `comment`, or `badge`), `config`, `node-version`, `pr-comment`, `version`, `badge-out`, `badge-url`, and `badge-link`.
|
|
245
245
|
|
|
246
|
-
Publishing docs (the npm package at tag `v1.15.
|
|
246
|
+
Publishing docs (the npm package at tag `v1.15.5`):
|
|
247
247
|
|
|
248
248
|
```yaml
|
|
249
249
|
name: Architecture docs
|
|
@@ -259,7 +259,7 @@ jobs:
|
|
|
259
259
|
runs-on: ubuntu-latest
|
|
260
260
|
steps:
|
|
261
261
|
- uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6.0.3
|
|
262
|
-
- uses: CHAPIBUNNY/repolens@v1.15.
|
|
262
|
+
- uses: CHAPIBUNNY/repolens@v1.15.5
|
|
263
263
|
with:
|
|
264
264
|
command: publish
|
|
265
265
|
config: .repolens.yml
|
|
@@ -267,7 +267,7 @@ jobs:
|
|
|
267
267
|
pr-comment: ${{ github.event_name == 'pull_request' && 'true' || 'false' }}
|
|
268
268
|
```
|
|
269
269
|
|
|
270
|
-
On `v1.15.2`, `pr-comment: true` runs the architecture diff from the action checkout and turns the published package's older short summary off. The diff lists modules, files, dependencies, the code-health delta, and new hotspots. It edits one sticky comment (`<!-- repolens:architecture-diff -->`) instead of adding a new comment each push. The footer is "Generated by RepoLens" and links to this repository and the [gallery](https://chapibunny.github.io/repolens/). Tag `v1.15.1` does not contain `command: comment`. Pin `v1.15.
|
|
270
|
+
On `v1.15.2`, `pr-comment: true` runs the architecture diff from the action checkout and turns the published package's older short summary off. The diff lists modules, files, dependencies, the code-health delta, and new hotspots. It edits one sticky comment (`<!-- repolens:architecture-diff -->`) instead of adding a new comment each push. The footer is "Generated by RepoLens" and links to this repository and the [gallery](https://chapibunny.github.io/repolens/). Tag `v1.15.1` does not contain `command: comment`. Pin `v1.15.5` or a commit SHA in production.
|
|
271
271
|
|
|
272
272
|
```yaml
|
|
273
273
|
name: RepoLens architecture comment
|
|
@@ -283,7 +283,7 @@ jobs:
|
|
|
283
283
|
- uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6.0.3
|
|
284
284
|
with:
|
|
285
285
|
fetch-depth: 0
|
|
286
|
-
- uses: CHAPIBUNNY/repolens@v1.15.
|
|
286
|
+
- uses: CHAPIBUNNY/repolens@v1.15.5
|
|
287
287
|
with:
|
|
288
288
|
command: comment
|
|
289
289
|
```
|
|
@@ -292,7 +292,7 @@ jobs:
|
|
|
292
292
|
|
|
293
293
|
`pr-comment: true` on `publish`, `demo`, or `doctor` (when the action ref contains this change) runs the same comment after the package and turns the package's older comment off, so the pull request still gets one comment.
|
|
294
294
|
|
|
295
|
-
The action is listed on the GitHub Marketplace only after a release is published with **Publish this Action to the GitHub Marketplace** checked. Those steps are in [docs/RELEASE.md](docs/RELEASE.md). The tag `v1.15.
|
|
295
|
+
The action is listed on the GitHub Marketplace only after a release is published with **Publish this Action to the GitHub Marketplace** checked. Those steps are in [docs/RELEASE.md](docs/RELEASE.md). The tag `v1.15.5` has to exist before `uses: CHAPIBUNNY/repolens@v1.15.5` resolves, and `@chappibunny/repolens@1.15.5` has to be on npm before `publish` can install it. `command: comment` and `command: badge` do not install that package.
|
|
296
296
|
|
|
297
297
|
## Add the badge to your repo
|
|
298
298
|
|
|
@@ -328,7 +328,7 @@ jobs:
|
|
|
328
328
|
runs-on: ubuntu-latest
|
|
329
329
|
steps:
|
|
330
330
|
- uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6.0.3
|
|
331
|
-
- uses: CHAPIBUNNY/repolens@v1.15.
|
|
331
|
+
- uses: CHAPIBUNNY/repolens@v1.15.5
|
|
332
332
|
with:
|
|
333
333
|
command: badge
|
|
334
334
|
badge-url: https://USER.github.io/REPO/repolens-badge.json
|
|
@@ -357,7 +357,7 @@ Gallery repositories already publish this. Each one has a [score page](https://c
|
|
|
357
357
|
|
|
358
358
|
Published system maps are Unicode diagrams drawn from import edges. RepoLens does not emit Mermaid.
|
|
359
359
|
|
|
360
|
-
The publisher set is Notion, Confluence, GitHub Wiki, Obsidian, and Markdown (`src/publishers`, 7 files). 1.14.0 adds the MCP server, the Marketplace action, and the docs badge. 1.14.1 sets the registry name to `io.github.CHAPIBUNNY/repolens`. 1.14.2 adds the gallery link to the badge line, `glama.json`, and `CITATION.cff`. 1.14.3 adds Cline setup docs and `llms-install.md`. 1.15.0 detects repository languages in `init --quick`, refuses empty scans, and adds per-command help. 1.15.1 makes `watch` exit 2 when those globs match nothing, and scores code health for Python and Go as well as JavaScript and TypeScript. 1.15.2 reads Python and Go imports in the dependency graph, skips comment and type-only imports, adds `repolens badge` and `repolens comment`, and adds gallery explained pages plus `llms.txt`. 1.15.3 skips Python `TYPE_CHECKING` imports, omits Node.js built-ins from external packages, groups Go hubs by package, and ignores test files as architecture-pattern evidence. 1.15.4 names the project from `package.json`, scores maturity from version, history, tests, CI, and docs, reports circular dependencies as findings, drops self-imports and noise data flows, and stops classifying `index` and `core` files by name. Layout and counts are in [ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
360
|
+
The publisher set is Notion, Confluence, GitHub Wiki, Obsidian, and Markdown (`src/publishers`, 7 files). 1.14.0 adds the MCP server, the Marketplace action, and the docs badge. 1.14.1 sets the registry name to `io.github.CHAPIBUNNY/repolens`. 1.14.2 adds the gallery link to the badge line, `glama.json`, and `CITATION.cff`. 1.14.3 adds Cline setup docs and `llms-install.md`. 1.15.0 detects repository languages in `init --quick`, refuses empty scans, and adds per-command help. 1.15.1 makes `watch` exit 2 when those globs match nothing, and scores code health for Python and Go as well as JavaScript and TypeScript. 1.15.2 reads Python and Go imports in the dependency graph, skips comment and type-only imports, adds `repolens badge` and `repolens comment`, and adds gallery explained pages plus `llms.txt`. 1.15.3 skips Python `TYPE_CHECKING` imports, omits Node.js built-ins from external packages, groups Go hubs by package, and ignores test files as architecture-pattern evidence. 1.15.4 names the project from `package.json`, scores maturity from version, history, tests, CI, and docs, reports circular dependencies as findings, drops self-imports and noise data flows, and stops classifying `index` and `core` files by name. 1.15.5 reads a flat `src/` as one module, tells a library from a CLI by its `package.json`, splits runtime from development packages, keeps tests, examples, and build scripts out of hubs and security severity, and skips empty Route Map and GraphQL pages. Layout and counts are in [ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
361
361
|
|
|
362
362
|
### System Overview (Technical Profile)
|
|
363
363
|
|
package/llms-install.md
CHANGED
|
@@ -20,3 +20,4 @@
|
|
|
20
20
|
11. CLI (1.15.2): the dependency graph includes Python and Go imports. Comments, strings, and TypeScript type-only imports are not edges. Go `*_test.go` files are not separate modules. Pattern text cites the matching path or import. "Custom React hooks" requires a `react` import.
|
|
21
21
|
12. CLI (1.15.3): Python imports under `if TYPE_CHECKING:` (including `typing.TYPE_CHECKING` and `import typing as t`) are not cycle edges. Node.js built-ins are not external packages. Go hub rows are packages. Test files are not architecture-pattern evidence, and "API route pattern" requires a route directory.
|
|
22
22
|
13. CLI (1.15.4): the project name comes from `package.json` or the real directory (never `.`). Maturity uses version, history, tests, CI, and docs. Circular dependencies are findings, not architectural patterns. A package's own name is not an external dependency. `index` and `main` files are not classified by name. Noise data flows are dropped.
|
|
23
|
+
14. CLI (1.15.5): a flat `src/` is one module. A package with `main`, `module`, or `exports` is a library, not a CLI. Hubs and security severity ignore tests, examples, and build scripts. External packages are split into runtime and development tooling. Route Map and GraphQL Schema are skipped when empty.
|
package/package.json
CHANGED
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
renderStructuredToMarkdown,
|
|
14
14
|
} from "./prompts.js";
|
|
15
15
|
import { identifyFlowDependencies, filterNoiseFlows } from "../analyzers/flow-inference.js";
|
|
16
|
+
import { isCliProject, isLibraryProject } from "../analyzers/context-builder.js";
|
|
16
17
|
import { scoreMaturity } from "../analyzers/maturity.js";
|
|
18
|
+
import { isNonProductPath, isTestPath } from "../utils/path-roles.js";
|
|
17
19
|
import { info, warn } from "../utils/logger.js";
|
|
18
20
|
|
|
19
21
|
// Strip conversational patterns that LLMs sometimes inject into documentation
|
|
@@ -162,7 +164,8 @@ function getFallbackExecutiveSummary(context, enrichment = {}) {
|
|
|
162
164
|
const frameworkList = context.techStack.frameworks.join(", ") || "general-purpose";
|
|
163
165
|
const languageList = context.techStack.languages.join(", ") || "multiple languages";
|
|
164
166
|
const testFrameworks = context.techStack.testFrameworks || [];
|
|
165
|
-
const isCLI = (context
|
|
167
|
+
const isCLI = isCliProject(context);
|
|
168
|
+
const isLibrary = isLibraryProject(context);
|
|
166
169
|
const projectName = context.project.name || "This system";
|
|
167
170
|
|
|
168
171
|
// Maturity comes from repo signals (version, history, tests, CI, docs, dependency health)
|
|
@@ -178,7 +181,8 @@ function getFallbackExecutiveSummary(context, enrichment = {}) {
|
|
|
178
181
|
const maturityMax = maturity.max;
|
|
179
182
|
|
|
180
183
|
// Determine project type for better descriptions
|
|
181
|
-
const projectType = isCLI ? "command-line tool" :
|
|
184
|
+
const projectType = isCLI ? "command-line tool" :
|
|
185
|
+
isLibrary ? "library" :
|
|
182
186
|
(context.project.apiRoutesDetected > 0 && context.project.pagesDetected > 0) ? "full-stack application" :
|
|
183
187
|
context.project.apiRoutesDetected > 0 ? "API service" :
|
|
184
188
|
context.project.pagesDetected > 0 ? "web application" : "software system";
|
|
@@ -190,7 +194,7 @@ function getFallbackExecutiveSummary(context, enrichment = {}) {
|
|
|
190
194
|
| Aspect | Summary |
|
|
191
195
|
|--------|---------|
|
|
192
196
|
| **What** | ${projectName} is ${withArticle(maturityLabel.toLowerCase())} ${projectType} |
|
|
193
|
-
| **Built With** | ${
|
|
197
|
+
| **Built With** | ${[...context.techStack.frameworks, languageList].join(", ")} |
|
|
194
198
|
| **Scale** | ${context.project.modulesDetected} modules across ${context.project.filesScanned} files |
|
|
195
199
|
| **Maturity** | ${maturityLabel} (${maturityScore}/${maturityMax} indicators) |
|
|
196
200
|
|
|
@@ -202,6 +206,8 @@ function getFallbackExecutiveSummary(context, enrichment = {}) {
|
|
|
202
206
|
|
|
203
207
|
if (isCLI) {
|
|
204
208
|
output += `It operates as a **command-line interface**, enabling users to interact with the system through terminal commands. This architecture is optimized for automation, scripting, and integration into CI/CD pipelines.`;
|
|
209
|
+
} else if (isLibrary) {
|
|
210
|
+
output += `It is a **library**: other programs import it as a dependency and call its public API. It serves no routes or pages of its own.`;
|
|
205
211
|
} else if (context.project.apiRoutesDetected > 0 && context.project.pagesDetected > 0) {
|
|
206
212
|
output += `It provides both a **user-facing interface** (${context.project.pagesDetected} pages) and a **programmatic API** (${context.project.apiRoutesDetected} endpoints), serving as a complete solution for end users and system integrations alike.`;
|
|
207
213
|
} else if (context.project.apiRoutesDetected > 0) {
|
|
@@ -222,7 +228,7 @@ The system is organized around **${context.domains.length} functional domains**,
|
|
|
222
228
|
output += `|--------|------------|-------|\n`;
|
|
223
229
|
for (const d of topDomains) {
|
|
224
230
|
const desc = d.description || inferDomainCapability(d.name);
|
|
225
|
-
output += `| **${d.name}** | ${desc} | ${d.moduleCount}
|
|
231
|
+
output += `| **${d.name}** | ${desc} | ${d.moduleCount} module${d.moduleCount === 1 ? "" : "s"}, ${d.fileCount} file${d.fileCount === 1 ? "" : "s"} |\n`;
|
|
226
232
|
}
|
|
227
233
|
if (context.domains.length > 6) {
|
|
228
234
|
output += `| *+${context.domains.length - 6} more* | Additional functional areas | — |\n`;
|
|
@@ -281,9 +287,9 @@ The system is organized around **${context.domains.length} functional domains**,
|
|
|
281
287
|
output += `| Metric | Value | Status |\n`;
|
|
282
288
|
output += `|--------|-------|--------|\n`;
|
|
283
289
|
output += `| Internal imports | ${stats.totalEdges} edges | ${stats.totalEdges > 0 ? "✅ Connected" : "⚠️ Isolated"} |\n`;
|
|
284
|
-
output += `| External Packages | ${stats
|
|
290
|
+
output += `| External Packages | ${externalPackagesLabel(stats)} | ${stats.externalDeps < 50 ? "✅ Manageable" : stats.externalDeps < 100 ? "⚠️ Consider audit" : "🔴 High dependency count"} |\n`;
|
|
285
291
|
output += `| Circular Dependencies | ${stats.cycles === 0 ? "cycle-free" : stats.cycles} | ${stats.cycles === 0 ? "✅ None" : `🔴 ${stats.cycles} detected`} |\n`;
|
|
286
|
-
output += `| Orphan Files | ${stats.orphanFiles} | ${stats
|
|
292
|
+
output += `| Orphan Files | ${stats.orphanFiles} | ${orphanStatus(stats)} |\n`;
|
|
287
293
|
|
|
288
294
|
// Key insights
|
|
289
295
|
output += `\n### Key Insights\n\n`;
|
|
@@ -294,19 +300,19 @@ The system is organized around **${context.domains.length} functional domains**,
|
|
|
294
300
|
}
|
|
295
301
|
if (stats.hubs && stats.hubs.length > 0) {
|
|
296
302
|
const topHub = stats.hubs[0];
|
|
297
|
-
if (topHub.importedBy > stats
|
|
298
|
-
output += `- ⚠️ **Coupling hotspot** — \`${topHub.key}\` is imported by ${topHub.importedBy} files (${Math.round(topHub.importedBy / stats
|
|
303
|
+
if (topHub.importedBy > sourceFileCount(stats) * 0.3) {
|
|
304
|
+
output += `- ⚠️ **Coupling hotspot** — \`${topHub.key}\` is imported by ${topHub.importedBy} files (${Math.round(topHub.importedBy / sourceFileCount(stats) * 100)}% of codebase). Consider splitting.\n`;
|
|
299
305
|
} else {
|
|
300
306
|
output += `- ✅ **Healthy coupling** — Most-imported module (\`${topHub.key}\`) serves ${topHub.importedBy} files without excessive centralization.\n`;
|
|
301
307
|
}
|
|
302
308
|
}
|
|
303
|
-
if (stats
|
|
309
|
+
if (orphanRatio(stats) > ORPHAN_WARN_RATIO) {
|
|
304
310
|
output += `- ⚠️ **Potential dead code** — ${stats.orphanFiles} files (${Math.round(stats.orphanFiles / stats.totalFiles * 100)}%) are not imported anywhere.\n`;
|
|
305
311
|
}
|
|
306
312
|
}
|
|
307
313
|
|
|
308
314
|
// Data flows summary
|
|
309
|
-
const summaryFlows = filterNoiseFlows(flows).filter(f => !f.name?.toLowerCase().includes('test'));
|
|
315
|
+
const summaryFlows = filterNoiseFlows(flows).filter(f => !f.name?.toLowerCase().includes('test') && !isTestPath(f.modules?.[0]));
|
|
310
316
|
if (summaryFlows.length > 0) {
|
|
311
317
|
output += `\n## Key Data Flows\n\n`;
|
|
312
318
|
output += `The system processes information through **${summaryFlows.length} identified flow${summaryFlows.length === 1 ? "" : "s"}**:\n\n`;
|
|
@@ -321,7 +327,7 @@ The system is organized around **${context.domains.length} functional domains**,
|
|
|
321
327
|
|
|
322
328
|
const observations = [];
|
|
323
329
|
if (context.patterns.length > 0) {
|
|
324
|
-
observations.push(`The codebase follows **${context.patterns.join(", ")}** architectural patterns, providing a familiar structure for developers.`);
|
|
330
|
+
observations.push(`The codebase follows **${context.patterns.map(patternLabel).join(", ")}** architectural patterns, providing a familiar structure for developers.`);
|
|
325
331
|
}
|
|
326
332
|
if (context.domains.length >= 5) {
|
|
327
333
|
observations.push(`With ${context.domains.length} distinct domains, the system has clear separation of concerns suitable for team-based development.`);
|
|
@@ -331,6 +337,8 @@ The system is organized around **${context.domains.length} functional domains**,
|
|
|
331
337
|
}
|
|
332
338
|
if (isCLI) {
|
|
333
339
|
observations.push(`As a CLI tool, this system is automation-friendly and can integrate into existing workflows and pipelines.`);
|
|
340
|
+
} else if (isLibrary) {
|
|
341
|
+
observations.push(`As a library, its public API is the contract: changes to exported functions affect every project that depends on it.`);
|
|
334
342
|
}
|
|
335
343
|
|
|
336
344
|
for (const obs of observations) {
|
|
@@ -347,7 +355,8 @@ The system is organized around **${context.domains.length} functional domains**,
|
|
|
347
355
|
function getFallbackSystemOverview(context, enrichment = {}) {
|
|
348
356
|
const { depGraph } = enrichment;
|
|
349
357
|
const testFrameworks = context.techStack.testFrameworks || [];
|
|
350
|
-
const isCLI = (context
|
|
358
|
+
const isCLI = isCliProject(context);
|
|
359
|
+
const isLibrary = isLibraryProject(context);
|
|
351
360
|
const projectName = context.project.name || "This system";
|
|
352
361
|
|
|
353
362
|
// Calculate scale classification
|
|
@@ -381,6 +390,8 @@ function getFallbackSystemOverview(context, enrichment = {}) {
|
|
|
381
390
|
}
|
|
382
391
|
if (isCLI) {
|
|
383
392
|
output += `\n| **Interface Type** | Command-Line Tool |`;
|
|
393
|
+
} else if (isLibrary) {
|
|
394
|
+
output += `\n| **Interface Type** | Library (imported API) |`;
|
|
384
395
|
}
|
|
385
396
|
|
|
386
397
|
output += `
|
|
@@ -515,9 +526,9 @@ The system is built on a modern technology foundation:\n\n`;
|
|
|
515
526
|
output += `| Metric | Value | Assessment |\n`;
|
|
516
527
|
output += `|--------|-------|------------|\n`;
|
|
517
528
|
output += `| Internal Imports | ${stats.totalEdges} | ${stats.totalEdges > 0 ? "Modules are connected" : "No internal imports"} |\n`;
|
|
518
|
-
output += `| External Packages | ${stats
|
|
529
|
+
output += `| External Packages | ${externalPackagesLabel(stats)} | ${stats.externalDeps < 50 ? "Lean dependencies" : stats.externalDeps < 100 ? "Moderate dependencies" : "Many dependencies"} |\n`;
|
|
519
530
|
output += `| Circular Dependencies | ${stats.cycles} | ${stats.cycles === 0 ? "✅ Clean" : "⚠️ Needs attention"} |\n`;
|
|
520
|
-
output += `| Orphan Files | ${stats.orphanFiles} | ${stats
|
|
531
|
+
output += `| Orphan Files | ${stats.orphanFiles} | ${orphanStatus(stats)} |\n`;
|
|
521
532
|
|
|
522
533
|
if (stats.hubs && stats.hubs.length > 0) {
|
|
523
534
|
output += `\n**Hub modules** (most imported):\n\n`;
|
|
@@ -682,7 +693,8 @@ ${context.domains.map(d => `| ${d.name} | ${d.moduleCount} | ${d.fileCount} | ${
|
|
|
682
693
|
|
|
683
694
|
function getFallbackArchitectureOverview(context, enrichment = {}) {
|
|
684
695
|
const { depGraph, driftResult } = enrichment;
|
|
685
|
-
const isCLI = (context
|
|
696
|
+
const isCLI = isCliProject(context);
|
|
697
|
+
const isLibrary = isLibraryProject(context);
|
|
686
698
|
const projectName = context.project.name || "This system";
|
|
687
699
|
const testFrameworks = context.techStack.testFrameworks || [];
|
|
688
700
|
|
|
@@ -693,9 +705,9 @@ function getFallbackArchitectureOverview(context, enrichment = {}) {
|
|
|
693
705
|
`;
|
|
694
706
|
|
|
695
707
|
if (context.patterns.length > 0) {
|
|
696
|
-
output += `**${projectName}** follows a **${context.patterns[0]}** architecture`;
|
|
708
|
+
output += `**${projectName}** follows a **${patternLabel(context.patterns[0])}** architecture`;
|
|
697
709
|
if (context.patterns.length > 1) {
|
|
698
|
-
output += ` with elements of **${context.patterns.slice(1).join(", ")}**`;
|
|
710
|
+
output += ` with elements of **${context.patterns.slice(1).map(patternLabel).join(", ")}**`;
|
|
699
711
|
}
|
|
700
712
|
output += `.\n\n`;
|
|
701
713
|
|
|
@@ -799,6 +811,8 @@ function getFallbackArchitectureOverview(context, enrichment = {}) {
|
|
|
799
811
|
}
|
|
800
812
|
if (isCLI) {
|
|
801
813
|
output += `| **Interface** | CLI | Terminal-driven interaction |\n`;
|
|
814
|
+
} else if (isLibrary) {
|
|
815
|
+
output += `| **Interface** | Library | Imported by other programs through its public API |\n`;
|
|
802
816
|
}
|
|
803
817
|
|
|
804
818
|
// Dependency health with deep analysis
|
|
@@ -813,9 +827,9 @@ function getFallbackArchitectureOverview(context, enrichment = {}) {
|
|
|
813
827
|
output += `| Metric | Value | Status | Recommendation |\n`;
|
|
814
828
|
output += `|--------|-------|--------|----------------|\n`;
|
|
815
829
|
output += `| Import Edges | ${stats.totalEdges} | ${stats.totalEdges > 0 ? "✅" : "⚠️"} | ${stats.totalEdges > 0 ? "Healthy module connectivity" : "Consider connecting modules"} |\n`;
|
|
816
|
-
output += `| External Packages | ${stats
|
|
830
|
+
output += `| External Packages | ${externalPackagesLabel(stats)} | ${stats.externalDeps < 50 ? "✅" : stats.externalDeps < 100 ? "⚠️" : "🔴"} | ${stats.externalDeps >= 100 ? "Audit for unused dependencies" : stats.externalDeps >= 50 ? "Review dependency necessity" : "Lean dependency set"} |\n`;
|
|
817
831
|
output += `| Circular Deps | ${stats.cycles} | ${stats.cycles === 0 ? "✅" : "🔴"} | ${stats.cycles === 0 ? "Clean module boundaries" : "Refactor to break cycles"} |\n`;
|
|
818
|
-
output += `| Orphan Files | ${stats.orphanFiles} | ${stats
|
|
832
|
+
output += `| Orphan Files | ${stats.orphanFiles} | ${orphanRatio(stats) > ORPHAN_WARN_RATIO ? "⚠️" : "✅"} | ${stats.orphanFiles === 0 ? "All code is connected" : orphanRatio(stats) > ORPHAN_WARN_RATIO ? "Review for dead code" : "A few unconnected files (examples, type tests, entry points) are normal"} |\n`;
|
|
819
833
|
|
|
820
834
|
// Architectural strengths and concerns
|
|
821
835
|
output += `\n### Architectural Strengths\n\n`;
|
|
@@ -826,7 +840,7 @@ function getFallbackArchitectureOverview(context, enrichment = {}) {
|
|
|
826
840
|
if (stats.orphanFiles === 0) {
|
|
827
841
|
strengths.push("**Fully connected codebase** — Every file is part of the dependency graph. No dead code or forgotten modules.");
|
|
828
842
|
}
|
|
829
|
-
if (stats.hubs && stats.hubs.length > 0 && stats.hubs[0].importedBy < stats
|
|
843
|
+
if (stats.hubs && stats.hubs.length > 0 && stats.hubs[0].importedBy < sourceFileCount(stats) * 0.3) {
|
|
830
844
|
strengths.push("**Balanced coupling** — No single module dominates the import graph. This reduces change risk and enables parallel development.");
|
|
831
845
|
}
|
|
832
846
|
if (testFrameworks.length > 0) {
|
|
@@ -848,8 +862,8 @@ function getFallbackArchitectureOverview(context, enrichment = {}) {
|
|
|
848
862
|
if (stats.orphanFiles > stats.totalFiles * 0.2) {
|
|
849
863
|
concerns.push(`**High orphan file ratio** — ${stats.orphanFiles}/${stats.totalFiles} files (${Math.round(stats.orphanFiles / stats.totalFiles * 100)}%) are not imported. Consider removing dead code. Priority: Medium.`);
|
|
850
864
|
}
|
|
851
|
-
if (stats.hubs && stats.hubs.length > 0 && stats.hubs[0].importedBy >= stats
|
|
852
|
-
concerns.push(`**Central coupling hotspot** — \`${stats.hubs[0].key}\` is imported by ${stats.hubs[0].importedBy} files (${Math.round(stats.hubs[0].importedBy / stats
|
|
865
|
+
if (stats.hubs && stats.hubs.length > 0 && stats.hubs[0].importedBy >= sourceFileCount(stats) * 0.3) {
|
|
866
|
+
concerns.push(`**Central coupling hotspot** — \`${stats.hubs[0].key}\` is imported by ${stats.hubs[0].importedBy} files (${Math.round(stats.hubs[0].importedBy / sourceFileCount(stats) * 100)}%). Consider breaking into smaller modules. Priority: Medium.`);
|
|
853
867
|
}
|
|
854
868
|
if (stats.externalDeps > 100) {
|
|
855
869
|
concerns.push(`**Heavy external dependencies** — ${stats.externalDeps} packages increase security surface and maintenance burden. Audit regularly. Priority: Low.`);
|
|
@@ -932,7 +946,7 @@ Data flows describe how information moves through **${projectName}** — from ex
|
|
|
932
946
|
`;
|
|
933
947
|
|
|
934
948
|
// Combine heuristic flows with dep-graph-derived flows, filtering out test file flows
|
|
935
|
-
const allFlows = filterNoiseFlows(flows).filter(f => !f.name?.toLowerCase().includes('test'));
|
|
949
|
+
const allFlows = filterNoiseFlows(flows).filter(f => !f.name?.toLowerCase().includes('test') && !isTestPath(f.modules?.[0]));
|
|
936
950
|
|
|
937
951
|
// Generate additional flows from dependency graph hub chains
|
|
938
952
|
if (depGraph?.nodes && depGraph.nodes.length > 0 && allFlows.length < 3) {
|
|
@@ -1135,7 +1149,7 @@ function getFallbackDeveloperOnboarding(context, enrichment = {}) {
|
|
|
1135
1149
|
const frameworkList = context.techStack.frameworks.join(", ") || "general-purpose tools";
|
|
1136
1150
|
const languageList = context.techStack.languages.join(", ") || "standard languages";
|
|
1137
1151
|
const testFrameworks = context.techStack.testFrameworks || [];
|
|
1138
|
-
const isCLI = (context
|
|
1152
|
+
const isCLI = isCliProject(context);
|
|
1139
1153
|
const projectName = context.project.name || "this project";
|
|
1140
1154
|
const routes = context.routes || {};
|
|
1141
1155
|
const pages = routes.pages || [];
|
|
@@ -1166,7 +1180,7 @@ This guide will help you get up to speed quickly. **${projectName}** is a ${fram
|
|
|
1166
1180
|
| **Build Tools** | ${context.techStack.buildTools.join(", ") || "Standard toolchain"} |
|
|
1167
1181
|
${testFrameworks.length > 0 ? `| **Testing** | ${testFrameworks.join(", ")} |\n` : ""}| **Size** | ${context.project.modulesDetected} modules, ${context.project.filesScanned} files |
|
|
1168
1182
|
| **Domains** | ${context.domains.length} functional areas |
|
|
1169
|
-
${isCLI ? "| **Type** | Command-line interface |\n" : ""}${context.project.apiRoutesDetected > 0 ? `| **API** | ${context.project.apiRoutesDetected} endpoints |\n` : ""}${context.project.pagesDetected > 0 ? `| **UI** | ${context.project.pagesDetected} pages |\n` : ""}
|
|
1183
|
+
${isCLI ? "| **Type** | Command-line interface |\n" : isLibraryProject(context) ? "| **Type** | Library |\n" : ""}${context.project.apiRoutesDetected > 0 ? `| **API** | ${context.project.apiRoutesDetected} endpoints |\n` : ""}${context.project.pagesDetected > 0 ? `| **UI** | ${context.project.pagesDetected} pages |\n` : ""}
|
|
1170
1184
|
|
|
1171
1185
|
---
|
|
1172
1186
|
|
|
@@ -1292,7 +1306,7 @@ ${context.repoRoots.map(root => {
|
|
|
1292
1306
|
}
|
|
1293
1307
|
|
|
1294
1308
|
// Data flows for understanding behavior
|
|
1295
|
-
const onboardingFlows = filterNoiseFlows(flows).filter(f => !f.name?.toLowerCase().includes('test'));
|
|
1309
|
+
const onboardingFlows = filterNoiseFlows(flows).filter(f => !f.name?.toLowerCase().includes('test') && !isTestPath(f.modules?.[0]));
|
|
1296
1310
|
if (onboardingFlows.length > 0) {
|
|
1297
1311
|
output += `---\n\n## How Data Flows\n\n`;
|
|
1298
1312
|
output += `Understanding these flows helps you see how the system processes information end-to-end:\n\n`;
|
|
@@ -1354,8 +1368,8 @@ ${context.repoRoots.map(root => {
|
|
|
1354
1368
|
if (depGraph?.stats?.cycles > 0) {
|
|
1355
1369
|
pitfalls.push(`**Circular dependencies** — This codebase has ${depGraph.stats.cycles} circular import${depGraph.stats.cycles === 1 ? '' : 's'}. Be careful not to introduce more.`);
|
|
1356
1370
|
}
|
|
1357
|
-
if (depGraph?.stats?.hubs?.length > 0 && depGraph.stats.hubs[0].importedBy >= depGraph.stats
|
|
1358
|
-
pitfalls.push(`**High-impact modules** — \`${depGraph.stats.hubs[0].key.split("/").pop()}\` is used by ${Math.round(depGraph.stats.hubs[0].importedBy / depGraph.stats
|
|
1371
|
+
if (depGraph?.stats?.hubs?.length > 0 && depGraph.stats.hubs[0].importedBy >= sourceFileCount(depGraph.stats) * 0.3) {
|
|
1372
|
+
pitfalls.push(`**High-impact modules** — \`${depGraph.stats.hubs[0].key.split("/").pop()}\` is used by ${Math.round(depGraph.stats.hubs[0].importedBy / sourceFileCount(depGraph.stats) * 100)}% of the codebase. Changes here need thorough testing.`);
|
|
1359
1373
|
}
|
|
1360
1374
|
if (context.monorepo) {
|
|
1361
1375
|
pitfalls.push(`**Monorepo boundaries** — Remember to build dependent packages when making changes.`);
|
|
@@ -1409,38 +1423,46 @@ function formatModuleType(type) {
|
|
|
1409
1423
|
state: "State Management",
|
|
1410
1424
|
route: "Route/Page Modules",
|
|
1411
1425
|
app: "Application Core",
|
|
1426
|
+
source: "Source Modules",
|
|
1412
1427
|
other: "Other Modules",
|
|
1413
1428
|
};
|
|
1414
1429
|
return labels[type] || type;
|
|
1415
1430
|
}
|
|
1416
1431
|
|
|
1417
|
-
//
|
|
1432
|
+
// File names that name a folder or an entry point rather than a piece of the system.
|
|
1433
|
+
const BARREL_NAMES = new Set(["index", "main", "mod", "__init__", "lib", "src", "app", "source", "pkg", "internal"]);
|
|
1434
|
+
|
|
1435
|
+
// Helper: derive flows from dependency graph hub chains.
|
|
1436
|
+
// A hub is an integration point only when it imports something itself. Barrels (`lib/index`),
|
|
1437
|
+
// leaf helpers (error classes, constants), tests, and examples are not flows.
|
|
1418
1438
|
function inferFlowsFromDepGraph(depGraph) {
|
|
1419
1439
|
const flows = [];
|
|
1420
1440
|
if (!depGraph?.nodes || depGraph.nodes.length === 0) return flows;
|
|
1421
1441
|
|
|
1422
|
-
|
|
1423
|
-
const hubThreshold = Math.max(3, Math.floor(
|
|
1424
|
-
const hubs =
|
|
1425
|
-
.filter(n => n.
|
|
1426
|
-
.
|
|
1442
|
+
const productNodes = depGraph.nodes.filter(n => !isNonProductPath(n.key));
|
|
1443
|
+
const hubThreshold = Math.max(3, Math.floor(productNodes.length * 0.05));
|
|
1444
|
+
const hubs = productNodes
|
|
1445
|
+
.filter(n => !BARREL_NAMES.has(String(n.key.split("/").pop()).toLowerCase()))
|
|
1446
|
+
.filter(n => n.imports.length > 0)
|
|
1447
|
+
.map(n => ({ node: n, importers: n.importedBy.filter(i => !isNonProductPath(i)) }))
|
|
1448
|
+
.filter(h => h.importers.length >= hubThreshold)
|
|
1449
|
+
.sort((a, b) => b.importers.length - a.importers.length)
|
|
1427
1450
|
.slice(0, 3);
|
|
1428
1451
|
|
|
1429
|
-
for (const hub of hubs) {
|
|
1430
|
-
const
|
|
1431
|
-
const importers = hub.importedBy.filter(i => !testPattern.test(i)).slice(0, 5);
|
|
1452
|
+
for (const { node: hub, importers: allImporters } of hubs) {
|
|
1453
|
+
const importers = allImporters.slice(0, 5);
|
|
1432
1454
|
const downstream = hub.imports.slice(0, 3);
|
|
1433
1455
|
const shortName = hub.key.split("/").pop();
|
|
1434
1456
|
|
|
1435
1457
|
flows.push({
|
|
1436
1458
|
name: `${shortName} Integration Flow`,
|
|
1437
|
-
description: `\`${hub.key}\` is a central module imported by ${
|
|
1459
|
+
description: `\`${hub.key}\` is a central module imported by ${allImporters.length} files, acting as an integration point for data and logic`,
|
|
1438
1460
|
steps: [
|
|
1439
1461
|
...importers.map(i => `\`${i.split("/").pop()}\` imports \`${shortName}\``),
|
|
1440
1462
|
...(downstream.length > 0 ? [`\`${shortName}\` depends on ${downstream.map(d => `\`${d.split("/").pop()}\``).join(", ")}`] : []),
|
|
1441
1463
|
],
|
|
1442
1464
|
modules: [hub.key, ...importers.slice(0, 3)],
|
|
1443
|
-
critical:
|
|
1465
|
+
critical: allImporters.length >= hubThreshold * 2,
|
|
1444
1466
|
});
|
|
1445
1467
|
}
|
|
1446
1468
|
|
|
@@ -1566,11 +1588,39 @@ function getLayerResponsibility(type) {
|
|
|
1566
1588
|
state: "Manages application-wide data synchronization",
|
|
1567
1589
|
route: "Maps URL paths to page components and handles navigation",
|
|
1568
1590
|
app: "Orchestrates application initialization and core bootstrapping",
|
|
1591
|
+
source: "The project's own source code",
|
|
1569
1592
|
other: "General purpose functionality",
|
|
1570
1593
|
};
|
|
1571
1594
|
return responsibilities[type] || "Supporting application functionality";
|
|
1572
1595
|
}
|
|
1573
1596
|
|
|
1597
|
+
// Orphan files above this share of all code files are a warning, and they cost health points.
|
|
1598
|
+
// Below it they are normal (generated entry points, type tests, examples) and shown as fine.
|
|
1599
|
+
const ORPHAN_WARN_RATIO = 0.05;
|
|
1600
|
+
|
|
1601
|
+
function orphanRatio(stats) {
|
|
1602
|
+
return stats.orphanFiles / Math.max(stats.totalFiles, 1);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
function orphanStatus(stats) {
|
|
1606
|
+
if (stats.orphanFiles === 0) return "✅ All connected";
|
|
1607
|
+
const pct = Math.round(orphanRatio(stats) * 100);
|
|
1608
|
+
return orphanRatio(stats) > ORPHAN_WARN_RATIO
|
|
1609
|
+
? `⚠️ ${stats.orphanFiles} unused (${pct}%)`
|
|
1610
|
+
: `✅ ${stats.orphanFiles} (${pct}%), within normal range`;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
// Files the product itself is made of. Hub shares are measured against these, not against tests.
|
|
1614
|
+
function sourceFileCount(stats) {
|
|
1615
|
+
return Math.max(stats.sourceFiles ?? stats.totalFiles, 1);
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// "12 (1 runtime, 11 development tooling)" when the split is known.
|
|
1619
|
+
function externalPackagesLabel(stats) {
|
|
1620
|
+
if (stats.externalRuntime === undefined || stats.externalDev === undefined || stats.externalDev === 0) return `${stats.externalDeps}`;
|
|
1621
|
+
return `${stats.externalDeps} (${stats.externalRuntime} runtime, ${stats.externalDev} dev/test tooling)`;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1574
1624
|
// Helper: calculate codebase health score
|
|
1575
1625
|
function calculateHealthScore(stats, context) {
|
|
1576
1626
|
let score = 100;
|
|
@@ -1579,9 +1629,10 @@ function calculateHealthScore(stats, context) {
|
|
|
1579
1629
|
if (stats.cycles > 0) score -= Math.min(30, stats.cycles * 10);
|
|
1580
1630
|
|
|
1581
1631
|
// Orphan files indicate potential dead code
|
|
1582
|
-
const
|
|
1583
|
-
if (
|
|
1584
|
-
else if (
|
|
1632
|
+
const ratio = orphanRatio(stats);
|
|
1633
|
+
if (ratio > 0.2) score -= 20;
|
|
1634
|
+
else if (ratio > 0.1) score -= 10;
|
|
1635
|
+
else if (ratio > ORPHAN_WARN_RATIO) score -= 5;
|
|
1585
1636
|
|
|
1586
1637
|
// Too many external dependencies
|
|
1587
1638
|
if (stats.externalDeps > 100) score -= 15;
|
|
@@ -1591,9 +1642,6 @@ function calculateHealthScore(stats, context) {
|
|
|
1591
1642
|
const hasTests = (context.techStack.testFrameworks || []).length > 0;
|
|
1592
1643
|
if (!hasTests) score -= 10;
|
|
1593
1644
|
|
|
1594
|
-
// Give bonus for clean, connected codebase
|
|
1595
|
-
if (stats.cycles === 0 && stats.totalEdges > 0) score = Math.min(100, score + 5);
|
|
1596
|
-
|
|
1597
1645
|
return Math.max(0, Math.min(100, score));
|
|
1598
1646
|
}
|
|
1599
1647
|
|
|
@@ -1642,10 +1690,40 @@ function inferFrameworkPurpose(frameworks) {
|
|
|
1642
1690
|
return purposes.length > 0 ? purposes.join(", ") : "Application runtime";
|
|
1643
1691
|
}
|
|
1644
1692
|
|
|
1693
|
+
// A pattern line is "Label — evidence" (`CLI tool architecture — \`bin\``). Only the label
|
|
1694
|
+
// says what the pattern is; the evidence is file names and must not pick the description.
|
|
1695
|
+
function patternLabel(pattern) {
|
|
1696
|
+
return String(pattern).split(" — ")[0].trim();
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
const PATTERN_COPY = [
|
|
1700
|
+
[/^next\.?js app router/i, "Routes are defined by folders under `app/`, with server and client components mixed per route.", "File-based routing, nested layouts, streaming"],
|
|
1701
|
+
[/^next\.?js pages router/i, "Routes are defined by files under `pages/`, one file per URL.", "File-based routing, simple mental model"],
|
|
1702
|
+
[/^layered component/i, "UI components sit in their own layer and call shared library code rather than reaching into each other.", "Reusable UI, clear dependency direction"],
|
|
1703
|
+
[/^react hooks/i, "Stateful logic is packaged as custom hooks that components share.", "Reusable stateful logic, testable in isolation"],
|
|
1704
|
+
[/^centralized state/i, "Application state lives in a dedicated store that components read from and write to.", "Predictable data flow, easier debugging"],
|
|
1705
|
+
[/^api route/i, "HTTP endpoints live in route or handler folders, separate from UI code.", "Clear external interface, versionable"],
|
|
1706
|
+
[/^core\/kernel/i, "Foundational logic lives in `core/` or `kernel/` folders that other modules build on.", "Stable foundation, shared behavior in one place"],
|
|
1707
|
+
[/^plugin system/i, "The system supports extensibility through plugins or extensions loaded at runtime.", "Extensibility, community contributions, core stability"],
|
|
1708
|
+
[/^middleware pipeline/i, "Requests or data pass through a chain of middleware steps.", "Composable steps, cross-cutting concerns in one place"],
|
|
1709
|
+
[/^renderer pipeline/i, "Output is produced by renderer or template modules, one per output type.", "Output formats can change without touching the data layer"],
|
|
1710
|
+
[/^multi-output publishing/i, "Results are published to several targets through separate publisher modules.", "Add a target without touching the others"],
|
|
1711
|
+
[/^analysis pipeline/i, "Analyzer or detector modules inspect input and feed their findings forward.", "Each analysis step is testable alone"],
|
|
1712
|
+
[/^cli tool/i, "The system runs as a command-line interface, accepting user input through terminal commands and producing structured output.", "Automation-friendly, scriptable, integrates with CI/CD pipelines"],
|
|
1713
|
+
[/^shared utility/i, "Common helpers sit in shared modules that other modules import instead of duplicating code.", "Less duplication, one place to fix a helper"],
|
|
1714
|
+
[/^integration adapters/i, "Adapters wrap outside services behind a local interface.", "Outside services can be swapped or mocked"],
|
|
1715
|
+
[/^ai\/llm/i, "The code calls an AI or LLM provider or SDK.", "Intelligent features behind a provider boundary"],
|
|
1716
|
+
[/^delivery pipeline/i, "Dispatch and delivery modules move finished content to its destination.", "Delivery channels are isolated from content creation"],
|
|
1717
|
+
[/^dedicated test/i, "Tests live in their own folders, apart from production code.", "Safe refactoring, regression protection"],
|
|
1718
|
+
[/^acyclic dependency/i, "No circular imports were found: modules depend on each other in one direction only, so each layer can be read, tested, and changed on its own.", "Safe refactoring, predictable build order, simple testing"],
|
|
1719
|
+
];
|
|
1720
|
+
|
|
1645
1721
|
// Helper: explain an architectural pattern
|
|
1646
1722
|
function explainPattern(pattern) {
|
|
1647
|
-
const
|
|
1648
|
-
|
|
1723
|
+
const label = patternLabel(pattern);
|
|
1724
|
+
const known = PATTERN_COPY.find(([regex]) => regex.test(label));
|
|
1725
|
+
if (known) return known[1];
|
|
1726
|
+
const lower = label.toLowerCase();
|
|
1649
1727
|
if (/monorepo/i.test(lower)) return "Multiple related packages are managed in a single repository, enabling coordinated development and shared tooling.";
|
|
1650
1728
|
if (/microservice/i.test(lower)) return "The system is decomposed into independently deployable services communicating over network protocols.";
|
|
1651
1729
|
if (/mvc|model.?view/i.test(lower)) return "The application separates data (Model), presentation (View), and business logic (Controller) into distinct layers.";
|
|
@@ -1653,14 +1731,15 @@ function explainPattern(pattern) {
|
|
|
1653
1731
|
if (/api.?first/i.test(lower)) return "The system is designed API-first, with a well-defined interface contract that drives both backend and frontend development.";
|
|
1654
1732
|
if (/layered/i.test(lower)) return "The codebase is organized into horizontal layers (e.g., presentation, business, data) with clear boundaries.";
|
|
1655
1733
|
if (/modular/i.test(lower)) return "Functionality is divided into self-contained modules with explicit interfaces and minimal coupling.";
|
|
1656
|
-
|
|
1657
|
-
return "A structured approach to organizing code and managing complexity.";
|
|
1734
|
+
return `Detected from the code layout: ${label}.`;
|
|
1658
1735
|
}
|
|
1659
1736
|
|
|
1660
1737
|
// Helper: get benefits of a pattern
|
|
1661
1738
|
function getBenefitsForPattern(pattern) {
|
|
1662
|
-
const
|
|
1663
|
-
|
|
1739
|
+
const label = patternLabel(pattern);
|
|
1740
|
+
const known = PATTERN_COPY.find(([regex]) => regex.test(label));
|
|
1741
|
+
if (known) return known[2];
|
|
1742
|
+
const lower = label.toLowerCase();
|
|
1664
1743
|
if (/monorepo/i.test(lower)) return "Shared tooling, atomic cross-package changes, unified versioning";
|
|
1665
1744
|
if (/microservice/i.test(lower)) return "Independent deployment, tech flexibility, team autonomy";
|
|
1666
1745
|
if (/mvc|model.?view/i.test(lower)) return "Separation of concerns, testability, parallel development";
|
|
@@ -1668,7 +1747,6 @@ function getBenefitsForPattern(pattern) {
|
|
|
1668
1747
|
if (/api.?first/i.test(lower)) return "Clear contracts, parallel development, documentation-driven";
|
|
1669
1748
|
if (/layered/i.test(lower)) return "Clear responsibilities, easier testing, technology swapping";
|
|
1670
1749
|
if (/modular/i.test(lower)) return "Reusability, isolated testing, team ownership boundaries";
|
|
1671
|
-
if (/plugin/i.test(lower)) return "Extensibility, community contributions, core stability";
|
|
1672
1750
|
return "Structured organization, maintainability, clarity";
|
|
1673
1751
|
}
|
|
1674
1752
|
|