alef 0.78.0

Opinionated polyglot binding generator for Rust libraries
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
mod binary;
mod create_once_seeds;
mod npmrc_marker;
mod ownership_record;

use super::*;
use crate::core::backend::GeneratedFile;
use crate::core::hash::content_has_alef_marker;

fn managed(relative: &str, content: &str, generated_header: bool, base: &Path) -> Vec<ManagedOutput> {
    managed_outputs(
        &[GeneratedFile {
            path: PathBuf::from(relative),
            content: content.to_owned(),
            generated_header,
        }],
        base,
    )
}

fn seed(base: &Path, relative: &str, content: &str) -> PathBuf {
    let full = base.join(relative);
    std::fs::create_dir_all(full.parent().expect("parent")).expect("mkdir");
    std::fs::write(&full, content).expect("seed");
    full
}

fn options(base: &Path, target: &str, write: bool) -> AdoptOptions {
    AdoptOptions {
        target: target.to_owned(),
        base_dir: base.to_path_buf(),
        write,
        converged_only: false,
        clobber_create_once_seeds: false,
    }
}

fn converged_only(base: &Path, target: &str) -> AdoptOptions {
    AdoptOptions {
        converged_only: true,
        ..options(base, target, true)
    }
}

fn clobbering_seeds(base: &Path, target: &str) -> AdoptOptions {
    AdoptOptions {
        clobber_create_once_seeds: true,
        ..options(base, target, true)
    }
}

fn previewing_seeds(base: &Path, target: &str) -> AdoptOptions {
    AdoptOptions {
        write: false,
        ..clobbering_seeds(base, target)
    }
}

/// The peer-repo case that motivates the create-once guard, in the two shapes that
/// matter, under one glob that sweeps both.
///
/// - `packages/ruby/liter_llm.gemspec` is `generated_header: true` — the marker rail.
///   alef rewrites it every run, so adopting it is what this command is *for*.
/// - `packages/zig/test/liter_llm_test.zig` is `generated_header: false` — a create-once
///   seed. The generated bytes are a placeholder stub; the bytes on disk are a real
///   12-test suite grown from it. Adopting it arms the next `alef version` bump to throw
///   that suite away.
///
/// Returns the two full paths, gemspec first.
fn mixed_rail_fixture(base: &Path) -> (PathBuf, PathBuf, Vec<ManagedOutput>) {
    let gemspec = seed(
        base,
        "packages/ruby/liter_llm.gemspec",
        "Gem::Specification.new do |spec|\n  spec.version = \"0.1.0\"\nend\n",
    );
    let suite = seed(base, "packages/zig/test/liter_llm_test.zig", HAND_GROWN_ZIG_SUITE);
    let outputs = managed_outputs(
        &[
            GeneratedFile {
                path: PathBuf::from("packages/ruby/liter_llm.gemspec"),
                content: "Gem::Specification.new do |spec|\n  spec.version = \"0.2.0\"\nend\n".to_owned(),
                generated_header: true,
            },
            GeneratedFile {
                path: PathBuf::from("packages/zig/test/liter_llm_test.zig"),
                content: PLACEHOLDER_ZIG_SEED.to_owned(),
                generated_header: false,
            },
        ],
        base,
    );
    (gemspec, suite, outputs)
}

/// What alef emits for a zig test seed: enough to compile, nothing to lose.
const PLACEHOLDER_ZIG_SEED: &str =
    "const std = @import(\"std\");\ntest \"smoke\" {\n    try std.testing.expect(true);\n}\n";

/// What the consumer repo actually has at that path after a year of work.
const HAND_GROWN_ZIG_SUITE: &str = "const std = @import(\"std\");\nconst api = @import(\"liter_llm.zig\");\n\ntest \"chat completion round trip\" {\n    try std.testing.expect(api.chat() != null);\n}\n\ntest \"list models\" {\n    try std.testing.expect(api.models().len > 0);\n}\n";

/// A fixture snippet as `e2e::snippets::render_snippet_markdown` emits it today: YAML
/// front matter first (Astro/Starlight requires the opening `---` to be the very first
/// bytes), then the HTML-comment provenance block `docs::render::with_html_header` adds.
fn snippet_with_marker(body: &str) -> String {
    crate::docs::with_html_header(
        format!("---\ntitle: \"Example\"\nid: fixture_python_example\n---\n\n{body}\n"),
        "alef e2e generate",
    )
}

/// The same snippet as consumer repos actually committed it — no marker from any side,
/// which is what froze 15,677 of them in one repo and 9,139 in another.
fn snippet_without_marker(body: &str) -> String {
    format!("---\ntitle: \"Example\"\nid: fixture_python_example\n---\n\n{body}\n")
}

/// The crawlberg case that motivates the command: a `.toml` manifest that has never
/// carried a marker in its history and has also drifted, so the write-time guard
/// refuses it on every run forever. Adoption must break that freeze.
#[test]
fn drifted_manifest_that_never_carried_a_marker_is_adopted_under_write() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let on_disk = "[package]\nname = \"crawlberg-ffi\"\n\n[dependencies]\nlibc = \"0.2\"\n";
    let target = seed(base, "crates/crawlberg-ffi/Cargo.toml", on_disk);
    let generated = "[package]\nname = \"crawlberg-ffi\"\n\n[dependencies]\nlibc = \"0.2\"\nserde = \"1\"\n";
    let outputs = managed("crates/crawlberg-ffi/Cargo.toml", generated, true, base);

    let report = run(&options(base, "crates/crawlberg-ffi/Cargo.toml", true), &outputs).expect("adopt");

    let after = std::fs::read_to_string(&target).expect("read after");
    assert!(
        content_has_alef_marker(&after),
        "adoption must leave a marker the guard will recognise, got:\n{after}"
    );
    assert_eq!(report.adopted, vec![PathBuf::from("crates/crawlberg-ffi/Cargo.toml")]);
    assert_eq!(report.diffs.len(), 1);
    assert_eq!(report.diffs[0].state, AdoptionState::Drifted);
}

/// NEGATIVE CONTROL, half one: adoption of a genuinely divergent file must not write
/// generated content. Adopt stamps the bytes that are already there and nothing else,
/// so the very content the diff warned about survives this command intact and is only
/// replaced later, by an ordinary `alef generate`, where `git diff` shows it.
///
/// This is the control that fails if adopt is ever "simplified" into writing
/// `candidate.generated` — the shape that would make every positive test above pass
/// while silently clobbering the consumer's file. ~keep
#[test]
fn adopting_a_drifted_file_stamps_it_without_replacing_a_single_body_line() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let on_disk = "[package]\nname = \"hand-tuned\"\n\n[dependencies]\nlibc = \"0.2\"\n";
    let target = seed(base, "crates/sample-ffi/Cargo.toml", on_disk);
    let generated = "[package]\nname = \"regenerated\"\n\n[dependencies]\nserde = \"1\"\n";
    let outputs = managed("crates/sample-ffi/Cargo.toml", generated, true, base);

    run(&options(base, "crates/sample-ffi/Cargo.toml", true), &outputs).expect("adopt");

    let after = std::fs::read_to_string(&target).expect("read after");
    assert_eq!(
        after,
        crate::cli::pipeline::ensure_generated_header(&target, on_disk),
        "adoption must be exactly the on-disk bytes plus a header -- nothing else may change"
    );
    assert!(
        after.contains("hand-tuned") && !after.contains("regenerated"),
        "adopt must never write generated content -- that is the clobber this guard exists to prevent, got:\n{after}"
    );
}

/// NEGATIVE CONTROL, half two: the diff is a required product of the command, not a
/// side effect of printing. A drifted file must yield a diff body that actually shows
/// both sides of the divergence.
///
/// Delete or stub the diff step and this fails: `report.diffs` goes empty, or its body
/// stops carrying the `-`/`+` lines. A test that only asserted on final file contents
/// would stay green through exactly that regression, which is why the rendered diff is
/// carried in [`AdoptReport`] rather than written straight to stdout. ~keep
#[test]
fn a_divergent_file_produces_a_full_diff_showing_both_sides() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    seed(
        base,
        "crates/sample-ffi/Cargo.toml",
        "[package]\nname = \"hand-tuned\"\n",
    );
    let outputs = managed(
        "crates/sample-ffi/Cargo.toml",
        "[package]\nname = \"regenerated\"\n",
        true,
        base,
    );

    let report = run(&options(base, "crates/sample-ffi/Cargo.toml", false), &outputs).expect("adopt preview");

    assert_eq!(report.diffs.len(), 1, "a divergent file must produce a diff");
    let body = &report.diffs[0].body;
    assert!(
        body.contains("-name = \"hand-tuned\""),
        "the diff must show the line adoption puts at risk, got:\n{body}"
    );
    assert!(
        body.contains("+name = \"regenerated\""),
        "the diff must show the line that would replace it, got:\n{body}"
    );
    assert_eq!(report.drifted().count(), 1, "divergence must be reported as drift");
}

/// The dry run is the default, and it must be inert: no marker, no content change, no
/// durable record. A human who types `alef adopt <path>` and walks away has consented
/// to nothing.
#[test]
fn preview_run_prints_a_diff_and_leaves_the_file_completely_untouched() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let on_disk = "[package]\nname = \"hand-tuned\"\n";
    let target = seed(base, "crates/sample-ffi/Cargo.toml", on_disk);
    let outputs = managed(
        "crates/sample-ffi/Cargo.toml",
        "[package]\nname = \"regenerated\"\n",
        true,
        base,
    );

    let report = run(&options(base, "crates/sample-ffi/Cargo.toml", false), &outputs).expect("adopt preview");

    assert_eq!(
        std::fs::read_to_string(&target).expect("read after"),
        on_disk,
        "a preview must leave the file byte-for-byte untouched"
    );
    assert!(report.preview);
    assert!(report.adopted.is_empty(), "a preview must adopt nothing");
    assert!(!report.diffs.is_empty(), "a preview must still produce the diff");
}

/// A converged file — identical to generated output apart from the header — is the case
/// an automatic predicate used to claim. It is still adoptable, but only here, and only
/// under `--write`.
///
/// It is reported by path and produces **no** diff: at consumer-repo scale a converged
/// diff is the whole file echoed back as context lines, 12,000 times over, which buries
/// the drifted diffs that do carry information. Asserting `diffs` stays empty here is
/// what fails if converged files are ever folded back onto the diff path. ~keep
#[test]
fn converged_file_is_summarised_without_a_diff_and_still_requires_write() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let body = "[package]\nname = \"sample-ffi\"\n";
    let target = seed(base, "crates/sample-ffi/Cargo.toml", body);
    let outputs = managed("crates/sample-ffi/Cargo.toml", body, true, base);

    let preview = run(&options(base, "crates/sample-ffi/Cargo.toml", false), &outputs).expect("preview");
    assert_eq!(preview.converged, vec![PathBuf::from("crates/sample-ffi/Cargo.toml")]);
    assert!(
        preview.diffs.is_empty(),
        "a converged file must not require a per-file diff read, got {} diff(s)",
        preview.diffs.len()
    );
    assert_eq!(
        std::fs::read_to_string(&target).expect("read after preview"),
        body,
        "even a converged file is not stamped without --write"
    );

    let applied = run(&options(base, "crates/sample-ffi/Cargo.toml", true), &outputs).expect("apply");
    assert_eq!(applied.adopted.len(), 1);
    assert!(content_has_alef_marker(
        &std::fs::read_to_string(&target).expect("read after write")
    ));
}

/// The measured migration blocker, at the file level: a committed e2e snippet `.md`
/// whose only difference from generated output is the provenance block. It must
/// classify as converged (not drifted — there is no content to read), and adoption must
/// put the marker *in the file*, because the write guard reads a marker on any
/// extension and a marker cannot be separated from the file it describes.
#[test]
fn frozen_e2e_snippet_is_converged_and_adopted_by_stamping_not_by_a_record_entry() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let relative = "docs/snippets/python/api/example.md";
    let target = seed(base, relative, &snippet_without_marker("example()"));
    let outputs = managed(relative, &snippet_with_marker("example()"), false, base);

    let report = run(&options(base, relative, true), &outputs).expect("adopt");

    assert_eq!(report.converged, vec![PathBuf::from(relative)]);
    assert!(report.diffs.is_empty(), "a marker-only difference is not drift");
    let after = std::fs::read_to_string(&target).expect("read after");
    assert!(
        content_has_alef_marker(&after),
        "the guard proves ownership from the marker in the file, got:\n{after}"
    );
    assert!(
        after.starts_with("---\n"),
        "the marker must land after the YAML front matter, not before it, got:\n{after}"
    );
    assert!(
        report.recorded_unstampable.is_empty(),
        "a stampable snippet must not consume an entry in the committed ownership record"
    );
    assert!(
        !base.join(".alef-ownership.toml").exists(),
        "12k snippet adoptions must not each take a line in a committed manifest"
    );
}

/// THE PHANTOM-DIFF DEFECT: a self-marking, non-Markdown backend (Swift/Kotlin/Dart/
/// Gleam/Zig custom headers are the shipped case; `.swift` here stands in for all of
/// them) bakes its own marker text into `generated`, which is `generated_header: false`
/// and so never passes through the generic `hash::header` route 1 uses. Before
/// [`stamp_for`]'s route 0, adoption stamped `existing` with that generic default
/// header instead of the backend's own marker wording, so `classify` compared two
/// different header spellings and called the file Drifted even though the body -- every
/// line the operator was told to review -- was byte-for-byte unchanged. The rendered
/// diff proved it: the only `+` line was the marker itself, no `-` line existed at all.
/// A phantom "content differs" trains an operator to consent to diffs without reading
/// them, which is precisely the failure a printed diff exists to prevent.
#[test]
fn a_self_marked_non_markdown_file_with_an_unchanged_body_converges_not_drifts() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let body = "struct Foo {\n    let x: Int\n}\n";
    let marker = "// Generated by alef. Do not edit by hand.\n";
    let target = seed(base, "Sources/Foo.swift", body);
    let outputs = managed("Sources/Foo.swift", &format!("{marker}{body}"), false, base);

    let report = run(&options(base, "Sources/Foo.swift", false), &outputs).expect("adopt preview");

    assert!(
        report.diffs.is_empty(),
        "a header-only difference is not drift -- the printed diff would show no changed body \
         line, which is exactly the phantom this test closes: {:?}",
        report.diffs
    );
    assert_eq!(report.converged, vec![PathBuf::from("Sources/Foo.swift")]);

    let applied = run(&options(base, "Sources/Foo.swift", true), &outputs).expect("adopt");
    assert_eq!(applied.adopted, vec![PathBuf::from("Sources/Foo.swift")]);
    let after = std::fs::read_to_string(&target).expect("read after");
    assert_eq!(
        after, outputs[0].content,
        "adoption must stamp the backend's own marker, not a generic default header that a \
         subsequent `alef generate` would immediately overwrite again"
    );
}

/// THE PAIRED NEGATIVE CONTROL: a self-marked file whose body genuinely diverged must
/// still classify as Drifted and print a diff that shows the real divergence -- route 0
/// must never suppress a true content difference just because both sides carry a marker.
#[test]
fn a_self_marked_non_markdown_file_with_a_changed_body_still_drifts() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let marker = "// Generated by alef. Do not edit by hand.\n";
    seed(base, "Sources/Foo.swift", "struct Foo {\n    let x: Int\n}\n");
    let outputs = managed(
        "Sources/Foo.swift",
        &format!("{marker}struct Foo {{\n    let x: Int\n    let y: Int\n}}\n"),
        false,
        base,
    );

    let report = run(&options(base, "Sources/Foo.swift", false), &outputs).expect("adopt preview");

    assert_eq!(
        report.diffs.len(),
        1,
        "a real body change must still be reported as drift"
    );
    assert_eq!(report.diffs[0].state, AdoptionState::Drifted);
    assert!(
        report.diffs[0].body.contains("+    let y: Int"),
        "the diff must show the real added line, got:\n{}",
        report.diffs[0].body
    );
}

/// The adoption must be exactly what the next generate would write, not merely
/// marker-bearing: the regenerate command is read back out of the generated bytes
/// rather than guessed, because guessing `alef docs` for a snippet whose header says
/// `alef e2e generate` leaves a one-line difference and re-drifts every file.
#[test]
fn stamped_snippet_is_byte_identical_to_generated_output() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let relative = "docs/snippets/python/api/example.md";
    let generated = snippet_with_marker("example()");
    let target = seed(base, relative, &snippet_without_marker("example()"));
    let outputs = managed(relative, &generated, false, base);

    run(&options(base, relative, true), &outputs).expect("adopt");

    assert_eq!(
        std::fs::read_to_string(&target).expect("read after"),
        outputs[0].content,
        "adoption must land the exact bytes the next generate writes, or the file is still not converged"
    );
}

/// THE END-TO-END QUESTION, put to the real guard rather than to a proxy: after
/// `alef adopt --write`, does an ordinary `alef generate` actually write the file?
///
/// Every other assertion here checks a marker or a report field, and a marker in the
/// right shape is not the same fact as a write the guard permits — the guard also
/// consults the ownership record, so a test that never ran it could pass for the wrong
/// reason. The temp dir carries no `.alef/` cache and no `.alef-ownership.toml`, which
/// is what a fresh clone and CI look like, so the marker adoption just wrote is the only
/// proof available. The control is the same file without the adoption: it must be
/// refused, or this test would pass with the guard deleted. ~keep
#[test]
fn an_adopted_snippet_is_regenerable_on_a_checkout_with_no_alef_cache_at_all() {
    let relative = PathBuf::from("docs/snippets/python/api/example.md");
    let generated = snippet_with_marker("example()");
    let updated = snippet_with_marker("updated_example()");

    let regenerate_over = |existing: &str, adopt_first: bool| {
        let dir = tempfile::tempdir().expect("tempdir");
        let base = dir.path();
        let target = seed(base, "docs/snippets/python/api/example.md", existing);
        if adopt_first {
            let outputs = managed("docs/snippets/python/api/example.md", &generated, false, base);
            run(&options(base, "docs/snippets/python/api/example.md", true), &outputs).expect("adopt");
        }
        assert!(
            !base.join(".alef").exists(),
            "no cache may exist for this to mean anything"
        );
        assert!(
            !base.join(".alef-ownership.toml").exists(),
            "the marker, not the record, must be what proves ownership here"
        );
        let report = crate::cli::pipeline::write_scaffold_files_report(
            &[GeneratedFile {
                path: relative.clone(),
                content: updated.clone(),
                generated_header: false,
            }],
            base,
            true,
        )
        .expect("write report");
        (
            report.changed_paths.contains(&target),
            report.refused_paths.contains(&target),
            std::fs::read_to_string(&target).expect("still readable"),
        )
    };

    let (written, refused, content) = regenerate_over(&snippet_without_marker("example()"), true);
    assert!(written, "an adopted snippet must be regenerable");
    assert!(!refused);
    assert!(content.contains("updated_example()"));

    let (control_written, control_refused, control_content) =
        regenerate_over(&snippet_without_marker("example()"), false);
    assert!(!control_written, "without the adoption the guard must still refuse");
    assert!(control_refused);
    assert!(
        control_content.contains("example()") && !control_content.contains("updated_example()"),
        "a refused write must leave the file untouched"
    );
}

/// `--converged-only` is the bulk-migration switch, and it must be incapable of touching
/// a drifted file. A drifted match is left byte-for-byte alone, reported separately, and
/// its full diff is still produced — so the reader's next step is reviewing it, not
/// discovering afterwards that it was swept up.
#[test]
fn converged_only_adopts_the_converged_set_and_refuses_to_sweep_a_drifted_file() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let clean = seed(base, "docs/snippets/python/api/a.md", &snippet_without_marker("a()"));
    let hand_edited = snippet_without_marker("hand_written_by_a_person()");
    let edited = seed(base, "docs/snippets/python/api/b.md", &hand_edited);
    let outputs = managed_outputs(
        &[
            GeneratedFile {
                path: PathBuf::from("docs/snippets/python/api/a.md"),
                content: snippet_with_marker("a()"),
                generated_header: false,
            },
            GeneratedFile {
                path: PathBuf::from("docs/snippets/python/api/b.md"),
                content: snippet_with_marker("b()"),
                generated_header: false,
            },
        ],
        base,
    );

    let report = run(&converged_only(base, "docs/snippets/**/*.md"), &outputs).expect("adopt");

    assert_eq!(report.converged, vec![PathBuf::from("docs/snippets/python/api/a.md")]);
    assert_eq!(
        report.adopted,
        vec![PathBuf::from("docs/snippets/python/api/a.md")],
        "only the converged file may be adopted"
    );
    assert_eq!(
        report.skipped_drifted,
        vec![PathBuf::from("docs/snippets/python/api/b.md")]
    );
    assert!(content_has_alef_marker(
        &std::fs::read_to_string(&clean).expect("read converged")
    ));
    assert_eq!(
        std::fs::read_to_string(&edited).expect("read drifted"),
        hand_edited,
        "a drifted file must survive --converged-only untouched -- it may be a deliberate hand-edit"
    );
    assert_eq!(report.diffs.len(), 1, "the drifted file still gets its full diff");
    assert!(
        report.diffs[0].body.contains("-hand_written_by_a_person()"),
        "the skipped file's diff must still show what adoption would put at risk, got:\n{}",
        report.diffs[0].body
    );
}

/// NEGATIVE CONTROL for the bulk path: without `--converged-only`, a plain `--write`
/// over a mixed glob still adopts the drifted file — and must still have produced its
/// full diff first. There is no flag that adopts a drifted file *without* one; this is
/// the test that fails if a `--yes`-style bypass is ever added. ~keep
#[test]
fn bulk_write_over_a_mixed_glob_never_adopts_a_drifted_file_without_producing_its_diff() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    seed(base, "docs/snippets/python/api/a.md", &snippet_without_marker("a()"));
    seed(
        base,
        "docs/snippets/python/api/b.md",
        &snippet_without_marker("hand_written()"),
    );
    let outputs = managed_outputs(
        &[
            GeneratedFile {
                path: PathBuf::from("docs/snippets/python/api/a.md"),
                content: snippet_with_marker("a()"),
                generated_header: false,
            },
            GeneratedFile {
                path: PathBuf::from("docs/snippets/python/api/b.md"),
                content: snippet_with_marker("b()"),
                generated_header: false,
            },
        ],
        base,
    );

    let report = run(&options(base, "docs/snippets/**/*.md", true), &outputs).expect("adopt");

    assert_eq!(report.adopted.len(), 2);
    assert!(report.skipped_drifted.is_empty());
    let drifted: Vec<PathBuf> = report.drifted().map(|diff| diff.relative.clone()).collect();
    assert_eq!(
        drifted,
        vec![PathBuf::from("docs/snippets/python/api/b.md")],
        "every drifted adoption must be accompanied by its own rendered diff"
    );
}

/// THE central case for the create-once guard: one glob sweeps both rails at once.
/// The marker-rail gemspec is adopted because that is what this command is for; the
/// create-once zig suite is excluded and named, because adopting it would arm the next
/// generate to replace a real 12-test suite with a placeholder stub. Both halves are
/// asserted in one test on purpose — a guard that excludes the seed but also stops
/// adopting the gemspec has broken the command instead of protecting it. ~keep
#[test]
fn a_mixed_glob_adopts_the_marker_rail_and_excludes_the_create_once_seed_by_name() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let (gemspec, suite, outputs) = mixed_rail_fixture(base);

    let report = run(&options(base, "packages/**", true), &outputs).expect("adopt");

    assert_eq!(
        report.skipped_create_once,
        vec![PathBuf::from("packages/zig/test/liter_llm_test.zig")],
        "the create-once seed must be excluded and reported by path, not as a count"
    );
    assert_eq!(
        report.adopted,
        vec![PathBuf::from("packages/ruby/liter_llm.gemspec")],
        "the marker-rail file must still be adopted; excluding it would break the command"
    );
    assert_eq!(
        std::fs::read_to_string(&suite).expect("read suite"),
        HAND_GROWN_ZIG_SUITE,
        "an excluded seed must be left byte-identical"
    );
    assert!(
        !std::fs::read_to_string(&suite).expect("read suite").contains("alef"),
        "an excluded seed must not be stamped, or the next generate replaces it"
    );
    assert!(
        std::fs::read_to_string(&gemspec)
            .expect("read gemspec")
            .contains("alef"),
        "the adopted gemspec must carry its marker"
    );
}

/// The escape hatch, proven to actually open. Without this test the flag could be inert
/// and every other assertion in this file would still pass. ~keep
#[test]
fn clobber_create_once_seeds_adopts_the_seed_it_otherwise_refuses() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let (_gemspec, suite, outputs) = mixed_rail_fixture(base);

    let report = run(&clobbering_seeds(base, "packages/**"), &outputs).expect("adopt");

    assert!(
        report.skipped_create_once.is_empty(),
        "nothing may be excluded once the operator has opted in: {:?}",
        report.skipped_create_once
    );
    assert_eq!(
        report.adopted,
        vec![
            PathBuf::from("packages/ruby/liter_llm.gemspec"),
            PathBuf::from("packages/zig/test/liter_llm_test.zig"),
        ],
        "both rails must be adopted under --clobber-create-once-seeds"
    );
    let after = std::fs::read_to_string(&suite).expect("read suite");
    assert!(
        after.contains("chat completion round trip"),
        "adoption stamps and preserves content; it must not replace the suite here: {after}"
    );
}

/// Adopt must never become a general-purpose "stamp this file" tool. A path alef does
/// not generate is refused outright, whatever the human types.
#[test]
fn a_path_alef_does_not_generate_is_refused() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    seed(base, "src/main.rs", "fn main() {}\n");
    let outputs = managed("crates/sample-ffi/Cargo.toml", "[package]\n", true, base);

    let error = run(&options(base, "src/main.rs", true), &outputs).expect_err("must refuse");

    assert!(
        error.to_string().contains("no alef-managed output matches"),
        "unexpected error: {error}"
    );
    assert_eq!(
        std::fs::read_to_string(base.join("src/main.rs")).expect("read after"),
        "fn main() {}\n",
        "a refused target must be left untouched"
    );
}

/// A file that already carries a marker is not in the ownership trap at all. Adopt
/// reports it and produces no diff, so a glob that sweeps a healthy tree stays quiet.
#[test]
fn already_marked_file_is_reported_as_owned_and_produces_no_diff() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let marked = format!(
        "{}[package]\nname = \"sample-ffi\"\n",
        crate::core::hash::header(crate::core::hash::CommentStyle::Hash)
    );
    seed(base, "crates/sample-ffi/Cargo.toml", &marked);
    let outputs = managed(
        "crates/sample-ffi/Cargo.toml",
        "[package]\nname = \"other\"\n",
        true,
        base,
    );

    let report = run(&options(base, "crates/sample-ffi/Cargo.toml", true), &outputs).expect("adopt");

    assert_eq!(
        report.already_owned,
        vec![PathBuf::from("crates/sample-ffi/Cargo.toml")]
    );
    assert!(report.diffs.is_empty());
    assert!(report.adopted.is_empty());
}

/// A format with no comment syntax at all (`.json`) cannot be stamped, so adoption
/// falls back to the committed `.alef-ownership.toml` record — the same proof route the
/// write-time guard already consults for unmarkable extensions.
#[test]
fn unstampable_format_is_adopted_through_the_durable_record_instead() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let on_disk = "{\n  \"name\": \"sample\"\n}\n";
    let target = seed(base, "packages/node/package.json", on_disk);
    assert!(!crate::cli::cache::is_scaffold_owned_path(base, &target));
    let outputs = managed(
        "packages/node/package.json",
        "{\n  \"name\": \"sample\",\n  \"version\": \"2\"\n}\n",
        true,
        base,
    );

    let report = run(&options(base, "packages/node/package.json", true), &outputs).expect("adopt");

    assert_eq!(
        std::fs::read_to_string(&target).expect("read after"),
        on_disk,
        "an unstampable file's bytes must not change: the record carries the ownership proof"
    );
    assert!(crate::cli::cache::is_scaffold_owned_path(base, &target));
    assert_eq!(
        report.recorded_unstampable,
        vec![PathBuf::from("packages/node/package.json")]
    );

    // The axis that matters and that `is_scaffold_owned_path` alone does not examine:
    // *where* the consent was written down. A human's adoption decision that only exists
    // inside the gitignored `.alef/` cache is not a decision the rest of the team, or CI,
    // can ever see — the operator reads the diff once and every other checkout still
    // refuses. Dropping the cache is what a fresh clone of their commit looks like. ~keep
    std::fs::remove_dir_all(base.join(".alef")).ok();
    assert!(
        base.join(".alef-ownership.toml").exists(),
        "adoption must leave its proof in a file the operator can commit"
    );
    assert!(
        crate::cli::cache::is_scaffold_owned_path(base, &target),
        "an adoption must survive into a checkout that never had the adopting machine's cache"
    );
}

/// A glob selects several managed paths in one invocation, and every one of them gets
/// its own full diff before anything is written.
#[test]
fn glob_target_diffs_every_match_before_adopting_any_of_them() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    seed(base, "crates/a-ffi/Cargo.toml", "[package]\nname = \"a\"\n");
    seed(base, "crates/b-ffi/Cargo.toml", "[package]\nname = \"b\"\n");
    let outputs = managed_outputs(
        &[
            GeneratedFile {
                path: PathBuf::from("crates/a-ffi/Cargo.toml"),
                content: "[package]\nname = \"a\"\nedition = \"2024\"\n".to_owned(),
                generated_header: true,
            },
            GeneratedFile {
                path: PathBuf::from("crates/b-ffi/Cargo.toml"),
                content: "[package]\nname = \"b\"\nedition = \"2024\"\n".to_owned(),
                generated_header: true,
            },
        ],
        base,
    );

    let report = run(&options(base, "crates/*-ffi/Cargo.toml", true), &outputs).expect("adopt");

    assert_eq!(report.diffs.len(), 2, "every match must be diffed");
    assert_eq!(report.adopted.len(), 2);
    for diff in &report.diffs {
        assert!(
            diff.body.contains("+edition = \"2024\""),
            "each match needs its own real diff, got:\n{}",
            diff.body
        );
    }
}

/// A target that matches managed output which does not exist on disk yet is refused
/// with a message pointing at `alef generate`: there is no ownership conflict to
/// resolve, and stamping a file into existence is not adoption.
#[test]
fn target_matching_only_absent_output_is_refused_with_generate_guidance() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let outputs = managed("crates/sample-ffi/Cargo.toml", "[package]\n", true, base);

    let error = run(&options(base, "crates/sample-ffi/Cargo.toml", true), &outputs).expect_err("must refuse");

    assert!(
        error.to_string().contains("nothing exists on disk yet"),
        "unexpected error: {error}"
    );
    assert!(!base.join("crates/sample-ffi/Cargo.toml").exists());
}

/// THE regression this lane exists to fix: the snippet-coverage ledger
/// (`e2e::snippets::COVERAGE_MANIFEST`) is strict JSON emitted with
/// `generated_header: false`, so before this fix `carries_alef_marker()` alone
/// classified it as a create-once seed and `alef adopt` refused it -- the exact loop
/// this module's header documents as the blocker. It must not be create-once. ~keep
#[test]
fn the_snippet_coverage_ledger_is_not_a_create_once_seed() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let relative = Path::new("docs/snippets").join(crate::e2e::snippets::COVERAGE_MANIFEST);
    let outputs = managed_outputs(
        &[GeneratedFile {
            path: relative,
            content: "{\n  \"format_version\": 2,\n  \"generated_paths\": []\n}\n".to_owned(),
            generated_header: false,
        }],
        base,
    );

    assert_eq!(outputs.len(), 1);
    assert!(
        !outputs[0].create_once,
        "the coverage ledger is pure derived output alef owns outright, not a human-grown seed"
    );
}

/// THE load-bearing negative control, and the one the zig test below cannot stand in for.
///
/// A fix that unblocked the ledger by answering "anything unmarkable is derived output"
/// would pass `the_snippet_coverage_ledger_is_not_a_create_once_seed` on its own, and
/// pass the zig case too — `.zig` is a markable extension, so it is protected by the
/// marker rail whatever the derived-output property says, which makes it no evidence at
/// all about this axis. `composer.json` is the file that actually distinguishes the two
/// fixes: strict JSON, structurally unmarkable, emitted `generated_header: false`, and
/// squarely a file a human edits. It must stay create-once. ~keep
#[test]
fn an_unmarkable_hand_editable_seed_stays_a_create_once_seed() {
    let dir = tempfile::tempdir().expect("tempdir");
    let outputs = managed(
        "packages/php/composer.json",
        "{\n  \"name\": \"vendor/generated\"\n}\n",
        false,
        dir.path(),
    );

    assert!(
        outputs[0].create_once,
        "an unmarkable file outside alef's derived-output registry is still a seed: widening the \
         property to every unmarkable path is a licence to clobber hand-written manifests"
    );
}

/// The same protection at the command level rather than the classifier level: a plain
/// `alef adopt --write` on an unmarkable hand-editable seed must still refuse, name the
/// path, and point at `--clobber-create-once-seeds` — the behaviour that stops a repo-wide
/// glob from arming a later `alef version` bump to replace real content with a placeholder.
/// Asserting only the classifier would not catch a `run` that stopped honouring it. ~keep
#[test]
fn adopt_still_refuses_an_unmarkable_hand_editable_seed_under_a_plain_write() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let hand_written = "{\n  \"name\": \"vendor/hand-written\",\n  \"require\": {\"ext-json\": \"*\"}\n}\n";
    let target = seed(base, "packages/php/composer.json", hand_written);
    let outputs = managed(
        "packages/php/composer.json",
        "{\n  \"name\": \"vendor/generated\"\n}\n",
        false,
        base,
    );

    let error = run(&options(base, "packages/php/composer.json", true), &outputs).expect_err("must refuse");

    assert!(
        error.to_string().contains("--clobber-create-once-seeds"),
        "the refusal must point at the deliberate opt-in, got: {error}"
    );
    assert_eq!(
        std::fs::read_to_string(&target).expect("read after"),
        hand_written,
        "a refused seed's bytes must be untouched"
    );
    assert!(
        !crate::cli::cache::is_scaffold_owned_path(base, &target),
        "and no ownership claim may be recorded for it either"
    );
}

/// NEGATIVE CONTROL: the ledger exclusion must be scoped to its own name, not widen the
/// create-once guard in general. An ordinary `generated_header: false` seed (the zig
/// test placeholder from `mixed_rail_fixture`) must still classify as create-once. ~keep
#[test]
fn a_genuine_create_once_seed_is_still_classified_as_create_once() {
    let dir = tempfile::tempdir().expect("tempdir");
    let outputs = managed(
        "packages/zig/test/liter_llm_test.zig",
        PLACEHOLDER_ZIG_SEED,
        false,
        dir.path(),
    );

    assert!(
        outputs[0].create_once,
        "a genuine human-grown seed must remain create-once, or --clobber-create-once-seeds \
         stops protecting anything"
    );
}

/// End-to-end: `alef adopt` must not refuse the ledger under a plain `--write`, with no
/// `--clobber-create-once-seeds` escape hatch needed. This is the other half of the fix
/// this module's header documents -- the write-time ownership guard alone does not
/// unblock a regeneration while the adopt path still treats the ledger as create-once.
///
/// The ledger classifies `AlreadyOwned`, not `Converged`/`Drifted` adopted-via-`--write`,
/// even though its on-disk content genuinely differs from what alef would generate here.
/// `classify` now asks the same `is_owned_by_ownership_record` union the write guard
/// consults, and that union answers "owned" for `.alef-snippet-coverage.json` purely by
/// name (`is_alef_derived_output`) -- matching the write guard, which already overwrites
/// this file unconditionally regardless of any `.alef-ownership.toml` entry. There is
/// truly nothing for `alef adopt` to unblock: `report.already_owned`, not
/// `report.adopted`, is the correct outcome. Before this fix `classify` never asked that
/// union at all, so a converged (or, as here, still-drifted) copy of the ledger was
/// re-offered as a live adoption decision on every run. ~keep
#[test]
fn adopt_does_not_refuse_the_snippet_coverage_ledger() {
    let dir = tempfile::tempdir().expect("tempdir");
    let base = dir.path();
    let relative = Path::new("docs/snippets").join(crate::e2e::snippets::COVERAGE_MANIFEST);
    let on_disk = "{\n  \"format_version\": 2,\n  \"generated_paths\": [\"a\"]\n}\n";
    seed(base, relative.to_str().expect("utf8 path"), on_disk);
    let generated = "{\n  \"format_version\": 2,\n  \"generated_paths\": [\"a\", \"b\"]\n}\n";
    let outputs = managed_outputs(
        &[GeneratedFile {
            path: relative.clone(),
            content: generated.to_owned(),
            generated_header: false,
        }],
        base,
    );

    let report = run(&options(base, relative.to_str().expect("utf8 path"), true), &outputs).expect("adopt");

    assert!(
        report.skipped_create_once.is_empty(),
        "the ledger must never be excluded as a create-once seed: {:?}",
        report.skipped_create_once
    );
    assert_eq!(
        report.already_owned,
        vec![relative],
        "the write guard already accepts this path unconditionally by name, so adopt must \
         report it already owned rather than offering it as a pending adoption decision"
    );
    assert!(
        report.adopted.is_empty(),
        "a file with nothing to adopt must never appear in the adopted list: {:?}",
        report.adopted
    );
}

#[test]
fn a_self_marked_file_whose_body_was_truncated_is_drifted_not_converged() {
    let dir = tempfile::tempdir().unwrap();
    let full = dir.path().join("Probe.swift");
    let generated = "// Code generated by alef. DO NOT EDIT.\nfunc a() {}\nfunc b() {}\n";
    let existing = "func b() {}\n";
    let candidate = super::classify(dir.path(), &full, Path::new("Probe.swift"), generated, existing, false);
    assert_eq!(
        candidate.state,
        super::AdoptionState::Drifted,
        "half the body is missing, so the suffix match must not be read as convergence"
    );
}

#[test]
fn an_emptied_self_marked_file_is_drifted_not_converged() {
    let dir = tempfile::tempdir().unwrap();
    let full = dir.path().join("Probe2.swift");
    let generated = "// Code generated by alef. DO NOT EDIT.\nfunc a() {}\n";
    let candidate = super::classify(dir.path(), &full, Path::new("Probe2.swift"), generated, "", false);
    assert_eq!(
        candidate.state,
        super::AdoptionState::Drifted,
        "every string ends with the empty string; an emptied file has not converged"
    );
}