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
//! Pin-file I/O — read, write, and slug derivation helpers.
//!
//! Why: Isolating the pin-file I/O from detection and validation keeps each
//! file under the 500-SLOC cap and groups the YAML serialization in one place.
//! What: `ProjectPin`, `PIN_SCHEMA_VERSION`, `PIN_FILE_REL`, `read_project_pin`,
//! `write_project_pin`, `project_slug_from_basename`, `project_slug_at`,
//! `project_slug_at_readonly`, `project_slug`.
//! Test: `pin_file_read_when_present`, `absent_pin_writes_computed_slug`,
//! `renamed_dir_with_pin_resolves_to_original_slug`.
use Result;
use ;
use ;
use crateslugify_string;
use ;
/// Schema version for `.trusty-tools/trusty-memory.yaml`.
///
/// Why: forward-proofing — a future phase may need to distinguish older pin
/// files that lack new fields. Hard-coding `1` now makes that migration
/// straightforward: read `schema_version`, branch on the value.
/// What: the `u32` constant `1`.
/// Test: `write_project_pin` embeds this value; `read_project_pin` accepts it.
pub const PIN_SCHEMA_VERSION: u32 = 1;
/// Relative path of the pin file within a project root.
///
/// Why: defined as a constant so every call site (`read_project_pin`,
/// `write_project_pin`, `find_project_root`) agrees on the same path and
/// tests can compare against this value instead of a bare string literal.
/// What: `".trusty-tools/trusty-memory.yaml"`.
/// Test: used in every pin-file test in this module.
pub const PIN_FILE_REL: &str = ".trusty-tools/trusty-memory.yaml";
/// Serialisable schema for `.trusty-tools/trusty-memory.yaml`.
///
/// Why: a typed struct with `serde` makes the YAML schema self-documenting
/// and prevents future fields from silently deserialising to wrong types.
/// What: holds `schema_version` (always 1 for Phase 1) and `palace` (the
/// pinned slug string). An optional `note` field is supported for humans who
/// want to document why the slug was pinned.
/// Test: `write_project_pin` round-trips through `read_project_pin`.
/// Read the palace pin from `.trusty-tools/trusty-memory.yaml` at `root`.
///
/// Why: the pin file is the authoritative source for a project's palace slug
/// when present. Reading it in a dedicated helper keeps the I/O concern
/// separate from the slug-derivation logic and makes it easy to test the
/// round-trip in isolation.
/// What: constructs the path `root/.trusty-tools/trusty-memory.yaml`, reads
/// it, and deserialises with `serde_yaml`. Returns `None` when the file does
/// not exist. Returns `Err` only on I/O or parse failures.
/// Test: `pin_file_read_when_present`, `read_project_pin_returns_none_when_absent`.
/// Write a palace pin to `.trusty-tools/trusty-memory.yaml` at `root`.
///
/// Why: the lazy-write path in `project_slug_at` and the explicit
/// `trusty-memory link` backfill command both need to emit the same YAML
/// schema. A single writer keeps the format consistent and avoids duplicated
/// YAML-construction logic.
/// What: creates `.trusty-tools/` if missing, serialises `pin` with
/// `serde_yaml`, and writes it atomically (write to `<file>.tmp`, then
/// rename). Returns the path that was written.
/// Test: `write_project_pin_creates_expected_yaml`,
/// `write_project_pin_round_trips_through_read`.
/// Compute the palace slug purely from the directory basename (the pre-Phase-1
/// logic, now extracted for composability).
///
/// Why: the resolution order in `project_slug_at` needs to call the basename
/// derivation without triggering the pin-file read/write side effects. Exposing
/// this as a separate function makes both paths testable in isolation.
/// What: calls `slugify_string` on the last path component of `root`. Returns
/// `None` when the basename is empty or slugifies to an empty string.
/// Test: `project_slug_from_basename_basic`.
/// Derive a palace slug from the project root found at or above `start`.
///
/// Why: the core of issue #88 with Phase-1 pin-file support. Palace names
/// must match the canonical slug of the project they belong to, and that slug
/// must survive directory renames. The pin file provides the stable anchor.
/// What: implements the two-step resolution order:
/// a. Walk up to the project root. If `.trusty-tools/trusty-memory.yaml`
/// exists, return `pin.palace` (authoritative — survives renames).
/// b. If absent, compute the slug via `project_slug_from_basename`, then
/// lazily write the pin file (best-effort, non-fatal) so future calls
/// always land on path (a).
/// Returns `None` when no project root is found.
/// Test: `pin_file_read_when_present`, `absent_pin_writes_computed_slug`,
/// `renamed_dir_with_pin_resolves_to_original_slug`.
/// Return the *pinned* palace slug for the project at or above `start`, and
/// ONLY when a committed pin file exists — never the basename fallback.
///
/// Why: issue #1217 inserts git-`owner/repo` identity derivation between the
/// pin file (authoritative, rename-stable) and the directory-basename
/// fallback. The default-palace resolver therefore needs to consult the pin
/// file *in isolation* — if it used `project_slug_at_readonly` it would also
/// receive the basename slug, which would shadow the new git derivation and
/// reduce the change to a no-op inside any project directory. This helper
/// returns `Some` strictly when a pin exists, so callers can honour the pin
/// first and fall through to identity derivation when absent.
/// What: walks up to the project root, reads `.trusty-tools/trusty-memory.yaml`
/// via [`read_project_pin`], and returns `Some(pin.palace)` when present.
/// Returns `None` when no project root is found OR no pin file exists OR the
/// pin file is unreadable (logged, non-fatal — never panics, never writes).
/// Test: `pinned_slug_at_returns_pin_when_present`,
/// `pinned_slug_at_returns_none_without_pin`.
/// Derive a palace slug from the project root found at or above `start`,
/// WITHOUT the lazy-write side-effect.
///
/// Why: the `prompt-context` hook runs in read-only or short-lived contexts
/// where creating `.trusty-tools/trusty-memory.yaml` would be surprising and
/// potentially disruptive. The slug is still resolved via the pin-file when
/// one already exists (step a), and falls back to the basename slug (step b)
/// without ever writing a new file. This makes `cwd_palace_slug_at` safe to
/// call unconditionally from hooks. The writing variant (`project_slug_at`)
/// remains the right choice for interactive commands (`trusty-memory link`,
/// `trusty-memory remember`) that want to stabilise the slug.
/// What: same two-step resolution as `project_slug_at` but step (b) only
/// computes and returns the basename slug — it does NOT write the pin file.
/// Returns `None` when no project root is found.
/// Test: `project_slug_at_readonly_no_write_when_absent`,
/// `project_slug_at_readonly_reads_existing_pin`,
/// `project_slug_at_readonly_falls_back_to_basename`.
/// Derive a palace slug for the current working directory.
///
/// Why: convenience wrapper over `project_slug_at` for callers that want
/// the "natural" project slug (CLI commands, MCP handlers, tests running
/// inside a repo).
/// What: calls `std::env::current_dir()`, propagates the error if the syscall
/// fails, then delegates to [`project_slug_at`].
/// Test: `project_slug_finds_git_root` (run from inside the trusty-tools repo
/// which is a git checkout).