diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 4732c16..a744fa0 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2992,4 +2992,13 @@ target_link_libraries(step456_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step457_test tests/step457_test.cpp) +target_include_directories(step457_test PRIVATE src) +target_link_libraries(step457_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/TranslationReport.h b/editor/src/TranslationReport.h new file mode 100644 index 0000000..ccddff3 --- /dev/null +++ b/editor/src/TranslationReport.h @@ -0,0 +1,174 @@ +#pragma once + +// Step 457: Translation Report — per-function and project-level reporting for +// transpilation choices, confidence, annotation changes, and review flags. + +#include "IntentTranslator.h" +#include "TranspilationConfidence.h" + +#include +#include + +struct AnnotationDelta { + std::vector added; + std::vector removed; + std::vector changed; +}; + +struct FunctionTranslationReport { + std::string functionName; + std::string sourceLanguage; + std::string targetLanguage; + std::string sourceConstruct; + std::string targetConstruct; + float confidence = 0.0f; + bool idiomatic = false; + bool reviewRequired = false; + std::string reviewReason; + std::string safetyDelta; // gained/lost/neutral + AnnotationDelta annotations; + std::string rationale; +}; + +struct ProjectTranslationSummary { + int totalFunctions = 0; + int idiomaticCount = 0; + int literalCount = 0; + int flaggedForReviewCount = 0; + + float idiomaticPercent() const { + return totalFunctions == 0 ? 0.0f : + (100.0f * static_cast(idiomaticCount) / static_cast(totalFunctions)); + } + float literalPercent() const { + return totalFunctions == 0 ? 0.0f : + (100.0f * static_cast(literalCount) / static_cast(totalFunctions)); + } + float reviewPercent() const { + return totalFunctions == 0 ? 0.0f : + (100.0f * static_cast(flaggedForReviewCount) / static_cast(totalFunctions)); + } +}; + +struct TranslationReport { + std::string sourceLanguage; + std::string targetLanguage; + std::vector functions; + ProjectTranslationSummary summary; + + bool hasFunction(const std::string& functionName) const { + for (const auto& fn : functions) + if (fn.functionName == functionName) return true; + return false; + } + + FunctionTranslationReport getFunction(const std::string& functionName) const { + for (const auto& fn : functions) + if (fn.functionName == functionName) return fn; + return {}; + } +}; + +class TranslationReportGenerator { +public: + static TranslationReport generate( + const IntentTranslationResult& translations, + const std::vector& hasIntents = {}) { + + TranslationReport report; + report.sourceLanguage = translations.sourceLanguage; + report.targetLanguage = translations.targetLanguage; + + for (size_t i = 0; i < translations.translations.size(); ++i) { + const auto& choice = translations.translations[i]; + bool hasIntent = choice.intent.size() > 0; + if (i < hasIntents.size()) hasIntent = hasIntents[i]; + + auto score = TranspilationScorer::score( + choice.functionName, + choice.sourceCode, + choice.targetCode, + translations.sourceLanguage, + translations.targetLanguage, + hasIntent); + + FunctionTranslationReport fn; + fn.functionName = choice.functionName; + fn.sourceLanguage = choice.sourceLanguage; + fn.targetLanguage = choice.targetLanguage; + fn.sourceConstruct = classifyConstruct(choice.sourceCode); + fn.targetConstruct = classifyConstruct(choice.targetCode); + fn.confidence = score.score; + fn.idiomatic = choice.isIdiomatic; + fn.reviewRequired = choice.needsReview || score.reviewRequired; + fn.reviewReason = !choice.reviewReason.empty() ? choice.reviewReason : score.reviewReason; + fn.safetyDelta = inferSafetyDelta(choice.sourceLanguage, choice.targetLanguage, choice.targetCode); + fn.annotations = inferAnnotationDelta(choice, score); + fn.rationale = buildRationale(choice, score); + + report.functions.push_back(std::move(fn)); + } + + report.summary = computeSummary(report.functions); + return report; + } + +private: + static std::string classifyConstruct(const std::string& code) { + if (code.find("sort") != std::string::npos) return "sort"; + if (code.find("find") != std::string::npos || code.find("lookup") != std::string::npos) return "lookup"; + if (code.find("filter") != std::string::npos) return "filter"; + if (code.find("map") != std::string::npos || code.find("transform") != std::string::npos) return "transform"; + if (code.find("fold") != std::string::npos || code.find("sum") != std::string::npos || + code.find("accumulate") != std::string::npos) return "reduce"; + if (code.find("read") != std::string::npos || code.find("open(") != std::string::npos) return "io"; + if (code.find("TODO") != std::string::npos) return "unknown"; + return "structural"; + } + + static std::string inferSafetyDelta(const std::string& srcLang, + const std::string& tgtLang, + const std::string& targetCode) { + if (srcLang == "c" && (tgtLang == "rust" || tgtLang == "java" || tgtLang == "python")) + return "gained"; + if (srcLang == "rust" && tgtLang == "c") + return "lost"; + if (targetCode.find("TODO") != std::string::npos) + return "neutral"; + return "neutral"; + } + + static AnnotationDelta inferAnnotationDelta(const TranslationChoice& choice, + const ConfidenceScore& score) { + AnnotationDelta d; + d.added.push_back(score.annotation); + if (choice.isIdiomatic) d.added.push_back("@Idiomatic(true)"); + else d.added.push_back("@Idiomatic(false)"); + if (choice.needsReview || score.reviewRequired) + d.added.push_back("@Review(required,human)"); + return d; + } + + static std::string buildRationale(const TranslationChoice& choice, + const ConfidenceScore& score) { + if (choice.isIdiomatic) + return "Selected idiomatic target construct for intent: " + choice.intent; + if (score.category == TranslationCategory::StructuralWithIntent) + return "Intent present but no direct idiomatic mapping; structural translation used"; + if (score.category == TranslationCategory::StructuralNoIntent) + return "No explicit intent; literal/structural translation preserved"; + return "Unrecognized translation shape; review recommended"; + } + + static ProjectTranslationSummary computeSummary( + const std::vector& functions) { + ProjectTranslationSummary s; + s.totalFunctions = static_cast(functions.size()); + for (const auto& fn : functions) { + if (fn.idiomatic) ++s.idiomaticCount; + else ++s.literalCount; + if (fn.reviewRequired) ++s.flaggedForReviewCount; + } + return s; + } +}; diff --git a/editor/tests/step457_test.cpp b/editor/tests/step457_test.cpp new file mode 100644 index 0000000..7c6bdb2 --- /dev/null +++ b/editor/tests/step457_test.cpp @@ -0,0 +1,168 @@ +// Step 457: Translation Report Tests (12 tests) + +#include "IntentTranslator.h" +#include "TranslationReport.h" + +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... "; } +#define PASS() { std::cout << "PASS\n"; ++passed; } +#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; } +#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {} + +static IntentTranslationResult makeSampleTranslations() { + std::vector funcs = { + {"sort_items", "sort list ascending", "items.sort()"}, + {"find_user", "find element by key", "users.get(key)"}, + {"mystery", "", "x = a + b * c"} + }; + return IntentTranslator::translate(funcs, "python", "rust"); +} + +void test_report_contains_all_functions() { + TEST(report_contains_all_functions); + auto tr = makeSampleTranslations(); + auto report = TranslationReportGenerator::generate(tr); + CHECK(report.functions.size() == 3, "expected 3 function reports"); + CHECK(report.hasFunction("sort_items"), "missing sort_items"); + CHECK(report.hasFunction("find_user"), "missing find_user"); + CHECK(report.hasFunction("mystery"), "missing mystery"); + PASS(); +} + +void test_source_and_target_languages_propagated() { + TEST(source_and_target_languages_propagated); + auto tr = makeSampleTranslations(); + auto report = TranslationReportGenerator::generate(tr); + CHECK(report.sourceLanguage == "python", "wrong source language"); + CHECK(report.targetLanguage == "rust", "wrong target language"); + PASS(); +} + +void test_idiomatic_and_literal_counts() { + TEST(idiomatic_and_literal_counts); + auto tr = makeSampleTranslations(); + auto report = TranslationReportGenerator::generate(tr); + CHECK(report.summary.totalFunctions == 3, "wrong total"); + CHECK(report.summary.idiomaticCount == 2, "expected 2 idiomatic"); + CHECK(report.summary.literalCount == 1, "expected 1 literal"); + PASS(); +} + +void test_review_flag_count_includes_no_intent() { + TEST(review_flag_count_includes_no_intent); + auto tr = makeSampleTranslations(); + auto report = TranslationReportGenerator::generate(tr); + CHECK(report.summary.flaggedForReviewCount == 1, "expected one review-required function"); + CHECK(report.getFunction("mystery").reviewRequired, "mystery should require review"); + PASS(); +} + +void test_construct_classification_detects_sort() { + TEST(construct_classification_detects_sort); + auto tr = makeSampleTranslations(); + auto report = TranslationReportGenerator::generate(tr); + auto fn = report.getFunction("sort_items"); + CHECK(fn.targetConstruct == "sort", "expected sort construct"); + PASS(); +} + +void test_annotation_delta_includes_confidence_and_idiomatic() { + TEST(annotation_delta_includes_confidence_and_idiomatic); + auto tr = makeSampleTranslations(); + auto report = TranslationReportGenerator::generate(tr); + auto fn = report.getFunction("sort_items"); + CHECK(!fn.annotations.added.empty(), "expected added annotations"); + bool hasConfidence = false; + bool hasIdiomatic = false; + for (const auto& a : fn.annotations.added) { + if (a.find("@Confidence(") != std::string::npos) hasConfidence = true; + if (a.find("@Idiomatic(") != std::string::npos) hasIdiomatic = true; + } + CHECK(hasConfidence, "expected confidence annotation"); + CHECK(hasIdiomatic, "expected idiomatic annotation"); + PASS(); +} + +void test_rationale_mentions_idiomatic_when_applicable() { + TEST(rationale_mentions_idiomatic_when_applicable); + auto tr = makeSampleTranslations(); + auto report = TranslationReportGenerator::generate(tr); + auto fn = report.getFunction("find_user"); + CHECK(fn.rationale.find("idiomatic") != std::string::npos, "expected idiomatic rationale"); + PASS(); +} + +void test_safety_delta_c_to_rust_gained() { + TEST(safety_delta_c_to_rust_gained); + std::vector funcs = {{"sum_values", "sum list", "total += x"}}; + auto tr = IntentTranslator::translate(funcs, "c", "rust"); + auto report = TranslationReportGenerator::generate(tr); + auto fn = report.getFunction("sum_values"); + CHECK(fn.safetyDelta == "gained", "expected gained safety delta for c->rust"); + PASS(); +} + +void test_safety_delta_rust_to_c_lost() { + TEST(safety_delta_rust_to_c_lost); + std::vector funcs = {{"sum_values", "sum list", "values.iter().fold(0, |a, x| a + x)"}}; + auto tr = IntentTranslator::translate(funcs, "rust", "c"); + auto report = TranslationReportGenerator::generate(tr); + auto fn = report.getFunction("sum_values"); + CHECK(fn.safetyDelta == "lost", "expected lost safety delta for rust->c"); + PASS(); +} + +void test_percentages_are_computed() { + TEST(percentages_are_computed); + auto tr = makeSampleTranslations(); + auto report = TranslationReportGenerator::generate(tr); + CHECK(report.summary.idiomaticPercent() > 66.0f && report.summary.idiomaticPercent() < 67.0f, + "expected idiomatic percent ~66.67"); + CHECK(report.summary.literalPercent() > 33.0f && report.summary.literalPercent() < 34.0f, + "expected literal percent ~33.33"); + PASS(); +} + +void test_get_function_returns_empty_for_missing_name() { + TEST(get_function_returns_empty_for_missing_name); + auto tr = makeSampleTranslations(); + auto report = TranslationReportGenerator::generate(tr); + auto fn = report.getFunction("does_not_exist"); + CHECK(fn.functionName.empty(), "missing function should return empty report entry"); + PASS(); +} + +void test_has_intent_override_affects_confidence_path() { + TEST(has_intent_override_affects_confidence_path); + std::vector funcs = {{"custom", "", "custom_transform(data)"}}; + auto tr = IntentTranslator::translate(funcs, "python", "java"); + auto reportNoIntent = TranslationReportGenerator::generate(tr, {false}); + auto reportWithIntent = TranslationReportGenerator::generate(tr, {true}); + auto a = reportNoIntent.getFunction("custom"); + auto b = reportWithIntent.getFunction("custom"); + CHECK(a.confidence != b.confidence, "intent override should affect confidence class"); + PASS(); +} + +int main() { + std::cout << "Step 457: Translation Report Tests\n"; + + test_report_contains_all_functions(); // 1 + test_source_and_target_languages_propagated(); // 2 + test_idiomatic_and_literal_counts(); // 3 + test_review_flag_count_includes_no_intent(); // 4 + test_construct_classification_detects_sort(); // 5 + test_annotation_delta_includes_confidence_and_idiomatic(); // 6 + test_rationale_mentions_idiomatic_when_applicable(); // 7 + test_safety_delta_c_to_rust_gained(); // 8 + test_safety_delta_rust_to_c_lost(); // 9 + test_percentages_are_computed(); // 10 + test_get_function_returns_empty_for_missing_name(); // 11 + test_has_intent_override_affects_confidence_path(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index be091e5..6e18b9d 100644 --- a/progress.md +++ b/progress.md @@ -5910,3 +5910,34 @@ translated functions, including aggregated report statistics. **Architecture gate check:** - `editor/src/TranspilationConfidence.h` within header-size limit (`153` <= `600`) - `editor/tests/step456_test.cpp` within test-file size guidance (`155` lines) + +### Step 457: Translation Report +**Status:** PASS (12/12 tests) + +Adds per-function and project-level transpilation reporting, including mapping +shape, confidence, annotation deltas, safety delta, idiomatic/literal split, +and review-flag rollups. + +**Files added:** +- `editor/src/TranslationReport.h` — report generator: + - function-level report model with construct mapping, confidence, rationale, + safety delta, and annotation-change tracking + - project summary metrics: total, idiomatic/literal counts, review flags, + and percentage rollups + - integration with intent translation + confidence scorer +- `editor/tests/step457_test.cpp` — 12 tests covering: + - per-function presence and retrieval behavior + - summary counts and percentage calculations + - annotation/rationale generation + - safety delta behavior for `c→rust` and `rust→c` + - intent override impact on confidence path +- `editor/CMakeLists.txt` — `step457_test` target + +**Verification run:** +- `cmake --build editor/build-native --target step457_test` — PASS +- `./editor/build-native/step457_test` — PASS (12/12) +- `./editor/build-native/step456_test` — PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/TranslationReport.h` within header-size limit (`174` <= `600`) +- `editor/tests/step457_test.cpp` within test-file size guidance (`168` lines)