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
//! Project-root detection and filesystem walking.
//!
//! Why: Isolating the detection walk from the pin-file I/O keeps each file
//! under the 500-SLOC cap and makes the walk independently testable.
//! What: `find_project_root`, `find_project_root_with_marker`,
//! `is_unsafe_pin_location`, `PROJECT_MARKERS`, `TRUSTY_TOOLS_DIR`,
//! `PERSONAL_PALACE`.
//! Test: `project_slug_finds_git_root`, `project_slug_returns_none_without_markers`,
//! `project_slug_uses_first_ancestor_marker`, `trusty_tools_dir_is_project_marker`.
use ;
/// The `.trusty-tools/` directory name (used as a project marker).
///
/// Why: a project that already contains `.trusty-tools/trusty-memory.yaml`
/// should be recognised as a project root even if it has no `.git` or
/// `Cargo.toml`. Adding the directory itself to `PROJECT_MARKERS` (decision
/// D5) lets `find_project_root` detect this case without special-casing.
/// What: `".trusty-tools"`.
/// Test: `trusty_tools_dir_is_project_marker`.
pub const TRUSTY_TOOLS_DIR: &str = ".trusty-tools";
/// Sentinel palace name that is always valid regardless of project context.
///
/// Why: users operating outside any project root (global notes, exploratory
/// sessions, personal task lists) need a stable palace that can receive
/// memories without failing the project-enforcement gate. The name `personal`
/// is the single reserved identifier for this purpose.
/// What: a `&str` constant that the enforcement logic tests against before
/// applying project-slug validation.
/// Test: `project_slug_personal_always_allowed`.
pub const PERSONAL_PALACE: &str = "personal";
/// File names that mark a directory as a project root.
///
/// Why: different ecosystems use different conventions for the project root;
/// we want a single, ordered list that every part of the codebase agrees on
/// so project detection is consistent whether invoked from CLI, MCP, or
/// tests. `.git` comes first because it is the most universal signal.
/// `.trusty-tools` is included (decision D5) so a directory that already
/// carries a pin file is recognised even without a `.git` or build manifest.
/// What: an ordered slice of filenames checked by `find_project_root`. A
/// directory is considered a project root when it contains *any* of these.
/// Test: `project_slug_uses_first_ancestor_marker`,
/// `trusty_tools_dir_is_project_marker`.
pub const PROJECT_MARKERS: & = &;
/// Walk upward from `start` and return the first ancestor directory (inclusive)
/// that contains at least one project marker.
///
/// Why: keeping the filesystem walk in a dedicated helper makes both the slug
/// derivation function and the tests easier to reason about — callers get the
/// root path, not just the slug.
/// What: starts at `start`, checks for every [`PROJECT_MARKERS`] file/dir,
/// and ascends to `parent()` until a root is found or the filesystem root is
/// reached. Returns `None` when no project root is found.
/// Test: `project_slug_finds_git_root`, `project_slug_uses_first_ancestor_marker`.
/// Walk upward from `start` and return the first ancestor directory (inclusive)
/// that contains at least one project marker, together with the name of the
/// marker that was found.
///
/// Why: the lazy-write path in `project_slug_at` needs to know whether the
/// detected root was anchored by a real marker (`.git`, `Cargo.toml`, etc.)
/// or whether no marker was found at all (in which case `find_project_root`
/// would already have returned `None` — this function's second return value
/// is always `Some` when the `PathBuf` is `Some`). The guard in
/// `project_slug_at` uses the marker name to distinguish a real project root
/// from a directory that only became the root by coincidence (e.g. a stale
/// pin file in `/tmp`).
/// What: same walk as `find_project_root`; returns `Some((root, marker))` on
/// success where `marker` is one of the strings from [`PROJECT_MARKERS`].
/// Returns `None` when no project root is found.
/// Test: indirectly via `find_project_root`, `project_slug_at`, and the
/// guard tests `lazy_write_skipped_for_temp_dir_root`.
pub
/// Return `true` when `root` is an unsafe location where we must not
/// lazily write a palace pin file.
///
/// Why (product guard): when `find_project_root` walks up from a temp or
/// scratch directory and finds no real project marker, it can fall through
/// to a fallback root such as the system temp dir, the user's home
/// directory, or the filesystem root. Writing a pin file there silently
/// poisons every future invocation from any subdirectory of that path —
/// including every `tempfile::tempdir()` in the test suite (which resolves
/// to a child of `/tmp`). The guard intercepts this before the write so
/// only genuine project roots ever receive a pin file.
/// What: canonicalises `root` and compares it against `std::env::temp_dir()`
/// (canonicalised), `dirs::home_dir()` (canonicalised, best-effort), and the
/// filesystem root `/`. Returns `true` when any comparison matches.
/// Test: `lazy_write_skipped_for_temp_dir_root`.
pub