@mrpatronz/nexusflow 0.2.0 → 0.2.2

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 (61) hide show
  1. package/dist/analyzers/index.d.ts +2 -0
  2. package/dist/analyzers/index.d.ts.map +1 -1
  3. package/dist/analyzers/index.js +9 -1
  4. package/dist/analyzers/index.js.map +1 -1
  5. package/dist/analyzers/messaging-analyzer.d.ts +17 -0
  6. package/dist/analyzers/messaging-analyzer.d.ts.map +1 -0
  7. package/dist/analyzers/messaging-analyzer.js +229 -0
  8. package/dist/analyzers/messaging-analyzer.js.map +1 -0
  9. package/dist/analyzers/readme-summarizer.d.ts.map +1 -1
  10. package/dist/analyzers/readme-summarizer.js +8 -4
  11. package/dist/analyzers/readme-summarizer.js.map +1 -1
  12. package/dist/analyzers/run-analyzer.d.ts +15 -0
  13. package/dist/analyzers/run-analyzer.d.ts.map +1 -0
  14. package/dist/analyzers/run-analyzer.js +246 -0
  15. package/dist/analyzers/run-analyzer.js.map +1 -0
  16. package/dist/core/config.js +1 -1
  17. package/dist/core/config.js.map +1 -1
  18. package/dist/generators/base.d.ts.map +1 -1
  19. package/dist/generators/base.js +18 -57
  20. package/dist/generators/base.js.map +1 -1
  21. package/dist/generators/index.d.ts.map +1 -1
  22. package/dist/generators/index.js +30 -14
  23. package/dist/generators/index.js.map +1 -1
  24. package/dist/generators/map-generator.d.ts +1 -1
  25. package/dist/generators/map-generator.d.ts.map +1 -1
  26. package/dist/generators/map-generator.js +157 -54
  27. package/dist/generators/map-generator.js.map +1 -1
  28. package/dist/generators/map-generator.test.js +71 -0
  29. package/dist/generators/map-generator.test.js.map +1 -1
  30. package/dist/generators/plan-generator.d.ts +2 -2
  31. package/dist/generators/plan-generator.d.ts.map +1 -1
  32. package/dist/generators/plan-generator.js +47 -67
  33. package/dist/generators/plan-generator.js.map +1 -1
  34. package/dist/generators/skills-generator.d.ts +15 -0
  35. package/dist/generators/skills-generator.d.ts.map +1 -0
  36. package/dist/generators/skills-generator.js +225 -0
  37. package/dist/generators/skills-generator.js.map +1 -0
  38. package/dist/mcp/server.d.ts.map +1 -1
  39. package/dist/mcp/server.js +16 -85
  40. package/dist/mcp/server.js.map +1 -1
  41. package/dist/server.d.ts +0 -5
  42. package/dist/server.d.ts.map +1 -1
  43. package/dist/server.js +9 -6
  44. package/dist/server.js.map +1 -1
  45. package/dist/types.d.ts +46 -0
  46. package/dist/types.d.ts.map +1 -1
  47. package/package.json +1 -1
  48. package/src/analyzers/index.ts +9 -1
  49. package/src/analyzers/messaging-analyzer.ts +254 -0
  50. package/src/analyzers/readme-summarizer.ts +9 -4
  51. package/src/analyzers/run-analyzer.ts +269 -0
  52. package/src/core/config.ts +1 -1
  53. package/src/generators/base.ts +19 -56
  54. package/src/generators/index.ts +32 -14
  55. package/src/generators/map-generator.test.ts +78 -0
  56. package/src/generators/map-generator.ts +164 -53
  57. package/src/generators/plan-generator.ts +53 -75
  58. package/src/generators/skills-generator.ts +255 -0
  59. package/src/mcp/server.ts +16 -89
  60. package/src/server.ts +9 -6
  61. package/src/types.ts +54 -0
@@ -17,7 +17,7 @@ function formatProjectSection(analysis: ProjectAnalysis, workspacePath: string):
17
17
  lines.push(`### ${analysis.name}`);
18
18
 
19
19
  const mapPath = path.join(workspacePath, `nexusflow-map-${analysis.name}.md`).replace(/\\/g, '/');
20
- lines.push(`- **Architecture Map**: [nexusflow-map-${analysis.name}.md](file:///${mapPath}) — **Instruction**: You MUST read this architecture map before exploring or modifying the \`${analysis.name}\` repository to understand its layout, API endpoints, test commands, and detected usage patterns.`);
20
+ lines.push(`- **Architecture Map**: [nexusflow-map-${analysis.name}.md](file:///${mapPath}) — **Instruction**: Before modifying this repository, read its architecture map. For exploration, consult the map's section index on demand.`);
21
21
 
22
22
  // Tech stack
23
23
  const { techStack } = analysis;
@@ -47,15 +47,7 @@ function formatProjectSection(analysis: ProjectAnalysis, workspacePath: string):
47
47
 
48
48
  // API endpoints
49
49
  if (analysis.endpoints.length > 0) {
50
- lines.push(`- **API endpoints** (${analysis.endpoints.length} detected):`);
51
- // Show up to 10 endpoints
52
- const shown = analysis.endpoints.slice(0, 10);
53
- for (const ep of shown) {
54
- lines.push(` - \`${ep.method} ${ep.path}\``);
55
- }
56
- if (analysis.endpoints.length > 10) {
57
- lines.push(` - _...and ${analysis.endpoints.length - 10} more_`);
58
- }
50
+ lines.push(`- **API surface**: ${analysis.endpoints.length} endpoints — see architecture map for details`);
59
51
  }
60
52
 
61
53
  // Ports
@@ -128,24 +120,18 @@ ${allConfigs.join('\n')}
128
120
  if (mockCommand) parts.push(`- **Setup/Mock Command**: \`${mockCommand}\``);
129
121
  if (startCommand) parts.push(`- **Start/Run Command**: \`${startCommand}\``);
130
122
 
123
+ const standardCommands = [
124
+ 'npm run test', 'npm test', 'npm t', 'yarn test', 'yarn t', 'pnpm test', 'pnpm t', 'bun test',
125
+ 'dotnet test',
126
+ 'pytest', 'python -m unittest', 'python -m pytest',
127
+ 'go test', 'go test ./...',
128
+ 'cargo test',
129
+ ];
130
+
131
131
  if (testCommand) {
132
- if (testCommand === 'npm run test') {
133
- const hasJs = repos.some(r => {
134
- const a = analysis?.get(r.path);
135
- return a?.techStack.languages.includes('typescript') || a?.techStack.languages.includes('javascript');
136
- });
137
- const hasCsharp = repos.some(r => {
138
- const a = analysis?.get(r.path);
139
- return a?.techStack.languages.includes('csharp');
140
- });
141
-
142
- if (hasCsharp && !hasJs) {
143
- testCommand = 'dotnet test';
144
- } else if (!hasJs && !hasCsharp) {
145
- testCommand = undefined;
146
- }
147
- }
148
- if (testCommand) {
132
+ const normalizedCmd = testCommand.trim().toLowerCase();
133
+ const isStandard = standardCommands.some(cmd => normalizedCmd === cmd || normalizedCmd.startsWith(cmd + ' '));
134
+ if (!isStandard) {
149
135
  parts.push(`- **Verification/Test Command**: \`${testCommand}\``);
150
136
  }
151
137
  }
@@ -163,39 +149,16 @@ ${parts.join('\n')}
163
149
  }
164
150
  }
165
151
 
166
- // Check if overview.md already exists
167
- const overviewFile = path.join(workspacePath, 'nexusflow-overview.md');
168
- const hasOverview = fs.existsSync(overviewFile);
169
-
170
- let taskSection = '';
171
- if (hasOverview) {
172
- taskSection = `## Task & Step-by-Step Maintenance
173
-
174
- The universal reference file **\`nexusflow-overview.md\`** has already been created. Your task is to:
175
-
176
- 1. **Keep it Updated**: Maintain and update \`nexusflow-overview.md\` with any new architectural findings, layout changes, or assumptions.
177
- 2. **Review Assumptions**: Ensure that inter-repo relationships and package dependencies documented there reflect the current codebase.
178
- 3. **Address Open Questions**: If there are outstanding items in the "Clarifying Questions for the User" section, discuss them with the user.
179
- `;
180
- } else {
181
- taskSection = `## Task & Step-by-Step Initialization
152
+ const knowledgePath = path.join(workspacePath, 'nexusflow-knowledge.md').replace(/\\/g, '/');
182
153
 
183
- Your very first task upon entering this workspace is to analyze the codebase and document it in a universal reference file:
154
+ const taskSection = `## First Steps
184
155
 
185
- 1. **Create \`nexusflow-overview.md\`** at the workspace root.
186
- 2. **Project Assumptions**: For each project, write down a clear assumption of what it does, its primary tech stack, and its core responsibilities.
187
- 3. **Inter-Repo Relationships**: Document how the repos relate:
188
- - Shared libraries/packages (producers and consumers).
189
- - API boundaries (which repos expose APIs, which ones consume them).
190
- - Data flows and dependencies.
191
- 4. **Clarifying Questions**: If any feature requirements, architectural patterns, or API contracts are unclear, list them explicitly under a section called **"Clarifying Questions for the User"**.
192
- 5. **Universal Reference**: Keep this file updated. This acts as a universal reference so that any LLM assistant (Claude, Antigravity, Codex, Cursor, Copilot) joining this workspace instantly understands the project landscape.
156
+ Your very first task upon entering this workspace is to explore the codebase and align with the user:
193
157
 
194
- Once you have created \`nexusflow-overview.md\` and compiled your questions, ask the user to verify your assumptions and answer your questions before proceeding to write code.
158
+ 1. **Verify Assumptions**: Open [nexusflow-knowledge.md](file:///${knowledgePath}) and fill in the **Project Assumptions** section with a brief description of what each project does, its tech stack, and responsibilities.
159
+ 2. **Raise Questions**: Document any outstanding uncertainties or architectural questions in the **Clarifying Questions for the User** section.
160
+ 3. **Obtain Approval**: Ask the user to confirm your assumptions and answer your questions before writing any code.
195
161
  `;
196
- }
197
-
198
- const knowledgePath = path.join(workspacePath, 'nexusflow-knowledge.md').replace(/\\/g, '/');
199
162
 
200
163
  return `# Multi-Repo Workspace Context
201
164
 
@@ -8,7 +8,7 @@ import { generateCopilotConfig } from './copilot.js';
8
8
  import { generateCursorConfig } from './cursor.js';
9
9
  import { buildContextContent } from './base.js';
10
10
  import { generateImplementationPlan } from './plan-generator.js';
11
- import { generateWorkspaceGraphFiles } from '../core/graph.js';
11
+ import { generateSkills } from './skills-generator.js';
12
12
 
13
13
  /** Maps each assistant to its generator function and the file it produces. */
14
14
  const GENERATORS: Record<
@@ -65,6 +65,18 @@ ${feature.description}
65
65
 
66
66
  ${repoList}
67
67
 
68
+ ## Project Assumptions (verify with user)
69
+
70
+ <!-- AI assistants: Fill this in during your first session. List each project and describe what you assume its main purpose, tech stack, and responsibilities are. -->
71
+
72
+ _(No assumptions recorded yet. AI assistant to populate.)_
73
+
74
+ ## Clarifying Questions for the User
75
+
76
+ <!-- AI assistants: List any clarifying questions or ambiguities about requirements/architecture here. -->
77
+
78
+ _(No open questions recorded yet. AI assistant to populate.)_
79
+
68
80
  ## Architecture Decisions
69
81
 
70
82
  <!-- AI assistants: append decisions here as they are made during development.
@@ -86,12 +98,6 @@ ${progressItems}
86
98
  the same debugging. -->
87
99
 
88
100
  _(No gotchas recorded yet.)_
89
-
90
- ## Open Questions
91
-
92
- <!-- Add questions that need human input before the AI can proceed. -->
93
-
94
- _(No open questions.)_
95
101
  `;
96
102
  }
97
103
 
@@ -141,12 +147,23 @@ export async function generateContextFiles(
141
147
 
142
148
  // Generate per-repo architecture maps
143
149
  if (ctx.analysis) {
150
+ const allProduced = new Set<string>();
151
+ for (const [, a] of ctx.analysis) {
152
+ if (a.produces) {
153
+ for (const p of a.produces) {
154
+ allProduced.add(p.name.toLowerCase());
155
+ }
156
+ }
157
+ // Also treat the repository name as a produced package concept
158
+ allProduced.add(a.name.toLowerCase());
159
+ }
160
+
144
161
  for (const repo of ctx.repos) {
145
162
  const a = ctx.analysis.get(repo.path);
146
163
  if (a) {
147
164
  try {
148
165
  const { generateRepoMap } = await import('./map-generator.js');
149
- await generateRepoMap(repo, a, workspacePath);
166
+ await generateRepoMap(repo, a, workspacePath, allProduced);
150
167
  console.log(chalk.green(' ✔'), `Generated Architecture Map for ${chalk.bold(repo.name)}`);
151
168
  } catch (error) {
152
169
  const message = error instanceof Error ? error.message : String(error);
@@ -174,25 +191,26 @@ export async function generateContextFiles(
174
191
  }
175
192
  }
176
193
 
177
- // Generate Workspace Architecture Graph
194
+
195
+ // Generate implementation plan from dependency analysis (if analysis data available)
178
196
  try {
179
- await generateWorkspaceGraphFiles(ctx, workspacePath);
197
+ await generateImplementationPlan(ctx, workspacePath);
180
198
  } catch (error) {
181
199
  const message = error instanceof Error ? error.message : String(error);
182
200
  console.error(
183
201
  chalk.red(' ✖'),
184
- `Failed to generate workspace architecture graph: ${message}`,
202
+ `Failed to generate implementation plan: ${message}`,
185
203
  );
186
204
  }
187
205
 
188
- // Generate implementation plan from dependency analysis (if analysis data available)
206
+ // Generate skills files for selected assistants
189
207
  try {
190
- await generateImplementationPlan(ctx, workspacePath);
208
+ await generateSkills(ctx, assistants, workspacePath);
191
209
  } catch (error) {
192
210
  const message = error instanceof Error ? error.message : String(error);
193
211
  console.error(
194
212
  chalk.red(' ✖'),
195
- `Failed to generate implementation plan: ${message}`,
213
+ `Failed to generate skills: ${message}`,
196
214
  );
197
215
  }
198
216
  }
@@ -78,4 +78,82 @@ describe('generateRepoMap', () => {
78
78
  expect(content).toContain('GET');
79
79
  expect(content).toContain('/api/v1/users');
80
80
  });
81
+
82
+ it('should render messaging topology, run config, and group endpoints by module', async () => {
83
+ const mockRepo = {
84
+ name: 'test-repo',
85
+ path: '/original/path/test-repo',
86
+ defaultBranch: 'main',
87
+ };
88
+
89
+ const mockAnalysis: ProjectAnalysis = {
90
+ name: 'test-repo',
91
+ path: '/original/path/test-repo',
92
+ techStack: {
93
+ languages: ['typescript' as Language],
94
+ frameworks: ['react' as Framework],
95
+ buildTools: ['vite'],
96
+ projectType: 'frontend' as const,
97
+ },
98
+ endpoints: [
99
+ { method: 'GET', path: '/api/v1/users', source: 'src/routes/users.ts' },
100
+ { method: 'POST', path: '/api/v1/users', source: 'src/routes/users.ts' }
101
+ ],
102
+ dependencies: [
103
+ { name: 'my-internal-package', type: 'npm' as const },
104
+ { name: 'eslint', type: 'npm' as const }
105
+ ],
106
+ ports: [],
107
+ readmeSummary: 'A test repository.',
108
+ existingAIConfigs: [],
109
+ messaging: {
110
+ publishers: [
111
+ { contractType: 'OrderCreated', topicOrQueue: 'order-events', publisherFile: 'src/services/order.ts' }
112
+ ],
113
+ subscribers: [
114
+ { contractType: 'OrderCreated', handlerFile: 'src/handlers/order.ts', registrationFile: 'src/index.ts' }
115
+ ]
116
+ },
117
+ runConfig: {
118
+ entryPoints: [
119
+ { projectPath: 'package.json', type: 'node', command: 'npm run dev' }
120
+ ],
121
+ databases: [
122
+ { provider: 'PostgreSQL', host: 'localhost', configFile: '.env' }
123
+ ],
124
+ sharedInfraWarnings: [
125
+ { resource: 'Database', host: 'staging-db.org', configFile: '.env', warning: '⚠️ SHARED INFRA warning' }
126
+ ],
127
+ committedSecrets: [
128
+ { file: '.env', lineHint: 'DATABASE_PASSWORD' }
129
+ ],
130
+ externalDependencies: []
131
+ }
132
+ };
133
+
134
+ vi.spyOn(globby, 'globby').mockImplementation(async () => []);
135
+
136
+ const writtenFiles: Record<string, string> = {};
137
+ vi.spyOn(fs, 'writeFile').mockImplementation(async (filePath: any, content: any) => {
138
+ writtenFiles[filePath as string] = content as string;
139
+ return Promise.resolve();
140
+ });
141
+ vi.spyOn(fs, 'readFile').mockImplementation(async () => '## custom rule here');
142
+
143
+ await generateRepoMap(mockRepo, mockAnalysis, workspacePath, new Set(['my-internal-package']));
144
+
145
+ const expectedOutPath = path.join(workspacePath, 'nexusflow-map-test-repo.md');
146
+ const content = writtenFiles[expectedOutPath]!;
147
+
148
+ expect(content).toContain('## 📨 Messaging Topology');
149
+ expect(content).toContain('OrderCreated');
150
+ expect(content).toContain('order-events');
151
+ expect(content).toContain('## ▶️ Running Locally');
152
+ expect(content).toContain('### Entry Points');
153
+ expect(content).toContain('### ⚠️ Shared Infrastructure Warnings');
154
+ expect(content).toContain('my-internal-package');
155
+ expect(content).not.toContain('eslint');
156
+ expect(content).toContain('Endpoint Group (Router/Module/File)');
157
+ expect(content).toContain('users');
158
+ });
81
159
  });
@@ -8,7 +8,6 @@ import * as fs from 'node:fs/promises';
8
8
  import * as path from 'node:path';
9
9
  import { globby } from 'globby';
10
10
  import type { ProjectAnalysis, RepoInfo } from '../types.js';
11
- import { loadConfig } from '../core/config.js';
12
11
 
13
12
  interface PatternRule {
14
13
  name: string;
@@ -119,6 +118,7 @@ export async function generateRepoMap(
119
118
  repo: RepoInfo,
120
119
  analysis: ProjectAnalysis,
121
120
  workspacePath: string,
121
+ allProducedPackages: Set<string> = new Set(),
122
122
  ): Promise<void> {
123
123
  const repoName = repo.name;
124
124
  const worktreePath = path.join(workspacePath, repoName);
@@ -133,13 +133,6 @@ export async function generateRepoMap(
133
133
  md.push(`> **Note**: Maps are advisory snapshots of the codebase. Always verify route parameters, patterns, and filenames before relying on them.`);
134
134
  md.push('');
135
135
 
136
- const config = await loadConfig();
137
- if (config.packContextXml) {
138
- const contextXmlPath = path.join(workspacePath, `nexusflow-context-${repoName}.xml`).replace(/\\/g, '/');
139
- md.push(`> **AI-friendly Packed Context**: [nexusflow-context-${repoName}.xml](file:///${contextXmlPath})`);
140
- md.push(`> — **Instruction**: If you need a complete, AI-friendly XML snapshot of this repository's codebase, read this file.`);
141
- md.push('');
142
- }
143
136
 
144
137
  // 1. Solution/Project Layout
145
138
  md.push('## 🏗️ Project Layout');
@@ -191,18 +184,55 @@ export async function generateRepoMap(
191
184
  md.push('');
192
185
  }
193
186
 
187
+ // 1.5. Messaging Topology
188
+ md.push('## 📨 Messaging Topology');
189
+ md.push('');
190
+ if (analysis.messaging && (analysis.messaging.publishers.length > 0 || analysis.messaging.subscribers.length > 0)) {
191
+ if (analysis.messaging.publishers.length > 0) {
192
+ md.push('### Publishes');
193
+ md.push('| Message Contract | Topic/Queue/Channel | Publisher (file) |');
194
+ md.push('|---|---|---|');
195
+ for (const p of analysis.messaging.publishers) {
196
+ const fileLink = p.publisherFile
197
+ ? `[${path.basename(p.publisherFile)}](file:///${path.join(worktreePath, p.publisherFile)})`
198
+ : '—';
199
+ md.push(`| ${p.contractType} | ${p.topicOrQueue} | ${fileLink} |`);
200
+ }
201
+ md.push('');
202
+ }
203
+
204
+ if (analysis.messaging.subscribers.length > 0) {
205
+ md.push('### Subscribes');
206
+ md.push('| Message Contract | Handler (file) | Registered in |');
207
+ md.push('|---|---|---|');
208
+ for (const s of analysis.messaging.subscribers) {
209
+ const handlerLink = s.handlerFile
210
+ ? `[${path.basename(s.handlerFile)}](file:///${path.join(worktreePath, s.handlerFile)})`
211
+ : '—';
212
+ const regLink = s.registrationFile
213
+ ? `[${path.basename(s.registrationFile)}](file:///${path.join(worktreePath, s.registrationFile)})`
214
+ : '—';
215
+ md.push(`| ${s.contractType} | ${handlerLink} | ${regLink} |`);
216
+ }
217
+ md.push('');
218
+ }
219
+ } else {
220
+ md.push('_No pub/sub messaging patterns detected in this repository._');
221
+ md.push('');
222
+ }
223
+
194
224
  // 2. Extensible Usage Pattern Scanning
195
225
  md.push('## 💡 Detected Architectural Patterns & Usages');
196
226
  md.push('');
197
227
 
198
228
  const detectedLanguages = analysis.techStack.languages;
199
- const patternCounts = new Map<string, { label: string; count: number; description: string }>();
229
+ const patternExamples = new Map<string, { label: string; firstFile?: string; description: string }>();
200
230
 
201
- // Initialize counts
231
+ // Initialize examples
202
232
  for (const lang of detectedLanguages) {
203
233
  const rules = LANG_PATTERNS[lang] || [];
204
234
  for (const rule of rules) {
205
- patternCounts.set(rule.name, { label: rule.label, count: 0, description: rule.description });
235
+ patternExamples.set(rule.name, { label: rule.label, description: rule.description });
206
236
  }
207
237
  }
208
238
 
@@ -231,11 +261,12 @@ export async function generateRepoMap(
231
261
  for (const lang of detectedLanguages) {
232
262
  const rules = LANG_PATTERNS[lang] || [];
233
263
  for (const rule of rules) {
264
+ const val = patternExamples.get(rule.name)!;
265
+ if (val.firstFile) continue; // Already found an example
266
+
234
267
  rule.regex.lastIndex = 0;
235
- const matches = content.match(rule.regex);
236
- if (matches) {
237
- const val = patternCounts.get(rule.name)!;
238
- val.count += matches.length;
268
+ if (rule.regex.test(content)) {
269
+ val.firstFile = file.replace(/\\/g, '/');
239
270
  }
240
271
  }
241
272
  }
@@ -246,22 +277,41 @@ export async function generateRepoMap(
246
277
  }
247
278
 
248
279
  md.push('### Static Analysis Findings');
249
- if (patternCounts.size > 0) {
250
- for (const [, v] of patternCounts) {
251
- md.push(`- **${v.label}**: Found ${v.count} occurrence(s). _(${v.description})_`);
280
+ let hasFindings = false;
281
+ if (patternExamples.size > 0) {
282
+ for (const [, v] of patternExamples) {
283
+ if (v.firstFile) {
284
+ hasFindings = true;
285
+ const fileLink = `[${path.basename(v.firstFile)}](file:///${path.join(worktreePath, v.firstFile).replace(/\\/g, '/')})`;
286
+ md.push(`- **${v.label}** example: ${fileLink} _(${v.description})_`);
287
+ }
252
288
  }
253
- } else {
289
+ }
290
+ if (!hasFindings) {
254
291
  md.push('_No architectural usage patterns detected via static analysis._');
255
292
  }
256
293
  md.push('');
257
294
 
258
295
  md.push('### Packages Present (Dependencies)');
259
- if (analysis.dependencies.length > 0) {
260
- for (const dep of analysis.dependencies) {
296
+ // Filter dependencies: only show internal packages produced by repos in this workspace,
297
+ // or those matching a common organization namespace prefix (e.g. if one package is MyOrg.Common, then MyOrg.*).
298
+ const internalPrefixes = Array.from(allProducedPackages).map(p => {
299
+ const parts = p.split('.');
300
+ return parts.length > 1 ? parts[0] + '.' : null;
301
+ }).filter((p): p is string => p !== null);
302
+
303
+ const filteredDeps = analysis.dependencies.filter(dep => {
304
+ const depNameLower = dep.name.toLowerCase();
305
+ if (allProducedPackages.has(depNameLower)) return true;
306
+ return internalPrefixes.some(prefix => depNameLower.startsWith(prefix));
307
+ });
308
+
309
+ if (filteredDeps.length > 0) {
310
+ for (const dep of filteredDeps) {
261
311
  md.push(`- \`${dep.name}\` (${dep.version || 'unknown version'})`);
262
312
  }
263
313
  } else {
264
- md.push('_No package dependencies detected._');
314
+ md.push('_No cross-repo or organization-internal package dependencies detected._');
265
315
  }
266
316
  md.push('');
267
317
 
@@ -269,13 +319,46 @@ export async function generateRepoMap(
269
319
  md.push('## 🔌 API Endpoints');
270
320
  md.push('');
271
321
  if (analysis.endpoints.length > 0) {
272
- md.push('| Method | Route | Controller/Source File |');
273
- md.push('|:---|:---|:---|');
322
+ md.push('| Endpoint Group (Router/Module/File) | Route Prefix / Pattern | Verbs | Source File |');
323
+ md.push('|:---|:---|:---|:---|');
324
+
325
+ // Group endpoints by source file
326
+ const grouped = new Map<string, typeof analysis.endpoints>();
274
327
  for (const ep of analysis.endpoints) {
275
- const sourceLink = ep.source
276
- ? `[${path.basename(ep.source)}](file:///${path.join(worktreePath, ep.source)})`
328
+ const src = ep.source || 'Unknown';
329
+ if (!grouped.has(src)) {
330
+ grouped.set(src, []);
331
+ }
332
+ grouped.get(src)!.push(ep);
333
+ }
334
+
335
+ const findCommonPrefix = (paths: string[]): string => {
336
+ if (paths.length === 0) return '';
337
+ if (paths.length === 1) return paths[0]!;
338
+ const sorted = [...paths].sort();
339
+ const first = sorted[0]!.split('/');
340
+ const last = sorted[sorted.length - 1]!.split('/');
341
+ const common: string[] = [];
342
+ for (let i = 0; i < first.length; i++) {
343
+ if (first[i] === last[i]) {
344
+ common.push(first[i]!);
345
+ } else {
346
+ break;
347
+ }
348
+ }
349
+ const prefix = common.join('/');
350
+ return prefix || '/';
351
+ };
352
+
353
+ for (const [src, eps] of grouped) {
354
+ const groupName = src !== 'Unknown' ? path.basename(src, path.extname(src)) : 'Inferred';
355
+ const paths = eps.map(e => e.path);
356
+ const commonPrefix = findCommonPrefix(paths);
357
+ const verbs = Array.from(new Set(eps.map(e => e.method.toUpperCase()))).join(', ');
358
+ const sourceLink = src !== 'Unknown'
359
+ ? `[${path.basename(src)}](file:///${path.join(worktreePath, src).replace(/\\/g, '/')})`
277
360
  : '—';
278
- md.push(`| \`${ep.method}\` | \`${ep.path}\` | ${sourceLink} |`);
361
+ md.push(`| ${groupName} | \`${commonPrefix}\` | \`${verbs}\` | ${sourceLink} |`);
279
362
  }
280
363
  } else {
281
364
  md.push('_No endpoints detected._');
@@ -324,6 +407,50 @@ export async function generateRepoMap(
324
407
  }
325
408
  md.push('');
326
409
 
410
+ // 4.5. Running Locally
411
+ md.push('## ▶️ Running Locally');
412
+ md.push('');
413
+ if (analysis.runConfig) {
414
+ const { entryPoints, databases, sharedInfraWarnings, committedSecrets } = analysis.runConfig;
415
+
416
+ if (entryPoints.length > 0) {
417
+ md.push('### Entry Points');
418
+ for (const ep of entryPoints) {
419
+ md.push(`- **${ep.type.toUpperCase()} App**: \`${ep.command || 'dotnet run'}\` (configured in \`${ep.projectPath}\`)`);
420
+ }
421
+ md.push('');
422
+ }
423
+
424
+ if (databases.length > 0) {
425
+ md.push('### Databases & Data Stores');
426
+ md.push('| Provider | Target Host | Config File |');
427
+ md.push('|---|---|---|');
428
+ for (const db of databases) {
429
+ md.push(`| ${db.provider} | ${db.host} | \`${db.configFile}\` |`);
430
+ }
431
+ md.push('');
432
+ }
433
+
434
+ if (sharedInfraWarnings.length > 0) {
435
+ md.push('### ⚠️ Shared Infrastructure Warnings');
436
+ for (const w of sharedInfraWarnings) {
437
+ md.push(`- ${w.warning}`);
438
+ }
439
+ md.push('');
440
+ }
441
+
442
+ if (committedSecrets.length > 0) {
443
+ md.push('### 🔒 Potential Committed Secrets');
444
+ for (const s of committedSecrets) {
445
+ md.push(`- **Warning**: Possible plaintext secret/key/password found in \`${s.file}\` (key hint: \`${s.lineHint}\`)`);
446
+ }
447
+ md.push('');
448
+ }
449
+ } else {
450
+ md.push('_No run configurations detected._');
451
+ md.push('');
452
+ }
453
+
327
454
  // 5. Custom Skills
328
455
  md.push('## 🛠️ Custom Agent Skills');
329
456
  md.push('');
@@ -363,41 +490,25 @@ export async function generateRepoMap(
363
490
  md.push('');
364
491
 
365
492
  // 7. Project-Specific Conventions (Agent-Defined)
493
+ md.push('## 📝 Discovered Conventions');
494
+ md.push('');
495
+
366
496
  const conventionsFile = path.join(workspacePath, `nexusflow-conventions-${repoName}.md`);
367
- let hasConventions = false;
368
- try {
369
- await fs.access(conventionsFile);
370
- hasConventions = true;
371
- } catch {}
372
-
373
- if (!hasConventions) {
374
- const starterContent = [
375
- `# Project Conventions — ${repoName}`,
376
- '',
377
- `<!--`,
378
- `This file is dedicated for the AI assistant and developers to document project-specific conventions.`,
379
- `Any corrections or guidelines discovered during implementation should be appended here.`,
380
- `The NexusFlow generator will automatically merge these into the architecture map during sync.`,
381
- `-->`,
382
- '',
383
- `## 📌 Custom Rules & Discovered Conventions`,
384
- '- ',
385
- ].join('\n');
386
- try {
387
- await fs.writeFile(conventionsFile, starterContent, 'utf-8');
388
- } catch {}
389
- }
390
-
391
497
  let customConventions = '';
392
498
  try {
393
499
  customConventions = await fs.readFile(conventionsFile, 'utf-8');
500
+ // Strip the title from the old conventions file if present
501
+ customConventions = customConventions.replace(/^#\s+.+\n?/, '').trim();
394
502
  } catch {}
395
503
 
396
504
  if (customConventions) {
397
- md.push('## 📝 Project-Specific Conventions (Agent-Defined)');
398
- md.push('');
399
505
  md.push(customConventions);
400
506
  md.push('');
507
+ } else {
508
+ md.push('<!-- AI assistants: Document any project-specific conventions, gotchas, or coding rules discovered here. -->');
509
+ md.push('');
510
+ md.push('- None recorded yet.');
511
+ md.push('');
401
512
  }
402
513
 
403
514
  const outPath = path.join(workspacePath, `nexusflow-map-${repoName}.md`);