@mrpatronz/nexusflow 0.2.18 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -3
- package/dist/analyzers/index.d.ts +23 -0
- package/dist/analyzers/index.d.ts.map +1 -1
- package/dist/analyzers/index.js +62 -0
- package/dist/analyzers/index.js.map +1 -1
- package/dist/commands/commands.test.js +20 -6
- package/dist/commands/commands.test.js.map +1 -1
- package/dist/commands/commit.d.ts +2 -0
- package/dist/commands/commit.d.ts.map +1 -1
- package/dist/commands/commit.js +27 -5
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/diff.d.ts +9 -2
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +36 -12
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/doctor.js +2 -2
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/handoff.js +2 -2
- package/dist/commands/handoff.js.map +1 -1
- package/dist/commands/refresh.d.ts +4 -2
- package/dist/commands/refresh.d.ts.map +1 -1
- package/dist/commands/refresh.js +20 -36
- package/dist/commands/refresh.js.map +1 -1
- package/dist/commands/schedule.d.ts +35 -0
- package/dist/commands/schedule.d.ts.map +1 -0
- package/dist/commands/schedule.js +151 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +58 -50
- package/dist/commands/sync.js.map +1 -1
- package/dist/core/analysis-cache.d.ts +58 -0
- package/dist/core/analysis-cache.d.ts.map +1 -0
- package/dist/core/analysis-cache.js +116 -0
- package/dist/core/analysis-cache.js.map +1 -0
- package/dist/core/analysis-cache.test.d.ts +2 -0
- package/dist/core/analysis-cache.test.d.ts.map +1 -0
- package/dist/core/analysis-cache.test.js +108 -0
- package/dist/core/analysis-cache.test.js.map +1 -0
- package/dist/core/refresh.d.ts +41 -0
- package/dist/core/refresh.d.ts.map +1 -0
- package/dist/core/refresh.js +60 -0
- package/dist/core/refresh.js.map +1 -0
- package/dist/core/scheduler.d.ts +117 -0
- package/dist/core/scheduler.d.ts.map +1 -0
- package/dist/core/scheduler.js +247 -0
- package/dist/core/scheduler.js.map +1 -0
- package/dist/core/scheduler.test.d.ts +2 -0
- package/dist/core/scheduler.test.d.ts.map +1 -0
- package/dist/core/scheduler.test.js +111 -0
- package/dist/core/scheduler.test.js.map +1 -0
- package/dist/core/sync.d.ts +50 -0
- package/dist/core/sync.d.ts.map +1 -0
- package/dist/core/sync.js +92 -0
- package/dist/core/sync.js.map +1 -0
- package/dist/core/workspace-state.d.ts +51 -0
- package/dist/core/workspace-state.d.ts.map +1 -0
- package/dist/core/workspace-state.js +108 -0
- package/dist/core/workspace-state.js.map +1 -0
- package/dist/core/workspace-state.test.d.ts +2 -0
- package/dist/core/workspace-state.test.d.ts.map +1 -0
- package/dist/core/workspace-state.test.js +84 -0
- package/dist/core/workspace-state.test.js.map +1 -0
- package/dist/generators/index.d.ts +7 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +14 -1
- package/dist/generators/index.js.map +1 -1
- package/dist/gui/assets/index-Bdbld6yY.css +1 -0
- package/dist/gui/assets/index-D8iJPvnk.js +26 -0
- package/dist/gui/index.html +2 -2
- package/dist/index.js +107 -4
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +42 -0
- package/dist/mcp/server.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +175 -13
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +66 -0
- package/dist/server.test.js.map +1 -1
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/multi-git.d.ts +54 -8
- package/dist/utils/multi-git.d.ts.map +1 -1
- package/dist/utils/multi-git.js +129 -21
- package/dist/utils/multi-git.js.map +1 -1
- package/dist/utils/multi-git.test.d.ts +2 -0
- package/dist/utils/multi-git.test.d.ts.map +1 -0
- package/dist/utils/multi-git.test.js +125 -0
- package/dist/utils/multi-git.test.js.map +1 -0
- package/package.json +13 -1
- package/.agents/skills/ccc/SKILL.md +0 -71
- package/.agents/skills/ccc/references/management.md +0 -110
- package/.agents/skills/ccc/references/settings.md +0 -126
- package/.github/dependabot.yml +0 -52
- package/.github/workflows/ci.yml +0 -44
- package/.github/workflows/dependabot-merge.yml +0 -53
- package/.github/workflows/release-desktop.yml +0 -67
- package/.github/workflows/release-npm.yml +0 -65
- package/.github/workflows/release-vscode.yml +0 -61
- package/.vscode/launch.json +0 -17
- package/.vscode/tasks.json +0 -17
- package/GETTING_STARTED.md +0 -114
- package/desktop/build-installer.js +0 -228
- package/desktop/build.js +0 -118
- package/desktop/installer/nexusflow.iss +0 -35
- package/desktop/neutralino.config.json +0 -39
- package/desktop/package-lock.json +0 -1060
- package/desktop/package.json +0 -14
- package/dist/gui/assets/index-CB-jWded.css +0 -1
- package/dist/gui/assets/index-DDXpZZu3.js +0 -25
- package/extension/media/icon.svg +0 -1
- package/extension/package-lock.json +0 -6044
- package/extension/package.json +0 -117
- package/extension/src/extension.ts +0 -653
- package/extension/tsconfig.json +0 -21
- package/gui/README.md +0 -73
- package/gui/e2e/wizard.spec.ts +0 -346
- package/gui/eslint.config.js +0 -26
- package/gui/index.html +0 -17
- package/gui/package-lock.json +0 -3079
- package/gui/package.json +0 -36
- package/gui/playwright.config.ts +0 -41
- package/gui/public/app_logo.png +0 -0
- package/gui/public/favicon.svg +0 -1
- package/gui/public/icons.svg +0 -24
- package/gui/src/App.css +0 -1
- package/gui/src/App.tsx +0 -3632
- package/gui/src/assets/hero.png +0 -0
- package/gui/src/assets/react.svg +0 -1
- package/gui/src/assets/vite.svg +0 -1
- package/gui/src/features/changes/ChangesViewer.tsx +0 -388
- package/gui/src/features/knowledge/KnowledgeBase.tsx +0 -84
- package/gui/src/features/onboarding/OnboardingWizard.tsx +0 -230
- package/gui/src/features/plan/ImplementationPlan.tsx +0 -32
- package/gui/src/features/services/ServiceConsole.tsx +0 -250
- package/gui/src/features/sessions/SessionHistory.tsx +0 -195
- package/gui/src/features/workspace/WorkspaceList.tsx +0 -666
- package/gui/src/index.css +0 -152
- package/gui/src/main.tsx +0 -94
- package/gui/src/types.ts +0 -62
- package/gui/tsconfig.app.json +0 -25
- package/gui/tsconfig.json +0 -7
- package/gui/tsconfig.node.json +0 -24
- package/gui/vite.config.ts +0 -12
- package/resources/workflows/plan-implement-review.md +0 -8
- package/resources/workflows/research-verify.md +0 -6
- package/resources/workflows/solo-developer.md +0 -3
- package/scripts/simulate-workspaces.ts +0 -55
- package/skills-lock.json +0 -11
- package/src/analyzers/detect-apis.ts +0 -290
- package/src/analyzers/detect-deps.ts +0 -315
- package/src/analyzers/detect-existing.ts +0 -74
- package/src/analyzers/detect-ports.ts +0 -110
- package/src/analyzers/index.ts +0 -97
- package/src/analyzers/messaging-analyzer.ts +0 -254
- package/src/analyzers/readme-summarizer.ts +0 -102
- package/src/analyzers/run-analyzer.ts +0 -269
- package/src/analyzers/tech-stack.ts +0 -283
- package/src/commands/adapter.ts +0 -348
- package/src/commands/add-repo.ts +0 -156
- package/src/commands/commands.test.ts +0 -156
- package/src/commands/commit.ts +0 -131
- package/src/commands/config.ts +0 -52
- package/src/commands/create.ts +0 -204
- package/src/commands/desktop.ts +0 -128
- package/src/commands/diff.ts +0 -125
- package/src/commands/doctor.ts +0 -370
- package/src/commands/handoff.ts +0 -266
- package/src/commands/init.ts +0 -134
- package/src/commands/list.ts +0 -46
- package/src/commands/logs.ts +0 -63
- package/src/commands/mcp.ts +0 -94
- package/src/commands/open.ts +0 -129
- package/src/commands/refresh.ts +0 -137
- package/src/commands/remove.ts +0 -98
- package/src/commands/start.ts +0 -117
- package/src/commands/status.ts +0 -54
- package/src/commands/stop.ts +0 -55
- package/src/commands/sync.ts +0 -147
- package/src/commands/tui.ts +0 -484
- package/src/commands/ui.ts +0 -111
- package/src/core/adapters/local-storage.ts +0 -66
- package/src/core/adapters/obsidian-storage.ts +0 -151
- package/src/core/adapters/registry.ts +0 -44
- package/src/core/adapters/storage.test.ts +0 -174
- package/src/core/adapters/vault-storage.ts +0 -86
- package/src/core/config.test.ts +0 -96
- package/src/core/config.ts +0 -140
- package/src/core/graph.ts +0 -344
- package/src/core/plugins/index.ts +0 -17
- package/src/core/plugins/loader.ts +0 -36
- package/src/core/ports/storage.ts +0 -71
- package/src/core/scanner.test.ts +0 -61
- package/src/core/scanner.ts +0 -91
- package/src/core/storage.ts +0 -37
- package/src/core/workspace.ts +0 -487
- package/src/core/worktree.ts +0 -120
- package/src/generators/antigravity.ts +0 -32
- package/src/generators/base.ts +0 -243
- package/src/generators/claude.ts +0 -34
- package/src/generators/codex.ts +0 -48
- package/src/generators/copilot.ts +0 -56
- package/src/generators/cursor.ts +0 -44
- package/src/generators/diff-context.ts +0 -75
- package/src/generators/index.ts +0 -322
- package/src/generators/map-generator.test.ts +0 -159
- package/src/generators/map-generator.ts +0 -539
- package/src/generators/plan-generator.ts +0 -483
- package/src/generators/skills-generator.ts +0 -259
- package/src/index.ts +0 -539
- package/src/mcp/server.ts +0 -294
- package/src/orchestration/detect.ts +0 -313
- package/src/orchestration/index.ts +0 -7
- package/src/orchestration/runner.ts +0 -283
- package/src/server.test.ts +0 -618
- package/src/server.ts +0 -1314
- package/src/types.ts +0 -415
- package/src/utils/detect-ai.test.ts +0 -44
- package/src/utils/detect-ai.ts +0 -84
- package/src/utils/detect-editors.test.ts +0 -48
- package/src/utils/detect-editors.ts +0 -57
- package/src/utils/git.test.ts +0 -74
- package/src/utils/git.ts +0 -117
- package/src/utils/local-ai.test.ts +0 -130
- package/src/utils/local-ai.ts +0 -116
- package/src/utils/multi-git.ts +0 -313
- package/src/utils/prompts.ts +0 -209
- package/src/utils/session-finder.ts +0 -483
- package/src/utils/system-scanner.test.ts +0 -89
- package/src/utils/system-scanner.ts +0 -96
- package/src/utils/update-check.ts +0 -286
- package/src/utils/workflow-advisor.ts +0 -118
- package/src/utils/workflows.test.ts +0 -114
- package/src/utils/workflows.ts +0 -178
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -14
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module analyzers/detect-ports
|
|
3
|
-
* Detects configured ports and services in a repository by inspecting
|
|
4
|
-
* environment files, Docker configs, and framework-specific settings.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as fs from 'node:fs/promises';
|
|
8
|
-
import * as path from 'node:path';
|
|
9
|
-
|
|
10
|
-
import type { ServicePort } from '../types.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Detects ports and service endpoints configured in a repository.
|
|
14
|
-
*
|
|
15
|
-
* Scans:
|
|
16
|
-
* - .env / .env.* files for PORT= patterns
|
|
17
|
-
* - docker-compose.yml for port mappings
|
|
18
|
-
* - launchSettings.json (.NET) for applicationUrl
|
|
19
|
-
* - package.json scripts for --port flags
|
|
20
|
-
*
|
|
21
|
-
* @param repoPath - Absolute path to the repository root.
|
|
22
|
-
* @returns Array of detected {@link ServicePort} objects.
|
|
23
|
-
*/
|
|
24
|
-
export async function detectPorts(repoPath: string): Promise<ServicePort[]> {
|
|
25
|
-
const ports: ServicePort[] = [];
|
|
26
|
-
const seen = new Set<number>();
|
|
27
|
-
|
|
28
|
-
const addPort = (port: number, protocol: 'http' | 'https' | 'grpc' | 'other', source: string) => {
|
|
29
|
-
if (!seen.has(port)) {
|
|
30
|
-
seen.add(port);
|
|
31
|
-
ports.push({ port, protocol, source });
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
// ── .env files ────────────────────────────────────────────────────
|
|
36
|
-
const envFiles = ['.env', '.env.local', '.env.development'];
|
|
37
|
-
for (const envFile of envFiles) {
|
|
38
|
-
try {
|
|
39
|
-
const content = await fs.readFile(path.join(repoPath, envFile), 'utf-8');
|
|
40
|
-
const portMatch = content.match(/PORT\s*=\s*(\d+)/i);
|
|
41
|
-
if (portMatch) {
|
|
42
|
-
addPort(parseInt(portMatch[1]!, 10), 'http', envFile);
|
|
43
|
-
}
|
|
44
|
-
} catch {
|
|
45
|
-
// File doesn't exist
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ── docker-compose.yml ────────────────────────────────────────────
|
|
50
|
-
const composeFiles = ['docker-compose.yml', 'docker-compose.yaml', 'compose.yaml', 'compose.yml'];
|
|
51
|
-
for (const composeFile of composeFiles) {
|
|
52
|
-
try {
|
|
53
|
-
const content = await fs.readFile(path.join(repoPath, composeFile), 'utf-8');
|
|
54
|
-
// Simple regex to extract port mappings like "3000:3000" or "- 8080:80"
|
|
55
|
-
const portRegex = /["']?(\d+):(\d+)["']?/g;
|
|
56
|
-
let match: RegExpExecArray | null;
|
|
57
|
-
while ((match = portRegex.exec(content)) !== null) {
|
|
58
|
-
addPort(parseInt(match[1]!, 10), 'http', composeFile);
|
|
59
|
-
}
|
|
60
|
-
} catch {
|
|
61
|
-
// File doesn't exist
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// ── launchSettings.json (.NET) ────────────────────────────────────
|
|
66
|
-
const launchSettingsPath = path.join(repoPath, 'Properties', 'launchSettings.json');
|
|
67
|
-
try {
|
|
68
|
-
const raw = await fs.readFile(launchSettingsPath, 'utf-8');
|
|
69
|
-
const settings = JSON.parse(raw) as Record<string, unknown>;
|
|
70
|
-
const profiles = settings.profiles as Record<string, Record<string, unknown>> | undefined;
|
|
71
|
-
|
|
72
|
-
if (profiles) {
|
|
73
|
-
for (const profile of Object.values(profiles)) {
|
|
74
|
-
const appUrl = profile.applicationUrl as string | undefined;
|
|
75
|
-
if (appUrl) {
|
|
76
|
-
const urlParts = appUrl.split(';');
|
|
77
|
-
for (const url of urlParts) {
|
|
78
|
-
const portMatch = url.match(/:(\d+)/);
|
|
79
|
-
if (portMatch) {
|
|
80
|
-
const protocol = url.startsWith('https') ? 'https' : 'http';
|
|
81
|
-
addPort(parseInt(portMatch[1]!, 10), protocol, 'launchSettings.json');
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
} catch {
|
|
88
|
-
// No launchSettings.json
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ── package.json scripts ──────────────────────────────────────────
|
|
92
|
-
try {
|
|
93
|
-
const raw = await fs.readFile(path.join(repoPath, 'package.json'), 'utf-8');
|
|
94
|
-
const pkg = JSON.parse(raw) as Record<string, unknown>;
|
|
95
|
-
const scripts = pkg.scripts as Record<string, string> | undefined;
|
|
96
|
-
|
|
97
|
-
if (scripts) {
|
|
98
|
-
for (const script of Object.values(scripts)) {
|
|
99
|
-
const portMatch = script.match(/--port\s+(\d+)/i) ?? script.match(/-p\s+(\d+)/);
|
|
100
|
-
if (portMatch) {
|
|
101
|
-
addPort(parseInt(portMatch[1]!, 10), 'http', 'package.json scripts');
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
} catch {
|
|
106
|
-
// No package.json
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return ports;
|
|
110
|
-
}
|
package/src/analyzers/index.ts
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module analyzers/index
|
|
3
|
-
* Orchestrates all project analyzers and produces a full ProjectAnalysis
|
|
4
|
-
* for each repository.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import chalk from 'chalk';
|
|
8
|
-
import ora from 'ora';
|
|
9
|
-
|
|
10
|
-
import type { ProjectAnalysis, RepoInfo } from '../types.js';
|
|
11
|
-
import { detectTechStack } from './tech-stack.js';
|
|
12
|
-
import { detectApis } from './detect-apis.js';
|
|
13
|
-
import { detectDependencies, detectProducedPackages, detectNuGetFeeds } from './detect-deps.js';
|
|
14
|
-
import { detectPorts } from './detect-ports.js';
|
|
15
|
-
import { detectExistingAIConfigs } from './detect-existing.js';
|
|
16
|
-
import { extractReadmeSummary } from './readme-summarizer.js';
|
|
17
|
-
import { analyzeMessaging } from './messaging-analyzer.js';
|
|
18
|
-
import { analyzeRunConfig } from './run-analyzer.js';
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Runs all analyzers against a single repository and returns
|
|
22
|
-
* a complete {@link ProjectAnalysis}.
|
|
23
|
-
*
|
|
24
|
-
* @param repo - The repo metadata.
|
|
25
|
-
* @returns Full analysis result.
|
|
26
|
-
*/
|
|
27
|
-
export async function analyzeRepo(repo: RepoInfo): Promise<ProjectAnalysis> {
|
|
28
|
-
const [techStack, endpoints, dependencies, produces, nugetFeeds, ports, existingAIConfigs, readmeSummary, messaging, runConfig] =
|
|
29
|
-
await Promise.all([
|
|
30
|
-
detectTechStack(repo.path),
|
|
31
|
-
detectApis(repo.path),
|
|
32
|
-
detectDependencies(repo.path),
|
|
33
|
-
detectProducedPackages(repo.path),
|
|
34
|
-
detectNuGetFeeds(repo.path),
|
|
35
|
-
detectPorts(repo.path),
|
|
36
|
-
detectExistingAIConfigs(repo.path),
|
|
37
|
-
extractReadmeSummary(repo.path),
|
|
38
|
-
analyzeMessaging(repo.path),
|
|
39
|
-
analyzeRunConfig(repo.path),
|
|
40
|
-
]);
|
|
41
|
-
|
|
42
|
-
return {
|
|
43
|
-
name: repo.name,
|
|
44
|
-
path: repo.path,
|
|
45
|
-
techStack,
|
|
46
|
-
endpoints,
|
|
47
|
-
dependencies,
|
|
48
|
-
produces,
|
|
49
|
-
nugetFeeds,
|
|
50
|
-
ports,
|
|
51
|
-
existingAIConfigs,
|
|
52
|
-
readmeSummary,
|
|
53
|
-
messaging,
|
|
54
|
-
runConfig,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Analyzes all repos in a list and returns a Map of path → analysis.
|
|
60
|
-
* Shows a progress spinner while running.
|
|
61
|
-
*
|
|
62
|
-
* @param repos - Array of repos to analyze.
|
|
63
|
-
* @returns Map of repo path to {@link ProjectAnalysis}.
|
|
64
|
-
*/
|
|
65
|
-
export async function analyzeAllRepos(
|
|
66
|
-
repos: RepoInfo[],
|
|
67
|
-
): Promise<Map<string, ProjectAnalysis>> {
|
|
68
|
-
const spinner = ora('Analyzing projects...').start();
|
|
69
|
-
const results = new Map<string, ProjectAnalysis>();
|
|
70
|
-
|
|
71
|
-
for (let i = 0; i < repos.length; i++) {
|
|
72
|
-
const repo = repos[i]!;
|
|
73
|
-
spinner.text = `Analyzing ${chalk.bold(repo.name)} (${i + 1}/${repos.length})...`;
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
const analysis = await analyzeRepo(repo);
|
|
77
|
-
results.set(repo.path, analysis);
|
|
78
|
-
} catch (error) {
|
|
79
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
80
|
-
spinner.warn(`Failed to analyze ${repo.name}: ${msg}`);
|
|
81
|
-
spinner.start();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
spinner.succeed(`Analyzed ${chalk.bold(results.size)} projects`);
|
|
86
|
-
return results;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Re-export individual analyzers
|
|
90
|
-
export { detectTechStack } from './tech-stack.js';
|
|
91
|
-
export { detectApis } from './detect-apis.js';
|
|
92
|
-
export { detectDependencies, findInterRepoDependencies, detectNuGetFeeds } from './detect-deps.js';
|
|
93
|
-
export { detectPorts } from './detect-ports.js';
|
|
94
|
-
export { detectExistingAIConfigs } from './detect-existing.js';
|
|
95
|
-
export { extractReadmeSummary } from './readme-summarizer.js';
|
|
96
|
-
export { analyzeMessaging } from './messaging-analyzer.js';
|
|
97
|
-
export { analyzeRunConfig } from './run-analyzer.js';
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module analyzers/messaging-analyzer
|
|
3
|
-
* Detects publishers and subscribers for pub/sub messaging topologies
|
|
4
|
-
* across multiple programming languages and frameworks.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as fs from 'node:fs/promises';
|
|
8
|
-
import * as path from 'node:path';
|
|
9
|
-
import { globby } from 'globby';
|
|
10
|
-
import type { MessagingTopology, MessagePublisher, MessageSubscriber } from '../types.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Analyzes messaging/event topology in a repository.
|
|
14
|
-
*
|
|
15
|
-
* Scans C#, JS/TS, Python, and Go source files for publish and subscribe
|
|
16
|
-
* patterns and extracts message/event contracts.
|
|
17
|
-
*
|
|
18
|
-
* @param repoPath - Absolute path to the repository root.
|
|
19
|
-
* @returns Detected publishers and subscribers.
|
|
20
|
-
*/
|
|
21
|
-
export async function analyzeMessaging(repoPath: string): Promise<MessagingTopology> {
|
|
22
|
-
const publishers: MessagePublisher[] = [];
|
|
23
|
-
const subscribers: MessageSubscriber[] = [];
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const files = await globby(
|
|
27
|
-
['**/*.ts', '**/*.js', '**/*.cs', '**/*.py', '**/*.go'],
|
|
28
|
-
{
|
|
29
|
-
cwd: repoPath,
|
|
30
|
-
absolute: true,
|
|
31
|
-
ignore: ['**/node_modules/**', '**/bin/**', '**/obj/**', '**/dist/**', '**/out/**', '**/.git/**'],
|
|
32
|
-
}
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
for (const file of files) {
|
|
36
|
-
try {
|
|
37
|
-
const stat = await fs.stat(file);
|
|
38
|
-
if (!stat.isFile() || stat.size > 200_000) continue; // Skip large files
|
|
39
|
-
|
|
40
|
-
const content = await fs.readFile(file, 'utf-8');
|
|
41
|
-
const relPath = path.relative(repoPath, file).replace(/\\/g, '/');
|
|
42
|
-
|
|
43
|
-
if (file.endsWith('.cs')) {
|
|
44
|
-
// ── C# Messaging Patterns ────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
// MediatR notification handler
|
|
47
|
-
// INotificationHandler<MyNotification>
|
|
48
|
-
const mediatrSubRegex = /:\s*INotificationHandler\s*<\s*(\w+)\s*>/g;
|
|
49
|
-
let match: RegExpExecArray | null;
|
|
50
|
-
while ((match = mediatrSubRegex.exec(content)) !== null) {
|
|
51
|
-
subscribers.push({
|
|
52
|
-
contractType: match[1]!,
|
|
53
|
-
handlerFile: relPath,
|
|
54
|
-
registrationFile: relPath,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// MediatR command/query handler
|
|
59
|
-
// IRequestHandler<MyRequest, MyResponse> or IRequestHandler<MyRequest>
|
|
60
|
-
const requestHandlerRegex = /:\s*IRequestHandler\s*<\s*(\w+)(?:\s*,\s*\w+)?\s*>/g;
|
|
61
|
-
while ((match = requestHandlerRegex.exec(content)) !== null) {
|
|
62
|
-
subscribers.push({
|
|
63
|
-
contractType: match[1]!,
|
|
64
|
-
handlerFile: relPath,
|
|
65
|
-
registrationFile: relPath,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// MassTransit Consumer: IConsumer<MyMessage>
|
|
70
|
-
const massTransitSubRegex = /:\s*IConsumer\s*<\s*(\w+)\s*>/g;
|
|
71
|
-
while ((match = massTransitSubRegex.exec(content)) !== null) {
|
|
72
|
-
subscribers.push({
|
|
73
|
-
contractType: match[1]!,
|
|
74
|
-
handlerFile: relPath,
|
|
75
|
-
registrationFile: relPath,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// NServiceBus Handler: IHandleMessages<MyMessage>
|
|
80
|
-
const nserviceBusSubRegex = /:\s*IHandleMessages\s*<\s*(\w+)\s*>/g;
|
|
81
|
-
while ((match = nserviceBusSubRegex.exec(content)) !== null) {
|
|
82
|
-
subscribers.push({
|
|
83
|
-
contractType: match[1]!,
|
|
84
|
-
handlerFile: relPath,
|
|
85
|
-
registrationFile: relPath,
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Azure Service Bus triggers: [ServiceBusTrigger("queueOrTopicName")]
|
|
90
|
-
const sbtRegex = /\[ServiceBusTrigger\s*\(\s*"([^"]+)"(?:\s*,\s*"[^"]+")?\s*\)\]/g;
|
|
91
|
-
while ((match = sbtRegex.exec(content)) !== null) {
|
|
92
|
-
subscribers.push({
|
|
93
|
-
contractType: 'ServiceBusMessage',
|
|
94
|
-
handlerFile: relPath,
|
|
95
|
-
registrationFile: relPath,
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Publishers in C#
|
|
100
|
-
// .Publish<MyEvent>( or .PublishAsync<MyEvent>( or .Send<MyCommand>( or .SendAsync<MyCommand>(
|
|
101
|
-
const csPubRegex = /\b(?:Publish|PublishAsync|Send|SendAsync)\s*<\s*(\w+)\s*>\s*\(/g;
|
|
102
|
-
while ((match = csPubRegex.exec(content)) !== null) {
|
|
103
|
-
publishers.push({
|
|
104
|
-
contractType: match[1]!,
|
|
105
|
-
topicOrQueue: 'direct/inferred',
|
|
106
|
-
publisherFile: relPath,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const mediatorSendRegex = /\bmediator\s*\.\s*(?:Send|Publish)\s*\(\s*new\s+(\w+)\s*\(/gi;
|
|
111
|
-
while ((match = mediatorSendRegex.exec(content)) !== null) {
|
|
112
|
-
publishers.push({
|
|
113
|
-
contractType: match[1]!,
|
|
114
|
-
topicOrQueue: 'mediator',
|
|
115
|
-
publisherFile: relPath,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
} else if (file.endsWith('.ts') || file.endsWith('.js')) {
|
|
120
|
-
// ── TS/JS Messaging Patterns ─────────────────────────────────────
|
|
121
|
-
|
|
122
|
-
// EventEmitter emitters: emit('event-name', ...)
|
|
123
|
-
const tsEmitRegex = /\bemit\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
124
|
-
let match: RegExpExecArray | null;
|
|
125
|
-
while ((match = tsEmitRegex.exec(content)) !== null) {
|
|
126
|
-
publishers.push({
|
|
127
|
-
contractType: match[1]!,
|
|
128
|
-
topicOrQueue: 'EventEmitter',
|
|
129
|
-
publisherFile: relPath,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// EventEmitter listeners: on('event-name', ...) or addListener('event-name', ...)
|
|
134
|
-
const tsOnRegex = /\b(?:on|addListener)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
135
|
-
while ((match = tsOnRegex.exec(content)) !== null) {
|
|
136
|
-
subscribers.push({
|
|
137
|
-
contractType: match[1]!,
|
|
138
|
-
handlerFile: relPath,
|
|
139
|
-
registrationFile: relPath,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Kafka/RabbitMQ subscribe: topic: 'topic-name' inside subscription object
|
|
144
|
-
const tsSubTopicRegex = /topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
145
|
-
while ((match = tsSubTopicRegex.exec(content)) !== null) {
|
|
146
|
-
subscribers.push({
|
|
147
|
-
contractType: 'Kafka/MQ Message',
|
|
148
|
-
handlerFile: relPath,
|
|
149
|
-
registrationFile: relPath,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// BullMQ add: queue.add('job-name', ...)
|
|
154
|
-
const bullmqAddRegex = /\badd\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
155
|
-
while ((match = bullmqAddRegex.exec(content)) !== null) {
|
|
156
|
-
publishers.push({
|
|
157
|
-
contractType: match[1]!,
|
|
158
|
-
topicOrQueue: 'BullMQ',
|
|
159
|
-
publisherFile: relPath,
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
} else if (file.endsWith('.py')) {
|
|
164
|
-
// ── Python Messaging Patterns ────────────────────────────────────
|
|
165
|
-
|
|
166
|
-
// Celery task definitions: @app.task or @shared_task
|
|
167
|
-
const celeryTaskRegex = /@(?:[a-zA-Z0-9_]+\.)?(?:task|shared_task)(?:\([^)]*\))?\s*\n\s*def\s+(\w+)\s*\(/g;
|
|
168
|
-
let match: RegExpExecArray | null;
|
|
169
|
-
while ((match = celeryTaskRegex.exec(content)) !== null) {
|
|
170
|
-
subscribers.push({
|
|
171
|
-
contractType: match[1]!,
|
|
172
|
-
handlerFile: relPath,
|
|
173
|
-
registrationFile: relPath,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Celery task invocations: task.delay(...) or task.apply_async(...)
|
|
178
|
-
const celeryDelayRegex = /\b(\w+)\s*\.\s*(?:delay|apply_async)\s*\(/g;
|
|
179
|
-
while ((match = celeryDelayRegex.exec(content)) !== null) {
|
|
180
|
-
publishers.push({
|
|
181
|
-
contractType: match[1]!,
|
|
182
|
-
topicOrQueue: 'Celery',
|
|
183
|
-
publisherFile: relPath,
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// RabbitMQ basic_publish: routing_key='key'
|
|
188
|
-
const pikaPublishRegex = /routing_key\s*=\s*['"`]([^'"`]+)['"`]/g;
|
|
189
|
-
while ((match = pikaPublishRegex.exec(content)) !== null) {
|
|
190
|
-
publishers.push({
|
|
191
|
-
contractType: match[1]!,
|
|
192
|
-
topicOrQueue: 'RabbitMQ',
|
|
193
|
-
publisherFile: relPath,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
} else if (file.endsWith('.go')) {
|
|
198
|
-
// ── Go Messaging Patterns ────────────────────────────────────────
|
|
199
|
-
|
|
200
|
-
// Go publish/produce calls: Publish("topic", ...) or Produce("topic", ...)
|
|
201
|
-
const goPubRegex = /\b(?:Publish|Produce|Send)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
202
|
-
let match: RegExpExecArray | null;
|
|
203
|
-
while ((match = goPubRegex.exec(content)) !== null) {
|
|
204
|
-
publishers.push({
|
|
205
|
-
contractType: 'GoMessage',
|
|
206
|
-
topicOrQueue: match[1]!,
|
|
207
|
-
publisherFile: relPath,
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Go subscribe/consume calls: Subscribe("topic", ...) or Consume("topic", ...)
|
|
212
|
-
const goSubRegex = /\b(?:Subscribe|Consume)\s*\(\s*['"`]([^'"`]+)['"`]/g;
|
|
213
|
-
while ((match = goSubRegex.exec(content)) !== null) {
|
|
214
|
-
subscribers.push({
|
|
215
|
-
contractType: 'GoMessage',
|
|
216
|
-
handlerFile: relPath,
|
|
217
|
-
registrationFile: relPath,
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
} catch {
|
|
222
|
-
// Skip unreadable files
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
} catch {
|
|
226
|
-
// Ignore errors
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// De-duplicate publishers & subscribers
|
|
230
|
-
const uniquePublishers: MessagePublisher[] = [];
|
|
231
|
-
const seenPub = new Set<string>();
|
|
232
|
-
for (const p of publishers) {
|
|
233
|
-
const key = `${p.contractType}:${p.topicOrQueue}:${p.publisherFile}`;
|
|
234
|
-
if (!seenPub.has(key)) {
|
|
235
|
-
seenPub.add(key);
|
|
236
|
-
uniquePublishers.push(p);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const uniqueSubscribers: MessageSubscriber[] = [];
|
|
241
|
-
const seenSub = new Set<string>();
|
|
242
|
-
for (const s of subscribers) {
|
|
243
|
-
const key = `${s.contractType}:${s.handlerFile}:${s.registrationFile}`;
|
|
244
|
-
if (!seenSub.has(key)) {
|
|
245
|
-
seenSub.add(key);
|
|
246
|
-
uniqueSubscribers.push(s);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return {
|
|
251
|
-
publishers: uniquePublishers,
|
|
252
|
-
subscribers: uniqueSubscribers,
|
|
253
|
-
};
|
|
254
|
-
}
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module analyzers/readme-summarizer
|
|
3
|
-
* Extracts a summary from a repository's README.md file.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as fs from 'node:fs/promises';
|
|
7
|
-
import * as path from 'node:path';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Reads a repository's README.md and extracts the first meaningful
|
|
11
|
-
* prose paragraph as a summary. Looks for README.md (case-insensitive).
|
|
12
|
-
*
|
|
13
|
-
* It automatically strips badges, TOC lists, headers, HTML tags, and redacts ClientIDs/Secrets.
|
|
14
|
-
*
|
|
15
|
-
* @param repoPath - Absolute path to the repository root.
|
|
16
|
-
* @returns The extracted summary text, or null if no README is found.
|
|
17
|
-
*/
|
|
18
|
-
export async function extractReadmeSummary(
|
|
19
|
-
repoPath: string,
|
|
20
|
-
): Promise<string | null> {
|
|
21
|
-
// Try common README filenames
|
|
22
|
-
const candidates = ['README.md', 'readme.md', 'Readme.md', 'README.MD', 'README'];
|
|
23
|
-
|
|
24
|
-
let content: string | null = null;
|
|
25
|
-
|
|
26
|
-
for (const filename of candidates) {
|
|
27
|
-
try {
|
|
28
|
-
content = await fs.readFile(path.join(repoPath, filename), 'utf-8');
|
|
29
|
-
break;
|
|
30
|
-
} catch {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!content) return null;
|
|
36
|
-
|
|
37
|
-
// 1. Strip code blocks to avoid false matching on secrets/prose
|
|
38
|
-
content = content.replace(/```[\s\S]*?```/g, '');
|
|
39
|
-
|
|
40
|
-
// 2. Scrub ClientIDs/Secrets/Credentials
|
|
41
|
-
// Match standard secret/ID patterns: GUIDs, client_id/secret variables, hex keys
|
|
42
|
-
const clientSecretsRegex = /(client_id|client_secret|appid|secret|password|key|token|credential)\s*[:=]\s*["']?[a-zA-Z0-9-_\/\+\.]{16,}["']?/gi;
|
|
43
|
-
const guidRegex = /[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}/g;
|
|
44
|
-
|
|
45
|
-
content = content
|
|
46
|
-
.replace(clientSecretsRegex, (match, p1) => `${p1}: [REDACTED]`)
|
|
47
|
-
.replace(guidRegex, '[REDACTED_ID]');
|
|
48
|
-
|
|
49
|
-
// 3. Process line-by-line to find the first prose paragraph
|
|
50
|
-
const lines = content.split('\n');
|
|
51
|
-
const proseLines: string[] = [];
|
|
52
|
-
|
|
53
|
-
for (const line of lines) {
|
|
54
|
-
const trimmed = line.trim();
|
|
55
|
-
|
|
56
|
-
// Skip empty lines
|
|
57
|
-
if (!trimmed) {
|
|
58
|
-
if (proseLines.length > 0) {
|
|
59
|
-
// We found a paragraph and hit an empty line. Let's finish!
|
|
60
|
-
break;
|
|
61
|
-
}
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Skip HTML tags
|
|
66
|
-
if (trimmed.startsWith('<') && trimmed.endsWith('>')) continue;
|
|
67
|
-
|
|
68
|
-
// Skip badges
|
|
69
|
-
if (trimmed.startsWith('[ or 1. [About](#about) or * 1. [Section](#section)
|
|
79
|
-
if (/^(?:[-*+]\s*|\d+\.\s*)(\d+\.)?\s*\[[^\]]+\]\(#[^)]+\)/.test(trimmed)) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Skip link-only lines (e.g., "[API Docs](https://...)")
|
|
84
|
-
if (/^\[[^\]]+\]\([^)]+\)$/.test(trimmed)) {
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Skip standard bullet and numbered lists if we are searching for prose (e.g. at the top of README before prose)
|
|
89
|
-
if (proseLines.length === 0 && (/^[-*+]\s+/.test(trimmed) || /^\d+\.\s+/.test(trimmed))) {
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Accumulate prose lines
|
|
94
|
-
proseLines.push(line);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const summary = proseLines.join(' ').replace(/\s+/g, ' ').trim();
|
|
98
|
-
if (!summary) return null;
|
|
99
|
-
|
|
100
|
-
// Limit the summary to a concise paragraph (first 250 characters)
|
|
101
|
-
return summary.length > 250 ? summary.slice(0, 250) + '...' : summary;
|
|
102
|
-
}
|