car-server-core 0.52.0

Transport-neutral library for the CAR daemon JSON-RPC dispatcher (used by car-server and tokhn-daemon)
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
//! What the repository itself says about how to work in it.
//!
//! Until this existed, everything a coder session knew about the repo it was
//! pointed at was two lines from `summarize_repo` — the top-level entries and
//! which build files exist. Three separate bodies of project knowledge sat on
//! disk and none of them reached the loop (car#1071).
//!
//! ## Why a directory listing is not enough
//!
//! The rules that get a diff rejected are almost never expressible as an
//! outcome contract. They are review-time rules: "no cargo feature flags,
//! ever"; "any change crossing the FFI boundary updates five surfaces in the
//! same change, two of which are hand-maintained files". A model that has not
//! read them does the *normal* thing — adds a `[features]` entry to make
//! something optional — and produces a diff that compiles, passes its contract,
//! and is rejected on sight. Contract-green and mergeable are different
//! properties, and only one of them was reaching the model.
//!
//! ## What this loads
//!
//! - **Agent instructions** — `CLAUDE.md` or `AGENTS.md` at the worktree root,
//!   plus any git-tracked nested ones, each labelled with the subtree it
//!   governs ([`nested_instructions`]).
//! - **`.car/` project knowledge** — identity and knowledge entries, via
//!   [`car_memgine::project`], whose discovery already walks up from a starting
//!   directory the way `.gitignore` resolution does.
//!
//! ## Two limits, stated because they are easy to mistake for coverage
//!
//! **Instructions that delegate are followed only one level.** This repo's own
//! `CLAUDE.md` says the full binding surface "lives in the `car-bindings-api`
//! skill — invoke it whenever work touches the FFI boundary". Loading the root
//! file gets the model the *pointer*, not the rule, and closing that needs
//! skill selection — a larger change than reading a file. [`available_skills`]
//! narrows the gap by indexing the skills so the pointer at least resolves to
//! a path the model can `read_file`.
//!
//! Nested `CLAUDE.md` files are no longer part of this limit:
//! [`nested_instructions`] inlines them, scoped to the directory each governs.
//!
//! **This is untrusted-adjacent input.** The text lands in a system prompt, and
//! it comes from the repository under work. That is the same trust level as the
//! code the session is already reading and editing, so for a repo an operator
//! pointed the coder at, it is fair. It would NOT be fair for content that
//! arrived from a public tracker — see car#1081 — and nothing here should ever
//! be extended to load one.

use std::path::Path;

/// Root-level files that carry agent instructions, in precedence order. Only
/// the first one found is read: a repo with both is not asking for them to be
/// concatenated.
const INSTRUCTION_FILES: [&str; 2] = ["CLAUDE.md", "AGENTS.md"];

/// Byte ceiling for the root instruction file.
///
/// Generous, because these are the rules that decide whether a diff is
/// acceptable and truncating them mid-rule is worse than not loading them.
///
/// **This constant went stale once and silently ate the payload.** It was
/// 24_000, documented as "this repo's own is ~19KB, which fits". CAR's
/// `CLAUDE.md` then grew to 38.7KB, and the cut landed at byte 24_000 — which
/// is 800 bytes *before* the section headed "Project conventions (hard
/// rules)". So the coder working on CAR's own repository received every
/// architecture note and **not one of the rules**: no cargo feature flags, no
/// runtime toggle that picks between implementations, keep all FFI bindings in
/// sync, documentation parity. Those are the two examples this module's own
/// header uses to explain why it exists, and both were in the discarded 38%.
///
/// A file whose whole job is carrying rules that no contract can check must
/// not lose those rules to a number nobody revisited, so
/// `the_repos_own_instructions_fit_the_cap` now fails the build when CAR's
/// `CLAUDE.md` outgrows this. Raise the constant when that test goes red;
/// do not raise it silently, and do not delete the test.
pub const MAX_INSTRUCTIONS_BYTES: usize = 64_000;

/// Combined ceiling for **nested** instruction files, and the per-file cap
/// inside it. Separate from the root budget so a deep tree cannot crowd out
/// the root rules, which apply everywhere.
const MAX_NESTED_INSTRUCTIONS_BYTES: usize = 12_000;
const MAX_NESTED_FILE_BYTES: usize = 6_000;

/// How many nested instruction files to inline before the rest become
/// pointers.
const MAX_NESTED_FILES: usize = 8;

/// Below this much remaining combined budget, defer a file rather than inline
/// a sliver of it. Truncating mid-rule is the failure this module exists to
/// avoid; a path the model can read is strictly better than half a sentence.
const MIN_USEFUL_NESTED_BYTES: usize = 400;

/// Byte ceiling for rendered `.car/` knowledge.
pub const MAX_KNOWLEDGE_BYTES: usize = 8_000;

/// How many knowledge entries to render before stopping.
const MAX_KNOWLEDGE_ENTRIES: usize = 40;

/// Read the repository's agent-instruction file, if it has one.
///
/// Truncation is announced in the text rather than silent — a model that has
/// been handed half a rule set should know that is what happened.
pub fn agent_instructions(worktree: &Path) -> Option<(String, String)> {
    for name in INSTRUCTION_FILES {
        let path = worktree.join(name);
        let Ok(raw) = std::fs::read_to_string(&path) else {
            continue;
        };
        if raw.trim().is_empty() {
            continue;
        }
        return Some((
            name.to_string(),
            truncate_note(&raw, MAX_INSTRUCTIONS_BYTES),
        ));
    }
    None
}

/// Nested `CLAUDE.md` / `AGENTS.md` files — scoped rules that apply to one
/// subtree (car#1071).
///
/// ## Why `git ls-files` rather than a directory walk
///
/// Tracked-ness is doing two jobs at once here, and both matter.
///
/// It is the **relevance** filter. A plain recursive walk of CAR's own
/// checkout finds 16 instruction files; `git ls-files` finds 2. The other 14
/// are five nested `.claude/worktrees/` checkouts of this same repository and
/// four extracted `bench/coder-ab` fixtures — copies and test data, none of
/// them guidance about the code under work. A skip-list would have to grow a
/// new entry every time someone adds a build or scratch directory, and would
/// be wrong until they did.
///
/// It is also the **trust** filter, which is the more important half. This
/// text lands in a system prompt. A tracked file is one a maintainer committed
/// and review saw; an untracked one is anything that happens to be sitting in
/// the worktree — including a file the model itself just wrote. Reading
/// untracked instruction files would let a session author its own rules
/// mid-run and have them injected as maintainer intent on the next iteration.
///
/// ## Inlined, not merely indexed
///
/// [`available_skills`] indexes rather than inlines, because five skill bodies
/// would swamp the prompt and a skill is opt-in by nature. Nested instruction
/// files are the opposite case: they are small (CAR's is 1.2KB), they are not
/// optional, and the failure they prevent is silent. `car-ffi-napi/CLAUDE.md`
/// opens with *"These bugs cost many hours. Do not reintroduce them."* — a
/// pointer to that is a rule the model has to choose to follow, and this
/// module's header already names "loading the pointer, not the rule" as a
/// known way this goes wrong.
///
/// Budgets keep that honest for a repo unlike this one: at most
/// [`MAX_NESTED_FILES`] files, [`MAX_NESTED_FILE_BYTES`] each and
/// [`MAX_NESTED_INSTRUCTIONS_BYTES`] combined. Anything past a budget is
/// listed as a path the model can `read_file`, so a monorepo degrades to
/// pointers instead of blowing the context — and is *told* that is what
/// happened.
///
/// Each block is labelled with the directory it governs, because a scoped rule
/// presented without its scope reads as a global one.
pub fn nested_instructions(worktree: &Path) -> Option<String> {
    let paths = tracked_instruction_files(worktree)?;
    if paths.is_empty() {
        return None;
    }

    let mut out = String::new();
    let mut used = 0usize;
    let mut deferred: Vec<String> = Vec::new();

    for (i, rel) in paths.iter().enumerate() {
        let dir = rel.rsplit_once('/').map(|(d, _)| d).unwrap_or(".");
        let over_budget = i >= MAX_NESTED_FILES || used >= MAX_NESTED_INSTRUCTIONS_BYTES;
        let raw = if over_budget {
            String::new()
        } else {
            std::fs::read_to_string(worktree.join(rel)).unwrap_or_default()
        };
        if over_budget || raw.trim().is_empty() {
            if over_budget {
                deferred.push(rel.clone());
            }
            continue;
        }
        let remaining = MAX_NESTED_INSTRUCTIONS_BYTES.saturating_sub(used);
        // A fragment of a rule is worse than a pointer to the whole one — the
        // same reason the root budget is generous. If what is left of the
        // combined budget could only show a sliver, defer the file instead of
        // inlining a sentence and a half of it.
        if remaining < MIN_USEFUL_NESTED_BYTES && raw.trim().len() > remaining {
            deferred.push(rel.clone());
            continue;
        }
        let body = truncate_note(raw.trim(), MAX_NESTED_FILE_BYTES.min(remaining));
        used += body.len();
        out.push_str(&format!(
            "--- {rel} — applies to everything under `{dir}/` ---\n{body}\n\n"
        ));
    }

    if !deferred.is_empty() {
        out.push_str(
            "Not shown, over budget. Read the file before editing anything under its \
             directory:\n",
        );
        for rel in &deferred {
            out.push_str(&format!("- {rel}\n"));
        }
    }

    let trimmed = out.trim();
    (!trimmed.is_empty()).then(|| trimmed.to_string())
}

/// Instruction files that git is tracking, excluding the root one
/// [`agent_instructions`] already loaded. `None` when the worktree is not a
/// git checkout or git is unavailable — a coder worktree always is, but tests
/// and embedders need not be.
fn tracked_instruction_files(worktree: &Path) -> Option<Vec<String>> {
    let out = std::process::Command::new("git")
        .arg("-C")
        .arg(worktree)
        .args(["ls-files", "-z", "--", "*CLAUDE.md", "*AGENTS.md"])
        .output()
        .ok()?;
    if !out.status.success() {
        return None;
    }
    let mut paths: Vec<String> = String::from_utf8_lossy(&out.stdout)
        .split('\0')
        .filter(|p| !p.is_empty())
        // A root-level path has no separator; that file is already loaded in
        // full by `agent_instructions`, and repeating it would spend the
        // nested budget on text the model already has.
        .filter(|p| p.contains('/'))
        .filter(|p| {
            let name = p.rsplit('/').next().unwrap_or(p);
            INSTRUCTION_FILES.contains(&name)
        })
        .map(str::to_string)
        .collect();
    // Shallowest first: a rule nearer the root governs more of the tree, so it
    // is the one most likely to matter if a budget cuts the list short.
    paths.sort_by_key(|p| (p.matches('/').count(), p.clone()));
    Some(paths)
}

/// Render `.car/` identity and knowledge entries, if a project is discoverable.
pub fn dot_car_knowledge(worktree: &Path) -> Option<String> {
    let car_dir = car_memgine::project::discover_project(worktree)?;
    let project = car_memgine::project::load_project(&car_dir).ok()?;

    let mut out = String::new();
    if let Some(identity) = project.identity.as_deref().map(str::trim) {
        if !identity.is_empty() {
            out.push_str(identity);
            out.push_str("\n\n");
        }
    }
    if !project.knowledge.is_empty() {
        out.push_str("Recorded project knowledge:\n");
        for entry in project.knowledge.iter().take(MAX_KNOWLEDGE_ENTRIES) {
            let kind = if entry.entry_type.is_empty() {
                "note"
            } else {
                &entry.entry_type
            };
            out.push_str(&format!("- [{kind}] {}", entry.fact.trim()));
            let recommendation = entry.recommendation.trim();
            if !recommendation.is_empty() {
                out.push_str(&format!("{recommendation}"));
            }
            out.push('\n');
        }
    }
    let trimmed = out.trim();
    (!trimmed.is_empty()).then(|| truncate_note(trimmed, MAX_KNOWLEDGE_BYTES))
}

/// The full block for the system prompt, or `None` when the repo carries
/// neither instructions nor a `.car/` project.
///
/// The framing around the instruction text is deliberate. It tells the model
/// these rules are **not** verified by the outcome contract, because the
/// failure this fixes is a model treating a green contract as sufficient. It
/// also pins precedence: the contract still decides done, and these decide
/// acceptable. A model that reads "follow the repo's conventions" and then
/// weakens a check to satisfy them has made things worse.
pub fn project_context(worktree: &Path) -> Option<String> {
    let instructions = agent_instructions(worktree);
    let nested = nested_instructions(worktree);
    let knowledge = dot_car_knowledge(worktree);
    // Only meaningful alongside instructions that delegate to them; a skills
    // directory with no instruction file is not something to volunteer.
    let skills = instructions
        .is_some()
        .then(|| available_skills(worktree))
        .flatten();
    if instructions.is_none() && nested.is_none() && knowledge.is_none() {
        return None;
    }

    let mut out = String::new();
    if let Some((name, body)) = instructions {
        out.push_str(&format!(
            "PROJECT INSTRUCTIONS (from {name}, written by this repository's maintainers).\n\
             These are review-time rules. Your outcome contract does NOT check them, so a \
             diff can pass every check and still be rejected for breaking one. Follow them \
             as constraints on HOW you implement, and never weaken or edit a contract check \
             to satisfy one — the contract decides whether the work is done, these decide \
             whether it is acceptable. Where a rule points at another document you have not \
             been given, say so in your summary rather than guessing at its contents.\n\n\
             {body}\n"
        ));
    }
    if let Some(nested) = nested {
        out.push_str(&format!(
            "\nDIRECTORY-SCOPED INSTRUCTIONS.\n\
             Each block below governs one subtree and carries the same weight as the \
             instructions above while you are working inside it. Where a scoped rule is \
             stricter than a root one, the scoped rule wins for that subtree.\n\n{nested}\n"
        ));
    }
    if let Some(skills) = skills {
        out.push_str(&format!("\nPROJECT SKILLS.\n{skills}\n"));
    }
    if let Some(knowledge) = knowledge {
        if !out.is_empty() {
            out.push('\n');
        }
        out.push_str(&format!(
            "PROJECT KNOWLEDGE (from .car/, recorded by the team).\n\n{knowledge}\n"
        ));
    }
    Some(out)
}

/// Index the repository's agent skills by name and description.
///
/// Instruction files delegate — this repo's `CLAUDE.md` says the full binding
/// surface "lives in the `car-bindings-api` skill … invoke it whenever work
/// touches the FFI boundary". Loading the root file alone hands the model a
/// pointer it cannot follow, which is worse than useless: it knows a rule
/// exists and not what it says.
///
/// Only the frontmatter is loaded, never the bodies — five skill files would
/// swamp the prompt, and the `description` field is written precisely so a
/// reader can decide whether it is relevant. The bodies are ordinary files in
/// the worktree, so the model can `read_file` the one it needs. That turns a
/// dangling reference into an instruction it can actually act on with the tools
/// it already has.
pub fn available_skills(worktree: &Path) -> Option<String> {
    let skills_dir = worktree.join(".claude").join("skills");
    let mut entries: Vec<(String, String)> = std::fs::read_dir(&skills_dir)
        .ok()?
        .flatten()
        .filter_map(|entry| {
            let manifest = entry.path().join("SKILL.md");
            let raw = std::fs::read_to_string(&manifest).ok()?;
            let (name, description) = parse_frontmatter(&raw)?;
            let rel = format!(
                ".claude/skills/{}/SKILL.md",
                entry.file_name().to_string_lossy()
            );
            Some((rel, format!("**{name}** — {description}")))
        })
        .collect();
    if entries.is_empty() {
        return None;
    }
    entries.sort();

    let mut out = String::from(
        "The instructions above delegate to these skill documents. They are files in this \
         worktree: when your work touches an area one of them covers, read it with \
         `read_file` BEFORE editing, rather than guessing at what it says.\n\n",
    );
    for (path, summary) in entries {
        out.push_str(&format!("- `{path}` — {summary}\n"));
    }
    Some(truncate_note(out.trim(), MAX_KNOWLEDGE_BYTES))
}

/// Pull `name` and `description` out of a SKILL.md YAML frontmatter block.
///
/// Deliberately not a YAML parser. Only two scalar fields are needed, and
/// `description` is commonly a folded (`>-`) block whose continuation lines are
/// indented — so a continuation is any following line that is indented and does
/// not itself open a new key. Anything it cannot read is skipped rather than
/// guessed at.
fn parse_frontmatter(raw: &str) -> Option<(String, String)> {
    let body = raw.strip_prefix("---")?;
    let end = body.find("\n---")?;
    let block = &body[..end];

    let mut name = None;
    let mut description: Option<String> = None;
    let mut in_description = false;
    for line in block.lines() {
        if let Some(rest) = line.strip_prefix("name:") {
            name = Some(rest.trim().to_string());
            in_description = false;
        } else if let Some(rest) = line.strip_prefix("description:") {
            let first = rest.trim().trim_start_matches(['>', '|', '-']).trim();
            description = Some(first.to_string());
            in_description = true;
        } else if in_description {
            let indented = line.starts_with(' ') || line.starts_with('\t');
            if indented && !line.trim().is_empty() {
                let existing = description.get_or_insert_with(String::new);
                if !existing.is_empty() {
                    existing.push(' ');
                }
                existing.push_str(line.trim());
            } else if !line.trim().is_empty() {
                in_description = false;
            }
        }
    }
    let name = name.filter(|n| !n.is_empty())?;
    let description = description
        .map(|d| first_sentence(&d))
        .filter(|d| !d.is_empty())?;
    Some((name, description))
}

/// Skill descriptions are written as long selection blurbs. One sentence is
/// enough for the model to decide whether to open the file.
fn first_sentence(text: &str) -> String {
    match text.find(". ") {
        Some(i) => text[..=i].trim().to_string(),
        None => text.trim().to_string(),
    }
}

/// Cut to a byte ceiling on a UTF-8 boundary, and say so when cut.
fn truncate_note(text: &str, max: usize) -> String {
    if text.len() <= max {
        return text.to_string();
    }
    let mut end = max;
    while end > 0 && !text.is_char_boundary(end) {
        end -= 1;
    }
    format!(
        "{}\n\n[truncated: {} of {} bytes shown]",
        &text[..end],
        end,
        text.len()
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn reads_claude_md_and_prefers_it_over_agents_md() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("CLAUDE.md"), "no feature flags, ever").unwrap();
        std::fs::write(dir.path().join("AGENTS.md"), "something else").unwrap();
        let (name, body) = agent_instructions(dir.path()).expect("instructions found");
        assert_eq!(name, "CLAUDE.md");
        assert!(body.contains("no feature flags"));
    }

    #[test]
    fn falls_back_to_agents_md() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("AGENTS.md"), "house rules").unwrap();
        let (name, _) = agent_instructions(dir.path()).expect("instructions found");
        assert_eq!(name, "AGENTS.md");
    }

    #[test]
    fn an_empty_instruction_file_is_not_instructions() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("CLAUDE.md"), "   \n\n").unwrap();
        assert!(agent_instructions(dir.path()).is_none());
    }

    #[test]
    fn a_repo_with_nothing_yields_no_block() {
        let dir = tempfile::tempdir().unwrap();
        assert!(project_context(dir.path()).is_none());
    }

    #[test]
    fn the_block_says_the_contract_does_not_check_these_rules() {
        // The whole failure this fixes is a model treating a green contract as
        // sufficient, so the framing is load-bearing, not decoration.
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("CLAUDE.md"), "rule one").unwrap();
        let block = project_context(dir.path()).expect("block");
        assert!(block.contains("does NOT check them"));
        assert!(block.contains("never weaken or edit a contract check"));
        assert!(block.contains("rule one"));
    }

    #[test]
    fn truncation_is_announced_not_silent() {
        let dir = tempfile::tempdir().unwrap();
        let huge = "x".repeat(MAX_INSTRUCTIONS_BYTES + 500);
        std::fs::write(dir.path().join("CLAUDE.md"), &huge).unwrap();
        let (_, body) = agent_instructions(dir.path()).expect("instructions");
        assert!(
            body.contains("[truncated:"),
            "a silent cut hides missing rules"
        );
        assert!(body.len() < huge.len() + 200);
    }

    #[test]
    fn truncation_lands_on_a_char_boundary() {
        // A multi-byte char straddling the ceiling must not panic or corrupt.
        let text = "é".repeat(100);
        let cut = truncate_note(&text, 51);
        assert!(cut.contains("[truncated:"));
    }

    fn write_skill(root: &std::path::Path, dir: &str, frontmatter: &str) {
        let d = root.join(".claude").join("skills").join(dir);
        std::fs::create_dir_all(&d).unwrap();
        std::fs::write(d.join("SKILL.md"), frontmatter).unwrap();
    }

    #[test]
    fn skills_are_indexed_by_name_and_first_sentence() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("CLAUDE.md"), "see the skills").unwrap();
        write_skill(
            dir.path(),
            "car-bindings-api",
            "---\nname: car-bindings-api\ndescription: >-\n  The complete CAR bindings API\n  surface. Use it whenever work touches the FFI boundary.\n---\nbody text here\n",
        );
        let block = project_context(dir.path()).expect("block");
        assert!(block.contains("car-bindings-api"));
        assert!(block.contains("The complete CAR bindings API surface."));
        // The path must be there, because the point is that the model can open it.
        assert!(block.contains(".claude/skills/car-bindings-api/SKILL.md"));
        assert!(block.contains("read_file"));
        // The body is NOT inlined — five of these would swamp the prompt.
        assert!(!block.contains("body text here"));
    }

    #[test]
    fn a_folded_description_is_joined_not_truncated_at_the_newline() {
        let (name, desc) = parse_frontmatter(
            "---\nname: thing\ndescription: >-\n  first part\n  second part. Rest.\n---\n",
        )
        .expect("parsed");
        assert_eq!(name, "thing");
        assert_eq!(desc, "first part second part.");
    }

    #[test]
    fn a_skill_without_usable_frontmatter_is_skipped_not_guessed() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("CLAUDE.md"), "rules").unwrap();
        write_skill(dir.path(), "broken", "no frontmatter at all\n");
        write_skill(
            dir.path(),
            "good",
            "---\nname: good\ndescription: Does a thing.\n---\n",
        );
        let block = project_context(dir.path()).expect("block");
        assert!(block.contains("good"));
        assert!(!block.contains("broken"));
    }

    #[test]
    fn skills_are_not_volunteered_without_instructions_that_delegate() {
        let dir = tempfile::tempdir().unwrap();
        write_skill(
            dir.path(),
            "lonely",
            "---\nname: lonely\ndescription: Nobody points here.\n---\n",
        );
        // No CLAUDE.md and no .car/ — nothing to attach the index to.
        assert!(project_context(dir.path()).is_none());
    }

    #[test]
    fn dot_car_knowledge_is_rendered_with_recommendations() {
        let dir = tempfile::tempdir().unwrap();
        let car = dir.path().join(".car");
        std::fs::create_dir_all(car.join("knowledge")).unwrap();
        std::fs::write(car.join("identity.md"), "The CAR runtime.").unwrap();
        std::fs::write(
            car.join("knowledge").join("gotchas.jsonl"),
            r#"{"id":"g1","type":"gotcha","fact":"cargo config follows cwd","recommendation":"run from car-rs"}"#,
        )
        .unwrap();
        let rendered = dot_car_knowledge(dir.path()).expect("knowledge loaded");
        assert!(rendered.contains("The CAR runtime."));
        assert!(rendered.contains("cargo config follows cwd"));
        assert!(rendered.contains("run from car-rs"));
        assert!(rendered.contains("[gotcha]"));
    }

    #[test]
    fn discovery_walks_up_from_a_nested_worktree() {
        // Same rule as .gitignore resolution: a coder session cut at a
        // subdirectory still finds the project.
        let dir = tempfile::tempdir().unwrap();
        std::fs::create_dir_all(dir.path().join(".car")).unwrap();
        std::fs::write(dir.path().join(".car").join("identity.md"), "root project").unwrap();
        let nested = dir.path().join("crates").join("thing");
        std::fs::create_dir_all(&nested).unwrap();
        let rendered = dot_car_knowledge(&nested).expect("found by walking up");
        assert!(rendered.contains("root project"));
    }

    // ---- car#1071: nested instruction files, and the cap that ate the rules --

    /// Make `dir` a git repo with `files` committed. Nested instruction files
    /// are found through `git ls-files`, so a plain tempdir will not do.
    fn repo_with(files: &[(&str, &str)]) -> tempfile::TempDir {
        let dir = tempfile::tempdir().unwrap();
        let git = |args: &[&str]| {
            let ok = std::process::Command::new("git")
                .arg("-C")
                .arg(dir.path())
                .args(args)
                .output()
                .unwrap()
                .status
                .success();
            assert!(ok, "git {args:?} failed");
        };
        git(&["init", "-q"]);
        git(&["config", "user.email", "t@example.com"]);
        git(&["config", "user.name", "t"]);
        for (rel, body) in files {
            let path = dir.path().join(rel);
            std::fs::create_dir_all(path.parent().unwrap()).unwrap();
            std::fs::write(&path, body).unwrap();
        }
        git(&["add", "-A"]);
        git(&["commit", "-qm", "seed"]);
        dir
    }

    #[test]
    fn a_nested_instruction_file_is_inlined_with_the_directory_it_governs() {
        let dir = repo_with(&[
            ("CLAUDE.md", "root rules"),
            ("crates/napi/CLAUDE.md", "do not reintroduce the five bugs"),
        ]);

        let nested = nested_instructions(dir.path()).expect("a nested file is found");
        assert!(
            nested.contains("do not reintroduce the five bugs"),
            "the nested rule must be inlined, not merely pointed at: {nested}"
        );
        assert!(
            nested.contains("crates/napi/CLAUDE.md") && nested.contains("`crates/napi/`"),
            "a scoped rule shown without its scope reads as a global one: {nested}"
        );
    }

    #[test]
    fn the_root_file_is_not_repeated_in_the_nested_block() {
        let dir = repo_with(&[("CLAUDE.md", "root rules"), ("sub/CLAUDE.md", "sub rules")]);
        let nested = nested_instructions(dir.path()).unwrap();
        assert!(
            !nested.contains("root rules"),
            "agent_instructions already loads the root file in full: {nested}"
        );
    }

    /// The relevance half of using `git ls-files`. A recursive walk of CAR's
    /// own checkout finds 16 instruction files, 14 of them inside nested
    /// worktrees and extracted bench fixtures.
    #[test]
    fn an_untracked_instruction_file_is_ignored() {
        let dir = repo_with(&[("CLAUDE.md", "root rules"), ("sub/CLAUDE.md", "tracked")]);
        // Written after the commit, exactly like a build artifact or a nested
        // scratch checkout.
        std::fs::create_dir_all(dir.path().join("target/scratch")).unwrap();
        std::fs::write(
            dir.path().join("target/scratch/CLAUDE.md"),
            "not maintainer intent",
        )
        .unwrap();

        let nested = nested_instructions(dir.path()).unwrap();
        assert!(nested.contains("tracked"));
        assert!(
            !nested.contains("not maintainer intent"),
            "untracked text must not reach the system prompt — a session could \
             otherwise write its own rules mid-run: {nested}"
        );
    }

    #[test]
    fn a_worktree_that_is_not_a_git_checkout_yields_nothing_rather_than_failing() {
        let dir = tempfile::tempdir().unwrap();
        std::fs::write(dir.path().join("CLAUDE.md"), "rules").unwrap();
        assert!(nested_instructions(dir.path()).is_none());
        // …and the surrounding block still works without it.
        assert!(project_context(dir.path()).unwrap().contains("rules"));
    }

    #[test]
    fn past_the_file_budget_the_rest_become_readable_pointers() {
        let mut files: Vec<(String, String)> = vec![("CLAUDE.md".into(), "root".into())];
        for i in 0..(MAX_NESTED_FILES + 3) {
            files.push((format!("d{i:02}/CLAUDE.md"), format!("rule {i}")));
        }
        let refs: Vec<(&str, &str)> = files
            .iter()
            .map(|(a, b)| (a.as_str(), b.as_str()))
            .collect();
        let dir = repo_with(&refs);

        let nested = nested_instructions(dir.path()).unwrap();
        assert!(
            nested.contains("Not shown, over budget"),
            "a repo past the budget must be TOLD it is seeing pointers: {nested}"
        );
        assert!(
            nested.contains("rule 0"),
            "the first files are still inlined"
        );
        assert!(
            nested.contains(&format!("d{:02}/CLAUDE.md", MAX_NESTED_FILES + 2)),
            "an over-budget file is still named so the model can read it"
        );
    }

    #[test]
    fn nested_instructions_reach_the_prompt_block_with_their_precedence_stated() {
        let dir = repo_with(&[
            ("CLAUDE.md", "root rules"),
            ("crates/napi/CLAUDE.md", "napi gotchas"),
        ]);
        let block = project_context(dir.path()).expect("a block");
        assert!(block.contains("DIRECTORY-SCOPED INSTRUCTIONS"));
        assert!(block.contains("napi gotchas"));
        assert!(
            block.contains("the scoped rule wins for that subtree"),
            "precedence between root and scoped rules must be stated, not guessed: {block}"
        );
    }

    /// End-to-end against THIS repository, which is the only place the whole
    /// chain can be checked: git tracking, the root budget, the nested budget,
    /// and the prompt framing all at once. Skipped in a checkout that does not
    /// look like CAR, so a vendored copy does not fail someone else's build.
    #[test]
    fn car_s_own_repo_yields_both_its_hard_rules_and_its_napi_gotchas() {
        let root = Path::new(env!("CARGO_MANIFEST_DIR"))
            .ancestors()
            .nth(3)
            .unwrap();
        if !root.join("car-rs/crates/car-ffi-napi/CLAUDE.md").exists() {
            return;
        }
        let block = project_context(root).expect("CAR has instructions");

        // The regression that motivated raising the cap: these headings live
        // past byte 24_000 of CLAUDE.md and were being discarded entirely.
        assert!(
            block.contains("No cargo feature flags"),
            "the hard rules must survive the root budget"
        );
        assert!(block.contains("Keep all FFI bindings in sync"));
        assert!(
            !block.contains("[truncated:"),
            "CAR's own instructions must not be truncated at all"
        );

        // And the nested half of car#1071.
        assert!(
            block.contains("Do not reintroduce them"),
            "car-ffi-napi/CLAUDE.md must reach the prompt"
        );
        assert!(block.contains("car-rs/crates/car-ffi-napi/CLAUDE.md"));
    }

    /// A file that would only fit as a sliver is deferred whole, not inlined
    /// as half a sentence — the same principle as the generous root budget.
    #[test]
    fn a_file_that_would_only_fit_as_a_fragment_is_deferred_instead() {
        let big = "r".repeat(MAX_NESTED_FILE_BYTES);
        let mut files: Vec<(String, String)> = vec![("CLAUDE.md".into(), "root".into())];
        // Two full-size files exhaust the combined budget to within a sliver.
        for i in 0..2 {
            files.push((format!("d{i}/CLAUDE.md"), big.clone()));
        }
        files.push(("zz/CLAUDE.md".into(), "z".repeat(5_000)));
        let refs: Vec<(&str, &str)> = files
            .iter()
            .map(|(a, b)| (a.as_str(), b.as_str()))
            .collect();
        let dir = repo_with(&refs);

        let nested = nested_instructions(dir.path()).unwrap();
        assert!(
            nested.contains("zz/CLAUDE.md"),
            "the deferred file must still be named: {}",
            &nested[nested.len().saturating_sub(400)..]
        );
        assert!(
            !nested.contains(&"z".repeat(200)),
            "a sliver of the deferred file must not be inlined"
        );
    }

    /// **The anti-staleness guard. Do not delete this test.**
    ///
    /// `MAX_INSTRUCTIONS_BYTES` was 24_000, with a comment reading "this repo's
    /// own is ~19KB, which fits". CAR's `CLAUDE.md` grew to 38.7KB and the cut
    /// landed 800 bytes before the heading "Project conventions (hard rules)",
    /// so the coder working on this repository got every architecture note and
    /// none of the rules — including the two this module's header cites as its
    /// reason for existing.
    ///
    /// Truncation was announced in the text, which is why nothing caught it:
    /// the model was told it had 62% of a document, not that the missing 38%
    /// was the entire rules section. A budget for rules has to be checked
    /// against the rules, so this asserts on the real file.
    #[test]
    fn the_repos_own_instructions_fit_the_cap() {
        let root = Path::new(env!("CARGO_MANIFEST_DIR"))
            .ancestors()
            .nth(3)
            .expect("crates/car-server-core is three levels below the repo root");
        let claude_md = root.join("CLAUDE.md");
        let Ok(raw) = std::fs::read_to_string(&claude_md) else {
            // Vendored or partial checkout — nothing to assert against.
            return;
        };
        assert!(
            raw.len() <= MAX_INSTRUCTIONS_BYTES,
            "CAR's own CLAUDE.md is {} bytes and MAX_INSTRUCTIONS_BYTES is {}, so the \
             coder working on this repo is silently losing the tail of its own rules. \
             Raise the constant (and read its doc comment first) — do not delete this test.",
            raw.len(),
            MAX_INSTRUCTIONS_BYTES
        );
    }

    /// The guard above is only worth having if it can fail, and the shape it
    /// guards against is "the cut lands before the rules". This proves the
    /// truncation it describes is real rather than hypothetical.
    #[test]
    fn truncation_drops_the_tail_it_claims_to() {
        let text = format!(
            "{}\n## Project conventions (hard rules)\nno feature flags",
            "x".repeat(100)
        );
        let cut = truncate_note(&text, 50);
        assert!(!cut.contains("hard rules"), "the tail really is discarded");
        assert!(
            cut.contains("[truncated: 50 of"),
            "and the loss is announced"
        );
    }
}