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#[cfg(feature = "schema")]
632impl SimilarCodeCandidateSnapshot {
633 #[must_use]
638 pub fn json_schema() -> serde_json::Value {
639 serde_json::to_value(schemars::schema_for!(SimilarCodeCandidateSnapshot))
640 .unwrap_or_default()
641 }
642}
643
644#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
646#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
647pub struct SimilarCodeNamedReference {
648 pub path: String,
650 pub name: String,
652 pub line: u32,
654}
655
656#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
658#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
659#[serde(rename_all = "kebab-case")]
660pub enum SimilarCodeSideEffectHint {
661 PureLooking,
663 MayHaveSideEffects,
665 Unknown,
667}
668
669#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
671#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
672pub struct SimilarCodeSideEvidence {
673 pub source_window: Option<String>,
675 pub parameter_count: Option<u32>,
677 pub is_async: Option<bool>,
679 pub is_generator: Option<bool>,
681 pub has_await: Option<bool>,
683 pub has_throw: Option<bool>,
685 pub side_effect_hint: Option<SimilarCodeSideEffectHint>,
687 pub entry_point_reachable: Option<bool>,
689 pub callers: Vec<SimilarCodeNamedReference>,
691 pub callees: Vec<SimilarCodeNamedReference>,
693 pub owners: Vec<String>,
695 pub churn_commits: Option<u64>,
697 pub tests: Vec<String>,
699 pub deterministic_clone_coverage: Option<f64>,
701 pub runtime_observations: Option<u64>,
703}
704
705#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
707#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
708pub struct SimilarCodeInspectPacket {
709 pub candidate_id: String,
711 pub review_key: String,
713 pub availability: SimilarCodeEnrichmentAvailability,
715 pub graph_relationship: Option<String>,
717 pub left: SimilarCodeSideEvidence,
719 pub right: SimilarCodeSideEvidence,
721}
722
723#[derive(Debug, Clone, Serialize)]
725#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
726pub struct SimilarCodeInspectOutput {
727 pub schema_version: SimilarCodeInspectSchemaVersion,
729 pub version: ToolVersion,
731 pub elapsed_ms: ElapsedMs,
733 pub generation: SimilarCodeGeneration,
735 pub candidate: SimilarCodeCandidate,
737 pub packet: SimilarCodeInspectPacket,
739 pub completion: SimilarCodeCompletion,
741 pub diagnostics: Vec<SimilarCodeDiagnostic>,
743}
744
745#[derive(Deserialize)]
746#[serde(deny_unknown_fields)]
747struct SimilarCodeInspectOutputWire {
748 schema_version: SimilarCodeInspectSchemaVersion,
749 version: String,
750 elapsed_ms: u64,
751 generation: SimilarCodeGeneration,
752 candidate: SimilarCodeCandidate,
753 packet: SimilarCodeInspectPacket,
754 completion: SimilarCodeCompletion,
755 diagnostics: Vec<SimilarCodeDiagnostic>,
756}
757
758impl<'de> Deserialize<'de> for SimilarCodeInspectOutput {
759 fn deserialize<Deserializer>(deserializer: Deserializer) -> Result<Self, Deserializer::Error>
760 where
761 Deserializer: serde::Deserializer<'de>,
762 {
763 let wire = SimilarCodeInspectOutputWire::deserialize(deserializer)?;
764 Ok(Self {
765 schema_version: wire.schema_version,
766 version: ToolVersion(wire.version),
767 elapsed_ms: ElapsedMs(wire.elapsed_ms),
768 generation: wire.generation,
769 candidate: wire.candidate,
770 packet: wire.packet,
771 completion: wire.completion,
772 diagnostics: wire.diagnostics,
773 })
774 }
775}
776
777#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
779#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
780#[serde(deny_unknown_fields)]
781pub struct SimilarCodeVerdict {
782 pub candidate_id: String,
784 pub review_key: String,
786 pub candidate_worthy: Option<bool>,
788 pub behaviorally_equivalent: Option<bool>,
790 pub refactor_safe: Option<bool>,
792 pub outcome: SimilarCodeDomainOutcome,
794 pub rationale: String,
796}
797
798impl SimilarCodeVerdict {
799 pub fn validate(&self) -> Result<(), SimilarCodeVerdictValidationError> {
806 if self.refactor_safe == Some(true) && self.behaviorally_equivalent != Some(true) {
807 return Err(SimilarCodeVerdictValidationError::RefactorSafetyRequiresEquivalence);
808 }
809 if self.behaviorally_equivalent == Some(true) && self.candidate_worthy != Some(true) {
810 return Err(SimilarCodeVerdictValidationError::EquivalenceRequiresCandidate);
811 }
812 Ok(())
813 }
814}
815
816#[derive(Debug, Clone, Copy, PartialEq, Eq)]
818pub enum SimilarCodeVerdictValidationError {
819 RefactorSafetyRequiresEquivalence,
821 EquivalenceRequiresCandidate,
823}
824
825impl fmt::Display for SimilarCodeVerdictValidationError {
826 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
827 match self {
828 Self::RefactorSafetyRequiresEquivalence => {
829 formatter.write_str("refactor_safe=true requires behaviorally_equivalent=true")
830 }
831 Self::EquivalenceRequiresCandidate => {
832 formatter.write_str("behaviorally_equivalent=true requires candidate_worthy=true")
833 }
834 }
835 }
836}
837
838impl std::error::Error for SimilarCodeVerdictValidationError {}
839
840#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
842#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
843#[serde(rename_all = "kebab-case")]
844pub enum SimilarCodeDomainOutcome {
845 SameResponsibility,
847 RelatedButDistinct,
849 IntentionalDuplication,
851 Unrelated,
853 NeedsHumanReview,
855}
856
857#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
859#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
860#[serde(deny_unknown_fields)]
861pub struct SimilarCodeVerdictInput {
862 pub schema_version: SimilarCodeVerdictSchemaVersion,
864 pub verdicts: Vec<SimilarCodeVerdict>,
866}
867
868#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
870#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
871#[serde(rename_all = "kebab-case")]
872pub enum SimilarCodeVerdictMatch {
873 CandidateId,
875 ReviewKey,
877 Unverified,
879 AmbiguousReviewKey,
881}
882
883#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
885#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
886pub struct SimilarCodeReviewedCandidate {
887 pub candidate: SimilarCodeCandidate,
889 pub verdict: Option<SimilarCodeVerdict>,
891 pub verdict_match: SimilarCodeVerdictMatch,
893 pub outcome: SimilarCodeDomainOutcome,
895}
896
897#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
899#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
900pub struct SimilarCodeReviewProvenance {
901 pub candidates_sha256: String,
903 pub verdicts_sha256: String,
905}
906
907#[derive(Debug, Clone, Serialize)]
909#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
910pub struct SimilarCodeReviewOutput {
911 pub schema_version: SimilarCodeReviewSchemaVersion,
913 pub version: ToolVersion,
915 pub elapsed_ms: ElapsedMs,
917 pub generation: SimilarCodeGeneration,
919 pub review: SimilarCodeReviewProvenance,
921 pub candidates: Vec<SimilarCodeReviewedCandidate>,
923 pub completion: SimilarCodeCompletion,
925 pub diagnostics: Vec<SimilarCodeDiagnostic>,
927}
928
929#[derive(Deserialize)]
930#[serde(deny_unknown_fields)]
931struct SimilarCodeReviewOutputWire {
932 schema_version: SimilarCodeReviewSchemaVersion,
933 version: String,
934 elapsed_ms: u64,
935 generation: SimilarCodeGeneration,
936 review: SimilarCodeReviewProvenance,
937 candidates: Vec<SimilarCodeReviewedCandidate>,
938 completion: SimilarCodeCompletion,
939 diagnostics: Vec<SimilarCodeDiagnostic>,
940}
941
942impl<'de> Deserialize<'de> for SimilarCodeReviewOutput {
943 fn deserialize<Deserializer>(deserializer: Deserializer) -> Result<Self, Deserializer::Error>
944 where
945 Deserializer: serde::Deserializer<'de>,
946 {
947 let wire = SimilarCodeReviewOutputWire::deserialize(deserializer)?;
948 Ok(Self {
949 schema_version: wire.schema_version,
950 version: ToolVersion(wire.version),
951 elapsed_ms: ElapsedMs(wire.elapsed_ms),
952 generation: wire.generation,
953 review: wire.review,
954 candidates: wire.candidates,
955 completion: wire.completion,
956 diagnostics: wire.diagnostics,
957 })
958 }
959}
960
961pub fn serialize_similar_code_json_output(
967 output: SimilarCodeOutput,
968 mode: RootEnvelopeMode,
969) -> Result<serde_json::Value, serde_json::Error> {
970 serialize_named_json_output(output, "similar-code", mode)
971}
972
973pub fn serialize_similar_code_inspect_json_output(
979 output: SimilarCodeInspectOutput,
980 mode: RootEnvelopeMode,
981) -> Result<serde_json::Value, serde_json::Error> {
982 serialize_named_json_output(output, "similar-code-inspect", mode)
983}
984
985pub fn serialize_similar_code_review_json_output(
991 output: SimilarCodeReviewOutput,
992 mode: RootEnvelopeMode,
993) -> Result<serde_json::Value, serde_json::Error> {
994 serialize_named_json_output(output, "similar-code-review", mode)
995}
996
997pub fn serialize_similar_code_status_json_output(
1003 output: SimilarCodeStatusOutput,
1004 mode: RootEnvelopeMode,
1005) -> Result<serde_json::Value, serde_json::Error> {
1006 serialize_named_json_output(output, "similar-code-status", mode)
1007}
1008
1009pub fn serialize_similar_code_cache_clear_json_output(
1015 output: SimilarCodeCacheClearOutput,
1016 mode: RootEnvelopeMode,
1017) -> Result<serde_json::Value, serde_json::Error> {
1018 serialize_named_json_output(output, "similar-code-cache-clear", mode)
1019}
1020
1021#[cfg(test)]
1022mod tests {
1023 use super::*;
1024
1025 #[test]
1026 fn verdict_axes_remain_independent_but_enforce_implication() {
1027 let verdict = SimilarCodeVerdict {
1028 candidate_id: "sc_123".to_string(),
1029 review_key: "scr_456".to_string(),
1030 candidate_worthy: Some(true),
1031 behaviorally_equivalent: Some(false),
1032 refactor_safe: Some(true),
1033 outcome: SimilarCodeDomainOutcome::RelatedButDistinct,
1034 rationale: "Same domain, different behavior.".to_string(),
1035 };
1036
1037 assert_eq!(
1038 verdict.validate(),
1039 Err(SimilarCodeVerdictValidationError::RefactorSafetyRequiresEquivalence)
1040 );
1041 }
1042
1043 #[test]
1044 fn raw_serializer_adds_only_the_similar_code_kind() {
1045 let output = raw_output();
1046
1047 let value = serialize_similar_code_json_output(output, RootEnvelopeMode::Tagged)
1048 .expect("similar-code output should serialize");
1049
1050 assert_eq!(value["kind"], "similar-code");
1051 assert_eq!(value["schema_version"], "1");
1052 assert!(value.get("severity").is_none());
1053 assert!(value.get("gate").is_none());
1054 assert!(value.get("fixes").is_none());
1055 }
1056
1057 #[test]
1058 fn raw_output_round_trips_for_review_input() {
1059 let value = serde_json::to_value(raw_output()).expect("raw output should serialize");
1060 let decoded: SimilarCodeOutput =
1061 serde_json::from_value(value).expect("raw output should deserialize");
1062
1063 assert_eq!(decoded.schema_version, SimilarCodeSchemaVersion::V1);
1064 assert_eq!(decoded.version.0, "3.9.0");
1065 assert_eq!(
1066 decoded.completion.status,
1067 SimilarCodeCompletionStatus::Complete
1068 );
1069 }
1070
1071 #[test]
1072 fn verdict_input_rejects_unknown_fields() {
1073 let value = serde_json::json!({
1074 "schema_version": "1",
1075 "verdicts": [],
1076 "unexpected": true
1077 });
1078
1079 assert!(serde_json::from_value::<SimilarCodeVerdictInput>(value).is_err());
1080 }
1081
1082 #[test]
1083 fn review_preserves_null_judgments() {
1084 let verdict = SimilarCodeVerdict {
1085 candidate_id: "sc_123".to_string(),
1086 review_key: "scr_456".to_string(),
1087 candidate_worthy: None,
1088 behaviorally_equivalent: None,
1089 refactor_safe: None,
1090 outcome: SimilarCodeDomainOutcome::NeedsHumanReview,
1091 rationale: "Insufficient evidence.".to_string(),
1092 };
1093
1094 let value = serde_json::to_value(verdict).expect("verdict should serialize");
1095 assert!(value["candidate_worthy"].is_null());
1096 assert!(value["behaviorally_equivalent"].is_null());
1097 assert!(value["refactor_safe"].is_null());
1098 }
1099
1100 fn generation() -> SimilarCodeGeneration {
1101 SimilarCodeGeneration {
1102 extraction_semantics_version: 1,
1103 embedding_semantics_version: 1,
1104 provider: SimilarCodeProviderProvenance {
1105 provider: SimilarCodeProvider::OfficialLocalCompanion,
1106 companion_version: "3.9.0".to_string(),
1107 protocol_version: 2,
1108 source_left_machine: false,
1109 },
1110 model: SimilarCodeModelProvenance {
1111 model_id: "example/model".to_string(),
1112 revision: "immutable-revision".to_string(),
1113 artifact_sha256: "abc".to_string(),
1114 license: "Apache-2.0".to_string(),
1115 dimensions: 384,
1116 },
1117 parameters: SimilarCodeGenerationParameters {
1118 dtype: "fp32".to_string(),
1119 pooling: "mean".to_string(),
1120 normalized: true,
1121 batch_size: 8,
1122 max_tokens: 1024,
1123 parameter_sha256: "def".to_string(),
1124 },
1125 scope: SimilarCodeScopeProvenance {
1126 active: true,
1127 paths: vec!["src/a.ts".to_string()],
1128 },
1129 threshold: 0.8,
1130 min_lines: 3,
1131 }
1132 }
1133
1134 fn raw_output() -> SimilarCodeOutput {
1135 SimilarCodeOutput {
1136 schema_version: SimilarCodeSchemaVersion::V1,
1137 version: ToolVersion("3.9.0".to_string()),
1138 elapsed_ms: ElapsedMs(4),
1139 generation: generation(),
1140 candidates: Vec::new(),
1141 completion: completion(),
1142 diagnostics: Vec::new(),
1143 }
1144 }
1145
1146 fn completion() -> SimilarCodeCompletion {
1147 SimilarCodeCompletion {
1148 status: SimilarCodeCompletionStatus::Complete,
1149 phases: Vec::new(),
1150 limits: SimilarCodeLimits {
1151 max_files: 10,
1152 max_functions: 100,
1153 max_source_bytes: 1_000_000,
1154 max_function_bytes: 10_000,
1155 max_batch_size: 8,
1156 max_vector_bytes: 1_000_000,
1157 max_comparisons: 1_000,
1158 max_candidates: 10,
1159 max_neighbors_per_function: 3,
1160 timeout_ms: 30_000,
1161 },
1162 skips: Vec::new(),
1163 cache: SimilarCodeCacheSummary {
1164 status: SimilarCodeCacheStatus::Miss,
1165 hits: 0,
1166 misses: 0,
1167 writes: 0,
1168 invalid_entries: 0,
1169 },
1170 provider_inference_ms: 0,
1171 }
1172 }
1173}