From dacea5964482ae94a3267dbb04689465754f0ca6 Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Wed, 22 Jul 2026 13:06:54 -0400 Subject: [PATCH 01/11] Add Swift codemap pipeline benchmark instrumentation --- Package.swift | 9 +- Scripts/conductor.py | 4 + Scripts/test_conductor_lifecycle.py | 12 + .../Features/CodeMap/CodeMapPerfStats.swift | 12 +- .../CodeMapSyntaxArtifactBuilder.swift | 18 +- .../CodeMapSyntaxEngine.swift | 10 +- .../Extraction/CodeMapCaptureIndex.swift | 168 +++- .../Extraction/CodeMapGenerator.swift | 48 +- .../SwiftCodeMapStrategy.swift | 272 ++++- .../CodeMapPerformanceCollector.swift | 55 +- ...deMapQueryOptimizationBenchmarkTests.swift | 253 +++++ ...SwiftCodeMapPipelineBenchmarkSupport.swift | 934 ++++++++++++++++++ ...rkspaceCodeMapPhase3BenchmarkSupport.swift | 1 + ...rkspaceCodeMapPhase4BenchmarkSupport.swift | 1 + ...SearchIndexTimeToReadyBenchmarkTests.swift | 4 +- 15 files changed, 1703 insertions(+), 98 deletions(-) create mode 100644 Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift diff --git a/Package.swift b/Package.swift index cdbd8ec29..4bfec97ee 100644 --- a/Package.swift +++ b/Package.swift @@ -88,6 +88,10 @@ var repoPromptTestSwiftSettings: [SwiftSetting] = [ .define("DEBUG", .when(configuration: .debug)) ] +var repoPromptCodeMapTestSwiftSettings: [SwiftSetting] = [ + .define("DEBUG", .when(configuration: .debug)) +] + if sentryEnabled { let sentryDependency = Target.Dependency.product(name: "Sentry", package: "sentry-cocoa") repoPromptAppDependencies.append(sentryDependency) @@ -98,6 +102,7 @@ if sentryEnabled { if benchmarkTestsEnabled { repoPromptTestSwiftSettings.append(.define("RPCE_BENCHMARK_TESTS")) + repoPromptCodeMapTestSwiftSettings.append(.define("RPCE_BENCHMARK_TESTS")) } let swift6LanguageMode: [SwiftSetting] = [ @@ -194,9 +199,7 @@ let package = Package( .copy("Fixtures"), .copy("Goldens") ], - swiftSettings: swift6LanguageMode + [ - .define("DEBUG", .when(configuration: .debug)) - ] + swiftSettings: swift6LanguageMode + repoPromptCodeMapTestSwiftSettings ), .testTarget( name: "RepoPromptTests", diff --git a/Scripts/conductor.py b/Scripts/conductor.py index 3efd4555f..377f43e58 100755 --- a/Scripts/conductor.py +++ b/Scripts/conductor.py @@ -1256,6 +1256,10 @@ class OperationRegistry: "RPCE_ENABLE_BENCHMARK_TESTS", "RPCE_RUN_CODEMAP_E2E", "RPCE_RUN_SCALE_TESTS", + "RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK", + "RP_SWIFT_CODEMAP_ALLOWED_REMOVED_CAPTURES", + "RP_SWIFT_CODEMAP_REFERENCE_MODE", + "RP_SWIFT_CODEMAP_REFERENCE_PATH", ] CONDUCTOR_ENV_KEYS = [ "REPOPROMPT_DEV_HEAVY_SLOTS", diff --git a/Scripts/test_conductor_lifecycle.py b/Scripts/test_conductor_lifecycle.py index 7ea24ae8b..eb85ddbdb 100644 --- a/Scripts/test_conductor_lifecycle.py +++ b/Scripts/test_conductor_lifecycle.py @@ -1730,6 +1730,10 @@ def test_test_gate_environment_survives_client_snapshot_and_job_prepare(self) -> "RPCE_ENABLE_BENCHMARK_TESTS": "1", "RPCE_RUN_CODEMAP_E2E": "1", "RPCE_RUN_SCALE_TESTS": "1", + "RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK": "1", + "RP_SWIFT_CODEMAP_ALLOWED_REMOVED_CAPTURES": "type.class", + "RP_SWIFT_CODEMAP_REFERENCE_MODE": "compare", + "RP_SWIFT_CODEMAP_REFERENCE_PATH": "/tmp/reference.json", "RPCE_UNRELATED_TEST_GATE": "1", }, clear=False, @@ -1739,6 +1743,10 @@ def test_test_gate_environment_survives_client_snapshot_and_job_prepare(self) -> self.assertEqual(snapshot["RPCE_ENABLE_BENCHMARK_TESTS"], "1") self.assertEqual(snapshot["RPCE_RUN_CODEMAP_E2E"], "1") self.assertEqual(snapshot["RPCE_RUN_SCALE_TESTS"], "1") + self.assertEqual(snapshot["RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK"], "1") + self.assertEqual(snapshot["RP_SWIFT_CODEMAP_ALLOWED_REMOVED_CAPTURES"], "type.class") + self.assertEqual(snapshot["RP_SWIFT_CODEMAP_REFERENCE_MODE"], "compare") + self.assertEqual(snapshot["RP_SWIFT_CODEMAP_REFERENCE_PATH"], "/tmp/reference.json") self.assertNotIn("RPCE_UNRELATED_TEST_GATE", snapshot) _argv, _lanes, _cwd, env, _timeout = registry.prepare( @@ -1752,6 +1760,10 @@ def test_test_gate_environment_survives_client_snapshot_and_job_prepare(self) -> self.assertEqual(env["RPCE_ENABLE_BENCHMARK_TESTS"], "1") self.assertEqual(env["RPCE_RUN_CODEMAP_E2E"], "1") self.assertEqual(env["RPCE_RUN_SCALE_TESTS"], "1") + self.assertEqual(env["RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK"], "1") + self.assertEqual(env["RP_SWIFT_CODEMAP_ALLOWED_REMOVED_CAPTURES"], "type.class") + self.assertEqual(env["RP_SWIFT_CODEMAP_REFERENCE_MODE"], "compare") + self.assertEqual(env["RP_SWIFT_CODEMAP_REFERENCE_PATH"], "/tmp/reference.json") self.assertNotIn("RPCE_UNRELATED_TEST_GATE", env) def test_test_cli_forwards_watchdog_options_and_requires_threshold(self) -> None: diff --git a/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift b/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift index db9103ac0..8625fb145 100644 --- a/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift +++ b/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift @@ -50,8 +50,7 @@ struct CodeMapSyntaxPerfStats { var parserCreates = 0 var queryExecutes = 0 var captures = 0 - var codeMapQueryCacheHits = 0 - var codeMapQueryCacheMisses = 0 + var codeMapQuerySuccessfulLookups = 0 } struct CodeMapPipelinePerfSnapshot: Equatable { @@ -142,8 +141,7 @@ struct CodeMapPipelinePerfSnapshot: Equatable { var parseFailures = 0 var generatedAPIs = 0 var nilAPIs = 0 - var codeMapQueryCacheHits = 0 - var codeMapQueryCacheMisses = 0 + var codeMapQuerySuccessfulLookups = 0 var syntaxWarmCacheLanguageCount = 0 var syntaxLanguageConfigCreateCount = 0 var syntaxLanguageConfigSuccessCount = 0 @@ -312,8 +310,7 @@ final class CodeMapPipelinePerfStats: @unchecked Sendable { storage.syntaxParserCreateCount += stats.parserCreates storage.syntaxQueryExecuteCount += stats.queryExecutes storage.syntaxCaptureCount += stats.captures - storage.codeMapQueryCacheHits += stats.codeMapQueryCacheHits - storage.codeMapQueryCacheMisses += stats.codeMapQueryCacheMisses + storage.codeMapQuerySuccessfulLookups += stats.codeMapQuerySuccessfulLookups } } @@ -336,8 +333,7 @@ final class CodeMapPipelinePerfStats: @unchecked Sendable { parserCreates: stats.syntaxParserCreates, queryExecutes: stats.syntaxQueryExecutes, captures: stats.syntaxCaptures, - codeMapQueryCacheHits: stats.syntaxCodeMapQueryCacheHits, - codeMapQueryCacheMisses: stats.syntaxCodeMapQueryCacheMisses + codeMapQuerySuccessfulLookups: stats.syntaxCodeMapQuerySuccessfulLookups ) ) } diff --git a/Sources/RepoPromptCodeMapCore/CodeMapSyntaxArtifactBuilder.swift b/Sources/RepoPromptCodeMapCore/CodeMapSyntaxArtifactBuilder.swift index 80103989c..0ee6ef35f 100644 --- a/Sources/RepoPromptCodeMapCore/CodeMapSyntaxArtifactBuilder.swift +++ b/Sources/RepoPromptCodeMapCore/CodeMapSyntaxArtifactBuilder.swift @@ -1,3 +1,5 @@ +import Foundation + package enum CodeMapSyntaxArtifactBuilder { package static func build( source: CodeMapCoreSourceSnapshot, @@ -6,6 +8,13 @@ package enum CodeMapSyntaxArtifactBuilder { performanceOptions: CodeMapPerfOptions = .disabled, performanceCollector: CodeMapPerformanceCollector? = nil ) throws -> CodeMapSyntaxArtifactOutcome { + let builderStart = performanceCollector.map { _ in ProcessInfo.processInfo.systemUptime } + defer { + if let builderStart { + performanceCollector?.builderTotalDuration += ProcessInfo.processInfo.systemUptime - builderStart + } + } + guard case let .decoded(decodedSource) = source.decodeResult else { guard case let .failed(failure) = source.decodeResult else { preconditionFailure("CodeMapSourceDecodeResult gained an unhandled case.") @@ -27,13 +36,18 @@ package enum CodeMapSyntaxArtifactBuilder { switch syntaxOutcome { case let .captures(captures): - guard let artifact = CodeMapGenerator.generateSyntaxArtifact( + let generatorStart = performanceCollector.map { _ in ProcessInfo.processInfo.systemUptime } + let artifact = CodeMapGenerator.generateSyntaxArtifact( from: captures, content: content, language: language, perfOptions: performanceOptions, perfStats: performanceCollector - ) else { + ) + if let generatorStart { + performanceCollector?.builderGeneratorDuration += ProcessInfo.processInfo.systemUptime - generatorStart + } + guard let artifact else { return .readyNoSymbols } return .ready(artifact) diff --git a/Sources/RepoPromptCodeMapCore/CodeMapSyntaxEngine.swift b/Sources/RepoPromptCodeMapCore/CodeMapSyntaxEngine.swift index acc3f8c43..6e9d75bf6 100644 --- a/Sources/RepoPromptCodeMapCore/CodeMapSyntaxEngine.swift +++ b/Sources/RepoPromptCodeMapCore/CodeMapSyntaxEngine.swift @@ -226,7 +226,13 @@ package struct CodeMapSyntaxEngine: CodeMapSyntaxPerformanceQuerying, Sendable { performanceCollector: CodeMapPerformanceCollector? ) throws -> CodeMapSyntaxQueryOutcome { let collect = performanceCollector != nil - if collect { performanceCollector?.syntaxCalls = 1 } + let totalStart = collect ? ProcessInfo.processInfo.systemUptime : nil + defer { + if let totalStart { + performanceCollector?.syntaxTotalDuration += ProcessInfo.processInfo.systemUptime - totalStart + } + } + if collect { performanceCollector?.syntaxCalls += 1 } let oversizeStart = collect ? ProcessInfo.processInfo.systemUptime : nil let reason = oversizeReason(for: content) @@ -282,7 +288,7 @@ package struct CodeMapSyntaxEngine: CodeMapSyntaxPerformanceQuerying, Sendable { if let queryLookupStart { performanceCollector?.syntaxCodeMapQueryLookupDuration += ProcessInfo.processInfo.systemUptime - queryLookupStart - performanceCollector?.syntaxCodeMapQueryCacheHits += 1 + performanceCollector?.syntaxCodeMapQuerySuccessfulLookups += 1 } let queryStart = collect ? ProcessInfo.processInfo.systemUptime : nil diff --git a/Sources/RepoPromptCodeMapCore/Extraction/CodeMapCaptureIndex.swift b/Sources/RepoPromptCodeMapCore/Extraction/CodeMapCaptureIndex.swift index e0ca78bba..6a48e7f3f 100644 --- a/Sources/RepoPromptCodeMapCore/Extraction/CodeMapCaptureIndex.swift +++ b/Sources/RepoPromptCodeMapCore/Extraction/CodeMapCaptureIndex.swift @@ -22,8 +22,12 @@ struct CodeMapCaptureIndex { /// Captures grouped by name, each list sorted by location let byName: [String: [CodeMapIndexedCapture]] + /// Present only for benchmark/debug/test attribution. The nil path retains the + /// original lookup loops without per-candidate instrumentation. + private let performanceCollector: CodeMapPerformanceCollector? + /// Creates an index from an array of captures - init(_ captures: [NamedRange]) { + init(_ captures: [NamedRange], performanceCollector: CodeMapPerformanceCollector? = nil) { let normalized = captures.map { capture in CodeMapIndexedCapture(name: capture.name, range: capture.range) } @@ -34,6 +38,9 @@ struct CodeMapCaptureIndex { grouped[cap.name, default: []].append(cap) } byName = grouped + self.performanceCollector = performanceCollector + performanceCollector?.captureIndexInputCaptureCount += captures.count + performanceCollector?.captureIndexBucketCount += grouped.count } /// Returns all captures with the given name, sorted by location @@ -43,72 +50,76 @@ struct CodeMapCaptureIndex { /// Returns the first capture with the given name that is fully contained within the parent range func firstCapture(named name: String, containedIn parent: NSRange) -> CodeMapIndexedCapture? { - guard let candidates = byName[name] else { return nil } + guard let performanceCollector else { + guard let candidates = byName[name] else { return nil } + return firstCapture(in: candidates, containedIn: parent) + } - // Binary search to find the first candidate that could be in range + performanceCollector.captureIndexFirstContainedLookupCount += 1 + guard let candidates = byName[name] else { return nil } let startIdx = candidates.binarySearch { $0.range.location < parent.location } - + var visits = 0 + var result: CodeMapIndexedCapture? for i in startIdx ..< candidates.count { + visits += 1 let cap = candidates[i] - // If we've gone past the parent range, stop if cap.range.location >= NSMaxRange(parent) { break } - // Check containment if rangeContains(parent, cap.range) { - return cap + result = cap + break } } - return nil + performanceCollector.captureIndexFirstContainedCandidateVisits += visits + recordMaximumCandidateVisits(visits, in: performanceCollector) + return result } /// Returns all captures with the given name that are fully contained within the parent range func captures(named name: String, containedIn parent: NSRange) -> [CodeMapIndexedCapture] { - guard let candidates = byName[name] else { return [] } - - var results: [CodeMapIndexedCapture] = [] - - // Binary search to find the first candidate that could be in range - let startIdx = candidates.binarySearch { $0.range.location < parent.location } - - for i in startIdx ..< candidates.count { - let cap = candidates[i] - // If we've gone past the parent range, stop - if cap.range.location >= NSMaxRange(parent) { break } - // Check containment - if rangeContains(parent, cap.range) { - results.append(cap) - } + guard let performanceCollector else { + guard let candidates = byName[name] else { return [] } + return containedCaptures(in: candidates, parent: parent) } - return results + + performanceCollector.captureIndexAllContainedLookupCount += 1 + guard let candidates = byName[name] else { return [] } + let result = countedContainedCaptures(in: candidates, parent: parent) + performanceCollector.captureIndexAllContainedCandidateVisits += result.visits + recordMaximumCandidateVisits(result.visits, in: performanceCollector) + return result.captures } /// Returns all captures (of any name) that are fully contained within the parent range func allCaptures(containedIn parent: NSRange) -> [CodeMapIndexedCapture] { - var results: [CodeMapIndexedCapture] = [] - - // Binary search to find the first capture that could be in range - let startIdx = all.binarySearch { $0.range.location < parent.location } - - for i in startIdx ..< all.count { - let cap = all[i] - // If we've gone past the parent range, stop - if cap.range.location >= NSMaxRange(parent) { break } - // Check containment - if rangeContains(parent, cap.range) { - results.append(cap) - } + guard let performanceCollector else { + return containedCaptures(in: all, parent: parent) } - return results + + performanceCollector.captureIndexAllContainedLookupCount += 1 + let result = countedContainedCaptures(in: all, parent: parent) + performanceCollector.captureIndexAllContainedCandidateVisits += result.visits + recordMaximumCandidateVisits(result.visits, in: performanceCollector) + return result.captures } /// Returns the smallest capture with the given name that fully contains the target range. func smallestCapture(named name: String, containing target: NSRange) -> CodeMapIndexedCapture? { + guard let performanceCollector else { + guard let candidates = byName[name] else { return nil } + return smallestCapture(in: candidates, containing: target) + } + + performanceCollector.captureIndexSmallestContainingLookupCount += 1 guard let candidates = byName[name] else { return nil } - return smallestCapture(in: candidates, containing: target) + let result = countedSmallestCapture(in: candidates, containing: target) + performanceCollector.captureIndexSmallestContainingCandidateVisits += result.visits + recordMaximumCandidateVisits(result.visits, in: performanceCollector) + return result.capture } /// Returns the smallest capture with any of the given names that fully contains the target range. func smallestCapture(namedAny names: [String], containing target: NSRange) -> CodeMapIndexedCapture? { - var best: CodeMapIndexedCapture? = nil + var best: CodeMapIndexedCapture? for name in names { guard let candidate = smallestCapture(named: name, containing: target) else { continue } if best == nil || isBetterContainingCandidate(candidate, than: best!) { @@ -118,6 +129,49 @@ struct CodeMapCaptureIndex { return best } + private func firstCapture( + in candidates: [CodeMapIndexedCapture], + containedIn parent: NSRange + ) -> CodeMapIndexedCapture? { + let startIdx = candidates.binarySearch { $0.range.location < parent.location } + for i in startIdx ..< candidates.count { + let cap = candidates[i] + if cap.range.location >= NSMaxRange(parent) { break } + if rangeContains(parent, cap.range) { return cap } + } + return nil + } + + private func containedCaptures( + in candidates: [CodeMapIndexedCapture], + parent: NSRange + ) -> [CodeMapIndexedCapture] { + let startIdx = candidates.binarySearch { $0.range.location < parent.location } + var results: [CodeMapIndexedCapture] = [] + for i in startIdx ..< candidates.count { + let cap = candidates[i] + if cap.range.location >= NSMaxRange(parent) { break } + if rangeContains(parent, cap.range) { results.append(cap) } + } + return results + } + + private func countedContainedCaptures( + in candidates: [CodeMapIndexedCapture], + parent: NSRange + ) -> (captures: [CodeMapIndexedCapture], visits: Int) { + let startIdx = candidates.binarySearch { $0.range.location < parent.location } + var results: [CodeMapIndexedCapture] = [] + var visits = 0 + for i in startIdx ..< candidates.count { + visits += 1 + let cap = candidates[i] + if cap.range.location >= NSMaxRange(parent) { break } + if rangeContains(parent, cap.range) { results.append(cap) } + } + return (results, visits) + } + private func smallestCapture( in candidates: [CodeMapIndexedCapture], containing target: NSRange @@ -125,7 +179,7 @@ struct CodeMapCaptureIndex { let endIdx = candidates.binarySearch { $0.range.location <= target.location } guard endIdx > 0 else { return nil } - var best: CodeMapIndexedCapture? = nil + var best: CodeMapIndexedCapture? for i in stride(from: endIdx - 1, through: 0, by: -1) { let candidate = candidates[i] if rangeContains(candidate.range, target), @@ -137,6 +191,35 @@ struct CodeMapCaptureIndex { return best } + private func countedSmallestCapture( + in candidates: [CodeMapIndexedCapture], + containing target: NSRange + ) -> (capture: CodeMapIndexedCapture?, visits: Int) { + let endIdx = candidates.binarySearch { $0.range.location <= target.location } + guard endIdx > 0 else { return (nil, 0) } + + var best: CodeMapIndexedCapture? + for i in stride(from: endIdx - 1, through: 0, by: -1) { + let candidate = candidates[i] + if rangeContains(candidate.range, target), + best == nil || isBetterContainingCandidate(candidate, than: best!) + { + best = candidate + } + } + return (best, endIdx) + } + + private func recordMaximumCandidateVisits( + _ visits: Int, + in performanceCollector: CodeMapPerformanceCollector + ) { + performanceCollector.captureIndexMaximumCandidateVisits = max( + performanceCollector.captureIndexMaximumCandidateVisits, + visits + ) + } + private func isBetterContainingCandidate( _ candidate: CodeMapIndexedCapture, than current: CodeMapIndexedCapture @@ -149,8 +232,7 @@ struct CodeMapCaptureIndex { /// Checks if inner range is fully contained within outer range private func rangeContains(_ outer: NSRange, _ inner: NSRange) -> Bool { - inner.location >= outer.location && - NSMaxRange(inner) <= NSMaxRange(outer) + inner.location >= outer.location && NSMaxRange(inner) <= NSMaxRange(outer) } } diff --git a/Sources/RepoPromptCodeMapCore/Extraction/CodeMapGenerator.swift b/Sources/RepoPromptCodeMapCore/Extraction/CodeMapGenerator.swift index 9bf3f2684..8a6b21b2f 100644 --- a/Sources/RepoPromptCodeMapCore/Extraction/CodeMapGenerator.swift +++ b/Sources/RepoPromptCodeMapCore/Extraction/CodeMapGenerator.swift @@ -306,7 +306,10 @@ struct CodeMapGenerator { perfOptions: perfOptions, perfStats: perfStats ) - return makeSyntaxArtifact(from: output) + return makeSyntaxArtifact( + from: output, + performanceCollector: perfOptions.enabled ? perfStats : nil + ) } private static func declarationTerminator(for language: LanguageType) -> Character { @@ -359,7 +362,10 @@ struct CodeMapGenerator { // Build capture index for efficient lookups let indexToken = Signpost.begin("codemap.index") let indexStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 - let captureIndex = CodeMapCaptureIndex(namedRanges) + let captureIndex = CodeMapCaptureIndex( + namedRanges, + performanceCollector: perfEnabled ? activePerfStats : nil + ) if perfEnabled { activePerfStats?.captureIndexDuration += (CFAbsoluteTimeGetCurrent() - indexStart) } @@ -699,7 +705,8 @@ struct CodeMapGenerator { swiftContext = SwiftCodeMapStrategy.buildContext( index: captureIndex, content: content, - boundaries: boundaries + boundaries: boundaries, + performanceCollector: activePerfStats ) if perfEnabled { activePerfStats?.swiftContextDuration += (CFAbsoluteTimeGetCurrent() - swiftContextStart) @@ -1919,6 +1926,7 @@ struct CodeMapGenerator { let finalReferences = referencedTypes.finalizeSorted() if perfEnabled { activePerfStats?.referencedTypesFinalizeDuration += (CFAbsoluteTimeGetCurrent() - typeFinalizeStart) + activePerfStats?.referencedTypesUniqueCount += finalReferences.count } Signpost.end("codemap.referenced_types", typeFinalizeToken) @@ -1937,20 +1945,36 @@ struct CodeMapGenerator { ) } - private static func makeSyntaxArtifact(from output: GenerationOutput) -> CodeMapSyntaxArtifact? { + private static func makeSyntaxArtifact( + from output: GenerationOutput, + performanceCollector: CodeMapPerformanceCollector? + ) -> CodeMapSyntaxArtifact? { + let finalizationStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } + defer { + if let finalizationStart { + performanceCollector?.artifactFinalizationDuration += + CFAbsoluteTimeGetCurrent() - finalizationStart + } + } + let classes = finalizedClasses(output.classesByLine) let interfaces = finalizedInterfaces(output.interfacesByLine) - guard hasMeaningfulContent( + let meaningfulStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } + let meaningful = hasMeaningfulContent( output: output, classes: classes, interfaces: interfaces, functions: output.globalFunctions, globalVars: output.globalVariables - ) else { - return nil + ) + if let meaningfulStart { + performanceCollector?.artifactMeaningfulContentCheckDuration += + CFAbsoluteTimeGetCurrent() - meaningfulStart } + guard meaningful else { return nil } - return CodeMapSyntaxArtifact( + let artifactStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } + let artifact = CodeMapSyntaxArtifact( imports: output.imports, exports: output.exports, classes: classes, @@ -1963,6 +1987,14 @@ struct CodeMapGenerator { macros: output.macros, referencedTypes: output.referencedTypes ) + if let artifactStart { + performanceCollector?.fileAPIInitDuration += CFAbsoluteTimeGetCurrent() - artifactStart + performanceCollector?.artifactFinalClassCount += classes.count + performanceCollector?.artifactFinalInterfaceCount += interfaces.count + performanceCollector?.artifactFinalFunctionCount += output.globalFunctions.count + performanceCollector?.artifactFinalGlobalVariableCount += output.globalVariables.count + } + return artifact } private static func finalizedClasses(_ classesByLine: [Int: ClassInfo]) -> [ClassInfo] { diff --git a/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift b/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift index 5acd747e4..ca697dbec 100644 --- a/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift +++ b/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift @@ -96,7 +96,8 @@ enum SwiftCodeMapStrategy { static func buildContext( index: CodeMapCaptureIndex, content: String, - boundaries: [Int] + boundaries: [Int], + performanceCollector: CodeMapPerformanceCollector? = nil ) -> Context { var ctx = Context() let nsContent = content as NSString @@ -150,12 +151,19 @@ enum SwiftCodeMapStrategy { // First pass: collect type names let typeDeclCaps = index.captures(named: "swift.type.decl") let typeNameCaps = index.captures(named: "swift.type.name") + performanceCollector?.swiftTypeDeclarationCount += typeDeclCaps.count + let typeMappingStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } ctx.typeNamesByRange = mapNamesToSmallestContainingDecl( nameCaps: typeNameCaps, declCaps: typeDeclCaps ) + if let typeMappingStart { + performanceCollector?.swiftTypeNameMappingDuration += + CFAbsoluteTimeGetCurrent() - typeMappingStart + } // Second pass: build boundaries with full ranges + let typeBoundaryStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } for cap in typeDeclCaps { if let name = ctx.typeNamesByRange[cap.range] { let declText = nsContent.substring(with: cap.range) @@ -182,14 +190,27 @@ enum SwiftCodeMapStrategy { } } + if let typeBoundaryStart { + performanceCollector?.swiftBoundaryConstructionDuration += + CFAbsoluteTimeGetCurrent() - typeBoundaryStart + } + // Also collect protocols let protocolDeclCaps = index.captures(named: "swift.protocol.decl") let protocolNameCaps = index.captures(named: "swift.protocol.name") + performanceCollector?.swiftProtocolDeclarationCount += protocolDeclCaps.count + let protocolMappingStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } ctx.protocolNamesByRange = mapNamesToSmallestContainingDecl( nameCaps: protocolNameCaps, declCaps: protocolDeclCaps ) + if let protocolMappingStart { + performanceCollector?.swiftProtocolNameMappingDuration += + CFAbsoluteTimeGetCurrent() - protocolMappingStart + } + + let protocolBoundaryStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } for cap in protocolDeclCaps { if let name = ctx.protocolNamesByRange[cap.range] { let lineNo = lineNumber(for: cap.range.location, using: boundaries) @@ -197,9 +218,26 @@ enum SwiftCodeMapStrategy { } } + if let protocolBoundaryStart { + performanceCollector?.swiftBoundaryConstructionDuration += + CFAbsoluteTimeGetCurrent() - protocolBoundaryStart + } + + let functionAssemblyStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } let topLevelFunctionCaps = index.captures(named: "swift.function.toplevel") let methodFunctionCaps = index.captures(named: "swift.function.method") let protocolFunctionCaps = index.captures(named: "swift.protocol.method") + performanceCollector?.swiftTopLevelFunctionCount += topLevelFunctionCaps.count + performanceCollector?.swiftMethodFunctionCount += methodFunctionCaps.count + performanceCollector?.swiftProtocolMethodCount += protocolFunctionCaps.count + performanceCollector?.swiftParameterNodeCount += index.captures(named: "swift.param.node").count + performanceCollector?.swiftPropertyDeclarationCount += index.captures(named: "swift.property.decl").count + performanceCollector?.swiftProtocolPropertyDeclarationCount += + index.captures(named: "swift.protocol.property.decl").count + performanceCollector?.swiftPropertyIdentifierCount += + index.captures(named: "swift.property.toplevel").count + + index.captures(named: "swift.property.member").count + + index.captures(named: "swift.protocol.property").count ctx.functionCaptures.reserveCapacity( topLevelFunctionCaps.count + methodFunctionCaps.count + protocolFunctionCaps.count ) @@ -210,6 +248,11 @@ enum SwiftCodeMapStrategy { // Sort boundaries by range location ctx.typeBoundaries.sort { $0.range.location < $1.range.location } + if let functionAssemblyStart { + performanceCollector?.swiftFunctionCaptureAssemblyDuration += + CFAbsoluteTimeGetCurrent() - functionAssemblyStart + performanceCollector?.swiftTypeBoundaryCount += ctx.typeBoundaries.count + } return ctx } @@ -226,18 +269,33 @@ enum SwiftCodeMapStrategy { private static func extractSwiftFunctionSignature( from functionRange: NSRange, nsContent: NSString, - boundaries: [Int] + boundaries: [Int], + performanceCollector: CodeMapPerformanceCollector? ) -> SwiftFunctionSignature { - let signatureEnd = signatureEndLocation(forFunctionRange: functionRange, nsContent: nsContent) + let scanStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } + let signatureEnd = signatureEndLocation( + forFunctionRange: functionRange, + nsContent: nsContent, + performanceCollector: performanceCollector + ) + if let scanStart { + performanceCollector?.swiftSignatureEndScanDuration += + CFAbsoluteTimeGetCurrent() - scanStart + } let signatureLength = signatureEnd - functionRange.location let signatureRange = NSRange(location: functionRange.location, length: signatureLength) // Get the signature text + let normalizationStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } var signature = nsContent.substring(with: signatureRange) .trimmingCharacters(in: .whitespacesAndNewlines) // Normalize whitespace (collapse multiple whitespace to single space) signature = signature.replacing(#/\s+/#, with: " ") + if let normalizationStart { + performanceCollector?.swiftSignatureNormalizationDuration += + CFAbsoluteTimeGetCurrent() - normalizationStart + } return SwiftFunctionSignature(definitionLine: signature, signatureEnd: signatureEnd) } @@ -271,7 +329,12 @@ enum SwiftCodeMapStrategy { // Top-level Swift functions go directly to globalFunctions // Use Swift-specific signature extraction to avoid semicolon heuristic issues let signatureStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 - let signature = extractSwiftFunctionSignature(from: cap.range, nsContent: nsContent, boundaries: boundaries) + let signature = extractSwiftFunctionSignature( + from: cap.range, + nsContent: nsContent, + boundaries: boundaries, + performanceCollector: activePerfStats + ) if perfEnabled { record(.functionSignature, duration: CFAbsoluteTimeGetCurrent() - signatureStart, perfStats: activePerfStats) } @@ -295,7 +358,8 @@ enum SwiftCodeMapStrategy { context: context, index: index, nsContent: nsContent, - referencedTypes: &referencedTypes + referencedTypes: &referencedTypes, + performanceCollector: activePerfStats ) if perfEnabled { record(.parameterExtraction, duration: CFAbsoluteTimeGetCurrent() - parameterStart, perfStats: activePerfStats) @@ -318,7 +382,11 @@ enum SwiftCodeMapStrategy { lineNumber: lineNo ) - if !globalFunctions.contains(where: { $0.definitionLine == decl }) { + if !containsFunction( + in: globalFunctions, + definitionLine: decl, + performanceCollector: activePerfStats + ) { globalFunctions.append(fnInfo) } if perfEnabled { @@ -333,7 +401,12 @@ enum SwiftCodeMapStrategy { // Swift methods - use range-based containment to find enclosing type // Use Swift-specific signature extraction to avoid semicolon heuristic issues let signatureStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 - let signature = extractSwiftFunctionSignature(from: cap.range, nsContent: nsContent, boundaries: boundaries) + let signature = extractSwiftFunctionSignature( + from: cap.range, + nsContent: nsContent, + boundaries: boundaries, + performanceCollector: activePerfStats + ) if perfEnabled { record(.functionSignature, duration: CFAbsoluteTimeGetCurrent() - signatureStart, perfStats: activePerfStats) } @@ -357,7 +430,8 @@ enum SwiftCodeMapStrategy { context: context, index: index, nsContent: nsContent, - referencedTypes: &referencedTypes + referencedTypes: &referencedTypes, + performanceCollector: activePerfStats ) if perfEnabled { record(.parameterExtraction, duration: CFAbsoluteTimeGetCurrent() - parameterStart, perfStats: activePerfStats) @@ -373,7 +447,11 @@ enum SwiftCodeMapStrategy { } // Find enclosing type by range containment let enclosingTypeStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 - let resolvedEnclosingType = enclosingType(for: cap.range, in: context.typeBoundaries) + let resolvedEnclosingType = enclosingType( + for: cap.range, + in: context.typeBoundaries, + performanceCollector: activePerfStats + ) if perfEnabled { record(.enclosingTypeLookup, duration: CFAbsoluteTimeGetCurrent() - enclosingTypeStart, perfStats: activePerfStats) } @@ -391,12 +469,20 @@ enum SwiftCodeMapStrategy { if classesByLine[lineNo] == nil { classesByLine[lineNo] = ClassInfo(name: enclosingType.name, methods: [], properties: []) } - if !classesByLine[lineNo]!.methods.contains(where: { $0.definitionLine == decl }) { + if !containsFunction( + in: classesByLine[lineNo]!.methods, + definitionLine: decl, + performanceCollector: activePerfStats + ) { classesByLine[lineNo]?.methods.append(fnInfo) } } else { // Fallback: treat as global if no enclosing type found - if !globalFunctions.contains(where: { $0.definitionLine == decl }) { + if !containsFunction( + in: globalFunctions, + definitionLine: decl, + performanceCollector: activePerfStats + ) { globalFunctions.append(fnInfo) } } @@ -412,7 +498,12 @@ enum SwiftCodeMapStrategy { // Protocol methods go to interfaces // Use Swift-specific signature extraction to avoid semicolon heuristic issues let signatureStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 - let signature = extractSwiftFunctionSignature(from: cap.range, nsContent: nsContent, boundaries: boundaries) + let signature = extractSwiftFunctionSignature( + from: cap.range, + nsContent: nsContent, + boundaries: boundaries, + performanceCollector: activePerfStats + ) if perfEnabled { record(.functionSignature, duration: CFAbsoluteTimeGetCurrent() - signatureStart, perfStats: activePerfStats) } @@ -433,7 +524,8 @@ enum SwiftCodeMapStrategy { context: context, index: index, nsContent: nsContent, - referencedTypes: &referencedTypes + referencedTypes: &referencedTypes, + performanceCollector: activePerfStats ) if perfEnabled { record(.parameterExtraction, duration: CFAbsoluteTimeGetCurrent() - parameterStart, perfStats: activePerfStats) @@ -448,7 +540,11 @@ enum SwiftCodeMapStrategy { } // Find enclosing protocol let enclosingTypeStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 - let resolvedEnclosingProto = enclosingType(for: cap.range, in: context.typeBoundaries) + let resolvedEnclosingProto = enclosingType( + for: cap.range, + in: context.typeBoundaries, + performanceCollector: activePerfStats + ) if perfEnabled { record(.enclosingTypeLookup, duration: CFAbsoluteTimeGetCurrent() - enclosingTypeStart, perfStats: activePerfStats) } @@ -481,7 +577,12 @@ enum SwiftCodeMapStrategy { case "swift.property.toplevel": // Top-level Swift properties go to globalVariables let propertyDeclarationStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 - let fullDecl = extractSwiftPropertyDeclaration(from: cap.range, index: index, nsContent: nsContent, fallback: { + let fullDecl = extractSwiftPropertyDeclaration( + from: cap.range, + index: index, + nsContent: nsContent, + performanceCollector: activePerfStats, + fallback: { captureDeclaration(cap.range, "{") }) if perfEnabled { @@ -498,7 +599,11 @@ enum SwiftCodeMapStrategy { let modelInsertionStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 let varInfo = VariableInfo(name: fullDecl, typeName: propType, definitionLine: fullDecl) - if !globalVariables.contains(where: { $0.definitionLine == fullDecl }) { + if !containsVariable( + in: globalVariables, + definitionLine: fullDecl, + performanceCollector: activePerfStats + ) { globalVariables.append(varInfo) } if perfEnabled { @@ -512,7 +617,12 @@ enum SwiftCodeMapStrategy { case "swift.property.member": // Swift member properties - use range-based containment let propertyDeclarationStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 - let fullDecl = extractSwiftPropertyDeclaration(from: cap.range, index: index, nsContent: nsContent, fallback: { + let fullDecl = extractSwiftPropertyDeclaration( + from: cap.range, + index: index, + nsContent: nsContent, + performanceCollector: activePerfStats, + fallback: { captureDeclaration(cap.range, "{") }) if perfEnabled { @@ -528,7 +638,11 @@ enum SwiftCodeMapStrategy { } let enclosingTypeStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 - let resolvedEnclosingType = enclosingType(for: cap.range, in: context.typeBoundaries) + let resolvedEnclosingType = enclosingType( + for: cap.range, + in: context.typeBoundaries, + performanceCollector: activePerfStats + ) if perfEnabled { record(.enclosingTypeLookup, duration: CFAbsoluteTimeGetCurrent() - enclosingTypeStart, perfStats: activePerfStats) } @@ -539,13 +653,21 @@ enum SwiftCodeMapStrategy { classesByLine[lineNo] = ClassInfo(name: enclosingType.name, methods: [], properties: []) } let propInfo = PropertyInfo(name: fullDecl, typeName: propType) - if !classesByLine[lineNo]!.properties.contains(where: { $0.name == fullDecl }) { + if !containsProperty( + in: classesByLine[lineNo]!.properties, + name: fullDecl, + performanceCollector: activePerfStats + ) { classesByLine[lineNo]?.properties.append(propInfo) } } else { // Fallback: treat as global if no enclosing type found let varInfo = VariableInfo(name: fullDecl, typeName: propType, definitionLine: fullDecl) - if !globalVariables.contains(where: { $0.definitionLine == fullDecl }) { + if !containsVariable( + in: globalVariables, + definitionLine: fullDecl, + performanceCollector: activePerfStats + ) { globalVariables.append(varInfo) } } @@ -560,7 +682,12 @@ enum SwiftCodeMapStrategy { case "swift.protocol.property": // Protocol properties go to interfaces let propertyDeclarationStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 - let fullDecl = extractSwiftPropertyDeclaration(from: cap.range, index: index, nsContent: nsContent, fallback: { + let fullDecl = extractSwiftPropertyDeclaration( + from: cap.range, + index: index, + nsContent: nsContent, + performanceCollector: activePerfStats, + fallback: { captureDeclaration(cap.range, "{") }) if perfEnabled { @@ -576,7 +703,11 @@ enum SwiftCodeMapStrategy { } let enclosingTypeStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 - let resolvedEnclosingProto = enclosingType(for: cap.range, in: context.typeBoundaries) + let resolvedEnclosingProto = enclosingType( + for: cap.range, + in: context.typeBoundaries, + performanceCollector: activePerfStats + ) if perfEnabled { record(.enclosingTypeLookup, duration: CFAbsoluteTimeGetCurrent() - enclosingTypeStart, perfStats: activePerfStats) } @@ -615,6 +746,54 @@ enum SwiftCodeMapStrategy { // MARK: - Helpers + private static func containsFunction( + in functions: [FunctionInfo], + definitionLine: String, + performanceCollector: CodeMapPerformanceCollector? + ) -> Bool { + guard let performanceCollector else { + return functions.contains { $0.definitionLine == definitionLine } + } + performanceCollector.swiftFunctionDuplicateCheckCount += 1 + for function in functions { + performanceCollector.swiftFunctionDuplicateCandidateVisits += 1 + if function.definitionLine == definitionLine { return true } + } + return false + } + + private static func containsVariable( + in variables: [VariableInfo], + definitionLine: String, + performanceCollector: CodeMapPerformanceCollector? + ) -> Bool { + guard let performanceCollector else { + return variables.contains { $0.definitionLine == definitionLine } + } + performanceCollector.swiftPropertyDuplicateCheckCount += 1 + for variable in variables { + performanceCollector.swiftPropertyDuplicateCandidateVisits += 1 + if variable.definitionLine == definitionLine { return true } + } + return false + } + + private static func containsProperty( + in properties: [PropertyInfo], + name: String, + performanceCollector: CodeMapPerformanceCollector? + ) -> Bool { + guard let performanceCollector else { + return properties.contains { $0.name == name } + } + performanceCollector.swiftPropertyDuplicateCheckCount += 1 + for property in properties { + performanceCollector.swiftPropertyDuplicateCandidateVisits += 1 + if property.name == name { return true } + } + return false + } + /// Extracts Swift parameters from a function capture range private static func extractSwiftParameters( from functionRange: NSRange, @@ -622,7 +801,8 @@ enum SwiftCodeMapStrategy { context: Context, index: CodeMapCaptureIndex, nsContent: NSString, - referencedTypes: inout ReferencedTypesAccumulator + referencedTypes: inout ReferencedTypesAccumulator, + performanceCollector: CodeMapPerformanceCollector? ) -> [ParameterInfo] { var params: [ParameterInfo] = [] @@ -635,7 +815,11 @@ enum SwiftCodeMapStrategy { continue } // Exclude params from nested functions inside this function range - if let enclosingFn = smallestContainingRange(in: context.functionCaptures, for: paramNode.range), + if let enclosingFn = smallestContainingRange( + in: context.functionCaptures, + for: paramNode.range, + performanceCollector: performanceCollector + ), !NSEqualRanges(enclosingFn.range, functionRange) { continue @@ -670,14 +854,22 @@ enum SwiftCodeMapStrategy { return params } - private static func signatureEndLocation(forFunctionRange functionRange: NSRange, nsContent: NSString) -> Int { + private static func signatureEndLocation( + forFunctionRange functionRange: NSRange, + nsContent: NSString, + performanceCollector: CodeMapPerformanceCollector? + ) -> Int { let end = NSMaxRange(functionRange) + var i = functionRange.location + defer { + let visited = max(0, min(i, end) - functionRange.location + (i < end ? 1 : 0)) + performanceCollector?.swiftSignatureCodeUnitVisits += visited + } var parenDepth = 0 var inString = false var escapeNext = false var inLineComment = false var inBlockComment = false - var i = functionRange.location while i < end { let ch = nsContent.character(at: i) @@ -878,8 +1070,10 @@ enum SwiftCodeMapStrategy { from identifierRange: NSRange, index: CodeMapCaptureIndex, nsContent: NSString, + performanceCollector: CodeMapPerformanceCollector?, fallback: () -> String ) -> String { + let lookupStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } let declCap = index.smallestCapture( named: "swift.property.decl", containing: identifierRange @@ -887,13 +1081,27 @@ enum SwiftCodeMapStrategy { named: "swift.protocol.property.decl", containing: identifierRange ) + if let lookupStart { + performanceCollector?.swiftPropertyDeclarationLookupDuration += + CFAbsoluteTimeGetCurrent() - lookupStart + } if let cap = declCap { + let substringStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } var decl = nsContent.substring(with: cap.range) .trimmingCharacters(in: .whitespacesAndNewlines) if let braceIndex = decl.firstIndex(of: "{") { decl = String(decl[.. CodeMapIndexedCapture? { + performanceCollector?.swiftNestedFunctionContainmentLookupCount += 1 let endIdx = ranges.binarySearch { $0.range.location <= target.location } + performanceCollector?.swiftNestedFunctionContainmentCandidateVisits += endIdx guard endIdx > 0 else { return nil } var best: CodeMapIndexedCapture? = nil @@ -927,8 +1138,13 @@ enum SwiftCodeMapStrategy { } /// Finds the smallest enclosing type boundary for a given range - private static func enclosingType(for range: NSRange, in typeBoundaries: [TypeBoundary]) -> TypeBoundary? { + private static func enclosingType( + for range: NSRange, + in typeBoundaries: [TypeBoundary], + performanceCollector: CodeMapPerformanceCollector? + ) -> TypeBoundary? { let endIdx = typeBoundaries.binarySearch { $0.range.location <= range.location } + performanceCollector?.swiftEnclosingTypeCandidateVisits += endIdx guard endIdx > 0 else { return nil } var smallestContaining: TypeBoundary? = nil diff --git a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift index 3b87a0821..b3544e71b 100644 --- a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift +++ b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift @@ -17,8 +17,13 @@ package struct CodeMapPerfOptions: Sendable { } package final class CodeMapPerformanceCollector { + // Builder boundary. Populated only when an invocation-local collector is supplied. + package var builderTotalDuration: TimeInterval = 0 + package var builderGeneratorDuration: TimeInterval = 0 + // Syntax parse/query stages. These values are populated only when the app // supplies this invocation-local collector. + package var syntaxTotalDuration: TimeInterval = 0 package var syntaxLanguageLookupDuration: TimeInterval = 0 package var syntaxOversizeGuardDuration: TimeInterval = 0 package var syntaxParserCreateDuration: TimeInterval = 0 @@ -38,8 +43,34 @@ package final class CodeMapPerformanceCollector { package var syntaxCaptures = 0 package var syntaxCaptureCountsByName: [String: Int] = [:] package let collectsCaptureNames: Bool - package var syntaxCodeMapQueryCacheHits = 0 - package var syntaxCodeMapQueryCacheMisses = 0 + package var syntaxCodeMapQuerySuccessfulLookups = 0 + + // Capture index construction and lookup complexity. + package var captureIndexInputCaptureCount = 0 + package var captureIndexBucketCount = 0 + package var captureIndexFirstContainedLookupCount = 0 + package var captureIndexFirstContainedCandidateVisits = 0 + package var captureIndexAllContainedLookupCount = 0 + package var captureIndexAllContainedCandidateVisits = 0 + package var captureIndexSmallestContainingLookupCount = 0 + package var captureIndexSmallestContainingCandidateVisits = 0 + package var captureIndexMaximumCandidateVisits = 0 + + // Swift context construction and declaration volume. + package var swiftTypeNameMappingDuration: TimeInterval = 0 + package var swiftProtocolNameMappingDuration: TimeInterval = 0 + package var swiftBoundaryConstructionDuration: TimeInterval = 0 + package var swiftFunctionCaptureAssemblyDuration: TimeInterval = 0 + package var swiftTypeDeclarationCount = 0 + package var swiftProtocolDeclarationCount = 0 + package var swiftTopLevelFunctionCount = 0 + package var swiftMethodFunctionCount = 0 + package var swiftProtocolMethodCount = 0 + package var swiftParameterNodeCount = 0 + package var swiftPropertyDeclarationCount = 0 + package var swiftProtocolPropertyDeclarationCount = 0 + package var swiftPropertyIdentifierCount = 0 + package var swiftTypeBoundaryCount = 0 // Capture loop package var capturesProcessed = 0 @@ -68,6 +99,14 @@ package final class CodeMapPerformanceCollector { package var swiftStrategyContextOnlyCount = 0 package var swiftStrategyHandledFunctionCount = 0 package var swiftStrategyHandledPropertyCount = 0 + package var swiftSignatureCodeUnitVisits = 0 + package var swiftNestedFunctionContainmentLookupCount = 0 + package var swiftNestedFunctionContainmentCandidateVisits = 0 + package var swiftEnclosingTypeCandidateVisits = 0 + package var swiftFunctionDuplicateCheckCount = 0 + package var swiftFunctionDuplicateCandidateVisits = 0 + package var swiftPropertyDuplicateCheckCount = 0 + package var swiftPropertyDuplicateCandidateVisits = 0 package var fallbackFunctionDeclarationCount = 0 package var fallbackFunctionJSTSSignatureCount = 0 package var fallbackFunctionNameExtractionCount = 0 @@ -130,6 +169,7 @@ package final class CodeMapPerformanceCollector { package var referencedTypesPrefilterSkips = 0 package var referencedTypesEmptyResults = 0 package var referencedTypesOutputTypeCount = 0 + package var referencedTypesUniqueCount = 0 // Extraction memo package var extractionMemoJSTSHits = 0 @@ -160,10 +200,15 @@ package final class CodeMapPerformanceCollector { package var captureLoopSkippedDuration: TimeInterval = 0 package var captureLoopUnclassifiedDuration: TimeInterval = 0 package var swiftStrategyFunctionSignatureDuration: TimeInterval = 0 + package var swiftSignatureEndScanDuration: TimeInterval = 0 + package var swiftSignatureNormalizationDuration: TimeInterval = 0 package var swiftStrategyFunctionNameLookupDuration: TimeInterval = 0 package var swiftStrategyParameterExtractionDuration: TimeInterval = 0 package var swiftStrategyReturnTypeExtractionDuration: TimeInterval = 0 package var swiftStrategyPropertyDeclarationDuration: TimeInterval = 0 + package var swiftPropertyDeclarationLookupDuration: TimeInterval = 0 + package var swiftPropertyDeclarationSubstringDuration: TimeInterval = 0 + package var swiftPropertyInitializerStripDuration: TimeInterval = 0 package var swiftStrategyPropertyTypeExtractionDuration: TimeInterval = 0 package var swiftStrategyEnclosingTypeLookupDuration: TimeInterval = 0 package var swiftStrategyModelInsertionDuration: TimeInterval = 0 @@ -194,7 +239,13 @@ package final class CodeMapPerformanceCollector { package var typeCleanerFilterDuration: TimeInterval = 0 package var typeCleanerDedupDuration: TimeInterval = 0 package var referencedTypesFinalizeDuration: TimeInterval = 0 + package var artifactFinalizationDuration: TimeInterval = 0 + package var artifactMeaningfulContentCheckDuration: TimeInterval = 0 package var fileAPIInitDuration: TimeInterval = 0 + package var artifactFinalClassCount = 0 + package var artifactFinalInterfaceCount = 0 + package var artifactFinalFunctionCount = 0 + package var artifactFinalGlobalVariableCount = 0 package init(collectsCaptureNames: Bool = false) { self.collectsCaptureNames = collectsCaptureNames diff --git a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift index 47a493558..71829fed3 100644 --- a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift +++ b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift @@ -3,6 +3,259 @@ import XCTest @testable import RepoPromptCodeMapCore final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { + #if RPCE_BENCHMARK_TESTS + func testSwiftCorpusCorrectnessReference() throws { + guard SwiftCodeMapPipelineBenchmarkSupport.isRuntimeEnabled else { + throw XCTSkip("Set RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK=1 to run the Swift corpus reference test") + } + + let files = SwiftCodeMapPipelineBenchmarkSupport.makeCorpus() + let artifacts = try SwiftCodeMapPipelineBenchmarkSupport.buildArtifacts(files: files) + let repeated = try SwiftCodeMapPipelineBenchmarkSupport.buildArtifacts(files: files) + XCTAssertEqual(artifacts, repeated) + let evidence = try SwiftCodeMapPipelineBenchmarkSupport.makeEvidence( + files: files, + artifacts: artifacts + ) + SwiftCodeMapPipelineBenchmarkSupport.printDigestRecord(evidence.reference) + let comparisonMode = try SwiftCodeMapPipelineBenchmarkSupport.configuredReferenceComparisonMode() + try SwiftCodeMapPipelineBenchmarkSupport.validateFixedDigests( + evidence.reference, + comparisonMode: comparisonMode + ) + try SwiftCodeMapPipelineBenchmarkSupport.applyReferenceMode(to: evidence.reference) + } + + func testSwiftFileToCodeMapPipelineBenchmark() throws { + guard SwiftCodeMapPipelineBenchmarkSupport.isRuntimeEnabled else { + throw XCTSkip("Set RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK=1 to run the Swift pipeline benchmark") + } + + let files = SwiftCodeMapPipelineBenchmarkSupport.makeCorpus() + let expected = try SwiftCodeMapPipelineBenchmarkSupport.buildArtifacts(files: files) + for _ in 0 ..< 2 { + XCTAssertEqual( + try SwiftCodeMapPipelineBenchmarkSupport.buildArtifacts(files: files), + expected + ) + } + + var samplesMS: [Double] = [] + samplesMS.reserveCapacity(5) + for _ in 0 ..< 5 { + let start = ProcessInfo.processInfo.systemUptime + let artifacts = try SwiftCodeMapPipelineBenchmarkSupport.buildArtifacts(files: files) + samplesMS.append((ProcessInfo.processInfo.systemUptime - start) * 1_000) + XCTAssertEqual(artifacts, expected) + } + + let collector = CodeMapPerformanceCollector(collectsCaptureNames: true) + let attributed = try SwiftCodeMapPipelineBenchmarkSupport.buildArtifacts( + files: files, + performanceCollector: collector + ) + XCTAssertEqual(attributed, expected) + XCTAssertEqual(collector.syntaxCalls, files.count) + XCTAssertEqual(collector.syntaxQueryExecutes, files.count) + XCTAssertGreaterThan(collector.syntaxCaptures, 0) + + let evidence = try SwiftCodeMapPipelineBenchmarkSupport.makeEvidence( + files: files, + artifacts: attributed + ) + SwiftCodeMapPipelineBenchmarkSupport.printDigestRecord(evidence.reference) + let comparisonMode = try SwiftCodeMapPipelineBenchmarkSupport.configuredReferenceComparisonMode() + try SwiftCodeMapPipelineBenchmarkSupport.validateFixedDigests( + evidence.reference, + comparisonMode: comparisonMode + ) + try SwiftCodeMapPipelineBenchmarkSupport.applyReferenceMode(to: evidence.reference) + + print([ + "SWIFT_CODEMAP_PIPELINE_PRIMARY", + "files=\(files.count)", + "raw_samples_ms=\(SwiftCodeMapPipelineBenchmarkSupport.formattedSamples(samplesMS))", + "invocation_median_ms=\(String(format: "%.3f", SwiftCodeMapPipelineBenchmarkSupport.median(samplesMS)))", + "query_sha256=\(evidence.reference.querySHA256)", + "content_sha256=\(evidence.reference.contentDigest)", + "capture_sha256=\(evidence.reference.captureDigest)", + "artifact_sha256=\(evidence.reference.artifactDigest)", + "reference_mode=\(SwiftCodeMapPipelineBenchmarkSupport.referenceMode)", + "artifact_parity=true", + ].joined(separator: " ")) + print(SwiftCodeMapPipelineBenchmarkSupport.attributionRecord( + collector: collector, + reference: evidence.reference + )) + } + + func testSwiftPipelineReferenceExactComparisonAcceptsOnlyIdenticalRecord() throws { + let reference = Self.makeSwiftPipelineReference() + XCTAssertNoThrow(try SwiftCodeMapPipelineBenchmarkSupport.compareReference( + expected: reference, + actual: reference + )) + + let changedQuery = Self.makeSwiftPipelineReference( + querySHA256: "candidate-query", + captureDigest: "candidate-captures" + ) + XCTAssertThrowsError(try SwiftCodeMapPipelineBenchmarkSupport.compareReference( + expected: reference, + actual: changedQuery + )) + } + + func testSwiftPipelineReferenceAllowsOnlyAllowlistedCaptureRemovals() throws { + let reference = Self.makeSwiftPipelineReference() + let candidate = Self.makeSwiftPipelineReference( + querySHA256: "candidate-query", + captureDigest: "candidate-captures", + captures: [reference.captures[0]] + ) + + XCTAssertNoThrow(try SwiftCodeMapPipelineBenchmarkSupport.compareReference( + expected: reference, + actual: candidate, + comparisonMode: .allowingCaptureRemovals(named: ["type.class"]) + )) + } + + func testSwiftPipelineReferenceRejectsUnsafeCaptureDeltas() throws { + let reference = Self.makeSwiftPipelineReference() + let allowedMode = SwiftCodeMapPipelineBenchmarkSupport.ReferenceComparisonMode + .allowingCaptureRemovals(named: ["type.class"]) + + let added = Self.makeSwiftPipelineReference( + querySHA256: "candidate-query", + captureDigest: "candidate-captures", + captures: [ + reference.captures[0], + .init(logicalPath: "Sources/Test.swift", name: "zzz.added", location: 40, length: 2) + ] + ) + XCTAssertThrowsError(try SwiftCodeMapPipelineBenchmarkSupport.compareReference( + expected: reference, + actual: added, + comparisonMode: allowedMode + )) + + let modified = Self.makeSwiftPipelineReference( + querySHA256: "candidate-query", + captureDigest: "candidate-captures", + captures: [ + .init(logicalPath: "Sources/Test.swift", name: "function.method", location: 10, length: 99) + ] + ) + XCTAssertThrowsError(try SwiftCodeMapPipelineBenchmarkSupport.compareReference( + expected: reference, + actual: modified, + comparisonMode: allowedMode + )) + + let modifiedAllowlistedCapture = Self.makeSwiftPipelineReference( + querySHA256: "candidate-query", + captureDigest: "candidate-captures", + captures: [ + reference.captures[0], + .init(logicalPath: "Sources/Test.swift", name: "type.class", location: 20, length: 99) + ] + ) + XCTAssertThrowsError(try SwiftCodeMapPipelineBenchmarkSupport.compareReference( + expected: reference, + actual: modifiedAllowlistedCapture, + comparisonMode: allowedMode + )) + + let unchangedCaptures = Self.makeSwiftPipelineReference( + querySHA256: "candidate-query", + captureDigest: "candidate-captures" + ) + XCTAssertThrowsError(try SwiftCodeMapPipelineBenchmarkSupport.compareReference( + expected: reference, + actual: unchangedCaptures, + comparisonMode: allowedMode + )) + + let disallowedRemoval = Self.makeSwiftPipelineReference( + querySHA256: "candidate-query", + captureDigest: "candidate-captures", + captures: [reference.captures[1]] + ) + XCTAssertThrowsError(try SwiftCodeMapPipelineBenchmarkSupport.compareReference( + expected: reference, + actual: disallowedRemoval, + comparisonMode: allowedMode + )) + + let changedArtifact = Self.makeSwiftPipelineReference( + querySHA256: "candidate-query", + captureDigest: "candidate-captures", + artifactDigest: "changed-artifact", + captures: [reference.captures[0]] + ) + XCTAssertThrowsError(try SwiftCodeMapPipelineBenchmarkSupport.compareReference( + expected: reference, + actual: changedArtifact, + comparisonMode: allowedMode + )) + + let changedContent = Self.makeSwiftPipelineReference( + querySHA256: "candidate-query", + contentDigest: "changed-content", + captureDigest: "candidate-captures", + captures: [reference.captures[0]] + ) + XCTAssertThrowsError(try SwiftCodeMapPipelineBenchmarkSupport.compareReference( + expected: reference, + actual: changedContent, + comparisonMode: allowedMode + )) + } + + private static func makeSwiftPipelineReference( + querySHA256: String = "base-query", + contentDigest: String = "content", + captureDigest: String = "base-captures", + artifactDigest: String = "base-artifact", + captures: [SwiftCodeMapPipelineBenchmarkSupport.CaptureRecord] = [ + .init(logicalPath: "Sources/Test.swift", name: "function.method", location: 10, length: 4), + .init(logicalPath: "Sources/Test.swift", name: "type.class", location: 20, length: 8) + ] + ) -> SwiftCodeMapPipelineBenchmarkSupport.ReferenceRecord { + SwiftCodeMapPipelineBenchmarkSupport.ReferenceRecord( + schemaVersion: 1, + semanticBase: "base", + fileCount: 1, + querySHA256: querySHA256, + contentDigest: contentDigest, + captureDigest: captureDigest, + artifactDigest: artifactDigest, + artifacts: [ + .init(logicalPath: "Sources/Test.swift", canonicalJSON: Data("artifact".utf8)) + ], + captures: captures + ) + } + #endif + + func testCaptureIndexCountsMissingNamedBuckets() { + let collector = CodeMapPerformanceCollector() + let index = CodeMapCaptureIndex([], performanceCollector: collector) + let parent = NSRange(location: 0, length: 1) + + XCTAssertNil(index.firstCapture(named: "missing", containedIn: parent)) + XCTAssertTrue(index.captures(named: "missing", containedIn: parent).isEmpty) + XCTAssertNil(index.smallestCapture(named: "missing", containing: parent)) + XCTAssertEqual(collector.captureIndexFirstContainedLookupCount, 1) + XCTAssertEqual(collector.captureIndexAllContainedLookupCount, 1) + XCTAssertEqual(collector.captureIndexSmallestContainingLookupCount, 1) + XCTAssertEqual(collector.captureIndexFirstContainedCandidateVisits, 0) + XCTAssertEqual(collector.captureIndexAllContainedCandidateVisits, 0) + XCTAssertEqual(collector.captureIndexSmallestContainingCandidateVisits, 0) + XCTAssertEqual(collector.captureIndexMaximumCandidateVisits, 0) + } + func testPreMaterializedSwiftAndTypeScriptGeneratorAttribution() throws { let cases: [(name: String, language: LanguageType, source: String)] = [ ("swift", .swift, Self.swiftSource(declarationCount: 200)), diff --git a/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift new file mode 100644 index 000000000..75302f695 --- /dev/null +++ b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift @@ -0,0 +1,934 @@ +#if RPCE_BENCHMARK_TESTS + import CryptoKit + import Darwin + import Foundation + @testable import RepoPromptCodeMapCore + + /// Opt-in, serial source-to-CodeMap benchmark support. This file is excluded + /// unless the package is built with RPCE_ENABLE_BENCHMARK_TESTS=1. + enum SwiftCodeMapPipelineBenchmarkSupport { + static let runtimeGateKey = "RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK" + static let referenceModeKey = "RP_SWIFT_CODEMAP_REFERENCE_MODE" + static let referencePathKey = "RP_SWIFT_CODEMAP_REFERENCE_PATH" + static let allowedRemovedCaptureNamesKey = "RP_SWIFT_CODEMAP_ALLOWED_REMOVED_CAPTURES" + static let defaultReferencePath = "/tmp/rpce-swift-codemap-bdc8ba10c.json" + static let referenceSemanticBase = "bdc8ba10c" + + // Filled from the deterministic corpus and intentionally fixed after setup. + static let expectedContentDigest = "972bc2e36fd6e177096800089c782576972d56675b10f6c934407face1f2af7e" + static let expectedArtifactDigest = "5befe73ca42db203c0825b133a4eeeed018b5cd65f41597644b93f1b35858664" + static let expectedCaptureDigestsByQuerySHA = [ + "c99914bc4c89f0c588a0717dfca902b2f25be24e3cca8720d0b026c250791448": + "9e82ba5a2ae5fec1b494f424a2fdcf24bfe5061167b93c5fc3f7b0dc8bf7254c" + ] + + struct CorpusFile { + let logicalPath: String + let source: String + let snapshot: CodeMapCoreSourceSnapshot + } + + struct ArtifactRecord: Codable, Equatable { + let logicalPath: String + let canonicalJSON: Data + } + + struct CaptureRecord: Codable, Equatable { + let logicalPath: String + let name: String + let location: Int + let length: Int + } + + struct ReferenceRecord: Codable, Equatable { + let schemaVersion: Int + let semanticBase: String + let fileCount: Int + let querySHA256: String + let contentDigest: String + let captureDigest: String + let artifactDigest: String + let artifacts: [ArtifactRecord] + let captures: [CaptureRecord] + } + + struct Evidence { + let reference: ReferenceRecord + let artifacts: [CodeMapSyntaxArtifact] + } + + enum ReferenceComparisonMode: Equatable { + case exact + case allowingCaptureRemovals(named: Set) + } + + enum SupportError: Error, CustomStringConvertible { + case artifactNotReady(String, CodeMapSyntaxArtifactOutcome) + case queryNotReady(String, CodeMapSyntaxQueryOutcome) + case referenceAlreadyExists(String) + case referenceMismatch(String) + case invalidCaptureRemovalAllowlist(String) + case shortWrite(String) + case unexpectedDigest(kind: String, expected: String, actual: String) + case unsupportedReferenceMode(String) + + var description: String { + switch self { + case let .artifactNotReady(path, outcome): + "Artifact was not ready for \(path): \(outcome)" + case let .queryNotReady(path, outcome): + "Query was not ready for \(path): \(outcome)" + case let .referenceAlreadyExists(path): + "Reference already exists and was not overwritten: \(path)" + case let .referenceMismatch(detail): + "Reference mismatch: \(detail)" + case let .invalidCaptureRemovalAllowlist(key): + "Candidate capture-removal comparison requires a nonempty comma-separated \(key)" + case let .shortWrite(path): + "Could not write the complete reference: \(path)" + case let .unexpectedDigest(kind, expected, actual): + "Unexpected \(kind) digest; expected \(expected), got \(actual)" + case let .unsupportedReferenceMode(mode): + "Unsupported reference mode: \(mode)" + } + } + } + + static var isRuntimeEnabled: Bool { + ProcessInfo.processInfo.environment[runtimeGateKey] == "1" + } + + static var referencePath: String { + ProcessInfo.processInfo.environment[referencePathKey] ?? defaultReferencePath + } + + static var referenceMode: String { + ProcessInfo.processInfo.environment[referenceModeKey] ?? "compare" + } + + static func configuredReferenceComparisonMode() throws -> ReferenceComparisonMode { + switch referenceMode { + case "write", "compare": + return .exact + case "compare-capture-removals": + let names = Set( + (ProcessInfo.processInfo.environment[allowedRemovedCaptureNamesKey] ?? "") + .split(separator: ",") + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty } + ) + guard !names.isEmpty else { + throw SupportError.invalidCaptureRemovalAllowlist(allowedRemovedCaptureNamesKey) + } + return .allowingCaptureRemovals(named: names) + default: + throw SupportError.unsupportedReferenceMode(referenceMode) + } + } + + static func makeCorpus() -> [CorpusFile] { + var entries: [(String, String)] = [] + entries.reserveCapacity(64) + + for index in 0 ..< 40 { + entries.append(( + String(format: "Sources/Small/Small%02d.swift", index), + smallSource(index: index) + )) + } + for index in 0 ..< 16 { + entries.append(( + String(format: "Sources/Medium/Medium%02d.swift", index), + mediumSource(index: index) + )) + } + for (index, source) in pathologicalSources().enumerated() { + entries.append(( + String(format: "Sources/Pathological/Pathological%02d.swift", index), + source + )) + } + + precondition(entries.count == 64) + return entries.sorted { $0.0 < $1.0 }.map { path, source in + CorpusFile( + logicalPath: path, + source: source, + snapshot: CodeMapFixtureRunner.makeSourceSnapshot(content: source) + ) + } + } + + static func buildArtifacts( + files: [CorpusFile], + performanceCollector: CodeMapPerformanceCollector? = nil + ) throws -> [CodeMapSyntaxArtifact] { + var artifacts: [CodeMapSyntaxArtifact] = [] + artifacts.reserveCapacity(files.count) + let options: CodeMapPerfOptions = performanceCollector == nil ? .disabled : .countersOnly + for file in files { + let outcome = try CodeMapSyntaxArtifactBuilder.build( + source: file.snapshot, + language: .swift, + performanceOptions: options, + performanceCollector: performanceCollector + ) + guard case let .ready(artifact) = outcome else { + throw SupportError.artifactNotReady(file.logicalPath, outcome) + } + artifacts.append(artifact) + } + return artifacts + } + + static func makeEvidence( + files: [CorpusFile], + artifacts: [CodeMapSyntaxArtifact] + ) throws -> Evidence { + precondition(files.count == artifacts.count) + let identity = try CodeMapSyntaxEngine.shared.pipelineIdentity( + for: .swift, + decoderPolicy: .workspaceAutomaticV1 + ) + let querySHA = identity.codeMapQuerySHA256.lowercaseHex + let encoder = JSONEncoder() + encoder.outputFormatting = [.sortedKeys] + + var artifactRecords: [ArtifactRecord] = [] + artifactRecords.reserveCapacity(files.count) + var captureRecords: [CaptureRecord] = [] + for (file, artifact) in zip(files, artifacts) { + try artifactRecords.append(ArtifactRecord( + logicalPath: file.logicalPath, + canonicalJSON: encoder.encode(artifact) + )) + let outcome = try CodeMapSyntaxEngine.shared.codeMap( + content: file.source, + language: .swift + ) + guard case let .captures(captures) = outcome else { + throw SupportError.queryNotReady(file.logicalPath, outcome) + } + captureRecords.append(contentsOf: captures.map { + CaptureRecord( + logicalPath: file.logicalPath, + name: $0.name, + location: $0.range.location, + length: $0.range.length + ) + }) + } + captureRecords.sort(by: captureLessThan) + + let contentDigest = contentDigest(files) + let captureDigest = captureDigest( + queryDigestBytes: identity.codeMapQuerySHA256.bytes, + captures: captureRecords + ) + let artifactDigest = artifactDigest(artifactRecords) + let reference = ReferenceRecord( + schemaVersion: 1, + semanticBase: referenceSemanticBase, + fileCount: files.count, + querySHA256: querySHA, + contentDigest: contentDigest, + captureDigest: captureDigest, + artifactDigest: artifactDigest, + artifacts: artifactRecords, + captures: captureRecords + ) + return Evidence(reference: reference, artifacts: artifacts) + } + + static func validateFixedDigests( + _ reference: ReferenceRecord, + comparisonMode: ReferenceComparisonMode = .exact + ) throws { + guard reference.contentDigest == expectedContentDigest else { + throw SupportError.unexpectedDigest( + kind: "content", + expected: expectedContentDigest, + actual: reference.contentDigest + ) + } + guard reference.artifactDigest == expectedArtifactDigest else { + throw SupportError.unexpectedDigest( + kind: "artifact", + expected: expectedArtifactDigest, + actual: reference.artifactDigest + ) + } + switch comparisonMode { + case .exact: + guard let expectedCapture = expectedCaptureDigestsByQuerySHA[reference.querySHA256] else { + throw SupportError.unexpectedDigest( + kind: "capture for query \(reference.querySHA256)", + expected: "registered query digest", + actual: reference.captureDigest + ) + } + guard reference.captureDigest == expectedCapture else { + throw SupportError.unexpectedDigest( + kind: "capture", + expected: expectedCapture, + actual: reference.captureDigest + ) + } + case let .allowingCaptureRemovals(names): + guard !names.isEmpty else { + throw SupportError.invalidCaptureRemovalAllowlist(allowedRemovedCaptureNamesKey) + } + } + } + + static func applyReferenceMode(to reference: ReferenceRecord) throws { + switch referenceMode { + case "write": + try writeReferenceExclusively(reference, path: referencePath) + case "compare", "compare-capture-removals": + let expected = try readReference(path: referencePath) + try compareReference( + expected: expected, + actual: reference, + comparisonMode: configuredReferenceComparisonMode() + ) + default: + throw SupportError.unsupportedReferenceMode(referenceMode) + } + } + + static func compareReference( + expected: ReferenceRecord, + actual: ReferenceRecord, + comparisonMode: ReferenceComparisonMode = .exact + ) throws { + switch comparisonMode { + case .exact: + guard expected == actual else { + throw SupportError.referenceMismatch(referenceDifference(expected: expected, actual: actual)) + } + case let .allowingCaptureRemovals(allowedNames): + guard !allowedNames.isEmpty else { + throw SupportError.invalidCaptureRemovalAllowlist(allowedRemovedCaptureNamesKey) + } + try compareReferenceAuthority(expected: expected, actual: actual) + guard expected.querySHA256 != actual.querySHA256 else { + throw SupportError.referenceMismatch("candidate query SHA did not change") + } + guard expected.captureDigest != actual.captureDigest else { + throw SupportError.referenceMismatch("candidate capture digest did not change") + } + + var actualIndex = 0 + var removedCount = 0 + for expectedCapture in expected.captures { + if actualIndex < actual.captures.count, + actual.captures[actualIndex] == expectedCapture + { + actualIndex += 1 + continue + } + guard allowedNames.contains(expectedCapture.name) else { + throw SupportError.referenceMismatch( + "removed or modified non-allowlisted capture tuple \(captureDescription(expectedCapture))" + ) + } + removedCount += 1 + } + guard actualIndex == actual.captures.count else { + throw SupportError.referenceMismatch( + "added or modified capture tuple \(captureDescription(actual.captures[actualIndex]))" + ) + } + guard removedCount > 0 else { + throw SupportError.referenceMismatch("candidate removed no allowlisted capture tuples") + } + } + } + + static func printDigestRecord(_ reference: ReferenceRecord) { + print([ + "SWIFT_CODEMAP_PIPELINE_DIGESTS", + "files=\(reference.fileCount)", + "semantic_base=\(reference.semanticBase)", + "query_sha256=\(reference.querySHA256)", + "content_sha256=\(reference.contentDigest)", + "capture_sha256=\(reference.captureDigest)", + "artifact_sha256=\(reference.artifactDigest)", + "captures=\(reference.captures.count)", + "reference_mode=\(referenceMode)", + "reference_path=\(referencePath)" + ].joined(separator: " ")) + } + + static func attributionRecord( + collector: CodeMapPerformanceCollector, + reference: ReferenceRecord + ) -> String { + let declarations = collector.swiftTypeDeclarationCount + + collector.swiftProtocolDeclarationCount + + collector.swiftTopLevelFunctionCount + + collector.swiftMethodFunctionCount + + collector.swiftProtocolMethodCount + + collector.swiftPropertyIdentifierCount + let indexVisits = collector.captureIndexFirstContainedCandidateVisits + + collector.captureIndexAllContainedCandidateVisits + + collector.captureIndexSmallestContainingCandidateVisits + let visitsPerCapture = ratio(indexVisits, max(1, collector.captureIndexInputCaptureCount)) + let visitsPerDeclaration = ratio(indexVisits, max(1, declarations)) + let histogram = collector.syntaxCaptureCountsByName.keys.sorted().map { + "\($0):\(collector.syntaxCaptureCountsByName[$0, default: 0])" + }.joined(separator: ",") + + return [ + "SWIFT_CODEMAP_PIPELINE_ATTRIBUTION", + "files=\(reference.fileCount)", + "query_sha256=\(reference.querySHA256)", + "content_sha256=\(reference.contentDigest)", + "capture_sha256=\(reference.captureDigest)", + "artifact_sha256=\(reference.artifactDigest)", + "builder_total_ms=\(milliseconds(collector.builderTotalDuration))", + "builder_generator_ms=\(milliseconds(collector.builderGeneratorDuration))", + "syntax_total_ms=\(milliseconds(collector.syntaxTotalDuration))", + "oversize_guard_ms=\(milliseconds(collector.syntaxOversizeGuardDuration))", + "language_lookup_ms=\(milliseconds(collector.syntaxLanguageLookupDuration))", + "parser_create_ms=\(milliseconds(collector.syntaxParserCreateDuration))", + "set_language_ms=\(milliseconds(collector.syntaxSetLanguageDuration))", + "parse_ms=\(milliseconds(collector.syntaxParseDuration))", + "query_lookup_ms=\(milliseconds(collector.syntaxCodeMapQueryLookupDuration))", + "query_execute_ms=\(milliseconds(collector.syntaxQueryExecuteDuration))", + "capture_materialize_ms=\(milliseconds(collector.syntaxCaptureMaterializationDuration))", + "capture_name_count_ms=\(milliseconds(collector.syntaxCaptureNameCountingDuration))", + "capture_index_ms=\(milliseconds(collector.captureIndexDuration))", + "swift_context_ms=\(milliseconds(collector.swiftContextDuration))", + "swift_type_name_map_ms=\(milliseconds(collector.swiftTypeNameMappingDuration))", + "swift_protocol_name_map_ms=\(milliseconds(collector.swiftProtocolNameMappingDuration))", + "swift_boundaries_ms=\(milliseconds(collector.swiftBoundaryConstructionDuration))", + "swift_function_assembly_ms=\(milliseconds(collector.swiftFunctionCaptureAssemblyDuration))", + "capture_loop_ms=\(milliseconds(collector.captureLoopDuration))", + "capture_loop_line_advance_ms=\(milliseconds(collector.captureLoopLineAdvanceDuration))", + "swift_loop_ms=\(milliseconds(collector.captureLoopSwiftStrategyDuration))", + "ts_loop_ms=\(milliseconds(collector.captureLoopTSStrategyDuration))", + "interface_heuristic_ms=\(milliseconds(collector.captureLoopInterfaceHeuristicDuration))", + "import_export_ms=\(milliseconds(collector.captureLoopImportExportDuration))", + "type_alias_ms=\(milliseconds(collector.captureLoopTypeAliasDuration))", + "enum_macro_ms=\(milliseconds(collector.captureLoopEnumMacroDuration))", + "fallback_function_ms=\(milliseconds(collector.captureLoopFunctionDuration))", + "fallback_variable_ms=\(milliseconds(collector.captureLoopVariableDuration))", + "skipped_ms=\(milliseconds(collector.captureLoopSkippedDuration))", + "unclassified_ms=\(milliseconds(collector.captureLoopUnclassifiedDuration))", + "swift_signature_ms=\(milliseconds(collector.swiftStrategyFunctionSignatureDuration))", + "swift_signature_scan_ms=\(milliseconds(collector.swiftSignatureEndScanDuration))", + "swift_signature_normalize_ms=\(milliseconds(collector.swiftSignatureNormalizationDuration))", + "swift_name_lookup_ms=\(milliseconds(collector.swiftStrategyFunctionNameLookupDuration))", + "swift_parameters_ms=\(milliseconds(collector.swiftStrategyParameterExtractionDuration))", + "swift_return_type_ms=\(milliseconds(collector.swiftStrategyReturnTypeExtractionDuration))", + "swift_property_ms=\(milliseconds(collector.swiftStrategyPropertyDeclarationDuration))", + "swift_property_lookup_ms=\(milliseconds(collector.swiftPropertyDeclarationLookupDuration))", + "swift_property_substring_ms=\(milliseconds(collector.swiftPropertyDeclarationSubstringDuration))", + "swift_property_initializer_ms=\(milliseconds(collector.swiftPropertyInitializerStripDuration))", + "swift_property_type_ms=\(milliseconds(collector.swiftStrategyPropertyTypeExtractionDuration))", + "swift_enclosing_type_ms=\(milliseconds(collector.swiftStrategyEnclosingTypeLookupDuration))", + "swift_model_insert_ms=\(milliseconds(collector.swiftStrategyModelInsertionDuration))", + "swift_context_only_ms=\(milliseconds(collector.swiftStrategyContextOnlyDuration))", + "referenced_finalize_ms=\(milliseconds(collector.referencedTypesFinalizeDuration))", + "type_cleaner_ms=\(milliseconds(collector.typeCleanerDuration))", + "type_cleaner_swift_ms=\(milliseconds(collector.typeCleanerSwiftDuration))", + "type_cleaner_preclean_ms=\(milliseconds(collector.typeCleanerPrecleanDuration))", + "type_cleaner_non_ts_ms=\(milliseconds(collector.typeCleanerNonTSLogicDuration))", + "type_cleaner_filter_ms=\(milliseconds(collector.typeCleanerFilterDuration))", + "type_cleaner_dedup_ms=\(milliseconds(collector.typeCleanerDedupDuration))", + "artifact_finalize_ms=\(milliseconds(collector.artifactFinalizationDuration))", + "artifact_meaningful_ms=\(milliseconds(collector.artifactMeaningfulContentCheckDuration))", + "artifact_init_ms=\(milliseconds(collector.fileAPIInitDuration))", + "syntax_calls=\(collector.syntaxCalls)", + "parser_creates=\(collector.syntaxParserCreates)", + "query_executes=\(collector.syntaxQueryExecutes)", + "query_successful_lookups=\(collector.syntaxCodeMapQuerySuccessfulLookups)", + "captures=\(collector.syntaxCaptures)", + "captures_processed=\(collector.capturesProcessed)", + "swift_strategy_handled=\(collector.swiftStrategyHandled)", + "ts_strategy_handled=\(collector.tsStrategyHandled)", + "fallback_handled=\(collector.fallbackHandled)", + "line_advance_count=\(collector.captureLoopLineAdvanceCount)", + "swift_loop_count=\(collector.captureLoopSwiftStrategyCount)", + "ts_loop_count=\(collector.captureLoopTSStrategyCount)", + "interface_heuristic_count=\(collector.captureLoopInterfaceHeuristicCount)", + "import_export_count=\(collector.captureLoopImportExportCount)", + "type_alias_count=\(collector.captureLoopTypeAliasCount)", + "enum_macro_count=\(collector.captureLoopEnumMacroCount)", + "fallback_function_count=\(collector.captureLoopFunctionCount)", + "fallback_variable_count=\(collector.captureLoopVariableCount)", + "skipped_count=\(collector.captureLoopSkippedCount)", + "unclassified_count=\(collector.captureLoopUnclassifiedCount)", + "capture_index_inputs=\(collector.captureIndexInputCaptureCount)", + "capture_index_buckets=\(collector.captureIndexBucketCount)", + "capture_index_first_lookups=\(collector.captureIndexFirstContainedLookupCount)", + "capture_index_first_visits=\(collector.captureIndexFirstContainedCandidateVisits)", + "capture_index_all_lookups=\(collector.captureIndexAllContainedLookupCount)", + "capture_index_all_visits=\(collector.captureIndexAllContainedCandidateVisits)", + "capture_index_smallest_lookups=\(collector.captureIndexSmallestContainingLookupCount)", + "capture_index_smallest_visits=\(collector.captureIndexSmallestContainingCandidateVisits)", + "capture_index_visits=\(indexVisits)", + "capture_index_max_visits=\(collector.captureIndexMaximumCandidateVisits)", + "capture_index_visits_per_capture=\(visitsPerCapture)", + "capture_index_visits_per_declaration=\(visitsPerDeclaration)", + "swift_declarations=\(declarations)", + "swift_type_declarations=\(collector.swiftTypeDeclarationCount)", + "swift_protocol_declarations=\(collector.swiftProtocolDeclarationCount)", + "swift_top_level_functions=\(collector.swiftTopLevelFunctionCount)", + "swift_methods=\(collector.swiftMethodFunctionCount)", + "swift_protocol_methods=\(collector.swiftProtocolMethodCount)", + "swift_parameter_nodes=\(collector.swiftParameterNodeCount)", + "swift_property_declarations=\(collector.swiftPropertyDeclarationCount)", + "swift_protocol_property_declarations=\(collector.swiftProtocolPropertyDeclarationCount)", + "swift_property_identifiers=\(collector.swiftPropertyIdentifierCount)", + "swift_type_boundaries=\(collector.swiftTypeBoundaryCount)", + "swift_signature_code_units=\(collector.swiftSignatureCodeUnitVisits)", + "swift_nested_containment_lookups=\(collector.swiftNestedFunctionContainmentLookupCount)", + "swift_nested_containment_visits=\(collector.swiftNestedFunctionContainmentCandidateVisits)", + "swift_enclosing_type_visits=\(collector.swiftEnclosingTypeCandidateVisits)", + "swift_function_duplicate_checks=\(collector.swiftFunctionDuplicateCheckCount)", + "swift_function_duplicate_visits=\(collector.swiftFunctionDuplicateCandidateVisits)", + "swift_property_duplicate_checks=\(collector.swiftPropertyDuplicateCheckCount)", + "swift_property_duplicate_visits=\(collector.swiftPropertyDuplicateCandidateVisits)", + "swift_signature_count=\(collector.swiftStrategyFunctionSignatureCount)", + "swift_name_lookup_count=\(collector.swiftStrategyFunctionNameLookupCount)", + "swift_parameter_extraction_count=\(collector.swiftStrategyParameterExtractionCount)", + "swift_return_type_count=\(collector.swiftStrategyReturnTypeExtractionCount)", + "swift_property_declaration_count=\(collector.swiftStrategyPropertyDeclarationCount)", + "swift_property_type_count=\(collector.swiftStrategyPropertyTypeExtractionCount)", + "swift_enclosing_type_count=\(collector.swiftStrategyEnclosingTypeLookupCount)", + "swift_model_insertion_count=\(collector.swiftStrategyModelInsertionCount)", + "swift_context_only_count=\(collector.swiftStrategyContextOnlyCount)", + "swift_functions_handled=\(collector.swiftStrategyHandledFunctionCount)", + "swift_properties_handled=\(collector.swiftStrategyHandledPropertyCount)", + "referenced_raw_insertions=\(collector.referencedTypesRawInsertions)", + "referenced_prefilter_skips=\(collector.referencedTypesPrefilterSkips)", + "referenced_empty_results=\(collector.referencedTypesEmptyResults)", + "referenced_output_types=\(collector.referencedTypesOutputTypeCount)", + "referenced_unique_types=\(collector.referencedTypesUniqueCount)", + "type_cleaner_calls=\(collector.typeCleanerExtractCalls)", + "type_cleaner_cache_hits=\(collector.typeCleanerCacheHits)", + "type_cleaner_cache_misses=\(collector.typeCleanerCacheMisses)", + "type_cleaner_swift_calls=\(collector.typeCleanerSwiftCalls)", + "type_cleaner_ts_calls=\(collector.typeCleanerTSCalls)", + "type_cleaner_tsx_calls=\(collector.typeCleanerTSXCalls)", + "type_cleaner_js_calls=\(collector.typeCleanerJSCalls)", + "type_cleaner_other_language_calls=\(collector.typeCleanerOtherLanguageCalls)", + "type_cleaner_preclean_count=\(collector.typeCleanerPrecleanCount)", + "type_cleaner_ts_logic_count=\(collector.typeCleanerTSLogicCount)", + "type_cleaner_non_ts_logic_count=\(collector.typeCleanerNonTSLogicCount)", + "type_cleaner_ts_object_literal_count=\(collector.typeCleanerTSObjectLiteralCount)", + "type_cleaner_filter_count=\(collector.typeCleanerFilterCount)", + "type_cleaner_dedup_count=\(collector.typeCleanerDedupCount)", + "artifact_classes=\(collector.artifactFinalClassCount)", + "artifact_interfaces=\(collector.artifactFinalInterfaceCount)", + "artifact_functions=\(collector.artifactFinalFunctionCount)", + "artifact_globals=\(collector.artifactFinalGlobalVariableCount)", + "oversized=\(collector.syntaxOversized)", + "unsupported=\(collector.syntaxUnsupported)", + "nil_trees=\(collector.syntaxParseNilTree)", + "nil_roots=\(collector.syntaxParseNilRoot)", + "capture_histogram=\(histogram)" + ].joined(separator: " ") + } + + static func median(_ values: [Double]) -> Double { + let sorted = values.sorted() + guard !sorted.isEmpty else { return 0 } + let middle = sorted.count / 2 + if sorted.count.isMultiple(of: 2) { + return (sorted[middle - 1] + sorted[middle]) / 2 + } + return sorted[middle] + } + + static func formattedSamples(_ samples: [Double]) -> String { + "[\(samples.map { String(format: "%.3f", $0) }.joined(separator: ","))]" + } + + static func milliseconds(_ duration: TimeInterval) -> String { + String(format: "%.3f", duration * 1000) + } + + private static func ratio(_ numerator: Int, _ denominator: Int) -> String { + String(format: "%.3f", Double(numerator) / Double(denominator)) + } + + private static func contentDigest(_ files: [CorpusFile]) -> String { + var framed = Data() + for file in files { + appendFrame(Data(file.logicalPath.utf8), to: &framed) + appendFrame(Data(file.source.utf8), to: &framed) + } + return sha256Hex(framed) + } + + private static func captureDigest( + queryDigestBytes: Data, + captures: [CaptureRecord] + ) -> String { + var framed = Data(queryDigestBytes) + for capture in captures { + appendFrame(Data(capture.logicalPath.utf8), to: &framed) + appendFrame(Data(capture.name.utf8), to: &framed) + appendUInt64(UInt64(capture.location), to: &framed) + appendUInt64(UInt64(capture.length), to: &framed) + } + return sha256Hex(framed) + } + + private static func artifactDigest(_ records: [ArtifactRecord]) -> String { + var framed = Data() + for record in records { + appendFrame(Data(record.logicalPath.utf8), to: &framed) + appendFrame(record.canonicalJSON, to: &framed) + } + return sha256Hex(framed) + } + + private static func appendFrame(_ bytes: Data, to data: inout Data) { + appendUInt64(UInt64(bytes.count), to: &data) + data.append(bytes) + } + + private static func appendUInt64(_ value: UInt64, to data: inout Data) { + for shift in stride(from: 56, through: 0, by: -8) { + data.append(UInt8((value >> UInt64(shift)) & 0xFF)) + } + } + + private static func sha256Hex(_ data: Data) -> String { + Data(SHA256.hash(data: data)).map { String(format: "%02x", $0) }.joined() + } + + private static func captureLessThan(_ lhs: CaptureRecord, _ rhs: CaptureRecord) -> Bool { + if lhs.logicalPath != rhs.logicalPath { return lhs.logicalPath < rhs.logicalPath } + if lhs.name != rhs.name { return lhs.name < rhs.name } + if lhs.location != rhs.location { return lhs.location < rhs.location } + return lhs.length < rhs.length + } + + private static func writeReferenceExclusively( + _ reference: ReferenceRecord, + path: String + ) throws { + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let data = try encoder.encode(reference) + + let descriptor: Int32 + while true { + let result = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR) + if result >= 0 { + descriptor = result + break + } + if errno == EINTR { continue } + if errno == EEXIST { throw SupportError.referenceAlreadyExists(path) } + throw POSIXError(POSIXErrorCode(rawValue: errno) ?? .EIO) + } + + var completed = false + defer { + _ = close(descriptor) + if !completed { + removeIncompleteReference(path: path) + } + } + try data.withUnsafeBytes { buffer in + guard let base = buffer.baseAddress else { + throw SupportError.shortWrite(path) + } + var total = 0 + while total < buffer.count { + let result = Darwin.write(descriptor, base.advanced(by: total), buffer.count - total) + if result < 0 { + if errno == EINTR { continue } + throw POSIXError(POSIXErrorCode(rawValue: errno) ?? .EIO) + } + guard result > 0 else { throw SupportError.shortWrite(path) } + total += result + } + } + completed = true + } + + private static func removeIncompleteReference(path: String) { + while unlink(path) < 0, errno == EINTR {} + } + + private static func readReference(path: String) throws -> ReferenceRecord { + try JSONDecoder().decode(ReferenceRecord.self, from: Data(contentsOf: URL(fileURLWithPath: path))) + } + + private static func compareReferenceAuthority( + expected: ReferenceRecord, + actual: ReferenceRecord + ) throws { + if expected.schemaVersion != actual.schemaVersion { + throw SupportError.referenceMismatch("schema version") + } + if expected.semanticBase != actual.semanticBase { + throw SupportError.referenceMismatch("semantic base") + } + if expected.fileCount != actual.fileCount { + throw SupportError.referenceMismatch("file count") + } + if expected.contentDigest != actual.contentDigest { + throw SupportError.referenceMismatch("content digest") + } + if expected.artifactDigest != actual.artifactDigest { + throw SupportError.referenceMismatch("artifact digest") + } + if expected.artifacts != actual.artifacts { + throw SupportError.referenceMismatch("canonical artifact bytes") + } + } + + private static func captureDescription(_ capture: CaptureRecord) -> String { + "\(capture.logicalPath):\(capture.name)@\(capture.location)+\(capture.length)" + } + + private static func referenceDifference( + expected: ReferenceRecord, + actual: ReferenceRecord + ) -> String { + if expected.schemaVersion != actual.schemaVersion { return "schema version" } + if expected.semanticBase != actual.semanticBase { return "semantic base" } + if expected.fileCount != actual.fileCount { return "file count" } + if expected.querySHA256 != actual.querySHA256 { return "query SHA" } + if expected.contentDigest != actual.contentDigest { return "content digest" } + if expected.captureDigest != actual.captureDigest { return "capture digest" } + if expected.artifactDigest != actual.artifactDigest { return "artifact digest" } + if expected.artifacts != actual.artifacts { return "canonical artifact bytes" } + if expected.captures != actual.captures { return "canonical capture tuples" } + return "unknown" + } + + private static func smallSource(index: Int) -> String { + """ + import Foundation + + protocol SmallContract\(index) { + associatedtype Value\(index) + func load\(index)(_ key: String) async throws -> Value\(index) + } + + struct SmallType\(index): Sendable where Payload: Sendable { + let value\(index): Payload + var metadata\(index): [String: Int] = [:] + + func transform\(index)( + _ input: Payload, + using body: (Payload) throws -> Output + ) rethrows -> Output { + try body(input) + } + } + + extension SmallType\(index) { + func labeled\(index)(_ first: Int, first second: Int = 0) -> Int { + first + second + } + } + + func smallTopLevel\(index)(_ value: Int) -> Result { + .success(value) + } + """ + } + + private static func mediumSource(index: Int) -> String { + let records = (0 ..< 4).map { record in + """ + struct MediumRecord\(index)_\(record): Sendable where Element: Sendable { + let id: UUID + var elements: [Element] + var lookup: [String: Result] = [:] + + func map( + _ transform: @Sendable (Element) async throws -> Output + ) async rethrows -> [Output] { + var output: [Output] = [] + for element in elements { output.append(try await transform(element)) } + return output + } + + func resolve( + _ key: String, + default fallback: @autoclosure () -> Element + ) -> Element { + (try? lookup[key]?.get()) ?? fallback() + } + } + """ + }.joined(separator: "\n") + return """ + import Foundation + + enum MediumError\(index): Error { case missing(String), invalid(Int) } + + protocol MediumService\(index): Sendable { + associatedtype Item: Sendable + var identifier: String { get } + func fetch(_ id: UUID) async throws -> Item + func update(_ item: Item, for id: UUID) async throws + } + + \(records) + + actor MediumStore\(index) { + private var values: [UUID: Value] = [:] + func value(for id: UUID) -> Value? { values[id] } + func set(_ value: Value, for id: UUID) { values[id] = value } + } + + final class MediumCoordinator\(index) { + let service: Service + init(service: Service) { self.service = service } + func run(ids: [UUID]) async throws -> [Service.Item] { + try await ids.asyncMap { try await service.fetch($0) } + } + } + + extension Array { + func asyncMap(_ transform: (Element) async throws -> Output) async rethrows -> [Output] { + var output: [Output] = [] + for element in self { output.append(try await transform(element)) } + return output + } + } + + func mediumTopLevel\(index)(_ value: T) async -> T { value } + func mediumTopLevel\(index)(_ value: Int) -> String { String(value) } + func mediumFactory\(index)(_ body: () throws -> T) rethrows -> T { try body() } + """ + } + + private static func pathologicalSources() -> [String] { + [ + """ + import Foundation + + @available(macOS 14, *) + public final class AttributedBox: @unchecked Sendable where Value: Sendable { + @MainActor public private(set) var value: Value + public init( + value: Value + ) where Value: Codable { + self.value = value + } + } + """, + """ + protocol NestedProtocol { + associatedtype Element + func transform( + _ value: T, + using body: (T) throws -> U + ) rethrows -> U where T: Collection, T.Element == Element, U: Sendable + } + + struct Outer { + struct Inner { let value: U } + enum State { case idle, running(progress: Double) } + func nested(_ value: T) { + func local(_ input: T, completion: (T) -> Void = { _ in }) { completion(input) } + local(value) + } + } + + extension Outer where T: Sendable { + func send(_ value: T) async -> T { value } + } + """, + """ + struct Overloads { + func render(_ value: Int) -> String { String(value) } + func render(_ value: String) -> String { value } + func label(_ value: Int, value other: Int) -> Int { value + other } + func underscore(_ value: Int, _ second: Int = 1) -> Int { value + second } + } + + func globalOverload(_ value: Int) -> Int { value } + func globalOverload(_ value: String) -> String { value } + """, + """ + struct PackBox { + let values: (repeat each Element) + init(_ values: repeat each Element) { self.values = (repeat each values) } + func apply( + _ transforms: repeat (each Element) -> each Output + ) -> (repeat each Output) { + (repeat (each transforms)(each values)) + } + } + """, + [ + "struct LiteralCases {", + " let raw = #\"raw { : -> // not comment }\"#", + " let multiline = \"\"\"", + " text { with : arrows -> and // markers", + " \"\"\"", + " /* outer /* nested */ block */", + " func render(_ value: String = #\"default } : ->\"#) -> String {", + " // { ignored comment", + " value + raw + multiline", + " }", + "}" + ].joined(separator: "\n"), + """ + struct DefaultClosures { + var handler: (Result) -> Void = { result in + if case let .success(value) = result { print("value: \\(value) -> }") } + } + + func execute( + values: [Int] = [1, 2, 3], + transform: (Int) -> String = { value in "{\\(value): ->}" }, + completion: ([String]) -> Void = { _ in } + ) { + completion(values.map(transform)) + } + } + """, + """ + final class AccessorCases { + let stored: Int = 1 + var observed: Int = 0 { + willSet { print(newValue) } + didSet { print(oldValue) } + } + var computed: String { String(observed) } + var expanded: Int { + get { observed } + set { observed = newValue } + } + subscript(index: Int) -> Int { + get { observed + index } + set { observed = newValue - index } + } + } + """, + """ + public protocol Repository where Key: Hashable, Value: Sendable { + associatedtype Key + associatedtype Value + func value(for key: Key) async throws -> Value? + func set(_ value: Value?, for key: Key) async throws + } + + public actor AnyRepository: Repository { + public typealias Key = K + public typealias Value = V + private var storage: [K: V] = [:] + public func value(for key: K) async throws -> V? { storage[key] } + public func set(_ value: V?, for key: K) async throws { storage[key] = value } + } + """ + ] + } + } +#endif diff --git a/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceCodeMapPhase3BenchmarkSupport.swift b/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceCodeMapPhase3BenchmarkSupport.swift index a3e3d98a2..40a70fd7c 100644 --- a/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceCodeMapPhase3BenchmarkSupport.swift +++ b/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceCodeMapPhase3BenchmarkSupport.swift @@ -2,6 +2,7 @@ import Darwin import Foundation @testable import RepoPromptApp + @testable import RepoPromptCodeMapCore enum WorkspaceCodeMapPhase3BenchmarkMetric: String, CaseIterable { case canonicalKeyPipelineConstruction diff --git a/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceCodeMapPhase4BenchmarkSupport.swift b/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceCodeMapPhase4BenchmarkSupport.swift index d52e7f290..53ba41fd8 100644 --- a/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceCodeMapPhase4BenchmarkSupport.swift +++ b/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceCodeMapPhase4BenchmarkSupport.swift @@ -3,6 +3,7 @@ import Darwin import Foundation @testable import RepoPromptApp + @testable import RepoPromptCodeMapCore enum WorkspaceCodeMapPhase4BenchmarkMetric: String, CaseIterable { case canonicalBatchClassification diff --git a/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceFileSearchIndexTimeToReadyBenchmarkTests.swift b/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceFileSearchIndexTimeToReadyBenchmarkTests.swift index 5ee7ffce4..b15f02541 100644 --- a/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceFileSearchIndexTimeToReadyBenchmarkTests.swift +++ b/Tests/RepoPromptTests/WorkspaceContext/Search/WorkspaceFileSearchIndexTimeToReadyBenchmarkTests.swift @@ -1,6 +1,6 @@ import Foundation @testable import RepoPromptApp -import RepoPromptCodeMapCore +@testable import RepoPromptCodeMapCore import XCTest #if DEBUG @@ -770,7 +770,7 @@ import XCTest try measurePhase2Synchronous { let totalSource = CodeMapSourceSnapshot(validatedContent: validatedContent) let outcome = try CodeMapSyntaxArtifactBuilder.build( - source: totalSource, + source: totalSource.coreSnapshot, language: language ) return (totalSource, outcome) From af6d45ec320c9f7f4ac63e3595d8e66b8ee79413 Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Wed, 22 Jul 2026 13:53:41 -0400 Subject: [PATCH 02/11] Optimize Swift signature whitespace normalization --- .../SwiftCodeMapStrategy.swift | 72 ++++++++++++++- .../CodeMapPerformanceCollector.swift | 5 + ...deMapQueryOptimizationBenchmarkTests.swift | 91 +++++++++++++++++++ ...SwiftCodeMapPipelineBenchmarkSupport.swift | 5 + 4 files changed, 171 insertions(+), 2 deletions(-) diff --git a/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift b/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift index ca697dbec..e418fc4e4 100644 --- a/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift +++ b/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift @@ -264,6 +264,71 @@ enum SwiftCodeMapStrategy { let signatureEnd: Int } + static func normalizeSwiftSignatureWhitespace( + _ trimmed: String, + performanceCollector: CodeMapPerformanceCollector? + ) -> String { + var needsRewrite = false + var previousWasWhitespace = false + var asciiByteCount = 0 + + for byte in trimmed.utf8 { + if byte >= 0x80 { + let normalized = trimmed.replacing(#/\s+/#, with: " ") + if let performanceCollector { + performanceCollector.swiftSignatureNormalizationUnicodeFallbackCount += 1 + performanceCollector.swiftSignatureNormalizationInputUTF8ByteCount += trimmed.utf8.count + performanceCollector.swiftSignatureNormalizationOutputUTF8ByteCount += normalized.utf8.count + } + return normalized + } + + asciiByteCount += 1 + let isWhitespace = byte == 0x20 || (byte >= 0x09 && byte <= 0x0D) + if isWhitespace { + if byte != 0x20 || previousWasWhitespace { + needsRewrite = true + } + previousWasWhitespace = true + } else { + previousWasWhitespace = false + } + } + + guard needsRewrite else { + if let performanceCollector { + performanceCollector.swiftSignatureNormalizationASCIINoOpCount += 1 + performanceCollector.swiftSignatureNormalizationInputUTF8ByteCount += asciiByteCount + performanceCollector.swiftSignatureNormalizationOutputUTF8ByteCount += asciiByteCount + } + return trimmed + } + + var normalizedUTF8: [UInt8] = [] + normalizedUTF8.reserveCapacity(asciiByteCount) + var isCollapsingWhitespace = false + for byte in trimmed.utf8 { + let isWhitespace = byte == 0x20 || (byte >= 0x09 && byte <= 0x0D) + if isWhitespace { + if !isCollapsingWhitespace { + normalizedUTF8.append(0x20) + isCollapsingWhitespace = true + } + } else { + normalizedUTF8.append(byte) + isCollapsingWhitespace = false + } + } + + let normalized = String(decoding: normalizedUTF8, as: UTF8.self) + if let performanceCollector { + performanceCollector.swiftSignatureNormalizationASCIIRewriteCount += 1 + performanceCollector.swiftSignatureNormalizationInputUTF8ByteCount += asciiByteCount + performanceCollector.swiftSignatureNormalizationOutputUTF8ByteCount += normalizedUTF8.count + } + return normalized + } + /// Extracts only the function signature (up to but not including `{`) from a Swift function capture range. /// Uses `signatureEndLocation` to correctly handle strings, comments, and nesting. private static func extractSwiftFunctionSignature( @@ -287,11 +352,14 @@ enum SwiftCodeMapStrategy { // Get the signature text let normalizationStart = performanceCollector.map { _ in CFAbsoluteTimeGetCurrent() } - var signature = nsContent.substring(with: signatureRange) + let trimmedSignature = nsContent.substring(with: signatureRange) .trimmingCharacters(in: .whitespacesAndNewlines) // Normalize whitespace (collapse multiple whitespace to single space) - signature = signature.replacing(#/\s+/#, with: " ") + let signature = normalizeSwiftSignatureWhitespace( + trimmedSignature, + performanceCollector: performanceCollector + ) if let normalizationStart { performanceCollector?.swiftSignatureNormalizationDuration += CFAbsoluteTimeGetCurrent() - normalizationStart diff --git a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift index b3544e71b..97c4b3b5a 100644 --- a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift +++ b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift @@ -89,6 +89,11 @@ package final class CodeMapPerformanceCollector { package var captureLoopSkippedCount = 0 package var captureLoopUnclassifiedCount = 0 package var swiftStrategyFunctionSignatureCount = 0 + package var swiftSignatureNormalizationASCIINoOpCount = 0 + package var swiftSignatureNormalizationASCIIRewriteCount = 0 + package var swiftSignatureNormalizationUnicodeFallbackCount = 0 + package var swiftSignatureNormalizationInputUTF8ByteCount = 0 + package var swiftSignatureNormalizationOutputUTF8ByteCount = 0 package var swiftStrategyFunctionNameLookupCount = 0 package var swiftStrategyParameterExtractionCount = 0 package var swiftStrategyReturnTypeExtractionCount = 0 diff --git a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift index 71829fed3..8175ad012 100644 --- a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift +++ b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift @@ -58,6 +58,18 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { XCTAssertEqual(collector.syntaxCalls, files.count) XCTAssertEqual(collector.syntaxQueryExecutes, files.count) XCTAssertGreaterThan(collector.syntaxCaptures, 0) + XCTAssertEqual( + collector.swiftSignatureNormalizationASCIINoOpCount + + collector.swiftSignatureNormalizationASCIIRewriteCount + + collector.swiftSignatureNormalizationUnicodeFallbackCount, + collector.swiftStrategyFunctionSignatureCount + ) + XCTAssertGreaterThan(collector.swiftSignatureNormalizationInputUTF8ByteCount, 0) + XCTAssertGreaterThan(collector.swiftSignatureNormalizationOutputUTF8ByteCount, 0) + XCTAssertLessThanOrEqual( + collector.swiftSignatureNormalizationOutputUTF8ByteCount, + collector.swiftSignatureNormalizationInputUTF8ByteCount + ) let evidence = try SwiftCodeMapPipelineBenchmarkSupport.makeEvidence( files: files, @@ -239,6 +251,80 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { } #endif + func testSwiftSignatureWhitespaceNormalizerMatchesLegacyBehavior() { + let cases: [(name: String, input: String)] = [ + ("space run", "func value()"), + ("tab", "func\tvalue()"), + ("newline", "func\nvalue()"), + ("vertical tab", "func\u{000B}value()"), + ("form feed", "func\u{000C}value()"), + ("carriage return", "func\rvalue()"), + ("mixed whitespace run", "func \t\n\u{000B}\u{000C}\r value()"), + ("already normalized", "func value(_ input: Int) -> String"), + ("no whitespace", "funcValue()"), + ("empty", ""), + ("trimmed away", " \t\n\r "), + ("string literal", #"func value(_ text: String = "a b") -> String"#), + ("line comment", "func value() -> Int // comment"), + ("block comment", "func value(/* comment */ _ input: Int)"), + ("closure default", "func value(_ transform: (Int) -> Int = { value in value })"), + ("braces colons arrows", #"func value(_ text: String = "{ key: -> }")"#), + ("non-ASCII identifier", "func café(_ value: Int)"), + ("nonbreaking space", "func\u{00A0}value()"), + ("em space", "func\u{2003}value()"), + ] + + for testCase in cases { + let trimmed = testCase.input.trimmingCharacters(in: .whitespacesAndNewlines) + XCTAssertEqual( + SwiftCodeMapStrategy.normalizeSwiftSignatureWhitespace( + trimmed, + performanceCollector: nil + ), + Self.legacySwiftSignatureWhitespaceNormalization(testCase.input), + testCase.name + ) + } + } + + func testSwiftSignatureWhitespaceNormalizerCounters() { + let inputs = [ + "func value() -> Int", + "func\tvalue(\n_ item: Int)", + "func café(\u{2003}_ value: Int)", + ] + let collector = CodeMapPerformanceCollector() + let outputs = inputs.map { + SwiftCodeMapStrategy.normalizeSwiftSignatureWhitespace( + $0, + performanceCollector: collector + ) + } + + XCTAssertEqual(outputs, inputs.map(Self.legacySwiftSignatureWhitespaceNormalization)) + XCTAssertEqual(collector.swiftSignatureNormalizationASCIINoOpCount, 1) + XCTAssertEqual(collector.swiftSignatureNormalizationASCIIRewriteCount, 1) + XCTAssertEqual(collector.swiftSignatureNormalizationUnicodeFallbackCount, 1) + XCTAssertEqual( + collector.swiftSignatureNormalizationASCIINoOpCount + + collector.swiftSignatureNormalizationASCIIRewriteCount + + collector.swiftSignatureNormalizationUnicodeFallbackCount, + inputs.count + ) + XCTAssertEqual( + collector.swiftSignatureNormalizationInputUTF8ByteCount, + inputs.reduce(0) { $0 + $1.utf8.count } + ) + XCTAssertEqual( + collector.swiftSignatureNormalizationOutputUTF8ByteCount, + outputs.reduce(0) { $0 + $1.utf8.count } + ) + XCTAssertLessThanOrEqual( + collector.swiftSignatureNormalizationOutputUTF8ByteCount, + collector.swiftSignatureNormalizationInputUTF8ByteCount + ) + } + func testCaptureIndexCountsMissingNamedBuckets() { let collector = CodeMapPerformanceCollector() let index = CodeMapCaptureIndex([], performanceCollector: collector) @@ -534,6 +620,11 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { } } + private static func legacySwiftSignatureWhitespaceNormalization(_ input: String) -> String { + input.trimmingCharacters(in: .whitespacesAndNewlines) + .replacing(#/\s+/#, with: " ") + } + private enum BenchmarkError: Error { case notReady(CodeMapSyntaxArtifactOutcome) case queryNotReady(CodeMapSyntaxQueryOutcome) diff --git a/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift index 75302f695..d37918116 100644 --- a/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift +++ b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift @@ -493,6 +493,11 @@ "swift_property_duplicate_checks=\(collector.swiftPropertyDuplicateCheckCount)", "swift_property_duplicate_visits=\(collector.swiftPropertyDuplicateCandidateVisits)", "swift_signature_count=\(collector.swiftStrategyFunctionSignatureCount)", + "swift_signature_ascii_noop_count=\(collector.swiftSignatureNormalizationASCIINoOpCount)", + "swift_signature_ascii_rewrite_count=\(collector.swiftSignatureNormalizationASCIIRewriteCount)", + "swift_signature_unicode_fallback_count=\(collector.swiftSignatureNormalizationUnicodeFallbackCount)", + "swift_signature_input_utf8_bytes=\(collector.swiftSignatureNormalizationInputUTF8ByteCount)", + "swift_signature_output_utf8_bytes=\(collector.swiftSignatureNormalizationOutputUTF8ByteCount)", "swift_name_lookup_count=\(collector.swiftStrategyFunctionNameLookupCount)", "swift_parameter_extraction_count=\(collector.swiftStrategyParameterExtractionCount)", "swift_return_type_count=\(collector.swiftStrategyReturnTypeExtractionCount)", From fd0d7696785a3d827b26721ba7b7873eeb6c1ace Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Wed, 22 Jul 2026 14:40:33 -0400 Subject: [PATCH 03/11] Optimize Swift parameter type scanning --- .../SwiftCodeMapStrategy.swift | 151 ++++++++++++++++- .../CodeMapPerformanceCollector.swift | 7 + ...deMapQueryOptimizationBenchmarkTests.swift | 157 +++++++++++++++++- ...SwiftCodeMapPipelineBenchmarkSupport.swift | 7 + 4 files changed, 319 insertions(+), 3 deletions(-) diff --git a/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift b/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift index e418fc4e4..e5a6ff916 100644 --- a/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift +++ b/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift @@ -904,11 +904,20 @@ enum SwiftCodeMapStrategy { local = nsContent.substring(with: locCap.range) } let paramText = nsContent.substring(with: paramNode.range) + let parameterTypeResolutionStart = performanceCollector == nil ? 0 : CFAbsoluteTimeGetCurrent() if let typeCap = index.firstCapture(named: "swift.param.type", containedIn: paramNode.range) { + performanceCollector?.swiftParameterTypeDirectCaptureCount += 1 type = nsContent.substring(with: typeCap.range) - } else if let parsedType = extractSwiftParamType(from: paramText) { + } else if let parsedType = extractSwiftParamType( + from: paramText, + performanceCollector: performanceCollector + ) { type = parsedType } + if let performanceCollector { + performanceCollector.swiftParameterTypeResolutionDuration += + CFAbsoluteTimeGetCurrent() - parameterTypeResolutionStart + } if let localName = local { let ext = (external == "_") ? "_" : external @@ -1014,7 +1023,56 @@ enum SwiftCodeMapStrategy { return end } - private static func extractSwiftParamType(from paramText: String) -> String? { + static func extractSwiftParamType( + from paramText: String, + performanceCollector: CodeMapPerformanceCollector? = nil + ) -> String? { + var inputUTF8ByteCount = 0 + var containsNonASCII = false + for byte in paramText.utf8 { + inputUTF8ByteCount += 1 + if byte >= 0x80 { + containsNonASCII = true + } + } + + performanceCollector?.swiftParameterTypeFallbackParserCount += 1 + performanceCollector?.swiftParameterTypeInputUTF8ByteCount += inputUTF8ByteCount + + if containsNonASCII { + performanceCollector?.swiftParameterTypeUnicodeLegacyFallbackCount += 1 + let legacyStart = performanceCollector == nil ? 0 : CFAbsoluteTimeGetCurrent() + let result = extractSwiftParamTypeLegacy(from: paramText) + if let performanceCollector { + performanceCollector.swiftParameterTypeLegacyFallbackDuration += + CFAbsoluteTimeGetCurrent() - legacyStart + } + return result + } + + performanceCollector?.swiftParameterTypeASCIIFastPathCount += 1 + let utf8 = paramText.utf8 + guard let colonIndex = firstTopLevelASCIISwiftDelimiter( + 0x3A, + in: utf8, + from: utf8.startIndex + ) else { return nil } + let afterColonStart = utf8.index(after: colonIndex) + let afterColon: Substring + if let equalIndex = firstTopLevelASCIISwiftDelimiter( + 0x3D, + in: utf8, + from: afterColonStart + ) { + afterColon = paramText[afterColonStart ..< equalIndex] + } else { + afterColon = paramText[afterColonStart...] + } + let trimmed = afterColon.trimmingCharacters(in: .whitespacesAndNewlines) + return trimmed.isEmpty ? nil : trimmed + } + + static func extractSwiftParamTypeLegacy(from paramText: String) -> String? { let parameter = paramText[...] guard let colonIndex = firstTopLevelSwiftDelimiter(":", in: parameter) else { return nil } var afterColon = parameter[parameter.index(after: colonIndex)...] @@ -1025,6 +1083,95 @@ enum SwiftCodeMapStrategy { return trimmed.isEmpty ? nil : trimmed } + private static func firstTopLevelASCIISwiftDelimiter( + _ target: UInt8, + in utf8: String.UTF8View, + from startIndex: String.UTF8View.Index + ) -> String.UTF8View.Index? { + var delimiterStack: [UInt8] = [] + var stringDelimiter: (pounds: Int, quotes: Int)? + var index = startIndex + + while index < utf8.endIndex { + if let activeStringDelimiter = stringDelimiter { + if utf8[index] == 0x5C, activeStringDelimiter.pounds == 0 { + index = utf8.index(after: index) + if index < utf8.endIndex { + index = utf8.index(after: index) + } + continue + } + + var terminatorEnd = index + var isTerminator = true + for _ in 0 ..< activeStringDelimiter.quotes { + guard terminatorEnd < utf8.endIndex, utf8[terminatorEnd] == 0x22 else { + isTerminator = false + break + } + terminatorEnd = utf8.index(after: terminatorEnd) + } + if isTerminator { + for _ in 0 ..< activeStringDelimiter.pounds { + guard terminatorEnd < utf8.endIndex, utf8[terminatorEnd] == 0x23 else { + isTerminator = false + break + } + terminatorEnd = utf8.index(after: terminatorEnd) + } + } + if isTerminator { + index = terminatorEnd + stringDelimiter = nil + continue + } + + index = utf8.index(after: index) + continue + } + + var quoteIndex = index + var poundCount = 0 + while quoteIndex < utf8.endIndex, utf8[quoteIndex] == 0x23 { + poundCount += 1 + quoteIndex = utf8.index(after: quoteIndex) + } + if quoteIndex < utf8.endIndex, utf8[quoteIndex] == 0x22 { + var openerEnd = utf8.index(after: quoteIndex) + var quoteCount = 1 + if openerEnd < utf8.endIndex, utf8[openerEnd] == 0x22 { + let thirdQuoteIndex = utf8.index(after: openerEnd) + if thirdQuoteIndex < utf8.endIndex, utf8[thirdQuoteIndex] == 0x22 { + quoteCount = 3 + openerEnd = utf8.index(after: thirdQuoteIndex) + } + } + stringDelimiter = (poundCount, quoteCount) + index = openerEnd + continue + } + + let byte = utf8[index] + switch byte { + case 0x28, 0x5B, 0x7B: + delimiterStack.append(byte) + case 0x29: + if delimiterStack.last == 0x28 { delimiterStack.removeLast() } + case 0x5D: + if delimiterStack.last == 0x5B { delimiterStack.removeLast() } + case 0x7D: + if delimiterStack.last == 0x7B { delimiterStack.removeLast() } + default: + if byte == target, delimiterStack.isEmpty { + return index + } + } + index = utf8.index(after: index) + } + + return nil + } + private static func firstTopLevelSwiftDelimiter( _ target: Character, in text: Substring diff --git a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift index 97c4b3b5a..69928cefc 100644 --- a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift +++ b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift @@ -96,6 +96,11 @@ package final class CodeMapPerformanceCollector { package var swiftSignatureNormalizationOutputUTF8ByteCount = 0 package var swiftStrategyFunctionNameLookupCount = 0 package var swiftStrategyParameterExtractionCount = 0 + package var swiftParameterTypeDirectCaptureCount = 0 + package var swiftParameterTypeFallbackParserCount = 0 + package var swiftParameterTypeASCIIFastPathCount = 0 + package var swiftParameterTypeUnicodeLegacyFallbackCount = 0 + package var swiftParameterTypeInputUTF8ByteCount = 0 package var swiftStrategyReturnTypeExtractionCount = 0 package var swiftStrategyPropertyDeclarationCount = 0 package var swiftStrategyPropertyTypeExtractionCount = 0 @@ -209,6 +214,8 @@ package final class CodeMapPerformanceCollector { package var swiftSignatureNormalizationDuration: TimeInterval = 0 package var swiftStrategyFunctionNameLookupDuration: TimeInterval = 0 package var swiftStrategyParameterExtractionDuration: TimeInterval = 0 + package var swiftParameterTypeResolutionDuration: TimeInterval = 0 + package var swiftParameterTypeLegacyFallbackDuration: TimeInterval = 0 package var swiftStrategyReturnTypeExtractionDuration: TimeInterval = 0 package var swiftStrategyPropertyDeclarationDuration: TimeInterval = 0 package var swiftPropertyDeclarationLookupDuration: TimeInterval = 0 diff --git a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift index 8175ad012..ce2d99e3d 100644 --- a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift +++ b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift @@ -70,6 +70,27 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { collector.swiftSignatureNormalizationOutputUTF8ByteCount, collector.swiftSignatureNormalizationInputUTF8ByteCount ) + XCTAssertEqual( + collector.swiftParameterTypeASCIIFastPathCount + + collector.swiftParameterTypeUnicodeLegacyFallbackCount, + collector.swiftParameterTypeFallbackParserCount + ) + XCTAssertGreaterThan( + collector.swiftParameterTypeDirectCaptureCount + + collector.swiftParameterTypeFallbackParserCount, + 0 + ) + XCTAssertLessThanOrEqual( + collector.swiftParameterTypeFallbackParserCount, + collector.swiftNestedFunctionContainmentLookupCount + ) + if collector.swiftParameterTypeFallbackParserCount > 0 { + XCTAssertGreaterThan(collector.swiftParameterTypeInputUTF8ByteCount, 0) + } + XCTAssertGreaterThanOrEqual( + collector.swiftParameterTypeResolutionDuration, + collector.swiftParameterTypeLegacyFallbackDuration + ) let evidence = try SwiftCodeMapPipelineBenchmarkSupport.makeEvidence( files: files, @@ -325,6 +346,125 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { ) } + func testSwiftParameterTypeASCIIFastPathMatchesLegacyBehavior() { + let cases: [(name: String, input: String, expected: String?)] = [ + ("simple", "value: Int", "Int"), + ("external local", "_ value: Int", "Int"), + ("labeled generic", "label value: Result", "Result"), + ("nested attribute colon", "@Wrapper(label: \"x:y\") value: Int = 42", "Int"), + ("nested default", "value: [String: Int] = [\"x\": 1]", "[String: Int]"), + ("tuple", "value: (Int, String)", "(Int, String)"), + ("closure default", "handler: (Int) -> Void = { _ in }", "(Int) -> Void"), + ("generic function type", "transform: (@escaping (Int) -> Result)?", "(@escaping (Int) -> Result)?"), + ("nested collection", "value: [String: (Int, Bool)]", "[String: (Int, Bool)]"), + ("raw string", ##"@Wrapper(text: #"x:y="#) value: Int = 42"##, "Int"), + ("multi-pound raw string", ###"@Wrapper(text: ##"x:y="##) value: Int"###, "Int"), + ("triple string", "@Wrapper(text: \"\"\"x:y=()[]{}\"\"\") value: String", "String"), + ("escaped quote", ##"@Wrapper(text: "x\"y:z") value: Int"##, "Int"), + ("line comment blindness", "value // note: marker: Int", "marker: Int"), + ("block comment colon blindness", "value /* note: marker */: Int", "marker */: Int"), + ("block comment equal blindness", "value: Int /* = default */", "Int /*"), + ("unmatched closing paren", "value): Int", "Int"), + ("unmatched opening paren", "value(: Int", nil), + ("mismatched closing bracket", "value]: Int", "Int"), + ("unclosed bracket", "value[: Int", nil), + ("unterminated string", "\"unterminated: value: Int", nil), + ("unterminated raw string", "##\"unterminated: value: Int", nil), + ("unterminated triple string", "\"\"\"unterminated: value: Int", nil), + ("isolated pound", "# value: Int", "Int"), + ("two quotes", "\"\": Int", "Int"), + ("missing colon", "value", nil), + ("empty type", "value:", nil), + ("whitespace type", "value: \t\n\u{000B}\u{000C}\r", nil), + ("angle brackets stay untracked", "value: Result", "Result UInt64 { + state = state &* 6_364_136_223_846_793_005 &+ 1_442_695_040_888_963_407 + return state + } + + for index in 0 ..< caseCount { + let length = Int(nextRandom() % 161) + var bytes: [UInt8] = [] + bytes.reserveCapacity(length) + for _ in 0 ..< length { + bytes.append(alphabet[Int(nextRandom() % UInt64(alphabet.count))]) + } + let input = String(decoding: bytes, as: UTF8.self) + totalUTF8ByteCount += input.utf8.count + XCTAssertEqual( + SwiftCodeMapStrategy.extractSwiftParamType( + from: input, + performanceCollector: collector + ), + SwiftCodeMapStrategy.extractSwiftParamTypeLegacy(from: input), + "generated case \(index): \(String(reflecting: input))" + ) + } + + XCTAssertEqual(collector.swiftParameterTypeFallbackParserCount, caseCount) + XCTAssertEqual(collector.swiftParameterTypeASCIIFastPathCount, caseCount) + XCTAssertEqual(collector.swiftParameterTypeUnicodeLegacyFallbackCount, 0) + XCTAssertEqual(collector.swiftParameterTypeInputUTF8ByteCount, totalUTF8ByteCount) + } + + func testSwiftParameterTypeUnicodeAlwaysUsesLegacyFallback() { + let inputs = [ + "café: Type", + "value: Café", + "value:\u{00A0}Type", + "value:\u{2003}Type", + "@Wrapper(label: \"🙂:x\") value: Int", + "café(: Int", + ] + let collector = CodeMapPerformanceCollector() + + for input in inputs { + XCTAssertEqual( + SwiftCodeMapStrategy.extractSwiftParamType( + from: input, + performanceCollector: collector + ), + SwiftCodeMapStrategy.extractSwiftParamTypeLegacy(from: input), + String(reflecting: input) + ) + } + + XCTAssertEqual(collector.swiftParameterTypeFallbackParserCount, inputs.count) + XCTAssertEqual(collector.swiftParameterTypeASCIIFastPathCount, 0) + XCTAssertEqual(collector.swiftParameterTypeUnicodeLegacyFallbackCount, inputs.count) + XCTAssertEqual( + collector.swiftParameterTypeInputUTF8ByteCount, + inputs.reduce(0) { $0 + $1.utf8.count } + ) + XCTAssertGreaterThanOrEqual(collector.swiftParameterTypeLegacyFallbackDuration, 0) + } + func testCaptureIndexCountsMissingNamedBuckets() { let collector = CodeMapPerformanceCollector() let index = CodeMapCaptureIndex([], performanceCollector: collector) @@ -540,12 +680,27 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { func wrapped(@Wrapper(label: "x:y") value: Int = 42) {} } """ - let artifact = try build(source: source, language: .swift) + let collector = CodeMapPerformanceCollector() + let artifact = try build( + source: source, + language: .swift, + options: .countersOnly, + collector: collector + ) let example = try XCTUnwrap(artifact.classes.first { $0.name == "Example" }) let wrapped = try XCTUnwrap(example.methods.first { $0.name == "wrapped" }) XCTAssertEqual(wrapped.parameters.map(\.localName), ["value"]) XCTAssertEqual(wrapped.parameters.map(\.typeName), ["Int"]) + XCTAssertEqual(collector.swiftParameterTypeDirectCaptureCount, 0) + XCTAssertEqual(collector.swiftParameterTypeFallbackParserCount, 1) + XCTAssertEqual(collector.swiftParameterTypeASCIIFastPathCount, 1) + XCTAssertEqual(collector.swiftParameterTypeUnicodeLegacyFallbackCount, 0) + XCTAssertGreaterThan(collector.swiftParameterTypeInputUTF8ByteCount, 0) + XCTAssertGreaterThanOrEqual( + collector.swiftParameterTypeResolutionDuration, + collector.swiftParameterTypeLegacyFallbackDuration + ) } func testTypeScriptUncontainedMembersFallThroughBeforeExtraction() throws { diff --git a/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift index d37918116..aaa07775a 100644 --- a/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift +++ b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift @@ -422,6 +422,8 @@ "swift_signature_normalize_ms=\(milliseconds(collector.swiftSignatureNormalizationDuration))", "swift_name_lookup_ms=\(milliseconds(collector.swiftStrategyFunctionNameLookupDuration))", "swift_parameters_ms=\(milliseconds(collector.swiftStrategyParameterExtractionDuration))", + "swift_parameter_type_resolution_ms=\(milliseconds(collector.swiftParameterTypeResolutionDuration))", + "swift_parameter_type_legacy_fallback_ms=\(milliseconds(collector.swiftParameterTypeLegacyFallbackDuration))", "swift_return_type_ms=\(milliseconds(collector.swiftStrategyReturnTypeExtractionDuration))", "swift_property_ms=\(milliseconds(collector.swiftStrategyPropertyDeclarationDuration))", "swift_property_lookup_ms=\(milliseconds(collector.swiftPropertyDeclarationLookupDuration))", @@ -500,6 +502,11 @@ "swift_signature_output_utf8_bytes=\(collector.swiftSignatureNormalizationOutputUTF8ByteCount)", "swift_name_lookup_count=\(collector.swiftStrategyFunctionNameLookupCount)", "swift_parameter_extraction_count=\(collector.swiftStrategyParameterExtractionCount)", + "swift_parameter_type_direct_capture_count=\(collector.swiftParameterTypeDirectCaptureCount)", + "swift_parameter_type_fallback_parser_count=\(collector.swiftParameterTypeFallbackParserCount)", + "swift_parameter_type_ascii_fast_path_count=\(collector.swiftParameterTypeASCIIFastPathCount)", + "swift_parameter_type_unicode_legacy_fallback_count=\(collector.swiftParameterTypeUnicodeLegacyFallbackCount)", + "swift_parameter_type_input_utf8_bytes=\(collector.swiftParameterTypeInputUTF8ByteCount)", "swift_return_type_count=\(collector.swiftStrategyReturnTypeExtractionCount)", "swift_property_declaration_count=\(collector.swiftStrategyPropertyDeclarationCount)", "swift_property_type_count=\(collector.swiftStrategyPropertyTypeExtractionCount)", From f861d16bb3a14305d1329e079ed393b317fc0508 Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Wed, 22 Jul 2026 14:38:48 -0400 Subject: [PATCH 04/11] Remove obsolete model benchmark and syntax highlighting --- Package.swift | 1 - .../Fixtures/test-suite-contract-ledger.tsv | 2 - Scripts/source_layout_guardrails.sh | 7 +- .../Features/CodeMap/CodeMapPerfStats.swift | 67 - .../Core/BelievableCodeFactory.swift | 663 -- .../Benchmark/Core/BenchmarkAuditor.swift | 620 -- .../Core/BenchmarkDecoyPlanner.swift | 830 -- .../Benchmark/Core/BenchmarkDiffApplier.swift | 229 - .../Core/BenchmarkDiffParserBridge.swift | 63 - .../Benchmark/Core/BenchmarkEngine.swift | 288 - .../Benchmark/Core/BenchmarkFileSystem.swift | 117 - .../Benchmark/Core/BenchmarkModels.swift | 632 -- .../Core/BenchmarkProgressEvent.swift | 12 - .../Core/BenchmarkProjectScaffolder.swift | 318 - .../Benchmark/Core/BenchmarkRandom.swift | 42 - .../Benchmark/Core/BenchmarkReporter.swift | 113 - .../Benchmark/Core/BenchmarkRunStore.swift | 50 - .../Benchmark/Core/BenchmarkRunSummary.swift | 171 - .../Core/BenchmarkTaskExecutor.swift | 1002 -- .../Core/BenchmarkTaskGenerator.swift | 8180 ----------------- .../Benchmark/Core/BenchmarkVerifier.swift | 2264 ----- .../Benchmark/Core/UnifiedPatchApplier.swift | 382 - .../BenchmarkSettingsViewModel.swift | 1087 --- .../Views/BenchmarkSettingsView.swift | 952 -- .../Views/Components/FilePreviewPopover.swift | 12 +- .../Views/AgentModelsSettingsView.swift | 8 +- .../Settings/Views/SettingsView.swift | 33 +- .../ViewModels/FileViewModel.swift | 112 +- .../ViewModels/WorkspaceFilesViewModel.swift | 9 - .../Infrastructure/AI/AIMessage.swift | 9 +- .../Infrastructure/AI/AIModel.swift | 40 - .../AI/Prompts/Code Examples/CExamples.swift | 522 -- .../Code Examples/CSharpExamples.swift | 506 - .../Prompts/Code Examples/CodeExamples.swift | 242 - .../Prompts/Code Examples/CppExamples.swift | 494 - .../Prompts/Code Examples/DartExamples.swift | 522 -- .../AI/Prompts/Code Examples/GoExamples.swift | 507 - .../Prompts/Code Examples/JavaExamples.swift | 533 -- .../Code Examples/JavaScriptExamples.swift | 490 - .../Prompts/Code Examples/PHPExamples.swift | 520 -- .../Code Examples/PythonExamples.swift | 440 - .../Prompts/Code Examples/RustExamples.swift | 479 - .../Prompts/Code Examples/SwiftExamples.swift | 553 -- .../Prompts/Code Examples/TSXExamples.swift | 639 -- .../Code Examples/TypeScriptExamples.swift | 513 -- .../AI/Prompts/Legacy/ChatPrompt.swift | 315 - .../AI/Prompts/Legacy/ChatPromptFull.swift | 225 - .../AI/Prompts/Legacy/ChatSearchReplace.swift | 417 - .../Legacy/ChatSearchReplaceNoIndent.swift | 389 - .../AI/Prompts/Legacy/DiffExamples.swift | 759 -- .../AI/Prompts/Legacy/DiffQuery.swift | 186 - .../AI/Prompts/Legacy/DirectDiffPrompt.swift | 138 - .../Prompts/Legacy/FileEditDiffIndent.swift | 282 - .../Prompts/Legacy/FileEditDiffPrompt.swift | 277 - .../AI/Prompts/Legacy/FileEditPrompt.swift | 94 - .../Prompts/Legacy/WholeFormatClipboard.swift | 226 - .../AI/Prompts/Legacy/xmlPrompt.swift | 120 - .../AI/Prompts/PromptFactory.swift | 1109 --- .../AI/SystemPromptService.swift | 29 - .../Infrastructure/Diffing/DiffParser.swift | 53 +- .../SyntaxParsing/Queries/GoQueries.swift | 139 - .../SyntaxParsing/Queries/JavaQueries.swift | 163 - .../Queries/JavaScriptQueries.swift | 217 - .../SyntaxParsing/Queries/PythonQueries.swift | 150 - .../SyntaxParsing/Queries/RubyQueries.swift | 30 - .../SyntaxParsing/Queries/RustQueries.swift | 175 - .../SyntaxParsing/Queries/SwiftQueries.swift | 202 - .../SyntaxParsing/Queries/cQueries.swift | 92 - .../SyntaxParsing/Queries/cSharpQueries.swift | 224 - .../SyntaxParsing/Queries/cppQueries.swift | 92 - .../SyntaxParsing/Queries/phpQueries.swift | 164 - .../SyntaxParsing/Queries/typeScript.swift | 96 - .../SyntaxParsing/QueryResourceLoader.swift | 33 - .../SyntaxParsing/SyntaxManager.swift | 288 +- .../SyntaxHighlightingQueryTests.swift | 19 - .../BenchmarkDiffApplierTests.swift | 60 - 76 files changed, 30 insertions(+), 32008 deletions(-) delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BelievableCodeFactory.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkAuditor.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDecoyPlanner.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDiffApplier.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDiffParserBridge.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkEngine.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkFileSystem.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkModels.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkProgressEvent.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkProjectScaffolder.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRandom.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkReporter.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRunStore.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRunSummary.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkTaskExecutor.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkTaskGenerator.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkVerifier.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/UnifiedPatchApplier.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/ViewModels/BenchmarkSettingsViewModel.swift delete mode 100644 Sources/RepoPrompt/Features/Diagnostics/Benchmark/Views/BenchmarkSettingsView.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CSharpExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CodeExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CppExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/DartExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/GoExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/JavaExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/JavaScriptExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/PHPExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/PythonExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/RustExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/SwiftExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/TSXExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/TypeScriptExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatPrompt.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatPromptFull.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatSearchReplace.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatSearchReplaceNoIndent.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DiffExamples.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DiffQuery.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DirectDiffPrompt.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditDiffIndent.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditDiffPrompt.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditPrompt.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/WholeFormatClipboard.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/xmlPrompt.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/AI/Prompts/PromptFactory.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/GoQueries.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/JavaQueries.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/JavaScriptQueries.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/PythonQueries.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/RubyQueries.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/RustQueries.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/SwiftQueries.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cQueries.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cSharpQueries.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cppQueries.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/phpQueries.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/typeScript.swift delete mode 100644 Sources/RepoPrompt/Infrastructure/SyntaxParsing/QueryResourceLoader.swift delete mode 100644 Tests/RepoPromptTests/CodeMap/SyntaxHighlightingQueryTests.swift delete mode 100644 Tests/RepoPromptTests/Diagnostics/BenchmarkDiffApplierTests.swift diff --git a/Package.swift b/Package.swift index 4bfec97ee..45b4b37b5 100644 --- a/Package.swift +++ b/Package.swift @@ -58,7 +58,6 @@ var repoPromptAppDependencies: [Target.Dependency] = [ .product(name: "MarkdownUI", package: "swift-markdown-ui"), .product(name: "Markdown", package: "swift-markdown"), .product(name: "MCP", package: "swift-sdk"), - .product(name: "SwiftTreeSitter", package: "swift-tree-sitter"), .product(name: "SwiftAnthropic", package: "SwiftAnthropic"), .product(name: "SwiftOpenAI", package: "SwiftOpenAI"), .product(name: "UniversalCharsetDetection", package: "UniversalCharsetDetection"), diff --git a/Scripts/Fixtures/test-suite-contract-ledger.tsv b/Scripts/Fixtures/test-suite-contract-ledger.tsv index 9f25a181f..f09e38adc 100644 --- a/Scripts/Fixtures/test-suite-contract-ledger.tsv +++ b/Scripts/Fixtures/test-suite-contract-ledger.tsv @@ -628,7 +628,6 @@ root/RepoPromptTests.AutomaticReviewGitDiffCoordinatorTests/testOneCheckoutFailu root/RepoPromptTests.AutomaticReviewGitDiffCoordinatorTests/testSingleCheckoutIncludesStagedUnstagedAndUntrackedChanges root Tests/RepoPromptTests/Prompt/AutomaticReviewGitDiffCoordinatorTests.swift RepoPromptTests.AutomaticReviewGitDiffCoordinatorTests testSingleCheckoutIncludesStagedUnstagedAndUntrackedChanges Prompt prompt.git_review.single_checkout_change_classes staged,unstaged,untracked real_git_integration root_swiftpm routine 3 ReviewGitRepositoryFixture A single checkout review includes staged, unstaged, and untracked selected changes. One working-tree change class could be omitted from automatic review. 0.177000 git_subprocess;filesystem per_test_temporary_repository_cleanup retain 0 Uncommitted #264 contract reconciliation root/RepoPromptTests.AutomaticReviewGitDiffCoordinatorTests/testUnresolvedNoRepositoryAndUnsupportedBackendAreStructuredFailures root Tests/RepoPromptTests/Prompt/AutomaticReviewGitDiffCoordinatorTests.swift RepoPromptTests.AutomaticReviewGitDiffCoordinatorTests testUnresolvedNoRepositoryAndUnsupportedBackendAreStructuredFailures Prompt prompt.git_review.structured_owner_failures unresolved_path,no_repository,unsupported_backend deterministic_unit root_swiftpm routine 3 Unresolved paths, missing repositories, and unsupported backends remain explicit structured failures. Owner-resolution failures could disappear and produce a plausible partial diff. 0.000000 test_case retain 0 Uncommitted #264 contract reconciliation root/RepoPromptTests.BashToolResultParserRecoveryTests/testParserKeepsCommandLivenessAndMetadataContractsInSync root Tests/RepoPromptTests/AgentMode/Codex/BashToolResultParserRecoveryTests.swift RepoPromptTests.BashToolResultParserRecoveryTests testParserKeepsCommandLivenessAndMetadataContractsInSync AgentMode unreviewed unreviewed root_swiftpm routine 1 unreviewed unreviewed 0.001000 unreviewed retain_pending_review 0 initial census source line 5 -root/RepoPromptTests.BenchmarkDiffApplierTests/testModifyChangeAppliesGeneratedDiffChunks root Tests/RepoPromptTests/Diagnostics/BenchmarkDiffApplierTests.swift RepoPromptTests.BenchmarkDiffApplierTests testModifyChangeAppliesGeneratedDiffChunks Diagnostics unreviewed unreviewed root_swiftpm diagnostic 1 unreviewed unreviewed 0.001000 unreviewed retain_pending_review 0 initial census source line 5 root/RepoPromptTests.BindContextRoutingRecoveryTests/testBindContextParsesCommaSeparatedWorkingDirs root Tests/RepoPromptTests/MCP/Control/BindContextRoutingRecoveryTests.swift RepoPromptTests.BindContextRoutingRecoveryTests testBindContextParsesCommaSeparatedWorkingDirs MCP unreviewed unreviewed root_swiftpm routine 1 unreviewed unreviewed 0.000500 unreviewed retain_pending_review 0 initial census source line 45 root/RepoPromptTests.BindContextRoutingRecoveryTests/testBindContextParsesReadOnlyOperationsWithoutSelectors root Tests/RepoPromptTests/MCP/Control/BindContextRoutingRecoveryTests.swift RepoPromptTests.BindContextRoutingRecoveryTests testBindContextParsesReadOnlyOperationsWithoutSelectors MCP unreviewed unreviewed root_swiftpm routine 1 unreviewed unreviewed 0.000000 unreviewed retain_pending_review 0 initial census source line 35 root/RepoPromptTests.BindContextRoutingRecoveryTests/testBindContextParsesWorkingDirsAndPrefersSelectedWindow root Tests/RepoPromptTests/MCP/Control/BindContextRoutingRecoveryTests.swift RepoPromptTests.BindContextRoutingRecoveryTests testBindContextParsesWorkingDirsAndPrefersSelectedWindow MCP unreviewed unreviewed root_swiftpm routine 1 unreviewed unreviewed 0.000000 unreviewed retain_pending_review 0 initial census source line 7 @@ -811,7 +810,6 @@ root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testStruc root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftParameterFallbackSkipsNestedAttributeColons root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftParameterFallbackSkipsNestedAttributeColons CodeMap codemap.query_optimization.swift_parameter_attribute_colon swift,parameter_type_fallback,property_wrapper,string_literal,nested_delimiters,default_value regression codemap_core fast 1 A Swift parameter with a property-wrapper argument containing a colon retains its local name and exact declared type while excluding the default value. The bounded fallback could treat an attribute argument colon as the parameter declaration separator and emit a malformed type. tree_sitter,synthetic_source syntax_manager test_case retain 0 Focused regression for top-level Swift parameter delimiter scanning. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSyntheticSwiftAndTypeScriptAttribution root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSyntheticSwiftAndTypeScriptAttribution CodeMap codemap.query_optimization.synthetic_attribution swift,typescript,query_capture_budget,performance_attribution diagnostic codemap_core routine 2 Deterministic 200-declaration Swift and TypeScript corpora complete successfully and report parse, capture materialization, indexing, capture-loop, regex/signature, and capture-count attribution without machine-dependent assertions. Future query changes could silently increase capture amplification or reintroduce regex/signature work without a repeatable focused measurement surface. 1.760000 tree_sitter,cpu_timing,synthetic_source syntax_manager test_case retain 0 Diagnostic timings are reported only; correctness and attribution coverage remain deterministic root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testTypeScriptUncontainedMembersFallThroughBeforeExtraction root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testTypeScriptUncontainedMembersFallThroughBeforeExtraction CodeMap codemap.query_optimization.ts_uncontained_fallthrough typescript,container_routing,generic_fallback,pre_extraction_guard regression codemap_core routine 7 Every TypeScript member capture family declines strategy handling before line, signature, or type extraction when no matching class or interface boundary exists. A widened TypeScript strategy gate could consume uncontained members, attach them to unrelated containers, or perform wasted extraction before fallback. tree_sitter,synthetic_source syntax_manager test_case retain 0 Direct strategy boundary regression for method, field, and interface signature captures. -root/RepoPromptTests.SyntaxHighlightingQueryTests/testEveryRegisteredHighlightingQueryCompilesAgainstItsGrammar root Tests/RepoPromptTests/CodeMap/SyntaxHighlightingQueryTests.swift RepoPromptTests.SyntaxHighlightingQueryTests testEveryRegisteredHighlightingQueryCompilesAgainstItsGrammar CodeMap codemap.highlighting.query_registry_compilation app_highlighting,language_registry,query_compilation,official_grammars compiler_boundary root_swiftpm routine 13 Every registered app highlighting query compiles against its exact registered official grammar. A grammar upgrade or stale app-owned query token could make highlighting fail at runtime for one supported language while CodeMap-only goldens remain green. 0.020000 tree_sitter test_case retain -1 Item 13 table-driven app highlighting query compatibility contract; Dart scenario removed alongside active Dart CodeMap support. root/RepoPromptTests.CodeMapGoldenTests/testSnapshotFileTreeMarksCodeMapFixtures root Tests/RepoPromptTests/CodeMap/CodeMapGoldenTests.swift RepoPromptTests.CodeMapGoldenTests testSnapshotFileTreeMarksCodeMapFixtures CodeMap unreviewed unreviewed root_swiftpm routine 1 unreviewed unreviewed 0.000500 unreviewed retain_pending_review 0 initial census source line 23 root/RepoPromptTests.CodeMapGoldenTests/testSnapshotFileTreeNoneModeProducesNoOutputOrLegend root Tests/RepoPromptTests/CodeMap/CodeMapGoldenTests.swift RepoPromptTests.CodeMapGoldenTests testSnapshotFileTreeNoneModeProducesNoOutputOrLegend CodeMap unreviewed unreviewed root_swiftpm routine 1 unreviewed unreviewed 0.000000 unreviewed retain_pending_review 0 initial census source line 36 root/RepoPromptTests.CodeMapGoldenTests/testSnapshotFileTreeSelectedModeStillRendersSelectionAndLegend root Tests/RepoPromptTests/CodeMap/CodeMapGoldenTests.swift RepoPromptTests.CodeMapGoldenTests testSnapshotFileTreeSelectedModeStillRendersSelectionAndLegend CodeMap unreviewed unreviewed root_swiftpm routine 1 unreviewed unreviewed 0.000000 unreviewed retain_pending_review 0 initial census source line 46 diff --git a/Scripts/source_layout_guardrails.sh b/Scripts/source_layout_guardrails.sh index 077d63634..c9c7cdb3f 100755 --- a/Scripts/source_layout_guardrails.sh +++ b/Scripts/source_layout_guardrails.sh @@ -216,8 +216,8 @@ if wrapper_manifest_pattern.search(manifest_text) is None: errors.append("Package.swift must use the unnamed URL/revision declaration for the approved RepoPrompt SwiftTreeSitter fork") if wrapper.get("location") != wrapper_url or wrapper.get("state", {}) != {"revision": wrapper_revision}: errors.append("SwiftTreeSitter fork location/revision drifted") -if ("SwiftTreeSitter", "swift-tree-sitter") not in repo_prompt_app_products: - errors.append("RepoPromptApp missing direct SwiftTreeSitter product dependency for syntax parsing") +if ("SwiftTreeSitter", "swift-tree-sitter") in repo_prompt_app_products: + errors.append("RepoPromptApp must not directly depend on SwiftTreeSitter") if ("SwiftTreeSitter", "swift-tree-sitter") not in repo_prompt_code_map_core_products: errors.append("RepoPromptCodeMapCore missing direct SwiftTreeSitter product dependency") if "https://github.com/ChimeHQ/SwiftTreeSitter" in manifest_text or "swifttreesitter" in resolved_pins: @@ -260,10 +260,7 @@ if code_map_core_tests.get("path") != "Tests/RepoPromptCodeMapCoreTests": if core_test_dependencies != ["RepoPromptCodeMapCore"]: errors.append("RepoPromptCodeMapCoreTests must depend only on RepoPromptCodeMapCore") -syntax_source = Path("Sources/RepoPrompt/Infrastructure/SyntaxParsing/SyntaxManager.swift").read_text() core_syntax_source = Path("Sources/RepoPromptCodeMapCore/CodeMapSyntaxEngine.swift").read_text() -if "import SwiftTreeSitter\n" not in syntax_source: - errors.append("SyntaxManager must retain direct SwiftTreeSitter import for highlighting") required_core_imports = { "SwiftTreeSitter", "TreeSitterC", "TreeSitterCPP", "TreeSitterCSharp", "TreeSitterGo", "TreeSitterJava", "TreeSitterJavaScript", "TreeSitterPHP", "TreeSitterPython", diff --git a/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift b/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift index 8625fb145..0f64c106a 100644 --- a/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift +++ b/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift @@ -9,29 +9,6 @@ import Foundation import RepoPromptCodeMapCore -struct CodeMapSyntaxStartupPerfStats { - var primeDuration: TimeInterval = 0 - var warmCacheDuration: TimeInterval = 0 - var warmCodeMapQueriesDuration: TimeInterval = 0 - var languageConfigCreateDuration: TimeInterval = 0 - var languagePointerDuration: TimeInterval = 0 - var highlightQueryDataDuration: TimeInterval = 0 - var highlightQueryCompileDuration: TimeInterval = 0 - var codeMapQueryDataDuration: TimeInterval = 0 - var codeMapQueryCompileDuration: TimeInterval = 0 - - var warmCacheLanguageCount = 0 - var languageConfigCreateCount = 0 - var languageConfigSuccessCount = 0 - var languageConfigFailureCount = 0 - var highlightQueryCompileSuccessCount = 0 - var highlightQueryCompileFailureCount = 0 - var warmCodeMapQueryLanguageCount = 0 - var codeMapQueryPrecomputeSuccessCount = 0 - var codeMapQueryPrecomputeFailureCount = 0 - var codeMapQueryPrecomputeSkippedCount = 0 -} - struct CodeMapSyntaxPerfStats { var languageLookupDuration: TimeInterval = 0 var oversizeGuardDuration: TimeInterval = 0 @@ -64,15 +41,6 @@ struct CodeMapPipelinePerfSnapshot: Equatable { var parseAndQueryDuration: TimeInterval = 0 var generatorDuration: TimeInterval = 0 var batchApplyDuration: TimeInterval = 0 - var syntaxManagerPrimeDuration: TimeInterval = 0 - var syntaxWarmCacheDuration: TimeInterval = 0 - var syntaxWarmCodeMapQueriesDuration: TimeInterval = 0 - var syntaxLanguageConfigCreateDuration: TimeInterval = 0 - var syntaxLanguagePointerDuration: TimeInterval = 0 - var syntaxHighlightQueryDataDuration: TimeInterval = 0 - var syntaxHighlightQueryCompileDuration: TimeInterval = 0 - var syntaxCodeMapQueryDataDuration: TimeInterval = 0 - var syntaxCodeMapQueryCompileDuration: TimeInterval = 0 var syntaxLanguageLookupDuration: TimeInterval = 0 var syntaxOversizeGuardDuration: TimeInterval = 0 var syntaxParserCreateDuration: TimeInterval = 0 @@ -142,16 +110,6 @@ struct CodeMapPipelinePerfSnapshot: Equatable { var generatedAPIs = 0 var nilAPIs = 0 var codeMapQuerySuccessfulLookups = 0 - var syntaxWarmCacheLanguageCount = 0 - var syntaxLanguageConfigCreateCount = 0 - var syntaxLanguageConfigSuccessCount = 0 - var syntaxLanguageConfigFailureCount = 0 - var syntaxHighlightQueryCompileSuccessCount = 0 - var syntaxHighlightQueryCompileFailureCount = 0 - var syntaxWarmCodeMapQueryLanguageCount = 0 - var syntaxCodeMapQueryPrecomputeSuccessCount = 0 - var syntaxCodeMapQueryPrecomputeFailureCount = 0 - var syntaxCodeMapQueryPrecomputeSkippedCount = 0 var syntaxCodeMapCalls = 0 var syntaxUnsupportedExtensionCount = 0 var syntaxOversizedSkipCount = 0 @@ -266,31 +224,6 @@ final class CodeMapPipelinePerfStats: @unchecked Sendable { } } - func mergeSyntaxManagerStartupStats(_ stats: CodeMapSyntaxStartupPerfStats) { - lock.withLock { - storage.syntaxManagerPrimeDuration += stats.primeDuration - storage.syntaxWarmCacheDuration += stats.warmCacheDuration - storage.syntaxWarmCodeMapQueriesDuration += stats.warmCodeMapQueriesDuration - storage.syntaxLanguageConfigCreateDuration += stats.languageConfigCreateDuration - storage.syntaxLanguagePointerDuration += stats.languagePointerDuration - storage.syntaxHighlightQueryDataDuration += stats.highlightQueryDataDuration - storage.syntaxHighlightQueryCompileDuration += stats.highlightQueryCompileDuration - storage.syntaxCodeMapQueryDataDuration += stats.codeMapQueryDataDuration - storage.syntaxCodeMapQueryCompileDuration += stats.codeMapQueryCompileDuration - - storage.syntaxWarmCacheLanguageCount += stats.warmCacheLanguageCount - storage.syntaxLanguageConfigCreateCount += stats.languageConfigCreateCount - storage.syntaxLanguageConfigSuccessCount += stats.languageConfigSuccessCount - storage.syntaxLanguageConfigFailureCount += stats.languageConfigFailureCount - storage.syntaxHighlightQueryCompileSuccessCount += stats.highlightQueryCompileSuccessCount - storage.syntaxHighlightQueryCompileFailureCount += stats.highlightQueryCompileFailureCount - storage.syntaxWarmCodeMapQueryLanguageCount += stats.warmCodeMapQueryLanguageCount - storage.syntaxCodeMapQueryPrecomputeSuccessCount += stats.codeMapQueryPrecomputeSuccessCount - storage.syntaxCodeMapQueryPrecomputeFailureCount += stats.codeMapQueryPrecomputeFailureCount - storage.syntaxCodeMapQueryPrecomputeSkippedCount += stats.codeMapQueryPrecomputeSkippedCount - } - } - func mergeSyntaxCodeMapStats(_ stats: CodeMapSyntaxPerfStats) { lock.withLock { storage.syntaxLanguageLookupDuration += stats.languageLookupDuration diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BelievableCodeFactory.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BelievableCodeFactory.swift deleted file mode 100644 index c0fc8d0b5..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BelievableCodeFactory.swift +++ /dev/null @@ -1,663 +0,0 @@ -import Foundation - -/// Generates deterministic, believable code for TS/Go/Swift using Mulberry32 RNG. -/// Used by the benchmark generator to replace filler lines and to add decoys. -enum BelievableCodeFactory { - // MARK: - Public entry points - - static func tsUtilityModule(rng: inout Mulberry32, module: String? = nil, approxLines: Int = 100) -> String { - let mod = module ?? "Util\(rng.nextInt(upperBound: 10000))" - var out: [String] = [] - out.append("/* auto-generated believable TS module */") - out.append("export namespace \(mod) {") - out.append(" export interface Pair { left: T; right: U }") - out.append(" export type Nullable = T | null;") - out.append("") - out.append(" export class Counter {") - out.append(" private n: number") - out.append(" constructor(n: number = 0) { this.n = n }") - out.append(" inc(): number { this.n++; return this.n }") - out.append(" add(d: number): number { this.n += d; return this.n }") - out.append(" value(): number { return this.n }") - out.append(" }") - out.append("") - out.append(" export function clamp(n: number, min = 0, max = 100): number {") - out.append(" return Math.min(Math.max(n, min), max)") - out.append(" }") - out.append("") - out.append(" export function toPairs(xs: string[]): Pair[] {") - out.append(" const res: Pair[] = [];") - out.append(" let i = 0;") - out.append(" for (const x of xs) { res.push({ left: x, right: i++ }) }") - out.append(" return res;") - out.append(" }") - out.append("") - out.append(" export function sum(ns: number[]): number {") - out.append(" let s = 0;") - out.append(" for (const n of ns) s += n;") - out.append(" return s;") - out.append(" }") - out.append("") - - let fnNames = ["format", "uniq", "flatten", "chunked", "pad", "slug", "join", "take", "drop"] - let words = ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf", "hotel", "india"] - var total = out.count - while total < approxLines { - let idx = rng.nextInt(upperBound: fnNames.count) - let w1 = words[rng.nextInt(upperBound: words.count)] - let w2 = words[rng.nextInt(upperBound: words.count)] - switch idx { - case 0: - out.append(" export function \(fnNames[idx])(s: string): string { return `[\(w1)] \(w2): ${'$'}{s}` }") - case 1: - out.append(" export function \(fnNames[idx])(xs: T[]): T[] { return Array.from(new Set(xs)) }") - case 2: - out.append(" export function \(fnNames[idx])(xss: T[][]): T[] { return xss.reduce((a, b) => a.concat(b), []) }") - case 3: - out.append(" export function \(fnNames[idx])(xs: T[], n = 2): T[][] {") - out.append(" const out: T[][] = [];") - out.append(" for (let i = 0; i < xs.length; i += n) out.push(xs.slice(i, i + n));") - out.append(" return out;") - out.append(" }") - total += 3 - case 4: - out.append(" export function \(fnNames[idx])(s: string, n = 2): string { return s.padStart(s.length + n, ' ') }") - case 5: - out.append(" export function \(fnNames[idx])(s: string): string { return s.toLowerCase().replace(/\\W+/g, '-') }") - case 6: - out.append(" export function \(fnNames[idx])(xs: string, sep = ','): string[] { return xs.split(sep) }") - case 7: - out.append(" export function \(fnNames[idx])(xs: T[], n: number): T[] { return xs.slice(0, n) }") - default: - out.append(" export function \(fnNames[idx])(xs: T[], n: number): T[] { return xs.slice(n) }") - } - total += 1 - } - out.append("}") - return out.joined(separator: "\n") - } - - static func goUtilityModule(rng: inout Mulberry32, pkg: String = "util", approxLines: Int = 80) -> String { - var out: [String] = [] - out.append("// auto-generated believable Go package") - out.append("package \(pkg)") - out.append("") - out.append("import (") - out.append(" \"fmt\"") - out.append(" \"strings\"") - out.append(")") - out.append("") - out.append("func Clamp(n, min, max int) int {") - out.append(" if n < min { return min }") - out.append(" if n > max { return max }") - out.append(" return n") - out.append("}") - out.append("") - out.append("func Sum(xs []int) int { s := 0; for _, v := range xs { s += v }; return s }") - out.append("func Join(xs []string, sep string) string { return strings.Join(xs, sep) }") - out.append("func Debug(v any) { fmt.Println(v) }") - out.append("") - let extras = [ - "func Map[T any, U any](xs []T, f func(T) U) []U { ys := make([]U, len(xs)); for i, v := range xs { ys[i] = f(v) }; return ys }", - "func Filter[T any](xs []T, f func(T) bool) []T { ys := make([]T, 0, len(xs)); for _, v := range xs { if f(v) { ys = append(ys, v) } }; return ys }", - "func Repeat(s string, n int) string { return strings.Repeat(s, n) }", - "func Pairwise(xs []int, f func(int, int) int) []int { if len(xs) < 2 { return nil }; ys := make([]int, 0, len(xs)-1); for i := 0; i < len(xs)-1; i++ { ys = append(ys, f(xs[i], xs[i+1])) }; return ys }", - "func Contains[T comparable](xs []T, needle T) bool { for _, v := range xs { if v == needle { return true } }; return false }" - ] - var total = out.count - while total < approxLines { - out.append(extras[rng.nextInt(upperBound: extras.count)]) - total += 1 - } - return out.joined(separator: "\n") - } - - // MARK: - Decoys - - static func tsDecoyFile(rng: inout Mulberry32, name: String) -> (path: String, content: String) { - let path = "src/ts/decoy/\(name).ts" - let mod = "Decoy\(name)" - var content = tsUtilityModule(rng: &rng, module: mod, approxLines: 60) - // Append plausible code without standardized markers - content += """ - - export function use(a: string, b: string): string { return a + b; } - - export function block2(n: number): number { - // alternative implementation - const t = n * 3 - return t - } - - export function render(list: string[]): string { - let out = "" - for (let i = 0; i < 2; i++) { - out += use("a" + i, "b" + i) - } - return out - } - """ - return (path, content) - } - - static func goDecoyFile(rng: inout Mulberry32, name: String) -> (path: String, content: String) { - let path = "src/go/decoy/\(name).go" - return (path, goUtilityModule(rng: &rng, pkg: "decoy\(name)", approxLines: 50)) - } - - // MARK: - Swift - - static func swiftUtilityModule(rng: inout Mulberry32, module: String = "Utils", approxLines: Int = 80) -> String { - let mod = module - var out: [String] = [] - out.append("/* auto-generated believable Swift module */") - out.append("public enum \(mod) {") - out.append("\tpublic struct Pair { public let left: T; public let right: U }") - out.append("") - out.append("\tpublic final class Counter {") - out.append("\t\tprivate var n: Int") - out.append("\t\tpublic init(_ n: Int = 0) { self.n = n }") - out.append("\t\tpublic func inc() -> Int { n += 1; return n }") - out.append("\t\tpublic func add(_ d: Int) -> Int { n += d; return n }") - out.append("\t\tpublic var value: Int { n }") - out.append("\t}") - out.append("") - out.append("\tpublic static func clamp(_ n: Int, min: Int = 0, max: Int = 100) -> Int {") - out.append("\t\treturn Swift.min(Swift.max(n, min), max)") - out.append("\t}") - out.append("") - out.append("\tpublic static func sum(_ xs: [Int]) -> Int { xs.reduce(0, +) }") - out.append("}") - out.append("") - let words = ["alpha", "bravo", "charlie", "delta", "echo", "foxtrot", "golf"] - while out.count < approxLines { - let w1 = words[rng.nextInt(upperBound: words.count)] - let w2 = words[rng.nextInt(upperBound: words.count)] - out.append("public func \(w1)_\(w2)(_ s: String) -> String { \"[\(w1)] \(w2): \\(s)\" }") - } - return out.joined(separator: "\n") - } - - static func swiftDecoyFile(rng: inout Mulberry32, name: String) -> (path: String, content: String) { - let path = "src/swift/decoy/\(name).swift" - var content = swiftUtilityModule(rng: &rng, module: "Decoy\(name)", approxLines: 60) - // Append plausible code without standardized markers - content += """ - - public func use(_ a: String, _ b: String) -> String { a + b } - - public func block2(_ n: Int) -> Int { - // alternative implementation - let t = n * 3 - return t - } - - public func render(_ list: [String]) -> String { - var out = "" - for i in 0..<2 { - out += use("a\\(i)", "b\\(i)") - } - return out - } - """ - return (path, content) - } - - // MARK: - Complex insert_guard hardened generators - - /// Generates a complex TypeScript clamp function family with nested code and near-duplicates. - /// Creates ambiguous search contexts to force models to use larger search blocks. - static func tsClampFamilyComplex( - rng: inout Mulberry32, - mainName: String = "clamp", - anchorUID: String? = nil, - decoyAnchorUIDs: [String] = [], - nearMissFunctions: Int = 4, - inFunctionShadowClusters: Int = 2 - ) -> String { - var out: [String] = [] - - // Main clamp function with complex nested structure - out.append("export function \(mainName)(n: number): number {") - out.append(" const limit = 100") - out.append("") - - // Cluster A - main insertion point - if let uid = anchorUID { - out.append(" // ANCHOR:start:\(uid)") - out.append(" const normalized = Math.abs(n)") - out.append(" // ANCHOR:end:\(uid)") - } else { - out.append(" const normalized = Math.abs(n)") - } - - // Add nested complexity with repeated normalized patterns - out.append(" if (normalized > limit) {") - out.append(" const normalized = normalized - 1 // shadowed normalized") - out.append(" return normalized") - out.append(" }") - out.append("") - - // Cluster B - similar pattern - out.append(" // Processing loop with normalized") - out.append(" for (let i = 0; i < 2; i++) {") - out.append(" const normalized = Math.abs(n + i)") - out.append(" if (normalized > limit) break") - out.append(" }") - out.append("") - - // Add shadow clusters within function if requested - for i in 0 ..< inFunctionShadowClusters { - out.append(" // Validation cluster \(i + 1)") - out.append(" const normalized\(i + 1) = Math.abs(n * \(i + 2))") - out.append(" if (normalized\(i + 1) > limit) {") - out.append(" const normalized = normalized\(i + 1) // another normalized reference") - out.append(" }") - out.append("") - } - - // Nested helper function - out.append(" function adjust(n: number): number {") - out.append(" const normalized = Math.abs(n) // nested normalized") - out.append(" return normalized") - out.append(" }") - out.append("") - out.append(" return Math.min(Math.abs(n), limit)") - out.append("}") - out.append("") - - // Generate near-miss functions - let nearMissNames = ["clampStrict", "clampBounded", "clampPrime", "clampNormalized", "clampNormalized2", "clampSafe"] - for i in 0 ..< min(nearMissFunctions, nearMissNames.count) { - let fnName = nearMissNames[i] - out.append("export function \(fnName)(n: number): number {") - out.append(" const limit = 100") - - // Add decoy anchor if available - if i < decoyAnchorUIDs.count { - out.append(" // ANCHOR:start:\(decoyAnchorUIDs[i])") - out.append(" const normalized = Math.abs(n)") - out.append(" // ANCHOR:end:\(decoyAnchorUIDs[i])") - } else { - out.append(" const normalized = Math.abs(n)") - } - - out.append(" if (normalized > limit) {") - out.append(" const normalized = normalized - 1") - out.append(" return normalized") - out.append(" }") - out.append(" return Math.min(normalized, limit)") - out.append("}") - out.append("") - } - - // Add shadow cluster at EOF if requested - if inFunctionShadowClusters > 0 { - out.append("// SHADOW START (do not edit)") - out.append("const normalized = Math.abs(42)") - out.append("// SHADOW END (do not edit)") - } - - return out.joined(separator: "\n") - } - - /// Generates a complex Go Clamp function family with nested code and near-duplicates. - static func goClampFamilyComplex( - rng: inout Mulberry32, - mainName: String = "Clamp", - anchorUID: String? = nil, - decoyAnchorUIDs: [String] = [], - nearMissFunctions: Int = 4, - inFunctionShadowClusters: Int = 2 - ) -> String { - var out: [String] = [] - - // Main Clamp function with complex nested structure - out.append("func \(mainName)(n int) int {") - out.append(" limit := 100") - out.append("") - - // Cluster A - main insertion point - if let uid = anchorUID { - out.append(" // ANCHOR:start:\(uid)") - out.append(" normalized := n") - out.append(" if normalized < 0 {") - out.append(" normalized = -normalized") - out.append(" }") - out.append(" // ANCHOR:end:\(uid)") - } else { - out.append(" normalized := n") - out.append(" if normalized < 0 {") - out.append(" normalized = -normalized") - out.append(" }") - } - - // Add nested complexity - out.append(" if normalized > limit {") - out.append(" normalized := normalized - 1 // shadowed normalized") - out.append(" return normalized") - out.append(" }") - out.append("") - - // Cluster B - similar pattern - out.append(" // Processing loop with normalized") - out.append(" for i := 0; i < 2; i++ {") - out.append(" normalized := n + i") - out.append(" if normalized < 0 {") - out.append(" normalized = -normalized") - out.append(" }") - out.append(" if normalized > limit {") - out.append(" break") - out.append(" }") - out.append(" }") - out.append("") - - // Add shadow clusters within function - for i in 0 ..< inFunctionShadowClusters { - out.append(" // Validation cluster \(i + 1)") - out.append(" normalized\(i + 1) := n * \(i + 2)") - out.append(" if normalized\(i + 1) < 0 {") - out.append(" normalized\(i + 1) = -normalized\(i + 1)") - out.append(" }") - out.append(" if normalized\(i + 1) > limit {") - out.append(" normalized := normalized\(i + 1) // another normalized reference") - out.append(" _ = normalized") - out.append(" }") - out.append("") - } - - // Nested helper function - out.append(" adjust := func(n int) int {") - out.append(" normalized := n // nested normalized") - out.append(" if normalized < 0 {") - out.append(" normalized = -normalized") - out.append(" }") - out.append(" return normalized") - out.append(" }") - out.append(" _ = adjust") - out.append("") - out.append(" if n < 0 {") - out.append(" return 0") - out.append(" }") - out.append(" if n > limit {") - out.append(" return limit") - out.append(" }") - out.append(" return n") - out.append("}") - out.append("") - - // Generate near-miss functions - let nearMissNames = ["ClampStrict", "ClampBounded", "ClampPrime", "ClampNormalized", "ClampNormalized2", "ClampSafe"] - for i in 0 ..< min(nearMissFunctions, nearMissNames.count) { - let fnName = nearMissNames[i] - out.append("func \(fnName)(n int) int {") - out.append(" limit := 100") - - // Add decoy anchor if available - if i < decoyAnchorUIDs.count { - out.append(" // ANCHOR:start:\(decoyAnchorUIDs[i])") - out.append(" normalized := n") - out.append(" if normalized < 0 {") - out.append(" normalized = -normalized") - out.append(" }") - out.append(" // ANCHOR:end:\(decoyAnchorUIDs[i])") - } else { - out.append(" normalized := n") - out.append(" if normalized < 0 {") - out.append(" normalized = -normalized") - out.append(" }") - } - - out.append(" if normalized > limit {") - out.append(" normalized := normalized - 1") - out.append(" return normalized") - out.append(" }") - out.append(" if normalized < 0 {") - out.append(" return 0") - out.append(" }") - out.append(" return normalized") - out.append("}") - out.append("") - } - - // Add shadow cluster at EOF if requested - if inFunctionShadowClusters > 0 { - out.append("// SHADOW START (do not edit)") - out.append("var normalized = 42") - out.append("// SHADOW END (do not edit)") - } - - return out.joined(separator: "\n") - } - - /// Generates a complex Swift clamp function family with nested code and near-duplicates. - /// Uses tabs for indentation. - static func swiftClampFamilyComplex( - rng: inout Mulberry32, - mainName: String = "clamp", - anchorUID: String? = nil, - decoyAnchorUIDs: [String] = [], - nearMissFunctions: Int = 4, - inFunctionShadowClusters: Int = 2 - ) -> String { - var out: [String] = [] - - // Main clamp function with complex nested structure - out.append("public func \(mainName)(_ n: Int) -> Int {") - out.append("\tlet limit = 100") - out.append("") - - // Cluster A - main insertion point - if let uid = anchorUID { - out.append("\t// ANCHOR:start:\(uid)") - out.append("\tlet normalized = abs(n)") - out.append("\t// ANCHOR:end:\(uid)") - } else { - out.append("\tlet normalized = abs(n)") - } - - // Add nested complexity - out.append("\tif normalized > limit {") - out.append("\t\tlet normalized = normalized - 1 // shadowed normalized") - out.append("\t\treturn normalized") - out.append("\t}") - out.append("") - - // Cluster B - similar pattern - out.append("\t// Processing loop with normalized") - out.append("\tfor i in 0..<2 {") - out.append("\t\tlet normalized = abs(n + i)") - out.append("\t\tif normalized > limit { break }") - out.append("\t}") - out.append("") - - // Add shadow clusters within function - for i in 0 ..< inFunctionShadowClusters { - out.append("\t// Validation cluster \(i + 1)") - out.append("\tlet normalized\(i + 1) = abs(n * \(i + 2))") - out.append("\tif normalized\(i + 1) > limit {") - out.append("\t\tlet normalized = normalized\(i + 1) // another normalized reference") - out.append("\t\t_ = normalized") - out.append("\t}") - out.append("") - } - - // Nested helper function - out.append("\tfunc adjust(_ n: Int) -> Int {") - out.append("\t\tlet normalized = abs(n) // nested normalized") - out.append("\t\treturn normalized") - out.append("\t}") - out.append("\t_ = adjust") - out.append("") - out.append("\treturn min(abs(n), limit)") - out.append("}") - out.append("") - - // Generate near-miss functions - let nearMissNames = ["clampStrict", "clampBounded", "clampPrime", "clampNormalized", "clampNormalized2", "clampSafe"] - for i in 0 ..< min(nearMissFunctions, nearMissNames.count) { - let fnName = nearMissNames[i] - out.append("public func \(fnName)(_ n: Int) -> Int {") - out.append("\tlet limit = 100") - - // Add decoy anchor if available - if i < decoyAnchorUIDs.count { - out.append("\t// ANCHOR:start:\(decoyAnchorUIDs[i])") - out.append("\tlet normalized = abs(n)") - out.append("\t// ANCHOR:end:\(decoyAnchorUIDs[i])") - } else { - out.append("\tlet normalized = abs(n)") - } - - out.append("\tif normalized > limit {") - out.append("\t\tlet normalized = normalized - 1") - out.append("\t\treturn normalized") - out.append("\t}") - out.append("\treturn min(normalized, limit)") - out.append("}") - out.append("") - } - - // Add shadow cluster at EOF if requested - if inFunctionShadowClusters > 0 { - out.append("// SHADOW START (do not edit)") - out.append("let normalized = abs(42)") - out.append("// SHADOW END (do not edit)") - } - - return out.joined(separator: "\n") - } - - // MARK: - Curly decoy generators for curly_fix hardening - - static func tsCurlyDecoy(rng: inout Mulberry32, name: String, approxLines: Int = 60) -> (path: String, content: String) { - let path = "src/ts/decoy/BraceMaze_\(name).ts" - var out: [String] = [] - out.append("// Decoy file with brace noise") - out.append("export function demo(xs: string[]): number {") - out.append(" let sum = 0") - out.append(" // Brace noise: }") - out.append(" const braceLiteral = \"}\"") - out.append(" for (let i = 0; i < xs.length; i++) {") - out.append(" if (xs[i].length > 0) {") - out.append(" sum += xs[i].length") - out.append(" }") - out.append(" }") - out.append(" return sum") - out.append("}") - out.append("") - out.append("export function balanced(n: number): number {") - out.append(" // Comment with brace: }") - out.append(" const braceStr = \"}\"") - out.append(" for (let i = 0; i < n; i++) {") - out.append(" const temp = i * 2 // another brace: }") - out.append(" if (temp > 10) {") - out.append(" return temp") - out.append(" }") - out.append(" }") - out.append(" return 0") - out.append("}") - out.append("") - out.append("// More brace noise: } } }") - out.append("const bracesInString = \"{ } { }\"") - - // Pad with additional noise if needed - let words = ["alpha", "bravo", "charlie", "delta", "echo"] - while out.count < approxLines { - let w = words[rng.nextInt(upperBound: words.count)] - out.append("export function \(w)\(rng.nextInt(upperBound: 100))(x: number): number { return x * 2 }") - } - - return (path, out.joined(separator: "\n")) - } - - static func goCurlyDecoy(rng: inout Mulberry32, name: String, approxLines: Int = 60) -> (path: String, content: String) { - let path = "src/go/decoy/BraceMaze_\(name).go" - var out: [String] = [] - out.append("// Decoy file with brace noise") - out.append("package decoy") - out.append("") - out.append("import \"fmt\"") - out.append("") - out.append("func Demo(xs []string) int {") - out.append(" sum := 0") - out.append(" // Brace noise: }") - out.append(" braceLiteral := \"}\"") - out.append(" for i := 0; i < len(xs); i++ {") - out.append(" if len(xs[i]) > 0 {") - out.append(" sum += len(xs[i])") - out.append(" }") - out.append(" }") - out.append(" _ = braceLiteral") - out.append(" return sum") - out.append("}") - out.append("") - out.append("func Balanced(n int) int {") - out.append(" // Comment with brace: }") - out.append(" braceStr := \"}\"") - out.append(" for i := 0; i < n; i++ {") - out.append(" temp := i * 2 // another brace: }") - out.append(" if temp > 10 {") - out.append(" fmt.Println(braceStr)") - out.append(" return temp") - out.append(" }") - out.append(" }") - out.append(" return 0") - out.append("}") - out.append("") - out.append("// More brace noise: } } }") - out.append("var bracesInString = \"{ } { }\"") - - // Pad with additional noise if needed - let words = ["Alpha", "Bravo", "Charlie", "Delta", "Echo"] - while out.count < approxLines { - let w = words[rng.nextInt(upperBound: words.count)] - out.append("func \(w)\(rng.nextInt(upperBound: 100))(x int) int { return x * 2 }") - } - - return (path, out.joined(separator: "\n")) - } - - static func swiftCurlyDecoy(rng: inout Mulberry32, name: String, approxLines: Int = 60) -> (path: String, content: String) { - let path = "src/swift/decoy/BraceMaze_\(name).swift" - var out: [String] = [] - out.append("// Decoy file with brace noise") - out.append("public func demo(_ xs: [String]) -> Int {") - out.append("\tvar sum = 0") - out.append("\t// Brace noise: }") - out.append("\tlet braceLiteral = \"}\"") - out.append("\tfor i in 0.. 0 {") - out.append("\t\t\tsum += xs[i].count") - out.append("\t\t}") - out.append("\t}") - out.append("\t_ = braceLiteral") - out.append("\treturn sum") - out.append("}") - out.append("") - out.append("public func balanced(_ n: Int) -> Int {") - out.append("\t// Comment with brace: }") - out.append("\tlet braceStr = \"}\"") - out.append("\tfor i in 0.. 10 {") - out.append("\t\t\tprint(braceStr)") - out.append("\t\t\treturn temp") - out.append("\t\t}") - out.append("\t}") - out.append("\treturn 0") - out.append("}") - out.append("") - out.append("// More brace noise: } } }") - out.append("let bracesInString = \"{ } { }\"") - - // Pad with additional noise if needed - let words = ["alpha", "bravo", "charlie", "delta", "echo"] - while out.count < approxLines { - let w = words[rng.nextInt(upperBound: words.count)] - out.append("public func \(w)\(rng.nextInt(upperBound: 100))(_ x: Int) -> Int { x * 2 }") - } - - return (path, out.joined(separator: "\n")) - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkAuditor.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkAuditor.swift deleted file mode 100644 index 238ea6175..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkAuditor.swift +++ /dev/null @@ -1,620 +0,0 @@ -import Foundation - -struct BenchmarkAuditIssue { - enum Severity: String { case info, warning, error } - let severity: Severity - let code: String - let message: String - let path: String? - let metrics: [String: BenchmarkJSONValue] -} - -struct BenchmarkSearchHint { - let path: String - let search: [String] - let replacement: [String] - let reason: String -} - -struct BenchmarkAuditResult { - let spec: BenchmarkTaskSpec - let issues: [BenchmarkAuditIssue] - let hints: [BenchmarkSearchHint] - let solvable: Bool -} - -struct BenchmarkAuditor { - func auditSeed(_ generated: BenchmarkGeneratedSeed) async -> [BenchmarkAuditResult] { - await withTaskGroup(of: BenchmarkAuditResult.self) { group in - let snapshot = generated.fileSystem.snapshot() - for task in generated.tasks { - group.addTask { - await auditTask(task, on: snapshot) - } - } - var results: [BenchmarkAuditResult] = [] - for await result in group { - results.append(result) - } - return results - } - } - - func auditTask(_ spec: BenchmarkTaskSpec, on fs: BenchmarkMockFileSystemSnapshot) async -> BenchmarkAuditResult { - var issues: [BenchmarkAuditIssue] = [] - var hints: [BenchmarkSearchHint] = [] - var simulatedPass = false - let missing = spec.selectFiles.filter { fs.content(for: $0) == nil } - if !missing.isEmpty { - issues.append(.err(code: "missingFile", msg: "Selected file(s) not present in baseline: \(missing.joined(separator: ", "))", path: nil)) - return BenchmarkAuditResult(spec: spec, issues: issues, hints: [], solvable: false) - } - switch spec.type { - case .insertGuardTs, .insertGuardGo, .insertGuardSwift: - let outcome = await auditInsertGuard(spec, fs: fs) - issues.append(contentsOf: outcome.issues) - hints.append(contentsOf: outcome.hints) - simulatedPass = outcome.simulatedPass - case .patchBlockTs, .patchBlockGo, .patchBlockSwift: - let outcome = await auditPatchBlock(spec, fs: fs) - issues.append(contentsOf: outcome.issues) - hints.append(contentsOf: outcome.hints) - simulatedPass = outcome.simulatedPass - case .swapArgsInRegionTs, .swapArgsInRegionGo, .swapArgsInRegionSwift: - if spec.params["markerless"]?.boolValue == true { - let outcome = await auditSwapArgsMarkerless(spec, fs: fs) - issues.append(contentsOf: outcome.issues) - hints.append(contentsOf: outcome.hints) - simulatedPass = outcome.simulatedPass - } else { - let outcome = await auditGeneric(spec, fs: fs) - issues.append(contentsOf: outcome.issues) - hints.append(contentsOf: outcome.hints) - simulatedPass = outcome.simulatedPass - } - case .removeXTs, .removeXGo, .removeXSwift, - .curlyFixTs, .curlyFixGo, .curlyFixSwift, - .indexOnlyAppsTs, .indexOnlyAppsGo, .indexOnlyAppsSwift, - .renameExportImportsTs, .renameExportImportsGo, .renameExportImportsSwift, - .moveFunctionTs, .moveFunctionGo, .moveFunctionSwift, - .insertFunctionBottomTs, .insertFunctionBottomGo, .insertFunctionBottomSwift, - .applyUnifiedPatchTs, .applyUnifiedPatchGo, .applyUnifiedPatchSwift: - let outcome = await auditGeneric(spec, fs: fs) - issues.append(contentsOf: outcome.issues) - hints.append(contentsOf: outcome.hints) - simulatedPass = outcome.simulatedPass - } - if spec.type == .removeXTs, - let path = spec.params["file"]?.stringValue, - let text = fs.content(for: path), - let target = spec.params["target"]?.stringValue - { - let count = max(0, text.components(separatedBy: target).count - 1) - if count > spec.maxEdits { - issues.append(.warn( - code: "maxEditsLikelyInsufficient", - msg: "Will likely need \(count) edits but maxEdits=\(spec.maxEdits).", - path: path, - metrics: ["needed": .integer(count)] - )) - } - } - return BenchmarkAuditResult(spec: spec, issues: issues, hints: hints, solvable: simulatedPass) - } - - private func auditInsertGuardMarkerless(_ spec: BenchmarkTaskSpec, fs: BenchmarkMockFileSystemSnapshot) async -> (issues: [BenchmarkAuditIssue], hints: [BenchmarkSearchHint], simulatedPass: Bool) { - var issues: [BenchmarkAuditIssue] = [] - var hints: [BenchmarkSearchHint] = [] - - guard - let functionName = spec.params["functionName"]?.stringValue, - let insertAfterPattern = spec.params["insertAfterPattern"]?.stringValue, - let snippet = spec.params["snippet"]?.stringValue, - let path = spec.selectFiles.first, - let text = fs.content(for: path) - else { - return ([.err(code: "missingParams", msg: "functionName/insertAfterPattern/snippet/or file missing", path: nil)], [], false) - } - - // Use DecoyPlanner's core location logic to find the insertion region - guard let core = DecoyPlanner.locateCore(for: spec, in: text, path: path) else { - issues.append(.err( - code: "functionOrPatternNotFound", - msg: "Could not locate function '\(functionName)' or pattern '\(insertAfterPattern)' in \(path)", - path: path - )) - return (issues, hints, false) - } - - // Build search block: should include function signature, pattern line, and at least one more line - let lines = text.split(separator: "\n", omittingEmptySubsequences: false).map { String($0) } - - // Find function signature line (should be before core) - var sigLine: String? - for i in stride(from: max(0, core.startLine - 1), through: 0, by: -1) { - let line = lines[i] - if line.contains("function \(functionName)(") || line.contains("func \(functionName)(") { - sigLine = line - break - } - } - - // Build recommended search block: sig + core lines (5-8 total recommended) - var searchLines: [String] = [] - if let sig = sigLine { - searchLines.append(sig) - } - searchLines.append(contentsOf: core.lines) - - // Count pattern occurrences to detect ambiguity - let patternOccurrencesInFile = text.components(separatedBy: insertAfterPattern).count - 1 - - // Count occurrences in the function body (approximate: core region +/- 10 lines) - let funcStart = max(0, core.startLine - 10) - let funcEnd = min(lines.count - 1, core.endLine + 10) - let funcBody = lines[funcStart ... funcEnd].joined(separator: "\n") - let patternOccurrencesInFunction = funcBody.components(separatedBy: insertAfterPattern).count - 1 - - // Check if search is too short (< 5 lines = likely ambiguous with complex code) - if searchLines.count < 5 { - issues.append(.warn( - code: "searchBlockTooShort", - msg: "Recommended search block has only \(searchLines.count) lines; strong ambiguity risk. Include signature + multiple body lines (5-8 lines).", - path: path, - metrics: [ - "searchLineCount": .integer(searchLines.count), - "patternOccurrencesInFile": .integer(patternOccurrencesInFile), - "patternOccurrencesInFunction": .integer(patternOccurrencesInFunction) - ] - )) - } - - // Warn if pattern appears multiple times within the function - if patternOccurrencesInFunction >= 2 { - issues.append(.warn( - code: "multiplePatternMatches", - msg: "Pattern '\(insertAfterPattern)' appears \(patternOccurrencesInFunction) times within target function; short search blocks likely ambiguous.", - path: path, - metrics: [ - "patternOccurrencesInFunction": .integer(patternOccurrencesInFunction), - "patternOccurrencesInFile": .integer(patternOccurrencesInFile) - ] - )) - } - - // Check indentation - let (_, lineEnding) = detectIndentInfo(in: text) - let usesTabIndentation = spec.language.usesTabIndentation - - if usesTabIndentation, snippet.contains(" ") { - issues.append(.warn( - code: "indentationMismatch", - msg: "Swift requires tabs, but snippet contains 4 spaces.", - path: path - )) - } else if !usesTabIndentation, snippet.contains("\t") { - issues.append(.err( - code: "snippetHasTabs", - msg: "TS/Go requires 4 spaces, but snippet contains tabs.", - path: path - )) - } - - // Build replacement: insert snippet after pattern line - let patternLineIdx = core.lines.firstIndex(where: { $0.contains(insertAfterPattern) }) ?? 0 - var replacementLines = core.lines - replacementLines.insert(snippet, at: patternLineIdx + 1) - - // Create hint - hints.append(BenchmarkSearchHint( - path: path, - search: searchLines, - replacement: replacementLines, - reason: "Insert snippet after '\(insertAfterPattern)' line inside \(functionName)() function. Include signature and 5-8 body lines in search to avoid ambiguity." - )) - - // Simulate - let simulated = await simulateOneModify( - path: path, - search: searchLines, - replacement: replacementLines, - fs: fs, - lineEnding: lineEnding, - caseType: spec.type - ) - issues.append(contentsOf: simulated.issues) - - return (issues, hints, simulated.pass) - } - - private func auditPatchBlockMarkerless(_ spec: BenchmarkTaskSpec, fs: BenchmarkMockFileSystemSnapshot) async -> (issues: [BenchmarkAuditIssue], hints: [BenchmarkSearchHint], simulatedPass: Bool) { - guard let path = spec.selectFiles.first, - let text = fs.content(for: path), - let functionName = spec.params["functionName"]?.stringValue, - let snippet = spec.params["snippet"]?.stringValue - else { - return ([.err(code: "missingParams", msg: "Missing params", path: nil)], [], false) - } - - // Use DecoyPlanner to locate the function - let coreRegion = DecoyPlanner.locateCore(for: spec, in: text, path: path) - guard let core = coreRegion else { - return ([.err(code: "functionNotFound", msg: "Function '\(functionName)' not found", path: path)], [], false) - } - - var issues: [BenchmarkAuditIssue] = [] - let lines = text.split(separator: "\n", omittingEmptySubsequences: false).map(String.init) - - // Build recommended search block: signature + body line + closing brace - let sigLine = core.startLine - 1 - let searchLines = Array(lines[max(0, sigLine) ... min(lines.count - 1, core.endLine + 1)]) - - if searchLines.count < 3 { - issues.append(.warn(code: "shortSearch", msg: "Search block < 3 lines may be ambiguous", path: path)) - } - - let (_, lineEnding) = detectIndentInfo(in: text) - - // Simulate the edit - let hint = BenchmarkSearchHint( - path: path, - search: searchLines, - replacement: [snippet], - reason: "Replace function '\(functionName)' body with snippet" - ) - - let (simIssues, simPass) = await simulateOneModify( - path: path, - search: searchLines, - replacement: [snippet], - fs: fs, - lineEnding: lineEnding, - caseType: spec.type - ) - issues.append(contentsOf: simIssues) - - return (issues, [hint], simPass) - } - - private func auditSwapArgsMarkerless(_ spec: BenchmarkTaskSpec, fs: BenchmarkMockFileSystemSnapshot) async -> (issues: [BenchmarkAuditIssue], hints: [BenchmarkSearchHint], simulatedPass: Bool) { - guard let path = spec.selectFiles.first, - let text = fs.content(for: path), - let functionName = spec.params["functionName"]?.stringValue - else { - return ([.err(code: "missingParams", msg: "Missing params", path: nil)], [], false) - } - - // Use DecoyPlanner to locate the core region containing use() calls - let coreRegion = DecoyPlanner.locateCore(for: spec, in: text, path: path) - guard let core = coreRegion else { - return ([.err(code: "regionNotFound", msg: "Function '\(functionName)' or use() calls not found", path: path)], [], false) - } - - var issues: [BenchmarkAuditIssue] = [] - let lines = text.split(separator: "\n", omittingEmptySubsequences: false).map(String.init) - - // Build search: include line before first use() + all use() lines + line after - let beforeLine = core.startLine - 1 >= 0 ? core.startLine - 1 : core.startLine - let afterLine = core.endLine + 1 < lines.count ? core.endLine + 1 : core.endLine - let searchLines = Array(lines[beforeLine ... afterLine]) - - if searchLines.count < 3 { - issues.append(.warn(code: "shortSearch", msg: "Search block < 3 lines may be ambiguous", path: path)) - } - - // Build replacement: swap use(a,b) to use(b,a) - var replacementLines = searchLines - for i in 0 ..< replacementLines.count { - replacementLines[i] = swapUseCallsInLine(replacementLines[i]) - } - - let (_, lineEnding) = detectIndentInfo(in: text) - - let hint = BenchmarkSearchHint( - path: path, - search: searchLines, - replacement: replacementLines, - reason: "Swap use(a,b) to use(b,a) in function '\(functionName)'" - ) - - let (simIssues, simPass) = await simulateOneModify( - path: path, - search: searchLines, - replacement: replacementLines, - fs: fs, - lineEnding: lineEnding, - caseType: spec.type - ) - issues.append(contentsOf: simIssues) - - return (issues, [hint], simPass) - } - - private func swapUseCallsInLine(_ line: String) -> String { - // Simple regex to swap use(a, b) -> use(b, a) - var result = line - let pattern = "use\\(([^,]+),\\s*([^)]+)\\)" - if let regex = try? NSRegularExpression(pattern: pattern, options: []) { - let nsString = line as NSString - let matches = regex.matches(in: line, range: NSRange(location: 0, length: nsString.length)) - for match in matches.reversed() { - if match.numberOfRanges == 3 { - let arg1 = nsString.substring(with: match.range(at: 1)) - let arg2 = nsString.substring(with: match.range(at: 2)) - let replacement = "use(\(arg2), \(arg1))" - result = (result as NSString).replacingCharacters(in: match.range, with: replacement) - } - } - } - return result - } - - private func auditInsertGuard(_ spec: BenchmarkTaskSpec, fs: BenchmarkMockFileSystemSnapshot) async -> (issues: [BenchmarkAuditIssue], hints: [BenchmarkSearchHint], simulatedPass: Bool) { - // Check for markerless mode - if spec.params["markerless"]?.boolValue == true { - return await auditInsertGuardMarkerless(spec, fs: fs) - } - - var issues: [BenchmarkAuditIssue] = [] - var hints: [BenchmarkSearchHint] = [] - guard - let uid = spec.params["uid"]?.stringValue, - let snippet = spec.params["snippet"]?.stringValue, - let path = spec.selectFiles.first, - let text = fs.content(for: path) - else { - return ([.err(code: "missingParams", msg: "uid/snippet/or file missing", path: nil)], [], false) - } - let startAnchor = "// ANCHOR:start:\(uid)" - let endAnchor = "// ANCHOR:end:\(uid)" - let startRanges = text.ranges(of: startAnchor) - let endRanges = text.ranges(of: endAnchor) - if startRanges.count != 1 || endRanges.count != 1 { - issues.append(.err( - code: "anchorMultiplicity", - msg: "Expected exactly one start/end anchor for uid \(uid) but found start=\(startRanges.count) end=\(endRanges.count)", - path: path - )) - return (issues, hints, false) - } - guard let start = startRanges.first, - let end = text.range(of: endAnchor, range: start.upperBound ..< text.endIndex) - else { - issues.append(.err(code: "anchorOrder", msg: "End anchor not found after start anchor", path: path)) - return (issues, hints, false) - } - let interior = text[start.upperBound ..< end.lowerBound] - if interior.contains("\t") { - issues.append(.warn( - code: "indentationRisk", - msg: "Anchor region currently uses TAB indentation; verifier requires 4-space snippet equality.", - path: path - )) - } - if snippet.contains("\t") { - issues.append(.err(code: "snippetHasTabs", msg: "Snippet contains tabs but spec requires 4 spaces only.", path: path)) - } - let (usesSpaces, lineEnding) = detectIndentInfo(in: text) - let searchLines = DiffParserUtils.splitContentToLines(String(interior), usesSpaces) - let replacementLines = DiffParserUtils.splitContentToLines(snippet, usesSpaces) - if let ambiguity = findAmbiguity(in: text, searchEncoded: searchLines, usesSpaces: usesSpaces) { - issues.append(ambiguity) - } - hints.append(BenchmarkSearchHint( - path: path, - search: searchLines, - replacement: replacementLines, - reason: "Exact match of inner anchor region (excluding anchor lines)." - )) - let simulated = await simulateOneModify( - path: path, - search: searchLines, - replacement: replacementLines, - fs: fs, - lineEnding: lineEnding, - caseType: spec.type - ) - issues.append(contentsOf: simulated.issues) - return (issues, hints, simulated.pass) - } - - private func auditPatchBlock(_ spec: BenchmarkTaskSpec, fs: BenchmarkMockFileSystemSnapshot) async -> (issues: [BenchmarkAuditIssue], hints: [BenchmarkSearchHint], simulatedPass: Bool) { - // Check for markerless mode - if spec.params["markerless"]?.boolValue == true { - return await auditPatchBlockMarkerless(spec, fs: fs) - } - - var issues: [BenchmarkAuditIssue] = [] - var hints: [BenchmarkSearchHint] = [] - guard - let uid = spec.params["uid"]?.stringValue, - let snippet = spec.params["snippet"]?.stringValue, - let path = spec.selectFiles.first, - let text = fs.content(for: path) - else { - return ([.err(code: "missingParams", msg: "uid/snippet/or file missing", path: nil)], [], false) - } - let startToken = "/* BLOCK START:\(uid) */" - let endToken = "/* BLOCK END:\(uid) */" - let startRanges = text.ranges(of: startToken) - let endRanges = text.ranges(of: endToken) - if startRanges.count != 1 || endRanges.count != 1 { - issues.append(.err( - code: "blockMultiplicity", - msg: "Expected exactly one block for uid \(uid) but found start=\(startRanges.count) end=\(endRanges.count)", - path: path - )) - return (issues, hints, false) - } - guard let start = startRanges.first, - let end = text.range(of: endToken, range: start.upperBound ..< text.endIndex) - else { - issues.append(.err(code: "blockOrder", msg: "END block not found after START", path: path)) - return (issues, hints, false) - } - let body = text[start.upperBound ..< end.lowerBound] - let (usesSpaces, lineEnding) = detectIndentInfo(in: text) - if body.contains("\t"), snippet.contains(" ") { - issues.append(.warn( - code: "indentationRisk", - msg: "Block body uses tabs but snippet uses spaces; strict equality will fail.", - path: path - )) - } - let searchLines = DiffParserUtils.splitContentToLines(String(body), usesSpaces) - let replacementLines = DiffParserUtils.splitContentToLines(snippet, usesSpaces) - if let ambiguity = findAmbiguity(in: text, searchEncoded: searchLines, usesSpaces: usesSpaces) { - issues.append(ambiguity) - } - hints.append(BenchmarkSearchHint( - path: path, - search: searchLines, - replacement: replacementLines, - reason: "Exact match of existing block body; replacement is the provided snippet." - )) - let simulated = await simulateOneModify( - path: path, - search: searchLines, - replacement: replacementLines, - fs: fs, - lineEnding: lineEnding, - caseType: spec.type - ) - issues.append(contentsOf: simulated.issues) - return (issues, hints, simulated.pass) - } - - private func auditGeneric(_ spec: BenchmarkTaskSpec, fs: BenchmarkMockFileSystemSnapshot) async -> (issues: [BenchmarkAuditIssue], hints: [BenchmarkSearchHint], simulatedPass: Bool) { - var issues: [BenchmarkAuditIssue] = [] - for path in spec.selectFiles where fs.content(for: path) == nil { - issues.append(.err(code: "missingFile", msg: "File not found", path: path)) - } - return (issues, [], true) - } - - private func detectIndentInfo(in text: String) -> (usesSpaces: Bool, lineEnding: String) { - let (lines, lineEnding) = String.splitContentPreservingLineEndings(text) - let (indentType, _) = String.detectIndentationTypeFromLines(lines) - return (indentType == "s", lineEnding) - } - - private func findAmbiguity(in fileText: String, searchEncoded: [String], usesSpaces: Bool) -> BenchmarkAuditIssue? { - let encodedFile = fileText - .components(separatedBy: .newlines) - .map { usesSpaces ? String.encodeIndentationAsSpaces($0) : String.encodeIndentationAsTabs($0) } - .map { DiffGenerationUtility.processLine($0, precision: .high) } - let indexMap = DiffGenerationUtility.buildLineIndexMapHigh(content: encodedFile) - do { - _ = try DiffGenerationUtility.matchSelectorFastWithAmbiguityCheck( - selector: searchEncoded.map { DiffGenerationUtility.processLine($0, precision: .high) }, - content: encodedFile, - lineIndex: indexMap - ) - return nil - } catch let DiffGenerationError.ambiguousMatch(message) { - return .warn(code: "ambiguousSearch", msg: message, path: nil) - } catch { - return .warn(code: "searchNoMatch", msg: "Search block not found in baseline: \(error.localizedDescription)", path: nil) - } - } - - private func languageFromCaseType(_ caseType: BenchmarkCaseType) -> BenchmarkLanguage { - let rawValue = caseType.rawValue - if rawValue.hasSuffix("_go") { - return .go - } else if rawValue.hasSuffix("_swift") { - return .swift - } else { - return .ts - } - } - - private func simulateOneModify( - path: String, - search: [String], - replacement: [String], - fs: BenchmarkMockFileSystemSnapshot, - lineEnding: String, - caseType: BenchmarkCaseType - ) async -> (issues: [BenchmarkAuditIssue], pass: Bool) { - var issues: [BenchmarkAuditIssue] = [] - var workingFS = BenchmarkMockFileSystem(files: fs.dictionary()) - let change = Change( - id: UUID(), - type: .modify, - summary: "preflight", - isSelected: true, - content: replacement, - startSelector: nil, - endSelector: nil, - searchBlock: search - ) - let parsed = ParsedFile( - fileName: path, - changes: [change], - fileContent: "", - canBeLoaded: true, - action: .modify, - lineEnding: lineEnding - ) - let spec = BenchmarkTaskSpec( - id: "preflight", - type: caseType, - language: languageFromCaseType(caseType), - difficulty: .medium, - selectFiles: [path], - maxEdits: 10, - instructions: [], - task: "", - acceptance: [], - params: [:] - ) - let (edited, errors) = await BenchmarkDiffApplier.apply( - parsedFiles: [parsed], - task: spec, - fileSystem: &workingFS, - baseline: fs - ) - if !errors.isEmpty { - let message = errors.map { error in - if let detail = error.detail { - return "\(error.code): \(detail)" - } - return error.code - }.joined(separator: ", ") - issues.append(.err(code: "applyFailed", msg: message, path: path)) - return (issues, false) - } - if edited.isEmpty { - issues.append(.warn(code: "noChangesGenerated", msg: "Diff applier produced no changes", path: path)) - return (issues, false) - } - return (issues, true) - } -} - -private extension String { - func ranges(of substring: String) -> [Range] { - var ranges: [Range] = [] - var searchStart = startIndex - while let range = range(of: substring, range: searchStart ..< endIndex) { - ranges.append(range) - searchStart = range.upperBound - } - return ranges - } -} - -private extension BenchmarkAuditIssue { - static func info(code: String, msg: String, path: String?, metrics: [String: BenchmarkJSONValue] = [:]) -> Self { - BenchmarkAuditIssue(severity: .info, code: code, message: msg, path: path, metrics: metrics) - } - - static func warn(code: String, msg: String, path: String?, metrics: [String: BenchmarkJSONValue] = [:]) -> Self { - BenchmarkAuditIssue(severity: .warning, code: code, message: msg, path: path, metrics: metrics) - } - - static func err(code: String, msg: String, path: String?, metrics: [String: BenchmarkJSONValue] = [:]) -> Self { - BenchmarkAuditIssue(severity: .error, code: code, message: msg, path: path, metrics: metrics) - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDecoyPlanner.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDecoyPlanner.swift deleted file mode 100644 index 23e173c04..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDecoyPlanner.swift +++ /dev/null @@ -1,830 +0,0 @@ -import Foundation - -/// Types of decoys we can produce. -/// - identicalCoreVariantHalo: core lines are identical to target; surrounding lines differ -/// - altReexport: barrel/re-export graph variant (not produced by default in this file) -/// - altFunctionOrder: identical functions but different neighbors/order (not produced by default here) -/// - patchContextMirror: mirrors unified diff context with tempting identical blocks elsewhere -enum DecoyKind { - case identicalCoreVariantHalo - case altReexport - case altFunctionOrder - case patchContextMirror -} - -/// Description of a produced decoy. -struct DecoySpec { - let path: String - let sourcePath: String - let kind: DecoyKind - /// Line span of the identical core inside the decoy file, 0-based inclusive. - let coreLineSpan: ClosedRange? -} - -/// Internal representation of a target core region inside a source file. -struct CoreRegion { - let startLine: Int // inclusive, 0-based - let endLine: Int // inclusive, 0-based - let lines: [String] // exact lines of the core - let beforeLine: String? // immediate line before core (in the source file) - let afterLine: String? // immediate line after core (in the source file) -} - -/// Entry point for planning and materializing decoys. -enum DecoyPlanner { - /// Generate decoys for a given task and materialize them into the in-memory file system. - /// Returns a list of DecoySpec entries describing produced decoys. - static func materialize( - for task: BenchmarkTaskSpec, - on fs: inout BenchmarkMockFileSystem, - baseline: BenchmarkMockFileSystemSnapshot, - policy: DecoyPolicy - ) -> [DecoySpec] { - guard policy.style != .off, policy.maxDecoysPerTask > 0 else { - return [] - } - - // Determine the list of primary paths to base decoys on. - // Prefer per-entry params (guards/blocks/regions/inserts) when present, - // else fall back to the first candidate in selectFiles. - let candidatePrimaryPaths = primaryEditTargets(for: task) - guard !candidatePrimaryPaths.isEmpty else { return [] } - - var produced: [DecoySpec] = [] - var decoyBudget = policy.maxDecoysPerTask - - // Optionally embed intra-file shadows on safe task types first (they don't count toward path budget) - if policy.enableIntraFileShadows, policy.maxIntraFileShadows > 0 { - let safeForShadows = isSafeForSameFileShadows(task.type) - if safeForShadows { - var shadowsLeft = policy.maxIntraFileShadows - for path in candidatePrimaryPaths { - guard shadowsLeft > 0 else { break } - guard var text = fs.content(for: path) ?? baseline.content(for: path) else { continue } - guard let core = locateCore(for: task, in: text, path: path) else { continue } - let updated = embedShadowCore(in: text, core: core, language: task.language, startMarker: "// SHADOW START (do not edit)", endMarker: "// SHADOW END (do not edit)") - if updated != text { - fs.setFile(path, content: updated) - text = updated - shadowsLeft -= 1 - } - } - } - } - - // Materialize separate decoy files with identical-core + variant halo - outer: for sourcePath in candidatePrimaryPaths { - guard decoyBudget > 0 else { break } - guard let source = fs.content(for: sourcePath) ?? baseline.content(for: sourcePath) else { continue } - guard let core = locateCore(for: task, in: source, path: sourcePath) else { continue } - - // Derive candidate decoy paths - var candidatePaths = variants(for: sourcePath, placement: policy.placement, count: max(policy.maxDecoysPerTask, 3)) - // Filter out any that already exist - candidatePaths.removeAll { p in - fs.content(for: p) != nil || baseline.content(for: p) != nil - } - if candidatePaths.isEmpty { continue } - - // For each decoy path, insert halo variants (slight differences near the core boundaries). - var variantIndex = 0 - while decoyBudget > 0, !candidatePaths.isEmpty { - let decoyPath = candidatePaths.removeFirst() - let halo = haloVariantStrings(language: task.language, core: core, index: variantIndex) - let (content, newSpan) = buildDecoyFile(from: source, core: core, language: task.language, haloVariant: halo) - fs.setFile(decoyPath, content: content) - produced.append(DecoySpec(path: decoyPath, sourcePath: sourcePath, kind: .identicalCoreVariantHalo, coreLineSpan: newSpan)) - variantIndex += 1 - decoyBudget -= 1 - if decoyBudget == 0 { break outer } - } - } - - // Optionally create context mirror decoys for insert_guard and apply_unified_patch tasks - let shouldCreateMirrors = switch task.type { - case .insertGuardTs, .insertGuardGo, .insertGuardSwift, - .applyUnifiedPatchTs, .applyUnifiedPatchGo, .applyUnifiedPatchSwift: - true - default: - false - } - - if shouldCreateMirrors, decoyBudget > 0 { - var mirrorsCreated = 0 - let maxMirrorsPerTask: Int = switch task.type { - case .applyUnifiedPatchTs, .applyUnifiedPatchGo, .applyUnifiedPatchSwift: - policy.style == .gauntlet ? 10 : 6 - default: - 2 // Hard cap to prevent excessive proliferation - } - - for sourcePath in candidatePrimaryPaths { - guard decoyBudget > 0, mirrorsCreated < maxMirrorsPerTask else { break } - guard let source = fs.content(for: sourcePath) ?? baseline.content(for: sourcePath) else { continue } - guard let core = locateCore(for: task, in: source, path: sourcePath) else { continue } - - // Create unique mirror path by including directory slug to avoid collisions - let fileName = (sourcePath as NSString).lastPathComponent - let ext = (fileName as NSString).pathExtension - // Convert path separators to underscores for uniqueness - let slug = sourcePath - .replacingOccurrences(of: "/", with: "__") - .replacingOccurrences(of: "\\", with: "__") - .replacingOccurrences(of: ".", with: "_") - let mirrorPath = "mirror_context/\(slug)__context.\(ext)" - - // Skip if already exists - if fs.content(for: mirrorPath) != nil || baseline.content(for: mirrorPath) != nil { - continue - } - - // Build and write mirror content - let mirrorContent = buildContextMirror(from: source, core: core, language: task.language) - fs.setFile(mirrorPath, content: mirrorContent) - produced.append(DecoySpec(path: mirrorPath, sourcePath: sourcePath, kind: .patchContextMirror, coreLineSpan: nil)) - decoyBudget -= 1 - mirrorsCreated += 1 - } - } - - return produced - } -} - -// MARK: - Primary target inference - -private extension DecoyPlanner { - /// Chooses the "primary" edit targets for a task using per-case params when available. - static func primaryEditTargets(for task: BenchmarkTaskSpec) -> [String] { - var paths: [String] = [] - - func uniqueAppend(_ path: String) { - let norm = BenchmarkMockFileSystem.normalize(path) - guard !norm.isEmpty else { return } - if !paths.contains(norm) { paths.append(norm) } - } - - switch task.type { - case .insertGuardTs, .insertGuardGo, .insertGuardSwift: - if let arr = task.params["guards"]?.arrayValue { - for item in arr { - if let p = item.objectValue?["path"]?.stringValue { uniqueAppend(p) } - } - } else if let p = task.params["file"]?.stringValue { - uniqueAppend(p) - } else if let p = task.selectFiles.first { - uniqueAppend(p) - } - case .patchBlockTs, .patchBlockGo, .patchBlockSwift: - if let arr = task.params["blocks"]?.arrayValue { - for item in arr { - if let p = item.objectValue?["path"]?.stringValue { uniqueAppend(p) } - } - } else if let p = task.selectFiles.first { - uniqueAppend(p) - } - case .swapArgsInRegionTs, .swapArgsInRegionGo, .swapArgsInRegionSwift: - if let arr = task.params["regions"]?.arrayValue { - for item in arr { - if let p = item.objectValue?["path"]?.stringValue { uniqueAppend(p) } - } - } else if let p = task.selectFiles.first { - uniqueAppend(p) - } - case .moveFunctionTs, .moveFunctionGo, .moveFunctionSwift, - .applyUnifiedPatchTs, .applyUnifiedPatchGo, .applyUnifiedPatchSwift, - .removeXTs, .removeXGo, .removeXSwift, - .indexOnlyAppsTs, .indexOnlyAppsGo, .indexOnlyAppsSwift: - if let p = task.selectFiles.first { - uniqueAppend(p) - } - default: - // For other tasks, best-effort: prefer first selection - if let p = task.selectFiles.first { uniqueAppend(p) } - } - - return paths - } - - /// Decide whether it's safe to embed an intra-file shadow core for this task type. - /// Prefer tasks where duplicates won't confuse acceptance logic. - static func isSafeForSameFileShadows(_ type: BenchmarkCaseType) -> Bool { - switch type { - case .removeXTs, .removeXGo, .removeXSwift: - true - case .swapArgsInRegionTs, .swapArgsInRegionGo, .swapArgsInRegionSwift: - true - // Avoid intra-file shadows for anchor/UID-based tasks to prevent confusion with acceptance. - case .insertGuardTs, .insertGuardGo, .insertGuardSwift, - .patchBlockTs, .patchBlockGo, .patchBlockSwift, - .moveFunctionTs, .moveFunctionGo, .moveFunctionSwift, - .indexOnlyAppsTs, .indexOnlyAppsGo, .indexOnlyAppsSwift, - .applyUnifiedPatchTs, .applyUnifiedPatchGo, .applyUnifiedPatchSwift, - .curlyFixTs, .curlyFixGo, .curlyFixSwift, - .renameExportImportsTs, .renameExportImportsGo, .renameExportImportsSwift, - .insertFunctionBottomTs, .insertFunctionBottomGo, .insertFunctionBottomSwift: - false - } - } -} - -// MARK: - Core location per task - -extension DecoyPlanner { - /// Locate a "core" region for this task inside the provided text. - /// - Parameter path: The source path (used to resolve UID/path-specific params). - static func locateCore(for task: BenchmarkTaskSpec, in text: String, path: String? = nil) -> CoreRegion? { - switch task.type { - case .insertGuardTs, .insertGuardGo, .insertGuardSwift: - // Check for markerless mode - if task.params["markerless"]?.boolValue == true, - let functionName = task.params["functionName"]?.stringValue, - let insertAfterPattern = task.params["insertAfterPattern"]?.stringValue - { - return locateInsertGuardMarkerlessCore(in: text, language: task.language, functionName: functionName, insertAfterPattern: insertAfterPattern) - } - // Fallback to anchor-based - let uid = uidForPath(task: task, arrayKey: "guards", pathKey: "path", uidKey: "uid", fallbackUidKey: "uid", path: path) - return locateAnchoredCore( - in: text, - startToken: "// ANCHOR:start:\(uid ?? "")", - endToken: "// ANCHOR:end:\(uid ?? "")", - fallbackTokenPrefix: "// ANCHOR:start:" - ) - case .patchBlockTs, .patchBlockGo, .patchBlockSwift: - // Check for markerless mode - if task.params["markerless"]?.boolValue == true, - let functionName = task.params["functionName"]?.stringValue - { - return locateFunctionCore(in: text, language: task.language, name: functionName) - } - // Fallback to anchor-based - let uid = uidForPath(task: task, arrayKey: "blocks", pathKey: "path", uidKey: "uid", fallbackUidKey: "uid", path: path) - return locateAnchoredCore( - in: text, - startToken: "/* BLOCK START:\(uid ?? "") */", - endToken: "/* BLOCK END:\(uid ?? "") */", - fallbackTokenPrefix: "/* BLOCK START:" - ) - case .swapArgsInRegionTs, .swapArgsInRegionGo, .swapArgsInRegionSwift: - // Check for markerless mode - if task.params["markerless"]?.boolValue == true, - let functionName = task.params["functionName"]?.stringValue - { - return locateSwapArgsMarkerlessCore(in: text, language: task.language, functionName: functionName) - } - // Fallback to anchor-based - let uid = uidForPath(task: task, arrayKey: "regions", pathKey: "path", uidKey: "uid", fallbackUidKey: "uid", path: path) - return locateAnchoredCore( - in: text, - startToken: "/* START_SWAP:\(uid ?? "") */", - endToken: "/* END_SWAP:\(uid ?? "") */", - fallbackTokenPrefix: "/* START_SWAP:" - ) - case .moveFunctionTs, .moveFunctionGo, .moveFunctionSwift: - // Use function name(s); prefer single-move params - if let name = task.params["fromName"]?.stringValue { - return locateFunctionCore(in: text, language: task.language, name: name) - } else if let moves = task.params["moves"]?.arrayValue { - if let first = moves.first?.objectValue?["from"]?.stringValue { - return locateFunctionCore(in: text, language: task.language, name: first) - } - } - return nil - case .indexOnlyAppsTs: - return locateIndexCoreTS(in: text) - case .indexOnlyAppsGo: - return locateIndexCoreGo(in: text) - case .indexOnlyAppsSwift: - return locateIndexCoreSwift(in: text) - case .removeXTs, .removeXGo, .removeXSwift: - return locateRemoveXCore(in: text, token: "CALL_X(") - case .applyUnifiedPatchTs, .applyUnifiedPatchGo, .applyUnifiedPatchSwift: - if let patch = task.params["patch"]?.stringValue { - return locatePatchHunkCore(in: text, patch: patch) - } - return nil - default: - return nil - } - } -} - -// MARK: - Decoy path variants - -extension DecoyPlanner { - /// Produce candidate decoy paths for the given source path based on placement strategy. - static func variants(for path: String, placement: DecoyPolicy.Placement, count: Int) -> [String] { - let normalized = BenchmarkMockFileSystem.normalize(path) - let fileName = (normalized as NSString).lastPathComponent - let dir = (normalized as NSString).deletingLastPathComponent - - func siblingDirCandidates() -> [String] { - var out: [String] = [] - out.append("\(dir)_shadow/\(fileName)") - out.append("\(dir)/alt/\(fileName)") - out.append("\(dir)/copy/\(fileName)") - out.append("\(dir)/mirror/\(fileName)") - return out - } - - func crossRootCandidates() -> [String] { - var out: [String] = [] - out.append("shadow/\(fileName)") - out.append("sandbox/\(fileName)") - out.append("mirror/\(fileName)") - out.append("decoys/\(fileName)") - return out - } - - let candidates: [String] = switch placement { - case .siblingDir: - siblingDirCandidates() - case .crossRoot: - crossRootCandidates() - case .mixed: - siblingDirCandidates() + crossRootCandidates() - } - - // Deduplicate and trim to requested count - var seen = Set() - var results: [String] = [] - for p in candidates { - let norm = BenchmarkMockFileSystem.normalize(p) - if !norm.isEmpty, seen.insert(norm).inserted { - results.append(norm) - } - if results.count >= max(0, count) { break } - } - return results - } -} - -// MARK: - Build decoy content - -extension DecoyPlanner { - /// Construct a decoy file content by inserting variant halo lines around an identical core region. - /// Returns the new content and the updated core line span inside that content. - static func buildDecoyFile( - from original: String, - core: CoreRegion, - language: BenchmarkLanguage, - haloVariant: (before: String?, after: String?) - ) -> (content: String, span: ClosedRange) { - var lines = splitLinesPreservingTrailing(original) - - var start = core.startLine - var end = core.endLine - - if let before = haloVariant.before { - let insertAt = max(0, start) - let prefixed = before - lines.insert(prefixed, at: insertAt) - start += 1 - end += 1 - } - if let after = haloVariant.after { - let insertAt = min(lines.count, end + 1) // line immediately after core end - let prefixed = after - lines.insert(prefixed, at: insertAt) - // No need to move end; core itself unchanged - } - - let content = lines.joined(separator: "\n") - return (content, start ... end) - } - - /// Append a shadow copy of the core region to the end of the file, fenced by marker comments. - static func embedShadowCore( - in original: String, - core: CoreRegion, - language: BenchmarkLanguage, - startMarker: String, - endMarker: String - ) -> String { - let lines = splitLinesPreservingTrailing(original) - var out = lines - // Append spacer newline if file doesn't already end with one - if let last = out.last, !last.isEmpty { - out.append("") - } - out.append(startMarker) - out.append(contentsOf: core.lines) - out.append(endMarker) - return out.joined(separator: "\n") - } - - /// Build a context mirror decoy: a small file containing the core lines with minimal wrapping. - /// This creates near-match confusion across files when models skim multiple files. - static func buildContextMirror( - from original: String, - core: CoreRegion, - language: BenchmarkLanguage - ) -> String { - let comment = lineCommentPrefix(for: language) - var out: [String] = [] - - // Add header - out.append("\(comment) Context mirror file - similar to target but with variations") - out.append("") - - // Add 1-2 helper lines before the core fragment - let indent = leadingIndentation(of: core.lines.first ?? "") - out.append("\(comment) Helper context") - if let before = core.beforeLine { - // Include the line before core, but with slight variation - out.append( - before.replacingOccurrences(of: "const", with: "let") - .replacingOccurrences(of: "let", with: "var") - ) - } - - // Include the core lines verbatim (without anchors) - for line in core.lines { - // Strip anchor comments if present - if line.contains("ANCHOR:start:") || line.contains("ANCHOR:end:") { - continue - } - out.append(line) - } - - // Add 1-2 helper lines after the core fragment - if let after = core.afterLine { - out.append(after.replacingOccurrences(of: "return", with: "// return")) - } - out.append("\(indent)\(comment) End mirror fragment") - - return out.joined(separator: "\n") - } -} - -// MARK: - Halo variant helpers - -private extension DecoyPlanner { - static func haloVariantStrings( - language: BenchmarkLanguage, - core: CoreRegion, - index: Int - ) -> (before: String?, after: String?) { - let indent = leadingIndentation(of: core.lines.first ?? "") - let comment = lineCommentPrefix(for: language) - // Vary the halo messages slightly by index to produce different contexts - let beforeMsg = "\(comment) halo:\(index) pre" - let afterMsg = "\(comment) halo:\(index) post" - - // Do not generate identical before/after to the original immediate lines if possible - let before: String? = "\(indent)\(beforeMsg)" - let after: String? = "\(indent)\(afterMsg)" - return (before, after) - } -} - -// MARK: - Core location helpers - -private extension DecoyPlanner { - static func locateAnchoredCore( - in text: String, - startToken: String, - endToken: String, - fallbackTokenPrefix: String - ) -> CoreRegion? { - let lines = splitLinesPreservingTrailing(text) - // First try exact UID tokens - if let startIdx = lines.firstIndex(where: { $0.contains(startToken) }) { - if let endIdx = lines[startIdx...].firstIndex(where: { $0.contains(endToken) }) { - let coreStart = min(lines.count - 1, startIdx + 1) - let coreEnd = max(coreStart, endIdx - 1) - let before = startIdx > 0 ? lines[startIdx - 1] : nil - let after = endIdx + 1 < lines.count ? lines[endIdx + 1] : nil - let slice = Array(lines[coreStart ... coreEnd]) - return CoreRegion(startLine: coreStart, endLine: coreEnd, lines: slice, beforeLine: before, afterLine: after) - } - } - // Fallback: first occurrence of prefixed anchor if UID missing - if let startIdx = lines.firstIndex(where: { $0.contains(fallbackTokenPrefix) }) { - // Find next matching end token using the detected UID if present - let uid = extractUID(from: lines[startIdx], prefix: fallbackTokenPrefix) - let endTok = endTokenContaining(uid: uid, originalEndToken: endToken, anchorTypePrefix: fallbackTokenPrefix) - if let endIdx = lines[startIdx...].firstIndex(where: { $0.contains(endTok) || $0.contains("END") || $0.contains("end") }) { - let coreStart = min(lines.count - 1, startIdx + 1) - let coreEnd = max(coreStart, endIdx - 1) - let before = startIdx > 0 ? lines[startIdx - 1] : nil - let after = endIdx + 1 < lines.count ? lines[endIdx + 1] : nil - let slice = Array(lines[coreStart ... coreEnd]) - return CoreRegion(startLine: coreStart, endLine: coreEnd, lines: slice, beforeLine: before, afterLine: after) - } - } - return nil - } - - static func extractUID(from line: String, prefix: String) -> String { - // Example line contains "/* BLOCK START:ABCD */" or "// ANCHOR:start:UID" - if let range = line.range(of: prefix) { - let after = line[range.upperBound...] - // Grab until next non-UID char - var collected = "" - for ch in after { - if ch == " " || ch == "*" || ch == "/" || ch == "-" { break } - if ch == ":" { continue } - collected.append(ch) - } - // Trim trailing ornaments - return collected.trimmingCharacters(in: .whitespacesAndNewlines) - } - return "" - } - - static func endTokenContaining(uid: String, originalEndToken: String, anchorTypePrefix: String) -> String { - if uid.isEmpty { - return originalEndToken - } - // Map the fallback prefix to the corresponding end marker form: - // START_SWAP -> END_SWAP, ANCHOR:start: -> ANCHOR:end: - if anchorTypePrefix.contains("START_SWAP") { - return "/* END_SWAP:\(uid) */" - } - if anchorTypePrefix.contains("ANCHOR:start:") { - return "// ANCHOR:end:\(uid)" - } - if anchorTypePrefix.contains("BLOCK START:") { - return "/* BLOCK END:\(uid) */" - } - return originalEndToken - } - - static func locateInsertGuardMarkerlessCore(in text: String, language: BenchmarkLanguage, functionName: String, insertAfterPattern: String) -> CoreRegion? { - let lines = splitLinesPreservingTrailing(text) - // Find the function signature - let signatureIndex: Int? = switch language { - case .ts: - lines.firstIndex(where: { $0.contains("function \(functionName)(") }) - case .go: - lines.firstIndex(where: { $0.contains("func \(functionName)(") }) - case .swift: - lines.firstIndex(where: { $0.contains("func \(functionName)(") }) - } - guard let sigIdx = signatureIndex else { return nil } - - // Find the insertAfterPattern line within the function - var patternIdx: Int? - var balance = 0 - var braceOpened = false - var endIdx = sigIdx - - // First pass: find function bounds and pattern line - search: for i in sigIdx ..< lines.count { - let line = lines[i] - for ch in line { - if ch == "{" { - balance += 1 - braceOpened = true - } else if ch == "}" { - balance -= 1 - if braceOpened, balance == 0 { - endIdx = i - break search - } - } - } - // Look for pattern inside function body - if braceOpened, line.contains(insertAfterPattern), patternIdx == nil { - patternIdx = i - } - } - - guard let patIdx = patternIdx else { return nil } - - // Core region: 3-6 lines around the insertion point (after pattern line) - // Include: pattern line, insertion point (pattern+1), and 1-2 lines after - let coreStart = patIdx - let coreEnd = min(endIdx - 1, patIdx + 3) // pattern + next 3 lines (or until function end) - - let before = coreStart - 1 >= 0 ? lines[coreStart - 1] : nil - let after = coreEnd + 1 < lines.count ? lines[coreEnd + 1] : nil - let slice = Array(lines[coreStart ... coreEnd]) - - return CoreRegion(startLine: coreStart, endLine: coreEnd, lines: slice, beforeLine: before, afterLine: after) - } - - static func locateFunctionCore(in text: String, language: BenchmarkLanguage, name: String) -> CoreRegion? { - let lines = splitLinesPreservingTrailing(text) - // Regex-like simple scanning; look for signature and braces - let signatureIndex: Int? = switch language { - case .ts: - // match: [export] [async] function name( - lines.firstIndex(where: { $0.contains("function \(name)(") }) - case .go: - // match: func [receiver] name( - lines.firstIndex(where: { $0.contains("func \(name)(") }) - case .swift: - // match: [public|private|...] func name( - lines.firstIndex(where: { $0.contains("func \(name)(") }) - } - guard let sigIdx = signatureIndex else { return nil } - // Find matching closing brace from the line with opening "{" - // Move forward to find first "{" and then track balance - var openLine = sigIdx - var braceOpened = false - var balance = 0 - var endIdx = sigIdx - search: for i in sigIdx ..< lines.count { - let line = lines[i] - for ch in line { - if ch == "{" { - balance += 1 - braceOpened = true - } else if ch == "}" { - balance -= 1 - if braceOpened, balance == 0 { - endIdx = i - break search - } - } - } - if !braceOpened, line.contains("{") { - openLine = i - } - } - // Core region: from line after the opening brace to the line before the closing brace - // If opening brace on same line, core starts at next line - let bodyStart = min(lines.count - 1, openLine + 1) - let bodyEnd = max(bodyStart, endIdx - 1) - let before = bodyStart - 1 >= 0 ? lines[bodyStart - 1] : nil - let after = bodyEnd + 1 < lines.count ? lines[bodyEnd + 1] : nil - let slice = Array(lines[bodyStart ... bodyEnd]) - return CoreRegion(startLine: bodyStart, endLine: bodyEnd, lines: slice, beforeLine: before, afterLine: after) - } - - static func locateSwapArgsMarkerlessCore(in text: String, language: BenchmarkLanguage, functionName: String) -> CoreRegion? { - // First, locate the function - guard let funcCore = locateFunctionCore(in: text, language: language, name: functionName) else { - return nil - } - - // Now find use() calls within the function body - let lines = splitLinesPreservingTrailing(text) - let funcStart = funcCore.startLine - let funcEnd = funcCore.endLine - - var useCallLines: [Int] = [] - for i in funcStart ... funcEnd { - if i < lines.count, lines[i].contains("use(") { - useCallLines.append(i) - } - } - - guard let first = useCallLines.first, let last = useCallLines.last else { - // No use() calls found, return the whole function body as core - return funcCore - } - - // Return the contiguous region containing all use() calls - let coreStart = first - let coreEnd = last - let before = coreStart - 1 >= 0 ? lines[coreStart - 1] : nil - let after = coreEnd + 1 < lines.count ? lines[coreEnd + 1] : nil - let slice = Array(lines[coreStart ... coreEnd]) - return CoreRegion(startLine: coreStart, endLine: coreEnd, lines: slice, beforeLine: before, afterLine: after) - } - - static func locateIndexCoreTS(in text: String) -> CoreRegion? { - let lines = splitLinesPreservingTrailing(text) - guard let sigIdx = lines.firstIndex(where: { $0.contains("export default function index()") }) else { return nil } - return locateBodyAfterSignature(in: lines, signatureIndex: sigIdx) - } - - static func locateIndexCoreGo(in text: String) -> CoreRegion? { - let lines = splitLinesPreservingTrailing(text) - guard let sigIdx = lines.firstIndex(where: { $0.contains("func index()") }) else { return nil } - return locateBodyAfterSignature(in: lines, signatureIndex: sigIdx) - } - - static func locateIndexCoreSwift(in text: String) -> CoreRegion? { - let lines = splitLinesPreservingTrailing(text) - // Prefer public signature, fallback to func index() - let sigIdx = lines.firstIndex(where: { $0.contains("public func index()") }) ?? lines.firstIndex(where: { $0.contains("func index()") }) - guard let idx = sigIdx else { return nil } - return locateBodyAfterSignature(in: lines, signatureIndex: idx) - } - - static func locateBodyAfterSignature(in lines: [String], signatureIndex: Int) -> CoreRegion? { - var openLine = signatureIndex - var braceOpened = false - var balance = 0 - var endIdx = signatureIndex - search: for i in signatureIndex ..< lines.count { - let line = lines[i] - for ch in line { - if ch == "{" { - balance += 1 - braceOpened = true - } else if ch == "}" { - balance -= 1 - if braceOpened, balance == 0 { - endIdx = i - break search - } - } - } - if !braceOpened, line.contains("{") { - openLine = i - } - } - let bodyStart = min(lines.count - 1, openLine + 1) - let bodyEnd = max(bodyStart, endIdx - 1) - let before = bodyStart - 1 >= 0 ? lines[bodyStart - 1] : nil - let after = bodyEnd + 1 < lines.count ? lines[bodyEnd + 1] : nil - let slice = Array(lines[bodyStart ... bodyEnd]) - return CoreRegion(startLine: bodyStart, endLine: bodyEnd, lines: slice, beforeLine: before, afterLine: after) - } - - static func locateRemoveXCore(in text: String, token: String) -> CoreRegion? { - let lines = splitLinesPreservingTrailing(text) - var indexes: [Int] = [] - for (i, line) in lines.enumerated() { - if line.contains(token) { - indexes.append(i) - } - } - guard let first = indexes.first, let last = indexes.last else { return nil } - // Expand one line above and one below to capture loop context if available - let coreStart = max(0, first) - let coreEnd = min(lines.count - 1, last) - let before = coreStart - 1 >= 0 ? lines[coreStart - 1] : nil - let after = coreEnd + 1 < lines.count ? lines[coreEnd + 1] : nil - let slice = Array(lines[coreStart ... coreEnd]) - return CoreRegion(startLine: coreStart, endLine: coreEnd, lines: slice, beforeLine: before, afterLine: after) - } - - static func locatePatchHunkCore(in text: String, patch: String) -> CoreRegion? { - guard let hunks = SimpleUnifiedPatchApplier.parseHunks(patch), !hunks.isEmpty else { return nil } - let lines = splitLinesPreservingTrailing(text) - // Use the first hunk's old range as the "core" (tempting context) - let h0 = hunks[0] - let start0 = max(1, h0.oldStart) - 1 - let end0 = max(start0, start0 + max(0, h0.oldCount) - 1) - let boundedStart = min(start0, max(0, lines.count - 1)) - let boundedEnd = min(end0, max(0, lines.count - 1)) - let before = boundedStart - 1 >= 0 ? lines[boundedStart - 1] : nil - let after = boundedEnd + 1 < lines.count ? lines[boundedEnd + 1] : nil - let slice = Array(lines[boundedStart ... boundedEnd]) - return CoreRegion(startLine: boundedStart, endLine: boundedEnd, lines: slice, beforeLine: before, afterLine: after) - } - - static func uidForPath( - task: BenchmarkTaskSpec, - arrayKey: String, - pathKey: String, - uidKey: String, - fallbackUidKey: String, - path: String? - ) -> String? { - if let arr = task.params[arrayKey]?.arrayValue { - // Prefer UID for the matching path entry - if let p = path { - for item in arr { - if let obj = item.objectValue, obj[pathKey]?.stringValue == p { - return obj[uidKey]?.stringValue - } - } - } - // Fallback: first UID in the array - for item in arr { - if let uid = item.objectValue?[uidKey]?.stringValue { - return uid - } - } - } - // Fallback: top-level UID if present - if let uid = task.params[fallbackUidKey]?.stringValue { - return uid - } - return nil - } -} - -// MARK: - Small utilities - -private extension DecoyPlanner { - static func splitLinesPreservingTrailing(_ text: String) -> [String] { - // Keep empty trailing line if present by avoiding omittingEmptySubsequences - text.split(separator: "\n", omittingEmptySubsequences: false).map(String.init) - } - - static func leadingIndentation(of line: String) -> String { - var indent = "" - for ch in line { - if ch == " " || ch == "\t" { - indent.append(ch) - } else { - break - } - } - return indent - } - - static func lineCommentPrefix(for language: BenchmarkLanguage) -> String { - // For TS, Go, and Swift we can safely use // - "//" - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDiffApplier.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDiffApplier.swift deleted file mode 100644 index 109d5973a..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDiffApplier.swift +++ /dev/null @@ -1,229 +0,0 @@ -import Foundation - -enum BenchmarkDiffApplicationError: Error { - case unexpectedFile(path: String) - case tooManyEdits(limit: Int, actual: Int) - case missingBaseline(path: String) - case noChangesGenerated(path: String) - case editApplicationFailed(path: String, reason: String) - case searchBlockNotFound(path: String) -} - -enum BenchmarkDiffApplier { - private static func relativePath(from canonicalPath: String) -> String { - let normalized = BenchmarkMockFileSystem.normalize(canonicalPath) - let prefix = "benchmark/" - if normalized.hasPrefix(prefix) { - return String(normalized.dropFirst(prefix.count)) - } - if normalized == "benchmark" { - return "" - } - return normalized - } - - /// Returns the minimum number of lines required in a search block for the given case type. - /// - /// **Purpose**: These minimums prevent models from using overly-simple search blocks that could - /// match decoy code intentionally placed in benchmark tasks. The model must provide sufficient - /// context to uniquely identify the correct location. - /// - /// **Correlation with Task Generators**: These values should align with the decoy generation - /// strategy for each task type. If a task generator creates single-line decoys, the minimum - /// must be >1 to force disambiguation. - /// - /// **No upper limit**: More specific search blocks are encouraged and not penalized, as they - /// reduce the risk of matching unintended locations (including shadow decoys). - /// - /// Note: In addition to line count, we require at least 2 non-empty lines to prevent - /// search blocks consisting only of blank lines. - private static func minSearchLines(for caseType: BenchmarkCaseType) -> Int { - switch caseType { - case .patchBlockTs, .patchBlockGo, .patchBlockSwift: - 3 // Blocks may have similar signatures; need surrounding context - case .swapArgsInRegionTs, .swapArgsInRegionGo, .swapArgsInRegionSwift: - 3 // Multiple similar regions may exist; need distinct context - case .applyUnifiedPatchTs, .applyUnifiedPatchGo, .applyUnifiedPatchSwift: - 3 // Patches should match sufficient context to avoid wrong location - case .insertGuardTs, .insertGuardGo, .insertGuardSwift: - 2 // Anchors provide unique UIDs, reducing ambiguity risk - case .renameExportImportsTs, .renameExportImportsGo, .renameExportImportsSwift: - 2 // Symbol names are typically unique within scope - default: - 2 // Conservative minimum for general disambiguation - } - } - - private static func forbidSearchBlockReuse(for caseType: BenchmarkCaseType) -> Bool { - switch caseType { - case .patchBlockTs, .patchBlockGo, .patchBlockSwift, - .swapArgsInRegionTs, .swapArgsInRegionGo, .swapArgsInRegionSwift, - .applyUnifiedPatchTs, .applyUnifiedPatchGo, .applyUnifiedPatchSwift: - true - default: - false - } - } - - static func apply( - parsedFiles: [ParsedFile], - task: BenchmarkTaskSpec, - fileSystem: inout BenchmarkMockFileSystem, - baseline: BenchmarkMockFileSystemSnapshot - ) async -> (edited: [BenchmarkEditedFile], errors: [BenchmarkTaskError]) { - let allowedPaths = Set(task.selectFiles.map { BenchmarkMockFileSystem.normalize($0) }) - var edited: [BenchmarkEditedFile] = [] - var errors: [BenchmarkTaskError] = [] - - let totalChangeCount = parsedFiles.reduce(0) { $0 + $1.changes.count } - if totalChangeCount > task.maxEdits { - errors.append( - BenchmarkTaskError( - code: "TOO_MANY_EDITS", - path: nil, - detail: "max=\(task.maxEdits)" - ) - ) - } - - for parsedFile in parsedFiles { - let normalizedPath = relativePath(from: parsedFile.fileName) - guard allowedPaths.contains(normalizedPath) else { - errors.append(BenchmarkTaskError(code: "UNEXPECTED_FILE_EDIT", path: normalizedPath, detail: nil)) - continue - } - - do { - let newContent: String - switch parsedFile.action { - case .create, .rewrite: - newContent = resolvedContentForCreate(parsedFile) - case .delete: - fileSystem.removeFile(normalizedPath) - edited.append(BenchmarkEditedFile(path: normalizedPath, content: "")) - continue - case .modify: - let baselineText = fileSystem.content(for: normalizedPath) - ?? baseline.content(for: normalizedPath) - ?? "" - newContent = try await applyModifyChange( - parsedFile: parsedFile, - originalText: baselineText, - caseType: task.type - ) - } - - let decodedContent = String.decodeIndentationPreservingAllLineEndings(newContent) - fileSystem.setFile(normalizedPath, content: decodedContent) - edited.append(BenchmarkEditedFile(path: normalizedPath, content: decodedContent)) - } catch let BenchmarkDiffApplicationError.noChangesGenerated(path) { - errors.append(BenchmarkTaskError(code: "EDIT_APPLY_FAILED", path: path, detail: "noChangesGenerated")) - } catch let BenchmarkDiffApplicationError.searchBlockNotFound(path) { - errors.append(BenchmarkTaskError(code: "SEARCH_BLOCK_NOT_FOUND", path: path, detail: nil)) - } catch let BenchmarkDiffApplicationError.editApplicationFailed(path, reason) { - errors.append(BenchmarkTaskError(code: "EDIT_APPLY_FAILED", path: path, detail: reason)) - } catch { - errors.append(BenchmarkTaskError(code: "EDIT_APPLY_FAILED", path: normalizedPath, detail: error.localizedDescription)) - } - } - - return (edited, errors) - } - - private static func resolvedContentForCreate(_ parsedFile: ParsedFile) -> String { - if !parsedFile.fileContent.isEmpty { - return parsedFile.fileContent - } - let changeStrings = parsedFile.changes.compactMap { change -> String? in - guard let lines = change.content else { return nil } - return lines.joined(separator: "\n") - } - return changeStrings.joined(separator: "\n\n") - } - - private static func applyModifyChange( - parsedFile: ParsedFile, - originalText: String, - caseType: BenchmarkCaseType - ) async throws -> String { - let normalizedPath = relativePath(from: parsedFile.fileName) - let (originalLines, _) = String.splitContentPreservingLineEndings(originalText) - let (indentType, _) = String.detectIndentationTypeFromLines(originalLines.isEmpty ? [""] : originalLines) - let encodedLines = originalLines.map { String.encodeIndentationWithConversion($0, desiredIndentationType: indentType) } - let processedLineData = encodedLines.map { DiffGenerationUtility.processLine($0, precision: .normal) } - let lineIndexMap = DiffGenerationUtility.buildLineIndexMapHigh(content: processedLineData) - let forbidReuse = forbidSearchBlockReuse(for: caseType) - var usedSearchKeys: Set = [] - var cursorMap: [String: Int] = [:] - var generatedChunks: [DiffChunk] = [] - - for change in parsedFile.changes where change.isSelected { - if Task.isCancelled { continue } - guard let newContent = change.content, !newContent.isEmpty else { continue } - guard let searchBlock = change.searchBlock, !searchBlock.isEmpty else { - throw BenchmarkDiffApplicationError.editApplicationFailed(path: normalizedPath, reason: "missingSearchBlock") - } - // Removed minimum line checks - let the model provide as much context as needed - // If insufficient context causes ambiguity, the edit will match the first occurrence - // and verification will catch incorrect edits, testing the model's disambiguation ability - let processedKey = searchBlock - .map { DiffGenerationUtility.processLine($0, precision: .normal).removedTagsHigh } - .joined(separator: "\n") - if forbidReuse && usedSearchKeys.contains(processedKey) { - throw BenchmarkDiffApplicationError.editApplicationFailed(path: normalizedPath, reason: "reusedSearchBlock") - } - if forbidReuse { - usedSearchKeys.insert(processedKey) - } - let searchStartLine = cursorMap[processedKey] ?? 0 - if searchStartLine == 0 { - let encodedSearch = searchBlock.map { String.encodeIndentationWithConversion($0, desiredIndentationType: indentType) } - let selectorProcessed = encodedSearch.map { DiffGenerationUtility.processLine($0, precision: .high) } - do { - _ = try DiffGenerationUtility.matchSelectorFastWithAmbiguityCheck( - selector: selectorProcessed, - content: processedLineData, - lineIndex: lineIndexMap - ) - } catch DiffGenerationError.ambiguousMatch { - throw BenchmarkDiffApplicationError.editApplicationFailed(path: normalizedPath, reason: "ambiguousSearch") - } catch { - // Allow slower diff generation to attempt matching - } - } - let effectiveLineIndexMap: [String: [Int]]? = (searchStartLine == 0) ? lineIndexMap : nil - let diffChunks: [DiffChunk] - do { - diffChunks = try await DiffGenerationUtility.generateDiff( - fileContent: encodedLines, - lineIndexMap: effectiveLineIndexMap, - startSelector: nil, - endSelector: nil, - searchBlock: searchBlock, - newContent: newContent, - action: parsedFile.action, - diffPrecision: .normal, - searchStartLine: searchStartLine - ) - } catch DiffGenerationError.noMatchFound { - throw BenchmarkDiffApplicationError.searchBlockNotFound(path: normalizedPath) - } catch { - throw BenchmarkDiffApplicationError.editApplicationFailed(path: normalizedPath, reason: error.localizedDescription) - } - if let firstChunk = diffChunks.first { - cursorMap[processedKey] = max(cursorMap[processedKey] ?? 0, firstChunk.startLine + searchBlock.count) - } - generatedChunks.append(contentsOf: diffChunks) - } - - guard !generatedChunks.isEmpty else { - throw BenchmarkDiffApplicationError.noChangesGenerated(path: normalizedPath) - } - - do { - return try DiffChunkTextApplier.apply(chunks: generatedChunks, to: originalText) - } catch { - throw BenchmarkDiffApplicationError.editApplicationFailed(path: parsedFile.fileName, reason: error.localizedDescription) - } - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDiffParserBridge.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDiffParserBridge.swift deleted file mode 100644 index 7a64387d3..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkDiffParserBridge.swift +++ /dev/null @@ -1,63 +0,0 @@ -import Foundation - -final class BenchmarkWorkspaceFilesViewModel: WorkspaceFilesViewModel { - private let baseline: BenchmarkMockFileSystemSnapshot - private let rootIdentifier = UUID() - private let rootPath = "/benchmark" - - init(baseline: BenchmarkMockFileSystemSnapshot) { - self.baseline = baseline - super.init(workspaceFileContextStore: WorkspaceFileContextStore()) - } - - override func pathLocation( - _ userPath: String, - exactMatchOnly: Bool = false, - profile: PathLocateProfile? = nil, - rootScopeOverride: WorkspaceFilesViewModel.LookupRootScope? = nil - ) async -> PathLocation? { - let normalized = BenchmarkMockFileSystem.normalize(userPath) - guard !normalized.isEmpty else { return nil } - // For benchmarks, only return a path location if the file exists in the baseline - // This ensures getBaselineContent can provide actual content for indentation detection - guard baseline.contains(normalized) else { - return nil - } - return PathLocation( - rootPath: rootPath, - correctedPath: normalized, - rootIdentifier: rootIdentifier - ) - } - - override func findFile( - atPath relativePath: String, - rootIdentifier: UUID? - ) async -> FileViewModel? { - // Benchmark runs operate entirely in-memory; we do not surface actual FileViewModels. - // The DiffParser will use getBaselineContent instead. - nil - } - - override func getBaselineContent(forPath relativePath: String, rootIdentifier: UUID?) async -> String? { - let normalized = BenchmarkMockFileSystem.normalize(relativePath) - return baseline.content(for: normalized) - } -} - -enum BenchmarkDiffParserFactory { - static func makeParser(baseline: BenchmarkMockFileSystemSnapshot) async -> DiffParser { - await MainActor.run { - let manager = BenchmarkWorkspaceFilesViewModel(baseline: baseline) - #if DEBUG - let config = DiffParser.DebugConfig( - treatNonExistentFilesAsExisting: true, - alwaysPreserveRewriteAction: true - ) - return DiffParser(fileManager: manager, debugConfig: config) - #else - return DiffParser(fileManager: manager) - #endif - } - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkEngine.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkEngine.swift deleted file mode 100644 index 803e198ab..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkEngine.swift +++ /dev/null @@ -1,288 +0,0 @@ -import Foundation - -struct BenchmarkTaskExecution { - let task: BenchmarkTaskSpec - let baseline: BenchmarkMockFileSystemSnapshot - let result: BenchmarkTaskExecResult -} - -struct BenchmarkSeedExecution { - let seed: UInt32 - let executions: [BenchmarkTaskExecution] -} - -actor AsyncSemaphore { - private var value: Int - private var waiters: [CheckedContinuation] = [] - - init(value: Int) { - self.value = max(1, value) - } - - func wait() async { - if value > 0 { - value -= 1 - return - } - await withCheckedContinuation { (continuation: CheckedContinuation) in - waiters.append(continuation) - } - } - - func signal() { - if waiters.isEmpty { - value += 1 - } else { - let continuation = waiters.removeFirst() - continuation.resume() - } - } -} - -final class BenchmarkEngine { - private let generator: BenchmarkTaskGenerator - private let executor: BenchmarkTaskExecutor - private let config: BenchConfig - private let parallelism: Int - - init( - generator: BenchmarkTaskGenerator, - executor: BenchmarkTaskExecutor, - config: BenchConfig, - parallelism: Int = 4 - ) { - self.generator = generator - self.executor = executor - self.config = config - self.parallelism = max(1, parallelism) - } - - func run( - coreSeed: UInt32, - subSeedCount: Int = 5, - progress: ((BenchmarkProgressEvent) -> Void)? = nil - ) async -> [BenchmarkSeedExecution] { - let seeds = BenchmarkSeedUtilities.deriveSubSeeds(coreSeed: coreSeed, count: subSeedCount) - let languagePlan = Self.buildLanguagePlan(count: seeds.count) - let progressCallback = progress - await MainActor.run { - progressCallback?(.started(totalSeeds: seeds.count)) - } - let generator = generator - let executor = executor - let config = config - let parallelism = parallelism - let seedCount = seeds.count - - return await withTaskCancellationHandler(operation: { - var orderedResults: [BenchmarkSeedExecution?] = Array(repeating: nil, count: seeds.count) - - await withTaskGroup(of: (Int, BenchmarkSeedExecution?).self) { group in - for (index, seed) in seeds.enumerated() { - group.addTask { - if Task.isCancelled { - return (index, nil) - } - let execution = await BenchmarkEngine.executeSeed( - index: index, - seed: seed, - seedCount: seedCount, - generator: generator, - executor: executor, - config: config, - parallelism: parallelism, - language: languagePlan.indices.contains(index) ? languagePlan[index] : nil, - progressCallback: progressCallback - ) - return (index, execution) - } - } - - for await (index, execution) in group { - orderedResults[index] = execution - } - } - - let completedAllSeeds = orderedResults.allSatisfy { $0 != nil } - if Task.isCancelled || !completedAllSeeds { - await MainActor.run { - progressCallback?(.cancelled) - } - } else { - await MainActor.run { - progressCallback?(.finished(totalSeeds: seeds.count)) - } - } - - return orderedResults.compactMap(\.self) - }, onCancel: { - executor.cancelInFlight() - }) - } - - private static func executeSeed( - index: Int, - seed: UInt32, - seedCount: Int, - generator: BenchmarkTaskGenerator, - executor: BenchmarkTaskExecutor, - config: BenchConfig, - parallelism: Int, - language: BenchmarkLanguage?, - progressCallback: ((BenchmarkProgressEvent) -> Void)? - ) async -> BenchmarkSeedExecution? { - if Task.isCancelled { - return nil - } - - let generated = generator.generateSeed(seed, config: config, language: language, subseedIndex: index) - let taskTypes = generated.tasks.map(\.type) - await MainActor.run { - progressCallback?(.seedStarted(index: index, seed: seed, totalSeeds: seedCount, taskCount: generated.tasks.count, taskTypes: taskTypes)) - } - - if generated.tasks.isEmpty { - await MainActor.run { - progressCallback?(.seedCompleted(index: index, seed: seed)) - } - return BenchmarkSeedExecution(seed: seed, executions: []) - } - - if config.tasksAreCumulative { - return await runCumulativeSeed( - index: index, - seed: seed, - generated: generated, - executor: executor, - progressCallback: progressCallback - ) - } else { - return await runIndependentSeed( - index: index, - seed: seed, - generated: generated, - executor: executor, - parallelism: parallelism, - progressCallback: progressCallback - ) - } - } - - private static func runCumulativeSeed( - index: Int, - seed: UInt32, - generated: BenchmarkGeneratedSeed, - executor: BenchmarkTaskExecutor, - progressCallback: ((BenchmarkProgressEvent) -> Void)? - ) async -> BenchmarkSeedExecution? { - var workingFileSystem = generated.fileSystem - var executions: [BenchmarkTaskExecution] = [] - - for (taskIndex, task) in generated.tasks.enumerated() { - if Task.isCancelled { - return nil - } - - var taskFileSystem = workingFileSystem - let baseline = workingFileSystem.snapshot() - let result = await executor.runTask(task, fileSystem: &taskFileSystem, baseline: baseline) - - if Task.isCancelled { - return nil - } - - executions.append(BenchmarkTaskExecution(task: task, baseline: baseline, result: result)) - workingFileSystem = taskFileSystem - - await MainActor.run { - progressCallback?(.taskCompleted(seed: seed, completed: taskIndex + 1, total: generated.tasks.count)) - } - } - - await MainActor.run { - progressCallback?(.seedCompleted(index: index, seed: seed)) - } - - return BenchmarkSeedExecution(seed: seed, executions: executions) - } - - private static func runIndependentSeed( - index: Int, - seed: UInt32, - generated: BenchmarkGeneratedSeed, - executor: BenchmarkTaskExecutor, - parallelism: Int, - progressCallback: ((BenchmarkProgressEvent) -> Void)? - ) async -> BenchmarkSeedExecution? { - let semaphore = AsyncSemaphore(value: parallelism) - var executions: [BenchmarkTaskExecution?] = Array(repeating: nil, count: generated.tasks.count) - var completed = 0 - var cancelled = false - - await withTaskGroup(of: (Int, BenchmarkTaskExecution?).self) { taskGroup in - for (taskIndex, task) in generated.tasks.enumerated() { - taskGroup.addTask { - if Task.isCancelled { - return (taskIndex, nil) - } - - await semaphore.wait() - if Task.isCancelled { - await semaphore.signal() - return (taskIndex, nil) - } - defer { - Task { - await semaphore.signal() - } - } - - var taskFileSystem = generated.fileSystem.clone() - let baseline = generated.baseline - let result = await executor.runTask(task, fileSystem: &taskFileSystem, baseline: baseline) - - if Task.isCancelled { - return (taskIndex, nil) - } - - return (taskIndex, BenchmarkTaskExecution(task: task, baseline: baseline, result: result)) - } - } - - for await (taskIndex, execution) in taskGroup { - completed += 1 - let currentCompleted = completed - if let execution { - executions[taskIndex] = execution - } else { - cancelled = true - } - - await MainActor.run { - progressCallback?(.taskCompleted(seed: seed, completed: currentCompleted, total: generated.tasks.count)) - } - } - } - - if cancelled || executions.contains(where: { $0 == nil }) { - return nil - } - - await MainActor.run { - progressCallback?(.seedCompleted(index: index, seed: seed)) - } - - return BenchmarkSeedExecution(seed: seed, executions: executions.compactMap(\.self)) - } - - private static func buildLanguagePlan(count: Int) -> [BenchmarkLanguage] { - guard count > 0 else { return [] } - let cycle: [BenchmarkLanguage] = [.ts, .go, .swift, .ts, .go] - var plan: [BenchmarkLanguage] = [] - while plan.count < count { - let remaining = count - plan.count - plan.append(contentsOf: cycle.prefix(remaining)) - } - return plan - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkFileSystem.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkFileSystem.swift deleted file mode 100644 index e541ce513..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkFileSystem.swift +++ /dev/null @@ -1,117 +0,0 @@ -import Foundation - -/// Lightweight, in-memory representation of benchmark files. -/// Paths are stored as normalized POSIX-relative strings (no leading slash). -struct BenchmarkMockFileSystem { - private var files: [String: String] - - init(files: [String: String] = [:]) { - self.files = files.reduce(into: [:]) { result, pair in - let normalized = BenchmarkMockFileSystem.normalize(pair.key) - guard !normalized.isEmpty else { return } - result[normalized] = pair.value - } - } - - var count: Int { - files.count - } - - var allPaths: [String] { - Array(files.keys).sorted() - } - - func content(for path: String) -> String? { - files[Self.normalize(path)] - } - - mutating func setFile(_ path: String, content: String) { - let normalized = Self.normalize(path) - guard !normalized.isEmpty else { return } - files[normalized] = content - } - - mutating func setFiles(_ newFiles: [String: String]) { - for (path, content) in newFiles { - setFile(path, content: content) - } - } - - mutating func removeFile(_ path: String) { - files.removeValue(forKey: Self.normalize(path)) - } - - func snapshot() -> BenchmarkMockFileSystemSnapshot { - BenchmarkMockFileSystemSnapshot(files: files) - } - - func clone() -> BenchmarkMockFileSystem { - BenchmarkMockFileSystem(files: files) - } - - func filterPaths(where predicate: (String) -> Bool) -> BenchmarkMockFileSystem { - var filtered: [String: String] = [:] - for (path, content) in files where predicate(path) { - filtered[path] = content - } - return BenchmarkMockFileSystem(files: filtered) - } - - func mapValues(_ transform: (String) -> String) -> BenchmarkMockFileSystem { - var mapped: [String: String] = [:] - for (path, content) in files { - mapped[path] = transform(content) - } - return BenchmarkMockFileSystem(files: mapped) - } - - static func normalize(_ rawPath: String) -> String { - let trimmed = rawPath.trimmingCharacters(in: .whitespacesAndNewlines) - guard !trimmed.isEmpty else { return "" } - let withoutLeading = trimmed.hasPrefix("./") ? String(trimmed.dropFirst(2)) : trimmed - let stripped = withoutLeading.hasPrefix("/") ? String(withoutLeading.dropFirst()) : withoutLeading - let components = stripped.split(separator: "/", omittingEmptySubsequences: true) - var stack: [String] = [] - for component in components { - switch component { - case ".": - continue - case "..": - if !stack.isEmpty { - stack.removeLast() - } - default: - stack.append(String(component)) - } - } - return stack.joined(separator: "/") - } -} - -struct BenchmarkMockFileSystemSnapshot { - private let files: [String: String] - - init(files: [String: String]) { - self.files = files - } - - var count: Int { - files.count - } - - var allPaths: [String] { - Array(files.keys).sorted() - } - - func content(for path: String) -> String? { - files[BenchmarkMockFileSystem.normalize(path)] - } - - func contains(_ path: String) -> Bool { - files.keys.contains(BenchmarkMockFileSystem.normalize(path)) - } - - func dictionary() -> [String: String] { - files - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkModels.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkModels.swift deleted file mode 100644 index d5ec65bec..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkModels.swift +++ /dev/null @@ -1,632 +0,0 @@ -import Foundation - -// MARK: - Core Benchmark Enumerations - -enum BenchmarkCaseType: String, CaseIterable, Codable { - // TypeScript tasks - case removeXTs = "remove_x_ts" - case curlyFixTs = "curly_fix_ts" - case insertGuardTs = "insert_guard_ts" - case patchBlockTs = "patch_block_ts" - case swapArgsInRegionTs = "swap_args_in_region_ts" - case indexOnlyAppsTs = "index_only_apps_ts" - case renameExportImportsTs = "rename_export_and_imports_ts" - case moveFunctionTs = "move_function_ts" - case insertFunctionBottomTs = "insert_function_bottom_ts" - case applyUnifiedPatchTs = "apply_unified_patch_ts" - - // Go tasks - case removeXGo = "remove_x_go" - case curlyFixGo = "curly_fix_go" - case insertGuardGo = "insert_guard_go" - case patchBlockGo = "patch_block_go" - case swapArgsInRegionGo = "swap_args_in_region_go" - case indexOnlyAppsGo = "index_only_apps_go" - case renameExportImportsGo = "rename_export_and_imports_go" - case moveFunctionGo = "move_function_go" - case insertFunctionBottomGo = "insert_function_bottom_go" - case applyUnifiedPatchGo = "apply_unified_patch_go" - - // Swift tasks - case removeXSwift = "remove_x_swift" - case curlyFixSwift = "curly_fix_swift" - case insertGuardSwift = "insert_guard_swift" - case patchBlockSwift = "patch_block_swift" - case swapArgsInRegionSwift = "swap_args_in_region_swift" - case indexOnlyAppsSwift = "index_only_apps_swift" - case renameExportImportsSwift = "rename_export_and_imports_swift" - case moveFunctionSwift = "move_function_swift" - case insertFunctionBottomSwift = "insert_function_bottom_swift" - case applyUnifiedPatchSwift = "apply_unified_patch_swift" -} - -enum BenchmarkLanguage: String, CaseIterable, Codable { - case ts - case go - case swift - - /// Returns the preferred indentation style for this language - var usesTabIndentation: Bool { - switch self { - case .ts, .go: - false // Use spaces - case .swift: - true // Use tabs - } - } - - /// Returns the indentation string for this language - var indentString: String { - usesTabIndentation ? "\t" : " " - } -} - -enum BenchmarkSizePreset: String, CaseIterable, Codable { - case small = "S" - case medium = "M" - case large = "L" -} - -// MARK: - Difficulty - -enum BenchmarkDifficulty: String, CaseIterable, Codable { - /// Retained for decoding backward compatibility; no longer scheduled. - case simple - /// Acts as the lowest scheduled tier after removing `.simple` from the plan. - case medium - case hard - case veryHard -} - -// MARK: - Flexible JSON Payload Support - -enum BenchmarkJSONValue: Codable, Equatable { - case string(String) - case integer(Int) - case double(Double) - case boolean(Bool) - case array([BenchmarkJSONValue]) - case object([String: BenchmarkJSONValue]) - case null - - init(from decoder: Decoder) throws { - let container = try decoder.singleValueContainer() - if container.decodeNil() { - self = .null - return - } - if let value = try? container.decode(Bool.self) { - self = .boolean(value) - return - } - if let value = try? container.decode(Int.self) { - self = .integer(value) - return - } - if let value = try? container.decode(Double.self) { - self = .double(value) - return - } - if let value = try? container.decode(String.self) { - self = .string(value) - return - } - if let value = try? container.decode([BenchmarkJSONValue].self) { - self = .array(value) - return - } - if let value = try? container.decode([String: BenchmarkJSONValue].self) { - self = .object(value) - return - } - throw DecodingError.dataCorruptedError(in: container, debugDescription: "Unsupported JSON value") - } - - func encode(to encoder: Encoder) throws { - var container = encoder.singleValueContainer() - switch self { - case let .string(value): - try container.encode(value) - case let .integer(value): - try container.encode(value) - case let .double(value): - try container.encode(value) - case let .boolean(value): - try container.encode(value) - case let .array(value): - try container.encode(value) - case let .object(value): - try container.encode(value) - case .null: - try container.encodeNil() - } - } - - var stringValue: String? { - if case let .string(value) = self { return value } - return nil - } - - var intValue: Int? { - switch self { - case let .integer(value): value - case let .double(value): Int(value) - case let .string(value): Int(value) - default: nil - } - } - - var doubleValue: Double? { - switch self { - case let .double(value): value - case let .integer(value): Double(value) - case let .string(value): Double(value) - default: nil - } - } - - var boolValue: Bool? { - switch self { - case let .boolean(value): return value - case let .integer(value): return value != 0 - case let .double(value): return value != 0 - case let .string(value): - let lowered = value.lowercased() - if lowered == "true" { return true } - if lowered == "false" { return false } - return nil - default: - return nil - } - } - - var objectValue: [String: BenchmarkJSONValue]? { - if case let .object(value) = self { return value } - return nil - } - - var arrayValue: [BenchmarkJSONValue]? { - if case let .array(value) = self { return value } - return nil - } -} - -// MARK: - Task Specification - -struct BenchmarkTaskSpec: Codable { - let id: String - let type: BenchmarkCaseType - let language: BenchmarkLanguage - let difficulty: BenchmarkDifficulty - let format: String - let selectFiles: [String] - let newChat: Bool - let maxEdits: Int - let instructions: [String] - let task: String - let acceptance: [String] - let params: [String: BenchmarkJSONValue] - - init( - id: String, - type: BenchmarkCaseType, - language: BenchmarkLanguage, - difficulty: BenchmarkDifficulty = .medium, - format: String = "search_replace", - selectFiles: [String], - newChat: Bool = true, - maxEdits: Int, - instructions: [String], - task: String, - acceptance: [String], - params: [String: BenchmarkJSONValue] - ) { - self.id = id - self.type = type - self.language = language - self.difficulty = difficulty - self.format = format - self.selectFiles = selectFiles - self.newChat = newChat - self.maxEdits = maxEdits - self.instructions = instructions - self.task = task - self.acceptance = acceptance - self.params = params - } -} - -// MARK: - Execution Result Types - -struct BenchmarkTaskError: Codable, Equatable { - let code: String - let path: String? - let detail: String? - - init(code: String, path: String? = nil, detail: String? = nil) { - self.code = code - self.path = path - self.detail = detail - } -} - -struct BenchmarkEditedFile: Codable, Equatable { - let path: String - let content: String -} - -struct BenchmarkTaskExecResult: Codable { - let errors: [BenchmarkTaskError] - let edited: [BenchmarkEditedFile] - let meta: [String: BenchmarkJSONValue]? - - init(errors: [BenchmarkTaskError] = [], edited: [BenchmarkEditedFile] = [], meta: [String: BenchmarkJSONValue]? = nil) { - self.errors = errors - self.edited = edited - self.meta = meta - } -} - -// MARK: - Benchmark Configuration - -enum GuidanceVerbosity: String, Codable { - case none - case minimal - case standard -} - -struct DecoyPolicy: Codable { - enum Style: String, Codable { - case off // produce no decoys - case relevant // produce a small number of focused decoys - case gauntlet // produce many strong, confusing decoys - } - - enum Placement: String, Codable { - case siblingDir // same subtree, sibling directories - case crossRoot // different top-level subtree - case mixed // mixture of sibling and cross-root - } - - var style: Style - var placement: Placement - var maxDecoysPerTask: Int - var preferExactClonesFirst: Bool - var enableIntraFileShadows: Bool - var maxIntraFileShadows: Int - - init( - style: Style = .relevant, - placement: Placement = .mixed, - maxDecoysPerTask: Int = 3, - preferExactClonesFirst: Bool = true, - enableIntraFileShadows: Bool = true, - maxIntraFileShadows: Int = 1 - ) { - self.style = style - self.placement = placement - self.maxDecoysPerTask = max(0, maxDecoysPerTask) - self.preferExactClonesFirst = preferExactClonesFirst - self.enableIntraFileShadows = enableIntraFileShadows - self.maxIntraFileShadows = max(0, maxIntraFileShadows) - } -} - -struct BenchConfig: Codable { - let languages: [BenchmarkLanguage] - let size: BenchmarkSizePreset - let sizeLines: Int? - let noise: Double - let enabledTypes: [BenchmarkCaseType] - let params: [String: BenchmarkJSONValue] - let tasksAreCumulative: Bool - let mediumCount: Int - let hardCount: Int - let veryHardCount: Int - let contextCharBudget: Int - let decoyCharCap: Int - let decoysMedium: Int - let decoysHard: Int - let decoysVeryHard: Int - // New fields for decoy planning and prompt guidance - let decoyPolicy: DecoyPolicy - let guidanceVerbosity: GuidanceVerbosity - let includeAutoPlannedDecoys: Bool - - init( - languages: [BenchmarkLanguage] = [.ts, .go], - size: BenchmarkSizePreset = .medium, - sizeLines: Int? = nil, - noise: Double = 0.0, - enabledTypes: [BenchmarkCaseType] = BenchmarkCaseType.allCases, - params: [String: BenchmarkJSONValue] = [:], - tasksAreCumulative: Bool = false, - mediumCount: Int = 2, - hardCount: Int = 3, - veryHardCount: Int = 1, - contextCharBudget: Int = 200_000, - decoyCharCap: Int = 40000, - decoysMedium: Int = 2, - decoysHard: Int = 3, - decoysVeryHard: Int = 6, - // New parameters with defaults - decoyPolicy: DecoyPolicy = DecoyPolicy( - style: .relevant, - placement: .mixed, - maxDecoysPerTask: 3, - preferExactClonesFirst: true, - enableIntraFileShadows: true, - maxIntraFileShadows: 1 - ), - guidanceVerbosity: GuidanceVerbosity = .minimal, - includeAutoPlannedDecoys: Bool = true - ) { - self.languages = languages - self.size = size - self.sizeLines = sizeLines - self.noise = noise - self.enabledTypes = enabledTypes - self.params = params - self.tasksAreCumulative = tasksAreCumulative - self.mediumCount = max(0, mediumCount) - self.hardCount = max(0, hardCount) - self.veryHardCount = max(0, veryHardCount) - self.contextCharBudget = max(10000, contextCharBudget) - self.decoyCharCap = max(2000, decoyCharCap) - self.decoysMedium = max(0, decoysMedium) - self.decoysHard = max(0, decoysHard) - self.decoysVeryHard = max(0, decoysVeryHard) - self.decoyPolicy = decoyPolicy - self.guidanceVerbosity = guidanceVerbosity - self.includeAutoPlannedDecoys = includeAutoPlannedDecoys - } -} - -struct RunConfig: Codable { - let coreSeed: UInt32 - let subSeedCount: Int - - init(coreSeed: UInt32, subSeedCount: Int = 5) { - self.coreSeed = coreSeed - self.subSeedCount = subSeedCount - } -} - -// MARK: - Verification Contracts - -struct BenchmarkVerifyInput { - let taskSpec: BenchmarkTaskSpec - let baseline: BenchmarkMockFileSystemSnapshot - let edited: [BenchmarkEditedFile] - let errors: [BenchmarkTaskError] -} - -struct BenchmarkVerifyOutput { - let pass: Bool - let score: Double - let reason: String - let metrics: [String: BenchmarkJSONValue] - - static func failure(reason: String, metrics: [String: BenchmarkJSONValue] = [:]) -> BenchmarkVerifyOutput { - BenchmarkVerifyOutput(pass: false, score: 0.0, reason: reason, metrics: metrics) - } - - static func success(score: Double = 1.0, metrics: [String: BenchmarkJSONValue] = [:]) -> BenchmarkVerifyOutput { - BenchmarkVerifyOutput(pass: true, score: score, reason: "", metrics: metrics) - } -} - -// MARK: - Reporting - -struct BenchmarkTaskReport { - let id: String - let type: BenchmarkCaseType - let pass: Bool - let score: Double - let reason: String - let metrics: [String: BenchmarkJSONValue] - let errors: [BenchmarkTaskError] - - // New point-based scoring fields - let difficulty: BenchmarkDifficulty - let normalizedScore: Double - let maxPoints: Double - let awardedPoints: Double - - init( - id: String, - type: BenchmarkCaseType, - pass: Bool, - score: Double, - reason: String, - metrics: [String: BenchmarkJSONValue], - errors: [BenchmarkTaskError], - difficulty: BenchmarkDifficulty = .medium, - normalizedScore: Double = 0.0, - maxPoints: Double = 1.0, - awardedPoints: Double = 0.0 - ) { - self.id = id - self.type = type - self.pass = pass - self.score = score - self.reason = reason - self.metrics = metrics - self.errors = errors - self.difficulty = difficulty - self.normalizedScore = normalizedScore - self.maxPoints = maxPoints - self.awardedPoints = awardedPoints - } -} - -struct BenchmarkSeedReport { - let seed: UInt32 - let tasks: [BenchmarkTaskReport] - let passRate: Double - let averageScore: Double - - // New point aggregates - let pointsEarned: Double - let maxPoints: Double - let pointsRate: Double - - init( - seed: UInt32, - tasks: [BenchmarkTaskReport], - passRate: Double, - averageScore: Double, - pointsEarned: Double = 0.0, - maxPoints: Double = 0.0, - pointsRate: Double = 0.0 - ) { - self.seed = seed - self.tasks = tasks - self.passRate = passRate - self.averageScore = averageScore - self.pointsEarned = pointsEarned - self.maxPoints = maxPoints - self.pointsRate = pointsRate - } -} - -struct BenchmarkFinalReport { - let coreSeed: UInt32 - let subSeeds: [UInt32] - let totalTasks: Int - let passRate: Double - let averageScore: Double - let perType: [BenchmarkCaseType: BenchmarkTypeStats] - let perSeed: [BenchmarkSeedReport] - - // New overall point totals - let totalMaxPoints: Double - let totalPointsEarned: Double - let pointsRate: Double - - init( - coreSeed: UInt32, - subSeeds: [UInt32], - totalTasks: Int, - passRate: Double, - averageScore: Double, - perType: [BenchmarkCaseType: BenchmarkTypeStats], - perSeed: [BenchmarkSeedReport], - totalMaxPoints: Double = 0.0, - totalPointsEarned: Double = 0.0, - pointsRate: Double = 0.0 - ) { - self.coreSeed = coreSeed - self.subSeeds = subSeeds - self.totalTasks = totalTasks - self.passRate = passRate - self.averageScore = averageScore - self.perType = perType - self.perSeed = perSeed - self.totalMaxPoints = totalMaxPoints - self.totalPointsEarned = totalPointsEarned - self.pointsRate = pointsRate - } -} - -struct BenchmarkTypeStats { - let count: Int - let passRate: Double - let averageScore: Double - - // New point aggregates - let pointsEarned: Double - let maxPoints: Double - let pointsRate: Double - - init( - count: Int, - passRate: Double, - averageScore: Double, - pointsEarned: Double = 0.0, - maxPoints: Double = 0.0, - pointsRate: Double = 0.0 - ) { - self.count = count - self.passRate = passRate - self.averageScore = averageScore - self.pointsEarned = pointsEarned - self.maxPoints = maxPoints - self.pointsRate = pointsRate - } -} - -extension BenchConfig { - func difficultyPlan() -> [BenchmarkDifficulty] { - // Ordered plan: medium → hard → veryHard; `.simple` is no longer scheduled. - var plan: [BenchmarkDifficulty] = [] - if mediumCount > 0 { - plan += Array(repeating: .medium, count: mediumCount) - } - if hardCount > 0 { - plan += Array(repeating: .hard, count: hardCount) - } - if veryHardCount > 0 { - plan += Array(repeating: .veryHard, count: veryHardCount) - } - return plan - } -} - -extension BenchmarkLanguage { - var displayName: String { - switch self { - case .ts: "TypeScript" - case .go: "Go" - case .swift: "Swift" - } - } - - var codeFenceIdentifier: String { - switch self { - case .ts: "ts" - case .go: "go" - case .swift: "swift" - } - } -} - -extension BenchmarkDifficulty { - /// Maximum point value awarded for a task at this difficulty - var maxPoints: Double { - switch self { - case .simple: - 1.0 - case .medium: - 1.0 - case .hard: - 3.0 - case .veryHard: - 6.0 - } - } -} - -enum BenchmarkPointScales { - @inline(__always) - private static func clamp(_ v: Double, _ lo: Double = 0.0, _ hi: Double = 1.0) -> Double { - min(max(v, lo), hi) - } - - /// Maps a normalized score [0,1] and pass/fail into difficulty-weighted points. - /// - medium/simple: binary – pass => 1.0, fail => 0.0 - /// - hard: scaled to 0...3, quantized to 0.5 - /// - veryHard: scaled to 0...6, quantized to 0.5 - static func points(for difficulty: BenchmarkDifficulty, normalizedScore: Double, pass: Bool) -> Double { - switch difficulty { - case .simple, .medium: - return pass ? 1.0 : 0.0 - case .hard: - let p = clamp(normalizedScore) * 3.0 - return (p * 2.0).rounded(.toNearestOrEven) / 2.0 - case .veryHard: - let p = clamp(normalizedScore) * 6.0 - return (p * 2.0).rounded(.toNearestOrEven) / 2.0 - } - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkProgressEvent.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkProgressEvent.swift deleted file mode 100644 index d9bb49980..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkProgressEvent.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation - -/// Progress updates emitted while running the benchmark engine. -/// Designed to mirror the Context Builder's progress UI with coarse-grained stages. -enum BenchmarkProgressEvent { - case started(totalSeeds: Int) - case seedStarted(index: Int, seed: UInt32, totalSeeds: Int, taskCount: Int, taskTypes: [BenchmarkCaseType]) - case taskCompleted(seed: UInt32, completed: Int, total: Int) - case seedCompleted(index: Int, seed: UInt32) - case finished(totalSeeds: Int) - case cancelled -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkProjectScaffolder.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkProjectScaffolder.swift deleted file mode 100644 index a09915a5e..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkProjectScaffolder.swift +++ /dev/null @@ -1,318 +0,0 @@ -import Foundation - -/// Project layout capturing canonical task targets and decoy pools for a seed. -struct BenchmarkProjectLayout { - let language: BenchmarkLanguage - let workPath: String - let auxWorkPaths: [String] - let exporterPath: String? - let importerPaths: [String] - let appsIndexPaths: [String] - let packageIndexPaths: [String] - let fullDecoyPaths: [String] - let decoyPool: [String] -} - -/// Scaffolds a believable project structure for benchmark tasks. -enum BenchmarkProjectScaffolder { - static func scaffoldProject( - language: BenchmarkLanguage, - rng: inout Mulberry32, - config: BenchConfig, - noise: Int, - into fs: inout BenchmarkMockFileSystem - ) -> BenchmarkProjectLayout { - switch language { - case .ts: scaffoldTs(rng: &rng, config: config, noise: noise, fs: &fs) - case .go: scaffoldGo(rng: &rng, config: config, noise: noise, fs: &fs) - case .swift: scaffoldSwift(rng: &rng, config: config, noise: noise, fs: &fs) - } - } - - // MARK: - TypeScript - - private static func scaffoldTs( - rng: inout Mulberry32, - config: BenchConfig, - noise: Int, - fs: inout BenchmarkMockFileSystem - ) -> BenchmarkProjectLayout { - let workPath = "src/ts/work/Work.ts" - let auxWorkPaths = ["src/ts/work/WorkA.ts", "src/ts/work/WorkB.ts"] - let exporterPath = "src/ts/lib/exporter.ts" - let importerPaths = [ - "apps/appA/src/useX_1.ts", - "apps/appB/src/useX_2.ts", - "apps/appC/src/useX_3.ts" - ] - - // Create work files with minimal bootstrapping - fs.setFile(workPath, content: bootstrapTsWork(rng: &rng, name: "Work", noise: noise)) - for auxPath in auxWorkPaths { - let name = auxPath.split(separator: "/").last?.replacingOccurrences(of: ".ts", with: "") ?? "WorkAux" - fs.setFile(auxPath, content: bootstrapTsWork(rng: &rng, name: name, noise: noise)) - } - - // Create exporter and importers for rename tasks - fs.setFile(exporterPath, content: "export function utilityX(n: number): number {\n return n * 2;\n}\n") - - for importerPath in importerPaths { - let appName = importerPath.split(separator: "/")[1] - fs.setFile(importerPath, content: "import { utilityX } from '../../lib/exporter';\n\nexport function use\(appName)() {\n return utilityX(42);\n}\n") - } - - // Create apps and packages for index-only tasks - let appNames = ["appA", "appB", "appC"] - var appsIndexPaths: [String] = [] - for app in appNames { - let indexPath = "apps/\(app)/src/index.ts" - appsIndexPaths.append(indexPath) - fs.setFile(indexPath, content: "console.log('App \(app) starting...');\nexport const VERSION = '1.0.0';\n") - } - - let pkgNames = ["pkg1", "pkg2", "pkg3"] - var packageIndexPaths: [String] = [] - for pkg in pkgNames { - let indexPath = "packages/\(pkg)/src/index.ts" - packageIndexPaths.append(indexPath) - fs.setFile(indexPath, content: "export const \(pkg.uppercased())_VERSION = '1.0.0';\nexport function \(pkg)Main() {}\n") - } - - // Create utility libraries with varying sizes - let libNames = ["util1", "util2", "util3"] - var decoyPool: [String] = [] - for libName in libNames { - let libPath = "src/ts/lib/\(libName).ts" - let multiplier = [0.6, 1.0, 1.4][rng.nextInt(upperBound: 3)] - let approxLines = Int(Double(noise) * multiplier) - fs.setFile(libPath, content: BelievableCodeFactory.tsUtilityModule( - rng: &rng, - module: libName.capitalized, - approxLines: approxLines - )) - decoyPool.append(libPath) - } - - // Create full decoys near work files - let fullDecoyPaths = ["src/ts/work/WorkShadow.ts", "src/ts/work/WorkClone.ts"] - for decoyPath in fullDecoyPaths { - let name = decoyPath.split(separator: "/").last?.replacingOccurrences(of: ".ts", with: "") ?? "Decoy" - fs.setFile(decoyPath, content: bootstrapTsWork(rng: &rng, name: name, noise: Int(Double(noise) * 0.8))) - } - - // Add more generic decoys - for i in 0 ..< 3 { - let (path, content) = BelievableCodeFactory.tsDecoyFile(rng: &rng, name: "D\(i)") - fs.setFile(path, content: content) - decoyPool.append(path) - } - - return BenchmarkProjectLayout( - language: .ts, - workPath: workPath, - auxWorkPaths: auxWorkPaths, - exporterPath: exporterPath, - importerPaths: importerPaths, - appsIndexPaths: appsIndexPaths, - packageIndexPaths: packageIndexPaths, - fullDecoyPaths: fullDecoyPaths, - decoyPool: decoyPool - ) - } - - private static func bootstrapTsWork(rng: inout Mulberry32, name: String, noise: Int) -> String { - let base = BelievableCodeFactory.tsUtilityModule(rng: &rng, module: name, approxLines: max(40, noise)) - return base + "\n\n// FOOTER_MARKER\n" - } - - // MARK: - Go - - private static func scaffoldGo( - rng: inout Mulberry32, - config: BenchConfig, - noise: Int, - fs: inout BenchmarkMockFileSystem - ) -> BenchmarkProjectLayout { - let workPath = "src/go/work/Work.go" - let auxWorkPaths = ["src/go/work/WorkA.go", "src/go/work/WorkB.go"] - let exporterPath = "src/go/lib/exporter.go" - let importerPaths = [ - "apps/appA/useX_1.go", - "apps/appB/useX_2.go", - "apps/appC/useX_3.go" - ] - - // Create work files - fs.setFile(workPath, content: bootstrapGoWork(rng: &rng, name: "work", noise: noise)) - for auxPath in auxWorkPaths { - fs.setFile(auxPath, content: bootstrapGoWork(rng: &rng, name: "work", noise: noise)) - } - - // Create exporter and importers - fs.setFile(exporterPath, content: "package lib\n\nfunc UtilityX(n int) int {\n return n * 2\n}\n") - - for importerPath in importerPaths { - fs.setFile(importerPath, content: "package main\n\nimport \"project/src/go/lib\"\n\nfunc UseApp() int {\n return lib.UtilityX(42)\n}\n") - } - - // Create apps for index-only tasks - let appNames = ["appA", "appB", "appC"] - var appsIndexPaths: [String] = [] - for app in appNames { - let mainPath = "apps/\(app)/main.go" - appsIndexPaths.append(mainPath) - fs.setFile(mainPath, content: "package main\n\nimport \"fmt\"\n\nfunc main() {\n fmt.Println(\"App \(app) starting...\")\n}\n") - } - - // Create packages - let pkgNames = ["pkg1", "pkg2", "pkg3"] - var packageIndexPaths: [String] = [] - for pkg in pkgNames { - let pkgPath = "packages/\(pkg)/\(pkg).go" - packageIndexPaths.append(pkgPath) - fs.setFile(pkgPath, content: "package \(pkg)\n\nconst VERSION = \"1.0.0\"\n\nfunc Main() {}\n") - } - - // Create utility libraries - let libNames = ["util1", "util2", "util3"] - var decoyPool: [String] = [] - for libName in libNames { - let libPath = "src/go/lib/\(libName).go" - let multiplier = [0.6, 1.0, 1.4][rng.nextInt(upperBound: 3)] - let approxLines = Int(Double(noise) * multiplier) - fs.setFile(libPath, content: BelievableCodeFactory.goUtilityModule( - rng: &rng, - pkg: libName, - approxLines: approxLines - )) - decoyPool.append(libPath) - } - - // Create full decoys - let fullDecoyPaths = ["src/go/work/WorkShadow.go", "src/go/work/WorkClone.go"] - for decoyPath in fullDecoyPaths { - fs.setFile(decoyPath, content: bootstrapGoWork(rng: &rng, name: "work", noise: Int(Double(noise) * 0.8))) - } - - // Add more generic decoys - for i in 0 ..< 3 { - let (path, content) = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "D\(i)") - fs.setFile(path, content: content) - decoyPool.append(path) - } - - return BenchmarkProjectLayout( - language: .go, - workPath: workPath, - auxWorkPaths: auxWorkPaths, - exporterPath: exporterPath, - importerPaths: importerPaths, - appsIndexPaths: appsIndexPaths, - packageIndexPaths: packageIndexPaths, - fullDecoyPaths: fullDecoyPaths, - decoyPool: decoyPool - ) - } - - private static func bootstrapGoWork(rng: inout Mulberry32, name: String, noise: Int) -> String { - let base = BelievableCodeFactory.goUtilityModule(rng: &rng, pkg: name, approxLines: max(40, noise)) - return base + "\n\n// FOOTER_MARKER\n" - } - - // MARK: - Swift - - private static func scaffoldSwift( - rng: inout Mulberry32, - config: BenchConfig, - noise: Int, - fs: inout BenchmarkMockFileSystem - ) -> BenchmarkProjectLayout { - let workPath = "src/swift/work/Work.swift" - let auxWorkPaths = ["src/swift/work/WorkA.swift", "src/swift/work/WorkB.swift"] - let exporterPath = "src/swift/lib/Exporter.swift" - let importerPaths = [ - "Apps/appA/UseX_1.swift", - "Apps/appB/UseX_2.swift", - "Apps/appC/UseX_3.swift" - ] - - // Create work files - fs.setFile(workPath, content: bootstrapSwiftWork(rng: &rng, name: "Work", noise: noise)) - for auxPath in auxWorkPaths { - let name = auxPath.split(separator: "/").last?.replacingOccurrences(of: ".swift", with: "") ?? "WorkAux" - fs.setFile(auxPath, content: bootstrapSwiftWork(rng: &rng, name: name, noise: noise)) - } - - // Create exporter and importers - fs.setFile(exporterPath, content: "public func utilityX(_ n: Int) -> Int {\n\treturn n * 2\n}\n") - - for importerPath in importerPaths { - let appName = importerPath.split(separator: "/")[1] - fs.setFile(importerPath, content: "import Foundation\n\nfunc use\(appName)() -> Int {\n\treturn utilityX(42)\n}\n") - } - - // Create apps for index-only tasks - let appNames = ["appA", "appB", "appC"] - var appsIndexPaths: [String] = [] - for app in appNames { - let indexPath = "Apps/\(app)/index.swift" - appsIndexPaths.append(indexPath) - fs.setFile(indexPath, content: "func main() {\n\tprint(\"App \(app) starting...\")\n\tlet VERSION = \"1.0.0\"\n}\n") - } - - // Create packages - let pkgNames = ["Pkg1", "Pkg2", "Pkg3"] - var packageIndexPaths: [String] = [] - for pkg in pkgNames { - let pkgPath = "Packages/\(pkg)/\(pkg).swift" - packageIndexPaths.append(pkgPath) - fs.setFile(pkgPath, content: "public enum \(pkg) {\n\tpublic static let VERSION = \"1.0.0\"\n\tpublic static func main() {}\n}\n") - } - - // Create utility libraries - let libNames = ["Utils1", "Utils2", "Utils3"] - var decoyPool: [String] = [] - for libName in libNames { - let libPath = "src/swift/lib/\(libName).swift" - let multiplier = [0.6, 1.0, 1.4][rng.nextInt(upperBound: 3)] - let approxLines = Int(Double(noise) * multiplier) - fs.setFile(libPath, content: BelievableCodeFactory.swiftUtilityModule( - rng: &rng, - module: libName, - approxLines: approxLines - )) - decoyPool.append(libPath) - } - - // Create full decoys - let fullDecoyPaths = ["src/swift/work/WorkShadow.swift", "src/swift/work/WorkClone.swift"] - for decoyPath in fullDecoyPaths { - let name = decoyPath.split(separator: "/").last?.replacingOccurrences(of: ".swift", with: "") ?? "Decoy" - fs.setFile(decoyPath, content: bootstrapSwiftWork(rng: &rng, name: name, noise: Int(Double(noise) * 0.8))) - } - - // Add more generic decoys - for i in 0 ..< 3 { - let (path, content) = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "D\(i)") - fs.setFile(path, content: content) - decoyPool.append(path) - } - - return BenchmarkProjectLayout( - language: .swift, - workPath: workPath, - auxWorkPaths: auxWorkPaths, - exporterPath: exporterPath, - importerPaths: importerPaths, - appsIndexPaths: appsIndexPaths, - packageIndexPaths: packageIndexPaths, - fullDecoyPaths: fullDecoyPaths, - decoyPool: decoyPool - ) - } - - private static func bootstrapSwiftWork(rng: inout Mulberry32, name: String, noise: Int) -> String { - let base = BelievableCodeFactory.swiftUtilityModule(rng: &rng, module: name, approxLines: max(40, noise)) - return base + "\n\n// FOOTER_MARKER\n" - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRandom.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRandom.swift deleted file mode 100644 index 6379d822d..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRandom.swift +++ /dev/null @@ -1,42 +0,0 @@ -import Foundation - -/// Deterministic PRNG implementation based on Mulberry32. -struct Mulberry32 { - private var state: UInt32 - - init(seed: UInt32) { - state = seed - } - - mutating func nextUInt32() -> UInt32 { - state &+= 0x6D2B_79F5 - var z = state - z = (z ^ (z >> 15)) &* (z | 1) - z ^= z &+ ((z ^ (z >> 7)) &* (z | 61)) - return z ^ (z >> 14) - } - - mutating func nextDouble() -> Double { - Double(nextUInt32()) / 4_294_967_296.0 - } - - mutating func nextInt(upperBound: Int) -> Int { - guard upperBound > 0 else { return 0 } - let value = nextUInt32() - return Int(value % UInt32(upperBound)) - } -} - -enum BenchmarkSeedUtilities { - /// Canonical core seed so runs start from a shared baseline. - static let canonicalCoreSeed: UInt32 = 3_618_045_077 - - static func deriveSubSeeds(coreSeed: UInt32, count: Int = 5) -> [UInt32] { - var seeds: [UInt32] = [] - var rng = Mulberry32(seed: coreSeed) - for _ in 0 ..< count { - seeds.append(rng.nextUInt32()) - } - return seeds - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkReporter.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkReporter.swift deleted file mode 100644 index c169130ee..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkReporter.swift +++ /dev/null @@ -1,113 +0,0 @@ -import Foundation - -struct BenchmarkReporter { - private let verifier: BenchmarkVerifying - - init(verifier: BenchmarkVerifying = BenchmarkVerifier()) { - self.verifier = verifier - } - - func buildReport(coreSeed: UInt32, executions: [BenchmarkSeedExecution]) -> BenchmarkFinalReport { - var seedReports: [BenchmarkSeedReport] = [] - var allTaskReports: [BenchmarkTaskReport] = [] - - for seedExecution in executions { - var taskReports: [BenchmarkTaskReport] = [] - for execution in seedExecution.executions { - let verification = verifier.verify(execution) - let difficulty = execution.task.difficulty - let maxPts = difficulty.maxPoints - let awardedPts = BenchmarkPointScales.points(for: difficulty, normalizedScore: verification.score, pass: verification.pass) - let report = BenchmarkTaskReport( - id: execution.task.id, - type: execution.task.type, - pass: verification.pass, - score: verification.score, - reason: verification.reason, - metrics: verification.metrics, - errors: execution.result.errors, - difficulty: difficulty, - normalizedScore: verification.score, - maxPoints: maxPts, - awardedPoints: awardedPts - ) - taskReports.append(report) - allTaskReports.append(report) - } - let passRate = average(taskReports.map { $0.pass ? 1.0 : 0.0 }) - let averageScore = average(taskReports.map(\.score)) - let pointsEarned = taskReports.reduce(0.0) { sum, report in - let multiplier = (report.pass && report.normalizedScore >= 1.0) ? 2.0 : 1.0 - return sum + (report.awardedPoints * multiplier) - } - let maxPoints = taskReports.reduce(0.0) { $0 + ($1.maxPoints * 2.0) } - let pointsRate = maxPoints > 0 ? pointsEarned / maxPoints : 0.0 - seedReports.append( - BenchmarkSeedReport( - seed: seedExecution.seed, - tasks: taskReports, - passRate: passRate, - averageScore: averageScore, - pointsEarned: pointsEarned, - maxPoints: maxPoints, - pointsRate: pointsRate - ) - ) - } - - let passRate = average(allTaskReports.map { $0.pass ? 1.0 : 0.0 }) - let averageScore = average(allTaskReports.map(\.score)) - let totalPointsEarned = allTaskReports.reduce(0.0) { sum, report in - let multiplier = (report.pass && report.normalizedScore >= 1.0) ? 2.0 : 1.0 - return sum + (report.awardedPoints * multiplier) - } - let totalMaxPoints = allTaskReports.reduce(0.0) { $0 + ($1.maxPoints * 2.0) } - let pointsRate = totalMaxPoints > 0 ? totalPointsEarned / totalMaxPoints : 0.0 - let perType = buildTypeStats(allTaskReports) - return BenchmarkFinalReport( - coreSeed: coreSeed, - subSeeds: executions.map(\.seed), - totalTasks: allTaskReports.count, - passRate: passRate, - averageScore: averageScore, - perType: perType, - perSeed: seedReports, - totalMaxPoints: totalMaxPoints, - totalPointsEarned: totalPointsEarned, - pointsRate: pointsRate - ) - } - - private func average(_ values: [Double]) -> Double { - guard !values.isEmpty else { return 0.0 } - let total = values.reduce(0, +) - return total / Double(values.count) - } - - private func buildTypeStats(_ reports: [BenchmarkTaskReport]) -> [BenchmarkCaseType: BenchmarkTypeStats] { - var grouped: [BenchmarkCaseType: [BenchmarkTaskReport]] = [:] - for report in reports { - grouped[report.type, default: []].append(report) - } - var stats: [BenchmarkCaseType: BenchmarkTypeStats] = [:] - for (type, items) in grouped { - let passRate = average(items.map { $0.pass ? 1.0 : 0.0 }) - let avgScore = average(items.map(\.score)) - let pointsEarned = items.reduce(0.0) { sum, report in - let multiplier = (report.pass && report.normalizedScore >= 1.0) ? 2.0 : 1.0 - return sum + (report.awardedPoints * multiplier) - } - let maxPoints = items.reduce(0.0) { $0 + ($1.maxPoints * 2.0) } - let pointsRate = maxPoints > 0 ? pointsEarned / maxPoints : 0.0 - stats[type] = BenchmarkTypeStats( - count: items.count, - passRate: passRate, - averageScore: avgScore, - pointsEarned: pointsEarned, - maxPoints: maxPoints, - pointsRate: pointsRate - ) - } - return stats - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRunStore.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRunStore.swift deleted file mode 100644 index baaa820c2..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRunStore.swift +++ /dev/null @@ -1,50 +0,0 @@ -import Foundation - -actor BenchmarkRunStore { - static let shared = BenchmarkRunStore() - - private let storageKey = "benchmarkRunHistory" - private let encoder: JSONEncoder - private let decoder: JSONDecoder - private let userDefaults: UserDefaults - - init(userDefaults: UserDefaults = .standard) { - let encoder = JSONEncoder() - encoder.dateEncodingStrategy = .iso8601 - self.encoder = encoder - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .iso8601 - self.decoder = decoder - self.userDefaults = userDefaults - } - - func loadRuns() -> [BenchmarkRunSummary] { - guard let data = userDefaults.data(forKey: storageKey) else { - return [] - } - do { - return try decoder.decode([BenchmarkRunSummary].self, from: data) - } catch { - return [] - } - } - - func saveRuns(_ runs: [BenchmarkRunSummary]) { - guard let data = try? encoder.encode(runs) else { return } - userDefaults.set(data, forKey: storageKey) - } - - @discardableResult - func append(_ run: BenchmarkRunSummary) -> [BenchmarkRunSummary] { - var runs = loadRuns() - runs.append(run) - // Keep most recent first when stored to simplify consumers - runs.sort { $0.timestamp > $1.timestamp } - saveRuns(runs) - return runs - } - - func clear() { - userDefaults.removeObject(forKey: storageKey) - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRunSummary.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRunSummary.swift deleted file mode 100644 index d925d9a7c..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkRunSummary.swift +++ /dev/null @@ -1,171 +0,0 @@ -import Foundation - -struct BenchmarkTaskSummary: Codable, Identifiable { - let id: String - let type: String - let pass: Bool - let score: Double - let reason: String - let difficulty: String - let maxPoints: Double - let awardedPoints: Double - let normalizedScore: Double -} - -struct BenchmarkSeedSummary: Codable, Identifiable { - let seed: UInt32 - let passRate: Double - let averageScore: Double - let tasks: [BenchmarkTaskSummary] - let pointsEarned: Double - let maxPoints: Double - let pointsRate: Double - - var id: UInt32 { - seed - } -} - -struct BenchmarkTypeSummary: Codable, Identifiable { - let type: String - let passRate: Double - let averageScore: Double - let count: Int - let pointsEarned: Double - let maxPoints: Double - let pointsRate: Double - - var id: String { - type - } -} - -struct BenchmarkRunSummary: Codable, Identifiable { - let id: UUID - let timestamp: Date - let modelRawValue: String - let modelDisplayName: String - let coreSeed: UInt32 - let subSeeds: [UInt32] - let totalTasks: Int - let passedTasks: Int - let averageScore: Double - let passRate: Double - let seedSummaries: [BenchmarkSeedSummary] - let typeSummaries: [BenchmarkTypeSummary] - let temperature: Double? - let totalMaxPoints: Double - let totalPointsEarned: Double - let pointsRate: Double - let hadErrors: Bool? - - var modelDisplayShort: String { - modelDisplayName.isEmpty ? modelRawValue : modelDisplayName - } - - var providerName: String { - guard let model = AIModel.fromModelName(modelRawValue) else { - return "Unknown" - } - return AIProviderType.displayName(for: model.providerType) - } -} - -extension BenchmarkRunSummary { - static func make( - report: BenchmarkFinalReport, - model: AIModel, - timestamp: Date = Date(), - temperature: Double? = nil - ) -> BenchmarkRunSummary { - let seedSummaries = report.perSeed.map { seed -> BenchmarkSeedSummary in - let tasks = seed.tasks.map { task in - BenchmarkTaskSummary( - id: task.id, - type: task.type.rawValue, - pass: task.pass, - score: task.score, - reason: task.reason, - difficulty: task.difficulty.rawValue, - maxPoints: task.maxPoints, - awardedPoints: task.awardedPoints, - normalizedScore: task.normalizedScore - ) - } - let passedCount = tasks.filter(\.pass).count - let passRate = seed.passRate == 0 ? (Double(passedCount) / Double(max(1, tasks.count))) : seed.passRate - return BenchmarkSeedSummary( - seed: seed.seed, - passRate: passRate, - averageScore: seed.averageScore, - tasks: tasks, - pointsEarned: seed.pointsEarned, - maxPoints: seed.maxPoints, - pointsRate: seed.pointsRate - ) - } - - let totalTasks = report.totalTasks - let passedTasks = seedSummaries.reduce(0) { result, summary in - result + summary.tasks.filter(\.pass).count - } - let passRate = totalTasks == 0 ? 0.0 : Double(passedTasks) / Double(totalTasks) - - let typeSummaries = report.perType.map { key, value in - BenchmarkTypeSummary( - type: key.rawValue, - passRate: value.passRate, - averageScore: value.averageScore, - count: value.count, - pointsEarned: value.pointsEarned, - maxPoints: value.maxPoints, - pointsRate: value.pointsRate - ) - }.sorted { $0.type < $1.type } - - // Check if any task had MODEL_EXECUTION_FAILED error (API error) - let hadAPIErrors = report.perSeed.contains { seedReport in - seedReport.tasks.contains { task in - task.errors.contains { error in - error.code == "MODEL_EXECUTION_FAILED" - } - } - } - - return BenchmarkRunSummary( - id: UUID(), - timestamp: timestamp, - modelRawValue: model.rawValue, - modelDisplayName: model.displayName, - coreSeed: report.coreSeed, - subSeeds: report.subSeeds, - totalTasks: totalTasks, - passedTasks: passedTasks, - averageScore: report.averageScore, - passRate: passRate, - seedSummaries: seedSummaries, - typeSummaries: typeSummaries, - temperature: temperature, - totalMaxPoints: report.totalMaxPoints, - totalPointsEarned: report.totalPointsEarned, - pointsRate: report.pointsRate, - hadErrors: hadAPIErrors - ) - } -} - -struct BenchmarkLeaderboardEntry: Identifiable { - let modelRawValue: String - let modelDisplayName: String - let runs: Int - let totalTasks: Int - let passedTasks: Int - let averageScore: Double - let passRate: Double - let pointsRate: Double - let lastRun: Date - - var id: String { - modelRawValue - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkTaskExecutor.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkTaskExecutor.swift deleted file mode 100644 index 16f389693..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkTaskExecutor.swift +++ /dev/null @@ -1,1002 +0,0 @@ -import Foundation -import SwiftAnthropic -import SwiftOpenAI - -/// Note: PromptFactory and PromptConfig are used to create custom benchmark prompts -enum BenchmarkTaskExecutorError: Error { - case missingModelService - case outputBudgetExceeded - case searchBlockTooLarge -} - -final class BenchmarkTaskExecutor { - typealias ModelOutputProvider = @Sendable (AIMessage, AIModel) async throws -> String - - private let aiQueriesService: AIQueriesService? - private let model: AIModel - private let outputProvider: ModelOutputProvider? - private let maxOutputBytes: Int - private let maxOutputLines: Int - private let maxContextChars: Int - private let maxDecoyPerFileChars: Int - - init( - aiQueriesService: AIQueriesService?, - model: AIModel, - outputProvider: ModelOutputProvider? = nil, - maxOutputBytes: Int = 120_000, - maxOutputLines: Int = 800, - maxContextChars: Int = 200_000, - maxDecoyPerFileChars: Int = 40000 - ) { - self.aiQueriesService = aiQueriesService - self.model = model - self.outputProvider = outputProvider - self.maxOutputBytes = maxOutputBytes - self.maxOutputLines = maxOutputLines - self.maxContextChars = max(10000, maxContextChars) - self.maxDecoyPerFileChars = max(2000, maxDecoyPerFileChars) - } - - func cancelInFlight() { - aiQueriesService?.cancelQuery() - } - - func runTask( - _ task: BenchmarkTaskSpec, - fileSystem: inout BenchmarkMockFileSystem, - baseline: BenchmarkMockFileSystemSnapshot - ) async -> BenchmarkTaskExecResult { - // Custom prompt config for benchmarks: disable create, delete, and rename capabilities - let fileExt = SystemPromptService.fileExtension(for: task.language.displayName) - let benchmarkConfig = PromptConfig( - role: .codeAssistant, - canCreate: false, - canRewrite: false, - canSearchReplace: true, - canDelete: false, - supportsRename: false, - language: task.language.displayName, - fileExtension: fileExt, - codeBlockFence: SystemPromptService.chatCodeFence, - includeIndentationEncoding: false, - includeEscapingRules: true - ) - let systemPrompt = PromptFactory.buildPrompt(with: benchmarkConfig) - let packaging = composePackagedUserMessage( - task: task, - fileSystem: fileSystem, - baseline: baseline - ) - let userPrompt = packaging.prompt - let aiMessage = AIMessage( - systemPrompt: systemPrompt, - userMessage: userPrompt, - disableTemperatureOverrides: true - ) - let promptMetaBase: [String: BenchmarkJSONValue] = [ - "systemPrompt": .string(systemPrompt), - "userPrompt": .string(userPrompt), - "virtualFiles": .array(packaging.virtualFiles.map { $0.metaValue() }) - ] - func mergedMeta(_ extra: [String: BenchmarkJSONValue] = [:]) -> [String: BenchmarkJSONValue] { - var combined = promptMetaBase - for (key, value) in extra { - combined[key] = value - } - return combined - } - - let rawOutput: String - do { - rawOutput = try await fetchModelOutput(for: aiMessage) - } catch is CancellationError { - return BenchmarkTaskExecResult(errors: [], edited: [], meta: promptMetaBase) - } catch BenchmarkTaskExecutorError.outputBudgetExceeded { - let err = BenchmarkTaskError(code: "MODEL_OUTPUT_TOO_LARGE", path: nil, detail: "Output exceeded budget") - return BenchmarkTaskExecResult( - errors: [err], - edited: [], - meta: promptMetaBase - ) - } catch BenchmarkTaskExecutorError.searchBlockTooLarge { - let err = BenchmarkTaskError(code: "SEARCH_BLOCK_TOO_LARGE", path: nil, detail: "Search block exceeded maximum line limit - attempted to echo entire file") - return BenchmarkTaskExecResult( - errors: [err], - edited: [], - meta: mergedMeta(["rawOutput": .string(""), "overBudget": .boolean(true)]) - ) - } catch { - let err = BenchmarkTaskError(code: "MODEL_EXECUTION_FAILED", path: nil, detail: error.localizedDescription) - return BenchmarkTaskExecResult( - errors: [err], - edited: [], - meta: mergedMeta(["rawOutput": .string("")]) - ) - } - - let parser = await BenchmarkDiffParserFactory.makeParser(baseline: baseline) - let parsedFiles: [ParsedFile] - do { - parsedFiles = try await parser.parse(rawOutput) - } catch { - let err = BenchmarkTaskError(code: "PARSE_OUTPUT_FAILED", path: nil, detail: error.localizedDescription) - return BenchmarkTaskExecResult( - errors: [err], - edited: [], - meta: mergedMeta(["rawOutput": .string(rawOutput)]) - ) - } - - let hasMissingContent = parsedFiles.contains { file in - guard file.action == .modify else { return false } - return file.changes.contains { change in - guard change.isSelected else { return false } - let content = change.content ?? [] - if content.isEmpty { return true } - return content.allSatisfy { $0.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } - } - } - if hasMissingContent { - let err = BenchmarkTaskError( - code: "PARSE_OUTPUT_FAILED", - path: nil, - detail: "One or more blocks are missing " - ) - let meta = mergedMeta([ - "rawOutput": .string(rawOutput), - "parsedFileCount": .integer(parsedFiles.count) - ]) - return BenchmarkTaskExecResult(errors: [err], edited: [], meta: meta) - } - - // Track edit block counts for soft penalty evaluation - let editBlockCountTotal = parsedFiles.reduce(0) { $0 + $1.changes.filter(\.isSelected).count } - var editBlockCountByFile: [String: BenchmarkJSONValue] = [:] - for parsedFile in parsedFiles { - let count = parsedFile.changes.filter(\.isSelected).count - // Normalize path for consistent keys - let normalizedPath = parsedFile.fileName.replacingOccurrences(of: "\\", with: "/") - editBlockCountByFile[normalizedPath] = .integer(count) - } - - let (edited, diffErrors) = await BenchmarkDiffApplier.apply( - parsedFiles: parsedFiles, - task: task, - fileSystem: &fileSystem, - baseline: baseline - ) - - let lineCount = rawOutput.split(whereSeparator: \.isNewline).count - let meta = mergedMeta([ - "rawOutput": .string(rawOutput), - "parsedFileCount": .integer(parsedFiles.count), - "rawCharCount": .integer(rawOutput.count), - "rawLineCount": .integer(lineCount), - "editBlockCountTotal": .integer(editBlockCountTotal), - "editBlockCountByFile": .object(editBlockCountByFile) - ]) - - return BenchmarkTaskExecResult(errors: diffErrors, edited: edited, meta: meta) - } - - private func fetchModelOutput(for message: AIMessage) async throws -> String { - func performRequest() async throws -> String { - if Task.isCancelled { - aiQueriesService?.cancelQuery() - throw CancellationError() - } - if let outputProvider { - let output = try await outputProvider(message, model) - let byteCount = output.utf8.count - let lineCount = output.split(whereSeparator: \.isNewline).count - if byteCount > maxOutputBytes || lineCount > maxOutputLines { - throw BenchmarkTaskExecutorError.outputBudgetExceeded - } - return output - } - guard let aiQueriesService else { - throw BenchmarkTaskExecutorError.missingModelService - } - // Benchmarks use cancelQuery() to cancel all streams for this executor - let (_, stream) = try await aiQueriesService.sendPrompt(message, model: model) - var collected = "" - do { - for try await chunk in stream { - if Task.isCancelled { - aiQueriesService.cancelQuery() - throw CancellationError() - } - if !chunk.text.isEmpty { - collected += chunk.text - let byteCount = collected.utf8.count - let lineCount = collected.split(whereSeparator: \.isNewline).count - if byteCount > maxOutputBytes || lineCount > maxOutputLines { - aiQueriesService.cancelQuery() - throw BenchmarkTaskExecutorError.outputBudgetExceeded - } - // Check for oversized search blocks (models echoing entire files) - if detectOversizedSearchBlock(in: collected) { - aiQueriesService.cancelQuery() - throw BenchmarkTaskExecutorError.searchBlockTooLarge - } - } - } - } catch is CancellationError { - aiQueriesService.cancelQuery() - throw CancellationError() - } - return collected.trimmingCharacters(in: .whitespacesAndNewlines) - } - - let maxAttempts = 3 - var attempt = 0 - var delay: TimeInterval = 1 - - while attempt < maxAttempts { - do { - return try await performRequest() - } catch { - if error is CancellationError { - aiQueriesService?.cancelQuery() - throw error - } - attempt += 1 - guard attempt < maxAttempts, shouldRetryAPIError(error) else { - throw error - } - aiQueriesService?.cancelQuery() - let cappedDelay = min(delay, 60) - let nanoseconds = UInt64(cappedDelay * 1_000_000_000) - try await Task.sleep(nanoseconds: nanoseconds) - delay = min(delay * 2, 60) - } - } - - throw BenchmarkTaskExecutorError.missingModelService - } - - /// Detect if any block exceeds the maximum allowed lines (30 lines) - /// This catches models trying to echo entire files in their search blocks - private func detectOversizedSearchBlock(in text: String) -> Bool { - // Pattern 1: blocks with === fences - let fencedPattern = #"\s*={3,}\s*\n(.*?)\n\s*={3,}\s*"# - if let regex = try? NSRegularExpression(pattern: fencedPattern, options: [.dotMatchesLineSeparators]) { - let range = NSRange(text.startIndex..., in: text) - let matches = regex.matches(in: text, options: [], range: range) - - for match in matches { - if match.numberOfRanges > 1, - let contentRange = Range(match.range(at: 1), in: text) - { - let content = String(text[contentRange]) - let lineCount = content.components(separatedBy: .newlines).count - if lineCount > 30 { - return true - } - } - } - } - - // Pattern 2: blocks without fences (plain pattern) - let plainPattern = #"(.*?)"# - if let regex = try? NSRegularExpression(pattern: plainPattern, options: [.dotMatchesLineSeparators]) { - let range = NSRange(text.startIndex..., in: text) - let matches = regex.matches(in: text, options: [], range: range) - - for match in matches { - if match.numberOfRanges > 1, - let contentRange = Range(match.range(at: 1), in: text) - { - let content = String(text[contentRange]).trimmingCharacters(in: .whitespacesAndNewlines) - let lineCount = content.components(separatedBy: .newlines).count - if lineCount > 30 { - return true - } - } - } - } - - return false - } - - private func composePackagedUserMessage( - task: BenchmarkTaskSpec, - fileSystem: BenchmarkMockFileSystem, - baseline: BenchmarkMockFileSystemSnapshot - ) -> PromptPackagingSnapshot { - // Scale context budget for harder difficulties (25% for hard, 50% for veryHard) - let effectiveMaxContextChars: Int = switch task.difficulty { - case .hard: - Int(Double(self.maxContextChars) * 1.25) - case .veryHard: - Int(Double(self.maxContextChars) * 1.5) - default: - self.maxContextChars - } - let maxContextChars = effectiveMaxContextChars - - let maxDecoys = max(0, task.params["maxDecoys"]?.intValue ?? 3) - let fullDecoyPaths = task.params["fullDecoys"]?.arrayValue?.compactMap(\.stringValue) ?? [] - let normalizedFullDecoys = fullDecoyPaths.map { BenchmarkMockFileSystem.normalize($0) } - let fullDecoySet = Set(normalizedFullDecoys) - - // Extract auto-planned decoys (prioritized over fullDecoys and computeContextPaths) - let decoyPaths = task.params["decoyPaths"]?.arrayValue?.compactMap(\.stringValue) ?? [] - let normalizedDecoyPaths = decoyPaths.map { BenchmarkMockFileSystem.normalize($0) } - - // Extract guidance verbosity - let hintVerbosity = task.params["hintVerbosity"]?.stringValue ?? "standard" - let candidatePaths = computeContextPaths(task: task, baseline: baseline, maxDecoys: maxDecoys) - let normalizedTargets = Set(task.selectFiles.map { BenchmarkMockFileSystem.normalize($0) }) - let normalizedCandidates = candidatePaths.map { BenchmarkMockFileSystem.normalize($0) } - let primaryPaths = normalizedCandidates.filter { normalizedTargets.contains($0) } - let decoyCandidates = normalizedCandidates.filter { !normalizedTargets.contains($0) } - let otherDecoys = decoyCandidates.filter { !fullDecoySet.contains($0) } - var totalChars = 0 - var blocks: [String] = [] - var seenFullDecoys: Set = [] - var seenDecoyPaths: Set = [] - var primaryList: [String] = [] - var includedAutoPlannedDecoys: [String] = [] - var includedFullDecoys: [String] = [] - var includedTrimmedDecoys: [String] = [] - var virtualFiles: [PromptVirtualFile] = [] - - func deduplicated(_ items: [String]) -> [String] { - var seen = Set() - return items.filter { seen.insert($0).inserted } - } - - func bulletList(_ items: [String]) -> String { - let values = deduplicated(items) - switch values.count { - case 0: - return "- (none)" - default: - return values.map { "- \($0)" }.joined(separator: "\n") - } - } - - func blockFor(path: String, content: String, role: String, truncated: Bool) -> String { - let normalized = BenchmarkMockFileSystem.normalize(path) - let fence = codeFenceStart(forPath: normalized, defaultLanguage: task.language) - let block = """ - File: \(normalized) - \(fence) - \(content) - ``` - """ - let trimmedBlock = block.trimmingCharacters(in: .whitespacesAndNewlines) - let virtualFile = PromptVirtualFile( - path: normalized, - content: content, - fence: fence, - role: role, - truncated: truncated, - block: trimmedBlock - ) - virtualFiles.append(virtualFile) - return trimmedBlock - } - - for path in primaryPaths { - let content = fileSystem.content(for: path) ?? baseline.content(for: path) ?? "" - let block = blockFor(path: path, content: content, role: "primary", truncated: false) - blocks.append(block.trimmingCharacters(in: .whitespacesAndNewlines)) - totalChars += content.count - primaryList.append(path) - } - - // Prioritize auto-planned decoys (decoyPaths) - these are problem-specific with identical cores - for path in normalizedDecoyPaths { - guard !normalizedTargets.contains(path), seenDecoyPaths.insert(path).inserted else { continue } - let remaining = maxContextChars - totalChars - guard remaining > 0 else { break } - let content = fileSystem.content(for: path) ?? baseline.content(for: path) ?? "" - let block = blockFor(path: path, content: content, role: "decoy_planned", truncated: false) - blocks.append(block.trimmingCharacters(in: .whitespacesAndNewlines)) - totalChars += content.count - includedAutoPlannedDecoys.append(path) - } - - for path in normalizedFullDecoys { - guard !normalizedTargets.contains(path), seenFullDecoys.insert(path).inserted else { continue } - let remaining = maxContextChars - totalChars - guard remaining > 0 else { break } - let content = fileSystem.content(for: path) ?? baseline.content(for: path) ?? "" - let block = blockFor(path: path, content: content, role: "decoy_full", truncated: false) - blocks.append(block.trimmingCharacters(in: .whitespacesAndNewlines)) - totalChars += content.count - includedFullDecoys.append(path) - } - - var addedDecoys = 0 - for path in otherDecoys { - guard addedDecoys < maxDecoys else { break } - guard !path.isEmpty else { continue } - let raw = fileSystem.content(for: path) ?? baseline.content(for: path) ?? "" - let remaining = maxContextChars - totalChars - guard remaining > 0 else { break } - let allowed = min(remaining, maxDecoyPerFileChars) - guard allowed > 0 else { break } - let truncated = raw.count > allowed - let content: String = if truncated { - String(raw.prefix(allowed)) - } else { - raw - } - let block = blockFor(path: path, content: content, role: truncated ? "decoy_trimmed" : "decoy", truncated: truncated) - blocks.append(block.trimmingCharacters(in: .whitespacesAndNewlines)) - totalChars += content.count - addedDecoys += 1 - includedTrimmedDecoys.append(path) - } - - let normalizedSelection = task.selectFiles.map { BenchmarkMockFileSystem.normalize($0) } - if primaryList.isEmpty { - primaryList = deduplicated(normalizedSelection) - } - let contextDecoys = deduplicated(includedAutoPlannedDecoys + includedFullDecoys + includedTrimmedDecoys) - - // Build Notes section based on guidance verbosity - let notesSection = switch hintVerbosity { - case "none": - "" - case "minimal": - """ - - Notes: - - Only edit the primary edit targets listed above. - """ - default: // "standard" or any other value - """ - - Notes: - - Some context files are intentional decoys that closely resemble the real targets—double-check the exact path before editing. - - Partial decoy files may be truncated. Editing them will cause verification to fail. - """ - } - - let targetDiscovery = task.params["targetDiscovery"]?.boolValue == true - let overviewSection = if targetDiscovery { - """ - - Candidate edit targets (exactly one should be edited): - \(bulletList(primaryList)) - - Context-only files included for reference: - \(bulletList(contextDecoys))\(notesSection) - - - """ - } else { - """ - - Primary edit targets (only modify these paths): - \(bulletList(primaryList)) - - Context-only files included for reference (do not edit unless promoted above): - \(bulletList(contextDecoys))\(notesSection) - - - """ - } - let joinedBlocks = blocks.joined(separator: "\n\n") - let fileSection = """ - - \(joinedBlocks) - - - """ - func parameterDetail(for task: BenchmarkTaskSpec) -> String? { - /// Helper functions for indentation - func leadingIndentation(of line: Substring) -> String { - var indent = "" - for char in line { - if char == " " || char == "\t" { - indent.append(char) - } else { - break - } - } - return indent - } - - func indentSnippet(_ value: String, indent: String) -> String { - guard !indent.isEmpty else { return value } - let lines = value.split(separator: "\n", omittingEmptySubsequences: false) - return lines.map { indent + String($0) }.joined(separator: "\n") - } - - func inferAnchorIndentation(in text: String, startToken: String, endToken: String, language: BenchmarkLanguage) -> String { - guard - let startRange = text.range(of: startToken), - let endRange = text.range(of: endToken, range: startRange.upperBound ..< text.endIndex) - else { - // Fallback to language default - return language == .swift ? "\t" : " " - } - - let interior = text[startRange.upperBound ..< endRange.lowerBound] - let lines = interior.split(separator: "\n", omittingEmptySubsequences: false) - if let sample = lines.first(where: { !$0.trimmingCharacters(in: .whitespaces).isEmpty }) { - return leadingIndentation(of: sample) - } - - // Try looking at the line after the end anchor - let tail = text[startRange.upperBound ..< text.endIndex] - if let newlineIndex = tail.firstIndex(of: "\n") { - let afterNewline = tail[tail.index(after: newlineIndex) ..< tail.endIndex] - if let nextLine = afterNewline.split(separator: "\n", maxSplits: 1, omittingEmptySubsequences: false).first { - return leadingIndentation(of: nextLine) - } - } - - // Fallback to language default - return language == .swift ? "\t" : " " - } - - switch task.type { - case .insertGuardTs, .insertGuardGo, .insertGuardSwift: - // Check for markerless mode first - if task.params["markerless"]?.boolValue == true, - let functionName = task.params["functionName"]?.stringValue, - let insertAfterPattern = task.params["insertAfterPattern"]?.stringValue, - let snippet = task.params["snippet"]?.stringValue - { - let indentType = task.language == .swift ? "tabs" : "4 spaces" - return """ - Locate the \(functionName)() function. Insert the provided snippet immediately after the first line containing '\(insertAfterPattern)'. - - Snippet to insert: - ```\(task.language.codeFenceIdentifier) - \(snippet) - ``` - - Your block must include: - - The function signature line (function \(functionName)(...) or func \(functionName)(...)) - - The line declaring '\(insertAfterPattern)' - - Multiple unchanged lines after the insertion point for context - - Total: 5–8 lines (strongly recommended due to multiple near-match regions) - - Note: This file contains many near-identical code fragments. Short search blocks will be ambiguous. Include both the function signature and multiple body lines to ensure uniqueness. - - Use \(indentType) for indentation. Only modify the \(functionName)() function—other functions (like \(functionName)Positive, \(functionName)Bounded) must remain unchanged. - """ - } - - // Multi-file array format - if let items = task.params["guards"]?.arrayValue, !items.isEmpty { - var sections: [String] = [] - sections.append("Insert the provided guard block in each file immediately after its ANCHOR:start:, keeping anchors unchanged and using the file's indentation (TS/Go: 4 spaces, Swift: tabs).") - - for item in items { - guard - let obj = item.objectValue, - let path = obj["path"]?.stringValue, - let uid = obj["uid"]?.stringValue, - let rawSnippet = obj["snippet"]?.stringValue - else { continue } - - let startToken = "// ANCHOR:start:\(uid)" - let endToken = "// ANCHOR:end:\(uid)" - let sourceText = fileSystem.content(for: path) ?? baseline.content(for: path) ?? "" - - let indent = inferAnchorIndentation(in: sourceText, startToken: startToken, endToken: endToken, language: task.language) - let indented = indentSnippet(rawSnippet, indent: indent) - - sections.append(""" - File: \(path) - UID: \(uid) - - Anchors (verbatim): - START: \(startToken) - END : \(endToken) - - Insert this snippet between those anchors exactly as shown: - ```\(task.language.codeFenceIdentifier) - \(indented) - ``` - - Your must include at least 3 lines. Example (start anchor + empty line + end anchor): - \(startToken) - - \(endToken) - """) - } - return sections.joined(separator: "\n\n") - } - - // Single-file fallback - guard - let uid = task.params["uid"]?.stringValue, - let snippet = task.params["snippet"]?.stringValue - else { - return nil - } - let startToken = "// ANCHOR:start:\(uid)" - let endToken = "// ANCHOR:end:\(uid)" - let path = task.selectFiles.first - let sourceText: String? = { - guard let path else { return nil } - return fileSystem.content(for: path) ?? baseline.content(for: path) - }() - let inferredIndent: String = { - guard let sourceText else { return "" } - return inferAnchorIndentation(in: sourceText, startToken: startToken, endToken: endToken, language: task.language) - }() - let indentedSnippet = indentSnippet(snippet, indent: inferredIndent) - return """ - Anchors (verbatim): - START: // ANCHOR:start:\(uid) - END : // ANCHOR:end:\(uid) - - Insert this snippet between those anchors exactly as shown (no tabs; indentation already matches the file): - ```\(task.language.codeFenceIdentifier) - \(indentedSnippet) - ``` - - Your must include at least 3 lines. Example (start anchor + empty line + end anchor): - // ANCHOR:start:\(uid) - - // ANCHOR:end:\(uid) - """ - case .indexOnlyAppsTs, .indexOnlyAppsGo, .indexOnlyAppsSwift: - guard let target = task.params["target"]?.stringValue else { return nil } - let signature: String - let ret: String - switch task.language { - case .ts: - signature = "export default function index() { ... }" - ret = "return \"\(target)\";" - case .go: - signature = "func index() { ... }" - ret = "return \"\(target)\"" - case .swift: - signature = "func index() { ... }" - ret = "return \"\(target)\"" - } - return """ - Required marker (verbatim): `// DONE:\(target)` - Placement: line immediately after `\(ret)` inside `\(signature)`. - The return line must stay exactly `\(ret)`. - """ - case .swapArgsInRegionTs, .swapArgsInRegionGo, .swapArgsInRegionSwift: - if task.params["markerless"]?.boolValue == true { - guard let functionName = task.params["functionName"]?.stringValue - else { - return nil - } - let indentNote = task.language == .swift ? "tabs" : "4 spaces" - return """ - Locate the function named '\(functionName)' in each file. - Swap all use(a, b) calls to use(b, a) within this function. - Your block must include: at least one unchanged line before the first use() call, all use() calls, and at least one unchanged line after (3-8 lines total). - Use \(indentNote) for indentation. - Do NOT modify other functions or code outside '\(functionName)'. - """ - } else { - guard - let uid = task.params["uid"]?.stringValue, - let expected = task.params["expectedSwaps"]?.intValue - else { - return nil - } - return """ - Only modify the region between these markers (UID=\(uid)): - /* START_SWAP:\(uid) */ - /* END_SWAP:\(uid) */ - - Change calls inside to `use(b, a)` and perform exactly \(expected) swaps. Everything outside stays identical. - - **Important**: If the region exceeds 8 lines, split it into multiple blocks (e.g., 2 lines before + 5 target lines + 1 after = 8 total). Decoy regions with different UIDs must remain unchanged. - """ - } - case .renameExportImportsTs, .renameExportImportsGo, .renameExportImportsSwift: - guard - let rename = task.params["rename"]?.objectValue, - let from = rename["from"]?.stringValue, - let to = rename["to"]?.stringValue - else { - return nil - } - let importerPaths = task.params["importPaths"]?.arrayValue?.compactMap(\.stringValue) ?? [] - let allowed = ([task.selectFiles.first].compactMap(\.self) + importerPaths).filter { !$0.isEmpty } - let allowedList = allowed.map { "- \($0)" }.joined(separator: "\n") - return """ - Rename `\(from)` to `\(to)` in the exporter and only these importers: - \(allowedList) - Do not touch files outside this list. - """ - case .moveFunctionTs, .moveFunctionGo, .moveFunctionSwift: - guard - let from = task.params["fromName"]?.stringValue, - let after = task.params["afterName"]?.stringValue, - let path = task.selectFiles.first - else { - return nil - } - return """ - File: \(path) - - Move the entire function `\(from)(...) { ... }` so it appears **after** the function `\(after)(...) { ... }`. - - Do NOT duplicate; remove it from its original location. - - Make no other edits. - """ - case .insertFunctionBottomTs, .insertFunctionBottomGo, .insertFunctionBottomSwift: - // Multi-file array format - if let inserts = task.params["inserts"]?.arrayValue, !inserts.isEmpty { - var sections: [String] = [] - sections.append("Insert each snippet immediately above the footer marker in the specified file(s). Keep all existing lines unchanged. Maintain exactly one blank line between the last existing function and the snippet, and one blank line before the footer.") - - for entry in inserts { - guard - let obj = entry.objectValue, - let path = obj["path"]?.stringValue, - let snippet = obj["snippet"]?.stringValue - else { continue } - - let footer = obj["footer"]?.stringValue ?? (task.params["footer"]?.stringValue ?? "// END-OF-FILE") - - sections.append(""" - File: \(path) - Footer marker (verbatim): - \(footer) - - Snippet to insert: - ```\(task.language.codeFenceIdentifier) - \(snippet) - ``` - """) - } - return sections.joined(separator: "\n\n") - } - - // Single-file fallback - guard - let snippet = task.params["snippet"]?.stringValue, - let footer = task.params["footer"]?.stringValue, - let path = task.selectFiles.first - else { - return nil - } - return """ - Append the following snippet **immediately above** `\(footer)` in \(path): - - ```\(task.language.codeFenceIdentifier) - \(snippet) - ``` - - Keep all existing lines identical. - """ - case .applyUnifiedPatchTs, .applyUnifiedPatchGo, .applyUnifiedPatchSwift: - let targetDiscovery = task.params["targetDiscovery"]?.boolValue == true - guard let patch = task.params["patch"]?.stringValue else { - return nil - } - - if targetDiscovery { - return """ - Apply this unified diff to exactly one file among the candidate edit targets below. Identify the correct file by matching the patch hunks to the file contents. Patch headers hide the file path on purpose. - - ```diff - \(patch) - ``` - - Rules: - - Edit exactly one file from the candidates list; do not modify others. - - Emit a minimal set of blocks to realize these hunks; avoid rewriting entire files. - - Search blocks must be precise enough to disambiguate near-identical regions across files. - """ - } else { - guard let path = task.selectFiles.first else { - return nil - } - return """ - Apply this unified diff to `\(path)` **exactly** (no more, no less): - - ```diff - \(patch) - ``` - - Emit a minimal set of blocks to realize these hunks; do not rewrite the whole file. - """ - } - case .curlyFixTs, .curlyFixGo, .curlyFixSwift: - guard let path = task.params["file"]?.stringValue else { return nil } - return """ - Target file: \(path) - Resolve any structural imbalance so the code compiles and the log/print occurs exactly once after iteration completes. - Make focused edits only; avoid rewriting or reformatting existing lines. - """ - case .patchBlockTs, .patchBlockGo, .patchBlockSwift: - if task.params["markerless"]?.boolValue == true { - guard let functionName = task.params["functionName"]?.stringValue, - let snippet = task.params["snippet"]?.stringValue - else { - return nil - } - let indentNote = task.language == .swift ? "tabs" : "4 spaces" - return """ - Locate the function named '\(functionName)' in each file. - Replace its entire body with the provided snippet. - Your block must include: the function signature line, at least one line from the existing body, and the closing brace (3-8 lines total). - Use \(indentNote) for indentation. - Do NOT modify other functions or code outside '\(functionName)'. - Emit unchanged outer lines verbatim in . - - Snippet: - ```\(task.language.codeFenceIdentifier) - \(snippet) - ``` - """ - } else { - guard - let uid = task.params["uid"]?.stringValue, - let snippet = task.params["snippet"]?.stringValue - else { - return nil - } - return """ - Only modify the body between these exact markers: - /* BLOCK START:\(uid) */ - /* BLOCK END:\(uid) */ - - Replace the body with the snippet below (verbatim). Do **not** touch the marker lines themselves. - - ```\(task.language.codeFenceIdentifier) - \(snippet) - ``` - """ - } - case .removeXTs, .removeXGo, .removeXSwift: - return nil - } - } - var instructionSections: [String] = [task.task] - if !task.instructions.isEmpty { - let bullet = task.instructions.map { "- \($0)" }.joined(separator: "\n") - instructionSections.append("Instructions:\n\(bullet)") - } - if !task.acceptance.isEmpty { - let bullet = task.acceptance.map { "- \($0)" }.joined(separator: "\n") - instructionSections.append("Acceptance:\n\(bullet)") - } - if let detail = parameterDetail(for: task) { - instructionSections.append(detail) - } - let instructionBody = instructionSections.joined(separator: "\n\n") - let instructionsSection = """ - - \(instructionBody) - - """ - let contract = """ - - - - Output ONLY blocks; no prose outside the XML. - - For each , include a block that EXACTLY matches baseline lines (whitespace included). - - Reusing an identical advances past the prior match; if the file has only one such region, the second change fails. Combine related edits into one or adjust the context. - - Each MUST include 3–8 lines (one unchanged line before, target line(s), one unchanged after). Exception: rename_export_and_imports_* may use 2 lines due to tiny barrel files. If a target region exceeds 8 lines, split into multiple blocks with 3–8 lines each. - - Do NOT emit files outside the provided selection. - - Total output budget is small; avoid echoing full files. Oversized output fails the task. - - """ - let prompt = overviewSection + fileSection + instructionsSection + contract - return PromptPackagingSnapshot(prompt: prompt, virtualFiles: virtualFiles) - } - - private func computeContextPaths( - task: BenchmarkTaskSpec, - baseline: BenchmarkMockFileSystemSnapshot, - maxDecoys: Int = 3 - ) -> [String] { - var ordered: [String] = [] - var seen = Set() - for path in task.selectFiles { - let normalized = BenchmarkMockFileSystem.normalize(path) - guard !normalized.isEmpty else { continue } - if seen.insert(normalized).inserted { - ordered.append(normalized) - } - } - let extensionSuffix = switch task.language { - case .ts: - ".ts" - case .go: - ".go" - case .swift: - ".swift" - } - let targetRoots = Set(task.selectFiles.map { BenchmarkMockFileSystem.normalize($0).split(separator: "/").first.map(String.init) ?? "" }) - let decoyCandidates = baseline.allPaths - .filter { !seen.contains($0) && $0.hasSuffix(extensionSuffix) } - // Prefer decoys from different top-level roots to stress path retention - .sorted { lhs, rhs in - let lroot = lhs.split(separator: "/").first.map(String.init) ?? "" - let rroot = rhs.split(separator: "/").first.map(String.init) ?? "" - let lprio = targetRoots.contains(lroot) ? 1 : 0 - let rprio = targetRoots.contains(rroot) ? 1 : 0 - if lprio == rprio { - return lhs < rhs - } - return lprio < rprio - } - for path in decoyCandidates.prefix(maxDecoys) { - if seen.insert(path).inserted { - ordered.append(path) - } - } - return ordered - } - - private func codeFenceStart(forPath path: String, defaultLanguage: BenchmarkLanguage) -> String { - let ext = URL(fileURLWithPath: path).pathExtension - if ext.isEmpty { - let identifier = defaultLanguage.codeFenceIdentifier - return identifier.isEmpty ? "```" : "```\(identifier)" - } - return PromptPackagingService.codeFenceStart(for: path) - } -} - -private func shouldRetryAPIError(_ error: Error) -> Bool { - if let providerError = error as? AIProviderError { - if case .apiError = providerError { - return true - } - return false - } - - if let customError = error as? CustomOpenAIProviderError { - switch customError { - case .rateLimitExceeded, .serverError, .serviceUnavailable: - return true - case let .requestFailed(statusCode: status, _), - let .invalidResponse(statusCode: status, _): - return [408, 429, 500, 502, 503, 504].contains(status) - default: - return false - } - } - - if error is SwiftOpenAI.APIError || error is SwiftAnthropic.APIError { - return true - } - - if error is URLError { - return true - } - let nsError = error as NSError - return nsError.domain == NSURLErrorDomain -} - -private struct PromptPackagingSnapshot { - let prompt: String - let virtualFiles: [PromptVirtualFile] -} - -private struct PromptVirtualFile { - let path: String - let content: String - let fence: String - let role: String - let truncated: Bool - let block: String - - func blockText() -> String { - block - } - - func metaValue() -> BenchmarkJSONValue { - .object([ - "path": .string(path), - "content": .string(content), - "fence": .string(fence), - "role": .string(role), - "truncated": .boolean(truncated), - "block": .string(block) - ]) - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkTaskGenerator.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkTaskGenerator.swift deleted file mode 100644 index 44979072d..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkTaskGenerator.swift +++ /dev/null @@ -1,8180 +0,0 @@ -import Foundation - -private struct BenchmarkNoiseConfig { - let lineCount: Int -} - -struct BenchmarkGeneratedSeed { - let seed: UInt32 - var fileSystem: BenchmarkMockFileSystem - let baseline: BenchmarkMockFileSystemSnapshot - let tasks: [BenchmarkTaskSpec] -} - -struct BenchmarkTaskGenerator { - /// Primary TypeScript work file for cumulative tasks. - private let tsWorkPath = "src/ts/work/Work.ts" - private let swiftWorkPath = "src/swift/work/Work.swift" - private let goWorkPath = "src/go/work/Work.go" - - func generateSeed(_ seed: UInt32, config: BenchConfig, language: BenchmarkLanguage? = nil, subseedIndex: Int = 0) -> BenchmarkGeneratedSeed { - var rng = Mulberry32(seed: seed) - var fileSystem = BenchmarkMockFileSystem() - var tasks: [BenchmarkTaskSpec] = [] - let noiseConfig = BenchmarkNoiseConfig(lineCount: targetLineCount(config)) - let taskLanguage = language ?? .ts - - // Pre-warm project once per seed - let layout = BenchmarkProjectScaffolder.scaffoldProject( - language: taskLanguage, - rng: &rng, - config: config, - noise: noiseConfig.lineCount, - into: &fileSystem - ) - - let difficulties = config.difficultyPlan() - let casePlan = scheduleCasePlan(for: taskLanguage, difficulties: difficulties, subseedIndex: subseedIndex, enabled: config.enabledTypes) - - for (index, difficulty) in difficulties.enumerated() { - guard index < casePlan.count else { break } - let caseType = casePlan[index] - if let task = generateTask( - of: caseType, - difficulty: difficulty, - rng: &rng, - fileSystem: &fileSystem, - config: config, - noise: noiseConfig, - language: taskLanguage, - layout: layout - ) { - tasks.append(task) - } - } - - let baseline = fileSystem.snapshot() - return BenchmarkGeneratedSeed(seed: seed, fileSystem: fileSystem, baseline: baseline, tasks: tasks) - } - - private func balancedTypePlan(count: Int, enabled: [BenchmarkCaseType], rng: inout Mulberry32, language: BenchmarkLanguage?) -> [BenchmarkCaseType] { - guard count > 0, !enabled.isEmpty else { return [] } - let filtered = language.flatMap { lang -> [BenchmarkCaseType]? in - let subset = enabled.filter { typeBelongsToLanguage($0, lang) } - return subset.isEmpty ? nil : subset - } ?? enabled - guard !filtered.isEmpty else { return [] } - let order = filtered - var planned: [BenchmarkCaseType] = [] - while planned.count < count { - planned.append(contentsOf: order) - } - if planned.count > count { - planned.removeLast(planned.count - count) - } - return planned - } - - // MARK: - Difficulty-aware case scheduling - - private func casePool(for language: BenchmarkLanguage, difficulty: BenchmarkDifficulty) -> [BenchmarkCaseType] { - switch (language, difficulty) { - case (.ts, .medium): - [.renameExportImportsTs, .removeXTs, .swapArgsInRegionTs, .indexOnlyAppsTs, .curlyFixTs] - case (.ts, .hard): - [.patchBlockTs, .insertGuardTs, .insertFunctionBottomTs] - case (.ts, .veryHard): - [.applyUnifiedPatchTs, .moveFunctionTs] - case (.go, .medium): - [.renameExportImportsGo, .removeXGo, .swapArgsInRegionGo, .indexOnlyAppsGo, .curlyFixGo, .moveFunctionGo] - case (.go, .hard): - [.insertFunctionBottomGo, .insertGuardGo, .patchBlockGo, .applyUnifiedPatchGo] - case (.go, .veryHard): - // Promote to VH using existing "VeryHard gauntlet" and multi-file exactness - [.applyUnifiedPatchGo, .patchBlockGo] - case (.swift, .medium): - [.swapArgsInRegionSwift, .indexOnlyAppsSwift, .insertFunctionBottomSwift] - case (.swift, .hard): - [.moveFunctionSwift, .insertGuardSwift, .patchBlockSwift] - case (.swift, .veryHard): - // Provide VH options: unified patch gauntlet, exact block patching, and strengthened move_function - [.applyUnifiedPatchSwift, .patchBlockSwift, .moveFunctionSwift] - default: - [] - } - } - - private func scheduleCasePlan(for language: BenchmarkLanguage, difficulties: [BenchmarkDifficulty], subseedIndex: Int, enabled: [BenchmarkCaseType] = []) -> [BenchmarkCaseType] { - var arr: [BenchmarkCaseType] = [] - - for (i, diff) in difficulties.enumerated() { - func pick(from pool: [BenchmarkCaseType]) -> BenchmarkCaseType? { - let filtered = pool.filter { enabled.isEmpty || enabled.contains($0) } - guard !filtered.isEmpty else { return nil } - let idx = (subseedIndex + i) % filtered.count - return filtered[idx] - } - - // Try native pool for current difficulty - var pool = casePool(for: language, difficulty: diff) - if let chosen = pick(from: pool) { - arr.append(chosen) - continue - } - - // Fallback to next-lower difficulty - if diff == .veryHard { - pool = casePool(for: language, difficulty: .hard) - if let chosen = pick(from: pool) { - arr.append(chosen) - continue - } - } - if diff == .hard { - pool = casePool(for: language, difficulty: .medium) - if let chosen = pick(from: pool) { - arr.append(chosen) - continue - } - } - - // Last resort: any enabled case for language - let anyEnabledForLanguage = BenchmarkCaseType.allCases.filter { - typeBelongsToLanguage($0, language) && (enabled.isEmpty || enabled.contains($0)) - } - if let fallback = anyEnabledForLanguage.first { - arr.append(fallback) - } - // else: omit slot - } - return arr - } - - /// Legacy scheduler (kept for backward compatibility if needed) - private func scheduleCasePlan(for language: BenchmarkLanguage, count: Int, subseedIndex: Int = 0) -> [BenchmarkCaseType] { - let canonical: [BenchmarkCaseType] = switch language { - case .ts: - // TypeScript: 5 subseeds × 6 tasks = 30 tasks - // Weighted to repeat more challenging tasks across all subseeds - // Each subseed has diverse coverage of task types - // Subseed 1: curlyFix, insertGuard, patchBlock, rename, swapArgs, patch - // Subseed 2: moveFunction, removeX, insertBottom, rename, indexOnly, patch - // Subseed 3: swapArgs, patchBlock, moveFunction, indexOnly, insertBottom, patch - // Subseed 4: removeX, insertGuard, rename, patchBlock, moveFunction, patch - // Subseed 5: curlyFix, swapArgs, insertBottom, rename, indexOnly, patch - [ - // Subseed 1 (warmup + fundamentals) - .curlyFixTs, - .insertGuardTs, - .patchBlockTs, - .renameExportImportsTs, - .swapArgsInRegionTs, - .applyUnifiedPatchTs, - // Subseed 2 (advanced multi-file) - .moveFunctionTs, - .removeXTs, - .insertFunctionBottomTs, - .renameExportImportsTs, - .indexOnlyAppsTs, - .applyUnifiedPatchTs, - // Subseed 3 (mixed complexity) - .swapArgsInRegionTs, - .patchBlockTs, - .moveFunctionTs, - .indexOnlyAppsTs, - .insertFunctionBottomTs, - .applyUnifiedPatchTs, - // Subseed 4 (comprehensive coverage) - .removeXTs, - .insertGuardTs, - .renameExportImportsTs, - .patchBlockTs, - .moveFunctionTs, - .applyUnifiedPatchTs, - // Subseed 5 (reinforcement) - .curlyFixTs, - .swapArgsInRegionTs, - .insertFunctionBottomTs, - .renameExportImportsTs, - .indexOnlyAppsTs, - .applyUnifiedPatchTs - ] - case .go: - // Go: 5 subseeds × 6 tasks = 30 tasks - // Comprehensive coverage across all task types - [ - // Subseed 1 (warmup + fundamentals) - .curlyFixGo, - .insertGuardGo, - .patchBlockGo, - .renameExportImportsGo, - .swapArgsInRegionGo, - .applyUnifiedPatchGo, - // Subseed 2 (advanced multi-file) - .moveFunctionGo, - .removeXGo, - .insertFunctionBottomGo, - .renameExportImportsGo, - .indexOnlyAppsGo, - .applyUnifiedPatchGo, - // Subseed 3 (mixed complexity) - .swapArgsInRegionGo, - .patchBlockGo, - .moveFunctionGo, - .indexOnlyAppsGo, - .insertFunctionBottomGo, - .applyUnifiedPatchGo, - // Subseed 4 (comprehensive coverage) - .removeXGo, - .insertGuardGo, - .renameExportImportsGo, - .patchBlockGo, - .moveFunctionGo, - .applyUnifiedPatchGo, - // Subseed 5 (reinforcement) - .curlyFixGo, - .swapArgsInRegionGo, - .insertFunctionBottomGo, - .renameExportImportsGo, - .indexOnlyAppsGo, - .applyUnifiedPatchGo - ] - case .swift: - // Swift: 5 subseeds × 6 tasks = 30 tasks - // Comprehensive coverage across all task types - [ - // Subseed 1 (warmup + fundamentals) - .curlyFixSwift, - .insertGuardSwift, - .patchBlockSwift, - .renameExportImportsSwift, - .swapArgsInRegionSwift, - .applyUnifiedPatchSwift, - // Subseed 2 (advanced multi-file) - .moveFunctionSwift, - .removeXSwift, - .insertFunctionBottomSwift, - .renameExportImportsSwift, - .indexOnlyAppsSwift, - .applyUnifiedPatchSwift, - // Subseed 3 (mixed complexity) - .swapArgsInRegionSwift, - .patchBlockSwift, - .moveFunctionSwift, - .indexOnlyAppsSwift, - .insertFunctionBottomSwift, - .applyUnifiedPatchSwift, - // Subseed 4 (comprehensive coverage) - .removeXSwift, - .insertGuardSwift, - .renameExportImportsSwift, - .patchBlockSwift, - .moveFunctionSwift, - .applyUnifiedPatchSwift, - // Subseed 5 (reinforcement) - .curlyFixSwift, - .swapArgsInRegionSwift, - .insertFunctionBottomSwift, - .renameExportImportsSwift, - .indexOnlyAppsSwift, - .applyUnifiedPatchSwift - ] - } - - // Calculate the starting index for this subseed - let startIndex = subseedIndex * count - - // If the requested slice fits within canonical, return it - if startIndex + count <= canonical.count { - return Array(canonical[startIndex ..< (startIndex + count)]) - } - - // Otherwise, wrap around and repeat the canonical plan as needed - var plan: [BenchmarkCaseType] = [] - for i in 0 ..< count { - let index = (startIndex + i) % canonical.count - plan.append(canonical[index]) - } - return plan - } - - private func typeBelongsToLanguage(_ type: BenchmarkCaseType, _ language: BenchmarkLanguage) -> Bool { - switch type { - case .curlyFixGo: - language == .go - default: - true - } - } - - private func targetLineCount(_ config: BenchConfig) -> Int { - if let override = config.sizeLines { return max(override, 40) } - switch config.size { - case .small: return 120 - case .medium: return 320 - case .large: return 640 - } - } - - // MARK: - Task routing - - private func generateTask( - of type: BenchmarkCaseType, - difficulty: BenchmarkDifficulty, - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - language: BenchmarkLanguage, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec? { - switch type { - case .removeXTs, .removeXGo, .removeXSwift: - generateRemoveXTaskFor( - language: language, - rng: &rng, - fileSystem: &fileSystem, - config: config, - noise: noise, - difficulty: difficulty, - layout: layout - ) - case .curlyFixTs, .curlyFixGo, .curlyFixSwift: - generateCurlyFixTaskFor( - language: language, - rng: &rng, - fileSystem: &fileSystem, - config: config, - noise: noise, - difficulty: difficulty, - layout: layout - ) - case .insertGuardTs, .insertGuardGo, .insertGuardSwift: - generateInsertGuardTaskFor( - language: language, - rng: &rng, - fileSystem: &fileSystem, - config: config, - noise: noise, - difficulty: difficulty, - layout: layout - ) - case .patchBlockTs, .patchBlockGo, .patchBlockSwift: - generatePatchBlockTaskFor( - language: language, - rng: &rng, - fileSystem: &fileSystem, - config: config, - noise: noise, - difficulty: difficulty, - layout: layout - ) - case .swapArgsInRegionTs, .swapArgsInRegionGo, .swapArgsInRegionSwift: - generateSwapArgsTaskFor( - language: language, - rng: &rng, - fileSystem: &fileSystem, - config: config, - noise: noise, - difficulty: difficulty, - layout: layout - ) - case .indexOnlyAppsTs, .indexOnlyAppsGo, .indexOnlyAppsSwift: - generateIndexOnlyTaskFor( - language: language, - rng: &rng, - fileSystem: &fileSystem, - config: config, - difficulty: difficulty, - layout: layout - ) - case .renameExportImportsTs, .renameExportImportsGo, .renameExportImportsSwift: - generateRenameExportsTaskFor( - language: language, - rng: &rng, - fileSystem: &fileSystem, - config: config, - difficulty: difficulty, - layout: layout - ) - case .moveFunctionTs, .moveFunctionGo, .moveFunctionSwift: - generateMoveFunctionTaskFor( - language: language, - rng: &rng, - fileSystem: &fileSystem, - config: config, - difficulty: difficulty, - layout: layout - ) - case .insertFunctionBottomTs, .insertFunctionBottomGo, .insertFunctionBottomSwift: - generateInsertFunctionBottomTaskFor( - language: language, - rng: &rng, - fileSystem: &fileSystem, - config: config, - difficulty: difficulty, - layout: layout - ) - case .applyUnifiedPatchTs, .applyUnifiedPatchGo, .applyUnifiedPatchSwift: - generateUnifiedPatchTaskFor( - language: language, - rng: &rng, - fileSystem: &fileSystem, - config: config, - difficulty: difficulty, - layout: layout - ) - } - } - - // MARK: - Language dispatch helpers - - private func generateRemoveXTaskFor( - language: BenchmarkLanguage, - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - switch language { - case .ts: - generateRemoveXTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - case .go: - generateRemoveXGoTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - case .swift: - generateRemoveXSwiftTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - } - } - - private func generateCurlyFixTaskFor( - language: BenchmarkLanguage, - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - switch language { - case .ts: - generateCurlyFixTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - case .go: - generateCurlyFixGoTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - case .swift: - generateCurlyFixSwiftTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - } - } - - private func generateInsertGuardTaskFor( - language: BenchmarkLanguage, - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - switch language { - case .ts: - generateInsertGuardTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - case .go: - generateInsertGuardGoTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - case .swift: - generateInsertGuardSwiftTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - } - } - - private func generatePatchBlockTaskFor( - language: BenchmarkLanguage, - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - switch language { - case .ts: - generatePatchBlockTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - case .go: - generatePatchBlockGoTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - case .swift: - generatePatchBlockSwiftTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - } - } - - private func generateSwapArgsTaskFor( - language: BenchmarkLanguage, - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - switch language { - case .ts: - generateSwapArgsTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - case .go: - generateSwapArgsGoTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - case .swift: - generateSwapArgsSwiftTask(rng: &rng, fileSystem: &fileSystem, config: config, noise: noise, difficulty: difficulty, layout: layout) - } - } - - private func generateIndexOnlyTaskFor( - language: BenchmarkLanguage, - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - switch language { - case .ts: - generateIndexOnlyTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - case .go: - generateIndexOnlyGoTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - case .swift: - generateIndexOnlySwiftTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - } - } - - private func generateRenameExportsTaskFor( - language: BenchmarkLanguage, - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - switch language { - case .ts: - generateRenameExportsTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - case .go: - generateRenameExportsGoTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - case .swift: - generateRenameExportsSwiftTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - } - } - - private func generateMoveFunctionTaskFor( - language: BenchmarkLanguage, - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - switch language { - case .ts: - generateMoveFunctionTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - case .go: - generateMoveFunctionGoTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - case .swift: - generateMoveFunctionSwiftTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - } - } - - private func generateInsertFunctionBottomTaskFor( - language: BenchmarkLanguage, - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - switch language { - case .ts: - generateInsertFunctionBottomTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - case .go: - generateInsertFunctionBottomGoTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - case .swift: - generateInsertFunctionBottomSwiftTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - } - } - - private func generateUnifiedPatchTaskFor( - language: BenchmarkLanguage, - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - switch language { - case .ts: - generateUnifiedPatchTsTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - case .go: - generateUnifiedPatchGoTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - case .swift: - generateUnifiedPatchSwiftTask(rng: &rng, fileSystem: &fileSystem, config: config, difficulty: difficulty, layout: layout) - } - } - - // MARK: - remove_x_ts - - private func generateRemoveXTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let path = tsWorkPath - let decoyCount = decoyCount(for: difficulty, config: config) - var existing = fileSystem.content(for: path) ?? "" - var lines: [String] = [] - lines.append("export function alpha(values: number[]): number {") - lines.append(" let total = 0;") - lines.append(" for (const value of values) {") - lines.append(" total += CALL_X(value);") - if difficultyIsAtLeastHard(difficulty) { - lines.append(" total += call_x(value); // near miss lower case") - lines.append(" total += CALL_XY(value); // near miss suffix") - lines.append(" if (value % 2 === 0) {") - lines.append(" const computed = CALL_X(CALL_X(value * 2));") - lines.append(" total += computed;") - lines.append(" // CALL_X should be removed even in comments? Keep comment text") - lines.append(" }") - } - lines.append(" }") - lines.append(" return total;") - lines.append("}") - if !existing.isEmpty && !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(lines.joined(separator: "\n")) - if !existing.contains("/* auto-generated believable TS module */") { - let approxLines = scaledLines(noise.lineCount, difficulty: .hard) - let helper = BelievableCodeFactory.tsUtilityModule(rng: &rng, module: "AlphaSupport", approxLines: approxLines) - existing.append("\n") - existing.append(helper) - } - fileSystem.setFile(path, content: existing) - let fullDecoys = decoyCount > 0 ? makeSimilarTsDecoys(rng: &rng, around: path) : [] - for decoy in fullDecoys { - fileSystem.setFile(decoy.path, content: decoy.content) - } - let instructions = [ - "Remove all CALL_X() invocations from the specified file using search-replace edits.", - "CALL_X appears with an opening parenthesis: CALL_X(value).", - "Do NOT modify near-miss tokens: call_x (lowercase) or CALL_XY (different suffix).", - "Do NOT remove CALL_X from comments - only remove actual function calls.", - "Do NOT rewrite the entire file - use targeted edits only." - ] - let acceptance = [ - "All CALL_X() invocations are removed from \(path).", - "Other identifiers including call_x and CALL_XY remain unchanged.", - "Comments containing CALL_X text remain unchanged." - ] - var params: [String: BenchmarkJSONValue] = [ - "target": .string("CALL_X("), - "file": .string(path), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoyCount) - ] - if !fullDecoys.isEmpty { - params["fullDecoys"] = .array(fullDecoys.map { .string($0.path) }) - } - - if config.includeAutoPlannedDecoys { - // Draft the task spec (pre-return) so the planner can locate the core region using current params - let draftTask = BenchmarkTaskSpec( - id: "remove_x_ts", - type: .removeXTs, - language: .ts, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEditsRemoveX(for: difficulty), - instructions: instructions, - task: "Remove all CALL_X() invocations from \(path).", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - // Store guidance verbosity to adjust prompt tone in the packager - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "remove_x_ts", - type: .removeXTs, - language: .ts, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEditsRemoveX(for: difficulty), - instructions: instructions, - task: "Remove all CALL_X() invocations from \(path).", - acceptance: acceptance, - params: params - ) - } - - // MARK: - remove_x_go - - private func generateRemoveXGoTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let path = goWorkPath - let decoyCount = decoyCount(for: difficulty, config: config) - _ = noise - var lines: [String] = [] - lines.append("package work") - lines.append("") - lines.append("func Alpha(values []int) int {") - lines.append(" total := 0") - lines.append(" for _, value := range values {") - lines.append(" total += CALL_X(value)") - if difficultyIsAtLeastHard(difficulty) { - lines.append(" total += call_x(value) // near miss lower case") - lines.append(" total += CALL_XY(value) // near miss suffix") - lines.append(" if value%2 == 0 {") - lines.append(" computed := CALL_X(CALL_X(value * 2))") - lines.append(" total += computed") - lines.append(" // CALL_X should be removed even in comments? Keep comment text") - lines.append(" }") - } - lines.append(" }") - lines.append(" return total") - lines.append("}") - var existing = fileSystem.content(for: path) ?? "" - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(lines.joined(separator: "\n")) - fileSystem.setFile(path, content: existing) - var decoyPaths: [String] = [] - if decoyCount >= 1 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "WorkShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoyCount >= 2 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "WorkClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - let instructions = [ - "Remove all CALL_X() invocations from the specified file using search-replace edits.", - "CALL_X appears with an opening parenthesis: CALL_X(value).", - "Do NOT modify near-miss tokens: call_x (lowercase) or CALL_XY (different suffix).", - "Do NOT remove CALL_X from comments - only remove actual function calls.", - "Do NOT rewrite the entire file - use targeted edits only." - ] - let acceptance = [ - "All CALL_X() invocations are removed from \(path).", - "Other identifiers including call_x and CALL_XY remain unchanged.", - "Comments containing CALL_X text remain unchanged." - ] - var params: [String: BenchmarkJSONValue] = [ - "target": .string("CALL_X("), - "file": .string(path), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoyCount) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "remove_x_go", - type: .removeXGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEditsRemoveX(for: difficulty), - instructions: instructions, - task: "Remove all CALL_X() invocations from \(path).", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "remove_x_go", - type: .removeXGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEditsRemoveX(for: difficulty), - instructions: instructions, - task: "Remove all CALL_X() invocations from \(path).", - acceptance: acceptance, - params: params - ) - } - - // MARK: - remove_x_swift - - private func generateRemoveXSwiftTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let path = swiftWorkPath - let decoyCount = decoyCount(for: difficulty, config: config) - _ = noise - var lines: [String] = [] - lines.append("public func alpha(_ values: [Int]) -> Int {") - lines.append("\tvar total = 0") - lines.append("\tfor value in values {") - lines.append("\t\ttotal += CALL_X(value)") - if difficultyIsAtLeastHard(difficulty) { - lines.append("\t\ttotal += call_x(value) // near miss lower case") - lines.append("\t\ttotal += CALL_XY(value) // near miss suffix") - lines.append("\t\tif value % 2 == 0 {") - lines.append("\t\t\tlet computed = CALL_X(CALL_X(value * 2))") - lines.append("\t\t\ttotal += computed") - lines.append("\t\t\t// CALL_X should be removed even in comments? Keep comment text") - lines.append("\t\t}") - } - lines.append("\t}") - lines.append("\treturn total") - lines.append("}") - var existing = fileSystem.content(for: path) ?? "" - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(lines.joined(separator: "\n")) - fileSystem.setFile(path, content: existing) - var decoyPaths: [String] = [] - if decoyCount >= 1 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "WorkShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoyCount >= 2 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "WorkClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - let instructions = [ - "Remove all CALL_X() invocations from the specified file using search-replace edits.", - "CALL_X appears with an opening parenthesis: CALL_X(value).", - "Do NOT modify near-miss tokens: call_x (lowercase) or CALL_XY (different suffix).", - "Do NOT remove CALL_X from comments - only remove actual function calls.", - "Do NOT rewrite the entire file - use targeted edits only." - ] - let acceptance = [ - "All CALL_X() invocations are removed from \(path).", - "Other identifiers including call_x and CALL_XY remain unchanged.", - "Comments containing CALL_X text remain unchanged." - ] - var params: [String: BenchmarkJSONValue] = [ - "target": .string("CALL_X("), - "file": .string(path), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoyCount) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "remove_x_swift", - type: .removeXSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEditsRemoveX(for: difficulty), - instructions: instructions, - task: "Remove all CALL_X() invocations from \(path).", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "remove_x_swift", - type: .removeXSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEditsRemoveX(for: difficulty), - instructions: instructions, - task: "Remove all CALL_X() invocations from \(path).", - acceptance: acceptance, - params: params - ) - } - - // MARK: - curly_fix_go - - private func generateCurlyFixGoTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let suffix = randomIdentifier(rng: &rng) - let files: [String] - let regions: Int // number of defect regions per file - let minEdits: Int // minimum edits per file - switch difficulty { - case .medium: - files = ["src/go/main_\(suffix).go"] - regions = 2 - minEdits = 2 - case .hard: - files = ["src/go/main_\(suffix)_a.go", "src/go/main_\(suffix)_b.go"] - regions = 3 - minEdits = 2 - case .veryHard: - files = ["src/go/main_\(suffix)_a.go", "src/go/main_\(suffix)_b.go", "src/go/main_\(suffix)_c.go"] - regions = 3 - minEdits = 3 - case .simple: - files = ["src/go/main_\(suffix).go"] - regions = 2 - minEdits = 2 - } - let decoyCount = decoyCount(for: difficulty, config: config) - - // Generate files with multi-region brace defects - for path in files { - var lines: [String] = [] - lines.append("package main") - lines.append("") - lines.append("import \"fmt\"") - lines.append("") - lines.append("func main() {") - lines.append(" sum := 0") - lines.append("") - - // Region A: for loop with nested if (missing 2 braces) - lines.append(" for i := 0; i < 5; i++ {") - lines.append(" sum += i") - lines.append(" if i%2 == 0 {") - lines.append(" sum += i") - // Missing closing brace for if - // Missing closing brace for for - - lines.append("") - lines.append(" // Decoy brace noise: }") - lines.append(" braceStr := \"}\"") - lines.append("") - - if regions >= 2 { - // Region B: another loop (missing 1 brace) - lines.append(" for j := 0; j < 3; j++ {") - lines.append(" sum += j") - // Missing closing brace for this for - } - - lines.append("") - - if regions >= 3 { - // Region C: switch statement (missing 1 brace) - lines.append(" switch sum % 2 {") - lines.append(" case 0:") - lines.append(" sum++") - // Missing closing brace for switch - lines.append("") - } - - lines.append(" _ = braceStr // more brace noise: \"}\"") - lines.append(" fmt.Println(sum)") - lines.append("}") // closing main - - fileSystem.setFile(path, content: lines.joined(separator: "\n")) - } - let approxLines = max(20, scaledLines(noise.lineCount / 2, difficulty: difficulty)) - let goHelper = BelievableCodeFactory.goUtilityModule(rng: &rng, pkg: "extras", approxLines: approxLines) - fileSystem.setFile("src/go/extras/util.go", content: goHelper) - - // Add curly-specific decoys with brace noise - var fullDecoys: [String] = [] - let curlyDecoyCount = min(decoyCount, difficulty == .medium ? 1 : (difficulty == .hard ? 2 : 3)) - for i in 0 ..< curlyDecoyCount { - let decoy = BelievableCodeFactory.goCurlyDecoy(rng: &rng, name: "Maze\(i)") - fileSystem.setFile(decoy.path, content: decoy.content) - fullDecoys.append(decoy.path) - } - - // Add regular decoys for additional context - let regularDecoyCount = decoyCount - curlyDecoyCount - if regularDecoyCount >= 1 { - let decoyOne = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "WorkShadow") - fileSystem.setFile(decoyOne.path, content: decoyOne.content) - fullDecoys.append(decoyOne.path) - } - if regularDecoyCount >= 2 { - let decoyTwo = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "WorkClone") - fileSystem.setFile(decoyTwo.path, content: decoyTwo.content) - fullDecoys.append(decoyTwo.path) - } - let instructions = [ - "Add the minimum number of closing braces (}) needed to balance the code.", - "Ensure the fmt.Println statement executes AFTER the loop completes (not inside it).", - "Do NOT modify any existing code - only add missing closing braces.", - "Do NOT reformat or change indentation of existing lines." - ] - let acceptance = [ - "All braces are balanced (every { has a matching }).", - "`fmt.Println(sum)` is outside the `for` loop and executes exactly once.", - "Loop logic and all other statements remain byte-for-byte identical to baseline.", - "Exactly \(files.count) file(s) were modified." - ] - var params: [String: BenchmarkJSONValue] = [ - "files": .array(files.map { .string($0) }), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoyCount), - "fullDecoys": .array(fullDecoys.map { .string($0) }), - "minEditsPerFile": .integer(minEdits) - ] - - // Increase edit budget to allow multiple edits per file - let editBudget = max(files.count * (difficulty == .medium ? 2 : (difficulty == .hard ? 3 : 4)), files.count) - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "curly_fix_go", - type: .curlyFixGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: editBudget, - instructions: instructions, - task: "Fix missing closing brace(s) so braces are balanced and println follows the loop, across multiple files when listed.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "curly_fix_go", - type: .curlyFixGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: editBudget, - instructions: instructions, - task: "Fix missing closing brace(s) so braces are balanced and println follows the loop, across multiple files when listed.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - curly_fix_ts - - private func generateCurlyFixTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let suffix = randomIdentifier(rng: &rng) - let files: [String] - let regions: Int // number of defect regions per file - let minEdits: Int // minimum edits per file - switch difficulty { - case .medium: - files = ["src/ts/main_\(suffix).ts"] - regions = 2 - minEdits = 2 - case .hard: - files = ["src/ts/main_\(suffix)_a.ts", "src/ts/main_\(suffix)_b.ts"] - regions = 3 - minEdits = 2 - case .veryHard: - files = ["src/ts/main_\(suffix)_a.ts", "src/ts/main_\(suffix)_b.ts", "src/ts/main_\(suffix)_c.ts"] - regions = 3 - minEdits = 3 - case .simple: - files = ["src/ts/main_\(suffix).ts"] - regions = 2 - minEdits = 2 - } - let decoyCount = decoyCount(for: difficulty, config: config) - - // Generate files with multi-region brace defects - for path in files { - var lines: [String] = [] - lines.append("// TypeScript main file") - lines.append("") - lines.append("import { log } from './util';") - lines.append("") - lines.append("function main() {") - lines.append(" let sum = 0;") - lines.append("") - - // Region A: for loop with nested if (missing 2 braces) - lines.append(" for (let i = 0; i < 5; i++) {") - lines.append(" sum += i;") - lines.append(" if (i % 2 === 0) {") - lines.append(" sum += i;") - // Missing closing brace for if - // Missing closing brace for for - - lines.append("") - lines.append(" // Decoy brace noise: }") - lines.append(" const braceStr = \"}\";") - lines.append("") - - if regions >= 2 { - // Region B: another loop (missing 1 brace) - lines.append(" for (let j = 0; j < 3; j++) {") - lines.append(" sum += j;") - // Missing closing brace for this for - } - - lines.append("") - - if regions >= 3 { - // Region C: switch statement (missing 1 brace) - lines.append(" switch (sum % 2) {") - lines.append(" case 0:") - lines.append(" sum++;") - lines.append(" break;") - // Missing closing brace for switch - lines.append("") - } - - lines.append(" void(braceStr); // more brace noise: \"}\"") - lines.append(" console.log(sum);") - lines.append("}") // closing main - - fileSystem.setFile(path, content: lines.joined(separator: "\n")) - } - let approxLines = max(20, scaledLines(noise.lineCount / 2, difficulty: difficulty)) - let tsHelper = BelievableCodeFactory.tsUtilityModule(rng: &rng, approxLines: approxLines) - fileSystem.setFile("src/ts/util.ts", content: tsHelper) - - // Add curly-specific decoys with brace noise - var fullDecoys: [String] = [] - let curlyDecoyCount = min(decoyCount, difficulty == .medium ? 1 : (difficulty == .hard ? 2 : 3)) - for i in 0 ..< curlyDecoyCount { - let decoy = BelievableCodeFactory.tsCurlyDecoy(rng: &rng, name: "Maze\(i)") - fileSystem.setFile(decoy.path, content: decoy.content) - fullDecoys.append(decoy.path) - } - - // Add regular decoys for additional context - let regularDecoyCount = decoyCount - curlyDecoyCount - if regularDecoyCount >= 1 { - let decoyOne = BelievableCodeFactory.tsDecoyFile(rng: &rng, name: "WorkShadow") - fileSystem.setFile(decoyOne.path, content: decoyOne.content) - fullDecoys.append(decoyOne.path) - } - if regularDecoyCount >= 2 { - let decoyTwo = BelievableCodeFactory.tsDecoyFile(rng: &rng, name: "WorkClone") - fileSystem.setFile(decoyTwo.path, content: decoyTwo.content) - fullDecoys.append(decoyTwo.path) - } - - let instructions = [ - "Add the minimum number of closing braces (}) needed to balance the code.", - "Ensure the console.log statement executes AFTER the loop completes (not inside it).", - "Do NOT modify any existing code - only add missing closing braces.", - "Do NOT reformat or change indentation of existing lines." - ] - let acceptance = [ - "All braces are balanced (every { has a matching }).", - "`console.log(sum)` is outside the `for` loop and executes exactly once.", - "Loop logic and all other statements remain byte-for-byte identical to baseline.", - "Exactly \(files.count) file(s) were modified." - ] - var params: [String: BenchmarkJSONValue] = [ - "files": .array(files.map { .string($0) }), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoyCount), - "fullDecoys": .array(fullDecoys.map { .string($0) }), - "minEditsPerFile": .integer(minEdits) - ] - - // Increase edit budget to allow multiple edits per file - let editBudget = max(files.count * (difficulty == .medium ? 2 : (difficulty == .hard ? 3 : 4)), files.count) - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "curly_fix_ts", - type: .curlyFixTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: editBudget, - instructions: instructions, - task: "Fix missing closing brace(s) so braces are balanced and console.log follows the loop, across multiple files when listed.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "curly_fix_ts", - type: .curlyFixTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: editBudget, - instructions: instructions, - task: "Fix missing closing brace(s) so braces are balanced and console.log follows the loop, across multiple files when listed.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - curly_fix_swift - - private func generateCurlyFixSwiftTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let suffix = randomIdentifier(rng: &rng) - let files: [String] - let regions: Int // number of defect regions per file - let minEdits: Int // minimum edits per file - switch difficulty { - case .medium: - files = ["src/swift/Main_\(suffix).swift"] - regions = 2 - minEdits = 2 - case .hard: - files = ["src/swift/Main_\(suffix)_a.swift", "src/swift/Main_\(suffix)_b.swift"] - regions = 3 - minEdits = 2 - case .veryHard: - files = ["src/swift/Main_\(suffix)_a.swift", "src/swift/Main_\(suffix)_b.swift", "src/swift/Main_\(suffix)_c.swift"] - regions = 3 - minEdits = 3 - case .simple: - files = ["src/swift/Main_\(suffix).swift"] - regions = 2 - minEdits = 2 - } - let decoyCount = decoyCount(for: difficulty, config: config) - - // Generate files with multi-region brace defects - for path in files { - var lines: [String] = [] - lines.append("import Foundation") - lines.append("") - lines.append("func main() {") - lines.append("\tvar sum = 0") - lines.append("") - - // Region A: for loop with nested if (missing 2 braces) - lines.append("\tfor i in 0..<5 {") - lines.append("\t\tsum += i") - lines.append("\t\tif i % 2 == 0 {") - lines.append("\t\t\tsum += i") - // Missing closing brace for if - // Missing closing brace for for - - lines.append("") - lines.append("\t// Decoy brace noise: }") - lines.append("\tlet braceStr = \"}\"") - lines.append("") - - if regions >= 2 { - // Region B: another loop (missing 1 brace) - lines.append("\tfor j in 0..<3 {") - lines.append("\t\tsum += j") - // Missing closing brace for this for - } - - lines.append("") - - if regions >= 3 { - // Region C: switch statement (missing 1 brace) - lines.append("\tswitch sum % 2 {") - lines.append("\tcase 0:") - lines.append("\t\tsum += 1") - lines.append("\tdefault:") - lines.append("\t\tbreak") - // Missing closing brace for switch - lines.append("") - } - - lines.append("\t_ = braceStr // more brace noise: \"}\"") - lines.append("\tprint(sum)") - lines.append("}") // closing main - - fileSystem.setFile(path, content: lines.joined(separator: "\n")) - } - let approxLines = max(20, scaledLines(noise.lineCount / 2, difficulty: difficulty)) - let swiftHelper = BelievableCodeFactory.swiftUtilityModule(rng: &rng, approxLines: approxLines) - fileSystem.setFile("src/swift/Util.swift", content: swiftHelper) - - // Add curly-specific decoys with brace noise - var fullDecoys: [String] = [] - let curlyDecoyCount = min(decoyCount, difficulty == .medium ? 1 : (difficulty == .hard ? 2 : 3)) - for i in 0 ..< curlyDecoyCount { - let decoy = BelievableCodeFactory.swiftCurlyDecoy(rng: &rng, name: "Maze\(i)") - fileSystem.setFile(decoy.path, content: decoy.content) - fullDecoys.append(decoy.path) - } - - // Add regular decoys for additional context - let regularDecoyCount = decoyCount - curlyDecoyCount - if regularDecoyCount >= 1 { - let decoyOne = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "WorkShadow") - fileSystem.setFile(decoyOne.path, content: decoyOne.content) - fullDecoys.append(decoyOne.path) - } - if regularDecoyCount >= 2 { - let decoyTwo = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "WorkClone") - fileSystem.setFile(decoyTwo.path, content: decoyTwo.content) - fullDecoys.append(decoyTwo.path) - } - - let instructions = [ - "Add the minimum number of closing braces (}) needed to balance the code.", - "Ensure the print statement executes AFTER the loop completes (not inside it).", - "Do NOT modify any existing code - only add missing closing braces.", - "Do NOT reformat or change indentation of existing lines." - ] - let acceptance = [ - "All braces are balanced (every { has a matching }).", - "`print(sum)` is outside the `for` loop and executes exactly once.", - "Loop logic and all other statements remain byte-for-byte identical to baseline.", - "Exactly \(files.count) file(s) were modified." - ] - var params: [String: BenchmarkJSONValue] = [ - "files": .array(files.map { .string($0) }), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoyCount), - "fullDecoys": .array(fullDecoys.map { .string($0) }), - "minEditsPerFile": .integer(minEdits) - ] - - // Increase edit budget to allow multiple edits per file - let editBudget = max(files.count * (difficulty == .medium ? 2 : (difficulty == .hard ? 3 : 4)), files.count) - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "curly_fix_swift", - type: .curlyFixSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: editBudget, - instructions: instructions, - task: "Fix missing closing brace(s) so braces are balanced and print follows the loop, across multiple files when listed.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "curly_fix_swift", - type: .curlyFixSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: editBudget, - instructions: instructions, - task: "Fix missing closing brace(s) so braces are balanced and print follows the loop, across multiple files when listed.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - rename_export_and_imports_go - - private func generateRenameExportsGoTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - _ = rng - _ = config - _ = difficulty - - let exporterPath = "src/go/lib/exporter.go" - let oldName = "OldX" - let newName = "NewX" - - // Determine importer count by difficulty - let importerCount = switch difficulty { - case .simple: - 2 - case .medium: - 4 - case .hard: - 6 - case .veryHard: - 8 - } - - // Apps pool scales with difficulty (A..H) - let apps: [String] = switch difficulty { - case .simple, .medium: - ["appA", "appB", "appC"] - case .hard: - ["appA", "appB", "appC", "appD", "appE", "appF"] - case .veryHard: - ["appA", "appB", "appC", "appD", "appE", "appF", "appG", "appH"] - } - - // Build importer paths and contents - var importers: [String] = [] - for idx in 1 ... importerCount { - let app = apps[(idx - 1) % apps.count] - let path = "apps/\(app)/useX_\(idx).go" - importers.append(path) - - let content: String - if difficulty == .hard || difficulty == .veryHard { - // Rotate import patterns across files - let pattern = idx % 3 - switch pattern { - case 0: - // Alias import: import X "lib/exporter" then X.OldX() - content = """ - package main - - import X "lib/exporter" - - func consume() string { - // \(oldName) is not used here - _ = "\(oldName)" - return X.\(oldName)() - } - """ - case 1: - // Package-named import: import exporter "lib/exporter" then exporter.OldX() - content = """ - package main - - import exporter "lib/exporter" - - func consume() string { - // \(oldName) is not used here - _ = "\(oldName)" - return exporter.\(oldName)() - } - """ - default: - // Direct import: import "lib/exporter" then exporter.OldX() - content = """ - package main - - import "lib/exporter" - - func consume() string { - // \(oldName) is not used here - _ = "\(oldName)" - return exporter.\(oldName)() - } - """ - } - } else { - // Simple/Medium: straightforward package-named import - content = """ - package main - - import exporter "lib/exporter" - - func consume() string { - return exporter.\(oldName)() - } - """ - } - - fileSystem.setFile(path, content: content) - } - - // Exporter with near-miss tokens that must remain unchanged - let exporterContent = """ - package lib - - // Near-miss tokens — must remain unchanged - const \(oldName)Helper = "helper" - type \(oldName)Type struct{} - const \(oldName)XY = 42 - - func \(oldName)() string { return "value" } - var Usage = \(oldName)() - """ - fileSystem.setFile(exporterPath, content: exporterContent) - - // Instructions differ for hard/veryHard - let instructions: [String] = { - if difficulty == .hard || difficulty == .veryHard { - return [ - "Rename the exported symbol \(oldName) to \(newName) in \(exporterPath).", - "Update every occurrence of \(oldName) to \(newName) within the exporter file.", - "Update all importer files that reference this export to use \(newName) instead of \(oldName).", - "Import patterns include alias imports, package-named imports, and direct imports—update referenced symbol names accordingly.", - "Do NOT modify near-miss tokens: \(oldName)Helper, \(oldName)XY, \(oldName)Type.", - "Do NOT modify comments or string literals containing \"\(oldName)\"." - ] - } else { - let listed = importers.joined(separator: ", ") - return [ - "Rename the exported symbol \(oldName) to \(newName) in \(exporterPath).", - "Update every occurrence of \(oldName) to \(newName) within the exporter file.", - "Update ONLY the listed importer files to use \(newName) instead of \(oldName): \(listed).", - "Each importer uses the symbol as: exporter.\(oldName)() or similar.", - "Do NOT search for or modify any importer files not explicitly listed." - ] - } - }() - - let acceptance = [ - "\(exporterPath) exports \(newName) with zero remaining references to \(oldName).", - "Each listed importer file references \(newName) and has zero references to \(oldName).", - "Function/symbol behavior is unchanged - only the name changed." - ] - - var params: [String: BenchmarkJSONValue] = [ - "rename": .object(["from": .string(oldName), "to": .string(newName)]), - "importPaths": .array(importers.map { .string($0) }), - "nearMissTokens": .array(["\(oldName)Helper", "\(oldName)XY", "\(oldName)Type"].map { .string($0) }), - "difficulty": .string(difficulty.rawValue) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "rename_export_and_imports_go", - type: .renameExportImportsGo, - language: .go, - difficulty: difficulty, - selectFiles: [exporterPath] + importers, - maxEdits: 10, - instructions: instructions, - task: difficulty == .hard || difficulty == .veryHard ? - "Rename \(oldName) to \(newName) in exporter.go and update all importers that reference this symbol." : - "Rename \(oldName) to \(newName) in exporter.go and the listed importers.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "rename_export_and_imports_go", - type: .renameExportImportsGo, - language: .go, - difficulty: difficulty, - selectFiles: [exporterPath] + importers, - maxEdits: 10, - instructions: instructions, - task: difficulty == .hard || difficulty == .veryHard ? - "Rename \(oldName) to \(newName) in exporter.go and update all importers that reference this symbol." : - "Rename \(oldName) to \(newName) in exporter.go and the listed importers.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - rename_export_and_imports_swift - - private func generateRenameExportsSwiftTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - _ = rng - _ = config - _ = difficulty - let exporterPath = "Sources/Lib/Exporter.swift" - let importers = [ - "Apps/appA/UseX_1.swift", - "Apps/appB/UseX_2.swift", - "Apps/appB/UseX_3.swift" - ] - let oldName = "OldX" - let newName = "NewX" - let exporterContent = "public func \(oldName)() -> String {\n\treturn \"value\"\n}\n\npublic let usage = \(oldName)()\n" - fileSystem.setFile(exporterPath, content: exporterContent) - for path in importers { - let content = "import Lib\n\npublic func consume() -> String {\n\treturn \(oldName)()\n}\n" - fileSystem.setFile(path, content: content) - } - let instructions = [ - "Rename the exported symbol \(oldName) to \(newName) in \(exporterPath).", - "Update every occurrence of \(oldName) to \(newName) within the exporter file.", - "Update ONLY the listed importer files to use \(newName) instead of \(oldName).", - "Each importer uses the symbol directly as: \(oldName)().", - "Do NOT search for or modify any importer files not explicitly listed." - ] - let acceptance = [ - "\(exporterPath) exports \(newName) with zero remaining references to \(oldName).", - "Each listed importer file references \(newName) and has zero references to \(oldName).", - "The total number of edits matches: 1 exporter + \(importers.count) importers.", - "Function/symbol behavior is unchanged - only the name changed." - ] - var params: [String: BenchmarkJSONValue] = [ - "rename": .object(["from": .string(oldName), "to": .string(newName)]), - "importPaths": .array(importers.map { .string($0) }), - "difficulty": .string(difficulty.rawValue) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "rename_export_and_imports_swift", - type: .renameExportImportsSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [exporterPath] + importers, - maxEdits: 8, - instructions: instructions, - task: "Rename \(oldName) to \(newName) in Exporter.swift and the listed importers.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "rename_export_and_imports_swift", - type: .renameExportImportsSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [exporterPath] + importers, - maxEdits: 8, - instructions: instructions, - task: "Rename \(oldName) to \(newName) in Exporter.swift and the listed importers.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - move_function_ts - - private func generateMoveFunctionTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - _ = config - _ = difficulty - if difficulty == .hard { - let token = randomIdentifier(rng: &rng) - let path = "src/ts/reorder/Order_\(token).ts" - // Similar function names to create ambiguity - let fnNames = ["alpha", "alphaHelper", "alphaUtil", "bravo", "bravoHelper", "charlie", "delta"] - var lines: [String] = [] - for (index, name) in fnNames.enumerated() { - // Leading doc comment to test preservation - lines.append("/** \(name) computes value */") - lines.append("export function \(name)(n: number): number {") - lines.append(" return n * \(index + 1);") - lines.append("}") - lines.append("") - } - lines.append("// FOOTER: keep below here unchanged") - fileSystem.setFile(path, content: lines.joined(separator: "\n")) - - // HARD: Multi-move parameters (do not provide explicit single from/after in top-level instructions) - let instructions = [ - "Move the specified functions to maintain logical grouping; preserve any leading documentation/comments; do not duplicate.", - "Preserve exact spacing/blank lines between other functions.", - "The moved functions' content and leading doc comments must remain byte-for-byte identical.", - "Make no other edits to the file." - ] - let acceptance = [ - "All specified functions appear exactly once in the file.", - "Each moved function appears immediately after its designated target function ends.", - "All other functions appear in the same relative order as baseline (excluding the moved functions).", - "Leading documentation comments for moved functions remain attached and unchanged.", - "The FOOTER comment and all code below it remain unchanged." - ] - let moves: [BenchmarkJSONValue] = [ - .object(["from": .string("alpha"), "after": .string("charlie")]), - .object(["from": .string("bravo"), "after": .string("delta")]) - ] - var params: [String: BenchmarkJSONValue] = [ - "moves": .array(moves), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoyCount(for: difficulty, config: config)) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "move_function_ts", - type: .moveFunctionTs, - language: .ts, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 2, - instructions: instructions, - task: "Reorder the specified functions according to the moves list while preserving leading documentation.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "move_function_ts", - type: .moveFunctionTs, - language: .ts, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 2, - instructions: instructions, - task: "Reorder the specified functions according to the moves list while preserving leading documentation.", - acceptance: acceptance, - params: params - ) - } else { - // Original behavior for non-HARD: single move of one function after another - let token = randomIdentifier(rng: &rng) - let path = "src/ts/reorder/Order_\(token).ts" - let fnNames = ["alpha", "bravo", "charlie", "delta", "echo"] - var lines: [String] = [] - for (index, name) in fnNames.enumerated() { - lines.append("export function \(name)(n: number): number {") - lines.append(" return n * \(index + 1);") - lines.append("}") - lines.append("") - } - lines.append("// FOOTER: keep below here unchanged") - fileSystem.setFile(path, content: lines.joined(separator: "\n")) - let from = fnNames[rng.nextInt(upperBound: fnNames.count)] - let afterIdx = (fnNames.firstIndex(of: from)! + 2) % fnNames.count - let after = fnNames[afterIdx] - let instructions = [ - "Move the entire function \(from) to appear immediately after function \(after) ends.", - "Remove the function from its original location (do NOT duplicate it).", - "Preserve exact spacing/blank lines between other functions.", - "The moved function's content must remain byte-for-byte identical.", - "Make no other edits to the file." - ] - let acceptance = [ - "Function `\(from)` appears exactly once in the file.", - "Function `\(from)` starts on the line immediately after `\(after)` ends.", - "All other functions appear in the same order as baseline (excluding the moved function).", - "The FOOTER comment and all code below it remain unchanged." - ] - var params: [String: BenchmarkJSONValue] = [ - "fromName": .string(from), - "afterName": .string(after), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoyCount(for: difficulty, config: config)) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "move_function_ts", - type: .moveFunctionTs, - language: .ts, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 2, - instructions: instructions, - task: "Move function `\(from)` to after `\(after)` in \(path).", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "move_function_ts", - type: .moveFunctionTs, - language: .ts, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 2, - instructions: instructions, - task: "Move function `\(from)` to after `\(after)` in \(path).", - acceptance: acceptance, - params: params - ) - } - } - - // MARK: - move_function_go - - private func generateMoveFunctionGoTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - _ = config - _ = difficulty - let token = randomIdentifier(rng: &rng) - let path = "src/go/reorder/Order_\(token).go" - let fnNames = ["alpha", "bravo", "charlie", "delta", "echo"] - var lines: [String] = [] - lines.append("package reorder") - lines.append("") - for (index, name) in fnNames.enumerated() { - lines.append("func \(name)(n int) int {") - lines.append(" return n * \(index + 1)") - lines.append("}") - lines.append("") - } - lines.append("// FOOTER: keep below here unchanged") - fileSystem.setFile(path, content: lines.joined(separator: "\n")) - let fromIdx = rng.nextInt(upperBound: fnNames.count) - let afterIdx = (fromIdx + 2) % fnNames.count - let from = fnNames[fromIdx] - let after = fnNames[afterIdx] - let instructions = [ - "Move the entire function \(from) to appear immediately after function \(after) ends.", - "Remove the function from its original location (do NOT duplicate it).", - "Preserve exact spacing/blank lines between other functions.", - "The moved function's content must remain byte-for-byte identical.", - "Make no other edits to the file." - ] - let acceptance = [ - "Function `\(from)` appears exactly once in the file.", - "Function `\(from)` starts on the line immediately after `\(after)` ends.", - "All other functions appear in the same order as baseline (excluding the moved function).", - "The FOOTER comment and all code below it remain unchanged." - ] - var params: [String: BenchmarkJSONValue] = [ - "fromName": .string(from), - "afterName": .string(after), - "difficulty": .string(difficulty.rawValue) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "move_function_go", - type: .moveFunctionGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 2, - instructions: instructions, - task: "Move function \(from) to after \(after) in \(path).", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "move_function_go", - type: .moveFunctionGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 2, - instructions: instructions, - task: "Move function \(from) to after \(after) in \(path).", - acceptance: acceptance, - params: params - ) - } - - // MARK: - move_function_swift - - private func generateMoveFunctionSwiftTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - _ = config - if difficulty == .veryHard { - let token = randomIdentifier(rng: &rng) - let path = "src/swift/reorder/Order_\(token).swift" - // Longer list with near-collisions to raise ambiguity and preserve tabs - let fnNames = [ - "alpha", - "alphaHelper", - "alphaUtil", - "alphaPrime", - "bravo", - "bravoHelper", - "charlie", - "charlieX", - "delta" - ] - var lines: [String] = [] - for (index, name) in fnNames.enumerated() { - // Leading documentation comment - lines.append("/// \(name) computes value") - lines.append("public func \(name)(_ n: Int) -> Int {") - lines.append("\treturn n * \(index + 1)") - lines.append("}") - lines.append("") - } - lines.append("// FOOTER: keep below here unchanged") - fileSystem.setFile(path, content: lines.joined(separator: "\n")) - - // Three moves increase difficulty; order forces long-distance reordering - let moves: [BenchmarkJSONValue] = [ - .object(["from": .string("alpha"), "after": .string("charlie")]), - .object(["from": .string("bravoHelper"), "after": .string("delta")]), - .object(["from": .string("alphaUtil"), "after": .string("alphaPrime")]) - ] - - let instructions = [ - "Reorder the specified functions per the moves list.", - "Preserve leading documentation comments and exact function bodies.", - "Do not duplicate; remove the original occurrence.", - "No other edits are permitted." - ] - let acceptance = [ - "All listed functions appear exactly once.", - "Each moved function starts immediately after its target function ends.", - "All leading documentation remains attached and unchanged.", - "No collateral changes elsewhere in the file.", - "FOOTER remains unchanged." - ] - - var params: [String: BenchmarkJSONValue] = [ - "moves": .array(moves), - "difficulty": .string(difficulty.rawValue) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "move_function_swift_vh", - type: .moveFunctionSwift, - language: .swift, - difficulty: .veryHard, - format: "xml", - selectFiles: [path], - newChat: false, - maxEdits: 3, - instructions: instructions, - task: "Perform the listed moves exactly, preserving docs and content.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "move_function_swift_vh", - type: .moveFunctionSwift, - language: .swift, - difficulty: .veryHard, - format: "xml", - selectFiles: [path], - newChat: false, - maxEdits: 3, - instructions: instructions, - task: "Perform the listed moves exactly, preserving docs and content.", - acceptance: acceptance, - params: params - ) - } else if difficulty == .hard { - let token = randomIdentifier(rng: &rng) - let path = "src/swift/reorder/Order_\(token).swift" - // Similar function names to create ambiguity - let fnNames = ["alpha", "alphaHelper", "alphaUtil", "bravo", "bravoHelper", "charlie", "delta"] - var lines: [String] = [] - for (index, name) in fnNames.enumerated() { - // Leading documentation comment - lines.append("/// \(name) computes value") - lines.append("public func \(name)(_ n: Int) -> Int {") - lines.append("\treturn n * \(index + 1)") - lines.append("}") - lines.append("") - } - lines.append("// FOOTER: keep below here unchanged") - fileSystem.setFile(path, content: lines.joined(separator: "\n")) - - // HARD: Multi-move parameters (avoid single explicit from/after in top instructions) - let instructions = [ - "Move the specified functions to maintain logical grouping; preserve any leading documentation/comments; do not duplicate.", - "Preserve exact spacing/blank lines between other functions.", - "The moved functions' content and leading doc comments must remain byte-for-byte identical.", - "Make no other edits to the file." - ] - let acceptance = [ - "All specified functions appear exactly once in the file.", - "Each moved function appears immediately after its designated target function ends.", - "All other functions appear in the same relative order as baseline (excluding the moved functions).", - "Leading documentation comments for moved functions remain attached and unchanged.", - "The FOOTER comment and all code below it remain unchanged." - ] - let moves: [BenchmarkJSONValue] = [ - .object(["from": .string("alpha"), "after": .string("charlie")]), - .object(["from": .string("bravo"), "after": .string("delta")]) - ] - var params: [String: BenchmarkJSONValue] = [ - "moves": .array(moves), - "difficulty": .string(difficulty.rawValue) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "move_function_swift", - type: .moveFunctionSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 2, - instructions: instructions, - task: "Reorder the specified functions according to the moves list while preserving leading documentation.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "move_function_swift", - type: .moveFunctionSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 2, - instructions: instructions, - task: "Reorder the specified functions according to the moves list while preserving leading documentation.", - acceptance: acceptance, - params: params - ) - } else { - // Original behavior for non-HARD: single move of one function after another - let token = randomIdentifier(rng: &rng) - let path = "src/swift/reorder/Order_\(token).swift" - let fnNames = ["alpha", "bravo", "charlie", "delta", "echo"] - var lines: [String] = [] - for (index, name) in fnNames.enumerated() { - lines.append("public func \(name)(_ n: Int) -> Int {") - lines.append("\treturn n * \(index + 1)") - lines.append("}") - lines.append("") - } - lines.append("// FOOTER: keep below here unchanged") - fileSystem.setFile(path, content: lines.joined(separator: "\n")) - let fromIdx = rng.nextInt(upperBound: fnNames.count) - let afterIdx = (fromIdx + 2) % fnNames.count - let from = fnNames[fromIdx] - let after = fnNames[afterIdx] - let instructions = [ - "Move the entire function \(from) to appear immediately after function \(after) ends.", - "Remove the function from its original location (do NOT duplicate it).", - "Preserve exact spacing/blank lines between other functions.", - "The moved function's content must remain byte-for-byte identical.", - "Make no other edits to the file." - ] - let acceptance = [ - "Function `\(from)` appears exactly once in the file.", - "Function `\(from)` starts on the line immediately after `\(after)` ends.", - "All other functions appear in the same order as baseline (excluding the moved function).", - "The FOOTER comment and all code below it remain unchanged." - ] - var params: [String: BenchmarkJSONValue] = [ - "fromName": .string(from), - "afterName": .string(after), - "difficulty": .string(difficulty.rawValue) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "move_function_swift", - type: .moveFunctionSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 2, - instructions: instructions, - task: "Move function \(from) to after \(after) in \(path).", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "move_function_swift", - type: .moveFunctionSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 2, - instructions: instructions, - task: "Move function \(from) to after \(after) in \(path).", - acceptance: acceptance, - params: params - ) - } - } - - // MARK: - insert_function_bottom_ts - - private func generateInsertFunctionBottomTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium: - [tsWorkPath] - case .hard: - [tsWorkPath, "src/ts/work/WorkA.ts"] - case .veryHard: - [tsWorkPath, "src/ts/work/WorkA.ts", "src/ts/work/WorkB.ts"] - case .simple: - [tsWorkPath] - } - let footer = "// END-OF-FILE (append new functions immediately above this line)" - for path in files { - var bootstrap = fileSystem.content(for: path) ?? "" - if !bootstrap.contains(footer) { - if !bootstrap.isEmpty, !bootstrap.hasSuffix("\n") { - bootstrap.append("\n") - } - bootstrap.append( - "export function ping(x: string): string {\n return `ping:${x}`;\n}\n\nexport function pong(y: number): number {\n return y + 1;\n}\n\n\(footer)\n" - ) - fileSystem.setFile(path, content: bootstrap) - } - } - let snippet = """ - export const curryAdd = (a: number) => (b: number) => a + b; - - export function compose(f: (b: B) => C, g: (a: A) => B) { - return (a: A) => f(g(a)); - } - """ - let instructions = [ - "In each listed file, insert the provided multi-line snippet at the bottom.", - "The snippet must be inserted immediately above the // END-OF-FILE marker line.", - "Maintain one blank line between the last existing function and the new snippet.", - "Maintain one blank line between the snippet and the END-OF-FILE marker.", - "Do NOT modify any existing code above the insertion point.", - "Insert the exact snippet once per file - do NOT modify or reformat it.", - "CRITICAL: Your blocks must contain 3-8 lines and match the file byte-for-byte. You WILL FAIL if you include entire files, large sections, or hundreds of lines in your search blocks. Keep search blocks minimal and precise." - ] - let acceptance = [ - "The snippet appears exactly once in each listed file.", - "The snippet is placed above the END-OF-FILE marker in each file.", - "There is exactly one blank line between the snippet and the END-OF-FILE marker.", - "All existing functions and code remain byte-for-byte unchanged.", - "Exactly \(files.count) file(s) were modified." - ] - let decoyBudget = decoyCount(for: difficulty, config: config) - let fullDecoys = decoyBudget > 0 ? makeSimilarTsDecoys(rng: &rng, around: tsWorkPath) : [] - for decoy in fullDecoys { - fileSystem.setFile(decoy.path, content: decoy.content) - } - let inserts = files.map { path -> BenchmarkJSONValue in - .object([ - "path": .string(path), - "snippet": .string(snippet), - "footer": .string("// END-OF-FILE") - ]) - } - var params: [String: BenchmarkJSONValue] = [ - "inserts": .array(inserts), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoyBudget) - ] - if !fullDecoys.isEmpty { - params["fullDecoys"] = .array(fullDecoys.map { .string($0.path) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "insert_function_bottom_ts", - type: .insertFunctionBottomTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Append a curried utility function block at the bottom of each listed file.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "insert_function_bottom_ts", - type: .insertFunctionBottomTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Append a curried utility function block at the bottom of each listed file.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - insert_function_bottom_go - - private func generateInsertFunctionBottomGoTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium, .simple: - [goWorkPath] - case .hard: - [goWorkPath, "src/go/work/WorkA.go"] - case .veryHard: - [goWorkPath, "src/go/work/WorkA.go", "src/go/work/WorkB.go"] - } - let footer = "// END-OF-FILE (append new functions immediately above this line)" - for path in files { - var existing = fileSystem.content(for: path) ?? "" - if !existing.contains(footer) { - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append("package work\n\nfunc ping(x string) string { return \"ping:\\(x)\" }\n\nfunc pong(y int) int { return y + 1 }\n\n\(footer)\n") - fileSystem.setFile(path, content: existing) - } - } - let snippet = """ - func curryAdd(a int) func(int) int { return func(b int) int { return a + b } } - - func compose[A any, B any, C any](f func(B) C, g func(A) B) func(A) C { - return func(a A) C { return f(g(a)) } - } - """ - let instructions = [ - "In each listed file, insert the provided multi-line snippet at the bottom.", - "The snippet must be inserted immediately above the // END-OF-FILE marker line.", - "Maintain one blank line between the last existing function and the new snippet.", - "Maintain one blank line between the snippet and the END-OF-FILE marker.", - "Do NOT modify any existing code above the insertion point.", - "Insert the exact snippet once per file - do NOT modify or reformat it.", - "CRITICAL: Your blocks must contain 3-8 lines and match the file byte-for-byte. You WILL FAIL if you include entire files, large sections, or hundreds of lines in your search blocks. Keep search blocks minimal and precise." - ] - let acceptance = [ - "The snippet appears exactly once in each listed file.", - "The snippet is placed above the END-OF-FILE marker in each file.", - "There is exactly one blank line between the snippet and the END-OF-FILE marker.", - "All existing functions and code remain byte-for-byte unchanged.", - "Exactly \(files.count) file(s) were modified." - ] - let decoys = decoyCount(for: difficulty, config: config) - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "BottomShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "BottomClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - let inserts = files.map { path -> BenchmarkJSONValue in - .object([ - "path": .string(path), - "snippet": .string(snippet), - "footer": .string("// END-OF-FILE") - ]) - } - var params: [String: BenchmarkJSONValue] = [ - "inserts": .array(inserts), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "insert_function_bottom_go", - type: .insertFunctionBottomGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Append the snippet at the bottom of each listed file.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "insert_function_bottom_go", - type: .insertFunctionBottomGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Append the snippet at the bottom of each listed file.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - insert_function_bottom_swift - - private func generateInsertFunctionBottomSwiftTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium, .simple: - [swiftWorkPath] - case .hard: - [swiftWorkPath, "src/swift/work/WorkA.swift"] - case .veryHard: - [swiftWorkPath, "src/swift/work/WorkA.swift", "src/swift/work/WorkB.swift"] - } - let footer = "// END-OF-FILE (append new functions immediately above this line)" - for path in files { - var existing = fileSystem.content(for: path) ?? "" - if !existing.contains(footer) { - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append("public func ping(_ x: String) -> String { \"ping:\\(x)\" }\n\npublic func pong(_ y: Int) -> Int { y + 1 }\n\n\(footer)\n") - fileSystem.setFile(path, content: existing) - } - } - let snippet = """ - public func curryAdd(_ a: Int) -> (Int) -> Int { { b in a + b } } - - public func compose(_ f: @escaping (B) -> C, _ g: @escaping (A) -> B) -> (A) -> C { - { a in f(g(a)) } - } - """ - let instructions = [ - "Append the provided multi-line snippet at the bottom of each listed file, immediately above the footer marker.", - "Do not change existing lines.", - "CRITICAL: Your blocks must contain 3-8 lines and match the file byte-for-byte. You WILL FAIL if you include entire files, large sections, or hundreds of lines in your search blocks. Keep search blocks minimal and precise." - ] - let acceptance = [ - "Snippet appears exactly once in each listed file.", - "Snippet is placed above the END-OF-FILE marker in each listed file.", - "No other lines changed." - ] - let decoys = decoyCount(for: difficulty, config: config) - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "BottomShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "BottomClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - let inserts = files.map { path -> BenchmarkJSONValue in - .object([ - "path": .string(path), - "snippet": .string(snippet), - "footer": .string("// END-OF-FILE") - ]) - } - var params: [String: BenchmarkJSONValue] = [ - "inserts": .array(inserts), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "insert_function_bottom_swift", - type: .insertFunctionBottomSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Append the snippet at the bottom of each listed file.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "insert_function_bottom_swift", - type: .insertFunctionBottomSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Append the snippet at the bottom of each listed file.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - apply_unified_patch_* - - // - // NOTE: Simple difficulty is retained for unit tests but excluded from production benchmarks. - // Production difficulty progression: Medium → Hard → VeryHard (gauntlet) - - // MARK: - apply_unified_patch_ts - - private func appendTsSection2(_ lines: inout [String]) { - lines.append("") - lines.append("export function d(n: number) {") - lines.append(" return n + 1") - lines.append("}") - lines.append("") - lines.append("export function e(s: string) {") - lines.append(" return s.toLowerCase()") - lines.append("}") - lines.append("") - lines.append("export function f(xs: number[]): number {") - lines.append(" return xs.length") - lines.append("}") - lines.append("") - lines.append("export const value2 = 7") - } - - private func appendTsSection3(_ lines: inout [String]) { - lines.append("") - lines.append("export function g(n: number) {") - lines.append(" return n * 2") - lines.append("}") - lines.append("") - lines.append("export function h(s: string) {") - lines.append(" return s") - lines.append("}") - lines.append("") - lines.append("export const value3 = 100") - } - - private func appendTsSection4(_ lines: inout [String]) { - lines.append("") - lines.append("export function i(n: number) {") - lines.append(" return n - 1") - lines.append("}") - lines.append("") - lines.append("export function j(s: string) {") - lines.append(" return s.trim()") - lines.append("}") - lines.append("") - lines.append("export const value4 = 256") - } - - // MARK: - Clone Infrastructure Helpers - - private func makeDeepClonePaths(token: String, ext: String, count: Int, language: BenchmarkLanguage) -> [String] { - let roots: [String] = switch language { - case .ts: - ["src/ts/patchables", "apps/appA/src/patchables", "apps/appB/src/patchables", "packages/pkg1/src/patchables", "packages/pkg2/src/patchables"] - case .go: - ["src/go/patchables", "apps/appA/patchables", "apps/appB/patchables", "packages/pkg1/patchables", "packages/pkg2/patchables"] - case .swift: - ["src/swift/patchables", "Apps/appA/patchables", "Apps/appB/patchables", "Packages/Pkg1/patchables", "Packages/Pkg2/patchables"] - @unknown default: - ["src/patchables"] - } - var out: [String] = [] - for i in 0 ..< count { - let root = roots[i % roots.count] - out.append("\(root)/Clone_\(token)_\(i).\(ext)") - } - return out - } - - private func stableShuffle(_ arr: [T], seed: UInt32) -> [T] { - var rng = Mulberry32(seed: seed) - var a = arr - for i in stride(from: a.count - 1, through: 1, by: -1) { - let j = rng.nextInt(upperBound: i + 1) - a.swapAt(i, j) - } - return a - } - - private func makePatchableClone(language: BenchmarkLanguage, origin: [String], variant: Int, includeSection4: Bool) -> [String] { - let clone = origin - - // 1) Large preamble padding (60-120 lines) - let padCount = 60 + (variant % 61) - var pre: [String] = [] - for k in 0 ..< padCount { - switch language { - case .ts: - pre.append("// pad \(k)") - if k % 5 == 0 { - pre.append("export const pad\(k) = \(k)") - } - case .go: - pre.append("// pad \(k)") - if k % 5 == 0 { - pre.append("const pad\(k) = \(k)") - } - case .swift: - pre.append("// pad \(k)") - if k % 5 == 0 { - pre.append("public let pad\(k) = \(k)") - } - default: - pre.append("// pad \(k)") - } - } - - // 2) Extract sections from origin - let sections = extractSections(language: language, from: clone, includeSection4: includeSection4) - - // 3) Reorder sections based on variant - let reordered = reorderSections(sections, variant: variant) - - // 4) Tweak constants to break minus matches - let tweaked = tweakConstants(in: reordered, language: language, variant: variant) - - // 5) Assemble: preamble + tweaked - return pre + tweaked - } - - private func extractSections(language: BenchmarkLanguage, from lines: [String], includeSection4: Bool) -> [[String]] { - var sections: [[String]] = [] - var currentSection: [String] = [] - - // Simple heuristic: split on empty lines or function boundaries - // Section 1: everything before first major function group (a, b, c, value) - // Section 2: d, e, f, value2 - // Section 3: g, h, value3 - // Section 4: i, j, value4 - - let markers: [String] = switch language { - case .ts: - includeSection4 - ? ["export function a(", "export function d(", "export function g(", "export function i("] - : ["export function a(", "export function d(", "export function g("] - case .go: - includeSection4 - ? ["func a(", "func d(", "func g(", "func i("] - : ["func a(", "func d(", "func g("] - case .swift: - includeSection4 - ? ["public func a(", "public func d(", "public func g(", "public func i("] - : ["public func a(", "public func d(", "public func g("] - @unknown default: - [] - } - - var markerIdx = 0 - for line in lines { - if markerIdx < markers.count, line.contains(markers[markerIdx]) { - if !currentSection.isEmpty { - sections.append(currentSection) - currentSection = [] - } - markerIdx += 1 - } - currentSection.append(line) - } - - if !currentSection.isEmpty { - sections.append(currentSection) - } - - return sections - } - - private func reorderSections(_ sections: [[String]], variant: Int) -> [String] { - guard sections.count > 1 else { - return sections.flatMap(\.self) - } - - // Permute sections based on variant - // Simple strategy: rotate sections - let shift = variant % sections.count - var reordered: [[String]] = [] - for i in 0 ..< sections.count { - let idx = (i + shift) % sections.count - reordered.append(sections[idx]) - } - - return reordered.flatMap(\.self) - } - - private func tweakConstants(in lines: [String], language: BenchmarkLanguage, variant: Int) -> [String] { - let tweak = variant % 10 + 1 - return lines.map { line in - switch language { - case .ts: - if line.contains("export const value = ") { - return line.replacingOccurrences(of: "= 42", with: "= \(42 + tweak)") - } - if line.contains("export const value2 = ") { - return line.replacingOccurrences(of: "= 7", with: "= \(7 + tweak)") - } - if line.contains("export const value3 = ") { - return line.replacingOccurrences(of: "= 100", with: "= \(100 + tweak)") - } - if line.contains("export const value4 = ") { - return line.replacingOccurrences(of: "= 256", with: "= \(256 + tweak)") - } - case .go: - if line.contains("const value = ") { - return line.replacingOccurrences(of: "= 42", with: "= \(42 + tweak)") - } - if line.contains("const value2 = ") { - return line.replacingOccurrences(of: "= 7", with: "= \(7 + tweak)") - } - if line.contains("const value3 = ") { - return line.replacingOccurrences(of: "= 100", with: "= \(100 + tweak)") - } - if line.contains("const value4 = ") { - return line.replacingOccurrences(of: "= 256", with: "= \(256 + tweak)") - } - case .swift: - if line.contains("public let value = ") { - return line.replacingOccurrences(of: "= 42", with: "= \(42 + tweak)") - } - if line.contains("public let value2 = ") { - return line.replacingOccurrences(of: "= 7", with: "= \(7 + tweak)") - } - if line.contains("public let value3 = ") { - return line.replacingOccurrences(of: "= 100", with: "= \(100 + tweak)") - } - if line.contains("public let value4 = ") { - return line.replacingOccurrences(of: "= 256", with: "= \(256 + tweak)") - } - @unknown default: - break - } - return line - } - } - - private func generateUnifiedPatchTsTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let token = randomIdentifier(rng: &rng) - let path = "src/ts/patchables/Patch_\(token).ts" - let maxDecoys = decoyCount(for: difficulty, config: config) - - func fallbackSpec() -> BenchmarkTaskSpec { - BenchmarkTaskSpec( - id: "apply_unified_patch_ts", - type: .applyUnifiedPatchTs, - language: .ts, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 6, - instructions: ["Apply the unified diff to \(path)."], - task: "Apply the unified diff to \(path).", - acceptance: ["Final file equals applying the provided unified diff."], - params: [ - "patch": .string(""), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(maxDecoys) - ] - ) - } - - // Build baseline (keep existing logic) - var base: [String] = [] - base.append("export function a(n: number) {") - base.append(" return n + 1") - base.append("}") - base.append("") - base.append("export function b(s: string) {") - base.append(" return s.toUpperCase()") - base.append("}") - base.append("") - if difficulty != .simple { - base.append("export function c(xs: number[]): number {") - base.append(" return xs.reduce((a, b) => a + b, 0)") - base.append("}") - base.append("") - } - base.append("export const value = 42") - - // VeryHard gauntlet: append additional sections to baseline - if difficulty == .veryHard { - appendTsSection2(&base) - appendTsSection3(&base) - appendTsSection4(&base) - } - - fileSystem.setFile(path, content: base.joined(separator: "\n")) - - /// Unified diff helpers - func hunkHeader(_ oldStart: Int, _ oldCount: Int, _ newStart: Int, _ newCount: Int) -> String { - "@@ -\(oldStart),\(oldCount) +\(newStart),\(newCount) @@" - } - - // Locate function a (for modify hunk) - guard - let aOpenIdx = base.firstIndex(where: { $0.hasPrefix("export function a(") }), - let aRetIdx = base.firstIndex(where: { $0.contains("return n + 1") }), - aRetIdx + 1 < base.count - else { - return fallbackSpec() - } - let aCloseIdx = aRetIdx + 1 - guard base[aCloseIdx] == "}" else { - return fallbackSpec() - } - - // Compute increment and modified return line for function a - let inc = 2 + rng.nextInt(upperBound: 3) - let oldTarget = base[aRetIdx] - let newTarget = oldTarget.replacingOccurrences(of: "n + 1", with: "n + \(inc)") - - // Start patch header - let useUnknownHeaders = (difficulty == .veryHard) - var patchLines: [String] = [] - patchLines.append(useUnknownHeaders ? "--- a/UNKNOWN" : "--- a/\(path)") - patchLines.append(useUnknownHeaders ? "+++ b/UNKNOWN" : "+++ b/\(path)") - - // Gather hunks - var hunks: [[String]] = [] - - // BOF header addition for medium/hard/veryHard - let bofAdded = (difficulty != .simple) ? 2 : 0 - if bofAdded > 0 { - var headerHunk: [String] = [] - headerHunk.append(hunkHeader(1, 0, 1, 2)) - headerHunk.append("+// NOTE: patched by benchmark") - headerHunk.append("+") - hunks.append(headerHunk) - } - - // VeryHard gauntlet multi-section logic - if difficulty == .veryHard { - var removedSpans: [ClosedRange] = [] - - // Create decoy clone files for target discovery (12-20 clones across nested dirs) - let gauntlet = (config.decoyPolicy.style == .gauntlet) - let cloneCount = gauntlet ? 20 : 12 - let clonePaths = makeDeepClonePaths(token: token, ext: "ts", count: cloneCount, language: .ts) - for (i, clonePath) in clonePaths.enumerated() { - let cloneLines = makePatchableClone(language: .ts, origin: base, variant: i, includeSection4: true) - fileSystem.setFile(clonePath, content: cloneLines.joined(separator: "\n")) - } - // Shuffle candidatePaths to hide target position - let allCandidates = [path] + clonePaths - let shuffleSeed = UInt32(truncatingIfNeeded: token.hashValue) - let candidatePaths = stableShuffle(allCandidates, seed: shuffleSeed) - - // Section 1: modify a - do { - var modifyHunk: [String] = [] - let modifyOldStart = aOpenIdx + 1 - let removedBefore = removedLineCount(before: aOpenIdx, in: removedSpans) - let modifyNewStart = newStart(oldStart1Based: modifyOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - modifyHunk.append(hunkHeader(modifyOldStart, 3, modifyNewStart, 3)) - modifyHunk.append(" \(base[aOpenIdx])") - modifyHunk.append("-\(oldTarget)") - modifyHunk.append("+\(newTarget)") - modifyHunk.append(" \(base[aCloseIdx])") - hunks.append(modifyHunk) - } - - // Section 1: remove b (with context) - guard let bRange = computeTsFunctionRange(base, fnName: "b") else { - return fallbackSpec() - } - do { - let bStart = bRange.lowerBound - let bEnd = bRange.upperBound - let ctxBeforeIdx = max(0, bStart - 1) - let ctxAfterIdx = min(base.count - 1, bEnd + 1) - let hasAfterContext = ctxAfterIdx > bEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in bStart ... bEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(bRange) - - // Section 1: noise hunk on value - do { - let noiseText = buildTsNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange], - context: 3 - ) - if !noiseText.isEmpty { - hunks.append(noiseText.components(separatedBy: "\n")) - } - } - - // Section 2: modify d - guard - let dOpenIdx = base.firstIndex(where: { $0.hasPrefix("export function d(") }) - else { - return fallbackSpec() - } - guard - let dRetIdx = base[dOpenIdx...].firstIndex(where: { $0.contains("return n + 1") && $0.hasPrefix(" ") }), - dRetIdx > dOpenIdx, - dRetIdx + 1 < base.count, - base[dRetIdx + 1] == "}" - else { - return fallbackSpec() - } - do { - let oldReturnD = base[dRetIdx] - let inc2 = 2 + rng.nextInt(upperBound: 3) - let newReturnD = oldReturnD.replacingOccurrences(of: "n + 1", with: "n + \(inc2)") - - var h: [String] = [] - let modifyOldStart = dOpenIdx + 1 - let removedBefore = removedLineCount(before: dOpenIdx, in: removedSpans) - let modifyNewStart = newStart(oldStart1Based: modifyOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - h.append(hunkHeader(modifyOldStart, 3, modifyNewStart, 3)) - h.append(" \(base[dOpenIdx])") - h.append("-\(oldReturnD)") - h.append("+\(newReturnD)") - h.append(" }") - hunks.append(h) - } - - // Section 2: remove e - guard let eRange = computeTsFunctionRange(base, fnName: "e") else { - return fallbackSpec() - } - do { - let eStart = eRange.lowerBound - let eEnd = eRange.upperBound - let ctxBeforeIdx = max(0, eStart - 1) - let ctxAfterIdx = min(base.count - 1, eEnd + 1) - let hasAfterContext = ctxAfterIdx > eEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in eStart ... eEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(eRange) - - // Section 2: noise hunk on value2 - do { - let noiseText = buildTsNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange], - valueMarker: "export const value2 = 7", - context: 3 - ) - if !noiseText.isEmpty { - hunks.append(noiseText.components(separatedBy: "\n")) - } - } - - // Section 3: modify g - guard - let gOpenIdx = base.firstIndex(where: { $0.hasPrefix("export function g(") }), - let gRetIdx = base.firstIndex(where: { $0.contains("return n * 2") }) - else { - return fallbackSpec() - } - guard gRetIdx + 1 < base.count, base[gRetIdx + 1] == "}" else { - return fallbackSpec() - } - do { - let oldReturnG = base[gRetIdx] - let newReturnG = oldReturnG.replacingOccurrences(of: "n * 2", with: "n * 3") - - var h: [String] = [] - let modifyOldStart = gOpenIdx + 1 - let removedBefore = removedLineCount(before: gOpenIdx, in: removedSpans) - let modifyNewStart = newStart(oldStart1Based: modifyOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - h.append(hunkHeader(modifyOldStart, 3, modifyNewStart, 3)) - h.append(" \(base[gOpenIdx])") - h.append("-\(oldReturnG)") - h.append("+\(newReturnG)") - h.append(" }") - hunks.append(h) - } - - // Section 3: remove h - guard let hRange = computeTsFunctionRange(base, fnName: "h") else { - return fallbackSpec() - } - do { - let hStart = hRange.lowerBound - let hEnd = hRange.upperBound - let ctxBeforeIdx = max(0, hStart - 1) - let ctxAfterIdx = min(base.count - 1, hEnd + 1) - let hasAfterContext = ctxAfterIdx > hEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in hStart ... hEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(hRange) - - // Section 3: noise hunk on value3 (additional noise) - do { - let noise3 = buildTsNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange, hRange], - valueMarker: "export const value3 = 100", - context: 2 - ) - if !noise3.isEmpty { - hunks.append(noise3.components(separatedBy: "\n")) - } - } - - // Section 4: modify i - guard - let iOpenIdx = base.firstIndex(where: { $0.hasPrefix("export function i(") }), - let iRetIdx = base.firstIndex(where: { $0.contains("return n - 1") }) - else { - return fallbackSpec() - } - guard iRetIdx + 1 < base.count, base[iRetIdx + 1] == "}" else { - return fallbackSpec() - } - do { - let oldReturnI = base[iRetIdx] - let newReturnI = oldReturnI.replacingOccurrences(of: "n - 1", with: "n - 2") - - var h: [String] = [] - let modifyOldStart = iOpenIdx + 1 - let removedBefore = removedLineCount(before: iOpenIdx, in: removedSpans) - let modifyNewStart = newStart(oldStart1Based: modifyOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - h.append(hunkHeader(modifyOldStart, 3, modifyNewStart, 3)) - h.append(" \(base[iOpenIdx])") - h.append("-\(oldReturnI)") - h.append("+\(newReturnI)") - h.append(" }") - hunks.append(h) - } - - // Section 4: remove j - guard let jRange = computeTsFunctionRange(base, fnName: "j") else { - return fallbackSpec() - } - do { - let jStart = jRange.lowerBound - let jEnd = jRange.upperBound - let ctxBeforeIdx = max(0, jStart - 1) - let ctxAfterIdx = min(base.count - 1, jEnd + 1) - let hasAfterContext = ctxAfterIdx > jEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in jStart ... jEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(jRange) - - // Section 4: noise hunk on value4 - do { - let noise4 = buildTsNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange, hRange, jRange], - valueMarker: "export const value4 = 256", - context: 1 - ) - if !noise4.isEmpty { - hunks.append(noise4.components(separatedBy: "\n")) - } - } - - // Additional noise hunk on value2 with different context - do { - let noise2Extra = buildTsNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange, hRange, jRange], - valueMarker: "export const value2 = 7", - context: 4 - ) - if !noise2Extra.isEmpty { - hunks.append(noise2Extra.components(separatedBy: "\n")) - } - } - - // Emit hunks - for hunk in hunks { - patchLines.append(contentsOf: hunk) - } - - let instructions = [ - "Apply the unified diff exactly to \(path).", - "Do not modify any other lines beyond the patch." - ] - let acceptance = [ - "The final file content matches the provided unified diff.", - "No additional changes beyond the hunks." - ] - let maxEdits = switch difficulty { - case .simple: - 4 - case .medium: - 7 - case .hard: - 12 - case .veryHard: - 28 - } - - var params: [String: BenchmarkJSONValue] = [ - "patch": .string(patchLines.joined(separator: "\n")), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(maxDecoys), - "targetDiscovery": .boolean(true), - "targetPath": .string(path), - "candidatePaths": .array(candidatePaths.map { .string($0) }) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "apply_unified_patch_ts", - type: .applyUnifiedPatchTs, - language: .ts, - difficulty: difficulty, - selectFiles: candidatePaths, - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to one of the candidate files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "apply_unified_patch_ts", - type: .applyUnifiedPatchTs, - language: .ts, - difficulty: difficulty, - selectFiles: candidatePaths, - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to one of the candidate files.", - acceptance: acceptance, - params: params - ) - } - - // Non‑VeryHard path (existing behavior) - - // Modify function a - var modifyHunk: [String] = [] - let modifyOldStart = aOpenIdx + 1 // original file line number (1-based) - let modifyNewStart = modifyOldStart + bofAdded - modifyHunk.append(hunkHeader(modifyOldStart, 3, modifyNewStart, 3)) - modifyHunk.append(" \(base[aOpenIdx])") - modifyHunk.append("-\(oldTarget)") - modifyHunk.append("+\(newTarget)") - modifyHunk.append(" \(base[aCloseIdx])") - hunks.append(modifyHunk) - - // Remove function b for medium/hard/veryHard using computeTsFunctionRange - var extraNoiseLines: [String] = [] - if difficulty != .simple { - guard let bRange = computeTsFunctionRange(base, fnName: "b") else { - return fallbackSpec() - } - let bStart = bRange.lowerBound - let bEnd = bRange.upperBound - - let ctxBeforeIdx = max(0, bStart - 1) - let ctxAfterIdx = min(base.count - 1, bEnd + 1) - - // Header math - let oldLineStart = ctxBeforeIdx + 1 - let newLineStart = oldLineStart + bofAdded - let hasAfterContext = ctxAfterIdx > bEnd - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in bStart ... bEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - - // Noise hunk for hard/veryHard via helper (3-line context default) - if difficulty == .hard || difficulty == .veryHard { - let noiseText = buildTsNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange], - context: 3 - ) - if !noiseText.isEmpty { - extraNoiseLines = noiseText.components(separatedBy: "\n") - } - - // For hard: add one more noise hunk with different context - if difficulty == .hard { - let noise2 = buildTsNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange], - valueMarker: "export const value = 42", - context: 1 - ) - if !noise2.isEmpty, !extraNoiseLines.isEmpty { - extraNoiseLines.append(contentsOf: noise2.components(separatedBy: "\n")) - } - } - } - } - - // Emit hunks - for hunk in hunks { - patchLines.append(contentsOf: hunk) - } - - // Append noise hunk (if any) - if !extraNoiseLines.isEmpty { - patchLines.append(contentsOf: extraNoiseLines) - } - - let instructions = [ - "Apply the unified diff exactly to \(path).", - "Do not modify any other lines beyond the patch." - ] - let acceptance = [ - "The final file content matches the provided unified diff.", - "No additional changes beyond the hunks." - ] - let maxEdits = switch difficulty { - case .simple: - 4 - case .medium: - 7 - case .hard: - 12 - case .veryHard: - 28 - } - - var params: [String: BenchmarkJSONValue] = [ - "patch": .string(patchLines.joined(separator: "\n")), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(maxDecoys) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "apply_unified_patch_ts", - type: .applyUnifiedPatchTs, - language: .ts, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "apply_unified_patch_ts", - type: .applyUnifiedPatchTs, - language: .ts, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: params - ) - } - - // MARK: - apply_unified_patch_go - - private func appendGoSection2(_ lines: inout [String]) { - lines.append("") - lines.append("func d(n int) int {") - lines.append(" return n + 1") - lines.append("}") - lines.append("") - lines.append("func e(s string) string {") - lines.append(" return s") - lines.append("}") - lines.append("") - lines.append("func f(xs []int) int {") - lines.append(" return len(xs)") - lines.append("}") - lines.append("") - lines.append("const value2 = 7") - } - - private func appendGoSection3(_ lines: inout [String]) { - lines.append("") - lines.append("func g(n int) int {") - lines.append(" return n * 2") - lines.append("}") - lines.append("") - lines.append("func h(s string) string {") - lines.append(" return s") - lines.append("}") - lines.append("") - lines.append("const value3 = 100") - } - - private func appendGoSection4(_ lines: inout [String]) { - lines.append("") - lines.append("func i(n int) int {") - lines.append(" return n - 1") - lines.append("}") - lines.append("") - lines.append("func j(s string) string {") - lines.append(" return strings.TrimSpace(s)") - lines.append("}") - lines.append("") - lines.append("const value4 = 256") - } - - private func generateUnifiedPatchGoTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - _ = config - let token = randomIdentifier(rng: &rng) - let path = "src/go/patchables/Patch_\(token).go" - - func hunkHeader(_ oldStart: Int, _ oldCount: Int, _ newStart: Int, _ newCount: Int) -> String { - "@@ -\(oldStart),\(oldCount) +\(newStart),\(newCount) @@" - } - - // Build baseline (preserve existing logic) - var base: [String] = [] - base.append("package patchables") - base.append("") - base.append("func a(n int) int {") - base.append(" return n + 1") - base.append("}") - base.append("") - base.append("func b(s string) string {") - base.append(" return s + s") - base.append("}") - base.append("") - if difficulty != .simple { - base.append("func c(xs []int) int {") - base.append(" return sum(xs)") - base.append("}") - base.append("") - } - base.append("const value = 42") - - // VeryHard gauntlet: append additional sections to baseline - if difficulty == .veryHard { - appendGoSection2(&base) - appendGoSection3(&base) - appendGoSection4(&base) - } - - fileSystem.setFile(path, content: base.joined(separator: "\n")) - - // Start patch output - let useUnknownHeaders = (difficulty == .veryHard) - var patchLines: [String] = [] - patchLines.append(useUnknownHeaders ? "--- a/UNKNOWN" : "--- a/\(path)") - patchLines.append(useUnknownHeaders ? "+++ b/UNKNOWN" : "+++ b/\(path)") - - // Hunks container - var hunks: [[String]] = [] - - // BOF header addition for medium/hard/veryHard - let bofAdded = (difficulty != .simple) ? 2 : 0 - if bofAdded > 0 { - var headerHunk: [String] = [] - headerHunk.append(hunkHeader(1, 0, 1, 2)) - headerHunk.append("+// NOTE: patched by benchmark") - headerHunk.append("+") - hunks.append(headerHunk) - } - - // Locate function a (for modify hunk) - guard - let aOpenIdx = base.firstIndex(where: { $0.hasPrefix("func a(") }), - let aRetIdx = base.firstIndex(where: { $0.contains("return n + 1") }), - aRetIdx + 1 < base.count, - base[aRetIdx + 1] == "}" - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - - // Compute increment and modified return line for function a - let inc = 2 + rng.nextInt(upperBound: 3) - let oldTargetA = base[aRetIdx] - let newTargetA = oldTargetA.replacingOccurrences(of: "n + 1", with: "n + \(inc)") - - // VeryHard gauntlet multi-section logic - if difficulty == .veryHard { - var removedSpans: [ClosedRange] = [] - - // Create decoy clone files for target discovery (12-20 clones across nested dirs) - let gauntlet = (config.decoyPolicy.style == .gauntlet) - let cloneCount = gauntlet ? 20 : 12 - let clonePaths = makeDeepClonePaths(token: token, ext: "go", count: cloneCount, language: .go) - for (i, clonePath) in clonePaths.enumerated() { - let cloneLines = makePatchableClone(language: .go, origin: base, variant: i, includeSection4: true) - fileSystem.setFile(clonePath, content: cloneLines.joined(separator: "\n")) - } - // Shuffle candidatePaths to hide target position - let allCandidates = [path] + clonePaths - let shuffleSeed = UInt32(truncatingIfNeeded: token.hashValue) - let candidatePaths = stableShuffle(allCandidates, seed: shuffleSeed) - - // Section 1: modify a (three-line hunk) - do { - var modifyAHunk: [String] = [] - let modifyAOldStart = aOpenIdx + 1 - let removedBefore = removedLineCount(before: aOpenIdx, in: removedSpans) - let modifyANewStart = newStart(oldStart1Based: modifyAOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - modifyAHunk.append(hunkHeader(modifyAOldStart, 3, modifyANewStart, 3)) - modifyAHunk.append(" \(base[aOpenIdx])") - modifyAHunk.append("-\(oldTargetA)") - modifyAHunk.append("+\(newTargetA)") - modifyAHunk.append(" \(base[aRetIdx + 1])") - hunks.append(modifyAHunk) - } - - // Section 1: remove b (with context lines) - guard let bRange = computeGoFunctionRange(base, fnName: "b") else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let bStart = bRange.lowerBound - let bEnd = bRange.upperBound - let ctxBeforeIdx = max(0, bStart - 1) - let ctxAfterIdx = min(base.count - 1, bEnd + 1) - let hasAfterContext = ctxAfterIdx > bEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in bStart ... bEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(bRange) - - // Section 1: noise hunk on value - do { - let noiseText = buildGoNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange], - context: 3 - ) - if !noiseText.isEmpty { - hunks.append(noiseText.components(separatedBy: "\n")) - } - } - - // Section 2: modify d - guard - let dOpenIdx = base.firstIndex(where: { $0.hasPrefix("func d(") }) - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - guard - let dRetIdx = base[dOpenIdx...].firstIndex(where: { $0.contains("return n + 1") && $0.hasPrefix(" ") }), - dRetIdx > dOpenIdx, - dRetIdx + 1 < base.count, - base[dRetIdx + 1] == "}" - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let oldReturnD = base[dRetIdx] - let inc2 = 2 + rng.nextInt(upperBound: 3) - let newReturnD = oldReturnD.replacingOccurrences(of: "n + 1", with: "n + \(inc2)") - - var h: [String] = [] - let modifyOldStart = dOpenIdx + 1 - let removedBefore = removedLineCount(before: dOpenIdx, in: removedSpans) - let modifyNewStart = newStart(oldStart1Based: modifyOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - h.append(hunkHeader(modifyOldStart, 3, modifyNewStart, 3)) - h.append(" \(base[dOpenIdx])") - h.append("-\(oldReturnD)") - h.append("+\(newReturnD)") - h.append(" }") - hunks.append(h) - } - - // Section 2: remove e - guard let eRange = computeGoFunctionRange(base, fnName: "e") else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let eStart = eRange.lowerBound - let eEnd = eRange.upperBound - let ctxBeforeIdx = max(0, eStart - 1) - let ctxAfterIdx = min(base.count - 1, eEnd + 1) - let hasAfterContext = ctxAfterIdx > eEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in eStart ... eEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(eRange) - - // Section 2: noise hunk on value2 - do { - let noiseText = buildGoNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange], - valueMarker: "const value2 = 7", - context: 3 - ) - if !noiseText.isEmpty { - hunks.append(noiseText.components(separatedBy: "\n")) - } - } - - // Section 3: modify g - guard - let gOpenIdx = base.firstIndex(where: { $0.hasPrefix("func g(") }), - let gRetIdx = base.firstIndex(where: { $0.contains("return n * 2") }), - gRetIdx + 1 < base.count, - base[gRetIdx + 1] == "}" - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let oldReturnG = base[gRetIdx] - let newReturnG = oldReturnG.replacingOccurrences(of: "n * 2", with: "n * 3") - - var h: [String] = [] - let modifyOldStart = gOpenIdx + 1 - let removedBefore = removedLineCount(before: gOpenIdx, in: removedSpans) - let modifyNewStart = newStart(oldStart1Based: modifyOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - h.append(hunkHeader(modifyOldStart, 3, modifyNewStart, 3)) - h.append(" \(base[gOpenIdx])") - h.append("-\(oldReturnG)") - h.append("+\(newReturnG)") - h.append(" }") - hunks.append(h) - } - - // Section 3: remove h - guard let hRange = computeGoFunctionRange(base, fnName: "h") else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let hStart = hRange.lowerBound - let hEnd = hRange.upperBound - let ctxBeforeIdx = max(0, hStart - 1) - let ctxAfterIdx = min(base.count - 1, hEnd + 1) - let hasAfterContext = ctxAfterIdx > hEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in hStart ... hEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(hRange) - - // Section 3: noise hunk on value3 (additional noise) - do { - let noise3 = buildGoNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange, hRange], - valueMarker: "const value3 = 100", - context: 2 - ) - if !noise3.isEmpty { - hunks.append(noise3.components(separatedBy: "\n")) - } - } - - // Section 4: modify i - guard - let iOpenIdx = base.firstIndex(where: { $0.hasPrefix("func i(") }), - let iRetIdx = base.firstIndex(where: { $0.contains("return n - 1") }), - iRetIdx + 1 < base.count, - base[iRetIdx + 1] == "}" - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let oldReturnI = base[iRetIdx] - let newReturnI = oldReturnI.replacingOccurrences(of: "n - 1", with: "n - 2") - - var h: [String] = [] - let modifyOldStart = iOpenIdx + 1 - let removedBefore = removedLineCount(before: iOpenIdx, in: removedSpans) - let modifyNewStart = newStart(oldStart1Based: modifyOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - h.append(hunkHeader(modifyOldStart, 3, modifyNewStart, 3)) - h.append(" \(base[iOpenIdx])") - h.append("-\(oldReturnI)") - h.append("+\(newReturnI)") - h.append(" }") - hunks.append(h) - } - - // Section 4: remove j - guard let jRange = computeGoFunctionRange(base, fnName: "j") else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let jStart = jRange.lowerBound - let jEnd = jRange.upperBound - let ctxBeforeIdx = max(0, jStart - 1) - let ctxAfterIdx = min(base.count - 1, jEnd + 1) - let hasAfterContext = ctxAfterIdx > jEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in jStart ... jEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(jRange) - - // Section 4: noise hunk on value4 - do { - let noise4 = buildGoNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange, hRange, jRange], - valueMarker: "const value4 = 256", - context: 1 - ) - if !noise4.isEmpty { - hunks.append(noise4.components(separatedBy: "\n")) - } - } - - // Additional noise hunk on value2 with different context - do { - let noise2Extra = buildGoNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange, hRange, jRange], - valueMarker: "const value2 = 7", - context: 4 - ) - if !noise2Extra.isEmpty { - hunks.append(noise2Extra.components(separatedBy: "\n")) - } - } - - // Emit hunks to patch - for hunk in hunks { - patchLines.append(contentsOf: hunk) - } - - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - let maxEdits = switch difficulty { - case .simple: - 4 - case .medium: - 7 - case .hard: - 12 - case .veryHard: - 28 - } - - var params: [String: BenchmarkJSONValue] = [ - "patch": .string(patchLines.joined(separator: "\n")), - "difficulty": .string(difficulty.rawValue), - "targetDiscovery": .boolean(true), - "targetPath": .string(path), - "candidatePaths": .array(candidatePaths.map { .string($0) }) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: candidatePaths, - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to one of the candidate files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: candidatePaths, - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to one of the candidate files.", - acceptance: acceptance, - params: params - ) - } - - // Non‑VeryHard path (existing behavior) - - // Modify function a hunk (3-line window: signature, return line, closing brace) - var modifyAHunk: [String] = [] - let modifyAOldStart = aOpenIdx + 1 - let modifyANewStart = modifyAOldStart + bofAdded - modifyAHunk.append(hunkHeader(modifyAOldStart, 3, modifyANewStart, 3)) - modifyAHunk.append(" \(base[aOpenIdx])") - modifyAHunk.append("-\(oldTargetA)") - modifyAHunk.append("+\(newTargetA)") - modifyAHunk.append(" \(base[aRetIdx + 1])") - hunks.append(modifyAHunk) - - // extra noise lines buffer - var extraNoiseLines: [String] = [] - - if difficulty != .simple { - // Remove function b using computed range - guard let bRange = computeGoFunctionRange(base, fnName: "b") else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - - let bStart = bRange.lowerBound - let bEnd = bRange.upperBound - - // Context one line before and (if present) one line after the removed span - let ctxBeforeIdx = max(0, bStart - 1) - let ctxAfterIdx = min(base.count - 1, bEnd + 1) - let hasAfterContext = ctxAfterIdx > bEnd - - let oldLineStart = ctxBeforeIdx + 1 - let newLineStart = oldLineStart + bofAdded - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in bStart ... bEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - - // Noise hunk for hard/veryHard via helper (3-line context default) - if difficulty == .hard || difficulty == .veryHard { - let noiseText = buildGoNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange], - context: 3 - ) - if !noiseText.isEmpty { - extraNoiseLines = noiseText.components(separatedBy: "\n") - } - - // For hard: add one more noise hunk with different context - if difficulty == .hard { - let noise2 = buildGoNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange], - valueMarker: "const value = 42", - context: 1 - ) - if !noise2.isEmpty, !extraNoiseLines.isEmpty { - extraNoiseLines.append(contentsOf: noise2.components(separatedBy: "\n")) - } - } - } - } else { - // Simple difficulty: modify function b (no BOF header, no removal) - guard - let bOpenIdx = base.firstIndex(where: { $0.hasPrefix("func b(") }), - let bRetIdx = base.firstIndex(where: { $0.contains("return s + s") }), - bRetIdx + 1 < base.count, - base[bRetIdx + 1] == "}" - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 4, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - - var modifyBHunk: [String] = [] - let modifyBOldStart = bOpenIdx + 1 - let modifyBNewStart = modifyBOldStart // no BOF addition for simple - modifyBHunk.append(hunkHeader(modifyBOldStart, 3, modifyBNewStart, 3)) - modifyBHunk.append(" \(base[bOpenIdx])") - modifyBHunk.append("-\(base[bRetIdx])") - modifyBHunk.append("+ return s") - modifyBHunk.append(" \(base[bRetIdx + 1])") - hunks.append(modifyBHunk) - } - - // Emit hunks to patch - for hunk in hunks { - patchLines.append(contentsOf: hunk) - } - - // Append noise hunk (if any) - if !extraNoiseLines.isEmpty { - patchLines.append(contentsOf: extraNoiseLines) - } - - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - let maxEdits = switch difficulty { - case .simple: - 4 - case .medium: - 7 - case .hard: - 12 - case .veryHard: - 28 - } - - var params: [String: BenchmarkJSONValue] = [ - "patch": .string(patchLines.joined(separator: "\n")), - "difficulty": .string(difficulty.rawValue) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "apply_unified_patch_go", - type: .applyUnifiedPatchGo, - language: .go, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: params - ) - } - - // MARK: - apply_unified_patch_swift - - private func appendSwiftSection2(_ lines: inout [String]) { - lines.append("") - lines.append("public func d(_ n: Int) -> Int {") - lines.append("\treturn n + 1") - lines.append("}") - lines.append("") - lines.append("public func e(_ s: String) -> String {") - lines.append("\treturn s") - lines.append("}") - lines.append("") - lines.append("public func f(_ xs: [Int]) -> Int {") - lines.append("\treturn xs.count") - lines.append("}") - lines.append("") - lines.append("public let value2 = 7") - } - - private func appendSwiftSection3(_ lines: inout [String]) { - lines.append("") - lines.append("public func g(_ n: Int) -> Int {") - lines.append("\treturn n * 2") - lines.append("}") - lines.append("") - lines.append("public func h(_ s: String) -> String {") - lines.append("\treturn s") - lines.append("}") - lines.append("") - lines.append("public let value3 = 100") - } - - private func appendSwiftSection4(_ lines: inout [String]) { - lines.append("") - lines.append("public func i(_ n: Int) -> Int {") - lines.append("\treturn n - 1") - lines.append("}") - lines.append("") - lines.append("public func j(_ s: String) -> String {") - lines.append("\treturn s.trimmingCharacters(in: .whitespaces)") - lines.append("}") - lines.append("") - lines.append("public let value4 = 256") - } - - private func generateUnifiedPatchSwiftTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - _ = config - let token = randomIdentifier(rng: &rng) - let path = "src/swift/patchables/Patch_\(token).swift" - - func hunkHeader(_ oldStart: Int, _ oldCount: Int, _ newStart: Int, _ newCount: Int) -> String { - "@@ -\(oldStart),\(oldCount) +\(newStart),\(newCount) @@" - } - - // Build baseline (preserve existing logic; Swift uses tabs) - var base: [String] = [] - base.append("public func a(_ n: Int) -> Int {") - base.append("\treturn n + 1") - base.append("}") - base.append("") - base.append("public func b(_ s: String) -> String {") - base.append("\treturn s.uppercased()") - base.append("}") - base.append("") - if difficulty != .simple { - base.append("public func c(_ xs: [Int]) -> Int {") - base.append("\treturn xs.reduce(0, +)") - base.append("}") - base.append("") - } - base.append("public let value = 42") - - // VeryHard gauntlet: append additional sections to baseline - if difficulty == .veryHard { - appendSwiftSection2(&base) - appendSwiftSection3(&base) - appendSwiftSection4(&base) - } - - fileSystem.setFile(path, content: base.joined(separator: "\n")) - - // Patch header - let useUnknownHeaders = (difficulty == .veryHard) - var patchLines: [String] = [] - patchLines.append(useUnknownHeaders ? "--- a/UNKNOWN" : "--- a/\(path)") - patchLines.append(useUnknownHeaders ? "+++ b/UNKNOWN" : "+++ b/\(path)") - - // Hunks container - var hunks: [[String]] = [] - - // BOF header addition for medium/hard/veryHard - let bofAdded = (difficulty != .simple) ? 2 : 0 - if bofAdded > 0 { - var headerHunk: [String] = [] - headerHunk.append(hunkHeader(1, 0, 1, 2)) - headerHunk.append("+// NOTE: patched by benchmark") - headerHunk.append("+") - hunks.append(headerHunk) - } - - // Modify function a - guard - let aOpenIdx = base.firstIndex(where: { $0.hasPrefix("public func a(") }), - let aRetIdx = base.firstIndex(where: { $0 == "\treturn n + 1" }), - aRetIdx + 1 < base.count, - base[aRetIdx + 1] == "}" - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 6, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string("")] - ) - } - - let inc = 2 + rng.nextInt(upperBound: 3) - let oldTargetA = base[aRetIdx] - let newTargetA = oldTargetA.replacingOccurrences(of: "n + 1", with: "n + \(inc)") - - // VeryHard gauntlet multi-section logic - if difficulty == .veryHard { - var removedSpans: [ClosedRange] = [] - - // Create decoy clone files for target discovery (12-20 clones across nested dirs) - let gauntlet = (config.decoyPolicy.style == .gauntlet) - let cloneCount = gauntlet ? 20 : 12 - let clonePaths = makeDeepClonePaths(token: token, ext: "swift", count: cloneCount, language: .swift) - for (i, clonePath) in clonePaths.enumerated() { - let cloneLines = makePatchableClone(language: .swift, origin: base, variant: i, includeSection4: true) - fileSystem.setFile(clonePath, content: cloneLines.joined(separator: "\n")) - } - // Shuffle candidatePaths to hide target position - let allCandidates = [path] + clonePaths - let shuffleSeed = UInt32(truncatingIfNeeded: token.hashValue) - let candidatePaths = stableShuffle(allCandidates, seed: shuffleSeed) - - // Section 1: modify a (three-line hunk) - do { - var modifyAHunk: [String] = [] - let modifyAOldStart = aOpenIdx + 1 - let removedBefore = removedLineCount(before: aOpenIdx, in: removedSpans) - let modifyANewStart = newStart(oldStart1Based: modifyAOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - modifyAHunk.append(hunkHeader(modifyAOldStart, 3, modifyANewStart, 3)) - modifyAHunk.append(" \(base[aOpenIdx])") - modifyAHunk.append("-\(oldTargetA)") - modifyAHunk.append("+\(newTargetA)") - modifyAHunk.append(" \(base[aRetIdx + 1])") - hunks.append(modifyAHunk) - } - - // Section 1: remove b (with context lines) - guard let bRange = computeSwiftFunctionRange(base, fnName: "b") else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let bStart = bRange.lowerBound - let bEnd = bRange.upperBound - let ctxBeforeIdx = max(0, bStart - 1) - let ctxAfterIdx = min(base.count - 1, bEnd + 1) - let hasAfterContext = ctxAfterIdx > bEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in bStart ... bEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(bRange) - - // Section 1: noise hunk on value - do { - let noiseText = buildSwiftNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange], - context: 3 - ) - if !noiseText.isEmpty { - hunks.append(noiseText.components(separatedBy: "\n")) - } - } - - // Section 2: modify d - guard - let dOpenIdx = base.firstIndex(where: { $0.hasPrefix("public func d(") }) - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - guard - let dRetIdx = base[dOpenIdx...].firstIndex(where: { $0 == "\treturn n + 1" }), - dRetIdx > dOpenIdx, - dRetIdx + 1 < base.count, - base[dRetIdx + 1] == "}" - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let oldReturnD = base[dRetIdx] - let inc2 = 2 + rng.nextInt(upperBound: 3) - let newReturnD = oldReturnD.replacingOccurrences(of: "n + 1", with: "n + \(inc2)") - - var h: [String] = [] - let modifyOldStart = dOpenIdx + 1 - let removedBefore = removedLineCount(before: dOpenIdx, in: removedSpans) - let modifyNewStart = newStart(oldStart1Based: modifyOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - h.append(hunkHeader(modifyOldStart, 3, modifyNewStart, 3)) - h.append(" \(base[dOpenIdx])") - h.append("-\(oldReturnD)") - h.append("+\(newReturnD)") - h.append(" }") - hunks.append(h) - } - - // Section 2: remove e - guard let eRange = computeSwiftFunctionRange(base, fnName: "e") else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let eStart = eRange.lowerBound - let eEnd = eRange.upperBound - let ctxBeforeIdx = max(0, eStart - 1) - let ctxAfterIdx = min(base.count - 1, eEnd + 1) - let hasAfterContext = ctxAfterIdx > eEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in eStart ... eEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(eRange) - - // Section 2: noise hunk on value2 - do { - let noiseText = buildSwiftNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange], - valueMarker: "public let value2 = 7", - context: 3 - ) - if !noiseText.isEmpty { - hunks.append(noiseText.components(separatedBy: "\n")) - } - } - - // Section 3: modify g - guard - let gOpenIdx = base.firstIndex(where: { $0.hasPrefix("public func g(") }), - let gRetIdx = base.firstIndex(where: { $0 == "\treturn n * 2" }), - gRetIdx + 1 < base.count, - base[gRetIdx + 1] == "}" - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let oldReturnG = base[gRetIdx] - let newReturnG = oldReturnG.replacingOccurrences(of: "n * 2", with: "n * 3") - - var h: [String] = [] - let modifyOldStart = gOpenIdx + 1 - let removedBefore = removedLineCount(before: gOpenIdx, in: removedSpans) - let modifyNewStart = newStart(oldStart1Based: modifyOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - h.append(hunkHeader(modifyOldStart, 3, modifyNewStart, 3)) - h.append(" \(base[gOpenIdx])") - h.append("-\(oldReturnG)") - h.append("+\(newReturnG)") - h.append(" }") - hunks.append(h) - } - - // Section 3: remove h - guard let hRange = computeSwiftFunctionRange(base, fnName: "h") else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let hStart = hRange.lowerBound - let hEnd = hRange.upperBound - let ctxBeforeIdx = max(0, hStart - 1) - let ctxAfterIdx = min(base.count - 1, hEnd + 1) - let hasAfterContext = ctxAfterIdx > hEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in hStart ... hEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(hRange) - - // Section 3: noise hunk on value3 (additional noise) - do { - let noise3 = buildSwiftNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange, hRange], - valueMarker: "public let value3 = 100", - context: 2 - ) - if !noise3.isEmpty { - hunks.append(noise3.components(separatedBy: "\n")) - } - } - - // Section 4: modify i - guard - let iOpenIdx = base.firstIndex(where: { $0.hasPrefix("public func i(") }), - let iRetIdx = base.firstIndex(where: { $0 == "\treturn n - 1" }), - iRetIdx + 1 < base.count, - base[iRetIdx + 1] == "}" - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let oldReturnI = base[iRetIdx] - let newReturnI = oldReturnI.replacingOccurrences(of: "n - 1", with: "n - 2") - - var h: [String] = [] - let modifyOldStart = iOpenIdx + 1 - let removedBefore = removedLineCount(before: iOpenIdx, in: removedSpans) - let modifyNewStart = newStart(oldStart1Based: modifyOldStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - h.append(hunkHeader(modifyOldStart, 3, modifyNewStart, 3)) - h.append(" \(base[iOpenIdx])") - h.append("-\(oldReturnI)") - h.append("+\(newReturnI)") - h.append(" }") - hunks.append(h) - } - - // Section 4: remove j - guard let jRange = computeSwiftFunctionRange(base, fnName: "j") else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - do { - let jStart = jRange.lowerBound - let jEnd = jRange.upperBound - let ctxBeforeIdx = max(0, jStart - 1) - let ctxAfterIdx = min(base.count - 1, jEnd + 1) - let hasAfterContext = ctxAfterIdx > jEnd - - let oldLineStart = ctxBeforeIdx + 1 - let removedBefore = removedLineCount(before: ctxBeforeIdx, in: removedSpans) - let newLineStart = newStart(oldStart1Based: oldLineStart, bofAdded: bofAdded, removedSpansBeforeTarget: removedBefore) - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in jStart ... jEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - } - removedSpans.append(jRange) - - // Section 4: noise hunk on value4 - do { - let noise4 = buildSwiftNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange, hRange, jRange], - valueMarker: "public let value4 = 256", - context: 1 - ) - if !noise4.isEmpty { - hunks.append(noise4.components(separatedBy: "\n")) - } - } - - // Additional noise hunk on value2 with different context - do { - let noise2Extra = buildSwiftNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange, eRange, hRange, jRange], - valueMarker: "public let value2 = 7", - context: 4 - ) - if !noise2Extra.isEmpty { - hunks.append(noise2Extra.components(separatedBy: "\n")) - } - } - - // Emit hunks to patch - for hunk in hunks { - patchLines.append(contentsOf: hunk) - } - - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - let maxEdits = switch difficulty { - case .simple: - 4 - case .medium: - 7 - case .hard: - 12 - case .veryHard: - 28 - } - - let patchString = patchLines.joined(separator: "\n") - // Self-check: ensure the patch we generated is actually valid - let baseText = base.joined(separator: "\n") - if SimpleUnifiedPatchApplier.apply(patch: patchString, to: baseText) == nil { - assertionFailure("Generator produced invalid unified patch for \(path) at difficulty \(difficulty)") - #if DEBUG - fatalError("Invalid unified patch generated; fix generator") - #endif - } - - var params: [String: BenchmarkJSONValue] = [ - "patch": .string(patchString), - "difficulty": .string(difficulty.rawValue), - "targetDiscovery": .boolean(true), - "targetPath": .string(path), - "candidatePaths": .array(candidatePaths.map { .string($0) }) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: candidatePaths, - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to one of the candidate files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: candidatePaths, - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to one of the candidate files.", - acceptance: acceptance, - params: params - ) - } - - // Non‑VeryHard path (existing behavior) - - // 3-line modify hunk: signature, return line, closing brace - var modifyAHunk: [String] = [] - let modifyAOldStart = aOpenIdx + 1 - let modifyANewStart = modifyAOldStart + bofAdded - modifyAHunk.append(hunkHeader(modifyAOldStart, 3, modifyANewStart, 3)) - modifyAHunk.append(" \(base[aOpenIdx])") - modifyAHunk.append("-\(oldTargetA)") - modifyAHunk.append("+\(newTargetA)") - modifyAHunk.append(" \(base[aRetIdx + 1])") - hunks.append(modifyAHunk) - - // Buffer for optional noise hunk lines - var extraNoiseLines: [String] = [] - - if difficulty != .simple { - // Remove function b using computed range - guard let bRange = computeSwiftFunctionRange(base, fnName: "b") else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 7, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - - let bStart = bRange.lowerBound - let bEnd = bRange.upperBound - - // Context one line before and possibly one after the removed span - let ctxBeforeIdx = max(0, bStart - 1) - let ctxAfterIdx = min(base.count - 1, bEnd + 1) - let hasAfterContext = ctxAfterIdx > bEnd - - let oldLineStart = ctxBeforeIdx + 1 - let newLineStart = oldLineStart + bofAdded - let oldCount = (ctxAfterIdx - ctxBeforeIdx + 1) - let newCount = hasAfterContext ? 2 : 1 - - var removalHunk: [String] = [] - removalHunk.append(hunkHeader(oldLineStart, oldCount, newLineStart, newCount)) - removalHunk.append(" \(base[ctxBeforeIdx])") - for idx in bStart ... bEnd { - removalHunk.append("-\(base[idx])") - } - if hasAfterContext { - removalHunk.append(" \(base[ctxAfterIdx])") - } - hunks.append(removalHunk) - - // Noise hunk for hard/veryHard via helper (3-line context default) - if difficulty == .hard || difficulty == .veryHard { - let noiseText = buildSwiftNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange], - context: 3 - ) - if !noiseText.isEmpty { - extraNoiseLines = noiseText.components(separatedBy: "\n") - } - - // For hard: add one more noise hunk with different context - if difficulty == .hard { - let noise2 = buildSwiftNoiseHunk( - baseline: base, - bofAdded: bofAdded, - removedSpans: [bRange], - valueMarker: "public let value = 42", - context: 1 - ) - if !noise2.isEmpty, !extraNoiseLines.isEmpty { - extraNoiseLines.append(contentsOf: noise2.components(separatedBy: "\n")) - } - } - } - } else { - // Simple difficulty: modify function b (no BOF header, no removal) - guard - let bOpenIdx = base.firstIndex(where: { $0.hasPrefix("public func b(") }), - let bRetIdx = base.firstIndex(where: { $0 == "\treturn s.uppercased()" }), - bRetIdx + 1 < base.count, - base[bRetIdx + 1] == "}" - else { - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: 4, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: ["patch": .string(""), "difficulty": .string(difficulty.rawValue)] - ) - } - - var modifyBHunk: [String] = [] - let modifyBOldStart = bOpenIdx + 1 - let modifyBNewStart = modifyBOldStart // no BOF addition for simple - modifyBHunk.append(hunkHeader(modifyBOldStart, 3, modifyBNewStart, 3)) - modifyBHunk.append(" \(base[bOpenIdx])") - modifyBHunk.append("-\(base[bRetIdx])") - modifyBHunk.append("+\treturn s.lowercased()") - modifyBHunk.append(" \(base[bRetIdx + 1])") - hunks.append(modifyBHunk) - } - - // Emit hunks to patch - for hunk in hunks { - patchLines.append(contentsOf: hunk) - } - - // Append noise hunk (if any) - if !extraNoiseLines.isEmpty { - patchLines.append(contentsOf: extraNoiseLines) - } - - let instructions = ["Apply the unified diff exactly to \(path)."] - let acceptance = ["Final file equals applying the provided unified diff."] - let maxEdits = switch difficulty { - case .simple: - 4 - case .medium: - 7 - case .hard: - 12 - case .veryHard: - 28 - } - - let patchString = patchLines.joined(separator: "\n") - // Self-check: ensure the patch we generated is actually valid - let baseText = base.joined(separator: "\n") - if SimpleUnifiedPatchApplier.apply(patch: patchString, to: baseText) == nil { - assertionFailure("Generator produced invalid unified patch for \(path) at difficulty \(difficulty)") - #if DEBUG - fatalError("Invalid unified patch generated; fix generator") - #endif - } - - var params: [String: BenchmarkJSONValue] = [ - "patch": .string(patchString), - "difficulty": .string(difficulty.rawValue) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "apply_unified_patch_swift", - type: .applyUnifiedPatchSwift, - language: .swift, - difficulty: difficulty, - selectFiles: [path], - maxEdits: maxEdits, - instructions: instructions, - task: "Apply the unified diff to \(path).", - acceptance: acceptance, - params: params - ) - } - - // MARK: - insert_guard_ts - - private func generateInsertGuardTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium: - [tsWorkPath] - case .hard: - [tsWorkPath, "src/ts/work/WorkA.ts"] - case .veryHard: - [tsWorkPath, "src/ts/work/WorkA.ts", "src/ts/work/WorkB.ts"] - case .simple: - [tsWorkPath] - } - let decoys = decoyCount(for: difficulty, config: config) - let snippet = "if (n < 0) {\n return 0;\n}" - let isMarkerless = shouldUseMarkerlessMode(for: difficulty) - var guardSpecs: [(path: String, uid: String)] = [] - - for path in files { - let uid = randomIdentifier(rng: &rng) - var existing = fileSystem.content(for: path) ?? "" - - // Determine complexity based on difficulty - let nearMiss: Int - let shadowClusters: Int - switch difficulty { - case .simple, .medium: - nearMiss = 2 - shadowClusters = 0 - case .hard: - nearMiss = 4 - shadowClusters = 1 - case .veryHard: - nearMiss = 6 - shadowClusters = 2 - } - - if isMarkerless { - // Markerless mode: use complex generator without anchors - let hardened = BelievableCodeFactory.tsClampFamilyComplex( - rng: &rng, - mainName: "clamp", - anchorUID: nil, - decoyAnchorUIDs: [], - nearMissFunctions: nearMiss, - inFunctionShadowClusters: shadowClusters - ) - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(hardened) - } else { - // Medium mode: use complex generator with anchors - let decoyUIDs = (0 ..< 3).map { _ in randomIdentifier(rng: &rng) } - let hardened = BelievableCodeFactory.tsClampFamilyComplex( - rng: &rng, - mainName: "clamp", - anchorUID: uid, - decoyAnchorUIDs: decoyUIDs, - nearMissFunctions: nearMiss, - inFunctionShadowClusters: shadowClusters > 0 ? 1 : 0 - ) - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(hardened) - } - - // Add helper code to increase file size and ambiguity - let helperLines = isMarkerless ? max(80, scaledLines(noise.lineCount, difficulty: difficulty)) : max(30, scaledLines(noise.lineCount / 3, difficulty: .hard)) - let helper = BelievableCodeFactory.tsUtilityModule(rng: &rng, module: "ModuleASupport", approxLines: helperLines) - existing.append("\n") - existing.append(helper) - fileSystem.setFile(path, content: existing) - guardSpecs.append((path, uid)) - } - let fullDecoys = decoys > 0 ? makeSimilarTsDecoys(rng: &rng, around: tsWorkPath) : [] - for decoy in fullDecoys { - fileSystem.setFile(decoy.path, content: decoy.content) - } - - let instructions: [String] - let acceptance: [String] - - if isMarkerless { - instructions = [ - "Insert the provided guard block inside the clamp() function, immediately after the line that declares 'const normalized'.", - "The guard block must be indented with 4 spaces to match surrounding code.", - "Only modify the clamp() function (not clampPositive, clampBounded, or other similar functions).", - "Your search block must include the function signature, the normalized line, and at least one more line (4–6 lines total).", - "Do NOT modify any other code outside the clamp() function." - ] - acceptance = [ - "Guard snippet appears inside clamp() function immediately after the normalized declaration.", - "Inserted code uses 4-space indentation without tabs.", - "Only the clamp() function was modified.", - "Other functions (clampPositive, clampBounded) remain unchanged.", - "Exactly \(files.count) file(s) were modified." - ] - } else { - instructions = [ - "Insert the provided guard block between the ANCHOR:start and ANCHOR:end comments with the matching UID.", - "The guard block must be indented with 4 spaces (not tabs) to match the anchor comment level.", - "Insert the guard on the line immediately after ANCHOR:start:UID.", - "Do NOT modify the anchor comments themselves.", - "Do NOT modify any other code outside the anchor region.", - "There are decoy anchor pairs with different UIDs - only insert into the matching UID." - ] - acceptance = [ - "Guard snippet appears exactly between matching anchors in each file.", - "The guard is inserted immediately after the ANCHOR:start line.", - "Inserted code uses 4-space indentation without tabs.", - "Anchor comments remain unchanged.", - "No other lines changed in any listed file.", - "Exactly \(files.count) file(s) were modified." - ] - } - - var params: [String: BenchmarkJSONValue] = [ - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys), - "markerless": .boolean(isMarkerless) - ] - - if isMarkerless { - params["functionName"] = .string("clamp") - params["insertAfterPattern"] = .string("normalized") - params["snippet"] = .string(snippet) - } else { - params["guards"] = .array(guardSpecs.map { - .object([ - "path": .string($0.path), - "uid": .string($0.uid), - "snippet": .string(snippet) - ]) - }) - } - if !fullDecoys.isEmpty { - params["fullDecoys"] = .array(fullDecoys.map { .string($0.path) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "insert_guard_ts", - type: .insertGuardTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Insert the guard block exactly between the anchor comments across multiple files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "insert_guard_ts", - type: .insertGuardTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Insert the guard block exactly between the anchor comments across multiple files.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - insert_guard_go - - private func generateInsertGuardGoTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium, .simple: - [goWorkPath] - case .hard: - [goWorkPath, "src/go/work/WorkA.go"] - case .veryHard: - [goWorkPath, "src/go/work/WorkA.go", "src/go/work/WorkB.go"] - } - let decoys = decoyCount(for: difficulty, config: config) - _ = noise - let snippet = "if n < 0 {\n return 0\n}" - let isMarkerless = shouldUseMarkerlessMode(for: difficulty) - var guardSpecs: [(path: String, uid: String)] = [] - - for path in files { - let uid = randomIdentifier(rng: &rng) - var existing = fileSystem.content(for: path) ?? "" - - // Ensure package header is present - if existing.isEmpty || !existing.contains("package work") { - existing = "package work\n\n" - } - - // Determine complexity based on difficulty - let nearMiss: Int - let shadowClusters: Int - switch difficulty { - case .simple, .medium: - nearMiss = 2 - shadowClusters = 0 - case .hard: - nearMiss = 4 - shadowClusters = 1 - case .veryHard: - nearMiss = 6 - shadowClusters = 2 - } - - if isMarkerless { - // Markerless mode: use complex generator without anchors - let hardened = BelievableCodeFactory.goClampFamilyComplex( - rng: &rng, - mainName: "Clamp", - anchorUID: nil, - decoyAnchorUIDs: [], - nearMissFunctions: nearMiss, - inFunctionShadowClusters: shadowClusters - ) - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(hardened) - } else { - // Medium mode: use complex generator with anchors - let decoyUIDs = (0 ..< 3).map { _ in randomIdentifier(rng: &rng) } - let hardened = BelievableCodeFactory.goClampFamilyComplex( - rng: &rng, - mainName: "Clamp", - anchorUID: uid, - decoyAnchorUIDs: decoyUIDs, - nearMissFunctions: nearMiss, - inFunctionShadowClusters: shadowClusters > 0 ? 1 : 0 - ) - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(hardened) - } - fileSystem.setFile(path, content: existing) - guardSpecs.append((path, uid)) - } - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "GuardShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "GuardClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - - let instructions: [String] - let acceptance: [String] - - if isMarkerless { - instructions = [ - "Insert the provided guard block inside the Clamp() function, immediately after the line that declares 'normalized := n'.", - "The guard block must be indented with 4 spaces to match surrounding code.", - "Only modify the Clamp() function (not ClampPositive, ClampBounded, or other similar functions).", - "Your search block must include the function signature, the normalized line, and at least one more line (4-6 lines total).", - "Do NOT modify any other code outside the Clamp() function." - ] - acceptance = [ - "Guard snippet appears inside Clamp() function immediately after the normalized declaration.", - "Inserted code uses 4-space indentation without tabs.", - "Only the Clamp() function was modified.", - "Other functions (ClampPositive, ClampBounded) remain unchanged.", - "Exactly \(files.count) file(s) were modified." - ] - } else { - instructions = [ - "Insert the provided guard block between the ANCHOR:start and ANCHOR:end comments with the matching UID.", - "The guard block must be indented with 4 spaces (not tabs) to match the anchor comment level.", - "Insert the guard on the line immediately after ANCHOR:start:UID.", - "Do NOT modify the anchor comments themselves.", - "Do NOT modify any other code outside the anchor region.", - "There are decoy anchor pairs with different UIDs - only insert into the matching UID." - ] - acceptance = [ - "Guard snippet appears exactly between matching anchors in each file.", - "The guard is inserted immediately after the ANCHOR:start line.", - "Inserted code uses 4-space indentation without tabs.", - "Anchor comments remain unchanged.", - "No other lines changed in any listed file.", - "Exactly \(files.count) file(s) were modified." - ] - } - - var params: [String: BenchmarkJSONValue] = [ - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys), - "markerless": .boolean(isMarkerless) - ] - - if isMarkerless { - params["functionName"] = .string("Clamp") - params["insertAfterPattern"] = .string("normalized") - params["snippet"] = .string(snippet) - } else { - params["guards"] = .array(guardSpecs.map { - .object([ - "path": .string($0.path), - "uid": .string($0.uid), - "snippet": .string(snippet) - ]) - }) - } - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "insert_guard_go", - type: .insertGuardGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Insert the guard block exactly between the anchor comments across multiple files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "insert_guard_go", - type: .insertGuardGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Insert the guard block exactly between the anchor comments across multiple files.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - insert_guard_swift - - private func generateInsertGuardSwiftTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium, .simple: - [swiftWorkPath] - case .hard: - [swiftWorkPath, "src/swift/work/WorkA.swift"] - case .veryHard: - [swiftWorkPath, "src/swift/work/WorkA.swift", "src/swift/work/WorkB.swift"] - } - let decoys = decoyCount(for: difficulty, config: config) - _ = noise - let snippet = "if n < 0 {\n\treturn 0\n}" - let isMarkerless = shouldUseMarkerlessMode(for: difficulty) - var guardSpecs: [(path: String, uid: String)] = [] - - for path in files { - let uid = randomIdentifier(rng: &rng) - var existing = fileSystem.content(for: path) ?? "" - - // Determine complexity based on difficulty - let nearMiss: Int - let shadowClusters: Int - switch difficulty { - case .simple, .medium: - nearMiss = 2 - shadowClusters = 0 - case .hard: - nearMiss = 4 - shadowClusters = 1 - case .veryHard: - nearMiss = 6 - shadowClusters = 2 - } - - if isMarkerless { - // Markerless mode: use complex generator without anchors - let hardened = BelievableCodeFactory.swiftClampFamilyComplex( - rng: &rng, - mainName: "clamp", - anchorUID: nil, - decoyAnchorUIDs: [], - nearMissFunctions: nearMiss, - inFunctionShadowClusters: shadowClusters - ) - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(hardened) - } else { - // Medium mode: use complex generator with anchors - let decoyUIDs = (0 ..< 3).map { _ in randomIdentifier(rng: &rng) } - let hardened = BelievableCodeFactory.swiftClampFamilyComplex( - rng: &rng, - mainName: "clamp", - anchorUID: uid, - decoyAnchorUIDs: decoyUIDs, - nearMissFunctions: nearMiss, - inFunctionShadowClusters: shadowClusters > 0 ? 1 : 0 - ) - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(hardened) - } - fileSystem.setFile(path, content: existing) - guardSpecs.append((path, uid)) - } - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "GuardShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "GuardClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - - let instructions: [String] - let acceptance: [String] - - if isMarkerless { - instructions = [ - "Insert the provided guard block inside the clamp() function, immediately after the line that declares 'let normalized'.", - "The guard block must be indented with tabs to match surrounding code.", - "Only modify the clamp() function (not clampPositive, clampBounded, or other similar functions).", - "Your search block must include the function signature, the normalized line, and at least one more line (4-6 lines total).", - "Do NOT modify any other code outside the clamp() function." - ] - acceptance = [ - "Guard snippet appears inside clamp() function immediately after the normalized declaration.", - "Inserted code uses tab indentation without spaces.", - "Only the clamp() function was modified.", - "Other functions (clampPositive, clampBounded) remain unchanged.", - "Exactly \(files.count) file(s) were modified." - ] - } else { - instructions = [ - "Insert the provided guard block between the ANCHOR:start and ANCHOR:end comments with the matching UID.", - "The guard block must be indented with tabs (not spaces) to match the anchor comment level.", - "Insert the guard on the line immediately after ANCHOR:start:UID.", - "Do NOT modify the anchor comments themselves.", - "Do NOT modify any other code outside the anchor region.", - "There are decoy anchor pairs with different UIDs - only insert into the matching UID." - ] - acceptance = [ - "Guard snippet appears exactly between matching anchors in each file.", - "The guard is inserted immediately after the ANCHOR:start line.", - "Inserted code uses tab indentation without spaces.", - "Anchor comments remain unchanged.", - "No other lines changed in any listed file.", - "Exactly \(files.count) file(s) were modified." - ] - } - - var params: [String: BenchmarkJSONValue] = [ - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys), - "markerless": .boolean(isMarkerless) - ] - - if isMarkerless { - params["functionName"] = .string("clamp") - params["insertAfterPattern"] = .string("normalized") - params["snippet"] = .string(snippet) - } else { - params["guards"] = .array(guardSpecs.map { - .object([ - "path": .string($0.path), - "uid": .string($0.uid), - "snippet": .string(snippet) - ]) - }) - } - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "insert_guard_swift", - type: .insertGuardSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Insert the guard block exactly between the anchor comments across multiple files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "insert_guard_swift", - type: .insertGuardSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, files.count), - instructions: instructions, - task: "Insert the guard block exactly between the anchor comments across multiple files.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - patch_block_ts - - private func generatePatchBlockTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium: - [tsWorkPath] - case .hard: - [tsWorkPath, "src/ts/work/WorkA.ts"] - case .veryHard: - [tsWorkPath, "src/ts/work/WorkA.ts", "src/ts/work/WorkB.ts"] - case .simple: - [tsWorkPath] - } - let decoys = decoyCount(for: difficulty, config: config) - let snippet = """ - export function block2(n: number): number { - const squared = n * n; - return squared; - } - """ - let isMarkerless = shouldUseMarkerlessMode(for: difficulty) - let functionName = "block2" - - if isMarkerless { - // Markerless mode: create ambiguous neighbor functions - for path in files { - var existing = fileSystem.content(for: path) ?? "" - let targetBlock = """ - export function block2(n: number): number { - return n * 2; - } - """ - let neighborBlock1 = """ - export function block2Alt(n: number): number { - return n * 2; - } - """ - let neighborBlock2 = """ - export function block2Helper(n: number): number { - // helper function - return n * 2; - } - """ - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(targetBlock) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(neighborBlock1) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(neighborBlock2) - fileSystem.setFile(path, content: existing) - } - - let fullDecoys = decoys > 0 ? makeSimilarTsDecoys(rng: &rng, around: tsWorkPath) : [] - for decoy in fullDecoys { - fileSystem.setFile(decoy.path, content: decoy.content) - } - - let instructions = [ - "Locate the function named '\(functionName)' in each file.", - "Replace its entire body with the provided snippet.", - "Your block must include: the function signature line, at least one line from the body, and the closing brace (3-8 lines total).", - "Use 4 spaces for indentation (NOT tabs).", - "Do NOT modify other functions or code outside '\(functionName)'.", - "Use precise search-replace; do NOT use placeholders." - ] - let acceptance = [ - "The function '\(functionName)' body exactly matches the provided snippet in each file.", - "Other functions (block2Alt, block2Helper, etc.) remain unchanged.", - "No code outside '\(functionName)' was modified.", - "Exactly \(files.count) file(s) were modified." - ] - - var params: [String: BenchmarkJSONValue] = [ - "functionName": .string(functionName), - "snippet": .string(snippet), - "markerless": .boolean(true), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !fullDecoys.isEmpty { - params["fullDecoys"] = .array(fullDecoys.map { .string($0.path) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "patch_block_ts", - type: .patchBlockTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of function '\(functionName)' in each file with the provided snippet.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "patch_block_ts", - type: .patchBlockTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of function '\(functionName)' in each file with the provided snippet.", - acceptance: acceptance, - params: params - ) - } else { - // Marker-based mode (Medium and below) - var blockSpecs: [(path: String, uid: String)] = [] - for path in files { - let uid = randomIdentifier(rng: &rng) - var existing = fileSystem.content(for: path) ?? "" - let block = """ - /* BLOCK START:\(uid) */ - export function block2(n: number): number { - return n * 2; - } - /* BLOCK END:\(uid) */ - """ - let twinUID = randomIdentifier(rng: &rng) - let twinBlock = """ - /* BLOCK START:\(twinUID) */ - export function block2(n: number): number { - // secondary block - return n * 2; - } - /* BLOCK END:\(twinUID) */ - """ - let shadowUID = randomIdentifier(rng: &rng) - let shadowBlock = """ - /* BLOCK START:\(shadowUID) */ - export function block2(n: number): number { - - return n * 2; - } - /* BLOCK END:\(shadowUID) */ - """ - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(block) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(twinBlock) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(shadowBlock) - fileSystem.setFile(path, content: existing) - blockSpecs.append((path, uid)) - } - let fullDecoys = decoys > 0 ? makeSimilarTsDecoys(rng: &rng, around: tsWorkPath) : [] - for decoy in fullDecoys { - fileSystem.setFile(decoy.path, content: decoy.content) - } - let instructions = [ - "Replace the code between /* BLOCK START:UID */ and /* BLOCK END:UID */ markers.", - "Keep the BLOCK START and BLOCK END comment markers unchanged.", - "Replace ONLY the function definition and body between these markers.", - "Do NOT modify blocks with different UIDs - there are decoy blocks present.", - "Use precise search-replace; do NOT use placeholders." - ] - let acceptance = [ - "The function between the specified UID markers exactly matches the provided snippet.", - "BLOCK START and BLOCK END markers remain unchanged.", - "All other blocks (with different UIDs) remain identical to baseline.", - "No code outside any block markers was modified.", - "Exactly \(files.count) file(s) were modified." - ] - var params: [String: BenchmarkJSONValue] = [ - "blocks": .array(blockSpecs.map { - .object([ - "path": .string($0.path), - "uid": .string($0.uid), - "snippet": .string(snippet) - ]) - }), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !fullDecoys.isEmpty { - params["fullDecoys"] = .array(fullDecoys.map { .string($0.path) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "patch_block_ts", - type: .patchBlockTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of the tagged block in each file.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "patch_block_ts", - type: .patchBlockTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of the tagged block in each file.", - acceptance: acceptance, - params: params - ) - } - } - - // MARK: - patch_block_go - - private func generatePatchBlockGoTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium, .simple: - [goWorkPath] - case .hard: - [goWorkPath, "src/go/work/WorkA.go"] - case .veryHard: - [goWorkPath, "src/go/work/WorkA.go", "src/go/work/WorkB.go"] - } - let decoys = decoyCount(for: difficulty, config: config) - _ = noise - let snippet = """ - func block2(n int) int { - squared := n * n - return squared - } - """ - let isMarkerless = shouldUseMarkerlessMode(for: difficulty) - let functionName = "block2" - - if isMarkerless { - // Markerless mode: create ambiguous neighbor functions - for path in files { - var existing = fileSystem.content(for: path) ?? "" - if existing.isEmpty { - existing.append("package work\n\n") - } else if !existing.hasSuffix("\n") { - existing.append("\n") - } - let targetBlock = """ - func block2(n int) int { - return n * 2 - } - """ - let neighborBlock1 = """ - func block2Alt(n int) int { - return n * 2 - } - """ - let neighborBlock2 = """ - func block2Helper(n int) int { - // helper function - return n * 2 - } - """ - existing.append(targetBlock) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(neighborBlock1) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(neighborBlock2) - fileSystem.setFile(path, content: existing) - } - - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "BlockShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "BlockClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - - let instructions = [ - "Locate the function named '\(functionName)' in each file.", - "Replace its entire body with the provided snippet.", - "Your block must include: the function signature line, at least one line from the body, and the closing brace (3-8 lines total).", - "Use 4 spaces for indentation (NOT tabs).", - "Do NOT modify other functions or code outside '\(functionName)'.", - "Use precise search-replace; do NOT use placeholders." - ] - let acceptance = [ - "The function '\(functionName)' body exactly matches the provided snippet in each file.", - "Other functions (block2Alt, block2Helper, etc.) remain unchanged.", - "No code outside '\(functionName)' was modified.", - "Exactly \(files.count) file(s) were modified." - ] - - var params: [String: BenchmarkJSONValue] = [ - "functionName": .string(functionName), - "snippet": .string(snippet), - "markerless": .boolean(true), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "patch_block_go", - type: .patchBlockGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of function '\(functionName)' in each file with the provided snippet.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "patch_block_go", - type: .patchBlockGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of function '\(functionName)' in each file with the provided snippet.", - acceptance: acceptance, - params: params - ) - } else { - // Marker-based mode (Medium and below) - var blockSpecs: [(path: String, uid: String)] = [] - for path in files { - let uid = randomIdentifier(rng: &rng) - var existing = fileSystem.content(for: path) ?? "" - if existing.isEmpty { - existing.append("package work\n\n") - } else if !existing.hasSuffix("\n") { - existing.append("\n") - } - let block = """ - /* BLOCK START:\(uid) */ - func block2(n int) int { - return n * 2 - } - /* BLOCK END:\(uid) */ - """ - let twinUID = randomIdentifier(rng: &rng) - let twinBlock = """ - /* BLOCK START:\(twinUID) */ - func block2(n int) int { - // secondary block - return n * 2 - } - /* BLOCK END:\(twinUID) */ - """ - let shadowUID = randomIdentifier(rng: &rng) - let shadowBlock = """ - /* BLOCK START:\(shadowUID) */ - func block2(n int) int { - - return n * 2 - } - /* BLOCK END:\(shadowUID) */ - """ - existing.append(block) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(twinBlock) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(shadowBlock) - fileSystem.setFile(path, content: existing) - blockSpecs.append((path, uid)) - } - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "BlockShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "BlockClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - let instructions = [ - "Replace the code between /* BLOCK START:UID */ and /* BLOCK END:UID */ markers.", - "Keep the BLOCK START and BLOCK END comment markers unchanged.", - "Replace ONLY the function definition and body between these markers.", - "Do NOT modify blocks with different UIDs - there are decoy blocks present.", - "Use precise search-replace; do NOT use placeholders." - ] - let acceptance = [ - "The function between the specified UID markers exactly matches the provided snippet.", - "BLOCK START and BLOCK END markers remain unchanged.", - "All other blocks (with different UIDs) remain identical to baseline.", - "No code outside any block markers was modified.", - "Exactly \(files.count) file(s) were modified." - ] - var params: [String: BenchmarkJSONValue] = [ - "blocks": .array(blockSpecs.map { - .object([ - "path": .string($0.path), - "uid": .string($0.uid), - "snippet": .string(snippet) - ]) - }), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "patch_block_go", - type: .patchBlockGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of the tagged block in each file.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "patch_block_go", - type: .patchBlockGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of the tagged block in each file.", - acceptance: acceptance, - params: params - ) - } - } - - // MARK: - patch_block_swift - - private func generatePatchBlockSwiftTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium, .simple: - [swiftWorkPath] - case .hard: - [swiftWorkPath, "src/swift/work/WorkA.swift"] - case .veryHard: - [swiftWorkPath, "src/swift/work/WorkA.swift", "src/swift/work/WorkB.swift"] - } - let decoys = decoyCount(for: difficulty, config: config) - _ = noise - let snippet = """ - public func block2(_ n: Int) -> Int { - let squared = n * n - return squared - } - """ - let isMarkerless = shouldUseMarkerlessMode(for: difficulty) - let functionName = "block2" - - if isMarkerless { - // Markerless mode: create ambiguous neighbor functions - for path in files { - var existing = fileSystem.content(for: path) ?? "" - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - let targetBlock = """ - public func block2(_ n: Int) -> Int { - return n * 2 - } - """ - let neighborBlock1 = """ - public func block2Alt(_ n: Int) -> Int { - return n * 2 - } - """ - let neighborBlock2 = """ - public func block2Helper(_ n: Int) -> Int { - // helper function - return n * 2 - } - """ - existing.append(targetBlock) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(neighborBlock1) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(neighborBlock2) - fileSystem.setFile(path, content: existing) - } - - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "BlockShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "BlockClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - - let instructions = [ - "Locate the function named '\(functionName)' in each file.", - "Replace its entire body with the provided snippet.", - "Your block must include: the function signature line, at least one line from the body, and the closing brace (3-8 lines total).", - "Use tabs for indentation (NOT spaces).", - "Do NOT modify other functions or code outside '\(functionName)'.", - "Use precise search-replace; do NOT use placeholders." - ] - let acceptance = [ - "The function '\(functionName)' body exactly matches the provided snippet in each file.", - "Other functions (block2Alt, block2Helper, etc.) remain unchanged.", - "No code outside '\(functionName)' was modified.", - "Exactly \(files.count) file(s) were modified." - ] - - var params: [String: BenchmarkJSONValue] = [ - "functionName": .string(functionName), - "snippet": .string(snippet), - "markerless": .boolean(true), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "patch_block_swift", - type: .patchBlockSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of function '\(functionName)' in each file with the provided snippet.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "patch_block_swift", - type: .patchBlockSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of function '\(functionName)' in each file with the provided snippet.", - acceptance: acceptance, - params: params - ) - } else { - // Marker-based mode (Medium and below) - var blockSpecs: [(path: String, uid: String)] = [] - for path in files { - let uid = randomIdentifier(rng: &rng) - var existing = fileSystem.content(for: path) ?? "" - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - let block = """ - /* BLOCK START:\(uid) */ - public func block2(_ n: Int) -> Int { - return n * 2 - } - /* BLOCK END:\(uid) */ - """ - let twinUID = randomIdentifier(rng: &rng) - let twinBlock = """ - /* BLOCK START:\(twinUID) */ - public func block2(_ n: Int) -> Int { - // secondary block - return n * 2 - } - /* BLOCK END:\(twinUID) */ - """ - let shadowUID = randomIdentifier(rng: &rng) - let shadowBlock = """ - /* BLOCK START:\(shadowUID) */ - public func block2(_ n: Int) -> Int { - - return n * 2 - } - /* BLOCK END:\(shadowUID) */ - """ - existing.append(block) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(twinBlock) - if !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(shadowBlock) - fileSystem.setFile(path, content: existing) - blockSpecs.append((path, uid)) - } - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "BlockShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "BlockClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - let instructions = [ - "Replace the code between /* BLOCK START:UID */ and /* BLOCK END:UID */ markers.", - "Keep the BLOCK START and BLOCK END comment markers unchanged.", - "Replace ONLY the function definition and body between these markers.", - "Do NOT modify blocks with different UIDs - there are decoy blocks present.", - "Use precise search-replace; do NOT use placeholders." - ] - let acceptance = [ - "The function between the specified UID markers exactly matches the provided snippet.", - "BLOCK START and BLOCK END markers remain unchanged.", - "All other blocks (with different UIDs) remain identical to baseline.", - "No code outside any block markers was modified.", - "Exactly \(files.count) file(s) were modified." - ] - var params: [String: BenchmarkJSONValue] = [ - "blocks": .array(blockSpecs.map { - .object([ - "path": .string($0.path), - "uid": .string($0.uid), - "snippet": .string(snippet) - ]) - }), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "patch_block_swift", - type: .patchBlockSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of the tagged block in each file.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "patch_block_swift", - type: .patchBlockSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(2, files.count * 2), - instructions: instructions, - task: "Replace the body of the tagged block in each file.", - acceptance: acceptance, - params: params - ) - } - } - - // MARK: - swap_args_in_region_ts - - private func generateSwapArgsTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium: - [tsWorkPath] - case .hard: - [tsWorkPath, "src/ts/work/WorkA.ts"] - case .veryHard: - [tsWorkPath, "src/ts/work/WorkA.ts", "src/ts/work/WorkB.ts", "src/ts/work/WorkC.ts"] - case .simple: - [tsWorkPath] - } - let helpers = BelievableCodeFactory.tsUtilityModule(rng: &rng, module: "RegionHelpers", approxLines: max(40, scaledLines(40, difficulty: .hard))) - fileSystem.setFile("src/ts/regions/helpers.ts", content: helpers) - let decoys = decoyCount(for: difficulty, config: config) - let isMarkerless = shouldUseMarkerlessMode(for: difficulty) - let functionName = "render" - - if isMarkerless { - // Markerless mode: create ambiguous neighbor functions - let expected = switch difficulty { - case .hard: - 4 - case .veryHard: - 8 - default: - 4 - } - - for path in files { - var existing = fileSystem.content(for: path) ?? "" - if !existing.contains("import { use }") { - existing.append("import { use } from './hooks';\n\n") - } - - let targetBlock = """ - export function render(list: string[]): string { - let output = ''; - """ - var targetLines = [String]() - for idx in 0 ..< expected { - targetLines.append(" output += use('a\(idx)', 'b\(idx)');") - } - targetLines.append(" return output;") - targetLines.append("}") - - let neighborBlock1 = """ - export function renderAlt(list: string[]): string { - let output = ''; - output += use('x0', 'y0'); - output += use('x1', 'y1'); - return output; - } - """ - let neighborBlock2 = """ - export function renderHelper(list: string[]): string { - // helper function - let output = ''; - output += use('p0', 'q0'); - return output; - } - """ - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(targetBlock) - existing.append("\n") - existing.append(targetLines.joined(separator: "\n")) - existing.append("\n") - existing.append(neighborBlock1) - existing.append("\n") - existing.append(neighborBlock2) - fileSystem.setFile(path, content: existing) - } - - let fullDecoys = decoys > 0 ? makeSimilarTsDecoys(rng: &rng, around: tsWorkPath) : [] - for decoy in fullDecoys { - fileSystem.setFile(decoy.path, content: decoy.content) - } - - let instructions = [ - "Locate the function named '\(functionName)' in each file.", - "Swap all use(a, b) calls to use(b, a) within this function.", - "Your block must include: at least one unchanged line before the first use() call, all use() calls, and at least one unchanged line after (3-8 lines total).", - "Use 4 spaces for indentation (NOT tabs).", - "Do NOT modify other functions or code outside '\(functionName)'.", - "Use precise search-replace; do NOT use placeholders." - ] - - let acceptance = [ - "All use() calls in function '\(functionName)' have swapped arguments: use(b, a).", - "Other functions (renderAlt, renderHelper, etc.) remain unchanged.", - "No code outside '\(functionName)' was modified.", - "Exactly \(files.count) file(s) were modified." - ] - - var params: [String: BenchmarkJSONValue] = [ - "functionName": .string(functionName), - "expectedSwaps": .integer(expected), - "markerless": .boolean(true), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !fullDecoys.isEmpty { - params["fullDecoys"] = .array(fullDecoys.map { .string($0.path) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "swap_args_in_region_ts", - type: .swapArgsInRegionTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, expected * files.count), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) in function '\(functionName)' across multiple files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "swap_args_in_region_ts", - type: .swapArgsInRegionTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, expected * files.count), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) in function '\(functionName)' across multiple files.", - acceptance: acceptance, - params: params - ) - } else { - // Marker-based mode (Medium and below) - var regionSpecs: [(path: String, uid: String, expected: Int)] = [] - for (index, path) in files.enumerated() { - let uid = randomIdentifier(rng: &rng) - let decoyUID = randomIdentifier(rng: &rng) - let shadowUID = randomIdentifier(rng: &rng) - let expected: Int = { - switch difficulty { - case .medium, .simple: - return 4 - case .hard: - return 8 - case .veryHard: - let base = index == 0 ? 8 : 4 - return base + (index == files.count - 1 ? 4 : 0) - } - }() - var existing = fileSystem.content(for: path) ?? "" - var block: [String] = [] - if index == 0, !existing.contains("import { use }") { - block.append("import { use } from './hooks';") - block.append("") - } - block.append("export function render(list: string[]): string {") - block.append(" let output = '';") - block.append(" /* START_SWAP:\(uid) */") - for idx in 0 ..< expected { - block.append(" output += use('a\(idx)', 'b\(idx)');") - } - block.append(" /* END_SWAP:\(uid) */") - block.append(" // alternate region") - block.append(" /* START_SWAP:\(decoyUID) */") - for idx in 0 ..< 2 { - block.append(" output += use('a\(idx)', 'b\(idx)');") - } - block.append(" /* END_SWAP:\(decoyUID) */") - block.append(" /* START_SWAP:\(shadowUID) */") - block.append(" // additional region") - for idx in 0 ..< 2 { - block.append(" output += use('a\(idx)', 'b\(idx)');") - } - block.append(" /* END_SWAP:\(shadowUID) */") - block.append(" output += use('outsideA', 'outsideB');") - block.append(" output += use('outsideC', 'outsideD');") - block.append(" output += use('outsideE', 'outsideF');") - block.append(" output += use('outsideG', 'outsideH');") - block.append(" return output;") - block.append("}") - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - existing.append(block.joined(separator: "\n")) - fileSystem.setFile(path, content: existing) - regionSpecs.append((path, uid, expected)) - } - let fullDecoys = decoys > 0 ? makeSimilarTsDecoys(rng: &rng, around: tsWorkPath) : [] - for decoy in fullDecoys { - fileSystem.setFile(decoy.path, content: decoy.content) - } - let instructions = [ - "Within each START_SWAP/END_SWAP region with the specified UID, swap all use(a, b) calls to use(b, a).", - "Example: use('a0', 'b0') becomes use('b0', 'a0').", - "Do NOT modify use() calls outside any swap region.", - "Do NOT modify swap regions with different UIDs - there are decoy regions present.", - "Do NOT rewrite the entire function - only modify the argument order in targeted calls." - ] - let acceptance = [ - "Every use() call inside the specified UID region has swapped arguments: use(b, a).", - "No use() calls outside any swap region were modified.", - "All use() calls outside swap regions still use the original (a, b) order.", - "The number of use() calls inside the region remains unchanged.", - "Exactly \(files.count) file(s) were modified." - ] - let totalSwaps = regionSpecs.reduce(0) { $0 + $1.expected } - var params: [String: BenchmarkJSONValue] = [ - "regions": .array(regionSpecs.map { - .object([ - "path": .string($0.path), - "uid": .string($0.uid), - "expectedSwaps": .integer($0.expected) - ]) - }), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !fullDecoys.isEmpty { - params["fullDecoys"] = .array(fullDecoys.map { .string($0.path) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "swap_args_in_region_ts", - type: .swapArgsInRegionTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, totalSwaps), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) within the marked regions across multiple files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "swap_args_in_region_ts", - type: .swapArgsInRegionTs, - language: .ts, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, totalSwaps), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) within the marked regions across multiple files.", - acceptance: acceptance, - params: params - ) - } - } - - // MARK: - swap_args_in_region_go - - private func generateSwapArgsGoTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium, .simple: - [goWorkPath] - case .hard: - [goWorkPath, "src/go/work/WorkA.go"] - case .veryHard: - [goWorkPath, "src/go/work/WorkA.go", "src/go/work/WorkB.go", "src/go/work/WorkC.go"] - } - let decoys = decoyCount(for: difficulty, config: config) - _ = noise - let isMarkerless = shouldUseMarkerlessMode(for: difficulty) - let functionName = "Render" - - if isMarkerless { - // Markerless mode: create ambiguous neighbor functions - let expected = switch difficulty { - case .hard: - 4 - case .veryHard: - 8 - default: - 4 - } - - for path in files { - var existing = fileSystem.content(for: path) ?? "" - let needsPrelude = existing.isEmpty - if needsPrelude { - existing.append("package work\n\n") - existing.append("func use(a, b string) string { return a + b }\n\n") - } else if !existing.hasSuffix("\n") { - existing.append("\n") - } - - let targetBlock = """ - func Render(list []string) string { - out := "" - """ - var targetLines = [String]() - for idx in 0 ..< expected { - targetLines.append(" out += use(\"a\(idx)\", \"b\(idx)\")") - } - targetLines.append(" return out") - targetLines.append("}") - - let neighborBlock1 = """ - func RenderAlt(list []string) string { - out := "" - out += use("x0", "y0") - out += use("x1", "y1") - return out - } - """ - let neighborBlock2 = """ - func RenderHelper(list []string) string { - // helper function - out := "" - out += use("p0", "q0") - return out - } - """ - existing.append(targetBlock) - existing.append("\n") - existing.append(targetLines.joined(separator: "\n")) - existing.append("\n") - existing.append(neighborBlock1) - existing.append("\n") - existing.append(neighborBlock2) - fileSystem.setFile(path, content: existing) - } - - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "SwapShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "SwapClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - - let instructions = [ - "Locate the function named '\(functionName)' in each file.", - "Swap all use(a, b) calls to use(b, a) within this function.", - "Your block must include: at least one unchanged line before the first use() call, all use() calls, and at least one unchanged line after (3-8 lines total).", - "Use 4 spaces for indentation (NOT tabs).", - "Do NOT modify other functions or code outside '\(functionName)'.", - "Use precise search-replace; do NOT use placeholders." - ] - - let acceptance = [ - "All use() calls in function '\(functionName)' have swapped arguments: use(b, a).", - "Other functions (RenderAlt, RenderHelper, etc.) remain unchanged.", - "No code outside '\(functionName)' was modified.", - "Exactly \(files.count) file(s) were modified." - ] - - var params: [String: BenchmarkJSONValue] = [ - "functionName": .string(functionName), - "expectedSwaps": .integer(expected), - "markerless": .boolean(true), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "swap_args_go", - type: .swapArgsInRegionGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, expected * files.count), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) in function '\(functionName)' across multiple files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "swap_args_go", - type: .swapArgsInRegionGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, expected * files.count), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) in function '\(functionName)' across multiple files.", - acceptance: acceptance, - params: params - ) - } else { - // Marker-based mode (Medium and below) - var regionSpecs: [(path: String, uid: String, expected: Int)] = [] - for (index, path) in files.enumerated() { - let uid = randomIdentifier(rng: &rng) - let decoyUID = randomIdentifier(rng: &rng) - let shadowUID = randomIdentifier(rng: &rng) - let expected: Int = { - switch difficulty { - case .medium, .simple: - return 4 - case .hard: - return 8 - case .veryHard: - let base = index == 0 ? 8 : 4 - return base + (index == files.count - 1 ? 4 : 0) - } - }() - var existing = fileSystem.content(for: path) ?? "" - var lines: [String] = [] - let needsPrelude = existing.isEmpty - if needsPrelude { - lines.append("package work") - lines.append("") - lines.append("func use(a, b string) string { return a + b }") - lines.append("") - } else if !existing.hasSuffix("\n") { - existing.append("\n") - } - lines.append("func Render(list []string) string {") - lines.append(" out := \"\"") - lines.append(" /* START_SWAP:\(uid) */") - for idx in 0 ..< expected { - lines.append(" out += use(\"a\(idx)\", \"b\(idx)\")") - } - lines.append(" /* END_SWAP:\(uid) */") - lines.append(" // alternate region") - lines.append(" /* START_SWAP:\(decoyUID) */") - for idx in 0 ..< 2 { - lines.append(" out += use(\"a\(idx)\", \"b\(idx)\")") - } - lines.append(" /* END_SWAP:\(decoyUID) */") - lines.append(" /* START_SWAP:\(shadowUID) */") - lines.append(" // additional region") - for idx in 0 ..< 2 { - lines.append(" out += use(\"a\(idx)\", \"b\(idx)\")") - } - lines.append(" /* END_SWAP:\(shadowUID) */") - lines.append(" out += use(\"outsideA\", \"outsideB\")") - lines.append(" out += use(\"outsideC\", \"outsideD\")") - lines.append(" out += use(\"outsideE\", \"outsideF\")") - lines.append(" out += use(\"outsideG\", \"outsideH\")") - lines.append(" return out") - lines.append("}") - if needsPrelude { - existing = lines.joined(separator: "\n") - } else { - existing.append(lines.joined(separator: "\n")) - } - fileSystem.setFile(path, content: existing) - regionSpecs.append((path, uid, expected)) - } - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "SwapShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.goDecoyFile(rng: &rng, name: "SwapClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - let instructions = [ - "Within each START_SWAP/END_SWAP region with the specified UID, swap all use(a, b) calls to use(b, a).", - "Example: use(\"a0\", \"b0\") becomes use(\"b0\", \"a0\").", - "Do NOT modify use() calls outside any swap region.", - "Do NOT modify swap regions with different UIDs - there are decoy regions present.", - "Do NOT rewrite the entire function - only modify the argument order in targeted calls." - ] - let acceptance = [ - "Every use() call inside the specified UID region has swapped arguments: use(b, a).", - "No use() calls outside any swap region were modified.", - "All use() calls outside swap regions still use the original (a, b) order.", - "The number of use() calls inside the region remains unchanged.", - "Exactly \(files.count) file(s) were modified." - ] - let totalSwaps = regionSpecs.reduce(0) { $0 + $1.expected } - var params: [String: BenchmarkJSONValue] = [ - "regions": .array(regionSpecs.map { - .object([ - "path": .string($0.path), - "uid": .string($0.uid), - "expectedSwaps": .integer($0.expected) - ]) - }), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "swap_args_go", - type: .swapArgsInRegionGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, totalSwaps), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) within the marked regions across multiple files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "swap_args_go", - type: .swapArgsInRegionGo, - language: .go, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, totalSwaps), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) within the marked regions across multiple files.", - acceptance: acceptance, - params: params - ) - } - } - - // MARK: - swap_args_in_region_swift - - private func generateSwapArgsSwiftTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - noise: BenchmarkNoiseConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let files: [String] = switch difficulty { - case .medium, .simple: - [swiftWorkPath] - case .hard: - [swiftWorkPath, "src/swift/work/WorkA.swift"] - case .veryHard: - [swiftWorkPath, "src/swift/work/WorkA.swift", "src/swift/work/WorkB.swift", "src/swift/work/WorkC.swift"] - } - let decoys = decoyCount(for: difficulty, config: config) - _ = noise - let isMarkerless = shouldUseMarkerlessMode(for: difficulty) - let functionName = "render" - - if isMarkerless { - // Markerless mode: create ambiguous neighbor functions - let expected = switch difficulty { - case .hard: - 4 - case .veryHard: - 8 - default: - 4 - } - - for path in files { - var existing = fileSystem.content(for: path) ?? "" - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - if !existing.contains("func use(") { - existing.append("func use(_ a: String, _ b: String) -> String { a + b }\n\n") - } - - let targetBlock = """ - func render(_ list: [String]) -> String { - \tvar output = "" - """ - var targetLines = [String]() - for idx in 0 ..< expected { - targetLines.append("\toutput += use(\"a\(idx)\", \"b\(idx)\")") - } - targetLines.append("\treturn output") - targetLines.append("}") - - let neighborBlock1 = """ - func renderAlt(_ list: [String]) -> String { - \tvar output = "" - \toutput += use("x0", "y0") - \toutput += use("x1", "y1") - \treturn output - } - """ - let neighborBlock2 = """ - func renderHelper(_ list: [String]) -> String { - \t// helper function - \tvar output = "" - \toutput += use("p0", "q0") - \treturn output - } - """ - existing.append(targetBlock) - existing.append("\n") - existing.append(targetLines.joined(separator: "\n")) - existing.append("\n") - existing.append(neighborBlock1) - existing.append("\n") - existing.append(neighborBlock2) - fileSystem.setFile(path, content: existing) - } - - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "SwapShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "SwapClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - - let instructions = [ - "Locate the function named '\(functionName)' in each file.", - "Swap all use(a, b) calls to use(b, a) within this function.", - "Your block must include: at least one unchanged line before the first use() call, all use() calls, and at least one unchanged line after (3-8 lines total).", - "Use tabs for indentation (NOT spaces).", - "Do NOT modify other functions or code outside '\(functionName)'.", - "Use precise search-replace; do NOT use placeholders." - ] - - let acceptance = [ - "All use() calls in function '\(functionName)' have swapped arguments: use(b, a).", - "Other functions (renderAlt, renderHelper, etc.) remain unchanged.", - "No code outside '\(functionName)' was modified.", - "Exactly \(files.count) file(s) were modified." - ] - - var params: [String: BenchmarkJSONValue] = [ - "functionName": .string(functionName), - "expectedSwaps": .integer(expected), - "markerless": .boolean(true), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "swap_args_in_region_swift", - type: .swapArgsInRegionSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, expected * files.count), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) in function '\(functionName)' across multiple files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "swap_args_in_region_swift", - type: .swapArgsInRegionSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, expected * files.count), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) in function '\(functionName)' across multiple files.", - acceptance: acceptance, - params: params - ) - } else { - // Marker-based mode (Medium and below) - var regionSpecs: [(path: String, uid: String, expected: Int)] = [] - for (index, path) in files.enumerated() { - let uid = randomIdentifier(rng: &rng) - let decoyUID = randomIdentifier(rng: &rng) - let shadowUID = randomIdentifier(rng: &rng) - let expected: Int = { - switch difficulty { - case .medium, .simple: - return 4 - case .hard: - return 8 - case .veryHard: - let base = index == 0 ? 8 : 4 - return base + (index == files.count - 1 ? 4 : 0) - } - }() - var existing = fileSystem.content(for: path) ?? "" - var lines: [String] = [] - if !existing.isEmpty, !existing.hasSuffix("\n") { - existing.append("\n") - } - if !existing.contains("func use(") { - lines.append("func use(_ a: String, _ b: String) -> String { a + b }") - lines.append("") - } - lines.append("func render(_ list: [String]) -> String {") - lines.append("\tvar output = \"\"") - lines.append("\t/* START_SWAP:\(uid) */") - for idx in 0 ..< expected { - lines.append("\toutput += use(\"a\(idx)\", \"b\(idx)\")") - } - lines.append("\t/* END_SWAP:\(uid) */") - lines.append("\t// alternate region") - lines.append("\t/* START_SWAP:\(decoyUID) */") - for idx in 0 ..< 2 { - lines.append("\toutput += use(\"a\(idx)\", \"b\(idx)\")") - } - lines.append("\t/* END_SWAP:\(decoyUID) */") - lines.append("\t/* START_SWAP:\(shadowUID) */") - lines.append("\t// additional region") - for idx in 0 ..< 2 { - lines.append("\toutput += use(\"a\(idx)\", \"b\(idx)\")") - } - lines.append("\t/* END_SWAP:\(shadowUID) */") - lines.append("\toutput += use(\"outsideA\", \"outsideB\")") - lines.append("\toutput += use(\"outsideC\", \"outsideD\")") - lines.append("\toutput += use(\"outsideE\", \"outsideF\")") - lines.append("\toutput += use(\"outsideG\", \"outsideH\")") - lines.append("\treturn output") - lines.append("}") - existing.append(lines.joined(separator: "\n")) - fileSystem.setFile(path, content: existing) - regionSpecs.append((path, uid, expected)) - } - var decoyPaths: [String] = [] - if decoys >= 1 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "SwapShadow") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - if decoys >= 2 { - let decoy = BelievableCodeFactory.swiftDecoyFile(rng: &rng, name: "SwapClone") - fileSystem.setFile(decoy.path, content: decoy.content) - decoyPaths.append(decoy.path) - } - let instructions = [ - "Within each START_SWAP/END_SWAP region with the specified UID, swap all use(a, b) calls to use(b, a).", - "Example: use(\"a0\", \"b0\") becomes use(\"b0\", \"a0\").", - "Do NOT modify use() calls outside any swap region.", - "Do NOT modify swap regions with different UIDs - there are decoy regions present.", - "Do NOT rewrite the entire function - only modify the argument order in targeted calls." - ] - let acceptance = [ - "Every use() call inside the specified UID region has swapped arguments: use(b, a).", - "No use() calls outside any swap region were modified.", - "All use() calls outside swap regions still use the original (a, b) order.", - "The number of use() calls inside the region remains unchanged.", - "Exactly \(files.count) file(s) were modified." - ] - let totalSwaps = regionSpecs.reduce(0) { $0 + $1.expected } - var params: [String: BenchmarkJSONValue] = [ - "regions": .array(regionSpecs.map { - .object([ - "path": .string($0.path), - "uid": .string($0.uid), - "expectedSwaps": .integer($0.expected) - ]) - }), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoys) - ] - if !decoyPaths.isEmpty { - params["fullDecoys"] = .array(decoyPaths.map { .string($0) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "swap_args_in_region_swift", - type: .swapArgsInRegionSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, totalSwaps), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) within the marked regions across multiple files.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "swap_args_in_region_swift", - type: .swapArgsInRegionSwift, - language: .swift, - difficulty: difficulty, - selectFiles: files, - maxEdits: max(1, totalSwaps), - instructions: instructions, - task: "Swap use(a, b) -> use(b, a) within the marked regions across multiple files.", - acceptance: acceptance, - params: params - ) - } - } - - // MARK: - index_only_apps_ts - - private func generateIndexOnlyTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let apps = ["appA", "appB", "appC"] - let packages: [String] = switch difficulty { - case .simple: - ["pkg1", "pkg2"] - case .medium: - ["pkg1", "pkg2", "pkg3", "pkg4"] - case .hard: - ["pkg1", "pkg2", "pkg3", "pkg4", "pkg5", "pkg6"] - case .veryHard: - ["pkg1", "pkg2", "pkg3", "pkg4", "pkg5", "pkg6", "pkg7", "pkg8"] - } - for app in apps { - let path = "apps/\(app)/src/index.ts" - let content = "export default function index() {\n return \"\(app)\";\n}" - fileSystem.setFile(path, content: content) - } - for pkg in packages { - let path = "packages/\(pkg)/src/index.ts" - let content = "export const value = \"\(pkg)\";" - fileSystem.setFile(path, content: content) - } - let targetApp = "appB" - let targetPath = "apps/\(targetApp)/src/index.ts" - let instructions = [ - "Modify ONLY the file apps/\(targetApp)/src/index.ts (no other files).", - "Add the comment // DONE:\(targetApp) immediately after the return statement in that file.", - "Do NOT modify any other index.ts files in apps/ or packages/ directories.", - "The marker must be added as a new line after the return statement, before the closing brace." - ] - let acceptance = [ - "apps/\(targetApp)/src/index.ts contains exactly one line with // DONE:\(targetApp).", - "The DONE marker appears on the line immediately following the return statement.", - "The function still returns \"\(targetApp)\" unchanged.", - "No other index.ts files were modified (no DONE markers, no changed return values).", - "Total edit count is exactly 1 file modified." - ] - var selectFiles = [targetPath] - selectFiles.append(contentsOf: apps.filter { $0 != targetApp }.map { "apps/\($0)/src/index.ts" }) - selectFiles.append(contentsOf: packages.map { "packages/\($0)/src/index.ts" }) - - var params: [String: BenchmarkJSONValue] = [ - "target": .string(targetApp), - "otherPaths": .array(selectFiles.filter { $0 != targetPath }.map { .string($0) }), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoyCount(for: difficulty, config: config)) - ] - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "index_only_apps_ts", - type: .indexOnlyAppsTs, - language: .ts, - difficulty: difficulty, - selectFiles: selectFiles, - maxEdits: 2, - instructions: instructions, - task: "Modify only \(targetPath) to mark completion for \(targetApp).", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "index_only_apps_ts", - type: .indexOnlyAppsTs, - language: .ts, - difficulty: difficulty, - selectFiles: selectFiles, - maxEdits: 2, - instructions: instructions, - task: "Modify only \(targetPath) to mark completion for \(targetApp).", - acceptance: acceptance, - params: params - ) - } - - // MARK: - index_only_apps_go - - private func generateIndexOnlyGoTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - _ = rng - _ = config - // Scale apps by difficulty: S/M=3, H=6, VH=8 - let apps: [String] = switch difficulty { - case .simple, .medium: - ["appA", "appB", "appC"] - case .hard: - ["appA", "appB", "appC", "appD", "appE", "appF"] - case .veryHard: - ["appA", "appB", "appC", "appD", "appE", "appF", "appG", "appH"] - } - let packages: [String] = switch difficulty { - case .simple: - ["pkg1", "pkg2"] - case .medium: - ["pkg1", "pkg2", "pkg3", "pkg4"] - case .hard: - ["pkg1", "pkg2", "pkg3", "pkg4", "pkg5", "pkg6"] - case .veryHard: - ["pkg1", "pkg2", "pkg3", "pkg4", "pkg5", "pkg6", "pkg7", "pkg8"] - } - // Create all app index files - for app in apps { - let path = "apps/\(app)/main.go" - let content = "package main\n\nfunc index() string {\n return \"\(app)\"\n}\n" - fileSystem.setFile(path, content: content) - } - // Create all package index files - for pkg in packages { - let path = "packages/\(pkg)/main.go" - let content = "package \(pkg)\n\nfunc value() string { return \"\(pkg)\" }\n" - fileSystem.setFile(path, content: content) - } - // Select target by PRIMARY selector (randomized) and write selector file - let targetIndex = rng.nextInt(upperBound: apps.count) - let targetApp = apps[targetIndex] - fileSystem.setFile("apps/PRIMARY", content: "\(targetApp)\n") - let targetPath = "apps/\(targetApp)/main.go" - - // Build selection set (target + other apps + packages) - var selectFiles: [String] = [] - selectFiles.append(targetPath) - selectFiles.append(contentsOf: apps.filter { $0 != targetApp }.map { "apps/\($0)/main.go" }) - selectFiles.append(contentsOf: packages.map { "packages/\($0)/main.go" }) - - // Instructions: explicit target for simple/medium; PRIMARY inference for hard/veryHard - let instructions: [String] = switch difficulty { - case .simple, .medium: - [ - "Modify ONLY the file \(targetPath) (no other files).", - "Add the comment // DONE:\(targetApp) immediately after the return statement in that file.", - "Do NOT modify any other main.go files in apps/ or packages/ directories.", - "The marker must be added as a new line after the return statement, before the closing brace." - ] - case .hard, .veryHard: - [ - "Read apps/PRIMARY to identify the primary app to modify.", - "Modify ONLY that app's index function file (apps//main.go).", - "Add the comment // DONE: immediately after the return statement in that file.", - "Do NOT modify any other main.go files in apps/ or packages/ directories.", - "The marker must be added as a new line after the return statement, before the closing brace." - ] - } - - // Acceptance remains explicit for verification - let acceptance = [ - "apps/\(targetApp)/main.go contains exactly one line with // DONE:\(targetApp).", - "The DONE marker appears on the line immediately following the return statement.", - "The function still returns \"\(targetApp)\" unchanged.", - "No other main.go files were modified (no DONE markers, no changed return values).", - "Total edit count is exactly 1 file modified." - ] - - // Params: include selectorPath for inference mode and otherPaths for unchanged checks - let otherPathsArray = selectFiles.filter { $0 != targetPath } - var params: [String: BenchmarkJSONValue] = [ - "target": .string(targetApp), - "otherPaths": .array(otherPathsArray.map { .string($0) }), - "difficulty": .string(difficulty.rawValue), - "selectorPath": .string("apps/PRIMARY") - ] - - // Task: explicit for S/M; generic for H/VH - let taskText = switch difficulty { - case .simple, .medium: - "Modify only \(targetPath) to mark completion for \(targetApp)." - case .hard, .veryHard: - "Identify the primary app from apps/PRIMARY and add the DONE marker to that app's index file only." - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "index_only_apps_go", - type: .indexOnlyAppsGo, - language: .go, - difficulty: difficulty, - selectFiles: selectFiles, - maxEdits: 2, - instructions: instructions, - task: taskText, - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "index_only_apps_go", - type: .indexOnlyAppsGo, - language: .go, - difficulty: difficulty, - selectFiles: selectFiles, - maxEdits: 2, - instructions: instructions, - task: taskText, - acceptance: acceptance, - params: params - ) - } - - // MARK: - index_only_apps_swift - - private func generateIndexOnlySwiftTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - _ = rng - _ = config - // Scale apps by difficulty: S/M=3, H=6, VH=8 - let apps: [String] = switch difficulty { - case .simple, .medium: - ["appA", "appB", "appC"] - case .hard: - ["appA", "appB", "appC", "appD", "appE", "appF"] - case .veryHard: - ["appA", "appB", "appC", "appD", "appE", "appF", "appG", "appH"] - } - let packages: [String] = switch difficulty { - case .simple: - ["pkg1", "pkg2"] - case .medium: - ["pkg1", "pkg2", "pkg3", "pkg4"] - case .hard: - ["pkg1", "pkg2", "pkg3", "pkg4", "pkg5", "pkg6"] - case .veryHard: - ["pkg1", "pkg2", "pkg3", "pkg4", "pkg5", "pkg6", "pkg7", "pkg8"] - } - // Create all app index files - for app in apps { - let path = "Apps/\(app)/index.swift" - let content = "public func index() -> String {\n\treturn \"\(app)\"\n}\n" - fileSystem.setFile(path, content: content) - } - // Create all package index files - for pkg in packages { - let path = "Packages/\(pkg)/index.swift" - let content = "public func value() -> String {\n\treturn \"\(pkg)\"\n}\n" - fileSystem.setFile(path, content: content) - } - // Select target by PRIMARY selector (randomized) and write selector file - let targetIndex = rng.nextInt(upperBound: apps.count) - let targetApp = apps[targetIndex] - fileSystem.setFile("Apps/PRIMARY", content: "\(targetApp)\n") - let targetPath = "Apps/\(targetApp)/index.swift" - - // Build selection set (target + other apps + packages) - var selectFiles: [String] = [] - selectFiles.append(targetPath) - selectFiles.append(contentsOf: apps.filter { $0 != targetApp }.map { "Apps/\($0)/index.swift" }) - selectFiles.append(contentsOf: packages.map { "Packages/\($0)/index.swift" }) - - // Instructions: explicit target for simple/medium; PRIMARY inference for hard/veryHard - let instructions: [String] = switch difficulty { - case .simple, .medium: - [ - "Modify ONLY the file \(targetPath) (no other files).", - "Add the comment // DONE:\(targetApp) immediately after the return statement in that file.", - "Do NOT modify any other index.swift files in Apps/ or Packages/ directories.", - "The marker must be added as a new line after the return statement, before the closing brace." - ] - case .hard, .veryHard: - [ - "Read Apps/PRIMARY to identify the primary app to modify.", - "Modify ONLY that app's index function file (Apps//index.swift).", - "Add the comment // DONE: immediately after the return statement in that file.", - "Do NOT modify any other index.swift files in Apps/ or Packages/ directories.", - "The marker must be added as a new line after the return statement, before the closing brace." - ] - } - - // Acceptance remains explicit for verification - let acceptance = [ - "\(targetPath) contains exactly one line with // DONE:\(targetApp).", - "The DONE marker appears on the line immediately following the return statement.", - "The function still returns \"\(targetApp)\" unchanged.", - "No other index.swift files were modified (no DONE markers, no changed return values).", - "Total edit count is exactly 1 file modified." - ] - - // Params: include selectorPath for inference mode and otherPaths for unchanged checks - let otherPathsArray = selectFiles.filter { $0 != targetPath } - var params: [String: BenchmarkJSONValue] = [ - "target": .string(targetApp), - "otherPaths": .array(otherPathsArray.map { .string($0) }), - "difficulty": .string(difficulty.rawValue), - "selectorPath": .string("Apps/PRIMARY") - ] - - // Task: explicit for S/M; generic for H/VH - let taskText = switch difficulty { - case .simple, .medium: - "Modify only \(targetPath) to mark completion for \(targetApp)." - case .hard, .veryHard: - "Identify the primary app from Apps/PRIMARY and add the DONE marker to that app's index file only." - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "index_only_apps_swift", - type: .indexOnlyAppsSwift, - language: .swift, - difficulty: difficulty, - selectFiles: selectFiles, - maxEdits: 2, - instructions: instructions, - task: taskText, - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "index_only_apps_swift", - type: .indexOnlyAppsSwift, - language: .swift, - difficulty: difficulty, - selectFiles: selectFiles, - maxEdits: 2, - instructions: instructions, - task: taskText, - acceptance: acceptance, - params: params - ) - } - - // MARK: - rename_export_and_imports_ts - - private func generateRenameExportsTask( - rng: inout Mulberry32, - fileSystem: inout BenchmarkMockFileSystem, - config: BenchConfig, - difficulty: BenchmarkDifficulty, - layout: BenchmarkProjectLayout - ) -> BenchmarkTaskSpec { - let exporterPath = "src/ts/lib/exporter.ts" - let barrelPath = "src/ts/lib/index.ts" - let decoyBarrel = "src/ts/lib/decoyIndex.ts" - let oldName = "OldX" - let newName = "NewX" - let exporterContent = """ - export function \(oldName)(): string { return \"value\" } - export const usage = \(oldName)() - - // near-miss tokens — keep unchanged - export function \(oldName)Helper(): number { return 1 } - export const \(oldName)XY = 2 - export type \(oldName)Type = number - const label = \"\(oldName)\" // string literal allowed to remain \"OldX\" - """ - fileSystem.setFile(exporterPath, content: exporterContent) - fileSystem.setFile(barrelPath, content: """ - export { \(oldName) } from \"./exporter\"; - export * as All from \"./exporter\"; - """) - fileSystem.setFile(decoyBarrel, content: """ - export { \(oldName) } from \"./exporter\"; - """) - let importerCount = switch difficulty { - case .simple: - 2 - case .medium: - 4 - case .hard: - 6 - case .veryHard: - 8 - } - var importers: [String] = [] - enum ImportStyle { case named, alias, namespace } - func randomStyle(for difficulty: BenchmarkDifficulty, rng: inout Mulberry32) -> ImportStyle { - switch difficulty { - case .simple: - return .named - case .medium: - return rng.nextInt(upperBound: 2) == 0 ? .named : .alias - case .hard, .veryHard: - let pick = rng.nextInt(upperBound: 3) - if pick == 0 { return .named } - return pick == 1 ? .alias : .namespace - } - } - for index in 1 ... importerCount { - let app = index.isMultiple(of: 2) ? "appB" : "appA" - let path = "apps/\(app)/src/useX_\(index).ts" - importers.append(path) - let viaBarrel = index.isMultiple(of: 3) - let basePath = viaBarrel ? "../../lib" : "../../lib/exporter" - let style = randomStyle(for: difficulty, rng: &rng) - let content = switch style { - case .named: - """ - import { \(oldName) } from '\(basePath)'; - - export function consume() { - return \(oldName)(); - } - """ - case .alias: - """ - import { \(oldName) as UseX } from '\(basePath)'; - - export function consume() { - return UseX(); - } - """ - case .namespace: - """ - import * as E from '\(basePath)'; - - export function consume() { - return E.\(oldName)(); - } - """ - } - fileSystem.setFile(path, content: content) - } - var otherPaths: [String] = [] - let decoyBudget = decoyCount(for: difficulty, config: config) - let negativeCount = min(decoyBudget, 3) - if negativeCount > 0 { - for index in 1 ... negativeCount { - let path = "apps/appC/src/decoy_useX_\(index).ts" - let decoy = """ - import { \(oldName) } from '../../lib/exporter'; - - export function probe() { - // must remain unchanged - return \(oldName)(); - } - """ - fileSystem.setFile(path, content: decoy) - otherPaths.append(path) - } - } - otherPaths.append(decoyBarrel) - let instructions = [ - "Rename the exported function \(oldName) to \(newName) in \(exporterPath) and the barrel \(barrelPath).", - "Update every export and re-export of \(oldName) to \(newName) in these two files.", - "Update ONLY the listed importer files to use \(newName) (some import via the barrel, some directly).", - "Importers may use named imports, aliases, or namespace imports - update the imported symbol name.", - "Do NOT modify near-miss tokens: \(oldName)Helper, \(oldName)XY, \(oldName)Type (different suffixes/contexts).", - "Do NOT modify string literals containing \"\(oldName)\".", - "Do NOT edit files not explicitly listed as importers - other files with \(oldName) must remain unchanged." - ] - let acceptance = [ - "\(exporterPath) exports function \(newName) with zero remaining exports named \(oldName).", - "\(barrelPath) re-exports \(newName) with zero remaining references to \(oldName).", - "Each listed importer uses \(newName) (via import, alias, or namespace) and has zero references to \(oldName).", - "Near-miss tokens (\(oldName)Helper, \(oldName)XY, \(oldName)Type) remain unchanged in all files.", - "Files in otherPaths remain byte-for-byte identical to baseline.", - "Total files modified: 2 exports + \(importers.count) importers." - ] - var selectFiles = [exporterPath, barrelPath] - selectFiles.append(contentsOf: importers) - let fullDecoys = decoyBudget > 0 ? makeSimilarTsDecoys(rng: &rng, around: tsWorkPath) : [] - for decoy in fullDecoys { - fileSystem.setFile(decoy.path, content: decoy.content) - } - var params: [String: BenchmarkJSONValue] = [ - "rename": .object([ - "from": .string(oldName), - "to": .string(newName) - ]), - "importPaths": .array(importers.map { .string($0) }), - "otherPaths": .array(otherPaths.map { .string($0) }), - "nearMissTokens": .array([ - .string("\(oldName)Helper"), - .string("\(oldName)XY"), - .string("\(oldName)Type") - ]), - "reexportPaths": .array([.string(barrelPath)]), - "difficulty": .string(difficulty.rawValue), - "maxDecoys": .integer(decoyBudget) - ] - if !fullDecoys.isEmpty { - params["fullDecoys"] = .array(fullDecoys.map { .string($0.path) }) - } - - if config.includeAutoPlannedDecoys { - let draftTask = BenchmarkTaskSpec( - id: "rename_export_and_imports_ts", - type: .renameExportImportsTs, - language: .ts, - difficulty: difficulty, - selectFiles: selectFiles, - maxEdits: 10, - instructions: instructions, - task: "Rename \(oldName) to \(newName) across exporter.ts and index.ts, updating ONLY the listed importers.", - acceptance: acceptance, - params: params - ) - let baselineSnapshot = fileSystem.snapshot() - let decoySpecs = DecoyPlanner.materialize( - for: draftTask, - on: &fileSystem, - baseline: baselineSnapshot, - policy: config.decoyPolicy - ) - if !decoySpecs.isEmpty { - params["decoyPaths"] = .array(decoySpecs.map { .string($0.path) }) - } - } - params["hintVerbosity"] = .string(config.guidanceVerbosity.rawValue) - - return BenchmarkTaskSpec( - id: "rename_export_and_imports_ts", - type: .renameExportImportsTs, - language: .ts, - difficulty: difficulty, - selectFiles: selectFiles, - maxEdits: 10, - instructions: instructions, - task: "Rename \(oldName) to \(newName) across exporter.ts and index.ts, updating ONLY the listed importers.", - acceptance: acceptance, - params: params - ) - } - - // MARK: - Helpers - - private func randomIdentifier(rng: inout Mulberry32, length: Int = 4) -> String { - let alphabet = Array("ABCDEFGHJKLMNPQRSTUVWXYZ23456789") - var chars: [Character] = [] - for _ in 0 ..< length { - let idx = rng.nextInt(upperBound: alphabet.count) - chars.append(alphabet[idx]) - } - return String(chars) - } - - private func scaledLines(_ base: Int, difficulty: BenchmarkDifficulty) -> Int { - switch difficulty { - case .medium: - max(20, Int(Double(base) * 0.35)) - case .hard: - max(30, Int(Double(base) * 0.6)) - case .veryHard: - max(40, base) - case .simple: - max(20, Int(Double(base) * 0.35)) - } - } - - private func decoyCount(for difficulty: BenchmarkDifficulty, config: BenchConfig) -> Int { - switch difficulty { - case .medium: - config.decoysMedium - case .hard: - config.decoysHard - case .veryHard: - config.decoysVeryHard - case .simple: - config.decoysMedium - } - } - - private func maxEditsRemoveX(for difficulty: BenchmarkDifficulty) -> Int { - switch difficulty { - case .medium: - 1 - case .hard: - 3 - case .veryHard: - 6 - case .simple: - 1 - } - } - - private func difficultyIsAtLeastHard(_ difficulty: BenchmarkDifficulty) -> Bool { - switch difficulty { - case .hard, .veryHard: - true - default: - false - } - } - - private func shouldUseMarkerlessMode(for difficulty: BenchmarkDifficulty) -> Bool { - difficulty == .hard || difficulty == .veryHard - } - - private func defaultFunctionName(for type: BenchmarkCaseType, language: BenchmarkLanguage) -> String { - switch type { - case .insertGuardTs, .insertGuardGo, .insertGuardSwift: - "clamp" - case .patchBlockTs, .patchBlockGo, .patchBlockSwift: - "block2" - case .swapArgsInRegionTs, .swapArgsInRegionGo, .swapArgsInRegionSwift: - "render" - default: - "function" - } - } - - private func makeSimilarTsDecoys(rng: inout Mulberry32, around path: String) -> [(path: String, content: String)] { - let baseDir = (path as NSString).deletingLastPathComponent - let names = ["WorkShadow", "WorkClone"] - return names.map { name in - var content = BelievableCodeFactory.tsUtilityModule(rng: &rng, module: name, approxLines: 60) - content += """ - - export function use(a: string, b: string): string { return a + b } - - export function block2(n: number): number { - const t = n * 3 - return t - } - """ - let decoyPath = baseDir.isEmpty ? "\(name).ts" : "\(baseDir)/\(name).ts" - return (path: decoyPath, content: content) - } - } - - // MARK: - Unified Patch Helpers - - /// Computes the line range for a TypeScript function (including trailing blank line if present) - /// - Parameters: - /// - base: Baseline file lines - /// - fnName: Function name (e.g., "b" for "export function b(...)") - /// - Returns: Closed range in 0-indexed line numbers, or nil if not found - private func computeTsFunctionRange(_ base: [String], fnName: String) -> ClosedRange? { - guard let start = base.firstIndex(where: { $0.hasPrefix("export function \(fnName)(") }) else { return nil } - var end = start - while end < base.count, base[end] != "}" { - end += 1 - } - if end >= base.count { return nil } - // Include trailing blank line if present - if end + 1 < base.count, base[end + 1].isEmpty { end += 1 } - return start ... end - } - - /// Computes the line range for a Go function (including trailing blank line if present) - private func computeGoFunctionRange(_ base: [String], fnName: String) -> ClosedRange? { - guard let start = base.firstIndex(where: { $0.hasPrefix("func \(fnName)(") }) else { return nil } - var end = start - while end < base.count, base[end] != "}" { - end += 1 - } - if end >= base.count { return nil } - // Include trailing blank line if present - if end + 1 < base.count, base[end + 1].isEmpty { end += 1 } - return start ... end - } - - /// Computes the line range for a Swift function (including trailing blank line if present) - private func computeSwiftFunctionRange(_ base: [String], fnName: String) -> ClosedRange? { - guard let start = base.firstIndex(where: { $0.hasPrefix("public func \(fnName)(") }) else { return nil } - var end = start - while end < base.count, base[end] != "}" { - end += 1 - } - if end >= base.count { return nil } - // Include trailing blank line if present - if end + 1 < base.count, base[end + 1].isEmpty { end += 1 } - return start ... end - } - - /// Builds a noise hunk for TypeScript (no-op change with context) - /// - Parameters: - /// - baseline: Original file lines - /// - bofAdded: Number of lines added at beginning of file - /// - removedSpans: Ranges of lines that will be removed by earlier hunks - /// - valueMarker: The line to target for no-op (default: "export const value = 42") - /// - context: Number of context lines to include (default: 3) - /// - Returns: Unified diff hunk string, or empty if marker not found - private func buildTsNoiseHunk( - baseline: [String], - bofAdded: Int, - removedSpans: [ClosedRange], - valueMarker: String = "export const value = 42", - context: Int = 3 - ) -> String { - guard let valueIdx = baseline.firstIndex(of: valueMarker), valueIdx > 0 else { return "" } - let availableAbove = min(context, valueIdx) - let ctxStart = valueIdx - availableAbove - let oldStart = ctxStart + 1 // 1-based - let oldCount = availableAbove + 1 // context above + 1 target - let removedCount = removedSpans.reduce(0) { $0 + ($1.count) } - let newStart = oldStart + bofAdded - removedCount - - var lines: [String] = [] - lines.append("@@ -\(oldStart),\(oldCount) +\(newStart),\(oldCount) @@") - for i in 0 ..< availableAbove { - lines.append(" \(baseline[ctxStart + i])") - } - lines.append("-\(baseline[valueIdx])") - lines.append("+\(baseline[valueIdx])") - return lines.joined(separator: "\n") - } - - /// Builds a noise hunk for Go (no-op change with context) - private func buildGoNoiseHunk( - baseline: [String], - bofAdded: Int, - removedSpans: [ClosedRange], - valueMarker: String = "const value = 42", - context: Int = 3 - ) -> String { - guard let idx = baseline.firstIndex(of: valueMarker) else { return "" } - let availableAbove = min(context, idx) - let ctxStart = idx - availableAbove - let oldStart = ctxStart + 1 // 1-based - let oldCount = availableAbove + 1 - let removed = removedSpans.reduce(0) { $0 + ($1.count) } - let newStart = oldStart + bofAdded - removed - - var h: [String] = [] - h.append("@@ -\(oldStart),\(oldCount) +\(newStart),\(oldCount) @@") - for i in 0 ..< availableAbove { - h.append(" \(baseline[ctxStart + i])") - } - h.append("-\(baseline[idx])") - h.append("+\(baseline[idx])") - return h.joined(separator: "\n") - } - - /// Builds a noise hunk for Swift (no-op change with context) - private func buildSwiftNoiseHunk( - baseline: [String], - bofAdded: Int, - removedSpans: [ClosedRange], - valueMarker: String = "public let value = 42", - context: Int = 3 - ) -> String { - guard let idx = baseline.firstIndex(of: valueMarker) else { return "" } - let availableAbove = min(context, idx) - let ctxStart = idx - availableAbove - let oldStart = ctxStart + 1 // 1-based - let oldCount = availableAbove + 1 - let removed = removedSpans.reduce(0) { $0 + ($1.count) } - let newStart = oldStart + bofAdded - removed - - var h: [String] = [] - h.append("@@ -\(oldStart),\(oldCount) +\(newStart),\(oldCount) @@") - for i in 0 ..< availableAbove { - h.append(" \(baseline[ctxStart + i])") - } - h.append("-\(baseline[idx])") - h.append("+\(baseline[idx])") - return h.joined(separator: "\n") - } - - /// Creates a slightly mutated clone of a patchable file to act as a decoy - /// The clone is similar enough to be confusing but different enough that the patch won't apply exactly - private func makePatchableClone(language: BenchmarkLanguage, origin: [String], variant: Int) -> [String] { - var clone = origin - - switch language { - case .ts: - // Mutate constants by incrementing variant offset - for (idx, line) in clone.enumerated() { - if line.contains("export const value = ") { - clone[idx] = "export const value = \(42 + variant + 1)" - } else if line.contains("export const value2 = ") { - clone[idx] = "export const value2 = \(7 + variant)" - } else if line.contains("export const value3 = ") { - clone[idx] = "export const value3 = \(100 + variant * 2)" - } - } - // Add a harmless comment near a different function than patch touches - if let cIdx = clone.firstIndex(where: { $0.hasPrefix("export function c(") }) { - clone.insert(" // Clone variant \(variant)", at: cIdx + 1) - } - - case .go: - // Mutate constants by incrementing variant offset - for (idx, line) in clone.enumerated() { - if line.contains("const value = ") { - clone[idx] = "const value = \(42 + variant + 1)" - } else if line.contains("const value2 = ") { - clone[idx] = "const value2 = \(7 + variant)" - } else if line.contains("const value3 = ") { - clone[idx] = "const value3 = \(100 + variant * 2)" - } - } - // Add a harmless comment near function c - if let cIdx = clone.firstIndex(where: { $0.hasPrefix("func c(") }) { - clone.insert(" // Clone variant \(variant)", at: cIdx + 1) - } - - case .swift: - // Mutate constants by incrementing variant offset - for (idx, line) in clone.enumerated() { - if line.contains("public let value = ") { - clone[idx] = "public let value = \(42 + variant + 1)" - } else if line.contains("public let value2 = ") { - clone[idx] = "public let value2 = \(7 + variant)" - } else if line.contains("public let value3 = ") { - clone[idx] = "public let value3 = \(100 + variant * 2)" - } - } - // Add a harmless comment near function c - if let cIdx = clone.firstIndex(where: { $0.hasPrefix("public func c(") }) { - clone.insert("\t// Clone variant \(variant)", at: cIdx + 1) - } - - @unknown default: - // For other languages, just add a comment at the top if possible - if !clone.isEmpty { - clone.insert("// Clone variant \(variant)", at: 0) - } - } - - return clone - } - - private func removedLineCount(before index: Int, in spans: [ClosedRange]) -> Int { - spans.filter { $0.upperBound < index }.map(\.count).reduce(0, +) - } - - private func newStart(oldStart1Based: Int, bofAdded: Int, removedSpansBeforeTarget: Int) -> Int { - oldStart1Based + bofAdded - removedSpansBeforeTarget - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkVerifier.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkVerifier.swift deleted file mode 100644 index cbb95e7fd..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/BenchmarkVerifier.swift +++ /dev/null @@ -1,2264 +0,0 @@ -import Foundation - -enum BenchmarkVerifierReasons { - static let outputPenalty = "Output Penalty" - static let othersChanged = "Files outside the allowed list were modified." - static let importerMismatch = "One or more importers still reference the old symbol name." - static let tabFound = "Inserted snippet contained a tab; use spaces for indentation." -} - -struct GradingPolicy { - var passThreshold: Double = 0.8 - var lenient: Bool = true - var softErrorPenalty: [String: Double] = [ - "TOO_MANY_EDITS": 0.15, - "EDIT_APPLY_FAILED": 0.2, - "TOO_FEW_EDITS": 0.15 - ] - var hardErrors: Set = [ - "UNEXPECTED_FILE_EDIT", - "PARSE_OUTPUT_FAILED", - "MODEL_EXECUTION_FAILED", - "MODEL_OUTPUT_TOO_LARGE" - ] -} - -protocol BenchmarkVerifying { - func verify(_ execution: BenchmarkTaskExecution) -> BenchmarkVerifyOutput -} - -struct BenchmarkVerifier: BenchmarkVerifying { - var policy: GradingPolicy - - init(policy: GradingPolicy = GradingPolicy()) { - self.policy = policy - } - - private func formatError(_ error: BenchmarkTaskError) -> String { - // If there's a detail field, use it for more context - if let detail = error.detail, !detail.isEmpty { - return detail - } - // Otherwise, just return the code (will be formatted by humanReadableReason) - return error.code - } - - func verify(_ execution: BenchmarkTaskExecution) -> BenchmarkVerifyOutput { - let errors = execution.result.errors - let hard = errors.filter { policy.hardErrors.contains($0.code) } - let soft = errors.filter { policy.softErrorPenalty[$0.code] != nil } - let unknown = errors.filter { policy.softErrorPenalty[$0.code] == nil && !policy.hardErrors.contains($0.code) } - if !hard.isEmpty { - let codes = hard.map { formatError($0) }.joined(separator: ",") - return BenchmarkVerifyOutput.failure(reason: codes) - } - if !unknown.isEmpty { - let codes = unknown.map { formatError($0) }.joined(separator: ",") - return BenchmarkVerifyOutput.failure(reason: codes) - } - if !soft.isEmpty, !policy.lenient { - let codes = soft.map { formatError($0) }.joined(separator: ",") - return BenchmarkVerifyOutput.failure(reason: codes) - } - - let task = execution.task - let baselineMap = execution.baseline.dictionary() - let finalMap = buildFinalMap(baseline: baselineMap, edits: execution.result.edited) - let base: BenchmarkVerifyOutput = switch task.type { - case .removeXTs, .removeXGo, .removeXSwift: - verifyRemoveX(task: task, baseline: baselineMap, final: finalMap) - case .curlyFixTs, .curlyFixGo, .curlyFixSwift: - verifyCurlyFix(task: task, baseline: baselineMap, final: finalMap) - case .insertGuardTs, .insertGuardGo, .insertGuardSwift: - verifyInsertGuard(task: task, baseline: baselineMap, final: finalMap) - case .patchBlockTs, .patchBlockGo, .patchBlockSwift: - verifyPatchBlock(task: task, baseline: baselineMap, final: finalMap) - case .swapArgsInRegionTs, .swapArgsInRegionGo, .swapArgsInRegionSwift: - verifySwapArgs(task: task, baseline: baselineMap, final: finalMap) - case .indexOnlyAppsTs, .indexOnlyAppsGo, .indexOnlyAppsSwift: - verifyIndexOnly(task: task, baseline: baselineMap, final: finalMap) - case .renameExportImportsTs, .renameExportImportsGo, .renameExportImportsSwift: - verifyRename(task: task, baseline: baselineMap, final: finalMap) - case .moveFunctionTs, .moveFunctionGo, .moveFunctionSwift: - verifyMoveFunction(task: task, baseline: baselineMap, final: finalMap) - case .insertFunctionBottomTs, .insertFunctionBottomGo, .insertFunctionBottomSwift: - verifyInsertFunctionBottom(task: task, baseline: baselineMap, final: finalMap) - case .applyUnifiedPatchTs, .applyUnifiedPatchGo, .applyUnifiedPatchSwift: - verifyApplyUnifiedPatch(task: task, baseline: baselineMap, final: finalMap) - } - var scored = applyOutputPenalty(base, meta: execution.result.meta) - - // Apply TOO_FEW_EDITS soft penalty for curly_fix tasks - if case .curlyFixTs = task.type, policy.lenient { - scored = applyTooFewEditsPenalty(scored, task: task, execution: execution) - } else if case .curlyFixGo = task.type, policy.lenient { - scored = applyTooFewEditsPenalty(scored, task: task, execution: execution) - } else if case .curlyFixSwift = task.type, policy.lenient { - scored = applyTooFewEditsPenalty(scored, task: task, execution: execution) - } - - if policy.lenient, !soft.isEmpty { - let factor = soft.reduce(1.0) { partial, err in - let penalty = policy.softErrorPenalty[err.code] ?? 0.0 - return partial * (1.0 - penalty) - } - let newScore = clampScore(scored.score * factor) - var metrics = scored.metrics - metrics["softErrors"] = .array(soft.map { .string($0.code) }) - metrics["softPenaltyFactor"] = .double(factor) - let passAfter = scored.pass && newScore >= policy.passThreshold - let reason = (!passAfter && scored.reason.isEmpty) ? soft.map { formatError($0) }.joined(separator: ",") : scored.reason - scored = BenchmarkVerifyOutput(pass: passAfter, score: newScore, reason: reason, metrics: metrics) - } - return scored - } - - private func clampScore(_ score: Double) -> Double { - max(0.0, min(1.0, score)) - } - - private func buildFinalMap(baseline: [String: String], edits: [BenchmarkEditedFile]) -> [String: String] { - var final = baseline - for edit in edits { - let normalized = BenchmarkMockFileSystem.normalize(edit.path) - final[normalized] = edit.content - } - return final - } - - private func applyOutputPenalty(_ result: BenchmarkVerifyOutput, meta: [String: BenchmarkJSONValue]?) -> BenchmarkVerifyOutput { - guard let meta else { return result } - let charCount = meta["rawCharCount"]?.intValue ?? 0 - let lineCount = meta["rawLineCount"]?.intValue ?? 0 - let charOver = max(0, charCount - 10000) - let lineOver = max(0, lineCount - 200) - if charOver == 0 && lineOver == 0 { return result } - let charPenalty = 1.0 / (1.0 + Double(charOver) / 5000.0) - let linePenalty = 1.0 / (1.0 + Double(lineOver) / 100.0) - let penalty = min(charPenalty, linePenalty) - let newScore = clampScore(result.score * penalty) - var metrics = result.metrics - metrics["penaltyApplied"] = .boolean(true) - metrics["rawCharCount"] = .integer(charCount) - metrics["rawLineCount"] = .integer(lineCount) - metrics["finalScore"] = .double(newScore) - let passAfter = result.pass && newScore >= policy.passThreshold - let reason = (!passAfter && result.reason.isEmpty) ? "outputPenalty" : result.reason - return BenchmarkVerifyOutput(pass: passAfter, score: newScore, reason: reason, metrics: metrics) - } - - private func applyTooFewEditsPenalty(_ result: BenchmarkVerifyOutput, task: BenchmarkTaskSpec, execution: BenchmarkTaskExecution) -> BenchmarkVerifyOutput { - // Read minEditsPerFile from task params - let minEditsPerFile = task.params["minEditsPerFile"]?.intValue ?? 0 - guard minEditsPerFile > 0 else { return result } - - // Read editBlockCountTotal from execution meta - let editBlockCountTotal = execution.result.meta?["editBlockCountTotal"]?.intValue ?? 0 - - // Calculate minimum required edits based on file count - let filesCount = task.params["files"]?.arrayValue?.count ?? max(1, task.selectFiles.count) - let minEditsRequired = minEditsPerFile * filesCount - - // Check if penalty should apply - guard editBlockCountTotal < minEditsRequired else { - // No penalty, but add metrics for transparency - var metrics = result.metrics - metrics["minEditsRequired"] = .integer(minEditsRequired) - metrics["editBlocksUsed"] = .integer(editBlockCountTotal) - metrics["tooFewEditsPenaltyApplied"] = .boolean(false) - return BenchmarkVerifyOutput(pass: result.pass, score: result.score, reason: result.reason, metrics: metrics) - } - - // Apply the TOO_FEW_EDITS soft penalty - let penaltyFactor = policy.softErrorPenalty["TOO_FEW_EDITS"] ?? 0.15 - let newScore = clampScore(result.score * (1.0 - penaltyFactor)) - var metrics = result.metrics - metrics["minEditsRequired"] = .integer(minEditsRequired) - metrics["editBlocksUsed"] = .integer(editBlockCountTotal) - metrics["tooFewEditsPenaltyApplied"] = .boolean(true) - metrics["tooFewEditsPenaltyFactor"] = .double(penaltyFactor) - - let passAfter = result.pass && newScore >= policy.passThreshold - let reason = (!passAfter && result.reason.isEmpty) ? "TOO_FEW_EDITS" : result.reason - - return BenchmarkVerifyOutput(pass: passAfter, score: newScore, reason: reason, metrics: metrics) - } - - // MARK: - remove_x_ts - - private func verifyRemoveX(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - guard let path = task.params["file"]?.stringValue, - let baselineText = baseline[path], - let finalText = final[path] - else { - return BenchmarkVerifyOutput.failure(reason: "missingTargetFile") - } - let target = task.params["target"]?.stringValue ?? "CALL_X(" - let baseCode = stripTSComments(baselineText) - let finalCode = stripTSComments(finalText) - let baselineCount = max(0, countOccurrences(of: target, in: baseCode)) - let finalCount = max(0, countOccurrences(of: target, in: finalCode)) - let removed = max(0, baselineCount - finalCount) - let denominator = max(1, baselineCount) - var score = Double(removed) / Double(denominator) - let nearTokens: [String] = [ - "call_x(", - "CALL_XY(" - ] - let baselineNear = nearTokens.reduce(0) { $0 + countOccurrences(of: $1, in: baseCode) } - let finalNear = nearTokens.reduce(0) { $0 + countOccurrences(of: $1, in: finalCode) } - var reason = "" - let nearMissChanged = finalNear < baselineNear - if nearMissChanged { - reason = "nearMissChanged" - if policy.lenient { - score = max(0.0, score - 0.25) - } else { - let metrics: [String: BenchmarkJSONValue] = [ - "nearMissBaseline": .integer(baselineNear), - "nearMissFinal": .integer(finalNear) - ] - return BenchmarkVerifyOutput.failure(reason: reason, metrics: metrics) - } - } else if finalCount > 0 { - reason = "refsRemain:\(finalCount)" - } - let metrics: [String: BenchmarkJSONValue] = [ - "baseline": .integer(baselineCount), - "final": .integer(finalCount), - "removed": .integer(removed), - "nearMissBaseline": .integer(baselineNear), - "nearMissFinal": .integer(finalNear) - ] - return makeScoredOutput(score: score, reason: reason, metrics: metrics) - } - - // MARK: - curly_fix_go - - private func verifyCurlyFix(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - if let files = task.params["files"]?.arrayValue?.compactMap(\.stringValue), !files.isEmpty { - var components: [(String, BenchmarkVerifyOutput)] = [] - for file in files { - let subTask = BenchmarkTaskSpec( - id: "\(task.id)::\(file)", - type: task.type, - language: task.language, - difficulty: task.difficulty, - format: task.format, - selectFiles: [file], - newChat: task.newChat, - maxEdits: max(1, task.maxEdits / max(files.count, 1)), - instructions: task.instructions, - task: task.task, - acceptance: task.acceptance, - params: ["file": .string(file)] - ) - components.append((file, verifyCurlyFix(task: subTask, baseline: baseline, final: final))) - } - return aggregate(components, label: "curlyFixBundle") - } - let path = task.params["file"]?.stringValue ?? "src/go/main.go" - guard let finalText = final[path], let baselineText = baseline[path] else { - return BenchmarkVerifyOutput.failure(reason: "missingTargetFile") - } - - // Use language-aware brace balancing (ignores braces in comments/strings) - let balanced = braceBalanced(finalText, language: task.language) - - // Find all for-loop blocks using the new helper - let forRanges = allForBlockRanges(in: finalText, language: task.language) - - // Count print statements inside vs outside all loops - let printRegex: NSRegularExpression? = switch task.language { - case .go: - try? NSRegularExpression(pattern: #"fmt\.Print(ln|f)\s*\([^)]*\)"#, options: []) - case .ts: - try? NSRegularExpression(pattern: #"console\.log\s*\([^)]*\)"#, options: []) - case .swift: - try? NSRegularExpression(pattern: #"print\s*\([^)]*\)"#, options: []) - } - var printsInside = 0 - var printsOutside = 0 - if let printRegex { - let ns = finalText as NSString - let full = NSRange(location: 0, length: ns.length) - printRegex.enumerateMatches(in: finalText, options: [], range: full) { match, _, _ in - guard let match, let range = Range(match.range, in: finalText) else { return } - // Check if this print is inside any loop - var inAnyLoop = false - for loopRange in forRanges { - if range.lowerBound >= loopRange.lowerBound, range.upperBound <= loopRange.upperBound { - inAnyLoop = true - break - } - } - if inAnyLoop { - printsInside += 1 - } else { - printsOutside += 1 - } - } - } - - // Check for collateral changes using sanitized comparison (removes all "}") - let sanitizedBaseline = sanitizeForCurlyComparison(baselineText) - let sanitizedFinal = sanitizeForCurlyComparison(finalText) - let unchangedOutside = sanitizedBaseline == sanitizedFinal - - // Scoring: 0.4 braces balanced + 0.4 prints correct + 0.2 no collateral - var score = 0.0 - if balanced { score += 0.4 } - if printsOutside == 1, printsInside == 0 { score += 0.4 } - if unchangedOutside { score += 0.2 } - - let reason: String = { - if !balanced { return "braceUnbalanced" } - if printsInside > 0 { return "printCallInsideLoop" } - if printsOutside != 1 { return "printCallCountMismatch" } - if !unchangedOutside { return "collateralChange" } - return "" - }() - - let metrics: [String: BenchmarkJSONValue] = [ - "balanced": .boolean(balanced), - "printInside": .integer(printsInside), - "printOutside": .integer(printsOutside), - "unchangedOutside": .boolean(unchangedOutside), - "forLoopCount": .integer(forRanges.count) - ] - return makeScoredOutput(score: score, reason: reason, metrics: metrics) - } - - private func braceBalanced(_ text: String, language: BenchmarkLanguage) -> Bool { - enum ScanState { - case code - case lineComment - case blockComment - case string(Character) // " or ' - case templateString // for TS backticks - } - - var balance = 0 - var state = ScanState.code - var escapeNext = false - var index = text.startIndex - - while index < text.endIndex { - let char = text[index] - - // Handle escape sequences - if escapeNext { - escapeNext = false - index = text.index(after: index) - continue - } - - switch state { - case .code: - // Check for comment starts - if char == "/", text.index(after: index) < text.endIndex { - let nextChar = text[text.index(after: index)] - if nextChar == "/" { - state = .lineComment - index = text.index(after: index) - index = text.index(after: index) - continue - } else if nextChar == "*" { - state = .blockComment - index = text.index(after: index) - index = text.index(after: index) - continue - } - } - // Check for string starts - if char == "\"" { - state = .string("\"") - index = text.index(after: index) - continue - } - if char == "'" { - state = .string("'") - index = text.index(after: index) - continue - } - // Check for template string (TypeScript) - if language == .ts, char == "`" { - state = .templateString - index = text.index(after: index) - continue - } - // Count braces only in code state - if char == "{" { - balance += 1 - } else if char == "}" { - balance -= 1 - if balance < 0 { return false } - } - - case .lineComment: - if char == "\n" { - state = .code - } - - case .blockComment: - if char == "*", text.index(after: index) < text.endIndex { - let nextChar = text[text.index(after: index)] - if nextChar == "/" { - state = .code - index = text.index(after: index) - } - } - - case let .string(delim): - if char == "\\" { - escapeNext = true - } else if char == delim { - state = .code - } - - case .templateString: - if char == "\\" { - escapeNext = true - } else if char == "`" { - state = .code - } - } - - index = text.index(after: index) - } - - return balance == 0 - } - - private func allForBlockRanges(in text: String, language: BenchmarkLanguage) -> [Range] { - var ranges: [Range] = [] - var searchStart = text.startIndex - - while searchStart < text.endIndex { - // Look for "for" keyword - let pattern = switch language { - case .ts: - "for (" - case .go: - "for " - case .swift: - "for " - } - - guard let forRange = text.range(of: pattern, range: searchStart ..< text.endIndex) else { - break - } - - // Find the opening brace after "for" - var cursor = forRange.upperBound - while cursor < text.endIndex, text[cursor] != "{" { - cursor = text.index(after: cursor) - } - - if cursor < text.endIndex { - // Try to find the matching block using language-aware matching - if let blockRange = findMatchingBlock(in: text, from: cursor, language: language) { - ranges.append(blockRange) - searchStart = blockRange.upperBound - } else { - searchStart = text.index(after: cursor) - } - } else { - break - } - } - - return ranges - } - - private func findMatchingBlock(in text: String, from start: String.Index, language: BenchmarkLanguage) -> Range? { - enum ScanState { - case code - case lineComment - case blockComment - case string(Character) - case templateString - } - - var depth = 0 - var seenOpen = false - var current = start - var blockStart: String.Index? - var state = ScanState.code - var escapeNext = false - - while current < text.endIndex { - let char = text[current] - - // Handle escape sequences - if escapeNext { - escapeNext = false - current = text.index(after: current) - continue - } - - switch state { - case .code: - // Check for comment starts - if char == "/", text.index(after: current) < text.endIndex { - let nextChar = text[text.index(after: current)] - if nextChar == "/" { - state = .lineComment - current = text.index(after: current) - current = text.index(after: current) - continue - } else if nextChar == "*" { - state = .blockComment - current = text.index(after: current) - current = text.index(after: current) - continue - } - } - // Check for string starts - if char == "\"" { - state = .string("\"") - current = text.index(after: current) - continue - } - if char == "'" { - state = .string("'") - current = text.index(after: current) - continue - } - // Check for template string (TypeScript) - if language == .ts, char == "`" { - state = .templateString - current = text.index(after: current) - continue - } - // Count braces only in code state - if char == "{" { - depth += 1 - if !seenOpen { - seenOpen = true - blockStart = current - } - } else if char == "}" { - if !seenOpen { return nil } - depth -= 1 - if depth == 0 { - guard let startIndex = blockStart else { return nil } - return startIndex ..< text.index(after: current) - } - } - - case .lineComment: - if char == "\n" { - state = .code - } - - case .blockComment: - if char == "*", text.index(after: current) < text.endIndex { - let nextChar = text[text.index(after: current)] - if nextChar == "/" { - state = .code - current = text.index(after: current) - } - } - - case let .string(delim): - if char == "\\" { - escapeNext = true - } else if char == delim { - state = .code - } - - case .templateString: - if char == "\\" { - escapeNext = true - } else if char == "`" { - state = .code - } - } - - current = text.index(after: current) - } - return nil - } - - private func normalizeForMoveComparison(_ text: String) -> String { - // Normalize line endings - let result = text.replacingOccurrences(of: "\r\n", with: "\n") - - // Drop all blank lines entirely and trim whitespace per line. - // This makes the comparison robust to blank-line differences that occur - // when removing and reinserting a moved function (top/bottom and between functions), - // while still detecting any non-empty line changes as collateral. - let lines = result.split(separator: "\n", omittingEmptySubsequences: false) - var out: [String] = [] - out.reserveCapacity(lines.count) - for rawLine in lines { - let trimmed = rawLine.trimmingCharacters(in: .whitespaces) - if trimmed.isEmpty { - continue - } - out.append(trimmed) - } - return out.joined(separator: "\n") - } - - /// Finds the range of a function declaration in the given text. - /// - /// Uses regex patterns to robustly match functions with various modifiers: - /// - TypeScript: export, async, decorators - /// - Swift: public, private, internal, static, @objc, etc. - /// - Go: standard func declarations - /// - /// Note: Currently supports regular function declarations. Arrow functions and class methods - /// are not yet supported but could be added if needed for benchmark tasks. - private func findFunctionRange(in text: String, name: String, language: BenchmarkLanguage) -> Range? { - let escapedName = NSRegularExpression.escapedPattern(for: name) - let regexPattern = switch language { - case .ts: - // Match: [export] [async] function name( - // Handles modifiers, whitespace, and optional type parameters - #"(?:^|\n)(?:(?:export|async)\s+)*function\s+"# + escapedName + #"\s*(?:<[^>]+>)?\s*\("# - case .go: - // Match: func name( - // Go is simpler - no export keyword, but may have receiver types - #"(?:^|\n)func\s+(?:\([^)]*\)\s+)?"# + escapedName + #"\s*\("# - case .swift: - // Match: [@decorator]* [public|private|internal|static|class|final|override]* func name( - // Handles multiple attributes (@objc @available) and modifiers (final override public) - #"(?:^|\n)(?:@\w+(?:\([^)]*\))?\s+)*(?:(?:public|private|internal|open|fileprivate|static|class|final|override)\s+)*func\s+"# + escapedName + #"\s*(?:<[^>]+>)?\s*\("# - } - - guard let regex = try? NSRegularExpression(pattern: regexPattern, options: []), - let match = regex.firstMatch(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count)), - let matchRange = Range(match.range, in: text) - else { - return nil - } - - // Start includes all modifiers from beginning of line - var start = matchRange.lowerBound - - // If the match included a leading newline (from (?:^|\n) pattern), - // skip past it - that newline belongs to the previous function's trailing whitespace - if start < text.endIndex, text[start].isNewline { - start = text.index(after: start) - } - - // Move to start of line to capture all leading modifiers/decorators - while start > text.startIndex { - let prev = text.index(before: start) - let char = text[prev] - if char.isNewline { - break - } - start = prev - } - - // Find the opening brace after the match - var cursor = matchRange.upperBound - while cursor < text.endIndex, text[cursor] != "{" { - cursor = text.index(after: cursor) - } - - guard cursor < text.endIndex, let block = findMatchingBlock(in: text, from: cursor, language: language) else { - return nil - } - - // Include trailing blank lines (up to 2 newlines total) so whitespace moves with the function - var end = block.upperBound - var newlineCount = 0 - while end < text.endIndex, text[end].isNewline, newlineCount < 2 { - end = text.index(after: end) - newlineCount += 1 - } - - return start ..< end - } - - private func textByRemovingRange(_ text: String, range: Range) -> String { - var mutable = text - mutable.removeSubrange(range) - return mutable - } - - // MARK: - move_function_ts - - private func extendRangeToIncludeLeadingComments(in text: String, range: Range, language: BenchmarkLanguage) -> Range { - let lines = text.split(separator: "\n", omittingEmptySubsequences: false) - // Find the line index of range start - var currentPos = text.startIndex - var lineIdx = 0 - for (idx, line) in lines.enumerated() { - let lineEndPos = text.index(currentPos, offsetBy: line.count + 1, limitedBy: text.endIndex) ?? text.endIndex - if range.lowerBound >= currentPos, range.lowerBound < lineEndPos { - lineIdx = idx - break - } - currentPos = lineEndPos - } - // Look backwards for contiguous comment lines or blank lines - var extendToLine = lineIdx - for i in (0 ..< lineIdx).reversed() { - let trimmed = lines[i].trimmingCharacters(in: .whitespaces) - if trimmed.starts(with: "//") || trimmed.starts(with: "/*") || trimmed.starts(with: "*") || trimmed.starts(with: "/**") || trimmed.starts(with: "///") || trimmed.isEmpty { - extendToLine = i - } else { - break - } - } - // Calculate new start position - if extendToLine < lineIdx { - var newStart = text.startIndex - for i in 0 ..< extendToLine { - newStart = text.index(newStart, offsetBy: lines[i].count + 1, limitedBy: text.endIndex) ?? text.endIndex - } - return newStart ..< range.upperBound - } - return range - } - - private func verifyMoveFunction(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - guard - let path = task.selectFiles.first, - let baseText = baseline[path], - let finalText = final[path] - else { - // This guard is preserved for anchoring; actual validation happens below. - return .failure(reason: "missingParams") - } - - // Multi-move path: params["moves"] contains an array of { from, after } - if let moves = task.params["moves"]?.arrayValue, !moves.isEmpty { - // Collect function names to remove for outside comparison - let moveNames: [String] = moves.compactMap { entry in - entry.objectValue?["from"]?.stringValue - } - - func textByRemovingFunctions(_ text: String, names: [String], language: BenchmarkLanguage) -> String { - var out = text - for name in names { - if let r = findFunctionRange(in: out, name: name, language: language) { - let extended = extendRangeToIncludeLeadingComments(in: out, range: r, language: language) - out = textByRemovingRange(out, range: extended) - } - } - // Normalize excessive newlines to be robust to whitespace shifts - return out.replacingOccurrences(of: #"\n{3,}"#, with: "\n\n", options: .regularExpression) - } - - let sanitizedBase = textByRemovingFunctions(baseText, names: moveNames, language: task.language) - let sanitizedFinal = textByRemovingFunctions(finalText, names: moveNames, language: task.language) - let outsideUnchangedGlobal = normalizeForOutsideComparison(sanitizedBase) == normalizeForOutsideComparison(sanitizedFinal) - - var totalScore = 0.0 - var detailReasons: [String] = [] - var anyContentChanged = false - - for entry in moves { - guard - let obj = entry.objectValue, - let from = obj["from"]?.stringValue, - let after = obj["after"]?.stringValue - else { - // If malformed move entry, score 0 for this one - detailReasons.append("malformedMove") - continue - } - - let baseFromRangeRaw = findFunctionRange(in: baseText, name: from, language: task.language) - let finalFromRangeRaw = findFunctionRange(in: finalText, name: from, language: task.language) - let finalAfterRange = findFunctionRange(in: finalText, name: after, language: task.language) - - let baseFromRange = baseFromRangeRaw.map { extendRangeToIncludeLeadingComments(in: baseText, range: $0, language: task.language) } - let finalFromRange = finalFromRangeRaw.map { extendRangeToIncludeLeadingComments(in: finalText, range: $0, language: task.language) } - - let placedAfter: Bool = { - guard let fFromRaw = finalFromRangeRaw, let fAfter = finalAfterRange else { return false } - return fFromRaw.lowerBound >= fAfter.upperBound - }() - - let sameFunctionContent: Bool = { - guard let bFrom = baseFromRange, let fFrom = finalFromRange else { return false } - return String(baseText[bFrom]) == String(finalText[fFrom]) - }() - - let outsideUnchanged = outsideUnchangedGlobal - - var score = 0.0 - if placedAfter { score += 0.5 } - if outsideUnchanged { score += 0.3 } - if sameFunctionContent { score += 0.2 } - totalScore += score - - if !placedAfter { detailReasons.append("\(from):notAfterTarget") } - if !outsideUnchanged { detailReasons.append("\(from):collateralChange") } - if !sameFunctionContent { - detailReasons.append("\(from):functionChanged") - anyContentChanged = true - } - } - - // Enforce exact content retention for TS/Swift - if task.language != .go, anyContentChanged { - return .failure(reason: "functionChanged", metrics: [ - "moveCount": .integer(moves.count) - ]) - } - - let movesCount = max(1, moves.count) - let avgScore = totalScore / Double(movesCount) - let reason = detailReasons.joined(separator: ",") - - return makeScoredOutput(score: avgScore, reason: reason, metrics: [ - "moveCount": .integer(movesCount), - "averageScore": .double(avgScore) - ]) - } - - // Single-move path (backward compatible) - guard - let from = task.params["fromName"]?.stringValue, - let after = task.params["afterName"]?.stringValue - else { - return .failure(reason: "missingParams") - } - - let baseFromRange0 = findFunctionRange(in: baseText, name: from, language: task.language) - let baseAfterRange = findFunctionRange(in: baseText, name: after, language: task.language) - - if baseFromRange0 == nil, baseAfterRange == nil { - return .failure(reason: "functionNotFoundBaseline(missing:\(from),\(after))") - } else if baseFromRange0 == nil { - return .failure(reason: "functionNotFoundBaseline(missing:\(from))") - } else if baseAfterRange == nil { - return .failure(reason: "functionNotFoundBaseline(missing:\(after))") - } - - guard let baseFromRangeRaw = baseFromRange0 else { - return .failure(reason: "functionNotFoundBaseline(missing:\(from))") - } - - // Extend ranges to include leading documentation/comments for robust equality check - let baseFromRange = extendRangeToIncludeLeadingComments(in: baseText, range: baseFromRangeRaw, language: task.language) - guard - let finalFromRangeRaw = findFunctionRange(in: finalText, name: from, language: task.language), - let finalAfterRange = findFunctionRange(in: finalText, name: after, language: task.language) - else { - return .failure(reason: "functionNotFoundFinal") - } - let finalFromRange = extendRangeToIncludeLeadingComments(in: finalText, range: finalFromRangeRaw, language: task.language) - - let baseFromBody = String(baseText[baseFromRange]) - let finalFromBody = String(finalText[finalFromRange]) - - let baseWithoutFrom = textByRemovingRange(baseText, range: baseFromRange) - let finalWithoutFrom = textByRemovingRange(finalText, range: finalFromRange) - - // Remove double-normalization; use move-specific normalization tolerant to blank-line runs - let outsideUnchanged = normalizeForMoveComparison(baseWithoutFrom) == normalizeForMoveComparison(finalWithoutFrom) - // Use raw header start for placement check (avoid bias from extended doc-range) - let fromHeaderStart = finalFromRangeRaw.lowerBound - let placedAfter = fromHeaderStart >= finalAfterRange.upperBound - // Compare function content with move-specific normalization to tolerate whitespace-only variations - let sameFunctionContent = normalizeForMoveComparison(baseFromBody) == normalizeForMoveComparison(finalFromBody) - - // Enforce exact content retention for TS/Swift - if task.language != .go, !sameFunctionContent { - return .failure(reason: "functionChanged", metrics: [ - "placedAfter": .boolean(placedAfter), - "outsideUnchanged": .boolean(outsideUnchanged) - ]) - } - - var score = 0.0 - if placedAfter { score += 0.5 } - if outsideUnchanged { score += 0.3 } - if sameFunctionContent { score += 0.2 } - - let reason: String = { - if !placedAfter { return "notAfterTarget" } - if !outsideUnchanged { return "collateralChange" } - if !sameFunctionContent { return "functionChanged" } - return "" - }() - - return makeScoredOutput(score: score, reason: reason, metrics: [ - "placedAfter": .boolean(placedAfter), - "outsideUnchanged": .boolean(outsideUnchanged), - "functionContentEqual": .boolean(sameFunctionContent) - ]) - } - - // MARK: - insert_function_bottom_ts - - private func verifyInsertFunctionBottom(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - if let inserts = task.params["inserts"]?.arrayValue, !inserts.isEmpty { - var components: [(String, BenchmarkVerifyOutput)] = [] - for entry in inserts { - guard - let object = entry.objectValue, - let path = object["path"]?.stringValue, - let snippet = object["snippet"]?.stringValue - else { continue } - let footer = object["footer"]?.stringValue ?? task.params["footer"]?.stringValue ?? "// END-OF-FILE" - let subTask = BenchmarkTaskSpec( - id: "\(task.id)::\(path)", - type: task.type, - language: task.language, - difficulty: task.difficulty, - format: task.format, - selectFiles: [path], - newChat: task.newChat, - maxEdits: max(1, task.maxEdits / max(inserts.count, 1)), - instructions: task.instructions, - task: task.task, - acceptance: task.acceptance, - params: [ - "snippet": .string(snippet), - "footer": .string(footer) - ] - ) - components.append((path, verifyInsertFunctionBottom(task: subTask, baseline: baseline, final: final))) - } - return aggregate(components, label: "insertFunctionBottomBundle") - } - guard - let path = task.selectFiles.first, - let baseText = baseline[path], - let finalText = final[path], - let snippet = task.params["snippet"]?.stringValue - else { - return .failure(reason: "missingParams") - } - let footer = task.params["footer"]?.stringValue ?? "// END-OF-FILE" - guard let footerRange = finalText.range(of: footer) else { - return .failure(reason: "footerMissing") - } - - // Try exact snippet match first - let exactRange = finalText.range(of: snippet) - - // If not exact, try normalized - let normalizedSnippetFound: Bool - if exactRange == nil { - let nf = normalizeForSnippetSearch(finalText) - let ns = normalizeForSnippetSearch(snippet) - normalizedSnippetFound = nf.range(of: ns) != nil - if !normalizedSnippetFound { - return .failure(reason: "snippetMissing", metrics: [ - "snippetExactFound": .boolean(false), - "snippetNormalizedFound": .boolean(false), - "footerString": .string(footer) - ]) - } - } else { - normalizedSnippetFound = true - } - - // Check placement above footer - let placedAboveFooter: Bool - if let r = exactRange { - placedAboveFooter = r.upperBound <= footerRange.lowerBound - } else { - let finalBeforeFooter = String(finalText[.. BenchmarkVerifyOutput { - let discovery = task.params["targetDiscovery"]?.boolValue == true - - if discovery { - // Target discovery mode: find which candidate matches the patch - guard let patch = task.params["patch"]?.stringValue else { - return .failure(reason: "missingParams") - } - - // Load candidates - let candidatesParam = task.params["candidatePaths"]?.arrayValue?.compactMap(\.stringValue) ?? [] - let candidates = candidatesParam.isEmpty ? task.selectFiles : candidatesParam - - var matched: String? - var bestCoverage: (path: String, applied: Int, total: Int) = ("", 0, 0) - - // Try to find exact match among candidates - for path in candidates { - guard let baseText = baseline[path], let finalText = final[path] else { continue } - if let expected = SimpleUnifiedPatchApplier.apply(patch: patch, to: baseText), expected == finalText { - matched = path - break - } - // Compute coverage for partial credit reporting - let (applied, total) = UnifiedPatchGrader.coverage(baseline: baseText, final: finalText, patch: patch) - if applied > bestCoverage.applied { - bestCoverage = (path, applied, total) - } - } - - guard let matchedPath = matched else { - return BenchmarkVerifyOutput( - pass: false, - score: bestCoverage.total > 0 ? Double(bestCoverage.applied) / Double(bestCoverage.total) : 0.0, - reason: "diffMismatch", - metrics: [ - "bestCandidatePath": .string(bestCoverage.path), - "hunksApplied": .integer(bestCoverage.applied), - "hunksTotal": .integer(bestCoverage.total) - ] - ) - } - - // Enforce "only this file changed" among candidates - var othersUnchanged = true - for path in candidates where path != matchedPath { - if let b = baseline[path], let f = final[path], b != f { - othersUnchanged = false - break - } - } - if !othersUnchanged { - return .failure(reason: "othersChanged", metrics: ["path": .string(matchedPath)]) - } - - // Indentation style check using matched final - let finalText = final[matchedPath]! - let usesTabIndentation = task.language.usesTabIndentation - let hasTab = finalText.contains("\t") - let hasFourSpaces = finalText.contains(" ") - if usesTabIndentation { - if hasFourSpaces { - return .failure(reason: "wrongIndentationStyle", metrics: [ - "expectedIndent": .string("tabs"), - "hasTabs": .boolean(hasTab), - "hasFourSpaces": .boolean(hasFourSpaces) - ]) - } - } else { - if hasTab { - return .failure(reason: "tabFound", metrics: [ - "expectedIndent": .string("spaces"), - "hasTabs": .boolean(hasTab), - "hasFourSpaces": .boolean(hasFourSpaces) - ]) - } - } - - // Exact success - return makeScoredOutput(score: 1.0, reason: "", metrics: [ - "appliedOK": .boolean(true), - "matchedPath": .string(matchedPath), - "candidates": .integer(candidates.count) - ]) - } - - // Non-discovery mode (existing behavior) - guard - let path = task.selectFiles.first, - let baseText = baseline[path], - let finalText = final[path], - let patch = task.params["patch"]?.stringValue - else { - return .failure(reason: "missingParams") - } - guard let expected = SimpleUnifiedPatchApplier.apply(patch: patch, to: baseText) else { - return .failure(reason: "invalidPatch") - } - - // Check indentation style matches language preference - let usesTabIndentation = task.language.usesTabIndentation - let hasTab = finalText.contains("\t") - let hasFourSpaces = finalText.contains(" ") - if usesTabIndentation { - // Swift should use tabs - reject if we have spaces being used for indentation - if hasFourSpaces { - return .failure(reason: "wrongIndentationStyle", metrics: [ - "expectedIndent": .string("tabs"), - "hasTabs": .boolean(hasTab), - "hasFourSpaces": .boolean(hasFourSpaces) - ]) - } - } else { - // TS/Go should use spaces - reject if we have tabs - if hasTab { - return .failure(reason: "tabFound", metrics: [ - "expectedIndent": .string("spaces"), - "hasTabs": .boolean(hasTab), - "hasFourSpaces": .boolean(hasFourSpaces) - ]) - } - } - - let match = expected == finalText - if match { - return makeScoredOutput(score: 1.0, reason: "", metrics: [ - "appliedOK": .boolean(true) - ]) - } - - // Exact match failed - compute partial credit based on hunk coverage for analytics - let (appliedHunks, totalHunks) = UnifiedPatchGrader.coverage(baseline: baseText, final: finalText, patch: patch) - let normalizedScore = totalHunks > 0 ? Double(appliedHunks) / Double(totalHunks) : 0.0 - - // Enforce exact application: pass=false on mismatch, preserve score for reporting - return BenchmarkVerifyOutput(pass: false, score: normalizedScore, reason: "diffMismatch", metrics: [ - "appliedOK": .boolean(false), - "hunksTotal": .integer(totalHunks), - "hunksApplied": .integer(appliedHunks) - ]) - } - - // MARK: - insert_guard_ts - - private func verifyInsertGuardMarkerless(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - guard - let path = task.selectFiles.first, - let finalText = final[path], - let baselineText = baseline[path], - let functionName = task.params["functionName"]?.stringValue, - let snippet = task.params["snippet"]?.stringValue, - let insertAfterPattern = task.params["insertAfterPattern"]?.stringValue - else { - return BenchmarkVerifyOutput.failure(reason: "missingParams") - } - - // Locate function in baseline and final - guard let baselineRange = findFunctionRange(in: baselineText, name: functionName, language: task.language), - let finalRange = findFunctionRange(in: finalText, name: functionName, language: task.language) - else { - return BenchmarkVerifyOutput.failure(reason: "functionNotFoundFinal") - } - - let finalBody = String(finalText[finalRange]) - - // Check that snippet appears in final function body - let snippetFound = containsGuardSnippet(in: finalBody, language: task.language, expected: snippet) - - // Check that snippet appears after the insertAfterPattern - var afterPatternOk = false - if let patternRange = finalBody.range(of: insertAfterPattern) { - let afterPattern = String(finalBody[patternRange.upperBound...]) - afterPatternOk = containsGuardSnippet(in: afterPattern, language: task.language, expected: snippet) - } - - // Check outside the function is unchanged - let baselineOutside = textByRemovingRange(baselineText, range: baselineRange) - let finalOutside = textByRemovingRange(finalText, range: finalRange) - let unchangedOutside = normalizeForMoveComparison(baselineOutside) == normalizeForMoveComparison(finalOutside) - - // Check indentation style (leading whitespace on non-empty lines only) - let usesTabIndentation = task.language.usesTabIndentation - - func hasLeadingIndentation(_ text: String, checkTabs: Bool) -> Bool { - let lines = text.split(separator: "\n", omittingEmptySubsequences: false) - for line in lines { - let trimmed = line.trimmingCharacters(in: .whitespaces) - if trimmed.isEmpty { continue } - // Get leading whitespace - var leading = "" - for ch in line { - if ch == " " || ch == "\t" { - leading.append(ch) - } else { - break - } - } - if checkTabs, leading.contains("\t") { - return true - } - if !checkTabs, leading.contains(" ") { - return true - } - } - return false - } - - let hasTabIndent = hasLeadingIndentation(snippet, checkTabs: true) || hasLeadingIndentation(finalBody, checkTabs: true) - let hasFourSpaceIndent = hasLeadingIndentation(snippet, checkTabs: false) || hasLeadingIndentation(finalBody, checkTabs: false) - - if usesTabIndentation { - if hasFourSpaceIndent, !hasTabIndent { - return BenchmarkVerifyOutput.failure(reason: "wrongIndentationStyle", metrics: [ - "expectedIndent": .string("tabs"), - "hasTabs": .boolean(hasTabIndent), - "hasFourSpaces": .boolean(hasFourSpaceIndent) - ]) - } - } else { - if hasTabIndent { - return BenchmarkVerifyOutput.failure(reason: "tabFound", metrics: [ - "expectedIndent": .string("spaces"), - "hasTabs": .boolean(hasTabIndent), - "hasFourSpaces": .boolean(hasFourSpaceIndent) - ]) - } - } - - var score = 0.0 - if snippetFound { score += 0.5 } - if afterPatternOk { score += 0.3 } - if unchangedOutside { score += 0.2 } - - let reason = if !snippetFound { - "snippetMissing" - } else if !afterPatternOk { - "snippetNotAfterPattern" - } else if !unchangedOutside { - "collateralDamage" - } else { - "" - } - - let metrics: [String: BenchmarkJSONValue] = [ - "snippetFound": .boolean(snippetFound), - "afterPatternOk": .boolean(afterPatternOk), - "unchangedOutside": .boolean(unchangedOutside) - ] - - return makeScoredOutput(score: score, reason: reason, metrics: metrics) - } - - private func verifyInsertGuard(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - // Check for markerless mode - if task.params["markerless"]?.boolValue == true { - return verifyInsertGuardMarkerless(task: task, baseline: baseline, final: final) - } - - if let guards = task.params["guards"]?.arrayValue, !guards.isEmpty { - var components: [(String, BenchmarkVerifyOutput)] = [] - for entry in guards { - guard - let object = entry.objectValue, - let path = object["path"]?.stringValue, - let uid = object["uid"]?.stringValue, - let snippet = object["snippet"]?.stringValue - else { continue } - let subTask = BenchmarkTaskSpec( - id: "\(task.id)::\(path)", - type: task.type, - language: task.language, - difficulty: task.difficulty, - format: task.format, - selectFiles: [path], - newChat: task.newChat, - maxEdits: max(1, task.maxEdits / max(guards.count, 1)), - instructions: task.instructions, - task: task.task, - acceptance: task.acceptance, - params: [ - "uid": .string(uid), - "snippet": .string(snippet), - "markerless": .boolean(false) - ] - ) - components.append((path, verifyInsertGuard(task: subTask, baseline: baseline, final: final))) - } - return aggregate(components, label: "insertGuardBundle") - } - guard let uid = task.params["uid"]?.stringValue, - let snippet = task.params["snippet"]?.stringValue, - let path = task.selectFiles.first, - let finalText = final[path], - let baselineText = baseline[path] - else { - return BenchmarkVerifyOutput.failure(reason: "missingParams") - } - let startToken = "// ANCHOR:start:\(uid)" - let endToken = "// ANCHOR:end:\(uid)" - guard let finalStart = finalText.range(of: startToken), - let finalEnd = finalText.range(of: endToken, range: finalStart.upperBound ..< finalText.endIndex), - let baselineStart = baselineText.range(of: startToken), - let baselineEnd = baselineText.range(of: endToken, range: baselineStart.upperBound ..< baselineText.endIndex) - else { - return BenchmarkVerifyOutput.failure(reason: "anchorsMissing") - } - // Check indentation style matches language preference - let usesTabIndentation = task.language.usesTabIndentation - let finalSegmentRaw = String(finalText[finalStart.upperBound ..< finalEnd.lowerBound]) - let hasTab = snippet.contains("\t") || finalSegmentRaw.contains("\t") - let hasFourSpaces = snippet.contains(" ") || finalSegmentRaw.contains(" ") - if usesTabIndentation { - // Swift should use tabs - reject if we have spaces being used for indentation - // We check for multiple spaces that look like indentation - if hasFourSpaces { - return BenchmarkVerifyOutput.failure(reason: "wrongIndentationStyle", metrics: [ - "expectedIndent": .string("tabs"), - "hasTabs": .boolean(hasTab), - "hasFourSpaces": .boolean(hasFourSpaces) - ]) - } - } else { - // TS/Go should use spaces - reject if we have tabs - if hasTab { - return BenchmarkVerifyOutput.failure(reason: "tabFound", metrics: [ - "expectedIndent": .string("spaces"), - "hasTabs": .boolean(hasTab), - "hasFourSpaces": .boolean(hasFourSpaces) - ]) - } - } - let finalSegment = finalSegmentRaw - let baselineSegment = String(baselineText[baselineStart.upperBound ..< baselineEnd.lowerBound]) - func replaceAnchorBody(_ text: String, uid: String, with body: String) -> String { - let startToken = "// ANCHOR:start:\(uid)" - let endToken = "// ANCHOR:end:\(uid)" - guard let start = text.range(of: startToken), - let end = text.range(of: endToken, range: start.upperBound ..< text.endIndex) - else { - return text - } - var copy = text - copy.replaceSubrange(start.upperBound ..< end.lowerBound, with: body) - return copy - } - if replaceAnchorBody(finalText, uid: uid, with: baselineSegment) != baselineText { - return BenchmarkVerifyOutput.failure(reason: "collateralDamage") - } - func leadingIndentation(of line: Substring) -> String { - var indent = "" - for character in line { - if character == " " || character == "\t" { - indent.append(character) - } else { - break - } - } - return indent - } - func indentationSample(in segment: String) -> String? { - let lines = segment.split(separator: "\n", omittingEmptySubsequences: false) - guard let sample = lines.first(where: { !$0.trimmingCharacters(in: .whitespaces).isEmpty }) else { - return nil - } - return leadingIndentation(of: sample) - } - func indentSnippet(_ value: String, with indent: String) -> String { - guard !indent.isEmpty else { return value } - let lines = value.split(separator: "\n", omittingEmptySubsequences: false) - return lines.map { indent + String($0) }.joined(separator: "\n") - } - var snippetCandidates: [String] = [snippet] - let baselineIndent = indentationSample(in: baselineSegment) - let finalIndent = indentationSample(in: finalSegment) - let indentOptions = [baselineIndent, finalIndent].compactMap(\.self).filter { !$0.isEmpty } - for indent in indentOptions { - let candidate = indentSnippet(snippet, with: indent) - if !snippetCandidates.contains(candidate) { - snippetCandidates.append(candidate) - } - } - let matchedSnippet = snippetCandidates.first(where: { finalSegment.contains($0) }) - // Updated snippetFound logic to be tolerant of formatting variations - let snippetFound = containsGuardSnippet(in: finalSegment, language: task.language, expected: snippet) - let withoutSnippet: String = if let used = matchedSnippet { - finalSegment.replacingOccurrences(of: used, with: "") - } else { - finalSegment - } - let unchangedOutside = withoutSnippet.trimmingCharacters(in: .whitespacesAndNewlines) == baselineSegment.trimmingCharacters(in: .whitespacesAndNewlines) - let anyGuard = finalSegment.contains("if (") && finalSegment.contains("return ") - var score = 0.0 - if snippetFound { score += 0.7 } - if unchangedOutside { score += 0.3 } - if !snippetFound, anyGuard, policy.lenient { - score = max(score, 0.3) - } - let reason: String = if snippetFound { - unchangedOutside ? "" : "collateralChangeInsideAnchors" - } else { - "snippetMismatch" - } - let metrics: [String: BenchmarkJSONValue] = [ - "snippetFound": .boolean(snippetFound), - "unchangedOutside": .boolean(unchangedOutside) - ] - return makeScoredOutput(score: score, reason: reason, metrics: metrics) - } - - // MARK: - patch_block_ts - - private func verifyPatchBlockMarkerless(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - guard - let path = task.selectFiles.first, - let finalText = final[path], - let baselineText = baseline[path], - let functionName = task.params["functionName"]?.stringValue, - let snippet = task.params["snippet"]?.stringValue - else { - return BenchmarkVerifyOutput.failure(reason: "missingParams") - } - - // Locate function in baseline and final - guard let baselineRange = findFunctionRange(in: baselineText, name: functionName, language: task.language), - let finalRange = findFunctionRange(in: finalText, name: functionName, language: task.language) - else { - return BenchmarkVerifyOutput.failure(reason: "functionNotFoundFinal") - } - - let finalBody = String(finalText[finalRange]) - - // Check indentation style (leading whitespace on non-empty lines only) - let usesTabIndentation = task.language.usesTabIndentation - - func hasLeadingIndentation(_ text: String, checkTabs: Bool) -> Bool { - let lines = text.split(separator: "\n", omittingEmptySubsequences: false) - for line in lines { - let trimmed = line.trimmingCharacters(in: .whitespaces) - if trimmed.isEmpty { continue } - // Get leading whitespace - var leading = "" - for ch in line { - if ch == " " || ch == "\t" { - leading.append(ch) - } else { - break - } - } - if checkTabs, leading.contains("\t") { - return true - } - if !checkTabs, leading.contains(" ") { - return true - } - } - return false - } - - let hasTabIndent = hasLeadingIndentation(snippet, checkTabs: true) || hasLeadingIndentation(finalBody, checkTabs: true) - let hasFourSpaceIndent = hasLeadingIndentation(snippet, checkTabs: false) || hasLeadingIndentation(finalBody, checkTabs: false) - - if usesTabIndentation { - if hasFourSpaceIndent, !hasTabIndent { - return BenchmarkVerifyOutput.failure(reason: "wrongIndentationStyle", metrics: [ - "expectedIndent": .string("tabs"), - "hasTabs": .boolean(hasTabIndent), - "hasFourSpaces": .boolean(hasFourSpaceIndent) - ]) - } - } else { - if hasTabIndent { - return BenchmarkVerifyOutput.failure(reason: "tabFound", metrics: [ - "expectedIndent": .string("spaces"), - "hasTabs": .boolean(hasTabIndent), - "hasFourSpaces": .boolean(hasFourSpaceIndent) - ]) - } - } - - // Check if function body equals snippet (after normalization) - let finalBodyNormalized = finalBody.trimmingCharacters(in: .whitespacesAndNewlines) - let snippetNormalized = snippet.trimmingCharacters(in: .whitespacesAndNewlines) - let bodyMatches = finalBodyNormalized == snippetNormalized - - // Check outside the function is unchanged - let baselineOutside = textByRemovingRange(baselineText, range: baselineRange) - let finalOutside = textByRemovingRange(finalText, range: finalRange) - let unchangedOutside = normalizeForOutsideComparison(baselineOutside) == normalizeForOutsideComparison(finalOutside) - - if bodyMatches, unchangedOutside { - return BenchmarkVerifyOutput.success() - } - - // Compute partial score for reporting - let signatureOK = finalBody.contains("\(functionName)(") && snippet.contains("\(functionName)(") - let similarity = jaccard(tokens(finalBodyNormalized), tokens(snippetNormalized)) - var score = 0.0 - if signatureOK { score += 0.4 } - if unchangedOutside { score += 0.2 } - score += 0.4 * similarity - let clamped = clampScore(score) - - let reason = if !bodyMatches { - "blockMismatch" - } else if !unchangedOutside { - "collateralDamage" - } else { - "" - } - - let metrics: [String: BenchmarkJSONValue] = [ - "signatureOk": .boolean(signatureOK), - "tokenSimilarity": .double(similarity), - "unchangedOutside": .boolean(unchangedOutside) - ] - - return makeScoredOutput(score: clamped, reason: reason, metrics: metrics) - } - - private func verifyPatchBlock(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - // Check for markerless mode - if task.params["markerless"]?.boolValue == true { - return verifyPatchBlockMarkerless(task: task, baseline: baseline, final: final) - } - - if let blocks = task.params["blocks"]?.arrayValue, !blocks.isEmpty { - var components: [(String, BenchmarkVerifyOutput)] = [] - for entry in blocks { - guard - let object = entry.objectValue, - let path = object["path"]?.stringValue, - let uid = object["uid"]?.stringValue, - let snippet = object["snippet"]?.stringValue - else { continue } - let subTask = BenchmarkTaskSpec( - id: "\(task.id)::\(path)", - type: task.type, - language: task.language, - difficulty: task.difficulty, - format: task.format, - selectFiles: [path], - newChat: task.newChat, - maxEdits: max(2, task.maxEdits / max(blocks.count, 1)), - instructions: task.instructions, - task: task.task, - acceptance: task.acceptance, - params: [ - "uid": .string(uid), - "snippet": .string(snippet) - ] - ) - components.append((path, verifyPatchBlock(task: subTask, baseline: baseline, final: final))) - } - return aggregate(components, label: "patchBlockBundle") - } - guard let uid = task.params["uid"]?.stringValue, - let snippet = task.params["snippet"]?.stringValue, - let path = task.selectFiles.first, - let finalText = final[path], - let baselineText = baseline[path] - else { - return BenchmarkVerifyOutput.failure(reason: "missingParams") - } - let startToken = "/* BLOCK START:\(uid) */" - let endToken = "/* BLOCK END:\(uid) */" - guard let start = finalText.range(of: startToken), - let end = finalText.range(of: endToken, range: start.upperBound ..< finalText.endIndex) - else { - return BenchmarkVerifyOutput.failure(reason: "blockAnchorsMissing") - } - - // Extract block content WITHOUT trimming to preserve indentation - let blockRaw = String(finalText[start.upperBound ..< end.lowerBound]) - - // Check indentation style matches language preference - let usesTabIndentation = task.language.usesTabIndentation - let hasTab = blockRaw.contains("\t") - let hasFourSpaces = blockRaw.contains(" ") - if usesTabIndentation { - // Swift should use tabs - reject if we have spaces being used for indentation - // Check for multiple spaces that look like indentation - if hasFourSpaces { - return BenchmarkVerifyOutput.failure(reason: "wrongIndentationStyle", metrics: [ - "expectedIndent": .string("tabs"), - "hasTabs": .boolean(hasTab), - "hasFourSpaces": .boolean(hasFourSpaces) - ]) - } - } else { - // TS/Go should use spaces - reject if we have tabs - if hasTab { - return BenchmarkVerifyOutput.failure(reason: "tabFound", metrics: [ - "expectedIndent": .string("spaces"), - "hasTabs": .boolean(hasTab), - "hasFourSpaces": .boolean(hasFourSpaces) - ]) - } - } - - // Normalize: trim outer whitespace only (newlines at start/end of block) - let block = blockRaw.trimmingCharacters(in: .whitespacesAndNewlines) - let target = snippet.trimmingCharacters(in: .whitespacesAndNewlines) - - let sanitizedFinal = replaceBlock(finalText, uid: uid, with: "") - let sanitizedBaseline = replaceBlock(baselineText, uid: uid, with: "") - // Use normalization to avoid false negatives on whitespace-only differences outside the block - if normalizeForOutsideComparison(sanitizedBaseline) != normalizeForOutsideComparison(sanitizedFinal) { - return BenchmarkVerifyOutput.failure(reason: "collateralDamage") - } - if block == target { - return BenchmarkVerifyOutput.success() - } - // Exactness required for patch_block - compute partial score for reporting only - let signatureOK = block.contains("block2(") && target.contains("block2(") - let similarity = jaccard(tokens(block), tokens(target)) - var score = 0.0 - if signatureOK { score += 0.4 } - score += 0.6 * similarity - let clamped = clampScore(score) - - let metrics: [String: BenchmarkJSONValue] = [ - "signatureOk": .boolean(signatureOK), - "tokenSimilarity": .double(similarity) - ] - // Enforce exact match: pass=false on mismatch, but preserve score for analytics - return BenchmarkVerifyOutput(pass: false, score: clamped, reason: "blockMismatch", metrics: metrics) - } - - private func replaceBlock(_ text: String, uid: String, with replacement: String) -> String { - let startToken = "/* BLOCK START:\(uid) */" - let endToken = "/* BLOCK END:\(uid) */" - guard let start = text.range(of: startToken), - let end = text.range(of: endToken, range: start.upperBound ..< text.endIndex) - else { return text } - var modified = text - modified.replaceSubrange(start.upperBound ..< end.lowerBound, with: replacement) - return modified - } - - private func normalizeForOutsideComparison(_ text: String) -> String { - var result = text - // Normalize line endings - result = result.replacingOccurrences(of: "\r\n", with: "\n") - // Trim trailing spaces from each line - result = result.split(separator: "\n", omittingEmptySubsequences: false) - .map { $0.trimmingCharacters(in: .whitespaces) } - .joined(separator: "\n") - // Collapse 3+ consecutive newlines to 2 - while result.contains("\n\n\n") { - result = result.replacingOccurrences(of: "\n\n\n", with: "\n\n") - } - return result - } - - private func sanitizeForCurlyComparison(_ text: String) -> String { - var result = text - // Normalize line endings to \n - result = result.replacingOccurrences(of: "\r\n", with: "\n") - // Trim trailing whitespace from each line - result = result.split(separator: "\n", omittingEmptySubsequences: false) - .map { $0.trimmingCharacters(in: .whitespaces) } - .joined(separator: "\n") - // Collapse 3+ consecutive newlines to 2 to be robust - while result.contains("\n\n\n") { - result = result.replacingOccurrences(of: "\n\n\n", with: "\n\n") - } - // Remove all closing braces - result = result.replacingOccurrences(of: "}", with: "") - return result - } - - private func normalizeForSnippetSearch(_ text: String) -> String { - var s = text.replacingOccurrences(of: "\r\n", with: "\n") - // Trim trailing spaces per line - s = s.split(separator: "\n", omittingEmptySubsequences: false) - .map { $0.trimmingCharacters(in: .whitespaces) } - .joined(separator: "\n") - // Collapse 3+ newlines to 2 - while s.contains("\n\n\n") { - s = s.replacingOccurrences(of: "\n\n\n", with: "\n\n") - } - // Trim leading/trailing newlines - return s.trimmingCharacters(in: .whitespacesAndNewlines) - } - - private func tokens(_ value: String) -> Set { - let pieces = value.lowercased().components(separatedBy: CharacterSet.alphanumerics.inverted) - return Set(pieces.filter { !$0.isEmpty }) - } - - private func jaccard(_ lhs: Set, _ rhs: Set) -> Double { - if lhs.isEmpty, rhs.isEmpty { return 1.0 } - let intersection = lhs.intersection(rhs).count - let union = lhs.union(rhs).count - guard union > 0 else { return 0.0 } - return Double(intersection) / Double(union) - } - - // MARK: - swap_args_in_region_ts - - private func verifySwapArgsMarkerless(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - guard - let path = task.selectFiles.first, - let finalText = final[path], - let baselineText = baseline[path], - let functionName = task.params["functionName"]?.stringValue - else { - return BenchmarkVerifyOutput.failure(reason: "missingParams") - } - - let expectedSwaps = task.params["expectedSwaps"]?.intValue ?? 0 - - // Locate function in baseline and final - guard let baselineRange = findFunctionRange(in: baselineText, name: functionName, language: task.language), - let finalRange = findFunctionRange(in: finalText, name: functionName, language: task.language) - else { - return BenchmarkVerifyOutput.failure(reason: "functionNotFoundFinal") - } - - let baselineBody = String(baselineText[baselineRange]) - let finalBody = String(finalText[finalRange]) - - // Extract use() pairs from the function bodies - let baselinePairs = extractUsePairs(baselineBody) - let finalPairs = extractUsePairs(finalBody) - - // For markerless mode, we check the largest contiguous region - // For simplicity, we'll check all pairs within the function - let total = max(expectedSwaps, baselinePairs.count) - var correct = 0 - for (lhs, rhs) in baselinePairs { - if finalPairs.contains(where: { $0.0 == rhs && $0.1 == lhs }) { - correct += 1 - } - } - - // Check outside the function is unchanged - let baselineOutside = textByRemovingRange(baselineText, range: baselineRange) - let finalOutside = textByRemovingRange(finalText, range: finalRange) - let unchangedOutside = normalizeForOutsideComparison(baselineOutside) == normalizeForOutsideComparison(finalOutside) - - if !unchangedOutside { - return BenchmarkVerifyOutput.failure(reason: "outsideChanged") - } - - let score = total == 0 ? 1.0 : Double(correct) / Double(total) - return makeScoredOutput(score: score, reason: "", metrics: [ - "swapsCorrect": .integer(correct), - "swapsExpected": .integer(total), - "unchangedOutside": .boolean(unchangedOutside) - ]) - } - - private func verifySwapArgs(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - // Check for markerless mode - if task.params["markerless"]?.boolValue == true { - return verifySwapArgsMarkerless(task: task, baseline: baseline, final: final) - } - - if let regions = task.params["regions"]?.arrayValue, !regions.isEmpty { - var components: [(String, BenchmarkVerifyOutput)] = [] - for entry in regions { - guard - let object = entry.objectValue, - let path = object["path"]?.stringValue, - let uid = object["uid"]?.stringValue - else { continue } - let expected = object["expectedSwaps"]?.intValue ?? 0 - let subTask = BenchmarkTaskSpec( - id: "\(task.id)::\(path)", - type: task.type, - language: task.language, - difficulty: task.difficulty, - format: task.format, - selectFiles: [path], - newChat: task.newChat, - maxEdits: max(1, expected), - instructions: task.instructions, - task: task.task, - acceptance: task.acceptance, - params: [ - "uid": .string(uid), - "expectedSwaps": .integer(expected) - ] - ) - components.append((path, verifySwapArgs(task: subTask, baseline: baseline, final: final))) - } - return aggregate(components, label: "swapArgsBundle") - } - guard let uid = task.params["uid"]?.stringValue, - let expected = task.params["expectedSwaps"]?.intValue, - let path = task.selectFiles.first, - let baselineText = baseline[path], - let finalText = final[path] - else { - return BenchmarkVerifyOutput.failure(reason: "missingParams") - } - let startToken = "/* START_SWAP:\(uid) */" - let endToken = "/* END_SWAP:\(uid) */" - guard let baseStart = baselineText.range(of: startToken), - let baseEnd = baselineText.range(of: endToken, range: baseStart.upperBound ..< baselineText.endIndex), - let finalStart = finalText.range(of: startToken), - let finalEnd = finalText.range(of: endToken, range: finalStart.upperBound ..< finalText.endIndex) - else { - return BenchmarkVerifyOutput.failure(reason: "swapRegionMissing") - } - let baseRegion = String(baselineText[baseStart.upperBound ..< baseEnd.lowerBound]) - let finalRegion = String(finalText[finalStart.upperBound ..< finalEnd.lowerBound]) - let baselinePairs = extractUsePairs(baseRegion) - let finalPairs = extractUsePairs(finalRegion) - let total = max(expected, baselinePairs.count) - var correct = 0 - for (lhs, rhs) in baselinePairs { - if finalPairs.contains(where: { $0.0 == rhs && $0.1 == lhs }) { - correct += 1 - } - } - let outsideBaseline = removeRegion(from: baselineText, start: baseStart, end: baseEnd) - let outsideFinal = removeRegion(from: finalText, start: finalStart, end: finalEnd) - if outsideBaseline != outsideFinal { - return BenchmarkVerifyOutput.failure(reason: "outsideChanged") - } - let score = total == 0 ? 1.0 : Double(correct) / Double(total) - return makeScoredOutput(score: score, reason: "", metrics: [ - "swapsCorrect": .integer(correct), - "swapsExpected": .integer(total) - ]) - } - - private func extractUsePairs(_ text: String) -> [(String, String)] { - var pairs: [(String, String)] = [] - let pattern = "use\\(\\s*([^,]+)\\s*,\\s*([^\\)]+)\\)" - guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return [] } - let ns = text as NSString - let matches = regex.matches(in: text, options: [], range: NSRange(location: 0, length: ns.length)) - for match in matches where match.numberOfRanges >= 3 { - let lhs = ns.substring(with: match.range(at: 1)).trimmingCharacters(in: .whitespacesAndNewlines) - let rhs = ns.substring(with: match.range(at: 2)).trimmingCharacters(in: .whitespacesAndNewlines) - pairs.append((lhs, rhs)) - } - return pairs - } - - private func removeRegion(from text: String, start: Range, end: Range) -> String { - var copy = text - copy.removeSubrange(start.lowerBound ..< end.upperBound) - return copy - } - - // MARK: - index_only_apps_ts - - private func verifyIndexOnly(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - guard let target = task.params["target"]?.stringValue, - let targetPath = task.selectFiles.first, - let finalText = final[targetPath] - else { - return BenchmarkVerifyOutput.failure(reason: "missingTarget") - } - if let others = task.params["otherPaths"]?.arrayValue { - for entry in others { - guard let path = entry.stringValue, - let baselineText = baseline[path], - let finalText = final[path] - else { continue } - if baselineText != finalText { - return BenchmarkVerifyOutput.failure(reason: "othersChanged", metrics: ["path": .string(path)]) - } - } - } - let signature: String - let expectedReturn: String - switch task.language { - case .ts: - signature = "export default function index()" - expectedReturn = "return \"\(target)\";" - case .go: - signature = "func index() string" - expectedReturn = "return \"\(target)\"" - case .swift: - signature = "public func index() -> String" - expectedReturn = "return \"\(target)\"" - } - let bodyRange = functionBodyRange(finalText, signature: signature) - let body = bodyRange.map { String(finalText[$0]) } ?? finalText - let donePattern = #"//\s*DONE\s*:\s*"# + NSRegularExpression.escapedPattern(for: target) - let doneInsideFunction: Bool = { - guard let bodyRange else { return false } - let regex = try? NSRegularExpression(pattern: donePattern, options: [.caseInsensitive]) - let range = NSRange(bodyRange, in: finalText) - return regex?.firstMatch(in: finalText, options: [], range: range) != nil - }() - let hasAltMarker = body.contains(target) && body.contains("//") - let returnExact = body.contains(expectedReturn) - let returnContainsTarget = !returnExact && body.contains("return \"\(target)") - var score = 0.0 - if doneInsideFunction { score += 0.6 } - if returnExact { score += 0.4 } - else if returnContainsTarget, policy.lenient { score += 0.15 } - if !doneInsideFunction, hasAltMarker, policy.lenient { - score = max(score, 0.3) - } - let reason = if doneInsideFunction, returnExact { - "" - } else if !doneInsideFunction { - "doneMarkerMissingOrOutsideFunction" - } else { - "returnChanged" - } - return makeScoredOutput(score: score, reason: reason, metrics: [ - "doneInsideFunction": .boolean(doneInsideFunction), - "returnExact": .boolean(returnExact) - ]) - } - - private func functionBodyRange(_ text: String, signature: String) -> Range? { - guard let signatureRange = text.range(of: signature) else { return nil } - var index = signatureRange.upperBound - while index < text.endIndex, text[index].isWhitespace { - index = text.index(after: index) - } - guard index < text.endIndex, text[index] == "{" else { return nil } - var depth = 0 - var bodyStart: String.Index? - while index < text.endIndex { - let character = text[index] - if character == "{" { - depth += 1 - if bodyStart == nil { - bodyStart = text.index(after: index) - } - } else if character == "}" { - depth -= 1 - if depth == 0 { - guard let start = bodyStart else { return nil } - return start ..< index - } - } - index = text.index(after: index) - } - return nil - } - - // MARK: - rename_export_and_imports_ts - - private func verifyRename(task: BenchmarkTaskSpec, baseline: [String: String], final: [String: String]) -> BenchmarkVerifyOutput { - guard let rename = task.params["rename"]?.objectValue, - let fromName = rename["from"]?.stringValue, - let toName = rename["to"]?.stringValue - else { - return BenchmarkVerifyOutput.failure(reason: "missingRenameParams") - } - guard let exporter = task.selectFiles.first, - let exporterText = final[exporter], - let exporterBaseline = baseline[exporter] - else { - return BenchmarkVerifyOutput.failure(reason: "missingExporter") - } - let nearTokens = task.params["nearMissTokens"]?.arrayValue?.compactMap(\.stringValue) ?? [] - func hasExportDecl(_ text: String, lang: BenchmarkLanguage, name: String) -> Bool { - switch lang { - case .ts: - let expressions = [ - "\\bexport\\s+function\\s+\(name)\\(", - "\\bexport\\s+const\\s+\(name)\\b", - "\\bexport\\s+class\\s+\(name)\\b" - ] - for raw in expressions { - let pattern = raw.replacingOccurrences(of: "(name)", with: NSRegularExpression.escapedPattern(for: name)) - if text.range(of: pattern, options: [.regularExpression]) != nil { - return true - } - } - return false - case .go: - let pattern = "(?m)^\\s*func\\s+\(name)\\s*\\(".replacingOccurrences(of: "(name)", with: NSRegularExpression.escapedPattern(for: name)) - return text.range(of: pattern, options: [.regularExpression]) != nil - case .swift: - let expressions = [ - "\\bpublic\\s+func\\s+\(name)\\s*\\(", - "\\bfunc\\s+\(name)\\s*\\(" - ] - for raw in expressions { - let pattern = raw.replacingOccurrences(of: "(name)", with: NSRegularExpression.escapedPattern(for: name)) - if text.range(of: pattern, options: [.regularExpression]) != nil { - return true - } - } - return false - } - } - let exporterHasOldDecl = hasExportDecl(exporterText, lang: task.language, name: fromName) - let exporterHasNewDecl = hasExportDecl(exporterText, lang: task.language, name: toName) - if exporterHasOldDecl || !exporterHasNewDecl { - return BenchmarkVerifyOutput.failure(reason: "exporterMismatch") - } - if let others = task.params["otherPaths"]?.arrayValue { - for entry in others { - guard let path = entry.stringValue, - let baselineText = baseline[path], - let finalText = final[path] - else { continue } - if baselineText != finalText { - return BenchmarkVerifyOutput.failure(reason: "othersChanged", metrics: ["path": .string(path)]) - } - } - } - if let importPaths = task.params["importPaths"]?.arrayValue { - for entry in importPaths { - guard let path = entry.stringValue, - let content = final[path] - else { continue } - if content.contains(fromName), !content.contains(toName) { - return BenchmarkVerifyOutput.failure(reason: "importerMismatch", metrics: ["path": .string(path)]) - } - } - } - if let reexports = task.params["reexportPaths"]?.arrayValue?.compactMap(\.stringValue), !reexports.isEmpty { - let escapedOld = NSRegularExpression.escapedPattern(for: fromName) - let escapedNew = NSRegularExpression.escapedPattern(for: toName) - let exportOldPattern = "\\bexport\\s*\\{[^\\}]*\\b\(escapedOld)\\b" - let exportNewPattern = "\\bexport\\s*\\{[^\\}]*\\b\(escapedNew)\\b" - for path in reexports { - guard let barrelFinal = final[path], let barrelBaseline = baseline[path] else { continue } - let hasOld = barrelFinal.range(of: exportOldPattern, options: [.regularExpression]) != nil - let hasNew = barrelFinal.range(of: exportNewPattern, options: [.regularExpression]) != nil - if hasOld || !hasNew { - return BenchmarkVerifyOutput.failure(reason: "exporterMismatch", metrics: ["path": .string(path)]) - } - if !nearTokens.isEmpty { - for token in nearTokens { - let baselineCount = countOccurrences(of: token, in: barrelBaseline) - let finalCount = countOccurrences(of: token, in: barrelFinal) - if baselineCount != finalCount { - return BenchmarkVerifyOutput.failure(reason: "nearMissChanged", metrics: [ - "path": .string(path), - "token": .string(token) - ]) - } - } - } - } - } - if !nearTokens.isEmpty { - for token in nearTokens { - let baselineCount = countOccurrences(of: token, in: exporterBaseline) - let finalCount = countOccurrences(of: token, in: exporterText) - if baselineCount != finalCount { - return BenchmarkVerifyOutput.failure(reason: "nearMissChanged", metrics: [ - "token": .string(token), - "baseline": .integer(baselineCount), - "final": .integer(finalCount) - ]) - } - } - } - return BenchmarkVerifyOutput.success() - } - - private func stripTSComments(_ text: String) -> String { - var result = text - if let block = try? NSRegularExpression(pattern: "/\\*[\\s\\S]*?\\*/", options: []) { - let range = NSRange(location: 0, length: result.utf16.count) - result = block.stringByReplacingMatches(in: result, options: [], range: range, withTemplate: "") - } - if let line = try? NSRegularExpression(pattern: "//.*", options: []) { - let range = NSRange(location: 0, length: result.utf16.count) - result = line.stringByReplacingMatches(in: result, options: [], range: range, withTemplate: "") - } - return result - } - - private func countOccurrences(of needle: String, in haystack: String) -> Int { - guard !needle.isEmpty else { return 0 } - return haystack.components(separatedBy: needle).count - 1 - } - - private func makeScoredOutput(score: Double, reason: String, metrics: [String: BenchmarkJSONValue]) -> BenchmarkVerifyOutput { - let clamped = clampScore(score) - let pass = clamped >= policy.passThreshold - return BenchmarkVerifyOutput(pass: pass, score: clamped, reason: reason, metrics: metrics) - } - - private func aggregate(_ parts: [(String, BenchmarkVerifyOutput)], label: String) -> BenchmarkVerifyOutput { - guard !parts.isEmpty else { return BenchmarkVerifyOutput.failure(reason: "\(label):empty") } - let totalScore = parts.reduce(0.0) { $0 + $1.1.score } - let average = totalScore / Double(parts.count) - let clamped = clampScore(average) - let allPass = parts.allSatisfy(\.1.pass) - let failingReasons = parts.compactMap { $0.1.pass ? nil : "\($0.0):\($0.1.reason)" } - let reason = failingReasons.joined(separator: ",") - let metrics: [String: BenchmarkJSONValue] = [ - "components": .array(parts.map { component in - .object([ - "path": .string(component.0), - "score": .double(component.1.score), - "pass": .boolean(component.1.pass), - "reason": .string(component.1.reason) - ]) - }), - "averageScore": .double(average) - ] - let pass = allPass && clamped >= policy.passThreshold - return BenchmarkVerifyOutput(pass: pass, score: clamped, reason: reason, metrics: metrics) - } - - private func withFriendlyReason(_ output: BenchmarkVerifyOutput) -> BenchmarkVerifyOutput { - let pretty = BenchmarkVerifier.humanReadableReason(output.reason) - if pretty == output.reason { - return output - } - return BenchmarkVerifyOutput(pass: output.pass, score: output.score, reason: pretty, metrics: output.metrics) - } - - static func humanReadableReason(_ reason: String) -> String { - if reason.isEmpty { return "" } - let mapping: [String: String] = [ - "missingTargetFile": "Edited output did not include the required file.", - "nearMissChanged": "Similar identifiers (such as call_x or CALL_XY) were changed—only remove CALL_X.", - "missingParams": "Benchmark verifier was missing required parameters (configuration issue).", - "functionNotFoundBaseline": "Baseline configuration is missing the referenced function (internal issue).", - "functionNotFoundFinal": "The expected function was missing after your edits.", - "footerMissing": "The `// END-OF-FILE` marker was removed or renamed.", - "snippetMissing": "Required snippet was not found in the output.", - "invalidPatch": "Unified diff could not be applied; please apply the provided patch exactly.", - "anchorsMissing": "Anchor comments were missing—insert code between the provided anchors only.", - "tabFound": "Inserted snippet contained a tab; use spaces for indentation.", - "wrongIndentationStyle": "Inserted snippet uses the wrong indentation style for this language.", - "blockAnchorsMissing": "Block markers were missing, so the snippet location could not be verified.", - "collateralDamage": "Lines outside the targeted block changed unexpectedly.", - "blockMismatch": "Block body does not match the required snippet.", - "swapRegionMissing": "Swap region markers were missing after the edit.", - "outsideChanged": "Code outside the swap region was modified.", - "missingTarget": "Required target file was not present in the submission.", - "othersChanged": "Files outside the allowed list were modified.", - "missingRenameParams": "Rename parameters were missing (configuration issue).", - "missingExporter": "Exporter file was missing in the output.", - "exporterMismatch": "Exporter still references the old symbol name.", - "importerMismatch": "One or more importers still reference the old symbol name.", - "diffMismatch": "File contents did not match the expected diff result.", - "braceUnbalanced": "Braces remain unbalanced after your changes.", - "printCallInsideLoop": "Required print/log call still sits inside the loop.", - "printCallCountMismatch": "Required print/log call was removed or duplicated.", - "notAfterTarget": "The moved function was not placed after the requested target function.", - "collateralChange": "Lines outside the moved function were modified.", - "functionChanged": "The function body content changed unexpectedly.", - "wrongPlacement": "Snippet was not inserted directly above the footer marker.", - "insertGuardBundle:empty": "Verifier did not receive any guard files to inspect (configuration issue).", - "patchBlockBundle:empty": "Verifier did not receive any block files to inspect (configuration issue).", - "swapArgsBundle:empty": "Verifier did not receive any swap-region files to inspect (configuration issue).", - "curlyFixBundle:empty": "Verifier did not receive any curly-fix files to inspect (configuration issue).", - "insertFunctionBottomBundle:empty": "Verifier did not receive any bottom-insert files to inspect (configuration issue).", - "TOO_MANY_EDITS": "Exceeded the allowed number of edits for this task.", - "SEARCH_BLOCK_NOT_FOUND": "The search block could not be found in the file.", - "EDIT_APPLY_FAILED": "Edit could not be applied to the file.", - "UNEXPECTED_FILE_EDIT": "Modified a file that was not in the allowed list.", - "PARSE_OUTPUT_FAILED": "Could not parse the model's output.", - "MODEL_EXECUTION_FAILED": "Model execution failed.", - "MODEL_OUTPUT_TOO_LARGE": "Model output exceeded the size limit.", - "SEARCH_BLOCK_TOO_LARGE": "Search block exceeded maximum line limit - attempted to echo entire file." - ] - let parts = reason.split(separator: ",").map { String($0).trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty } - var friendly: [String] = [] - var messageToFiles: [String: [String]] = [:] - - for rawPart in parts { - if let mapped = mapping[rawPart] { - friendly.append(mapped) - continue - } - if let colonIndex = rawPart.firstIndex(of: ":") { - let prefix = String(rawPart[.. 1 { - // Likely a filename with extension but no path - messageToFiles[message, default: []].append(prefix) - } else if let prefixMapped = mapping[prefix] { - friendly.append(prefixMapped) - } else { - // It's a file key like "alpha", "bravo" - include it - messageToFiles[message, default: []].append(prefix) - } - } else { - friendly.append(mapping[rawPart] ?? fallbackReasonMessage(for: rawPart)) - } - } - - // Consolidate messages with file identifiers - for (message, files) in messageToFiles.sorted(by: { $0.key < $1.key }) { - if files.count == 1 { - friendly.append("\(files[0]): \(message)") - } else if files.count > 1 { - let fileList = files.sorted().joined(separator: ", ") - friendly.append("\(fileList): \(message)") - } else { - friendly.append(message) - } - } - - return friendly.joined(separator: "; ") - } - - private static func fallbackReasonMessage(for code: String) -> String { - let trimmed = code.trimmingCharacters(in: .whitespacesAndNewlines) - if trimmed.isEmpty { return "" } - var result = "" - for character in trimmed { - if character == "_" || character == "-" { - result.append(" ") - continue - } - if character.isUppercase, let last = result.last, last != " " { - result.append(" ") - } - result.append(character) - } - let cleaned = result.trimmingCharacters(in: .whitespacesAndNewlines) - return cleaned.isEmpty ? trimmed : cleaned.capitalized - } - - private func containsGuardSnippet(in segment: String, language: BenchmarkLanguage, expected: String) -> Bool { - // Try exact match first (with indentation already handled by caller) - if segment.contains(expected) { - return true - } - - // Normalize whitespace for comparison - let normalizedSegment = segment - .replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression) - .replacingOccurrences(of: " ;", with: ";") - let normalizedExpected = expected - .replacingOccurrences(of: "\\s+", with: " ", options: .regularExpression) - .replacingOccurrences(of: " ;", with: ";") - - if normalizedSegment.contains(normalizedExpected) { - return true - } - - // Try regex-based structure matching as last resort - let pattern = switch language { - case .ts: - #"if\s*\(\s*n\s*<\s*0\s*\)\s*\{\s*return\s+0\s*;?\s*\}"# - case .go: - #"if\s+n\s*<\s*0\s*\{\s*return\s+0\s*\}"# - case .swift: - #"if\s+n\s*<\s*0\s*\{\s*return\s+0\s*\}"# - } - - return segment.range(of: pattern, options: .regularExpression) != nil - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/UnifiedPatchApplier.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/UnifiedPatchApplier.swift deleted file mode 100644 index f9b6f120b..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Core/UnifiedPatchApplier.swift +++ /dev/null @@ -1,382 +0,0 @@ -import Foundation - -enum SimpleUnifiedPatchApplier { - enum Op { - case context(String) - case minus(String) - case plus(String) - } - - struct Hunk { - let oldStart: Int - let oldCount: Int - let newStart: Int - let newCount: Int - let ops: [Op] - } - - // Debug toggle and helper - private static let debug: Bool = false - private static func log(_ message: String) { - if debug { - print("[PatchApplier] \(message)") - } - } - - /// Apply a (possibly multi-hunk) unified diff. - /// Supports '+', '-', and optional ' ' (context) lines. - /// Tracks line count deltas across hunks and clamps insertions to EOF. - static func apply(patch: String, to text: String) -> String? { - var lines = text.components(separatedBy: "\n") - let hadTrailingNewline = text.hasSuffix("\n") - guard var hunks = parse(patch) else { - log("parse failed: no hunks") - return nil - } - - // Enforce deterministic order: sort by oldStart (stable), tie-break on newStart - hunks.sort { lhs, rhs in - if lhs.oldStart == rhs.oldStart { return lhs.newStart < rhs.newStart } - return lhs.oldStart < rhs.oldStart - } - if debug { - let headers = hunks.enumerated().map { i, h in - "#\(i){-\(h.oldStart),\(h.oldCount) +\(h.newStart),\(h.newCount) ops:\(h.ops.count)}" - }.joined(separator: ", ") - log("sorted hunks: [\(headers)]") - } - - var delta = 0 - for (idx, hunk) in hunks.enumerated() { - log("apply hunk[\(idx)] header: -\(hunk.oldStart),\(hunk.oldCount) +\(hunk.newStart),\(hunk.newCount) (delta=\(delta))") - guard apply(hunk: hunk, to: &lines, delta: &delta) else { - log("FAILED applying hunk[\(idx)]") - return nil - } - log("applied hunk[\(idx)] ok; new delta=\(delta)") - } - var output = lines.joined(separator: "\n") - if hadTrailingNewline { output += "\n" } - return output - } - - // MARK: - Parser - - static func parseHunks(_ patch: String) -> [Hunk]? { - parse(patch) - } - - private static func parse(_ patch: String) -> [Hunk]? { - let allLines = patch.components(separatedBy: "\n") - var index = 0 - var hunks: [Hunk] = [] - while index < allLines.count { - let line = allLines[index] - if line.hasPrefix("@@") { - guard let header = parseHeader(line) else { - log("parse header failed at line \(index): \(line)") - return nil - } - if debug { - log("found hunk header @@ -\(header.oldStart),\(header.oldCount) +\(header.newStart),\(header.newCount) @@") - } - index += 1 - var ops: [Op] = [] - while index < allLines.count { - let payload = allLines[index] - if payload.hasPrefix("@@") { break } - if payload.hasPrefix("---") || payload.hasPrefix("+++") { - index += 1 - continue - } - if payload.hasPrefix("+") { - ops.append(.plus(String(payload.dropFirst()))) - } else if payload.hasPrefix("-") { - ops.append(.minus(String(payload.dropFirst()))) - } else if payload.hasPrefix(" ") { - ops.append(.context(String(payload.dropFirst()))) - } else if payload.isEmpty { - // Treat empty line as context ONLY if it's not the trailing line of this hunk. - // If the next line starts a new hunk/header or we're at EOF, skip it to avoid spurious context at EOF. - let nextLine: String? = (index + 1 < allLines.count) ? allLines[index + 1] : nil - let nextStartsHeader = nextLine?.hasPrefix("@@") == true || nextLine?.hasPrefix("---") == true || nextLine?.hasPrefix("+++") == true - if let _ = nextLine, !nextStartsHeader { - ops.append(.context("")) - } else { - // skip trailing empty context at end of hunk/patch - } - } - index += 1 - } - if debug { log("collected ops: \(ops.count)") } - hunks.append(Hunk(oldStart: header.oldStart, oldCount: header.oldCount, newStart: header.newStart, newCount: header.newCount, ops: ops)) - } else { - index += 1 - } - } - if debug { log("parse finished: hunks=\(hunks.count)") } - return hunks.isEmpty ? nil : hunks - } - - private static func parseHeader(_ line: String) -> (oldStart: Int, oldCount: Int, newStart: Int, newCount: Int)? { - guard line.hasPrefix("@@") else { return nil } - let cleaned = line.replacingOccurrences(of: "@@", with: "") - let parts = cleaned.trimmingCharacters(in: .whitespaces).split(separator: " ") - guard parts.count >= 2 else { return nil } - func parseSpan(_ span: Substring) -> (Int, Int)? { - let raw = span.dropFirst() - let components = raw.split(separator: ",") - guard components.count == 2, let a = Int(components[0]), let b = Int(components[1]) else { return nil } - return (a, b) - } - guard - let (oldStart, oldCount) = parseSpan(parts[0]), - let (newStart, newCount) = parseSpan(parts[1]) - else { return nil } - return (oldStart, oldCount, newStart, newCount) - } - - private static func apply(hunk: Hunk, to lines: inout [String], delta: inout Int) -> Bool { - // First attempt: index based on oldStart plus accumulated delta - let expectedIndex = clamp((hunk.oldStart - 1) + delta, 0 ... (lines.count)) - log(" attempt#1 at expectedIndex=\(expectedIndex)") - if let res = applyOpsTransactional(ops: hunk.ops, lines: lines, index: expectedIndex, delta: delta), res.success { - lines = res.lines - delta = res.delta - log(" attempt#1 succeeded") - return true - } - log(" attempt#1 failed") - - // Second attempt: re-anchor using leading context lines near expected index - let leadCtx = leadingContext(hunk.ops, maxCount: 3) - if !leadCtx.isEmpty { - log(" attempt#2 (leading-context) with context=\(leadCtx)") - if let anchor = findAnchorIndex(lines: lines, around: expectedIndex, context: leadCtx, window: 12) { - log(" context anchor found at \(anchor)") - if let res = applyOpsTransactional(ops: hunk.ops, lines: lines, index: anchor, delta: delta), res.success { - lines = res.lines - delta = res.delta - log(" attempt#2 succeeded at anchor=\(anchor)") - return true - } else { - log(" attempt#2 failed at anchor=\(anchor)") - } - } else { - log(" context anchor not found near expectedIndex") - } - } else { - log(" attempt#2 skipped (no leading context)") - } - - // Third attempt: anchor using the first deletion line (more specific than blank context) - if let minusVal = firstMinus(hunk.ops) { - log(" attempt#3 (minus-anchor) for value='\(minusVal)'") - if let minusIdx = findLineIndex(lines: lines, value: minusVal, around: expectedIndex, window: 48) { - let startIdx = clamp(minusIdx - leadCtx.count, 0 ... lines.count) - log(" minus anchor found at \(minusIdx); trying startIdx=\(startIdx)") - if let res = applyOpsTransactional(ops: hunk.ops, lines: lines, index: startIdx, delta: delta), res.success { - lines = res.lines - delta = res.delta - log(" attempt#3 succeeded at startIdx=\(startIdx)") - return true - } else { - log(" attempt#3 failed at startIdx=\(startIdx)") - } - } else { - log(" minus anchor not found near expectedIndex") - } - } else { - log(" attempt#3 skipped (no minus op)") - } - - // Final attempt: use newStart as an alternate anchor if provided - if hunk.newStart > 0 { - let alt = clamp(hunk.newStart - 1, 0 ... (lines.count)) - log(" attempt#4 (newStart fallback) at altIndex=\(alt)") - if let res = applyOpsTransactional(ops: hunk.ops, lines: lines, index: alt, delta: delta), res.success { - lines = res.lines - delta = res.delta - log(" attempt#4 succeeded") - return true - } - log(" attempt#4 failed") - } else { - log(" attempt#4 skipped (no newStart)") - } - return false - } - - private static func applyOpsTransactional(ops: [Op], lines: [String], index: Int, delta: Int) -> (success: Bool, lines: [String], delta: Int)? { - var newLines = lines - var idx = index - var d = delta - - for (opIdx, op) in ops.enumerated() { - switch op { - case let .context(value): - guard idx < newLines.count, newLines[idx] == value else { - let actual = (idx < newLines.count) ? newLines[idx] : "" - log(" mismatch at op#\(opIdx) CONTEXT: expected='\(value)' actual='\(actual)' at idx=\(idx)") - return (false, lines, delta) - } - idx += 1 - case let .minus(value): - guard idx < newLines.count, newLines[idx] == value else { - let actual = (idx < newLines.count) ? newLines[idx] : "" - log(" mismatch at op#\(opIdx) MINUS: expected='\(value)' actual='\(actual)' at idx=\(idx)") - return (false, lines, delta) - } - newLines.remove(at: idx) - d -= 1 - case let .plus(value): - let clamped = clamp(idx, 0 ... newLines.count) - newLines.insert(value, at: clamped) - idx = clamped + 1 - d += 1 - } - } - return (true, newLines, d) - } - - private static func leadingContext(_ ops: [Op], maxCount: Int = 3) -> [String] { - var out: [String] = [] - for op in ops { - switch op { - case let .context(value): - out.append(value) - if out.count >= maxCount { return out } - default: - return out - } - } - return out - } - - private static func firstMinus(_ ops: [Op]) -> String? { - for op in ops { - if case let .minus(val) = op { - return val - } - } - return nil - } - - private static func findAnchorIndex(lines: [String], around expected: Int, context: [String], window: Int = 12) -> Int? { - guard !context.isEmpty else { return nil } - let lower = max(0, expected - window) - let upper = min(lines.count, expected + window) - let maxStart = max(0, upper - context.count) - var i = lower - while i <= maxStart { - var matched = true - for (j, ctx) in context.enumerated() { - if i + j >= lines.count || lines[i + j] != ctx { - matched = false - break - } - } - if matched { - return i - } - i += 1 - } - return nil - } - - private static func findLineIndex(lines: [String], value: String, around expected: Int, window: Int = 48) -> Int? { - let lower = max(0, expected - window) - let upper = min(lines.count - 1, expected + window) - if lower > upper { return nil } - // Prefer proximity: search outward from expected index - var left = expected - var right = expected + 1 - while left >= lower || right <= upper { - if left >= lower, left < lines.count, lines[left] == value { - return left - } - if right <= upper, right < lines.count, lines[right] == value { - return right - } - left -= 1 - right += 1 - } - return nil - } - - private static func clamp(_ value: Int, _ range: ClosedRange) -> Int { - if value < range.lowerBound { return range.lowerBound } - if value > range.upperBound { return range.upperBound } - return value - } -} - -enum UnifiedPatchGrader { - /// Compute how many hunks from a unified patch were successfully applied. - /// Returns (applied: Int, total: Int) for partial credit calculation. - static func coverage(baseline: String, final: String, patch: String) -> (applied: Int, total: Int) { - guard let hunks = SimpleUnifiedPatchApplier.parseHunks(patch) else { - return (0, 0) - } - - let baselineLines = baseline.components(separatedBy: "\n") - let finalLines = final.components(separatedBy: "\n") - - var appliedCount = 0 - for hunk in hunks { - if isHunkApplied(hunk: hunk, baseline: baselineLines, final: finalLines) { - appliedCount += 1 - } - } - - return (appliedCount, hunks.count) - } - - /// Check if a specific hunk appears to have been applied by verifying: - /// - Minus lines are no longer present (or present in reduced quantity) - /// - Plus lines are now present in the final text - private static func isHunkApplied(hunk: SimpleUnifiedPatchApplier.Hunk, baseline: [String], final: [String]) -> Bool { - var minusLines: [String] = [] - var plusLines: [String] = [] - - for op in hunk.ops { - switch op { - case let .minus(line): - minusLines.append(line) - case let .plus(line): - plusLines.append(line) - case .context: - break - } - } - - // If there are no changes in this hunk, consider it applied - if minusLines.isEmpty && plusLines.isEmpty { - return true - } - - // Check minus lines: they should be removed or reduced in final - var minusScore = 0.0 - for minusLine in minusLines { - let baselineCount = baseline.count(where: { $0 == minusLine }) - let finalCount = final.count(where: { $0 == minusLine }) - if finalCount < baselineCount { - minusScore += 1.0 - } - } - - // Check plus lines: they should appear in final - var plusScore = 0.0 - for plusLine in plusLines { - if final.contains(plusLine) { - plusScore += 1.0 - } - } - - // Hunk is considered applied if both conditions are met reasonably well - let minusOK = minusLines.isEmpty || (minusScore / Double(minusLines.count)) >= 0.5 - let plusOK = plusLines.isEmpty || (plusScore / Double(plusLines.count)) >= 0.5 - - return minusOK && plusOK - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/ViewModels/BenchmarkSettingsViewModel.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/ViewModels/BenchmarkSettingsViewModel.swift deleted file mode 100644 index 981c57d9c..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/ViewModels/BenchmarkSettingsViewModel.swift +++ /dev/null @@ -1,1087 +0,0 @@ -import Combine -import Foundation - -@MainActor -final class BenchmarkSettingsViewModel: ObservableObject { - enum Tab: String, CaseIterable, Identifiable { - case run - case history - case leaderboard - - var id: String { - rawValue - } - - var title: String { - switch self { - case .run: "Run" - case .history: "Run History" - case .leaderboard: "Local Rankings" - } - } - } - - struct ProgressStep: Identifiable { - let id = UUID() - var description: String - var detailText: String = "" - var additionalInfo: String? - var isComplete: Bool = false - var isActive: Bool = false - } - - struct ProgressSummary { - var totalSeeds: Int = 0 - var completedSeeds: Int = 0 - var totalTasks: Int = 0 - var completedTasks: Int = 0 - } - - private struct SeedProgress { - var totalTasks: Int - var completedTasks: Int - var taskTypes: [BenchmarkCaseType] - } - - struct DebugTaskRef: Identifiable { - let id: String // e.g., "\(seed)-\(taskIndex)-\(spec.id)" - let seed: UInt32 - let seedIndex: Int // 0-based index within the 5 sub-seeds - let taskIndex: Int // 0-based index within that seed - let spec: BenchmarkTaskSpec - } - - @Published var activeTab: Tab = .run - @Published var selectedModelRaw: String - @Published var coreSeedInput: String - @Published var debugLoggingEnabled: Bool = false - @Published var subSeedCount: Int = 5 - @Published private(set) var isRunning: Bool = false - @Published private(set) var progressSteps: [ProgressStep] = [] - @Published private(set) var latestReport: BenchmarkFinalReport? - @Published private(set) var latestSummary: BenchmarkRunSummary? - @Published private(set) var runError: String? - @Published private(set) var history: [BenchmarkRunSummary] = [] - @Published private(set) var leaderboard: [BenchmarkLeaderboardEntry] = [] - @Published private(set) var availableModels: [AIModel] = [] - @Published private(set) var lastAudit: [BenchmarkAuditResult] = [] - @Published private(set) var progressSummary: ProgressSummary = .init() - @Published private(set) var latestLogURL: URL? - @Published private(set) var runningTestIDs: Set = [] - - private let promptViewModel: PromptViewModel - private let apiSettingsViewModel: APISettingsViewModel - private let runStore: BenchmarkRunStore - private var cancellables: Set = [] - private var runTask: Task? - private var seedStepIndices: [UInt32: Int] = [:] - private var seedProgress: [UInt32: SeedProgress] = [:] - - private static let selectedModelDefaultsKey = "BenchmarkSettingsView.selectedModel" - - private static let logTimestampFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateStyle = .medium - formatter.timeStyle = .medium - return formatter - }() - - private static let logFileNameFormatter: DateFormatter = { - let formatter = DateFormatter() - formatter.dateFormat = "yyyyMMdd-HHmmss" - return formatter - }() - - init( - promptViewModel: PromptViewModel, - apiSettingsViewModel: APISettingsViewModel, - runStore: BenchmarkRunStore = .shared - ) { - self.promptViewModel = promptViewModel - self.apiSettingsViewModel = apiSettingsViewModel - self.runStore = runStore - selectedModelRaw = promptViewModel.preferredModel - coreSeedInput = String(BenchmarkSeedUtilities.canonicalCoreSeed) - availableModels = apiSettingsViewModel.availableModels - if let persistedModel = UserDefaults.standard.string(forKey: Self.selectedModelDefaultsKey) { - selectedModelRaw = persistedModel - } - ensureValidModelSelection() - - apiSettingsViewModel.$availableModels - .receive(on: DispatchQueue.main) - .sink { [weak self] models in - self?.availableModels = models - self?.ensureValidModelSelection() - } - .store(in: &cancellables) - - $selectedModelRaw - .removeDuplicates() - .sink { newValue in - UserDefaults.standard.set(newValue, forKey: Self.selectedModelDefaultsKey) - } - .store(in: &cancellables) - - Task { - await loadHistory() - } - } - - deinit { - runTask?.cancel() - } - - func generateRandomSeed() { - coreSeedInput = String(Self.makeRandomCoreSeed()) - } - - func validateCurrentSeedForDebug() async { - let seed = UInt32(coreSeedInput) ?? 42 - let config = BenchConfig(tasksAreCumulative: true) - let generator = BenchmarkTaskGenerator() - let generated = generator.generateSeed(seed, config: config) - let auditor = BenchmarkAuditor() - lastAudit = await auditor.auditSeed(generated) - } - - func runBenchmark() { - guard !isRunning else { return } - runError = nil - let model = AIModel.fromModelName(selectedModelRaw) ?? promptViewModel.preferredAIModel - guard let aiService = promptViewModel.aiQueriesService else { - runError = "AI service unavailable. Configure an API key first." - return - } - guard let coreSeed = UInt32(coreSeedInput) else { - runError = "Core seed must be a positive integer." - return - } - progressSummary = ProgressSummary(totalSeeds: max(1, subSeedCount), completedSeeds: 0, totalTasks: 0, completedTasks: 0) - seedProgress.removeAll() - latestLogURL = nil - isRunning = true - progressSteps = [] - latestReport = nil - latestSummary = nil - - let benchConfig = BenchConfig(tasksAreCumulative: true) - let generator = BenchmarkTaskGenerator() - let executor = BenchmarkTaskExecutor( - aiQueriesService: aiService, - model: model, - maxContextChars: benchConfig.contextCharBudget, - maxDecoyPerFileChars: benchConfig.decoyCharCap - ) - let engine = BenchmarkEngine(generator: generator, executor: executor, config: benchConfig) - - runTask = Task { [weak self] in - await self?.performRun( - engine: engine, - model: model, - coreSeed: coreSeed - ) - } - } - - func stopRun() { - runTask?.cancel() - promptViewModel.aiQueriesService?.cancelQuery() - isRunning = false - seedStepIndices.removeAll() - seedProgress.removeAll() - progressSteps.removeAll() - progressSummary = ProgressSummary() - latestReport = nil - latestSummary = nil - latestLogURL = nil - runError = "Benchmark run cancelled." - runTask = nil - } - - func clearHistory() { - runTask?.cancel() - Task { - await runStore.clear() - history = [] - rebuildLeaderboard() - } - } - - func deleteRun(at offsets: IndexSet) { - Task { - var updatedHistory = history - updatedHistory.remove(atOffsets: offsets) - await runStore.saveRuns(updatedHistory) - history = updatedHistory - rebuildLeaderboard() - } - } - - private func ensureValidModelSelection() { - let current = selectedModelRaw - guard availableModels.contains(where: { $0.rawValue == current }) else { - if let fallback = availableModels.first { - selectedModelRaw = fallback.rawValue - } else { - selectedModelRaw = promptViewModel.preferredModel - } - return - } - } - - private func performRun(engine: BenchmarkEngine, model: AIModel, coreSeed: UInt32) async { - let subSeeds = max(1, subSeedCount) - progressSteps.append(ProgressStep(description: "Preparing benchmark config...", isComplete: true)) - seedStepIndices.removeAll() - - let executions = await engine.run( - coreSeed: coreSeed, - subSeedCount: subSeeds, - progress: { [weak self] event in - self?.handleProgressEvent(event) - } - ) - - if Task.isCancelled { - runTask = nil - return - } - - progressSteps.append(ProgressStep(description: "Compiling report...", isComplete: false)) - - let reporter = BenchmarkReporter( - verifier: BenchmarkVerifier( - policy: GradingPolicy( - passThreshold: 0.92, - lenient: false - ) - ) - ) - let report = reporter.buildReport(coreSeed: coreSeed, executions: executions) - let summary = BenchmarkRunSummary.make(report: report, model: model, temperature: nil) - let runs = await runStore.append(summary) - - progressSummary.totalSeeds = max(progressSummary.totalSeeds, executions.count) - progressSummary.completedSeeds = progressSummary.totalSeeds - let totalTasks = executions.reduce(0) { $0 + $1.executions.count } - progressSummary.totalTasks = max(progressSummary.totalTasks, totalTasks) - progressSummary.completedTasks = progressSummary.totalTasks - - latestReport = report - latestSummary = summary - runError = nil - if let lastIndex = progressSteps.indices.last { - progressSteps[lastIndex].isComplete = true - progressSteps[lastIndex].detailText = "Pass rate: \(Int(summary.passRate * 100))%" - } - - if debugLoggingEnabled { - await writeDebugLog( - coreSeed: coreSeed, - model: model, - report: report, - executions: executions, - steps: progressSteps, - progress: progressSummary - ) - } - - isRunning = false - history = runs - rebuildLeaderboard() - runTask = nil - } - - private func handleProgressEvent(_ event: BenchmarkProgressEvent) { - switch event { - case let .started(totalSeeds): - seedStepIndices.removeAll() - seedProgress.removeAll() - progressSummary.totalSeeds = totalSeeds - progressSummary.completedSeeds = 0 - progressSummary.totalTasks = 0 - progressSummary.completedTasks = 0 - // Pre-allocate progress steps array with placeholders - progressSteps = (0 ..< totalSeeds).map { index in - ProgressStep( - description: "Task Group \(index + 1) of \(totalSeeds)", - detailText: "Waiting...", - additionalInfo: nil, - isComplete: false, - isActive: false - ) - } - case let .seedStarted(index, seed, totalSeeds, taskCount, taskTypes): - let description = "Task Group \(index + 1) of \(totalSeeds)" - // Update the step at the correct index - if index < progressSteps.count { - progressSteps[index].description = description - progressSteps[index].detailText = "0/\(max(1, taskCount)) tasks" - progressSteps[index].isActive = true - progressSteps[index].isComplete = false - } - seedStepIndices[seed] = index - seedProgress[seed] = SeedProgress(totalTasks: max(0, taskCount), completedTasks: 0, taskTypes: taskTypes) - updateCurrentTaskInfo(for: seed) - recalculateTaskProgress() - progressSummary.totalSeeds = totalSeeds - case let .taskCompleted(seed, completed, total): - if var progress = seedProgress[seed] { - progress.totalTasks = max(progress.totalTasks, total) - progress.completedTasks = min(completed, progress.totalTasks) - seedProgress[seed] = progress - recalculateTaskProgress() - updateCurrentTaskInfo(for: seed) - } - guard let stepIndex = seedStepIndices[seed], stepIndex < progressSteps.count else { return } - progressSteps[stepIndex].detailText = "\(completed)/\(max(1, total)) tasks" - progressSteps[stepIndex].isActive = true - if completed >= total { - progressSteps[stepIndex].isComplete = true - progressSteps[stepIndex].isActive = false - progressSteps[stepIndex].additionalInfo = nil - } - case let .seedCompleted(_, seed): - if var progress = seedProgress[seed] { - progress.completedTasks = progress.totalTasks - seedProgress[seed] = progress - recalculateTaskProgress() - updateCurrentTaskInfo(for: seed) - } - if let stepIndex = seedStepIndices[seed], stepIndex < progressSteps.count { - progressSteps[stepIndex].isComplete = true - progressSteps[stepIndex].isActive = false - progressSteps[stepIndex].detailText = "Completed" - progressSteps[stepIndex].additionalInfo = nil - } - progressSummary.completedSeeds = min(progressSummary.totalSeeds, progressSummary.completedSeeds + 1) - case .finished: - recalculateTaskProgress() - progressSummary.completedSeeds = progressSummary.totalSeeds - progressSummary.completedTasks = progressSummary.totalTasks - progressSteps.append(ProgressStep(description: "All task groups complete", detailText: "", isComplete: true)) - case .cancelled: - isRunning = false - runTask = nil - seedStepIndices.removeAll() - seedProgress.removeAll() - progressSteps.removeAll() - progressSummary = ProgressSummary() - latestReport = nil - latestSummary = nil - latestLogURL = nil - runError = "Benchmark run cancelled." - } - } - - private func loadHistory() async { - let stored = await runStore.loadRuns() - history = stored - rebuildLeaderboard() - } - - private func rebuildLeaderboard() { - var aggregates: [String: (runs: Int, totalTasks: Int, passed: Int, totalScore: Double, totalPointsEarned: Double, totalMaxPoints: Double, lastRun: Date, display: String)] = [:] - // Only include eligible benchmarks (no API errors) in leaderboard - for summary in eligibleBenchmarks { - let key = summary.modelRawValue - let entry = aggregates[key] ?? (0, 0, 0, 0, 0, 0, .distantPast, summary.modelDisplayShort) - let newRuns = entry.runs + 1 - let newTotalTasks = entry.totalTasks + summary.totalTasks - let newPassed = entry.passed + summary.passedTasks - let weightedScore = entry.totalScore + (summary.averageScore * Double(summary.totalTasks)) - let newPointsEarned = entry.totalPointsEarned + summary.totalPointsEarned - let newMaxPoints = entry.totalMaxPoints + summary.totalMaxPoints - let lastRun = max(entry.lastRun, summary.timestamp) - aggregates[key] = (newRuns, newTotalTasks, newPassed, weightedScore, newPointsEarned, newMaxPoints, lastRun, summary.modelDisplayShort) - } - let entries = aggregates.map { key, value -> BenchmarkLeaderboardEntry in - let averageScore = value.totalTasks == 0 ? 0 : value.totalScore / Double(value.totalTasks) - let passRate = value.totalTasks == 0 ? 0 : Double(value.passed) / Double(value.totalTasks) - let pointsRate = value.totalMaxPoints > 0 ? value.totalPointsEarned / value.totalMaxPoints : 0.0 - return BenchmarkLeaderboardEntry( - modelRawValue: key, - modelDisplayName: value.display, - runs: value.runs, - totalTasks: value.totalTasks, - passedTasks: value.passed, - averageScore: averageScore, - passRate: passRate, - pointsRate: pointsRate, - lastRun: value.lastRun - ) - } - .sorted { lhs, rhs in - if lhs.pointsRate == rhs.pointsRate { - if lhs.passRate == rhs.passRate { - return lhs.totalTasks > rhs.totalTasks - } - return lhs.passRate > rhs.passRate - } - return lhs.pointsRate > rhs.pointsRate - } - - leaderboard = entries - } - - var eligibleBenchmarks: [BenchmarkRunSummary] { - // Filter out runs that had API errors (hadErrors == true) - // nil is treated as eligible for backward compatibility with old runs - history.filter { run in - if let hadErrors = run.hadErrors, hadErrors { - return false - } - return true - } - } - - private func recalculateTaskProgress() { - var totalTasks = 0 - var completedTasks = 0 - for progress in seedProgress.values { - let taskTotal = max(0, progress.totalTasks) - totalTasks += taskTotal - completedTasks += min(progress.completedTasks, taskTotal) - } - progressSummary.totalTasks = totalTasks - progressSummary.completedTasks = min(completedTasks, totalTasks) - } - - private func updateCurrentTaskInfo(for seed: UInt32) { - guard let stepIndex = seedStepIndices[seed], stepIndex < progressSteps.count else { return } - guard let progress = seedProgress[seed] else { return } - if let name = currentTaskName(for: progress) { - progressSteps[stepIndex].additionalInfo = name - } else { - progressSteps[stepIndex].additionalInfo = nil - } - } - - private func currentTaskName(for progress: SeedProgress) -> String? { - guard progress.totalTasks > 0 else { return nil } - guard progress.completedTasks < progress.totalTasks else { return nil } - guard let type = progress.taskTypes[safe: progress.completedTasks] else { return nil } - return friendlyName(for: type) - } - - private func writeDebugLog( - coreSeed: UInt32, - model: AIModel, - report: BenchmarkFinalReport, - executions: [BenchmarkSeedExecution], - steps: [ProgressStep], - progress: ProgressSummary - ) async { - guard let downloadsURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first else { - if runError == nil { - runError = "Failed to locate Downloads folder for debug log." - } - return - } - let timestamp = Self.logFileNameFormatter.string(from: Date()) - let fileName = "RepoPrompt-Benchmark-\(timestamp)-seed-\(coreSeed).md" - let fileURL = downloadsURL.appendingPathComponent(fileName) - let content = makeDebugLogContent( - coreSeed: coreSeed, - model: model, - report: report, - executions: executions, - steps: steps, - progress: progress - ) - do { - try content.write(to: fileURL, atomically: true, encoding: .utf8) - latestLogURL = fileURL - } catch { - if runError == nil { - runError = "Failed to write debug log: \(error.localizedDescription)" - } - } - } - - private func makeDebugLogContent( - coreSeed: UInt32, - model: AIModel, - report: BenchmarkFinalReport, - executions: [BenchmarkSeedExecution], - steps: [ProgressStep], - progress: ProgressSummary - ) -> String { - let now = Date() - var lines: [String] = [] - lines.append("# RepoPrompt Benchmark Run") - lines.append("") - lines.append("- Timestamp: \(Self.logTimestampFormatter.string(from: now))") - lines.append("- Model: \(model.displayName) (\(model.rawValue))") - let temperatureLine = "- Temperature: model default (app overrides ignored)" - lines.append(temperatureLine) - lines.append("- Core Seed: \(coreSeed)") - if !report.subSeeds.isEmpty { - let seedsList = report.subSeeds.map(String.init).joined(separator: ", ") - lines.append("- Sub Seeds: \(seedsList)") - } - lines.append("- Total Tasks: \(report.totalTasks)") - lines.append(String(format: "- Pass Rate: %.2f%%", report.passRate * 100)) - lines.append(String(format: "- Average Score: %.2f", report.averageScore)) - lines.append("- Seeds Progress: \(progress.completedSeeds)/\(max(progress.totalSeeds, 1))") - lines.append("- Tasks Progress: \(progress.completedTasks)/\(max(progress.totalTasks, 1))") - lines.append("- Debug Logging: Enabled") - lines.append("") - - let sortedTypeStats = report.perType.sorted { $0.key.rawValue < $1.key.rawValue } - if !sortedTypeStats.isEmpty { - lines.append("## Case Type Summary") - for (type, stats) in sortedTypeStats { - lines.append(String( - format: "- %@ • count %d • pass %.2f%% • avg %.2f", - type.rawValue, - stats.count, - stats.passRate * 100, - stats.averageScore - )) - } - lines.append("") - } - - if !steps.isEmpty { - lines.append("## Progress Timeline") - for step in steps { - let status = step.isComplete ? "✅" : "⏳" - let detail = step.detailText.isEmpty ? "" : " — \(step.detailText)" - lines.append("- \(status) \(step.description)\(detail)") - } - lines.append("") - } - - let seedReports = Dictionary(uniqueKeysWithValues: report.perSeed.map { ($0.seed, $0) }) - lines.append("## Seeds") - for seedExecution in executions { - let seed = seedExecution.seed - let seedReport = seedReports[seed] - lines.append("### Seed \(seed)") - if let seedReport { - lines.append(String(format: "- Pass Rate: %.2f%%", seedReport.passRate * 100)) - lines.append(String(format: "- Average Score: %.2f", seedReport.averageScore)) - lines.append("- Tasks: \(seedReport.tasks.count)") - } else { - lines.append("- Tasks: \(seedExecution.executions.count)") - } - lines.append("") - for (index, execution) in seedExecution.executions.enumerated() { - let spec = execution.task - let header = "#### Task \(index + 1): \(spec.id) [\(spec.type.rawValue)]" - lines.append(header) - if let taskReport = seedReport?.tasks[safe: index] { - lines.append(String(format: "- Result: %@", taskReport.pass ? "Pass" : "Fail")) - lines.append(String(format: "- Score: %.2f", taskReport.score)) - if !taskReport.reason.isEmpty { - let friendlyReason = BenchmarkVerifier.humanReadableReason(taskReport.reason) - lines.append("- Reason: \(friendlyReason)") - } - } - if !spec.task.isEmpty { - lines.append("- User Task: \(spec.task)") - } - if !spec.instructions.isEmpty { - lines.append("- Instructions:") - for instruction in spec.instructions { - lines.append(" - \(instruction)") - } - } - if !spec.acceptance.isEmpty { - lines.append("- Acceptance Criteria:") - for item in spec.acceptance { - lines.append(" - \(item)") - } - } - lines.append("- Selected Files: \(spec.selectFiles.joined(separator: ", "))") - lines.append("- Max Edits: \(spec.maxEdits)") - if !spec.params.isEmpty { - lines.append("- Params:") - for key in spec.params.keys.sorted() { - if let value = spec.params[key] { - lines.append(" - \(key): \(describeJSONValue(value))") - } - } - } - if !execution.result.errors.isEmpty { - lines.append("- Errors:") - for error in execution.result.errors { - var parts: [String] = [error.code] - if let path = error.path { - parts.append("path=\(path)") - } - if let detail = error.detail, !detail.isEmpty { - parts.append(detail) - } - lines.append(" - \(parts.joined(separator: " • "))") - } - } - if !execution.result.edited.isEmpty { - lines.append("- Edited Files:") - for edit in execution.result.edited { - lines.append(" - \(edit.path)") - } - } - if let meta = execution.result.meta, !meta.isEmpty { - let sortedMeta = meta.keys.sorted() - let filteredKeys = sortedMeta.filter { !["rawOutput", "systemPrompt", "userPrompt", "virtualFiles"].contains($0) } - if !filteredKeys.isEmpty { - lines.append("- Meta:") - for key in filteredKeys { - if let value = meta[key] { - lines.append(" - \(key): \(describeJSONValue(value))") - } - } - } - if let rawOutput = meta["rawOutput"]?.stringValue, !rawOutput.isEmpty { - lines.append("- Raw Output:") - lines.append("```xml") - lines.append(rawOutput) - lines.append("```") - } - } - lines.append("") - } - } - - return lines.joined(separator: "\n") - } - - private func describeJSONValue(_ value: BenchmarkJSONValue) -> String { - switch value { - case let .string(string): - return string - case let .integer(integer): - return String(integer) - case let .double(double): - return String(double) - case let .boolean(bool): - return bool ? "true" : "false" - case let .array(array): - let items = array.map { describeJSONValue($0) } - return "[" + items.joined(separator: ", ") + "]" - case let .object(object): - let entries = object.sorted { $0.key < $1.key } - let parts = entries.map { "\($0.key): \(describeJSONValue($0.value))" } - return "{" + parts.joined(separator: ", ") + "}" - case .null: - return "null" - } - } - - private static func makeRandomCoreSeed() -> UInt32 { - UInt32.random(in: 1 ... UInt32.max) - } - - private func friendlyName(for type: BenchmarkCaseType) -> String { - let tokens = type.rawValue.split(separator: "_") - let words = tokens.map { token -> String in - switch token.lowercased() { - case "ts": - return "TypeScript" - case "go": - return "Go" - default: - return token.capitalized - } - } - return words.joined(separator: " ") - } - - private func friendlyName(forRawType rawType: String) -> String { - let tokens = rawType.split(separator: "_") - let words = tokens.map { token -> String in - switch token.lowercased() { - case "ts": - return "TypeScript" - case "go": - return "Go" - default: - return token.capitalized - } - } - return words.joined(separator: " ") - } - - func exportHistoryToCSV() -> URL? { - guard !history.isEmpty else { return nil } - guard let downloadsURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first else { - return nil - } - - let timestamp = Self.logFileNameFormatter.string(from: Date()) - let fileName = "RepoPrompt-Benchmark-History-\(timestamp).csv" - let fileURL = downloadsURL.appendingPathComponent(fileName) - - var csvLines: [String] = [] - - // CSV Header - One row per run - csvLines.append("Run ID,Timestamp,Model Name,Model Raw Value,Provider,Temperature,Total Tasks,Passed Tasks,Failed Tasks,Pass Rate (%),Average Score,Points Earned,Max Points,Points Rate (%),Failed Task Types") - - // CSV Rows - One row per run - for run in history { - let runID = run.id.uuidString - let timestamp = ISO8601DateFormatter().string(from: run.timestamp) - let modelName = csvEscape(run.modelDisplayShort) - let modelRaw = csvEscape(run.modelRawValue) - let provider = csvEscape(run.providerName) - let temperature = if let model = AIModel.fromModelName(run.modelRawValue), - let resolvedTemp = model.resolveTemperature(explicitTemperature: run.temperature, includeOverrides: false) - { - String(format: "%.2f", resolvedTemp) - } else { - "API Default" - } - let totalTasks = String(run.totalTasks) - let passedTasks = String(run.passedTasks) - let failedTasks = String(run.totalTasks - run.passedTasks) - let passRate = String(format: "%.2f", run.passRate * 100) - let averageScore = String(format: "%.2f", run.averageScore) - let pointsEarned = String(format: "%.1f", run.totalPointsEarned) - let maxPoints = String(format: "%.0f", run.totalMaxPoints) - let pointsRate = String(format: "%.2f", run.pointsRate * 100) - - // Build failed task types summary - var failedByType: [String: Int] = [:] - for seedSummary in run.seedSummaries { - for task in seedSummary.tasks where !task.pass { - let taskType = friendlyName(forRawType: task.type) - failedByType[taskType, default: 0] += 1 - } - } - let failedSummary = failedByType.isEmpty ? "None" : failedByType.map { "\($0.key)(\($0.value))" }.sorted().joined(separator: "; ") - - csvLines.append([ - runID, - timestamp, - modelName, - modelRaw, - provider, - temperature, - totalTasks, - passedTasks, - failedTasks, - passRate, - averageScore, - pointsEarned, - maxPoints, - pointsRate, - csvEscape(failedSummary) - ].joined(separator: ",")) - } - - let csvContent = csvLines.joined(separator: "\n") - - do { - try csvContent.write(to: fileURL, atomically: true, encoding: .utf8) - return fileURL - } catch { - runError = "Failed to export CSV: \(error.localizedDescription)" - return nil - } - } - - private func csvEscape(_ value: String) -> String { - if value.contains(",") || value.contains("\"") || value.contains("\n") { - return "\"\(value.replacingOccurrences(of: "\"", with: "\"\""))\"" - } - return value - } - - // MARK: - Debug Single Test Run - - var debugAvailableTests: [(seed: UInt32, tasks: [DebugTaskRef])] { - let coreSeed = UInt32(coreSeedInput) ?? BenchmarkSeedUtilities.canonicalCoreSeed - let subSeeds = BenchmarkSeedUtilities.deriveSubSeeds(coreSeed: coreSeed, count: max(1, subSeedCount)) - let config = BenchConfig(tasksAreCumulative: true) - let generator = BenchmarkTaskGenerator() - - return subSeeds.enumerated().map { seedIndex, seed in - let generated = generator.generateSeed(seed, config: config) - let tasks = generated.tasks.enumerated().map { taskIndex, spec in - DebugTaskRef( - id: "\(seed)-\(taskIndex)-\(spec.id)", - seed: seed, - seedIndex: seedIndex, - taskIndex: taskIndex, - spec: spec - ) - } - return (seed: seed, tasks: tasks) - } - } - - func runSingleTestDebug(_ ref: DebugTaskRef) async { - // Track running state immediately - runningTestIDs.insert(ref.id) - runError = nil - - defer { - runningTestIDs.remove(ref.id) - } - - guard !isRunning else { - runError = "Cannot run single test while full benchmark is running." - return - } - - let model = AIModel.fromModelName(selectedModelRaw) ?? promptViewModel.preferredAIModel - guard let aiService = promptViewModel.aiQueriesService else { - runError = "AI service unavailable." - return - } - - // Generate the test fresh from the seed - let config = BenchConfig(tasksAreCumulative: false) - let generator = BenchmarkTaskGenerator() - let generated = generator.generateSeed(ref.seed, config: config) - - guard let taskSpec = generated.tasks[safe: ref.taskIndex] else { - runError = "Task not found in generated seed." - return - } - - // Use the initial baseline (no cumulative state for debug) - let baseline = generated.baseline - - // Create a fresh file system from the baseline - var fs = BenchmarkMockFileSystem(files: baseline.dictionary()) - - // Build executor - let executor = BenchmarkTaskExecutor( - aiQueriesService: aiService, - model: model, - maxContextChars: config.contextCharBudget, - maxDecoyPerFileChars: config.decoyCharCap - ) - - // Execute the task - let result = await executor.runTask(taskSpec, fileSystem: &fs, baseline: baseline) - - // Run verification - let verifier = BenchmarkVerifier(policy: GradingPolicy(passThreshold: 0.92, lenient: false)) - let execution = BenchmarkTaskExecution(task: taskSpec, baseline: baseline, result: result) - let verifyOutput = verifier.verify(execution) - - // Write debug log with verification results - let url = await writeSingleTestDebugLog( - model: model, - seed: ref.seed, - spec: taskSpec, - baseline: baseline, - result: result, - verification: verifyOutput - ) - - if let url { - latestLogURL = url - runError = nil - } else { - if runError == nil { - runError = "Failed to write debug log." - } - } - } - - private func writeSingleTestDebugLog( - model: AIModel, - seed: UInt32, - spec: BenchmarkTaskSpec, - baseline: BenchmarkMockFileSystemSnapshot, - result: BenchmarkTaskExecResult, - verification: BenchmarkVerifyOutput - ) async -> URL? { - guard let downloadsURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first else { - if runError == nil { - runError = "Failed to locate Downloads folder." - } - return nil - } - - let timestamp = Self.logFileNameFormatter.string(from: Date()) - let fileName = "RepoPrompt-Benchmark-Test-\(timestamp)-seed-\(seed)-\(spec.id).md" - let fileURL = downloadsURL.appendingPathComponent(fileName) - - let content = makeSingleTestDebugLogContent( - model: model, - seed: seed, - spec: spec, - baseline: baseline, - result: result, - verification: verification - ) - - do { - try content.write(to: fileURL, atomically: true, encoding: .utf8) - return fileURL - } catch { - if runError == nil { - runError = "Failed to write debug log: \(error.localizedDescription)" - } - return nil - } - } - - private func makeSingleTestDebugLogContent( - model: AIModel, - seed: UInt32, - spec: BenchmarkTaskSpec, - baseline: BenchmarkMockFileSystemSnapshot, - result: BenchmarkTaskExecResult, - verification: BenchmarkVerifyOutput - ) -> String { - var lines: [String] = [] - lines.append("# RepoPrompt Single-Test Debug") - lines.append("") - lines.append("- Timestamp: \(Self.logTimestampFormatter.string(from: Date()))") - lines.append("- Model: \(model.displayName) (\(model.rawValue))") - lines.append("- Temperature: API Default (app overrides ignored)") - lines.append("- Seed: \(seed)") - lines.append("- Test: \(spec.id) [\(spec.type.rawValue)]") - lines.append("") - - lines.append("## Result") - lines.append("- Status: \(verification.pass ? "✅ PASS" : "❌ FAIL")") - lines.append(String(format: "- Score: %.2f", verification.score)) - if !verification.reason.isEmpty { - let friendlyReason = BenchmarkVerifier.humanReadableReason(verification.reason) - lines.append("- Reason: \(friendlyReason)") - } - if !verification.metrics.isEmpty { - lines.append("- Metrics:") - for key in verification.metrics.keys.sorted() { - if let value = verification.metrics[key] { - lines.append(" - \(key): \(describeJSONValue(value))") - } - } - } - lines.append("") - - lines.append("## Task") - lines.append("- Select Files: \(spec.selectFiles.joined(separator: ", "))") - lines.append("- Max Edits: \(spec.maxEdits)") - if !spec.task.isEmpty { - lines.append("- User Task: \(spec.task)") - } - if !spec.instructions.isEmpty { - lines.append("- Instructions:") - for instruction in spec.instructions { - lines.append(" - \(instruction)") - } - } - if !spec.acceptance.isEmpty { - lines.append("- Acceptance:") - for item in spec.acceptance { - lines.append(" - \(item)") - } - } - if !spec.params.isEmpty { - lines.append("- Params:") - for key in spec.params.keys.sorted() { - if let value = spec.params[key] { - lines.append(" - \(key): \(describeJSONValue(value))") - } - } - } - lines.append("") - - // Prompt meta - if let meta = result.meta { - if let systemPrompt = meta["systemPrompt"]?.stringValue, !systemPrompt.isEmpty { - lines.append("## System Prompt") - lines.append("```") - lines.append(systemPrompt) - lines.append("```") - lines.append("") - } - - if let userPrompt = meta["userPrompt"]?.stringValue, !userPrompt.isEmpty { - lines.append("## User Prompt") - lines.append("```") - lines.append(userPrompt) - lines.append("```") - lines.append("") - } - - if case let .array(virtualFilesArray)? = meta["virtualFiles"] { - lines.append("## Virtual Files") - for vf in virtualFilesArray { - if case let .object(obj) = vf, - let path = obj["path"]?.stringValue, - let role = obj["role"]?.stringValue, - let fence = obj["fence"]?.stringValue, - let block = obj["block"]?.stringValue - { - let truncated = obj["truncated"]?.boolValue ?? false - lines.append("### \(path)") - lines.append("- Role: \(role)") - if truncated { - lines.append("- Truncated: yes") - } - lines.append(block) - lines.append("") - } - } - lines.append("") - } - - if let rawOutput = meta["rawOutput"]?.stringValue, !rawOutput.isEmpty { - lines.append("## Raw Output") - lines.append("```xml") - lines.append(rawOutput) - lines.append("```") - lines.append("") - } - - // Parse summary meta - let metaKeys = meta.keys.filter { !["systemPrompt", "userPrompt", "virtualFiles", "rawOutput"].contains($0) } - if !metaKeys.isEmpty { - lines.append("## Parse Meta") - for key in metaKeys.sorted() { - if let value = meta[key] { - lines.append("- \(key): \(describeJSONValue(value))") - } - } - lines.append("") - } - } - - // Edited files - if !result.edited.isEmpty { - lines.append("## Edited Files") - for edit in result.edited { - lines.append("### \(edit.path)") - lines.append("```") - lines.append(edit.content) - lines.append("```") - lines.append("") - } - } - - // Errors - if !result.errors.isEmpty { - lines.append("## Errors") - for error in result.errors { - var parts: [String] = [error.code] - if let path = error.path { - parts.append("path=\(path)") - } - if let detail = error.detail, !detail.isEmpty { - parts.append(detail) - } - lines.append("- \(parts.joined(separator: " • "))") - } - lines.append("") - } - - return lines.joined(separator: "\n") - } -} - -private extension Array { - subscript(safe index: Int) -> Element? { - indices.contains(index) ? self[index] : nil - } -} diff --git a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Views/BenchmarkSettingsView.swift b/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Views/BenchmarkSettingsView.swift deleted file mode 100644 index c7a618ae2..000000000 --- a/Sources/RepoPrompt/Features/Diagnostics/Benchmark/Views/BenchmarkSettingsView.swift +++ /dev/null @@ -1,952 +0,0 @@ -import SwiftUI -#if os(macOS) - import AppKit -#endif - -struct BenchmarkSettingsView: View { - @StateObject var viewModel: BenchmarkSettingsViewModel - @State private var showClearHistoryConfirmation = false - private let canonicalTaskTotal = 30 - - init(promptViewModel: PromptViewModel, apiSettingsViewModel: APISettingsViewModel) { - _viewModel = StateObject(wrappedValue: BenchmarkSettingsViewModel( - promptViewModel: promptViewModel, - apiSettingsViewModel: apiSettingsViewModel - )) - } - - var body: some View { - VStack(alignment: .leading, spacing: 18) { - header - tabSelector - Divider() - groupedContent - } - .padding(20) - } - - private var header: some View { - VStack(alignment: .leading, spacing: 6) { - Text("Repo Bench") - .font(.title2).bold() - Text("Run the benchmark against different models, track history, and compare local rankings.") - .font(.subheadline) - .foregroundColor(.secondary) - HStack(spacing: 6) { - Image(systemName: "exclamationmark.triangle.fill") - .foregroundColor(.orange) - .font(.caption) - Text("Benchmark runs make ~30 API calls and may incur significant costs.") - .font(.caption) - .foregroundColor(.orange) - } - .padding(.top, 4) - } - } - - private var tabSelector: some View { - HStack(spacing: 12) { - ForEach(BenchmarkSettingsViewModel.Tab.allCases) { tab in - let isSelected = viewModel.activeTab == tab - Button { - withAnimation(.easeInOut(duration: 0.15)) { - viewModel.activeTab = tab - } - } label: { - Text(tab.title) - .font(.headline) - .padding(.vertical, 6) - .padding(.horizontal, 14) - .background( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(isSelected ? Color.accentColor.opacity(0.18) : Color.clear) - ) - .overlay( - RoundedRectangle(cornerRadius: 8, style: .continuous) - .stroke(isSelected ? Color.accentColor.opacity(0.5) : Color.primary.opacity(0.08), lineWidth: 1) - ) - .foregroundColor(isSelected ? .accentColor : .primary) - } - .buttonStyle(.plain) - } - Spacer() - } - } - - private var groupedContent: some View { - Group { - switch viewModel.activeTab { - case .run: - runContent - case .history: - historyContent - case .leaderboard: - leaderboardContent - } - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - } - - private var runContent: some View { - ScrollView { - ViewThatFits(in: .horizontal) { - wideRunLayout - compactRunLayout - } - .padding(.vertical, 12) - .animation(.easeInOut(duration: 0.2), value: viewModel.isRunning) - } - } - - private var wideRunLayout: some View { - Group { - if viewModel.isRunning { - runStatusColumn(inlineStart: true) - .frame(maxWidth: .infinity, alignment: .leading) - } else { - HStack(alignment: .top, spacing: 20) { - runControlsStack(maxWidth: 420) - runStatusColumn(inlineStart: false) - } - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .transition(.move(edge: .leading).combined(with: .opacity)) - } - - private var compactRunLayout: some View { - VStack(alignment: .leading, spacing: 16) { - if viewModel.isRunning { - runStatusColumn(inlineStart: true) - } else { - runControlsStack() - runStatusColumn(inlineStart: false) - } - } - .frame(maxWidth: .infinity, alignment: .leading) - .transition(.move(edge: .top).combined(with: .opacity)) - } - - @ViewBuilder - private func runControlsStack(maxWidth: CGFloat? = nil) -> some View { - if !viewModel.isRunning { - VStack(alignment: .leading, spacing: 20) { - configurationSection - #if DEBUG - if let logURL = viewModel.latestLogURL { - debugLogCard(url: logURL) - } - #endif - } - .frame(maxWidth: maxWidth ?? .infinity, alignment: .leading) - .transition(.move(edge: maxWidth == nil ? .top : .leading).combined(with: .opacity)) - } - } - - @ViewBuilder - private func runStatusColumn(inlineStart: Bool) -> some View { - let showProgress = inlineStart || viewModel.isRunning || shouldShowProgressSection - let showSummary = !viewModel.isRunning && viewModel.latestSummary != nil - let showRunLog = shouldShowRunLogSection - if showProgress || showSummary || showRunLog { - VStack(alignment: .leading, spacing: 20) { - if showSummary, let summary = viewModel.latestSummary { - resultsCard(summary: summary) - .transition(.move(edge: .trailing).combined(with: .opacity)) - } - if showProgress { - progressSection(forceVisible: inlineStart, inlineStart: inlineStart) - .transition(.move(edge: .trailing).combined(with: .opacity)) - } - if showRunLog { - runLogSection - .transition(.move(edge: .trailing).combined(with: .opacity)) - } - } - .frame(maxWidth: .infinity, alignment: .leading) - } - } - - private var configurationSection: some View { - BenchmarkCard( - title: "Run Controls", - subtitle: "Select a model and manage the canonical benchmark run.", - systemImage: "slider.horizontal.3", - spacing: 16 - ) { - VStack(alignment: .leading, spacing: 16) { - HStack(alignment: .bottom, spacing: 16) { - modelSelector - .frame(maxWidth: 360) - Spacer(minLength: 16) - runControlButton - } - if let error = viewModel.runError { - Text(error) - .font(.footnote) - .foregroundColor(.red) - } - #if DEBUG - Divider() - - VStack(alignment: .leading, spacing: 12) { - Toggle(isOn: $viewModel.debugLoggingEnabled) { - VStack(alignment: .leading, spacing: 2) { - Text("Write debug log to Downloads") - .font(.subheadline) - Text("Captures prompts, responses, and verifier results after each run.") - .font(.caption) - .foregroundColor(.secondary) - } - } - .toggleStyle(.switch) - .disabled(viewModel.isRunning) - - DisclosureGroup("Debug individual tests") { - VStack(alignment: .leading, spacing: 8) { - Text("Run any test individually and write a debug log. Uses current seed and subseed count.") - .font(.caption) - .foregroundColor(.secondary) - .padding(.bottom, 4) - - ForEach(viewModel.debugAvailableTests.indices, id: \.self) { idx in - let group = viewModel.debugAvailableTests[idx] - DisclosureGroup("Task Group \(idx + 1) – Seed \(group.seed)") { - VStack(alignment: .leading, spacing: 8) { - ForEach(group.tasks) { ref in - HStack(spacing: 12) { - Text("\(ref.taskIndex + 1). \(friendlyName(for: ref.spec.type))") - .font(.callout) - Spacer() - if viewModel.runningTestIDs.contains(ref.id) { - ProgressView().controlSize(.small) - } - Button("Run") { - Task { await viewModel.runSingleTestDebug(ref) } - } - .buttonStyle(.borderedProminent) - .disabled(viewModel.isRunning || viewModel.runningTestIDs.contains(ref.id)) - } - } - } - .padding(.leading, 6) - } - .font(.caption) - } - } - } - .padding(.top, 8) - } - #endif - if !viewModel.lastAudit.isEmpty { - Divider() - VStack(alignment: .leading, spacing: 10) { - Text("Preflight Audit") - .font(.subheadline) - ForEach(Array(viewModel.lastAudit.enumerated()), id: \.element.spec.id) { index, audit in - VStack(alignment: .leading, spacing: 6) { - Text("Task Group \(index + 1)") - .font(.callout) - Text(audit.solvable ? "Ready" : "Issues detected") - .font(.caption) - .foregroundColor(audit.solvable ? .secondary : .orange) - if audit.issues.isEmpty { - Text("No issues detected.") - .font(.caption) - .foregroundColor(.secondary) - } else { - ForEach(audit.issues.indices, id: \.self) { issueIndex in - let issue = audit.issues[issueIndex] - Label("[\(issue.severity.rawValue.uppercased())] \(issue.code)", systemImage: "exclamationmark.triangle.fill") - .labelStyle(.titleAndIcon) - .font(.caption) - .foregroundColor(severityColor(issue.severity)) - Text(issue.message) - .font(.caption2) - .foregroundColor(.secondary) - } - } - } - .padding(12) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) - } - } - } - } - } - } - - private var shouldShowProgressSection: Bool { - viewModel.isRunning - } - - private var shouldShowRunLogSection: Bool { - viewModel.isRunning - } - - @ViewBuilder - private func progressSection(forceVisible: Bool = false, inlineStart: Bool = false) -> some View { - let show = forceVisible || viewModel.isRunning || shouldShowProgressSection - if show { - let completed = min(viewModel.progressSummary.completedTasks, canonicalTaskTotal) - let percentage = canonicalTaskTotal == 0 ? 0 : Int((Double(completed) / Double(canonicalTaskTotal)) * 100) - let runningModelName = viewModel.isRunning ? (AIModel.fromModelName(viewModel.selectedModelRaw)?.displayName ?? viewModel.selectedModelRaw) : nil - BenchmarkCard( - title: viewModel.isRunning ? "Benchmark running" : "Progress", - subtitle: viewModel.isRunning ? "Live updates in progress…" : "Most recent canonical run snapshot.", - systemImage: viewModel.isRunning ? "bolt.fill" : "checkmark.seal.fill", - trailingTitle: runningModelName - ) { - VStack(alignment: .leading, spacing: 12) { - ProgressView(value: Double(completed), total: Double(canonicalTaskTotal)) - .progressViewStyle(.linear) - .tint(.accentColor) - HStack { - Text("\(completed) of \(canonicalTaskTotal) tasks complete") - .font(.subheadline) - .monospacedDigit() - Spacer() - Text("\(percentage)%") - .font(.footnote) - .foregroundColor(.secondary) - } - if viewModel.progressSummary.totalTasks > canonicalTaskTotal { - Text("Processed \(viewModel.progressSummary.totalTasks) tasks in total during the run.") - .font(.caption) - .foregroundColor(.secondary) - } - if inlineStart { - Divider() - .transition(.opacity) - Button { - if viewModel.isRunning { - viewModel.stopRun() - } else { - viewModel.runBenchmark() - } - } label: { - Label(viewModel.isRunning ? "Stop Run" : "Run Benchmark", systemImage: viewModel.isRunning ? "stop.fill" : "play.circle.fill") - } - .frame(maxWidth: .infinity, alignment: .leading) - .buttonStyle(.borderedProminent) - .controlSize(.large) - .disabled(!viewModel.isRunning && viewModel.availableModels.isEmpty) - .transition(.move(edge: .bottom).combined(with: .opacity)) - } - } - } - .transition(.move(edge: .top).combined(with: .opacity)) - } - } - - private var runLogSection: some View { - let steps = viewModel.progressSteps - return BenchmarkCard( - title: "Run log", - subtitle: viewModel.isRunning ? "Live task group updates" : "Latest canonical run", - systemImage: "list.bullet.rectangle" - ) { - if steps.isEmpty, viewModel.latestReport == nil { - Text("Run the benchmark to see detailed task progress and pass/fail results.") - .font(.caption) - .foregroundColor(.secondary) - .frame(maxWidth: .infinity, alignment: .leading) - } else { - VStack(alignment: .leading, spacing: 12) { - ForEach(steps) { step in - let state: RunLogRow.State = if step.isComplete { - .complete - } else if step.isActive, viewModel.isRunning { - .active - } else { - .pending - } - RunLogRow( - title: step.description, - detail: step.detailText, - additionalInfo: step.additionalInfo, - state: state - ) - } - if let report = viewModel.latestReport { - if !steps.isEmpty { - Divider() - } - VStack(alignment: .leading, spacing: 12) { - ForEach(report.perSeed.indices, id: \.self) { index in - let seedReport = report.perSeed[index] - VStack(alignment: .leading, spacing: 8) { - HStack(alignment: .firstTextBaseline) { - Text("Task Group \(index + 1)") - .font(.callout) - Text("Pts \(String(format: "%.1f/%.0f", seedReport.pointsEarned, seedReport.maxPoints)) (\(Int(seedReport.pointsRate * 100))%) • Pass \(Int(seedReport.passRate * 100))%") - .font(.caption) - .foregroundColor(.secondary) - } - ForEach(seedReport.tasks, id: \.id) { task in - TaskResultRow( - title: friendlyName(for: task.type), - passed: task.pass, - score: task.score, - summary: taskSummary(for: task), - awardedPoints: task.awardedPoints, - maxPoints: task.maxPoints, - normalizedScore: task.normalizedScore - ) - } - } - .padding(14) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) - } - } - } - } - } - } - } - - private func resultsCard(summary: BenchmarkRunSummary) -> some View { - BenchmarkCard( - title: "Latest run", - subtitle: summary.timestamp.formatted(date: .abbreviated, time: .shortened), - systemImage: "chart.bar.fill" - ) { - VStack(alignment: .leading, spacing: 16) { - HStack(spacing: 8) { - Label(summary.modelDisplayShort, systemImage: "cpu") - .font(.subheadline) - .foregroundColor(.secondary) - if let hadErrors = summary.hadErrors, hadErrors { - Image(systemName: "exclamationmark.triangle.fill") - .foregroundColor(.orange) - .font(.caption) - .hoverTooltip("This run encountered API errors and is excluded from local rankings") - } - } - ViewThatFits(in: .horizontal) { - HStack(alignment: .top, spacing: 24) { - resultMetrics(summary: summary) - } - VStack(alignment: .leading, spacing: 16) { - resultMetrics(summary: summary) - } - } - if !summary.seedSummaries.isEmpty { - Divider() - VStack(alignment: .leading, spacing: 12) { - ForEach(summary.seedSummaries.indices, id: \.self) { index in - let seed = summary.seedSummaries[index] - VStack(alignment: .leading, spacing: 8) { - HStack(alignment: .firstTextBaseline) { - Text("Task Group \(index + 1)") - .font(.callout) - Text("Pts \(String(format: "%.1f/%.0f", seed.pointsEarned, seed.maxPoints)) • Pass \(Int(seed.passRate * 100))%") - .font(.caption) - .foregroundColor(.secondary) - } - ForEach(seed.tasks, id: \.id) { task in - TaskResultRow( - title: friendlyName(for: task.type), - passed: task.pass, - score: task.score, - summary: taskSummary(for: task), - awardedPoints: task.awardedPoints, - maxPoints: task.maxPoints, - normalizedScore: task.normalizedScore - ) - } - } - .padding(14) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) - } - } - } - } - } - } - - @ViewBuilder - private func resultMetrics(summary: BenchmarkRunSummary) -> some View { - BenchmarkMetric(title: "Points rate", value: "\(Int(summary.pointsRate * 100))%") - BenchmarkMetric(title: "Points earned", value: String(format: "%.1f / %.0f", summary.totalPointsEarned, summary.totalMaxPoints)) - BenchmarkMetric(title: "Pass rate", value: "\(Int(summary.passRate * 100))%") - BenchmarkMetric(title: "Passed", value: "\(summary.passedTasks)") - BenchmarkMetric(title: "Failed", value: "\(summary.totalTasks - summary.passedTasks)") - } - - private func debugLogCard(url: URL) -> some View { - BenchmarkCard( - title: "Debug log saved", - subtitle: "Files are written to Downloads when logging is enabled.", - systemImage: "doc.richtext" - ) { - VStack(alignment: .leading, spacing: 12) { - Text(url.lastPathComponent) - .font(.caption) - .foregroundColor(.secondary) - HStack(spacing: 12) { - Button("Reveal in Finder") { - revealLog(at: url) - } - .buttonStyle(.bordered) - Button("Copy Path") { - copyLogPath(url) - } - .buttonStyle(.bordered) - } - } - } - } - - private var runControlButton: some View { - HStack(spacing: 10) { - Button { - if viewModel.isRunning { - viewModel.stopRun() - } else { - viewModel.runBenchmark() - } - } label: { - Label(viewModel.isRunning ? "Stop Run" : "Run Benchmark", systemImage: viewModel.isRunning ? "stop.fill" : "play.circle.fill") - } - .buttonStyle(.borderedProminent) - .controlSize(.large) - .disabled(!viewModel.isRunning && viewModel.availableModels.isEmpty) - if viewModel.isRunning { - ProgressView() - .controlSize(.small) - } - } - } - - private var modelSelector: some View { - VStack(alignment: .leading, spacing: 4) { - Text("Model") - .font(.subheadline) - OptimizedModelPicker( - selection: $viewModel.selectedModelRaw, - availableModels: viewModel.availableModels, - font: .body, - widthStyle: .flexible(minWidth: 220, maxWidth: 360, alignment: .leading) - ) - .accessibilityLabel("Benchmark model") - } - } - - private var historyContent: some View { - VStack(alignment: .leading, spacing: 12) { - HStack { - Text("Run history") - .font(.headline) - Spacer() - Button("Export CSV") { - if let url = viewModel.exportHistoryToCSV() { - revealLog(at: url) - } - } - .disabled(viewModel.history.isEmpty) - Button("Clear history") { - showClearHistoryConfirmation = true - } - .disabled(viewModel.history.isEmpty) - .confirmationDialog( - "Clear all benchmark history?", - isPresented: $showClearHistoryConfirmation - ) { - Button("Clear All History", role: .destructive) { - viewModel.clearHistory() - } - Button("Cancel", role: .cancel) {} - } message: { - Text("This will permanently delete all benchmark run history and leaderboard data. This action cannot be undone.") - } - } - if viewModel.history.isEmpty { - Spacer() - VStack(spacing: 8) { - Image(systemName: "clock.arrow.circlepath") - .font(.system(size: 28)) - .foregroundColor(.secondary) - Text("No runs yet") - .font(.headline) - Text("Run the benchmark to populate history.") - .font(.caption) - .foregroundColor(.secondary) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } else { - ScrollView { - VStack(alignment: .leading, spacing: 16) { - ForEach(viewModel.history) { run in - runHistoryRow(run, onDelete: { - if let index = viewModel.history.firstIndex(where: { $0.id == run.id }) { - viewModel.deleteRun(at: IndexSet(integer: index)) - } - }) - } - } - } - } - } - .padding(.trailing, 4) - } - - private func runHistoryRow(_ run: BenchmarkRunSummary, onDelete: @escaping () -> Void) -> some View { - BenchmarkCard(title: nil, subtitle: nil, systemImage: nil) { - DisclosureGroup { - VStack(alignment: .leading, spacing: 10) { - ForEach(run.seedSummaries.indices, id: \.self) { index in - let seed = run.seedSummaries[index] - VStack(alignment: .leading, spacing: 6) { - Text("Task Group \(index + 1)") - .font(.subheadline) - Text("Pts \(String(format: "%.1f/%.0f", seed.pointsEarned, seed.maxPoints)) (\(Int(seed.pointsRate * 100))%) • Pass \(Int(seed.passRate * 100))%") - .font(.caption) - .foregroundColor(.secondary) - if !seed.tasks.isEmpty { - ForEach(seed.tasks, id: \.id) { task in - TaskResultRow( - title: friendlyName(for: task.type), - passed: task.pass, - score: task.score, - summary: taskSummary(for: task), - awardedPoints: task.awardedPoints, - maxPoints: task.maxPoints, - normalizedScore: task.normalizedScore - ) - } - } - } - .padding(12) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) - } - } - .padding(.top, 6) - } label: { - VStack(alignment: .leading, spacing: 4) { - HStack { - HStack(spacing: 6) { - Text(run.modelDisplayShort) - .font(.headline) - if let hadErrors = run.hadErrors, hadErrors { - Image(systemName: "exclamationmark.triangle.fill") - .foregroundColor(.orange) - .font(.caption) - .hoverTooltip("This run encountered API errors and is excluded from local rankings") - } - } - Spacer() - Button { - onDelete() - } label: { - Image(systemName: "trash") - .foregroundColor(.red) - } - .buttonStyle(.plain) - Text(run.timestamp, style: .date) - .font(.caption) - .foregroundColor(.secondary) - } - Text("Pts \(String(format: "%.1f/%.0f", run.totalPointsEarned, run.totalMaxPoints)) (\(Int(run.pointsRate * 100))%) • Pass \(Int(run.passRate * 100))%") - .font(.caption) - .foregroundColor(.secondary) - } - } - } - .buttonStyle(.plain) - } - - private var leaderboardContent: some View { - VStack(alignment: .leading, spacing: 12) { - HStack { - Text("Local rankings") - .font(.headline) - } - if viewModel.leaderboard.isEmpty { - Spacer() - VStack(spacing: 8) { - Image(systemName: "chart.bar.doc.horizontal") - .font(.system(size: 28)) - .foregroundColor(.secondary) - Text("No data yet") - .font(.headline) - Text("Run benchmarks to populate model rankings.") - .font(.caption) - .foregroundColor(.secondary) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } else { - ScrollView { - VStack(alignment: .leading, spacing: 12) { - ForEach(viewModel.leaderboard.indices, id: \.self) { index in - leaderboardRow(rank: index + 1, entry: viewModel.leaderboard[index]) - } - } - } - } - } - .padding(.trailing, 4) - } - - private func severityColor(_ severity: BenchmarkAuditIssue.Severity) -> Color { - switch severity { - case .error: - .red - case .warning: - .orange - case .info: - .secondary - } - } - - private func leaderboardRow(rank: Int, entry: BenchmarkLeaderboardEntry) -> some View { - BenchmarkCard(title: nil, subtitle: nil, systemImage: nil) { - HStack(spacing: 16) { - Text("#\(rank)") - .font(.title3) - .fontWeight(.semibold) - .frame(width: 40, alignment: .leading) - VStack(alignment: .leading, spacing: 4) { - Text(entry.modelDisplayName) - .font(.headline) - Text("Pts \(Int(entry.pointsRate * 100))% • Pass \(Int(entry.passRate * 100))% • Tasks \(entry.passedTasks)/\(entry.totalTasks)") - .font(.caption) - .foregroundColor(.secondary) - } - Spacer() - Text(entry.lastRun, style: .date) - .font(.caption2) - .foregroundColor(.secondary) - } - } - } - - private func friendlyName(for type: BenchmarkCaseType) -> String { - let tokens = type.rawValue.split(separator: "_") - let words = tokens.map { token -> String in - switch token.lowercased() { - case "ts": - return "TypeScript" - case "go": - return "Go" - default: - return token.capitalized - } - } - return words.joined(separator: " ") - } - - private func friendlyName(for rawType: String) -> String { - if let type = BenchmarkCaseType(rawValue: rawType) { - return friendlyName(for: type) - } - let tokens = rawType.split(separator: "_") - let words = tokens.map { token -> String in - switch token.lowercased() { - case "ts": - return "TypeScript" - case "go": - return "Go" - default: - return token.capitalized - } - } - return words.joined(separator: " ") - } - - private func taskSummary(for task: BenchmarkTaskReport) -> String { - let reason = task.reason.trimmingCharacters(in: .whitespacesAndNewlines) - if !reason.isEmpty { - return BenchmarkVerifier.humanReadableReason(reason) - } - return task.pass ? "Passed" : "Needs review" - } - - private func taskSummary(for task: BenchmarkTaskSummary) -> String { - let reason = task.reason.trimmingCharacters(in: .whitespacesAndNewlines) - if !reason.isEmpty { - return BenchmarkVerifier.humanReadableReason(reason) - } - return task.pass ? "Passed" : "Needs review" - } - - private func revealLog(at url: URL) { - #if os(macOS) - NSWorkspace.shared.activateFileViewerSelecting([url]) - #endif - } - - private func copyLogPath(_ url: URL) { - #if os(macOS) - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setString(url.path, forType: .string) - #endif - } -} - -private struct BenchmarkCard: View { - let title: String? - let subtitle: String? - let systemImage: String? - var spacing: CGFloat = 16 - let trailingTitle: String? - @ViewBuilder var content: Content - - init(title: String?, subtitle: String?, systemImage: String?, spacing: CGFloat = 16, trailingTitle: String? = nil, @ViewBuilder content: () -> Content) { - self.title = title - self.subtitle = subtitle - self.systemImage = systemImage - self.spacing = spacing - self.trailingTitle = trailingTitle - self.content = content() - } - - var body: some View { - VStack(alignment: .leading, spacing: spacing) { - if let title { - HStack(alignment: .firstTextBaseline, spacing: 8) { - if let systemImage { - Image(systemName: systemImage) - .font(.headline) - } - VStack(alignment: .leading, spacing: 2) { - Text(title) - .font(.headline) - if let subtitle, !subtitle.isEmpty { - Text(subtitle) - .font(.caption) - .foregroundColor(.secondary) - } - } - Spacer() - if let trailingTitle { - Text(trailingTitle) - .font(.headline) - .foregroundColor(.secondary) - } - } - } - content - } - .padding(20) - .background( - RoundedRectangle(cornerRadius: 16, style: .continuous) - .fill(.regularMaterial) - ) - .overlay( - RoundedRectangle(cornerRadius: 16, style: .continuous) - .strokeBorder(Color.primary.opacity(0.05)) - ) - } -} - -private struct BenchmarkMetric: View { - let title: String - let value: String - - var body: some View { - VStack(alignment: .leading, spacing: 4) { - Text(title) - .font(.caption) - .foregroundColor(.secondary) - Text(value) - .font(.title3) - .fontWeight(.semibold) - } - } -} - -private struct RunLogRow: View { - enum State { - case pending - case active - case complete - } - - let title: String - let detail: String - let additionalInfo: String? - let state: State - - var body: some View { - HStack(alignment: .top, spacing: 12) { - stateIcon - VStack(alignment: .leading, spacing: 2) { - Text(title) - .font(.callout) - .fontWeight(state == .complete ? .regular : .medium) - .foregroundColor(state == .pending ? .secondary : .primary) - if !detail.isEmpty { - Text(detail) - .font(.caption) - .foregroundColor(.secondary) - } - if let additionalInfo, !additionalInfo.isEmpty { - Text(additionalInfo) - .font(.caption2) - .foregroundColor(.secondary) - } - } - Spacer(minLength: 0) - } - .padding(12) - .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 16, style: .continuous)) - } - - @ViewBuilder - private var stateIcon: some View { - switch state { - case .complete: - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.green) - case .active: - ProgressView() - .controlSize(.small) - case .pending: - Image(systemName: "circle.dashed") - .foregroundColor(.secondary) - } - } -} - -private struct TaskResultRow: View { - let title: String - let passed: Bool - let score: Double - let summary: String - let awardedPoints: Double? - let maxPoints: Double? - let normalizedScore: Double? - - var body: some View { - HStack(alignment: .top, spacing: 12) { - Image(systemName: passed ? "checkmark.seal.fill" : "xmark.seal.fill") - .foregroundColor(passed ? .green : .red) - VStack(alignment: .leading, spacing: 2) { - HStack { - Text(title) - .font(.callout) - Spacer() - if let awarded = awardedPoints { - // Apply 2x multiplier for perfectly completed tasks - let normalizedScore = normalizedScore ?? score - let multiplier = (passed && normalizedScore >= 1.0) ? 2.0 : 1.0 - let finalPoints = awarded * multiplier - Text(String(format: "%.2f", finalPoints)) - .font(.caption) - .foregroundColor(.secondary) - } else { - Text(String(format: "%.2f", score)) - .font(.caption) - .foregroundColor(.secondary) - } - } - Text(summary) - .font(.caption) - .foregroundColor(.secondary) - .lineLimit(2) - } - } - } -} diff --git a/Sources/RepoPrompt/Features/Prompt/Views/Components/FilePreviewPopover.swift b/Sources/RepoPrompt/Features/Prompt/Views/Components/FilePreviewPopover.swift index 7ed09dbcb..ed925e269 100644 --- a/Sources/RepoPrompt/Features/Prompt/Views/Components/FilePreviewPopover.swift +++ b/Sources/RepoPrompt/Features/Prompt/Views/Components/FilePreviewPopover.swift @@ -11,7 +11,7 @@ struct FilePreviewPopover: View { @State private var loadingTask: Task? = nil // Task handle @State private var showSlicesOnly: Bool = true // Default to showing slices if available @State private var viewRefreshID = UUID() // Force view refresh - @State private var previewMode: FilePreviewMode = .syntaxHighlighted + @State private var previewMode: FilePreviewMode = .plainText @State private var statusMessage: String? = nil @State private var codemapLogicalPath: String? = nil @@ -81,8 +81,8 @@ struct FilePreviewPopover: View { case .disabled: // Show disabled state with message disabledPreviewView - case .plainText, .syntaxHighlighted: - // Show content (plain or highlighted based on available ranges) + case .plainText: + // Show plain-text content if previewContent != "Loading..." { textPreviewView } else { @@ -146,7 +146,7 @@ struct FilePreviewPopover: View { previewContent = "Codemap preview was revoked because the workspace or selection changed." statusMessage = "Preview revoked" } - previewMode = .syntaxHighlighted + previewMode = .plainText viewRefreshID = UUID() } return @@ -162,7 +162,7 @@ struct FilePreviewPopover: View { if shouldShowSlices, let slices = fileSlices { // Get SVG safety info from previewSnapshot first let snapshot = await MainActor.run { file.previewSnapshot } - let svgMode = snapshot?.mode ?? .syntaxHighlighted + let svgMode = snapshot?.mode ?? .plainText // For disabled SVGs, don't render slices at all - use snapshot message if svgMode == .disabled { @@ -211,7 +211,7 @@ struct FilePreviewPopover: View { if !Task.isCancelled { await MainActor.run { - previewMode = .syntaxHighlighted + previewMode = .plainText previewContent = loadedPreviewContent statusMessage = nil viewRefreshID = UUID() diff --git a/Sources/RepoPrompt/Features/Settings/Views/AgentModelsSettingsView.swift b/Sources/RepoPrompt/Features/Settings/Views/AgentModelsSettingsView.swift index 99bc1e39c..66f5d2f43 100644 --- a/Sources/RepoPrompt/Features/Settings/Views/AgentModelsSettingsView.swift +++ b/Sources/RepoPrompt/Features/Settings/Views/AgentModelsSettingsView.swift @@ -672,7 +672,7 @@ struct AgentModelsSettingsView: View { /// no longer requires a click to uncover. /// /// SEARCH-HELPER: Agent Models related settings, Oracle Model Presets link, - /// Benchmark Model link, related-settings footer + /// related-settings footer private var relatedSettingsSection: some View { VStack(alignment: .leading, spacing: 10) { Text("Related Settings") @@ -685,12 +685,6 @@ struct AgentModelsSettingsView: View { detail: "Named Oracle model choices exposed to MCP clients.", tab: .modelPresets ) - linkRow( - icon: "gauge", - title: "Benchmark Model", - detail: "Head-to-head model benchmarks.", - tab: .benchmark - ) } .padding(.top, 8) } diff --git a/Sources/RepoPrompt/Features/Settings/Views/SettingsView.swift b/Sources/RepoPrompt/Features/Settings/Views/SettingsView.swift index 2afbe1326..2eb70417b 100644 --- a/Sources/RepoPrompt/Features/Settings/Views/SettingsView.swift +++ b/Sources/RepoPrompt/Features/Settings/Views/SettingsView.swift @@ -37,8 +37,7 @@ struct SettingsView: View { /// Canonical sidebar order. Agent-mode first, then General (app-wide /// preferences), MCP, models/providers, workspaces, and the copy-&-chat - /// workflow. Benchmark is intentionally grouped under Models & Providers - /// rather than being its own top-level section. + /// workflow. private static let sidebarSectionOrder: [TabSection] = [ .agentMode, .general, .mcp, .api, .workspaces, .copyChat ] @@ -250,7 +249,7 @@ struct SettingsView: View { case .mcp: [.mcp, .mcpTools, .permissions, .modelPresets] case .api: - [.apiGeneral, .openRouter, .customProvider, .modelOverrides, .benchmark] + [.apiGeneral, .openRouter, .customProvider, .modelOverrides] case .workspaces: [.manageWorkspaces, .managePresets] case .general: @@ -307,12 +306,6 @@ struct SettingsView: View { case .chatSettings: ChatSettingsView(promptViewModel: promptViewModel, windowID: windowState.windowID, closeAction: closeAction) .transition(.opacity.animation(.easeInOut(duration: 0.15))) - case .benchmark: - BenchmarkSettingsView( - promptViewModel: promptViewModel, - apiSettingsViewModel: apiSettingsViewModel - ) - .transition(.opacity.animation(.easeInOut(duration: 0.15))) case .apiGeneral: APISettingsView( viewModel: apiSettingsViewModel, @@ -522,7 +515,6 @@ enum SettingsTab: String, CaseIterable { case advanced case telemetry case chatSettings - case benchmark case apiGeneral case openRouter case customProvider @@ -552,7 +544,6 @@ enum SettingsTab: String, CaseIterable { case .advanced: "Advanced" case .telemetry: "Telemetry" case .chatSettings: "Chat Settings" - case .benchmark: "Benchmark" case .apiGeneral: "API Providers" case .openRouter: "OpenRouter" case .customProvider: "Custom API" @@ -584,7 +575,6 @@ enum SettingsTab: String, CaseIterable { case .advanced: "gearshape.2" case .telemetry: "lock.shield" case .chatSettings: "message" - case .benchmark: "gauge" case .apiGeneral: "key" case .openRouter: "network" case .customProvider: "server.rack" @@ -620,9 +610,8 @@ enum SettingsTab: String, CaseIterable { case .mcp, .mcpTools, .permissions, .modelPresets: .mcp - // Models & Providers (Oracle + API key providers). Benchmark lives here - // rather than being its own top-level section. - case .apiGeneral, .openRouter, .customProvider, .modelOverrides, .benchmark: + // Models & Providers (Oracle + API key providers) + case .apiGeneral, .openRouter, .customProvider, .modelOverrides: .api // Workspaces @@ -751,20 +740,6 @@ enum SettingsTab: String, CaseIterable { "chat history", "built-in chat" ] - case .benchmark: - [ - "benchmark", - "bench", - "score", - "ranking", - "model benchmark", - "run benchmark", - "benchmark history", - "benchmark leaderboard", - "seed", - "performance test", - "model evaluation" - ] case .apiGeneral: [ "api", diff --git a/Sources/RepoPrompt/Features/WorkspaceFiles/ViewModels/FileViewModel.swift b/Sources/RepoPrompt/Features/WorkspaceFiles/ViewModels/FileViewModel.swift index 9801db862..69b8c6016 100644 --- a/Sources/RepoPrompt/Features/WorkspaceFiles/ViewModels/FileViewModel.swift +++ b/Sources/RepoPrompt/Features/WorkspaceFiles/ViewModels/FileViewModel.swift @@ -1,18 +1,15 @@ import AppKit import Combine import Foundation -import SwiftTreeSitter // MARK: - SVG-Safe Preview Types /// Determines how a file should be previewed based on safety and performance considerations. enum FilePreviewMode { - /// Preview is disabled - file is too risky to display (e.g., extremely large SVG). + /// Preview is disabled because the file is too risky to display. case disabled - /// Plain text preview without syntax highlighting - safer for large or complex files. + /// Preview is rendered as plain text. case plainText - /// Full syntax-highlighted preview - default for normal files. - case syntaxHighlighted } enum FileContentFreshnessPolicy { @@ -40,7 +37,6 @@ struct FilePreviewSnapshot { let lineCount: Int let byteCount: Int let previewText: String - let namedRanges: [NamedRange]? /// User-facing message explaining any preview limitations. var statusMessage: String? { @@ -198,8 +194,6 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq /// Preview content, limited for performance (lines/bytes). @Published private(set) var previewContent: String? - /// Syntax ranges corresponding to the preview content. - @Published private(set) var previewNamedRanges: [NamedRange]? /// SVG-safe preview snapshot with mode, truncation info, and content. /// Views should use this instead of directly accessing previewContent for SVG-aware rendering. @@ -293,9 +287,6 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq /// Cached line counts for quick UI access @Published private(set) var contentLineCount: Int? = nil - /// Cached syntax tokens for highlighting (loaded on demand) - private var cachedNamedRanges: [NamedRange]? - // MARK: - Off-main content mirror (for zero MainActor hop reads) private actor ContentStore { @@ -481,14 +472,6 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq } } - /// Getter that returns valid named ranges. If they’re not cached yet, it loads them. - var latestNamedRanges: [NamedRange]? { - get async { - await loadSyntaxHighlighting() - return await MainActor.run { self.cachedNamedRanges } - } - } - // ───────────────────────────────────────────────────────────── // MARK: - DEBUG aid: fallback-return telemetry (lightweight) @@ -559,7 +542,6 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq isChecked = false loadingState = .notLoaded cachedContent = nil - cachedNamedRanges = nil self.hierarchyLevel = hierarchyLevel self.fileSystemService = fileSystemService if let contentProvider { @@ -576,7 +558,6 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq // Preview previewContent = nil - previewNamedRanges = nil // Extension let ext = (file.name as NSString).pathExtension @@ -616,7 +597,6 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq isChecked = false loadingState = .notLoaded cachedContent = nil - cachedNamedRanges = nil self.hierarchyLevel = hierarchyLevel self.fileSystemService = fileSystemService if let contentProvider { @@ -633,7 +613,6 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq // Preview previewContent = nil - previewNamedRanges = nil // Extension let ext = (file.name as NSString).pathExtension @@ -671,13 +650,11 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq guard forceInvalidation || modificationDate != newDate else { return } modificationDate = newDate - // Invalidate UI-visible caches (but keep cachedContent as a fallback) - cachedNamedRanges = nil + // Invalidate UI-visible state (but keep cachedContent as a fallback) lastLoadedDate = nil loadingState = .notLoaded error = nil previewContent = nil - previewNamedRanges = nil contentLineCount = nil // ensure line count refreshes after next load // Cancel ongoing work @@ -859,54 +836,6 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq return await getCachedContentFallback() } - // MARK: - Syntax Highlighting (off-main safe) - - /// Asynchronously loads the syntax highlighting tokens on demand, - /// caching the result in the view model. Also updates preview ranges. - func loadSyntaxHighlighting() async { - guard let ext = fileExtension else { return } - - // Only compute if not cached - let alreadyCached = await MainActor.run { self.cachedNamedRanges != nil } - if alreadyCached { return } - - // Read content off-main to avoid MainActor hops - guard let content = await contentStore.get() else { return } - - do { - let tokens = try SyntaxManager.shared.highlight(content: content, fileExtension: ext) - await MainActor.run { - self.cachedNamedRanges = tokens - let preview = self.previewContent - self.previewNamedRanges = self.filterRangesForContent(tokens, content: preview) - - // Update previewSnapshot with the new syntax ranges - // so file preview popovers get highlighting. - // Skip SVGs entirely - they use plainText/disabled modes for safety. - if let snapshot = self.previewSnapshot, - snapshot.mode == .syntaxHighlighted, - !snapshot.isSvg - { - self.previewSnapshot = FilePreviewSnapshot( - mode: snapshot.mode, - isSvg: snapshot.isSvg, - wasTruncatedByLines: snapshot.wasTruncatedByLines, - wasTruncatedByBytes: snapshot.wasTruncatedByBytes, - lineCount: snapshot.lineCount, - byteCount: snapshot.byteCount, - previewText: snapshot.previewText, - namedRanges: self.previewNamedRanges - ) - } - } - } catch { - print("Error parsing content for syntax tokens: \(error)") - await MainActor.run { - self.previewNamedRanges = nil - } - } - } - // MARK: - Internal content updates (MainActor) @discardableResult @@ -930,15 +859,13 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq byteSize: byteCount, lineCount: lineCount, maxPreviewLines: Self.maxPreviewLines, - wasTruncatedByBytes: truncation.wasTruncatedByBytes, - namedRanges: nil + wasTruncatedByBytes: truncation.wasTruncatedByBytes ) let previewArtifacts = PreviewArtifacts(snapshot: snapshot, lineCount: lineCount) await updateContentInternal( resolvedContent, modificationDate, - tokens: nil, previewArtifacts: previewArtifacts ) return resolvedContent @@ -948,11 +875,9 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq private func updateContentInternal( _ newContent: String, _ modificationDate: Date, - tokens: [NamedRange]?, previewArtifacts: PreviewArtifacts ) async { cachedContent = newContent - cachedNamedRanges = tokens // will be nil on load; re-tokenize on demand loadingState = .loaded lastLoadedDate = modificationDate // Keep the UI-facing timestamp in sync with disk mtime so future setModificationDate(newDate) @@ -961,7 +886,6 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq // Preview (use precomputed to avoid main-thread work) previewContent = previewArtifacts.snapshot.previewText - previewNamedRanges = filterRangesForContent(cachedNamedRanges, content: previewContent) contentLineCount = previewArtifacts.lineCount previewSnapshot = previewArtifacts.snapshot @@ -982,8 +906,7 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq byteSize: Int, lineCount: Int, maxPreviewLines: Int, - wasTruncatedByBytes: Bool, - namedRanges: [NamedRange]? + wasTruncatedByBytes: Bool ) -> FilePreviewSnapshot { let ext = ((relativePath as NSString).pathExtension).lowercased() let isSvg = ext == "svg" || ext == "svgz" @@ -996,29 +919,23 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq let mode: FilePreviewMode let finalPreviewText: String - let finalNamedRanges: [NamedRange]? // Disable preview for any file over 5MB if byteSize > Self.disabledPreviewThresholdBytes { mode = .disabled finalPreviewText = "[Preview disabled - file too large (\(byteSize / 1_000_000) MB)]" - finalNamedRanges = nil } else if isSvg { - // All SVGs use plain text - no syntax highlighting to avoid CoreSVG issues + // Avoid CoreSVG rendering and bound pathological lines. if hasExtremelyLongLines { mode = .plainText finalPreviewText = Self.truncateLongLines(previewText, maxLength: Self.maxSafeLineLength) - finalNamedRanges = nil } else { mode = .plainText finalPreviewText = previewText - finalNamedRanges = nil } } else { - // Non-SVG files use normal syntax highlighting - mode = .syntaxHighlighted + mode = .plainText finalPreviewText = previewText - finalNamedRanges = namedRanges } return FilePreviewSnapshot( @@ -1028,8 +945,7 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq wasTruncatedByBytes: wasTruncatedByBytes, lineCount: lineCount, byteCount: byteSize, - previewText: finalPreviewText, - namedRanges: finalNamedRanges + previewText: finalPreviewText ) } @@ -1045,9 +961,7 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq contentLineCount = Self.countLinesCapped(newContent, cap: Self.maxPreviewLines + 1) } else { // Evict memory-only caches - cachedNamedRanges = nil previewContent = nil - previewNamedRanges = nil previewSnapshot = nil contentLineCount = nil // Intentionally do not touch loadingState / lastLoadedDate here @@ -1383,16 +1297,6 @@ class FileViewModel: ObservableObject, Identifiable, FileSystemItemViewModel, Eq return result } - /// Helper to filter syntax ranges to only include those within the bounds of the given content length. - private func filterRangesForContent(_ ranges: [NamedRange]?, content: String?) -> [NamedRange]? { - guard let ranges, let content else { return nil } - let contentLength = content.utf16.count // Use UTF16 count for NSRange compatibility - return ranges.filter { range in - let rangeEnd = range.range.location + range.range.length - return rangeEnd <= contentLength - } - } - // MARK: - Equatable / Hashable static func == (lhs: FileViewModel, rhs: FileViewModel) -> Bool { diff --git a/Sources/RepoPrompt/Features/WorkspaceFiles/ViewModels/WorkspaceFilesViewModel.swift b/Sources/RepoPrompt/Features/WorkspaceFiles/ViewModels/WorkspaceFilesViewModel.swift index 26563aa33..7e6358677 100644 --- a/Sources/RepoPrompt/Features/WorkspaceFiles/ViewModels/WorkspaceFilesViewModel.swift +++ b/Sources/RepoPrompt/Features/WorkspaceFiles/ViewModels/WorkspaceFilesViewModel.swift @@ -5481,15 +5481,6 @@ class WorkspaceFilesViewModel: ObservableObject { return nil } - /// Provides baseline file content for a given path. - /// This is a "back door" method that subclasses can override to provide - /// content without needing full FileViewModel infrastructure (e.g., for benchmarks). - /// Default implementation returns nil. - @MainActor - func getBaselineContent(forPath relativePath: String, rootIdentifier: UUID?) async -> String? { - nil - } - private func findFilesByName(_ fileName: String, in folder: FolderViewModel) -> [FileViewModel] { let normalizedFileName = (fileName as NSString).lastPathComponent let standardizedFileName = (normalizedFileName as NSString).standardizingPath diff --git a/Sources/RepoPrompt/Infrastructure/AI/AIMessage.swift b/Sources/RepoPrompt/Infrastructure/AI/AIMessage.swift index 12b8fd833..488f32c80 100644 --- a/Sources/RepoPrompt/Infrastructure/AI/AIMessage.swift +++ b/Sources/RepoPrompt/Infrastructure/AI/AIMessage.swift @@ -33,7 +33,6 @@ struct AIMessage { let conversationMessages: [ConversationEntry] let temperature: Double? - let disableTemperatureOverrides: Bool /// User-defined ordering of prompt sections let promptSectionsOrder: [PromptSection] @@ -120,7 +119,6 @@ struct AIMessage { gitDiff: String? = nil, conversationMessages: [ConversationEntry] = [], temperature: Double?, - disableTemperatureOverrides: Bool = false, promptSectionsOrder: [PromptSection], disabledPromptSections: Set, duplicateUserInstructionsAtTop: Bool = false @@ -132,7 +130,6 @@ struct AIMessage { self.gitDiff = gitDiff self.conversationMessages = conversationMessages self.temperature = temperature - self.disableTemperatureOverrides = disableTemperatureOverrides self.promptSectionsOrder = promptSectionsOrder self.disabledPromptSections = disabledPromptSections self.duplicateUserInstructionsAtTop = duplicateUserInstructionsAtTop @@ -140,14 +137,13 @@ struct AIMessage { /// Simpler initializer for "system prompt + user message" usage /// (e.g. older single-user instructions approach). - init(systemPrompt: String, userMessage: String, temperature: Double? = nil, disableTemperatureOverrides: Bool = false) { + init(systemPrompt: String, userMessage: String, temperature: Double? = nil) { self.systemPrompt = systemPrompt metaPrompts = [] fileTree = "" fileBlocks = [] gitDiff = nil self.temperature = temperature - self.disableTemperatureOverrides = disableTemperatureOverrides // Store the single user message in conversationMessages conversationMessages = [ ConversationEntry(role: .user, content: userMessage) @@ -296,9 +292,6 @@ struct AIMessage { /// Returns the final temperature to send for a specific model, /// respecting global on/off and per-model overrides. func effectiveTemperature(for model: AIModel) -> Double? { - if disableTemperatureOverrides { - return nil - } // 1) Explicit per-model override stored by the user. if let override = ModelOverridesSettings.shared .temperatureOverride(for: model.rawValue) diff --git a/Sources/RepoPrompt/Infrastructure/AI/AIModel.swift b/Sources/RepoPrompt/Infrastructure/AI/AIModel.swift index 6d8740013..4dd472db5 100644 --- a/Sources/RepoPrompt/Infrastructure/AI/AIModel.swift +++ b/Sources/RepoPrompt/Infrastructure/AI/AIModel.swift @@ -2418,44 +2418,4 @@ public enum AIModel: Equatable, Hashable { nil } } - - /// Resolves the actual temperature that will be used for this model, - /// taking into account overrides, explicit values, and model defaults. - /// Returns nil when the temperature is not determinable (e.g., API uses its own default). - /// - Parameters: - /// - explicitTemperature: Optional explicit temperature (e.g., from benchmark settings) - /// - includeOverrides: Whether per-model overrides should be considered (default: true) - /// - Returns: The resolved temperature value, or nil if unknown - func resolveTemperature(explicitTemperature: Double? = nil, includeOverrides: Bool = true) -> Double? { - // 1. Per-model override from settings - if includeOverrides, - let override = ModelOverridesSettings.shared.temperatureOverride(for: rawValue) - { - return override - } - - // 2. Explicit temperature passed in (e.g., benchmark override) - if let explicit = explicitTemperature, explicit > 0.0 { - return explicit - } - - // 3. Model-specific default - if let modelDefault = defaultTemperature { - return modelDefault - } - - // 4. Provider-specific behavior when effectiveTemperature returns nil - switch providerType { - case .anthropic: - // AnthropicProvider explicitly sets temperature = 0 if not overridden (line 102 in AnthropicProvider.swift) - return 0.0 - case .customProvider: - // CustomProviderConfiguration uses 0.3 as default (line 10 in CustomProviderConfiguration.swift) - return 0.3 - case .openAI, .azure, .openRouter, .gemini, .deepseek, .fireworks, .grok, .groq, .zAI, .claudeCode, .codex, .ollama, .openCode, .cursor: - // These providers don't set temperature when nil - API uses its own default (typically 1.0) - // But we can't be certain what the API actually used - return nil - } - } } diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CExamples.swift deleted file mode 100644 index 5afff19e6..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CExamples.swift +++ /dev/null @@ -1,522 +0,0 @@ -// -// CExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-07-14. -// - -import Foundation - -/** - * CExamples implements CodeExamples for C-specific snippets. - */ -public struct CExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" struct - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "typedef struct {", - "char id[37];", - "char name[100];", - "} User;" - ] - } else { - [ - "typedef struct {", - " char id[37];", - " char name[100];", - "} User;" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "typedef struct {", - "char id[37];", - "char name[100];", - "char email[256];", - "} User;" - ] - } else { - [ - "typedef struct {", - " char id[37];", - " char name[100];", - " char email[256];", - "} User;" - ] - } - } - - // MARK: 2) Rewrite Entire File with an "email" field - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "#ifndef USER_H", - "#define USER_H", - "", - "#include ", - "#include ", - "", - "typedef struct {", - "char id[37];", - "char name[100];", - "char email[256];", - "} User;", - "", - "User* create_user(const char* name, const char* email) {", - "User* user = (User*)malloc(sizeof(User));", - "if (user) {", - "// Simple ID generation (placeholder)", - "sprintf(user->id, \"user_%d\", rand());", - "strncpy(user->name, name, 99);", - "user->name[99] = '\\0';", - "strncpy(user->email, email, 255);", - "user->email[255] = '\\0';", - "}", - "return user;", - "}", - "", - "#endif" - ] - } else { - [ - "#ifndef USER_H", - "#define USER_H", - "", - "#include ", - "#include ", - "", - "typedef struct {", - " char id[37];", - " char name[100];", - " char email[256];", - "} User;", - "", - "User* create_user(const char* name, const char* email) {", - " User* user = (User*)malloc(sizeof(User));", - " if (user) {", - " // Simple ID generation (placeholder)", - " sprintf(user->id, \"user_%d\", rand());", - " strncpy(user->name, name, 99);", - " user->name[99] = '\\0';", - " strncpy(user->email, email, 255);", - " user->email[255] = '\\0';", - " }", - " return user;", - "}", - "", - "#endif" - ] - } - } - - // MARK: 3) Create a new "rounded_button" file - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "#ifndef ROUNDED_BUTTON_H", - "#define ROUNDED_BUTTON_H", - "", - "#include ", - "", - "typedef struct {", - "GtkButton parent;", - "gdouble corner_radius;", - "} RoundedButton;", - "", - "typedef struct {", - "GtkButtonClass parent_class;", - "} RoundedButtonClass;", - "", - "GType rounded_button_get_type(void);", - "GtkWidget* rounded_button_new(void);", - "void rounded_button_set_corner_radius(RoundedButton* button, gdouble radius);", - "", - "#endif" - ] - } else { - [ - "#ifndef ROUNDED_BUTTON_H", - "#define ROUNDED_BUTTON_H", - "", - "#include ", - "", - "typedef struct {", - " GtkButton parent;", - " gdouble corner_radius;", - "} RoundedButton;", - "", - "typedef struct {", - " GtkButtonClass parent_class;", - "} RoundedButtonClass;", - "", - "GType rounded_button_get_type(void);", - "GtkWidget* rounded_button_new(void);", - "void rounded_button_set_corner_radius(RoundedButton* button, gdouble radius);", - "", - "#endif" - ] - } - } - - // MARK: 4) NetworkManager async conversion (using callbacks) - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "void fetch_data(const char* url, void (*completion)(const char*)) {", - "CURL* curl = curl_easy_init();", - "if (curl) {", - "curl_easy_setopt(curl, CURLOPT_URL, url);", - "// Synchronous blocking call", - "CURLcode res = curl_easy_perform(curl);", - "if (res == CURLE_OK) {", - "completion(response_buffer);", - "}", - "curl_easy_cleanup(curl);", - "}", - "}" - ] - } else { - [ - "void fetch_data(const char* url, void (*completion)(const char*)) {", - " CURL* curl = curl_easy_init();", - " if (curl) {", - " curl_easy_setopt(curl, CURLOPT_URL, url);", - " // Synchronous blocking call", - " CURLcode res = curl_easy_perform(curl);", - " if (res == CURLE_OK) {", - " completion(response_buffer);", - " }", - " curl_easy_cleanup(curl);", - " }", - "}" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "typedef struct {", - "void (*callback)(const char*);", - "char* buffer;", - "} FetchContext;", - "", - "void fetch_data_async(const char* url, void (*completion)(const char*)) {", - "pthread_t thread;", - "FetchContext* ctx = malloc(sizeof(FetchContext));", - "ctx->callback = completion;", - "// Launch in background thread", - "pthread_create(&thread, NULL, fetch_worker, ctx);", - "pthread_detach(thread);", - "}" - ] - } else { - [ - "typedef struct {", - " void (*callback)(const char*);", - " char* buffer;", - "} FetchContext;", - "", - "void fetch_data_async(const char* url, void (*completion)(const char*)) {", - " pthread_t thread;", - " FetchContext* ctx = malloc(sizeof(FetchContext));", - " ctx->callback = completion;", - " // Launch in background thread", - " pthread_create(&thread, NULL, fetch_worker, ctx);", - " pthread_detach(thread);", - "}" - ] - } - } - - // MARK: Negative Examples - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "#include \"user_service.h\"", - "#include \"logger.h\"", - "", - "void process_user(User* user) {", - "if (user == NULL) {", - "log_error(\"User is NULL\");", - "return;", - "}", - "log_info(\"Processing user: %s\", user->name);", - "}" - ] - } else { - [ - "#include \"user_service.h\"", - "#include \"logger.h\"", - "", - "void process_user(User* user) {", - " if (user == NULL) {", - " log_error(\"User is NULL\");", - " return;", - " }", - " log_info(\"Processing user: %s\", user->name);", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - // Intentionally mismatched - missing braces - if includeIndentation { - [ - "if (user == NULL)", - "log_error(\"User is NULL\");" - ] - } else { - [ - " if (user == NULL)", - " log_error(\"User is NULL\");" - ] - } - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "if (user == NULL || strlen(user->name) == 0) {", - "log_error(\"User is invalid\");", - "return;", - "}" - ] - } else { - [ - " if (user == NULL || strlen(user->name) == 0) {", - " log_error(\"User is invalid\");", - " return;", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - userSearchReplaceNegativeExampleFileContents(includeIndentation: includeIndentation) - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "log_info(\"Processing user: %s\", user->name);", - "}" - ] - } else { - [ - " }", - " log_info(\"Processing user: %s\", user->name);", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - // Extra closing brace added - if includeIndentation { - [ - "}", - "log_info(\"Processing user: %s\", user->name);", - "// Additional validation", - "validate_user(user);", - "}", - "}" // Extra brace - ] - } else { - [ - " }", - " log_info(\"Processing user: %s\", user->name);", - " // Additional validation", - " validate_user(user);", - "}", - "}" // Extra brace - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["log_info(\"Processing user: %s\", user->name);"] - } else { - [" log_info(\"Processing user: %s\", user->name);"] - } - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["log_debug(\"Processing user: %s (ID: %s)\", user->name, user->id);"] - } else { - [" log_debug(\"Processing user: %s (ID: %s)\", user->name, user->id);"] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - // Just closing braces - ambiguous - if includeIndentation { - [ - "}", - "}" - ] - } else { - [ - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "// TODO: Add more processing", - "}" - ] - } else { - [ - " }", - " // TODO: Add more processing", - "}" - ] - } - } - - public func commentSyntax() -> String { - "//" - } - - // File editor examples use default implementation from protocol extension - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - "#include ", - "#include ", - "#include ", - "", - "typedef struct {", - " char* id;", - " char* name;", - "} User;", - "", - "typedef struct {", - " User* users;", - " int count;", - " int capacity;", - "} UserService;", - "", - "UserService* create_user_service() {", - " UserService* service = malloc(sizeof(UserService));", - " service->users = malloc(10 * sizeof(User));", - " service->count = 0;", - " service->capacity = 10;", - " return service;", - "}", - "", - "User process_user(const User* user_data) {", - " // Process user data", - " User user;", - " user.id = strdup(user_data->id);", - " user.name = strdup(user_data->name);", - " return user;", - "}", - "", - "void save_user(UserService* service, User user) {", - " // Save user to database", - " if (service->count >= service->capacity) {", - " service->capacity *= 2;", - " service->users = realloc(service->users, service->capacity * sizeof(User));", - " }", - " service->users[service->count++] = user;", - "}" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - "User process_user(const User* user_data) {", - " // Add validation", - " if (user_data == NULL || user_data->id == NULL || user_data->name == NULL) {", - " fprintf(stderr, \"Invalid user data\\n\");", - " User empty = {NULL, NULL};", - " return empty;", - " }", - " ", - " // ... existing code ...", - "}" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - "void save_user(UserService* service, User user) {", - " // ... existing code ...", - " printf(\"User saved successfully\\n\");", - "}" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "#include ", - "#include ", - "#include ", - "", - "typedef struct {", - " char* id;", - " char* name;", - "} User;", - "", - "typedef struct {", - " User* users;", - " int count;", - " int capacity;", - "} UserService;", - "", - "UserService* create_user_service() {", - " UserService* service = malloc(sizeof(UserService));", - " service->users = malloc(10 * sizeof(User));", - " service->count = 0;", - " service->capacity = 10;", - " return service;", - "}", - "", - "User process_user(const User* user_data) {", - " // Add validation", - " if (user_data == NULL || user_data->id == NULL || user_data->name == NULL) {", - " fprintf(stderr, \"Invalid user data\\n\");", - " User empty = {NULL, NULL};", - " return empty;", - " }", - " ", - " // Process user data", - " User user;", - " user.id = strdup(user_data->id);", - " user.name = strdup(user_data->name);", - " return user;", - "}", - "", - "void save_user(UserService* service, User user) {", - " // Save user to database", - " if (service->count >= service->capacity) {", - " service->capacity *= 2;", - " service->users = realloc(service->users, service->capacity * sizeof(User));", - " }", - " service->users[service->count++] = user;", - " printf(\"User saved successfully\\n\");", - "}" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CSharpExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CSharpExamples.swift deleted file mode 100644 index 78c550d2f..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CSharpExamples.swift +++ /dev/null @@ -1,506 +0,0 @@ -// -// CSharpExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-07-14. -// - -import Foundation - -/** - * CSharpExamples implements CodeExamples for C#-specific snippets. - */ -public struct CSharpExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" class - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "public class User", - "{", - "public Guid Id { get; set; }", - "public string Name { get; set; }", - "}" - ] - } else { - [ - "public class User", - "{", - " public Guid Id { get; set; }", - " public string Name { get; set; }", - "}" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "public class User", - "{", - "public Guid Id { get; set; }", - "public string Name { get; set; }", - "public string Email { get; set; }", - "}" - ] - } else { - [ - "public class User", - "{", - " public Guid Id { get; set; }", - " public string Name { get; set; }", - " public string Email { get; set; }", - "}" - ] - } - } - - // MARK: 2) Rewrite Entire File with an "Email" property - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "using System;", - "", - "namespace Models", - "{", - "public class User", - "{", - "public Guid Id { get; set; }", - "public string Name { get; set; }", - "public string Email { get; set; }", - "", - "public User(string name, string email)", - "{", - "Id = Guid.NewGuid();", - "Name = name;", - "Email = email;", - "}", - "}", - "}" - ] - } else { - [ - "using System;", - "", - "namespace Models", - "{", - " public class User", - " {", - " public Guid Id { get; set; }", - " public string Name { get; set; }", - " public string Email { get; set; }", - "", - " public User(string name, string email)", - " {", - " Id = Guid.NewGuid();", - " Name = name;", - " Email = email;", - " }", - " }", - "}" - ] - } - } - - // MARK: 3) Create a new "RoundedButton" file - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "using System.Windows;", - "using System.Windows.Controls;", - "", - "namespace Views", - "{", - "public class RoundedButton : Button", - "{", - "public static readonly DependencyProperty CornerRadiusProperty =", - "DependencyProperty.Register(\"CornerRadius\", typeof(double), typeof(RoundedButton));", - "", - "public double CornerRadius", - "{", - "get { return (double)GetValue(CornerRadiusProperty); }", - "set { SetValue(CornerRadiusProperty, value); }", - "}", - "}", - "}" - ] - } else { - [ - "using System.Windows;", - "using System.Windows.Controls;", - "", - "namespace Views", - "{", - " public class RoundedButton : Button", - " {", - " public static readonly DependencyProperty CornerRadiusProperty =", - " DependencyProperty.Register(\"CornerRadius\", typeof(double), typeof(RoundedButton));", - "", - " public double CornerRadius", - " {", - " get { return (double)GetValue(CornerRadiusProperty); }", - " set { SetValue(CornerRadiusProperty, value); }", - " }", - " }", - "}" - ] - } - } - - // MARK: 4) NetworkManager async/await conversion - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "public void FetchData(string url, Action completion)", - "{", - "using (var client = new WebClient())", - "{", - "client.DownloadStringCompleted += (sender, e) =>", - "{", - "completion(e.Result);", - "};", - "client.DownloadStringAsync(new Uri(url));", - "}", - "}" - ] - } else { - [ - "public void FetchData(string url, Action completion)", - "{", - " using (var client = new WebClient())", - " {", - " client.DownloadStringCompleted += (sender, e) =>", - " {", - " completion(e.Result);", - " };", - " client.DownloadStringAsync(new Uri(url));", - " }", - "}" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "public async Task FetchData(string url)", - "{", - "using (var client = new HttpClient())", - "{", - "return await client.GetStringAsync(url);", - "}", - "}" - ] - } else { - [ - "public async Task FetchData(string url)", - "{", - " using (var client = new HttpClient())", - " {", - " return await client.GetStringAsync(url);", - " }", - "}" - ] - } - } - - // MARK: Negative Examples - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "namespace Services", - "{", - "public class UserService", - "{", - "private readonly ILogger _logger;", - "", - "public void ProcessUser(User user)", - "{", - "if (user == null)", - "{", - "throw new ArgumentNullException(nameof(user));", - "}", - "_logger.LogInfo($\"Processing user: {user.Name}\");", - "}", - "}", - "}" - ] - } else { - [ - "namespace Services", - "{", - " public class UserService", - " {", - " private readonly ILogger _logger;", - "", - " public void ProcessUser(User user)", - " {", - " if (user == null)", - " {", - " throw new ArgumentNullException(nameof(user));", - " }", - " _logger.LogInfo($\"Processing user: {user.Name}\");", - " }", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - // Intentionally mismatched - missing braces and different indentation - if includeIndentation { - [ - "if (user == null)", - "throw new ArgumentNullException(nameof(user));" - ] - } else { - [ - " if (user == null)", - " throw new ArgumentNullException(nameof(user));" - ] - } - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "if (user == null || string.IsNullOrEmpty(user.Name))", - "{", - "throw new ArgumentException(\"User is invalid\");", - "}" - ] - } else { - [ - " if (user == null || string.IsNullOrEmpty(user.Name))", - " {", - " throw new ArgumentException(\"User is invalid\");", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - userSearchReplaceNegativeExampleFileContents(includeIndentation: includeIndentation) - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "_logger.LogInfo($\"Processing user: {user.Name}\");", - "}" - ] - } else { - [ - " }", - " _logger.LogInfo($\"Processing user: {user.Name}\");", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - // Extra closing brace added - if includeIndentation { - [ - "}", - "_logger.LogInfo($\"Processing user: {user.Name}\");", - "// Additional processing", - "ValidateUser(user);", - "}", - "}" // Extra brace - ] - } else { - [ - " }", - " _logger.LogInfo($\"Processing user: {user.Name}\");", - " // Additional processing", - " ValidateUser(user);", - " }", - " }" // Extra brace - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["_logger.LogInfo($\"Processing user: {user.Name}\");"] - } else { - [" _logger.LogInfo($\"Processing user: {user.Name}\");"] - } - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["_logger.LogDebug($\"Processing user: {user.Name} with ID: {user.Id}\");"] - } else { - [" _logger.LogDebug($\"Processing user: {user.Name} with ID: {user.Id}\");"] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - // Just closing braces - ambiguous - if includeIndentation { - [ - "}", - "}" - ] - } else { - [ - " }", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "// TODO: Add more processing", - "}" - ] - } else { - [ - " }", - " // TODO: Add more processing", - " }" - ] - } - } - - public func commentSyntax() -> String { - "//" - } - - // File editor examples use default implementation from protocol extension - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - "using System;", - "using System.Collections.Generic;", - "", - "namespace Services", - "{", - " public class UserService", - " {", - " private List users;", - " ", - " public UserService()", - " {", - " users = new List();", - " }", - " ", - " public User ProcessUser(UserData userData)", - " {", - " // Process user data", - " var user = new User", - " {", - " Id = userData.Id,", - " Name = userData.Name", - " };", - " return user;", - " }", - " ", - " public void SaveUser(User user)", - " {", - " // Save user to database", - " users.Add(user);", - " }", - " }", - "}" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - " public User ProcessUser(UserData userData)", - " {", - " // Add validation", - " if (userData == null || string.IsNullOrEmpty(userData.Id) || string.IsNullOrEmpty(userData.Name))", - " {", - " throw new ArgumentException(\"Invalid user data\");", - " }", - " ", - " // ... existing code ...", - " }" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - " public void SaveUser(User user)", - " {", - " try", - " {", - " // ... existing code ...", - " Console.WriteLine(\"User saved successfully\");", - " }", - " catch (Exception ex)", - " {", - " Console.Error.WriteLine($\"Failed to save user: {ex.Message}\");", - " throw;", - " }", - " }" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "using System;", - "using System.Collections.Generic;", - "", - "namespace Services", - "{", - " public class UserService", - " {", - " private List users;", - " ", - " public UserService()", - " {", - " users = new List();", - " }", - " ", - " public User ProcessUser(UserData userData)", - " {", - " // Add validation", - " if (userData == null || string.IsNullOrEmpty(userData.Id) || string.IsNullOrEmpty(userData.Name))", - " {", - " throw new ArgumentException(\"Invalid user data\");", - " }", - " ", - " // Process user data", - " var user = new User", - " {", - " Id = userData.Id,", - " Name = userData.Name", - " };", - " return user;", - " }", - " ", - " public void SaveUser(User user)", - " {", - " try", - " {", - " // Save user to database", - " users.Add(user);", - " Console.WriteLine(\"User saved successfully\");", - " }", - " catch (Exception ex)", - " {", - " Console.Error.WriteLine($\"Failed to save user: {ex.Message}\");", - " throw;", - " }", - " }", - " }", - "}" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CodeExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CodeExamples.swift deleted file mode 100644 index 839f88b75..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CodeExamples.swift +++ /dev/null @@ -1,242 +0,0 @@ -// -// CodeExamples.swift -// RepoPrompt -// -// Created by Eric Provencher on 2024-12-28. -// - -import Foundation - -/** - * CodeExamples protocol is a generic interface for providing code snippets - * in various languages. SwiftExamples is our Swift-specific implementation. - */ -public protocol CodeExamples { - func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] - func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] - - func userRewriteAllLines(includeIndentation: Bool) -> [String] - func userCreateAllLines(includeIndentation: Bool) -> [String] - - func networkManagerOldLines(includeIndentation: Bool) -> [String] - func networkManagerNewLines(includeIndentation: Bool) -> [String] - - // Negative example for search/replace (mismatched search block) - func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] - func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] - func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] - - // Additional negative example for mismatched braces - func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] - func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] - func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] - - /// New negative example: one-line search block (should be avoided) - func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] - /// New block for one-line negative example (content must match search block) - func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] - - /// New negative example: ambiguous search block (should be avoided) - func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] - /// New block for ambiguous negative example (content must match search block) - func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] - - /// Returns the comment syntax for this language (e.g., "//" for Swift/C, "#" for Python) - func commentSyntax() -> String - - // File editor example methods - func fileEditorExampleFileContents() -> [String] - func fileEditorExampleChange1() -> [String] - func fileEditorExampleChange2() -> [String] - func fileEditorExampleSearchBlock() -> [String] - func fileEditorExampleContentBlock() -> [String] - func fileEditorExampleSearchBlock2() -> [String] - func fileEditorExampleContentBlock2() -> [String] - - // File editor rewrite-only example methods - func fileEditorRewriteExampleFileContents() -> [String] - func fileEditorRewriteExampleChange1() -> [String] - func fileEditorRewriteExampleChange2() -> [String] - func fileEditorRewriteExampleCompleteFile() -> [String] -} - -// MARK: - Default Implementations for File Editor Examples - -public extension CodeExamples { - /// Default implementations using generic JavaScript-like syntax - func fileEditorExampleFileContents() -> [String] { - [ - "class GameManager {", - " constructor() {", - " this.score = 0;", - " this.level = 1;", - " this.isRunning = false;", - " }", - " ", - " reset() {", - " this.score = 0;", - " this.level = 1;", - " this.isRunning = false;", - " }", - " ", - " checkProximity(position) {", - " // Calculate distance logic here", - " return 0.0;", - " }", - "}" - ] - } - - func fileEditorExampleChange1() -> [String] { - [ - " // ... existing code ...", - " this.isRunning = false;", - " ", - " console.log('GameManager initialized');", - " }", - " ", - " reset() {", - " // ... existing code ..." - ] - } - - func fileEditorExampleChange2() -> [String] { - [ - " // ... existing code ...", - " }", - " ", - " destroy() {", - " console.log('GameManager cleaned up');", - " }", - "}" - ] - } - - func fileEditorExampleSearchBlock() -> [String] { - [ - " this.isRunning = false;", - " }", - " ", - " reset() {" - ] - } - - func fileEditorExampleContentBlock() -> [String] { - [ - " this.isRunning = false;", - " ", - " console.log('GameManager initialized');", - " }", - " ", - " reset() {" - ] - } - - func fileEditorExampleSearchBlock2() -> [String] { - [ - " return 0.0;", - " }", - "}" - ] - } - - func fileEditorExampleContentBlock2() -> [String] { - [ - " return 0.0;", - " }", - " ", - " destroy() {", - " console.log('GameManager cleaned up');", - " }", - "}" - ] - } - - // MARK: - Rewrite-Only File Editor Example Methods - - func fileEditorRewriteExampleFileContents() -> [String] { - [ - "class UserService {", - " constructor() {", - " this.users = [];", - " }", - " ", - " processUser(userData) {", - " // Process user data", - " const user = {", - " id: userData.id,", - " name: userData.name", - " };", - " return user;", - " }", - " ", - " saveUser(user) {", - " // Save user to database", - " this.users.push(user);", - " }", - "}" - ] - } - - func fileEditorRewriteExampleChange1() -> [String] { - [ - " processUser(userData) {", - " // Add validation", - " if (!userData || !userData.id || !userData.name) {", - " throw new Error('Invalid user data');", - " }", - " ", - " // ... existing code ...", - " }" - ] - } - - func fileEditorRewriteExampleChange2() -> [String] { - [ - " saveUser(user) {", - " try {", - " // ... existing code ...", - " console.log('User saved successfully');", - " } catch (error) {", - " console.error('Failed to save user:', error);", - " throw error;", - " }", - " }" - ] - } - - func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "class UserService {", - " constructor() {", - " this.users = [];", - " }", - " ", - " processUser(userData) {", - " // Add validation", - " if (!userData || !userData.id || !userData.name) {", - " throw new Error('Invalid user data');", - " }", - " ", - " // Process user data", - " const user = {", - " id: userData.id,", - " name: userData.name", - " };", - " return user;", - " }", - " ", - " saveUser(user) {", - " try {", - " // Save user to database", - " this.users.push(user);", - " console.log('User saved successfully');", - " } catch (error) {", - " console.error('Failed to save user:', error);", - " throw error;", - " }", - " }", - "}" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CppExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CppExamples.swift deleted file mode 100644 index 55651931a..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/CppExamples.swift +++ /dev/null @@ -1,494 +0,0 @@ -// -// CppExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-07-14. -// - -import Foundation - -/** - * CppExamples implements CodeExamples for C++-specific snippets. - */ -public struct CppExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" class - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User {", - "public:", - "std::string id;", - "std::string name;", - "};" - ] - } else { - [ - "class User {", - "public:", - " std::string id;", - " std::string name;", - "};" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User {", - "public:", - "std::string id;", - "std::string name;", - "std::string email;", - "};" - ] - } else { - [ - "class User {", - "public:", - " std::string id;", - " std::string name;", - " std::string email;", - "};" - ] - } - } - - // MARK: 2) Rewrite Entire File with an "email" field - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "#pragma once", - "#include ", - "#include ", - "", - "class User {", - "private:", - "std::string id;", - "std::string name;", - "std::string email;", - "", - "public:", - "User(const std::string& name, const std::string& email) ", - ": name(name), email(email) {", - "// Generate UUID", - "uuid_t uuid;", - "uuid_generate(uuid);", - "char uuid_str[37];", - "uuid_unparse(uuid, uuid_str);", - "id = std::string(uuid_str);", - "}", - "", - "const std::string& getId() const { return id; }", - "const std::string& getName() const { return name; }", - "const std::string& getEmail() const { return email; }", - "};" - ] - } else { - [ - "#pragma once", - "#include ", - "#include ", - "", - "class User {", - "private:", - " std::string id;", - " std::string name;", - " std::string email;", - "", - "public:", - " User(const std::string& name, const std::string& email) ", - " : name(name), email(email) {", - " // Generate UUID", - " uuid_t uuid;", - " uuid_generate(uuid);", - " char uuid_str[37];", - " uuid_unparse(uuid, uuid_str);", - " id = std::string(uuid_str);", - " }", - "", - " const std::string& getId() const { return id; }", - " const std::string& getName() const { return name; }", - " const std::string& getEmail() const { return email; }", - "};" - ] - } - } - - // MARK: 3) Create a new "RoundedButton" file - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "#pragma once", - "#include ", - "", - "class RoundedButton : public QPushButton {", - "Q_OBJECT", - "Q_PROPERTY(double cornerRadius READ cornerRadius WRITE setCornerRadius)", - "", - "private:", - "double m_cornerRadius;", - "", - "public:", - "explicit RoundedButton(QWidget* parent = nullptr)", - ": QPushButton(parent), m_cornerRadius(0.0) {}", - "", - "double cornerRadius() const { return m_cornerRadius; }", - "void setCornerRadius(double radius) { ", - "m_cornerRadius = radius;", - "update();", - "}", - "};" - ] - } else { - [ - "#pragma once", - "#include ", - "", - "class RoundedButton : public QPushButton {", - " Q_OBJECT", - " Q_PROPERTY(double cornerRadius READ cornerRadius WRITE setCornerRadius)", - "", - "private:", - " double m_cornerRadius;", - "", - "public:", - " explicit RoundedButton(QWidget* parent = nullptr)", - " : QPushButton(parent), m_cornerRadius(0.0) {}", - "", - " double cornerRadius() const { return m_cornerRadius; }", - " void setCornerRadius(double radius) { ", - " m_cornerRadius = radius;", - " update();", - " }", - "};" - ] - } - } - - // MARK: 4) NetworkManager async/await conversion - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "void fetchData(const std::string& url, std::function completion) {", - "std::thread([url, completion]() {", - "// Simulated blocking network call", - "std::string response = performBlockingRequest(url);", - "completion(response);", - "}).detach();", - "}" - ] - } else { - [ - "void fetchData(const std::string& url, std::function completion) {", - " std::thread([url, completion]() {", - " // Simulated blocking network call", - " std::string response = performBlockingRequest(url);", - " completion(response);", - " }).detach();", - "}" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "std::future fetchData(const std::string& url) {", - "return std::async(std::launch::async, [url]() {", - "// Async network call", - "return performAsyncRequest(url);", - "});", - "}" - ] - } else { - [ - "std::future fetchData(const std::string& url) {", - " return std::async(std::launch::async, [url]() {", - " // Async network call", - " return performAsyncRequest(url);", - " });", - "}" - ] - } - } - - // MARK: Negative Examples - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "#include \"user_service.h\"", - "#include \"logger.h\"", - "", - "void UserService::processUser(User* user) {", - "if (user == nullptr) {", - "Logger::error(\"User is nullptr\");", - "return;", - "}", - "Logger::info(\"Processing user: \" + user->getName());", - "}" - ] - } else { - [ - "#include \"user_service.h\"", - "#include \"logger.h\"", - "", - "void UserService::processUser(User* user) {", - " if (user == nullptr) {", - " Logger::error(\"User is nullptr\");", - " return;", - " }", - " Logger::info(\"Processing user: \" + user->getName());", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - // Intentionally mismatched - missing braces - if includeIndentation { - [ - "if (user == nullptr)", - "Logger::error(\"User is nullptr\");" - ] - } else { - [ - " if (user == nullptr)", - " Logger::error(\"User is nullptr\");" - ] - } - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "if (user == nullptr || user->getName().empty()) {", - "Logger::error(\"User is invalid\");", - "throw std::invalid_argument(\"Invalid user\");", - "}" - ] - } else { - [ - " if (user == nullptr || user->getName().empty()) {", - " Logger::error(\"User is invalid\");", - " throw std::invalid_argument(\"Invalid user\");", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - userSearchReplaceNegativeExampleFileContents(includeIndentation: includeIndentation) - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "Logger::info(\"Processing user: \" + user->getName());", - "}" - ] - } else { - [ - " }", - " Logger::info(\"Processing user: \" + user->getName());", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - // Extra closing brace added - if includeIndentation { - [ - "}", - "Logger::info(\"Processing user: \" + user->getName());", - "// Additional validation", - "validateUser(user);", - "}", - "}" // Extra brace - ] - } else { - [ - " }", - " Logger::info(\"Processing user: \" + user->getName());", - " // Additional validation", - " validateUser(user);", - "}", - "}" // Extra brace - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["Logger::info(\"Processing user: \" + user->getName());"] - } else { - [" Logger::info(\"Processing user: \" + user->getName());"] - } - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["Logger::debug(\"Processing user: \" + user->getName() + \" (ID: \" + user->getId() + \")\");"] - } else { - [" Logger::debug(\"Processing user: \" + user->getName() + \" (ID: \" + user->getId() + \")\");"] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - // Just closing braces - ambiguous - if includeIndentation { - [ - "}", - "}" - ] - } else { - [ - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "// TODO: Add more processing", - "}" - ] - } else { - [ - " }", - " // TODO: Add more processing", - "}" - ] - } - } - - public func commentSyntax() -> String { - "//" - } - - // File editor examples use default implementation from protocol extension - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - "#include ", - "#include ", - "#include ", - "#include ", - "", - "struct User {", - " std::string id;", - " std::string name;", - "};", - "", - "class UserService {", - "private:", - " std::vector users;", - " ", - "public:", - " UserService() {", - " // Initialize service", - " }", - " ", - " User processUser(const User& userData) {", - " // Process user data", - " User user;", - " user.id = userData.id;", - " user.name = userData.name;", - " return user;", - " }", - " ", - " void saveUser(const User& user) {", - " // Save user to database", - " users.push_back(user);", - " }", - "};" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - " User processUser(const User& userData) {", - " // Add validation", - " if (userData.id.empty() || userData.name.empty()) {", - " throw std::invalid_argument(\"Invalid user data\");", - " }", - " ", - " // ... existing code ...", - " }" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - " void saveUser(const User& user) {", - " try {", - " // ... existing code ...", - " std::cout << \"User saved successfully\" << std::endl;", - " } catch (const std::exception& e) {", - " std::cerr << \"Failed to save user: \" << e.what() << std::endl;", - " throw;", - " }", - " }" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "#include ", - "#include ", - "#include ", - "#include ", - "", - "struct User {", - " std::string id;", - " std::string name;", - "};", - "", - "class UserService {", - "private:", - " std::vector users;", - " ", - "public:", - " UserService() {", - " // Initialize service", - " }", - " ", - " User processUser(const User& userData) {", - " // Add validation", - " if (userData.id.empty() || userData.name.empty()) {", - " throw std::invalid_argument(\"Invalid user data\");", - " }", - " ", - " // Process user data", - " User user;", - " user.id = userData.id;", - " user.name = userData.name;", - " return user;", - " }", - " ", - " void saveUser(const User& user) {", - " try {", - " // Save user to database", - " users.push_back(user);", - " std::cout << \"User saved successfully\" << std::endl;", - " } catch (const std::exception& e) {", - " std::cerr << \"Failed to save user: \" << e.what() << std::endl;", - " throw;", - " }", - " }", - "};" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/DartExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/DartExamples.swift deleted file mode 100644 index c4aa93d8d..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/DartExamples.swift +++ /dev/null @@ -1,522 +0,0 @@ -// -// DartExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-07-14. -// - -import Foundation - -/** - * DartExamples implements CodeExamples for Dart-specific snippets. - */ -public struct DartExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" class - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User {", - "final String id;", - "final String name;", - "}" - ] - } else { - [ - "class User {", - " final String id;", - " final String name;", - "}" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User {", - "final String id;", - "final String name;", - "final String email;", - "}" - ] - } else { - [ - "class User {", - " final String id;", - " final String name;", - " final String email;", - "}" - ] - } - } - - // MARK: 2) Rewrite Entire File with an "Email" property - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User {", - "final String id;", - "final String name;", - "final String email;", - "", - "User({required this.id, required this.name, required this.email});", - "", - "factory User.fromJson(Map json) {", - "return User(", - "id: json['id'],", - "name: json['name'],", - "email: json['email'],", - ");", - "}", - "", - "Map toJson() {", - "return {", - "'id': id,", - "'name': name,", - "'email': email,", - "};", - "}", - "}" - ] - } else { - [ - "class User {", - " final String id;", - " final String name;", - " final String email;", - "", - " User({required this.id, required this.name, required this.email});", - "", - " factory User.fromJson(Map json) {", - " return User(", - " id: json['id'],", - " name: json['name'],", - " email: json['email'],", - " );", - " }", - "", - " Map toJson() {", - " return {", - " 'id': id,", - " 'name': name,", - " 'email': email,", - " };", - " }", - "}" - ] - } - } - - // MARK: 3) Create a new "RoundedButton" widget - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import 'package:flutter/material.dart';", - "", - "class RoundedButton extends StatelessWidget {", - "final String text;", - "final VoidCallback? onPressed;", - "final double borderRadius;", - "final Color backgroundColor;", - "", - "const RoundedButton({", - "Key? key,", - "required this.text,", - "this.onPressed,", - "this.borderRadius = 8.0,", - "this.backgroundColor = Colors.blue,", - "}) : super(key: key);", - "", - "@override", - "Widget build(BuildContext context) {", - "return ElevatedButton(", - "onPressed: onPressed,", - "style: ElevatedButton.styleFrom(", - "backgroundColor: backgroundColor,", - "shape: RoundedRectangleBorder(", - "borderRadius: BorderRadius.circular(borderRadius),", - "),", - "),", - "child: Text(text),", - ");", - "}", - "}" - ] - } else { - [ - "import 'package:flutter/material.dart';", - "", - "class RoundedButton extends StatelessWidget {", - " final String text;", - " final VoidCallback? onPressed;", - " final double borderRadius;", - " final Color backgroundColor;", - "", - " const RoundedButton({", - " Key? key,", - " required this.text,", - " this.onPressed,", - " this.borderRadius = 8.0,", - " this.backgroundColor = Colors.blue,", - " }) : super(key: key);", - "", - " @override", - " Widget build(BuildContext context) {", - " return ElevatedButton(", - " onPressed: onPressed,", - " style: ElevatedButton.styleFrom(", - " backgroundColor: backgroundColor,", - " shape: RoundedRectangleBorder(", - " borderRadius: BorderRadius.circular(borderRadius),", - " ),", - " ),", - " child: Text(text),", - " );", - " }", - "}" - ] - } - } - - // MARK: 4) NetworkManager async/await conversion - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "void fetchData(String url, Function(String) completion) {", - "http.get(Uri.parse(url)).then((response) {", - "if (response.statusCode == 200) {", - "completion(response.body);", - "} else {", - "completion('Error: ${response.statusCode}');", - "}", - "}).catchError((error) {", - "completion('Error: $error');", - "});", - "}" - ] - } else { - [ - "void fetchData(String url, Function(String) completion) {", - " http.get(Uri.parse(url)).then((response) {", - " if (response.statusCode == 200) {", - " completion(response.body);", - " } else {", - " completion('Error: ${response.statusCode}');", - " }", - " }).catchError((error) {", - " completion('Error: $error');", - " });", - "}" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "Future fetchData(String url) async {", - "try {", - "final response = await http.get(Uri.parse(url));", - "if (response.statusCode == 200) {", - "return response.body;", - "} else {", - "throw Exception('HTTP Error: ${response.statusCode}');", - "}", - "} catch (error) {", - "throw Exception('Network Error: $error');", - "}", - "}" - ] - } else { - [ - "Future fetchData(String url) async {", - " try {", - " final response = await http.get(Uri.parse(url));", - " if (response.statusCode == 200) {", - " return response.body;", - " } else {", - " throw Exception('HTTP Error: ${response.statusCode}');", - " }", - " } catch (error) {", - " throw Exception('Network Error: $error');", - " }", - "}" - ] - } - } - - // MARK: Negative Examples - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class UserService {", - "final ApiClient _apiClient;", - "", - "UserService(this._apiClient);", - "", - "Future processUser(User user) async {", - "if (user == null) {", - "throw ArgumentError('User cannot be null');", - "}", - "print('Processing user: ${user.name}');", - "}", - "}" - ] - } else { - [ - "class UserService {", - " final ApiClient _apiClient;", - "", - " UserService(this._apiClient);", - "", - " Future processUser(User user) async {", - " if (user == null) {", - " throw ArgumentError('User cannot be null');", - " }", - " print('Processing user: ${user.name}');", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - // Intentionally mismatched - missing braces and different indentation - if includeIndentation { - [ - "if (user == null)", - "throw ArgumentError('User cannot be null');" - ] - } else { - [ - " if (user == null)", - " throw ArgumentError('User cannot be null');" - ] - } - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "if (user == null || user.name.isEmpty) {", - "throw ArgumentError('User is invalid');", - "}" - ] - } else { - [ - " if (user == null || user.name.isEmpty) {", - " throw ArgumentError('User is invalid');", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - userSearchReplaceNegativeExampleFileContents(includeIndentation: includeIndentation) - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "print('Processing user: ${user.name}');", - "}" - ] - } else { - [ - " }", - " print('Processing user: ${user.name}');", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - // Extra closing brace added - if includeIndentation { - [ - "}", - "print('Processing user: ${user.name}');", - "// Additional processing", - "validateUser(user);", - "}", - "}" // Extra brace - ] - } else { - [ - " }", - " print('Processing user: ${user.name}');", - " // Additional processing", - " validateUser(user);", - " }", - " }" // Extra brace - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["print('Processing user: ${user.name}');"] - } else { - [" print('Processing user: ${user.name}');"] - } - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["print('Processing user: ${user.name} with ID: ${user.id}');"] - } else { - [" print('Processing user: ${user.name} with ID: ${user.id}');"] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - // Just closing braces - ambiguous - if includeIndentation { - [ - "}", - "}" - ] - } else { - [ - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "// TODO: Add more processing", - "}" - ] - } else { - [ - " }", - " // TODO: Add more processing", - "}" - ] - } - } - - public func commentSyntax() -> String { - "//" - } - - // File editor examples use default implementation from protocol extension - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - "class User {", - " final String id;", - " final String name;", - " ", - " User({required this.id, required this.name});", - "}", - "", - "class UserService {", - " final List _users = [];", - " ", - " UserService() {", - " // Initialize service", - " }", - " ", - " User processUser(Map userData) {", - " // Process user data", - " final user = User(", - " id: userData['id'],", - " name: userData['name'],", - " );", - " return user;", - " }", - " ", - " void saveUser(User user) {", - " // Save user to database", - " _users.add(user);", - " }", - "}" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - " User processUser(Map userData) {", - " // Add validation", - " if (userData['id'] == null || userData['name'] == null ||", - " userData['id'].isEmpty || userData['name'].isEmpty) {", - " throw ArgumentError('Invalid user data');", - " }", - " ", - " // ... existing code ...", - " }" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - " void saveUser(User user) {", - " try {", - " // ... existing code ...", - " print('User saved successfully');", - " } catch (e) {", - " print('Failed to save user: $e');", - " rethrow;", - " }", - " }" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "class User {", - " final String id;", - " final String name;", - " ", - " User({required this.id, required this.name});", - "}", - "", - "class UserService {", - " final List _users = [];", - " ", - " UserService() {", - " // Initialize service", - " }", - " ", - " User processUser(Map userData) {", - " // Add validation", - " if (userData['id'] == null || userData['name'] == null ||", - " userData['id'].isEmpty || userData['name'].isEmpty) {", - " throw ArgumentError('Invalid user data');", - " }", - " ", - " // Process user data", - " final user = User(", - " id: userData['id'],", - " name: userData['name'],", - " );", - " return user;", - " }", - " ", - " void saveUser(User user) {", - " try {", - " // Save user to database", - " _users.add(user);", - " print('User saved successfully');", - " } catch (e) {", - " print('Failed to save user: $e');", - " rethrow;", - " }", - " }", - "}" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/GoExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/GoExamples.swift deleted file mode 100644 index 88500a9dc..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/GoExamples.swift +++ /dev/null @@ -1,507 +0,0 @@ -// -// GoExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-07-14. -// - -import Foundation - -/** - * GoExamples implements CodeExamples for Go-specific snippets. - */ -public struct GoExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" struct - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "type User struct {", - "ID string `json:\"id\"`", - "Name string `json:\"name\"`", - "}" - ] - } else { - [ - "type User struct {", - "\tID string `json:\"id\"`", - "\tName string `json:\"name\"`", - "}" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "type User struct {", - "ID string `json:\"id\"`", - "Name string `json:\"name\"`", - "Email string `json:\"email\"`", - "}" - ] - } else { - [ - "type User struct {", - "\tID string `json:\"id\"`", - "\tName string `json:\"name\"`", - "\tEmail string `json:\"email\"`", - "}" - ] - } - } - - // MARK: 2) Rewrite Entire File with an "email" field - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "package models", - "", - "import (", - "\"github.com/google/uuid\"", - ")", - "", - "// User represents a user in the system", - "type User struct {", - "ID string `json:\"id\"`", - "Name string `json:\"name\"`", - "Email string `json:\"email\"`", - "}", - "", - "// NewUser creates a new user with the given name and email", - "func NewUser(name, email string) *User {", - "return &User{", - "ID: uuid.New().String(),", - "Name: name,", - "Email: email,", - "}", - "}" - ] - } else { - [ - "package models", - "", - "import (", - "\t\"github.com/google/uuid\"", - ")", - "", - "// User represents a user in the system", - "type User struct {", - "\tID string `json:\"id\"`", - "\tName string `json:\"name\"`", - "\tEmail string `json:\"email\"`", - "}", - "", - "// NewUser creates a new user with the given name and email", - "func NewUser(name, email string) *User {", - "\treturn &User{", - "\t\tID: uuid.New().String(),", - "\t\tName: name,", - "\t\tEmail: email,", - "\t}", - "}" - ] - } - } - - // MARK: 3) Create a new "RoundedButton" file - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "package views", - "", - "import (", - "\"fyne.io/fyne/v2\"", - "\"fyne.io/fyne/v2/widget\"", - ")", - "", - "// RoundedButton is a button with configurable corner radius", - "type RoundedButton struct {", - "widget.Button", - "cornerRadius float32", - "}", - "", - "// NewRoundedButton creates a new rounded button", - "func NewRoundedButton(label string, tapped func()) *RoundedButton {", - "btn := &RoundedButton{", - "Button: *widget.NewButton(label, tapped),", - "cornerRadius: 0.0,", - "}", - "return btn", - "}", - "", - "// SetCornerRadius sets the corner radius", - "func (b *RoundedButton) SetCornerRadius(radius float32) {", - "b.cornerRadius = radius", - "b.Refresh()", - "}" - ] - } else { - [ - "package views", - "", - "import (", - "\t\"fyne.io/fyne/v2\"", - "\t\"fyne.io/fyne/v2/widget\"", - ")", - "", - "// RoundedButton is a button with configurable corner radius", - "type RoundedButton struct {", - "\twidget.Button", - "\tcornerRadius float32", - "}", - "", - "// NewRoundedButton creates a new rounded button", - "func NewRoundedButton(label string, tapped func()) *RoundedButton {", - "\tbtn := &RoundedButton{", - "\t\tButton: *widget.NewButton(label, tapped),", - "\t\tcornerRadius: 0.0,", - "\t}", - "\treturn btn", - "}", - "", - "// SetCornerRadius sets the corner radius", - "func (b *RoundedButton) SetCornerRadius(radius float32) {", - "\tb.cornerRadius = radius", - "\tb.Refresh()", - "}" - ] - } - } - - // MARK: 4) NetworkManager async/await conversion - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "func fetchData(url string, completion func(string)) {", - "resp, err := http.Get(url)", - "if err != nil {", - "completion(\"\")", - "return", - "}", - "defer resp.Body.Close()", - "body, _ := ioutil.ReadAll(resp.Body)", - "completion(string(body))", - "}" - ] - } else { - [ - "func fetchData(url string, completion func(string)) {", - "\tresp, err := http.Get(url)", - "\tif err != nil {", - "\t\tcompletion(\"\")", - "\t\treturn", - "\t}", - "\tdefer resp.Body.Close()", - "\tbody, _ := ioutil.ReadAll(resp.Body)", - "\tcompletion(string(body))", - "}" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "func fetchData(ctx context.Context, url string) (string, error) {", - "req, err := http.NewRequestWithContext(ctx, \"GET\", url, nil)", - "if err != nil {", - "return \"\", err", - "}", - "resp, err := http.DefaultClient.Do(req)", - "if err != nil {", - "return \"\", err", - "}", - "defer resp.Body.Close()", - "body, err := io.ReadAll(resp.Body)", - "return string(body), err", - "}" - ] - } else { - [ - "func fetchData(ctx context.Context, url string) (string, error) {", - "\treq, err := http.NewRequestWithContext(ctx, \"GET\", url, nil)", - "\tif err != nil {", - "\t\treturn \"\", err", - "\t}", - "\tresp, err := http.DefaultClient.Do(req)", - "\tif err != nil {", - "\t\treturn \"\", err", - "\t}", - "\tdefer resp.Body.Close()", - "\tbody, err := io.ReadAll(resp.Body)", - "\treturn string(body), err", - "}" - ] - } - } - - // MARK: Negative Examples - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "package services", - "", - "import \"log\"", - "", - "func processUser(user *User) {", - "if user == nil {", - "log.Println(\"User is nil\")", - "return", - "}", - "log.Printf(\"Processing user: %s\\n\", user.Name)", - "}" - ] - } else { - [ - "package services", - "", - "import \"log\"", - "", - "func processUser(user *User) {", - "\tif user == nil {", - "\t\tlog.Println(\"User is nil\")", - "\t\treturn", - "\t}", - "\tlog.Printf(\"Processing user: %s\\n\", user.Name)", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - // Intentionally mismatched - missing braces - if includeIndentation { - [ - "if user == nil", - "log.Println(\"User is nil\")" - ] - } else { - [ - "\tif user == nil", - "\t\tlog.Println(\"User is nil\")" - ] - } - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "if user == nil || user.Name == \"\" {", - "log.Println(\"User is invalid\")", - "panic(\"invalid user\")", - "}" - ] - } else { - [ - "\tif user == nil || user.Name == \"\" {", - "\t\tlog.Println(\"User is invalid\")", - "\t\tpanic(\"invalid user\")", - "\t}" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - userSearchReplaceNegativeExampleFileContents(includeIndentation: includeIndentation) - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "log.Printf(\"Processing user: %s\\n\", user.Name)", - "}" - ] - } else { - [ - "\t}", - "\tlog.Printf(\"Processing user: %s\\n\", user.Name)", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - // Extra closing brace added - if includeIndentation { - [ - "}", - "log.Printf(\"Processing user: %s\\n\", user.Name)", - "// Additional validation", - "validateUser(user)", - "}", - "}" // Extra brace - ] - } else { - [ - "\t}", - "\tlog.Printf(\"Processing user: %s\\n\", user.Name)", - "\t// Additional validation", - "\tvalidateUser(user)", - "}", - "}" // Extra brace - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["log.Printf(\"Processing user: %s\\n\", user.Name)"] - } else { - ["\tlog.Printf(\"Processing user: %s\\n\", user.Name)"] - } - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["log.Printf(\"Processing user: %s (ID: %s)\\n\", user.Name, user.ID)"] - } else { - ["\tlog.Printf(\"Processing user: %s (ID: %s)\\n\", user.Name, user.ID)"] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - // Just closing braces - ambiguous - if includeIndentation { - [ - "}", - "}" - ] - } else { - [ - "\t}", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "// TODO: Add more processing", - "}" - ] - } else { - [ - "\t}", - "\t// TODO: Add more processing", - "}" - ] - } - } - - public func commentSyntax() -> String { - "//" - } - - // File editor examples use default implementation from protocol extension - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - "package services", - "", - "import (", - " \"errors\"", - " \"fmt\"", - ")", - "", - "type UserService struct {", - " users []User", - "}", - "", - "func NewUserService() *UserService {", - " return &UserService{", - " users: make([]User, 0),", - " }", - "}", - "", - "func (s *UserService) ProcessUser(userData UserData) (User, error) {", - " // Process user data", - " user := User{", - " ID: userData.ID,", - " Name: userData.Name,", - " }", - " return user, nil", - "}", - "", - "func (s *UserService) SaveUser(user User) error {", - " // Save user to database", - " s.users = append(s.users, user)", - " return nil", - "}" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - "func (s *UserService) ProcessUser(userData UserData) (User, error) {", - " // Add validation", - " if userData.ID == \"\" || userData.Name == \"\" {", - " return User{}, errors.New(\"invalid user data\")", - " }", - " ", - " // ... existing code ...", - "}" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - "func (s *UserService) SaveUser(user User) error {", - " // ... existing code ...", - " fmt.Println(\"User saved successfully\")", - " return nil", - "}" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "package services", - "", - "import (", - " \"errors\"", - " \"fmt\"", - ")", - "", - "type UserService struct {", - " users []User", - "}", - "", - "func NewUserService() *UserService {", - " return &UserService{", - " users: make([]User, 0),", - " }", - "}", - "", - "func (s *UserService) ProcessUser(userData UserData) (User, error) {", - " // Add validation", - " if userData.ID == \"\" || userData.Name == \"\" {", - " return User{}, errors.New(\"invalid user data\")", - " }", - " ", - " // Process user data", - " user := User{", - " ID: userData.ID,", - " Name: userData.Name,", - " }", - " return user, nil", - "}", - "", - "func (s *UserService) SaveUser(user User) error {", - " // Save user to database", - " s.users = append(s.users, user)", - " fmt.Println(\"User saved successfully\")", - " return nil", - "}" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/JavaExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/JavaExamples.swift deleted file mode 100644 index 95bdddbf9..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/JavaExamples.swift +++ /dev/null @@ -1,533 +0,0 @@ -// -// JavaExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-01-14. -// - -import Foundation - -/** - * JavaExamples implements CodeExamples for Java-specific snippets. - */ -public struct JavaExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" class - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "public class User {", - "private UUID id;", - "private String name;", - "}" - ] - } else { - [ - "public class User {", - " private UUID id;", - " private String name;", - "}" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "public class User {", - "private UUID id;", - "private String name;", - "private String email;", - "}" - ] - } else { - [ - "public class User {", - " private UUID id;", - " private String name;", - " private String email;", - "}" - ] - } - } - - // MARK: 2) Rewrite Entire File with an "email" field - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import java.util.UUID;", - "", - "public class User {", - "private final UUID id;", - "private String name;", - "private String email;", - "", - "public User(String name, String email) {", - "this.id = UUID.randomUUID();", - "this.name = name;", - "this.email = email;", - "}", - "", - "public UUID getId() {", - "return id;", - "}", - "", - "public String getName() {", - "return name;", - "}", - "", - "public void setName(String name) {", - "this.name = name;", - "}", - "", - "public String getEmail() {", - "return email;", - "}", - "", - "public void setEmail(String email) {", - "this.email = email;", - "}", - "}" - ] - } else { - [ - "import java.util.UUID;", - "", - "public class User {", - " private final UUID id;", - " private String name;", - " private String email;", - " ", - " public User(String name, String email) {", - " this.id = UUID.randomUUID();", - " this.name = name;", - " this.email = email;", - " }", - " ", - " public UUID getId() {", - " return id;", - " }", - " ", - " public String getName() {", - " return name;", - " }", - " ", - " public void setName(String name) {", - " this.name = name;", - " }", - " ", - " public String getEmail() {", - " return email;", - " }", - " ", - " public void setEmail(String email) {", - " this.email = email;", - " }", - "}" - ] - } - } - - // MARK: 3) Create a new "RoundedButton" file - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import javax.swing.JButton;", - "import java.awt.Graphics;", - "import java.awt.Graphics2D;", - "import java.awt.RenderingHints;", - "", - "public class RoundedButton extends JButton {", - "private int cornerRadius = 0;", - "", - "public RoundedButton(String text) {", - "super(text);", - "setOpaque(false);", - "}", - "", - "public void setCornerRadius(int cornerRadius) {", - "this.cornerRadius = cornerRadius;", - "repaint();", - "}", - "", - "@Override", - "protected void paintComponent(Graphics g) {", - "Graphics2D g2 = (Graphics2D) g.create();", - "g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);", - "g2.fillRoundRect(0, 0, getWidth()-1, getHeight()-1, cornerRadius, cornerRadius);", - "super.paintComponent(g2);", - "g2.dispose();", - "}", - "}" - ] - } else { - [ - "import javax.swing.JButton;", - "import java.awt.Graphics;", - "import java.awt.Graphics2D;", - "import java.awt.RenderingHints;", - "", - "public class RoundedButton extends JButton {", - " private int cornerRadius = 0;", - " ", - " public RoundedButton(String text) {", - " super(text);", - " setOpaque(false);", - " }", - " ", - " public void setCornerRadius(int cornerRadius) {", - " this.cornerRadius = cornerRadius;", - " repaint();", - " }", - " ", - " @Override", - " protected void paintComponent(Graphics g) {", - " Graphics2D g2 = (Graphics2D) g.create();", - " g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);", - " g2.fillRoundRect(0, 0, getWidth()-1, getHeight()-1, cornerRadius, cornerRadius);", - " super.paintComponent(g2);", - " g2.dispose();", - " }", - "}" - ] - } - } - - // MARK: 4) Indentation-Preserving Example (async/await equivalent using CompletableFuture) - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "public class NetworkManager {", - "public void fetchData(URL url, Consumer callback) {", - "// old synchronous code", - "}", - "}" - ] - } else { - [ - "public class NetworkManager {", - " public void fetchData(URL url, Consumer callback) {", - " // old synchronous code", - " }", - "}" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "public class NetworkManager {", - "public CompletableFuture fetchData(URL url) {", - "return CompletableFuture.supplyAsync(() -> {", - "try {", - "HttpURLConnection conn = (HttpURLConnection) url.openConnection();", - "return conn.getInputStream().readAllBytes();", - "} catch (IOException e) {", - "throw new CompletionException(e);", - "}", - "});", - "}", - "}" - ] - } else { - [ - "public class NetworkManager {", - " public CompletableFuture fetchData(URL url) {", - " return CompletableFuture.supplyAsync(() -> {", - " try {", - " HttpURLConnection conn = (HttpURLConnection) url.openConnection();", - " return conn.getInputStream().readAllBytes();", - " } catch (IOException e) {", - " throw new CompletionException(e);", - " }", - " });", - " }", - "}" - ] - } - } - - // MARK: - Negative Examples for Search/Replace - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import java.util.*;", - "public class Example {", - " void foo() {", - " bar();", - " }", - "}" - ] - } else { - [ - "import java.util.*;", - "public class Example {", - " void foo() {", - " bar();", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "void foo() {", - "bar();", - "}" - ] - } else { - [ - " void foo() {", - " bar();", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "void foo() {", - "bar();", - "bar2();", - "}" - ] - } else { - [ - " void foo() {", - " bar();", - " bar2();", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "public void someMethod() {", - "foo() {", - "bar();", - "}", - "}" - ] - } else { - [ - "public void someMethod() {", - " foo() {", - " bar();", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo() {", - "bar();", - "}" - ] - } else { - [ - " foo() {", - " bar();", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo() {", - "bar();", - "}", - "", - "baz() {", - "foo2();", - "}", - "}" - ] - } else { - [ - " foo() {", - " bar();", - " }", - "", - " baz() {", - " foo2();", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "private String email;" - ] - } else { - [ - "private String email;" - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "private String emailAddress;" - ] - } else { - [ - "private String emailAddress;" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "}" - ] - } else { - [ - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo() {", - "}", - "}", - "}" - ] - } else { - [ - " foo() {", - " }", - " }", - "}" - ] - } - } - - public func commentSyntax() -> String { - "//" - } - - // File editor examples use default implementation from protocol extension - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - "import java.util.ArrayList;", - "import java.util.List;", - "", - "public class UserService {", - " private List users;", - " ", - " public UserService() {", - " this.users = new ArrayList<>();", - " }", - " ", - " public User processUser(UserData userData) {", - " // Process user data", - " User user = new User(", - " userData.getId(),", - " userData.getName()", - " );", - " return user;", - " }", - " ", - " public void saveUser(User user) {", - " // Save user to database", - " users.add(user);", - " }", - "}" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - " public User processUser(UserData userData) {", - " // Add validation", - " if (userData == null || userData.getId() == null || userData.getName() == null) {", - " throw new IllegalArgumentException(\"Invalid user data\");", - " }", - " ", - " // ... existing code ...", - " }" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - " public void saveUser(User user) {", - " try {", - " // ... existing code ...", - " System.out.println(\"User saved successfully\");", - " } catch (Exception e) {", - " System.err.println(\"Failed to save user: \" + e.getMessage());", - " throw e;", - " }", - " }" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "import java.util.ArrayList;", - "import java.util.List;", - "", - "public class UserService {", - " private List users;", - " ", - " public UserService() {", - " this.users = new ArrayList<>();", - " }", - " ", - " public User processUser(UserData userData) {", - " // Add validation", - " if (userData == null || userData.getId() == null || userData.getName() == null) {", - " throw new IllegalArgumentException(\"Invalid user data\");", - " }", - " ", - " // Process user data", - " User user = new User(", - " userData.getId(),", - " userData.getName()", - " );", - " return user;", - " }", - " ", - " public void saveUser(User user) {", - " try {", - " // Save user to database", - " users.add(user);", - " System.out.println(\"User saved successfully\");", - " } catch (Exception e) {", - " System.err.println(\"Failed to save user: \" + e.getMessage());", - " throw e;", - " }", - " }", - "}" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/JavaScriptExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/JavaScriptExamples.swift deleted file mode 100644 index f08247fa9..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/JavaScriptExamples.swift +++ /dev/null @@ -1,490 +0,0 @@ -// -// JavaScriptExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-01-14. -// - -import Foundation - -/** - * JavaScriptExamples implements CodeExamples for JavaScript-specific snippets. - */ -public struct JavaScriptExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" class - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User {", - "constructor(id, name) {", - "this.id = id;", - "this.name = name;", - "}", - "}" - ] - } else { - [ - "class User {", - " constructor(id, name) {", - " this.id = id;", - " this.name = name;", - " }", - "}" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User {", - "constructor(id, name, email) {", - "this.id = id;", - "this.name = name;", - "this.email = email;", - "}", - "}" - ] - } else { - [ - "class User {", - " constructor(id, name, email) {", - " this.id = id;", - " this.name = name;", - " this.email = email;", - " }", - "}" - ] - } - } - - // MARK: 2) Rewrite All Lines - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User {", - "constructor(id, name, email, role = 'user') {", - "this.id = id;", - "this.name = name;", - "this.email = email;", - "this.role = role;", - "this.createdAt = new Date();", - "}", - "", - "getDisplayName() {", - "return `${this.name} (${this.email})`;", - "}", - "}" - ] - } else { - [ - "class User {", - " constructor(id, name, email, role = 'user') {", - " this.id = id;", - " this.name = name;", - " this.email = email;", - " this.role = role;", - " this.createdAt = new Date();", - " }", - "", - " getDisplayName() {", - " return `${this.name} (${this.email})`;", - " }", - "}" - ] - } - } - - // MARK: 3) Create All Lines - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "// models/User.js", - "export class User {", - "constructor(id, name, email) {", - "this.id = id;", - "this.name = name;", - "this.email = email;", - "}", - "", - "toJSON() {", - "return {", - "id: this.id,", - "name: this.name,", - "email: this.email", - "};", - "}", - "}" - ] - } else { - [ - "// models/User.js", - "export class User {", - " constructor(id, name, email) {", - " this.id = id;", - " this.name = name;", - " this.email = email;", - " }", - "", - " toJSON() {", - " return {", - " id: this.id,", - " name: this.name,", - " email: this.email", - " };", - " }", - "}" - ] - } - } - - // MARK: 4) NetworkManager Example - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class APIClient {", - "async fetchData(endpoint) {", - "const response = await fetch(endpoint);", - "return response.json();", - "}", - "}" - ] - } else { - [ - "class APIClient {", - " async fetchData(endpoint) {", - " const response = await fetch(endpoint);", - " return response.json();", - " }", - "}" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class APIClient {", - "async fetchData(endpoint, options = {}) {", - "try {", - "const response = await fetch(endpoint, {", - "...options,", - "headers: {", - "'Content-Type': 'application/json',", - "...options.headers", - "}", - "});", - "", - "if (!response.ok) {", - "throw new Error(`HTTP ${response.status}: ${response.statusText}`);", - "}", - "", - "return response.json();", - "} catch (error) {", - "console.error('API request failed:', error);", - "throw error;", - "}", - "}", - "}" - ] - } else { - [ - "class APIClient {", - " async fetchData(endpoint, options = {}) {", - " try {", - " const response = await fetch(endpoint, {", - " ...options,", - " headers: {", - " 'Content-Type': 'application/json',", - " ...options.headers", - " }", - " });", - " ", - " if (!response.ok) {", - " throw new Error(`HTTP ${response.status}: ${response.statusText}`);", - " }", - " ", - " return response.json();", - " } catch (error) {", - " console.error('API request failed:', error);", - " throw error;", - " }", - " }", - "}" - ] - } - } - - // MARK: 5) Negative Examples - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - [ - "class User {", - " constructor(id, name) {", - " this.id = id;", - " this.name = name;", - " this.isActive = true;", - " }", - " ", - " getInfo() {", - " return `User: ${this.name}`;", - " }", - "}" - ] - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - [ - " constructor(id, name) {", - " this.id = id;", - " this.name = name;" - ] - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - [ - " constructor(id, name, email) {", - " this.id = id;", - " this.name = name;", - " this.email = email;" - ] - } - - /// Brace mismatch example - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - [ - "function processData(items) {", - " if (items.length > 0) {", - " items.forEach(item => {", - " console.log(item);", - " });", - " }", - "}" - ] - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - [ - " if (items.length > 0) {", - " items.forEach(item => {", - " console.log(item);" - ] - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - [ - " if (items.length > 0) {", - " console.log(`Processing ${items.length} items`);", - " items.forEach(item => {", - " console.log(item);" - ] - } - - /// One-line search block - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - ["console.log(item);"] - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - ["console.log('Item:', item);"] - } - - /// Ambiguous search block - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - ["}"] - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - [ - " }", - "}" - ] - } - - public func commentSyntax() -> String { - "//" - } - - // MARK: - File Editor Example Methods - - public func fileEditorExampleFileContents() -> [String] { - [ - "class GameManager {", - " constructor() {", - " this.score = 0;", - " this.level = 1;", - " this.isRunning = false;", - " }", - " ", - " reset() {", - " this.score = 0;", - " this.level = 1;", - " this.isRunning = false;", - " }", - " ", - " checkProximity(position) {", - " // Calculate distance logic here", - " return 0.0;", - " }", - "}" - ] - } - - public func fileEditorExampleChange1() -> [String] { - [ - " // ... existing code ...", - " this.isRunning = false;", - " ", - " console.log('GameManager initialized');", - " }", - " ", - " reset() {", - " // ... existing code ..." - ] - } - - public func fileEditorExampleChange2() -> [String] { - [ - " // ... existing code ...", - " }", - " ", - " destroy() {", - " console.log('GameManager cleaned up');", - " }", - "}" - ] - } - - public func fileEditorExampleSearchBlock() -> [String] { - [ - " this.isRunning = false;", - " }", - " ", - " reset() {" - ] - } - - public func fileEditorExampleContentBlock() -> [String] { - [ - " this.isRunning = false;", - " ", - " console.log('GameManager initialized');", - " }", - " ", - " reset() {" - ] - } - - public func fileEditorExampleSearchBlock2() -> [String] { - [ - " return 0.0;", - " }", - "}" - ] - } - - public func fileEditorExampleContentBlock2() -> [String] { - [ - " return 0.0;", - " }", - " ", - " destroy() {", - " console.log('GameManager cleaned up');", - " }", - "}" - ] - } - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - // Using the default implementation from the protocol extension - [ - "class UserService {", - " constructor() {", - " this.users = [];", - " }", - " ", - " processUser(userData) {", - " // Process user data", - " const user = {", - " id: userData.id,", - " name: userData.name", - " };", - " return user;", - " }", - " ", - " saveUser(user) {", - " // Save user to database", - " this.users.push(user);", - " }", - "}" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - " processUser(userData) {", - " // Add validation", - " if (!userData || !userData.id || !userData.name) {", - " throw new Error('Invalid user data');", - " }", - " ", - " // ... existing code ...", - " }" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - " saveUser(user) {", - " try {", - " // ... existing code ...", - " console.log('User saved successfully');", - " } catch (error) {", - " console.error('Failed to save user:', error);", - " throw error;", - " }", - " }" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "class UserService {", - " constructor() {", - " this.users = [];", - " }", - " ", - " processUser(userData) {", - " // Add validation", - " if (!userData || !userData.id || !userData.name) {", - " throw new Error('Invalid user data');", - " }", - " ", - " // Process user data", - " const user = {", - " id: userData.id,", - " name: userData.name", - " };", - " return user;", - " }", - " ", - " saveUser(user) {", - " try {", - " // Save user to database", - " this.users.push(user);", - " console.log('User saved successfully');", - " } catch (error) {", - " console.error('Failed to save user:', error);", - " throw error;", - " }", - " }", - "}" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/PHPExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/PHPExamples.swift deleted file mode 100644 index 11b8cf15a..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/PHPExamples.swift +++ /dev/null @@ -1,520 +0,0 @@ -// -// PHPExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-07-14. -// - -import Foundation - -/** - * PHPExamples implements CodeExamples for PHP-specific snippets. - */ -public struct PHPExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" class - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User", - "{", - "public $id;", - "public $name;", - "}" - ] - } else { - [ - "class User", - "{", - " public $id;", - " public $name;", - "}" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User", - "{", - "public $id;", - "public $name;", - "public $email;", - "}" - ] - } else { - [ - "class User", - "{", - " public $id;", - " public $name;", - " public $email;", - "}" - ] - } - } - - // MARK: 2) Rewrite Entire File with an "Email" property - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "", - "namespace Models;", - "", - "class User", - "{", - "public $id;", - "public $name;", - "public $email;", - "", - "public function __construct($name, $email)", - "{", - "$this->id = uniqid();", - "$this->name = $name;", - "$this->email = $email;", - "}", - "", - "public function toArray()", - "{", - "return [", - "'id' => $this->id,", - "'name' => $this->name,", - "'email' => $this->email,", - "];", - "}", - "}" - ] - } else { - [ - "id = uniqid();", - " $this->name = $name;", - " $this->email = $email;", - " }", - "", - " public function toArray()", - " {", - " return [", - " 'id' => $this->id,", - " 'name' => $this->name,", - " 'email' => $this->email,", - " ];", - " }", - "}" - ] - } - } - - // MARK: 3) Create a new "RoundedButton" component - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "", - "namespace Components;", - "", - "class RoundedButton", - "{", - "private $text;", - "private $onClick;", - "private $borderRadius;", - "private $backgroundColor;", - "", - "public function __construct($text, $onClick = null, $borderRadius = '8px', $backgroundColor = '#007bff')", - "{", - "$this->text = $text;", - "$this->onClick = $onClick;", - "$this->borderRadius = $borderRadius;", - "$this->backgroundColor = $backgroundColor;", - "}", - "", - "public function render()", - "{", - "$style = \"background-color: {$this->backgroundColor}; border-radius: {$this->borderRadius}; border: none; padding: 10px 20px; color: white; cursor: pointer;\";", - "$onClickAttr = $this->onClick ? \"onclick='{$this->onClick}'\" : '';", - "", - "return \"\";", - "}", - "}" - ] - } else { - [ - "text = $text;", - " $this->onClick = $onClick;", - " $this->borderRadius = $borderRadius;", - " $this->backgroundColor = $backgroundColor;", - " }", - "", - " public function render()", - " {", - " $style = \"background-color: {$this->backgroundColor}; border-radius: {$this->borderRadius}; border: none; padding: 10px 20px; color: white; cursor: pointer;\";", - " $onClickAttr = $this->onClick ? \"onclick='{$this->onClick}'\" : '';", - "", - " return \"\";", - " }", - "}" - ] - } - } - - // MARK: 4) NetworkManager cURL to Guzzle conversion - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "public function fetchData($url, $completion)", - "{", - "$ch = curl_init();", - "curl_setopt($ch, CURLOPT_URL, $url);", - "curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);", - "$response = curl_exec($ch);", - "curl_close($ch);", - "$completion($response);", - "}" - ] - } else { - [ - "public function fetchData($url, $completion)", - "{", - " $ch = curl_init();", - " curl_setopt($ch, CURLOPT_URL, $url);", - " curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);", - " $response = curl_exec($ch);", - " curl_close($ch);", - " $completion($response);", - "}" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "public function fetchData($url)", - "{", - "$client = new \\GuzzleHttp\\Client();", - "try {", - "$response = $client->get($url);", - "return $response->getBody()->getContents();", - "} catch (\\Exception $e) {", - "throw new \\Exception('Failed to fetch data: ' . $e->getMessage());", - "}", - "}" - ] - } else { - [ - "public function fetchData($url)", - "{", - " $client = new \\GuzzleHttp\\Client();", - " try {", - " $response = $client->get($url);", - " return $response->getBody()->getContents();", - " } catch (\\Exception $e) {", - " throw new \\Exception('Failed to fetch data: ' . $e->getMessage());", - " }", - "}" - ] - } - } - - // MARK: Negative Examples - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "namespace Services;", - "", - "class UserService", - "{", - "private $logger;", - "", - "public function processUser($user)", - "{", - "if ($user === null) {", - "throw new \\InvalidArgumentException('User cannot be null');", - "}", - "$this->logger->info(\"Processing user: {$user->name}\");", - "}", - "}" - ] - } else { - [ - "namespace Services;", - "", - "class UserService", - "{", - " private $logger;", - "", - " public function processUser($user)", - " {", - " if ($user === null) {", - " throw new \\InvalidArgumentException('User cannot be null');", - " }", - " $this->logger->info(\"Processing user: {$user->name}\");", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - // Intentionally mismatched - missing braces and different indentation - if includeIndentation { - [ - "if ($user === null)", - "throw new \\InvalidArgumentException('User cannot be null');" - ] - } else { - [ - " if ($user === null)", - " throw new \\InvalidArgumentException('User cannot be null');" - ] - } - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "if ($user === null || empty($user->name)) {", - "throw new \\InvalidArgumentException('User is invalid');", - "}" - ] - } else { - [ - " if ($user === null || empty($user->name)) {", - " throw new \\InvalidArgumentException('User is invalid');", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - userSearchReplaceNegativeExampleFileContents(includeIndentation: includeIndentation) - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "$this->logger->info(\"Processing user: {$user->name}\");", - "}" - ] - } else { - [ - " }", - " $this->logger->info(\"Processing user: {$user->name}\");", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - // Extra closing brace added - if includeIndentation { - [ - "}", - "$this->logger->info(\"Processing user: {$user->name}\");", - "// Additional processing", - "$this->validateUser($user);", - "}", - "}" // Extra brace - ] - } else { - [ - " }", - " $this->logger->info(\"Processing user: {$user->name}\");", - " // Additional processing", - " $this->validateUser($user);", - " }", - " }" // Extra brace - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["$this->logger->info(\"Processing user: {$user->name}\");"] - } else { - [" $this->logger->info(\"Processing user: {$user->name}\");"] - } - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["$this->logger->debug(\"Processing user: {$user->name} with ID: {$user->id}\");"] - } else { - [" $this->logger->debug(\"Processing user: {$user->name} with ID: {$user->id}\");"] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - // Just closing braces - ambiguous - if includeIndentation { - [ - "}", - "}" - ] - } else { - [ - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "// TODO: Add more processing", - "}" - ] - } else { - [ - " }", - " // TODO: Add more processing", - "}" - ] - } - } - - public func commentSyntax() -> String { - "//" - } - - // File editor examples use default implementation from protocol extension - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - " $userData['id'],", - " 'name' => $userData['name']", - " ];", - " return $user;", - " }", - " ", - " public function saveUser($user)", - " {", - " // Save user to database", - " $this->users[] = $user;", - " }", - "}" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - " public function processUser($userData)", - " {", - " // Add validation", - " if (empty($userData) || empty($userData['id']) || empty($userData['name'])) {", - " throw new InvalidArgumentException('Invalid user data');", - " }", - " ", - " // ... existing code ...", - " }" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - " public function saveUser($user)", - " {", - " try {", - " // ... existing code ...", - " echo \"User saved successfully\\n\";", - " } catch (Exception $e) {", - " error_log('Failed to save user: ' . $e->getMessage());", - " throw $e;", - " }", - " }" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - " $userData['id'],", - " 'name' => $userData['name']", - " ];", - " return $user;", - " }", - " ", - " public function saveUser($user)", - " {", - " try {", - " // Save user to database", - " $this->users[] = $user;", - " echo \"User saved successfully\\n\";", - " } catch (Exception $e) {", - " error_log('Failed to save user: ' . $e->getMessage());", - " throw $e;", - " }", - " }", - "}" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/PythonExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/PythonExamples.swift deleted file mode 100644 index 872da2788..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/PythonExamples.swift +++ /dev/null @@ -1,440 +0,0 @@ -// -// PythonExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-01-14. -// - -import Foundation - -/** - * PythonExamples implements CodeExamples for Python-specific snippets. - */ -public struct PythonExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" class - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User:", - "def __init__(self, id, name):", - "self.id = id", - "self.name = name" - ] - } else { - [ - "class User:", - " def __init__(self, id, name):", - " self.id = id", - " self.name = name" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class User:", - "def __init__(self, id, name, email):", - "self.id = id", - "self.name = name", - "self.email = email" - ] - } else { - [ - "class User:", - " def __init__(self, id, name, email):", - " self.id = id", - " self.name = name", - " self.email = email" - ] - } - } - - // MARK: 2) Rewrite All Lines - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "from datetime import datetime", - "", - "class User:", - "def __init__(self, id, name, email, role='user'):", - "self.id = id", - "self.name = name", - "self.email = email", - "self.role = role", - "self.created_at = datetime.now()", - "", - "def get_display_name(self):", - "return f\"{self.name} ({self.email})\"" - ] - } else { - [ - "from datetime import datetime", - "", - "class User:", - " def __init__(self, id, name, email, role='user'):", - " self.id = id", - " self.name = name", - " self.email = email", - " self.role = role", - " self.created_at = datetime.now()", - " ", - " def get_display_name(self):", - " return f\"{self.name} ({self.email})\"" - ] - } - } - - // MARK: 3) Create All Lines - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "# models/user.py", - "from dataclasses import dataclass", - "from typing import Dict, Any", - "", - "@dataclass", - "class User:", - "id: str", - "name: str", - "email: str", - "", - "def to_dict(self) -> Dict[str, Any]:", - "return {", - "'id': self.id,", - "'name': self.name,", - "'email': self.email", - "}" - ] - } else { - [ - "# models/user.py", - "from dataclasses import dataclass", - "from typing import Dict, Any", - "", - "@dataclass", - "class User:", - " id: str", - " name: str", - " email: str", - " ", - " def to_dict(self) -> Dict[str, Any]:", - " return {", - " 'id': self.id,", - " 'name': self.name,", - " 'email': self.email", - " }" - ] - } - } - - // MARK: 4) NetworkManager Example - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import requests", - "", - "class APIClient:", - "def fetch_data(self, endpoint):", - "response = requests.get(endpoint)", - "return response.json()" - ] - } else { - [ - "import requests", - "", - "class APIClient:", - " def fetch_data(self, endpoint):", - " response = requests.get(endpoint)", - " return response.json()" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import requests", - "import logging", - "", - "class APIClient:", - "def fetch_data(self, endpoint, **kwargs):", - "try:", - "headers = kwargs.get('headers', {})", - "headers['Content-Type'] = 'application/json'", - "", - "response = requests.get(endpoint, headers=headers, **kwargs)", - "response.raise_for_status()", - "", - "return response.json()", - "except requests.RequestException as e:", - "logging.error(f'API request failed: {e}')", - "raise" - ] - } else { - [ - "import requests", - "import logging", - "", - "class APIClient:", - " def fetch_data(self, endpoint, **kwargs):", - " try:", - " headers = kwargs.get('headers', {})", - " headers['Content-Type'] = 'application/json'", - " ", - " response = requests.get(endpoint, headers=headers, **kwargs)", - " response.raise_for_status()", - " ", - " return response.json()", - " except requests.RequestException as e:", - " logging.error(f'API request failed: {e}')", - " raise" - ] - } - } - - // MARK: 5) Negative Examples - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - [ - "class User:", - " def __init__(self, id, name):", - " self.id = id", - " self.name = name", - " self.is_active = True", - " ", - " def get_info(self):", - " return f\"User: {self.name}\"" - ] - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - [ - " def __init__(self, id, name):", - " self.id = id", - " self.name = name" - ] - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - [ - " def __init__(self, id, name, email):", - " self.id = id", - " self.name = name", - " self.email = email" - ] - } - - /// Brace mismatch example (Python uses indentation) - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - [ - "def process_data(items):", - " if len(items) > 0:", - " for item in items:", - " print(item)", - " return len(items)" - ] - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - [ - " if len(items) > 0:", - " for item in items:", - " print(item)" - ] - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - [ - " if len(items) > 0:", - " print(f'Processing {len(items)} items')", - " for item in items:", - " print(item)" - ] - } - - /// One-line search block - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - ["print(item)"] - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - ["print('Item:', item)"] - } - - /// Ambiguous search block - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - ["return"] - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - [ - " logging.debug('Processing complete')", - " return" - ] - } - - public func commentSyntax() -> String { - "#" - } - - // MARK: - File Editor Example Methods - - public func fileEditorExampleFileContents() -> [String] { - [ - "class GameManager:", - " def __init__(self):", - " self.score = 0", - " self.level = 1", - " self.is_running = False", - " ", - " def reset(self):", - " self.score = 0", - " self.level = 1", - " self.is_running = False", - " ", - " def check_proximity(self, position):", - " # Calculate distance logic here", - " return 0.0" - ] - } - - public func fileEditorExampleChange1() -> [String] { - [ - " # ... existing code ...", - " self.is_running = False", - " print('GameManager initialized')", - " ", - " def reset(self):", - " # ... existing code ..." - ] - } - - public func fileEditorExampleChange2() -> [String] { - [ - " # ... existing code ...", - " return 0.0", - " ", - " def __del__(self):", - " print('GameManager cleaned up')" - ] - } - - public func fileEditorExampleSearchBlock() -> [String] { - [ - " self.is_running = False", - " ", - " def reset(self):" - ] - } - - public func fileEditorExampleContentBlock() -> [String] { - [ - " self.is_running = False", - " print('GameManager initialized')", - " ", - " def reset(self):" - ] - } - - public func fileEditorExampleSearchBlock2() -> [String] { - [ - " def check_proximity(self, position):", - " # Calculate distance logic here", - " return 0.0" - ] - } - - public func fileEditorExampleContentBlock2() -> [String] { - [ - " def check_proximity(self, position):", - " # Calculate distance logic here", - " return 0.0", - " ", - " def __del__(self):", - " print('GameManager cleaned up')" - ] - } - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - "from typing import Dict, List", - "", - "class UserService:", - " def __init__(self):", - " self.users: List[Dict] = []", - " ", - " def process_user(self, user_data: Dict) -> Dict:", - " # Process user data", - " user = {", - " 'id': user_data['id'],", - " 'name': user_data['name']", - " }", - " return user", - " ", - " def save_user(self, user: Dict) -> None:", - " # Save user to database", - " self.users.append(user)" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - " def process_user(self, user_data: Dict) -> Dict:", - " # Add validation", - " if not user_data or 'id' not in user_data or 'name' not in user_data:", - " raise ValueError('Invalid user data')", - " ", - " # ... existing code ..." - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - " def save_user(self, user: Dict) -> None:", - " try:", - " # ... existing code ...", - " print('User saved successfully')", - " except Exception as e:", - " print(f'Failed to save user: {e}')", - " raise" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "from typing import Dict, List", - "", - "class UserService:", - " def __init__(self):", - " self.users: List[Dict] = []", - " ", - " def process_user(self, user_data: Dict) -> Dict:", - " # Add validation", - " if not user_data or 'id' not in user_data or 'name' not in user_data:", - " raise ValueError('Invalid user data')", - " ", - " # Process user data", - " user = {", - " 'id': user_data['id'],", - " 'name': user_data['name']", - " }", - " return user", - " ", - " def save_user(self, user: Dict) -> None:", - " try:", - " # Save user to database", - " self.users.append(user)", - " print('User saved successfully')", - " except Exception as e:", - " print(f'Failed to save user: {e}')", - " raise" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/RustExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/RustExamples.swift deleted file mode 100644 index 745702397..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/RustExamples.swift +++ /dev/null @@ -1,479 +0,0 @@ -// -// RustExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-07-14. -// - -import Foundation - -/** - * RustExamples implements CodeExamples for Rust-specific snippets. - */ -public struct RustExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" struct - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "pub struct User {", - "pub id: Uuid,", - "pub name: String,", - "}" - ] - } else { - [ - "pub struct User {", - " pub id: Uuid,", - " pub name: String,", - "}" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "pub struct User {", - "pub id: Uuid,", - "pub name: String,", - "pub email: String,", - "}" - ] - } else { - [ - "pub struct User {", - " pub id: Uuid,", - " pub name: String,", - " pub email: String,", - "}" - ] - } - } - - // MARK: 2) Rewrite Entire File with an "email" field - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "use uuid::Uuid;", - "", - "#[derive(Debug, Clone)]", - "pub struct User {", - "pub id: Uuid,", - "pub name: String,", - "pub email: String,", - "}", - "", - "impl User {", - "pub fn new(name: String, email: String) -> Self {", - "Self {", - "id: Uuid::new_v4(),", - "name,", - "email,", - "}", - "}", - "}" - ] - } else { - [ - "use uuid::Uuid;", - "", - "#[derive(Debug, Clone)]", - "pub struct User {", - " pub id: Uuid,", - " pub name: String,", - " pub email: String,", - "}", - "", - "impl User {", - " pub fn new(name: String, email: String) -> Self {", - " Self {", - " id: Uuid::new_v4(),", - " name,", - " email,", - " }", - " }", - "}" - ] - } - } - - // MARK: 3) Create a new "RoundedButton" file - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "use iced::{Button, Element, Length, Sandbox};", - "", - "pub struct RoundedButton {", - "corner_radius: f32,", - "button: Button,", - "}", - "", - "impl RoundedButton {", - "pub fn new() -> Self {", - "Self {", - "corner_radius: 0.0,", - "button: Button::new(),", - "}", - "}", - "", - "pub fn corner_radius(mut self, radius: f32) -> Self {", - "self.corner_radius = radius;", - "self", - "}", - "}" - ] - } else { - [ - "use iced::{Button, Element, Length, Sandbox};", - "", - "pub struct RoundedButton {", - " corner_radius: f32,", - " button: Button,", - "}", - "", - "impl RoundedButton {", - " pub fn new() -> Self {", - " Self {", - " corner_radius: 0.0,", - " button: Button::new(),", - " }", - " }", - "", - " pub fn corner_radius(mut self, radius: f32) -> Self {", - " self.corner_radius = radius;", - " self", - " }", - "}" - ] - } - } - - // MARK: 4) NetworkManager async/await conversion - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "pub fn fetch_data(url: &str, completion: impl FnOnce(String)) {", - "let response = reqwest::blocking::get(url)", - ".expect(\"Failed to fetch\")", - ".text()", - ".expect(\"Failed to read\");", - "completion(response);", - "}" - ] - } else { - [ - "pub fn fetch_data(url: &str, completion: impl FnOnce(String)) {", - " let response = reqwest::blocking::get(url)", - " .expect(\"Failed to fetch\")", - " .text()", - " .expect(\"Failed to read\");", - " completion(response);", - "}" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "pub async fn fetch_data(url: &str) -> Result {", - "let response = reqwest::get(url)", - ".await?", - ".text()", - ".await?;", - "Ok(response)", - "}" - ] - } else { - [ - "pub async fn fetch_data(url: &str) -> Result {", - " let response = reqwest::get(url)", - " .await?", - " .text()", - " .await?;", - " Ok(response)", - "}" - ] - } - } - - // MARK: Negative Examples - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "use log::{error, info};", - "", - "pub fn process_user(user: Option<&User>) {", - "match user {", - "None => {", - "error!(\"User is None\");", - "return;", - "}", - "Some(u) => {", - "info!(\"Processing user: {}\", u.name);", - "}", - "}", - "}" - ] - } else { - [ - "use log::{error, info};", - "", - "pub fn process_user(user: Option<&User>) {", - " match user {", - " None => {", - " error!(\"User is None\");", - " return;", - " }", - " Some(u) => {", - " info!(\"Processing user: {}\", u.name);", - " }", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - // Intentionally mismatched - missing match arm structure - if includeIndentation { - [ - "None => {", - "error!(\"User is None\");" - ] - } else { - [ - " None => {", - " error!(\"User is None\");" - ] - } - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "None => {", - "error!(\"User is None or invalid\");", - "panic!(\"Cannot process invalid user\");", - "}" - ] - } else { - [ - " None => {", - " error!(\"User is None or invalid\");", - " panic!(\"Cannot process invalid user\");", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - userSearchReplaceNegativeExampleFileContents(includeIndentation: includeIndentation) - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "}", - "}" - ] - } else { - [ - " }", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - // Extra closing brace added - if includeIndentation { - [ - "}", - "}", - "// Additional validation", - "validate_user(user);", - "}", - "}" // Extra brace - ] - } else { - [ - " }", - " }", - " // Additional validation", - " validate_user(user);", - "}", - "}" // Extra brace - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["info!(\"Processing user: {}\", u.name);"] - } else { - [" info!(\"Processing user: {}\", u.name);"] - } - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - ["debug!(\"Processing user: {} (ID: {})\", u.name, u.id);"] - } else { - [" debug!(\"Processing user: {} (ID: {})\", u.name, u.id);"] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - // Just closing braces - ambiguous - if includeIndentation { - [ - "}", - "}" - ] - } else { - [ - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "// TODO: Add more processing", - "}" - ] - } else { - [ - " }", - " // TODO: Add more processing", - "}" - ] - } - } - - public func commentSyntax() -> String { - "//" - } - - // File editor examples use default implementation from protocol extension - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - "use std::error::Error;", - "", - "#[derive(Debug, Clone)]", - "struct User {", - " id: String,", - " name: String,", - "}", - "", - "struct UserService {", - " users: Vec,", - "}", - "", - "impl UserService {", - " fn new() -> Self {", - " UserService {", - " users: Vec::new(),", - " }", - " }", - " ", - " fn process_user(&self, user_data: &User) -> Result> {", - " // Process user data", - " let user = User {", - " id: user_data.id.clone(),", - " name: user_data.name.clone(),", - " };", - " Ok(user)", - " }", - " ", - " fn save_user(&mut self, user: User) -> Result<(), Box> {", - " // Save user to database", - " self.users.push(user);", - " Ok(())", - " }", - "}" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - " fn process_user(&self, user_data: &User) -> Result> {", - " // Add validation", - " if user_data.id.is_empty() || user_data.name.is_empty() {", - " return Err(\"Invalid user data\".into());", - " }", - " ", - " // ... existing code ...", - " }" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - " fn save_user(&mut self, user: User) -> Result<(), Box> {", - " // ... existing code ...", - " println!(\"User saved successfully\");", - " Ok(())", - " }" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "use std::error::Error;", - "", - "#[derive(Debug, Clone)]", - "struct User {", - " id: String,", - " name: String,", - "}", - "", - "struct UserService {", - " users: Vec,", - "}", - "", - "impl UserService {", - " fn new() -> Self {", - " UserService {", - " users: Vec::new(),", - " }", - " }", - " ", - " fn process_user(&self, user_data: &User) -> Result> {", - " // Add validation", - " if user_data.id.is_empty() || user_data.name.is_empty() {", - " return Err(\"Invalid user data\".into());", - " }", - " ", - " // Process user data", - " let user = User {", - " id: user_data.id.clone(),", - " name: user_data.name.clone(),", - " };", - " Ok(user)", - " }", - " ", - " fn save_user(&mut self, user: User) -> Result<(), Box> {", - " // Save user to database", - " self.users.push(user);", - " println!(\"User saved successfully\");", - " Ok(())", - " }", - "}" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/SwiftExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/SwiftExamples.swift deleted file mode 100644 index df1646ee6..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/SwiftExamples.swift +++ /dev/null @@ -1,553 +0,0 @@ -// -// SwiftExamples.swift -// RepoPrompt -// -// Created by Eric Provencher on 2024-12-28. -// - -import Foundation - -/** - * SwiftExamples implements CodeExamples for Swift-specific snippets. - */ -public struct SwiftExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" struct - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "struct User {", - "let id: UUID", - "var name: String", - "}" - ] - } else { - [ - "struct User {", - " let id: UUID", - " var name: String", - "}" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "struct User {", - "let id: UUID", - "var name: String", - "var email: String", - "}" - ] - } else { - [ - "struct User {", - " let id: UUID", - " var name: String", - " var email: String", - "}" - ] - } - } - - // MARK: 2) Rewrite Entire File with an "email" field - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import Foundation", - "struct User {", - "let id: UUID", - "var name: String", - "var email: String", - "init(name: String, email: String) {", - "self.id = UUID()", - "self.name = name", - "self.email = email", - "}", - "}" - ] - } else { - [ - "import Foundation", - "struct User {", - " let id: UUID", - " var name: String", - " var email: String", - "", - " init(name: String, email: String) {", - " self.id = UUID()", - " self.name = name", - " self.email = email", - " }", - "}" - ] - } - } - - // MARK: 3) Create a new "RoundedButton" file - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import UIKit", - "@IBDesignable", - "class RoundedButton: UIButton {", - "@IBInspectable var cornerRadius: CGFloat = 0", - "}" - ] - } else { - [ - "import UIKit", - "@IBDesignable", - "class RoundedButton: UIButton {", - " @IBInspectable var cornerRadius: CGFloat = 0", - "}" - ] - } - } - - // MARK: 4) Indentation-Preserving Example (async/await) - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class NetworkManager {", - "func fetchData(from url: URL, completion: @escaping (Data?) -> Void) {", - "// old code", - "}", - "}" - ] - } else { - [ - "class NetworkManager {", - " func fetchData(from url: URL, completion: @escaping (Data?) -> Void) {", - " // old code", - " }", - "}" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "class NetworkManager {", - "func fetchData(from url: URL) async throws -> Data {", - "let (data, _) = try await URLSession.shared.data(from: url)", - "return data", - "}", - "}" - ] - } else { - [ - "class NetworkManager {", - " func fetchData(from url: URL) async throws -> Data {", - " let (data, _) = try await URLSession.shared.data(from: url)", - " return data", - " }", - "}" - ] - } - } - - // MARK: - Negative Examples for Search/Replace - - /** - * Represents the "original file contents" portion for the negative example. - * If includeIndentation is true, we add , , etc. - */ - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import Foundation", - "class Example {", - " foo() {", - " Bar()", - " }", - "}" - ] - } else { - [ - "import Foundation", - "class Example {", - " foo() {", - " Bar()", - " }", - "}" - ] - } - } - - /** - * The mismatched search block for the negative example. - * Notice it omits or shifts whitespace from the original lines. - */ - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo() {", - "Bar()", - "}" - ] - } else { - // Note: We now indent the first closing brace with 4 spaces. - [ - " foo() {", - " Bar()", - " }" - ] - } - } - - /** - * The intended "new" content block for the negative example. - * Illustrates an added line (Bar2()) that won’t properly match because - * the search block is missing or has mismatched indentation/spacing. - */ - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo() {", - "Bar()", - "Bar2()", - "}" - ] - } else { - [ - " foo() {", - " Bar()", - " Bar2()", - " }" - ] - } - } - - /** - * Demonstrates a file that, when partially matched, can cause mismatched braces in the replacement content. - * The "foo() { ... }" block is correct, but the new content has an extra block plus an extra closing brace. - */ - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "func someFunction() {", - "foo() {", - "Bar()", - "}", - "}" - ] - } else { - [ - "func someFunction() {", - " foo() {", - " Bar()", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo() {", - "Bar()", - "}" - ] - } else { - [ - " foo() {", - " Bar()", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo() {", - "Bar()", - "}", - "", - "bar() {", - "foo2()", - "}", - "}" - ] - } else { - // All lines here are meant to be indented at the level of - [ - " foo() {", - " Bar()", - " }", - "", - " bar() {", - " foo2()", - " }", - "}" - ] - } - } - - /// New negative example: one-line search block (should be avoided) - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "var email: String" // Only one line – too short for a reliable search block. - ] - } else { - [ - "var email: String" // Only one line – too short for a reliable search block. - ] - } - } - - /// New negative example: one-line new block (content must match search block) - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "var emailNew: String" // Only one line – too short for a reliable search block. - ] - } else { - [ - "var emailNew: String" // Only one line – too short for a reliable search block. - ] - } - } - - /// New negative example: ambiguous search block (should be avoided) - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "}" - ] - } else { - // Here we follow the token levels: one line at 4 spaces then one at 0. - [ - " }", - "}" - ] - } - } - - /// New negative example: ambiguous new block (content must match search block) - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo() {", - "}", - "}", - "}" - ] - } else { - // Replace each token with its equivalent number of spaces: - // → 8 spaces, → 4 spaces, → 0 spaces. - [ - " foo() {", - " }", - " }", - "}" - ] - } - } - - public func commentSyntax() -> String { - "//" - } - - // MARK: - File Editor Example Methods - - public func fileEditorExampleFileContents() -> [String] { - [ - "import Foundation", - "", - "class GameManager {", - " private var score: Int = 0", - " private var level: Int = 1", - " private var isRunning: Bool = false", - " ", - " func reset() {", - " score = 0", - " level = 1", - " isRunning = false", - " }", - " ", - " func checkProximity(to position: CGPoint) -> Float {", - " // Calculate distance logic here", - " return 0.0", - " }", - "}" - ] - } - - public func fileEditorExampleChange1() -> [String] { - [ - " // ... existing code ...", - " private var isRunning: Bool = false", - " ", - " init() {", - " print(\"GameManager initialized\")", - " }", - " ", - " func reset() {", - " // ... existing code ..." - ] - } - - public func fileEditorExampleChange2() -> [String] { - [ - " // ... existing code ...", - " }", - " ", - " deinit {", - " print(\"GameManager cleaned up\")", - " }", - " }" - ] - } - - public func fileEditorExampleSearchBlock() -> [String] { - [ - " private var isRunning: Bool = false", - " ", - " func reset() {" - ] - } - - public func fileEditorExampleContentBlock() -> [String] { - [ - " private var isRunning: Bool = false", - " ", - " init() {", - " print(\"GameManager initialized\")", - " }", - " ", - " func reset() {" - ] - } - - public func fileEditorExampleSearchBlock2() -> [String] { - [ - " return 0.0", - " }", - "}" - ] - } - - public func fileEditorExampleContentBlock2() -> [String] { - [ - " return 0.0", - " }", - " ", - " deinit {", - " print(\"GameManager cleaned up\")", - " }", - "}" - ] - } - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - "import Foundation", - "", - "class UserService {", - " private var users: [User] = []", - " ", - " init() {", - " // Initialize service", - " }", - " ", - " func processUser(_ userData: UserData) -> User {", - " // Process user data", - " let user = User(", - " id: userData.id,", - " name: userData.name", - " )", - " return user", - " }", - " ", - " func saveUser(_ user: User) {", - " // Save user to database", - " users.append(user)", - " }", - "}" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - " func processUser(_ userData: UserData) -> User {", - " // Add validation", - " guard !userData.id.isEmpty,", - " !userData.name.isEmpty else {", - " throw UserServiceError.invalidUserData", - " }", - " ", - " // ... existing code ...", - " }" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - " func saveUser(_ user: User) {", - " do {", - " // ... existing code ...", - " print(\"User saved successfully\")", - " } catch {", - " print(\"Failed to save user: \\(error)\")", - " throw error", - " }", - " }" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "import Foundation", - "", - "enum UserServiceError: Error {", - " case invalidUserData", - "}", - "", - "class UserService {", - " private var users: [User] = []", - " ", - " init() {", - " // Initialize service", - " }", - " ", - " func processUser(_ userData: UserData) throws -> User {", - " // Add validation", - " guard !userData.id.isEmpty,", - " !userData.name.isEmpty else {", - " throw UserServiceError.invalidUserData", - " }", - " ", - " // Process user data", - " let user = User(", - " id: userData.id,", - " name: userData.name", - " )", - " return user", - " }", - " ", - " func saveUser(_ user: User) throws {", - " do {", - " // Save user to database", - " users.append(user)", - " print(\"User saved successfully\")", - " } catch {", - " print(\"Failed to save user: \\(error)\")", - " throw error", - " }", - " }", - "}" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/TSXExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/TSXExamples.swift deleted file mode 100644 index 582f23131..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/TSXExamples.swift +++ /dev/null @@ -1,639 +0,0 @@ -// -// TSXExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-07-14. -// - -import Foundation - -/** - * TSXExamples implements CodeExamples for TSX (TypeScript + JSX) specific snippets. - */ -public struct TSXExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" interface - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "interface User {", - "id: string;", - "name: string;", - "}" - ] - } else { - [ - "interface User {", - " id: string;", - " name: string;", - "}" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "interface User {", - "id: string;", - "name: string;", - "email: string;", - "}" - ] - } else { - [ - "interface User {", - " id: string;", - " name: string;", - " email: string;", - "}" - ] - } - } - - // MARK: 2) Rewrite Entire File with an "email" field - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import React from 'react';", - "import { v4 as uuidv4 } from 'uuid';", - "", - "export interface User {", - "id: string;", - "name: string;", - "email: string;", - "role?: 'admin' | 'user';", - "createdAt: Date;", - "}", - "", - "interface UserCardProps {", - "user: User;", - "onEdit?: (user: User) => void;", - "}", - "", - "export const UserCard: React.FC = ({ user, onEdit }) => {", - "const handleEdit = () => {", - "onEdit?.(user);", - "};", - "", - "return (", - "
", - "

{user.name}

", - "

Email: {user.email}

", - "

Role: {user.role || 'user'}

", - "{onEdit && (", - "", - ")}", - "
", - ");", - "};" - ] - } else { - [ - "import React from 'react';", - "import { v4 as uuidv4 } from 'uuid';", - "", - "export interface User {", - " id: string;", - " name: string;", - " email: string;", - " role?: 'admin' | 'user';", - " createdAt: Date;", - "}", - "", - "interface UserCardProps {", - " user: User;", - " onEdit?: (user: User) => void;", - "}", - "", - "export const UserCard: React.FC = ({ user, onEdit }) => {", - " const handleEdit = () => {", - " onEdit?.(user);", - " };", - "", - " return (", - "
", - "

{user.name}

", - "

Email: {user.email}

", - "

Role: {user.role || 'user'}

", - " {onEdit && (", - " ", - " )}", - "
", - " );", - "};" - ] - } - } - - // MARK: 3) Create a new "RoundedButton" component - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import React, { CSSProperties } from 'react';", - "", - "interface RoundedButtonProps {", - "text: string;", - "cornerRadius?: number;", - "onClick?: () => void;", - "variant?: 'primary' | 'secondary';", - "disabled?: boolean;", - "}", - "", - "export const RoundedButton: React.FC = ({", - "text,", - "cornerRadius = 8,", - "onClick,", - "variant = 'primary',", - "disabled = false", - "}) => {", - "const baseStyle: CSSProperties = {", - "borderRadius: `${cornerRadius}px`,", - "padding: '10px 20px',", - "border: 'none',", - "cursor: disabled ? 'not-allowed' : 'pointer',", - "opacity: disabled ? 0.6 : 1,", - "fontSize: '14px',", - "fontWeight: 'medium'", - "};", - "", - "const variantStyle: CSSProperties = variant === 'primary' ? {", - "backgroundColor: '#007bff',", - "color: 'white'", - "} : {", - "backgroundColor: '#f8f9fa',", - "color: '#212529',", - "border: '1px solid #dee2e6'", - "};", - "", - "return (", - "style={{ ...baseStyle, ...variantStyle }}", - "onClick={onClick}", - "disabled={disabled}", - ">", - "{text}", - "", - ");", - "};" - ] - } else { - [ - "import React, { CSSProperties } from 'react';", - "", - "interface RoundedButtonProps {", - " text: string;", - " cornerRadius?: number;", - " onClick?: () => void;", - " variant?: 'primary' | 'secondary';", - " disabled?: boolean;", - "}", - "", - "export const RoundedButton: React.FC = ({", - " text,", - " cornerRadius = 8,", - " onClick,", - " variant = 'primary',", - " disabled = false", - "}) => {", - " const baseStyle: CSSProperties = {", - " borderRadius: `${cornerRadius}px`,", - " padding: '10px 20px',", - " border: 'none',", - " cursor: disabled ? 'not-allowed' : 'pointer',", - " opacity: disabled ? 0.6 : 1,", - " fontSize: '14px',", - " fontWeight: 'medium'", - " };", - "", - " const variantStyle: CSSProperties = variant === 'primary' ? {", - " backgroundColor: '#007bff',", - " color: 'white'", - " } : {", - " backgroundColor: '#f8f9fa',", - " color: '#212529',", - " border: '1px solid #dee2e6'", - " };", - "", - " return (", - " ", - " {text}", - " ", - " );", - "};" - ] - } - } - - // MARK: 4) Component lifecycle example (React hooks) - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "export const UserList: React.FC = () => {", - "const [users, setUsers] = useState([]);", - "", - "useEffect(() => {", - "// old Promise-based approach", - "fetchUsers().then(setUsers);", - "}, []);", - "", - "return
{/* render users */}
;", - "};" - ] - } else { - [ - "export const UserList: React.FC = () => {", - " const [users, setUsers] = useState([]);", - "", - " useEffect(() => {", - " // old Promise-based approach", - " fetchUsers().then(setUsers);", - " }, []);", - "", - " return
{/* render users */}
;", - "};" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "export const UserList: React.FC = () => {", - "const [users, setUsers] = useState([]);", - "const [loading, setLoading] = useState(false);", - "const [error, setError] = useState(null);", - "", - "useEffect(() => {", - "const loadUsers = async () => {", - "setLoading(true);", - "setError(null);", - "try {", - "const userData = await fetchUsers();", - "setUsers(userData);", - "} catch (err) {", - "setError(err instanceof Error ? err.message : 'Unknown error');", - "} finally {", - "setLoading(false);", - "}", - "};", - "", - "loadUsers();", - "}, []);", - "", - "if (loading) return
Loading...
;", - "if (error) return
Error: {error}
;", - "", - "return
{/* render users */}
;", - "};" - ] - } else { - [ - "export const UserList: React.FC = () => {", - " const [users, setUsers] = useState([]);", - " const [loading, setLoading] = useState(false);", - " const [error, setError] = useState(null);", - "", - " useEffect(() => {", - " const loadUsers = async () => {", - " setLoading(true);", - " setError(null);", - " try {", - " const userData = await fetchUsers();", - " setUsers(userData);", - " } catch (err) {", - " setError(err instanceof Error ? err.message : 'Unknown error');", - " } finally {", - " setLoading(false);", - " }", - " };", - "", - " loadUsers();", - " }, []);", - "", - " if (loading) return
Loading...
;", - " if (error) return
Error: {error}
;", - "", - " return
{/* render users */}
;", - "};" - ] - } - } - - // MARK: - Negative Examples for Search/Replace - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import React from 'react';", - "", - "export const Example: React.FC = () => {", - "const foo = () => {", - "bar();", - "};", - "", - "return
Hello
;", - "};" - ] - } else { - [ - "import React from 'react';", - "", - "export const Example: React.FC = () => {", - " const foo = () => {", - " bar();", - " };", - "", - " return
Hello
;", - "};" - ] - } - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "const foo = () => {", - "bar();", - "};" - ] - } else { - [ - " const foo = () => {", - " bar();", - " };" - ] - } - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "const foo = () => {", - "bar();", - "bar2();", - "};" - ] - } else { - [ - " const foo = () => {", - " bar();", - " bar2();", - " };" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "function someFunction(): JSX.Element {", - "const foo = () => {", - "bar();", - "};", - "return
;", - "}" - ] - } else { - [ - "function someFunction(): JSX.Element {", - " const foo = () => {", - " bar();", - " };", - " return
;", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "const foo = () => {", - "bar();", - "};" - ] - } else { - [ - " const foo = () => {", - " bar();", - " };" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "const foo = () => {", - "bar();", - "};", - "", - "const baz = () => {", - "foo2();", - "};", - "}" - ] - } else { - [ - " const foo = () => {", - " bar();", - " };", - "", - " const baz = () => {", - " foo2();", - " };", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "email: string;" - ] - } else { - [ - "email: string;" - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "emailAddress: string;" - ] - } else { - [ - "emailAddress: string;" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "};", - "};" - ] - } else { - [ - " };", - "};" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "const foo = () => {", - "};", - "};", - "};" - ] - } else { - [ - " const foo = () => {", - " };", - " };", - "};" - ] - } - } - - public func commentSyntax() -> String { - "//" - } - - // File editor examples use default implementation from protocol extension - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - "import React, { useState } from 'react';", - "", - "interface User {", - " id: string;", - " name: string;", - "}", - "", - "interface UserServiceProps {", - " onUserSaved?: (user: User) => void;", - "}", - "", - "export const UserService: React.FC = ({ onUserSaved }) => {", - " const [users, setUsers] = useState([]);", - " ", - " const processUser = (userData: Partial): User => {", - " // Process user data", - " const user: User = {", - " id: userData.id!,", - " name: userData.name!", - " };", - " return user;", - " };", - " ", - " const saveUser = (user: User): void => {", - " // Save user to database", - " setUsers([...users, user]);", - " };", - " ", - " return (", - "
", - " {/* User service UI */}", - "
", - " );", - "};" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - " const processUser = (userData: Partial): User => {", - " // Add validation", - " if (!userData || !userData.id || !userData.name) {", - " throw new Error('Invalid user data');", - " }", - " ", - " // ... existing code ...", - " };" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - " const saveUser = (user: User): void => {", - " try {", - " // ... existing code ...", - " console.log('User saved successfully');", - " onUserSaved?.(user);", - " } catch (error) {", - " console.error('Failed to save user:', error);", - " throw error;", - " }", - " };" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "import React, { useState } from 'react';", - "", - "interface User {", - " id: string;", - " name: string;", - "}", - "", - "interface UserServiceProps {", - " onUserSaved?: (user: User) => void;", - "}", - "", - "export const UserService: React.FC = ({ onUserSaved }) => {", - " const [users, setUsers] = useState([]);", - " ", - " const processUser = (userData: Partial): User => {", - " // Add validation", - " if (!userData || !userData.id || !userData.name) {", - " throw new Error('Invalid user data');", - " }", - " ", - " // Process user data", - " const user: User = {", - " id: userData.id!,", - " name: userData.name!", - " };", - " return user;", - " };", - " ", - " const saveUser = (user: User): void => {", - " try {", - " // Save user to database", - " setUsers([...users, user]);", - " console.log('User saved successfully');", - " onUserSaved?.(user);", - " } catch (error) {", - " console.error('Failed to save user:', error);", - " throw error;", - " }", - " };", - " ", - " return (", - "
", - " {/* User service UI */}", - "
", - " );", - "};" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/TypeScriptExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/TypeScriptExamples.swift deleted file mode 100644 index 1d253e527..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Code Examples/TypeScriptExamples.swift +++ /dev/null @@ -1,513 +0,0 @@ -// -// TypeScriptExamples.swift -// RepoPrompt -// -// Created by Assistant on 2025-01-14. -// - -import Foundation - -/** - * TypeScriptExamples implements CodeExamples for TypeScript-specific snippets. - */ -public struct TypeScriptExamples: CodeExamples { - // MARK: 1) Search & Replace Lines for "User" interface - - public func userSearchReplaceOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "interface User {", - "id: string;", - "name: string;", - "}" - ] - } else { - [ - "interface User {", - " id: string;", - " name: string;", - "}" - ] - } - } - - public func userSearchReplaceNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "interface User {", - "id: string;", - "name: string;", - "email: string;", - "}" - ] - } else { - [ - "interface User {", - " id: string;", - " name: string;", - " email: string;", - "}" - ] - } - } - - // MARK: 2) Rewrite Entire File with an "email" field - - public func userRewriteAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import { v4 as uuidv4 } from 'uuid';", - "", - "export interface User {", - "id: string;", - "name: string;", - "email: string;", - "role?: 'admin' | 'user';", - "createdAt: Date;", - "}", - "", - "export class UserService {", - "createUser(name: string, email: string, role: 'admin' | 'user' = 'user'): User {", - "return {", - "id: uuidv4(),", - "name,", - "email,", - "role,", - "createdAt: new Date()", - "};", - "}", - "", - "getDisplayName(user: User): string {", - "return `${user.name} (${user.email})`;", - "}", - "}" - ] - } else { - [ - "import { v4 as uuidv4 } from 'uuid';", - "", - "export interface User {", - " id: string;", - " name: string;", - " email: string;", - " role?: 'admin' | 'user';", - " createdAt: Date;", - "}", - "", - "export class UserService {", - " createUser(name: string, email: string, role: 'admin' | 'user' = 'user'): User {", - " return {", - " id: uuidv4(),", - " name,", - " email,", - " role,", - " createdAt: new Date()", - " };", - " }", - " ", - " getDisplayName(user: User): string {", - " return `${user.name} (${user.email})`;", - " }", - "}" - ] - } - } - - // MARK: 3) Create a new "RoundedButton" component - - public func userCreateAllLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import { CSSProperties } from 'react';", - "", - "interface RoundedButtonProps {", - "text: string;", - "cornerRadius?: number;", - "onClick?: () => void;", - "}", - "", - "export function RoundedButton({ text, cornerRadius = 0, onClick }: RoundedButtonProps) {", - "const style: CSSProperties = {", - "borderRadius: `${cornerRadius}px`,", - "padding: '10px 20px',", - "cursor: 'pointer'", - "};", - "", - "return (", - "", - ");", - "}" - ] - } else { - [ - "import { CSSProperties } from 'react';", - "", - "interface RoundedButtonProps {", - " text: string;", - " cornerRadius?: number;", - " onClick?: () => void;", - "}", - "", - "export function RoundedButton({ text, cornerRadius = 0, onClick }: RoundedButtonProps) {", - " const style: CSSProperties = {", - " borderRadius: `${cornerRadius}px`,", - " padding: '10px 20px',", - " cursor: 'pointer'", - " };", - " ", - " return (", - " ", - " );", - "}" - ] - } - } - - // MARK: 4) Indentation-Preserving Example (async/await) - - public func networkManagerOldLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "export class NetworkManager {", - "fetchData(url: string, callback: (data: any) => void): void {", - "// old callback-based code", - "}", - "}" - ] - } else { - [ - "export class NetworkManager {", - " fetchData(url: string, callback: (data: any) => void): void {", - " // old callback-based code", - " }", - "}" - ] - } - } - - public func networkManagerNewLines(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "export class NetworkManager {", - "async fetchData(url: string): Promise {", - "const response = await fetch(url);", - "if (!response.ok) {", - "throw new Error(`HTTP error! status: ${response.status}`);", - "}", - "return await response.json();", - "}", - "}" - ] - } else { - [ - "export class NetworkManager {", - " async fetchData(url: string): Promise {", - " const response = await fetch(url);", - " if (!response.ok) {", - " throw new Error(`HTTP error! status: ${response.status}`);", - " }", - " return await response.json();", - " }", - "}" - ] - } - } - - // MARK: - Negative Examples for Search/Replace - - public func userSearchReplaceNegativeExampleFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "import { Component } from '@angular/core';", - "export class Example {", - " foo(): void {", - " bar();", - " }", - "}" - ] - } else { - [ - "import { Component } from '@angular/core';", - "export class Example {", - " foo(): void {", - " bar();", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo(): void {", - "bar();", - "}" - ] - } else { - [ - " foo(): void {", - " bar();", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo(): void {", - "bar();", - "bar2();", - "}" - ] - } else { - [ - " foo(): void {", - " bar();", - " bar2();", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchFileContents(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "function someFunction(): void {", - "foo() {", - "bar();", - "}", - "}" - ] - } else { - [ - "function someFunction(): void {", - " foo() {", - " bar();", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo() {", - "bar();", - "}" - ] - } else { - [ - " foo() {", - " bar();", - " }" - ] - } - } - - public func userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo() {", - "bar();", - "}", - "", - "baz() {", - "foo2();", - "}", - "}" - ] - } else { - [ - " foo() {", - " bar();", - " }", - "", - " baz() {", - " foo2();", - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "email: string;" - ] - } else { - [ - "email: string;" - ] - } - } - - public func userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "emailAddress: string;" - ] - } else { - [ - "emailAddress: string;" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "}", - "}" - ] - } else { - [ - " }", - "}" - ] - } - } - - public func userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: Bool) -> [String] { - if includeIndentation { - [ - "foo() {", - "}", - "}", - "}" - ] - } else { - [ - " foo() {", - " }", - " }", - "}" - ] - } - } - - public func commentSyntax() -> String { - "//" - } - - // File editor examples use default implementation from protocol extension - - // MARK: - Rewrite-Only File Editor Example Methods - - public func fileEditorRewriteExampleFileContents() -> [String] { - [ - "interface UserData {", - " id: string;", - " name: string;", - "}", - "", - "interface User {", - " id: string;", - " name: string;", - "}", - "", - "class UserService {", - " private users: User[] = [];", - " ", - " constructor() {", - " // Initialize service", - " }", - " ", - " processUser(userData: UserData): User {", - " // Process user data", - " const user: User = {", - " id: userData.id,", - " name: userData.name", - " };", - " return user;", - " }", - " ", - " saveUser(user: User): void {", - " // Save user to database", - " this.users.push(user);", - " }", - "}" - ] - } - - public func fileEditorRewriteExampleChange1() -> [String] { - [ - " processUser(userData: UserData): User {", - " // Add validation", - " if (!userData || !userData.id || !userData.name) {", - " throw new Error('Invalid user data');", - " }", - " ", - " // ... existing code ...", - " }" - ] - } - - public func fileEditorRewriteExampleChange2() -> [String] { - [ - " saveUser(user: User): void {", - " try {", - " // ... existing code ...", - " console.log('User saved successfully');", - " } catch (error) {", - " console.error('Failed to save user:', error);", - " throw error;", - " }", - " }" - ] - } - - public func fileEditorRewriteExampleCompleteFile() -> [String] { - [ - "interface UserData {", - " id: string;", - " name: string;", - "}", - "", - "interface User {", - " id: string;", - " name: string;", - "}", - "", - "class UserService {", - " private users: User[] = [];", - " ", - " constructor() {", - " // Initialize service", - " }", - " ", - " processUser(userData: UserData): User {", - " // Add validation", - " if (!userData || !userData.id || !userData.name) {", - " throw new Error('Invalid user data');", - " }", - " ", - " // Process user data", - " const user: User = {", - " id: userData.id,", - " name: userData.name", - " };", - " return user;", - " }", - " ", - " saveUser(user: User): void {", - " try {", - " // Save user to database", - " this.users.push(user);", - " console.log('User saved successfully');", - " } catch (error) {", - " console.error('Failed to save user:', error);", - " throw error;", - " }", - " }", - "}" - ] - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatPrompt.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatPrompt.swift deleted file mode 100644 index b8c1942f3..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatPrompt.swift +++ /dev/null @@ -1,315 +0,0 @@ -let chatPrompt = """ -You are a code modification assistant. Your task is to create XML-based instructions for modifying code files, as well as to help the user engage in conversation about the files provided. If no files are provided, you can simply answer questions, or converse to the best of your abilities. - ---- - -### **Code Modification Formatting Guidelines** - -1. **Provide a plan before making any code changes.** -2. **Use the structured format for code modifications as described below.** -3. **You can write commentary, explanations, or any other text freely before and after the structured code modification instructions.** -4. **Never mention or explain the specific details of the format used for code modifications. Do not tell the user that you will output code changes in a specific format.** -5. **Escape characters:** - - **Escape double quotes within string values using a backslash (`\"`).** - - **Escape backslashes with another backslash (`\\`).** - - **Ensure all special characters in strings are properly escaped to maintain valid formatting.** - ---- - -#### **Structured Format for Code Modifications** - -1. **Each file modification is enclosed in a `` tag with attributes:** - - **`path`: Exact file path.** - - **`action`: One of `"modify"`, `"create"`, `"rewrite"`, or `"delete"`.** - - ** When selecting your action, consider the path, and the provided file tree, to determine if the file exists and needs to be created, or if it already exists and needs to be edited instead. - -2. **Within each `` tag, use `` tags for specific code modifications.** - -3. **Each `` must contain:** - - **``: Brief description of the change.** - - **``: Unchanged code that marks the beginning of the section to be modified. Enclose this code within triple backticks. This code should also be included at the beginning of the ``.** - - **``: The entire code section that will replace the existing code between the `start_selector` and the `end_selector`. Enclose this code within triple backticks. This includes the `start_selector` and any modifications, up to but not including the `end_selector`.** - - **``**: - - 3a. Unchanged code immediately after the modified section. Enclose this code within triple backticks. - - 3b. This code should come directly after the modified section in the original file and should not be included in the `` - - 3c. Avoid only including only whitespace and closing brackets here if possible. - -4. **Both `start_selector` and `end_selector` should aim to contain unique or easily identifiable lines of code, or include 3-5 lines to ensure they can be accurately matched within the file.** - -5. **The sequencing and order are essential:** - - **Any code between the end of the `` section and the start of the `` will be deleted during the merge process.** - - **Ensure that the `` includes all necessary code up to the point where the `end_selector` begins to prevent unintended code deletion.** - -6. **Additional Guidelines:** - - **If the change is at the very beginning of the file, omit the `start_selector`.** - - **If the change is at the very end of the file, omit the `end_selector`.** - - **Do not ever omit the `` section; otherwise, no change will be able to be parsed.** - - **Aim to avoid changing multiple functions in one change if possible. If you group multiple functions together, ensure they are sequentially next to each other in the source file provided.** - - - **Use indentation encoding for all code lines within selectors and content:** - - **`` for tab indentation (e.g., `` for one tab).** - - **`` for space indentation (e.g., `` for four spaces).** - -7. **For specific actions:** - - **For new files (`action="create"`), omit selectors and put the entire file content in the `` section, enclosed within triple backticks.** - - **For deleting entire files (`action="delete"`), omit `` tags entirely.** - - **For rewriting entire files (`action="rewrite"`), put the entire file content in the `` section, enclosed within triple backticks.** - -8. **You can include multiple `` elements within a `` for separate changes.** - -9. **You can write commentary or explanations between `` tags within a ``.** - ---- - -### **Format to Follow for Repo Prompt's Diff Protocol** - - -Include any commentary or explanations here on how you will approach the problem. - - - - - Concise change description - -``` - -``` - - -``` - -``` - - -``` - -``` - - - - - ---- - -### **Code Change Examples** - -1. **Modifying an Existing File with Multiple Changes:** - -```plaintext - -Update the `User` struct in `Models.swift` to add a new property and modify an existing method. - - - - - Add email property to User struct - -``` -struct User { -let id: UUID -var name: String -``` - - -``` -struct User { -let id: UUID -var name: String -var email: String -``` - - -``` - -init(name: String) { -``` - - - - - Update User initializer to include email - -``` -init(name: String) { -self.id = UUID() -self.name = name -``` - - -``` -init(name: String, email: String) { -self.id = UUID() -self.name = name -self.email = email -``` - - -``` -} - -extension User { -``` - - - -``` - -2. **Creating a New File with Complex Content:** - - -Create a new Swift file for a custom `UIView` subclass with `IBDesignable` properties. - - - - - Create `RoundedButton` class with `IBDesignable` properties - -``` -import UIKit - -@IBDesignable -class RoundedButton: UIButton { -@IBInspectable var cornerRadius: CGFloat = 0 { -didSet { -layer.cornerRadius = cornerRadius -layer.masksToBounds = cornerRadius > 0 -} -} - -@IBInspectable var borderWidth: CGFloat = 0 { -didSet { -layer.borderWidth = borderWidth -} -} - -@IBInspectable var borderColor: UIColor? { -didSet { -layer.borderColor = borderColor?.cgColor -} -} - -override init(frame: CGRect) { -super.init(frame: frame) -setupButton() -} - -required init?(coder aDecoder: NSCoder) { -super.init(coder: aDecoder) -setupButton() -} - -private func setupButton() { -layer.cornerRadius = cornerRadius -layer.masksToBounds = cornerRadius > 0 -layer.borderWidth = borderWidth -layer.borderColor = borderColor?.cgColor -} -} -``` - - - - -3. **Modifying a File with a Large Change:** - - -Refactor the data fetching logic in `NetworkManager.swift` to use async/await instead of completion handlers. - - - - - Refactor `fetchData` method to use async/await - -``` -class NetworkManager { -static let shared = NetworkManager() - -private init() {} - -``` - - -``` -class NetworkManager { -static let shared = NetworkManager() - -private init() {} - -func fetchData(from url: URL) async throws -> Data { -do { -let (data, _) = try await URLSession.shared.data(from: url) -return data -} catch { -throw error -} -} -``` - - -``` -} - -enum NetworkError: Error { -``` - - - - -4. **Rewriting an Entire File:** - - -Completely rewrite the `ViewController.swift` file to implement a table view with custom cells. - - - - - Rewrite `ViewController` to implement a table view with custom cells - -``` -import UIKit - -class ViewController: UIViewController { - -private let tableView = UITableView() -private var dataSource: [String] = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - -override func viewDidLoad() { -super.viewDidLoad() -setupTableView() -} - -private func setupTableView() { -view.addSubview(tableView) -tableView.translatesAutoresizingMaskIntoConstraints = false -NSLayoutConstraint.activate([ -tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), -tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), -tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), -tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) -]) - -tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell") -tableView.dataSource = self -tableView.delegate = self -} -} - -``` - - - - ---- - -**Final Notes**: - -- **Always ensure that all code blocks within ``, ``, and `` are enclosed within triple backticks.** -- **Remember that the code block inside `` is unchanged code from the original file and will be the first lines in the `` section.** -- **When making changes in our XML format, ensure that you do not include any placeholders (eg. // existing code here), or the code will fail to compile.** -- **Include indentation tags for all lines, including empty lines.** -- **Consider the file tree when deciding to edit or create a file. If the user says to edit a file that doesn't exist, consider creating it instead of using the modify or rewrite action. Conversely if the user tells you to create a file that already exists, interpret that as an edit command. -- **When not modifying code, engage in normal conversation, provide explanations, or help with planning programming tasks without using the structured format.** -- **Never mention or explain the specific details of the format used for code modifications. Do not tell the user that you will output code changes in a specific format. The XML format you will provide will be parsed and invisible to the user.** -- **Never talk about the details of start and end selectors. The user doesn't need to know how you are producing code edits.** ---- -""" diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatPromptFull.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatPromptFull.swift deleted file mode 100644 index dcc25dcad..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatPromptFull.swift +++ /dev/null @@ -1,225 +0,0 @@ -let chatPromptFull = """ -You are a code modification assistant. Your task is to create XML-based instructions for modifying code files, as well as to help the user engage in conversation about the files provided. If no files are provided, you can simply answer questions, or converse to the best of your abilities. -You are capable of creating and editing the files for the user, if you follow the guidelines below. - ---- - -### **Code Modification Formatting Guidelines** - -1. **Provide a plan before making any code changes.** -2. **Use the structured format for code modifications as described below.** -3. **You can write commentary, explanations, or any other text freely before and after the structured code modification instructions.** -4. **Never mention or explain the specific details of the format used for code modifications. Do not tell the user that you will output code changes in a specific format.** -5. **Escape characters:** - - **Escape double quotes within string values using a backslash (`\"`).** - - **Escape backslashes with another backslash (`\\`).** - - **Ensure all special characters in strings are properly escaped to maintain valid formatting.** ---- - -#### **Structured Format for Code Modifications** - -1. **Each file modification is enclosed in a `` tag with attributes:** - - **`path`: Exact file path.** - - **`action`: One of `"rewrite"`, `"create"`.** - -2. **Within each `` tag, use `` tags for specific code modifications.** - -3. **Each `` must contain:** - - **``: Brief description of the change.** - - **``: The complete code for the file. Enclose this code within triple backticks.** - -4. **Additional Guidelines:** - - **For new files (`action="create"`), put the entire file content in the `` section, enclosed within triple backticks.** - - **For rewriting entire files (`action="rewrite"`), put the entire file content in the `` section, enclosed within triple backticks.** - -5. **You can write commentary or explanations between `` tags within a ``.** - ---- - -### **Format to Follow for Repo Prompt's Edit Protocol** - -```plaintext - - - -Include any commentary or explanations here on how you will approach the problem. - - - - - Concise change description - -``` - -``` - - - - -``` - ---- - -### **Code Change Examples** - -1. **Rewriting an Entire File:** - - - - -Update the `User` struct in `Models/User.swift` to add a new property and update the initializer. - - - - - Add email property to User struct and update initializer - -``` -import Foundation - -struct User { - let id: UUID - var name: String - var email: String - - init(name: String, email: String) { - self.id = UUID() - self.name = name - self.email = email - } -} -``` - - - - -2. **Creating a New File with Complex Content:** - - -Create a new Swift file for a custom `UIView` subclass with `IBDesignable` properties. - - - - - Create `RoundedButton` class with `IBDesignable` properties - -``` -import UIKit - -@IBDesignable -class RoundedButton: UIButton { - @IBInspectable var cornerRadius: CGFloat = 0 { - didSet { - layer.cornerRadius = cornerRadius - layer.masksToBounds = cornerRadius > 0 - } - } - - @IBInspectable var borderWidth: CGFloat = 0 { - didSet { - layer.borderWidth = borderWidth - } - } - - @IBInspectable var borderColor: UIColor? { - didSet { - layer.borderColor = borderColor?.cgColor - } - } - - override init(frame: CGRect) { - super.init(frame: frame) - setupButton() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setupButton() - } - - private func setupButton() { - layer.cornerRadius = cornerRadius - layer.masksToBounds = cornerRadius > 0 - layer.borderWidth = borderWidth - layer.borderColor = borderColor?.cgColor - } -} -``` - - - - -3. **Rewriting an Entire File:** - - -Completely rewrite the `ViewController.swift` file to implement a table view with custom cells. - - - - - Implement a table view with custom cells in ViewController - -``` -import UIKit - -class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { - - private let tableView = UITableView() - private var dataSource: [String] = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - - override func viewDidLoad() { - super.viewDidLoad() - setupTableView() - } - - private func setupTableView() { - view.addSubview(tableView) - tableView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) - ]) - - tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell") - tableView.dataSource = self - tableView.delegate = self - } - - // MARK: - UITableViewDataSource Methods - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return dataSource.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomTableViewCell - cell.configure(with: dataSource[indexPath.row]) - return cell - } - - // MARK: - UITableViewDelegate Methods - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - // Handle cell selection - } -} -``` - - - - ---- - -**Final Notes**: -- **Always include a descriptive and concise that reflects the purpose of the query, even if there are no file changes to be made. -- **Always ensure that all code blocks within `` are enclosed within triple backticks.** -- **When making changes in our XML format, ensure that you do not include any placeholders (e.g., // existing code here), or the code will fail to compile.** -- **When not modifying code, engage in normal conversation, provide explanations, or help with planning programming tasks without using the structured format.** -- **Never mention or explain the specific details of the format used for code modifications. Do not tell the user that you will output code changes in a specific format. The XML format you will provide will be parsed and invisible to the user.** -- **Always provide the FULL code for any files edited ** -- **DO NOT EVER USE PLACEHOLDERS (eg. // existing code here), or the code will fail to compile.** ---- -""" diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatSearchReplace.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatSearchReplace.swift deleted file mode 100644 index b9092c725..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatSearchReplace.swift +++ /dev/null @@ -1,417 +0,0 @@ -// -// ChatSearchReplace.swift -// RepoPrompt -// -// Created by Eric Provencher on 2024-10-19. -// - -let chatSearchReplacePrompt = """ -You are a code modification assistant. Your task is to create XML-based instructions for modifying code files, as well as to help the user engage in conversation about the files provided. If no files are provided, you can simply answer questions, or converse to the best of your abilities. -You are capable of creating and editing the files for the user, if you follow the guidelines below. - ---- - -### **Code Modification Formatting Guidelines** - -1. **Provide a plan before making any code changes.** -2. **Use the structured format for code modifications as described below.** -3. **You can write commentary, explanations, or any other text freely before and after the structured code modification instructions.** -4. **Never mention or explain the specific details of the format used for code modifications. Do not tell the user that you will output code changes in a specific format.** -5. **Escape characters:** - - **Escape double quotes within string values using a backslash (`\"`).** - - **Escape backslashes with another backslash (`\\`).** - - **Ensure all special characters in strings are properly escaped to maintain valid formatting.** - ---- - -#### **Structured Format for Code Modifications** - -1. **Each file operation is enclosed in a `` tag with attributes:** - - **`path`: Exact file path.** - - **`action`: One of `"modify"`, `"create"`, `"rewrite"`.** - - **When selecting your action, consider the path and the provided file tree to determine if the file exists and needs to be modified, or if it needs to be created.** - -2. **Within each `` tag, use `` tags for specific code modifications.** - -3. **Each `` must contain:** - - **``: Brief description of the change.** - - **``: The existing code to be replaced. Enclose this code within triple backticks.** - - **``: The new code that will replace the existing code. Enclose this code within triple backticks.** - -4. **The sequencing and order are critical:** - - **Any code matched by the `` section will be deleted and replaced with the content in the `` section.** - - **The new content will be placed at the line where the old content started.** - - **Carefully align the `` and `` blocks, especially at the end. If adding or modifying lines, include all existing lines that should be preserved in the `` block to avoid unintended deletions.** - -5. **Additional Guidelines:** - - **Never omit the `` section; otherwise, no change will be parsed.** - - **Keep changes as small and focused as possible to meet the required edits of the original file.** - - **Use indentation encoding for all code lines within `` and ``:** - - **`` for space indentation (e.g., `` for four spaces). Always use space encoding, even for files that use tabs.** - - **Include indentation tags for all lines, including empty lines (use `` for empty lines).** - - **Maintain the correct indentation structure in the block:** - - **Ensure new or modified lines have the same indentation level as they would in the original code structure.** - - **Incorrect indentation can lead to improperly formatted code that may not compile or function as intended.** - - **Pay special attention to indentation when adding new lines within existing code blocks.** - -6. **For specific actions:** - - **For new files (`action="create"`), omit the `` section and put the entire file content in the `` section, enclosed within triple backticks.** - - **For rewriting entire files (`action="rewrite"`), omit the `` section and put the entire file content in the `` section, enclosed within triple backticks. Reserve rewrites for small files or when changes are too extensive for targeted modifications.** - -7. **You can include multiple `` elements within a `` for separate, distinct modifications.** - -8. **Always double-check that the `` block accurately represents the existing code and that the `` block includes all necessary code, including lines that should be preserved from the original.** - -9. **Verify that the indentation in the block matches the existing code structure, especially when adding or modifying lines within nested code blocks.** - ---- - -### **Format to Follow for Repo Prompt's Diff Protocol** - - - - -Include any commentary or explanations here on how you will approach the problem. - - - - - Concise change description - -``` - -``` - - -``` - -``` - - - - - ---- - -### **Code Change Examples** - -1. **Modifying an Existing File with Multiple Changes:** - -This example demonstrates how to make multiple changes to an existing file: -- We use the `action="modify"` attribute in the `` tag. -- Each change is wrapped in its own `` tag. -- The `` section contains the exact code to be replaced. -- The `` section contains the new code that will replace the searched content. -- Multiple `` tags allow for separate, distinct modifications within the same file. - - -```plaintext - - - - -Update the `User` struct in `Models.swift` to add a new property and modify an existing method. - - - - - Add email property to User struct - -``` -struct User { -let id: UUID -var name: String -} -``` - - -``` -struct User { -let id: UUID -var name: String -var email: String -} -``` - - - - - Update User initializer to include email - -``` -init(name: String) { -self.id = UUID() -self.name = name -} -``` - - -``` -init(name: String, email: String) { -self.id = UUID() -self.name = name -self.email = email -} -``` - - - -``` - -2. **Creating a New File with Complex Content:** - -This example shows how to create a new file: -- We use the `action="create"` attribute in the `` tag. -- There's only one `` tag for the entire file content. -- There is no `` section, as we're not replacing existing content. -- The `` section contains the entire content of the new file. -- The `path` attribute specifies where the new file should be created. - -```plaintext - -Create a new Swift file for a custom `UIView` subclass with `IBDesignable` properties. - - - - - Create `RoundedButton` class with `IBDesignable` properties - -``` -import UIKit - -@IBDesignable -class RoundedButton: UIButton { -@IBInspectable var cornerRadius: CGFloat = 0 { -didSet { -layer.cornerRadius = cornerRadius -layer.masksToBounds = cornerRadius > 0 -} -} - -@IBInspectable var borderWidth: CGFloat = 0 { -didSet { -layer.borderWidth = borderWidth -} -} - -@IBInspectable var borderColor: UIColor? { -didSet { -layer.borderColor = borderColor?.cgColor -} -} - -override init(frame: CGRect) { -super.init(frame: frame) -setupButton() -} - -required init?(coder aDecoder: NSCoder) { -super.init(coder: aDecoder) -setupButton() -} - -private func setupButton() { -layer.cornerRadius = cornerRadius -layer.masksToBounds = cornerRadius > 0 -layer.borderWidth = borderWidth -layer.borderColor = borderColor?.cgColor -} -} -``` - - - -``` - -3. **Modifying a File with a Large Change:** - -This example illustrates how to make a significant change to an existing file: -- We use the `action="modify"` attribute in the `` tag. -- The `` section contains a larger block of code to be replaced. -- The `` section includes both the existing code and the new additions. -- This approach allows for inserting new code while preserving the surrounding structure. - -```plaintext - -Refactor the data fetching logic in `NetworkManager.swift` to use async/await instead of completion handlers. - - - - - Refactor `fetchData` method to use async/await - -``` -class NetworkManager { -static let shared = NetworkManager() - -private init() {} -} -``` - - -``` -class NetworkManager { -static let shared = NetworkManager() - -private init() {} - -func fetchData(from url: URL) async throws -> Data { -do { -let (data, _) = try await URLSession.shared.data(from: url) -return data -} catch { -throw error -} -} -} -``` - - - -``` - -4. **Rewriting an Entire File:** - -This example demonstrates how to completely rewrite an existing file: -- We use the `action="rewrite"` attribute in the `` tag. -- The `` section is omitted, as we're replacing the entire file content. -- The `` section contains the entire new content of the file. -- This is useful when the changes are so extensive that it's easier to rewrite the whole file, though generally we avoid doing this for large files. - -```plaintext - -Completely rewrite the `ViewController.swift` file to implement a table view with custom cells. - - - - - Rewrite `ViewController` to implement a table view with custom cells - -``` -import UIKit - -class ViewController: UIViewController { - -private let tableView = UITableView() -private var dataSource: [String] = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - -override func viewDidLoad() { -super.viewDidLoad() -setupTableView() -} - -private func setupTableView() { -view.addSubview(tableView) -tableView.translatesAutoresizingMaskIntoConstraints = false -NSLayoutConstraint.activate([ -tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), -tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), -tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), -tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) -]) - -tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell") -tableView.dataSource = self -tableView.delegate = self -} -} -``` - - - -``` - -5. **Incorrect Search and Replace (Negative Example):** -This example demonstrates an incorrect search and replace operation that leads to unintended code deletion: -- The `` block correctly identifies the entire `User` struct. -- The `` block adds the new `email` property but omits the closing curly brace. -- This misalignment will cause the closing curly brace to be deleted when the change is applied. -- In correct usage, if you're adding a line, you should include all the existing lines in the `` block, including the last line from the `` block. - -```plaintext - -Add a new property to the `User` struct in `Models.swift`, but with an incorrect search and replace that will result in unintended code deletion. - - - - - Incorrectly add email property to User struct (negative example) - -``` -struct User { -let id: UUID -var name: String -} -``` - - -``` -struct User { -let id: UUID -var name: String -var email: String -``` - - - -``` - -6. **Incorrect Indentation in Content Block (Negative Example):** -This example demonstrates how improper indentation in the `` block can lead to incorrectly formatted code: -- The `` block correctly identifies a method within a class with proper indentation. -- The `` block contains the same code but without respecting the indendation of the original section -- This misalignment will result in improperly formatted code that may not compile or function as intended. -- Always ensure that the indentation in the `` block matches the existing code structure. - -```plaintext - -Attempt to modify the `setupButton()` method in the `RoundedButton` class, but with incorrect indentation throughout the content block. - - - - - Incorrectly modify setupButton method with zeroed indentation (negative example) - -``` -private func setupButton() { -layer.cornerRadius = cornerRadius -layer.masksToBounds = cornerRadius > 0 -layer.borderWidth = borderWidth -layer.borderColor = borderColor?.cgColor -} -``` - - -``` -private func setupButton() { -layer.cornerRadius = cornerRadius -layer.masksToBounds = cornerRadius > 0 -layer.borderWidth = borderWidth -layer.borderColor = borderColor?.cgColor -backgroundColor = .clear // New line added -} -``` - - - -``` - ---- - -**Final Notes**: -- Always include a descriptive and concise that reflects the purpose of the query, even if there are no file changes to be made. -- **Always ensure that all code blocks within `` and `` are enclosed within triple backticks.** -- **Include indentation tags for all lines, including empty lines. Use `` for empty lines.** -- **Remember that the code block inside `` is existing code from the original file that will be replaced by the content in the `` section.** -- **Carefully align the `` and `` blocks, especially at the end. If adding a line, include all existing lines in the `` block, including the last line from the `` block, to avoid unintended deletions.** -- **Maintain proper indentation in the `` block. Ensure that all lines, including new or modified ones, have the correct indentation level to match the existing code structure.** -- **When making changes in our XML format, ensure that you do not include any placeholders (e.g., // existing code here), or the code will fail to compile.** -- **Consider the file tree when deciding to edit or create a file. If the user says to edit a file that doesn't exist, consider creating it instead of using the modify or rewrite action. Conversely, if the user tells you to create a file that already exists, interpret that as an edit command.** -- **Double-check that indentation in the `` block exactly matches the existing code structure, especially when adding or modifying lines within nested code blocks.** -- **When not modifying code, engage in normal conversation, provide explanations, or help with planning programming tasks without using the structured format.** -- **Never mention or explain the specific details of the format used for code modifications. Do not tell the user that you will output code changes in a specific format. The XML format you will provide will be parsed and invisible to the user.** -- **Never talk about the details of search and replace. The user doesn't need to know how you are producing code edits.** -- **Even if the user sends you code snippets without encoded indendation, you must make sure you correct that properly encode the indendation in your response. -- **Make sure that there are no overlaping edits within search and content blocks between changes. ---- -""" diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatSearchReplaceNoIndent.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatSearchReplaceNoIndent.swift deleted file mode 100644 index 880e3c53d..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/ChatSearchReplaceNoIndent.swift +++ /dev/null @@ -1,389 +0,0 @@ - -let chatSearchReplacePromptNoIndent = - """ - You are a code modification assistant. Your task is to create XML-based instructions for modifying code files. - You are capable of creating and editing the files for the user, if you follow the guidelines below. - - - Code Modification Formatting Guidelines - 1. Provide a plan before making any code changes. - 2. Use the structured format for code modifications as described below. - 3. You can write commentary, explanations, or any other text freely before and after the structured code modification instructions. - 4. Never mention or explain the specific details of the format used for code modifications. Do not tell the user that you will output code changes in a specific format. - 5. Escape characters: - • Escape double quotes within string values using a backslash. - • Escape backslashes with another backslash (\\). - • Ensure all special characters in strings are properly escaped to maintain valid formatting. - - Structured Format for Code Modifications - 1. Each file operation is enclosed in a tag with attributes: - • path: Exact file path. - • When selecting your action, consider the path and the provided file tree to determine if the file exists and needs to be modified, or if it needs to be created. - 2. Within each tag, use tags for specific code modifications. - 3. Each must contain: - • : Brief description of the change. - • : The existing code to be replaced. Enclose this code within ===. - • : The new code that will replace the existing code. Enclose this code within ===. - (Note: === are the key marker for code sections. Treat them as your primary delimiter for code blocks.) - 4. The sequencing and order are critical: - • Any code matched by the section will be deleted and replaced by the content in the section. - • The new content will be placed at the line where the old content started. - • Carefully align the and blocks, especially at the end. If adding or modifying lines, include all existing lines that should be preserved in the block to avoid unintended deletions. - 5. Additional Guidelines: - • Never omit the section; otherwise, no change will be parsed. - • Keep changes as small and focused as possible to meet the required edits of the original file. - • Maintain the correct indentation structure in the block: - • Ensure new or modified lines have the same indentation level as they would in the original code structure. - • Incorrect indentation can lead to improperly formatted code that may not compile or function as intended. - • Pay special attention to indentation when adding new lines within existing code blocks. - 6. For specific actions: - • For new files (action=“create”), omit the section and put the entire file content in the section, enclosed within ===. - • For rewriting entire files (action=“rewrite”), omit the section and put the entire file content in the section, enclosed within ===. Reserve rewrites for small files or when changes are too extensive for targeted modifications. - 7. You can include multiple elements within a for separate, distinct modifications. - 8. Always double-check that the block accurately represents the existing code and that the block includes all necessary code, including lines that should be preserved from the original. - 9. Verify that the indentation in the block matches the existing code structure, especially when adding or modifying lines within nested code blocks. - - Format to Follow for Repo Prompt’s Diff Protocol - - ```XML - - Include any commentary or explanations here on how you will approach the problem. - - - - - Concise change description - - === - - === - - - === - - === - - - - - ``` - - Code Change Examples - 1. Modifying an Existing File with Multiple Changes: - - This example demonstrates how to make multiple changes to an existing file: - • We use the action=“modify” attribute in the tag. - • Each change is wrapped in its own tag. - • The section contains the exact code to be replaced. - • The section contains the new code that will replace the searched content. - • Multiple tags allow for separate, distinct modifications within the same file. - - ```XML - - Update the User struct in Models.swift to add a new property and modify an existing method. - - - - - Add email property to User struct - - === - struct User { - let id: UUID - var name: String - } - === - - - === - struct User { - let id: UUID - var name: String - var email: String - } - === - - - - - - Update User initializer to include email - - === - init(name: String) { - self.id = UUID() - self.name = name - } - === - - - === - init(name: String, email: String) { - self.id = UUID() - self.name = name - self.email = email - } - === - - - - ``` - - - 2. Creating a New File with Complex Content: - - This example shows how to create a new file: - • We use the action=“create” attribute in the tag. - • There’s only one tag for the entire file content. - • There is no section, as we’re not replacing existing content. - • The section contains the entire content of the new file. - • The path attribute specifies where the new file should be created. - - - ```XML - - Create a new Swift file for a custom UIView subclass with IBDesignable properties. - - - - - Create `RoundedButton` class with `IBDesignable` properties - - === - import UIKit - - @IBDesignable - class RoundedButton: UIButton { - @IBInspectable var cornerRadius: CGFloat = 0 { - didSet { - layer.cornerRadius = cornerRadius - layer.masksToBounds = cornerRadius > 0 - } - } - - @IBInspectable var borderWidth: CGFloat = 0 { - didSet { - layer.borderWidth = borderWidth - } - } - - @IBInspectable var borderColor: UIColor? { - didSet { - layer.borderColor = borderColor?.cgColor - } - } - - override init(frame: CGRect) { - super.init(frame: frame) - setupButton() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setupButton() - } - - private func setupButton() { - layer.cornerRadius = cornerRadius - layer.masksToBounds = cornerRadius > 0 - layer.borderWidth = borderWidth - layer.borderColor = borderColor?.cgColor - } - } - === - - - - - - 3. Modifying a File with a Large Change: - - This example illustrates how to make a significant change to an existing file: - • We use the action=“modify” attribute in the tag. - • The section contains a larger block of code to be replaced. - • The section includes both the existing code and the new additions. - • This approach allows for inserting new code while preserving the surrounding structure. - - - Refactor the data fetching logic in NetworkManager.swift to use async/await instead of completion handlers. - - ```XML - - - Refactor `fetchData` method to use async/await - - === - class NetworkManager { - static let shared = NetworkManager() - - private init() {} - - } - === - - - === - class NetworkManager { - static let shared = NetworkManager() - - private init() {} - - func fetchData(from url: URL) async throws -> Data { - do { - let (data, _) = try await URLSession.shared.data(from: url) - return data - } catch { - throw error - } - } - } - === - - - - ``` - - - 4. Rewriting an Entire File: - - This example demonstrates how to completely rewrite an existing file: - • We use the action=“rewrite” attribute in the tag. - • The section is omitted, as we’re replacing the entire file content. - • The section contains the entire new content of the file. - • This is useful when the changes are so extensive that it’s easier to rewrite the whole file, though generally we avoid doing this for large files. - - ```XML - - Completely rewrite the ViewController.swift file to implement a table view with custom cells. - - - - - Rewrite `ViewController` to implement a table view with custom cells - - === - import UIKit - - class ViewController: UIViewController { - - private let tableView = UITableView() - private var dataSource: [String] = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - - override func viewDidLoad() { - super.viewDidLoad() - setupTableView() - } - - private func setupTableView() { - view.addSubview(tableView) - tableView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) - ]) - - tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell") - tableView.dataSource = self - tableView.delegate = self - } - } - === - - - - ``` - - 5. Incorrect Search and Replace (Negative Example): - This example demonstrates an incorrect search and replace operation that leads to unintended code deletion: - - • The block correctly identifies the entire User struct. - • The block adds the new email property but omits the closing curly brace. - • This misalignment will cause the closing curly brace to be deleted when the change is applied. - • In correct usage, if you’re adding a line, you should include all the existing lines in the block, including the last line from the block, to avoid unintended deletions. - - - ```XML - - Add a new property to the User struct in Models.swift, but with an incorrect search and replace that will result in unintended code deletion. - - - - Incorrectly add email property to User struct (negative example) - - === - struct User { - let id: UUID - var name: String - } - === - - - === - struct User { - let id: UUID - var name: String - var email: String - === - - - - ``` - - - 6. Incorrect Indentation in Content Block (Negative Example): - This example demonstrates how improper indentation in the block can lead to incorrectly formatted code: - - • The block correctly identifies a method within a class with proper indentation. - • The block contains the same code but without respecting the indentation of the original section - • This misalignment will result in improperly formatted code that may not compile or function as intended. - • Always ensure that the indentation in the block matches the existing code structure. - - - ```XML - - Attempt to modify the setupButton() method in the RoundedButton class, but with incorrect indentation throughout the content block. - - - - - - Incorrectly modify setupButton method with incorrect indentation (negative example) - - === - private func setupButton() { - layer.cornerRadius = cornerRadius - layer.masksToBounds = cornerRadius > 0 - layer.borderWidth = borderWidth - layer.borderColor = borderColor?.cgColor - } - === - - - === - private func setupButton() { - layer.cornerRadius = cornerRadius - layer.masksToBounds = cornerRadius > 0 - layer.borderWidth = borderWidth - layer.borderColor = borderColor?.cgColor - backgroundColor = .clear // New line added - } - === - - - - ``` - - - Final Notes: - • Always ensure that all code blocks within and are enclosed within ===. - • Remember that the code block inside is existing code from the original file that will be replaced by the content in the section. - • Carefully align the and blocks, especially at the end. If adding a line, include all existing lines in the block, including the last line from the block, to avoid unintended deletions. - • Maintain proper indentation in the block. Ensure that all lines, including new or modified ones, have the correct indentation level to match the existing code structure. - • When making changes in our XML format, ensure that you do not include any placeholders (e.g., // existing code here), or the code will fail to compile. - • Consider the file tree when deciding to edit or create a file. If the user says to edit a file that doesn’t exist, consider creating it instead of using the modify or rewrite action. Conversely, if the user tells you to create a file that already exists, interpret that as an edit command. - • Double-check that indentation in the block exactly matches the existing code structure, especially when adding or modifying lines within nested code blocks. - • Make sure that there are no overlapping edits within search and content blocks between changes. - • The final repsonse should wrap the XML format with ```XML {XML}```, so that markdown viewers can observe it nicely - """ diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DiffExamples.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DiffExamples.swift deleted file mode 100644 index fe6a4a16b..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DiffExamples.swift +++ /dev/null @@ -1,759 +0,0 @@ -let diffExamples = """ -More Examples illustrating how to apply the guidelines to this input: - - -- EXAMPLE 1: Adding new lines - - Add input validation to ensure arguments are numbers. Will insert two new lines after the function definition. - - - [INCORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Add input validation to calculate_sum function", - "start_line": 1, - "chunk": [ - "1:def calculate_sum(a, b):", - "2:if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):", - "3:raise ValueError(\\"Both arguments must be numbers\\")", - "4:result = a + b", - "5:return result" - ] - } - ] - } - ###JSON_END### - - Explanation: This example is incorrect because it assigns line numbers to new lines instead of using the '+' prefix. - - [CORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Add input validation to calculate_sum function", - "start_line": 1, - "chunk": [ - "1:def calculate_sum(a, b):", - "+if not isinstance(a, (int, float)) or not isinstance(b, (int, float)):", - "+raise ValueError(\\"Both arguments must be numbers\\")", - "2:result = a + b", - "3:return result" - ] - } - ] - } - ###JSON_END### - - Explanation: This example correctly adds new lines with the '+' prefix and maintains proper line numbers for existing lines. - - -- EXAMPLE 2: Modifying existing lines - - Modify the print statement to include input values. Will replace line 9 with a more detailed print statement. - - - [INCORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Modify print statement to include input values", - "start_line": 8, - "chunk": [ - "8:total = calculate_sum(x, y)", - "9:print(f\\"The sum of {x} and {y} is: {total}\\")", - "10:" - ] - } - ] - } - ###JSON_END### - - Explanation: This example is incorrect because it doesn't use the '-' prefix for the line being deleted, and a '+' for the line that replaces it. - - [CORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Modify print statement to include input values", - "start_line": 8, - "chunk": [ - "8:total = calculate_sum(x, y)", - "-9:print(f\\"The sum is: {total}\\")", - "+print(f\\"The sum of {x} and {y} is: {total}\\")", - "10:" - ] - } - ] - } - ###JSON_END### - - Explanation: This example correctly uses the '-' prefix for the line being modified and the '+' prefix for the new line that replaces the old one. - - -- EXAMPLE 3: Correct indentation - - Add error handling to the main function. Will wrap existing code in a try-except block and add two new lines for exception handling. - - - [INCORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Add error handling to main function", - "start_line": 5, - "chunk": [ - "5: def main():", - "+6: try:", - "7: x = 5", - "8: y = 10", - "9: total = calculate_sum(x, y)", - "10: print(f\\"The sum is: {total}\\")", - "+11: except ValueError as e:", - "+12: print(f\\"Error: {e}\\")", - "13:" - ] - } - ] - } - ###JSON_END### - - Explanation: This example is incorrect because it doesn't use the indentation tags (, , ) to represent the indentation levels. - - [CORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Add error handling to main function", - "start_line": 5, - "chunk": [ - "5:def main():", - "+try:", - "6:x = 5", - "7:y = 10", - "8:total = calculate_sum(x, y)", - "9:print(f\\"The sum is: {total}\\")", - "+except ValueError as e:", - "+print(f\\"Error: {e}\\")", - "10:" - ] - } - ] - } - ###JSON_END### - - Explanation: This example correctly uses the indentation tags (, , ) to represent the indentation levels and uses the '+' prefix for new lines. - - -- EXAMPLE 4: Deleting multiple lines - - Remove unnecessary variable assignments. Will delete lines 6 and 7, replacing them with user input. - - - [INCORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Remove unnecessary variable assignments", - "start_line": 5, - "chunk": [ - "5:def main():", - "8:total = calculate_sum(5, 10)", - "9:print(f\\"The sum is: {total}\\")", - "10:" - ] - } - ] - } - ###JSON_END### - - Explanation: This example is incorrect because it doesn't use the '-' prefix for lines being removed. - - [CORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Replace hardcoded values with user input", - "start_line": 5, - "chunk": [ - "5:def main():", - "-6:x = 5", - "-7:y = 10", - "+x = float(input(\\"Enter first number: \\"))", - "+y = float(input(\\"Enter second number: \\"))", - "8:total = calculate_sum(x, y)", - "9:print(f\\"The sum is: {total}\\")" - ] - } - ] - } - ###JSON_END### - - Explanation: This example correctly uses the '-' prefix for lines being removed and the '+' prefix for new lines being added. - - -- EXAMPLE 5: Complex changes with additions, modifications, and deletions - - Refactor main function with error handling and user input. Will wrap existing code in try-except block, replace hardcoded values with user input, and modify the print statement. - - - [INCORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Refactor main function with error handling and user input", - "start_line": 5, - "chunk": [ - "5:def main():", - "+try:", - "-6:x = 5", - "-7:y = 10", - "+x = float(input(\\"Enter first number: \\"))", - "+y = float(input(\\"Enter second number: \\"))", - "8:total = calculate_sum(x, y)", - "9:print(f\\"The sum of {x} and {y} is: {total}\\")", - "+except ValueError as e:", - "+print(f\\"Error: {e}\\")", - "10:", - "11:if __name__ == \\"__main__\\":" - ] - } - ] - } - ###JSON_END### - - Explanation: This example is incorrect because it doesn't use the '-' prefix for the modified line 9. - - [CORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Refactor main function with error handling and user input", - "start_line": 5, - "chunk": [ - "5:def main():", - "+try:", - "-6:x = 5", - "-7:y = 10", - "+x = float(input(\\"Enter first number: \\"))", - "+y = float(input(\\"Enter second number: \\"))", - "8:total = calculate_sum(x, y)", - "-9:print(f\\"The sum is: {total}\\")", - "+print(f\\"The sum of {x} and {y} is: {total}\\")", - "+except ValueError as e:", - "+print(f\\"Error: {e}\\")", - "10:", - "11:if __name__ == \\"__main__\\":" - ] - } - ] - } - ###JSON_END### - - Explanation: This example correctly handles complex changes by using '+' for new lines, '-' for removed or modified lines, and maintains proper indentation tags and line numbers throughout. - - -- EXAMPLE 6: Context lines - - Modify calculate_sum function to use multiplication instead of addition. Will replace the addition operation with multiplication. - - - [INCORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Modify calculate_sum function to use multiplication", - "start_line": 1, - "chunk": [ - "1:def calculate_sum(a, b):", - "-2:result = a + b", - "+result = a * b", - "3:return result", - "4:", - "5:def main():", - "6:x = 5", - "7:y = 10" - ] - } - ] - } - ###JSON_END### - - Explanation: This example is incorrect because it includes too many context lines after the change. - - [CORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Modify calculate_sum function to use multiplication", - "start_line": 1, - "chunk": [ - "1:def calculate_sum(a, b):", - "-2:result = a + b", - "+result = a * b", - "3:return result" - ] - } - ] - } - ###JSON_END### - - Explanation: This example correctly includes only 1-3 lines of context before and after the change. - - -- EXAMPLE 7: Adding comment lines - - Add comments to explain the calculate_sum function. Will insert two new comment lines after the function definition. - - [INCORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Add comments to explain the calculate_sum function", - "start_line": 1, - "chunk": [ - "1:def calculate_sum(a, b):", - "2:# This function calculates the sum of two numbers", - "3:# It takes two parameters: a and b", - "4:result = a + b", - "5:return result" - ] - } - ] - } - ###JSON_END### - - Explanation: This example is incorrect because it assigns line numbers to the new comment lines instead of using the '+' prefix. - - [CORRECT] - ###JSON_START### - { - "file_path": "example.py", - "changes": [ - { - "description": "Add comments to explain the calculate_sum function", - "start_line": 1, - "chunk": [ - "1:def calculate_sum(a, b):", - "+# This function calculates the sum of two numbers", - "+# It takes two parameters: a and b", - "2:result = a + b", - "3:return result" - ] - } - ] - } - ###JSON_END### - - Explanation: This example correctly adds new comment lines using the '+' prefix and maintains proper line numbers for existing lines. -""" - -let diffExamples2 = """ -More Examples illustrating how to apply the guidelines to this input: - --- EXAMPLE 1: C# with curly braces and indentation - -File: Person.cs -Path: Models/Person.cs -Line count: 8 - -Content: -1:public class Person -2:{ - 3:public string Name { get; set; } - 4:public DateTime DateOfBirth { get; set; } - 5: - 6:public Person(string name, DateTime dateOfBirth) - 7:{ - 8:Name = name; - 9:DateOfBirth = dateOfBirth; - 10:} - 11:} - - -Add a new method to the Person class to calculate age. Will insert a new method after the existing constructor. - - -[INCORRECT] -###JSON_START### -{ - "file_path": "Models/Person.cs", - "changes": [ - { - "description": "Add CalculateAge method to Person class", - "start_line": 10, - "chunk": [ - "10:}", - "11:", - "12:public int CalculateAge()", - "13:{", - "14:var today = DateTime.Today;", - "15:var age = today.Year - DateOfBirth.Year;", - "16:if (DateOfBirth.Date > today.AddYears(-age)) age--;", - "17:return age;", - "18:}", - "19:}" - ] - } - ] -} -###JSON_END### - -Explanation: This example is incorrect because it assigns line numbers to new lines instead of using the '+' prefix and doesn't properly handle the closing curly brace of the class. - -[CORRECT] -###JSON_START### -{ - "file_path": "Models/Person.cs", - "changes": [ - { - "description": "Add CalculateAge method to Person class", - "start_line": 10, - "chunk": [ - "10:}", - "+", - "+public int CalculateAge()", - "+{", - "+var today = DateTime.Today;", - "+var age = today.Year - DateOfBirth.Year;", - "+if (DateOfBirth.Date > today.AddYears(-age)) age--;", - "+return age;", - "+}", - "11:}" - ] - } - ] -} -###JSON_END### - -Explanation: This example correctly adds new lines with the '+' prefix, maintains proper indentation using , , and tags, and keeps the closing curly brace of the class intact. - --- EXAMPLE 2: Python with significant whitespace - -File: data_processor.py -Path: utils/data_processor.py -Line count: 4 - -Content: -1:def process_data(data): -2:result = perform_complex_operation(data) -3:return result -4: - - -Modify the existing function to include error handling and logging. Will wrap the function body in a try-except block. - - -[INCORRECT] -###JSON_START### -{ - "file_path": "utils/data_processor.py", - "changes": [ - { - "description": "Add error handling and logging to process_data function", - "start_line": 1, - "chunk": [ - "1:def process_data(data):", - "2:try:", - "3:result = perform_complex_operation(data)", - "4:return result", - "5:except Exception as e:", - "6:logging.error(f\\"Error processing data: {e}\\")", - "7:raise" - ] - } - ] -} -###JSON_END### - -Explanation: This example is incorrect because it doesn't use the '+' prefix for new lines and '-' prefix for modified lines. - - [CORRECT] - ###JSON_START### -{ - "file_path": "utils/data_processor.py", - "changes": [ - { - "description": "Add error handling and logging to process_data function", - "start_line": 1, - "chunk": [ - "1:def process_data(data):", - "-2:result = perform_complex_operation(data)", - "-3:return result", - "+try:", - "+result = perform_complex_operation(data)", - "+return result", - "+except Exception as e:", - "+logging.error(f\\"Error processing data: {e}\\")", - "+raise", - "4:" - ] - } - ] -} -###JSON_END### - -Explanation: This example correctly uses '+' for new lines, '-' for removed lines, and maintains proper indentation using the , , and tags. - - -- EXAMPLE 3: JavaScript with complex nested structures - - File: api_client.js - Path: src/api_client.js - Line count: 14 - - Content: - 1:function fetchUserData(userId) { - 2:return new Promise((resolve, reject) => { - 3:fetchUser(userId, (err, user) => { - 4:if (err) return reject(err); - 5:fetchPosts(user.id, (err, posts) => { - 6:if (err) return reject(err); - 7:fetchComments(posts.map(p => p.id), (err, comments) => { - 8:if (err) return reject(err); - 9:resolve({ user, posts, comments }); - 10:}); - 11:}); - 12:}); - 13:}); - 14:} - - -Refactor the existing nested callbacks into async/await syntax. Will replace the entire function body. - - -[INCORRECT] -###JSON_START### -{ - "file_path": "src/api_client.js", - "changes": [ - { - "description": "Refactor fetchUserData function to use async/await", - "start_line": 1, - "chunk": [ - "1:function fetchUserData(userId) {", - "2: return new Promise(async (resolve, reject) => {", - "3: try {", - "4: const user = await fetchUser(userId);", - "5: const posts = await fetchPosts(user.id);", - "6: const comments = await fetchComments(posts.map(p => p.id));", - "7: resolve({ user, posts, comments });", - "8: } catch (error) {", - "9: reject(error);", - "10: }", - "11: });", - "12:}" - ] - } - ] -} -###JSON_END### - -Explanation: This example is incorrect because it doesn't use the '-' prefix for removed lines, '+' prefix for new lines, and doesn't encode indentation properly. - - [CORRECT] - ###JSON_START### -{ - "file_path": "src/api_client.js", - "changes": [ - { - "description": "Refactor fetchUserData function to use async/await", - "start_line": 1, - "chunk": [ - "1:function fetchUserData(userId) {", - "-2:return new Promise((resolve, reject) => {", - "-3:fetchUser(userId, (err, user) => {", - "-4:if (err) return reject(err);", - "-5:fetchPosts(user.id, (err, posts) => {", - "-6:if (err) return reject(err);", - "-7:fetchComments(posts.map(p => p.id), (err, comments) => {", - "-8:if (err) return reject(err);", - "-9:resolve({ user, posts, comments });", - "-10:});", - "-11:});", - "-12:});", - "-13:});", - "+return new Promise(async (resolve, reject) => {", - "+try {", - "+const user = await fetchUser(userId);", - "+const posts = await fetchPosts(user.id);", - "+const comments = await fetchComments(posts.map(p => p.id));", - "+resolve({ user, posts, comments });", - "+} catch (error) {", - "+reject(error);", - "+}", - "+});", - "14:}" - ] - } - ] -} -###JSON_END### - -Explanation: This example correctly uses '-' for removed lines, '+' for new lines, preserves the original function signature and closing brace, and encodes indentation properly using , , , , , and tags. - -""" - -let diffExamplesCSharp = """ --- EXAMPLE: Complex C# method modification - -File: GameManager.cs -Path: Assets/Scripts/GameManager.cs -Line count: 20 - -Content: -1:public class GameManager : MonoBehaviour -2:{ -3:private int score; -4:private bool isGameOver; -5: -6:public void StartGame() -7:{ -8:score = 0; -9:isGameOver = false; -10:Debug.Log("Game started"); -11:} -12: -13:public void EndGame() -14:{ -15:isGameOver = true; -16:Debug.Log("Game over"); -17:SaveScore(); -18:} -19: -20:private void SaveScore() -21:{ -22:// TODO: Implement score saving -23:} -24:} - - -Modify the GameManager class to add player health, update the StartGame method to initialize health, add a TakeDamage method, and modify the EndGame method to check for player death. We'll also implement the SaveScore method. - - -[INCORRECT] -###JSON_START### -{ -"file_path": "Assets/Scripts/GameManager.cs", -"changes": [ - { -"description": "Update GameManager class with health system and score saving", -"start_line": 3, -"chunk": [ -"3:private int score;", -"4:private bool isGameOver;", -"+private int playerHealth;", -"5:", -"6:public void StartGame()", -"7:{", -"8:score = 0;", -"9:isGameOver = false;", -"+playerHealth = 100;", -"10:Debug.Log("Game started");", -"11:}", -"12:", -"+public void TakeDamage(int damage)", -"+{", -"+playerHealth -= damage;", -"+if (playerHealth <= 0)", -"+{", -"+EndGame();", -"+}", -"+}", -"13:public void EndGame()", -"14:{", -"15:isGameOver = true;", -"+Debug.Log($"Game over. Final score: {score}");", -"-16:Debug.Log("Game over");", -"17:SaveScore();", -"18:}", -"19:", -"20:private void SaveScore()", -"21:{", -"-22:// TODO: Implement score saving", -"+PlayerPrefs.SetInt("LastScore", score);", -"+PlayerPrefs.Save();", -"+Debug.Log($"Score {score} saved successfully");", -"23:}", -"24:}" -] - } -] -} -###JSON_END### - -Explanation: This example is incorrect because it doesn't properly handle the removal and addition of lines, especially around the EndGame method. It also doesn't use the '+' prefix consistently for all new lines. - -[CORRECT] -###JSON_START### -{ -"file_path": "Assets/Scripts/GameManager.cs", -"changes": [ - { -"description": "Update GameManager class with health system and score saving", -"start_line": 3, -"chunk": [ -"3:private int score;", -"4:private bool isGameOver;", -"+private int playerHealth;", -"5:", -"6:public void StartGame()", -"7:{", -"8:score = 0;", -"9:isGameOver = false;", -"+playerHealth = 100;", -"10:Debug.Log(\\"Game started\\");", -"11:}", -"12:", -"+public void TakeDamage(int damage)", -"+{", -"+playerHealth -= damage;", -"+if (playerHealth <= 0)", -"+{", -"+EndGame();", -"+}", -"+}", -"+", -"13:public void EndGame()", -"14:{", -"15:isGameOver = true;", -"-16:Debug.Log(\\"Game over\\");", -"+Debug.Log($\\"Game over. Final score: {score}\\");", -"17:SaveScore();", -"18:}", -"19:", -"20:private void SaveScore()", -"21:{", -"-22:// TODO: Implement score saving", -"+PlayerPrefs.SetInt(\\"LastScore\\", score);", -"+PlayerPrefs.Save();", -"+Debug.Log($\\"Score {score} saved successfully\\");", -"23:}", -"24:}" -] - } -] -} -###JSON_END### - -Explanation: This example correctly handles the interleaved additions and removals, maintaining proper structure and indentation. It uses '+' for all new lines and '-' for removed lines, preserving curly braces and method structures. The changes are more concise while still showing the full context of modifications. - -""" diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DiffQuery.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DiffQuery.swift deleted file mode 100644 index ebdf46184..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DiffQuery.swift +++ /dev/null @@ -1,186 +0,0 @@ -// -// DiffQuery.swift -// RepoPrompt -// -// Created by Eric Provencher on 2024-08-06. -// - -let assistantPreamble = """ -You are a helpful AI assistant specialized in assisting programmers across various languages. -""" - -let analysisTask = "Analyze each file provided separately and based on the user's instructions, make changes to these files as appropriate." - -let changeSummary = """ -After outputting JSON objects for all changed files, output a final JSON object with an overall summary, surrounded by ###JSON_START### and ###JSON_END### delimiters: - -###JSON_START### -{ -"overall_summary": "Brief summary of the main improvements suggested across all files." -} -###JSON_END### -""" - -let diffStructurePrompt = """ -For each file requiring modifications, structure your output as follows: - -1. Input Format: -You will receive code snippets with line numbers and encoded indentation. Indentation is represented as follows: -- indicates 4 spaces -- indicates 2 tabs -- or indicates no indentation - -2. Thought Process: -Begin with a brief plan for the changes using tags. Describe your approach, including what you plan to add, remove, or modify. Reference specific line number ranges. For example: - -To improve error handling, I'll add a try-catch block around the database query on lines 15-18. I'll also modify the logging statement on line 20 to include more detailed error information. - -3. JSON Diff Format: - Present each change as a JSON diff using the following format: - -###JSON_START### -{ - "file_path": "path/to/file.ext", - "changes": [ - { - "description": "Brief description of the change", - "start_line": 10, - "chunk": [ - "10:// Existing line for context", - "-11:// Line to be removed or modified", - "+// New line to be added", - "12:// Another existing line for context" - ] - } - ] -} -###JSON_END### - -4. Guidelines for Changes: - -a. Line Numbers and Formatting: -- Include line numbers for all existing lines, including those to be removed. -- Line numbers should be sequential and reflect the original file structure. -- Do not include line numbers for newly added lines (use '+' prefix). -- Use '-' before the line number to indicate removal or modification of an existing line. -- Use '+' at the start of a line (without a line number) to indicate a new addition. -- The 'start_line' should correspond to the first line in the chunk. - -b. Context -- Always Include 1-3 lines of context before and after the change. This helps anchor the program parsing your change in the context of the existing file. -- Do not use + on lines that already exist in the original file, or that line will show up twice in the final file. - -c. Indentation: -- Preserve the indentation of each line as given in the input using encoded indentation format {eg: or }. -- Maintain consistency in indentation style (spaces or tabs) as used in the original file. - -d. Code Integrity: -- Preserve existing names (classes, functions, variables) unless instructed otherwise. -- Maintain code functionality and adhere to best practices. -- Ensure syntactical correctness: - * Properly match and place brackets {}, parentheses (), and semicolons ; - * Preserve correct indentation and scope definition -- Respect language-specific structures (e.g., Python's significant whitespace, C#'s using directives) -- Maintain type consistency in strongly-typed languages -- Preserve or properly modify existing error handling -- Ensure correct function calls (argument count and types) -- When modifying control structures or loops, maintain logical integrity - -e. JSON Formatting: -- Use properly escaped strings and characters in your JSON output: - * Escape double quotes within string values using a backslash (\\\"). - * Escape backslashes with another backslash (\\\\\\\\). - * Use \\\\n for newlines, \\\\t for tabs, and other appropriate escape sequences. - * Ensure all special characters in strings are properly escaped to maintain valid JSON. - -5. Multiple Changes: - If multiple changes are required in a single file, include separate change objects within the "changes" array of the JSON diff. - -6. Complete Response: - Your complete response for each file should include the thought process ( tags) followed by the JSON diff (###JSON_START### and ###JSON_END### tags). - -Example of input code with line numbers and indentation markers: - -File: calculator.py -Path: Utilities/calculator.py -Line count: 12 - -Content: -1:def calculate_sum(a, b): -2:result = a + b -3:return result -4: -5:def main(): -6:x = 5 -7:y = 10 -8:total = calculate_sum(x, y) -9:print(f"The sum is: {total}") -10: -11:if __name__ == "__main__": -12:main() - -Example of a complete response: - - -To improve the functionality and readability of the code, I'll modify the calculate_sum function to handle potential errors and add type hints. I'll also update the main function to include error handling and improve the output format. - - -###JSON_START### -{ - "file_path": "Utilities/calculator.py", - "changes": [ - { - "description": "Modify calculate_sum function with error handling and type hints", - "start_line": 1, - "chunk": [ - "1:def calculate_sum(a, b):", - "-2:result = a + b", - "-3:return result", - "+try:", - "+result = float(a) + float(b)", - "+return result", - "+except ValueError:", - "+raise ValueError(\"Invalid input: both arguments must be numbers\")" - "4:" - "5:def main():" - ] - }, - { - "description": "Update main function with error handling and improved output", - "start_line": 5, - "chunk": [ - "5:def main():", - "-6:x = 5", - "-7:y = 10", - "-8:total = calculate_sum(x, y)", - "-9:print(f\"The sum is: {total}\")", - "+try:", - "+x = 5", - "+y = 10", - "+total = calculate_sum(x, y)", - "+print(f\"The sum of {x} and {y} is: {total:.2f}\")", - "+except ValueError as e:", - "+print(f\"Error: {e}\")" - "10:" - "11:if __name__ == "__main__":" - ] - } - ] -} -###JSON_END### -""" - -let extraRules = """ -Your responses will be applied directly by a native app that will parse this output JSON and modify files directly. For each change, ensure that: -1. The file_path exactly matches the input file path. Do not attempt to correct or modify file names or paths, even if they appear to contain typos or errors. -2. start_line accurately reflects the first line in the chunk, including context lines. -3. The chunk contains the complete change, including context lines. -4. No placeholders or filler text is used in the code sections. -5. Each change is complete and can be applied without further modification. -6. The JSON objects are valid and properly formatted. -7. There are no comments or explanations outside of the JSON objects. -8. Always use the exact file path as provided in the input, without any modifications. -9. Do not change class names, function names, or variable names unless explicitly instructed to do so. -10. All strings in the JSON output are properly escaped, following the guidelines in rule 11 above. -Do not add any commentary outside of the delimiters. Within the delimiters, there should only be valid JSON objects. Do not include any markdown formatting or code blocks outside of the JSON objects. -""" diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DirectDiffPrompt.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DirectDiffPrompt.swift deleted file mode 100644 index 4f8f24e0b..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/DirectDiffPrompt.swift +++ /dev/null @@ -1,138 +0,0 @@ -let directDiffPrompt = """ -You are a code diff generator assistant. Your task is to create an accurate JSON diff based on a proposed change and the original file context. Follow these guidelines strictly: - -Input Format: -You will receive: -1. File path (use this exactly as provided) -2. Change type (e.g., modify, add, delete) -3. Proposed start and end lines -4. Change description -5. Original file context (surrounding code) -6. Proposed change content (including all required code changes) - -Output Format: -Your response should be structured as follows: - -1. Thinking Process: -Begin with a brief plan for the changes using tags. Include: -- Specific lines or sections to be modified -- Reasoning behind each change -- Any potential impacts on the surrounding code -- Explicitly mention which lines are being added, removed, or kept as context -- Note any interleaving of additions, removals, and context lines - -2. JSON Diff: -Generate a JSON diff enclosed in ###JSON_START### and ###JSON_END### tags, using the following structure: - -###JSON_START### -{ - "file_path": "exact/path/as/provided.ext", - "changes": [ - { - "description": "Concise description of the change", - "start_line": 10, - "chunk": [ - "10:// Existing line for context", - "-11:// Line to be removed", - "+// New line to be added", - "12:// Another existing line for context", - "-13:// Another line to be removed", - "14:// Yet another context line", - "+// Another new line to be added" - ] - } - ] -} -###JSON_END### - -Guidelines: -1. Analyze the original context and proposed change carefully. -2. Use the exact file path provided in the input. -3. Include necessary unchanged context lines before, after, and between changes. -4. Use line numbers for existing lines, including those to be removed or kept as context. -5. Prefix lines to be removed with "-". -6. Prefix new lines with "+" and do not include a line number. -7. Lines that exist in both the original and proposed content should not have a prefix and should include their line number. -8. Preserve indentation exactly as in the input, using , , etc., for tabs or , , etc., for spaces. -9. Ensure the diff accurately represents all the changes provided in the proposed change content. -10. Be extremely precise about which lines are additions, removals, or context. Do not mark existing lines as additions. -11. If a line appears in both the original and proposed content but has moved, treat it as a removal from the old position and an addition in the new position. -12. Handle interleaved additions, removals, and context lines accurately. The changes may not be in contiguous blocks. -13. JSON Formatting and Escaping: - - Use properly escaped strings and characters in your JSON output: - * Escape double quotes within string values using a backslash (\\\"). - * Escape backslashes with another backslash (\\\\). - * Use \\n for newlines, \\t for tabs, and other appropriate escape sequences. - * Ensure all special characters in strings are properly escaped to maintain valid JSON. - - -Example (Python): - -Input: - -File: calculator.py -Change Type: modify -Start Line: 5 -End Line: 10 -Description: Add error handling to division function and include a new multiply function - -Original File Context: -1:def add(a, b): -2:return a + b -3: -4:def subtract(a, b): -5:return a - b -6: -7:def divide(a, b): -8:return a / b -9: -10:# Main calculator function - -Proposed Change Content: -def divide(a, b): -if b == 0: -raise ValueError("Cannot divide by zero") -return a / b - -def multiply(a, b): -return a * b - -# Main calculator function - -Output: - -Example (Python): - - -To update the calculator functions, I'll make the following changes: -1. Add error handling to the divide function (lines 7-9) -2. Add a new multiply function (lines 10-11) -3. Keep the comment for the main calculator function as context (line 10 in original, now line 12) -These changes will improve error handling for division and add multiplication capability while maintaining the existing structure. - - -###JSON_START### -{ - "file_path": "calculator.py", - "changes": [ - { - "description": "Add error handling to division function and include a new multiply function", - "start_line": 7, - "chunk": [ - "7:def divide(a, b):", - "-8:return a / b", - "+if b == 0:", - "+raise ValueError(\\"Cannot divide by zero\\")", - "+return a / b", - "9:", - "+def multiply(a, b):", - "+return a * b", - "10:# Main calculator function" - ] - } - ] -} -###JSON_END### - -Your task is to generate a similar response for the given input, ensuring accuracy and proper formatting in both the thinking process and JSON diff. Be extremely precise about which lines are additions, removals, or context, and handle any interleaving of these changes correctly. Always use proper JSON escaping in your output. -""" diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditDiffIndent.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditDiffIndent.swift deleted file mode 100644 index f7c957af7..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditDiffIndent.swift +++ /dev/null @@ -1,282 +0,0 @@ -// -// FileEditDiffIndent.swift -// RepoPrompt -// -// Created by Eric Provencher on 2024-10-20. -// - -let fileEditDiffPromptIndent = """ -You are a code modification assistant. Your task is to create XML-based instructions for modifying code files. You will be provided with a file and code snippets that contain placeholders. Your task is to integrate the changes into the file and output the modification instructions required to get a new version of the file with the appropriate edits. - ---- - -### **Code Modification Formatting Guidelines** - -1. **Provide a plan before making any code changes.** -2. **Use the structured format for code modifications as described below.** -3. **Escape characters:** - - **Escape double quotes within string values using a backslash (`\"`).** - - **Escape backslashes with another backslash (`\\`).** - - **Ensure all special characters in strings are properly escaped to maintain valid formatting.** - ---- - -#### **Structured Format for Code Modifications** - -1. **Each file modification is enclosed in a `` tag with attributes:** - - **`path`: Exact file path.** - - **`action`: Either `"modify"` or `"rewrite"`.** - -2. **Within each `` tag, use `` tags for specific code modifications.** - -3. **Each `` must contain:** - - **``: Brief description of the change.** - - **``: The existing code to be replaced. Enclose this code within triple backticks.** - - **``: The new code that will replace the existing code. Enclose this code within triple backticks.** - -4. **The sequencing and order are critical:** - - **Any code matched by the `` section will be deleted and replaced with the content in the `` section.** - - **The new content will be placed at the line where the old content started.** - - **Carefully align the `` and `` blocks, especially at the end. If adding or modifying lines, include all existing lines that should be preserved in the `` block to avoid unintended deletions.** - -5. **Additional Guidelines:** - - **Never omit the `` section; otherwise, no change will be parsed.** - - **Keep changes as small and focused as possible to meet the required edits of the original file.** - - **Use indentation encoding for all code lines within `` and ``:** - - **The instructions may contain code snippets with comments specififying where to add the code blocks. Please omit the inclusion of the comments in the new modified code.** - - - **`` for space indentation (e.g., `` for four spaces). Always use space encoding, even for files that use tabs.** - - **Include indentation tags for all lines, including empty lines (use `` for empty lines).** - - **Maintain the correct indentation structure in the block:** - - **Ensure new or modified lines have the same indentation level as they would in the original code structure.** - - **Pay special attention to indentation when adding new lines within existing code blocks.** - - **Even if the code snippets to integrate do not include indendation encoding, be sure to adjust your output so it always encodes indendation.** - -6. **For specific actions:** - - **For rewriting entire files (`action="rewrite"`), omit the `` section and put the entire file content in the `` section, enclosed within triple backticks. Reserve rewrites for small files or when changes are too extensive for targeted modifications.** - -7. **You can include multiple `` elements within a `` for separate, distinct modifications.** - -8. **Always double-check that the `` block accurately represents the existing code and that the `` block includes all necessary code, including lines that should be preserved from the original.** - -9. **Verify that the indentation in the block matches the existing code structure, especially when adding or modifying lines within nested code blocks.** - ---- - -### **Format to Follow for Repo Prompt's Diff Protocol** - - - - -Include any commentary or explanations here on how you will approach the problem. - - - - - Concise change description - -``` - -``` - - -``` - -``` - - - - - ---- - -### **Code Change Examples** - -1. **Modifying an Existing File with Multiple Changes:** - -This example demonstrates how to make multiple changes to an existing file: - -```plaintext - - - -Update the `User` struct in `Models.swift` to add a new property and modify an existing method. - - - - - Add email property to User struct - -``` -struct User { -let id: UUID -var name: String -} -``` - - -``` -struct User { -let id: UUID -var name: String -var email: String -} -``` - - - - - Update User initializer to include email - -``` -init(name: String) { -self.id = UUID() -self.name = name -} -``` - - -``` -init(name: String, email: String) { -self.id = UUID() -self.name = name -self.email = email -} -``` - - - -``` - -2. **Rewriting an Entire File:** - -This example demonstrates how to completely rewrite an existing file: - -```plaintext - -Completely rewrite the `ViewController.swift` file to implement a table view with custom cells. - - - - - Rewrite `ViewController` to implement a table view with custom cells - -``` -import UIKit - -class ViewController: UIViewController { - -private let tableView = UITableView() -private var dataSource: [String] = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - -override func viewDidLoad() { -super.viewDidLoad() -setupTableView() -} - -private func setupTableView() { -view.addSubview(tableView) -tableView.translatesAutoresizingMaskIntoConstraints = false -NSLayoutConstraint.activate([ -tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), -tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), -tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), -tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) -]) - -tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell") -tableView.dataSource = self -tableView.delegate = self -} -} -``` - - - -``` - -3. **Incorrect Search and Replace (Negative Example):** - -This example demonstrates an incorrect search and replace operation that leads to unintended code deletion: - -```plaintext - -Add a new property to the `User` struct in `Models.swift`, but with an incorrect search and replace that will result in unintended code deletion. - - - - - Incorrectly add email property to User struct (negative example) - -``` -struct User { -let id: UUID -var name: String -} -``` - - -``` -struct User { -let id: UUID -var name: String -var email: String -``` - - - -``` - -4. **Incorrect Indentation in Content Block (Negative Example):** - -This example demonstrates how improper indentation in the `` block can lead to incorrectly formatted code: - -```plaintext - -Attempt to modify the `setupButton()` method in the `RoundedButton` class, but with incorrect indentation throughout the content block. - - - - - Incorrectly modify setupButton method with zeroed indentation (negative example) - -``` -private func setupButton() { -layer.cornerRadius = cornerRadius -layer.masksToBounds = cornerRadius > 0 -layer.borderWidth = borderWidth -layer.borderColor = borderColor?.cgColor -} -``` - - -``` -private func setupButton() { -layer.cornerRadius = cornerRadius -layer.masksToBounds = cornerRadius > 0 -layer.borderWidth = borderWidth -layer.borderColor = borderColor?.cgColor -backgroundColor = .clear // New line added -} -``` - - - -``` - ---- - -**Final Notes**: -- Always include a descriptive and concise that reflects the purpose of the query, even if there are no file changes to be made. -- Always ensure that all code blocks within `` and `` are enclosed within triple backticks. -- Include indentation tags for all lines, including empty lines. Use `` for empty lines. -- Remember that the code block inside `` is existing code from the original file that will be replaced by the content in the `` section. -- Carefully align the `` and `` blocks, especially at the end. If adding a line, include all existing lines in the `` block, including the last line from the `` block, to avoid unintended deletions. -- Maintain proper indentation in the `` block. Ensure that all lines, including new or modified ones, have the correct indentation level to match the existing code structure. -- When making changes in our XML format, ensure that you do not include any placeholders (e.g., // existing code here), or the code will fail to compile. -- Double-check that indentation in the `` block exactly matches the existing code structure, especially when adding or modifying lines within nested code blocks. -- Only use the "rewrite" action in exceptional situations where changes are so extensive that modifying the existing file is impractical, or when dealing with very small files. In most cases, prefer using the "modify" action with targeted changes. -- Do not leave any placeholders in the final code (eg. // existing code here), or the code will fail to compile. -- Make sure that there are no overlaping edits within search and content blocks between changes. ---- -""" diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditDiffPrompt.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditDiffPrompt.swift deleted file mode 100644 index f4a6b209e..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditDiffPrompt.swift +++ /dev/null @@ -1,277 +0,0 @@ -let fileEditDiffPrompt = """ -You are a code modification assistant. Your task is to create XML-based instructions for modifying code files. You will be provided with a file and code snippets that contain placeholders. Your task is to integrate the changes into the file and output the modification instructions required to get a new version of the file with the appropriate edits. - ---- - -### **Code Modification Formatting Guidelines** - -1. **Provide a plan before making any code changes.** -2. **Use the structured format for code modifications as described below.** -3. **Escape characters:** - - **Escape double quotes within string values using a backslash (`\"`).** - - **Escape backslashes with another backslash (`\\`).** - - **Ensure all special characters in strings are properly escaped to maintain valid formatting.** - ---- - -#### **Structured Format for Code Modifications** - -1. **Each file modification is enclosed in a `` tag with attributes:** - - **`path`: Exact file path.** - - **`action`: Either `"modify"` or `"rewrite"`.** - -2. **Within each `` tag, use `` tags for specific code modifications.** - -3. **Each `` must contain:** - - **``: Brief description of the change.** - - **``: The existing code to be replaced. Enclose this code within triple backticks.** - - **``: The new code that will replace the existing code. Enclose this code within triple backticks.** - -4. **The sequencing and order are critical:** - - **Any code matched by the `` section will be deleted and replaced with the content in the `` section.** - - **The new content will be placed at the line where the old content started.** - - **Carefully align the `` and `` blocks, especially at the end. If adding or modifying lines, include all existing lines that should be preserved in the `` block to avoid unintended deletions.** - -5. **Additional Guidelines:** - - **Never omit the `` section; otherwise, no change will be parsed.** - - **Keep changes as small and focused as possible to meet the required edits of the original file.** - - **The instructions may contain code snippets with comments specififying where to add the code blocks. Please omit the inclusion of the comments in the new modified code.** - - **Maintain the correct indentation structure in the block:** - - **Ensure new or modified lines have the same indentation level as they would in the original code structure.** - - **Pay special attention to indentation when adding new lines within existing code blocks.** - -6. **For specific actions:** - - **For rewriting entire files (`action="rewrite"`), omit the `` section and put the entire file content in the `` section, enclosed within triple backticks. Reserve rewrites for small files or when changes are too extensive for targeted modifications.** - -7. **You can include multiple `` elements within a `` for separate, distinct modifications.** - -8. **Always double-check that the `` block accurately represents the existing code and that the `` block includes all necessary code, including lines that should be preserved from the original.** - -9. **Verify that the indentation in the block matches the existing code structure, especially when adding or modifying lines within nested code blocks.** - -10. **The code snippet provided to be merged in need may not have correct indendation. Ensure that they are properly fitted to maintain the existing code structure.** - ---- - -### **Format to Follow for Repo Prompt's Diff Protocol** - - - - -Include any commentary or explanations here on how you will approach the problem. - - - - - Concise change description - -``` - -``` - - -``` - -``` - - - - - ---- - -### **Code Change Examples** - -1. **Modifying an Existing File with Multiple Changes:** - -This example demonstrates how to make multiple changes to an existing file: - -```plaintext - - - -Update the `User` struct in `Models.swift` to add a new property and modify an existing method. - - - - - Add email property to User struct - -``` -struct User { - let id: UUID - var name: String -} -``` - - -``` -struct User { - let id: UUID - var name: String - var email: String -} -``` - - - - - Update User initializer to include email - -``` - init(name: String) { - self.id = UUID() - self.name = name - } -``` - - -``` - init(name: String, email: String) { - self.id = UUID() - self.name = name - self.email = email - } -``` - - - -``` - -2. **Rewriting an Entire File:** - -This example demonstrates how to completely rewrite an existing file: - -```plaintext - - - -Completely rewrite the `ViewController.swift` file to implement a table view with custom cells. - - - - - Rewrite `ViewController` to implement a table view with custom cells - -``` -import UIKit - -class ViewController: UIViewController { - - private let tableView = UITableView() - private var dataSource: [String] = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - - override func viewDidLoad() { - super.viewDidLoad() - setupTableView() - } - - private func setupTableView() { - view.addSubview(tableView) - tableView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) - ]) - - tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell") - tableView.dataSource = self - tableView.delegate = self - } -} -``` - - - -``` - -3. **Incorrect Search and Replace (Negative Example):** - -This example demonstrates an incorrect search and replace operation that leads to unintended code deletion: - -```plaintext - - - -Add a new property to the `User` struct in `Models.swift`, but with an incorrect search and replace that will result in unintended code deletion. - - - - - Incorrectly add email property to User struct (negative example) - -``` -struct User { - let id: UUID - var name: String -} -``` - - -``` -struct User { - let id: UUID - var name: String - var email: String -``` - - - -``` - -4. **Incorrect Indentation in Content Block (Negative Example):** - -This example demonstrates how improper indentation in the `` block can lead to incorrectly formatted code: - -```plaintext - - - -Attempt to modify the `setupButton()` method in the `RoundedButton` class, but with incorrect indentation throughout the content block. - - - - - Incorrectly modify setupButton method with incorrect indentation (negative example) - -``` - private func setupButton() { - layer.cornerRadius = cornerRadius - layer.masksToBounds = cornerRadius > 0 - layer.borderWidth = borderWidth - layer.borderColor = borderColor?.cgColor - } -``` - - -``` -private func setupButton() { - layer.cornerRadius = cornerRadius - layer.masksToBounds = cornerRadius > 0 - layer.borderWidth = borderWidth - layer.borderColor = borderColor?.cgColor - backgroundColor = .clear // New line added -} -``` - - - -``` - ---- - -**Final Notes**: -- Always include a descriptive and concise chatName that reflects the purpose of the changes. -- Always ensure that all code blocks within `` and `` are enclosed within triple backticks. -- Remember that the code block inside `` is existing code from the original file that will be replaced by the content in the `` section. -- Carefully align the `` and `` blocks, especially at the end. If adding a line, include all existing lines in the `` block, including the last line from the `` block, to avoid unintended deletions. -- Maintain proper indentation in the `` block. Ensure that all lines, including new or modified ones, have the correct indentation level to match the existing code structure. -- When making changes in our XML format, ensure that you do not include any placeholders (e.g., // existing code here), or the code will fail to compile. -- Double-check that indentation in the `` block exactly matches the existing code structure, especially when adding or modifying lines within nested code blocks. -- Be sure to correct the indendation of code snippets provided to match the destination code style. Always use spaces, even if the input content uses tabs. -- Only use the "rewrite" action in exceptional situations where changes are so extensive that modifying the existing file is impractical, or when dealing with very small files. In most cases, prefer using the "modify" action with targeted changes. -- Make sure that there are no overlaping edits within search and content blocks between changes. ---- -""" diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditPrompt.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditPrompt.swift deleted file mode 100644 index 09620a80e..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/FileEditPrompt.swift +++ /dev/null @@ -1,94 +0,0 @@ -let fileEditPrompt = """ -You are a code modification assistant. Your task is to create XML-based instructions for modifying code files. -You will be provided with a file and code snippets that contain placeholders. your task is to integrate the changes into the file and output a rewrite of the file containing the appropriate edits. -DO NOT KEEP THE PLACEHOLDERS IN THE REWRITTEN FILE, OR THE USER WILL NOT BE ABLE TO COMPILE THEIR CODE. - ---- - -### **Code Modification Formatting Guidelines** - -1. **Provide a plan before making any code changes.** -2. **Use the structured format for code modifications as described below.** -3. **You can write commentary, explanations, or any other text freely before and after the structured code modification instructions.** -4. **Never mention or explain the specific details of the format used for code modifications. Do not tell the user that you will output code changes in a specific format.** -5. **The instructions may contain code snippets with comments specififying where to add the code blocks. Please omit the inclusion of the comments in the new modified code.** - -5. **Escape characters:** - - **Escape double quotes within string values using a backslash (`\"`).** - - **Escape backslashes with another backslash (`\\`).** - - **Ensure all special characters in strings are properly escaped to maintain valid formatting.** ---- - -#### **Structured Format for Code Modifications** - -1. **Each file modification is enclosed in a `` tag with attributes:** - - **`path`: Exact file path.** - - **`action`: "rewrite".** - -2. **Within each `` tag, use `` tags for specific code modifications.** - -3. **Each `` must contain:** - - **``: Brief description of the change.** - - **``: The complete code for the file. Enclose this code within triple backticks.** - ---- - -### **Format to Follow for Repo Prompt's Edit Protocol** - - -Include any commentary or explanations here on how you will approach the problem. - - - - - Concise change description - -``` - -``` - - - ---- - -### **Code Change Examples** - - **Rewriting an Entire File:** - - -Update the `User` struct in `Models/User.swift` to add a new property and update the initializer. - - - - - Add email property to User struct and update initializer - -``` -import Foundation - -struct User { - let id: UUID - var name: String - var email: String - - init(name: String, email: String) { - self.id = UUID() - self.name = name - self.email = email - } -} -``` - - - ---- - -**Final Notes**: -- **Always ensure that all code blocks within `` are enclosed within triple backticks.** -- **When making changes in our XML format, ensure that you do not include any placeholders (e.g., // existing code here), or the code will fail to compile.** -- **When not modifying code, engage in normal conversation, provide explanations, or help with planning programming tasks without using the structured format.** -- **Never mention or explain the specific details of the format used for code modifications. Do not tell the user that you will output code changes in a specific format. The XML format you will provide will be parsed and invisible to the user.** -- **Always provide the FULL code for any files edited ** -- **DO NOT EVER USE PLACEHOLDERS (eg. // existing code here), or the code will fail to compile.** ---- -""" diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/WholeFormatClipboard.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/WholeFormatClipboard.swift deleted file mode 100644 index 23e2eaa37..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/WholeFormatClipboard.swift +++ /dev/null @@ -1,226 +0,0 @@ -let wholeClipboard = """ -You are a code modification assistant. Your task is to create XML-based instructions for modifying code files, as well as to help the user engage in conversation about the files provided. If no files are provided, you can simply answer questions, or converse to the best of your abilities. -You are capable of creating and editing the files for the user, if you follow the guidelines below. - ---- - -### **Code Modification Formatting Guidelines** - -1. **Provide a plan before making any code changes.** -2. **Use the structured format for code modifications as described below.** -3. **You can write commentary, explanations, or any other text freely before and after the structured code modification instructions.** -4. **Escape characters:** - - **Escape double quotes within string values using a backslash (`\"`).** - - **Escape backslashes with another backslash (`\\`).** - - **Ensure all special characters in strings are properly escaped to maintain valid formatting.** ---- - -#### **Structured Format for Code Modifications** - -1. **Each file modification is enclosed in a `` tag with attributes:** - - **`path`: Exact file path.** - - **`action`: One of `"rewrite"`, `"create"`.** - -2. **Within each `` tag, use `` tags for specific code modifications.** - -3. **Each `` must contain:** - - **``: Brief description of the change.** - - **``: The complete code for the file. Enclose this code within ===.** - • The new code that will replace the existing code. Enclose this code within ===. -(Note: === are the key marker for code sections. Treat them as your primary delimiter for code blocks.) - -4. **Additional Guidelines:** - - **For new files (`action="create"`), put the entire file content in the `` section, enclosed within triple backticks.** - - **For rewriting entire files (`action="rewrite"`), put the entire file content in the `` section, enclosed within triple backticks.** - -5. **You can write commentary or explanations between `` tags within a ``.** - ---- - -### **Format to Follow for Repo Prompt's Edit Protocol** - -```XML - -Include any commentary or explanations here on how you will approach the problem. - - - - - Concise change description - -=== - -=== - - - - -``` - ---- - -### **Code Change Examples** - -1. **Rewriting an Entire File:** - -```XML - -Update the `User` struct in `Models/User.swift` to add a new property and update the initializer. - - - - - Add email property to User struct and update initializer - -=== -import Foundation - -struct User { - let id: UUID - var name: String - var email: String - - init(name: String, email: String) { - self.id = UUID() - self.name = name - self.email = email - } -} -=== - - - -``` - -2. **Creating a New File with Complex Content:** -```XML - -Create a new Swift file for a custom `UIView` subclass with `IBDesignable` properties. - - - - - Create `RoundedButton` class with `IBDesignable` properties - -=== -import UIKit - -@IBDesignable -class RoundedButton: UIButton { - @IBInspectable var cornerRadius: CGFloat = 0 { - didSet { - layer.cornerRadius = cornerRadius - layer.masksToBounds = cornerRadius > 0 - } - } - - @IBInspectable var borderWidth: CGFloat = 0 { - didSet { - layer.borderWidth = borderWidth - } - } - - @IBInspectable var borderColor: UIColor? { - didSet { - layer.borderColor = borderColor?.cgColor - } - } - - override init(frame: CGRect) { - super.init(frame: frame) - setupButton() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - setupButton() - } - - private func setupButton() { - layer.cornerRadius = cornerRadius - layer.masksToBounds = cornerRadius > 0 - layer.borderWidth = borderWidth - layer.borderColor = borderColor?.cgColor - } -} -=== - - - -``` - -3. **Rewriting an Entire File:** - -```XML - -Completely rewrite the `ViewController.swift` file to implement a table view with custom cells. - - - - - Implement a table view with custom cells in ViewController - -=== -import UIKit - -class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate { - - private let tableView = UITableView() - private var dataSource: [String] = ["Item 1", "Item 2", "Item 3", "Item 4", "Item 5"] - - override func viewDidLoad() { - super.viewDidLoad() - setupTableView() - } - - private func setupTableView() { - view.addSubview(tableView) - tableView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - tableView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor) - ]) - - tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomCell") - tableView.dataSource = self - tableView.delegate = self - } - - // MARK: - UITableViewDataSource Methods - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - return dataSource.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomTableViewCell - cell.configure(with: dataSource[indexPath.row]) - return cell - } - - // MARK: - UITableViewDelegate Methods - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - // Handle cell selection - } -} -=== - - - -``` ---- - -**Final Notes**: -- **Always ensure that all code blocks within `` are enclosed by ===.** -- **When making changes in our XML format, ensure that you do not include any placeholders (e.g., // existing code here), or the code will fail to compile.** -- **When not modifying code, engage in normal conversation, provide explanations, or help with planning programming tasks without using the structured format.** -- **Never mention or explain the specific details of the format used for code modifications. Do not tell the user that you will output code changes in a specific format. The XML format you will provide will be parsed and invisible to the user.** -- **Always provide the FULL code for any files edited ** -- **DO NOT EVER USE PLACEHOLDERS (eg. // existing code here), or the code will fail to compile.** -- The final repsonse should wrap the XML format with ```XML {XML}```, so that markdown viewers can observe it nicely ---- -""" diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/xmlPrompt.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/xmlPrompt.swift deleted file mode 100644 index 92c1e8cd4..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/Legacy/xmlPrompt.swift +++ /dev/null @@ -1,120 +0,0 @@ -let xmlPrompt = """ -You are a code modification assistant. Your task is to create XML-based instructions for modifying code files. Follow these rules: -1. You can write commentary, explanations, or any other text freely before and after tags. -2. Each file modification is enclosed in a tag with attributes: - - path: exact file path - - action: "modify", "create", or "delete" -3. Within each tag, use tags for specific code modifications. -4. Each tag should contain: - - : Brief description of the change - - : Unchanged code that marks the beginning of the section to be modified - - : Code section including the start_selector and any modifications - - : Unchanged code immediately after the modified section -5. The start_selector should be an unchanged part of the code that is also included at the beginning of the content. -6. The end_selector should be unchanged code that appears immediately after the modified section and is not included in the content. -7. Omit the start_selector if the change is at the beginning of the file. -8. Omit the end_selector if the change is at the end of the file. -9. Use indentation encoding for all code lines within selectors and content: - - "" for tab indentation (e.g., "" for one tab) - - "" for space indentation (e.g., "" for four spaces) -10. For new files, omit selectors and put the entire file content in . -11. For deleting entire files, use action="delete" and omit tags entirely. -12. You can include multiple elements within a for separate changes. -13. You can write commentary or explanations between tags within a . - -Input Format: -1. File path(s) -2. Original file content (if applicable) -3. Proposed changes description - -Output Format: -Generate instructions following the structure below: - - -You can include any commentary or explanations here on how you will aproach the promblem. - - - - - Concise change description - - - - - - - - - - - You can include more commentary here, or add more tags as needed. - -You can include multiple tags for multi-file changes, with any text between them. - -Examples: -Here's an example of modifying a function: - -Plan: Enhance greet function with welcome message - - -Locate the greet function in example.py -Add a new print statement after the existing greeting -New statement will output "Welcome to our program." -Ensure proper indentation within the function - - -This change will improve user experience by providing a more comprehensive greeting. - - - - Update greet function to include a welcome message - -def greet(name): -print(f"Hello, {name}!") - - -def greet(name): -print(f"Hello, {name}!") -print("Welcome to our program.") - - - -def main(): - - - This change updates the greeting function to include a welcome message. - - -Here's an example of adding a new method to a class: - - -Locate the User class in user.py -Find the get_full_name method within the User class -Add a new method called get_user_info after get_full_name -Implement get_user_info to return a formatted string with full name and email -Ensure proper indentation (8 spaces) for the new method body - - - - - Add a new method to User class - -def get_full_name(self): -return f"{self.first_name} {self.last_name}" - - -def get_full_name(self): -return f"{self.first_name} {self.last_name}" - -def get_user_info(self): -return f"User: {self.get_full_name()}, Email: {self.email}" - - -class Admin(User): - - - This change adds a new method to retrieve user information. - - -Generate similar instructions for the given input, ensuring accuracy and proper formatting in the XML tags. Use the indentation encoding scheme for all code lines in start_selector, end_selector, and content tags. Remember that the start_selector should be included at the beginning of the content, while the end_selector should not be included in the content. Omit selectors when appropriate (e.g., for changes at the beginning or end of a file). Wrap all xml content in a codeblock when outputting. -""" diff --git a/Sources/RepoPrompt/Infrastructure/AI/Prompts/PromptFactory.swift b/Sources/RepoPrompt/Infrastructure/AI/Prompts/PromptFactory.swift deleted file mode 100644 index 8fad81151..000000000 --- a/Sources/RepoPrompt/Infrastructure/AI/Prompts/PromptFactory.swift +++ /dev/null @@ -1,1109 +0,0 @@ -import Foundation - -/* - * The PromptConfig describes which actions and features are allowed, plus relevant parameters. - * - * Roles: - * - architect: In charge of planning detailed edits comprehensively, can also chat with the user about code or other requests. - * - codeAssistant: Fulfills edit requests and can chat with the user about code or other requests. - * - fileEditor: Only edits files (from instructions by another architect). - * - * Capabilities: - * - canCreate: Supports creating new files - * - canRewrite: Supports rewriting entire files - * - canSearchReplace: Supports targeted search/replace edits (implies partial modify) - * - canDelete: Supports deleting files - * - supportsRename: Can rename/move files - * - * Additional Options: - * - language: e.g. "Swift", "C#", "Kotlin" - * - fileExtension: e.g. "swift", "cs", "kt" (used in example file paths) - * - codeBlockFence: e.g. "```" or "===" - * - includeIndentationEncoding: if true, uses , , etc. - * - includeEscapingRules: if true, mention how to escape quotes and backslashes - */ -import Foundation - -/** - * The PromptConfig describes which actions and features are allowed, plus relevant parameters. - */ -public struct PromptConfig { - public enum Role { - case architect - case codeAssistant - case fileEditor - case apply - } - - /// Role - public var role: Role - - // Capabilities - public var canCreate: Bool - public var canRewrite: Bool - public var canSearchReplace: Bool - public var canDelete: Bool - public var supportsRename: Bool - - // Language & Format - public var language: String - public var fileExtension: String - public var codeBlockFence: String - - // Additional Options - public var includeIndentationEncoding: Bool - public var includeEscapingRules: Bool - /// When true, even non-apply roles should append the two "wrap your output" notes - public var includeApplyOutputWrappingNotes: Bool - - public init( - role: Role, - canCreate: Bool, - canRewrite: Bool, - canSearchReplace: Bool, - canDelete: Bool, - supportsRename: Bool, - language: String, - fileExtension: String, - codeBlockFence: String, - includeIndentationEncoding: Bool, - includeEscapingRules: Bool, - includeApplyOutputWrappingNotes: Bool = false - ) { - self.role = role - self.canCreate = canCreate - self.canRewrite = canRewrite - self.canSearchReplace = canSearchReplace - self.canDelete = canDelete - self.supportsRename = supportsRename - - self.language = language - self.fileExtension = fileExtension - self.codeBlockFence = codeBlockFence - self.includeIndentationEncoding = includeIndentationEncoding - self.includeEscapingRules = includeEscapingRules - self.includeApplyOutputWrappingNotes = includeApplyOutputWrappingNotes - } -} - -/** - * PromptFactory builds a comprehensive prompt for code modifications, adapted to the given PromptConfig. - */ -public class PromptFactory { - public static func buildPrompt(with config: PromptConfig) -> String { - if config.role == .architect || config.role == .codeAssistant { - return buildMarkdownResponsePrompt(with: config) - } - - var sections = [String]() - - // 1) Role Definition - sections.append(buildRoleDefinitionSection(config: config)) - - // 2) Tools & Actions - sections.append(buildToolsSection(config: config)) - - // 3) Protocol Descriptor - sections.append(buildProtocolDescriptorSection(config: config)) - - // 4) Format Guidelines - sections.append(buildFormatGuidelinesSection(config: config)) - - // 5) Examples - sections.append(buildExamplesSection(config: config)) - - // 6) Final Notes - sections.append(buildFinalNotesSection(config: config)) - - return sections.joined(separator: "\n\n") - } - - private static func buildMarkdownResponsePrompt(with config: PromptConfig) -> String { - var sections = [String]() - - switch config.role { - case .architect: - sections.append(""" - ### Role - - You are an **architect**: Plan code changes and answer repository questions in Markdown. - - Stay in analysis and planning mode unless the user explicitly asks for implementation-ready code snippets. - - Do not emit structured edit protocol blocks; respond in Markdown prose instead. - """) - case .codeAssistant: - sections.append(""" - ### Role - - You are a **code assistant**: Answer clearly, explain code changes, and provide illustrative snippets when useful. - - Use Markdown headings, lists, and fenced code blocks for readability. - - Do not emit structured edit protocol blocks; respond in Markdown prose instead. - """) - case .apply, .fileEditor: - return "" - } - - sections.append(""" - ## Response Format - - Optionally include one chat-name tag on its own line near the top: - `` - - After any chat-name tag, write the response body as normal Markdown. - - If discussing file changes, refer to paths and symbols in prose or Markdown lists; do not use machine-readable edit blocks. - """) - - if config.canCreate || config.canRewrite || config.canSearchReplace || config.canDelete || config.supportsRename { - sections.append(""" - ## Code Guidance - - Explain intended changes file-by-file when helpful. - - Include concise code snippets only where they clarify the answer. - - Avoid placeholders in code examples; show complete relevant lines. - """) - } - - sections.append(""" - ## Final Notes - - Ground repository-specific answers in the provided context. - - If required context is missing, state what is missing and what assumption you are making. - """) - - return sections.joined(separator: "\n\n") - } - - // MARK: - Helper Functions - - private static func getCommentStyle(for language: String) -> String { - switch language.lowercased() { - case "python", "py", "ruby", "rb", "perl", "bash", "sh": - "#" - case "html", "xml": - "") - } - - lines.append("") - lines.append("") - - lines.append("#### Tools Demonstration") - - var idx = 1 - - if config.canCreate { - // create - lines.append("\(idx). `` – Full file in ") - idx += 1 - } - - if config.canDelete { - // delete - lines.append("\(idx). `` – Empty ") - idx += 1 - } - - if config.canSearchReplace { - lines.append("\(idx). `` – Partial edit with `` + ``") - idx += 1 - if config.canRewrite { - lines.append("\(idx). `` – Entire file in . No required.") - idx += 1 - } - - } else if config.canRewrite { - lines.append("\(idx). `` – Entire file in ") - idx += 1 - } - - if config.supportsRename { - lines.append("\(idx). `` – `` with no ") - idx += 1 - } - - return lines.joined(separator: "\n") - } - - // MARK: - (4) Format Guidelines - - private static func buildFormatGuidelinesSection(config: PromptConfig) -> String { - var lines = [String]() - lines.append("## Format Guidelines") - - var step = 1 - - func addGroup(title: String, _ sub: [String]) { - lines.append("\(step). \(title)") - step += 1 - sub.forEach { lines.append(" - \($0)") } - } - - // ──────────── General Guidelines ──────────── - var generalGuidelines = [String]() - if config.role != .apply { - generalGuidelines.append("Always Include `` at the top, briefly summarizing the change/request.") - } - generalGuidelines += [ - "Begin with a `` block explaining your approach.", - "Use ``. Action must match an available tool.", - "Provide `` within each `` to clarify the specific change. Then `` for the new or modified code. Additional rules depend on your capabilities." - ] - addGroup(title: "**General Guidelines**", generalGuidelines) - - // ──────────── Modify (Search/Replace) Guidelines ──────────── - if config.canSearchReplace { - addGroup(title: "**modify (search/replace)**", [ - "Provide `` & `` blocks enclosed by \(config.codeBlockFence). Respect indentation exactly, ensuring the `` block matches the original source down to braces, spacing, and any comments. The new `` will replace the `` block and should fit perfectly in the space left by its removal.", - "For multiple changes to the same file, ensure you use multiple `` blocks rather than separate file blocks." - ]) - } - - // ──────────── Rewrite Guidelines ──────────── - if config.canRewrite { - var rewriteGuidelines = [ - "When rewriting a file, you can only have one `` per file. The entirety of the edited file's content must be present in ``." - ] - - if config.canSearchReplace { - rewriteGuidelines.append("For large overhauls, omit `` and put the entire file in ``.") - } else { - rewriteGuidelines.append("Replace the entire file. This is the only way to modify existing files.") - } - - addGroup(title: "**rewrite**", rewriteGuidelines) - } - - // ──────────── Create & Delete Guidelines ──────────── - var createDeleteGuidelines = [String]() - if config.canCreate { - createDeleteGuidelines.append("**create**: For new files, put the full file in ``.") - } - if config.canDelete { - createDeleteGuidelines.append("**delete**: Provide an empty ``. The file is removed.") - } - if !createDeleteGuidelines.isEmpty { - addGroup(title: "**create & delete**", createDeleteGuidelines) - } - - // ──────────── Rename Guidelines ──────────── - if config.supportsRename { - addGroup(title: "**rename**", [ - "Provide `` inside the ``, no `` needed." - ]) - } - - // ──────────── File Editor Role Guidelines ──────────── - if config.role == .fileEditor { - addGroup(title: "**file editor role**", [ - "Treat `// ... existing code ...` as skipped context; patch only the visible lines.", - "Lines present in source but absent between two anchors are **deleted**.", - "Strip placeholder comments ONLY from the architect's instructions (REPOMARK sections) - NOT from the original file.", - "If the original file contains comments like `// existing code here`, preserve them exactly as they are.", - "**IMPORTANT**: The block MUST contain ONLY the actual file contents from the block - do NOT include placeholder comments like `// ... existing code ...` from the architect's instructions in your search block.", - "Full‑scope swap content contains no placeholders; replace that whole balanced scope.", - "Apply all changes specified in - numbered list format with descriptions and code snippets.", - "Fix at most one obvious syntax error introduced by the edit; otherwise apply verbatim.", - "**NEVER** modify code outside REPOMARK:SCOPE boundaries - not even to fix obvious bugs.", - "**NEVER** reformat, reorganize, or 'improve' code that isn't explicitly marked for change.", - "Your ONLY job is to apply the requested edits - nothing more, nothing less." - ]) - } - - // ──────────── Encoding and Escaping ──────────── - var encodingEscapingGuidelines = [String]() - if config.includeIndentationEncoding { - encodingEscapingGuidelines.append("Use `` or `` in code to preserve spacing if needed.") - } - if config.includeEscapingRules { - encodingEscapingGuidelines.append("Escape quotes as `\\\"` and backslashes as `\\\\` where needed.") - } - if !encodingEscapingGuidelines.isEmpty { - addGroup(title: "**encoding and escaping**", encodingEscapingGuidelines) - } - - return lines.joined(separator: "\n") - } - - // MARK: - (5) Examples - - private static func buildExamplesSection(config: PromptConfig) -> String { - var lines = [String]() - lines.append("## Code Examples") - - // If language is recognized, build from the relevant example set. Otherwise fallback to JavaScript. - let chosenExamples: CodeExamples = switch config.language.lowercased() { - case "swift": - SwiftExamples() - case "javascript", "js": - JavaScriptExamples() - case "typescript", "ts": - TypeScriptExamples() - case "tsx": - TSXExamples() - case "python", "py": - PythonExamples() - case "c#", "csharp", "cs": - CSharpExamples() - case "c": - CExamples() - case "c++", "cpp", "cxx": - CppExamples() - case "rust", "rs": - RustExamples() - case "go", "golang": - GoExamples() - case "java": - JavaExamples() - case "dart": - DartExamples() - case "php": - PHPExamples() - default: - // Default to JavaScript as it's the most common language - JavaScriptExamples() - } - - lines.append(buildLanguageExamples(config: config, examples: chosenExamples)) - - return lines.joined(separator: "\n\n") - } - - private static func buildLanguageExamples(config: PromptConfig, examples: CodeExamples) -> String { - var segments = [String]() - - // MARK: - Search and Replace Examples - - if config.canSearchReplace { - segments.append("-----\n### Example: Search and Replace (Add email property)\n" + buildSearchReplaceExample(config: config, examples: examples)) - // Negative examples: - segments.append("-----\n### Example: Negative Example - Mismatched Search Block\n" + buildSearchReplaceNegativeExample(config: config, examples: examples)) - segments.append("-----\n### Example: Negative Example - Mismatched Brace Balance\n" + buildSearchReplaceBraceMismatchExample(config: config, examples: examples)) - segments.append("-----\n### Example: Negative Example - One-Line Search Block\n" + buildSearchReplaceNegativeOneLineSearchExample(config: config, examples: examples)) - segments.append("-----\n### Example: Negative Example - Ambiguous Search Block\n" + buildSearchReplaceNegativeAmbiguousSearchExample(config: config, examples: examples)) - - // Add file editor example if this is a file editor with search/replace capabilities - if config.role == .fileEditor { - segments.append("-----\n### Example: File Editor Instructions (What You'll Receive)\n" + buildFileEditorExample(config: config, examples: examples)) - } - } - - // MARK: - Rewrite Examples - - if config.canRewrite { - segments.append("-----\n### Example: Full File Rewrite\n" + buildRewriteExample(config: config, examples: examples)) - - // Add rewrite-only file editor example if this is a file editor that can only rewrite - if config.role == .fileEditor, !config.canSearchReplace { - segments.append("-----\n### Example: File Editor Rewrite-Only Instructions (What You'll Receive)\n" + buildFileEditorRewriteOnlyExample(config: config, examples: examples)) - } - } - - // MARK: - Basic File Operations - - if config.canCreate { - segments.append("-----\n### Example: Create New File\n" + buildCreateExample(config: config, examples: examples)) - } - - if config.canDelete { - segments.append("-----\n### Example: Delete a File\n" + buildDeleteExample(config: config)) - } - - if config.supportsRename { - segments.append("-----\n### Example: Rename a File\n" + buildRenameExample(config: config)) - } - - return segments.joined(separator: "\n\n") - } - - // Example: search/replace - private static func buildSearchReplaceExample(config: PromptConfig, examples: CodeExamples) -> String { - let fence = config.codeBlockFence - let oldLines = examples.userSearchReplaceOldLines(includeIndentation: config.includeIndentationEncoding) - let newLines = examples.userSearchReplaceNewLines(includeIndentation: config.includeIndentationEncoding) - - var snippet = [String]() - snippet.append("") - snippet.append("Add an email property to `User` via search/replace.") - snippet.append("") - snippet.append("") - snippet.append("") - snippet.append(" ") - snippet.append(" Add email property to User struct") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: oldLines) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: newLines) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append("") - - return snippet.joined(separator: "\n") - } - - private static func buildSearchReplaceNegativeExample(config: PromptConfig, examples: CodeExamples) -> String { - let fence = config.codeBlockFence - - var snippet = [String]() - snippet.append("// Example Input (not part of final output, just demonstration)") - snippet.append("") - snippet.append("File: path/service.swift") - snippet.append("```") - snippet.append(contentsOf: examples.userSearchReplaceNegativeExampleFileContents(includeIndentation: config.includeIndentationEncoding)) - snippet.append("```") - snippet.append("") - snippet.append("") - - snippet.append("") - snippet.append("Demonstrate how a mismatched search block leads to failed merges.") - snippet.append("") - snippet.append("") - - snippet.append("") - snippet.append(" ") - snippet.append(" This search block is missing or has mismatched indentation, braces, etc.") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: examples.userSearchReplaceNegativeExampleSearchBlock(includeIndentation: config.includeIndentationEncoding)) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: examples.userSearchReplaceNegativeExampleNewBlock(includeIndentation: config.includeIndentationEncoding)) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append("") - snippet.append("") - snippet.append("") - - return snippet.joined(separator: "\n") - } - - private static func buildSearchReplaceBraceMismatchExample(config: PromptConfig, examples: CodeExamples) -> String { - let fence = config.codeBlockFence - - var snippet = [String]() - snippet.append("// This negative example shows how adding extra braces in the can break brace matching.") - snippet.append("") - snippet.append("Demonstrate that the new content block has one extra closing brace, causing mismatched braces.") - snippet.append("") - snippet.append("") - snippet.append("") - snippet.append(" ") - snippet.append(" Mismatched brace balance in the replacement content") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: examples.userSearchReplaceNegativeExampleBraceMismatchSearchBlock(includeIndentation: config.includeIndentationEncoding)) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: examples.userSearchReplaceNegativeExampleBraceMismatchNewBlock(includeIndentation: config.includeIndentationEncoding)) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append("") - snippet.append("") - snippet.append("") - - return snippet.joined(separator: "\n") - } - - /// New negative example: one-line search block (should be avoided) - private static func buildSearchReplaceNegativeOneLineSearchExample(config: PromptConfig, examples: CodeExamples) -> String { - let fence = config.codeBlockFence - var snippet = [String]() - snippet.append("") - snippet.append("Demonstrate a one-line search block, which is too short to be reliable.") - snippet.append("") - snippet.append("") - - snippet.append("") - snippet.append(" ") - snippet.append(" One-line search block is ambiguous") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: examples.userSearchReplaceNegativeExampleOneLineSearchBlock(includeIndentation: config.includeIndentationEncoding)) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: examples.userSearchReplaceNegativeExampleOneLineNewBlock(includeIndentation: config.includeIndentationEncoding)) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append("") - snippet.append("") - snippet.append("") - return snippet.joined(separator: "\n") - } - - /// New negative example: ambiguous search block (should be avoided) - private static func buildSearchReplaceNegativeAmbiguousSearchExample(config: PromptConfig, examples: CodeExamples) -> String { - let fence = config.codeBlockFence - var snippet = [String]() - snippet.append("") - snippet.append("Demonstrate an ambiguous search block that can match multiple blocks (e.g., multiple closing braces).") - snippet.append("") - snippet.append("") - - snippet.append("") - snippet.append(" ") - snippet.append(" Ambiguous search block with multiple closing braces") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: examples.userSearchReplaceNegativeExampleAmbiguousSearchBlock(includeIndentation: config.includeIndentationEncoding)) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: examples.userSearchReplaceNegativeExampleAmbiguousNewBlock(includeIndentation: config.includeIndentationEncoding)) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append("") - snippet.append("") - snippet.append("") - return snippet.joined(separator: "\n") - } - - // Example: rewrite - private static func buildRewriteExample(config: PromptConfig, examples: CodeExamples) -> String { - let fence = config.codeBlockFence - let linesForRewrite = examples.userRewriteAllLines(includeIndentation: config.includeIndentationEncoding) - - var snippet = [String]() - snippet.append("") - snippet.append("Rewrite the entire User file to include an email property.") - snippet.append("") - snippet.append("") - snippet.append("") - snippet.append(" ") - snippet.append(" Full file rewrite with new email field") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: linesForRewrite) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append("") - - return snippet.joined(separator: "\n") - } - - // Example: create - private static func buildCreateExample(config: PromptConfig, examples: CodeExamples) -> String { - let fence = config.codeBlockFence - let linesForCreate = examples.userCreateAllLines(includeIndentation: config.includeIndentationEncoding) - - var snippet = [String]() - snippet.append("") - snippet.append("Create a new RoundedButton for a custom Swift UIButton subclass.") - snippet.append("") - snippet.append("") - snippet.append("") - snippet.append(" ") - snippet.append(" Create custom RoundedButton class") - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: linesForCreate) - snippet.append(fence) - snippet.append(" ") - snippet.append(" ") - snippet.append("") - - return snippet.joined(separator: "\n") - } - - // Example: indentation-preserving - private static func buildIndentationPreservingExample(config: PromptConfig, examples: CodeExamples) -> String { - let fence = config.codeBlockFence - let oldBlock = examples.networkManagerOldLines(includeIndentation: config.includeIndentationEncoding) - let newBlock = examples.networkManagerNewLines(includeIndentation: config.includeIndentationEncoding) - - var snippet = [String]() - snippet.append("") - snippet.append("Modify `fetchData` to use async/await, keeping indentation identical.") - snippet.append("") - snippet.append("") - snippet.append("") - snippet.append(" ") - snippet.append(" Switch from completion handler to async/await") - - if config.canSearchReplace { - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: oldBlock) - snippet.append(fence) - snippet.append(" ") - } - - snippet.append(" ") - snippet.append(fence) - snippet.append(contentsOf: newBlock) - snippet.append(fence) - snippet.append(" ") - - snippet.append(" ") - snippet.append("") - - return snippet.joined(separator: "\n") - } - - // MARK: - Delete example - - private static func buildDeleteExample(config: PromptConfig) -> String { - var snippet = [String]() - snippet.append("") - snippet.append("Remove an obsolete file.") - snippet.append("") - snippet.append("") - snippet.append("") - snippet.append(" ") - snippet.append(" Completely remove the file from the project") - snippet.append(" ") - snippet.append(config.codeBlockFence) - // empty block - snippet.append(config.codeBlockFence) - snippet.append(" ") - snippet.append(" ") - snippet.append("") - return snippet.joined(separator: "\n") - } - - // MARK: - Rename example - - private static func buildRenameExample(config: PromptConfig) -> String { - var snippet = [String]() - snippet.append("") - snippet.append("Rename OldName to NewName.") - snippet.append("") - snippet.append("") - snippet.append("") - snippet.append(" ") - snippet.append("") - return snippet.joined(separator: "\n") - } - - // MARK: - File Editor Example - - private static func buildFileEditorExample(config: PromptConfig, examples: CodeExamples) -> String { - var snippet = [String]() - snippet.append("**This example shows what you'll receive as a file editor.**") - snippet.append("") - snippet.append("The architect will provide changes with placeholders like `// ... existing code ...` to indicate context.") - snippet.append("Your job is to:") - snippet.append("1. Use the actual file contents from `` to locate where changes should be applied") - snippet.append("2. Interpret the architect's instructions to determine the exact insertion points") - snippet.append("3. Apply ALL changes exactly as specified in ``") - snippet.append("4. NEVER modify code that isn't explicitly part of the changes") - snippet.append("") - snippet.append("Here's what you'll see:") - snippet.append("") - snippet.append("") - snippet.append("File: /path/to/GameManager.\(config.fileExtension)") - snippet.append("```\(config.language.lowercased())") - snippet.append(contentsOf: examples.fileEditorExampleFileContents()) - snippet.append("```") - snippet.append("") - snippet.append("") - snippet.append("") - snippet.append("Edit the file specified in with the following changes.") - snippet.append("Ensure that every single change specified in is applied exactly as specified.") - snippet.append("") - snippet.append("") - snippet.append("Change 1:") - snippet.append("Add initialization method with logging") - snippet.append("```") - snippet.append(contentsOf: examples.fileEditorExampleChange1()) - snippet.append("```") - snippet.append("") - snippet.append("Change 2:") - snippet.append("Add cleanup in destructor") - snippet.append("```") - snippet.append(contentsOf: examples.fileEditorExampleChange2()) - snippet.append("```") - snippet.append("") - snippet.append("") - snippet.append("") - snippet.append("**Your response should be:**") - snippet.append("") - snippet.append("") - snippet.append(" ") - snippet.append(" Add initialization method with logging") - snippet.append(" ") - snippet.append(config.codeBlockFence) - snippet.append(contentsOf: examples.fileEditorExampleSearchBlock()) - snippet.append(config.codeBlockFence) - snippet.append(" ") - snippet.append(" ") - snippet.append(config.codeBlockFence) - snippet.append(contentsOf: examples.fileEditorExampleContentBlock()) - snippet.append(config.codeBlockFence) - snippet.append(" ") - snippet.append(" ") - snippet.append(" ") - snippet.append(" Add cleanup in destructor") - snippet.append(" ") - snippet.append(config.codeBlockFence) - snippet.append(contentsOf: examples.fileEditorExampleSearchBlock2()) - snippet.append(config.codeBlockFence) - snippet.append(" ") - snippet.append(" ") - snippet.append(config.codeBlockFence) - snippet.append(contentsOf: examples.fileEditorExampleContentBlock2()) - snippet.append(config.codeBlockFence) - snippet.append(" ") - snippet.append(" ") - snippet.append("") - snippet.append("") - snippet.append("**Key points:**") - snippet.append("- The architect uses `// ... existing code ...` as context markers") - snippet.append("- You must find the actual code from `` that matches the context") - snippet.append("- Your `` block must contain the EXACT code from the file, not the architect's placeholders") - snippet.append("- Apply all changes while preserving the rest of the file exactly as-is") - - return snippet.joined(separator: "\n") - } - - // MARK: - File Editor Rewrite-Only Example - - private static func buildFileEditorRewriteOnlyExample(config: PromptConfig, examples: CodeExamples) -> String { - var snippet = [String]() - snippet.append("**This example shows what you'll receive as a file editor that can only rewrite (no search/replace).**") - snippet.append("") - snippet.append("Since you cannot do partial search/replace, you must:") - snippet.append("1. Read the entire file content from ``") - snippet.append("2. Apply ALL changes specified in `` to create the complete updated file") - snippet.append("3. Return the ENTIRE file with all changes integrated using action=\"rewrite\"") - snippet.append("") - snippet.append("Here's what you'll see:") - snippet.append("") - snippet.append("") - snippet.append("File: /path/to/UserService.\(config.fileExtension)") - snippet.append("```\(config.language.lowercased())") - snippet.append(contentsOf: examples.fileEditorRewriteExampleFileContents()) - snippet.append("```") - snippet.append("") - snippet.append("") - snippet.append("") - snippet.append("Edit the file specified in with the following changes.") - snippet.append("Since you can only rewrite files, you must output the COMPLETE updated file.") - snippet.append("") - snippet.append("") - snippet.append("Change 1:") - snippet.append("Add input validation to the processUser function") - snippet.append("```") - snippet.append(contentsOf: examples.fileEditorRewriteExampleChange1()) - snippet.append("```") - snippet.append("") - snippet.append("Change 2:") - snippet.append("Add error handling to the saveUser function") - snippet.append("```") - snippet.append(contentsOf: examples.fileEditorRewriteExampleChange2()) - snippet.append("```") - snippet.append("") - snippet.append("") - snippet.append("") - snippet.append("**Your response should be:**") - snippet.append("") - snippet.append("") - snippet.append(" ") - snippet.append(" Complete file with validation in processUser and error handling in saveUser") - snippet.append(" ") - snippet.append(config.codeBlockFence) - snippet.append(contentsOf: examples.fileEditorRewriteExampleCompleteFile()) - snippet.append(config.codeBlockFence) - snippet.append(" ") - snippet.append(" ") - snippet.append("") - snippet.append("") - snippet.append("**Key differences from search/replace:**") - snippet.append("- You must include the ENTIRE file content, not just the changed parts") - snippet.append("- Use action=\"rewrite\" instead of action=\"modify\"") - snippet.append("- No `` blocks - just the complete updated file in ``") - snippet.append("- Integrate all changes while preserving the rest of the file exactly as-is") - snippet.append("- The architect's placeholders (`// ... existing code ...`) indicate unchanged sections you must preserve from the original") - - return snippet.joined(separator: "\n") - } - - // MARK: - (4) Format Guidelines - - private static func buildFinalNotesSection(config: PromptConfig) -> String { - var lines = [String]() - lines.append("## Final Notes") - - var idx = 1 - - /// Convenience function for grouped bullets - func addGroup(title: String, _ sub: [String]) { - lines.append("\(idx). \(title)") - idx += 1 - sub.forEach { lines.append(" - \($0)") } - } - - // ───────────────────────────── modify / rewrite ────────────────────────── - if config.canRewrite { - var rewriteBullets = [ - "For rewriting an entire file, place all new content in ``. No partial modifications are possible here. Avoid all use of placeholders.", - "You must include **exactly one** `` block when performing a rewrite, and the `` inside that block must contain the full, updated content of the file." - ] - - addGroup(title: "**rewrite**", rewriteBullets) - } - - if config.canSearchReplace { - var modifyBullets = [ - "Always wrap the exact original lines in and your updated lines in , each enclosed by \(config.codeBlockFence).", - "The block must match the source code exactly—down to indentation, braces, spacing, and any comments. Even a minor mismatch causes failed merges.", - "Ensure that all blocks have unique lines in them that unambiguosly match the precise part of the file we're trying to edit.", - "If editing two very similar parts of the file, ensure that each is uniquely specific to the part each is supposed to edit.", - "Only replace exactly what you need. Avoid including entire functions or files if only a small snippet changes, and ensure the content is unique and easy to identify." - ] - - if config.canRewrite { - modifyBullets.append("Use `rewrite` for major overhauls, and `modify` for smaller, localized edits. Rewrite requires the entire code to be replaced, so use it sparingly.") - } - - addGroup(title: "**modify**", modifyBullets) - } - - // ───────────────────────────── create & delete ─────────────────────────── - if config.role != .fileEditor { - addGroup(title: "**create & delete**", [ - "**BEFORE CREATING**: Always check file_contents first - if a similar file already exists, consider editing it instead of creating a duplicate.", - "You can always **create** new files and **delete** existing files. Provide full code for create, and empty content for delete.", - "When user instructions imply modifying existing functionality, prioritize editing files from file_contents over creating new ones.", - "If a file tree is provided, place your files logically within that structure. Respect the user's relative or absolute paths." - ]) - } else { - addGroup(title: "**file editor – recap**", [ - "Skip regions marked `// ... existing code ...` in the architect's instructions; apply edits in between.", - "Lines omitted between anchors are deleted.", - "Remove placeholder comments from architect's instructions ONLY - preserve ALL original file comments and placheolders (located in the block).", - "Replace full scopes exactly when given without placeholders.", - // "Process `` blocks sequentially; reject overlaps.", - "Fix ≤ 1 obvious syntax error; otherwise apply verbatim.", - "**ABSOLUTE RULE**: Never modify ANY code outside REPOMARK:SCOPE markers.", - "**NO EXCEPTIONS**: Even if code looks wrong, preserve it exactly if not in scope.", - "**TRUST THE ARCHITECT**: They chose what to change - respect their boundaries.", - "**ORIGINAL FILE INTEGRITY**: If the original file has placeholder-like comments, they are real code - keep them!" - ]) - } - - // ───────────────────────────── rename rules ────────────────────────────── - if config.supportsRename { - var renameBullets = [ - "Use **rename** to move a file by adding `` and leaving `` empty. This deletes the old file and materialises the new one with the original content." - ] - - if config.canSearchReplace { - if config.canRewrite { - renameBullets.append("After a rename, **do not** pair it with **modify** or **rewrite** on either the old **or** the new path in the same response.") - } else { - renameBullets.append("After a rename, **do not** pair it with **modify** on either the old **or** the new path in the same response.") - } - } else { - renameBullets.append("No additional file edits for the same file in the same response.") - } - - renameBullets += [ - "Never reference the *old* path again, and never add a `` that duplicates the **new** path in the same run.", - "Ensure the destination path does **not** already exist and rename a given file **at most once per response**.", - "If the new file requires changes, first delete it, then create a fresh file with the desired content." - ] - - addGroup(title: "**rename**", renameBullets) - } - - // ───────────────────────────── output-format rules ─────────────────────── - if config.role == .apply || config.includeApplyOutputWrappingNotes { - addGroup(title: "**additional formatting rules**", [ - "Wrap your final output in ```XML … ``` for clarity.", - "**Important:** do **not** wrap XML in CDATA tags (``). Repo Prompt expects raw XML exactly as shown in the examples." - ]) - } - - if config.role != .apply { - addGroup(title: "**capabilities**", [ - "If you see mentions of capabilities not listed above in the user’s chat history, **do not** try to use them." - ]) - } - - if config.role == .architect || config.role == .codeAssistant { - addGroup(title: "**chatName**", [ - "Always include `` near the top when you produce multi-file or complex changes." - ]) - addGroup(title: "**Editing rules**", [ - "**CRITICAL**: Before deciding whether to create or edit files, carefully examine ALL files in the file_contents section.", - "When user instructions reference a file or functionality, first check if a related file exists in file_contents - if it does, edit that file instead of creating a new one.", - "Files in file_contents are your primary working context - they represent the actual codebase structure and existing implementations.", - "**PATH PRECISION**: Use the EXACT file path shown in file_contents when writing `` - do not modify or approximate the path.", - "The path after 'File: ' in each file_contents block is the precise path you must use in your file edits.", - "Never attempt to edit a file not listed in the user prompt's file_contents section.", - "If you must edit a file not in the file_contents block, ask the user to include it in their next message.", - "If the file is in the file_contents block, you have everything you need to successfully complete the edit." - ]) - } - - if config.includeIndentationEncoding { - addGroup(title: "**indentation encoding**", [ - "Maintain `` or `` tags consistently." - ]) - } - - if config.includeEscapingRules { - addGroup(title: "**escaping**", [ - "Escape quotes as `\\\"` and backslashes as `\\\\` if necessary." - ]) - } - - // ───────────────────────────── mandatory reminders ─────────────────────── - let mandatoryBullets = [ - "WHEN MAKING FILE CHANGES, YOU **MUST** USE THE XML FORMATTING CAPABILITIES SHOWN ABOVE—IT IS THE *ONLY* WAY FOR CHANGES TO BE APPLIED.", - "The final output must apply cleanly with **no leftover syntax errors**." - ] - - /* - // Add file editor specific mandatory rules - if config.role == .fileEditor { - mandatoryBullets += [ - "**CRITICAL**: You MUST NOT modify ANY code that is not explicitly marked within REPOMARK:SCOPE markers.", - "**PRESERVE EXACTLY**: All code outside scope markers must be returned VERBATIM - do not 'improve', reformat, or change it in ANY way.", - "**NO UNAUTHORIZED CHANGES**: Even if you see obvious bugs, style issues, or improvements outside the marked scopes - DO NOT TOUCH THEM.", - "**SCOPE BOUNDARIES ARE SACRED**: If code appears between `// ... existing code ...` markers IN THE INSTRUCTIONS, it is OFF LIMITS - preserve it exactly as shown.", - "**VERBATIM MEANS VERBATIM**: Copy all unmarked code character-for-character, including 'wrong' indentation, trailing spaces, or any quirks.", - "**PLACEHOLDER CLARIFICATION**: Only remove placeholders from the ARCHITECT'S INSTRUCTIONS - if the original file contains similar comments, they are REAL CODE and must be preserved.", - "**REJECTION CRITERIA**: Your edit will be REJECTED if you modify even a single character outside the designated scope markers." - ] - } - */ - - addGroup(title: "**MANDATORY**", mandatoryBullets) - - return lines.joined(separator: "\n") - } -} diff --git a/Sources/RepoPrompt/Infrastructure/AI/SystemPromptService.swift b/Sources/RepoPrompt/Infrastructure/AI/SystemPromptService.swift index dd5d9caef..276c90f93 100644 --- a/Sources/RepoPrompt/Infrastructure/AI/SystemPromptService.swift +++ b/Sources/RepoPrompt/Infrastructure/AI/SystemPromptService.swift @@ -1,20 +1,6 @@ import Foundation class SystemPromptService { - static let chatCodeFence = "```" - - // MARK: - Language to Extension Mapping - - private static let languageToExtension: [String: String] = [ - "Swift": "swift", "JavaScript": "js", "TypeScript": "ts", - "Python": "py", "Java": "java", "C#": "cs", "C++": "cpp", "C": "c", - "Go": "go", "Rust": "rs", "PHP": "php", "Ruby": "rb", "Dart": "dart" - ] - - static func fileExtension(for language: String) -> String { - languageToExtension[language] ?? "txt" - } - /// Returns the Discover prompt with an optional token budget. /// - Parameter tokenBudget: Optional token budget for the final selection. If nil, targets 50-80k tokens. /// - Parameter agentKind: The agent that will execute this prompt, affects tool restriction warnings. @@ -1118,11 +1104,6 @@ class SystemPromptService { """ } - static func predominantLanguage(from files: [FileViewModel]) -> String { - let extensions = files.compactMap { $0.fileExtension?.lowercased() } - return predominantLanguage(fromExtensions: extensions) - } - static func predominantLanguage(from files: [WorkspaceFileRecord]) -> String { let extensions = files.map { file in let ext = (file.name as NSString).pathExtension.lowercased() @@ -1147,16 +1128,6 @@ class SystemPromptService { return map[mostCommon] ?? "Swift" } - /// Determines the predominant language from selected files - @MainActor - static func getPredominantLanguage(from fileManager: WorkspaceFilesViewModel) -> (language: String, fileExtension: String) { - let files = fileManager.selectedFiles - let language = predominantLanguage(from: files) - // Keep the old signature for backward compatibility, but the extension is derived from language - let ext = fileExtension(for: language) - return (language: language, fileExtension: ext) - } - static func getFileRecommendationPrompt() -> String { """ You are an assistant tasked with recommending relevant plain-text files from a codebase, based solely on the user's prompt, a provided file tree, and available codemaps. diff --git a/Sources/RepoPrompt/Infrastructure/Diffing/DiffParser.swift b/Sources/RepoPrompt/Infrastructure/Diffing/DiffParser.swift index 9b2e7bfa3..ca0456ab1 100644 --- a/Sources/RepoPrompt/Infrastructure/Diffing/DiffParser.swift +++ b/Sources/RepoPrompt/Infrastructure/Diffing/DiffParser.swift @@ -1069,30 +1069,13 @@ enum DiffParserUtils { class DiffParser { private let fileManager: WorkspaceFilesViewModel - #if DEBUG - /// Debug configuration for testing - only available in DEBUG builds - struct DebugConfig { - var treatNonExistentFilesAsExisting: Bool = false - var alwaysPreserveRewriteAction: Bool = false - } - - private let debugConfig: DebugConfig? - #endif - private func dbg(_ msg: @autoclosure () -> String) { dprint(msg()) } - #if DEBUG - init(fileManager: WorkspaceFilesViewModel, debugConfig: DebugConfig? = nil) { - self.fileManager = fileManager - self.debugConfig = debugConfig - } - #else - init(fileManager: WorkspaceFilesViewModel) { - self.fileManager = fileManager - } - #endif + init(fileManager: WorkspaceFilesViewModel) { + self.fileManager = fileManager + } /// Merges two actions for the same file into a single effective action /// following these rules: @@ -1218,13 +1201,6 @@ class DiffParser { canBeLoaded = true // latestContent is an async getter loadedFile = await file.latestContent ?? "" - } else if let baselineContent = await fileManager.getBaselineContent( - forPath: location.correctedPath, - rootIdentifier: location.rootIdentifier - ) { - // Use baseline content if available (e.g., in benchmark mode) - canBeLoaded = true - loadedFile = baselineContent } else { // File doesn't exist in hierarchy yet canBeLoaded = false @@ -1245,26 +1221,11 @@ class DiffParser { // ---- Handle modify on non-existent file (error but continue) ----- if originalAction == .modify, !canBeLoaded { errors.append(.fileNotFoundForModify(filePath: canonicalPath)) - #if DEBUG - // In debug mode, we can treat non-existent files as existing for testing - if debugConfig?.treatNonExistentFilesAsExisting == true { - canBeLoaded = true - } - #endif } // ---- Convert rewrite → add if file doesn't exist ----- if originalAction == .rewrite, !canBeLoaded { - #if DEBUG - // In debug mode with alwaysPreserveRewriteAction, keep it as rewrite - if debugConfig?.alwaysPreserveRewriteAction == true { - effectiveAction = .rewrite - } else { - effectiveAction = .create - } - #else - effectiveAction = .create - #endif + effectiveAction = .create canBeLoaded = true // Treat as new file } @@ -1415,12 +1376,6 @@ class DiffParser { if let data = await file.latestContent { loadedOld = data } - } else if let baselineContent = await fileManager.getBaselineContent( - forPath: locationOld.correctedPath, - rootIdentifier: locationOld.rootIdentifier - ) { - // Use baseline content if available (e.g., in benchmark mode) - loadedOld = baselineContent } } diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/GoQueries.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/GoQueries.swift deleted file mode 100644 index 8d9292e6c..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/GoQueries.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// GoQueries.swift -// RepoPrompt -// -// Created by Eric Provencher on 2025-02-06. -// - -// GoQueries.swift -// Make sure no tab characters are present; use only spaces for indentation. - -import Foundation - -let goQuery = """ -; Function calls - -(call_expression - function: (identifier) @function) - -(call_expression - function: (identifier) @function.builtin - (#match? @function.builtin "^(append|cap|close|complex|copy|delete|imag|len|make|new|panic|print|println|real|recover)$")) - -(call_expression - function: (selector_expression - field: (field_identifier) @function.method)) - -; Function definitions - -(function_declaration - name: (identifier) @function) - -(method_declaration - name: (field_identifier) @function.method) - -; Identifiers - -(type_identifier) @type -(field_identifier) @property -(identifier) @variable - -; Operators - -[ - "--" - "-" - "-=" - ":=" - "!" - "!=" - "..." - "*" - "*" - "*=" - "/" - "/=" - "&" - "&&" - "&=" - "%" - "%=" - "^" - "^=" - "+" - "++" - "+=" - "<-" - "<" - "<<" - "<<=" - "<=" - "=" - "==" - ">" - ">=" - ">>" - ">>=" - "|" - "|=" - "||" - "~" -] @operator - -; Keywords - -[ - "break" - "case" - "chan" - "const" - "continue" - "default" - "defer" - "else" - "fallthrough" - "for" - "func" - "go" - "goto" - "if" - "import" - "interface" - "map" - "package" - "range" - "return" - "select" - "struct" - "switch" - "type" - "var" -] @keyword - -; Literals - -[ - (interpreted_string_literal) - (raw_string_literal) - (rune_literal) -] @string - -(escape_sequence) @escape - -[ - (int_literal) - (float_literal) - (imaginary_literal) -] @number - -[ - (true) - (false) - (nil) - (iota) -] @constant.builtin - -(comment) @comment -""" - -// Code-map (structural) query for Go, capturing top-level declarations, imports, etc. diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/JavaQueries.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/JavaQueries.swift deleted file mode 100644 index 72e0abe3c..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/JavaQueries.swift +++ /dev/null @@ -1,163 +0,0 @@ -// -// JavaQueries.swift -// RepoPrompt -// -// Created by Eric Provencher on 2025-02-06. -// - -// JavaQuery.swift -// Make sure no tab characters are present; use only spaces for indentation. - -import Foundation - -let javaQuery = """ -; Variables - -(identifier) @variable - -; Methods - -(method_declaration - name: (identifier) @function.method) -(method_invocation - name: (identifier) @function.method) -(super) @function.builtin - -; Annotations - -(annotation - name: (identifier) @attribute) -(marker_annotation - name: (identifier) @attribute) - -"@" @operator - -; Types - -(type_identifier) @type - -(interface_declaration - name: (identifier) @type) -(class_declaration - name: (identifier) @type) -(enum_declaration - name: (identifier) @type) - -((field_access - object: (identifier) @type) - (#match? @type "^[A-Z]")) -((scoped_identifier - scope: (identifier) @type) - (#match? @type "^[A-Z]")) -((method_invocation - object: (identifier) @type) - (#match? @type "^[A-Z]")) -((method_reference - . (identifier) @type) - (#match? @type "^[A-Z]")) - -(constructor_declaration - name: (identifier) @type) - -[ - (boolean_type) - (integral_type) - (floating_point_type) - (floating_point_type) - (void_type) -] @type.builtin - -; Constants - -((identifier) @constant - (#match? @constant "^_*[A-Z][A-Z\\d_]+$")) - -; Builtins - -(this) @variable.builtin - -; Literals - -[ - (hex_integer_literal) - (decimal_integer_literal) - (octal_integer_literal) - (decimal_floating_point_literal) - (hex_floating_point_literal) -] @number - -[ - (character_literal) - (string_literal) -] @string -(escape_sequence) @string.escape - -[ - (true) - (false) - (null_literal) -] @constant.builtin - -[ - (line_comment) - (block_comment) -] @comment - -; Keywords - -[ - "abstract" - "assert" - "break" - "case" - "catch" - "class" - "continue" - "default" - "do" - "else" - "enum" - "exports" - "extends" - "final" - "finally" - "for" - "if" - "implements" - "import" - "instanceof" - "interface" - "module" - "native" - "new" - "non-sealed" - "open" - "opens" - "package" - "permits" - "private" - "protected" - "provides" - "public" - "requires" - "record" - "return" - "sealed" - "static" - "strictfp" - "switch" - "synchronized" - "throw" - "throws" - "to" - "transient" - "transitive" - "try" - "uses" - "volatile" - "when" - "while" - "with" - "yield" -] @keyword -""" diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/JavaScriptQueries.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/JavaScriptQueries.swift deleted file mode 100644 index bae2a2e4d..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/JavaScriptQueries.swift +++ /dev/null @@ -1,217 +0,0 @@ -// -// JavaScriptQueries.swift -// RepoPrompt -// -// Created by Eric Provencher on 2025-02-06. -// -// JavaScriptQueries.swift -// Make sure no tab characters are present; use only spaces for indentation. - -import Foundation - -let javascriptQuery = """ -; Variables -;---------- - -(identifier) @variable - -; Properties -;----------- - -(property_identifier) @property - -; Function and method definitions -;-------------------------------- - -(function_expression - name: (identifier) @function) -(function_declaration - name: (identifier) @function) -(method_definition - name: (property_identifier) @function.method) - -(pair - key: (property_identifier) @function.method - value: [(function_expression) (arrow_function)]) - -(assignment_expression - left: (member_expression - property: (property_identifier) @function.method) - right: [(function_expression) (arrow_function)]) - -(variable_declarator - name: (identifier) @function - value: [(function_expression) (arrow_function)]) - -(assignment_expression - left: (identifier) @function - right: [(function_expression) (arrow_function)]) - -; Function and method calls -;-------------------------- - -(call_expression - function: (identifier) @function) - -(call_expression - function: (member_expression - property: (property_identifier) @function.method)) - -; Special identifiers -;-------------------- - -((identifier) @constructor - (#match? @constructor "^[A-Z]")) - -([ - (identifier) - (shorthand_property_identifier) - (shorthand_property_identifier_pattern) - ] @constant - (#match? @constant "^[A-Z_][A-Z\\d_]+$")) - -((identifier) @variable.builtin - (#match? @variable.builtin "^(arguments|module|console|window|document)$") - (#is-not? local)) - -((identifier) @function.builtin - (#eq? @function.builtin "require") - (#is-not? local)) - -; Literals -;--------- - -(this) @variable.builtin -(super) @variable.builtin - -[ - (true) - (false) - (null) - (undefined) -] @constant.builtin - -(comment) @comment - -[ - (string) - (template_string) -] @string - -(regex) @string.special -(number) @number - -; Tokens -;------- - -[ - ";" - (optional_chain) - "." - "," -] @punctuation.delimiter - -[ - "-" - "--" - "-=" - "+" - "++" - "+=" - "*" - "*=" - "**" - "**=" - "/" - "/=" - "%" - "%=" - "<" - "<=" - "<<" - "<<=" - "=" - "==" - "===" - "!" - "!=" - "!==" - "=>" - ">" - ">=" - ">>" - ">>=" - ">>>" - ">>>=" - "~" - "^" - "&" - "|" - "^=" - "&=" - "|=" - "&&" - "||" - "??" - "&&=" - "||=" - "??=" -] @operator - -[ - "(" - ")" - "[" - "]" - "{" - "}" -] @punctuation.bracket - -(template_substitution - "${" @punctuation.special - "}" @punctuation.special) @embedded - -[ - "as" - "async" - "await" - "break" - "case" - "catch" - "class" - "const" - "continue" - "debugger" - "default" - "delete" - "do" - "else" - "export" - "extends" - "finally" - "for" - "from" - "function" - "get" - "if" - "import" - "in" - "instanceof" - "let" - "new" - "of" - "return" - "set" - "static" - "switch" - "target" - "throw" - "try" - "typeof" - "var" - "void" - "while" - "with" - "yield" -] @keyword -""" diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/PythonQueries.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/PythonQueries.swift deleted file mode 100644 index 323b15dc9..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/PythonQueries.swift +++ /dev/null @@ -1,150 +0,0 @@ -// -// PythonQueries.swift -// RepoPrompt -// -// Created by Eric Provencher on 2025-02-06. -// -// PythonQueries.swift -// Make sure no tab characters are present; use only spaces for indentation. - -import Foundation - -let pythonQuery = """ -; Identifier naming conventions - -(identifier) @variable - -((identifier) @constructor - (#match? @constructor "^[A-Z]")) - -((identifier) @constant - (#match? @constant "^[A-Z][A-Z_]*$")) - -; Function calls - -(decorator) @function -(decorator - (identifier) @function) - -(call - function: (attribute attribute: (identifier) @function.method)) -(call - function: (identifier) @function) - -; Builtin functions - -((call - function: (identifier) @function.builtin) - (#match? - @function.builtin - "^(abs|all|any|ascii|bin|bool|breakpoint|bytearray|bytes|callable|chr|classmethod|compile|complex|delattr|dict|dir|divmod|enumerate|eval|exec|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|isinstance|issubclass|iter|len|list|locals|map|max|memoryview|min|next|object|oct|open|ord|pow|print|property|range|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|vars|zip|__import__)$")) - -; Function definitions - -(function_definition - name: (identifier) @function) - -(attribute attribute: (identifier) @property) -(type (identifier) @type) - -; Literals - -[ - (none) - (true) - (false) -] @constant.builtin - -[ - (integer) - (float) -] @number - -(comment) @comment -(string) @string -(escape_sequence) @escape - -(interpolation - "{" @punctuation.special - "}" @punctuation.special) @embedded - -[ - "-" - "-=" - "!=" - "*" - "**" - "**=" - "*=" - "/" - "//" - "//=" - "/=" - "&" - "&=" - "%" - "%=" - "^" - "^=" - "+" - "->" - "+=" - "<" - "<<" - "<<=" - "<=" - "<>" - "=" - ":=" - "==" - ">" - ">=" - ">>" - ">>=" - "|" - "|=" - "~" - "@=" - "and" - "in" - "is" - "not" - "or" - "is not" - "not in" -] @operator - -[ - "as" - "assert" - "async" - "await" - "break" - "class" - "continue" - "def" - "del" - "elif" - "else" - "except" - "exec" - "finally" - "for" - "from" - "global" - "if" - "import" - "lambda" - "nonlocal" - "pass" - "print" - "raise" - "return" - "try" - "while" - "with" - "yield" - "match" - "case" -] @keyword -""" diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/RubyQueries.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/RubyQueries.swift deleted file mode 100644 index 9f7b50416..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/RubyQueries.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// RubyQueries.swift -// RepoPrompt -// -// Created by RepoPrompt on 2026-01-30. -// - -import Foundation - -let rubyHighlightQuery = """ -; Minimal, compiler-safe Ruby highlight query - -(string) @string -(comment) @comment -(integer) @number -(float) @number - -(simple_symbol) @string.special.symbol -(delimited_symbol) @string.special.symbol -(hash_key_symbol) @string.special.symbol -(bare_symbol) @string.special.symbol - -(constant) @constant -(identifier) @variable - -"def" @keyword -"class" @keyword -"module" @keyword -"end" @keyword -""" diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/RustQueries.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/RustQueries.swift deleted file mode 100644 index 23ea7bf5f..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/RustQueries.swift +++ /dev/null @@ -1,175 +0,0 @@ -// -// RustQueries.swift -// RepoPrompt -// -// Created by Eric Provencher on 2025-02-06. -// - -// RustQuery.swift -// Make sure no tab characters are present; use only spaces for indentation. - -import Foundation - -let rustQuery = """ -; Identifiers - - (type_identifier) @type - (primitive_type) @type.builtin - (field_identifier) @property - -; Identifier conventions - -; Assume all-caps names are constants - ((identifier) @constant - (#match? @constant "^[A-Z][A-Z\\d_]+$'")) - -; Assume uppercase names are enum constructors -((identifier) @constructor - (#match? @constructor "^[A-Z]")) - -; Assume that uppercase names in paths are types - ((scoped_identifier - path: (identifier) @type) - (#match? @type "^[A-Z]")) -((scoped_identifier - path: (scoped_identifier - name: (identifier) @type)) - (#match? @type "^[A-Z]")) -((scoped_type_identifier - path: (identifier) @type) - (#match? @type "^[A-Z]")) -((scoped_type_identifier - path: (scoped_identifier - name: (identifier) @type)) - (#match? @type "^[A-Z]")) - -; Assume all qualified names in struct patterns are enum constructors. (They're - ; either that, or struct names; highlighting both as constructors seems to be - ; the less glaring choice of error, visually.) -(struct_pattern - type: (scoped_type_identifier - name: (type_identifier) @constructor)) - -; Function calls - - (call_expression - function: (identifier) @function) -(call_expression - function: (field_expression - field: (field_identifier) @function.method)) -(call_expression - function: (scoped_identifier - "::" - name: (identifier) @function)) - -(generic_function - function: (identifier) @function) -(generic_function - function: (scoped_identifier - name: (identifier) @function)) -(generic_function - function: (field_expression - field: (field_identifier) @function.method)) - -(macro_invocation - macro: (identifier) @function.macro - "!" @function.macro) - -; Function definitions - - (function_item (identifier) @function) -(function_signature_item (identifier) @function) - -(line_comment) @comment - (block_comment) @comment - - (line_comment (doc_comment)) @comment.documentation - (block_comment (doc_comment)) @comment.documentation - -"(" @punctuation.bracket -")" @punctuation.bracket -"[" @punctuation.bracket -"]" @punctuation.bracket -"{" @punctuation.bracket -"}" @punctuation.bracket - - (type_arguments - "<" @punctuation.bracket - ">" @punctuation.bracket) -(type_parameters - "<" @punctuation.bracket - ">" @punctuation.bracket) - -"::" @punctuation.delimiter -":" @punctuation.delimiter -"." @punctuation.delimiter -"," @punctuation.delimiter -";" @punctuation.delimiter - - (parameter (identifier) @variable.parameter) - -(lifetime (identifier) @label) - -"as" @keyword -"async" @keyword -"await" @keyword -"break" @keyword -"const" @keyword -"continue" @keyword -"default" @keyword -"dyn" @keyword -"else" @keyword -"enum" @keyword -"extern" @keyword -"fn" @keyword -"for" @keyword -"gen" @keyword -"if" @keyword -"impl" @keyword -"in" @keyword -"let" @keyword -"loop" @keyword -"macro_rules!" @keyword -"match" @keyword -"mod" @keyword -"move" @keyword -"pub" @keyword -"raw" @keyword -"ref" @keyword -"return" @keyword -"static" @keyword -"struct" @keyword -"trait" @keyword -"type" @keyword -"union" @keyword -"unsafe" @keyword -"use" @keyword -"where" @keyword -"while" @keyword -"yield" @keyword - (crate) @keyword - (mutable_specifier) @keyword - (use_list (self) @keyword) -(scoped_use_list (self) @keyword) -(scoped_identifier (self) @keyword) -(super) @keyword - - (self) @variable.builtin - - (char_literal) @string - (string_literal) @string - (raw_string_literal) @string - - (boolean_literal) @constant.builtin - (integer_literal) @constant.builtin - (float_literal) @constant.builtin - - (escape_sequence) @escape - - (attribute_item) @attribute - (inner_attribute_item) @attribute - -"*" @operator -"&" @operator -"'" @operator -""" diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/SwiftQueries.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/SwiftQueries.swift deleted file mode 100644 index ed0b78af7..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/SwiftQueries.swift +++ /dev/null @@ -1,202 +0,0 @@ -// -// SwiftQueries.swift -// RepoPrompt -// -// Created by Eric Provencher on 2025-02-06. -// Updated to match the tree-sitter-swift grammar (per attached JSON). -// Ensure only spaces are used for indentation. -// - -import Foundation - -let swiftQuery = #""" -[ "." ";" ":" "," ] @punctuation.delimiter -[ "\\(" "(" ")" "[" "]" "{" "}"] @punctuation.bracket ; TODO: "\\(" ")" in interpolations should be @punctuation.special - -; Identifiers -(attribute) @variable -(type_identifier) @type -(self_expression) @variable.builtin -(user_type (type_identifier) @variable.builtin (#eq? @variable.builtin "Self")) - -; Declarations -"func" @keyword.function -[ - (visibility_modifier) - (member_modifier) - (function_modifier) - (property_modifier) - (parameter_modifier) - (inheritance_modifier) -] @keyword - -(function_declaration (simple_identifier) @method) -(init_declaration ["init" @constructor]) -(deinit_declaration ["deinit" @constructor]) -(throws) @keyword -"async" @keyword -"await" @keyword -(where_keyword) @keyword -(parameter external_name: (simple_identifier) @parameter) -(parameter name: (simple_identifier) @parameter) -(type_parameter (type_identifier) @parameter) -(inheritance_constraint (identifier (simple_identifier) @parameter)) -(equality_constraint (identifier (simple_identifier) @parameter)) -(pattern bound_identifier: (simple_identifier)) @variable - -[ - "typealias" - "struct" - "class" - "actor" - "enum" - "protocol" - "extension" - "indirect" - "nonisolated" - "override" - "convenience" - "required" - "mutating" - "associatedtype" - "package" -] @keyword - -(opaque_type ["some" @keyword]) -(existential_type ["any" @keyword]) - -(precedence_group_declaration - ["precedencegroup" @keyword] - (simple_identifier) @type) -(precedence_group_attribute - (simple_identifier) @keyword - [(simple_identifier) @type - (boolean_literal) @boolean]) - -[ - (getter_specifier) - (setter_specifier) - (modify_specifier) -] @keyword - -(class_body (property_declaration (pattern (simple_identifier) @property))) -(protocol_property_declaration (pattern (simple_identifier) @property)) - -(import_declaration ["import" @include]) - -(enum_entry ["case" @keyword]) - -; Function calls -(call_expression (simple_identifier) @function.call) ; foo() -(call_expression ; foo.bar.baz(): highlight the baz() - (navigation_expression - (navigation_suffix (simple_identifier) @function.call))) -((navigation_expression - (simple_identifier) @type) ; SomeType.method(): highlight SomeType as a type - (#match? @type "^[A-Z]")) -(call_expression (simple_identifier) @keyword (#eq? @keyword "defer")) ; defer { ... } - -(try_operator) @operator -(try_operator ["try" @keyword]) - -(directive) @function.macro -(diagnostic) @function.macro - -; Statements -(for_statement ["for" @repeat]) -(for_statement ["in" @repeat]) -(for_statement (pattern) @variable) -(else) @keyword -(as_operator) @keyword - -["while" "repeat" "continue" "break"] @repeat - -["let" "var"] @keyword - -(guard_statement ["guard" @conditional]) -(if_statement ["if" @conditional]) -(switch_statement ["switch" @conditional]) -(switch_entry ["case" @keyword]) -(switch_entry ["fallthrough" @keyword]) -(switch_entry (default_keyword) @keyword) -"return" @keyword.return -(ternary_expression - ["?" ":"] @conditional) - -["do" (throw_keyword) (catch_keyword)] @keyword - -(statement_label) @label - -; Comments -[ - (comment) - (multiline_comment) -] @comment @spell - -; String literals -(line_str_text) @string -(str_escaped_char) @string -(multi_line_str_text) @string -(raw_str_part) @string -(raw_str_end_part) @string -(raw_str_interpolation_start) @punctuation.special -["\"" "\"\"\""] @string - -; Lambda literals -(lambda_literal ["in" @keyword.operator]) - -; Basic literals -[ - (integer_literal) - (hex_literal) - (oct_literal) - (bin_literal) -] @number -(real_literal) @float -(boolean_literal) @boolean -"nil" @variable.builtin - -; Regex literals -(regex_literal) @string.regex - -; Operators -(custom_operator) @operator -[ - "!" - "?" - "+" - "-" - "*" - "/" - "%" - "=" - "+=" - "-=" - "*=" - "/=" - "<" - ">" - "<=" - ">=" - "++" - "--" - "&" - "~" - "%=" - "!=" - "!==" - "==" - "===" - "??" - - "->" - - "..<" - "..." -] @operator - -(value_parameter_pack ["each" @keyword]) -(value_pack_expansion ["repeat" @keyword]) -(type_parameter_pack ["each" @keyword]) -(type_pack_expansion ["repeat" @keyword]) -"""# diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cQueries.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cQueries.swift deleted file mode 100644 index ca4271890..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cQueries.swift +++ /dev/null @@ -1,92 +0,0 @@ -// cQueries.swift -// RepoPrompt -// -// Created by Eric Provencher on 2025-02-06. -// -// Make sure no tab characters are present; use only spaces for indentation. - -import Foundation - -let cQuery = """ -(identifier) @variable - -((identifier) @constant - (#match? @constant "^[A-Z][A-Z\\d_]*$")) - -"break" @keyword -"case" @keyword -"const" @keyword -"continue" @keyword -"default" @keyword -"do" @keyword -"else" @keyword -"enum" @keyword -"extern" @keyword -"for" @keyword -"if" @keyword -"inline" @keyword -"return" @keyword -"sizeof" @keyword -"static" @keyword -"struct" @keyword -"switch" @keyword -"typedef" @keyword -"union" @keyword -"volatile" @keyword -"while" @keyword - -"#define" @keyword -"#elif" @keyword -"#else" @keyword -"#endif" @keyword -"#if" @keyword -"#ifdef" @keyword -"#ifndef" @keyword -"#include" @keyword -(preproc_directive) @keyword - -"--" @operator -"-" @operator -"-=" @operator -"->" @operator -"=" @operator -"!=" @operator -"*" @operator -"&" @operator -"&&" @operator -"+" @operator -"++" @operator -"+=" @operator -"<" @operator -"==" @operator -">" @operator -"||" @operator - -"." @delimiter -";" @delimiter - -(string_literal) @string -(system_lib_string) @string - -(null) @constant -(number_literal) @number -(char_literal) @number - -(field_identifier) @property -(statement_identifier) @label -(type_identifier) @type -(primitive_type) @type -(sized_type_specifier) @type - -(call_expression - function: (identifier) @function) -(call_expression - function: (field_expression - field: (field_identifier) @function)) -(function_declarator - declarator: (identifier) @function) -(preproc_function_def - name: (identifier) @function.special) - -(comment) @comment -""" diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cSharpQueries.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cSharpQueries.swift deleted file mode 100644 index d01f6f8ca..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cSharpQueries.swift +++ /dev/null @@ -1,224 +0,0 @@ -// cSharpQueries.swift -// RepoPrompt -// -// Created by Eric Provencher on 2025-02-06. -// Updated to use new C# queries. -// -// Make sure no tab characters are present; use only spaces for indentation. - -import Foundation - -let csharpQuery = """ -(identifier) @variable - -;; Methods - -(method_declaration name: (identifier) @function) -(local_function_statement name: (identifier) @function) - -;; Types - -(interface_declaration name: (identifier) @type) -(class_declaration name: (identifier) @type) -(enum_declaration name: (identifier) @type) -(struct_declaration (identifier) @type) -(record_declaration (identifier) @type) -(namespace_declaration name: (identifier) @module) - -(generic_name (identifier) @type) -(type_parameter (identifier) @property.definition) -(parameter type: (identifier) @type) -(type_argument_list (identifier) @type) -(as_expression right: (identifier) @type) -(is_expression right: (identifier) @type) - -(constructor_declaration name: (identifier) @constructor) -(destructor_declaration name: (identifier) @constructor) - -(_ type: (identifier) @type) - -(base_list (identifier) @type) - -(predefined_type) @type.builtin - -;; Enum -(enum_member_declaration (identifier) @property.definition) - -;; Literals - -[ - (real_literal) - (integer_literal) -] @number - -[ - (character_literal) - (string_literal) - (raw_string_literal) - (verbatim_string_literal) - (interpolated_string_expression) - (interpolation_start) - (interpolation_quote) -] @string - -(escape_sequence) @string.escape - -[ - (boolean_literal) - (null_literal) -] @constant.builtin - -;; Comments - -(comment) @comment - -;; Tokens - -[ - ";" - "." - "," -] @punctuation.delimiter - -[ - "--" - "-" - "-=" - "&" - "&=" - "&&" - "+" - "++" - "+=" - "<" - "<=" - "<<" - "<<=" - "=" - "==" - "!" - "!=" - "=>" - ">" - ">=" - ">>" - ">>=" - ">>>" - ">>>=" - "|" - "|=" - "||" - "?" - "??" - "??=" - "^" - "^=" - "~" - "*" - "*=" - "/" - "/=" - "%" - "%=" - ":" -] @operator - -[ - "(" - ")" - "[" - "]" - "{" - "}" - (interpolation_brace) -] @punctuation.bracket - -;; Keywords - -[ - (modifier) - "this" - (implicit_type) -] @keyword - -[ - "add" - "alias" - "as" - "base" - "break" - "case" - "catch" - "checked" - "class" - "continue" - "default" - "delegate" - "do" - "else" - "enum" - "event" - "explicit" - "extern" - "finally" - "for" - "foreach" - "global" - "goto" - "if" - "implicit" - "interface" - "is" - "lock" - "namespace" - "notnull" - "operator" - "params" - "return" - "remove" - "sizeof" - "stackalloc" - "static" - "struct" - "switch" - "throw" - "try" - "typeof" - "unchecked" - "using" - "while" - "new" - "await" - "in" - "yield" - "get" - "set" - "when" - "out" - "ref" - "from" - "where" - "select" - "record" - "init" - "with" - "let" -] @keyword - -;; Attribute - -(attribute name: (identifier) @attribute) - -;; Parameters - -(parameter - name: (identifier) @variable.parameter) - -;; Type constraints - -(type_parameter_constraints_clause (identifier) @property.definition) - -;; Method calls - -(invocation_expression (member_access_expression name: (identifier) @function)) -""" diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cppQueries.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cppQueries.swift deleted file mode 100644 index a4c484825..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/cppQueries.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// cppQueries.swift -// RepoPrompt -// -// Created by Eric Provencher on 2025-02-06. -// - -// CppQueries.swift -// Make sure no tab characters are present; use only spaces for indentation. - -import Foundation - -let cppQuery = """ -; Functions - -(call_expression - function: (qualified_identifier - name: (identifier) @function)) - -(template_function - name: (identifier) @function) - -(template_method - name: (field_identifier) @function) - -(template_function - name: (identifier) @function) - -(function_declarator - declarator: (qualified_identifier - name: (identifier) @function)) - -(function_declarator - declarator: (field_identifier) @function) - -; Types - -((namespace_identifier) @type - (#match? @type "^[A-Z]")) - -(auto) @type - -; Constants - -(this) @variable.builtin -(null "nullptr" @constant) - -; Keywords - -[ - "catch" - "class" - "co_await" - "co_return" - "co_yield" - "constexpr" - "constinit" - "consteval" - "delete" - "explicit" - "final" - "friend" - "mutable" - "namespace" - "noexcept" - "new" - "override" - "private" - "protected" - "public" - "template" - "throw" - "try" - "typename" - "using" - "concept" - "requires" - "virtual" -] @keyword - -; Strings - -(raw_string_literal) @string -""" - -// New C++ Code‑Map Query modeled after the Swift version. -// This query is organized into sections similar to the Swift query, -// but with node types and names specialized for C++. - -// CppQueries.swift -// New C++ Code‑Map Query modeled after the Swift version. -// Adjusted for tree-sitter-cpp using union patterns for member variables. diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/phpQueries.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/phpQueries.swift deleted file mode 100644 index 311b258b0..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/phpQueries.swift +++ /dev/null @@ -1,164 +0,0 @@ -// -// phpQueries.swift -// RepoPrompt -// -// Created by RepoPrompt on 2025-01-09. -// - -import Foundation - -/// PHP highlight query based on tree-sitter-php's highlights.scm -let phpHighlightQuery = """ -; Keywords -"as" @keyword -"break" @keyword -"case" @keyword -"catch" @keyword -"class" @keyword -"const" @keyword -"continue" @keyword -"declare" @keyword -"default" @keyword -"do" @keyword -"echo" @keyword -"else" @keyword -"elseif" @keyword -"enddeclare" @keyword -"endforeach" @keyword -"endif" @keyword -"endswitch" @keyword -"endwhile" @keyword -"extends" @keyword -"abstract" @keyword -"final" @keyword -"finally" @keyword -"foreach" @keyword -"function" @keyword -"global" @keyword -"if" @keyword -"implements" @keyword -"include" @keyword -"include_once" @keyword -"insteadof" @keyword -"interface" @keyword -"namespace" @keyword -"new" @keyword -"private" @keyword -"protected" @keyword -"public" @keyword -"require" @keyword -"require_once" @keyword -"return" @keyword -"static" @keyword -"switch" @keyword -"throw" @keyword -"trait" @keyword -"try" @keyword -"use" @keyword -"while" @keyword -(abstract_modifier) @keyword -(final_modifier) @keyword -(readonly_modifier) @keyword -(static_modifier) @keyword -(visibility_modifier) @keyword - -; Types -(primitive_type) @type.builtin -(cast_type) @type.builtin -(named_type) @type - -; Variables -(variable_name) @variable - -; Functions -(function_definition - name: (name) @function) - -(method_declaration - name: (name) @function.method) - -; Strings -(string) @string -(encapsed_string) @string -(heredoc) @string -(heredoc_body) @string -(nowdoc_body) @string - -; Numbers -(integer) @number -(float) @number - -; Constants -(boolean) @constant.builtin -(null) @constant.builtin - -; Comments -(comment) @comment -""" - -let phpTagQuery = """ -(namespace_definition - name: (namespace_name) @name) @definition.module - -(interface_declaration - name: (name) @name) @definition.interface - -(trait_declaration - name: (name) @name) @definition.interface - -(class_declaration - name: (name) @name) @definition.class - -(class_interface_clause [(name) (qualified_name)] @name) @reference.implementation - -(property_declaration - (property_element (variable_name (name) @name))) @definition.field - -(function_definition - name: (name) @name) @definition.function - -(method_declaration - name: (name) @name) @definition.function - -(object_creation_expression - [ - (qualified_name (name) @name) - (variable_name (name) @name) - ]) @reference.class - -(function_call_expression - function: [ - (qualified_name (name) @name) - (variable_name (name)) @name - ]) @reference.call - -(scoped_call_expression - name: (name) @name) @reference.call - -(member_call_expression - name: (name) @name) @reference.call -""" - -let basicPhpQuery = """ -; === Minimal, compiler-safe PHP highlight query === -; NOTE: Each pattern is on its own line (no top-level [ ... ] lists) -; and only node names present in the embedded grammar are referenced. - -(string) @string -(encapsed_string) @string -(heredoc) @string -(heredoc_body) @string - -(boolean) @constant.builtin -(null) @constant.builtin -(integer) @number -(float) @number -(comment) @comment - -; sprinkle a few keywords so unit tests detect highlighting -"function" @keyword -"class" @keyword -"return" @keyword -"if" @keyword -"else" @keyword -""" diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/typeScript.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/typeScript.swift deleted file mode 100644 index 86c9e49f6..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/Queries/typeScript.swift +++ /dev/null @@ -1,96 +0,0 @@ -// -// typeScript.swift -// RepoPrompt -// -// Created by Eric Provencher on 2025-02-10. -// - -// -// typeScript.swift -// RepoPrompt -// -// Created by Eric Provencher on 2025-02-10. -// Contains two queries for TypeScript/TSX: -// 1) A highlight query (combining highlights.scm, locals.scm, tags.scm). -// 2) A code-map query for tracking top-level imports, classes, interfaces, functions, etc. -// Uses only spaces for indentation. -// - -import Foundation - -/// Main highlight query for TypeScript/TSX. -/// Combines the logic from highlights.scm, locals.scm, and tags.scm. -let typeScriptHighlightQuery = #""" -; ---- Types ---- -(type_identifier) @type -(predefined_type) @type.builtin - -((identifier) @type - (#match? @type "^[A-Z]")) - -(type_arguments - "<" @punctuation.bracket - ">" @punctuation.bracket) - -; ---- Variables ---- -(required_parameter (identifier) @variable.parameter) -(optional_parameter (identifier) @variable.parameter) - -; ---- Keywords ---- -[ "abstract" - "declare" - "enum" - "export" - "implements" - "interface" - "keyof" - "namespace" - "private" - "protected" - "public" - "type" - "readonly" - "override" - "satisfies" -] @keyword - -; ---- Locals (from locals.scm) ---- -(required_parameter (identifier) @local.definition) -(optional_parameter (identifier) @local.definition) - -; ---- Definitions (from tags.scm) ---- -(function_signature - name: (identifier) @definition.function) - -(method_signature - name: (property_identifier) @definition.method) - -(abstract_method_signature - name: (property_identifier) @definition.method) - -(abstract_class_declaration - name: (type_identifier) @definition.class) - -(module - name: (identifier) @definition.module) - -(interface_declaration - name: (type_identifier) @definition.interface) - -(type_annotation - (type_identifier) @reference.type) - -(new_expression - constructor: (identifier) @reference.class) -"""# - -// Code-map query for TypeScript / TSX -// Captures used by `CodeMapGenerator`: -// • @import – top-level imports -// • @export – export statements (NOTE: Currently captures full declaration body) -// • @type.class – class names (matches Swift pattern) -// • @interface – interface names -// • @type.enum – enum names -// • @function.definition – functions and methods -// • @variable.global – variables and class properties -// • @typeAlias – type-alias declarations diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/QueryResourceLoader.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/QueryResourceLoader.swift deleted file mode 100644 index bbde5bf71..000000000 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/QueryResourceLoader.swift +++ /dev/null @@ -1,33 +0,0 @@ -import Foundation - -/// Loads query `.scm` files that were copied into the binary as -/// Swift-PM resources (e.g. via `.copy("queries")` in `Package.swift`). -/// We currently only need PHP support, but the helper can be reused. -enum QueryResourceLoader { - /// Returns the *raw Data* for *queries/highlights.scm* bundled with - /// the `TreeSitterPHP` Swift-PM package, or `nil` if the file cannot - /// be found. - static func phpHighlightData() -> Data? { - let bundleNameKey = "treesitterphp" - for bundle in Bundle.allBundles + Bundle.allFrameworks { - let bundleName = bundle.bundleURL.lastPathComponent.lowercased() - if bundleName.contains(bundleNameKey), - let url = bundle.url( - forResource: "highlights", - withExtension: "scm", - subdirectory: "queries" - ) - { - return try? Data(contentsOf: url) - } - } - return nil - } - - /// Convenience wrapper that converts the raw data into `String` - /// when a textual representation is preferred by the caller. - static func phpHighlight() -> String? { - guard let data = phpHighlightData() else { return nil } - return String(data: data, encoding: .utf8) - } -} diff --git a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/SyntaxManager.swift b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/SyntaxManager.swift index 175a06819..4a41dd605 100644 --- a/Sources/RepoPrompt/Infrastructure/SyntaxParsing/SyntaxManager.swift +++ b/Sources/RepoPrompt/Infrastructure/SyntaxParsing/SyntaxManager.swift @@ -3,15 +3,13 @@ // RepoPrompt // -import Foundation import RepoPromptCodeMapCore -import SwiftTreeSitter -/// App-owned syntax highlighting and general parsing adapter. +/// App-facing façade for the shared CodeMap syntax engine. /// -/// CodeMap grammar/query identity and synchronous extraction live in -/// `RepoPromptCodeMapCore`; this type retains only highlighting caches, -/// app diagnostics, and compatibility entry points. +/// Grammar ownership, query compilation, parsing, and extraction live in +/// `RepoPromptCodeMapCore`. This façade preserves the app's stable CodeMap +/// call surface without retaining the removed syntax-highlighting pipeline. final class SyntaxManager: @unchecked Sendable { static let shared = SyntaxManager() @@ -19,100 +17,10 @@ final class SyntaxManager: @unchecked Sendable { static let parseUTF16Limit = CodeMapSyntaxEngine.parseUTF16Limit static let parseUTF8Limit = CodeMapSyntaxEngine.parseUTF8Limit - let optimizedQueries: [LanguageType: String] = [ - .swift: swiftQuery, - .js: javascriptQuery, - .c_sharp: csharpQuery, - .python: pythonQuery, - .c: cQuery, - .rust: rustQuery, - .cpp: cppQuery, - .go: goQuery, - .java: javaQuery, - .ts: typeScriptHighlightQuery, - .tsx: typeScriptHighlightQuery, - .php: basicPhpQuery, - .ruby: rubyHighlightQuery - ] - - private let languageConfigCacheLock = NSLock() - private var languageConfigs: [LanguageType: LanguageConfiguration] = [:] - - private let highlightQueryCacheLock = NSLock() - private var highlightQueryResults: [LanguageType: Result] = [:] - - private enum HighlightQueryLookupStatus { - case cached - case compiled - } - - private struct HighlightQueryLookupResult { - let query: Query - let status: HighlightQueryLookupStatus - } - - init() { - let pipelineStats = CodeMapPerfRuntime.sharedPipelineStats - let collectStartupPerf = pipelineStats != nil - var startupStats = CodeMapSyntaxStartupPerfStats() - let primeStart = collectStartupPerf ? CodeMapPerfRuntime.currentTime() : nil - - warmCache(startupStats: &startupStats, collectPerf: collectStartupPerf) - - if let primeStart { - startupStats.primeDuration += CodeMapPerfRuntime.durationSince(primeStart) - pipelineStats?.mergeSyntaxManagerStartupStats(startupStats) - } - } - var extensionToLanguage: [String: LanguageType] { CodeMapSyntaxEngine.extensionToLanguage } - func parsingOversizeReason(for content: String) -> CodeMapSyntaxOversizeReason? { - CodeMapSyntaxEngine.shared.oversizeReason(for: content) - } - - private func warmCache(startupStats: inout CodeMapSyntaxStartupPerfStats, collectPerf: Bool) { - let warmCacheStart = collectPerf ? CodeMapPerfRuntime.currentTime() : nil - defer { - if let warmCacheStart { - startupStats.warmCacheDuration += CodeMapPerfRuntime.durationSince(warmCacheStart) - } - } - - languageConfigCacheLock.withLock { - for languageType in optimizedQueries.keys.sorted() { - if collectPerf { startupStats.warmCacheLanguageCount += 1 } - if languageConfigs[languageType] == nil, - let config = createLanguageConfig( - for: languageType, - startupStats: &startupStats, - collectPerf: collectPerf - ) - { - languageConfigs[languageType] = config - } - } - } - } - - func languageConfig(forFileExtension fileExtension: String) -> LanguageConfiguration? { - guard let language = language(forFileExtension: fileExtension) else { return nil } - return languageConfig(for: language) - } - - private func languageConfig(for language: LanguageType) -> LanguageConfiguration? { - languageConfigCacheLock.withLock { - if let config = languageConfigs[language] { return config } - if let newConfig = createLanguageConfig(for: language) { - languageConfigs[language] = newConfig - return newConfig - } - return nil - } - } - func language(forFileExtension fileExtension: String) -> LanguageType? { CodeMapSyntaxEngine.shared.language(forFileExtension: fileExtension) } @@ -131,194 +39,6 @@ final class SyntaxManager: @unchecked Sendable { ) } - private func createLanguageConfig(for languageType: LanguageType) -> LanguageConfiguration? { - var startupStats = CodeMapSyntaxStartupPerfStats() - return createLanguageConfig( - for: languageType, - startupStats: &startupStats, - collectPerf: false - ) - } - - private func createLanguageConfig( - for languageType: LanguageType, - startupStats: inout CodeMapSyntaxStartupPerfStats, - collectPerf: Bool - ) -> LanguageConfiguration? { - if collectPerf { startupStats.languageConfigCreateCount += 1 } - let createStart = collectPerf ? CodeMapPerfRuntime.currentTime() : nil - defer { - if let createStart { - startupStats.languageConfigCreateDuration += CodeMapPerfRuntime.durationSince(createStart) - } - } - - let pointerStart = collectPerf ? CodeMapPerfRuntime.currentTime() : nil - let descriptor: CodeMapGrammarDescriptor - do { - descriptor = try CodeMapSyntaxEngine.shared.grammarDescriptor(for: languageType) - } catch { - if collectPerf { startupStats.languageConfigFailureCount += 1 } - print("No language pointer for \(languageType.displayName): \(error)") - return nil - } - if let pointerStart { - startupStats.languagePointerDuration += CodeMapPerfRuntime.durationSince(pointerStart) - } - - if collectPerf { startupStats.languageConfigSuccessCount += 1 } - return LanguageConfiguration( - descriptor.language, - name: descriptor.displayName, - queries: [:] - ) - } - - func parse(content: String, fileExtension: String) throws -> MutableTree? { - guard language(forFileExtension: fileExtension) != nil else { return nil } - if let reason = parsingOversizeReason(for: content) { - print("[SyntaxManager] Skipping parse for .\(fileExtension): \(reason)") - return nil - } - - guard let config = languageConfig(forFileExtension: fileExtension) else { return nil } - let parser = Parser() - try parser.setLanguage(config.language) - return parser.parse(content) - } - - func compileHighlightQuery(for languageType: LanguageType) throws { - guard let queryText = optimizedQueries[languageType], - let data = queryText.data(using: .utf8) - else { - return - } - let descriptor = try CodeMapSyntaxEngine.shared.grammarDescriptor(for: languageType) - _ = try Query(language: descriptor.language, data: data) - } - - func highlight(content: String, fileExtension: String) throws -> [NamedRange] { - guard CodeMapSyntaxEngine.exceededLineCount(in: content.utf8, limit: 5000) == nil else { - return [] - } - - guard let languageType = language(forFileExtension: fileExtension) else { return [] } - if let reason = parsingOversizeReason(for: content) { - print("[SyntaxManager] Skipping highlight parse for .\(fileExtension): \(reason)") - return [] - } - - guard let config = languageConfig(forFileExtension: fileExtension) else { return [] } - let parser = Parser() - try parser.setLanguage(config.language) - - guard let tree = parser.parse(content), - let root = tree.rootNode - else { - return [] - } - guard let highlightLookup = try highlightQuery( - for: languageType, - language: config.language - ) else { - return [] - } - - let cursor = highlightLookup.query.execute(node: root, in: tree) - return cursor.highlights() - } - - private func highlightQuery( - for languageType: LanguageType, - language: Language - ) throws -> HighlightQueryLookupResult? { - try highlightQueryCacheLock.withLock { - if let cachedResult = highlightQueryResults[languageType] { - switch cachedResult { - case let .success(query): - return HighlightQueryLookupResult(query: query, status: .cached) - case let .failure(error): - if languageType == .php || languageType == .ruby { - return nil - } - throw error - } - } - - guard let highlightQueryText = optimizedQueries[languageType], - let data = highlightQueryText.data(using: .utf8) - else { - return nil - } - - let result = Result { - try Query(language: language, data: data) - } - highlightQueryResults[languageType] = result - - switch result { - case let .success(query): - return HighlightQueryLookupResult(query: query, status: .compiled) - case let .failure(error): - print("Error creating query for \(languageType.displayName): \(error)") - if languageType == .php || languageType == .ruby { - return nil - } - throw error - } - } - } - - func codeMap(content: String, fileExtension: String) throws -> [NamedRange] { - let pipelineStats = CodeMapPerfRuntime.sharedPipelineStats - let lookupStart = pipelineStats != nil ? CodeMapPerfRuntime.currentTime() : nil - guard let language = language(forFileExtension: fileExtension) else { - if let pipelineStats { - var syntaxPerf = CodeMapSyntaxPerfStats() - syntaxPerf.calls = 1 - syntaxPerf.unsupported = 1 - if let lookupStart { - syntaxPerf.languageLookupDuration = CodeMapPerfRuntime.durationSince(lookupStart) - } - pipelineStats.mergeSyntaxCodeMapStats(syntaxPerf) - } - return [] - } - - let operationStart = pipelineStats != nil ? CodeMapPerfRuntime.currentTime() : nil - let outcome = try CodeMapSyntaxEngine.shared.codeMap(content: content, language: language) - if let pipelineStats { - var syntaxPerf = CodeMapSyntaxPerfStats() - syntaxPerf.calls = 1 - if let lookupStart { - syntaxPerf.languageLookupDuration = CodeMapPerfRuntime.durationSince(lookupStart) - } - if let operationStart { - syntaxPerf.parseDuration = CodeMapPerfRuntime.durationSince(operationStart) - } - switch outcome { - case let .captures(captures): - syntaxPerf.captures = captures.count - syntaxPerf.queryExecutes = 1 - case .oversize: - syntaxPerf.oversized = 1 - case let .parseFailed(failure): - switch failure { - case .parserReturnedNilTree: syntaxPerf.parseNilTree = 1 - case .parserReturnedNilRoot: syntaxPerf.parseNilRoot = 1 - } - } - pipelineStats.mergeSyntaxCodeMapStats(syntaxPerf) - } - - switch outcome { - case let .captures(captures): - return captures - case .oversize, .parseFailed: - return [] - } - } - func codeMap(content: String, language: LanguageType) throws -> CodeMapSyntaxQueryOutcome { try CodeMapSyntaxEngine.shared.codeMap(content: content, language: language) } diff --git a/Tests/RepoPromptTests/CodeMap/SyntaxHighlightingQueryTests.swift b/Tests/RepoPromptTests/CodeMap/SyntaxHighlightingQueryTests.swift deleted file mode 100644 index 06d5a1577..000000000 --- a/Tests/RepoPromptTests/CodeMap/SyntaxHighlightingQueryTests.swift +++ /dev/null @@ -1,19 +0,0 @@ -@testable import RepoPromptApp -@testable import RepoPromptCodeMapCore -import XCTest - -final class SyntaxHighlightingQueryTests: XCTestCase { - func testEveryRegisteredHighlightingQueryCompilesAgainstItsGrammar() throws { - let syntaxManager = SyntaxManager() - let queries = syntaxManager.optimizedQueries - - XCTAssertEqual(Set(queries.keys), Set(LanguageType.allCases)) - - for languageType in LanguageType.allCases.sorted() { - XCTAssertNoThrow( - try syntaxManager.compileHighlightQuery(for: languageType), - languageType.rawValue - ) - } - } -} diff --git a/Tests/RepoPromptTests/Diagnostics/BenchmarkDiffApplierTests.swift b/Tests/RepoPromptTests/Diagnostics/BenchmarkDiffApplierTests.swift deleted file mode 100644 index 5d6579b42..000000000 --- a/Tests/RepoPromptTests/Diagnostics/BenchmarkDiffApplierTests.swift +++ /dev/null @@ -1,60 +0,0 @@ -@testable import RepoPromptApp -import XCTest - -final class BenchmarkDiffApplierTests: XCTestCase { - func testModifyChangeAppliesGeneratedDiffChunks() async { - let original = "func greet() {\n print(\"hello\")\n}\n" - let expected = "func greet() {\n print(\"goodbye\")\n}\n" - var fileSystem = BenchmarkMockFileSystem(files: ["src/App.swift": original]) - let baseline = fileSystem.snapshot() - let parsedFile = ParsedFile( - fileName: "benchmark/src/App.swift", - changes: [ - Change( - id: UUID(), - type: .modify, - summary: "Update greeting", - isSelected: true, - content: [ - "func greet() {", - " print(\"goodbye\")", - "}" - ], - startSelector: nil, - endSelector: nil, - searchBlock: [ - "func greet() {", - " print(\"hello\")", - "}" - ] - ) - ], - fileContent: original, - canBeLoaded: true, - action: .modify, - lineEnding: "\n" - ) - let task = BenchmarkTaskSpec( - id: "benchmark-diff-applier-modify", - type: .curlyFixSwift, - language: .swift, - selectFiles: ["src/App.swift"], - maxEdits: 1, - instructions: [], - task: "Update greeting", - acceptance: [], - params: [:] - ) - - let result = await BenchmarkDiffApplier.apply( - parsedFiles: [parsedFile], - task: task, - fileSystem: &fileSystem, - baseline: baseline - ) - - XCTAssertEqual(result.errors, []) - XCTAssertEqual(result.edited, [BenchmarkEditedFile(path: "src/App.swift", content: expected)]) - XCTAssertEqual(fileSystem.content(for: "src/App.swift"), expected) - } -} From c76113ad84c5ba94e0ad8a1af20c63c4d0522151 Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Wed, 22 Jul 2026 15:09:11 -0400 Subject: [PATCH 05/11] Record Swift optimization test contracts --- Scripts/Fixtures/test-suite-contract-ledger.tsv | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Scripts/Fixtures/test-suite-contract-ledger.tsv b/Scripts/Fixtures/test-suite-contract-ledger.tsv index f09e38adc..256ec2ef4 100644 --- a/Scripts/Fixtures/test-suite-contract-ledger.tsv +++ b/Scripts/Fixtures/test-suite-contract-ledger.tsv @@ -808,6 +808,11 @@ root/RepoPromptCodeMapCoreTests.CodeMapGoldenTests/testFixturesMatchGoldenCodeMa root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testPreMaterializedSwiftAndTypeScriptGeneratorAttribution root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testPreMaterializedSwiftAndTypeScriptGeneratorAttribution CodeMap codemap.query_optimization.prematerialized_generator_attribution swift,typescript,pre_materialized_captures,generator_attribution,repeat_artifact_equality diagnostic codemap_core routine 2 Pre-materialized Swift and TypeScript captures generate an identical artifact across repeated runs and expose generator-only capture-index and capture-loop timing attribution. Generator-only optimization could change artifact output or lose a deterministic attribution surface that separates query execution from extraction cost. tree_sitter,cpu_timing,synthetic_source syntax_manager test_case retain 0 Generator-only diagnostic timings are reported without machine-dependent thresholds. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testStructuralFastPathsPreserveRoutingAndTypes root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testStructuralFastPathsPreserveRoutingAndTypes CodeMap codemap.query_optimization.structural_routing swift,typescript,tsx,query_capture_budget,duplicate_suppression regression codemap_core routine 3 Complex Swift declarations preserve bounded parameter/type extraction, while function-only TypeScript arrows are emitted only as functions and non-function initializer text containing arrows remains a global variable. Query slimming or duplicate suppression could lose Swift type detail, duplicate TypeScript arrows as globals, or suppress ordinary variables based on initializer text. 0.312000 tree_sitter,synthetic_source syntax_manager test_case retain 0 Query-first optimization correctness and attribution contract root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftParameterFallbackSkipsNestedAttributeColons root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftParameterFallbackSkipsNestedAttributeColons CodeMap codemap.query_optimization.swift_parameter_attribute_colon swift,parameter_type_fallback,property_wrapper,string_literal,nested_delimiters,default_value regression codemap_core fast 1 A Swift parameter with a property-wrapper argument containing a colon retains its local name and exact declared type while excluding the default value. The bounded fallback could treat an attribute argument colon as the parameter declaration separator and emit a malformed type. tree_sitter,synthetic_source syntax_manager test_case retain 0 Focused regression for top-level Swift parameter delimiter scanning. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftParameterTypeASCIIFastPathGeneratedEquivalenceAndCounters root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftParameterTypeASCIIFastPathGeneratedEquivalenceAndCounters CodeMap codemap.query_optimization.swift_parameter_ascii_generated_equivalence swift,parameter_type,ascii_fast_path,deterministic_generated_corpus,legacy_equivalence,counter_accounting,utf8_byte_accounting regression codemap_core fast 1 Two thousand deterministically generated ASCII parameter strings exactly match the legacy parser while every call is attributed to the ASCII fast path with exact invocation and input-byte counters. The optimized scanner could diverge on an unanticipated ASCII delimiter shape or undercount routed work and processed bytes. synthetic_source test_case retain 0 Bounded generated equivalence and fast-path attribution regression. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftParameterTypeASCIIFastPathMatchesLegacyBehavior root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftParameterTypeASCIIFastPathMatchesLegacyBehavior CodeMap codemap.query_optimization.swift_parameter_ascii_equivalence swift,parameter_type,ascii_fast_path,legacy_equivalence,nested_delimiters,string_literals,malformed_input regression codemap_core fast 30 Thirty explicit ASCII parameter shapes produce the legacy parser's exact optional type result across labels, attributes, defaults, nested delimiters, strings, comments, malformed input, and missing or empty types. The ASCII fast scanner could choose the wrong top-level colon or equals sign, accept malformed nesting, or otherwise change extracted parameter types. test_case retain 0 Explicit ASCII parameter scanner equivalence matrix. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftParameterTypeUnicodeAlwaysUsesLegacyFallback root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftParameterTypeUnicodeAlwaysUsesLegacyFallback CodeMap codemap.query_optimization.swift_parameter_unicode_fallback swift,parameter_type,unicode,legacy_fallback,legacy_equivalence,counter_accounting,utf8_byte_accounting regression codemap_core fast 6 Six non-ASCII parameter shapes exactly match legacy results, never enter the ASCII fast path, and produce exact Unicode-fallback, invocation, and input-byte accounting. Unicode identifiers, types, spacing, literals, or malformed input could be misrouted through the ASCII scanner and change extraction semantics or attribution. test_case retain 0 Unicode routing and legacy-equivalence regression. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftSignatureWhitespaceNormalizerCounters root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftSignatureWhitespaceNormalizerCounters CodeMap codemap.query_optimization.swift_signature_whitespace_counters swift,signature_whitespace,ascii_noop,ascii_rewrite,unicode_fallback,legacy_equivalence,utf8_byte_accounting instrumentation_contract codemap_core fast 3 One ASCII no-op, one ASCII rewrite, and one Unicode fallback exactly match legacy normalization while route totals and aggregate input/output UTF-8 byte counters equal the processed strings. Normalization work could be attributed to the wrong route or report incomplete byte totals, hiding fast-path regressions despite correct visible strings. test_case retain 0 Deterministic signature-normalization attribution contract. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftSignatureWhitespaceNormalizerMatchesLegacyBehavior root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftSignatureWhitespaceNormalizerMatchesLegacyBehavior CodeMap codemap.query_optimization.swift_signature_whitespace_equivalence swift,signature_whitespace,ascii,unicode,legacy_equivalence,string_literals,comments regression codemap_core fast 19 Nineteen explicit signature shapes exactly match legacy trimming and whitespace collapsing across ASCII runs, empty and unchanged text, literals, comments, closures, non-ASCII identifiers, and Unicode spaces. The optimized normalizer could change signature text by collapsing the wrong characters or mishandling empty, literal, comment, or Unicode content. test_case retain 0 Explicit signature whitespace equivalence matrix. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSyntheticSwiftAndTypeScriptAttribution root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSyntheticSwiftAndTypeScriptAttribution CodeMap codemap.query_optimization.synthetic_attribution swift,typescript,query_capture_budget,performance_attribution diagnostic codemap_core routine 2 Deterministic 200-declaration Swift and TypeScript corpora complete successfully and report parse, capture materialization, indexing, capture-loop, regex/signature, and capture-count attribution without machine-dependent assertions. Future query changes could silently increase capture amplification or reintroduce regex/signature work without a repeatable focused measurement surface. 1.760000 tree_sitter,cpu_timing,synthetic_source syntax_manager test_case retain 0 Diagnostic timings are reported only; correctness and attribution coverage remain deterministic root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testTypeScriptUncontainedMembersFallThroughBeforeExtraction root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testTypeScriptUncontainedMembersFallThroughBeforeExtraction CodeMap codemap.query_optimization.ts_uncontained_fallthrough typescript,container_routing,generic_fallback,pre_extraction_guard regression codemap_core routine 7 Every TypeScript member capture family declines strategy handling before line, signature, or type extraction when no matching class or interface boundary exists. A widened TypeScript strategy gate could consume uncontained members, attach them to unrelated containers, or perform wasted extraction before fallback. tree_sitter,synthetic_source syntax_manager test_case retain 0 Direct strategy boundary regression for method, field, and interface signature captures. root/RepoPromptTests.CodeMapGoldenTests/testSnapshotFileTreeMarksCodeMapFixtures root Tests/RepoPromptTests/CodeMap/CodeMapGoldenTests.swift RepoPromptTests.CodeMapGoldenTests testSnapshotFileTreeMarksCodeMapFixtures CodeMap unreviewed unreviewed root_swiftpm routine 1 unreviewed unreviewed 0.000500 unreviewed retain_pending_review 0 initial census source line 23 From 7789e2e553e13ff3be347a27a5c35a64d4353de4 Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Wed, 22 Jul 2026 15:54:12 -0400 Subject: [PATCH 06/11] Optimize Swift property type extraction --- .../SwiftCodeMapStrategy.swift | 281 ++++++++++++++++- .../CodeMapPerformanceCollector.swift | 10 + ...deMapQueryOptimizationBenchmarkTests.swift | 286 +++++++++++++++++- ...SwiftCodeMapPipelineBenchmarkSupport.swift | 10 + 4 files changed, 585 insertions(+), 2 deletions(-) diff --git a/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift b/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift index e5a6ff916..6e00fec6e 100644 --- a/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift +++ b/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/SwiftCodeMapStrategy.swift @@ -1271,7 +1271,84 @@ enum SwiftCodeMapStrategy { return nil } - private static func extractSwiftPropertyType(from declaration: String, perfStats: CodeMapPerformanceCollector? = nil) -> String? { + enum SwiftASCIIPropertyTypeResolution: Equatable { + case type(String) + case noType + case fallback + } + + private static let swiftPropertyModifierKeywords = [ + "private(set)", "public", "private", "internal", "fileprivate", "open", + "class", "static", "final", "lazy", "override", "mutating", "actor", "inout", + "required", "convenience", "indirect", "weak", "unowned", "dynamic", "distributed", "isolated", + ] + + static func extractSwiftPropertyType( + from declaration: String, + perfStats: CodeMapPerformanceCollector? = nil + ) -> String? { + let resolutionStart = perfStats == nil ? 0 : CFAbsoluteTimeGetCurrent() + perfStats?.swiftPropertyTypeResolutionCount += 1 + defer { + if let perfStats { + perfStats.swiftPropertyTypeResolutionDuration += + CFAbsoluteTimeGetCurrent() - resolutionStart + } + } + + var inputUTF8ByteCount = 0 + var containsNonASCII = false + for byte in declaration.utf8 { + inputUTF8ByteCount += 1 + if byte >= 0x80 { + containsNonASCII = true + } + } + perfStats?.swiftPropertyTypeInputUTF8ByteCount += inputUTF8ByteCount + + if containsNonASCII { + perfStats?.swiftPropertyTypeLegacyFallbackCount += 1 + perfStats?.swiftPropertyTypeUnicodeLegacyFallbackCount += 1 + let legacyStart = perfStats == nil ? 0 : CFAbsoluteTimeGetCurrent() + let result = extractSwiftPropertyTypeLegacy(from: declaration, perfStats: perfStats) + if let perfStats { + perfStats.swiftPropertyTypeLegacyFallbackDuration += + CFAbsoluteTimeGetCurrent() - legacyStart + } + return result + } + + let fastPathStart = perfStats == nil ? 0 : resolutionStart + let resolution = resolveSwiftASCIIPropertyType(in: declaration.utf8) + if let perfStats { + perfStats.swiftPropertyTypeASCIIFastPathDuration += + CFAbsoluteTimeGetCurrent() - fastPathStart + } + + switch resolution { + case let .type(type): + perfStats?.swiftPropertyTypeASCIIDirectTypeCount += 1 + return type + case .noType: + perfStats?.swiftPropertyTypeASCIIDirectNilCount += 1 + return nil + case .fallback: + perfStats?.swiftPropertyTypeLegacyFallbackCount += 1 + perfStats?.swiftPropertyTypeASCIIIneligibleFallbackCount += 1 + let legacyStart = perfStats == nil ? 0 : CFAbsoluteTimeGetCurrent() + let result = extractSwiftPropertyTypeLegacy(from: declaration, perfStats: perfStats) + if let perfStats { + perfStats.swiftPropertyTypeLegacyFallbackDuration += + CFAbsoluteTimeGetCurrent() - legacyStart + } + return result + } + } + + static func extractSwiftPropertyTypeLegacy( + from declaration: String, + perfStats: CodeMapPerformanceCollector? = nil + ) -> String? { if let match = LanguageTypeExtractor.matchAnyVariableLine(declaration, language: .swift, stats: perfStats), let propType = match["type"], !propType.isEmpty @@ -1281,6 +1358,208 @@ enum SwiftCodeMapStrategy { return nil } + static func resolveSwiftASCIIPropertyType( + in utf8: String.UTF8View + ) -> SwiftASCIIPropertyTypeResolution { + var index = utf8.startIndex + let end = utf8.endIndex + + for byte in utf8 where byte >= 0x80 { + return .fallback + } + if containsAmbiguousSwiftPropertyByteSequence(in: utf8) { + return .fallback + } + + if index < end, utf8[index] == 0x2D || utf8[index] == 0x2A { + index = utf8.index(after: index) + } + skipASCIIWhitespace(in: utf8, index: &index) + + while index < end, utf8[index] == 0x40 { + index = utf8.index(after: index) + guard index < end, isASCIIIdentifierStart(utf8[index]) else { return .fallback } + index = utf8.index(after: index) + while index < end, isASCIIIdentifierContinuation(utf8[index]) { + index = utf8.index(after: index) + } + if index < end, utf8[index] == 0x28 { + index = utf8.index(after: index) + while index < end, utf8[index] != 0x29 { + let byte = utf8[index] + if byte == 0x28 || !isSafeASCIIAttributeArgumentByte(byte) { + return .fallback + } + index = utf8.index(after: index) + } + guard index < end else { return .fallback } + index = utf8.index(after: index) + } + skipASCIIWhitespace(in: utf8, index: &index) + } + + while true { + if let afterKeyword = matchingASCIIKeyword("var", in: utf8, at: index), + afterKeyword < end, + isASCIIWhitespace(utf8[afterKeyword]) + { + index = afterKeyword + skipASCIIWhitespace(in: utf8, index: &index) + break + } + if let afterKeyword = matchingASCIIKeyword("let", in: utf8, at: index), + afterKeyword < end, + isASCIIWhitespace(utf8[afterKeyword]) + { + index = afterKeyword + skipASCIIWhitespace(in: utf8, index: &index) + break + } + + var matchedModifier = false + for modifier in swiftPropertyModifierKeywords { + guard let afterModifier = matchingASCIIKeyword(modifier, in: utf8, at: index), + afterModifier < end, + isASCIIWhitespace(utf8[afterModifier]) + else { continue } + index = afterModifier + skipASCIIWhitespace(in: utf8, index: &index) + matchedModifier = true + break + } + if !matchedModifier { + return .fallback + } + } + + guard index < end, isASCIIIdentifierStart(utf8[index]) else { return .fallback } + index = utf8.index(after: index) + while index < end, isASCIIIdentifierContinuation(utf8[index]) { + index = utf8.index(after: index) + } + skipASCIIWhitespace(in: utf8, index: &index) + guard index < end else { return .noType } + guard utf8[index] == 0x3A else { return .fallback } + index = utf8.index(after: index) + let afterColon = index + skipASCIIWhitespace(in: utf8, index: &index) + if index == end { + return afterColon == end ? .noType : .fallback + } + + let typeStart = index + var typeEnd = end + var delimiters: [UInt8] = [] + var sawLineBreak = false + while index < end { + let byte = utf8[index] + if sawLineBreak, !isASCIIWhitespace(byte) { + return .fallback + } + if byte == 0x0A || byte == 0x0D { + sawLineBreak = true + } + + switch byte { + case 0x28, 0x5B, 0x3C: + delimiters.append(byte) + case 0x29: + guard delimiters.last == 0x28 else { return .fallback } + delimiters.removeLast() + case 0x5D: + guard delimiters.last == 0x5B else { return .fallback } + delimiters.removeLast() + case 0x3E: + let previous = index > typeStart ? utf8[utf8.index(before: index)] : 0 + if previous != 0x2D { + guard delimiters.last == 0x3C else { return .fallback } + delimiters.removeLast() + } + case 0x3D: + guard delimiters.isEmpty else { return .fallback } + let next = utf8.index(after: index) + guard next == end || utf8[next] != 0x3D else { return .fallback } + typeEnd = index + index = end + continue + case 0x2C: + guard !delimiters.isEmpty else { return .fallback } + default: + guard isSafeASCIIPropertyTypeByte(byte) else { return .fallback } + } + index = utf8.index(after: index) + } + guard delimiters.isEmpty else { return .fallback } + + let type = String(decoding: utf8[typeStart ..< typeEnd], as: UTF8.self) + .trimmingCharacters(in: .whitespacesAndNewlines) + return type.isEmpty ? .noType : .type(type) + } + + private static func containsAmbiguousSwiftPropertyByteSequence( + in utf8: String.UTF8View + ) -> Bool { + var index = utf8.startIndex + while index < utf8.endIndex { + switch utf8[index] { + case 0x22, 0x23, 0x5C, 0x7B, 0x7D: + return true + case 0x2F: + return true + default: + index = utf8.index(after: index) + } + } + return false + } + + private static func skipASCIIWhitespace( + in utf8: String.UTF8View, + index: inout String.UTF8View.Index + ) { + while index < utf8.endIndex, isASCIIWhitespace(utf8[index]) { + index = utf8.index(after: index) + } + } + + private static func matchingASCIIKeyword( + _ keyword: String, + in utf8: String.UTF8View, + at start: String.UTF8View.Index + ) -> String.UTF8View.Index? { + var index = start + for expected in keyword.utf8 { + guard index < utf8.endIndex, utf8[index] == expected else { return nil } + index = utf8.index(after: index) + } + return index + } + + private static func isASCIIWhitespace(_ byte: UInt8) -> Bool { + byte == 0x20 || (0x09 ... 0x0D).contains(byte) + } + + private static func isASCIIIdentifierStart(_ byte: UInt8) -> Bool { + byte == 0x5F || (0x41 ... 0x5A).contains(byte) || (0x61 ... 0x7A).contains(byte) + } + + private static func isASCIIIdentifierContinuation(_ byte: UInt8) -> Bool { + isASCIIIdentifierStart(byte) || (0x30 ... 0x39).contains(byte) + } + + private static func isSafeASCIIAttributeArgumentByte(_ byte: UInt8) -> Bool { + isASCIIIdentifierContinuation(byte) || isASCIIWhitespace(byte) || [ + 0x2C, 0x2E, 0x2D, 0x2B, 0x3A, 0x3D, 0x3F, 0x21, 0x26, 0x3C, 0x3E, + 0x5B, 0x5D, + ].contains(byte) + } + + private static func isSafeASCIIPropertyTypeByte(_ byte: UInt8) -> Bool { + isASCIIIdentifierContinuation(byte) || isASCIIWhitespace(byte) || [ + 0x2E, 0x3F, 0x21, 0x3A, 0x26, 0x2D, 0x40, + ].contains(byte) + } + private static func extractSwiftPropertyDeclaration( from identifierRange: NSRange, index: CodeMapCaptureIndex, diff --git a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift index 69928cefc..0911c454b 100644 --- a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift +++ b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift @@ -104,6 +104,13 @@ package final class CodeMapPerformanceCollector { package var swiftStrategyReturnTypeExtractionCount = 0 package var swiftStrategyPropertyDeclarationCount = 0 package var swiftStrategyPropertyTypeExtractionCount = 0 + package var swiftPropertyTypeResolutionCount = 0 + package var swiftPropertyTypeASCIIDirectTypeCount = 0 + package var swiftPropertyTypeASCIIDirectNilCount = 0 + package var swiftPropertyTypeLegacyFallbackCount = 0 + package var swiftPropertyTypeUnicodeLegacyFallbackCount = 0 + package var swiftPropertyTypeASCIIIneligibleFallbackCount = 0 + package var swiftPropertyTypeInputUTF8ByteCount = 0 package var swiftStrategyEnclosingTypeLookupCount = 0 package var swiftStrategyModelInsertionCount = 0 package var swiftStrategyContextOnlyCount = 0 @@ -222,6 +229,9 @@ package final class CodeMapPerformanceCollector { package var swiftPropertyDeclarationSubstringDuration: TimeInterval = 0 package var swiftPropertyInitializerStripDuration: TimeInterval = 0 package var swiftStrategyPropertyTypeExtractionDuration: TimeInterval = 0 + package var swiftPropertyTypeResolutionDuration: TimeInterval = 0 + package var swiftPropertyTypeASCIIFastPathDuration: TimeInterval = 0 + package var swiftPropertyTypeLegacyFallbackDuration: TimeInterval = 0 package var swiftStrategyEnclosingTypeLookupDuration: TimeInterval = 0 package var swiftStrategyModelInsertionDuration: TimeInterval = 0 package var swiftStrategyContextOnlyDuration: TimeInterval = 0 diff --git a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift index ce2d99e3d..15b70543b 100644 --- a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift +++ b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift @@ -91,6 +91,41 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { collector.swiftParameterTypeResolutionDuration, collector.swiftParameterTypeLegacyFallbackDuration ) + XCTAssertEqual( + collector.swiftPropertyTypeResolutionCount, + collector.swiftStrategyPropertyTypeExtractionCount + ) + XCTAssertEqual( + collector.swiftPropertyTypeASCIIDirectTypeCount + + collector.swiftPropertyTypeASCIIDirectNilCount + + collector.swiftPropertyTypeLegacyFallbackCount, + collector.swiftPropertyTypeResolutionCount + ) + XCTAssertEqual( + collector.swiftPropertyTypeLegacyFallbackCount, + collector.swiftPropertyTypeUnicodeLegacyFallbackCount + + collector.swiftPropertyTypeASCIIIneligibleFallbackCount + ) + XCTAssertGreaterThan( + collector.swiftPropertyTypeASCIIDirectTypeCount + + collector.swiftPropertyTypeASCIIDirectNilCount, + 0 + ) + if collector.swiftPropertyTypeResolutionCount > 0 { + XCTAssertGreaterThan(collector.swiftPropertyTypeInputUTF8ByteCount, 0) + } + XCTAssertGreaterThanOrEqual( + collector.swiftPropertyTypeResolutionDuration, + collector.swiftPropertyTypeASCIIFastPathDuration + ) + XCTAssertGreaterThanOrEqual( + collector.swiftPropertyTypeResolutionDuration, + collector.swiftPropertyTypeLegacyFallbackDuration + ) + XCTAssertLessThan( + collector.swiftPropertyTypeLegacyFallbackCount, + collector.swiftPropertyTypeResolutionCount + ) let evidence = try SwiftCodeMapPipelineBenchmarkSupport.makeEvidence( files: files, @@ -465,6 +500,253 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { XCTAssertGreaterThanOrEqual(collector.swiftParameterTypeLegacyFallbackDuration, 0) } + func testSwiftPropertyTypeASCIIFastPathMatchesLegacyBehavior() { + let directCases: [(String, String, SwiftCodeMapStrategy.SwiftASCIIPropertyTypeResolution)] = [ + ("no type var", "var value", .noType), + ("no type let", "let value \t", .noType), + ("empty colon", "var value:", .noType), + ("simple", "var value: Int", .type("Int")), + ("bullet", "- let value: String", .type("String")), + ("attribute", "@Published var value: String?", .type("String?")), + ("attribute arguments", "@Option(flag: true) var value: Int!", .type("Int!")), + ("generic", "let value: Result", .type("Result")), + ("nested generic", "var value: Result<[String: (Int, Bool)], Error>", .type("Result<[String: (Int, Bool)], Error>")), + ("tuple", "var value: (Int, String)", .type("(Int, String)")), + ("function", "var handler: (@escaping (Int) -> Result)?", .type("(@escaping (Int) -> Result)?")), + ("array", "var values: [String]", .type("[String]")), + ("dictionary", "var values: [String: Int]", .type("[String: Int]")), + ("existential", "var value: any Sendable", .type("any Sendable")), + ("opaque", "var value: some Sequence", .type("some Sequence")), + ("composition", "var value: P & Q", .type("P & Q")), + ("initializer", "var value: Int = 42", .type("Int")), + ("newline after colon", "var value:\n Int", .type("Int")), + ("trailing newlines", "var value: Int\r\n", .type("Int")), + ] + + for (name, declaration, expectedResolution) in directCases { + let legacy = SwiftCodeMapStrategy.extractSwiftPropertyTypeLegacy(from: declaration) + XCTAssertEqual( + SwiftCodeMapStrategy.resolveSwiftASCIIPropertyType(in: declaration.utf8), + expectedResolution, + name + ) + XCTAssertEqual( + SwiftCodeMapStrategy.extractSwiftPropertyType(from: declaration), + legacy, + name + ) + } + + let modifiers = [ + "private(set)", "public", "private", "internal", "fileprivate", "open", + "class", "static", "final", "lazy", "override", "mutating", "actor", "inout", + "required", "convenience", "indirect", "weak", "unowned", "dynamic", "distributed", "isolated", + ] + for modifier in modifiers { + let declaration = "\(modifier) var value: Int" + XCTAssertEqual( + SwiftCodeMapStrategy.resolveSwiftASCIIPropertyType(in: declaration.utf8), + .type("Int"), + modifier + ) + XCTAssertEqual( + SwiftCodeMapStrategy.extractSwiftPropertyType(from: declaration), + SwiftCodeMapStrategy.extractSwiftPropertyTypeLegacy(from: declaration), + modifier + ) + } + + let fallbackCases = [ + "var value: \t\r", + "@Outer(inner(value)) var value: Int", + "@Broken( var value: Int", + "unknown var value: Int", + "var value /* comment */: Int", + "var value: /* comment */ Int", + "var value: Int // comment", + "var value: Int, other: String", + "var value: Result
", + "var value: (A = B)", + "var value: Int == 42", + "var value: Int { get }", + "var value: Int = \"text\"", + "var value: Result", "[String: Int]", + "(Int, String)", "(Int) -> Void", "any Sendable", "P & Q", + ] + let caseCount = 2_000 + var state: UInt64 = 0x6006_5A17_C0DE_0060 + var totalUTF8ByteCount = 0 + let collector = CodeMapPerformanceCollector() + + func nextRandom() -> UInt64 { + state = state &* 6_364_136_223_846_793_005 &+ 1_442_695_040_888_963_407 + return state + } + + for caseIndex in 0 ..< caseCount { + let input: String + if caseIndex.isMultiple(of: 4) { + let keyword = caseIndex.isMultiple(of: 8) ? "var" : "let" + let type = directTypes[Int(nextRandom() % UInt64(directTypes.count))] + input = "\(keyword) value\(caseIndex): \(type)" + } else { + let length = Int(nextRandom() % 161) + var bytes: [UInt8] = [] + bytes.reserveCapacity(length) + for _ in 0 ..< length { + bytes.append(alphabet[Int(nextRandom() % UInt64(alphabet.count))]) + } + input = String(decoding: bytes, as: UTF8.self) + } + totalUTF8ByteCount += input.utf8.count + + let legacy = SwiftCodeMapStrategy.extractSwiftPropertyTypeLegacy(from: input) + let direct = SwiftCodeMapStrategy.resolveSwiftASCIIPropertyType(in: input.utf8) + switch direct { + case let .type(type): + XCTAssertEqual(type, legacy, "generated direct type \(caseIndex)") + case .noType: + XCTAssertNil(legacy, "generated direct nil \(caseIndex)") + case .fallback: + break + } + XCTAssertEqual( + SwiftCodeMapStrategy.extractSwiftPropertyType(from: input, perfStats: collector), + legacy, + "generated case \(caseIndex): \(String(reflecting: input))" + ) + } + + XCTAssertEqual(collector.swiftPropertyTypeResolutionCount, caseCount) + XCTAssertGreaterThan( + collector.swiftPropertyTypeASCIIDirectTypeCount + + collector.swiftPropertyTypeASCIIDirectNilCount, + 0 + ) + XCTAssertEqual( + collector.swiftPropertyTypeASCIIDirectTypeCount + + collector.swiftPropertyTypeASCIIDirectNilCount + + collector.swiftPropertyTypeLegacyFallbackCount, + caseCount + ) + XCTAssertEqual( + collector.swiftPropertyTypeLegacyFallbackCount, + collector.swiftPropertyTypeASCIIIneligibleFallbackCount + ) + XCTAssertEqual(collector.swiftPropertyTypeUnicodeLegacyFallbackCount, 0) + XCTAssertEqual(collector.swiftPropertyTypeInputUTF8ByteCount, totalUTF8ByteCount) + XCTAssertGreaterThanOrEqual( + collector.swiftPropertyTypeResolutionDuration, + collector.swiftPropertyTypeASCIIFastPathDuration + ) + XCTAssertGreaterThanOrEqual( + collector.swiftPropertyTypeResolutionDuration, + collector.swiftPropertyTypeLegacyFallbackDuration + ) + } + + func testSwiftPropertyTypeUnicodeAlwaysUsesLegacyFallback() { + let declarations = [ + "var café: Type", + "var value: Café", + "var value:\u{00A0}Type", + "var value:\u{2003}Type", + "@Wrapper(label: \"🙂\") var value: Int", + "var value: String = \"🙂\"", + "var mixed: Result", + ] + let collector = CodeMapPerformanceCollector() + + for declaration in declarations { + XCTAssertEqual( + SwiftCodeMapStrategy.resolveSwiftASCIIPropertyType(in: declaration.utf8), + .fallback + ) + XCTAssertEqual( + SwiftCodeMapStrategy.extractSwiftPropertyType(from: declaration, perfStats: collector), + SwiftCodeMapStrategy.extractSwiftPropertyTypeLegacy(from: declaration), + String(reflecting: declaration) + ) + } + + XCTAssertEqual(collector.swiftPropertyTypeResolutionCount, declarations.count) + XCTAssertEqual(collector.swiftPropertyTypeLegacyFallbackCount, declarations.count) + XCTAssertEqual(collector.swiftPropertyTypeUnicodeLegacyFallbackCount, declarations.count) + XCTAssertEqual(collector.swiftPropertyTypeASCIIIneligibleFallbackCount, 0) + XCTAssertEqual(collector.swiftPropertyTypeASCIIDirectTypeCount, 0) + XCTAssertEqual(collector.swiftPropertyTypeASCIIDirectNilCount, 0) + XCTAssertEqual( + collector.swiftPropertyTypeInputUTF8ByteCount, + declarations.reduce(0) { $0 + $1.utf8.count } + ) + XCTAssertGreaterThanOrEqual( + collector.swiftPropertyTypeResolutionDuration, + collector.swiftPropertyTypeLegacyFallbackDuration + ) + } + + func testSwiftPropertyTypePipelineRoutesTopLevelMemberProtocolAndComputedDeclarations() throws { + let source = """ + let globalValue: Int = 1 + struct Example { + private(set) var memberValue: Result = .success(\"ok\") + var computedValue: [String: Int] { [:] } + } + protocol Shape { + var protocolValue: any Sendable { get } + } + """ + let collector = CodeMapPerformanceCollector() + let artifact = try build( + source: source, + language: .swift, + options: .countersOnly, + collector: collector + ) + + let global = try XCTUnwrap(artifact.globalVars.first { $0.name.contains("globalValue") }) + XCTAssertEqual(global.typeName, "Int") + let example = try XCTUnwrap(artifact.classes.first { $0.name == "Example" }) + XCTAssertEqual(example.properties.map(\.typeName), ["Result", "[String: Int]"]) + XCTAssertTrue(example.properties[1].name.contains("computedValue")) + XCTAssertFalse(example.properties[1].name.contains("{")) + let shape = try XCTUnwrap(artifact.interfaces.first { $0.name == "Shape" }) + XCTAssertEqual(shape.properties.map(\.typeName), ["any Sendable"]) + + XCTAssertEqual(collector.swiftPropertyTypeResolutionCount, 4) + XCTAssertEqual(collector.swiftPropertyTypeASCIIDirectTypeCount, 4) + XCTAssertEqual(collector.swiftPropertyTypeASCIIDirectNilCount, 0) + XCTAssertEqual(collector.swiftPropertyTypeLegacyFallbackCount, 0) + XCTAssertEqual(collector.lteMatchAnyVariableCalls, 0) + XCTAssertGreaterThan(collector.swiftPropertyTypeInputUTF8ByteCount, 0) + } + func testCaptureIndexCountsMissingNamedBuckets() { let collector = CodeMapPerformanceCollector() let index = CodeMapCaptureIndex([], performanceCollector: collector) @@ -638,7 +920,9 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { ) XCTAssertEqual(swiftCollector.syntaxCaptureCountsByName["swift.param.type", default: 0], 0) XCTAssertEqual(swiftCollector.syntaxCaptureCountsByName["function.definition", default: 0], 0) - XCTAssertEqual(swiftCollector.lteMatchAnyVariableCalls, 1) + XCTAssertEqual(swiftCollector.swiftPropertyTypeASCIIDirectTypeCount, 1) + XCTAssertEqual(swiftCollector.swiftPropertyTypeLegacyFallbackCount, 0) + XCTAssertEqual(swiftCollector.lteMatchAnyVariableCalls, 0) let tsCollector = CodeMapPerformanceCollector(collectsCaptureNames: true) let tsSource = """ diff --git a/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift index aaa07775a..dc1a470a7 100644 --- a/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift +++ b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift @@ -430,6 +430,9 @@ "swift_property_substring_ms=\(milliseconds(collector.swiftPropertyDeclarationSubstringDuration))", "swift_property_initializer_ms=\(milliseconds(collector.swiftPropertyInitializerStripDuration))", "swift_property_type_ms=\(milliseconds(collector.swiftStrategyPropertyTypeExtractionDuration))", + "swift_property_type_resolution_ms=\(milliseconds(collector.swiftPropertyTypeResolutionDuration))", + "swift_property_type_ascii_fast_path_ms=\(milliseconds(collector.swiftPropertyTypeASCIIFastPathDuration))", + "swift_property_type_legacy_fallback_ms=\(milliseconds(collector.swiftPropertyTypeLegacyFallbackDuration))", "swift_enclosing_type_ms=\(milliseconds(collector.swiftStrategyEnclosingTypeLookupDuration))", "swift_model_insert_ms=\(milliseconds(collector.swiftStrategyModelInsertionDuration))", "swift_context_only_ms=\(milliseconds(collector.swiftStrategyContextOnlyDuration))", @@ -510,6 +513,13 @@ "swift_return_type_count=\(collector.swiftStrategyReturnTypeExtractionCount)", "swift_property_declaration_count=\(collector.swiftStrategyPropertyDeclarationCount)", "swift_property_type_count=\(collector.swiftStrategyPropertyTypeExtractionCount)", + "swift_property_type_resolution_count=\(collector.swiftPropertyTypeResolutionCount)", + "swift_property_type_ascii_direct_type_count=\(collector.swiftPropertyTypeASCIIDirectTypeCount)", + "swift_property_type_ascii_direct_nil_count=\(collector.swiftPropertyTypeASCIIDirectNilCount)", + "swift_property_type_legacy_fallback_count=\(collector.swiftPropertyTypeLegacyFallbackCount)", + "swift_property_type_unicode_legacy_fallback_count=\(collector.swiftPropertyTypeUnicodeLegacyFallbackCount)", + "swift_property_type_ascii_ineligible_fallback_count=\(collector.swiftPropertyTypeASCIIIneligibleFallbackCount)", + "swift_property_type_input_utf8_bytes=\(collector.swiftPropertyTypeInputUTF8ByteCount)", "swift_enclosing_type_count=\(collector.swiftStrategyEnclosingTypeLookupCount)", "swift_model_insertion_count=\(collector.swiftStrategyModelInsertionCount)", "swift_context_only_count=\(collector.swiftStrategyContextOnlyCount)", From d76d44edb027cd7e1cfe5830b74c54b8b5d8b773 Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Wed, 22 Jul 2026 16:03:51 -0400 Subject: [PATCH 07/11] Record Swift property optimization tests --- Scripts/Fixtures/test-suite-contract-ledger.tsv | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Scripts/Fixtures/test-suite-contract-ledger.tsv b/Scripts/Fixtures/test-suite-contract-ledger.tsv index 256ec2ef4..d2710395a 100644 --- a/Scripts/Fixtures/test-suite-contract-ledger.tsv +++ b/Scripts/Fixtures/test-suite-contract-ledger.tsv @@ -811,6 +811,10 @@ root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwift root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftParameterTypeASCIIFastPathGeneratedEquivalenceAndCounters root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftParameterTypeASCIIFastPathGeneratedEquivalenceAndCounters CodeMap codemap.query_optimization.swift_parameter_ascii_generated_equivalence swift,parameter_type,ascii_fast_path,deterministic_generated_corpus,legacy_equivalence,counter_accounting,utf8_byte_accounting regression codemap_core fast 1 Two thousand deterministically generated ASCII parameter strings exactly match the legacy parser while every call is attributed to the ASCII fast path with exact invocation and input-byte counters. The optimized scanner could diverge on an unanticipated ASCII delimiter shape or undercount routed work and processed bytes. synthetic_source test_case retain 0 Bounded generated equivalence and fast-path attribution regression. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftParameterTypeASCIIFastPathMatchesLegacyBehavior root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftParameterTypeASCIIFastPathMatchesLegacyBehavior CodeMap codemap.query_optimization.swift_parameter_ascii_equivalence swift,parameter_type,ascii_fast_path,legacy_equivalence,nested_delimiters,string_literals,malformed_input regression codemap_core fast 30 Thirty explicit ASCII parameter shapes produce the legacy parser's exact optional type result across labels, attributes, defaults, nested delimiters, strings, comments, malformed input, and missing or empty types. The ASCII fast scanner could choose the wrong top-level colon or equals sign, accept malformed nesting, or otherwise change extracted parameter types. test_case retain 0 Explicit ASCII parameter scanner equivalence matrix. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftParameterTypeUnicodeAlwaysUsesLegacyFallback root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftParameterTypeUnicodeAlwaysUsesLegacyFallback CodeMap codemap.query_optimization.swift_parameter_unicode_fallback swift,parameter_type,unicode,legacy_fallback,legacy_equivalence,counter_accounting,utf8_byte_accounting regression codemap_core fast 6 Six non-ASCII parameter shapes exactly match legacy results, never enter the ASCII fast path, and produce exact Unicode-fallback, invocation, and input-byte accounting. Unicode identifiers, types, spacing, literals, or malformed input could be misrouted through the ASCII scanner and change extraction semantics or attribution. test_case retain 0 Unicode routing and legacy-equivalence regression. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftPropertyTypeASCIIFastPathMatchesLegacyBehavior root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftPropertyTypeASCIIFastPathMatchesLegacyBehavior CodeMap codemap.query_optimization.swift_property_ascii_equivalence swift,property_type,ascii_fast_path,legacy_equivalence,modifiers,attributes,nested_delimiters,malformed_input regression codemap_core fast 57 Nineteen direct declarations, twenty-two modifier-prefixed declarations, and sixteen fallback declarations preserve the legacy parser's exact optional property type while the ASCII resolver selects the expected direct or fallback route. The ASCII property scanner could mis-handle declaration modifiers, attributes, nested delimiters, initializers, comments, malformed input, or missing types and change extracted property types. test_case retain 0 Run 006 explicit ASCII property scanner equivalence matrix. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftPropertyTypeGeneratedASCIIEquivalenceAndCounters root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftPropertyTypeGeneratedASCIIEquivalenceAndCounters CodeMap codemap.query_optimization.swift_property_ascii_generated_equivalence swift,property_type,ascii_fast_path,deterministic_generated_corpus,legacy_equivalence,counter_accounting,utf8_byte_accounting regression codemap_core fast 1 Two thousand deterministically generated ASCII property strings exactly match the legacy parser while direct and fallback route totals, invocation count, and input-byte accounting remain exact. The optimized scanner could diverge on an unanticipated ASCII declaration shape or undercount routed work and processed bytes. synthetic_source test_case retain 0 Run 006 bounded generated equivalence and route-attribution regression. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftPropertyTypePipelineRoutesTopLevelMemberProtocolAndComputedDeclarations root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftPropertyTypePipelineRoutesTopLevelMemberProtocolAndComputedDeclarations CodeMap codemap.query_optimization.swift_property_pipeline_routing swift,property_type,pipeline_routing,top_level,member,computed_property,protocol_requirement,counter_accounting regression codemap_core routine 4 A single Swift artifact preserves exact types for top-level, stored-member, computed-member, and protocol-requirement properties while routing all four through the ASCII property path with no legacy or LTE regex fallback. Pipeline routing could omit or corrupt a property type, retain computed accessor text in the name, or silently bypass the optimized property scanner for a declaration context. tree_sitter,synthetic_source syntax_manager test_case retain 0 Run 006 end-to-end property declaration-context routing regression. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftPropertyTypeUnicodeAlwaysUsesLegacyFallback root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftPropertyTypeUnicodeAlwaysUsesLegacyFallback CodeMap codemap.query_optimization.swift_property_unicode_fallback swift,property_type,unicode,legacy_fallback,legacy_equivalence,counter_accounting,utf8_byte_accounting regression codemap_core fast 7 Seven non-ASCII property declarations exactly match legacy results, never take an ASCII direct route, and produce exact Unicode-fallback, invocation, and input-byte accounting. Unicode identifiers, types, spacing, attributes, or literals could be misrouted through the ASCII scanner and change extraction semantics or attribution. test_case retain 0 Run 006 Unicode routing and legacy-equivalence regression. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftSignatureWhitespaceNormalizerCounters root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftSignatureWhitespaceNormalizerCounters CodeMap codemap.query_optimization.swift_signature_whitespace_counters swift,signature_whitespace,ascii_noop,ascii_rewrite,unicode_fallback,legacy_equivalence,utf8_byte_accounting instrumentation_contract codemap_core fast 3 One ASCII no-op, one ASCII rewrite, and one Unicode fallback exactly match legacy normalization while route totals and aggregate input/output UTF-8 byte counters equal the processed strings. Normalization work could be attributed to the wrong route or report incomplete byte totals, hiding fast-path regressions despite correct visible strings. test_case retain 0 Deterministic signature-normalization attribution contract. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftSignatureWhitespaceNormalizerMatchesLegacyBehavior root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftSignatureWhitespaceNormalizerMatchesLegacyBehavior CodeMap codemap.query_optimization.swift_signature_whitespace_equivalence swift,signature_whitespace,ascii,unicode,legacy_equivalence,string_literals,comments regression codemap_core fast 19 Nineteen explicit signature shapes exactly match legacy trimming and whitespace collapsing across ASCII runs, empty and unchanged text, literals, comments, closures, non-ASCII identifiers, and Unicode spaces. The optimized normalizer could change signature text by collapsing the wrong characters or mishandling empty, literal, comment, or Unicode content. test_case retain 0 Explicit signature whitespace equivalence matrix. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSyntheticSwiftAndTypeScriptAttribution root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSyntheticSwiftAndTypeScriptAttribution CodeMap codemap.query_optimization.synthetic_attribution swift,typescript,query_capture_budget,performance_attribution diagnostic codemap_core routine 2 Deterministic 200-declaration Swift and TypeScript corpora complete successfully and report parse, capture materialization, indexing, capture-loop, regex/signature, and capture-count attribution without machine-dependent assertions. Future query changes could silently increase capture amplification or reintroduce regex/signature work without a repeatable focused measurement surface. 1.760000 tree_sitter,cpu_timing,synthetic_source syntax_manager test_case retain 0 Diagnostic timings are reported only; correctness and attribution coverage remain deterministic From 4e5a6bfa3f172903ad0597d5e67188132f2de1bd Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Wed, 22 Jul 2026 16:30:58 -0400 Subject: [PATCH 08/11] Deduplicate Swift referenced type inputs --- .../Features/CodeMap/CodeMapPerfStats.swift | 10 + .../ReferencedTypesAccumulator.swift | 22 ++ .../CodeMapPerformanceCollector.swift | 5 + ...deMapQueryOptimizationBenchmarkTests.swift | 241 ++++++++++++++++++ ...SwiftCodeMapPipelineBenchmarkSupport.swift | 5 + 5 files changed, 283 insertions(+) diff --git a/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift b/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift index 0f64c106a..d3ea1e5a2 100644 --- a/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift +++ b/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift @@ -98,6 +98,7 @@ struct CodeMapPipelinePerfSnapshot: Equatable { var generatorTypeCleanerTSObjectLiteralDuration: TimeInterval = 0 var generatorTypeCleanerFilterDuration: TimeInterval = 0 var generatorTypeCleanerDedupDuration: TimeInterval = 0 + var generatorReferencedTypesSwiftRawTypeDedupDuration: TimeInterval = 0 var generatorReferencedTypesFinalizeDuration: TimeInterval = 0 var generatorFileAPIInitDuration: TimeInterval = 0 @@ -179,6 +180,10 @@ struct CodeMapPipelinePerfSnapshot: Equatable { var typeCleanerDedupCount = 0 var referencedTypesRawInsertions = 0 var referencedTypesPrefilterSkips = 0 + var referencedTypesSwiftDedupEligibleCount = 0 + var referencedTypesSwiftFirstSeenCount = 0 + var referencedTypesSwiftDuplicateSkipCount = 0 + var referencedTypesSwiftDuplicateSkippedUTF8ByteCount = 0 var referencedTypesEmptyResults = 0 var referencedTypesOutputTypeCount = 0 var extractionMemoJSTSHits = 0 @@ -322,6 +327,7 @@ final class CodeMapPipelinePerfStats: @unchecked Sendable { storage.generatorTypeCleanerTSObjectLiteralDuration += stats.typeCleanerTSObjectLiteralDuration storage.generatorTypeCleanerFilterDuration += stats.typeCleanerFilterDuration storage.generatorTypeCleanerDedupDuration += stats.typeCleanerDedupDuration + storage.generatorReferencedTypesSwiftRawTypeDedupDuration += stats.referencedTypesSwiftRawTypeDedupDuration storage.generatorReferencedTypesFinalizeDuration += stats.referencedTypesFinalizeDuration storage.generatorFileAPIInitDuration += stats.fileAPIInitDuration @@ -386,6 +392,10 @@ final class CodeMapPipelinePerfStats: @unchecked Sendable { storage.typeCleanerDedupCount += stats.typeCleanerDedupCount storage.referencedTypesRawInsertions += stats.referencedTypesRawInsertions storage.referencedTypesPrefilterSkips += stats.referencedTypesPrefilterSkips + storage.referencedTypesSwiftDedupEligibleCount += stats.referencedTypesSwiftDedupEligibleCount + storage.referencedTypesSwiftFirstSeenCount += stats.referencedTypesSwiftFirstSeenCount + storage.referencedTypesSwiftDuplicateSkipCount += stats.referencedTypesSwiftDuplicateSkipCount + storage.referencedTypesSwiftDuplicateSkippedUTF8ByteCount += stats.referencedTypesSwiftDuplicateSkippedUTF8ByteCount storage.referencedTypesEmptyResults += stats.referencedTypesEmptyResults storage.referencedTypesOutputTypeCount += stats.referencedTypesOutputTypeCount storage.extractionMemoJSTSHits += stats.extractionMemoJSTSHits diff --git a/Sources/RepoPromptCodeMapCore/Extraction/ReferencedTypesAccumulator.swift b/Sources/RepoPromptCodeMapCore/Extraction/ReferencedTypesAccumulator.swift index 5abb00917..2a3431a6e 100644 --- a/Sources/RepoPromptCodeMapCore/Extraction/ReferencedTypesAccumulator.swift +++ b/Sources/RepoPromptCodeMapCore/Extraction/ReferencedTypesAccumulator.swift @@ -11,6 +11,7 @@ struct ReferencedTypesAccumulator { let language: LanguageType private(set) var types: Set = [] private var cache: [TypeCleaner.TypeCleanerCacheKey: [String]] = [:] + private var seenSwiftRawTypes: Set = [] private var rawInsertCount = 0 private let stats: CodeMapPerformanceCollector? private let perfOptions: CodeMapPerfOptions @@ -38,6 +39,27 @@ struct ReferencedTypesAccumulator { } return } + if language == .swift { + let dedupStart = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 + if perfCollectCounters { + activeStats?.referencedTypesSwiftDedupEligibleCount += 1 + } + let inserted = seenSwiftRawTypes.insert(raw).inserted + if perfCollectCounters { + if inserted { + activeStats?.referencedTypesSwiftFirstSeenCount += 1 + } else { + activeStats?.referencedTypesSwiftDuplicateSkipCount += 1 + activeStats?.referencedTypesSwiftDuplicateSkippedUTF8ByteCount += raw.utf8.count + } + } + if perfEnabled { + activeStats?.referencedTypesSwiftRawTypeDedupDuration += CFAbsoluteTimeGetCurrent() - dedupStart + } + if !inserted { + return + } + } let start = perfEnabled ? CFAbsoluteTimeGetCurrent() : 0 let extracted = TypeCleaner.extractBaseTypes(from: raw, language: language, cache: &cache, stats: activeStats) if perfEnabled { diff --git a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift index 0911c454b..23897b52a 100644 --- a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift +++ b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift @@ -184,6 +184,10 @@ package final class CodeMapPerformanceCollector { package var typeCleanerDedupCount = 0 package var referencedTypesRawInsertions = 0 package var referencedTypesPrefilterSkips = 0 + package var referencedTypesSwiftDedupEligibleCount = 0 + package var referencedTypesSwiftFirstSeenCount = 0 + package var referencedTypesSwiftDuplicateSkipCount = 0 + package var referencedTypesSwiftDuplicateSkippedUTF8ByteCount = 0 package var referencedTypesEmptyResults = 0 package var referencedTypesOutputTypeCount = 0 package var referencedTypesUniqueCount = 0 @@ -260,6 +264,7 @@ package final class CodeMapPerformanceCollector { package var typeCleanerTSObjectLiteralDuration: TimeInterval = 0 package var typeCleanerFilterDuration: TimeInterval = 0 package var typeCleanerDedupDuration: TimeInterval = 0 + package var referencedTypesSwiftRawTypeDedupDuration: TimeInterval = 0 package var referencedTypesFinalizeDuration: TimeInterval = 0 package var artifactFinalizationDuration: TimeInterval = 0 package var artifactMeaningfulContentCheckDuration: TimeInterval = 0 diff --git a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift index 15b70543b..bfb5b5737 100644 --- a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift +++ b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift @@ -126,6 +126,32 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { collector.swiftPropertyTypeLegacyFallbackCount, collector.swiftPropertyTypeResolutionCount ) + XCTAssertGreaterThan(collector.referencedTypesSwiftDedupEligibleCount, 0) + XCTAssertGreaterThan(collector.referencedTypesSwiftFirstSeenCount, 0) + XCTAssertGreaterThan(collector.referencedTypesSwiftDuplicateSkipCount, 0) + XCTAssertEqual( + collector.referencedTypesSwiftFirstSeenCount + + collector.referencedTypesSwiftDuplicateSkipCount, + collector.referencedTypesSwiftDedupEligibleCount + ) + XCTAssertGreaterThan(collector.referencedTypesSwiftDuplicateSkippedUTF8ByteCount, 0) + XCTAssertGreaterThanOrEqual(collector.referencedTypesSwiftRawTypeDedupDuration, 0) + XCTAssertLessThanOrEqual( + collector.referencedTypesSwiftRawTypeDedupDuration, + collector.builderGeneratorDuration + ) + XCTAssertLessThanOrEqual( + collector.referencedTypesSwiftRawTypeDedupDuration, + collector.builderTotalDuration + ) + XCTAssertEqual( + collector.typeCleanerExtractCalls, + collector.typeCleanerCacheHits + collector.typeCleanerCacheMisses + ) + XCTAssertLessThan(collector.typeCleanerExtractCalls, 1_497) + XCTAssertLessThan(collector.typeCleanerCacheHits, 759) + XCTAssertEqual(collector.typeCleanerCacheMisses, 738) + XCTAssertEqual(collector.typeCleanerSwiftCalls, 738) let evidence = try SwiftCodeMapPipelineBenchmarkSupport.makeEvidence( files: files, @@ -747,6 +773,199 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { XCTAssertGreaterThan(collector.swiftPropertyTypeInputUTF8ByteCount, 0) } + func testSwiftReferencedTypesDedupMatchesFreshInsertionAuthority() { + let sequences: [(name: String, rawTypes: [String?])] = [ + ("exact duplicate", ["Result", "Result"]), + ("trimmed duplicate", [" Result\n", "Result"]), + ("case-sensitive", ["Widget", "widget", "Widget"]), + ("complex generics", [ + "Outer, Result>", + "Outer, Result>", + ]), + ("functions", [ + "(Input, @escaping (Nested) -> Output) async throws -> Result", + "(Input, @escaping (Nested) -> Output) async throws -> Result", + ]), + ("tuples arrays dictionaries", [ + "(Left, Right)", + "[Element]", + "[Key: Value]", + "(Left, Right)", + ]), + ("compositions opaque existential", [ + "FirstProtocol & SecondProtocol", + "some Shape", + "any Shape", + "FirstProtocol & SecondProtocol", + ]), + ("unicode", [ + "Résultat<Élément, Ошибка>", + "Résultat<Élément, Ошибка>", + "résultat<Élément, Ошибка>", + ]), + ("prefilter and empty", [nil, "", " \n\t", "Int", "Void", "Array", "Payload", "Payload"]), + ] + + for sequence in sequences { + assertSwiftReferencedTypesSequenceMatchesFreshAuthority( + sequence.rawTypes, + name: sequence.name + ) + } + + let ordered: [String?] = [ + "Result", + "(Input) -> Output", + "[Key: Value]", + "FirstProtocol & SecondProtocol", + "some Shape", + "any Shape", + "Résultat<Élément>", + "Result", + ] + assertSwiftReferencedTypesSequenceMatchesFreshAuthority(ordered, name: "forward order") + assertSwiftReferencedTypesSequenceMatchesFreshAuthority(Array(ordered.reversed()), name: "reverse order") + + let manyRawTypes = ordered.compactMap { $0 } + var individual = ReferencedTypesAccumulator(language: .swift) + for rawType in manyRawTypes { + individual.insert(rawType: rawType) + } + var many = ReferencedTypesAccumulator(language: .swift) + many.insertMany(rawTypes: manyRawTypes) + XCTAssertEqual(many.types, individual.types) + XCTAssertEqual(many.finalizeSorted(), individual.finalizeSorted()) + } + + func testSwiftReferencedTypesDedupPreservesFirstSeenBehavior() { + let raw = "Result" + let expected = Set(TypeCleaner.extractBaseTypes(from: raw, language: .swift) + .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } + .filter { !$0.isEmpty }) + let collector = CodeMapPerformanceCollector() + var accumulator = ReferencedTypesAccumulator( + language: .swift, + stats: collector, + perfOptions: .countersOnly + ) + + accumulator.insert(rawType: raw) + + XCTAssertEqual(accumulator.types, expected) + XCTAssertEqual(accumulator.finalizeSorted(), expected.sorted()) + XCTAssertEqual(collector.referencedTypesRawInsertions, 1) + XCTAssertEqual(collector.referencedTypesSwiftDedupEligibleCount, 1) + XCTAssertEqual(collector.referencedTypesSwiftFirstSeenCount, 1) + XCTAssertEqual(collector.referencedTypesSwiftDuplicateSkipCount, 0) + XCTAssertEqual(collector.referencedTypesSwiftDuplicateSkippedUTF8ByteCount, 0) + XCTAssertGreaterThan(collector.typeCleanerExtractCalls, 0) + XCTAssertEqual( + collector.typeCleanerExtractCalls, + collector.typeCleanerCacheHits + collector.typeCleanerCacheMisses + ) + XCTAssertEqual(collector.referencedTypesEmptyResults, expected.isEmpty ? 1 : 0) + XCTAssertEqual(collector.referencedTypesOutputTypeCount, expected.count) + XCTAssertGreaterThanOrEqual(collector.referencedTypesSwiftRawTypeDedupDuration, 0) + } + + func testSwiftReferencedTypesDedupSkipsExactTrimmedDuplicates() { + let raw = "Result" + let collector = CodeMapPerformanceCollector() + var accumulator = ReferencedTypesAccumulator( + language: .swift, + stats: collector, + perfOptions: .countersOnly + ) + accumulator.insert(rawType: raw) + let firstTypes = accumulator.types + let firstCleanerCalls = collector.typeCleanerExtractCalls + let firstCacheHits = collector.typeCleanerCacheHits + let firstCacheMisses = collector.typeCleanerCacheMisses + let firstSwiftCalls = collector.typeCleanerSwiftCalls + let firstEmptyResults = collector.referencedTypesEmptyResults + let firstOutputTypes = collector.referencedTypesOutputTypeCount + let firstDedupDuration = collector.referencedTypesSwiftRawTypeDedupDuration + + accumulator.insert(rawType: " \(raw)\n") + + XCTAssertEqual(accumulator.types, firstTypes) + XCTAssertEqual(accumulator.finalizeSorted(), firstTypes.sorted()) + XCTAssertEqual(collector.referencedTypesRawInsertions, 2) + XCTAssertEqual(collector.referencedTypesSwiftDedupEligibleCount, 2) + XCTAssertEqual(collector.referencedTypesSwiftFirstSeenCount, 1) + XCTAssertEqual(collector.referencedTypesSwiftDuplicateSkipCount, 1) + XCTAssertEqual(collector.referencedTypesSwiftDuplicateSkippedUTF8ByteCount, raw.utf8.count) + XCTAssertEqual(collector.typeCleanerExtractCalls, firstCleanerCalls) + XCTAssertEqual(collector.typeCleanerCacheHits, firstCacheHits) + XCTAssertEqual(collector.typeCleanerCacheMisses, firstCacheMisses) + XCTAssertEqual(collector.typeCleanerSwiftCalls, firstSwiftCalls) + XCTAssertEqual(collector.referencedTypesEmptyResults, firstEmptyResults) + XCTAssertEqual(collector.referencedTypesOutputTypeCount, firstOutputTypes) + XCTAssertGreaterThanOrEqual( + collector.referencedTypesSwiftRawTypeDedupDuration, + firstDedupDuration + ) + } + + func testSwiftReferencedTypesPrefilterRunsBeforeDedup() { + let collector = CodeMapPerformanceCollector() + var accumulator = ReferencedTypesAccumulator( + language: .swift, + stats: collector, + perfOptions: .countersOnly + ) + + let rawTypes: [String?] = ["Int", "Int", "Void", "Array", nil, "", " \n\t"] + for rawType in rawTypes { + accumulator.insert(rawType: rawType) + } + + XCTAssertEqual(accumulator.rawInsertions, 4) + XCTAssertEqual(collector.referencedTypesRawInsertions, 4) + XCTAssertEqual(collector.referencedTypesPrefilterSkips, 4) + XCTAssertEqual(collector.referencedTypesEmptyResults, 4) + XCTAssertEqual(collector.referencedTypesSwiftDedupEligibleCount, 0) + XCTAssertEqual(collector.referencedTypesSwiftFirstSeenCount, 0) + XCTAssertEqual(collector.referencedTypesSwiftDuplicateSkipCount, 0) + XCTAssertEqual(collector.referencedTypesSwiftDuplicateSkippedUTF8ByteCount, 0) + XCTAssertEqual(collector.referencedTypesSwiftRawTypeDedupDuration, 0) + XCTAssertEqual(collector.typeCleanerExtractCalls, 0) + XCTAssertTrue(accumulator.types.isEmpty) + } + + func testNonSwiftReferencedTypeDuplicatesStillUseTypeCleanerCache() { + for language: LanguageType in [.ts, .tsx] { + let raw = "Promise" + let collector = CodeMapPerformanceCollector() + var accumulator = ReferencedTypesAccumulator( + language: language, + stats: collector, + perfOptions: .countersOnly + ) + accumulator.insert(rawType: raw) + let firstTypes = accumulator.types + let firstCleanerCalls = collector.typeCleanerExtractCalls + let firstCacheHits = collector.typeCleanerCacheHits + let firstCacheMisses = collector.typeCleanerCacheMisses + let firstOutputTypes = collector.referencedTypesOutputTypeCount + let firstEmptyResults = collector.referencedTypesEmptyResults + + accumulator.insert(rawType: raw) + + XCTAssertEqual(accumulator.types, firstTypes, "language=\(language)") + XCTAssertEqual(collector.typeCleanerExtractCalls, firstCleanerCalls + 1, "language=\(language)") + XCTAssertEqual(collector.typeCleanerCacheHits, firstCacheHits + 1, "language=\(language)") + XCTAssertEqual(collector.typeCleanerCacheMisses, firstCacheMisses, "language=\(language)") + XCTAssertEqual(collector.referencedTypesOutputTypeCount, firstOutputTypes * 2, "language=\(language)") + XCTAssertEqual(collector.referencedTypesEmptyResults, firstEmptyResults, "language=\(language)") + XCTAssertEqual(collector.referencedTypesSwiftDedupEligibleCount, 0, "language=\(language)") + XCTAssertEqual(collector.referencedTypesSwiftFirstSeenCount, 0, "language=\(language)") + XCTAssertEqual(collector.referencedTypesSwiftDuplicateSkipCount, 0, "language=\(language)") + XCTAssertEqual(collector.referencedTypesSwiftDuplicateSkippedUTF8ByteCount, 0, "language=\(language)") + XCTAssertEqual(collector.referencedTypesSwiftRawTypeDedupDuration, 0, "language=\(language)") + } + } + func testCaptureIndexCountsMissingNamedBuckets() { let collector = CodeMapPerformanceCollector() let index = CodeMapCaptureIndex([], performanceCollector: collector) @@ -1059,6 +1278,28 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { } } + private func assertSwiftReferencedTypesSequenceMatchesFreshAuthority( + _ rawTypes: [String?], + name: String, + file: StaticString = #filePath, + line: UInt = #line + ) { + var expected: Set = [] + for rawType in rawTypes { + var fresh = ReferencedTypesAccumulator(language: .swift) + fresh.insert(rawType: rawType) + expected.formUnion(fresh.types) + } + + var accumulated = ReferencedTypesAccumulator(language: .swift) + for rawType in rawTypes { + accumulated.insert(rawType: rawType) + } + + XCTAssertEqual(accumulated.types, expected, name, file: file, line: line) + XCTAssertEqual(accumulated.finalizeSorted(), expected.sorted(), name, file: file, line: line) + } + private static func legacySwiftSignatureWhitespaceNormalization(_ input: String) -> String { input.trimmingCharacters(in: .whitespacesAndNewlines) .replacing(#/\s+/#, with: " ") diff --git a/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift index dc1a470a7..01dd05cdf 100644 --- a/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift +++ b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift @@ -437,6 +437,7 @@ "swift_model_insert_ms=\(milliseconds(collector.swiftStrategyModelInsertionDuration))", "swift_context_only_ms=\(milliseconds(collector.swiftStrategyContextOnlyDuration))", "referenced_finalize_ms=\(milliseconds(collector.referencedTypesFinalizeDuration))", + "referenced_swift_raw_dedup_ms=\(milliseconds(collector.referencedTypesSwiftRawTypeDedupDuration))", "type_cleaner_ms=\(milliseconds(collector.typeCleanerDuration))", "type_cleaner_swift_ms=\(milliseconds(collector.typeCleanerSwiftDuration))", "type_cleaner_preclean_ms=\(milliseconds(collector.typeCleanerPrecleanDuration))", @@ -527,6 +528,10 @@ "swift_properties_handled=\(collector.swiftStrategyHandledPropertyCount)", "referenced_raw_insertions=\(collector.referencedTypesRawInsertions)", "referenced_prefilter_skips=\(collector.referencedTypesPrefilterSkips)", + "referenced_swift_dedup_eligible=\(collector.referencedTypesSwiftDedupEligibleCount)", + "referenced_swift_first_seen=\(collector.referencedTypesSwiftFirstSeenCount)", + "referenced_swift_duplicate_skips=\(collector.referencedTypesSwiftDuplicateSkipCount)", + "referenced_swift_duplicate_skipped_utf8_bytes=\(collector.referencedTypesSwiftDuplicateSkippedUTF8ByteCount)", "referenced_empty_results=\(collector.referencedTypesEmptyResults)", "referenced_output_types=\(collector.referencedTypesOutputTypeCount)", "referenced_unique_types=\(collector.referencedTypesUniqueCount)", From 4a89fe2baf30a90ae911f0d50727dec742c32dba Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Wed, 22 Jul 2026 16:47:25 -0400 Subject: [PATCH 09/11] Record Swift referenced type tests --- Scripts/Fixtures/test-suite-contract-ledger.tsv | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Scripts/Fixtures/test-suite-contract-ledger.tsv b/Scripts/Fixtures/test-suite-contract-ledger.tsv index d2710395a..556ed411b 100644 --- a/Scripts/Fixtures/test-suite-contract-ledger.tsv +++ b/Scripts/Fixtures/test-suite-contract-ledger.tsv @@ -805,6 +805,7 @@ root/RepoPromptTests.CodeMapBuildAdmissionTests/testCancellingQueuedCodemapBuild root/RepoPromptTests.CodeMapBuildAdmissionTests/testCodemapBuildAdmissionPreservesForegroundPriorityAndOwnerRoundRobin root Tests/RepoPromptTests/Services/FileSystem/CodeMapBuildAdmissionTests.swift RepoPromptTests.CodeMapBuildAdmissionTests testCodemapBuildAdmissionPreservesForegroundPriorityAndOwnerRoundRobin CodeMap codemap.build_admission.existing_scheduler_metadata foreground_precedence,owner_round_robin,owner_fifo,task_priority,bulk_accounting deterministic_concurrency root_swiftpm routine 2 ContentReadAsyncLimiter,CodeMapAdmissionGate,CodeMapAdmissionRecorder A content-search waiter queued after codemap builds is admitted first, then codemap requests alternate owners while preserving owner FIFO, execute at no less than requested task priority, and retain exact normal/bulk accounting. The admission seam could bypass foreground priority, discard owner identity, reorder one owner's work, ignore execution priority, or classify builds outside the established codemap counters. 0.057500 async_tasks,cancellation_gates per_test_limiter task_join+gate_release retain 0 Phase 5A owner and priority metadata contract root/RepoPromptTests.CodeMapBuildAdmissionTests/testProcessWideCodemapBuildAdmissionWaitsForForegroundActivityAndUsesRequestedPriority root Tests/RepoPromptTests/Services/FileSystem/CodeMapBuildAdmissionTests.swift RepoPromptTests.CodeMapBuildAdmissionTests testProcessWideCodemapBuildAdmissionWaitsForForegroundActivityAndUsesRequestedPriority CodeMap codemap.build_admission.process_wide_foreground_gate process_wide_singleton,root_load_token,task_priority,codemap_accounting deterministic_concurrency root_swiftpm routine 2 CodeMapAdmissionGate,CodeMapAdmissionSignal The process-wide wrapper queues without starting or granting during a real root-load foreground activity, then runs at no less than requested task priority after final token release and increments only the existing bulk/codemap grant accounting. A new build path could bypass the singleton limiter, run during foreground root work, lose priority context, or introduce separate counters and capacity. 0.011500 async_tasks,cancellation_gates process_wide_content_read_limiter task_join+explicit_token_cleanup retain 0 Phase 5A process-wide wrapper contract root/RepoPromptCodeMapCoreTests.CodeMapGoldenTests/testFixturesMatchGoldenCodeMapDescriptions root Tests/RepoPromptCodeMapCoreTests/CodeMapGoldenTests.swift RepoPromptCodeMapCoreTests.CodeMapGoldenTests testFixturesMatchGoldenCodeMapDescriptions CodeMap codemap.golden.artifact_renderer path_free_artifact,artifact_renderer,language_matrix,golden golden_snapshot codemap_core routine 13 c_smoke,cpp_edge_methods,cs_smoke,go_smoke,java_smoke,js_smoke,php_edge_namespaces,py_smoke,rb_smoke,rs_smoke,swift_smoke,ts_smoke,tsx_component Each practical-language fixture renders through the immutable artifact terminal and matches its committed golden. The artifact renderer could drift from current extraction/rendering or committed golden snapshots across the supported language matrix. 0.543500 tree_sitter,fixture_bundle syntax_manager test_case retain -1 Item 1 duplicate render removal; path-free artifact golden coverage across all 13 registered language fixtures including C#; Item 3 fixtures/goldens sole-owned by RepoPromptCodeMapCoreTests; Dart scenario removed alongside active Dart CodeMap support +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testNonSwiftReferencedTypeDuplicatesStillUseTypeCleanerCache root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testNonSwiftReferencedTypeDuplicatesStillUseTypeCleanerCache CodeMap codemap.query_optimization.non_swift_referenced_types_cache typescript,tsx,referenced_types,duplicate_input,type_cleaner_cache,swift_dedup_exclusion,counter_accounting regression codemap_core fast 2 TypeScript and TSX duplicate raw types preserve the same type set while the second insertion reuses the TypeCleaner cache, doubles output accounting, and leaves every Swift-only dedup counter at zero. Swift-only raw-type deduplication could leak into TypeScript or TSX, bypassing established TypeCleaner cache and output accounting behavior. test_case retain 0 Run 007 non-Swift cache-path and Swift-dedup exclusion regression. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testPreMaterializedSwiftAndTypeScriptGeneratorAttribution root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testPreMaterializedSwiftAndTypeScriptGeneratorAttribution CodeMap codemap.query_optimization.prematerialized_generator_attribution swift,typescript,pre_materialized_captures,generator_attribution,repeat_artifact_equality diagnostic codemap_core routine 2 Pre-materialized Swift and TypeScript captures generate an identical artifact across repeated runs and expose generator-only capture-index and capture-loop timing attribution. Generator-only optimization could change artifact output or lose a deterministic attribution surface that separates query execution from extraction cost. tree_sitter,cpu_timing,synthetic_source syntax_manager test_case retain 0 Generator-only diagnostic timings are reported without machine-dependent thresholds. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testStructuralFastPathsPreserveRoutingAndTypes root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testStructuralFastPathsPreserveRoutingAndTypes CodeMap codemap.query_optimization.structural_routing swift,typescript,tsx,query_capture_budget,duplicate_suppression regression codemap_core routine 3 Complex Swift declarations preserve bounded parameter/type extraction, while function-only TypeScript arrows are emitted only as functions and non-function initializer text containing arrows remains a global variable. Query slimming or duplicate suppression could lose Swift type detail, duplicate TypeScript arrows as globals, or suppress ordinary variables based on initializer text. 0.312000 tree_sitter,synthetic_source syntax_manager test_case retain 0 Query-first optimization correctness and attribution contract root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftParameterFallbackSkipsNestedAttributeColons root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftParameterFallbackSkipsNestedAttributeColons CodeMap codemap.query_optimization.swift_parameter_attribute_colon swift,parameter_type_fallback,property_wrapper,string_literal,nested_delimiters,default_value regression codemap_core fast 1 A Swift parameter with a property-wrapper argument containing a colon retains its local name and exact declared type while excluding the default value. The bounded fallback could treat an attribute argument colon as the parameter declaration separator and emit a malformed type. tree_sitter,synthetic_source syntax_manager test_case retain 0 Focused regression for top-level Swift parameter delimiter scanning. @@ -815,6 +816,10 @@ root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwift root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftPropertyTypeGeneratedASCIIEquivalenceAndCounters root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftPropertyTypeGeneratedASCIIEquivalenceAndCounters CodeMap codemap.query_optimization.swift_property_ascii_generated_equivalence swift,property_type,ascii_fast_path,deterministic_generated_corpus,legacy_equivalence,counter_accounting,utf8_byte_accounting regression codemap_core fast 1 Two thousand deterministically generated ASCII property strings exactly match the legacy parser while direct and fallback route totals, invocation count, and input-byte accounting remain exact. The optimized scanner could diverge on an unanticipated ASCII declaration shape or undercount routed work and processed bytes. synthetic_source test_case retain 0 Run 006 bounded generated equivalence and route-attribution regression. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftPropertyTypePipelineRoutesTopLevelMemberProtocolAndComputedDeclarations root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftPropertyTypePipelineRoutesTopLevelMemberProtocolAndComputedDeclarations CodeMap codemap.query_optimization.swift_property_pipeline_routing swift,property_type,pipeline_routing,top_level,member,computed_property,protocol_requirement,counter_accounting regression codemap_core routine 4 A single Swift artifact preserves exact types for top-level, stored-member, computed-member, and protocol-requirement properties while routing all four through the ASCII property path with no legacy or LTE regex fallback. Pipeline routing could omit or corrupt a property type, retain computed accessor text in the name, or silently bypass the optimized property scanner for a declaration context. tree_sitter,synthetic_source syntax_manager test_case retain 0 Run 006 end-to-end property declaration-context routing regression. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftPropertyTypeUnicodeAlwaysUsesLegacyFallback root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftPropertyTypeUnicodeAlwaysUsesLegacyFallback CodeMap codemap.query_optimization.swift_property_unicode_fallback swift,property_type,unicode,legacy_fallback,legacy_equivalence,counter_accounting,utf8_byte_accounting regression codemap_core fast 7 Seven non-ASCII property declarations exactly match legacy results, never take an ASCII direct route, and produce exact Unicode-fallback, invocation, and input-byte accounting. Unicode identifiers, types, spacing, attributes, or literals could be misrouted through the ASCII scanner and change extraction semantics or attribution. test_case retain 0 Run 006 Unicode routing and legacy-equivalence regression. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftReferencedTypesDedupMatchesFreshInsertionAuthority root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftReferencedTypesDedupMatchesFreshInsertionAuthority CodeMap codemap.query_optimization.swift_referenced_types_dedup_equivalence swift,referenced_types,raw_type_dedup,fresh_insertion_authority,ordering,insert_many,unicode,prefilter regression codemap_core fast 12 Nine named raw-type sequence families plus forward and reverse orderings produce the exact union and sorted output of fresh Swift accumulator insertions, and batched insertion matches individual insertion. Raw-type deduplication could change extracted referenced types for duplicates, trimming, case, complex syntax, Unicode, filtered inputs, ordering, or the batched API. test_case retain 0 Run 007 fresh-insertion authority matrix with nine sequence, two ordering, and one batched-insertion scenarios. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftReferencedTypesDedupPreservesFirstSeenBehavior root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftReferencedTypesDedupPreservesFirstSeenBehavior CodeMap codemap.query_optimization.swift_referenced_types_first_seen swift,referenced_types,raw_type_dedup,first_seen,type_cleaner,counter_accounting regression codemap_core fast 1 A first-seen Swift generic raw type produces the fresh TypeCleaner-derived type set and sorted output with one eligible and first-seen insertion, no duplicate bytes, and internally consistent cleaner/output counters. The dedup gate could suppress a first occurrence, change its extracted type set, or misattribute first-seen cleaner work as a duplicate. test_case retain 0 Run 007 first-seen behavior and attribution regression. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftReferencedTypesDedupSkipsExactTrimmedDuplicates root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftReferencedTypesDedupSkipsExactTrimmedDuplicates CodeMap codemap.query_optimization.swift_referenced_types_trimmed_duplicate swift,referenced_types,raw_type_dedup,trimmed_duplicate,type_cleaner_bypass,counter_accounting,utf8_byte_accounting regression codemap_core fast 1 A whitespace-padded repeat of one Swift generic type preserves the first type set and sorted output, records one normalized duplicate and its exact UTF-8 bytes, and performs no additional TypeCleaner cache or output work. Deduplication could compare unnormalized input, re-run expensive cleaning for an equivalent type, alter output, or report incorrect duplicate accounting. test_case retain 0 Run 007 trimmed-duplicate bypass and exact counter regression. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftReferencedTypesPrefilterRunsBeforeDedup root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftReferencedTypesPrefilterRunsBeforeDedup CodeMap codemap.query_optimization.swift_referenced_types_prefilter_ordering swift,referenced_types,prefilter,raw_type_dedup,primitive_types,container_types,nil_empty_whitespace,counter_accounting regression codemap_core fast 7 Repeated Int, Void, Array, nil, empty, and whitespace-only Swift inputs remain empty; the four nonempty types are prefiltered with exact accounting before any dedup or TypeCleaner work. Moving dedup ahead of input and type prefilters could admit irrelevant types, count filtered duplicates, or perform unnecessary TypeCleaner work. test_case retain 0 Run 007 seven-input prefilter-ordering and zero-downstream-work regression. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftSignatureWhitespaceNormalizerCounters root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftSignatureWhitespaceNormalizerCounters CodeMap codemap.query_optimization.swift_signature_whitespace_counters swift,signature_whitespace,ascii_noop,ascii_rewrite,unicode_fallback,legacy_equivalence,utf8_byte_accounting instrumentation_contract codemap_core fast 3 One ASCII no-op, one ASCII rewrite, and one Unicode fallback exactly match legacy normalization while route totals and aggregate input/output UTF-8 byte counters equal the processed strings. Normalization work could be attributed to the wrong route or report incomplete byte totals, hiding fast-path regressions despite correct visible strings. test_case retain 0 Deterministic signature-normalization attribution contract. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftSignatureWhitespaceNormalizerMatchesLegacyBehavior root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftSignatureWhitespaceNormalizerMatchesLegacyBehavior CodeMap codemap.query_optimization.swift_signature_whitespace_equivalence swift,signature_whitespace,ascii,unicode,legacy_equivalence,string_literals,comments regression codemap_core fast 19 Nineteen explicit signature shapes exactly match legacy trimming and whitespace collapsing across ASCII runs, empty and unchanged text, literals, comments, closures, non-ASCII identifiers, and Unicode spaces. The optimized normalizer could change signature text by collapsing the wrong characters or mishandling empty, literal, comment, or Unicode content. test_case retain 0 Explicit signature whitespace equivalence matrix. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSyntheticSwiftAndTypeScriptAttribution root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSyntheticSwiftAndTypeScriptAttribution CodeMap codemap.query_optimization.synthetic_attribution swift,typescript,query_capture_budget,performance_attribution diagnostic codemap_core routine 2 Deterministic 200-declaration Swift and TypeScript corpora complete successfully and report parse, capture materialization, indexing, capture-loop, regex/signature, and capture-count attribution without machine-dependent assertions. Future query changes could silently increase capture amplification or reintroduce regex/signature work without a repeatable focused measurement surface. 1.760000 tree_sitter,cpu_timing,synthetic_source syntax_manager test_case retain 0 Diagnostic timings are reported only; correctness and attribution coverage remain deterministic From 051d9dbfc20346d3c4752a2feaae19eefeba0d01 Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Wed, 22 Jul 2026 18:32:08 -0400 Subject: [PATCH 10/11] Add TypeScript codemap benchmark references --- .../Fixtures/test-suite-contract-ledger.tsv | 5 +- Scripts/conductor.py | 4 + Scripts/test_conductor_lifecycle.py | 12 + ...deMapQueryOptimizationBenchmarkTests.swift | 200 +++- ...SwiftCodeMapPipelineBenchmarkSupport.swift | 868 +++++++++++------- 5 files changed, 722 insertions(+), 367 deletions(-) diff --git a/Scripts/Fixtures/test-suite-contract-ledger.tsv b/Scripts/Fixtures/test-suite-contract-ledger.tsv index 556ed411b..4f4630b37 100644 --- a/Scripts/Fixtures/test-suite-contract-ledger.tsv +++ b/Scripts/Fixtures/test-suite-contract-ledger.tsv @@ -806,7 +806,7 @@ root/RepoPromptTests.CodeMapBuildAdmissionTests/testCodemapBuildAdmissionPreserv root/RepoPromptTests.CodeMapBuildAdmissionTests/testProcessWideCodemapBuildAdmissionWaitsForForegroundActivityAndUsesRequestedPriority root Tests/RepoPromptTests/Services/FileSystem/CodeMapBuildAdmissionTests.swift RepoPromptTests.CodeMapBuildAdmissionTests testProcessWideCodemapBuildAdmissionWaitsForForegroundActivityAndUsesRequestedPriority CodeMap codemap.build_admission.process_wide_foreground_gate process_wide_singleton,root_load_token,task_priority,codemap_accounting deterministic_concurrency root_swiftpm routine 2 CodeMapAdmissionGate,CodeMapAdmissionSignal The process-wide wrapper queues without starting or granting during a real root-load foreground activity, then runs at no less than requested task priority after final token release and increments only the existing bulk/codemap grant accounting. A new build path could bypass the singleton limiter, run during foreground root work, lose priority context, or introduce separate counters and capacity. 0.011500 async_tasks,cancellation_gates process_wide_content_read_limiter task_join+explicit_token_cleanup retain 0 Phase 5A process-wide wrapper contract root/RepoPromptCodeMapCoreTests.CodeMapGoldenTests/testFixturesMatchGoldenCodeMapDescriptions root Tests/RepoPromptCodeMapCoreTests/CodeMapGoldenTests.swift RepoPromptCodeMapCoreTests.CodeMapGoldenTests testFixturesMatchGoldenCodeMapDescriptions CodeMap codemap.golden.artifact_renderer path_free_artifact,artifact_renderer,language_matrix,golden golden_snapshot codemap_core routine 13 c_smoke,cpp_edge_methods,cs_smoke,go_smoke,java_smoke,js_smoke,php_edge_namespaces,py_smoke,rb_smoke,rs_smoke,swift_smoke,ts_smoke,tsx_component Each practical-language fixture renders through the immutable artifact terminal and matches its committed golden. The artifact renderer could drift from current extraction/rendering or committed golden snapshots across the supported language matrix. 0.543500 tree_sitter,fixture_bundle syntax_manager test_case retain -1 Item 1 duplicate render removal; path-free artifact golden coverage across all 13 registered language fixtures including C#; Item 3 fixtures/goldens sole-owned by RepoPromptCodeMapCoreTests; Dart scenario removed alongside active Dart CodeMap support root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testNonSwiftReferencedTypeDuplicatesStillUseTypeCleanerCache root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testNonSwiftReferencedTypeDuplicatesStillUseTypeCleanerCache CodeMap codemap.query_optimization.non_swift_referenced_types_cache typescript,tsx,referenced_types,duplicate_input,type_cleaner_cache,swift_dedup_exclusion,counter_accounting regression codemap_core fast 2 TypeScript and TSX duplicate raw types preserve the same type set while the second insertion reuses the TypeCleaner cache, doubles output accounting, and leaves every Swift-only dedup counter at zero. Swift-only raw-type deduplication could leak into TypeScript or TSX, bypassing established TypeCleaner cache and output accounting behavior. test_case retain 0 Run 007 non-Swift cache-path and Swift-dedup exclusion regression. -root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testPreMaterializedSwiftAndTypeScriptGeneratorAttribution root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testPreMaterializedSwiftAndTypeScriptGeneratorAttribution CodeMap codemap.query_optimization.prematerialized_generator_attribution swift,typescript,pre_materialized_captures,generator_attribution,repeat_artifact_equality diagnostic codemap_core routine 2 Pre-materialized Swift and TypeScript captures generate an identical artifact across repeated runs and expose generator-only capture-index and capture-loop timing attribution. Generator-only optimization could change artifact output or lose a deterministic attribution surface that separates query execution from extraction cost. tree_sitter,cpu_timing,synthetic_source syntax_manager test_case retain 0 Generator-only diagnostic timings are reported without machine-dependent thresholds. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testPreMaterializedSwiftAndTypeScriptGeneratorAttribution root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testPreMaterializedSwiftAndTypeScriptGeneratorAttribution CodeMap codemap.query_optimization.prematerialized_generator_attribution swift,typescript,tsx,pre_materialized_captures,generator_attribution,repeat_artifact_equality diagnostic codemap_core routine 3 Pre-materialized Swift, TypeScript, and TSX captures generate an identical artifact across repeated runs and expose generator-only capture-index and capture-loop timing attribution. Generator-only optimization could change artifact output or lose a deterministic attribution surface that separates query execution from extraction cost. tree_sitter,cpu_timing,synthetic_source syntax_manager test_case retain 0 Generator-only diagnostic timings are reported without machine-dependent thresholds. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testStructuralFastPathsPreserveRoutingAndTypes root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testStructuralFastPathsPreserveRoutingAndTypes CodeMap codemap.query_optimization.structural_routing swift,typescript,tsx,query_capture_budget,duplicate_suppression regression codemap_core routine 3 Complex Swift declarations preserve bounded parameter/type extraction, while function-only TypeScript arrows are emitted only as functions and non-function initializer text containing arrows remains a global variable. Query slimming or duplicate suppression could lose Swift type detail, duplicate TypeScript arrows as globals, or suppress ordinary variables based on initializer text. 0.312000 tree_sitter,synthetic_source syntax_manager test_case retain 0 Query-first optimization correctness and attribution contract root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftParameterFallbackSkipsNestedAttributeColons root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftParameterFallbackSkipsNestedAttributeColons CodeMap codemap.query_optimization.swift_parameter_attribute_colon swift,parameter_type_fallback,property_wrapper,string_literal,nested_delimiters,default_value regression codemap_core fast 1 A Swift parameter with a property-wrapper argument containing a colon retains its local name and exact declared type while excluding the default value. The bounded fallback could treat an attribute argument colon as the parameter declaration separator and emit a malformed type. tree_sitter,synthetic_source syntax_manager test_case retain 0 Focused regression for top-level Swift parameter delimiter scanning. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftParameterTypeASCIIFastPathGeneratedEquivalenceAndCounters root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftParameterTypeASCIIFastPathGeneratedEquivalenceAndCounters CodeMap codemap.query_optimization.swift_parameter_ascii_generated_equivalence swift,parameter_type,ascii_fast_path,deterministic_generated_corpus,legacy_equivalence,counter_accounting,utf8_byte_accounting regression codemap_core fast 1 Two thousand deterministically generated ASCII parameter strings exactly match the legacy parser while every call is attributed to the ASCII fast path with exact invocation and input-byte counters. The optimized scanner could diverge on an unanticipated ASCII delimiter shape or undercount routed work and processed bytes. synthetic_source test_case retain 0 Bounded generated equivalence and fast-path attribution regression. @@ -822,7 +822,8 @@ root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwift root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftReferencedTypesPrefilterRunsBeforeDedup root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftReferencedTypesPrefilterRunsBeforeDedup CodeMap codemap.query_optimization.swift_referenced_types_prefilter_ordering swift,referenced_types,prefilter,raw_type_dedup,primitive_types,container_types,nil_empty_whitespace,counter_accounting regression codemap_core fast 7 Repeated Int, Void, Array, nil, empty, and whitespace-only Swift inputs remain empty; the four nonempty types are prefiltered with exact accounting before any dedup or TypeCleaner work. Moving dedup ahead of input and type prefilters could admit irrelevant types, count filtered duplicates, or perform unnecessary TypeCleaner work. test_case retain 0 Run 007 seven-input prefilter-ordering and zero-downstream-work regression. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftSignatureWhitespaceNormalizerCounters root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftSignatureWhitespaceNormalizerCounters CodeMap codemap.query_optimization.swift_signature_whitespace_counters swift,signature_whitespace,ascii_noop,ascii_rewrite,unicode_fallback,legacy_equivalence,utf8_byte_accounting instrumentation_contract codemap_core fast 3 One ASCII no-op, one ASCII rewrite, and one Unicode fallback exactly match legacy normalization while route totals and aggregate input/output UTF-8 byte counters equal the processed strings. Normalization work could be attributed to the wrong route or report incomplete byte totals, hiding fast-path regressions despite correct visible strings. test_case retain 0 Deterministic signature-normalization attribution contract. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSwiftSignatureWhitespaceNormalizerMatchesLegacyBehavior root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSwiftSignatureWhitespaceNormalizerMatchesLegacyBehavior CodeMap codemap.query_optimization.swift_signature_whitespace_equivalence swift,signature_whitespace,ascii,unicode,legacy_equivalence,string_literals,comments regression codemap_core fast 19 Nineteen explicit signature shapes exactly match legacy trimming and whitespace collapsing across ASCII runs, empty and unchanged text, literals, comments, closures, non-ASCII identifiers, and Unicode spaces. The optimized normalizer could change signature text by collapsing the wrong characters or mishandling empty, literal, comment, or Unicode content. test_case retain 0 Explicit signature whitespace equivalence matrix. -root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSyntheticSwiftAndTypeScriptAttribution root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSyntheticSwiftAndTypeScriptAttribution CodeMap codemap.query_optimization.synthetic_attribution swift,typescript,query_capture_budget,performance_attribution diagnostic codemap_core routine 2 Deterministic 200-declaration Swift and TypeScript corpora complete successfully and report parse, capture materialization, indexing, capture-loop, regex/signature, and capture-count attribution without machine-dependent assertions. Future query changes could silently increase capture amplification or reintroduce regex/signature work without a repeatable focused measurement surface. 1.760000 tree_sitter,cpu_timing,synthetic_source syntax_manager test_case retain 0 Diagnostic timings are reported only; correctness and attribution coverage remain deterministic +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testSyntheticSwiftAndTypeScriptAttribution root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testSyntheticSwiftAndTypeScriptAttribution CodeMap codemap.query_optimization.synthetic_attribution swift,typescript,tsx,query_capture_budget,performance_attribution diagnostic codemap_core routine 3 Deterministic 200-declaration Swift, TypeScript, and TSX corpora complete successfully and report parse, capture materialization, indexing, capture-loop, regex/signature, and capture-count attribution without machine-dependent assertions. Future query changes could silently increase capture amplification or reintroduce regex/signature work without a repeatable focused measurement surface. 1.760000 tree_sitter,cpu_timing,synthetic_source syntax_manager test_case retain 0 Diagnostic timings are reported only; correctness and attribution coverage remain deterministic +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testTypeScriptAndTSXCorpusCorrectnessReference root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testTypeScriptAndTSXCorpusCorrectnessReference CodeMap codemap.query_optimization.typescript_tsx_strict_reference typescript,tsx,compile_gate,runtime_gate,write_once,exact_compare,external_local_references,atomic_cleanup,ordered_capture_tuples,canonical_artifacts regression codemap_core routine 4 TypeScriptSyntheticCorpus,TSXSyntheticCorpus,LocalCodeMapReferenceFiles When compiled with RPCE_ENABLE_BENCHMARK_TESTS=1 and enabled by RP_RUN_TYPESCRIPT_CODEMAP_REFERENCE=1, deterministic TypeScript and TSX corpora each build identical repeated artifacts; write mode exclusively creates both caller-supplied external local reference files only when neither exists and removes files created by the current attempt if the two-file write fails, while compare mode requires exact query, content, ordered-capture, artifact-digest, canonical-artifact, and capture-tuple equality for both languages. A candidate could silently drift TypeScript or TSX captures/artifacts, overwrite a baseline authority, leave a partial two-file reference set after failure, or treat a self-generated candidate artifact as correctness proof. 1.300000 tree_sitter,synthetic_source,filesystem external_tmp_reference_files caller_managed_local_references test_case retain 0 Opt-in compile/runtime-gated TS/TSX baseline authority; four language-by-mode scenarios, external files are never committed and successful references are write-once. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testTypeScriptUncontainedMembersFallThroughBeforeExtraction root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testTypeScriptUncontainedMembersFallThroughBeforeExtraction CodeMap codemap.query_optimization.ts_uncontained_fallthrough typescript,container_routing,generic_fallback,pre_extraction_guard regression codemap_core routine 7 Every TypeScript member capture family declines strategy handling before line, signature, or type extraction when no matching class or interface boundary exists. A widened TypeScript strategy gate could consume uncontained members, attach them to unrelated containers, or perform wasted extraction before fallback. tree_sitter,synthetic_source syntax_manager test_case retain 0 Direct strategy boundary regression for method, field, and interface signature captures. root/RepoPromptTests.CodeMapGoldenTests/testSnapshotFileTreeMarksCodeMapFixtures root Tests/RepoPromptTests/CodeMap/CodeMapGoldenTests.swift RepoPromptTests.CodeMapGoldenTests testSnapshotFileTreeMarksCodeMapFixtures CodeMap unreviewed unreviewed root_swiftpm routine 1 unreviewed unreviewed 0.000500 unreviewed retain_pending_review 0 initial census source line 23 root/RepoPromptTests.CodeMapGoldenTests/testSnapshotFileTreeNoneModeProducesNoOutputOrLegend root Tests/RepoPromptTests/CodeMap/CodeMapGoldenTests.swift RepoPromptTests.CodeMapGoldenTests testSnapshotFileTreeNoneModeProducesNoOutputOrLegend CodeMap unreviewed unreviewed root_swiftpm routine 1 unreviewed unreviewed 0.000000 unreviewed retain_pending_review 0 initial census source line 36 diff --git a/Scripts/conductor.py b/Scripts/conductor.py index 377f43e58..e5edd4eec 100755 --- a/Scripts/conductor.py +++ b/Scripts/conductor.py @@ -1257,6 +1257,10 @@ class OperationRegistry: "RPCE_RUN_CODEMAP_E2E", "RPCE_RUN_SCALE_TESTS", "RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK", + "RP_RUN_TYPESCRIPT_CODEMAP_REFERENCE", + "RP_TYPESCRIPT_CODEMAP_REFERENCE_MODE", + "RP_TYPESCRIPT_CODEMAP_TS_REFERENCE_PATH", + "RP_TYPESCRIPT_CODEMAP_TSX_REFERENCE_PATH", "RP_SWIFT_CODEMAP_ALLOWED_REMOVED_CAPTURES", "RP_SWIFT_CODEMAP_REFERENCE_MODE", "RP_SWIFT_CODEMAP_REFERENCE_PATH", diff --git a/Scripts/test_conductor_lifecycle.py b/Scripts/test_conductor_lifecycle.py index eb85ddbdb..f4c73df7c 100644 --- a/Scripts/test_conductor_lifecycle.py +++ b/Scripts/test_conductor_lifecycle.py @@ -1731,6 +1731,10 @@ def test_test_gate_environment_survives_client_snapshot_and_job_prepare(self) -> "RPCE_RUN_CODEMAP_E2E": "1", "RPCE_RUN_SCALE_TESTS": "1", "RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK": "1", + "RP_RUN_TYPESCRIPT_CODEMAP_REFERENCE": "1", + "RP_TYPESCRIPT_CODEMAP_REFERENCE_MODE": "compare", + "RP_TYPESCRIPT_CODEMAP_TS_REFERENCE_PATH": "/tmp/typescript-reference.json", + "RP_TYPESCRIPT_CODEMAP_TSX_REFERENCE_PATH": "/tmp/tsx-reference.json", "RP_SWIFT_CODEMAP_ALLOWED_REMOVED_CAPTURES": "type.class", "RP_SWIFT_CODEMAP_REFERENCE_MODE": "compare", "RP_SWIFT_CODEMAP_REFERENCE_PATH": "/tmp/reference.json", @@ -1744,6 +1748,10 @@ def test_test_gate_environment_survives_client_snapshot_and_job_prepare(self) -> self.assertEqual(snapshot["RPCE_RUN_CODEMAP_E2E"], "1") self.assertEqual(snapshot["RPCE_RUN_SCALE_TESTS"], "1") self.assertEqual(snapshot["RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK"], "1") + self.assertEqual(snapshot["RP_RUN_TYPESCRIPT_CODEMAP_REFERENCE"], "1") + self.assertEqual(snapshot["RP_TYPESCRIPT_CODEMAP_REFERENCE_MODE"], "compare") + self.assertEqual(snapshot["RP_TYPESCRIPT_CODEMAP_TS_REFERENCE_PATH"], "/tmp/typescript-reference.json") + self.assertEqual(snapshot["RP_TYPESCRIPT_CODEMAP_TSX_REFERENCE_PATH"], "/tmp/tsx-reference.json") self.assertEqual(snapshot["RP_SWIFT_CODEMAP_ALLOWED_REMOVED_CAPTURES"], "type.class") self.assertEqual(snapshot["RP_SWIFT_CODEMAP_REFERENCE_MODE"], "compare") self.assertEqual(snapshot["RP_SWIFT_CODEMAP_REFERENCE_PATH"], "/tmp/reference.json") @@ -1761,6 +1769,10 @@ def test_test_gate_environment_survives_client_snapshot_and_job_prepare(self) -> self.assertEqual(env["RPCE_RUN_CODEMAP_E2E"], "1") self.assertEqual(env["RPCE_RUN_SCALE_TESTS"], "1") self.assertEqual(env["RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK"], "1") + self.assertEqual(env["RP_RUN_TYPESCRIPT_CODEMAP_REFERENCE"], "1") + self.assertEqual(env["RP_TYPESCRIPT_CODEMAP_REFERENCE_MODE"], "compare") + self.assertEqual(env["RP_TYPESCRIPT_CODEMAP_TS_REFERENCE_PATH"], "/tmp/typescript-reference.json") + self.assertEqual(env["RP_TYPESCRIPT_CODEMAP_TSX_REFERENCE_PATH"], "/tmp/tsx-reference.json") self.assertEqual(env["RP_SWIFT_CODEMAP_ALLOWED_REMOVED_CAPTURES"], "type.class") self.assertEqual(env["RP_SWIFT_CODEMAP_REFERENCE_MODE"], "compare") self.assertEqual(env["RP_SWIFT_CODEMAP_REFERENCE_PATH"], "/tmp/reference.json") diff --git a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift index bfb5b5737..b8036c4b9 100644 --- a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift +++ b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift @@ -333,6 +333,41 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { } #endif + func testTypeScriptAndTSXCorpusCorrectnessReference() throws { + #if RPCE_BENCHMARK_TESTS + guard TypeScriptCodeMapPipelineBenchmarkSupport.isRuntimeEnabled else { + throw XCTSkip("Set RP_RUN_TYPESCRIPT_CODEMAP_REFERENCE=1 to run the TS/TSX corpus reference test") + } + + let files = TypeScriptCodeMapPipelineBenchmarkSupport.makeCorpus( + tsSource: Self.typeScriptSource(declarationCount: 200), + tsxSource: Self.typeScriptXSource(declarationCount: 200) + ) + let artifacts = try files.map(TypeScriptCodeMapPipelineBenchmarkSupport.buildArtifact) + let repeated = try files.map(TypeScriptCodeMapPipelineBenchmarkSupport.buildArtifact) + XCTAssertEqual(artifacts, repeated) + + let evidences = try zip(files, artifacts).map { + try TypeScriptCodeMapPipelineBenchmarkSupport.makeEvidence(file: $0, artifact: $1) + } + XCTAssertEqual(evidences.count, 2) + TypeScriptCodeMapPipelineBenchmarkSupport.printDigestRecord( + evidences[0].reference, + language: .ts + ) + TypeScriptCodeMapPipelineBenchmarkSupport.printDigestRecord( + evidences[1].reference, + language: .tsx + ) + try TypeScriptCodeMapPipelineBenchmarkSupport.applyReferenceMode( + tsReference: evidences[0].reference, + tsxReference: evidences[1].reference + ) + #else + throw XCTSkip("Compile with RPCE_ENABLE_BENCHMARK_TESTS=1 to run the TS/TSX corpus reference test") + #endif + } + func testSwiftSignatureWhitespaceNormalizerMatchesLegacyBehavior() { let cases: [(name: String, input: String)] = [ ("space run", "func value()"), @@ -987,6 +1022,7 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { let cases: [(name: String, language: LanguageType, source: String)] = [ ("swift", .swift, Self.swiftSource(declarationCount: 200)), ("typescript", .ts, Self.typeScriptSource(declarationCount: 200)), + ("tsx", .tsx, Self.typeScriptXSource(declarationCount: 200)), ] for benchmark in cases { @@ -1038,21 +1074,19 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { let repeatArtifactEquality = attributedArtifact == expectedArtifact XCTAssertTrue(repeatArtifactEquality) - print( - [ - "CODEMAP_PREMATERIALIZED_GENERATOR_BENCHMARK", - "language=\(benchmark.name)", - "declarations=200", - "raw_samples_ms=\(Self.formattedSamples(samplesMS))", - "median_ms=\(Self.formattedMilliseconds(Self.median(samplesMS)))", - "p95_ms=\(Self.formattedMilliseconds(Self.percentile95(samplesMS)))", - "max_ms=\(Self.formattedMilliseconds(samplesMS.max() ?? 0))", - "capture_index_ms=\(Self.milliseconds(collector.captureIndexDuration))", - "capture_loop_ms=\(Self.milliseconds(collector.captureLoopDuration))", - "captures=\(captures.count)", - "repeat_artifact_equality=\(repeatArtifactEquality)", - ].joined(separator: " ") - ) + var record = [ + "CODEMAP_PREMATERIALIZED_GENERATOR_BENCHMARK", + "language=\(benchmark.name)", + "declarations=200", + "raw_samples_ms=\(Self.formattedSamples(samplesMS))", + "median_ms=\(Self.formattedMilliseconds(Self.median(samplesMS)))", + "p95_ms=\(Self.formattedMilliseconds(Self.percentile95(samplesMS)))", + "max_ms=\(Self.formattedMilliseconds(samplesMS.max() ?? 0))", + "captures=\(captures.count)", + "repeat_artifact_equality=\(repeatArtifactEquality)", + ] + record.append(contentsOf: Self.codeMapAttributionFields(collector)) + print(record.joined(separator: " ")) } } @@ -1060,54 +1094,56 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { let cases: [(name: String, language: LanguageType, source: String)] = [ ("swift", .swift, Self.swiftSource(declarationCount: 200)), ("typescript", .ts, Self.typeScriptSource(declarationCount: 200)), + ("tsx", .tsx, Self.typeScriptXSource(declarationCount: 200)), ] for benchmark in cases { + let expectedArtifact = try build(source: benchmark.source, language: benchmark.language) for _ in 0 ..< 2 { - _ = try build(source: benchmark.source, language: benchmark.language) + XCTAssertEqual( + try build(source: benchmark.source, language: benchmark.language), + expectedArtifact + ) } var samplesMS: [Double] = [] + samplesMS.reserveCapacity(5) for _ in 0 ..< 5 { let start = ProcessInfo.processInfo.systemUptime - _ = try build(source: benchmark.source, language: benchmark.language) + let artifact = try build(source: benchmark.source, language: benchmark.language) samplesMS.append((ProcessInfo.processInfo.systemUptime - start) * 1_000) + XCTAssertEqual(artifact, expectedArtifact) } - samplesMS.sort() + let collector = CodeMapPerformanceCollector(collectsCaptureNames: true) - _ = try build( + let attributedArtifact = try build( source: benchmark.source, language: benchmark.language, options: .countersOnly, collector: collector ) + let repeatArtifactEquality = attributedArtifact == expectedArtifact + XCTAssertTrue(repeatArtifactEquality) XCTAssertEqual(collector.syntaxQueryExecutes, 1) XCTAssertGreaterThan(collector.syntaxCaptures, 0) - print( - [ - "CODEMAP_QUERY_BENCHMARK", - "language=\(benchmark.name)", - "declarations=200", - "median_ms=\(String(format: "%.3f", samplesMS[samplesMS.count / 2]))", - "max_ms=\(String(format: "%.3f", samplesMS.last ?? 0))", - "parse_ms=\(Self.milliseconds(collector.syntaxParseDuration))", - "query_ms=\(Self.milliseconds(collector.syntaxQueryExecuteDuration))", - "materialize_ms=\(Self.milliseconds(collector.syntaxCaptureMaterializationDuration))", - "capture_name_count_ms=\(Self.milliseconds(collector.syntaxCaptureNameCountingDuration))", - "index_ms=\(Self.milliseconds(collector.captureIndexDuration))", - "capture_loop_ms=\(Self.milliseconds(collector.captureLoopDuration))", - "captures=\(collector.syntaxCaptures)", - "lte_function_calls=\(collector.lteMatchAnyFunctionCalls)", - "lte_variable_calls=\(collector.lteMatchAnyVariableCalls)", - "jsts_calls=\(collector.jstsSignatureCallsFunctionLike + collector.jstsSignatureCallsStatementLike)", - "ts_duplicate_suppressions=\(collector.tsDuplicateFunctionVariableSuppressions)", - "swift_bodies=\(collector.syntaxCaptureCountsByName["swift.function.body", default: 0])", - "swift_returns=\(collector.syntaxCaptureCountsByName["swift.function.return_type", default: 0])", - "swift_property_types=\(collector.syntaxCaptureCountsByName["swift.property.type", default: 0])", - "ts_variables=\(collector.syntaxCaptureCountsByName["variable.global", default: 0])", - ].joined(separator: " ") - ) + var record = [ + "CODEMAP_QUERY_BENCHMARK", + "language=\(benchmark.name)", + "declarations=200", + "raw_samples_ms=\(Self.formattedSamples(samplesMS))", + "median_ms=\(Self.formattedMilliseconds(Self.median(samplesMS)))", + "p95_ms=\(Self.formattedMilliseconds(Self.percentile95(samplesMS)))", + "max_ms=\(Self.formattedMilliseconds(samplesMS.max() ?? 0))", + "captures=\(collector.syntaxCaptures)", + "repeat_artifact_equality=\(repeatArtifactEquality)", + "swift_bodies=\(collector.syntaxCaptureCountsByName["swift.function.body", default: 0])", + "swift_returns=\(collector.syntaxCaptureCountsByName["swift.function.return_type", default: 0])", + "swift_property_types=\(collector.syntaxCaptureCountsByName["swift.property.type", default: 0])", + "ts_variables=\(collector.syntaxCaptureCountsByName["variable.global", default: 0])", + ] + record.append(contentsOf: Self.codeMapAttributionFields(collector)) + print(record.joined(separator: " ")) } } @@ -1337,6 +1373,63 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { String(format: "%.3f", milliseconds) } + private static func codeMapAttributionFields(_ collector: CodeMapPerformanceCollector) -> [String] { + [ + "builder_total_ms=\(milliseconds(collector.builderTotalDuration))", + "builder_generator_ms=\(milliseconds(collector.builderGeneratorDuration))", + "syntax_total_ms=\(milliseconds(collector.syntaxTotalDuration))", + "parse_ms=\(milliseconds(collector.syntaxParseDuration))", + "query_ms=\(milliseconds(collector.syntaxQueryExecuteDuration))", + "materialize_ms=\(milliseconds(collector.syntaxCaptureMaterializationDuration))", + "capture_name_count_ms=\(milliseconds(collector.syntaxCaptureNameCountingDuration))", + "capture_index_ms=\(milliseconds(collector.captureIndexDuration))", + "ts_context_ms=\(milliseconds(collector.tsContextDuration))", + "capture_loop_ms=\(milliseconds(collector.captureLoopDuration))", + "ts_loop_ms=\(milliseconds(collector.captureLoopTSStrategyDuration))", + "jsts_ms=\(milliseconds(collector.jstsSignatureDuration))", + "lte_function_ms=\(milliseconds(collector.languageTypeExtractorFunctionDuration))", + "lte_variable_ms=\(milliseconds(collector.languageTypeExtractorVariableDuration))", + "type_cleaner_ms=\(milliseconds(collector.typeCleanerDuration))", + "type_cleaner_ts_ms=\(milliseconds(collector.typeCleanerTSDuration))", + "type_cleaner_tsx_ms=\(milliseconds(collector.typeCleanerTSXDuration))", + "referenced_finalize_ms=\(milliseconds(collector.referencedTypesFinalizeDuration))", + "artifact_finalize_ms=\(milliseconds(collector.artifactFinalizationDuration))", + "captures_processed=\(collector.capturesProcessed)", + "ts_strategy_handled=\(collector.tsStrategyHandled)", + "jsts_function_calls=\(collector.jstsSignatureCallsFunctionLike)", + "jsts_statement_calls=\(collector.jstsSignatureCallsStatementLike)", + "lte_function_calls=\(collector.lteMatchAnyFunctionCalls)", + "lte_variable_calls=\(collector.lteMatchAnyVariableCalls)", + "ts_duplicate_suppressions=\(collector.tsDuplicateFunctionVariableSuppressions)", + "ts_return_fast_path_hits=\(collector.tsReturnTypeFastPathHits)", + "ts_type_annotation_fast_path_hits=\(collector.tsTypeAnnotationFastPathHits)", + "ts_alias_rhs_fast_path_hits=\(collector.tsTypeAliasRhsFastPathHits)", + "type_cleaner_calls=\(collector.typeCleanerExtractCalls)", + "type_cleaner_cache_hits=\(collector.typeCleanerCacheHits)", + "type_cleaner_cache_misses=\(collector.typeCleanerCacheMisses)", + "type_cleaner_ts_calls=\(collector.typeCleanerTSCalls)", + "type_cleaner_tsx_calls=\(collector.typeCleanerTSXCalls)", + "referenced_raw_insertions=\(collector.referencedTypesRawInsertions)", + "referenced_prefilter_skips=\(collector.referencedTypesPrefilterSkips)", + "referenced_output_types=\(collector.referencedTypesOutputTypeCount)", + "referenced_unique_types=\(collector.referencedTypesUniqueCount)", + "memo_jsts_hits=\(collector.extractionMemoJSTSHits)", + "memo_jsts_misses=\(collector.extractionMemoJSTSMisses)", + "memo_function_hits=\(collector.extractionMemoFunctionHits)", + "memo_function_misses=\(collector.extractionMemoFunctionMisses)", + "memo_function_parsed_hits=\(collector.extractionMemoFunctionParsedHits)", + "memo_function_parsed_misses=\(collector.extractionMemoFunctionParsedMisses)", + "memo_variable_hits=\(collector.extractionMemoVariableHits)", + "memo_variable_misses=\(collector.extractionMemoVariableMisses)", + "memo_ts_fast_path_hits=\(collector.extractionMemoTSFastPathHits)", + "memo_ts_fast_path_misses=\(collector.extractionMemoTSFastPathMisses)", + "artifact_classes=\(collector.artifactFinalClassCount)", + "artifact_interfaces=\(collector.artifactFinalInterfaceCount)", + "artifact_functions=\(collector.artifactFinalFunctionCount)", + "artifact_globals=\(collector.artifactFinalGlobalVariableCount)", + ] + } + private static func formattedSamples(_ samples: [Double]) -> String { "[\(samples.map(formattedMilliseconds).joined(separator: ","))]" } @@ -1386,4 +1479,25 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { """ }.joined(separator: "\n") } + + private static func typeScriptXSource(declarationCount: Int) -> String { + (0 ..< declarationCount).map { index in + """ + export interface ViewProps\(index) { + value\(index): Promise>; + onSelect\(index)?: (value: T | { fallback: string }) => Promise<{ value: T }>; + } + export function View\(index)(props: ViewProps\(index)): JSX.Element { + return
{props.value\(index).toString()}
; + } + export const Card\(index) = (props: { title: string; payload: Record; render?: (value: number) => JSX.Element }) => ( +
{props.render?.(\(index)) ?? {props.title}}
+ ); + export const payload\(index): { primary: Record; state: "ready" | "idle" } = { + primary: { value: \(index) }, + state: "ready" + }; + """ + }.joined(separator: "\n") + } } diff --git a/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift index 01dd05cdf..5c19ccd58 100644 --- a/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift +++ b/Tests/RepoPromptCodeMapCoreTests/SwiftCodeMapPipelineBenchmarkSupport.swift @@ -4,28 +4,12 @@ import Foundation @testable import RepoPromptCodeMapCore - /// Opt-in, serial source-to-CodeMap benchmark support. This file is excluded - /// unless the package is built with RPCE_ENABLE_BENCHMARK_TESTS=1. - enum SwiftCodeMapPipelineBenchmarkSupport { - static let runtimeGateKey = "RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK" - static let referenceModeKey = "RP_SWIFT_CODEMAP_REFERENCE_MODE" - static let referencePathKey = "RP_SWIFT_CODEMAP_REFERENCE_PATH" - static let allowedRemovedCaptureNamesKey = "RP_SWIFT_CODEMAP_ALLOWED_REMOVED_CAPTURES" - static let defaultReferencePath = "/tmp/rpce-swift-codemap-bdc8ba10c.json" - static let referenceSemanticBase = "bdc8ba10c" - - // Filled from the deterministic corpus and intentionally fixed after setup. - static let expectedContentDigest = "972bc2e36fd6e177096800089c782576972d56675b10f6c934407face1f2af7e" - static let expectedArtifactDigest = "5befe73ca42db203c0825b133a4eeeed018b5cd65f41597644b93f1b35858664" - static let expectedCaptureDigestsByQuerySHA = [ - "c99914bc4c89f0c588a0717dfca902b2f25be24e3cca8720d0b026c250791448": - "9e82ba5a2ae5fec1b494f424a2fdcf24bfe5061167b93c5fc3f7b0dc8bf7254c" - ] - - struct CorpusFile { + /// Shared deterministic reference mechanics for opt-in CodeMap benchmarks. + /// Language-specific corpus construction and reporting remain in thin façades below. + enum CodeMapPipelineReferenceSupport { + struct SourceFile { let logicalPath: String let source: String - let snapshot: CodeMapCoreSourceSnapshot } struct ArtifactRecord: Codable, Equatable { @@ -94,23 +78,15 @@ } } - static var isRuntimeEnabled: Bool { - ProcessInfo.processInfo.environment[runtimeGateKey] == "1" - } - - static var referencePath: String { - ProcessInfo.processInfo.environment[referencePathKey] ?? defaultReferencePath - } - - static var referenceMode: String { - ProcessInfo.processInfo.environment[referenceModeKey] ?? "compare" - } - - static func configuredReferenceComparisonMode() throws -> ReferenceComparisonMode { + static func configuredReferenceComparisonMode( + referenceMode: String, + allowedRemovedCaptureNamesKey: String, + allowsCaptureRemovals: Bool + ) throws -> ReferenceComparisonMode { switch referenceMode { case "write", "compare": return .exact - case "compare-capture-removals": + case "compare-capture-removals" where allowsCaptureRemovals: let names = Set( (ProcessInfo.processInfo.environment[allowedRemovedCaptureNamesKey] ?? "") .split(separator: ",") @@ -126,6 +102,500 @@ } } + static func makeEvidence( + files: [SourceFile], + artifacts: [CodeMapSyntaxArtifact], + language: LanguageType, + semanticBase: String + ) throws -> Evidence { + precondition(files.count == artifacts.count) + let identity = try CodeMapSyntaxEngine.shared.pipelineIdentity( + for: language, + decoderPolicy: .workspaceAutomaticV1 + ) + let querySHA = identity.codeMapQuerySHA256.lowercaseHex + let encoder = JSONEncoder() + encoder.outputFormatting = [.sortedKeys] + + var artifactRecords: [ArtifactRecord] = [] + artifactRecords.reserveCapacity(files.count) + var captureRecords: [CaptureRecord] = [] + for (file, artifact) in zip(files, artifacts) { + try artifactRecords.append(ArtifactRecord( + logicalPath: file.logicalPath, + canonicalJSON: encoder.encode(artifact) + )) + let outcome = try CodeMapSyntaxEngine.shared.codeMap( + content: file.source, + language: language + ) + guard case let .captures(captures) = outcome else { + throw SupportError.queryNotReady(file.logicalPath, outcome) + } + captureRecords.append(contentsOf: captures.map { + CaptureRecord( + logicalPath: file.logicalPath, + name: $0.name, + location: $0.range.location, + length: $0.range.length + ) + }) + } + captureRecords.sort(by: captureLessThan) + + let reference = ReferenceRecord( + schemaVersion: 1, + semanticBase: semanticBase, + fileCount: files.count, + querySHA256: querySHA, + contentDigest: contentDigest(files), + captureDigest: captureDigest( + queryDigestBytes: identity.codeMapQuerySHA256.bytes, + captures: captureRecords + ), + artifactDigest: artifactDigest(artifactRecords), + artifacts: artifactRecords, + captures: captureRecords + ) + return Evidence(reference: reference, artifacts: artifacts) + } + + static func applyReferenceMode( + to reference: ReferenceRecord, + referenceMode: String, + referencePath: String, + allowedRemovedCaptureNamesKey: String, + allowsCaptureRemovals: Bool + ) throws { + switch referenceMode { + case "write": + try writeReferenceExclusively(reference, path: referencePath) + case "compare", "compare-capture-removals": + let expected = try readReference(path: referencePath) + try compareReference( + expected: expected, + actual: reference, + comparisonMode: configuredReferenceComparisonMode( + referenceMode: referenceMode, + allowedRemovedCaptureNamesKey: allowedRemovedCaptureNamesKey, + allowsCaptureRemovals: allowsCaptureRemovals + ), + allowedRemovedCaptureNamesKey: allowedRemovedCaptureNamesKey + ) + default: + throw SupportError.unsupportedReferenceMode(referenceMode) + } + } + + static func compareReference( + expected: ReferenceRecord, + actual: ReferenceRecord, + comparisonMode: ReferenceComparisonMode = .exact, + allowedRemovedCaptureNamesKey: String + ) throws { + switch comparisonMode { + case .exact: + guard expected == actual else { + throw SupportError.referenceMismatch(referenceDifference(expected: expected, actual: actual)) + } + case let .allowingCaptureRemovals(allowedNames): + guard !allowedNames.isEmpty else { + throw SupportError.invalidCaptureRemovalAllowlist(allowedRemovedCaptureNamesKey) + } + try compareReferenceAuthority(expected: expected, actual: actual) + guard expected.querySHA256 != actual.querySHA256 else { + throw SupportError.referenceMismatch("candidate query SHA did not change") + } + guard expected.captureDigest != actual.captureDigest else { + throw SupportError.referenceMismatch("candidate capture digest did not change") + } + + var actualIndex = 0 + var removedCount = 0 + for expectedCapture in expected.captures { + if actualIndex < actual.captures.count, + actual.captures[actualIndex] == expectedCapture + { + actualIndex += 1 + continue + } + guard allowedNames.contains(expectedCapture.name) else { + throw SupportError.referenceMismatch( + "removed or modified non-allowlisted capture tuple \(captureDescription(expectedCapture))" + ) + } + removedCount += 1 + } + guard actualIndex == actual.captures.count else { + throw SupportError.referenceMismatch( + "added or modified capture tuple \(captureDescription(actual.captures[actualIndex]))" + ) + } + guard removedCount > 0 else { + throw SupportError.referenceMismatch("candidate removed no allowlisted capture tuples") + } + } + } + + static func printDigestRecord( + _ reference: ReferenceRecord, + prefix: String, + referenceMode: String, + referencePath: String + ) { + print([ + prefix, + "files=\(reference.fileCount)", + "semantic_base=\(reference.semanticBase)", + "query_sha256=\(reference.querySHA256)", + "content_sha256=\(reference.contentDigest)", + "capture_sha256=\(reference.captureDigest)", + "artifact_sha256=\(reference.artifactDigest)", + "captures=\(reference.captures.count)", + "reference_mode=\(referenceMode)", + "reference_path=\(referencePath)" + ].joined(separator: " ")) + } + + static func writeReferencesExclusively(_ entries: [(ReferenceRecord, String)]) throws { + for (_, path) in entries where FileManager.default.fileExists(atPath: path) { + throw SupportError.referenceAlreadyExists(path) + } + + var createdPaths: [String] = [] + do { + for (reference, path) in entries { + try writeReferenceExclusively(reference, path: path) + createdPaths.append(path) + } + } catch { + for path in createdPaths { + removeIncompleteReference(path: path) + } + throw error + } + } + + private static func contentDigest(_ files: [SourceFile]) -> String { + var framed = Data() + for file in files { + appendFrame(Data(file.logicalPath.utf8), to: &framed) + appendFrame(Data(file.source.utf8), to: &framed) + } + return sha256Hex(framed) + } + + private static func captureDigest( + queryDigestBytes: Data, + captures: [CaptureRecord] + ) -> String { + var framed = Data(queryDigestBytes) + for capture in captures { + appendFrame(Data(capture.logicalPath.utf8), to: &framed) + appendFrame(Data(capture.name.utf8), to: &framed) + appendUInt64(UInt64(capture.location), to: &framed) + appendUInt64(UInt64(capture.length), to: &framed) + } + return sha256Hex(framed) + } + + private static func artifactDigest(_ records: [ArtifactRecord]) -> String { + var framed = Data() + for record in records { + appendFrame(Data(record.logicalPath.utf8), to: &framed) + appendFrame(record.canonicalJSON, to: &framed) + } + return sha256Hex(framed) + } + + private static func appendFrame(_ bytes: Data, to data: inout Data) { + appendUInt64(UInt64(bytes.count), to: &data) + data.append(bytes) + } + + private static func appendUInt64(_ value: UInt64, to data: inout Data) { + for shift in stride(from: 56, through: 0, by: -8) { + data.append(UInt8((value >> UInt64(shift)) & 0xFF)) + } + } + + private static func sha256Hex(_ data: Data) -> String { + Data(SHA256.hash(data: data)).map { String(format: "%02x", $0) }.joined() + } + + private static func captureLessThan(_ lhs: CaptureRecord, _ rhs: CaptureRecord) -> Bool { + if lhs.logicalPath != rhs.logicalPath { return lhs.logicalPath < rhs.logicalPath } + if lhs.name != rhs.name { return lhs.name < rhs.name } + if lhs.location != rhs.location { return lhs.location < rhs.location } + return lhs.length < rhs.length + } + + private static func writeReferenceExclusively( + _ reference: ReferenceRecord, + path: String + ) throws { + let encoder = JSONEncoder() + encoder.outputFormatting = [.prettyPrinted, .sortedKeys] + let data = try encoder.encode(reference) + + let descriptor: Int32 + while true { + let result = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR) + if result >= 0 { + descriptor = result + break + } + if errno == EINTR { continue } + if errno == EEXIST { throw SupportError.referenceAlreadyExists(path) } + throw POSIXError(POSIXErrorCode(rawValue: errno) ?? .EIO) + } + + var completed = false + defer { + _ = close(descriptor) + if !completed { + removeIncompleteReference(path: path) + } + } + try data.withUnsafeBytes { buffer in + guard let base = buffer.baseAddress else { + throw SupportError.shortWrite(path) + } + var total = 0 + while total < buffer.count { + let result = Darwin.write(descriptor, base.advanced(by: total), buffer.count - total) + if result < 0 { + if errno == EINTR { continue } + throw POSIXError(POSIXErrorCode(rawValue: errno) ?? .EIO) + } + guard result > 0 else { throw SupportError.shortWrite(path) } + total += result + } + } + completed = true + } + + private static func removeIncompleteReference(path: String) { + while unlink(path) < 0, errno == EINTR {} + } + + private static func readReference(path: String) throws -> ReferenceRecord { + try JSONDecoder().decode(ReferenceRecord.self, from: Data(contentsOf: URL(fileURLWithPath: path))) + } + + private static func compareReferenceAuthority( + expected: ReferenceRecord, + actual: ReferenceRecord + ) throws { + if expected.schemaVersion != actual.schemaVersion { + throw SupportError.referenceMismatch("schema version") + } + if expected.semanticBase != actual.semanticBase { + throw SupportError.referenceMismatch("semantic base") + } + if expected.fileCount != actual.fileCount { + throw SupportError.referenceMismatch("file count") + } + if expected.contentDigest != actual.contentDigest { + throw SupportError.referenceMismatch("content digest") + } + if expected.artifactDigest != actual.artifactDigest { + throw SupportError.referenceMismatch("artifact digest") + } + if expected.artifacts != actual.artifacts { + throw SupportError.referenceMismatch("canonical artifact bytes") + } + } + + private static func captureDescription(_ capture: CaptureRecord) -> String { + "\(capture.logicalPath):\(capture.name)@\(capture.location)+\(capture.length)" + } + + private static func referenceDifference( + expected: ReferenceRecord, + actual: ReferenceRecord + ) -> String { + if expected.schemaVersion != actual.schemaVersion { return "schema version" } + if expected.semanticBase != actual.semanticBase { return "semantic base" } + if expected.fileCount != actual.fileCount { return "file count" } + if expected.querySHA256 != actual.querySHA256 { return "query SHA" } + if expected.contentDigest != actual.contentDigest { return "content digest" } + if expected.captureDigest != actual.captureDigest { return "capture digest" } + if expected.artifactDigest != actual.artifactDigest { return "artifact digest" } + if expected.artifacts != actual.artifacts { return "canonical artifact bytes" } + if expected.captures != actual.captures { return "canonical capture tuples" } + return "unknown" + } + } + + enum TypeScriptCodeMapPipelineBenchmarkSupport { + static let runtimeGateKey = "RP_RUN_TYPESCRIPT_CODEMAP_REFERENCE" + static let referenceModeKey = "RP_TYPESCRIPT_CODEMAP_REFERENCE_MODE" + static let tsReferencePathKey = "RP_TYPESCRIPT_CODEMAP_TS_REFERENCE_PATH" + static let tsxReferencePathKey = "RP_TYPESCRIPT_CODEMAP_TSX_REFERENCE_PATH" + static let defaultTSReferencePath = "/tmp/rpce-typescript-codemap-ts-v1.json" + static let defaultTSXReferencePath = "/tmp/rpce-typescript-codemap-tsx-v1.json" + private static let unsupportedCaptureRemovalKey = "TS/TSX references are exact-only" + + typealias Evidence = CodeMapPipelineReferenceSupport.Evidence + typealias ReferenceRecord = CodeMapPipelineReferenceSupport.ReferenceRecord + typealias SupportError = CodeMapPipelineReferenceSupport.SupportError + + struct CorpusFile { + let logicalPath: String + let source: String + let language: LanguageType + let snapshot: CodeMapCoreSourceSnapshot + } + + static var isRuntimeEnabled: Bool { + ProcessInfo.processInfo.environment[runtimeGateKey] == "1" + } + + static var referenceMode: String { + ProcessInfo.processInfo.environment[referenceModeKey] ?? "compare" + } + + static var tsReferencePath: String { + ProcessInfo.processInfo.environment[tsReferencePathKey] ?? defaultTSReferencePath + } + + static var tsxReferencePath: String { + ProcessInfo.processInfo.environment[tsxReferencePathKey] ?? defaultTSXReferencePath + } + + static func makeCorpus(tsSource: String, tsxSource: String) -> [CorpusFile] { + [ + CorpusFile( + logicalPath: "Synthetic/TypeScriptBench.ts", + source: tsSource, + language: .ts, + snapshot: CodeMapFixtureRunner.makeSourceSnapshot(content: tsSource) + ), + CorpusFile( + logicalPath: "Synthetic/TypeScriptBench.tsx", + source: tsxSource, + language: .tsx, + snapshot: CodeMapFixtureRunner.makeSourceSnapshot(content: tsxSource) + ) + ] + } + + static func buildArtifact(file: CorpusFile) throws -> CodeMapSyntaxArtifact { + let outcome = try CodeMapSyntaxArtifactBuilder.build( + source: file.snapshot, + language: file.language + ) + guard case let .ready(artifact) = outcome else { + throw SupportError.artifactNotReady(file.logicalPath, outcome) + } + return artifact + } + + static func makeEvidence( + file: CorpusFile, + artifact: CodeMapSyntaxArtifact + ) throws -> Evidence { + try CodeMapPipelineReferenceSupport.makeEvidence( + files: [.init(logicalPath: file.logicalPath, source: file.source)], + artifacts: [artifact], + language: file.language, + semanticBase: file.language == .ts ? "typescript-codemap-synthetic-v1" : "tsx-codemap-synthetic-v1" + ) + } + + static func applyReferenceMode( + tsReference: ReferenceRecord, + tsxReference: ReferenceRecord + ) throws { + switch referenceMode { + case "write": + try CodeMapPipelineReferenceSupport.writeReferencesExclusively([ + (tsReference, tsReferencePath), + (tsxReference, tsxReferencePath) + ]) + case "compare": + try CodeMapPipelineReferenceSupport.applyReferenceMode( + to: tsReference, + referenceMode: referenceMode, + referencePath: tsReferencePath, + allowedRemovedCaptureNamesKey: unsupportedCaptureRemovalKey, + allowsCaptureRemovals: false + ) + try CodeMapPipelineReferenceSupport.applyReferenceMode( + to: tsxReference, + referenceMode: referenceMode, + referencePath: tsxReferencePath, + allowedRemovedCaptureNamesKey: unsupportedCaptureRemovalKey, + allowsCaptureRemovals: false + ) + default: + throw SupportError.unsupportedReferenceMode(referenceMode) + } + } + + static func printDigestRecord(_ reference: ReferenceRecord, language: LanguageType) { + let isTypeScript = language == .ts + CodeMapPipelineReferenceSupport.printDigestRecord( + reference, + prefix: isTypeScript ? "TYPESCRIPT_CODEMAP_PIPELINE_DIGESTS" : "TSX_CODEMAP_PIPELINE_DIGESTS", + referenceMode: referenceMode, + referencePath: isTypeScript ? tsReferencePath : tsxReferencePath + ) + } + } + + /// Opt-in, serial source-to-CodeMap benchmark support. This file is excluded + /// unless the package is built with RPCE_ENABLE_BENCHMARK_TESTS=1. + enum SwiftCodeMapPipelineBenchmarkSupport { + static let runtimeGateKey = "RP_RUN_SWIFT_CODEMAP_PIPELINE_BENCHMARK" + static let referenceModeKey = "RP_SWIFT_CODEMAP_REFERENCE_MODE" + static let referencePathKey = "RP_SWIFT_CODEMAP_REFERENCE_PATH" + static let allowedRemovedCaptureNamesKey = "RP_SWIFT_CODEMAP_ALLOWED_REMOVED_CAPTURES" + static let defaultReferencePath = "/tmp/rpce-swift-codemap-bdc8ba10c.json" + static let referenceSemanticBase = "bdc8ba10c" + + // Filled from the deterministic corpus and intentionally fixed after setup. + static let expectedContentDigest = "972bc2e36fd6e177096800089c782576972d56675b10f6c934407face1f2af7e" + static let expectedArtifactDigest = "5befe73ca42db203c0825b133a4eeeed018b5cd65f41597644b93f1b35858664" + static let expectedCaptureDigestsByQuerySHA = [ + "c99914bc4c89f0c588a0717dfca902b2f25be24e3cca8720d0b026c250791448": + "9e82ba5a2ae5fec1b494f424a2fdcf24bfe5061167b93c5fc3f7b0dc8bf7254c" + ] + + struct CorpusFile { + let logicalPath: String + let source: String + let snapshot: CodeMapCoreSourceSnapshot + } + + typealias ArtifactRecord = CodeMapPipelineReferenceSupport.ArtifactRecord + typealias CaptureRecord = CodeMapPipelineReferenceSupport.CaptureRecord + typealias ReferenceRecord = CodeMapPipelineReferenceSupport.ReferenceRecord + typealias Evidence = CodeMapPipelineReferenceSupport.Evidence + typealias ReferenceComparisonMode = CodeMapPipelineReferenceSupport.ReferenceComparisonMode + typealias SupportError = CodeMapPipelineReferenceSupport.SupportError + + static var isRuntimeEnabled: Bool { + ProcessInfo.processInfo.environment[runtimeGateKey] == "1" + } + + static var referencePath: String { + ProcessInfo.processInfo.environment[referencePathKey] ?? defaultReferencePath + } + + static var referenceMode: String { + ProcessInfo.processInfo.environment[referenceModeKey] ?? "compare" + } + + static func configuredReferenceComparisonMode() throws -> ReferenceComparisonMode { + try CodeMapPipelineReferenceSupport.configuredReferenceComparisonMode( + referenceMode: referenceMode, + allowedRemovedCaptureNamesKey: allowedRemovedCaptureNamesKey, + allowsCaptureRemovals: true + ) + } + static func makeCorpus() -> [CorpusFile] { var entries: [(String, String)] = [] entries.reserveCapacity(64) @@ -163,81 +633,34 @@ files: [CorpusFile], performanceCollector: CodeMapPerformanceCollector? = nil ) throws -> [CodeMapSyntaxArtifact] { - var artifacts: [CodeMapSyntaxArtifact] = [] - artifacts.reserveCapacity(files.count) - let options: CodeMapPerfOptions = performanceCollector == nil ? .disabled : .countersOnly - for file in files { - let outcome = try CodeMapSyntaxArtifactBuilder.build( - source: file.snapshot, - language: .swift, - performanceOptions: options, - performanceCollector: performanceCollector - ) - guard case let .ready(artifact) = outcome else { - throw SupportError.artifactNotReady(file.logicalPath, outcome) - } - artifacts.append(artifact) - } - return artifacts - } - - static func makeEvidence( - files: [CorpusFile], - artifacts: [CodeMapSyntaxArtifact] - ) throws -> Evidence { - precondition(files.count == artifacts.count) - let identity = try CodeMapSyntaxEngine.shared.pipelineIdentity( - for: .swift, - decoderPolicy: .workspaceAutomaticV1 - ) - let querySHA = identity.codeMapQuerySHA256.lowercaseHex - let encoder = JSONEncoder() - encoder.outputFormatting = [.sortedKeys] - - var artifactRecords: [ArtifactRecord] = [] - artifactRecords.reserveCapacity(files.count) - var captureRecords: [CaptureRecord] = [] - for (file, artifact) in zip(files, artifacts) { - try artifactRecords.append(ArtifactRecord( - logicalPath: file.logicalPath, - canonicalJSON: encoder.encode(artifact) - )) - let outcome = try CodeMapSyntaxEngine.shared.codeMap( - content: file.source, - language: .swift + var artifacts: [CodeMapSyntaxArtifact] = [] + artifacts.reserveCapacity(files.count) + let options: CodeMapPerfOptions = performanceCollector == nil ? .disabled : .countersOnly + for file in files { + let outcome = try CodeMapSyntaxArtifactBuilder.build( + source: file.snapshot, + language: .swift, + performanceOptions: options, + performanceCollector: performanceCollector ) - guard case let .captures(captures) = outcome else { - throw SupportError.queryNotReady(file.logicalPath, outcome) + guard case let .ready(artifact) = outcome else { + throw SupportError.artifactNotReady(file.logicalPath, outcome) } - captureRecords.append(contentsOf: captures.map { - CaptureRecord( - logicalPath: file.logicalPath, - name: $0.name, - location: $0.range.location, - length: $0.range.length - ) - }) + artifacts.append(artifact) } - captureRecords.sort(by: captureLessThan) + return artifacts + } - let contentDigest = contentDigest(files) - let captureDigest = captureDigest( - queryDigestBytes: identity.codeMapQuerySHA256.bytes, - captures: captureRecords - ) - let artifactDigest = artifactDigest(artifactRecords) - let reference = ReferenceRecord( - schemaVersion: 1, - semanticBase: referenceSemanticBase, - fileCount: files.count, - querySHA256: querySHA, - contentDigest: contentDigest, - captureDigest: captureDigest, - artifactDigest: artifactDigest, - artifacts: artifactRecords, - captures: captureRecords + static func makeEvidence( + files: [CorpusFile], + artifacts: [CodeMapSyntaxArtifact] + ) throws -> Evidence { + try CodeMapPipelineReferenceSupport.makeEvidence( + files: files.map { .init(logicalPath: $0.logicalPath, source: $0.source) }, + artifacts: artifacts, + language: .swift, + semanticBase: referenceSemanticBase ) - return Evidence(reference: reference, artifacts: artifacts) } static func validateFixedDigests( @@ -282,19 +705,13 @@ } static func applyReferenceMode(to reference: ReferenceRecord) throws { - switch referenceMode { - case "write": - try writeReferenceExclusively(reference, path: referencePath) - case "compare", "compare-capture-removals": - let expected = try readReference(path: referencePath) - try compareReference( - expected: expected, - actual: reference, - comparisonMode: configuredReferenceComparisonMode() - ) - default: - throw SupportError.unsupportedReferenceMode(referenceMode) - } + try CodeMapPipelineReferenceSupport.applyReferenceMode( + to: reference, + referenceMode: referenceMode, + referencePath: referencePath, + allowedRemovedCaptureNamesKey: allowedRemovedCaptureNamesKey, + allowsCaptureRemovals: true + ) } static func compareReference( @@ -302,63 +719,21 @@ actual: ReferenceRecord, comparisonMode: ReferenceComparisonMode = .exact ) throws { - switch comparisonMode { - case .exact: - guard expected == actual else { - throw SupportError.referenceMismatch(referenceDifference(expected: expected, actual: actual)) - } - case let .allowingCaptureRemovals(allowedNames): - guard !allowedNames.isEmpty else { - throw SupportError.invalidCaptureRemovalAllowlist(allowedRemovedCaptureNamesKey) - } - try compareReferenceAuthority(expected: expected, actual: actual) - guard expected.querySHA256 != actual.querySHA256 else { - throw SupportError.referenceMismatch("candidate query SHA did not change") - } - guard expected.captureDigest != actual.captureDigest else { - throw SupportError.referenceMismatch("candidate capture digest did not change") - } - - var actualIndex = 0 - var removedCount = 0 - for expectedCapture in expected.captures { - if actualIndex < actual.captures.count, - actual.captures[actualIndex] == expectedCapture - { - actualIndex += 1 - continue - } - guard allowedNames.contains(expectedCapture.name) else { - throw SupportError.referenceMismatch( - "removed or modified non-allowlisted capture tuple \(captureDescription(expectedCapture))" - ) - } - removedCount += 1 - } - guard actualIndex == actual.captures.count else { - throw SupportError.referenceMismatch( - "added or modified capture tuple \(captureDescription(actual.captures[actualIndex]))" - ) - } - guard removedCount > 0 else { - throw SupportError.referenceMismatch("candidate removed no allowlisted capture tuples") - } - } + try CodeMapPipelineReferenceSupport.compareReference( + expected: expected, + actual: actual, + comparisonMode: comparisonMode, + allowedRemovedCaptureNamesKey: allowedRemovedCaptureNamesKey + ) } static func printDigestRecord(_ reference: ReferenceRecord) { - print([ - "SWIFT_CODEMAP_PIPELINE_DIGESTS", - "files=\(reference.fileCount)", - "semantic_base=\(reference.semanticBase)", - "query_sha256=\(reference.querySHA256)", - "content_sha256=\(reference.contentDigest)", - "capture_sha256=\(reference.captureDigest)", - "artifact_sha256=\(reference.artifactDigest)", - "captures=\(reference.captures.count)", - "reference_mode=\(referenceMode)", - "reference_path=\(referencePath)" - ].joined(separator: " ")) + CodeMapPipelineReferenceSupport.printDigestRecord( + reference, + prefix: "SWIFT_CODEMAP_PIPELINE_DIGESTS", + referenceMode: referenceMode, + referencePath: referencePath + ) } static func attributionRecord( @@ -583,157 +958,6 @@ String(format: "%.3f", Double(numerator) / Double(denominator)) } - private static func contentDigest(_ files: [CorpusFile]) -> String { - var framed = Data() - for file in files { - appendFrame(Data(file.logicalPath.utf8), to: &framed) - appendFrame(Data(file.source.utf8), to: &framed) - } - return sha256Hex(framed) - } - - private static func captureDigest( - queryDigestBytes: Data, - captures: [CaptureRecord] - ) -> String { - var framed = Data(queryDigestBytes) - for capture in captures { - appendFrame(Data(capture.logicalPath.utf8), to: &framed) - appendFrame(Data(capture.name.utf8), to: &framed) - appendUInt64(UInt64(capture.location), to: &framed) - appendUInt64(UInt64(capture.length), to: &framed) - } - return sha256Hex(framed) - } - - private static func artifactDigest(_ records: [ArtifactRecord]) -> String { - var framed = Data() - for record in records { - appendFrame(Data(record.logicalPath.utf8), to: &framed) - appendFrame(record.canonicalJSON, to: &framed) - } - return sha256Hex(framed) - } - - private static func appendFrame(_ bytes: Data, to data: inout Data) { - appendUInt64(UInt64(bytes.count), to: &data) - data.append(bytes) - } - - private static func appendUInt64(_ value: UInt64, to data: inout Data) { - for shift in stride(from: 56, through: 0, by: -8) { - data.append(UInt8((value >> UInt64(shift)) & 0xFF)) - } - } - - private static func sha256Hex(_ data: Data) -> String { - Data(SHA256.hash(data: data)).map { String(format: "%02x", $0) }.joined() - } - - private static func captureLessThan(_ lhs: CaptureRecord, _ rhs: CaptureRecord) -> Bool { - if lhs.logicalPath != rhs.logicalPath { return lhs.logicalPath < rhs.logicalPath } - if lhs.name != rhs.name { return lhs.name < rhs.name } - if lhs.location != rhs.location { return lhs.location < rhs.location } - return lhs.length < rhs.length - } - - private static func writeReferenceExclusively( - _ reference: ReferenceRecord, - path: String - ) throws { - let encoder = JSONEncoder() - encoder.outputFormatting = [.prettyPrinted, .sortedKeys] - let data = try encoder.encode(reference) - - let descriptor: Int32 - while true { - let result = open(path, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR) - if result >= 0 { - descriptor = result - break - } - if errno == EINTR { continue } - if errno == EEXIST { throw SupportError.referenceAlreadyExists(path) } - throw POSIXError(POSIXErrorCode(rawValue: errno) ?? .EIO) - } - - var completed = false - defer { - _ = close(descriptor) - if !completed { - removeIncompleteReference(path: path) - } - } - try data.withUnsafeBytes { buffer in - guard let base = buffer.baseAddress else { - throw SupportError.shortWrite(path) - } - var total = 0 - while total < buffer.count { - let result = Darwin.write(descriptor, base.advanced(by: total), buffer.count - total) - if result < 0 { - if errno == EINTR { continue } - throw POSIXError(POSIXErrorCode(rawValue: errno) ?? .EIO) - } - guard result > 0 else { throw SupportError.shortWrite(path) } - total += result - } - } - completed = true - } - - private static func removeIncompleteReference(path: String) { - while unlink(path) < 0, errno == EINTR {} - } - - private static func readReference(path: String) throws -> ReferenceRecord { - try JSONDecoder().decode(ReferenceRecord.self, from: Data(contentsOf: URL(fileURLWithPath: path))) - } - - private static func compareReferenceAuthority( - expected: ReferenceRecord, - actual: ReferenceRecord - ) throws { - if expected.schemaVersion != actual.schemaVersion { - throw SupportError.referenceMismatch("schema version") - } - if expected.semanticBase != actual.semanticBase { - throw SupportError.referenceMismatch("semantic base") - } - if expected.fileCount != actual.fileCount { - throw SupportError.referenceMismatch("file count") - } - if expected.contentDigest != actual.contentDigest { - throw SupportError.referenceMismatch("content digest") - } - if expected.artifactDigest != actual.artifactDigest { - throw SupportError.referenceMismatch("artifact digest") - } - if expected.artifacts != actual.artifacts { - throw SupportError.referenceMismatch("canonical artifact bytes") - } - } - - private static func captureDescription(_ capture: CaptureRecord) -> String { - "\(capture.logicalPath):\(capture.name)@\(capture.location)+\(capture.length)" - } - - private static func referenceDifference( - expected: ReferenceRecord, - actual: ReferenceRecord - ) -> String { - if expected.schemaVersion != actual.schemaVersion { return "schema version" } - if expected.semanticBase != actual.semanticBase { return "semantic base" } - if expected.fileCount != actual.fileCount { return "file count" } - if expected.querySHA256 != actual.querySHA256 { return "query SHA" } - if expected.contentDigest != actual.contentDigest { return "content digest" } - if expected.captureDigest != actual.captureDigest { return "capture digest" } - if expected.artifactDigest != actual.artifactDigest { return "artifact digest" } - if expected.artifacts != actual.artifacts { return "canonical artifact bytes" } - if expected.captures != actual.captures { return "canonical capture tuples" } - return "unknown" - } - private static func smallSource(index: Int) -> String { """ import Foundation From 2db7459d5484ef4e08a1f0279b98bb6a11f76c88 Mon Sep 17 00:00:00 2001 From: Eric Provencher Date: Wed, 22 Jul 2026 18:55:33 -0400 Subject: [PATCH 11/11] Optimize JavaScript and TypeScript signatures --- .../Fixtures/test-suite-contract-ledger.tsv | 4 + .../Features/CodeMap/CodeMapPerfStats.swift | 10 + .../Extraction/JSTSSignatureExtractor.swift | 99 +++++++- .../TypeScriptCodeMapStrategy.swift | 3 +- .../CodeMapPerformanceCollector.swift | 5 + ...deMapQueryOptimizationBenchmarkTests.swift | 221 ++++++++++++++++++ 6 files changed, 338 insertions(+), 4 deletions(-) diff --git a/Scripts/Fixtures/test-suite-contract-ledger.tsv b/Scripts/Fixtures/test-suite-contract-ledger.tsv index 4f4630b37..6eb779142 100644 --- a/Scripts/Fixtures/test-suite-contract-ledger.tsv +++ b/Scripts/Fixtures/test-suite-contract-ledger.tsv @@ -805,6 +805,10 @@ root/RepoPromptTests.CodeMapBuildAdmissionTests/testCancellingQueuedCodemapBuild root/RepoPromptTests.CodeMapBuildAdmissionTests/testCodemapBuildAdmissionPreservesForegroundPriorityAndOwnerRoundRobin root Tests/RepoPromptTests/Services/FileSystem/CodeMapBuildAdmissionTests.swift RepoPromptTests.CodeMapBuildAdmissionTests testCodemapBuildAdmissionPreservesForegroundPriorityAndOwnerRoundRobin CodeMap codemap.build_admission.existing_scheduler_metadata foreground_precedence,owner_round_robin,owner_fifo,task_priority,bulk_accounting deterministic_concurrency root_swiftpm routine 2 ContentReadAsyncLimiter,CodeMapAdmissionGate,CodeMapAdmissionRecorder A content-search waiter queued after codemap builds is admitted first, then codemap requests alternate owners while preserving owner FIFO, execute at no less than requested task priority, and retain exact normal/bulk accounting. The admission seam could bypass foreground priority, discard owner identity, reorder one owner's work, ignore execution priority, or classify builds outside the established codemap counters. 0.057500 async_tasks,cancellation_gates per_test_limiter task_join+gate_release retain 0 Phase 5A owner and priority metadata contract root/RepoPromptTests.CodeMapBuildAdmissionTests/testProcessWideCodemapBuildAdmissionWaitsForForegroundActivityAndUsesRequestedPriority root Tests/RepoPromptTests/Services/FileSystem/CodeMapBuildAdmissionTests.swift RepoPromptTests.CodeMapBuildAdmissionTests testProcessWideCodemapBuildAdmissionWaitsForForegroundActivityAndUsesRequestedPriority CodeMap codemap.build_admission.process_wide_foreground_gate process_wide_singleton,root_load_token,task_priority,codemap_accounting deterministic_concurrency root_swiftpm routine 2 CodeMapAdmissionGate,CodeMapAdmissionSignal The process-wide wrapper queues without starting or granting during a real root-load foreground activity, then runs at no less than requested task priority after final token release and increments only the existing bulk/codemap grant accounting. A new build path could bypass the singleton limiter, run during foreground root work, lose priority context, or introduce separate counters and capacity. 0.011500 async_tasks,cancellation_gates process_wide_content_read_limiter task_join+explicit_token_cleanup retain 0 Phase 5A process-wide wrapper contract root/RepoPromptCodeMapCoreTests.CodeMapGoldenTests/testFixturesMatchGoldenCodeMapDescriptions root Tests/RepoPromptCodeMapCoreTests/CodeMapGoldenTests.swift RepoPromptCodeMapCoreTests.CodeMapGoldenTests testFixturesMatchGoldenCodeMapDescriptions CodeMap codemap.golden.artifact_renderer path_free_artifact,artifact_renderer,language_matrix,golden golden_snapshot codemap_core routine 13 c_smoke,cpp_edge_methods,cs_smoke,go_smoke,java_smoke,js_smoke,php_edge_namespaces,py_smoke,rb_smoke,rs_smoke,swift_smoke,ts_smoke,tsx_component Each practical-language fixture renders through the immutable artifact terminal and matches its committed golden. The artifact renderer could drift from current extraction/rendering or committed golden snapshots across the supported language matrix. 0.543500 tree_sitter,fixture_bundle syntax_manager test_case retain -1 Item 1 duplicate render removal; path-free artifact golden coverage across all 13 registered language fixtures including C#; Item 3 fixtures/goldens sole-owned by RepoPromptCodeMapCoreTests; Dart scenario removed alongside active Dart CodeMap support +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testJSTSSignatureNormalizerGeneratedASCIIEquivalenceAndCounters root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testJSTSSignatureNormalizerGeneratedASCIIEquivalenceAndCounters CodeMap codemap.query_optimization.jsts_signature_ascii_generated_equivalence javascript,typescript,tsx,signature_normalization,ascii_fast_path,deterministic_generated_corpus,legacy_equivalence,counter_accounting regression codemap_core fast 1 Two thousand deterministically generated ASCII signature strings exactly match the unchanged Character-based legacy authority while no-op and rewrite route accounting covers every input. The UTF-8 normalizer could diverge on punctuation, whitespace, or trailing-semicolon shapes or misattribute routed ASCII work. synthetic_source test_case retain 0 Iteration 1 bounded generated equivalence and route-accounting regression. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testJSTSSignatureNormalizerMatchesLegacyBehavior root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testJSTSSignatureNormalizerMatchesLegacyBehavior CodeMap codemap.query_optimization.jsts_signature_ascii_equivalence javascript,typescript,tsx,signature_normalization,ascii_fast_path,legacy_equivalence,whitespace,semicolon,lexical_blindness regression codemap_core fast 26 Twenty-six explicit ASCII shapes preserve lexical-blind whitespace collapse and exactly-one trailing-semicolon removal against the unchanged Character-based authority. The UTF-8 fast path could change normalized signatures for whitespace, syntax-shaped punctuation, literals, comments, templates, JSX, or repeated semicolons. test_case retain 0 Iteration 1 explicit ASCII normalization equivalence matrix. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testJSTSSignatureNormalizerRoutesSharedJavaScriptTypeScriptAndTSXPipeline root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testJSTSSignatureNormalizerRoutesSharedJavaScriptTypeScriptAndTSXPipeline CodeMap codemap.query_optimization.jsts_signature_pipeline_routes javascript,typescript,tsx,signature_normalization,pipeline_routing,counter_accounting regression codemap_core routine 3 JavaScript, TypeScript, and TSX pipeline builds exercise the shared extractor and account every normalization through exactly one ASCII or Unicode route while preserving successful artifacts. A language route could bypass normalization, double-count calls, or silently stop exercising the shared primitive. tree_sitter,synthetic_source syntax_manager test_case retain 0 Iteration 1 shared JS/TS/TSX execution and accounting regression. +root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testJSTSSignatureNormalizerUnicodeUsesLegacyFallback root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testJSTSSignatureNormalizerUnicodeUsesLegacyFallback CodeMap codemap.query_optimization.jsts_signature_unicode_fallback javascript,typescript,tsx,signature_normalization,unicode,legacy_fallback,legacy_equivalence,counter_accounting regression codemap_core fast 7 Seven non-ASCII identifier, type, spacing, and literal shapes exactly match the unchanged Character-based authority and route exclusively through Unicode fallback. Unicode input could be partially rewritten by the ASCII path or reported under the wrong mechanism route. test_case retain 0 Iteration 1 Unicode fallback and legacy-equivalence regression. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testNonSwiftReferencedTypeDuplicatesStillUseTypeCleanerCache root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testNonSwiftReferencedTypeDuplicatesStillUseTypeCleanerCache CodeMap codemap.query_optimization.non_swift_referenced_types_cache typescript,tsx,referenced_types,duplicate_input,type_cleaner_cache,swift_dedup_exclusion,counter_accounting regression codemap_core fast 2 TypeScript and TSX duplicate raw types preserve the same type set while the second insertion reuses the TypeCleaner cache, doubles output accounting, and leaves every Swift-only dedup counter at zero. Swift-only raw-type deduplication could leak into TypeScript or TSX, bypassing established TypeCleaner cache and output accounting behavior. test_case retain 0 Run 007 non-Swift cache-path and Swift-dedup exclusion regression. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testPreMaterializedSwiftAndTypeScriptGeneratorAttribution root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testPreMaterializedSwiftAndTypeScriptGeneratorAttribution CodeMap codemap.query_optimization.prematerialized_generator_attribution swift,typescript,tsx,pre_materialized_captures,generator_attribution,repeat_artifact_equality diagnostic codemap_core routine 3 Pre-materialized Swift, TypeScript, and TSX captures generate an identical artifact across repeated runs and expose generator-only capture-index and capture-loop timing attribution. Generator-only optimization could change artifact output or lose a deterministic attribution surface that separates query execution from extraction cost. tree_sitter,cpu_timing,synthetic_source syntax_manager test_case retain 0 Generator-only diagnostic timings are reported without machine-dependent thresholds. root/RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests/testStructuralFastPathsPreserveRoutingAndTypes root Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift RepoPromptCodeMapCoreTests.CodeMapQueryOptimizationBenchmarkTests testStructuralFastPathsPreserveRoutingAndTypes CodeMap codemap.query_optimization.structural_routing swift,typescript,tsx,query_capture_budget,duplicate_suppression regression codemap_core routine 3 Complex Swift declarations preserve bounded parameter/type extraction, while function-only TypeScript arrows are emitted only as functions and non-function initializer text containing arrows remains a global variable. Query slimming or duplicate suppression could lose Swift type detail, duplicate TypeScript arrows as globals, or suppress ordinary variables based on initializer text. 0.312000 tree_sitter,synthetic_source syntax_manager test_case retain 0 Query-first optimization correctness and attribution contract diff --git a/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift b/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift index d3ea1e5a2..f39839fdd 100644 --- a/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift +++ b/Sources/RepoPrompt/Features/CodeMap/CodeMapPerfStats.swift @@ -84,6 +84,8 @@ struct CodeMapPipelinePerfSnapshot: Equatable { var generatorFallbackFunctionSkippedDuration: TimeInterval = 0 var generatorDeclarationExtractionDuration: TimeInterval = 0 var generatorJSTSSignatureDuration: TimeInterval = 0 + var generatorJSTSNormalizationASCIIFastPathDuration: TimeInterval = 0 + var generatorJSTSNormalizationLegacyFallbackDuration: TimeInterval = 0 var generatorLanguageTypeExtractorFunctionDuration: TimeInterval = 0 var generatorLanguageTypeExtractorVariableDuration: TimeInterval = 0 var generatorTypeCleanerDuration: TimeInterval = 0 @@ -162,6 +164,9 @@ struct CodeMapPipelinePerfSnapshot: Equatable { var captureDeclarationCalls = 0 var jstsSignatureCallsFunctionLike = 0 var jstsSignatureCallsStatementLike = 0 + var jstsNormalizationASCIINoOpCount = 0 + var jstsNormalizationASCIIRewriteCount = 0 + var jstsNormalizationUnicodeFallbackCount = 0 var lteMatchAnyFunctionCalls = 0 var lteMatchAnyVariableCalls = 0 var typeCleanerExtractCalls = 0 @@ -313,6 +318,8 @@ final class CodeMapPipelinePerfStats: @unchecked Sendable { storage.generatorFallbackFunctionSkippedDuration += stats.fallbackFunctionSkippedDuration storage.generatorDeclarationExtractionDuration += stats.captureDeclarationDuration storage.generatorJSTSSignatureDuration += stats.jstsSignatureDuration + storage.generatorJSTSNormalizationASCIIFastPathDuration += stats.jstsNormalizationASCIIFastPathDuration + storage.generatorJSTSNormalizationLegacyFallbackDuration += stats.jstsNormalizationLegacyFallbackDuration storage.generatorLanguageTypeExtractorFunctionDuration += stats.languageTypeExtractorFunctionDuration storage.generatorLanguageTypeExtractorVariableDuration += stats.languageTypeExtractorVariableDuration storage.generatorTypeCleanerDuration += stats.typeCleanerDuration @@ -374,6 +381,9 @@ final class CodeMapPipelinePerfStats: @unchecked Sendable { storage.captureDeclarationCalls += stats.captureDeclarationCalls storage.jstsSignatureCallsFunctionLike += stats.jstsSignatureCallsFunctionLike storage.jstsSignatureCallsStatementLike += stats.jstsSignatureCallsStatementLike + storage.jstsNormalizationASCIINoOpCount += stats.jstsNormalizationASCIINoOpCount + storage.jstsNormalizationASCIIRewriteCount += stats.jstsNormalizationASCIIRewriteCount + storage.jstsNormalizationUnicodeFallbackCount += stats.jstsNormalizationUnicodeFallbackCount storage.lteMatchAnyFunctionCalls += stats.lteMatchAnyFunctionCalls storage.lteMatchAnyVariableCalls += stats.lteMatchAnyVariableCalls storage.typeCleanerExtractCalls += stats.typeCleanerExtractCalls diff --git a/Sources/RepoPromptCodeMapCore/Extraction/JSTSSignatureExtractor.swift b/Sources/RepoPromptCodeMapCore/Extraction/JSTSSignatureExtractor.swift index d126bfe05..1adb00c41 100644 --- a/Sources/RepoPromptCodeMapCore/Extraction/JSTSSignatureExtractor.swift +++ b/Sources/RepoPromptCodeMapCore/Extraction/JSTSSignatureExtractor.swift @@ -49,7 +49,11 @@ enum JSTSSignatureExtractor { case .statementLike: extractStatementSignature(line) } - let normalized = normalizeSingleLine(result) + let normalized = normalizeSingleLine( + result, + perfStats: perfStats, + perfOptions: perfOptions + ) if perfOptions.enabled { perfStats?.jstsSignatureDuration += (CFAbsoluteTimeGetCurrent() - start) } @@ -426,7 +430,98 @@ enum JSTSSignatureExtractor { return trimmed } - private static func normalizeSingleLine(_ line: String) -> String { + static func normalizeSingleLine( + _ line: String, + perfStats: CodeMapPerformanceCollector? = nil, + perfOptions: CodeMapPerfOptions = .disabled + ) -> String { + let asciiStart = perfOptions.enabled ? CFAbsoluteTimeGetCurrent() : 0 + var needsRewrite = false + var previousWasWhitespace = true + var asciiByteCount = 0 + var lastByte: UInt8? + + for byte in line.utf8 { + if byte >= 0x80 { + let legacyStart = perfOptions.enabled ? CFAbsoluteTimeGetCurrent() : 0 + let normalized = normalizeSingleLineLegacy(line) + if perfOptions.collectCounters { + perfStats?.jstsNormalizationUnicodeFallbackCount += 1 + } + if perfOptions.enabled { + perfStats?.jstsNormalizationLegacyFallbackDuration += + CFAbsoluteTimeGetCurrent() - legacyStart + } + return normalized + } + + asciiByteCount += 1 + lastByte = byte + let isWhitespace = byte == 0x20 || (byte >= 0x09 && byte <= 0x0D) + if isWhitespace { + if previousWasWhitespace || byte != 0x20 { + needsRewrite = true + } + previousWasWhitespace = true + } else { + previousWasWhitespace = false + } + } + + if asciiByteCount > 0, previousWasWhitespace { + needsRewrite = true + } + if lastByte == 0x3B { + needsRewrite = true + } + + guard needsRewrite else { + if perfOptions.collectCounters { + perfStats?.jstsNormalizationASCIINoOpCount += 1 + } + if perfOptions.enabled { + perfStats?.jstsNormalizationASCIIFastPathDuration += + CFAbsoluteTimeGetCurrent() - asciiStart + } + return line + } + + var normalizedUTF8: [UInt8] = [] + normalizedUTF8.reserveCapacity(asciiByteCount) + var pendingWhitespace = false + for byte in line.utf8 { + let isWhitespace = byte == 0x20 || (byte >= 0x09 && byte <= 0x0D) + if isWhitespace { + if !normalizedUTF8.isEmpty { + pendingWhitespace = true + } + } else { + if pendingWhitespace { + normalizedUTF8.append(0x20) + pendingWhitespace = false + } + normalizedUTF8.append(byte) + } + } + if normalizedUTF8.last == 0x3B { + normalizedUTF8.removeLast() + if normalizedUTF8.last == 0x20 { + normalizedUTF8.removeLast() + } + } + + let normalized = String(decoding: normalizedUTF8, as: UTF8.self) + if perfOptions.collectCounters { + perfStats?.jstsNormalizationASCIIRewriteCount += 1 + } + if perfOptions.enabled { + perfStats?.jstsNormalizationASCIIFastPathDuration += + CFAbsoluteTimeGetCurrent() - asciiStart + } + return normalized + } + + static func normalizeSingleLineLegacy(_ line: String) -> String { var out = "" out.reserveCapacity(line.count) var previousWasWhitespace = true diff --git a/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/TypeScriptCodeMapStrategy.swift b/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/TypeScriptCodeMapStrategy.swift index 0acfb6a86..bb20cd528 100644 --- a/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/TypeScriptCodeMapStrategy.swift +++ b/Sources/RepoPromptCodeMapCore/Extraction/LanguageStrategies/TypeScriptCodeMapStrategy.swift @@ -454,13 +454,12 @@ enum TypeScriptCodeMapStrategy { ) -> String { let activePerfStats = perfStats let activePerfOptions = perfOptions - let extracted = extractionMemo.jstsSignature( + return extractionMemo.jstsSignature( from: line, context: context, perfStats: activePerfStats, perfOptions: activePerfOptions ) - return extracted.trimmingCharacters(in: .whitespacesAndNewlines) } private static func stripTSInitializer(_ line: String) -> String { diff --git a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift index 23897b52a..1d770caa7 100644 --- a/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift +++ b/Sources/RepoPromptCodeMapCore/Performance/CodeMapPerformanceCollector.swift @@ -143,6 +143,9 @@ package final class CodeMapPerformanceCollector { package var captureDeclarationCalls = 0 package var jstsSignatureCallsFunctionLike = 0 package var jstsSignatureCallsStatementLike = 0 + package var jstsNormalizationASCIINoOpCount = 0 + package var jstsNormalizationASCIIRewriteCount = 0 + package var jstsNormalizationUnicodeFallbackCount = 0 // LanguageTypeExtractor package var lteMatchAnyFunctionCalls = 0 @@ -250,6 +253,8 @@ package final class CodeMapPerformanceCollector { package var fallbackFunctionSkippedDuration: TimeInterval = 0 package var captureDeclarationDuration: TimeInterval = 0 package var jstsSignatureDuration: TimeInterval = 0 + package var jstsNormalizationASCIIFastPathDuration: TimeInterval = 0 + package var jstsNormalizationLegacyFallbackDuration: TimeInterval = 0 package var languageTypeExtractorFunctionDuration: TimeInterval = 0 package var languageTypeExtractorVariableDuration: TimeInterval = 0 package var typeCleanerDuration: TimeInterval = 0 diff --git a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift index b8036c4b9..f5e90a593 100644 --- a/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift +++ b/Tests/RepoPromptCodeMapCoreTests/CodeMapQueryOptimizationBenchmarkTests.swift @@ -368,6 +368,196 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { #endif } + func testJSTSSignatureNormalizerMatchesLegacyBehavior() { + let cases: [(name: String, input: String, expected: String)] = [ + ("empty", "", ""), + ("already normalized", "function value(input: string): string", "function value(input: string): string"), + ("no whitespace", "constValue", "constValue"), + ("spaces", " const value: string ", "const value: string"), + ("tab", "const\tvalue: string", "const value: string"), + ("newline", "const\nvalue: string", "const value: string"), + ("vertical tab", "const\u{000B}value: string", "const value: string"), + ("form feed", "const\u{000C}value: string", "const value: string"), + ("carriage return", "const\rvalue: string", "const value: string"), + ("mixed whitespace", "\tconst \n\u{000B}\u{000C}\r value: string\r\n", "const value: string"), + ("one semicolon", "const value: string;", "const value: string"), + ("space before semicolon", "const value: string ;", "const value: string"), + ("two semicolons", "const value: string;;", "const value: string;"), + ("three semicolons", "const value: string;;;", "const value: string;;"), + ("semicolon only", ";", ""), + ("arrow", "const map = ( value: T ): U =>", "const map = ( value: T ): U =>"), + ("generic", "method(input: T): Promise", "method(input: T): Promise"), + ("object literal", "type Payload = { value: string; count: number };", "type Payload = { value: string; count: number }"), + ("union", "type State = Ready | Pending | Failed;", "type State = Ready | Pending | Failed"), + ("conditional", "type Pick = T extends string ? Text : Other;", "type Pick = T extends string ? Text : Other"), + ("template", "const value = `a b; ${item}`;", "const value = `a b; ${item}`"), + ("line comment", "const value = 1; // comment", "const value = 1; // comment"), + ("block comment", "const value = /* comment */ 1;", "const value = /* comment */ 1"), + ("JSX", "const view =
{value}
;", "const view =
{value}
"), + ("string default", #"function value(text = "a b;")"#, #"function value(text = "a b;")"#), + ("template default", "function value(text = `a b;`)", "function value(text = `a b;`)") + ] + + for testCase in cases { + let legacy = JSTSSignatureExtractor.normalizeSingleLineLegacy(testCase.input) + XCTAssertEqual(legacy, testCase.expected, "legacy: \(testCase.name)") + XCTAssertEqual( + JSTSSignatureExtractor.normalizeSingleLine(testCase.input), + legacy, + testCase.name + ) + } + } + + func testJSTSSignatureNormalizerUnicodeUsesLegacyFallback() { + let inputs = [ + "const café: Type = value;", + "const value: Café = item;", + "const\u{00A0}value: Type = item;", + "const\u{2003}value: Type = item;", + "const emoji = \"🙂 value;\";", + "前 const value: Type;", + "const value: Type; 後", + ] + let collector = CodeMapPerformanceCollector() + + for input in inputs { + XCTAssertEqual( + JSTSSignatureExtractor.normalizeSingleLine( + input, + perfStats: collector, + perfOptions: .countersOnly + ), + JSTSSignatureExtractor.normalizeSingleLineLegacy(input), + String(reflecting: input) + ) + } + + XCTAssertEqual(collector.jstsNormalizationASCIINoOpCount, 0) + XCTAssertEqual(collector.jstsNormalizationASCIIRewriteCount, 0) + XCTAssertEqual(collector.jstsNormalizationUnicodeFallbackCount, inputs.count) + XCTAssertGreaterThanOrEqual(collector.jstsNormalizationLegacyFallbackDuration, 0) + } + + func testJSTSSignatureNormalizerGeneratedASCIIEquivalenceAndCounters() { + let alphabet = Array("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_".utf8) + [ + 0x20, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, + 0x22, 0x27, 0x60, 0x2F, 0x5C, + 0x28, 0x29, 0x5B, 0x5D, 0x7B, 0x7D, 0x3C, 0x3E, + 0x3A, 0x3D, 0x2D, 0x2C, 0x2E, 0x3F, 0x21, 0x26, 0x7C, 0x3B, + ] + let caseCount = 2_000 + var state: UInt64 = 0xA5C1_17E5_51A6_0001 + let collector = CodeMapPerformanceCollector() + + func nextRandom() -> UInt64 { + state = state &* 6_364_136_223_846_793_005 &+ 1_442_695_040_888_963_407 + return state + } + + for caseIndex in 0 ..< caseCount { + let input: String + switch caseIndex { + case 0: + input = "function value(input: string): string" + case 1: + input = " function\tvalue( input: string ); " + default: + let length = Int(nextRandom() % 161) + var bytes: [UInt8] = [] + bytes.reserveCapacity(length) + for _ in 0 ..< length { + bytes.append(alphabet[Int(nextRandom() % UInt64(alphabet.count))]) + } + input = String(decoding: bytes, as: UTF8.self) + } + + XCTAssertEqual( + JSTSSignatureExtractor.normalizeSingleLine( + input, + perfStats: collector, + perfOptions: .countersOnly + ), + JSTSSignatureExtractor.normalizeSingleLineLegacy(input), + "generated case \(caseIndex): \(String(reflecting: input))" + ) + } + + XCTAssertGreaterThan(collector.jstsNormalizationASCIINoOpCount, 0) + XCTAssertGreaterThan(collector.jstsNormalizationASCIIRewriteCount, 0) + XCTAssertEqual(collector.jstsNormalizationUnicodeFallbackCount, 0) + XCTAssertEqual( + collector.jstsNormalizationASCIINoOpCount + + collector.jstsNormalizationASCIIRewriteCount, + caseCount + ) + XCTAssertGreaterThanOrEqual(collector.jstsNormalizationASCIIFastPathDuration, 0) + XCTAssertEqual(collector.jstsNormalizationLegacyFallbackDuration, 0) + } + + func testJSTSSignatureNormalizerRoutesSharedJavaScriptTypeScriptAndTSXPipeline() throws { + let cases: [(name: String, language: LanguageType, source: String)] = [ + ( + "javascript", + .js, + """ + export function run(value) { return value; } + export const payload = value; + """ + ), + ( + "typescript", + .ts, + """ + export interface Example { + value: string; + run(input: string): Promise; + } + export const transform = (input: string): string => input; + """ + ), + ( + "tsx", + .tsx, + """ + export interface Props { + title: string; + } + export function Card(props: Props): JSX.Element { + return
{props.title}
; + } + """ + ), + ] + + for testCase in cases { + let collector = CodeMapPerformanceCollector() + _ = try build( + source: testCase.source, + language: testCase.language, + options: .countersOnly, + collector: collector + ) + + let extractorCalls = collector.jstsSignatureCallsFunctionLike + + collector.jstsSignatureCallsStatementLike + let normalizationRoutes = collector.jstsNormalizationASCIINoOpCount + + collector.jstsNormalizationASCIIRewriteCount + + collector.jstsNormalizationUnicodeFallbackCount + XCTAssertGreaterThan(extractorCalls, 0, testCase.name) + XCTAssertEqual(normalizationRoutes, extractorCalls, testCase.name) + XCTAssertGreaterThan( + collector.jstsNormalizationASCIINoOpCount + + collector.jstsNormalizationASCIIRewriteCount, + 0, + testCase.name + ) + XCTAssertEqual(collector.jstsNormalizationUnicodeFallbackCount, 0, testCase.name) + XCTAssertGreaterThanOrEqual(collector.jstsNormalizationASCIIFastPathDuration, 0, testCase.name) + XCTAssertEqual(collector.jstsNormalizationLegacyFallbackDuration, 0, testCase.name) + } + } + func testSwiftSignatureWhitespaceNormalizerMatchesLegacyBehavior() { let cases: [(name: String, input: String)] = [ ("space run", "func value()"), @@ -1073,6 +1263,19 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { ) let repeatArtifactEquality = attributedArtifact == expectedArtifact XCTAssertTrue(repeatArtifactEquality) + if benchmark.language == .ts || benchmark.language == .tsx { + XCTAssertEqual( + collector.jstsNormalizationASCIINoOpCount + + collector.jstsNormalizationASCIIRewriteCount + + collector.jstsNormalizationUnicodeFallbackCount, + collector.jstsSignatureCallsFunctionLike + collector.jstsSignatureCallsStatementLike + ) + XCTAssertGreaterThan( + collector.jstsNormalizationASCIINoOpCount + + collector.jstsNormalizationASCIIRewriteCount, + 0 + ) + } var record = [ "CODEMAP_PREMATERIALIZED_GENERATOR_BENCHMARK", @@ -1126,6 +1329,19 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { XCTAssertTrue(repeatArtifactEquality) XCTAssertEqual(collector.syntaxQueryExecutes, 1) XCTAssertGreaterThan(collector.syntaxCaptures, 0) + if benchmark.language == .ts || benchmark.language == .tsx { + XCTAssertEqual( + collector.jstsNormalizationASCIINoOpCount + + collector.jstsNormalizationASCIIRewriteCount + + collector.jstsNormalizationUnicodeFallbackCount, + collector.jstsSignatureCallsFunctionLike + collector.jstsSignatureCallsStatementLike + ) + XCTAssertGreaterThan( + collector.jstsNormalizationASCIINoOpCount + + collector.jstsNormalizationASCIIRewriteCount, + 0 + ) + } var record = [ "CODEMAP_QUERY_BENCHMARK", @@ -1387,6 +1603,8 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { "capture_loop_ms=\(milliseconds(collector.captureLoopDuration))", "ts_loop_ms=\(milliseconds(collector.captureLoopTSStrategyDuration))", "jsts_ms=\(milliseconds(collector.jstsSignatureDuration))", + "jsts_normalization_ascii_ms=\(milliseconds(collector.jstsNormalizationASCIIFastPathDuration))", + "jsts_normalization_legacy_ms=\(milliseconds(collector.jstsNormalizationLegacyFallbackDuration))", "lte_function_ms=\(milliseconds(collector.languageTypeExtractorFunctionDuration))", "lte_variable_ms=\(milliseconds(collector.languageTypeExtractorVariableDuration))", "type_cleaner_ms=\(milliseconds(collector.typeCleanerDuration))", @@ -1398,6 +1616,9 @@ final class CodeMapQueryOptimizationBenchmarkTests: XCTestCase { "ts_strategy_handled=\(collector.tsStrategyHandled)", "jsts_function_calls=\(collector.jstsSignatureCallsFunctionLike)", "jsts_statement_calls=\(collector.jstsSignatureCallsStatementLike)", + "jsts_normalization_ascii_noop=\(collector.jstsNormalizationASCIINoOpCount)", + "jsts_normalization_ascii_rewrite=\(collector.jstsNormalizationASCIIRewriteCount)", + "jsts_normalization_unicode_fallback=\(collector.jstsNormalizationUnicodeFallbackCount)", "lte_function_calls=\(collector.lteMatchAnyFunctionCalls)", "lte_variable_calls=\(collector.lteMatchAnyVariableCalls)", "ts_duplicate_suppressions=\(collector.tsDuplicateFunctionVariableSuppressions)",