1use std::fmt;
4
5use crate::root_envelopes::{RootEnvelopeMode, serialize_named_json_output};
6use fallow_types::envelope::{ElapsedMs, ToolVersion};
7use serde::{Deserialize, Serialize};
8
9pub const SIMILAR_CODE_SCHEMA_VERSION: u32 = 1;
11pub const SIMILAR_CODE_INSPECT_SCHEMA_VERSION: u32 = 1;
13pub const SIMILAR_CODE_REVIEW_SCHEMA_VERSION: u32 = 1;
15pub const SIMILAR_CODE_STATUS_SCHEMA_VERSION: u32 = 1;
17pub const SIMILAR_CODE_CACHE_CLEAR_SCHEMA_VERSION: u32 = 1;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
22#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
23pub enum SimilarCodeSchemaVersion {
24 #[serde(rename = "1")]
26 V1,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
31#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
32pub enum SimilarCodeInspectSchemaVersion {
33 #[serde(rename = "1")]
35 V1,
36}
37
38#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
40#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
41pub enum SimilarCodeReviewSchemaVersion {
42 #[serde(rename = "1")]
44 V1,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
49#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
50pub enum SimilarCodeStatusSchemaVersion {
51 #[serde(rename = "1")]
53 V1,
54}
55
56#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
58#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
59pub enum SimilarCodeCacheClearSchemaVersion {
60 #[serde(rename = "1")]
62 V1,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
67#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
68pub struct SimilarCodeStatusOutput {
69 pub schema_version: SimilarCodeStatusSchemaVersion,
71 pub version: ToolVersion,
73 pub protocol_version: u32,
75 pub embedding_semantics_version: u32,
77 pub companion_version: String,
79 pub model_ready: bool,
81 pub model_id: String,
83 pub model_revision: String,
85 pub dimensions: u32,
87 pub max_tokens: u32,
89 pub license: String,
91 pub cache_dir: String,
93 pub download_bytes: u64,
95 pub analysis_offline: bool,
97 pub integrity_verified: bool,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub problem: Option<String>,
102 #[serde(default, skip_serializing_if = "Option::is_none")]
104 pub downloaded: Option<bool>,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
109#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
110pub struct SimilarCodeCacheClearOutput {
111 pub schema_version: SimilarCodeCacheClearSchemaVersion,
113 pub version: ToolVersion,
115 pub removed: bool,
117 pub model_removed: bool,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
123#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
124pub enum SimilarCodeVerdictSchemaVersion {
125 #[serde(rename = "1")]
127 V1,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
132#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
133pub struct SimilarCodeProviderProvenance {
134 pub provider: SimilarCodeProvider,
136 pub companion_version: String,
138 pub protocol_version: u32,
140 pub source_left_machine: bool,
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
146#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
147#[serde(rename_all = "kebab-case")]
148pub enum SimilarCodeProvider {
149 OfficialLocalCompanion,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
155#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
156pub struct SimilarCodeModelProvenance {
157 pub model_id: String,
159 pub revision: String,
161 pub artifact_sha256: String,
163 pub license: String,
165 pub dimensions: u32,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
171#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
172pub struct SimilarCodeGenerationParameters {
173 pub dtype: String,
175 pub pooling: String,
177 pub normalized: bool,
179 pub batch_size: u32,
181 pub max_tokens: u32,
183 pub parameter_sha256: String,
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
189#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
190pub struct SimilarCodeScopeProvenance {
191 pub active: bool,
193 pub paths: Vec<String>,
195}
196
197#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
199#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
200pub struct SimilarCodeGeneration {
201 pub extraction_semantics_version: u32,
203 pub embedding_semantics_version: u32,
205 pub provider: SimilarCodeProviderProvenance,
207 pub model: SimilarCodeModelProvenance,
209 pub parameters: SimilarCodeGenerationParameters,
211 pub scope: SimilarCodeScopeProvenance,
213 pub threshold: f64,
215 pub min_lines: u64,
217}
218
219#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
221#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
222pub struct SimilarCodeLocation {
223 pub path: String,
225 pub name: String,
227 pub start_line: u32,
229 pub start_column: u32,
231 pub end_line: u32,
233 pub end_column: u32,
235 pub source_sha256: String,
237}
238
239#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
241#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
242#[serde(rename_all = "kebab-case")]
243pub enum SimilarCodeSimilarityBand {
244 Moderate,
246 High,
248 VeryHigh,
250}
251
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
254#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
255#[serde(rename_all = "kebab-case")]
256pub enum SimilarCodeVerificationStatus {
257 Unverified,
259}
260
261#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
263#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
264#[serde(rename_all = "kebab-case")]
265pub enum SimilarCodeEnrichmentState {
266 Available,
268 Unavailable,
270 NotRequested,
272}
273
274#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
276#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
277pub struct SimilarCodeEnrichmentAvailability {
278 pub graph_relationship: SimilarCodeEnrichmentState,
280 pub entry_point_reachability: SimilarCodeEnrichmentState,
282 pub callers: SimilarCodeEnrichmentState,
284 pub callees: SimilarCodeEnrichmentState,
286 pub ownership: SimilarCodeEnrichmentState,
288 pub churn: SimilarCodeEnrichmentState,
290 pub tests: SimilarCodeEnrichmentState,
292 pub deterministic_clone_coverage: SimilarCodeEnrichmentState,
294 pub runtime: SimilarCodeEnrichmentState,
296}
297
298#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
300#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
301pub struct SimilarCodeAction {
302 pub action: SimilarCodeActionType,
304 pub description: String,
306 pub read_only: bool,
308}
309
310#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
312#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
313#[serde(rename_all = "kebab-case")]
314pub enum SimilarCodeActionType {
315 Inspect,
317 Review,
319}
320
321#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
323#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
324pub struct SimilarCodeCandidate {
325 pub candidate_id: String,
327 pub review_key: String,
329 pub left: SimilarCodeLocation,
331 pub right: SimilarCodeLocation,
333 pub similarity: f64,
335 pub similarity_band: SimilarCodeSimilarityBand,
337 pub verification_status: SimilarCodeVerificationStatus,
339 pub enrichment: SimilarCodeEnrichmentAvailability,
341 pub actions: Vec<SimilarCodeAction>,
343}
344
345#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
347#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
348#[serde(rename_all = "kebab-case")]
349pub enum SimilarCodePhase {
350 Discovery,
352 Extraction,
354 Cache,
356 Embedding,
358 Validation,
360 Comparison,
362 Enrichment,
364}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
368#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
369#[serde(rename_all = "kebab-case")]
370pub enum SimilarCodePhaseStatus {
371 Complete,
373 Partial,
375 Skipped,
377 TimedOut,
379}
380
381#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
383#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
384pub struct SimilarCodePhaseCompletion {
385 pub phase: SimilarCodePhase,
387 pub status: SimilarCodePhaseStatus,
389 pub processed: u64,
391 pub total: Option<u64>,
393 pub reason: Option<String>,
395}
396
397#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
399#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
400pub struct SimilarCodeLimits {
401 pub max_files: u64,
403 pub max_functions: u64,
405 pub max_source_bytes: u64,
407 pub max_function_bytes: u64,
409 pub max_batch_size: u64,
411 pub max_vector_bytes: u64,
413 pub max_comparisons: u64,
415 pub max_candidates: u64,
417 pub max_neighbors_per_function: u64,
419 pub timeout_ms: u64,
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
425#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
426#[serde(rename_all = "kebab-case")]
427pub enum SimilarCodeSkipReason {
428 BelowMinimumLines,
430 UnsupportedFunction,
432 GeneratedSource,
434 FunctionTooLarge,
436 InputLimit,
438 SourceBytesLimit,
440 VectorMemoryLimit,
442 ComparisonLimit,
444 CandidateLimit,
446 NeighborLimit,
448 Timeout,
450 ProviderFailure,
452 TokenTruncation,
454 EnrichmentUnavailable,
456}
457
458#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
460#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
461pub struct SimilarCodeSkip {
462 pub phase: SimilarCodePhase,
464 pub reason: SimilarCodeSkipReason,
466 pub count: u64,
468}
469
470#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
472#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
473#[serde(rename_all = "kebab-case")]
474pub enum SimilarCodeCacheStatus {
475 Disabled,
477 Hit,
479 Miss,
481 Mixed,
483}
484
485#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
487#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
488pub struct SimilarCodeCacheSummary {
489 pub status: SimilarCodeCacheStatus,
491 pub hits: u64,
493 pub misses: u64,
495 pub writes: u64,
497 pub invalid_entries: u64,
499}
500
501#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
503#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
504#[serde(rename_all = "kebab-case")]
505pub enum SimilarCodeCompletionStatus {
506 Complete,
508 Partial,
510}
511
512#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
514#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
515pub struct SimilarCodeCompletion {
516 pub status: SimilarCodeCompletionStatus,
518 pub phases: Vec<SimilarCodePhaseCompletion>,
520 pub limits: SimilarCodeLimits,
522 pub skips: Vec<SimilarCodeSkip>,
524 pub cache: SimilarCodeCacheSummary,
526 pub provider_inference_ms: u64,
528}
529
530#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
532#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
533#[serde(rename_all = "kebab-case")]
534pub enum SimilarCodeDiagnosticDomain {
535 Workspace,
537 Extraction,
539 Provider,
541 Cache,
543 Enrichment,
545 Review,
547}
548
549#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
551#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
552pub struct SimilarCodeDiagnostic {
553 pub domain: SimilarCodeDiagnosticDomain,
555 pub code: String,
557 pub message: String,
559 pub path: Option<String>,
561}
562
563#[derive(Debug, Clone, Serialize)]
565#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
566pub struct SimilarCodeOutput {
567 pub schema_version: SimilarCodeSchemaVersion,
569 pub version: ToolVersion,
571 pub elapsed_ms: ElapsedMs,
573 pub generation: SimilarCodeGeneration,
575 pub candidates: Vec<SimilarCodeCandidate>,
577 pub completion: SimilarCodeCompletion,
579 pub diagnostics: Vec<SimilarCodeDiagnostic>,
581}
582
583#[derive(Deserialize)]
584#[serde(deny_unknown_fields)]
585struct SimilarCodeOutputWire {
586 schema_version: SimilarCodeSchemaVersion,
587 version: String,
588 elapsed_ms: u64,
589 generation: SimilarCodeGeneration,
590 candidates: Vec<SimilarCodeCandidate>,
591 completion: SimilarCodeCompletion,
592 diagnostics: Vec<SimilarCodeDiagnostic>,
593}
594
595impl<'de> Deserialize<'de> for SimilarCodeOutput {
596 fn deserialize<Deserializer>(deserializer: Deserializer) -> Result<Self, Deserializer::Error>
597 where
598 Deserializer: serde::Deserializer<'de>,
599 {
600 let wire = SimilarCodeOutputWire::deserialize(deserializer)?;
601 Ok(Self {
602 schema_version: wire.schema_version,
603 version: ToolVersion(wire.version),
604 elapsed_ms: ElapsedMs(wire.elapsed_ms),
605 generation: wire.generation,
606 candidates: wire.candidates,
607 completion: wire.completion,
608 diagnostics: wire.diagnostics,
609 })
610 }
611}
612
613#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
616#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
617#[serde(deny_unknown_fields)]
618pub struct SimilarCodeCandidateSnapshot {
619 pub schema_version: SimilarCodeSchemaVersion,
621 pub generation: SimilarCodeGeneration,
623 pub candidate: SimilarCodeCandidate,
625 pub completion: SimilarCodeCompletion,
627 pub diagnostics: Vec<SimilarCodeDiagnostic>,
629}
630
631#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
633#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
634pub struct SimilarCodeNamedReference {
635 pub path: String,
637 pub name: String,
639 pub line: u32,
641}
642
643#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
645#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
646#[serde(rename_all = "kebab-case")]
647pub enum SimilarCodeSideEffectHint {
648 PureLooking,
650 MayHaveSideEffects,
652 Unknown,
654}
655
656#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
658#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
659pub struct SimilarCodeSideEvidence {
660 pub source_window: Option<String>,
662 pub parameter_count: Option<u32>,
664 pub is_async: Option<bool>,
666 pub is_generator: Option<bool>,
668 pub has_await: Option<bool>,
670 pub has_throw: Option<bool>,
672 pub side_effect_hint: Option<SimilarCodeSideEffectHint>,
674 pub entry_point_reachable: Option<bool>,
676 pub callers: Vec<SimilarCodeNamedReference>,
678 pub callees: Vec<SimilarCodeNamedReference>,
680 pub owners: Vec<String>,
682 pub churn_commits: Option<u64>,
684 pub tests: Vec<String>,
686 pub deterministic_clone_coverage: Option<f64>,
688 pub runtime_observations: Option<u64>,
690}
691
692#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
694#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
695pub struct SimilarCodeInspectPacket {
696 pub candidate_id: String,
698 pub review_key: String,
700 pub availability: SimilarCodeEnrichmentAvailability,
702 pub graph_relationship: Option<String>,
704 pub left: SimilarCodeSideEvidence,
706 pub right: SimilarCodeSideEvidence,
708}
709
710#[derive(Debug, Clone, Serialize)]
712#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
713pub struct SimilarCodeInspectOutput {
714 pub schema_version: SimilarCodeInspectSchemaVersion,
716 pub version: ToolVersion,
718 pub elapsed_ms: ElapsedMs,
720 pub generation: SimilarCodeGeneration,
722 pub candidate: SimilarCodeCandidate,
724 pub packet: SimilarCodeInspectPacket,
726 pub completion: SimilarCodeCompletion,
728 pub diagnostics: Vec<SimilarCodeDiagnostic>,
730}
731
732#[derive(Deserialize)]
733#[serde(deny_unknown_fields)]
734struct SimilarCodeInspectOutputWire {
735 schema_version: SimilarCodeInspectSchemaVersion,
736 version: String,
737 elapsed_ms: u64,
738 generation: SimilarCodeGeneration,
739 candidate: SimilarCodeCandidate,
740 packet: SimilarCodeInspectPacket,
741 completion: SimilarCodeCompletion,
742 diagnostics: Vec<SimilarCodeDiagnostic>,
743}
744
745impl<'de> Deserialize<'de> for SimilarCodeInspectOutput {
746 fn deserialize<Deserializer>(deserializer: Deserializer) -> Result<Self, Deserializer::Error>
747 where
748 Deserializer: serde::Deserializer<'de>,
749 {
750 let wire = SimilarCodeInspectOutputWire::deserialize(deserializer)?;
751 Ok(Self {
752 schema_version: wire.schema_version,
753 version: ToolVersion(wire.version),
754 elapsed_ms: ElapsedMs(wire.elapsed_ms),
755 generation: wire.generation,
756 candidate: wire.candidate,
757 packet: wire.packet,
758 completion: wire.completion,
759 diagnostics: wire.diagnostics,
760 })
761 }
762}
763
764#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
766#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
767#[serde(deny_unknown_fields)]
768pub struct SimilarCodeVerdict {
769 pub candidate_id: String,
771 pub review_key: String,
773 pub candidate_worthy: Option<bool>,
775 pub behaviorally_equivalent: Option<bool>,
777 pub refactor_safe: Option<bool>,
779 pub outcome: SimilarCodeDomainOutcome,
781 pub rationale: String,
783}
784
785impl SimilarCodeVerdict {
786 pub fn validate(&self) -> Result<(), SimilarCodeVerdictValidationError> {
793 if self.refactor_safe == Some(true) && self.behaviorally_equivalent != Some(true) {
794 return Err(SimilarCodeVerdictValidationError::RefactorSafetyRequiresEquivalence);
795 }
796 if self.behaviorally_equivalent == Some(true) && self.candidate_worthy != Some(true) {
797 return Err(SimilarCodeVerdictValidationError::EquivalenceRequiresCandidate);
798 }
799 Ok(())
800 }
801}
802
803#[derive(Debug, Clone, Copy, PartialEq, Eq)]
805pub enum SimilarCodeVerdictValidationError {
806 RefactorSafetyRequiresEquivalence,
808 EquivalenceRequiresCandidate,
810}
811
812impl fmt::Display for SimilarCodeVerdictValidationError {
813 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
814 match self {
815 Self::RefactorSafetyRequiresEquivalence => {
816 formatter.write_str("refactor_safe=true requires behaviorally_equivalent=true")
817 }
818 Self::EquivalenceRequiresCandidate => {
819 formatter.write_str("behaviorally_equivalent=true requires candidate_worthy=true")
820 }
821 }
822 }
823}
824
825impl std::error::Error for SimilarCodeVerdictValidationError {}
826
827#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
829#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
830#[serde(rename_all = "kebab-case")]
831pub enum SimilarCodeDomainOutcome {
832 SameResponsibility,
834 RelatedButDistinct,
836 IntentionalDuplication,
838 Unrelated,
840 NeedsHumanReview,
842}
843
844#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
846#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
847#[serde(deny_unknown_fields)]
848pub struct SimilarCodeVerdictInput {
849 pub schema_version: SimilarCodeVerdictSchemaVersion,
851 pub verdicts: Vec<SimilarCodeVerdict>,
853}
854
855#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
857#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
858#[serde(rename_all = "kebab-case")]
859pub enum SimilarCodeVerdictMatch {
860 CandidateId,
862 ReviewKey,
864 Unverified,
866 AmbiguousReviewKey,
868}
869
870#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
872#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
873pub struct SimilarCodeReviewedCandidate {
874 pub candidate: SimilarCodeCandidate,
876 pub verdict: Option<SimilarCodeVerdict>,
878 pub verdict_match: SimilarCodeVerdictMatch,
880 pub outcome: SimilarCodeDomainOutcome,
882}
883
884#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
886#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
887pub struct SimilarCodeReviewProvenance {
888 pub candidates_sha256: String,
890 pub verdicts_sha256: String,
892}
893
894#[derive(Debug, Clone, Serialize)]
896#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
897pub struct SimilarCodeReviewOutput {
898 pub schema_version: SimilarCodeReviewSchemaVersion,
900 pub version: ToolVersion,
902 pub elapsed_ms: ElapsedMs,
904 pub generation: SimilarCodeGeneration,
906 pub review: SimilarCodeReviewProvenance,
908 pub candidates: Vec<SimilarCodeReviewedCandidate>,
910 pub completion: SimilarCodeCompletion,
912 pub diagnostics: Vec<SimilarCodeDiagnostic>,
914}
915
916#[derive(Deserialize)]
917#[serde(deny_unknown_fields)]
918struct SimilarCodeReviewOutputWire {
919 schema_version: SimilarCodeReviewSchemaVersion,
920 version: String,
921 elapsed_ms: u64,
922 generation: SimilarCodeGeneration,
923 review: SimilarCodeReviewProvenance,
924 candidates: Vec<SimilarCodeReviewedCandidate>,
925 completion: SimilarCodeCompletion,
926 diagnostics: Vec<SimilarCodeDiagnostic>,
927}
928
929impl<'de> Deserialize<'de> for SimilarCodeReviewOutput {
930 fn deserialize<Deserializer>(deserializer: Deserializer) -> Result<Self, Deserializer::Error>
931 where
932 Deserializer: serde::Deserializer<'de>,
933 {
934 let wire = SimilarCodeReviewOutputWire::deserialize(deserializer)?;
935 Ok(Self {
936 schema_version: wire.schema_version,
937 version: ToolVersion(wire.version),
938 elapsed_ms: ElapsedMs(wire.elapsed_ms),
939 generation: wire.generation,
940 review: wire.review,
941 candidates: wire.candidates,
942 completion: wire.completion,
943 diagnostics: wire.diagnostics,
944 })
945 }
946}
947
948pub fn serialize_similar_code_json_output(
954 output: SimilarCodeOutput,
955 mode: RootEnvelopeMode,
956) -> Result<serde_json::Value, serde_json::Error> {
957 serialize_named_json_output(output, "similar-code", mode)
958}
959
960pub fn serialize_similar_code_inspect_json_output(
966 output: SimilarCodeInspectOutput,
967 mode: RootEnvelopeMode,
968) -> Result<serde_json::Value, serde_json::Error> {
969 serialize_named_json_output(output, "similar-code-inspect", mode)
970}
971
972pub fn serialize_similar_code_review_json_output(
978 output: SimilarCodeReviewOutput,
979 mode: RootEnvelopeMode,
980) -> Result<serde_json::Value, serde_json::Error> {
981 serialize_named_json_output(output, "similar-code-review", mode)
982}
983
984pub fn serialize_similar_code_status_json_output(
990 output: SimilarCodeStatusOutput,
991 mode: RootEnvelopeMode,
992) -> Result<serde_json::Value, serde_json::Error> {
993 serialize_named_json_output(output, "similar-code-status", mode)
994}
995
996pub fn serialize_similar_code_cache_clear_json_output(
1002 output: SimilarCodeCacheClearOutput,
1003 mode: RootEnvelopeMode,
1004) -> Result<serde_json::Value, serde_json::Error> {
1005 serialize_named_json_output(output, "similar-code-cache-clear", mode)
1006}
1007
1008#[cfg(test)]
1009mod tests {
1010 use super::*;
1011
1012 #[test]
1013 fn verdict_axes_remain_independent_but_enforce_implication() {
1014 let verdict = SimilarCodeVerdict {
1015 candidate_id: "sc_123".to_string(),
1016 review_key: "scr_456".to_string(),
1017 candidate_worthy: Some(true),
1018 behaviorally_equivalent: Some(false),
1019 refactor_safe: Some(true),
1020 outcome: SimilarCodeDomainOutcome::RelatedButDistinct,
1021 rationale: "Same domain, different behavior.".to_string(),
1022 };
1023
1024 assert_eq!(
1025 verdict.validate(),
1026 Err(SimilarCodeVerdictValidationError::RefactorSafetyRequiresEquivalence)
1027 );
1028 }
1029
1030 #[test]
1031 fn raw_serializer_adds_only_the_similar_code_kind() {
1032 let output = raw_output();
1033
1034 let value = serialize_similar_code_json_output(output, RootEnvelopeMode::Tagged)
1035 .expect("similar-code output should serialize");
1036
1037 assert_eq!(value["kind"], "similar-code");
1038 assert_eq!(value["schema_version"], "1");
1039 assert!(value.get("severity").is_none());
1040 assert!(value.get("gate").is_none());
1041 assert!(value.get("fixes").is_none());
1042 }
1043
1044 #[test]
1045 fn raw_output_round_trips_for_review_input() {
1046 let value = serde_json::to_value(raw_output()).expect("raw output should serialize");
1047 let decoded: SimilarCodeOutput =
1048 serde_json::from_value(value).expect("raw output should deserialize");
1049
1050 assert_eq!(decoded.schema_version, SimilarCodeSchemaVersion::V1);
1051 assert_eq!(decoded.version.0, "3.9.0");
1052 assert_eq!(
1053 decoded.completion.status,
1054 SimilarCodeCompletionStatus::Complete
1055 );
1056 }
1057
1058 #[test]
1059 fn verdict_input_rejects_unknown_fields() {
1060 let value = serde_json::json!({
1061 "schema_version": "1",
1062 "verdicts": [],
1063 "unexpected": true
1064 });
1065
1066 assert!(serde_json::from_value::<SimilarCodeVerdictInput>(value).is_err());
1067 }
1068
1069 #[test]
1070 fn review_preserves_null_judgments() {
1071 let verdict = SimilarCodeVerdict {
1072 candidate_id: "sc_123".to_string(),
1073 review_key: "scr_456".to_string(),
1074 candidate_worthy: None,
1075 behaviorally_equivalent: None,
1076 refactor_safe: None,
1077 outcome: SimilarCodeDomainOutcome::NeedsHumanReview,
1078 rationale: "Insufficient evidence.".to_string(),
1079 };
1080
1081 let value = serde_json::to_value(verdict).expect("verdict should serialize");
1082 assert!(value["candidate_worthy"].is_null());
1083 assert!(value["behaviorally_equivalent"].is_null());
1084 assert!(value["refactor_safe"].is_null());
1085 }
1086
1087 fn generation() -> SimilarCodeGeneration {
1088 SimilarCodeGeneration {
1089 extraction_semantics_version: 1,
1090 embedding_semantics_version: 1,
1091 provider: SimilarCodeProviderProvenance {
1092 provider: SimilarCodeProvider::OfficialLocalCompanion,
1093 companion_version: "3.9.0".to_string(),
1094 protocol_version: 2,
1095 source_left_machine: false,
1096 },
1097 model: SimilarCodeModelProvenance {
1098 model_id: "example/model".to_string(),
1099 revision: "immutable-revision".to_string(),
1100 artifact_sha256: "abc".to_string(),
1101 license: "Apache-2.0".to_string(),
1102 dimensions: 384,
1103 },
1104 parameters: SimilarCodeGenerationParameters {
1105 dtype: "fp32".to_string(),
1106 pooling: "mean".to_string(),
1107 normalized: true,
1108 batch_size: 8,
1109 max_tokens: 1024,
1110 parameter_sha256: "def".to_string(),
1111 },
1112 scope: SimilarCodeScopeProvenance {
1113 active: true,
1114 paths: vec!["src/a.ts".to_string()],
1115 },
1116 threshold: 0.8,
1117 min_lines: 3,
1118 }
1119 }
1120
1121 fn raw_output() -> SimilarCodeOutput {
1122 SimilarCodeOutput {
1123 schema_version: SimilarCodeSchemaVersion::V1,
1124 version: ToolVersion("3.9.0".to_string()),
1125 elapsed_ms: ElapsedMs(4),
1126 generation: generation(),
1127 candidates: Vec::new(),
1128 completion: completion(),
1129 diagnostics: Vec::new(),
1130 }
1131 }
1132
1133 fn completion() -> SimilarCodeCompletion {
1134 SimilarCodeCompletion {
1135 status: SimilarCodeCompletionStatus::Complete,
1136 phases: Vec::new(),
1137 limits: SimilarCodeLimits {
1138 max_files: 10,
1139 max_functions: 100,
1140 max_source_bytes: 1_000_000,
1141 max_function_bytes: 10_000,
1142 max_batch_size: 8,
1143 max_vector_bytes: 1_000_000,
1144 max_comparisons: 1_000,
1145 max_candidates: 10,
1146 max_neighbors_per_function: 3,
1147 timeout_ms: 30_000,
1148 },
1149 skips: Vec::new(),
1150 cache: SimilarCodeCacheSummary {
1151 status: SimilarCodeCacheStatus::Miss,
1152 hits: 0,
1153 misses: 0,
1154 writes: 0,
1155 invalid_entries: 0,
1156 },
1157 provider_inference_ms: 0,
1158 }
1159 }
1160}