@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.
Files changed (236) hide show
  1. package/README.md +27 -3
  2. package/dist/analyzers/index.d.ts +23 -0
  3. package/dist/analyzers/index.d.ts.map +1 -1
  4. package/dist/analyzers/index.js +62 -0
  5. package/dist/analyzers/index.js.map +1 -1
  6. package/dist/commands/commands.test.js +20 -6
  7. package/dist/commands/commands.test.js.map +1 -1
  8. package/dist/commands/commit.d.ts +2 -0
  9. package/dist/commands/commit.d.ts.map +1 -1
  10. package/dist/commands/commit.js +27 -5
  11. package/dist/commands/commit.js.map +1 -1
  12. package/dist/commands/diff.d.ts +9 -2
  13. package/dist/commands/diff.d.ts.map +1 -1
  14. package/dist/commands/diff.js +36 -12
  15. package/dist/commands/diff.js.map +1 -1
  16. package/dist/commands/doctor.js +2 -2
  17. package/dist/commands/doctor.js.map +1 -1
  18. package/dist/commands/handoff.js +2 -2
  19. package/dist/commands/handoff.js.map +1 -1
  20. package/dist/commands/refresh.d.ts +4 -2
  21. package/dist/commands/refresh.d.ts.map +1 -1
  22. package/dist/commands/refresh.js +20 -36
  23. package/dist/commands/refresh.js.map +1 -1
  24. package/dist/commands/schedule.d.ts +35 -0
  25. package/dist/commands/schedule.d.ts.map +1 -0
  26. package/dist/commands/schedule.js +151 -0
  27. package/dist/commands/schedule.js.map +1 -0
  28. package/dist/commands/sync.d.ts.map +1 -1
  29. package/dist/commands/sync.js +58 -50
  30. package/dist/commands/sync.js.map +1 -1
  31. package/dist/core/analysis-cache.d.ts +58 -0
  32. package/dist/core/analysis-cache.d.ts.map +1 -0
  33. package/dist/core/analysis-cache.js +116 -0
  34. package/dist/core/analysis-cache.js.map +1 -0
  35. package/dist/core/analysis-cache.test.d.ts +2 -0
  36. package/dist/core/analysis-cache.test.d.ts.map +1 -0
  37. package/dist/core/analysis-cache.test.js +108 -0
  38. package/dist/core/analysis-cache.test.js.map +1 -0
  39. package/dist/core/refresh.d.ts +41 -0
  40. package/dist/core/refresh.d.ts.map +1 -0
  41. package/dist/core/refresh.js +60 -0
  42. package/dist/core/refresh.js.map +1 -0
  43. package/dist/core/scheduler.d.ts +117 -0
  44. package/dist/core/scheduler.d.ts.map +1 -0
  45. package/dist/core/scheduler.js +247 -0
  46. package/dist/core/scheduler.js.map +1 -0
  47. package/dist/core/scheduler.test.d.ts +2 -0
  48. package/dist/core/scheduler.test.d.ts.map +1 -0
  49. package/dist/core/scheduler.test.js +111 -0
  50. package/dist/core/scheduler.test.js.map +1 -0
  51. package/dist/core/sync.d.ts +50 -0
  52. package/dist/core/sync.d.ts.map +1 -0
  53. package/dist/core/sync.js +92 -0
  54. package/dist/core/sync.js.map +1 -0
  55. package/dist/core/workspace-state.d.ts +51 -0
  56. package/dist/core/workspace-state.d.ts.map +1 -0
  57. package/dist/core/workspace-state.js +108 -0
  58. package/dist/core/workspace-state.js.map +1 -0
  59. package/dist/core/workspace-state.test.d.ts +2 -0
  60. package/dist/core/workspace-state.test.d.ts.map +1 -0
  61. package/dist/core/workspace-state.test.js +84 -0
  62. package/dist/core/workspace-state.test.js.map +1 -0
  63. package/dist/generators/index.d.ts +7 -1
  64. package/dist/generators/index.d.ts.map +1 -1
  65. package/dist/generators/index.js +14 -1
  66. package/dist/generators/index.js.map +1 -1
  67. package/dist/gui/assets/index-Bdbld6yY.css +1 -0
  68. package/dist/gui/assets/index-D8iJPvnk.js +26 -0
  69. package/dist/gui/index.html +2 -2
  70. package/dist/index.js +107 -4
  71. package/dist/index.js.map +1 -1
  72. package/dist/mcp/server.d.ts.map +1 -1
  73. package/dist/mcp/server.js +42 -0
  74. package/dist/mcp/server.js.map +1 -1
  75. package/dist/server.d.ts.map +1 -1
  76. package/dist/server.js +175 -13
  77. package/dist/server.js.map +1 -1
  78. package/dist/server.test.js +66 -0
  79. package/dist/server.test.js.map +1 -1
  80. package/dist/types.d.ts +31 -0
  81. package/dist/types.d.ts.map +1 -1
  82. package/dist/utils/multi-git.d.ts +54 -8
  83. package/dist/utils/multi-git.d.ts.map +1 -1
  84. package/dist/utils/multi-git.js +129 -21
  85. package/dist/utils/multi-git.js.map +1 -1
  86. package/dist/utils/multi-git.test.d.ts +2 -0
  87. package/dist/utils/multi-git.test.d.ts.map +1 -0
  88. package/dist/utils/multi-git.test.js +125 -0
  89. package/dist/utils/multi-git.test.js.map +1 -0
  90. package/package.json +13 -1
  91. package/.agents/skills/ccc/SKILL.md +0 -71
  92. package/.agents/skills/ccc/references/management.md +0 -110
  93. package/.agents/skills/ccc/references/settings.md +0 -126
  94. package/.github/dependabot.yml +0 -52
  95. package/.github/workflows/ci.yml +0 -44
  96. package/.github/workflows/dependabot-merge.yml +0 -53
  97. package/.github/workflows/release-desktop.yml +0 -67
  98. package/.github/workflows/release-npm.yml +0 -65
  99. package/.github/workflows/release-vscode.yml +0 -61
  100. package/.vscode/launch.json +0 -17
  101. package/.vscode/tasks.json +0 -17
  102. package/GETTING_STARTED.md +0 -114
  103. package/desktop/build-installer.js +0 -228
  104. package/desktop/build.js +0 -118
  105. package/desktop/installer/nexusflow.iss +0 -35
  106. package/desktop/neutralino.config.json +0 -39
  107. package/desktop/package-lock.json +0 -1060
  108. package/desktop/package.json +0 -14
  109. package/dist/gui/assets/index-CB-jWded.css +0 -1
  110. package/dist/gui/assets/index-DDXpZZu3.js +0 -25
  111. package/extension/media/icon.svg +0 -1
  112. package/extension/package-lock.json +0 -6044
  113. package/extension/package.json +0 -117
  114. package/extension/src/extension.ts +0 -653
  115. package/extension/tsconfig.json +0 -21
  116. package/gui/README.md +0 -73
  117. package/gui/e2e/wizard.spec.ts +0 -346
  118. package/gui/eslint.config.js +0 -26
  119. package/gui/index.html +0 -17
  120. package/gui/package-lock.json +0 -3079
  121. package/gui/package.json +0 -36
  122. package/gui/playwright.config.ts +0 -41
  123. package/gui/public/app_logo.png +0 -0
  124. package/gui/public/favicon.svg +0 -1
  125. package/gui/public/icons.svg +0 -24
  126. package/gui/src/App.css +0 -1
  127. package/gui/src/App.tsx +0 -3632
  128. package/gui/src/assets/hero.png +0 -0
  129. package/gui/src/assets/react.svg +0 -1
  130. package/gui/src/assets/vite.svg +0 -1
  131. package/gui/src/features/changes/ChangesViewer.tsx +0 -388
  132. package/gui/src/features/knowledge/KnowledgeBase.tsx +0 -84
  133. package/gui/src/features/onboarding/OnboardingWizard.tsx +0 -230
  134. package/gui/src/features/plan/ImplementationPlan.tsx +0 -32
  135. package/gui/src/features/services/ServiceConsole.tsx +0 -250
  136. package/gui/src/features/sessions/SessionHistory.tsx +0 -195
  137. package/gui/src/features/workspace/WorkspaceList.tsx +0 -666
  138. package/gui/src/index.css +0 -152
  139. package/gui/src/main.tsx +0 -94
  140. package/gui/src/types.ts +0 -62
  141. package/gui/tsconfig.app.json +0 -25
  142. package/gui/tsconfig.json +0 -7
  143. package/gui/tsconfig.node.json +0 -24
  144. package/gui/vite.config.ts +0 -12
  145. package/resources/workflows/plan-implement-review.md +0 -8
  146. package/resources/workflows/research-verify.md +0 -6
  147. package/resources/workflows/solo-developer.md +0 -3
  148. package/scripts/simulate-workspaces.ts +0 -55
  149. package/skills-lock.json +0 -11
  150. package/src/analyzers/detect-apis.ts +0 -290
  151. package/src/analyzers/detect-deps.ts +0 -315
  152. package/src/analyzers/detect-existing.ts +0 -74
  153. package/src/analyzers/detect-ports.ts +0 -110
  154. package/src/analyzers/index.ts +0 -97
  155. package/src/analyzers/messaging-analyzer.ts +0 -254
  156. package/src/analyzers/readme-summarizer.ts +0 -102
  157. package/src/analyzers/run-analyzer.ts +0 -269
  158. package/src/analyzers/tech-stack.ts +0 -283
  159. package/src/commands/adapter.ts +0 -348
  160. package/src/commands/add-repo.ts +0 -156
  161. package/src/commands/commands.test.ts +0 -156
  162. package/src/commands/commit.ts +0 -131
  163. package/src/commands/config.ts +0 -52
  164. package/src/commands/create.ts +0 -204
  165. package/src/commands/desktop.ts +0 -128
  166. package/src/commands/diff.ts +0 -125
  167. package/src/commands/doctor.ts +0 -370
  168. package/src/commands/handoff.ts +0 -266
  169. package/src/commands/init.ts +0 -134
  170. package/src/commands/list.ts +0 -46
  171. package/src/commands/logs.ts +0 -63
  172. package/src/commands/mcp.ts +0 -94
  173. package/src/commands/open.ts +0 -129
  174. package/src/commands/refresh.ts +0 -137
  175. package/src/commands/remove.ts +0 -98
  176. package/src/commands/start.ts +0 -117
  177. package/src/commands/status.ts +0 -54
  178. package/src/commands/stop.ts +0 -55
  179. package/src/commands/sync.ts +0 -147
  180. package/src/commands/tui.ts +0 -484
  181. package/src/commands/ui.ts +0 -111
  182. package/src/core/adapters/local-storage.ts +0 -66
  183. package/src/core/adapters/obsidian-storage.ts +0 -151
  184. package/src/core/adapters/registry.ts +0 -44
  185. package/src/core/adapters/storage.test.ts +0 -174
  186. package/src/core/adapters/vault-storage.ts +0 -86
  187. package/src/core/config.test.ts +0 -96
  188. package/src/core/config.ts +0 -140
  189. package/src/core/graph.ts +0 -344
  190. package/src/core/plugins/index.ts +0 -17
  191. package/src/core/plugins/loader.ts +0 -36
  192. package/src/core/ports/storage.ts +0 -71
  193. package/src/core/scanner.test.ts +0 -61
  194. package/src/core/scanner.ts +0 -91
  195. package/src/core/storage.ts +0 -37
  196. package/src/core/workspace.ts +0 -487
  197. package/src/core/worktree.ts +0 -120
  198. package/src/generators/antigravity.ts +0 -32
  199. package/src/generators/base.ts +0 -243
  200. package/src/generators/claude.ts +0 -34
  201. package/src/generators/codex.ts +0 -48
  202. package/src/generators/copilot.ts +0 -56
  203. package/src/generators/cursor.ts +0 -44
  204. package/src/generators/diff-context.ts +0 -75
  205. package/src/generators/index.ts +0 -322
  206. package/src/generators/map-generator.test.ts +0 -159
  207. package/src/generators/map-generator.ts +0 -539
  208. package/src/generators/plan-generator.ts +0 -483
  209. package/src/generators/skills-generator.ts +0 -259
  210. package/src/index.ts +0 -539
  211. package/src/mcp/server.ts +0 -294
  212. package/src/orchestration/detect.ts +0 -313
  213. package/src/orchestration/index.ts +0 -7
  214. package/src/orchestration/runner.ts +0 -283
  215. package/src/server.test.ts +0 -618
  216. package/src/server.ts +0 -1314
  217. package/src/types.ts +0 -415
  218. package/src/utils/detect-ai.test.ts +0 -44
  219. package/src/utils/detect-ai.ts +0 -84
  220. package/src/utils/detect-editors.test.ts +0 -48
  221. package/src/utils/detect-editors.ts +0 -57
  222. package/src/utils/git.test.ts +0 -74
  223. package/src/utils/git.ts +0 -117
  224. package/src/utils/local-ai.test.ts +0 -130
  225. package/src/utils/local-ai.ts +0 -116
  226. package/src/utils/multi-git.ts +0 -313
  227. package/src/utils/prompts.ts +0 -209
  228. package/src/utils/session-finder.ts +0 -483
  229. package/src/utils/system-scanner.test.ts +0 -89
  230. package/src/utils/system-scanner.ts +0 -96
  231. package/src/utils/update-check.ts +0 -286
  232. package/src/utils/workflow-advisor.ts +0 -118
  233. package/src/utils/workflows.test.ts +0 -114
  234. package/src/utils/workflows.ts +0 -178
  235. package/tsconfig.json +0 -19
  236. 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
- }
@@ -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('[![') || (trimmed.startsWith('![') && trimmed.includes('http'))) continue;
70
-
71
- // Skip horizontal rules
72
- if (/^(-{3,}|={3,}|\*{3,})$/.test(trimmed)) continue;
73
-
74
- // Skip headers (Markdown # )
75
- if (trimmed.startsWith('#')) continue;
76
-
77
- // Detect Table of Contents (TOC) lists
78
- // Skip lines that look like: - [About](#about) 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
- }