git-slop 0.12.0

A deterministic repository token-defragmenter for humans and AI agents.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
fn compressed_bytes(format: &str, source: &[u8]) -> Result<Option<(String, Vec<u8>)>> {
    match format {
        "none" => Ok(None),
        "gzip" => {
            let mut encoder =
                flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
            encoder.write_all(source)?;
            Ok(Some(("report.json.gz".to_string(), encoder.finish()?)))
        }
        "zstd" => Ok(Some((
            "report.json.zst".to_string(),
            zstd::stream::encode_all(source, 3)?,
        ))),
        value => anyhow::bail!("unsupported report compression {value:?}"),
    }
}

pub fn write_json_atomically(path: &Path, value: &Value) -> Result<()> {
    let parent = path
        .parent()
        .filter(|parent| !parent.as_os_str().is_empty())
        .unwrap_or_else(|| Path::new("."));
    fs::create_dir_all(parent).with_context(|| format!("failed to create {}", parent.display()))?;
    let temporary = temporary_directory(parent, "report-migration");
    fs::write(&temporary, serde_json::to_string_pretty(value)? + "\n")
        .with_context(|| format!("failed to write {}", temporary.display()))?;
    fs::rename(&temporary, path).with_context(|| format!("failed to publish {}", path.display()))
}

pub fn schema() -> Value {
    let mut schema = json!({
        "$schema": "https://json-schema.org/draft/2020-12/schema",
        "$id": "https://github.com/coreycoto/git-slop/blob/v0.11.6/schemas/report-5.json",
        "title": "Git Slop report schema 5",
        "type": "object",
        "additionalProperties": false,
        "required": ["schema_version", "analyzer", "generated_at", "analyzed_revision_at", "repo", "scope", "config", "stats", "summary", "files", "folders", "ranked_files", "action_queue", "observation_feed", "costs", "overlays", "health", "diagnostics", "collection_metadata", "evidence_completeness", "terminology"],
        "properties": {
            "schema_version": {"const": 5},
            "generated_at": {"type": "string", "format": "date-time"},
            "analyzed_revision_at": {"type": ["string", "null"], "format": "date-time"},
            "analyzer": {
                "type": "object",
                "additionalProperties": false,
                "required": ["name", "version", "report_profile", "analysis_contract_version", "config_digest", "analysis_config_digest", "evidence_config_digest", "policy_config_digest", "presentation_config_digest", "context_tokenizer"],
                "properties": {
                    "name": {"const": "git-slop"},
                    "version": {"type": "string"},
                    "report_profile": {"enum": ["compact", "standard", "full_evidence"]},
                    "analysis_clock": {"type": "string", "format": "date-time"},
                    "analysis_contract_version": {"type": "integer", "minimum": 1},
                    "config_digest": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
                    "analysis_config_digest": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
                    "evidence_config_digest": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
                    "policy_config_digest": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
                    "presentation_config_digest": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
                    "context_tokenizer": {"type": "string"}
                }
            },
            "repo": {"type": "object", "additionalProperties": false, "required": ["repo_name", "repository_id", "repository_identity_source", "branch", "head_commit_timestamp", "is_shallow", "detached_head", "worktree_clean", "staged_change_count", "modified_tracked_file_count", "untracked_file_count", "worktree_state_digest", "analyzed_content_digest", "head_sha", "remote_url", "has_head_commit"], "properties": {"repo_name":{"type":"string"},"repository_id":{"type":["string","null"]},"repository_identity_source":{"type":["string","null"]},"branch":{"type":["string","null"]},"head_commit_timestamp":{"type":["string","null"],"format":"date-time"},"is_shallow":{"type":"boolean"},"detached_head":{"type":"boolean"},"worktree_clean":{"type":"boolean"},"staged_change_count":{"type":"integer"},"modified_tracked_file_count":{"type":"integer"},"untracked_file_count":{"type":"integer"},"worktree_state_digest":{"type":"string","pattern":"^[a-f0-9]{64}$"},"analyzed_content_digest":{"type":["string","null"]},"head_sha":{"type":["string","null"]},"remote_url":{"type":["string","null"]},"has_head_commit":{"type":"boolean"}}},
            "scope": {
                "type": "object",
                "additionalProperties": false,
                "required": ["mode", "path", "selected_path_count", "selected_path_digest"],
                "properties": {
                    "mode": {"enum": ["repository", "scoped"]},
                    "path": {"type": ["string", "null"]},
                    "selected_path_count": {"type": "integer", "minimum": 0},
                    "selected_path_digest": {"type": "string", "pattern": "^[a-f0-9]{64}$"}
                }
            },
            "config": {"type": "object"},
            "stats": {"type": "object"},
            "summary": {"type": "object"},
            "policy_evaluation": {"type": "object", "additionalProperties": false, "required": ["policy_failures", "intervention_candidates", "advisory_findings", "emitted_annotations", "thresholds", "count_semantics"], "properties": {"policy_failures":{"type":"integer","minimum":0},"intervention_candidates":{"type":"integer","minimum":0},"advisory_findings":{"type":"integer","minimum":0},"emitted_annotations":{"type":"object"},"thresholds":{"type":"object"},"count_semantics":{"type":"string"}}},
            "files": {"type": "array", "items": {"$ref": "#/$defs/file"}},
            "folders": {"type": "array", "items": {"$ref": "#/$defs/folder"}},
            "compare_index": {
                "type": "object",
                "additionalProperties": false,
                "required": ["files", "folders"],
                "properties": {
                    "files": {"type": "array", "items": {"$ref": "#/$defs/compare_record"}},
                    "folders": {"type": "array", "items": {"$ref": "#/$defs/compare_record"}}
                }
            },
            "policy_index": {
                "type": "object",
                "additionalProperties": false,
                "required": ["files", "folders"],
                "properties": {
                    "files": {"type": "array", "items": {"$ref": "#/$defs/policy_record"}},
                    "folders": {"type": "array", "items": {"$ref": "#/$defs/policy_record"}}
                }
            },
            "action_queue": {"type": "array", "items": {"$ref": "#/$defs/queue_item"}},
            "observation_feed": {"type": "array", "items": {"$ref": "#/$defs/queue_item"}},
            "ranked_files": {"type": "array", "items": {"$ref": "#/$defs/ranked_file"}},
            "costs": {"$ref": "#/$defs/costs"},
            "overlays": {"type": "object"},
            "health": {"type": "object"},
            "diagnostics": {"type": "object"},
            "collection_metadata": {"type": "object"},
            "evidence_completeness": {"type": "object"}
            ,"terminology": {"type": "object", "required": ["attention_required", "budget_exceeded", "critical", "error"]}
        },
        "$defs": {
            "classification": {"type":"string","enum": crate::model::Classification::values()},
            "costs": {"type":"object","additionalProperties":false,"properties":{"load":{"type":"object","additionalProperties":false,"properties":{"analysis_status":{"type":"string"},"analysis_version":{"type":"integer"},"file_token_count":{"type":"integer"},"folder_token_count":{"type":"integer"},"top_file_share":{"type":"number"},"top_3_file_share":{"type":"number"},"token_concentration_ratio":{"type":"number"},"context_band":{"type":"string"},"load_pressure":{"type":"number"}}},"volatility":{"type":"object","additionalProperties":false,"properties":{"analysis_status":{"type":"string"},"analysis_version":{"type":"integer"},"commit_count_window":{"type":"number"},"recency_weighted_commits":{"type":"number"},"line_churn_window":{"type":"number"},"token_churn_window":{"type":"number"},"relative_token_churn":{"type":"number"},"late_churn_spike":{"type":"number"},"volatility_pressure":{"type":"number"},"churn_measurement":{"type":"string"}}},"coordination":{"type":"object","additionalProperties":false,"properties":{"analysis_status":{"type":"string"},"analysis_version":{"type":"integer"},"files_touched_per_change":{"type":"number"},"folders_touched_per_change":{"type":"number"},"edit_hunks_per_change":{"type":"number"},"change_diffusion":{"type":"number"},"cochange_degree":{"type":"number"},"cochange_centrality":{"type":"number"},"cochange_pagerank":{"type":"number"},"cross_folder_cochange_ratio":{"type":"number"},"coordination_pressure":{"type":"number"}}}}},
            "compare_record": {"type":"object","additionalProperties":false,"required":["path","content_fingerprint","content_sha256","analysis_status","skipped_reason","tokens","context_band","slop_score","slop_band","costs","overlays"],"properties":{"path":{"type":"string"},"content_fingerprint":{"type":["string","null"]},"content_sha256":{"type":["string","null"],"pattern":"^[0-9a-f]{64}$"},"analysis_status":{"type":"string"},"skipped_reason":{"type":["string","null"]},"tokens":{"type":"integer","minimum":0},"context_band":{"type":"string"},"slop_score":{"type":"number"},"slop_band":{"type":"string"},"costs":{"$ref":"#/$defs/costs"},"overlays":{"type":"object"}}},
            "generated_provenance": {"type":"object","additionalProperties":false,"required":["source_paths","source_globs","generator_command","verification_command"],"properties":{"source_paths":{"type":"array","items":{"type":"string"}},"source_globs":{"type":"array","items":{"type":"string"}},"generator_command":{"type":["string","null"]},"verification_command":{"type":["string","null"]}}},
            "policy_record": {"type":"object","additionalProperties":false,"required":["path","classification","profile","generated_from","tokens","context_band","slop_score","slop_band","reason_codes"],"properties":{"path":{"type":"string"},"classification":{"$ref":"#/$defs/classification"},"profile":{"type":["string","null"]},"generated_from":{"type":"array","items":{"type":"string"}},"generated_provenance":{"$ref":"#/$defs/generated_provenance"},"tokens":{"type":"integer","minimum":0},"context_band":{"type":"string"},"slop_score":{"type":"number"},"slop_band":{"type":"string"},"reason_codes":{"type":"array","items":{"type":"string"}}}},
            "file": {"type": "object", "additionalProperties": false, "required": ["path", "bytes", "lines", "blank_lines", "code_lines", "comment_lines", "language", "profile", "classification", "generated_from", "analysis_status", "skipped_reason", "symlink_metadata", "has_inline_tests", "tokens", "context_band", "context_pressure", "content_fingerprint", "content_sha256", "structural_token_count", "top_structural_terms", "structural_categories", "age_days", "revisions_window", "recency_weighted_commits", "added_window", "deleted_window", "churn_lines_window", "line_churn_window", "token_churn_window", "relative_churn_window", "late_churn_spike", "author_count_window", "author_entropy", "top_author_share", "days_since_non_bot_edit", "recent_maintainer_diversity", "age_pressure", "revision_norm", "relative_churn_norm", "churn_pressure", "slop_score", "slop_band", "reason_codes", "costs", "overlays"], "properties": {"path":{"type":"string"},"bytes":{"type":"integer"},"lines":{"type":"integer"},"blank_lines":{"type":"integer"},"code_lines":{"type":"integer"},"comment_lines":{"type":"integer"},"language":{"type":"string"},"profile":{"type":"string"},"classification":{"type":"string"},"generated_from":{"type":"array","items":{"type":"string"}},"generated_provenance":{"$ref":"#/$defs/generated_provenance"},"analysis_status":{"type":"string"},"skipped_reason":{"type":["string","null"]},"symlink_metadata":{"type":["object","null"]},"has_inline_tests":{"type":"boolean"},"tokens":{"type":"integer"},"context_band":{"type":"string"},"context_pressure":{"type":"number"},"content_fingerprint":{"type":"string"},"content_sha256":{"type":"string","pattern":"^[0-9a-f]{64}$"},"structural_token_count":{"type":"integer"},"top_structural_terms":{"type":"array"},"structural_categories":{"type":"object"},"age_days":{"type":"integer"},"revisions_window":{"type":"integer"},"recency_weighted_commits":{"type":"number"},"added_window":{"type":"integer"},"deleted_window":{"type":"integer"},"churn_lines_window":{"type":"integer"},"line_churn_window":{"type":"integer"},"token_churn_window":{"type":"integer"},"relative_churn_window":{"type":"number"},"late_churn_spike":{"type":"number"},"author_count_window":{"type":"integer"},"author_entropy":{"type":"number"},"top_author_share":{"type":"number"},"days_since_non_bot_edit":{"type":["integer","null"]},"recent_maintainer_diversity":{"type":"integer"},"age_pressure":{"type":"number"},"revision_norm":{"type":"number"},"relative_churn_norm":{"type":"number"},"churn_pressure":{"type":"number"},"slop_score":{"type":"number"},"slop_band":{"type":"string"},"reason_codes":{"type":"array"},"costs":{"$ref":"#/$defs/costs"},"overlays":{"type":"object"}}},
            "folder": {"type": "object", "additionalProperties": false, "required": ["path", "descendant_file_count", "direct_file_count", "bytes", "lines", "tokens", "direct_tokens", "context_band", "health_band", "context_pressure", "slop_score", "slop_band", "reason_codes", "top_file_path", "classification", "costs", "overlays"], "properties":{"path":{"type":"string"},"descendant_file_count":{"type":"integer"},"direct_file_count":{"type":"integer"},"bytes":{"type":"integer"},"lines":{"type":"integer"},"tokens":{"type":"integer"},"direct_tokens":{"type":"integer"},"context_band":{"type":"string"},"health_band":{"type":"string"},"context_pressure":{"type":"number"},"slop_score":{"type":"number"},"slop_band":{"type":"string"},"reason_codes":{"type":"array"},"top_file_path":{"type":"string"},"classification":{"type":"string"},"costs":{"$ref":"#/$defs/costs"},"overlays":{"type":"object"}}},
            "queue_item": {"type": "object", "additionalProperties": false, "required": ["path", "profile", "classification", "generated_from", "synchronization_group", "remediation_kind", "slop_score", "slop_band", "context_band", "tokens", "age_days", "revisions_window", "churn_pressure", "reason_codes", "is_pure_context_hotspot", "severity", "evidence_status", "next_action"], "properties":{"path":{"type":"string"},"profile":{"enum":["agent_context","data_context"]},"classification":{"type":"string"},"generated_from":{"type":"array","items":{"type":"string"}},"synchronization_group":{"type":["string","null"]},"remediation_kind":{"type":"string"},"slop_score":{"type":"number"},"slop_band":{"type":"string"},"context_band":{"type":"string"},"tokens":{"type":"integer"},"age_days":{"type":"integer"},"revisions_window":{"type":"integer"},"churn_pressure":{"type":"number"},"reason_codes":{"type":"array","items":{"type":"string"}},"is_pure_context_hotspot":{"type":"boolean"},"severity":{"enum":["error","warning","notice"]},"evidence_status":{"type":"string"},"next_action":{"type":"string"}}},
            "ranked_file": {"type": "object", "additionalProperties": false, "required": ["path", "slop_score", "slop_band", "context_band", "tokens", "reason_codes"], "properties":{"path":{"type":"string"},"slop_score":{"type":"number"},"slop_band":{"type":"string"},"context_band":{"type":"string"},"tokens":{"type":"integer"},"reason_codes":{"type":"array","items":{"type":"string"}}}}
        }
    });
    apply_shared_classification_schema(&mut schema);
    harden_generated_contracts(&mut schema);
    harden_scalar_contracts(&mut schema);
    schema
}

fn harden_scalar_contracts(schema: &mut Value) {
    schema["$defs"]["profile"] = json!({"enum":["agent_context","data_context"]});
    schema["$defs"]["context_band"] =
        json!({"enum":["compact","healthy","warning","critical"]});
    schema["$defs"]["slop_band"] = json!({"enum":["low","moderate","high"]});
    schema["$defs"]["analysis_status"] = json!({"enum":[
        "analyzed","skipped","stable","experimental","not_applicable","legacy_unknown",
        "complete","degraded_resource_budget","degraded_large_files","degraded_incomplete_inventory"
    ]});
    schema["$defs"]["evidence_status"] = json!({"enum":[
        "supported","limited","low_support","not_applicable","evidence_unavailable",
        "mapping_confidence_low","evidence_found","no_mapping","no_evidence","legacy_unknown"
    ]});
    schema["$defs"]["sha1"] = json!({"type":"string","pattern":"^[0-9a-f]{40}$"});
    schema["$defs"]["digest"] = json!({"type":"string","pattern":"^[0-9a-f]{64}$"});
    schema["$defs"]["fingerprint"] = json!({
        "oneOf": [
            {"$ref":"#/$defs/digest"},
            {"type":"string","pattern":"^incomplete:[a-z_]+:[0-9]+$"}
        ]
    });

    fn visit(value: &mut Value) {
        let Some(object) = value.as_object_mut() else {
            if let Some(values) = value.as_array_mut() {
                for child in values {
                    visit(child);
                }
            }
            return;
        };
        if let Some(properties) = object.get_mut("properties").and_then(Value::as_object_mut) {
            for (name, property) in properties.iter_mut() {
                let nullable = property
                    .get("type")
                    .and_then(Value::as_array)
                    .is_some_and(|types| types.iter().any(|kind| kind == "null"));
                let typed = |reference: &str| {
                    if nullable {
                        json!({"oneOf":[{"$ref":reference},{"type":"null"}]})
                    } else {
                        json!({"$ref":reference})
                    }
                };
                let replacement = match name.as_str() {
                    "profile" => Some(typed("#/$defs/profile")),
                    "context_band" | "health_band" => {
                        Some(typed("#/$defs/context_band"))
                    }
                    "slop_band" => Some(typed("#/$defs/slop_band")),
                    "analysis_status" => Some(typed("#/$defs/analysis_status")),
                    "evidence_status" => Some(typed("#/$defs/evidence_status")),
                    "head_sha" => Some(typed("#/$defs/sha1")),
                    "content_sha256" => Some(typed("#/$defs/digest")),
                    "content_fingerprint" => Some(typed("#/$defs/fingerprint")),
                    "worktree_state_digest" | "analyzed_content_digest"
                    | "selected_path_digest" | "config_digest" | "analysis_config_digest"
                    | "evidence_config_digest" | "policy_config_digest"
                    | "presentation_config_digest" => Some(typed("#/$defs/digest")),
                    "slop_score" => Some(json!({"type":"number","minimum":0,"maximum":100})),
                    "context_pressure" | "churn_pressure" | "load_pressure"
                    | "volatility_pressure" | "coordination_pressure" | "top_file_share"
                    | "top_3_file_share" | "token_concentration_ratio" | "top_author_share"
                    | "late_churn_spike" | "cochange_centrality" | "cochange_pagerank"
                    | "cross_folder_cochange_ratio" | "change_diffusion" | "evidence_score"
                    | "similarity" | "similarity_ratio" | "jaccard" | "calibrated_jaccard"
                    | "evidence_lower_bound" | "confidence_lower_bound" => {
                        Some(json!({"type":"number","minimum":0,"maximum":1}))
                    }
                    _ => None,
                };
                if let Some(replacement) = replacement {
                    *property = replacement;
                }
                visit(property);
            }
        }
        for (key, child) in object.iter_mut() {
            if key != "properties" {
                visit(child);
            }
        }
    }
    visit(schema);
}

fn apply_shared_classification_schema(value: &mut Value) {
    match value {
        Value::Object(object) => {
            if let Some(classification) = object
                .get_mut("properties")
                .and_then(Value::as_object_mut)
                .and_then(|properties| properties.get_mut("classification"))
            {
                *classification = json!({"$ref": "#/$defs/classification"});
            }
            for child in object.values_mut() {
                apply_shared_classification_schema(child);
            }
        }
        Value::Array(values) => {
            for child in values {
                apply_shared_classification_schema(child);
            }
        }
        _ => {}
    }
}

fn harden_generated_contracts(schema: &mut Value) {
    let mut aggregate_classifications = crate::model::Classification::values();
    aggregate_classifications.push("mixed");
    schema["$defs"]["classification_or_mixed"] =
        json!({"type":"string","enum":aggregate_classifications});
    schema["$defs"]["json_value"] = json!({
        "oneOf": [
            {"type":"null"}, {"type":"boolean"}, {"type":"number"}, {"type":"string"},
            {"type":"array","items":{"$ref":"#/$defs/json_value"}},
            {"$ref":"#/$defs/json_object"}
        ]
    });
    schema["$defs"]["json_object"] = json!({
        "type":"object","additionalProperties":{"$ref":"#/$defs/json_value"}
    });
    let mut config_schema = crate::config::schema();
    if let Some(object) = config_schema.as_object_mut() {
        for key in ["$schema", "$id", "title", "description", "x-git-slop-deprecated-keys"] {
            object.remove(key);
        }
    }
    schema["properties"]["config"] = config_schema;
    schema["properties"]["stats"] = json!({
        "type":"object","additionalProperties":false,
        "required":["tracked_file_count","analyzed_file_count","skipped_ignored_count","skipped_missing_count","skipped_binary_count","skipped_undecodable_count","critical_context_file_count","critical_slop_file_count","history_complete"],
        "properties":{
            "tracked_file_count":{"type":"integer","minimum":0},"analyzed_file_count":{"type":"integer","minimum":0},
            "skipped_ignored_count":{"type":"integer","minimum":0},"skipped_missing_count":{"type":"integer","minimum":0},
            "skipped_binary_count":{"type":"integer","minimum":0},"skipped_undecodable_count":{"type":"integer","minimum":0},
            "critical_context_file_count":{"type":"integer","minimum":0},"critical_slop_file_count":{"type":"integer","minimum":0},
            "history_complete":{"type":"boolean"},"migration_status":{"type":"string"}
        }
    });
    schema["properties"]["summary"] = json!({
        "type":"object","additionalProperties":false,
        "required":["top_hotspots","top_structural_files","top_verification_gaps","health"],
        "properties":{
            "top_hotspots":{"type":"array","items":{"type":"string"}},
            "top_structural_files":{"type":"array","items":{"type":"string"}},
            "top_verification_gaps":{"type":"array","items":{"type":"string"}},
            "health":{"type":"object","additionalProperties":false,"required":["file_band_counts","folder_band_counts"],"properties":{
                "file_band_counts":{"$ref":"#/$defs/band_counts"},"folder_band_counts":{"$ref":"#/$defs/band_counts"}
            }}
        }
    });
    schema["properties"]["overlays"] = json!({"$ref":"#/$defs/json_object"});
    schema["properties"]["diagnostics"] = json!({
        "type":"object","additionalProperties":false,
        "properties":{
            "suppressed_saturated_overlays":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["overlay","reason","measured_count","range","entropy_bits"],"properties":{
                "overlay":{"type":"string"},"reason":{"enum":["saturated","low_variance","low_entropy"]},
                "measured_count":{"type":"integer","minimum":0},"range":{"type":"number"},"entropy_bits":{"type":"number","minimum":0}
            }}},
            "relationship_count":{"type":"integer","minimum":0},
            "structural_token_payload_omitted":{"type":"boolean"},
            "analysis":{"$ref":"#/$defs/json_object"},
            "compact_profile_note":{"type":"string"},"evidence_limit":{"type":"string"},
            "migration":{"type":"string"},"report_profile":{"enum":["compact","standard","full_evidence"]},
            "report_profile_semantics":{"type":"string"},
            "report_sizes":{"type":"object","additionalProperties":false,"required":["report_json_bytes","report_yaml_bytes","logical_artifact_bytes","physical_storage_semantics"],"properties":{
                "report_json_bytes":{"type":"integer","minimum":0},"report_yaml_bytes":{"type":"integer","minimum":0},
                "logical_artifact_bytes":{"type":"integer","minimum":0},"physical_storage_semantics":{"type":"string"}
            }}
        }
    });
    schema["properties"]["evidence_completeness"] = json!({
        "type":"object","additionalProperties":false,
        "required":["history","repository_size","history_window_days","history_max_commits","first_seen_age","churn_window","author_evidence","relationship_evidence","missing_test_evidence_count","weak_test_mapping_count","low_test_cochange_evidence_count","relationship_support"],
        "properties":{
            "history":{"type":"string"},"repository_size":{"type":"string"},
            "history_window_days":{"type":["integer","null"],"minimum":0},"history_max_commits":{"type":["integer","null"],"minimum":0},
            "first_seen_age":{"type":"string"},"churn_window":{"type":"string"},"author_evidence":{"type":"string"},
            "relationship_evidence":{"type":"string"},"missing_test_evidence_count":{"type":"integer","minimum":0},
            "weak_test_mapping_count":{"type":"integer","minimum":0},"low_test_cochange_evidence_count":{"type":"integer","minimum":0},
            "relationship_support":{"type":"string"}
        }
    });
    schema["properties"]["terminology"] = json!({
        "type":"object","additionalProperties":false,
        "required":["attention_required","budget_exceeded","critical","error"],
        "properties":{
            "attention_required":{"type":"string"},"budget_exceeded":{"type":"string"},
            "critical":{"type":"string"},"error":{"type":"string"}
        }
    });
    schema["$defs"]["band_counts"] = json!({
        "type":"object","additionalProperties":{"type":"integer","minimum":0}
    });
    schema["$defs"]["collection_page"] = json!({
        "type":"object","additionalProperties":false,"required":["total","returned","limit","truncated"],
        "properties":{"total":{"type":"integer","minimum":0},"returned":{"type":"integer","minimum":0},"limit":{"type":["integer","null"],"minimum":0},"truncated":{"type":"boolean"},"low_support_aggregated":{"type":"integer","minimum":0},"scope":{"type":"string"}}
    });
    schema["$defs"]["index_collection_metadata"] = json!({
        "type":"object","additionalProperties":false,"required":["files","folders"],
        "properties":{"files":{"$ref":"#/$defs/collection_page"},"folders":{"$ref":"#/$defs/collection_page"}}
    });
    schema["properties"]["collection_metadata"] = json!({
        "type":"object","additionalProperties":{"$ref":"#/$defs/collection_page"},
        "properties":{
            "files":{"$ref":"#/$defs/collection_page"},"folders":{"$ref":"#/$defs/collection_page"},
            "compare_index":{"$ref":"#/$defs/index_collection_metadata"},"policy_index":{"$ref":"#/$defs/index_collection_metadata"},
            "action_queue":{"$ref":"#/$defs/collection_page"},"observation_feed":{"$ref":"#/$defs/collection_page"},
            "ranked_files":{"$ref":"#/$defs/collection_page"},"health.findings":{"$ref":"#/$defs/collection_page"},
            "health.refactor_candidates":{"$ref":"#/$defs/collection_page"},"health.watchlist":{"$ref":"#/$defs/collection_page"}
        }
    });
    if let Some(properties) = schema
        .pointer_mut("/$defs/queue_item/properties")
        .and_then(Value::as_object_mut)
    {
        properties.insert(
            "remediation_target_paths".to_string(),
            json!({"type":"array","items":{"type":"string"}}),
        );
    }
    if let Some(required) = schema
        .pointer_mut("/$defs/queue_item/required")
        .and_then(Value::as_array_mut)
    {
        required.push(json!("remediation_target_paths"));
    }
    if let Some(properties) = schema
        .pointer_mut("/$defs/ranked_file/properties")
        .and_then(Value::as_object_mut)
    {
        properties.insert(
            "classification".to_string(),
            json!({"$ref":"#/$defs/classification"}),
        );
        properties.insert("profile".to_string(), json!({"type":"string"}));
        properties.insert("remediation_kind".to_string(), json!({"type":"string"}));
    }
    if let Some(required) = schema
        .pointer_mut("/$defs/ranked_file/required")
        .and_then(Value::as_array_mut)
    {
        required.extend([
            json!("classification"),
            json!("profile"),
            json!("remediation_kind"),
        ]);
    }
    schema["$defs"]["finding"] = json!({
        "type":"object",
        "additionalProperties":false,
        "required":["path","profile","severity","title","message","next_command","slop_band","context_band","slop_score","tokens","reasons"],
        "properties":{
            "path":{"type":"string"},"profile":{"type":"string"},"severity":{"enum":["error","warning","notice"]},"title":{"type":"string"},"message":{"type":"string"},"next_command":{"type":"string"},"slop_band":{"type":"string"},"context_band":{"type":"string"},"slop_score":{"type":"number"},"tokens":{"type":"integer","minimum":0},"reasons":{"type":"array","items":{"type":"string"}}
        }
    });
    schema["$defs"]["totals"] = json!({
        "type":"object","additionalProperties":false,"required":["files","lines","code","comments","blanks","tokens"],
        "properties":{"files":{"type":"integer","minimum":0},"lines":{"type":"integer","minimum":0},"code":{"type":"integer","minimum":0},"comments":{"type":"integer","minimum":0},"blanks":{"type":"integer","minimum":0},"tokens":{"type":"integer","minimum":0}}
    });
    schema["$defs"]["profile_rollup"] = json!({
        "type":"object","additionalProperties":false,"required":["name","totals"],
        "properties":{"name":{"type":"string"},"totals":{"$ref":"#/$defs/totals"}}
    });
    schema["$defs"]["language_rollup"] = json!({
        "type":"object","additionalProperties":false,"required":["profile","language","files","lines","code","comments","blanks","tokens","token_share"],
        "properties":{"profile":{"type":"string"},"language":{"type":"string"},"files":{"type":"integer","minimum":0},"lines":{"type":"integer","minimum":0},"code":{"type":"integer","minimum":0},"comments":{"type":"integer","minimum":0},"blanks":{"type":"integer","minimum":0},"tokens":{"type":"integer","minimum":0},"token_share":{"type":"number","minimum":0,"maximum":1}}
    });
    schema["$defs"]["distribution"] = json!({
        "type":"object","additionalProperties":false,"required":["count","total","p50","p90","p95","p99","max","top_1_share","top_5_share","top_10_share"],
        "properties":{"count":{"type":"integer","minimum":0},"total":{"type":"integer","minimum":0},"p50":{"type":"number","minimum":0},"p90":{"type":"number","minimum":0},"p95":{"type":"number","minimum":0},"p99":{"type":"number","minimum":0},"max":{"type":"integer","minimum":0},"top_1_share":{"type":"number","minimum":0,"maximum":1},"top_5_share":{"type":"number","minimum":0,"maximum":1},"top_10_share":{"type":"number","minimum":0,"maximum":1}}
    });
    schema["$defs"]["candidate"] = json!({
        "type":"object","additionalProperties":false,
        "required":["kind","path","profile","class","tokens","parent_tokens","band","slop_band","slop_score","reason_codes"],
        "properties":{"kind":{"enum":["file","folder"]},"path":{"type":"string"},"profile":{"type":"string"},"class":{"$ref":"#/$defs/classification_or_mixed"},"files":{"type":"integer","minimum":0},"descendant_files":{"type":"integer","minimum":0},"tokens":{"type":"integer","minimum":0},"recursive_tokens":{"type":"integer","minimum":0},"parent_tokens":{"type":"integer","minimum":0},"band":{"type":"string"},"slop_band":{"type":"string"},"slop_score":{"type":"number"},"reason_codes":{"type":"array","items":{"type":"string"}}}
    });
    schema["$defs"]["health"] = json!({
        "type":"object",
        "additionalProperties":false,
        "required":["file_band_counts","folder_band_counts","profile_rollups","language_rollups","file_distribution","folder_distribution","refactor_candidates","watchlist","findings"],
        "properties":{
            "file_band_counts":{"$ref":"#/$defs/band_counts"},
            "folder_band_counts":{"$ref":"#/$defs/band_counts"},
            "profile_rollups":{"type":"array","items":{"$ref":"#/$defs/profile_rollup"}},
            "language_rollups":{"type":"object","additionalProperties":{"type":"array","items":{"$ref":"#/$defs/language_rollup"}}},
            "file_distribution":{"$ref":"#/$defs/distribution"},"folder_distribution":{"$ref":"#/$defs/distribution"},
            "refactor_candidates":{"type":"array","items":{"$ref":"#/$defs/candidate"}},
            "watchlist":{"type":"array","items":{"$ref":"#/$defs/candidate"}},
            "findings":{"type":"array","items":{"$ref":"#/$defs/finding"}}
        }
    });
    schema["properties"]["health"] = json!({"$ref":"#/$defs/health"});
    for pointer in [
        "/$defs/file/properties/overlays", "/$defs/folder/properties/overlays",
        "/$defs/compare_record/properties/overlays"
    ] {
        if let Some(value) = schema.pointer_mut(pointer) {
            *value = json!({"$ref":"#/$defs/json_object"});
        }
    }
    schema["$defs"]["file"]["properties"]["top_structural_terms"] =
        json!({"type":"array","items":{"type":"string"}});
    schema["$defs"]["file"]["properties"]["reason_codes"] =
        json!({"type":"array","items":{"type":"string"}});
    schema["$defs"]["folder"]["properties"]["reason_codes"] =
        json!({"type":"array","items":{"type":"string"}});
    schema["$defs"]["folder"]["properties"]["classification"] =
        json!({"$ref":"#/$defs/classification_or_mixed"});
    schema["$defs"]["policy_record"]["properties"]["classification"] =
        json!({"$ref":"#/$defs/classification_or_mixed"});
    schema["$defs"]["file"]["properties"]["structural_categories"] =
        json!({"$ref":"#/$defs/json_object"});
    schema["$defs"]["file"]["properties"]["symlink_metadata"] =
        json!({"type":["object","null"],"additionalProperties":{"$ref":"#/$defs/json_value"}});
}

pub fn load_report(path: &Path) -> Result<Value> {
    load_report_with_legacy(path, true)
}

pub fn load_report_with_legacy(path: &Path, allow_legacy: bool) -> Result<Value> {
    let source =
        fs::read_to_string(path).with_context(|| format!("failed to read {}", path.display()))?;
    let report: Value = serde_json::from_str(&source)
        .with_context(|| format!("invalid git-slop report JSON: {}", path.display()))?;
    let schema_version = report.get("schema_version").and_then(Value::as_u64);
    let report = if schema_version == Some(4) {
        if !allow_legacy {
            anyhow::bail!(
                "legacy report schema 4 requires --allow-legacy or `git slop report migrate`"
            );
        }
        migrate_legacy_report(report)?
    } else {
        report
    };
    validate_report_shape(&report)
        .with_context(|| format!("invalid git-slop report shape: {}", path.display()))?;
    Ok(report)
}

pub fn migrate_legacy_report(mut report: Value) -> Result<Value> {
    if report.get("schema_version").and_then(Value::as_u64) != Some(4) {
        anyhow::bail!("only report schema 4 can be migrated to schema 5");
    }
    let legacy_relationships = report.get("relationships").cloned();
    let legacy_clusters = report.get("clusters").cloned();
    let legacy_metrics = report.get("organization_metrics").cloned();
    let root = report
        .as_object_mut()
        .ok_or_else(|| anyhow!("report root must be an object"))?;
    root.insert("schema_version".to_string(), json!(5));
    let zero_digest = "0".repeat(64);
    let analyzer = root
        .entry("analyzer")
        .or_insert_with(|| json!({}))
        .as_object_mut()
        .ok_or_else(|| anyhow!("analyzer must be an object"))?;
    analyzer.entry("name").or_insert_with(|| json!("git-slop"));
    analyzer
        .entry("version")
        .or_insert_with(|| json!("legacy-schema-4"));
    analyzer
        .entry("report_profile")
        .or_insert_with(|| json!("standard"));
    analyzer
        .entry("analysis_contract_version")
        .or_insert_with(|| json!(1));
    for key in [
        "config_digest",
        "analysis_config_digest",
        "evidence_config_digest",
        "policy_config_digest",
        "presentation_config_digest",
    ] {
        analyzer
            .entry(key)
            .or_insert_with(|| json!(zero_digest.clone()));
    }
    analyzer
        .entry("context_tokenizer")
        .or_insert_with(|| json!("unknown"));
    if let Some(repo) = root.get_mut("repo").and_then(Value::as_object_mut) {
        repo.remove("repo_root");
        let legacy_head = repo.remove("head_commit");
        let legacy_remote = repo.remove("git_remote_url");
        repo.entry("repository_id").or_insert(Value::Null);
        repo.entry("repository_identity_source")
            .or_insert(Value::Null);
        repo.entry("branch").or_insert(Value::Null);
        repo.entry("head_commit_timestamp").or_insert(Value::Null);
        repo.entry("is_shallow").or_insert_with(|| json!(false));
        repo.entry("detached_head").or_insert_with(|| json!(false));
        repo.entry("worktree_clean").or_insert_with(|| json!(false));
        repo.entry("staged_change_count")
            .or_insert_with(|| json!(0));
        repo.entry("modified_tracked_file_count")
            .or_insert_with(|| json!(0));
        repo.entry("untracked_file_count")
            .or_insert_with(|| json!(0));
        repo.entry("worktree_state_digest")
            .or_insert_with(|| json!(zero_digest.clone()));
        repo.entry("analyzed_content_digest")
            .or_insert_with(|| json!(zero_digest.clone()));
        repo.entry("head_sha")
            .or_insert_with(|| legacy_head.unwrap_or(Value::Null));
        if repo
            .get("head_sha")
            .and_then(Value::as_str)
            .is_some_and(|value| value.len() != 40 || !value.bytes().all(|byte| byte.is_ascii_hexdigit()))
        {
            repo.insert("head_sha".to_string(), Value::Null);
        }
        repo.entry("remote_url")
            .or_insert_with(|| legacy_remote.unwrap_or(Value::Null));
        let has_head = repo.get("head_sha").is_some_and(|value| !value.is_null());
        repo.entry("has_head_commit")
            .or_insert_with(|| json!(has_head));
    }
    root.entry("generated_at")
        .or_insert_with(|| json!("1970-01-01T00:00:00Z"));
    root.entry("analyzed_revision_at").or_insert(Value::Null);
    let selected_path_count = root
        .get("files")
        .and_then(Value::as_array)
        .map(Vec::len)
        .unwrap_or_default();
    root.entry("scope").or_insert_with(|| {
        json!({
            "mode": "repository",
            "path": null,
            "selected_path_count": selected_path_count,
            "selected_path_digest": zero_digest
        })
    });
    root.entry("diagnostics").or_insert_with(|| {
        json!({
            "migration": "schema_4",
            "evidence_limit": "legacy report omitted schema-5 diagnostics"
        })
    });
    root.entry("terminology").or_insert_with(|| json!({
        "attention_required": "A review is warranted; the detector does not prove a refactor is required.",
        "budget_exceeded": "A configured file or folder context budget was exceeded.",
        "critical": "The highest detector context or maintenance-pressure band.",
        "error": "A delivery severity used by CI annotations for budget-exceeded findings."
    }));
    root.entry("costs").or_insert_with(|| json!({}));
    let legacy_config = root.get("config").cloned().unwrap_or_else(|| json!({}));
    root.insert(
        "config".to_string(),
        crate::config::effective_from_override(legacy_config)
            .context("legacy embedded configuration cannot be normalized")?,
    );
    if let Some(files) = root.get_mut("files").and_then(Value::as_array_mut) {
        for file in files {
            if let Some(file) = file.as_object_mut() {
                let tokens = file
                    .get("costs")
                    .and_then(|value| value.pointer("/load/file_token_count"))
                    .and_then(Value::as_u64)
                    .unwrap_or_default();
                for (key, value) in [
                    ("bytes", json!(0)),
                    ("lines", json!(0)),
                    ("blank_lines", json!(0)),
                    ("code_lines", json!(0)),
                    ("comment_lines", json!(0)),
                    ("language", json!("Unknown")),
                    ("profile", json!("agent_context")),
                    ("classification", json!("other")),
                    ("generated_from", json!([])),
                    ("generated_provenance", json!({"source_paths": [], "source_globs": [], "generator_command": null, "verification_command": null})),
                    ("has_inline_tests", json!(false)),
                    ("tokens", json!(tokens)),
                    ("context_pressure", json!(0.0)),
                    ("content_fingerprint", json!("")),
                    (
                        "content_sha256",
                        json!("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"),
                    ),
                    ("structural_token_count", json!(0)),
                    ("top_structural_terms", json!([])),
                    ("age_days", json!(0)),
                    ("revisions_window", json!(0)),
                    ("recency_weighted_commits", json!(0.0)),
                    ("added_window", json!(0)),
                    ("deleted_window", json!(0)),
                    ("churn_lines_window", json!(0)),
                    ("line_churn_window", json!(0)),
                    ("token_churn_window", json!(0)),
                    ("relative_churn_window", json!(0.0)),
                    ("late_churn_spike", json!(0.0)),
                    ("author_count_window", json!(0)),
                    ("author_entropy", json!(0.0)),
                    ("top_author_share", json!(0.0)),
                    ("days_since_non_bot_edit", Value::Null),
                    ("recent_maintainer_diversity", json!(0)),
                    ("age_pressure", json!(0.0)),
                    ("revision_norm", json!(0.0)),
                    ("relative_churn_norm", json!(0.0)),
                    ("churn_pressure", json!(0.0)),
                    ("context_band", json!("compact")),
                    ("slop_score", json!(0.0)),
                    ("slop_band", json!("low")),
                    ("reason_codes", json!([])),
                    ("costs", json!({})),
                    ("overlays", json!({})),
                ] {
                    file.entry(key).or_insert(value);
                }
                if file
                    .get("content_fingerprint")
                    .and_then(Value::as_str)
                    .is_none_or(str::is_empty)
                {
                    file.insert("content_fingerprint".to_string(), json!(zero_digest.clone()));
                }
                file.entry("analysis_status")
                    .or_insert_with(|| json!("analyzed"));
                file.entry("skipped_reason").or_insert(Value::Null);
                file.entry("structural_categories")
                    .or_insert_with(|| json!({"mode": "legacy_unknown"}));
                file.entry("symlink_metadata").or_insert(Value::Null);
                if let Some(overlays) = file.get_mut("overlays").and_then(Value::as_object_mut) {
                    if let Some(mut concept) = overlays.remove("semantic_drift") {
                        if let Some(object) = concept.as_object_mut() {
                            if let Some(value) = object.remove("semantic_drift_pressure") {
                                object.insert("concept_dispersion_pressure".to_string(), value);
                            }
                        }
                        overlays.insert("concept_dispersion".to_string(), concept);
                    }
                }
            }
        }
    }
    if let Some(folders) = root.get_mut("folders").and_then(Value::as_array_mut) {
        for folder in folders {
            if let Some(folder) = folder.as_object_mut() {
                let tokens = folder
                    .get("costs")
                    .and_then(|value| value.pointer("/load/folder_token_count"))
                    .and_then(Value::as_u64)
                    .unwrap_or_default();
                for (key, value) in [
                    ("descendant_file_count", json!(0)),
                    ("direct_file_count", json!(0)),
                    ("bytes", json!(0)),
                    ("lines", json!(0)),
                    ("tokens", json!(tokens)),
                    ("direct_tokens", json!(tokens)),
                    ("health_band", json!("compact")),
                    ("context_pressure", json!(0.0)),
                    ("top_file_path", json!("")),
                    ("classification", json!("other")),
                    ("context_band", json!("compact")),
                    ("slop_score", json!(0.0)),
                    ("slop_band", json!("low")),
                    ("reason_codes", json!([])),
                    ("costs", json!({})),
                    ("overlays", json!({})),
                ] {
                    folder.entry(key).or_insert(value);
                }
                if let Some(overlays) = folder.get_mut("overlays").and_then(Value::as_object_mut) {
                    if let Some(mut concept) = overlays.remove("semantic_drift") {
                        if let Some(object) = concept.as_object_mut() {
                            if let Some(value) = object.remove("semantic_drift_pressure") {
                                object.insert("concept_dispersion_pressure".to_string(), value);
                            }
                        }
                        overlays.insert("concept_dispersion".to_string(), concept);
                    }
                }
            }
        }
    }
    let files_by_path = root
        .get("files")
        .and_then(Value::as_array)
        .into_iter()
        .flatten()
        .filter_map(|file| {
            file.get("path")
                .and_then(Value::as_str)
                .map(|path| (path.to_string(), file.clone()))
        })
        .collect::<std::collections::BTreeMap<_, _>>();
    if let Some(queue) = root.get_mut("action_queue").and_then(Value::as_array_mut) {
        for item in queue {
            let Some(object) = item.as_object_mut() else {
                continue;
            };
            let path = object
                .get("path")
                .and_then(Value::as_str)
                .unwrap_or_default();
            let source = files_by_path
                .get(path)
                .cloned()
                .unwrap_or_else(|| json!({}));
            let reasons = object
                .get("reason_codes")
                .or_else(|| source.get("reason_codes"))
                .cloned()
                .unwrap_or_else(|| json!([]));
            for (key, value) in [
                (
                    "profile",
                    source
                        .get("profile")
                        .cloned()
                        .unwrap_or_else(|| json!("agent_context")),
                ),
                (
                    "classification",
                    source
                        .get("classification")
                        .cloned()
                        .unwrap_or_else(|| json!("other")),
                ),
                (
                    "generated_from",
                    source
                        .get("generated_from")
                        .cloned()
                        .unwrap_or_else(|| json!([])),
                ),
                ("synchronization_group", Value::Null),
                ("remediation_kind", json!("investigation")),
                ("remediation_target_paths", json!([path])),
                (
                    "slop_score",
                    source
                        .get("slop_score")
                        .cloned()
                        .unwrap_or_else(|| json!(0.0)),
                ),
                (
                    "slop_band",
                    source
                        .get("slop_band")
                        .cloned()
                        .unwrap_or_else(|| json!("low")),
                ),
                (
                    "context_band",
                    source
                        .get("context_band")
                        .cloned()
                        .unwrap_or_else(|| json!("compact")),
                ),
                (
                    "tokens",
                    source.get("tokens").cloned().unwrap_or_else(|| json!(0)),
                ),
                (
                    "age_days",
                    source.get("age_days").cloned().unwrap_or_else(|| json!(0)),
                ),
                (
                    "revisions_window",
                    source
                        .get("revisions_window")
                        .cloned()
                        .unwrap_or_else(|| json!(0)),
                ),
                (
                    "churn_pressure",
                    source
                        .get("churn_pressure")
                        .cloned()
                        .unwrap_or_else(|| json!(0.0)),
                ),
                ("reason_codes", reasons.clone()),
                (
                    "is_pure_context_hotspot",
                    json!(reasons.as_array().is_some_and(|values| !values.is_empty()
                        && values.iter().all(|reason| matches!(
                            reason.as_str(),
                            Some("high_token_cost" | "critical_token_cost")
                        )))),
                ),
                ("severity", json!("notice")),
                ("evidence_status", json!("legacy_unknown")),
                (
                    "next_action",
                    json!(format!(
                        "git slop explain --path {}",
                        source
                            .get("path")
                            .and_then(Value::as_str)
                            .unwrap_or_default()
                    )),
                ),
            ] {
                object.entry(key).or_insert(value);
            }
        }
    }
    root.entry("observation_feed")
        .or_insert_with(|| json!([]));
    let ranked_files = root
        .get("files")
        .and_then(Value::as_array)
        .cloned()
        .unwrap_or_default()
        .into_iter()
        .map(|file| {
            json!({
                "path": file.get("path").cloned().unwrap_or(Value::Null),
                "classification": file.get("classification").cloned().unwrap_or_else(|| json!("other")),
                "profile": file.get("profile").cloned().unwrap_or_else(|| json!("agent_context")),
                "remediation_kind": "investigation",
                "slop_score": file.get("slop_score").cloned().unwrap_or_else(|| json!(0.0)),
                "slop_band": file.get("slop_band").cloned().unwrap_or_else(|| json!("low")),
                "context_band": file.get("context_band").cloned().unwrap_or_else(|| json!("compact")),
                "tokens": file.get("tokens").cloned().unwrap_or_else(|| json!(0)),
                "reason_codes": file.get("reason_codes").cloned().unwrap_or_else(|| json!([]))
            })
        })
        .collect::<Vec<_>>();
    root.entry("ranked_files")
        .or_insert_with(|| json!(ranked_files));
    root.entry("collection_metadata")
        .or_insert_with(|| json!({}));
    let comparison_record = |record: &Value| {
        let overlays = record.get("overlays").unwrap_or(&Value::Null);
        json!({
            "path": record.get("path").cloned().unwrap_or(Value::Null),
            "content_fingerprint": record.get("content_fingerprint").cloned().unwrap_or(Value::Null),
            "content_sha256": record.get("content_sha256").cloned().unwrap_or(Value::Null),
            "analysis_status": record.get("analysis_status").cloned().unwrap_or_else(|| json!("legacy_unknown")),
            "skipped_reason": record.get("skipped_reason").cloned().unwrap_or(Value::Null),
            "tokens": record.get("tokens").cloned().unwrap_or_else(|| json!(0)),
            "context_band": record.get("context_band").cloned().unwrap_or_else(|| json!("compact")),
            "slop_score": record.get("slop_score").cloned().unwrap_or_else(|| json!(0.0)),
            "slop_band": record.get("slop_band").cloned().unwrap_or_else(|| json!("low")),
            "overlays": {
                "organization_health": {
                    "duplication_pressure": overlays.pointer("/organization_health/duplication_pressure").cloned().unwrap_or(Value::Null),
                    "diffusion_pressure": overlays.pointer("/organization_health/diffusion_pressure").cloned().unwrap_or(Value::Null),
                    "coupling_pressure": overlays.pointer("/organization_health/coupling_pressure").cloned().unwrap_or(Value::Null),
                    "boundary_pressure": overlays.pointer("/organization_health/boundary_pressure").cloned().unwrap_or(Value::Null)
                },
                "verification": {"verification_gap": overlays.pointer("/verification/verification_gap").cloned().unwrap_or(Value::Null)},
                "navigation": {"navigation_pressure": overlays.pointer("/navigation/navigation_pressure").cloned().unwrap_or(Value::Null)},
                "blast_radius": {"blast_radius_pressure": overlays.pointer("/blast_radius/blast_radius_pressure").cloned().unwrap_or(Value::Null)},
                "stewardship": {"stewardship_pressure": overlays.pointer("/stewardship/stewardship_pressure").cloned().unwrap_or(Value::Null)},
                "concept_dispersion": {"concept_dispersion_pressure": overlays.pointer("/concept_dispersion/concept_dispersion_pressure").cloned().unwrap_or(Value::Null)}
            },
            "costs": {"load": {"load_pressure": record.pointer("/costs/load/load_pressure").cloned().unwrap_or_else(|| json!(0.0))}}
        })
    };
    let compare_files = root
        .get("files")
        .and_then(Value::as_array)
        .map(|records| records.iter().map(&comparison_record).collect::<Vec<_>>())
        .unwrap_or_default();
    let compare_folders = root
        .get("folders")
        .and_then(Value::as_array)
        .map(|records| records.iter().map(comparison_record).collect::<Vec<_>>())
        .unwrap_or_default();
    root.entry("compare_index")
        .or_insert_with(|| json!({"files": compare_files, "folders": compare_folders}));
    let compare_file_count = root
        .get("compare_index")
        .and_then(|value| value.get("files"))
        .and_then(Value::as_array)
        .map(Vec::len)
        .unwrap_or_default();
    let compare_folder_count = root
        .get("compare_index")
        .and_then(|value| value.get("folders"))
        .and_then(Value::as_array)
        .map(Vec::len)
        .unwrap_or_default();
    let collection_metadata = root
        .get_mut("collection_metadata")
        .and_then(Value::as_object_mut)
        .ok_or_else(|| anyhow!("collection_metadata must be an object"))?;
    if !collection_metadata.contains_key("compare_index") {
        let files_metadata = collection_metadata
            .get("files")
            .cloned()
            .unwrap_or_else(|| json!({"total": compare_file_count, "returned": compare_file_count, "limit": null, "truncated": false}));
        let folders_metadata = collection_metadata
            .get("folders")
            .cloned()
            .unwrap_or_else(|| json!({"total": compare_folder_count, "returned": compare_folder_count, "limit": null, "truncated": false}));
        collection_metadata.insert(
            "compare_index".to_string(),
            json!({"files": files_metadata, "folders": folders_metadata}),
        );
    }
    root.entry("evidence_completeness").or_insert_with(|| {
        json!({
            "history": "legacy_unknown",
            "repository_size": "legacy_unknown",
            "relationship_evidence": "legacy_unknown"
        })
    });
    if let Some(evidence) = root
        .get_mut("evidence_completeness")
        .and_then(Value::as_object_mut)
    {
        for (key, value) in [
            ("history", json!("legacy_unknown")),
            ("repository_size", json!("legacy_unknown")),
            ("history_window_days", Value::Null),
            ("history_max_commits", Value::Null),
            ("first_seen_age", json!("legacy_unknown")),
            ("churn_window", json!("legacy_unknown")),
            ("author_evidence", json!("legacy_unknown")),
            ("relationship_evidence", json!("legacy_unknown")),
            ("missing_test_evidence_count", json!(0)),
            ("weak_test_mapping_count", json!(0)),
            ("low_test_cochange_evidence_count", json!(0)),
            ("relationship_support", json!("legacy_unknown")),
        ] {
            evidence.entry(key).or_insert(value);
        }
    }
    let tracked_file_count = root
        .get("files")
        .and_then(Value::as_array)
        .map(Vec::len)
        .unwrap_or_default();
    let stats = root
        .entry("stats")
        .or_insert_with(|| json!({}))
        .as_object_mut()
        .ok_or_else(|| anyhow!("stats must be an object"))?;
    for (key, value) in [
        ("tracked_file_count", json!(tracked_file_count)),
        ("analyzed_file_count", json!(tracked_file_count)),
        ("skipped_ignored_count", json!(0)),
        ("skipped_missing_count", json!(0)),
        ("skipped_binary_count", json!(0)),
        ("skipped_undecodable_count", json!(0)),
        ("critical_context_file_count", json!(0)),
        ("critical_slop_file_count", json!(0)),
        ("history_complete", json!(false)),
        ("migration_status", json!("legacy_unknown")),
    ] {
        stats.entry(key).or_insert(value);
    }
    let overlays = root
        .entry("overlays")
        .or_insert_with(|| json!({}))
        .as_object_mut()
        .ok_or_else(|| anyhow!("overlays must be an object"))?;
    let organization = overlays
        .entry("organization_health")
        .or_insert_with(|| json!({}))
        .as_object_mut()
        .ok_or_else(|| anyhow!("overlays.organization_health must be an object"))?;
    if let Some(value) = legacy_relationships {
        organization.entry("relationships").or_insert(value);
    }
    if let Some(value) = legacy_clusters {
        organization.entry("clusters").or_insert(value);
    }
    if let Some(value) = legacy_metrics {
        organization.entry("organization_metrics").or_insert(value);
    }
    root.remove("relationships");
    root.remove("clusters");
    root.remove("organization_metrics");
    let migrated_snapshot = Value::Object(root.clone());
    let health = crate::health::health_rollup_from_report(&migrated_snapshot)
        .context("legacy health evidence cannot be normalized")?;
    root.insert("health".to_string(), serde_json::to_value(health)?);
    root.entry("summary").or_insert_with(|| json!({}));
    if let Some(summary) = root.get_mut("summary").and_then(Value::as_object_mut) {
        summary.retain(|key, _| {
            matches!(
                key.as_str(),
                "top_hotspots" | "top_structural_files" | "top_verification_gaps" | "health"
            )
        });
        for (key, value) in [
            ("top_hotspots", json!([])),
            ("top_structural_files", json!([])),
            ("top_verification_gaps", json!([])),
            (
                "health",
                json!({"file_band_counts": {}, "folder_band_counts": {}}),
            ),
        ] {
            summary.entry(key).or_insert(value);
        }
    }
    let mut migrated = Value::Object(root.clone());
    normalize_legacy_scalar_contracts(&mut migrated);
    Ok(migrated)
}

fn normalize_legacy_scalar_contracts(value: &mut Value) {
    match value {
        Value::Array(values) => {
            for value in values {
                normalize_legacy_scalar_contracts(value);
            }
        }
        Value::Object(object) => {
            for (key, value) in object.iter_mut() {
                match key.as_str() {
                    "context_band" | "health_band" => {
                        let canonical = match value.as_str() {
                            Some("compact" | "healthy" | "warning" | "critical") => None,
                            Some("refactor_required") => Some("critical"),
                            _ => Some("compact"),
                        };
                        if let Some(canonical) = canonical {
                            *value = json!(canonical);
                        }
                    }
                    "slop_band" => {
                        let canonical = match value.as_str() {
                            Some("low" | "moderate" | "high") => None,
                            Some("critical" | "warning") => Some("high"),
                            _ => Some("low"),
                        };
                        if let Some(canonical) = canonical {
                            *value = json!(canonical);
                        }
                    }
                    "slop_score" => {
                        let score = value.as_f64().unwrap_or_default().clamp(0.0, 100.0);
                        *value = json!(score);
                    }
                    _ => {}
                }
                normalize_legacy_scalar_contracts(value);
            }
        }
        _ => {}
    }
}