Skip to content

Commit 0f02acf

Browse files
Copilotjkordick
andauthored
Pass reverse-engineered business logic into migration/conversion pipeline (#45)
* Pass reverse-engineered business logic into migration/conversion pipeline Co-authored-by: jkordick <52427852+jkordick@users.noreply.github.com> * feat: Enhance reverse engineering process with dependency mapping and business logic persistence - Added dependency mapping step to ChunkedReverseEngineeringProcess and ReverseEngineeringProcess. - Integrated IMigrationRepository for saving and reusing business logic and dependency maps. - Updated RunMigrationAsync to support loading persisted business logic from previous runs with --reuse-re flag. - Modified command-line interface to include options for reusing business logic and improved documentation. - Enhanced README and architecture documentation to reflect changes in business logic persistence and usage. * Fixing quality bots comments --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jkordick <52427852+jkordick@users.noreply.github.com> Co-authored-by: Julia Kordick <moin@jkordick.dev>
1 parent 0a27703 commit 0f02acf

26 files changed

+1033
-301
lines changed

Agents/BusinessLogicExtractorAgent.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -472,13 +472,19 @@ private List<FeatureDescription> ExtractFeatures(string analysisText, string fil
472472
{
473473
var line = lines[i].Trim();
474474

475-
if (line.StartsWith("###") && line.Contains("Feature", StringComparison.OrdinalIgnoreCase))
475+
if (line.StartsWith("###") && (line.Contains("Feature", StringComparison.OrdinalIgnoreCase)
476+
|| line.Contains("Use Case", StringComparison.OrdinalIgnoreCase)
477+
|| line.Contains("Operation", StringComparison.OrdinalIgnoreCase)))
476478
{
477479
if (currentFeature != null) features.Add(currentFeature);
478480
currentFeature = new FeatureDescription
479481
{
480482
Id = $"F-{features.Count + 1}",
481-
Name = line.Replace("###", "").Replace("Feature:", "").Trim(),
483+
Name = System.Text.RegularExpressions.Regex.Replace(
484+
line.Replace("###", "").Trim(),
485+
@"^(Feature|Use Case \d+|Operation)[\s:]*",
486+
"",
487+
System.Text.RegularExpressions.RegexOptions.IgnoreCase).Trim(),
482488
SourceLocation = fileName
483489
};
484490
}

Agents/CSharpConverterAgent.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class CSharpConverterAgent : AgentBase, ICodeConverterAgent
2222
public string FileExtension => ".cs";
2323

2424
private int? _runId;
25+
private List<BusinessLogic> _businessLogicExtracts = new();
2526

2627
/// <summary>
2728
/// Sets the Run ID for the current context.
@@ -31,6 +32,12 @@ public void SetRunId(int runId)
3132
_runId = runId;
3233
}
3334

35+
/// <inheritdoc/>
36+
public void SetBusinessLogicContext(List<BusinessLogic> businessLogicExtracts)
37+
{
38+
_businessLogicExtracts = businessLogicExtracts ?? new();
39+
}
40+
3441
/// <summary>
3542
/// Initializes a new instance using Responses API (for codex models like gpt-5.1-codex-mini).
3643
/// </summary>
@@ -107,7 +114,18 @@ public async Task<CodeFile> ConvertAsync(CobolFile cobolFile, CobolAnalysis cobo
107114
userPromptBuilder.AppendLine("Here is the analysis of the COBOL program:");
108115
userPromptBuilder.AppendLine();
109116
userPromptBuilder.AppendLine(cobolAnalysis.RawAnalysisData);
110-
117+
118+
// Inject business logic context from reverse engineering when available
119+
var businessLogic = _businessLogicExtracts
120+
.FirstOrDefault(bl => string.Equals(bl.FileName, cobolFile.FileName, StringComparison.OrdinalIgnoreCase));
121+
if (businessLogic != null)
122+
{
123+
userPromptBuilder.AppendLine();
124+
userPromptBuilder.AppendLine("Here is the extracted business logic from the reverse engineering phase. Use this to ensure the converted code faithfully implements all business rules and features:");
125+
userPromptBuilder.AppendLine();
126+
userPromptBuilder.Append(FormatBusinessLogicContext(businessLogic));
127+
}
128+
111129
userPromptBuilder.AppendLine();
112130
userPromptBuilder.AppendLine("IMPORTANT REQUIREMENTS:");
113131
userPromptBuilder.AppendLine("1. Return ONLY the C# code - NO explanations, NO markdown blocks");

Agents/ChunkAwareCSharpConverter.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class ChunkAwareCSharpConverter : AgentBase, IChunkAwareConverter
1818
{
1919
private readonly ConversionSettings _conversionSettings;
2020
private int? _runId;
21+
private List<BusinessLogic> _businessLogicExtracts = new();
2122

2223
public string TargetLanguage => "CSharp";
2324
public string FileExtension => ".cs";
@@ -31,6 +32,12 @@ public void SetRunId(int runId)
3132
_runId = runId;
3233
}
3334

35+
/// <inheritdoc/>
36+
public void SetBusinessLogicContext(List<BusinessLogic> businessLogicExtracts)
37+
{
38+
_businessLogicExtracts = businessLogicExtracts ?? new();
39+
}
40+
3441
/// <summary>
3542
/// Initializes a new instance using Responses API (for codex models like gpt-5.1-codex-mini).
3643
/// </summary>
@@ -384,6 +391,16 @@ private string BuildChunkAwareUserPrompt(ChunkResult chunk, ChunkContext context
384391
sb.AppendLine();
385392
}
386393

394+
// Inject business logic context from reverse engineering when available
395+
var businessLogic = _businessLogicExtracts
396+
.FirstOrDefault(bl => string.Equals(bl.FileName, chunk.SourceFile, StringComparison.OrdinalIgnoreCase));
397+
if (businessLogic != null)
398+
{
399+
sb.AppendLine("Business logic context from reverse engineering (use to ensure accurate conversion):");
400+
sb.AppendLine();
401+
sb.Append(FormatBusinessLogicContext(businessLogic));
402+
}
403+
387404
sb.AppendLine("Return ONLY C# code. No markdown blocks. No explanations.");
388405

389406
return sb.ToString();

Agents/ChunkAwareJavaConverter.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public class ChunkAwareJavaConverter : AgentBase, IChunkAwareConverter
1818
{
1919
private readonly ConversionSettings _conversionSettings;
2020
private int? _runId;
21+
private List<BusinessLogic> _businessLogicExtracts = new();
2122

2223
public string TargetLanguage => "Java";
2324
public string FileExtension => ".java";
@@ -31,6 +32,12 @@ public void SetRunId(int runId)
3132
_runId = runId;
3233
}
3334

35+
/// <inheritdoc/>
36+
public void SetBusinessLogicContext(List<BusinessLogic> businessLogicExtracts)
37+
{
38+
_businessLogicExtracts = businessLogicExtracts ?? new();
39+
}
40+
3441
/// <summary>
3542
/// Initializes a new instance using Responses API (for codex models like gpt-5.1-codex-mini).
3643
/// </summary>
@@ -388,6 +395,16 @@ private string BuildChunkAwareUserPrompt(ChunkResult chunk, ChunkContext context
388395
sb.AppendLine();
389396
}
390397

398+
// Inject business logic context from reverse engineering when available
399+
var businessLogic = _businessLogicExtracts
400+
.FirstOrDefault(bl => string.Equals(bl.FileName, chunk.SourceFile, StringComparison.OrdinalIgnoreCase));
401+
if (businessLogic != null)
402+
{
403+
sb.AppendLine("Business logic context from reverse engineering (use to ensure accurate conversion):");
404+
sb.AppendLine();
405+
sb.Append(FormatBusinessLogicContext(businessLogic));
406+
}
407+
391408
sb.AppendLine("Return ONLY Java code. No markdown blocks. No explanations.");
392409

393410
return sb.ToString();

Agents/Infrastructure/AgentBase.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -811,4 +811,57 @@ private string MergeAndValidateChunkOutputs(string part1, string part2, string c
811811

812812
return merged;
813813
}
814+
815+
/// <summary>
816+
/// Formats a <see cref="BusinessLogic"/> object as a concise text block suitable for injection
817+
/// into AI conversion prompts. Shared by all converter agents.
818+
/// </summary>
819+
/// <param name="businessLogic">The business logic extracted during reverse engineering.</param>
820+
/// <returns>A formatted string describing the business logic context.</returns>
821+
protected static string FormatBusinessLogicContext(BusinessLogic businessLogic)
822+
{
823+
if (string.IsNullOrWhiteSpace(businessLogic.BusinessPurpose)
824+
&& businessLogic.BusinessRules.Count == 0
825+
&& businessLogic.Features.Count == 0
826+
&& businessLogic.UserStories.Count == 0)
827+
{
828+
return string.Empty;
829+
}
830+
831+
var sb = new StringBuilder();
832+
if (!string.IsNullOrWhiteSpace(businessLogic.BusinessPurpose))
833+
{
834+
sb.AppendLine($"Purpose: {businessLogic.BusinessPurpose}");
835+
}
836+
if (businessLogic.BusinessRules.Count > 0)
837+
{
838+
sb.AppendLine("Business Rules:");
839+
foreach (var rule in businessLogic.BusinessRules)
840+
{
841+
sb.AppendLine($"- {rule.Description}");
842+
if (!string.IsNullOrWhiteSpace(rule.Condition))
843+
sb.AppendLine($" Condition: {rule.Condition}");
844+
if (!string.IsNullOrWhiteSpace(rule.Action))
845+
sb.AppendLine($" Action: {rule.Action}");
846+
}
847+
}
848+
if (businessLogic.Features.Count > 0)
849+
{
850+
sb.AppendLine("Features:");
851+
foreach (var feature in businessLogic.Features)
852+
{
853+
sb.AppendLine($"- {feature.Name}: {feature.Description}");
854+
}
855+
}
856+
if (businessLogic.UserStories.Count > 0)
857+
{
858+
sb.AppendLine("Feature Descriptions:");
859+
foreach (var story in businessLogic.UserStories)
860+
{
861+
sb.AppendLine($"- {story.Title}: {story.Action}");
862+
}
863+
}
864+
sb.AppendLine();
865+
return sb.ToString();
866+
}
814867
}

Agents/Interfaces/ICodeConverterAgent.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,12 @@ public interface ICodeConverterAgent
3838
/// Sets the Run ID for the current context.
3939
/// </summary>
4040
void SetRunId(int runId);
41+
42+
/// <summary>
43+
/// Sets the business logic context extracted during reverse engineering.
44+
/// When provided, this context is injected into conversion prompts to guide
45+
/// the AI in producing semantically accurate target code.
46+
/// </summary>
47+
/// <param name="businessLogicExtracts">Per-file business logic extracted during reverse engineering.</param>
48+
void SetBusinessLogicContext(List<BusinessLogic> businessLogicExtracts);
4149
}

Agents/JavaConverterAgent.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class JavaConverterAgent : AgentBase, IJavaConverterAgent, ICodeConverter
2222
public string FileExtension => ".java";
2323

2424
private int? _runId;
25+
private List<BusinessLogic> _businessLogicExtracts = new();
2526

2627
/// <summary>
2728
/// Sets the Run ID for the current context.
@@ -31,6 +32,12 @@ public void SetRunId(int runId)
3132
_runId = runId;
3233
}
3334

35+
/// <inheritdoc/>
36+
public void SetBusinessLogicContext(List<BusinessLogic> businessLogicExtracts)
37+
{
38+
_businessLogicExtracts = businessLogicExtracts ?? new();
39+
}
40+
3441
/// <summary>
3542
/// Initializes a new instance using Responses API (for codex models like gpt-5.1-codex-mini).
3643
/// </summary>
@@ -105,7 +112,18 @@ public async Task<JavaFile> ConvertToJavaAsync(CobolFile cobolFile, CobolAnalysi
105112
userPromptBuilder.AppendLine("Here is the analysis of the COBOL program to help you understand its structure:");
106113
userPromptBuilder.AppendLine();
107114
userPromptBuilder.AppendLine(cobolAnalysis.RawAnalysisData);
108-
115+
116+
// Inject business logic context from reverse engineering when available
117+
var businessLogic = _businessLogicExtracts
118+
.FirstOrDefault(bl => string.Equals(bl.FileName, cobolFile.FileName, StringComparison.OrdinalIgnoreCase));
119+
if (businessLogic != null)
120+
{
121+
userPromptBuilder.AppendLine();
122+
userPromptBuilder.AppendLine("Here is the extracted business logic from the reverse engineering phase. Use this to ensure the converted code faithfully implements all business rules and features:");
123+
userPromptBuilder.AppendLine();
124+
userPromptBuilder.Append(FormatBusinessLogicContext(businessLogic));
125+
}
126+
109127
userPromptBuilder.AppendLine();
110128
userPromptBuilder.AppendLine("IMPORTANT REQUIREMENTS:");
111129
userPromptBuilder.AppendLine("1. Return ONLY the Java code - NO explanations, NO markdown blocks, NO additional text");

CHANGELOG.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,23 @@ All notable changes to this repository are documented here.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.5.0] - 2026-02-23
9+
10+
### Added
11+
- **Business Logic Persistence**`ReverseEngineeringProcess` and `ChunkedReverseEngineeringProcess` now persist extracted `BusinessLogic` records to a new `business_logic` SQLite table via `IMigrationRepository.SaveBusinessLogicAsync`. Added `GetBusinessLogicAsync` and `DeleteBusinessLogicAsync` to `IMigrationRepository`, `SqliteMigrationRepository`, and `HybridMigrationRepository`.
12+
- **`--reuse-re` CLI flag** — When combined with `--skip-reverse-engineering`, loads business logic from the latest persisted RE run and injects it into conversion prompts. `doctor.sh convert-only` now prompts interactively for this choice.
13+
- **REST API: `GET/DELETE /api/runs/{runId}/business-logic`** — Returns per-file business logic summary (story/feature/rule counts); DELETE removes persisted results to allow re-running RE for that run.
14+
- **Portal: per-run `🔬 RE Results` button** — Shows the business logic summary table for a run and allows deletion of persisted results directly from the UI.
15+
- **RE Results in Portal Chat** — Chat endpoint injects business purpose, user stories, features, and business rules from the `business_logic` table into the AI prompt context. Updated AI system prompt accordingly.
16+
17+
### Fixed
18+
- **Empty Technical Analysis in RE output**`ReverseEngineeringProcess` and `ChunkedReverseEngineeringProcess` now fall back to rendering `RawAnalysisData` when structured `CobolAnalysis` fields are unpopulated.
19+
- **Total Features always 0**`BusinessLogicExtractorAgent.ExtractFeatures()` now matches `### Use Case N:` and `### Operation` headings in addition to `### Feature:`, reflecting the actual AI prompt output.
20+
21+
### Changed
22+
- **Dependency mapping runs once per full run** — RE processes (`ReverseEngineeringProcess`, `ChunkedReverseEngineeringProcess`) now include a dedicated dependency mapping step (step 4/5) and store the result on `ReverseEngineeringResult.DependencyMap`. `MigrationProcess` and `ChunkedMigrationProcess` accept a `SetDependencyMap()` call and skip `AnalyzeDependenciesAsync` when a map is already provided. `SmartMigrationOrchestrator.RunAsync` threads `existingDependencyMap` through to both migration paths. Dependency output files (`dependency-map.json`, `dependency-diagram.md`) are now generated in the RE output folder as well as the migration output folder.
23+
- **`doctor.sh`** — Updated `convert-only` to prompt for `--reuse-re`; corrected portal navigation references to match current UI (`'📄 Reverse Engineering Results'`).
24+
825
## [2.4.0] - 2026-02-16
926

1027
### Added

Helpers/FileHelper.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,16 @@ private string ExtractClassNameFromContent(string content)
429429
return "GeneratedClass";
430430
}
431431

432+
/// <summary>Writes dependency-map.json and dependency-diagram.md to <paramref name="outputFolder"/>.</summary>
433+
public async Task SaveDependencyOutputsAsync(DependencyMap dependencyMap, string outputFolder)
434+
{
435+
Directory.CreateDirectory(outputFolder);
436+
await SaveDependencyMapAsync(dependencyMap, Path.Join(outputFolder, "dependency-map.json"));
437+
await File.WriteAllTextAsync(
438+
Path.Join(outputFolder, "dependency-diagram.md"),
439+
$"# COBOL Dependency Diagram\n\n```mermaid\n{dependencyMap.MermaidDiagram}\n```");
440+
}
441+
432442
/// <summary>
433443
/// Saves a dependency map to disk as JSON.
434444
/// </summary>

0 commit comments

Comments
 (0)